Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions packages/cli/oclif.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5737,6 +5737,70 @@
"strict": true,
"summary": "Authenticate an app against a store for store commands."
},
"store:create:dev": {
"aliases": [
],
"args": {
},
"customPluginName": "@shopify/store",
"description": "Creates a new app development store in your organization.",
"descriptionWithMarkdown": "Creates a new app development store in your organization.",
"enableJsonFlag": false,
"flags": {
"json": {
"allowNo": false,
"char": "j",
"description": "Output the result as JSON. Automatically disables color output.",
"env": "SHOPIFY_FLAG_JSON",
"hidden": false,
"name": "json",
"type": "boolean"
},
"name": {
"description": "Name for the new development store.",
"env": "SHOPIFY_FLAG_STORE_NAME",
"hasDynamicHelp": false,
"multiple": false,
"name": "name",
"required": true,
"type": "option"
},
"no-color": {
"allowNo": false,
"description": "Disable color output.",
"env": "SHOPIFY_FLAG_NO_COLOR",
"hidden": false,
"name": "no-color",
"type": "boolean"
},
"organization-id": {
"description": "The organization to create the store in (numeric ID). Auto-selects if you belong to a single org.",
"env": "SHOPIFY_FLAG_ORGANIZATION_ID",
"hasDynamicHelp": false,
"multiple": false,
"name": "organization-id",
"type": "option"
},
"verbose": {
"allowNo": false,
"description": "Increase the verbosity of the output.",
"env": "SHOPIFY_FLAG_VERBOSE",
"hidden": false,
"name": "verbose",
"type": "boolean"
}
},
"hasDynamicHelp": false,
"hidden": true,
"hiddenAliases": [
],
"id": "store:create:dev",
"pluginAlias": "@shopify/cli",
"pluginName": "@shopify/cli",
"pluginType": "core",
"strict": true,
"summary": "Create a new development store."
},
"store:execute": {
"aliases": [
],
Expand Down
3 changes: 2 additions & 1 deletion packages/store/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@
"dependencies": {
"@graphql-typed-document-node/core": "3.2.0",
"@oclif/core": "4.11.4",
"@shopify/cli-kit": "4.1.0"
"@shopify/cli-kit": "4.1.0",
"@shopify/organizations": "4.1.0"
},
"devDependencies": {
"@vitest/coverage-istanbul": "^3.2.6"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/* eslint-disable @typescript-eslint/consistent-type-definitions */
import * as Types from './types.js'

import {TypedDocumentNode as DocumentNode} from '@graphql-typed-document-node/core'

export type CreateAppDevelopmentStoreMutationVariables = Types.Exact<{
shopName: Types.Scalars['String']['input']
priceLookupKey: Types.Scalars['String']['input']
prepopulateTestData?: Types.InputMaybe<Types.Scalars['Boolean']['input']>
}>

export type CreateAppDevelopmentStoreMutation = {
createAppDevelopmentStore: {
shopAdminUrl?: string | null
shopDomain?: string | null
userErrors?: {code?: string | null; field: string[]; message: string}[] | null
}
}

export const CreateAppDevelopmentStore = {
kind: 'Document',
definitions: [
{
kind: 'OperationDefinition',
operation: 'mutation',
name: {kind: 'Name', value: 'CreateAppDevelopmentStore'},
variableDefinitions: [
{
kind: 'VariableDefinition',
variable: {kind: 'Variable', name: {kind: 'Name', value: 'shopName'}},
type: {kind: 'NonNullType', type: {kind: 'NamedType', name: {kind: 'Name', value: 'String'}}},
},
{
kind: 'VariableDefinition',
variable: {kind: 'Variable', name: {kind: 'Name', value: 'priceLookupKey'}},
type: {kind: 'NonNullType', type: {kind: 'NamedType', name: {kind: 'Name', value: 'String'}}},
},
{
kind: 'VariableDefinition',
variable: {kind: 'Variable', name: {kind: 'Name', value: 'prepopulateTestData'}},
type: {kind: 'NamedType', name: {kind: 'Name', value: 'Boolean'}},
},
],
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'Field',
name: {kind: 'Name', value: 'createAppDevelopmentStore'},
arguments: [
{
kind: 'Argument',
name: {kind: 'Name', value: 'shopName'},
value: {kind: 'Variable', name: {kind: 'Name', value: 'shopName'}},
},
{
kind: 'Argument',
name: {kind: 'Name', value: 'priceLookupKey'},
value: {kind: 'Variable', name: {kind: 'Name', value: 'priceLookupKey'}},
},
{
kind: 'Argument',
name: {kind: 'Name', value: 'prepopulateTestData'},
value: {kind: 'Variable', name: {kind: 'Name', value: 'prepopulateTestData'}},
},
],
selectionSet: {
kind: 'SelectionSet',
selections: [
{kind: 'Field', name: {kind: 'Name', value: 'shopAdminUrl'}},
{kind: 'Field', name: {kind: 'Name', value: 'shopDomain'}},
{
kind: 'Field',
name: {kind: 'Name', value: 'userErrors'},
selectionSet: {
kind: 'SelectionSet',
selections: [
{kind: 'Field', name: {kind: 'Name', value: 'code'}},
{kind: 'Field', name: {kind: 'Name', value: 'field'}},
{kind: 'Field', name: {kind: 'Name', value: 'message'}},
{kind: 'Field', name: {kind: 'Name', value: '__typename'}},
],
},
},
{kind: 'Field', name: {kind: 'Name', value: '__typename'}},
],
},
},
],
},
},
],
} as unknown as DocumentNode<CreateAppDevelopmentStoreMutation, CreateAppDevelopmentStoreMutationVariables>
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/* eslint-disable @typescript-eslint/consistent-type-definitions */
import * as Types from './types.js'

