From ecf152f2eb9b8d62f05ce6ef3e0cc2558d304967 Mon Sep 17 00:00:00 2001 From: Gonzalo Riestra Date: Thu, 24 Sep 2026 11:28:27 +0200 Subject: [PATCH 1/6] Add typed JSON output to theme share --- packages/cli/oclif.manifest.json | 11 +- .../rules/json-output-command-exceptions.js | 1 - .../src/cli/commands/theme/share.test.ts | 119 ++++++++++++++++++ .../theme/src/cli/commands/theme/share.ts | 23 +++- .../theme/transfer-environments.test.ts | 7 +- .../theme/src/cli/services/share/result.ts | 21 ++++ .../theme/src/cli/services/share/types.ts | 10 ++ 7 files changed, 186 insertions(+), 6 deletions(-) create mode 100644 packages/theme/src/cli/commands/theme/share.test.ts create mode 100644 packages/theme/src/cli/services/share/result.ts create mode 100644 packages/theme/src/cli/services/share/types.ts diff --git a/packages/cli/oclif.manifest.json b/packages/cli/oclif.manifest.json index 68428dc023d..08611baa9aa 100644 --- a/packages/cli/oclif.manifest.json +++ b/packages/cli/oclif.manifest.json @@ -13062,7 +13062,7 @@ "args": { }, "customPluginName": "@shopify/theme", - "description": "Uploads your theme as a new, unpublished theme in your theme library. The theme is given a randomized name.\n\n This command returns a \"preview link\" (https://help.shopify.com/manual/online-store/themes/adding-themes#share-a-theme-preview-with-others) that you can share with others.", + "description": "Uploads your theme as a new, unpublished theme in your theme library. The theme is given a randomized name.\n\n This command returns a \"preview link\" (https://help.shopify.com/manual/online-store/themes/adding-themes#share-a-theme-preview-with-others) that you can share with others.\n\nUse `--json-schema` to print the result, error, and event schemas.\n\nOutput from `--json` conforms to the `ThemeShareResult` schema.\n\n```json\n{\n \"anyOf\": [\n {\n \"type\": \"object\",\n \"properties\": {\n \"status\": {\n \"type\": \"string\",\n \"enum\": [\n \"success\",\n \"failed\"\n ]\n },\n \"environment\": {\n \"type\": \"string\"\n },\n \"theme\": {\n \"$ref\": \"#/definitions/ThemePushTheme\"\n }\n },\n \"required\": [\n \"status\",\n \"theme\"\n ],\n \"additionalProperties\": false\n },\n {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"status\": {\n \"$ref\": \"#/definitions/ThemeShareResult/anyOf/0/properties/status\"\n },\n \"environment\": {\n \"type\": \"string\"\n },\n \"theme\": {\n \"$ref\": \"#/definitions/ThemePushTheme\"\n }\n },\n \"required\": [\n \"status\",\n \"environment\",\n \"theme\"\n ],\n \"additionalProperties\": false\n }\n }\n ],\n \"title\": \"ThemeShareResult\",\n \"definitions\": {\n \"ThemePushTheme\": {\n \"type\": \"object\",\n \"properties\": {\n \"id\": {\n \"type\": \"number\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"role\": {\n \"type\": \"string\"\n },\n \"shop\": {\n \"type\": \"string\"\n },\n \"editor_url\": {\n \"type\": \"string\"\n },\n \"preview_url\": {\n \"type\": \"string\"\n },\n \"warning\": {\n \"type\": \"string\"\n },\n \"errors\": {\n \"type\": \"object\",\n \"additionalProperties\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n }\n },\n \"required\": [\n \"id\",\n \"name\",\n \"role\",\n \"shop\",\n \"editor_url\",\n \"preview_url\"\n ],\n \"additionalProperties\": false\n }\n },\n \"$schema\": \"http://json-schema.org/draft-07/schema#\"\n}\n```", "descriptionWithMarkdown": "Uploads your theme as a new, unpublished theme in your theme library. The theme is given a randomized name.\n\n This command returns a [preview link](https://help.shopify.com/manual/online-store/themes/adding-themes#share-a-theme-preview-with-others) that you can share with others.", "enableJsonFlag": false, "flags": { @@ -13092,6 +13092,15 @@ "name": "force", "type": "boolean" }, + "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" + }, "json-schema": { "allowNo": false, "description": "Print the command's JSON schemas.", diff --git a/packages/eslint-plugin-cli/rules/json-output-command-exceptions.js b/packages/eslint-plugin-cli/rules/json-output-command-exceptions.js index 0eea0481aee..82c9f33b6b3 100644 --- a/packages/eslint-plugin-cli/rules/json-output-command-exceptions.js +++ b/packages/eslint-plugin-cli/rules/json-output-command-exceptions.js @@ -11,7 +11,6 @@ const commandExceptions = [ 'packages/theme/src/cli/commands/theme/package.ts', 'packages/theme/src/cli/commands/theme/preview.ts', 'packages/theme/src/cli/commands/theme/profile.ts', - 'packages/theme/src/cli/commands/theme/share.ts', // App Security commands, launched before adopting typed JSON output. Remove entries as they adopt it. 'packages/app/src/cli/commands/app/security/check.ts', diff --git a/packages/theme/src/cli/commands/theme/share.test.ts b/packages/theme/src/cli/commands/theme/share.test.ts new file mode 100644 index 00000000000..1526a9263ee --- /dev/null +++ b/packages/theme/src/cli/commands/theme/share.test.ts @@ -0,0 +1,119 @@ +import Share from './share.js' +import {executeThemePush} from '../../services/push.js' +import {themeShareJsonOutputSchema} from '../../services/share/types.js' +import {Config} from '@oclif/core' +import {getRandomName} from '@shopify/cli-kit/common/string' +import {mockAndCaptureOutput, withCapturedStandardStreams} from '@shopify/cli-kit/node/testing/output' +import {describe, expect, test, vi} from 'vitest' + +vi.mock('../../services/push.js') +vi.mock('@shopify/cli-kit/common/string', async (importOriginal) => ({ + ...(await importOriginal()), + getRandomName: vi.fn(), +})) + +const session = {storeFqdn: 'test.myshopify.com', token: 'token'} + +function result() { + return { + theme: { + id: 1, + name: 'Creative Theme', + role: 'unpublished', + shop: session.storeFqdn, + editor_url: 'editor', + preview_url: 'preview', + }, + published: false, + hasErrors: false, + errors: {}, + } +} + +describe('theme share', () => { + test('exposes JSON and schema flags and documents its own result', () => { + expect(Share.jsonOutputSchema).toBe(themeShareJsonOutputSchema) + expect(Share.description).toContain('ThemeShareResult') + expect(Share.flags.json).toBeDefined() + expect(Share.baseFlags).toHaveProperty('json-schema') + }) + + test('creates an unpublished theme with a random name and forwards the listing', async () => { + vi.mocked(getRandomName).mockReturnValue('Creative Theme') + vi.mocked(executeThemePush).mockResolvedValue(result()) + const command = new Share([], new Config({root: '.'})) + await withCapturedStandardStreams(async ({stdout, stderr}) => { + await command.command({json: true, path: '/theme', listing: 'preset'} as never, session, false) + + expect(getRandomName).toHaveBeenCalledWith('creative') + expect(executeThemePush).toHaveBeenCalledWith( + expect.objectContaining({unpublished: true, theme: 'Creative Theme', path: '/theme', listing: 'preset'}), + session, + false, + undefined, + ) + expect(stdout()).toBe(`${themeShareJsonOutputSchema.encode({status: 'success', theme: result().theme})}\n`) + expect(stderr()).toBe('') + }) + }) + + test('keeps upload errors and environment in JSON', async () => { + vi.mocked(executeThemePush).mockResolvedValue({ + ...result(), + environment: 'staging', + hasErrors: true, + errors: {'assets/theme.css': ['bad CSS']}, + }) + const command = new Share([], new Config({root: '.'})) + await withCapturedStandardStreams(async ({stdout}) => { + await command.command({json: true, environment: ['staging']} as never, session, false) + + expect(JSON.parse(stdout())).toEqual({ + status: 'failed', + environment: 'staging', + theme: { + ...result().theme, + warning: "[staging] The theme 'Creative Theme' was pushed with errors", + errors: {'assets/theme.css': ['bad CSS']}, + }, + }) + }) + }) + + test('preserves the push success banner in text mode', async () => { + vi.mocked(executeThemePush).mockResolvedValue(result()) + const output = mockAndCaptureOutput() + output.clear() + await new Share([], new Config({root: '.'})).command({} as never, session, false) + expect(output.info()).toContain("The theme 'Creative Theme' (#1) was pushed successfully.") + expect(output.info()).toContain('View your theme') + expect(output.info()).toContain('Customize your theme at the theme editor') + }) + + test('does not emit a result when cancelled', async () => { + vi.mocked(executeThemePush).mockResolvedValue(undefined) + await withCapturedStandardStreams(async ({stdout}) => { + await new Share([], new Config({root: '.'})).command({json: true} as never, session, false) + + expect(stdout()).toBe('') + }) + }) + + test('propagates theme creation failures through the shared error path', async () => { + const failure = new Error('theme creation failed') + vi.mocked(executeThemePush).mockRejectedValue(failure) + await withCapturedStandardStreams(async ({stdout}) => { + await expect(new Share([], new Config({root: '.'})).command({json: true} as never, session, false)).rejects.toBe( + failure, + ) + + expect(stdout()).toBe('') + }) + }) + + test.each([{id: '1'}, {role: null}, {errors: {file: 'invalid'}}])('rejects malformed theme data %j', (fields) => { + expect(() => + themeShareJsonOutputSchema.validate({status: 'success', theme: {...result().theme, ...fields}}), + ).toThrow() + }) +}) diff --git a/packages/theme/src/cli/commands/theme/share.ts b/packages/theme/src/cli/commands/theme/share.ts index 3700dfa313e..f97d99b4739 100644 --- a/packages/theme/src/cli/commands/theme/share.ts +++ b/packages/theme/src/cli/commands/theme/share.ts @@ -1,8 +1,10 @@ +import {themeShareJsonOutputSchema} from '../../services/share/types.js' +import {renderThemeShareResult, renderThemeShareEnvironmentResults} from '../../services/share/result.js' import {themeFlags} from '../../flags.js' import ThemeCommand from '../../utilities/theme-command.js' -import {push, PushFlags} from '../../services/push.js' +import {executeThemePush, PushFlags} from '../../services/push.js' import {Flags} from '@oclif/core' -import {globalFlags} from '@shopify/cli-kit/node/cli' +import {globalFlags, jsonFlag} from '@shopify/cli-kit/node/cli' import {getRandomName} from '@shopify/cli-kit/common/string' import {recordTiming} from '@shopify/cli-kit/node/analytics' import {InferredFlags} from '@oclif/core/interfaces' @@ -13,6 +15,10 @@ import {Writable} from 'stream' type ShareFlags = InferredFlags export default class Share extends ThemeCommand { + static get jsonOutputSchema() { + return themeShareJsonOutputSchema + } + static summary = 'Creates a shareable, unpublished, and new theme on your theme library with a randomized name.' static descriptionWithMarkdown = `Uploads your theme as a new, unpublished theme in your theme library. The theme is given a randomized name. @@ -23,6 +29,7 @@ export default class Share extends ThemeCommand { static flags = { ...globalFlags, + ...jsonFlag, ...themeFlags, force: Flags.boolean({ hidden: true, @@ -59,7 +66,17 @@ export default class Share extends ThemeCommand { } recordTiming('theme-command:share') - await push(pushFlags, adminSession, multiEnvironment, context) + const result = await executeThemePush(pushFlags, adminSession, multiEnvironment, context) + if (result && !(flags.json && multiEnvironment)) renderThemeShareResult(result, flags.json ? 'json' : 'text') recordTiming('theme-command:share') + return result + } + + protected collectsEnvironmentResults(flags: {json?: boolean}): boolean { + return Boolean(flags.json) + } + + protected renderEnvironmentResults(results: {environment: string; result: unknown}[]): void { + renderThemeShareEnvironmentResults(results) } } diff --git a/packages/theme/src/cli/commands/theme/transfer-environments.test.ts b/packages/theme/src/cli/commands/theme/transfer-environments.test.ts index a12f8679dfc..38bab40c6e8 100644 --- a/packages/theme/src/cli/commands/theme/transfer-environments.test.ts +++ b/packages/theme/src/cli/commands/theme/transfer-environments.test.ts @@ -1,5 +1,6 @@ import Push from './push.js' import Pull from './pull.js' +import Share from './share.js' import {executeThemePush} from '../../services/push.js' import {executeThemePull} from '../../services/pull.js' import {withCapturedStandardStreams} from '@shopify/cli-kit/node/testing/output' @@ -24,7 +25,11 @@ class TestPull extends Pull { public parse = vi.fn() } -describe.each([TestPush, TestPull])('%s', (Command) => { +class TestShare extends Share { + public parse = vi.fn() +} + +describe.each([TestPush, TestPull, TestShare])('%s', (Command) => { test.each(['none', 'partial', 'total', 'cancelled'] as const)( 'retains all environments in request order with %s failures', async (failures) => { diff --git a/packages/theme/src/cli/services/share/result.ts b/packages/theme/src/cli/services/share/result.ts new file mode 100644 index 00000000000..af3788d6f17 --- /dev/null +++ b/packages/theme/src/cli/services/share/result.ts @@ -0,0 +1,21 @@ +import {themeShareJsonOutputSchema, type ThemeShareResult} from './types.js' +import {themePushJsonResult, renderThemePushResult} from '../push/result.js' +import {themePushResultSchema, type ThemePushResult} from '../push/types.js' +import {outputResult} from '@shopify/cli-kit/node/output' + +export function renderThemeShareResult(result: ThemePushResult, format: 'text' | 'json'): void { + if (format === 'json') { + outputResult(themeShareJsonOutputSchema.encode(themePushJsonResult(result))) + } else { + renderThemePushResult(result, 'text') + } +} + +export function renderThemeShareEnvironmentResults(results: {environment: string; result: unknown}[]): void { + const output: ThemeShareResult = results.flatMap(({environment, result}) => + result === undefined + ? [] + : [{...themePushJsonResult({...themePushResultSchema.parse(result), environment}), environment}], + ) + outputResult(themeShareJsonOutputSchema.encode(output)) +} diff --git a/packages/theme/src/cli/services/share/types.ts b/packages/theme/src/cli/services/share/types.ts new file mode 100644 index 00000000000..d568e983d17 --- /dev/null +++ b/packages/theme/src/cli/services/share/types.ts @@ -0,0 +1,10 @@ +import {themePushJsonOutputSchema} from '../push/types.js' +import {defineJsonOutputSchema, type InferJsonOutputSchema} from '@shopify/cli-kit/node/json-output-schema' + +export const themeShareJsonOutputSchema = defineJsonOutputSchema({ + name: 'ThemeShareResult', + schema: themePushJsonOutputSchema.schema, + definitions: themePushJsonOutputSchema.definitions, +}) + +export type ThemeShareResult = InferJsonOutputSchema From 7ac606f5c9e154d90dc827d10f4262ea7bae396c Mon Sep 17 00:00:00 2001 From: Gonzalo Riestra Date: Tue, 6 Oct 2026 14:14:05 +0200 Subject: [PATCH 2/6] Reuse the canonical upload JSON projection in theme share --- packages/cli/README.md | 347 +++++++++++++++++- packages/cli/oclif.manifest.json | 2 +- .../src/cli/commands/theme/share.test.ts | 23 +- .../theme/src/cli/commands/theme/share.ts | 12 +- .../theme/src/cli/services/share/result.ts | 17 +- .../theme/src/cli/services/share/types.ts | 4 +- 6 files changed, 378 insertions(+), 27 deletions(-) diff --git a/packages/cli/README.md b/packages/cli/README.md index f31d095b06c..03bd2eb9dcc 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -16659,14 +16659,18 @@ Creates a shareable, unpublished, and new theme on your theme library with a ran ``` USAGE - $ shopify theme share [--auth-alias ] [-e ...] [--json-schema] [--listing ] [--no-color] - [--no-input] [--password ] [--path ] [-s ] [--verbose] + $ shopify theme share [--auth-alias ] [-e ...] [-j] [--json-schema] [--listing ] + [--no-color] [--no-input] [--password ] [--path ] [-s ] [--verbose] FLAGS -e, --environment=... The environment to apply to the current command. [env: SHOPIFY_FLAG_ENVIRONMENT] + -j, --json + Output the result as JSON. Automatically disables color output. + [env: SHOPIFY_FLAG_JSON] + -s, --store= Store URL. It can be the store prefix (example) or the full myshopify.com URL (example.myshopify.com, https://example.myshopify.com). @@ -16712,6 +16716,345 @@ DESCRIPTION This command returns a "preview link" (https://help.shopify.com/manual/online-store/themes/adding-themes#share-a-theme-preview-with-others) that you can share with others. + + Use `--json-schema` to print the result, error, and event schemas. + + Output from `--json` conforms to the `ThemeShareResult` schema. + + ```json + { + "anyOf": [ + { + "$ref": "#/definitions/ThemeEnvironment/anyOf/0/properties/result" + }, + { + "type": "object", + "properties": { + "environments": { + "type": "array", + "items": { + "$ref": "#/definitions/ThemeEnvironment" + } + } + }, + "required": [ + "environments" + ], + "additionalProperties": false + } + ], + "title": "ThemeShareResult", + "definitions": { + "ThemePushTheme": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^\\d+$", + "description": "The decimal Online Store theme ID, not a Shopify GID." + }, + "name": { + "type": "string" + }, + "role": { + "type": "string", + "description": "The upstream theme role; known values include main, unpublished, and development." + }, + "storeDomain": { + "anyOf": [ + { + "type": "string", + "pattern": "^[a-z0-9][a-z0-9-]*\\.myshopify\\.com$" + }, + { + "type": "null" + } + ] + }, + "previewUrl": { + "anyOf": [ + { + "type": "string", + "format": "uri" + }, + { + "type": "null" + } + ] + }, + "editorUrl": { + "anyOf": [ + { + "type": "string", + "format": "uri" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "id", + "name", + "role", + "storeDomain", + "previewUrl", + "editorUrl" + ], + "additionalProperties": false + }, + "ThemeEnvironment": { + "anyOf": [ + { + "type": "object", + "properties": { + "environment": { + "type": "string" + }, + "result": { + "anyOf": [ + { + "anyOf": [ + { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "success", + "partial" + ] + }, + "changed": { + "type": "boolean" + }, + "theme": { + "$ref": "#/definitions/ThemePushTheme" + }, + "issues": { + "type": "array", + "items": { + "type": "object", + "properties": { + "filePath": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "filePath", + "message" + ], + "additionalProperties": false + } + } + }, + "required": [ + "status", + "changed", + "theme", + "issues" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "status": { + "type": "string", + "const": "skipped" + }, + "reason": { + "type": "string", + "const": "unsafe-directory" + } + }, + "required": [ + "status", + "reason" + ], + "additionalProperties": false + } + ] + }, + { + "type": "object", + "properties": { + "status": { + "type": "string", + "const": "cancelled" + } + }, + "required": [ + "status" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "environment", + "result" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "environment": { + "type": "string" + }, + "error": { + "anyOf": [ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "abort" + }, + "message": { + "type": "string" + }, + "tryMessage": { + "type": "string" + }, + "nextSteps": { + "type": "array", + "items": { + "type": "string" + } + }, + "customSections": { + "type": "array", + "items": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "body": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "string" + } + } + } + ] + } + }, + "required": [ + "body" + ], + "additionalProperties": false + } + }, + "details": {} + }, + "required": [ + "type", + "message" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "bug" + }, + "message": { + "$ref": "#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/message" + }, + "tryMessage": { + "$ref": "#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/tryMessage" + }, + "nextSteps": { + "$ref": "#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/nextSteps" + }, + "customSections": { + "$ref": "#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/customSections" + }, + "details": { + "$ref": "#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/details" + }, + "stack": { + "type": "string" + } + }, + "required": [ + "type", + "message" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "external" + }, + "message": { + "$ref": "#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/message" + }, + "tryMessage": { + "$ref": "#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/tryMessage" + }, + "nextSteps": { + "$ref": "#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/nextSteps" + }, + "customSections": { + "$ref": "#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/customSections" + }, + "details": { + "$ref": "#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/details" + }, + "command": { + "type": "string" + }, + "args": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "type", + "message", + "command", + "args" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "environment", + "error" + ], + "additionalProperties": false + } + ] + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + } + ``` ``` ## `shopify upgrade` diff --git a/packages/cli/oclif.manifest.json b/packages/cli/oclif.manifest.json index 08611baa9aa..72dea442480 100644 --- a/packages/cli/oclif.manifest.json +++ b/packages/cli/oclif.manifest.json @@ -13062,7 +13062,7 @@ "args": { }, "customPluginName": "@shopify/theme", - "description": "Uploads your theme as a new, unpublished theme in your theme library. The theme is given a randomized name.\n\n This command returns a \"preview link\" (https://help.shopify.com/manual/online-store/themes/adding-themes#share-a-theme-preview-with-others) that you can share with others.\n\nUse `--json-schema` to print the result, error, and event schemas.\n\nOutput from `--json` conforms to the `ThemeShareResult` schema.\n\n```json\n{\n \"anyOf\": [\n {\n \"type\": \"object\",\n \"properties\": {\n \"status\": {\n \"type\": \"string\",\n \"enum\": [\n \"success\",\n \"failed\"\n ]\n },\n \"environment\": {\n \"type\": \"string\"\n },\n \"theme\": {\n \"$ref\": \"#/definitions/ThemePushTheme\"\n }\n },\n \"required\": [\n \"status\",\n \"theme\"\n ],\n \"additionalProperties\": false\n },\n {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"status\": {\n \"$ref\": \"#/definitions/ThemeShareResult/anyOf/0/properties/status\"\n },\n \"environment\": {\n \"type\": \"string\"\n },\n \"theme\": {\n \"$ref\": \"#/definitions/ThemePushTheme\"\n }\n },\n \"required\": [\n \"status\",\n \"environment\",\n \"theme\"\n ],\n \"additionalProperties\": false\n }\n }\n ],\n \"title\": \"ThemeShareResult\",\n \"definitions\": {\n \"ThemePushTheme\": {\n \"type\": \"object\",\n \"properties\": {\n \"id\": {\n \"type\": \"number\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"role\": {\n \"type\": \"string\"\n },\n \"shop\": {\n \"type\": \"string\"\n },\n \"editor_url\": {\n \"type\": \"string\"\n },\n \"preview_url\": {\n \"type\": \"string\"\n },\n \"warning\": {\n \"type\": \"string\"\n },\n \"errors\": {\n \"type\": \"object\",\n \"additionalProperties\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n }\n },\n \"required\": [\n \"id\",\n \"name\",\n \"role\",\n \"shop\",\n \"editor_url\",\n \"preview_url\"\n ],\n \"additionalProperties\": false\n }\n },\n \"$schema\": \"http://json-schema.org/draft-07/schema#\"\n}\n```", + "description": "Uploads your theme as a new, unpublished theme in your theme library. The theme is given a randomized name.\n\n This command returns a \"preview link\" (https://help.shopify.com/manual/online-store/themes/adding-themes#share-a-theme-preview-with-others) that you can share with others.\n\nUse `--json-schema` to print the result, error, and event schemas.\n\nOutput from `--json` conforms to the `ThemeShareResult` schema.\n\n```json\n{\n \"anyOf\": [\n {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/0/properties/result\"\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"environments\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/definitions/ThemeEnvironment\"\n }\n }\n },\n \"required\": [\n \"environments\"\n ],\n \"additionalProperties\": false\n }\n ],\n \"title\": \"ThemeShareResult\",\n \"definitions\": {\n \"ThemePushTheme\": {\n \"type\": \"object\",\n \"properties\": {\n \"id\": {\n \"type\": \"string\",\n \"pattern\": \"^\\\\d+$\",\n \"description\": \"The decimal Online Store theme ID, not a Shopify GID.\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"role\": {\n \"type\": \"string\",\n \"description\": \"The upstream theme role; known values include main, unpublished, and development.\"\n },\n \"storeDomain\": {\n \"anyOf\": [\n {\n \"type\": \"string\",\n \"pattern\": \"^[a-z0-9][a-z0-9-]*\\\\.myshopify\\\\.com$\"\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"previewUrl\": {\n \"anyOf\": [\n {\n \"type\": \"string\",\n \"format\": \"uri\"\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"editorUrl\": {\n \"anyOf\": [\n {\n \"type\": \"string\",\n \"format\": \"uri\"\n },\n {\n \"type\": \"null\"\n }\n ]\n }\n },\n \"required\": [\n \"id\",\n \"name\",\n \"role\",\n \"storeDomain\",\n \"previewUrl\",\n \"editorUrl\"\n ],\n \"additionalProperties\": false\n },\n \"ThemeEnvironment\": {\n \"anyOf\": [\n {\n \"type\": \"object\",\n \"properties\": {\n \"environment\": {\n \"type\": \"string\"\n },\n \"result\": {\n \"anyOf\": [\n {\n \"anyOf\": [\n {\n \"type\": \"object\",\n \"properties\": {\n \"status\": {\n \"type\": \"string\",\n \"enum\": [\n \"success\",\n \"partial\"\n ]\n },\n \"changed\": {\n \"type\": \"boolean\"\n },\n \"theme\": {\n \"$ref\": \"#/definitions/ThemePushTheme\"\n },\n \"issues\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"filePath\": {\n \"type\": \"string\"\n },\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"filePath\",\n \"message\"\n ],\n \"additionalProperties\": false\n }\n }\n },\n \"required\": [\n \"status\",\n \"changed\",\n \"theme\",\n \"issues\"\n ],\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"status\": {\n \"type\": \"string\",\n \"const\": \"skipped\"\n },\n \"reason\": {\n \"type\": \"string\",\n \"const\": \"unsafe-directory\"\n }\n },\n \"required\": [\n \"status\",\n \"reason\"\n ],\n \"additionalProperties\": false\n }\n ]\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"status\": {\n \"type\": \"string\",\n \"const\": \"cancelled\"\n }\n },\n \"required\": [\n \"status\"\n ],\n \"additionalProperties\": false\n }\n ]\n }\n },\n \"required\": [\n \"environment\",\n \"result\"\n ],\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"environment\": {\n \"type\": \"string\"\n },\n \"error\": {\n \"anyOf\": [\n {\n \"type\": \"object\",\n \"properties\": {\n \"type\": {\n \"type\": \"string\",\n \"const\": \"abort\"\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"tryMessage\": {\n \"type\": \"string\"\n },\n \"nextSteps\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"customSections\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"title\": {\n \"type\": \"string\"\n },\n \"body\": {\n \"anyOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n }\n ]\n }\n },\n \"required\": [\n \"body\"\n ],\n \"additionalProperties\": false\n }\n },\n \"details\": {}\n },\n \"required\": [\n \"type\",\n \"message\"\n ],\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"type\": {\n \"type\": \"string\",\n \"const\": \"bug\"\n },\n \"message\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/message\"\n },\n \"tryMessage\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/tryMessage\"\n },\n \"nextSteps\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/nextSteps\"\n },\n \"customSections\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/customSections\"\n },\n \"details\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/details\"\n },\n \"stack\": {\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"type\",\n \"message\"\n ],\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"type\": {\n \"type\": \"string\",\n \"const\": \"external\"\n },\n \"message\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/message\"\n },\n \"tryMessage\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/tryMessage\"\n },\n \"nextSteps\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/nextSteps\"\n },\n \"customSections\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/customSections\"\n },\n \"details\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/details\"\n },\n \"command\": {\n \"type\": \"string\"\n },\n \"args\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"required\": [\n \"type\",\n \"message\",\n \"command\",\n \"args\"\n ],\n \"additionalProperties\": false\n }\n ]\n }\n },\n \"required\": [\n \"environment\",\n \"error\"\n ],\n \"additionalProperties\": false\n }\n ]\n }\n },\n \"$schema\": \"http://json-schema.org/draft-07/schema#\"\n}\n```", "descriptionWithMarkdown": "Uploads your theme as a new, unpublished theme in your theme library. The theme is given a randomized name.\n\n This command returns a [preview link](https://help.shopify.com/manual/online-store/themes/adding-themes#share-a-theme-preview-with-others) that you can share with others.", "enableJsonFlag": false, "flags": { diff --git a/packages/theme/src/cli/commands/theme/share.test.ts b/packages/theme/src/cli/commands/theme/share.test.ts index 1526a9263ee..0e1dae05f8c 100644 --- a/packages/theme/src/cli/commands/theme/share.test.ts +++ b/packages/theme/src/cli/commands/theme/share.test.ts @@ -21,8 +21,8 @@ function result() { name: 'Creative Theme', role: 'unpublished', shop: session.storeFqdn, - editor_url: 'editor', - preview_url: 'preview', + editor_url: 'https://test.myshopify.com/admin/themes/1/editor', + preview_url: 'https://test.myshopify.com?preview_theme_id=1', }, published: false, hasErrors: false, @@ -52,7 +52,7 @@ describe('theme share', () => { false, undefined, ) - expect(stdout()).toBe(`${themeShareJsonOutputSchema.encode({status: 'success', theme: result().theme})}\n`) + expect(stdout()).toBe(`${themeShareJsonOutputSchema.encode(result())}\n`) expect(stderr()).toBe('') }) }) @@ -68,15 +68,14 @@ describe('theme share', () => { await withCapturedStandardStreams(async ({stdout}) => { await command.command({json: true, environment: ['staging']} as never, session, false) - expect(JSON.parse(stdout())).toEqual({ - status: 'failed', - environment: 'staging', - theme: { - ...result().theme, - warning: "[staging] The theme 'Creative Theme' was pushed with errors", - errors: {'assets/theme.css': ['bad CSS']}, - }, + expect(JSON.parse(stdout())).toMatchObject({ + status: 'partial', + changed: true, + theme: {id: '1'}, + issues: [{message: 'bad CSS'}], }) + expect(process.exitCode).toBe(1) + process.exitCode = 0 }) }) @@ -95,7 +94,7 @@ describe('theme share', () => { await withCapturedStandardStreams(async ({stdout}) => { await new Share([], new Config({root: '.'})).command({json: true} as never, session, false) - expect(stdout()).toBe('') + expect(JSON.parse(stdout())).toEqual({status: 'cancelled'}) }) }) diff --git a/packages/theme/src/cli/commands/theme/share.ts b/packages/theme/src/cli/commands/theme/share.ts index f97d99b4739..b02a57f2a0c 100644 --- a/packages/theme/src/cli/commands/theme/share.ts +++ b/packages/theme/src/cli/commands/theme/share.ts @@ -1,8 +1,10 @@ +import {ThemeEnvironmentResult} from '../../services/json-output/schema.js' import {themeShareJsonOutputSchema} from '../../services/share/types.js' import {renderThemeShareResult, renderThemeShareEnvironmentResults} from '../../services/share/result.js' import {themeFlags} from '../../flags.js' import ThemeCommand from '../../utilities/theme-command.js' import {executeThemePush, PushFlags} from '../../services/push.js' +import {outputResult} from '@shopify/cli-kit/node/output' import {Flags} from '@oclif/core' import {globalFlags, jsonFlag} from '@shopify/cli-kit/node/cli' import {getRandomName} from '@shopify/cli-kit/common/string' @@ -67,16 +69,20 @@ export default class Share extends ThemeCommand { recordTiming('theme-command:share') const result = await executeThemePush(pushFlags, adminSession, multiEnvironment, context) - if (result && !(flags.json && multiEnvironment)) renderThemeShareResult(result, flags.json ? 'json' : 'text') + if (result?.hasErrors) process.exitCode = 1 + if (!(flags.json && multiEnvironment)) { + if (result) renderThemeShareResult(result, flags.json ? 'json' : 'text') + else if (flags.json) outputResult(themeShareJsonOutputSchema.encode({status: 'cancelled'})) + } recordTiming('theme-command:share') - return result + return result ?? (flags.json && multiEnvironment ? {status: 'skipped', reason: 'unsafe-directory'} : undefined) } protected collectsEnvironmentResults(flags: {json?: boolean}): boolean { return Boolean(flags.json) } - protected renderEnvironmentResults(results: {environment: string; result: unknown}[]): void { + protected renderEnvironmentResults(results: ThemeEnvironmentResult[]): void { renderThemeShareEnvironmentResults(results) } } diff --git a/packages/theme/src/cli/services/share/result.ts b/packages/theme/src/cli/services/share/result.ts index af3788d6f17..e7c76c5d92c 100644 --- a/packages/theme/src/cli/services/share/result.ts +++ b/packages/theme/src/cli/services/share/result.ts @@ -1,4 +1,5 @@ -import {themeShareJsonOutputSchema, type ThemeShareResult} from './types.js' +import {themeShareJsonOutputSchema} from './types.js' +import {ThemeEnvironmentResult} from '../json-output/schema.js' import {themePushJsonResult, renderThemePushResult} from '../push/result.js' import {themePushResultSchema, type ThemePushResult} from '../push/types.js' import {outputResult} from '@shopify/cli-kit/node/output' @@ -11,11 +12,11 @@ export function renderThemeShareResult(result: ThemePushResult, format: 'text' | } } -export function renderThemeShareEnvironmentResults(results: {environment: string; result: unknown}[]): void { - const output: ThemeShareResult = results.flatMap(({environment, result}) => - result === undefined - ? [] - : [{...themePushJsonResult({...themePushResultSchema.parse(result), environment}), environment}], - ) - outputResult(themeShareJsonOutputSchema.encode(output)) +export function renderThemeShareEnvironmentResults(results: ThemeEnvironmentResult[]): void { + const environments = results.map((entry) => { + if ('error' in entry) return entry + if (themePushResultSchema.safeParse(entry.result).data?.hasErrors) process.exitCode = 1 + return {...entry, result: entry.result ?? {status: 'skipped', reason: 'unsafe-directory'}} + }) + outputResult(themeShareJsonOutputSchema.encode({environments})) } diff --git a/packages/theme/src/cli/services/share/types.ts b/packages/theme/src/cli/services/share/types.ts index d568e983d17..be8d98d41e1 100644 --- a/packages/theme/src/cli/services/share/types.ts +++ b/packages/theme/src/cli/services/share/types.ts @@ -1,10 +1,12 @@ import {themePushJsonOutputSchema} from '../push/types.js' import {defineJsonOutputSchema, type InferJsonOutputSchema} from '@shopify/cli-kit/node/json-output-schema' -export const themeShareJsonOutputSchema = defineJsonOutputSchema({ +const outputSchema = defineJsonOutputSchema({ name: 'ThemeShareResult', schema: themePushJsonOutputSchema.schema, definitions: themePushJsonOutputSchema.definitions, }) +export const themeShareJsonOutputSchema = {...outputSchema, encode: themePushJsonOutputSchema.encode} + export type ThemeShareResult = InferJsonOutputSchema From e4924b89041216d066bacaafa3c99182dc32514e Mon Sep 17 00:00:00 2001 From: Gonzalo Riestra Date: Tue, 6 Oct 2026 14:16:43 +0200 Subject: [PATCH 3/6] Refresh theme share JSON schema documentation --- packages/cli/README.md | 2 +- packages/cli/oclif.manifest.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/README.md b/packages/cli/README.md index 03bd2eb9dcc..955084ca399 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -16758,7 +16758,7 @@ DESCRIPTION }, "role": { "type": "string", - "description": "The upstream theme role; known values include main, unpublished, and development." + "description": "The upstream theme role; known values include live, unpublished, and development." }, "storeDomain": { "anyOf": [ diff --git a/packages/cli/oclif.manifest.json b/packages/cli/oclif.manifest.json index 72dea442480..997171734a7 100644 --- a/packages/cli/oclif.manifest.json +++ b/packages/cli/oclif.manifest.json @@ -13062,7 +13062,7 @@ "args": { }, "customPluginName": "@shopify/theme", - "description": "Uploads your theme as a new, unpublished theme in your theme library. The theme is given a randomized name.\n\n This command returns a \"preview link\" (https://help.shopify.com/manual/online-store/themes/adding-themes#share-a-theme-preview-with-others) that you can share with others.\n\nUse `--json-schema` to print the result, error, and event schemas.\n\nOutput from `--json` conforms to the `ThemeShareResult` schema.\n\n```json\n{\n \"anyOf\": [\n {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/0/properties/result\"\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"environments\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/definitions/ThemeEnvironment\"\n }\n }\n },\n \"required\": [\n \"environments\"\n ],\n \"additionalProperties\": false\n }\n ],\n \"title\": \"ThemeShareResult\",\n \"definitions\": {\n \"ThemePushTheme\": {\n \"type\": \"object\",\n \"properties\": {\n \"id\": {\n \"type\": \"string\",\n \"pattern\": \"^\\\\d+$\",\n \"description\": \"The decimal Online Store theme ID, not a Shopify GID.\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"role\": {\n \"type\": \"string\",\n \"description\": \"The upstream theme role; known values include main, unpublished, and development.\"\n },\n \"storeDomain\": {\n \"anyOf\": [\n {\n \"type\": \"string\",\n \"pattern\": \"^[a-z0-9][a-z0-9-]*\\\\.myshopify\\\\.com$\"\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"previewUrl\": {\n \"anyOf\": [\n {\n \"type\": \"string\",\n \"format\": \"uri\"\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"editorUrl\": {\n \"anyOf\": [\n {\n \"type\": \"string\",\n \"format\": \"uri\"\n },\n {\n \"type\": \"null\"\n }\n ]\n }\n },\n \"required\": [\n \"id\",\n \"name\",\n \"role\",\n \"storeDomain\",\n \"previewUrl\",\n \"editorUrl\"\n ],\n \"additionalProperties\": false\n },\n \"ThemeEnvironment\": {\n \"anyOf\": [\n {\n \"type\": \"object\",\n \"properties\": {\n \"environment\": {\n \"type\": \"string\"\n },\n \"result\": {\n \"anyOf\": [\n {\n \"anyOf\": [\n {\n \"type\": \"object\",\n \"properties\": {\n \"status\": {\n \"type\": \"string\",\n \"enum\": [\n \"success\",\n \"partial\"\n ]\n },\n \"changed\": {\n \"type\": \"boolean\"\n },\n \"theme\": {\n \"$ref\": \"#/definitions/ThemePushTheme\"\n },\n \"issues\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"filePath\": {\n \"type\": \"string\"\n },\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"filePath\",\n \"message\"\n ],\n \"additionalProperties\": false\n }\n }\n },\n \"required\": [\n \"status\",\n \"changed\",\n \"theme\",\n \"issues\"\n ],\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"status\": {\n \"type\": \"string\",\n \"const\": \"skipped\"\n },\n \"reason\": {\n \"type\": \"string\",\n \"const\": \"unsafe-directory\"\n }\n },\n \"required\": [\n \"status\",\n \"reason\"\n ],\n \"additionalProperties\": false\n }\n ]\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"status\": {\n \"type\": \"string\",\n \"const\": \"cancelled\"\n }\n },\n \"required\": [\n \"status\"\n ],\n \"additionalProperties\": false\n }\n ]\n }\n },\n \"required\": [\n \"environment\",\n \"result\"\n ],\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"environment\": {\n \"type\": \"string\"\n },\n \"error\": {\n \"anyOf\": [\n {\n \"type\": \"object\",\n \"properties\": {\n \"type\": {\n \"type\": \"string\",\n \"const\": \"abort\"\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"tryMessage\": {\n \"type\": \"string\"\n },\n \"nextSteps\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"customSections\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"title\": {\n \"type\": \"string\"\n },\n \"body\": {\n \"anyOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n }\n ]\n }\n },\n \"required\": [\n \"body\"\n ],\n \"additionalProperties\": false\n }\n },\n \"details\": {}\n },\n \"required\": [\n \"type\",\n \"message\"\n ],\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"type\": {\n \"type\": \"string\",\n \"const\": \"bug\"\n },\n \"message\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/message\"\n },\n \"tryMessage\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/tryMessage\"\n },\n \"nextSteps\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/nextSteps\"\n },\n \"customSections\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/customSections\"\n },\n \"details\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/details\"\n },\n \"stack\": {\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"type\",\n \"message\"\n ],\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"type\": {\n \"type\": \"string\",\n \"const\": \"external\"\n },\n \"message\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/message\"\n },\n \"tryMessage\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/tryMessage\"\n },\n \"nextSteps\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/nextSteps\"\n },\n \"customSections\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/customSections\"\n },\n \"details\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/details\"\n },\n \"command\": {\n \"type\": \"string\"\n },\n \"args\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"required\": [\n \"type\",\n \"message\",\n \"command\",\n \"args\"\n ],\n \"additionalProperties\": false\n }\n ]\n }\n },\n \"required\": [\n \"environment\",\n \"error\"\n ],\n \"additionalProperties\": false\n }\n ]\n }\n },\n \"$schema\": \"http://json-schema.org/draft-07/schema#\"\n}\n```", + "description": "Uploads your theme as a new, unpublished theme in your theme library. The theme is given a randomized name.\n\n This command returns a \"preview link\" (https://help.shopify.com/manual/online-store/themes/adding-themes#share-a-theme-preview-with-others) that you can share with others.\n\nUse `--json-schema` to print the result, error, and event schemas.\n\nOutput from `--json` conforms to the `ThemeShareResult` schema.\n\n```json\n{\n \"anyOf\": [\n {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/0/properties/result\"\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"environments\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/definitions/ThemeEnvironment\"\n }\n }\n },\n \"required\": [\n \"environments\"\n ],\n \"additionalProperties\": false\n }\n ],\n \"title\": \"ThemeShareResult\",\n \"definitions\": {\n \"ThemePushTheme\": {\n \"type\": \"object\",\n \"properties\": {\n \"id\": {\n \"type\": \"string\",\n \"pattern\": \"^\\\\d+$\",\n \"description\": \"The decimal Online Store theme ID, not a Shopify GID.\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"role\": {\n \"type\": \"string\",\n \"description\": \"The upstream theme role; known values include live, unpublished, and development.\"\n },\n \"storeDomain\": {\n \"anyOf\": [\n {\n \"type\": \"string\",\n \"pattern\": \"^[a-z0-9][a-z0-9-]*\\\\.myshopify\\\\.com$\"\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"previewUrl\": {\n \"anyOf\": [\n {\n \"type\": \"string\",\n \"format\": \"uri\"\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"editorUrl\": {\n \"anyOf\": [\n {\n \"type\": \"string\",\n \"format\": \"uri\"\n },\n {\n \"type\": \"null\"\n }\n ]\n }\n },\n \"required\": [\n \"id\",\n \"name\",\n \"role\",\n \"storeDomain\",\n \"previewUrl\",\n \"editorUrl\"\n ],\n \"additionalProperties\": false\n },\n \"ThemeEnvironment\": {\n \"anyOf\": [\n {\n \"type\": \"object\",\n \"properties\": {\n \"environment\": {\n \"type\": \"string\"\n },\n \"result\": {\n \"anyOf\": [\n {\n \"anyOf\": [\n {\n \"type\": \"object\",\n \"properties\": {\n \"status\": {\n \"type\": \"string\",\n \"enum\": [\n \"success\",\n \"partial\"\n ]\n },\n \"changed\": {\n \"type\": \"boolean\"\n },\n \"theme\": {\n \"$ref\": \"#/definitions/ThemePushTheme\"\n },\n \"issues\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"filePath\": {\n \"type\": \"string\"\n },\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"filePath\",\n \"message\"\n ],\n \"additionalProperties\": false\n }\n }\n },\n \"required\": [\n \"status\",\n \"changed\",\n \"theme\",\n \"issues\"\n ],\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"status\": {\n \"type\": \"string\",\n \"const\": \"skipped\"\n },\n \"reason\": {\n \"type\": \"string\",\n \"const\": \"unsafe-directory\"\n }\n },\n \"required\": [\n \"status\",\n \"reason\"\n ],\n \"additionalProperties\": false\n }\n ]\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"status\": {\n \"type\": \"string\",\n \"const\": \"cancelled\"\n }\n },\n \"required\": [\n \"status\"\n ],\n \"additionalProperties\": false\n }\n ]\n }\n },\n \"required\": [\n \"environment\",\n \"result\"\n ],\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"environment\": {\n \"type\": \"string\"\n },\n \"error\": {\n \"anyOf\": [\n {\n \"type\": \"object\",\n \"properties\": {\n \"type\": {\n \"type\": \"string\",\n \"const\": \"abort\"\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"tryMessage\": {\n \"type\": \"string\"\n },\n \"nextSteps\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"customSections\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"title\": {\n \"type\": \"string\"\n },\n \"body\": {\n \"anyOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n }\n ]\n }\n },\n \"required\": [\n \"body\"\n ],\n \"additionalProperties\": false\n }\n },\n \"details\": {}\n },\n \"required\": [\n \"type\",\n \"message\"\n ],\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"type\": {\n \"type\": \"string\",\n \"const\": \"bug\"\n },\n \"message\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/message\"\n },\n \"tryMessage\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/tryMessage\"\n },\n \"nextSteps\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/nextSteps\"\n },\n \"customSections\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/customSections\"\n },\n \"details\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/details\"\n },\n \"stack\": {\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"type\",\n \"message\"\n ],\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"type\": {\n \"type\": \"string\",\n \"const\": \"external\"\n },\n \"message\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/message\"\n },\n \"tryMessage\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/tryMessage\"\n },\n \"nextSteps\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/nextSteps\"\n },\n \"customSections\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/customSections\"\n },\n \"details\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/details\"\n },\n \"command\": {\n \"type\": \"string\"\n },\n \"args\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"required\": [\n \"type\",\n \"message\",\n \"command\",\n \"args\"\n ],\n \"additionalProperties\": false\n }\n ]\n }\n },\n \"required\": [\n \"environment\",\n \"error\"\n ],\n \"additionalProperties\": false\n }\n ]\n }\n },\n \"$schema\": \"http://json-schema.org/draft-07/schema#\"\n}\n```", "descriptionWithMarkdown": "Uploads your theme as a new, unpublished theme in your theme library. The theme is given a randomized name.\n\n This command returns a [preview link](https://help.shopify.com/manual/online-store/themes/adding-themes#share-a-theme-preview-with-others) that you can share with others.", "enableJsonFlag": false, "flags": { From 03da27ba1b5da00f24dc1d19216cae6cb5c109d6 Mon Sep 17 00:00:00 2001 From: Gonzalo Riestra Date: Tue, 6 Oct 2026 14:17:14 +0200 Subject: [PATCH 4/6] Refresh generated theme share command docs --- .../generated/generated_docs_data_v2.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/docs-shopify.dev/generated/generated_docs_data_v2.json b/docs-shopify.dev/generated/generated_docs_data_v2.json index 61b9b88da90..44b4377d86a 100644 --- a/docs-shopify.dev/generated/generated_docs_data_v2.json +++ b/docs-shopify.dev/generated/generated_docs_data_v2.json @@ -10756,6 +10756,15 @@ "isOptional": true, "environmentValue": "SHOPIFY_FLAG_ENVIRONMENT" }, + { + "filePath": "docs-shopify.dev/commands/interfaces/theme-share.interface.ts", + "syntaxKind": "PropertySignature", + "name": "-j, --json", + "value": "''", + "description": "Output the result as JSON. Automatically disables color output.", + "isOptional": true, + "environmentValue": "SHOPIFY_FLAG_JSON" + }, { "filePath": "docs-shopify.dev/commands/interfaces/theme-share.interface.ts", "syntaxKind": "PropertySignature", @@ -10766,7 +10775,7 @@ "environmentValue": "SHOPIFY_FLAG_STORE" } ], - "value": "export interface themeshare {\n /**\n * Alias of the Shopify account to use for authentication.\n * @environment SHOPIFY_FLAG_AUTH_ALIAS\n */\n '--auth-alias '?: string\n\n /**\n * The environment to apply to the current command.\n * @environment SHOPIFY_FLAG_ENVIRONMENT\n */\n '-e, --environment '?: string\n\n /**\n * Print the command's JSON schemas.\n * @environment SHOPIFY_FLAG_JSON_SCHEMA\n */\n '--json-schema'?: ''\n\n /**\n * The listing preset to use for multi-preset themes. Applies preset files from listings/[preset-name] directory.\n * @environment SHOPIFY_FLAG_LISTING\n */\n '--listing '?: string\n\n /**\n * Disable color output.\n * @environment SHOPIFY_FLAG_NO_COLOR\n */\n '--no-color'?: ''\n\n /**\n * Disable interactive prompts and browser authentication.\n * @environment SHOPIFY_FLAG_NO_INPUT\n */\n '--no-input'?: ''\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 * 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 * Increase the verbosity of the output. May include sensitive data.\n * @environment SHOPIFY_FLAG_VERBOSE\n */\n '--verbose'?: ''\n}" + "value": "export interface themeshare {\n /**\n * Alias of the Shopify account to use for authentication.\n * @environment SHOPIFY_FLAG_AUTH_ALIAS\n */\n '--auth-alias '?: string\n\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 result as JSON. Automatically disables color output.\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 * The listing preset to use for multi-preset themes. Applies preset files from listings/[preset-name] directory.\n * @environment SHOPIFY_FLAG_LISTING\n */\n '--listing '?: string\n\n /**\n * Disable color output.\n * @environment SHOPIFY_FLAG_NO_COLOR\n */\n '--no-color'?: ''\n\n /**\n * Disable interactive prompts and browser authentication.\n * @environment SHOPIFY_FLAG_NO_INPUT\n */\n '--no-input'?: ''\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 * 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 * Increase the verbosity of the output. May include sensitive data.\n * @environment SHOPIFY_FLAG_VERBOSE\n */\n '--verbose'?: ''\n}" } }, "upgrade": { From 5250e9580e6faa348ca256c4cb0fb83197523aed Mon Sep 17 00:00:00 2001 From: Gonzalo Riestra Date: Tue, 6 Oct 2026 14:30:55 +0200 Subject: [PATCH 5/6] Retain named schema types in theme share declarations --- packages/theme/src/cli/services/share/types.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/theme/src/cli/services/share/types.ts b/packages/theme/src/cli/services/share/types.ts index be8d98d41e1..c8bf3db8e0e 100644 --- a/packages/theme/src/cli/services/share/types.ts +++ b/packages/theme/src/cli/services/share/types.ts @@ -1,5 +1,5 @@ import {themePushJsonOutputSchema} from '../push/types.js' -import {defineJsonOutputSchema, type InferJsonOutputSchema} from '@shopify/cli-kit/node/json-output-schema' +import {defineJsonOutputSchema} from '@shopify/cli-kit/node/json-output-schema' const outputSchema = defineJsonOutputSchema({ name: 'ThemeShareResult', @@ -7,6 +7,7 @@ const outputSchema = defineJsonOutputSchema({ definitions: themePushJsonOutputSchema.definitions, }) -export const themeShareJsonOutputSchema = {...outputSchema, encode: themePushJsonOutputSchema.encode} - -export type ThemeShareResult = InferJsonOutputSchema +export const themeShareJsonOutputSchema: Omit & {encode(value: unknown): string} = { + ...outputSchema, + encode: themePushJsonOutputSchema.encode, +} From 124ac26d128fbb0128c672e0f40688e49f4180de Mon Sep 17 00:00:00 2001 From: Gonzalo Riestra Date: Thu, 8 Oct 2026 10:38:05 +0200 Subject: [PATCH 6/6] Refresh JSON schema documentation after rebase --- packages/cli/README.md | 15 ++++++++++++++- packages/cli/oclif.manifest.json | 2 +- packages/theme/src/cli/commands/theme/share.ts | 4 ---- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/packages/cli/README.md b/packages/cli/README.md index 955084ca399..0a7192145df 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -16920,6 +16920,11 @@ DESCRIPTION "message": { "type": "string" }, + "code": { + "type": "string", + "minLength": 1, + "description": "A stable error code, included only when known." + }, "tryMessage": { "type": "string" }, @@ -16960,7 +16965,9 @@ DESCRIPTION "additionalProperties": false } }, - "details": {} + "details": { + "description": "Selected domain details, preserving native API payloads such as GraphQL errors, extensions, and data." + } }, "required": [ "type", @@ -16978,6 +16985,9 @@ DESCRIPTION "message": { "$ref": "#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/message" }, + "code": { + "$ref": "#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/code" + }, "tryMessage": { "$ref": "#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/tryMessage" }, @@ -17010,6 +17020,9 @@ DESCRIPTION "message": { "$ref": "#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/message" }, + "code": { + "$ref": "#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/code" + }, "tryMessage": { "$ref": "#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/tryMessage" }, diff --git a/packages/cli/oclif.manifest.json b/packages/cli/oclif.manifest.json index 997171734a7..5c9f58d93c5 100644 --- a/packages/cli/oclif.manifest.json +++ b/packages/cli/oclif.manifest.json @@ -13062,7 +13062,7 @@ "args": { }, "customPluginName": "@shopify/theme", - "description": "Uploads your theme as a new, unpublished theme in your theme library. The theme is given a randomized name.\n\n This command returns a \"preview link\" (https://help.shopify.com/manual/online-store/themes/adding-themes#share-a-theme-preview-with-others) that you can share with others.\n\nUse `--json-schema` to print the result, error, and event schemas.\n\nOutput from `--json` conforms to the `ThemeShareResult` schema.\n\n```json\n{\n \"anyOf\": [\n {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/0/properties/result\"\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"environments\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/definitions/ThemeEnvironment\"\n }\n }\n },\n \"required\": [\n \"environments\"\n ],\n \"additionalProperties\": false\n }\n ],\n \"title\": \"ThemeShareResult\",\n \"definitions\": {\n \"ThemePushTheme\": {\n \"type\": \"object\",\n \"properties\": {\n \"id\": {\n \"type\": \"string\",\n \"pattern\": \"^\\\\d+$\",\n \"description\": \"The decimal Online Store theme ID, not a Shopify GID.\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"role\": {\n \"type\": \"string\",\n \"description\": \"The upstream theme role; known values include live, unpublished, and development.\"\n },\n \"storeDomain\": {\n \"anyOf\": [\n {\n \"type\": \"string\",\n \"pattern\": \"^[a-z0-9][a-z0-9-]*\\\\.myshopify\\\\.com$\"\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"previewUrl\": {\n \"anyOf\": [\n {\n \"type\": \"string\",\n \"format\": \"uri\"\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"editorUrl\": {\n \"anyOf\": [\n {\n \"type\": \"string\",\n \"format\": \"uri\"\n },\n {\n \"type\": \"null\"\n }\n ]\n }\n },\n \"required\": [\n \"id\",\n \"name\",\n \"role\",\n \"storeDomain\",\n \"previewUrl\",\n \"editorUrl\"\n ],\n \"additionalProperties\": false\n },\n \"ThemeEnvironment\": {\n \"anyOf\": [\n {\n \"type\": \"object\",\n \"properties\": {\n \"environment\": {\n \"type\": \"string\"\n },\n \"result\": {\n \"anyOf\": [\n {\n \"anyOf\": [\n {\n \"type\": \"object\",\n \"properties\": {\n \"status\": {\n \"type\": \"string\",\n \"enum\": [\n \"success\",\n \"partial\"\n ]\n },\n \"changed\": {\n \"type\": \"boolean\"\n },\n \"theme\": {\n \"$ref\": \"#/definitions/ThemePushTheme\"\n },\n \"issues\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"filePath\": {\n \"type\": \"string\"\n },\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"filePath\",\n \"message\"\n ],\n \"additionalProperties\": false\n }\n }\n },\n \"required\": [\n \"status\",\n \"changed\",\n \"theme\",\n \"issues\"\n ],\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"status\": {\n \"type\": \"string\",\n \"const\": \"skipped\"\n },\n \"reason\": {\n \"type\": \"string\",\n \"const\": \"unsafe-directory\"\n }\n },\n \"required\": [\n \"status\",\n \"reason\"\n ],\n \"additionalProperties\": false\n }\n ]\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"status\": {\n \"type\": \"string\",\n \"const\": \"cancelled\"\n }\n },\n \"required\": [\n \"status\"\n ],\n \"additionalProperties\": false\n }\n ]\n }\n },\n \"required\": [\n \"environment\",\n \"result\"\n ],\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"environment\": {\n \"type\": \"string\"\n },\n \"error\": {\n \"anyOf\": [\n {\n \"type\": \"object\",\n \"properties\": {\n \"type\": {\n \"type\": \"string\",\n \"const\": \"abort\"\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"tryMessage\": {\n \"type\": \"string\"\n },\n \"nextSteps\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"customSections\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"title\": {\n \"type\": \"string\"\n },\n \"body\": {\n \"anyOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n }\n ]\n }\n },\n \"required\": [\n \"body\"\n ],\n \"additionalProperties\": false\n }\n },\n \"details\": {}\n },\n \"required\": [\n \"type\",\n \"message\"\n ],\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"type\": {\n \"type\": \"string\",\n \"const\": \"bug\"\n },\n \"message\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/message\"\n },\n \"tryMessage\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/tryMessage\"\n },\n \"nextSteps\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/nextSteps\"\n },\n \"customSections\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/customSections\"\n },\n \"details\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/details\"\n },\n \"stack\": {\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"type\",\n \"message\"\n ],\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"type\": {\n \"type\": \"string\",\n \"const\": \"external\"\n },\n \"message\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/message\"\n },\n \"tryMessage\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/tryMessage\"\n },\n \"nextSteps\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/nextSteps\"\n },\n \"customSections\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/customSections\"\n },\n \"details\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/details\"\n },\n \"command\": {\n \"type\": \"string\"\n },\n \"args\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"required\": [\n \"type\",\n \"message\",\n \"command\",\n \"args\"\n ],\n \"additionalProperties\": false\n }\n ]\n }\n },\n \"required\": [\n \"environment\",\n \"error\"\n ],\n \"additionalProperties\": false\n }\n ]\n }\n },\n \"$schema\": \"http://json-schema.org/draft-07/schema#\"\n}\n```", + "description": "Uploads your theme as a new, unpublished theme in your theme library. The theme is given a randomized name.\n\n This command returns a \"preview link\" (https://help.shopify.com/manual/online-store/themes/adding-themes#share-a-theme-preview-with-others) that you can share with others.\n\nUse `--json-schema` to print the result, error, and event schemas.\n\nOutput from `--json` conforms to the `ThemeShareResult` schema.\n\n```json\n{\n \"anyOf\": [\n {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/0/properties/result\"\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"environments\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/definitions/ThemeEnvironment\"\n }\n }\n },\n \"required\": [\n \"environments\"\n ],\n \"additionalProperties\": false\n }\n ],\n \"title\": \"ThemeShareResult\",\n \"definitions\": {\n \"ThemePushTheme\": {\n \"type\": \"object\",\n \"properties\": {\n \"id\": {\n \"type\": \"string\",\n \"pattern\": \"^\\\\d+$\",\n \"description\": \"The decimal Online Store theme ID, not a Shopify GID.\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"role\": {\n \"type\": \"string\",\n \"description\": \"The upstream theme role; known values include live, unpublished, and development.\"\n },\n \"storeDomain\": {\n \"anyOf\": [\n {\n \"type\": \"string\",\n \"pattern\": \"^[a-z0-9][a-z0-9-]*\\\\.myshopify\\\\.com$\"\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"previewUrl\": {\n \"anyOf\": [\n {\n \"type\": \"string\",\n \"format\": \"uri\"\n },\n {\n \"type\": \"null\"\n }\n ]\n },\n \"editorUrl\": {\n \"anyOf\": [\n {\n \"type\": \"string\",\n \"format\": \"uri\"\n },\n {\n \"type\": \"null\"\n }\n ]\n }\n },\n \"required\": [\n \"id\",\n \"name\",\n \"role\",\n \"storeDomain\",\n \"previewUrl\",\n \"editorUrl\"\n ],\n \"additionalProperties\": false\n },\n \"ThemeEnvironment\": {\n \"anyOf\": [\n {\n \"type\": \"object\",\n \"properties\": {\n \"environment\": {\n \"type\": \"string\"\n },\n \"result\": {\n \"anyOf\": [\n {\n \"anyOf\": [\n {\n \"type\": \"object\",\n \"properties\": {\n \"status\": {\n \"type\": \"string\",\n \"enum\": [\n \"success\",\n \"partial\"\n ]\n },\n \"changed\": {\n \"type\": \"boolean\"\n },\n \"theme\": {\n \"$ref\": \"#/definitions/ThemePushTheme\"\n },\n \"issues\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"filePath\": {\n \"type\": \"string\"\n },\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"filePath\",\n \"message\"\n ],\n \"additionalProperties\": false\n }\n }\n },\n \"required\": [\n \"status\",\n \"changed\",\n \"theme\",\n \"issues\"\n ],\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"status\": {\n \"type\": \"string\",\n \"const\": \"skipped\"\n },\n \"reason\": {\n \"type\": \"string\",\n \"const\": \"unsafe-directory\"\n }\n },\n \"required\": [\n \"status\",\n \"reason\"\n ],\n \"additionalProperties\": false\n }\n ]\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"status\": {\n \"type\": \"string\",\n \"const\": \"cancelled\"\n }\n },\n \"required\": [\n \"status\"\n ],\n \"additionalProperties\": false\n }\n ]\n }\n },\n \"required\": [\n \"environment\",\n \"result\"\n ],\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"environment\": {\n \"type\": \"string\"\n },\n \"error\": {\n \"anyOf\": [\n {\n \"type\": \"object\",\n \"properties\": {\n \"type\": {\n \"type\": \"string\",\n \"const\": \"abort\"\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"code\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"A stable error code, included only when known.\"\n },\n \"tryMessage\": {\n \"type\": \"string\"\n },\n \"nextSteps\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"customSections\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"title\": {\n \"type\": \"string\"\n },\n \"body\": {\n \"anyOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n }\n ]\n }\n },\n \"required\": [\n \"body\"\n ],\n \"additionalProperties\": false\n }\n },\n \"details\": {\n \"description\": \"Selected domain details, preserving native API payloads such as GraphQL errors, extensions, and data.\"\n }\n },\n \"required\": [\n \"type\",\n \"message\"\n ],\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"type\": {\n \"type\": \"string\",\n \"const\": \"bug\"\n },\n \"message\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/message\"\n },\n \"code\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/code\"\n },\n \"tryMessage\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/tryMessage\"\n },\n \"nextSteps\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/nextSteps\"\n },\n \"customSections\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/customSections\"\n },\n \"details\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/details\"\n },\n \"stack\": {\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"type\",\n \"message\"\n ],\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"type\": {\n \"type\": \"string\",\n \"const\": \"external\"\n },\n \"message\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/message\"\n },\n \"code\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/code\"\n },\n \"tryMessage\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/tryMessage\"\n },\n \"nextSteps\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/nextSteps\"\n },\n \"customSections\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/customSections\"\n },\n \"details\": {\n \"$ref\": \"#/definitions/ThemeEnvironment/anyOf/1/properties/error/anyOf/0/properties/details\"\n },\n \"command\": {\n \"type\": \"string\"\n },\n \"args\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"required\": [\n \"type\",\n \"message\",\n \"command\",\n \"args\"\n ],\n \"additionalProperties\": false\n }\n ]\n }\n },\n \"required\": [\n \"environment\",\n \"error\"\n ],\n \"additionalProperties\": false\n }\n ]\n }\n },\n \"$schema\": \"http://json-schema.org/draft-07/schema#\"\n}\n```", "descriptionWithMarkdown": "Uploads your theme as a new, unpublished theme in your theme library. The theme is given a randomized name.\n\n This command returns a [preview link](https://help.shopify.com/manual/online-store/themes/adding-themes#share-a-theme-preview-with-others) that you can share with others.", "enableJsonFlag": false, "flags": { diff --git a/packages/theme/src/cli/commands/theme/share.ts b/packages/theme/src/cli/commands/theme/share.ts index b02a57f2a0c..ceea5f25070 100644 --- a/packages/theme/src/cli/commands/theme/share.ts +++ b/packages/theme/src/cli/commands/theme/share.ts @@ -78,10 +78,6 @@ export default class Share extends ThemeCommand { return result ?? (flags.json && multiEnvironment ? {status: 'skipped', reason: 'unsafe-directory'} : undefined) } - protected collectsEnvironmentResults(flags: {json?: boolean}): boolean { - return Boolean(flags.json) - } - protected renderEnvironmentResults(results: ThemeEnvironmentResult[]): void { renderThemeShareEnvironmentResults(results) }