Skip to content

Commit a95b7a4

Browse files
Add production preview store create command
1 parent c9b3ef0 commit a95b7a4

17 files changed

Lines changed: 988 additions & 0 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@shopify/cli': minor
3+
'@shopify/store': minor
4+
---
5+
6+
Add `shopify store create preview` to create preview stores and persist their Admin API token in local store auth.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// This is an autogenerated file. Don't edit this file manually.
2+
/**
3+
* The following flags are available for the `store create preview` command:
4+
* @publicDocs
5+
*/
6+
export interface storecreatepreview {
7+
/**
8+
* Output the result as JSON. Automatically disables color output.
9+
* @environment SHOPIFY_FLAG_JSON
10+
*/
11+
'-j, --json'?: ''
12+
13+
/**
14+
* The ISO country code for the default locale for your store.
15+
* @environment SHOPIFY_FLAG_PREVIEW_STORE_COUNTRY
16+
*/
17+
'--country <value>'?: string
18+
19+
/**
20+
* The name of the store.
21+
* @environment SHOPIFY_FLAG_PREVIEW_STORE_NAME
22+
*/
23+
'--name <value>'?: string
24+
25+
/**
26+
* Disable color output.
27+
* @environment SHOPIFY_FLAG_NO_COLOR
28+
*/
29+
'--no-color'?: ''
30+
31+
/**
32+
* Increase the verbosity of the output.
33+
* @environment SHOPIFY_FLAG_VERBOSE
34+
*/
35+
'--verbose'?: ''
36+
}