import {TypedDocumentNode as DocumentNode} from '@graphql-typed-document-node/core'

export type PollStoreCreationQueryVariables = Types.Exact<{
shopDomain: Types.Scalars['String']['input']
}>

export type PollStoreCreationQuery = {
organization?: {id: string; storeCreation?: {status: Types.StoreCreationStatus} | null} | null
}

export const PollStoreCreation = {
kind: 'Document',
definitions: [
{
kind: 'OperationDefinition',
operation: 'query',
name: {kind: 'Name', value: 'PollStoreCreation'},
variableDefinitions: [
{
kind: 'VariableDefinition',
variable: {kind: 'Variable', name: {kind: 'Name', value: 'shopDomain'}},
type: {kind: 'NonNullType', type: {kind: 'NamedType', name: {kind: 'Name', value: 'String'}}},
},
],
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'Field',
name: {kind: 'Name', value: 'organization'},
selectionSet: {
kind: 'SelectionSet',
selections: [
{kind: 'Field', name: {kind: 'Name', value: 'id'}},
{
kind: 'Field',
name: {kind: 'Name', value: 'storeCreation'},
arguments: [
{
kind: 'Argument',
name: {kind: 'Name', value: 'shopDomain'},
value: {kind: 'Variable', name: {kind: 'Name', value: 'shopDomain'}},
},
],
selectionSet: {
kind: 'SelectionSet',
selections: [
{kind: 'Field', name: {kind: 'Name', value: 'status'}},
{kind: 'Field', name: {kind: 'Name', value: '__typename'}},
],
},
},
{kind: 'Field', name: {kind: 'Name', value: '__typename'}},
],
},
},
],
},
},
],
} as unknown as DocumentNode<PollStoreCreationQuery, PollStoreCreationQueryVariables>
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,12 @@ export type Store =
| 'DEVELOPMENT'
| 'DEVELOPMENT_SUPERSET'
| 'PRODUCTION';

