diff --git a/.changeset/new-sails-relate.md b/.changeset/new-sails-relate.md new file mode 100644 index 0000000000..845b3d995f --- /dev/null +++ b/.changeset/new-sails-relate.md @@ -0,0 +1,5 @@ +--- +'@shopify/theme': minor +--- + +Add --json flag to theme preview to configure a json output diff --git a/docs-shopify.dev/commands/interfaces/theme-preview.interface.ts b/docs-shopify.dev/commands/interfaces/theme-preview.interface.ts index 312237c01e..5d443d9370 100644 --- a/docs-shopify.dev/commands/interfaces/theme-preview.interface.ts +++ b/docs-shopify.dev/commands/interfaces/theme-preview.interface.ts @@ -6,6 +6,12 @@ export interface themepreview { */ '-e, --environment '?: string + /** + * Output the preview URL and identifier as JSON. + * @environment SHOPIFY_FLAG_JSON + */ + '--json'?: '' + /** * Disable color output. * @environment SHOPIFY_FLAG_NO_COLOR diff --git a/docs-shopify.dev/generated/generated_docs_data.json b/docs-shopify.dev/generated/generated_docs_data.json index 81f7fcc91f..7fa689fa3a 100644 --- a/docs-shopify.dev/generated/generated_docs_data.json +++ b/docs-shopify.dev/generated/generated_docs_data.json @@ -7186,6 +7186,15 @@ "name": "themepreview", "description": "", "members": [ + { + "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", @@ -7275,7 +7284,7 @@ "environmentValue": "SHOPIFY_FLAG_THEME_ID" } ], - "value": "export interface themepreview {\n /**\n * The environment to apply to the current command.\n * @environment SHOPIFY_FLAG_ENVIRONMENT\n */\n '-e, --environment '?: string\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 ': 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 '?: 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 '?: 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 '?: 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 '?: string\n\n /**\n * Theme ID or name of the remote theme.\n * @environment SHOPIFY_FLAG_THEME_ID\n */\n '-t, --theme ': string\n\n /**\n * Increase the verbosity of the output.\n * @environment SHOPIFY_FLAG_VERBOSE\n */\n '--verbose'?: ''\n}" + "value": "export interface themepreview {\n /**\n * The environment to apply to the current command.\n * @environment SHOPIFY_FLAG_ENVIRONMENT\n */\n '-e, --environment '?: string\n\n /**\n * Output the preview URL and identifier as JSON.\n * @environment SHOPIFY_FLAG_JSON\n */\n '--json'?: ''\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 ': 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 '?: 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 '?: 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 '?: 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 '?: string\n\n /**\n * Theme ID or name of the remote theme.\n * @environment SHOPIFY_FLAG_THEME_ID\n */\n '-t, --theme ': string\n\n /**\n * Increase the verbosity of the output.\n * @environment SHOPIFY_FLAG_VERBOSE\n */\n '--verbose'?: ''\n}" } } } diff --git a/packages/cli/README.md b/packages/cli/README.md index 4c967d354d..6de1f60163 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -2549,14 +2549,15 @@ Applies JSON overrides to a theme and returns a preview URL. ``` USAGE - $ shopify theme preview --overrides -t [-e ...] [--no-color] [--open] [--password ] - [--path ] [--preview-id ] [-s ] [--verbose] + $ shopify theme preview --overrides -t [-e ...] [--json] [--no-color] [--open] [--password + ] [--path ] [--preview-id ] [-s ] [--verbose] FLAGS -e, --environment=... [env: SHOPIFY_FLAG_ENVIRONMENT] The environment to apply to the current command. -s, --store= [env: SHOPIFY_FLAG_STORE] Store URL. It can be the store prefix (example) or the full myshopify.com URL (example.myshopify.com, https://example.myshopify.com). -t, --theme= (required) [env: SHOPIFY_FLAG_THEME_ID] Theme ID or name of the remote theme. + --json [env: SHOPIFY_FLAG_JSON] Output the preview URL and identifier as JSON. --no-color [env: SHOPIFY_FLAG_NO_COLOR] Disable color output. --open [env: SHOPIFY_FLAG_OPEN] Automatically launch the theme preview in your default web browser. diff --git a/packages/cli/oclif.manifest.json b/packages/cli/oclif.manifest.json index 906abac5f3..284c157a45 100644 --- a/packages/cli/oclif.manifest.json +++ b/packages/cli/oclif.manifest.json @@ -6953,6 +6953,13 @@ "name": "environment", "type": "option" }, + "json": { + "allowNo": false, + "description": "Output the preview URL and identifier as JSON.", + "env": "SHOPIFY_FLAG_JSON", + "name": "json", + "type": "boolean" + }, "no-color": { "allowNo": false, "description": "Disable color output.", diff --git a/packages/theme/src/cli/commands/theme/preview.test.ts b/packages/theme/src/cli/commands/theme/preview.test.ts index f98bf6dde5..c0076dcef9 100644 --- a/packages/theme/src/cli/commands/theme/preview.test.ts +++ b/packages/theme/src/cli/commands/theme/preview.test.ts @@ -107,4 +107,18 @@ 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) + + await run(['--overrides=/path/to/overrides.json', `--theme=${expectedTheme.id}`, '--json']) + + expect(devWithOverrideFile).toHaveBeenCalledWith( + expect.objectContaining({ + themeId: expectedTheme.id.toString(), + json: true, + }), + ) + }) }) diff --git a/packages/theme/src/cli/commands/theme/preview.ts b/packages/theme/src/cli/commands/theme/preview.ts index 81d720ba88..f0cacecfb2 100644 --- a/packages/theme/src/cli/commands/theme/preview.ts +++ b/packages/theme/src/cli/commands/theme/preview.ts @@ -42,6 +42,11 @@ export default class Preview extends ThemeCommand { env: 'SHOPIFY_FLAG_OPEN', default: false, }), + json: Flags.boolean({ + description: 'Output the preview URL and identifier as JSON.', + env: 'SHOPIFY_FLAG_JSON', + default: false, + }), } static multiEnvironmentsFlags: RequiredFlags = null @@ -55,6 +60,7 @@ export default class Preview extends ThemeCommand { previewIdentifier: flags['preview-id'], open: flags.open, password: flags.password, + json: flags.json, }) } } diff --git a/packages/theme/src/cli/services/dev-override.test.ts b/packages/theme/src/cli/services/dev-override.test.ts index f46c5802fb..2a1b374248 100644 --- a/packages/theme/src/cli/services/dev-override.test.ts +++ b/packages/theme/src/cli/services/dev-override.test.ts @@ -4,6 +4,7 @@ import {fetchDevServerSession} from '../utilities/theme-environment/dev-server-s import {createThemePreview, updateThemePreview} from '../utilities/theme-previews/preview.js' import {describe, expect, test, vi} from 'vitest' import {renderSuccess} from '@shopify/cli-kit/node/ui' +import {collectedLogs, clearCollectedLogs} from '@shopify/cli-kit/node/output' import {fileExistsSync, readFile} from '@shopify/cli-kit/node/fs' vi.mock('../utilities/theme-environment/dev-server-session.js') @@ -84,6 +85,7 @@ describe('devWithOverrideFile', () => { themeId: expectedThemeId, previewIdentifier: expectedPreviewId, open: false, + json: false, }) // Then @@ -120,6 +122,7 @@ describe('devWithOverrideFile', () => { overrideJson: '/bad.json', themeId: '123', open: false, + json: false, }).catch((err) => err) expect(error.message).toBe('Failed to parse override file: /bad.json') expect(error.tryMessage).toMatch(/not valid json/i) @@ -166,10 +169,43 @@ describe('devWithOverrideFile', () => { overrideJson: '/overrides.json', themeId: '789', open: false, + json: false, password: 'shptka_abc123', }) // Then expect(fetchDevServerSession).toHaveBeenCalledWith('789', adminSession, 'shptka_abc123') }) + + test('outputs JSON when json flag is true', async () => { + // Given + vi.mocked(fileExistsSync).mockReturnValue(true) + vi.mocked(readFile).mockResolvedValue(Buffer.from(JSON.stringify({templates: {}}))) + vi.mocked(fetchDevServerSession).mockResolvedValue(mockSession) + vi.mocked(createThemePreview).mockResolvedValue({url: expectedPreviewUrl, preview_identifier: expectedPreviewId}) + clearCollectedLogs() + + // When + await devWithOverrideFile({adminSession, overrideJson: '/overrides.json', themeId: '789', open: false, json: true}) + + // Then + const expectedJson = JSON.stringify({url: expectedPreviewUrl, preview_identifier: expectedPreviewId}) + expect(collectedLogs.info).toContainEqual(expectedJson) + expect(renderSuccess).not.toHaveBeenCalled() + }) + + test('renders success body by default when json flag is omitted', async () => { + // Given + vi.mocked(fileExistsSync).mockReturnValue(true) + vi.mocked(readFile).mockResolvedValue(Buffer.from(JSON.stringify({templates: {}}))) + vi.mocked(fetchDevServerSession).mockResolvedValue(mockSession) + vi.mocked(createThemePreview).mockResolvedValue({url: expectedPreviewUrl, preview_identifier: expectedPreviewId}) + clearCollectedLogs() + + // When + await devWithOverrideFile({adminSession, overrideJson: '/overrides.json', themeId: '789', open: false}) + + // Then + expect(renderSuccess).toHaveBeenCalled() + }) }) diff --git a/packages/theme/src/cli/services/dev-override.ts b/packages/theme/src/cli/services/dev-override.ts index b070b96658..be29c90c22 100644 --- a/packages/theme/src/cli/services/dev-override.ts +++ b/packages/theme/src/cli/services/dev-override.ts @@ -2,6 +2,7 @@ import {openURLSafely} from './dev.js' import {fetchDevServerSession} from '../utilities/theme-environment/dev-server-session.js' import {createThemePreview, updateThemePreview} from '../utilities/theme-previews/preview.js' import {renderSuccess} from '@shopify/cli-kit/node/ui' +import {outputInfo} from '@shopify/cli-kit/node/output' import {AdminSession} from '@shopify/cli-kit/node/session' import {AbortError} from '@shopify/cli-kit/node/error' import {readFile, fileExistsSync} from '@shopify/cli-kit/node/fs' @@ -17,6 +18,7 @@ interface DevWithOverrideFileOptions { previewIdentifier?: string open: boolean password?: string + json?: boolean } /** @@ -53,16 +55,20 @@ export async function devWithOverrideFile(options: DevWithOverrideFileOptions) { themeId: options.themeId, }) - renderSuccess({ - body: [ - { - list: { - title: options.previewIdentifier ? 'Preview updated' : 'Preview is ready', - items: [{link: {url: preview.url}}, `Preview ID: ${preview.preview_identifier}`], + if (options.json) { + outputInfo(JSON.stringify({url: preview.url, preview_identifier: preview.preview_identifier})) + } else { + renderSuccess({ + body: [ + { + list: { + title: options.previewIdentifier ? 'Preview updated' : 'Preview is ready', + items: [{link: {url: preview.url}}, `Preview ID: ${preview.preview_identifier}`], + }, }, - }, - ], - }) + ], + }) + } if (options.open) { openURLSafely(preview.url, 'theme preview')