Skip to content
Open
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
20 changes: 10 additions & 10 deletions docs-shopify.dev/generated/generated_docs_data_v2.json
Original file line number Diff line number Diff line change
Expand Up @@ -8291,15 +8291,6 @@
"isOptional": true,
"environmentValue": "SHOPIFY_FLAG_AUTH_ALIAS"
},
{
"filePath": "docs-shopify.dev/commands/interfaces/theme-preview.interface.ts",
"syntaxKind": "PropertySignature",
"name": "--json",
"value": "''",
"description": "Output the preview URL and identifier as JSON.",
"isOptional": true,
"environmentValue": "SHOPIFY_FLAG_JSON"
},
{
"filePath": "docs-shopify.dev/commands/interfaces/theme-preview.interface.ts",
"syntaxKind": "PropertySignature",
Expand Down Expand Up @@ -8380,6 +8371,15 @@
"isOptional": true,
"environmentValue": "SHOPIFY_FLAG_ENVIRONMENT"
},
{
"filePath": "docs-shopify.dev/commands/interfaces/theme-preview.interface.ts",
"syntaxKind": "PropertySignature",
"name": "-j, --json",
"value": "''",
"description": "Output the preview URL and identifier as JSON.",
"isOptional": true,
"environmentValue": "SHOPIFY_FLAG_JSON"
},
{
"filePath": "docs-shopify.dev/commands/interfaces/theme-preview.interface.ts",
"syntaxKind": "PropertySignature",
Expand All @@ -8398,7 +8398,7 @@
"environmentValue": "SHOPIFY_FLAG_THEME_ID"
}
],
"value": "export interface themepreview {\n /**\n * Alias of the Shopify account to use for authentication.\n * @environment SHOPIFY_FLAG_AUTH_ALIAS\n */\n '--auth-alias <value>'?: string\n\n /**\n * The environment to apply to the current command.\n * @environment SHOPIFY_FLAG_ENVIRONMENT\n */\n '-e, --environment <value>'?: string\n\n /**\n * Output the preview URL and identifier as JSON.\n * @environment SHOPIFY_FLAG_JSON\n */\n '--json'?: ''\n\n /**\n * Print the command's JSON schemas.\n * @environment SHOPIFY_FLAG_JSON_SCHEMA\n */\n '--json-schema'?: ''\n\n /**\n * Disable color output.\n * @environment SHOPIFY_FLAG_NO_COLOR\n */\n '--no-color'?: ''\n\n /**\n * Automatically launch the theme preview in your default web browser.\n * @environment SHOPIFY_FLAG_OPEN\n */\n '--open'?: ''\n\n /**\n * Path to a JSON overrides file.\n * @environment SHOPIFY_FLAG_OVERRIDES\n */\n '--overrides <value>': string\n\n /**\n * Password generated from the Theme Access app or an Admin API token.\n * @environment SHOPIFY_CLI_THEME_TOKEN\n */\n '--password <value>'?: string\n\n /**\n * The path where you want to run the command. Defaults to the current working directory.\n * @environment SHOPIFY_FLAG_PATH\n */\n '--path <value>'?: string\n\n /**\n * An existing preview identifier to update instead of creating a new preview.\n * @environment SHOPIFY_FLAG_PREVIEW_ID\n */\n '--preview-id <value>'?: string\n\n /**\n * Store URL. It can be the store prefix (example) or the full myshopify.com URL (example.myshopify.com, https://example.myshopify.com).\n * @environment SHOPIFY_FLAG_STORE\n */\n '-s, --store <value>'?: string\n\n /**\n * Theme ID or name of the remote theme.\n * @environment SHOPIFY_FLAG_THEME_ID\n */\n '-t, --theme <value>': string\n\n /**\n * Increase the verbosity of the output. May include sensitive data.\n * @environment SHOPIFY_FLAG_VERBOSE\n */\n '--verbose'?: ''\n}"
"value": "export interface themepreview {\n /**\n * Alias of the Shopify account to use for authentication.\n * @environment SHOPIFY_FLAG_AUTH_ALIAS\n */\n '--auth-alias <value>'?: string\n\n /**\n * The environment to apply to the current command.\n * @environment SHOPIFY_FLAG_ENVIRONMENT\n */\n '-e, --environment <value>'?: string\n\n /**\n * Output the preview URL and identifier as JSON.\n * @environment SHOPIFY_FLAG_JSON\n */\n '-j, --json'?: ''\n\n /**\n * Print the command's JSON schemas.\n * @environment SHOPIFY_FLAG_JSON_SCHEMA\n */\n '--json-schema'?: ''\n\n /**\n * Disable color output.\n * @environment SHOPIFY_FLAG_NO_COLOR\n */\n '--no-color'?: ''\n\n /**\n * Automatically launch the theme preview in your default web browser.\n * @environment SHOPIFY_FLAG_OPEN\n */\n '--open'?: ''\n\n /**\n * Path to a JSON overrides file.\n * @environment SHOPIFY_FLAG_OVERRIDES\n */\n '--overrides <value>': string\n\n /**\n * Password generated from the Theme Access app or an Admin API token.\n * @environment SHOPIFY_CLI_THEME_TOKEN\n */\n '--password <value>'?: string\n\n /**\n * The path where you want to run the command. Defaults to the current working directory.\n * @environment SHOPIFY_FLAG_PATH\n */\n '--path <value>'?: string\n\n /**\n * An existing preview identifier to update instead of creating a new preview.\n * @environment SHOPIFY_FLAG_PREVIEW_ID\n */\n '--preview-id <value>'?: string\n\n /**\n * Store URL. It can be the store prefix (example) or the full myshopify.com URL (example.myshopify.com, https://example.myshopify.com).\n * @environment SHOPIFY_FLAG_STORE\n */\n '-s, --store <value>'?: string\n\n /**\n * Theme ID or name of the remote theme.\n * @environment SHOPIFY_FLAG_THEME_ID\n */\n '-t, --theme <value>': string\n\n /**\n * Increase the verbosity of the output. May include sensitive data.\n * @environment SHOPIFY_FLAG_VERBOSE\n */\n '--verbose'?: ''\n}"
}
},
"themeprofile": {
Expand Down
35 changes: 30 additions & 5 deletions packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8811,7 +8811,7 @@ Applies JSON overrides to a theme and returns a preview URL.

```
USAGE
$ shopify theme preview --overrides <value> -t <value> [--auth-alias <value>] [-e <value>...] [--json]
$ shopify theme preview --overrides <value> -t <value> [--auth-alias <value>] [-e <value>...] [-j]
[--json-schema] [--no-color] [--open] [--password <value>] [--path <value>] [--preview-id <value>] [-s <value>]
[--verbose]

Expand All @@ -8820,6 +8820,10 @@ FLAGS
The environment to apply to the current command.
[env: SHOPIFY_FLAG_ENVIRONMENT]

-j, --json
Output the preview URL and identifier as JSON.
[env: SHOPIFY_FLAG_JSON]

-s, --store=<value>
Store URL. It can be the store prefix (example) or the full myshopify.com URL (example.myshopify.com,
https://example.myshopify.com).
Expand All @@ -8833,10 +8837,6 @@ FLAGS
Alias of the Shopify account to use for authentication.
[env: SHOPIFY_FLAG_AUTH_ALIAS]

--json
Output the preview URL and identifier as JSON.
[env: SHOPIFY_FLAG_JSON]

--json-schema
Print the command's JSON schemas.
[env: SHOPIFY_FLAG_JSON_SCHEMA]
Expand Down Expand Up @@ -8876,6 +8876,31 @@ DESCRIPTION

The command returns a preview URL and a preview identifier. You can reuse the preview identifier with `--preview-id`
to update an existing preview instead of creating a new one.

Use `--json-schema` to print the result, error, and event schemas.

Output from `--json` conforms to the `ThemePreviewResult` schema.

```json
{
"type": "object",
"properties": {
"url": {
"type": "string"
},
"preview_identifier": {
"type": "string"
}
},
"required": [
"url",
"preview_identifier"
],
"additionalProperties": false,
"title": "ThemePreviewResult",
"$schema": "http://json-schema.org/draft-07/schema#"
}
```
```

## `shopify theme profile`
Expand Down
4 changes: 3 additions & 1 deletion packages/cli/oclif.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -10805,7 +10805,7 @@
"args": {
},
"customPluginName": "@shopify/theme",
"description": "Applies a JSON overrides file to a theme and creates or updates a preview. This lets you quickly preview changes.\n\n The command returns a preview URL and a preview identifier. You can reuse the preview identifier with `--preview-id` to update an existing preview instead of creating a new one.",
"description": "Applies a JSON overrides file to a theme and creates or updates a preview. This lets you quickly preview changes.\n\n The command returns a preview URL and a preview identifier. You can reuse the preview identifier with `--preview-id` to update an existing preview instead of creating a new one.\n\nUse `--json-schema` to print the result, error, and event schemas.\n\nOutput from `--json` conforms to the `ThemePreviewResult` schema.\n\n```json\n{\n \"type\": \"object\",\n \"properties\": {\n \"url\": {\n \"type\": \"string\"\n },\n \"preview_identifier\": {\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"url\",\n \"preview_identifier\"\n ],\n \"additionalProperties\": false,\n \"title\": \"ThemePreviewResult\",\n \"$schema\": \"http://json-schema.org/draft-07/schema#\"\n}\n```",
"descriptionWithMarkdown": "Applies a JSON overrides file to a theme and creates or updates a preview. This lets you quickly preview changes.\n\n The command returns a preview URL and a preview identifier. You can reuse the preview identifier with `--preview-id` to update an existing preview instead of creating a new one.",
"enableJsonFlag": false,
"flags": {
Expand All @@ -10828,8 +10828,10 @@
},
"json": {
"allowNo": false,
"char": "j",
"description": "Output the preview URL and identifier as JSON.",
"env": "SHOPIFY_FLAG_JSON",
"hidden": false,
"name": "json",
"type": "boolean"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ const commandExceptions = [
'packages/theme/src/cli/commands/theme/init.ts',
'packages/theme/src/cli/commands/theme/metafields/pull.ts',
'packages/theme/src/cli/commands/theme/package.ts',
'packages/theme/src/cli/commands/theme/preview.ts',
'packages/theme/src/cli/commands/theme/profile.ts',

// Streaming commands without a single finite result.
Expand Down
89 changes: 75 additions & 14 deletions packages/theme/src/cli/commands/theme/preview.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
import Preview from './preview.js'
import {themePreviewJsonOutputSchema} from '../../services/dev-override/types.js'
import {devWithOverrideFile} from '../../services/dev-override.js'
import {findOrSelectTheme} from '../../utilities/theme-selector.js'
import {ensureThemeStore} from '../../utilities/theme-store.js'
import {openURL} from '@shopify/cli-kit/node/system'
import {runWithCommandEventsForCommand} from '@shopify/cli-kit/node/command-events'
import {renderSuccess} from '@shopify/cli-kit/node/ui'
import {buildTheme} from '@shopify/cli-kit/node/themes/factories'
import {recordEvent} from '@shopify/cli-kit/node/analytics'
import {ensureAuthenticatedThemes} from '@shopify/cli-kit/node/session'
import {withCapturedStandardStreams} from '@shopify/cli-kit/node/testing/output'
import {Config} from '@oclif/core'
import {describe, vi, expect, test, beforeEach} from 'vitest'

vi.mock('@shopify/cli-kit/node/ui')
vi.mock('@shopify/cli-kit/node/system', async (importOriginal) => ({
...(await importOriginal<typeof import('@shopify/cli-kit/node/system')>()),
openURL: vi.fn(),
}))
vi.mock('@shopify/cli-kit/node/session')
vi.mock('@shopify/cli-kit/node/analytics', () => ({
recordEvent: vi.fn(),
Expand All @@ -24,6 +34,8 @@ vi.mock('../../utilities/theme-store.js')

const CommandConfig = new Config({root: __dirname})

const result = {url: 'https://abc123.shopifypreview.com', preview_identifier: 'abc123'}

const adminSession = {token: 'test-token', storeFqdn: 'test-store.myshopify.com'}
const namedTheme = buildTheme({id: 2, name: 'My Theme', role: 'unpublished'})!

Expand All @@ -38,7 +50,7 @@ describe('Preview', () => {
vi.mocked(ensureThemeStore).mockReturnValue('test-store.myshopify.com')
vi.mocked(ensureAuthenticatedThemes).mockResolvedValue(adminSession)
vi.mocked(findOrSelectTheme).mockResolvedValue(namedTheme)
vi.mocked(devWithOverrideFile).mockResolvedValue(undefined)
vi.mocked(devWithOverrideFile).mockResolvedValue(result)
})

test('calls devWithOverrideFile with minimum options passed into the command', async () => {
Expand All @@ -52,7 +64,6 @@ describe('Preview', () => {
adminSession,
overrideJson: '/path/to/overrides.json',
themeId: expectedTheme.id.toString(),
open: false,
}),
)
})
Expand All @@ -71,16 +82,18 @@ describe('Preview', () => {
)
})

test('passes --open to devWithOverrideFile when provided', async () => {
test('opens the resulting preview when requested', async () => {
vi.mocked(openURL).mockResolvedValue(true)
const expectedTheme = buildTheme({id: 5, name: 'Expected Theme', role: 'unpublished'})!
vi.mocked(findOrSelectTheme).mockResolvedValue(expectedTheme)

await run(['--overrides=/path/to/overrides.json', `--theme=${expectedTheme.id}`, '--open'])

expect(openURL).toHaveBeenCalledWith(result.url)

expect(devWithOverrideFile).toHaveBeenCalledWith(
expect.objectContaining({
themeId: expectedTheme.id.toString(),
open: true,
}),
)
})
Expand Down Expand Up @@ -108,17 +121,65 @@ describe('Preview', () => {
)
})

test('passes --json to devWithOverrideFile when provided', async () => {
const expectedTheme = buildTheme({id: 5, name: 'Expected Theme', role: 'unpublished'})!
vi.mocked(findOrSelectTheme).mockResolvedValue(expectedTheme)
test('writes the JSON result to stdout through the real presenter and writer', async () => {
await withCapturedStandardStreams(async ({stdout, stderr}) => {
await runWithCommandEventsForCommand(['--json'], () =>
run(['--overrides=/path/to/overrides.json', '--theme=2', '--json']),
)

expect(JSON.parse(stdout())).toEqual(result)
expect(stderr()).toBe('')
})
expect(renderSuccess).not.toHaveBeenCalled()
expect(openURL).not.toHaveBeenCalled()
expect(devWithOverrideFile).toHaveBeenCalledWith({
adminSession,
overrideJson: '/path/to/overrides.json',
themeId: '2',
previewIdentifier: undefined,
password: undefined,
})
})

await run(['--overrides=/path/to/overrides.json', `--theme=${expectedTheme.id}`, '--json'])
test('exposes its result schema in help', () => {
expect(Preview.jsonOutputSchema).toBe(themePreviewJsonOutputSchema)
expect(Preview.description).toContain('ThemePreviewResult')
expect(Preview.description).toContain('preview_identifier')
expect(Preview.flags.json.env).toBe('SHOPIFY_FLAG_JSON')
})

expect(devWithOverrideFile).toHaveBeenCalledWith(
expect.objectContaining({
themeId: expectedTheme.id.toString(),
json: true,
}),
)
test('propagates failures without printing a success result or opening a browser', async () => {
const error = new Error('Failed to parse override file')
vi.mocked(devWithOverrideFile).mockRejectedValue(error)

await withCapturedStandardStreams(async ({stdout, stderr}) => {
await expect(run(['--overrides=/path/to/overrides.json', '--theme=2', '--json', '--open'])).rejects.toBe(error)

expect(stdout()).toBe('')
expect(stderr()).toBe('')
})

expect(renderSuccess).not.toHaveBeenCalled()
expect(openURL).not.toHaveBeenCalled()
})

test('keeps browser failures nonfatal and sends a typed warning to stderr', async () => {
const error = new Error('Browser unavailable')
vi.mocked(openURL).mockRejectedValue(error)

await withCapturedStandardStreams(async ({stdout, stderr}) => {
await runWithCommandEventsForCommand(['--json'], () =>
run(['--overrides=/path/to/overrides.json', '--theme=2', '--json', '--open']),
)

const events = stderr()
.trim()
.split('\n')
.map((line) => JSON.parse(line))
expect(events).toMatchObject([
{type: 'diagnostic', level: 'warning', message: `Failed to open theme preview.\n${error.stack}`},
])
expect(JSON.parse(stdout())).toEqual(result)
})
})
})
20 changes: 16 additions & 4 deletions packages/theme/src/cli/commands/theme/preview.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
import {themeFlags} from '../../flags.js'
import ThemeCommand, {RequiredFlags} from '../../utilities/theme-command.js'
import {devWithOverrideFile} from '../../services/dev-override.js'
import {renderThemePreviewResult, renderThemePreviewOpenError} from '../../services/dev-override/result.js'
import {themePreviewJsonOutputSchema} from '../../services/dev-override/types.js'
import {findOrSelectTheme} from '../../utilities/theme-selector.js'
import {Flags} from '@oclif/core'
import {globalFlags} from '@shopify/cli-kit/node/cli'
import {globalFlags, jsonFlag} from '@shopify/cli-kit/node/cli'
import {openURL} from '@shopify/cli-kit/node/system'
import {AdminSession} from '@shopify/cli-kit/node/session'
import {InferredFlags} from '@oclif/core/interfaces'

type PreviewFlags = InferredFlags<typeof Preview.flags>

export default class Preview extends ThemeCommand {
static get jsonOutputSchema() {
return themePreviewJsonOutputSchema
}

static summary = 'Applies JSON overrides to a theme and returns a preview URL.'

static descriptionWithMarkdown = `Applies a JSON overrides file to a theme and creates or updates a preview. This lets you quickly preview changes.
Expand All @@ -20,6 +27,7 @@ export default class Preview extends ThemeCommand {

static flags = {
...globalFlags,
...jsonFlag,
...themeFlags,
theme: Flags.string({
char: 't',
Expand All @@ -42,6 +50,7 @@ export default class Preview extends ThemeCommand {
default: false,
}),
json: Flags.boolean({
...jsonFlag.json,
description: 'Output the preview URL and identifier as JSON.',
env: 'SHOPIFY_FLAG_JSON',
default: false,
Expand All @@ -52,14 +61,17 @@ export default class Preview extends ThemeCommand {

async command(flags: PreviewFlags, adminSession: AdminSession) {
const theme = await findOrSelectTheme(adminSession, {filter: {theme: flags.theme}})
await devWithOverrideFile({
const result = await devWithOverrideFile({
adminSession,
overrideJson: flags.overrides,
themeId: theme.id.toString(),
previewIdentifier: flags['preview-id'],
open: flags.open,
password: flags.password,
json: flags.json,
})
const format = flags.json ? 'json' : 'text'
renderThemePreviewResult(result, format, Boolean(flags['preview-id']))
if (flags.open) {
openURL(result.url).catch((error: Error) => renderThemePreviewOpenError(error, format))
}
}
}
Loading
Loading