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
11 changes: 10 additions & 1 deletion docs-shopify.dev/generated/generated_docs_data_v2.json
Original file line number Diff line number Diff line change
Expand Up @@ -8709,6 +8709,15 @@
"isOptional": true,
"environmentValue": "SHOPIFY_FLAG_ENVIRONMENT"
},
{
"filePath": "docs-shopify.dev/commands/interfaces/theme-pull.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-pull.interface.ts",
"syntaxKind": "PropertySignature",
Expand Down Expand Up @@ -8764,7 +8773,7 @@
"environmentValue": "SHOPIFY_FLAG_IGNORE"
}
],
"value": "export interface themepull {\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 * Pull theme files from your remote development theme. Use --development, --live, or --theme in non-interactive environments.\n * @environment SHOPIFY_FLAG_DEVELOPMENT\n */\n '-d, --development'?: ''\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 * Skip downloading the specified files (Multiple flags allowed). Wrap the value in double quotes if you're using wildcards.\n * @environment SHOPIFY_FLAG_IGNORE\n */\n '-x, --ignore <value>'?: string\n\n /**\n * Print the command's JSON schemas.\n * @environment SHOPIFY_FLAG_JSON_SCHEMA\n */\n '--json-schema'?: ''\n\n /**\n * Pull theme files from your remote live theme. Use --development, --live, or --theme in non-interactive environments.\n * @environment SHOPIFY_FLAG_LIVE\n */\n '-l, --live'?: ''\n\n /**\n * Disable color output.\n * @environment SHOPIFY_FLAG_NO_COLOR\n */\n '--no-color'?: ''\n\n /**\n * Prevent deleting local files that don't exist remotely.\n * @environment SHOPIFY_FLAG_NODELETE\n */\n '-n, --nodelete'?: ''\n\n /**\n * Download only the specified files (Multiple flags allowed). Wrap the value in double quotes if you're using wildcards.\n * @environment SHOPIFY_FLAG_ONLY\n */\n '-o, --only <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 * 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. Use --development, --live, or --theme in non-interactive environments.\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 themepull {\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 * Pull theme files from your remote development theme. Use --development, --live, or --theme in non-interactive environments.\n * @environment SHOPIFY_FLAG_DEVELOPMENT\n */\n '-d, --development'?: ''\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 * Skip downloading the specified files (Multiple flags allowed). Wrap the value in double quotes if you're using wildcards.\n * @environment SHOPIFY_FLAG_IGNORE\n */\n '-x, --ignore <value>'?: string\n\n /**\n * Output the result as JSON. Automatically disables color output.\n * @environment SHOPIFY_FLAG_JSON\n */\n '-j, --json'?: ''\n\n /**\n * Print the command's JSON schemas.\n * @environment SHOPIFY_FLAG_JSON_SCHEMA\n */\n '--json-schema'?: ''\n\n /**\n * Pull theme files from your remote live theme. Use --development, --live, or --theme in non-interactive environments.\n * @environment SHOPIFY_FLAG_LIVE\n */\n '-l, --live'?: ''\n\n /**\n * Disable color output.\n * @environment SHOPIFY_FLAG_NO_COLOR\n */\n '--no-color'?: ''\n\n /**\n * Prevent deleting local files that don't exist remotely.\n * @environment SHOPIFY_FLAG_NODELETE\n */\n '-n, --nodelete'?: ''\n\n /**\n * Download only the specified files (Multiple flags allowed). Wrap the value in double quotes if you're using wildcards.\n * @environment SHOPIFY_FLAG_ONLY\n */\n '-o, --only <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 * 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. Use --development, --live, or --theme in non-interactive environments.\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}"
}
},
"themepush": {
Expand Down
102 changes: 101 additions & 1 deletion packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9120,7 +9120,7 @@ Download your remote theme files locally.

```
USAGE
$ shopify theme pull [--auth-alias <value>] [-d] [-e <value>...] [-x <value>...] [--json-schema] [-l]
$ shopify theme pull [--auth-alias <value>] [-d] [-e <value>...] [-x <value>...] [-j] [--json-schema] [-l]
[--no-color] [-n] [-o <value>...] [--password <value>] [--path <value>] [-s <value>] [-t <value>] [--verbose]

FLAGS
Expand All @@ -9133,6 +9133,10 @@ FLAGS
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]

-l, --live
Pull theme files from your remote live theme. Use --development, --live, or --theme in non-interactive environments.
[env: SHOPIFY_FLAG_LIVE]
Expand Down Expand Up @@ -9190,6 +9194,102 @@ DESCRIPTION
Retrieves theme files from Shopify.

If no theme is specified, then you're prompted to select the theme to pull from the list of the themes in your store.

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

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

```json
{
"anyOf": [
{
"type": "object",
"properties": {
"environment": {
"type": "string"
},
"path": {
"type": "string"
},
"theme": {
"$ref": "#/definitions/ThemePullTheme"
}
},
"required": [
"path",
"theme"
],
"additionalProperties": false
},
{
"type": "array",
"items": {
"type": "object",
"properties": {
"environment": {
"type": "string"
},
"path": {
"$ref": "#/definitions/ThemePullResult/anyOf/0/properties/path"
},
"theme": {
"$ref": "#/definitions/ThemePullTheme"
}
},
"required": [
"environment",
"path",
"theme"
],
"additionalProperties": false
}
}
],
"title": "ThemePullResult",
"definitions": {
"ThemePullTheme": {
"type": "object",
"properties": {
"id": {
"type": "number"
},
"name": {
"type": "string"
},
"role": {
"type": "string"
},
"processing": {
"type": "boolean"
},
"src": {
"type": "string"
},
"shop": {
"type": "string"
},
"editor_url": {
"type": "string"
},
"preview_url": {
"type": "string"
}
},
"required": [
"id",
"name",
"role",
"processing",
"shop",
"editor_url",
"preview_url"
],
"additionalProperties": false
}
},
"$schema": "http://json-schema.org/draft-07/schema#"
}
```
```

## `shopify theme push`
Expand Down
11 changes: 10 additions & 1 deletion packages/cli/oclif.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -11179,7 +11179,7 @@
"args": {
},
"customPluginName": "@shopify/theme",
"description": "Retrieves theme files from Shopify.\n\nIf no theme is specified, then you're prompted to select the theme to pull from the list of the themes in your store.",
"description": "Retrieves theme files from Shopify.\n\nIf no theme is specified, then you're prompted to select the theme to pull from the list of the themes in your store.\n\nUse `--json-schema` to print the result, error, and event schemas.\n\nOutput from `--json` conforms to the `ThemePullResult` schema.\n\n```json\n{\n \"anyOf\": [\n {\n \"type\": \"object\",\n \"properties\": {\n \"environment\": {\n \"type\": \"string\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"theme\": {\n \"$ref\": \"#/definitions/ThemePullTheme\"\n }\n },\n \"required\": [\n \"path\",\n \"theme\"\n ],\n \"additionalProperties\": false\n },\n {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"environment\": {\n \"type\": \"string\"\n },\n \"path\": {\n \"$ref\": \"#/definitions/ThemePullResult/anyOf/0/properties/path\"\n },\n \"theme\": {\n \"$ref\": \"#/definitions/ThemePullTheme\"\n }\n },\n \"required\": [\n \"environment\",\n \"path\",\n \"theme\"\n ],\n \"additionalProperties\": false\n }\n }\n ],\n \"title\": \"ThemePullResult\",\n \"definitions\": {\n \"ThemePullTheme\": {\n \"type\": \"object\",\n \"properties\": {\n \"id\": {\n \"type\": \"number\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"role\": {\n \"type\": \"string\"\n },\n \"processing\": {\n \"type\": \"boolean\"\n },\n \"src\": {\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 },\n \"required\": [\n \"id\",\n \"name\",\n \"role\",\n \"processing\",\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": "Retrieves theme files from Shopify.\n\nIf no theme is specified, then you're prompted to select the theme to pull from the list of the themes in your store.",
"enableJsonFlag": false,
"flags": {
Expand Down Expand Up @@ -11226,6 +11226,15 @@
"name": "ignore",
"type": "option"
},
"json": {
"allowNo": false,
"char": "j",
"description": "Output the result as JSON. Automatically disables color output.",
"env": "SHOPIFY_FLAG_JSON",
"hidden": false,
"name": "json",
"type": "boolean"
},
"json-schema": {
"allowNo": false,
"description": "Print the command's JSON schemas.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,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/pull.ts',
'packages/theme/src/cli/commands/theme/share.ts',

// Streaming commands without a single finite result.
Expand Down
73 changes: 73 additions & 0 deletions packages/theme/src/cli/commands/theme/pull.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import Pull from './pull.js'
import {executeThemePull} from '../../services/pull.js'
import {themePullJsonOutputSchema} from '../../services/pull/types.js'
import {withCapturedStandardStreams} from '@shopify/cli-kit/node/testing/output'
import {describe, expect, test, vi} from 'vitest'
import {Config} from '@oclif/core'
import {AbortError} from '@shopify/cli-kit/node/error'

vi.mock('../../services/pull.js')
const session = {storeFqdn: 'test.myshopify.com', token: 'token'}

describe('theme pull JSON', () => {
test('exposes the schema in help and retains JSON and inherited flags', () => {
expect(Pull.jsonOutputSchema).toBe(themePullJsonOutputSchema)
expect(Pull.description).toContain('ThemePullResult')
expect(Pull.flags.json).toBeDefined()
expect(Pull.baseFlags).toHaveProperty('json-schema')
})

test('executes and writes the result through the real encoder', async () => {
vi.mocked(executeThemePull).mockResolvedValue({
theme: {
id: 1,
name: 'Theme',
role: 'unpublished',
processing: false,
shop: session.storeFqdn,
editor_url: 'editor',
preview_url: 'preview',
},
path: '/theme',
})
const command = new Pull([], new Config({root: '.'}))
await withCapturedStandardStreams(async ({stdout, stderr}) => {
await command.command({json: true} as never, session, false)

expect(JSON.parse(stdout())).toEqual({
path: '/theme',
theme: {
id: 1,
name: 'Theme',
role: 'unpublished',
processing: false,
shop: session.storeFqdn,
editor_url: 'editor',
preview_url: 'preview',
},
})
expect(stderr()).toBe('')
})
})

test('retains cancellation without emitting a success result', async () => {
vi.mocked(executeThemePull).mockResolvedValue(undefined)
const command = new Pull([], new Config({root: '.'}))
await withCapturedStandardStreams(async ({stdout}) => {
await command.command({json: true} as never, session, false)

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

test('propagates download failures without writing a result', async () => {
const failure = new AbortError('download failed')
vi.mocked(executeThemePull).mockRejectedValueOnce(failure)
const command = new Pull([], new Config({root: '.'}))
await withCapturedStandardStreams(async ({stdout}) => {
await expect(command.command({json: true} as never, session, false)).rejects.toBe(failure)

expect(stdout()).toBe('')
})
})
})
28 changes: 25 additions & 3 deletions packages/theme/src/cli/commands/theme/pull.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import {globFlags, themeFlags} from '../../flags.js'
import ThemeCommand, {RequiredFlags} from '../../utilities/theme-command.js'
import {pull} from '../../services/pull.js'
import {globalFlags} from '@shopify/cli-kit/node/cli'
import {executeThemePull} from '../../services/pull.js'
import {renderThemePullResult, renderThemePullEnvironmentResults} from '../../services/pull/result.js'
import {themePullJsonOutputSchema} from '../../services/pull/types.js'
import {globalFlags, jsonFlag} from '@shopify/cli-kit/node/cli'
import {Flags} from '@oclif/core'
import {recordTiming} from '@shopify/cli-kit/node/analytics'
import {InferredFlags} from '@oclif/core/interfaces'
Expand All @@ -12,6 +14,10 @@ import type {NonTTYFlagRequirement} from '@shopify/cli-kit/node/base-command'

type PullFlags = InferredFlags<typeof Pull.flags>
export default class Pull extends ThemeCommand {
static get jsonOutputSchema() {
return themePullJsonOutputSchema
}

static summary = 'Download your remote theme files locally.'

static descriptionWithMarkdown = `Retrieves theme files from Shopify.
Expand All @@ -22,6 +28,7 @@ If no theme is specified, then you're prompted to select the theme to pull from

static flags = {
...globalFlags,
...jsonFlag,
...themeFlags,
...globFlags('download'),
theme: Flags.string({
Expand Down Expand Up @@ -69,8 +76,23 @@ If no theme is specified, then you're prompted to select the theme to pull from
context?: {stdout?: Writable; stderr?: Writable},
) {
recordTiming('theme-command:pull')
await pull({...flags, noColor: flags['no-color']}, adminSession, multiEnvironment, context)
const result = await executeThemePull(
{...flags, noColor: flags['no-color']},
adminSession,
multiEnvironment,
context,
)
if (result && !(flags.json && multiEnvironment)) renderThemePullResult(result, flags.json ? 'json' : 'text')
recordTiming('theme-command:pull')
return result
}

protected collectsEnvironmentResults(flags: {json?: boolean}): boolean {
return Boolean(flags.json)
}

protected renderEnvironmentResults(results: {environment: string; result: unknown}[]): void {
renderThemePullEnvironmentResults(results)
}

protected storeAuthScopes(): string[] {
Expand Down
Loading
Loading