docs-shopify.dev/generated/generated_docs_data_v2.json

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4184,6 +4184,62 @@
41844184
"value": "export interface storeauth {\n /**\n * Output the result as JSON. Automatically disables color output.\n * @environment SHOPIFY_FLAG_JSON\n */\n '-j, --json'?: ''\n\n /**\n * Disable color output.\n * @environment SHOPIFY_FLAG_NO_COLOR\n */\n '--no-color'?: ''\n\n /**\n * Comma-separated Admin API scopes to request for the app.\n * @environment SHOPIFY_FLAG_SCOPES\n */\n '--scopes <value>': string\n\n /**\n * The myshopify.com domain of the store to authenticate against.\n * @environment SHOPIFY_FLAG_STORE\n */\n '-s, --store <value>': string\n\n /**\n * Increase the verbosity of the output.\n * @environment SHOPIFY_FLAG_VERBOSE\n */\n '--verbose'?: ''\n}"
41854185
}
41864186
},
4187+
"storecreatepreview": {
4188+
"docs-shopify.dev/commands/interfaces/store-create-preview.interface.ts": {
4189+
"filePath": "docs-shopify.dev/commands/interfaces/store-create-preview.interface.ts",
4190+
"name": "storecreatepreview",
4191+
"description": "The following flags are available for the `store create preview` command:",
4192+
"isPublicDocs": true,
4193+
"members": [
4194+
{
4195+
"filePath": "docs-shopify.dev/commands/interfaces/store-create-preview.interface.ts",
4196+
"syntaxKind": "PropertySignature",
4197+
"name": "--country <value>",
4198+
"value": "string",
4199+
"description": "The ISO country code for the default locale for your store.",
4200+
"isOptional": true,
4201+
"environmentValue": "SHOPIFY_FLAG_PREVIEW_STORE_COUNTRY"
4202+
},
4203+
{
4204+
"filePath": "docs-shopify.dev/commands/interfaces/store-create-preview.interface.ts",
4205+
"syntaxKind": "PropertySignature",
4206+
"name": "--name <value>",
4207+
"value": "string",
4208+
"description": "The name of the store.",
4209+
"isOptional": true,
4210+
"environmentValue": "SHOPIFY_FLAG_PREVIEW_STORE_NAME"
4211+
},
4212+
{
4213+
"filePath": "docs-shopify.dev/commands/interfaces/store-create-preview.interface.ts",
4214+
"syntaxKind": "PropertySignature",
4215+
"name": "--no-color",
4216+
"value": "''",
4217+
"description": "Disable color output.",
4218+
"isOptional": true,
4219+
"environmentValue": "SHOPIFY_FLAG_NO_COLOR"
4220+
},
4221+
{
4222+
"filePath": "docs-shopify.dev/commands/interfaces/store-create-preview.interface.ts",
4223+
"syntaxKind": "PropertySignature",
4224+
"name": "--verbose",
4225+
"value": "''",
4226+
"description": "Increase the verbosity of the output.",
4227+
"isOptional": true,
4228+
"environmentValue": "SHOPIFY_FLAG_VERBOSE"
4229+
},
4230+
{
4231+
"filePath": "docs-shopify.dev/commands/interfaces/store-create-preview.interface.ts",
4232+
"syntaxKind": "PropertySignature",
4233+
"name": "-j, --json",
4234+
"value": "''",
4235+
"description": "Output the result as JSON. Automatically disables color output.",
4236+
"isOptional": true,
4237+
"environmentValue": "SHOPIFY_FLAG_JSON"
4238+
}
4239+
],
4240+
"value": "export interface storecreatepreview {\n /**\n * Output the result as JSON. Automatically disables color output.\n * @environment SHOPIFY_FLAG_JSON\n */\n '-j, --json'?: ''\n\n /**\n * The ISO country code for the default locale for your store.\n * @environment SHOPIFY_FLAG_PREVIEW_STORE_COUNTRY\n */\n '--country <value>'?: string\n\n /**\n * The name of the store.\n * @environment SHOPIFY_FLAG_PREVIEW_STORE_NAME\n */\n '--name <value>'?: string\n\n /**\n * Disable color output.\n * @environment SHOPIFY_FLAG_NO_COLOR\n */\n '--no-color'?: ''\n\n /**\n * Increase the verbosity of the output.\n * @environment SHOPIFY_FLAG_VERBOSE\n */\n '--verbose'?: ''\n}"
4241+
}
4242+
},
41874243
"storeexecute": {
41884244
"docs-shopify.dev/commands/interfaces/store-execute.interface.ts": {
41894245
"filePath": "docs-shopify.dev/commands/interfaces/store-execute.interface.ts",

packages/cli/README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
* [`shopify plugins update`](#shopify-plugins-update)
7878
* [`shopify search [query]`](#shopify-search-query)
7979
* [`shopify store auth`](#shopify-store-auth)
80+
* [`shopify store create preview`](#shopify-store-create-preview)
8081
* [`shopify store execute`](#shopify-store-execute)
8182
* [`shopify theme check`](#shopify-theme-check)
8283
* [`shopify theme console`](#shopify-theme-console)
@@ -2131,6 +2132,36 @@ EXAMPLES
21312132
$ shopify store auth --store shop.myshopify.com --scopes read_products,write_products --json
21322133
```
21332134

2135+
## `shopify store create preview`
2136+
2137+
Create a preview Shopify store.
2138+
2139+
```
2140+
USAGE
2141+
$ shopify store create preview [--country <value>] [-j] [--name <value>] [--no-color] [--verbose]
2142+
2143+
FLAGS
2144+
-j, --json [env: SHOPIFY_FLAG_JSON] Output the result as JSON. Automatically disables color output.
2145+
--country=<value> [env: SHOPIFY_FLAG_PREVIEW_STORE_COUNTRY] The ISO country code for the default locale for your
2146+
store.
2147+
--name=<value> [env: SHOPIFY_FLAG_PREVIEW_STORE_NAME] The name of the store.
2148+
--no-color [env: SHOPIFY_FLAG_NO_COLOR] Disable color output.
2149+
--verbose [env: SHOPIFY_FLAG_VERBOSE] Increase the verbosity of the output.
2150+
2151+
DESCRIPTION
2152+
Create a preview Shopify store.
2153+
2154+
Creates a new preview Shopify store for a merchant who wants to try Shopify without needing to immediately create an
2155+
account.
2156+
2157+
EXAMPLES
2158+
$ shopify store create preview --name "Lavender Candles"
2159+
2160+
$ shopify store create preview --name "Lavender Candles" --country US
2161+
2162+
$ shopify store create preview --name "Lavender Candles" --json
2163+
```
2164+
21342165
## `shopify store execute`
21352166

21362167
Execute GraphQL queries and mutations on a store.

packages/cli/oclif.manifest.json

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5737,6 +5737,74 @@
57375737
"strict": true,
57385738
"summary": "Authenticate an app against a store for store commands."
57395739
},
5740+
"store:create:preview": {
5741+
"aliases": [
5742+
],
5743+
"args": {
5744+
},
5745+
"customPluginName": "@shopify/store",
5746+
"description": "Creates a new preview Shopify store for a merchant who wants to try Shopify without needing to immediately create an account.",
5747+
"descriptionWithMarkdown": "Creates a new preview Shopify store for a merchant who wants to try Shopify without needing to immediately create an account.",
5748+
"examples": [
5749+
"<%= config.bin %> <%= command.id %> --name \"Lavender Candles\"",
5750+
"<%= config.bin %> <%= command.id %> --name \"Lavender Candles\" --country US",
5751+
"<%= config.bin %> <%= command.id %> --name \"Lavender Candles\" --json"
5752+
],
5753+
"flags": {
5754+
"country": {
5755+
"description": "The ISO country code for the default locale for your store.",
5756+
"env": "SHOPIFY_FLAG_PREVIEW_STORE_COUNTRY",
5757+
"hasDynamicHelp": false,
5758+
"multiple": false,
5759+
"name": "country",
5760+
"required": false,
5761+
"type": "option"
5762+
},
5763+
"json": {
5764+
"allowNo": false,
5765+
"char": "j",
5766+
"description": "Output the result as JSON. Automatically disables color output.",
5767+
"env": "SHOPIFY_FLAG_JSON",
5768+
"hidden": false,
5769+
"name": "json",
5770+
"type": "boolean"
5771+
},
5772+
"name": {
5773+
"description": "The name of the store.",
5774+
"env": "SHOPIFY_FLAG_PREVIEW_STORE_NAME",
5775+
"hasDynamicHelp": false,
5776+
"multiple": false,
5777+
"name": "name",
5778+
"required": false,
5779+
"type": "option"
5780+
},
5781+
"no-color": {
5782+
"allowNo": false,
5783+
"description": "Disable color output.",
5784+
"env": "SHOPIFY_FLAG_NO_COLOR",
5785+
"hidden": false,
5786+
"name": "no-color",
5787+
"type": "boolean"
5788+
},
5789+
"verbose": {
5790+
"allowNo": false,
5791+
"description": "Increase the verbosity of the output.",
5792+
"env": "SHOPIFY_FLAG_VERBOSE",
5793+
"hidden": false,
5794+
"name": "verbose",
5795+
"type": "boolean"
5796+
}
5797+
},
5798+
"hasDynamicHelp": false,
5799+
"hiddenAliases": [
5800+
],
5801+
"id": "store:create:preview",
5802+
"pluginAlias": "@shopify/cli",
5803+
"pluginName": "@shopify/cli",
5804+
"pluginType": "core",
5805+
"strict": true,
5806+
"summary": "Create a preview Shopify store."
5807+
},
57405808
"store:execute": {
57415809
"aliases": [
57425810
],

packages/e2e/data/snapshots/commands.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@
9494
├─ search
9595
├─ store
9696
│ ├─ auth
97+
│ ├─ create
98+
│ │ └─ preview
9799
│ └─ execute
98100
├─ theme
99101
│ ├─ check
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import StoreCreatePreview from './preview.js'
2+
import {createPreviewStoreCommand} from '../../../services/store/create/preview/index.js'
3+
import {writeCreatePreviewStoreResult} from '../../../services/store/create/preview/result.js'
4+
import {renderSingleTask} from '@shopify/cli-kit/node/ui'
5+
import {describe, expect, test, vi} from 'vitest'
6+
7+
vi.mock('../../../services/store/create/preview/index.js')
8+
vi.mock('../../../services/store/create/preview/result.js')
9+
vi.mock('@shopify/cli-kit/node/ui', async () => {
10+
const actual = await vi.importActual<typeof import('@shopify/cli-kit/node/ui')>('@shopify/cli-kit/node/ui')
11+
return {...actual, renderSingleTask: vi.fn(async ({task}) => task())}
12+
})
13+
14+
describe('store create preview command', () => {
15+
test('passes parsed flags through to the service', async () => {
16+
const result = {
17+
status: 'success' as const,
18+
message: 'Your preview store is ready.',
19+
store: {id: '123', name: 'Lavender Candles', subdomain: 'x.myshopify.com', country: 'US'},
20+
nextSteps: [],
21+
}
22+
vi.mocked(createPreviewStoreCommand).mockResolvedValueOnce(result)
23+
24+
await StoreCreatePreview.run(['--name', 'Lavender Candles', '--country', 'us', '--json'])
25+
26+
expect(renderSingleTask).toHaveBeenCalledWith({
27+
title: expect.objectContaining({value: 'Creating store…'}),
28+
task: expect.any(Function),
29+
})
30+
expect(createPreviewStoreCommand).toHaveBeenCalledWith({name: 'Lavender Candles', country: 'US'})
31+
expect(writeCreatePreviewStoreResult).toHaveBeenCalledWith(result, 'json')
32+
})
33+
34+
test('rejects invalid country codes before calling the service', async () => {
35+
await expect(StoreCreatePreview.run(['--country', 'USA'])).rejects.toThrow('process.exit unexpectedly called')
36+
37+
expect(createPreviewStoreCommand).not.toHaveBeenCalled()
38+
})
39+
})
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import {createPreviewStoreCommand} from '../../../services/store/create/preview/index.js'
2+
import {writeCreatePreviewStoreResult} from '../../../services/store/create/preview/result.js'
3+
import StoreCommand from '../../../utilities/store-command.js'
4+
import {globalFlags, jsonFlag} from '@shopify/cli-kit/node/cli'
5+
import {outputContent} from '@shopify/cli-kit/node/output'
6+
import {renderSingleTask} from '@shopify/cli-kit/node/ui'
7+
import {Flags} from '@oclif/core'
8+
9+
export default class StoreCreatePreview extends StoreCommand {
10+
static summary = 'Create a preview Shopify store.'
11+
12+
static descriptionWithMarkdown = `Creates a new preview Shopify store for a merchant who wants to try Shopify without needing to immediately create an account.`
13+
14+
static description = this.descriptionWithoutMarkdown()
15+
16+
static examples = [
17+
'<%= config.bin %> <%= command.id %> --name "Lavender Candles"',
18+
'<%= config.bin %> <%= command.id %> --name "Lavender Candles" --country US',
19+
'<%= config.bin %> <%= command.id %> --name "Lavender Candles" --json',
20+
]
21+
22+
static flags = {
23+
...globalFlags,
24+
...jsonFlag,
25+
name: Flags.string({
26+
description: 'The name of the store.',
27+
env: 'SHOPIFY_FLAG_PREVIEW_STORE_NAME',
28+
required: false,
29+
}),
30+
country: Flags.string({
31+
description: 'The ISO country code for the default locale for your store.',
32+
env: 'SHOPIFY_FLAG_PREVIEW_STORE_COUNTRY',
33+
required: false,
34+
parse: async (value) => value.trim().toUpperCase(),
35+
}),
36+
}
37+
38+
public async run(): Promise<void> {
39+
const {flags} = await this.parse(StoreCreatePreview)
40+
41+
if (flags.country && !isCountryCode(flags.country)) {
42+
this.error('Country must be a two-letter ISO country code, for example: US.')
43+
}
44+
45+
const result = await renderSingleTask({
46+
title: outputContent`Creating store…`,
47+
task: async () => createPreviewStoreCommand({name: flags.name, country: flags.country}),
48+
})
49+
50+
writeCreatePreviewStoreResult(result, flags.json ? 'json' : 'text')
51+
}
52+
}
53+
54+
function isCountryCode(value: string): boolean {
55+
return /^[A-Z]{2}$/.test(value)
56+
}

packages/store/src/cli/services/store/auth/session-store.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,42 @@ describe('store session storage', () => {
144144
})
145145
})
146146

147+
test('round-trips preview store session metadata', () => {
148+
const storage = inMemoryStorage()
149+
const previewSession = buildSession({
150+
userId: 'preview:placeholder-uuid',
151+
scopes: [],
152+
kind: 'preview',
153+
preview: {
154+
placeholderAccountUuid: 'placeholder-uuid',
155+
shopId: '123',
156+
name: 'Lavender Candles',
157+
country: 'US',
158+
createdAt: '2026-06-08T12:00:00.000Z',
159+
},
160+
})
161+
162+
setStoredStoreAppSession(previewSession, storage as any)
163+
164+
expect(getCurrentStoredStoreAppSession('shop.myshopify.com', storage as any)).toEqual(previewSession)
165+
})
166+
167+
test('rejects preview store sessions with malformed metadata', () => {
168+
const storage = inMemoryStorage()
169+
storage.set(storeAuthSessionKey('shop.myshopify.com'), {
170+
currentUserId: 'preview:placeholder-uuid',
171+
sessionsByUserId: {
172+
'preview:placeholder-uuid': {
173+
...buildSession({userId: 'preview:placeholder-uuid', kind: 'preview'}),
174+
preview: {placeholderAccountUuid: 'placeholder-uuid'},
175+
},
176+
},
177+
})
178+
179+
expect(getCurrentStoredStoreAppSession('shop.myshopify.com', storage as any)).toBeUndefined()
180+
expect(storage.get(storeAuthSessionKey('shop.myshopify.com'))).toBeUndefined()
181+
})
182+
147183
test('overwrites a malformed bucket when writing a new session', () => {
148184
const storage = inMemoryStorage()
149185
storage.set(storeAuthSessionKey('shop.myshopify.com'), {

0 commit comments

Comments
 (0)