export type StoreCreationStatus =
| 'AWAITING_CORE_STORE_READY'
| 'CALLING_CORE'
| 'COMPLETE'
| 'FAILED'
| 'FINALIZING'
| 'TIMED_OUT'
| 'USER_ERROR';
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
mutation CreateAppDevelopmentStore($shopName: String!, $priceLookupKey: String!, $prepopulateTestData: Boolean) {
createAppDevelopmentStore(
shopName: $shopName
priceLookupKey: $priceLookupKey
prepopulateTestData: $prepopulateTestData
) {
shopAdminUrl
shopDomain
userErrors {
code
field
message
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
query PollStoreCreation($shopDomain: String!) {
organization {
id
storeCreation(shopDomain: $shopDomain) {
status
}
}
}
88 changes: 88 additions & 0 deletions packages/store/src/cli/commands/store/create/dev.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import StoreCreateDev from './dev.js'
import {createDevStore} from '../../../services/store/create/dev.js'
import {AbortError} from '@shopify/cli-kit/node/error'
import {outputResult} from '@shopify/cli-kit/node/output'
import {describe, expect, test, vi} from 'vitest'

vi.mock('../../../services/store/create/dev.js')

vi.mock('@shopify/cli-kit/node/output', async (importOriginal) => {
const actual: Record<string, unknown> = await importOriginal()
return {
...actual,
outputResult: vi.fn(),
}
})

describe('store create dev command', () => {
test('passes parsed flags through to the service', async () => {
await StoreCreateDev.run(['--name', 'my-test-store'])

expect(createDevStore).toHaveBeenCalledWith({
name: 'my-test-store',
organizationId: undefined,
json: false,
})
})

test('passes organization-id flag through to the service', async () => {
await StoreCreateDev.run(['--name', 'my-test-store', '--organization-id', '12345'])

expect(createDevStore).toHaveBeenCalledWith({
name: 'my-test-store',
organizationId: 12345,
json: false,
})
})

test('passes json flag through to the service', async () => {
await StoreCreateDev.run(['--name', 'my-test-store', '--json'])

expect(createDevStore).toHaveBeenCalledWith({
name: 'my-test-store',
organizationId: undefined,
json: true,
})
})

test('defines the expected flags', () => {
expect(StoreCreateDev.flags.name).toBeDefined()
expect(StoreCreateDev.flags['organization-id']).toBeDefined()
expect(StoreCreateDev.flags.json).toBeDefined()
})

test('outputs structured JSON error when --json is active and service throws AbortError', async () => {
vi.mocked(createDevStore).mockRejectedValueOnce(new AbortError('Something went wrong'))
const mockExit = vi.spyOn(process, 'exit').mockImplementation((() => {
throw new Error('process.exit')
}) as never)

await expect(StoreCreateDev.run(['--name', 'my-test-store', '--json'])).rejects.toThrow('process.exit')

const call = vi.mocked(outputResult).mock.calls[0]![0] as string
const parsed = JSON.parse(call)
expect(parsed).toEqual({
error: true,
message: 'Something went wrong',
nextSteps: [],
exitCode: 1,
})
expect(mockExit).toHaveBeenCalledWith(1)

mockExit.mockRestore()
})

test('does not output JSON for non-AbortError even when --json is active', async () => {
vi.mocked(createDevStore).mockRejectedValueOnce(new Error('unexpected'))

await expect(StoreCreateDev.run(['--name', 'my-test-store', '--json'])).rejects.toThrow()
expect(vi.mocked(outputResult)).not.toHaveBeenCalled()
})

test('does not output JSON for AbortError when --json is not active', async () => {
vi.mocked(createDevStore).mockRejectedValueOnce(new AbortError('Something went wrong'))

await expect(StoreCreateDev.run(['--name', 'my-test-store'])).rejects.toThrow()
expect(vi.mocked(outputResult)).not.toHaveBeenCalled()
})
})
Loading
Loading