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
5 changes: 5 additions & 0 deletions packages/app/src/cli/prompts/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {ApplicationURLs} from '../services/dev/urls.js'
import {
devStoreNamePrompt as sharedDevStoreNamePrompt,
devStorePlanPrompt as sharedDevStorePlanPrompt,
devStoreDemoDataPrompt as sharedDevStoreDemoDataPrompt,
} from '@shopify/organizations'
import {
RenderAutocompleteOptions,
Expand All @@ -24,6 +25,10 @@ export function devStorePlanPrompt(): Promise<DevStorePlan> {
return sharedDevStorePlanPrompt()
}

export function devStoreDemoDataPrompt(): Promise<boolean> {
return sharedDevStoreDemoDataPrompt()
}

export async function selectAppPrompt(
onSearchForAppsByName: (term: string) => Promise<{apps: MinimalOrganizationApp[]; hasMorePages: boolean}>,
apps: MinimalOrganizationApp[],
Expand Down
38 changes: 37 additions & 1 deletion packages/app/src/cli/services/dev/select-store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@ import {selectStore} from './select-store.js'
import {devStoreCapReached} from './cap.js'
import {fetchStore, StoreNotFoundError} from './fetch.js'
import {Organization, OrganizationSource, OrganizationStore} from '../../models/organization.js'
import {devStoreNamePrompt, devStorePlanPrompt, reloadStoreListPrompt, selectStorePrompt} from '../../prompts/dev.js'
import {
devStoreDemoDataPrompt,
devStoreNamePrompt,
devStorePlanPrompt,
reloadStoreListPrompt,
selectStorePrompt,
} from '../../prompts/dev.js'
import {testDeveloperPlatformClient} from '../../models/app/app.test-data.js'
import {ClientName} from '../../utilities/developer-platform-client.js'
import {sleep} from '@shopify/cli-kit/node/system'
Expand Down Expand Up @@ -374,6 +380,7 @@ describe('selectStore', async () => {
vi.mocked(devStoreCapReached).mockResolvedValue(false)
vi.mocked(devStoreNamePrompt).mockResolvedValue('created-store')
vi.mocked(devStorePlanPrompt).mockResolvedValue('grow')
vi.mocked(devStoreDemoDataPrompt).mockResolvedValue(false)
vi.mocked(createDevStore).mockResolvedValue('created-store.myshopify.com')
vi.mocked(fetchStore).mockResolvedValueOnce(STORE1)
vi.mocked(renderTasks).mockImplementation(async (tasks: Task[]) => {
Expand All @@ -392,13 +399,38 @@ describe('selectStore', async () => {
expect(createDevStore).toHaveBeenCalledWith({
name: 'created-store',
plan: 'grow',
withDemoData: false,
organization: ORG1,
json: false,
summary: false,
})
expect(renderSuccess).toHaveBeenCalledWith({headline: 'Development store "store1" created successfully.'})
})

test('passes an affirmative demo data answer through to store creation', async () => {
const developerPlatformClient = testDeveloperPlatformClient({clientName: ClientName.AppManagement})
vi.mocked(devStoreCapReached).mockResolvedValue(false)
vi.mocked(devStoreNamePrompt).mockResolvedValue('created-store')
vi.mocked(devStorePlanPrompt).mockResolvedValue('grow')
vi.mocked(devStoreDemoDataPrompt).mockResolvedValue(true)
vi.mocked(createDevStore).mockResolvedValue('created-store.myshopify.com')
vi.mocked(fetchStore).mockResolvedValueOnce(STORE1)
vi.mocked(renderTasks).mockImplementation(async (tasks: Task[]) => {
for (const task of tasks) {
// eslint-disable-next-line no-await-in-loop
await task.task({}, task)
}
return {}
})
vi.mocked(selectStorePrompt).mockImplementation(async ({onCreateStore}) => onCreateStore!())

await expect(
selectStore({stores: [STORE2], hasMorePages: false}, ORG1, developerPlatformClient, 'selection-option'),
).resolves.toEqual(STORE1)
expect(devStoreDemoDataPrompt).toHaveBeenCalled()
expect(createDevStore).toHaveBeenCalledWith(expect.objectContaining({withDemoData: true}))
})

test('keeps the dashboard fallback when the app-management store prompt is cancelled', async () => {
const developerPlatformClient = testDeveloperPlatformClient({clientName: ClientName.AppManagement})
vi.mocked(selectStorePrompt).mockResolvedValueOnce(undefined)
Expand All @@ -418,6 +450,7 @@ describe('selectStore', async () => {
vi.mocked(devStoreCapReached).mockResolvedValue(false)
vi.mocked(devStoreNamePrompt).mockResolvedValue('created-store')
vi.mocked(devStorePlanPrompt).mockResolvedValue('grow')
vi.mocked(devStoreDemoDataPrompt).mockResolvedValue(false)
vi.mocked(createDevStore).mockResolvedValue('created-store.myshopify.com')
vi.mocked(fetchStore)
.mockRejectedValueOnce(new StoreNotFoundError('Store is still being provisioned'))
Expand All @@ -437,6 +470,7 @@ describe('selectStore', async () => {
expect(createDevStore).toHaveBeenCalledWith({
name: 'created-store',
plan: 'grow',
withDemoData: false,
organization: ORG1,
json: false,
summary: false,
Expand All @@ -451,6 +485,7 @@ describe('selectStore', async () => {
vi.mocked(devStoreCapReached).mockResolvedValue(false)
vi.mocked(devStoreNamePrompt).mockResolvedValue('created-store')
vi.mocked(devStorePlanPrompt).mockResolvedValue('grow')
vi.mocked(devStoreDemoDataPrompt).mockResolvedValue(false)
vi.mocked(createDevStore).mockResolvedValue('created-store.myshopify.com')
vi.mocked(fetchStore).mockRejectedValue(new StoreNotFoundError('Store is still being provisioned'))
vi.mocked(renderTasks).mockImplementation(async (tasks: Task[]) => {
Expand All @@ -476,6 +511,7 @@ describe('selectStore', async () => {
vi.mocked(devStoreCapReached).mockResolvedValue(false)
vi.mocked(devStoreNamePrompt).mockResolvedValue('created-store')
vi.mocked(devStorePlanPrompt).mockResolvedValue('grow')
vi.mocked(devStoreDemoDataPrompt).mockResolvedValue(false)
vi.mocked(createDevStore).mockResolvedValue('created-store.myshopify.com')
vi.mocked(fetchStore).mockRejectedValueOnce(new AbortError('Fetching failed'))
vi.mocked(renderTasks).mockImplementation(async (tasks: Task[]) => {
Expand Down
11 changes: 9 additions & 2 deletions packages/app/src/cli/services/dev/select-store.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import {devStoreCapReached} from './cap.js'
import {fetchStore, StoreNotFoundError} from './fetch.js'
import {Organization, OrganizationStore} from '../../models/organization.js'
import {devStoreNamePrompt, devStorePlanPrompt, reloadStoreListPrompt, selectStorePrompt} from '../../prompts/dev.js'
import {
devStoreDemoDataPrompt,
devStoreNamePrompt,
devStorePlanPrompt,
reloadStoreListPrompt,
selectStorePrompt,
} from '../../prompts/dev.js'
import {ClientName, DeveloperPlatformClient, Paginateable} from '../../utilities/developer-platform-client.js'
import {sleep} from '@shopify/cli-kit/node/system'
import {isTTY, renderInfo, renderSuccess, renderTasks} from '@shopify/cli-kit/node/ui'
Expand Down Expand Up @@ -30,7 +36,8 @@ export async function selectStore(

const name = await devStoreNamePrompt()
const plan = await devStorePlanPrompt()
const domain = await createDevStore({name, plan, organization: org, json: false, summary: false})
const withDemoData = await devStoreDemoDataPrompt()
const domain = await createDevStore({name, plan, withDemoData, organization: org, json: false, summary: false})
const createdStore = await waitForCreatedStoreByDomain(org, domain, developerPlatformClient)
renderSuccess({headline: `Development store "${createdStore.shopName}" created successfully.`})
return createdStore
Expand Down
16 changes: 8 additions & 8 deletions packages/cli/oclif.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7191,7 +7191,7 @@
"examples": [
"<%= config.bin %> <%= command.id %>",
"<%= config.bin %> <%= command.id %> --name \"Lavender Candles\" --organization-id 1234567 --plan basic",
"<%= config.bin %> <%= command.id %> --name \"Lavender Candles\" --organization-id 1234567 --plan basic --with-demo-data",
"<%= config.bin %> <%= command.id %> --name \"Lavender Candles\" --organization-id 1234567 --plan basic --demo-data",
"<%= config.bin %> <%= command.id %> --name \"Lavender Candles\" --organization-id 1234567 --plan basic --json"
],
"flags": {
Expand All @@ -7204,6 +7204,13 @@
"required": false,
"type": "option"
},
"demo-data": {
"allowNo": true,
"description": "Populate the new development store with demo data.",
"env": "SHOPIFY_FLAG_STORE_DEMO_DATA",
"name": "demo-data",
"type": "boolean"
},
"feature-preview": {
"description": "The handle of a feature preview to enable on the new development store.",
"env": "SHOPIFY_FLAG_STORE_FEATURE_PREVIEW",
Expand Down Expand Up @@ -7266,13 +7273,6 @@
"hidden": false,
"name": "verbose",
"type": "boolean"
},
"with-demo-data": {
"allowNo": false,
"description": "Populate the new development store with demo data.",
"env": "SHOPIFY_FLAG_STORE_WITH_DEMO_DATA",
"name": "with-demo-data",
"type": "boolean"
}
},
"hasDynamicHelp": false,
Expand Down
9 changes: 9 additions & 0 deletions packages/organizations/src/cli/prompts/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,12 @@ export function devStorePlanPrompt(): Promise<DevStorePlan> {
choices: devStorePlanHandles.map((handle) => ({label: PLAN_LABELS[handle], value: handle})),
})
}

export function devStoreDemoDataPrompt(): Promise<boolean> {
return ui.renderConfirmationPrompt({
message: 'Populate the store with demo data?',
confirmationMessage: 'Yes, add demo data',
cancellationMessage: 'No, start with an empty store',
defaultValue: true,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CLI defaults to no demo data; it's an opt-in. Here it's phrased as an opt-out. Do we want to align the two experiences?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should encourage new users to use it.

})
}
2 changes: 1 addition & 1 deletion packages/organizations/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ export type {Organization} from './cli/models/organization.js'
export {businessPlatformTokenRefreshHandler} from './cli/services/business-platform.js'
export {createDevStore, devStorePlanHandles} from './cli/services/dev/create-dev-store.js'
export type {CreateDevStoreOptions, DevStorePlan} from './cli/services/dev/create-dev-store.js'
export {devStoreNamePrompt, devStorePlanPrompt} from './cli/prompts/dev.js'
export {devStoreNamePrompt, devStorePlanPrompt, devStoreDemoDataPrompt} from './cli/prompts/dev.js'
71 changes: 64 additions & 7 deletions packages/store/src/cli/commands/store/create/dev.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import StoreCreateDev from './dev.js'
import {createDevStore} from '../../../services/store/create/dev.js'
import {storeNamePrompt, storePlanPrompt} from '../../../prompts/store.js'
import {storeNamePrompt, storePlanPrompt, storeDemoDataPrompt} from '../../../prompts/store.js'
import {selectOrg} from '@shopify/organizations'
import {AbortError} from '@shopify/cli-kit/node/error'
import {outputResult} from '@shopify/cli-kit/node/output'
Expand Down Expand Up @@ -39,7 +39,15 @@ beforeEach(() => {

describe('store create dev command', () => {
test('resolves the organization and passes parsed flags through to the service', async () => {
await StoreCreateDev.run(['--name', 'my-test-store', '--plan', 'plus', '--organization-id', '12345'])
await StoreCreateDev.run([
'--name',
'my-test-store',
'--plan',
'plus',
'--organization-id',
'12345',
'--no-demo-data',
])

expect(selectOrg).toHaveBeenCalledWith('12345')
expect(createDevStore).toHaveBeenCalledWith({
Expand All @@ -54,7 +62,16 @@ describe('store create dev command', () => {
})

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

expect(createDevStore).toHaveBeenCalledWith({
name: 'my-test-store',
Expand All @@ -67,7 +84,7 @@ describe('store create dev command', () => {
})
})

test('passes plan, feature-preview, and with-demo-data flags through to the service', async () => {
test('passes plan, feature-preview, and demo-data flags through to the service', async () => {
await StoreCreateDev.run([
'--name',
'my-test-store',
Expand All @@ -77,7 +94,7 @@ describe('store create dev command', () => {
'12345',
'--feature-preview',
'extended_variants',
'--with-demo-data',
'--demo-data',
])

expect(createDevStore).toHaveBeenCalledWith({
Expand Down Expand Up @@ -146,13 +163,53 @@ describe('store create dev command', () => {
expect(createDevStore).toHaveBeenCalledWith(expect.objectContaining({plan: 'advanced'}))
})

test('does not prompt when all flags are provided', async () => {
test('does not prompt for the name or plan when both flags are provided', async () => {
await StoreCreateDev.run(['--name', 'my-test-store', '--plan', 'plus', '--organization-id', '12345'])

expect(storeNamePrompt).not.toHaveBeenCalled()
expect(storePlanPrompt).not.toHaveBeenCalled()
})

test('prompts for demo data when the flag is omitted in an interactive environment', async () => {
vi.mocked(storeDemoDataPrompt).mockResolvedValue(true)

await StoreCreateDev.run(['--name', 'my-test-store', '--plan', 'plus', '--organization-id', '12345'])

expect(storeDemoDataPrompt).toHaveBeenCalled()
expect(createDevStore).toHaveBeenCalledWith(expect.objectContaining({withDemoData: true}))
})

test('defaults demo data to false in a non-interactive environment', async () => {
vi.mocked(terminalSupportsPrompting).mockReturnValue(false)

await StoreCreateDev.run(['--name', 'my-test-store', '--plan', 'plus', '--organization-id', '12345'])

expect(storeDemoDataPrompt).not.toHaveBeenCalled()
expect(createDevStore).toHaveBeenCalledWith(expect.objectContaining({withDemoData: false}))
})

test('skips the demo data prompt when --demo-data is set explicitly', async () => {
await StoreCreateDev.run(['--name', 'my-test-store', '--plan', 'plus', '--organization-id', '12345', '--demo-data'])

expect(storeDemoDataPrompt).not.toHaveBeenCalled()
expect(createDevStore).toHaveBeenCalledWith(expect.objectContaining({withDemoData: true}))
})

test('skips the demo data prompt when --no-demo-data is set explicitly', async () => {
await StoreCreateDev.run([
'--name',
'my-test-store',
'--plan',
'plus',
'--organization-id',
'12345',
'--no-demo-data',
])

expect(storeDemoDataPrompt).not.toHaveBeenCalled()
expect(createDevStore).toHaveBeenCalledWith(expect.objectContaining({withDemoData: false}))
})

test('rejects an invalid plan value without calling the service', async () => {
await expect(
StoreCreateDev.run(['--name', 'my-test-store', '--plan', 'enterprise', '--organization-id', '12345']),
Expand Down Expand Up @@ -187,7 +244,7 @@ describe('store create dev command', () => {
expect(StoreCreateDev.flags['organization-id']).toBeDefined()
expect(StoreCreateDev.flags.plan).toBeDefined()
expect(StoreCreateDev.flags['feature-preview']).toBeDefined()
expect(StoreCreateDev.flags['with-demo-data']).toBeDefined()
expect(StoreCreateDev.flags['demo-data']).toBeDefined()
expect(StoreCreateDev.flags.country).toBeDefined()
expect(StoreCreateDev.flags.json).toBeDefined()
})
Expand Down
15 changes: 9 additions & 6 deletions packages/store/src/cli/commands/store/create/dev.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import {createDevStore} from '../../../services/store/create/dev.js'
import {devStorePlanHandles, DevStorePlan} from '../../../services/store/constants.js'
import {storeNamePrompt, storePlanPrompt} from '../../../prompts/store.js'
import {storeNamePrompt, storePlanPrompt, storeDemoDataPrompt} from '../../../prompts/store.js'
import {countryFlag, storeFlags} from '../../../flags.js'
import {selectOrg} from '@shopify/organizations'
import Command from '@shopify/cli-kit/node/base-command'
import {globalFlags, jsonFlag, requiredIfNonInteractive} from '@shopify/cli-kit/node/cli'
import {terminalSupportsPrompting} from '@shopify/cli-kit/node/system'
import {AbortError} from '@shopify/cli-kit/node/error'
import {outputResult} from '@shopify/cli-kit/node/output'
import {Flags} from '@oclif/core'
Expand All @@ -21,7 +22,7 @@ export default class StoreCreateDev extends Command {
static examples = [
'<%= config.bin %> <%= command.id %>',
'<%= config.bin %> <%= command.id %> --name "Lavender Candles" --organization-id 1234567 --plan basic',
'<%= config.bin %> <%= command.id %> --name "Lavender Candles" --organization-id 1234567 --plan basic --with-demo-data',
'<%= config.bin %> <%= command.id %> --name "Lavender Candles" --organization-id 1234567 --plan basic --demo-data',
'<%= config.bin %> <%= command.id %> --name "Lavender Candles" --organization-id 1234567 --plan basic --json',
]

Expand All @@ -46,10 +47,10 @@ export default class StoreCreateDev extends Command {
description: 'The handle of a feature preview to enable on the new development store.',
env: 'SHOPIFY_FLAG_STORE_FEATURE_PREVIEW',
}),
'with-demo-data': Flags.boolean({
'demo-data': Flags.boolean({
description: 'Populate the new development store with demo data.',
default: false,
env: 'SHOPIFY_FLAG_STORE_WITH_DEMO_DATA',
allowNo: true,
env: 'SHOPIFY_FLAG_STORE_DEMO_DATA',
}),
country: countryFlag,
}
Expand All @@ -60,14 +61,16 @@ export default class StoreCreateDev extends Command {
const organization = await selectOrg(flags['organization-id']?.toString())
const name = flags.name ?? (await storeNamePrompt())
const plan = (flags.plan as DevStorePlan | undefined) ?? (await storePlanPrompt())
// An unspecified flag means "ask" where we can prompt and "no" where we can't.
const withDemoData = flags['demo-data'] ?? (terminalSupportsPrompting() ? await storeDemoDataPrompt() : false)

try {
await createDevStore({
name,
organization,
plan,
featurePreview: flags['feature-preview'],
withDemoData: flags['with-demo-data'],
withDemoData,
country: flags.country,
json: flags.json,
})
Expand Down
Loading
Loading