Skip to content
Draft
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
6 changes: 6 additions & 0 deletions .changeset/preview-store-create-production.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@shopify/cli': minor
'@shopify/store': minor
---

Add `shopify store create preview` to create preview stores and persist their Admin API token in local store auth.
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// This is an autogenerated file. Don't edit this file manually.
/**
* The following flags are available for the `store create preview` command:
* @publicDocs
*/
export interface storecreatepreview {
/**
* Two-letter ISO 3166-1 alpha-2 country code for the store, such as US, CA, or GB.
* @environment SHOPIFY_FLAG_PREVIEW_STORE_COUNTRY
*/
'--country <value>'?: string

/**
* Output the result as JSON. Automatically disables color output.
* @environment SHOPIFY_FLAG_JSON
*/
'-j, --json'?: ''

/**
* The name of the store.
* @environment SHOPIFY_FLAG_PREVIEW_STORE_NAME
*/
'--name <value>'?: string

/**
* Disable color output.
* @environment SHOPIFY_FLAG_NO_COLOR
*/
'--no-color'?: ''

/**
* Increase the verbosity of the output.
* @environment SHOPIFY_FLAG_VERBOSE
*/
'--verbose'?: ''
}
56 changes: 56 additions & 0 deletions docs-shopify.dev/generated/generated_docs_data_v2.json
Original file line number Diff line number Diff line change
Expand Up @@ -4184,6 +4184,62 @@
"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}"
}
},
"storecreatepreview": {
"docs-shopify.dev/commands/interfaces/store-create-preview.interface.ts": {
"filePath": "docs-shopify.dev/commands/interfaces/store-create-preview.interface.ts",
"name": "storecreatepreview",
"description": "The following flags are available for the `store create preview` command:",
"isPublicDocs": true,
"members": [
{
"filePath": "docs-shopify.dev/commands/interfaces/store-create-preview.interface.ts",
"syntaxKind": "PropertySignature",
"name": "--country <value>",
"value": "string",
"description": "Two-letter ISO 3166-1 alpha-2 country code for the store, such as US, CA, or GB.",
"isOptional": true,
"environmentValue": "SHOPIFY_FLAG_PREVIEW_STORE_COUNTRY"
},
{
"filePath": "docs-shopify.dev/commands/interfaces/store-create-preview.interface.ts",
"syntaxKind": "PropertySignature",
"name": "--name <value>",
"value": "string",
"description": "The name of the store.",
"isOptional": true,
"environmentValue": "SHOPIFY_FLAG_PREVIEW_STORE_NAME"
},
{
"filePath": "docs-shopify.dev/commands/interfaces/store-create-preview.interface.ts",
"syntaxKind": "PropertySignature",
"name": "--no-color",
"value": "''",
"description": "Disable color output.",
"isOptional": true,
"environmentValue": "SHOPIFY_FLAG_NO_COLOR"
},
{
"filePath": "docs-shopify.dev/commands/interfaces/store-create-preview.interface.ts",
"syntaxKind": "PropertySignature",
"name": "--verbose",
"value": "''",
"description": "Increase the verbosity of the output.",
"isOptional": true,
"environmentValue": "SHOPIFY_FLAG_VERBOSE"
},
{
"filePath": "docs-shopify.dev/commands/interfaces/store-create-preview.interface.ts",
"syntaxKind": "PropertySignature",
"name": "-j, --json",
"value": "''",
"description": "Output the result as JSON. Automatically disables color output.",
"isOptional": true,
"environmentValue": "SHOPIFY_FLAG_JSON"
}
],
"value": "export interface storecreatepreview {\n /**\n * Two-letter ISO 3166-1 alpha-2 country code for the store, such as US, CA, or GB.\n * @environment SHOPIFY_FLAG_PREVIEW_STORE_COUNTRY\n */\n '--country <value>'?: string\n\n /**\n * Output the result as JSON. Automatically disables color output.\n * @environment SHOPIFY_FLAG_JSON\n */\n '-j, --json'?: ''\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}"
}
},
"storeexecute": {
"docs-shopify.dev/commands/interfaces/store-execute.interface.ts": {
"filePath": "docs-shopify.dev/commands/interfaces/store-execute.interface.ts",
Expand Down
31 changes: 31 additions & 0 deletions packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
* [`shopify plugins update`](#shopify-plugins-update)
* [`shopify search [query]`](#shopify-search-query)
* [`shopify store auth`](#shopify-store-auth)
* [`shopify store create preview`](#shopify-store-create-preview)
* [`shopify store execute`](#shopify-store-execute)
* [`shopify theme check`](#shopify-theme-check)
* [`shopify theme console`](#shopify-theme-console)
Expand Down Expand Up @@ -2131,6 +2132,36 @@ EXAMPLES
$ shopify store auth --store shop.myshopify.com --scopes read_products,write_products --json
```

## `shopify store create preview`

Create a preview Shopify store.

```
USAGE
$ shopify store create preview [--country <value>] [-j] [--name <value>] [--no-color] [--verbose]

FLAGS
-j, --json [env: SHOPIFY_FLAG_JSON] Output the result as JSON. Automatically disables color output.
--country=<value> [env: SHOPIFY_FLAG_PREVIEW_STORE_COUNTRY] Two-letter ISO 3166-1 alpha-2 country code for the
store, such as US, CA, or GB.
--name=<value> [env: SHOPIFY_FLAG_PREVIEW_STORE_NAME] The name of the store.
--no-color [env: SHOPIFY_FLAG_NO_COLOR] Disable color output.
--verbose [env: SHOPIFY_FLAG_VERBOSE] Increase the verbosity of the output.

DESCRIPTION
Create a preview Shopify store.

Creates a new preview Shopify store for a merchant who wants to try Shopify without needing to immediately create an
account.

EXAMPLES
$ shopify store create preview --name "Lavender Candles"

$ shopify store create preview --name "Lavender Candles" --country US

$ shopify store create preview --name "Lavender Candles" --json
```

## `shopify store execute`

Execute GraphQL queries and mutations on a store.
Expand Down
68 changes: 68 additions & 0 deletions packages/cli/oclif.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5737,6 +5737,74 @@
"strict": true,
"summary": "Authenticate an app against a store for store commands."
},
"store:create:preview": {
"aliases": [
],
"args": {
},
"customPluginName": "@shopify/store",
"description": "Creates a new preview Shopify store for a merchant who wants to try Shopify without needing to immediately create an account.",
"descriptionWithMarkdown": "Creates a new preview Shopify store for a merchant who wants to try Shopify without needing to immediately create an account.",
"examples": [
"<%= config.bin %> <%= command.id %> --name \"Lavender Candles\"",
"<%= config.bin %> <%= command.id %> --name \"Lavender Candles\" --country US",
"<%= config.bin %> <%= command.id %> --name \"Lavender Candles\" --json"
],
"flags": {
"country": {
"description": "Two-letter ISO 3166-1 alpha-2 country code for the store, such as US, CA, or GB.",
"env": "SHOPIFY_FLAG_PREVIEW_STORE_COUNTRY",
"hasDynamicHelp": false,
"multiple": false,
"name": "country",
"required": false,
"type": "option"
},
"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": "The name of the store.",
"env": "SHOPIFY_FLAG_PREVIEW_STORE_NAME",
"hasDynamicHelp": false,
"multiple": false,
"name": "name",
"required": false,
"type": "option"
},
"no-color": {
"allowNo": false,
"description": "Disable color output.",
"env": "SHOPIFY_FLAG_NO_COLOR",
"hidden": false,
"name": "no-color",
"type": "boolean"
},
"verbose": {
"allowNo": false,
"description": "Increase the verbosity of the output.",
"env": "SHOPIFY_FLAG_VERBOSE",
"hidden": false,
"name": "verbose",
"type": "boolean"
}
},
"hasDynamicHelp": false,
"hiddenAliases": [
],
"id": "store:create:preview",
"pluginAlias": "@shopify/cli",
"pluginName": "@shopify/cli",
"pluginType": "core",
"strict": true,
"summary": "Create a preview Shopify store."
},
"store:execute": {
"aliases": [
],
Expand Down
2 changes: 2 additions & 0 deletions packages/e2e/data/snapshots/commands.txt
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@
├─ search
├─ store
│ ├─ auth
│ ├─ create
│ │ └─ preview
│ └─ execute
├─ theme
│ ├─ check
Expand Down
39 changes: 39 additions & 0 deletions packages/store/src/cli/commands/store/create/preview.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import StoreCreatePreview from './preview.js'
import {createPreviewStoreCommand} from '../../../services/store/create/preview/index.js'
import {writeCreatePreviewStoreResult} from '../../../services/store/create/preview/result.js'
import {renderSingleTask} from '@shopify/cli-kit/node/ui'
import {describe, expect, test, vi} from 'vitest'

vi.mock('../../../services/store/create/preview/index.js')
vi.mock('../../../services/store/create/preview/result.js')
vi.mock('@shopify/cli-kit/node/ui', async () => {
const actual = await vi.importActual<typeof import('@shopify/cli-kit/node/ui')>('@shopify/cli-kit/node/ui')
return {...actual, renderSingleTask: vi.fn(async ({task}) => task())}
})

describe('store create preview command', () => {
test('passes parsed flags through to the service', async () => {
const result = {
status: 'success' as const,
message: 'Your preview store is ready.',
store: {id: '123', name: 'Lavender Candles', subdomain: 'x.myshopify.com', requestedCountry: 'US'},
nextSteps: [],
}
vi.mocked(createPreviewStoreCommand).mockResolvedValueOnce(result)

await StoreCreatePreview.run(['--name', 'Lavender Candles', '--country', 'us', '--json'])

expect(renderSingleTask).toHaveBeenCalledWith({
title: expect.objectContaining({value: 'Creating store…'}),
task: expect.any(Function),
})
expect(createPreviewStoreCommand).toHaveBeenCalledWith({name: 'Lavender Candles', country: 'US'})
expect(writeCreatePreviewStoreResult).toHaveBeenCalledWith(result, 'json')
})

test('rejects invalid country codes before calling the service', async () => {
await expect(StoreCreatePreview.run(['--country', 'USA'])).rejects.toThrow('process.exit unexpectedly called')

expect(createPreviewStoreCommand).not.toHaveBeenCalled()
})
})
56 changes: 56 additions & 0 deletions packages/store/src/cli/commands/store/create/preview.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import {type CreatePreviewStoreResult, createPreviewStoreCommand} from '../../../services/store/create/preview/index.js'
import {writeCreatePreviewStoreResult} from '../../../services/store/create/preview/result.js'
import StoreCommand from '../../../utilities/store-command.js'
import {globalFlags, jsonFlag} from '@shopify/cli-kit/node/cli'
import {outputContent} from '@shopify/cli-kit/node/output'
import {renderSingleTask} from '@shopify/cli-kit/node/ui'
import {Flags} from '@oclif/core'

export default class StoreCreatePreview extends StoreCommand {
static summary = 'Create a preview Shopify store.'

static descriptionWithMarkdown = `Creates a new preview Shopify store for a merchant who wants to try Shopify without needing to immediately create an account.`

static description = this.descriptionWithoutMarkdown()

static examples = [
'<%= config.bin %> <%= command.id %> --name "Lavender Candles"',
'<%= config.bin %> <%= command.id %> --name "Lavender Candles" --country US',
'<%= config.bin %> <%= command.id %> --name "Lavender Candles" --json',
]

static flags = {
...globalFlags,
...jsonFlag,
name: Flags.string({
description: 'The name of the store.',
env: 'SHOPIFY_FLAG_PREVIEW_STORE_NAME',
required: false,
}),
country: Flags.string({
description: 'Two-letter ISO 3166-1 alpha-2 country code for the store, such as US, CA, or GB.',
env: 'SHOPIFY_FLAG_PREVIEW_STORE_COUNTRY',
required: false,
parse: async (value) => value.trim().toUpperCase(),
}),
}

public async run(): Promise<void> {
const {flags} = await this.parse(StoreCreatePreview)

if (flags.country !== undefined && !isCountryCode(flags.country)) {
this.error('Country must be a two-letter ISO country code, for example: US.')
}

const result = await renderSingleTask<CreatePreviewStoreResult>({
title: outputContent`Creating store…`,
task: async () => createPreviewStoreCommand({name: flags.name, country: flags.country}),
})

writeCreatePreviewStoreResult(result, flags.json ? 'json' : 'text')
}
}

function isCountryCode(value: string): boolean {
return /^[A-Z]{2}$/.test(value)
}
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,42 @@ describe('store session storage', () => {
})
})

test('round-trips preview store session metadata', () => {
const storage = inMemoryStorage()
const previewSession = buildSession({
userId: 'preview:placeholder-uuid',
scopes: [],
kind: 'preview',
preview: {
placeholderAccountUuid: 'placeholder-uuid',
shopId: '123',
name: 'Lavender Candles',
country: 'US',
createdAt: '2026-06-08T12:00:00.000Z',
},
})

setStoredStoreAppSession(previewSession, storage as any)

expect(getCurrentStoredStoreAppSession('shop.myshopify.com', storage as any)).toEqual(previewSession)
})

test('rejects preview store sessions with malformed metadata', () => {
const storage = inMemoryStorage()
storage.set(storeAuthSessionKey('shop.myshopify.com'), {
currentUserId: 'preview:placeholder-uuid',
sessionsByUserId: {
'preview:placeholder-uuid': {
...buildSession({userId: 'preview:placeholder-uuid', kind: 'preview'}),
preview: {placeholderAccountUuid: 'placeholder-uuid'},
},
},
})

expect(getCurrentStoredStoreAppSession('shop.myshopify.com', storage as any)).toBeUndefined()
expect(storage.get(storeAuthSessionKey('shop.myshopify.com'))).toBeUndefined()
})

test('overwrites a malformed bucket when writing a new session', () => {
const storage = inMemoryStorage()
storage.set(storeAuthSessionKey('shop.myshopify.com'), {
Expand Down
Loading
Loading