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 @@ -9046,6 +9046,15 @@
"isOptional": true,
"environmentValue": "SHOPIFY_FLAG_ENVIRONMENT"
},
{
"filePath": "docs-shopify.dev/commands/interfaces/theme-rename.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-rename.interface.ts",
"syntaxKind": "PropertySignature",
Expand Down Expand Up @@ -9083,7 +9092,7 @@
"environmentValue": "SHOPIFY_FLAG_THEME_ID"
}
],
"value": "export interface themerename {\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 * Rename your 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 * Print the command's JSON schemas.\n * @environment SHOPIFY_FLAG_JSON_SCHEMA\n */\n '--json-schema'?: ''\n\n /**\n * Rename 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 * The new name for the theme. Required if non interactive.\n * @environment SHOPIFY_FLAG_NEW_NAME\n */\n '-n, --name <value>'?: string\n\n /**\n * Disable color output.\n * @environment SHOPIFY_FLAG_NO_COLOR\n */\n '--no-color'?: ''\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 themerename {\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 * Rename your 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 * 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 * Rename 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 * The new name for the theme. Required if non interactive.\n * @environment SHOPIFY_FLAG_NEW_NAME\n */\n '-n, --name <value>'?: string\n\n /**\n * Disable color output.\n * @environment SHOPIFY_FLAG_NO_COLOR\n */\n '--no-color'?: ''\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}"
}
},
"themeshare": {
Expand Down
109 changes: 108 additions & 1 deletion packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9337,7 +9337,7 @@ Renames an existing theme.

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

FLAGS
Expand All @@ -9349,6 +9349,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
Rename your remote live theme. Use --development, --live, or --theme in non-interactive environments.
[env: SHOPIFY_FLAG_LIVE]
Expand Down Expand Up @@ -9397,6 +9401,109 @@ DESCRIPTION

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


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

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

```json
{
"anyOf": [
{
"$ref": "#/definitions/ThemeRenameEnvironment/properties/result"
},
{
"type": "object",
"properties": {
"environments": {
"type": "array",
"items": {
"$ref": "#/definitions/ThemeRenameEnvironment"
}
}
},
"required": [
"environments"
],
"additionalProperties": false
}
],
"title": "ThemeRenameResult",
"definitions": {
"RenamedTheme": {
"type": "object",
"properties": {
"id": {
"type": "number"
},
"name": {
"type": "string"
},
"role": {
"type": "string"
},
"processing": {
"type": "boolean"
},
"createdAtRuntime": {
"type": "boolean"
},
"src": {
"type": "string"
},
"shop": {
"type": "string"
}
},
"required": [
"id",
"name",
"role",
"processing",
"createdAtRuntime",
"shop"
],
"additionalProperties": false
},
"ThemeRenameEnvironment": {
"type": "object",
"properties": {
"environment": {
"type": "string"
},
"result": {
"type": "object",
"properties": {
"status": {
"type": "string",
"const": "success"
},
"originalName": {
"type": "string"
},
"theme": {
"$ref": "#/definitions/RenamedTheme"
}
},
"required": [
"status",
"originalName",
"theme"
],
"additionalProperties": false
}
},
"required": [
"environment",
"result"
],
"additionalProperties": false
}
},
"$schema": "http://json-schema.org/draft-07/schema#"
}
```
```

## `shopify theme share`
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 @@ -11560,7 +11560,7 @@
"args": {
},
"customPluginName": "@shopify/theme",
"description": "Renames a theme in your store.\n\n If no theme is specified, then you're prompted to select the theme that you want to rename from the list of themes in your store.\n ",
"description": "Renames a theme in your store.\n\n If no theme is specified, then you're prompted to select the theme that you want to rename from the list of themes in your store.\n \n\nUse `--json-schema` to print the result, error, and event schemas.\n\nOutput from `--json` conforms to the `ThemeRenameResult` schema.\n\n```json\n{\n \"anyOf\": [\n {\n \"$ref\": \"#/definitions/ThemeRenameEnvironment/properties/result\"\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"environments\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/definitions/ThemeRenameEnvironment\"\n }\n }\n },\n \"required\": [\n \"environments\"\n ],\n \"additionalProperties\": false\n }\n ],\n \"title\": \"ThemeRenameResult\",\n \"definitions\": {\n \"RenamedTheme\": {\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 \"createdAtRuntime\": {\n \"type\": \"boolean\"\n },\n \"src\": {\n \"type\": \"string\"\n },\n \"shop\": {\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"id\",\n \"name\",\n \"role\",\n \"processing\",\n \"createdAtRuntime\",\n \"shop\"\n ],\n \"additionalProperties\": false\n },\n \"ThemeRenameEnvironment\": {\n \"type\": \"object\",\n \"properties\": {\n \"environment\": {\n \"type\": \"string\"\n },\n \"result\": {\n \"type\": \"object\",\n \"properties\": {\n \"status\": {\n \"type\": \"string\",\n \"const\": \"success\"\n },\n \"originalName\": {\n \"type\": \"string\"\n },\n \"theme\": {\n \"$ref\": \"#/definitions/RenamedTheme\"\n }\n },\n \"required\": [\n \"status\",\n \"originalName\",\n \"theme\"\n ],\n \"additionalProperties\": false\n }\n },\n \"required\": [\n \"environment\",\n \"result\"\n ],\n \"additionalProperties\": false\n }\n },\n \"$schema\": \"http://json-schema.org/draft-07/schema#\"\n}\n```",
"descriptionWithMarkdown": "Renames a theme in your store.\n\n If no theme is specified, then you're prompted to select the theme that you want to rename from the list of themes in your store.\n ",
"enableJsonFlag": false,
"flags": {
Expand Down Expand Up @@ -11589,6 +11589,15 @@
"name": "environment",
"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 @@ -51,7 +51,6 @@ const commandExceptions = [
'packages/theme/src/cli/commands/theme/profile.ts',
'packages/theme/src/cli/commands/theme/pull.ts',
'packages/theme/src/cli/commands/theme/push.ts',
'packages/theme/src/cli/commands/theme/rename.ts',
'packages/theme/src/cli/commands/theme/share.ts',

// Streaming commands without a single finite result.
Expand Down
166 changes: 166 additions & 0 deletions packages/theme/src/cli/commands/theme/rename.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import Rename from './rename.js'
import {themeRenameJsonOutputSchema} from '../../services/rename/types.js'
import {findOrSelectTheme} from '../../utilities/theme-selector.js'
import {Config} from '@oclif/core'
import {ensureAuthenticatedThemes} from '@shopify/cli-kit/node/session'
import {themeUpdate} from '@shopify/cli-kit/node/themes/api'
import {withCapturedStandardStreams} from '@shopify/cli-kit/node/testing/output'
import {runWithCommandEventsForCommand} from '@shopify/cli-kit/node/command-events'
import {inTemporaryDirectory, writeFile} from '@shopify/cli-kit/node/fs'
import {joinPath} from '@shopify/cli-kit/node/path'
import {outputWarn} from '@shopify/cli-kit/node/output'
import {describe, expect, test, vi} from 'vitest'

vi.mock('@shopify/cli-kit/node/session')
vi.mock('@shopify/cli-kit/node/themes/api')
vi.mock('@shopify/cli-kit/node/metadata')
vi.mock('../../utilities/theme-selector.js')
vi.mock('../../utilities/theme-store.js', () => ({ensureThemeStore: ({store}: {store: string}) => store}))

const originalTheme = {id: 1, name: 'Original', role: 'unpublished', processing: false, createdAtRuntime: false}
const renamedTheme = {...originalTheme, name: 'Renamed Theme'}
const store = 'test.myshopify.com'

async function run(argv: string[]) {
const config = new Config({root: __dirname})
await config.load()
vi.mocked(ensureAuthenticatedThemes).mockImplementation(async (storeFqdn) => ({token: 'token', storeFqdn}))
return runWithCommandEventsForCommand(argv, () => new Rename(argv, config).run())
}

async function inEnvironments(run: () => Promise<void>) {
await inTemporaryDirectory(async (directory) => {
await writeFile(
joinPath(directory, 'shopify.theme.toml'),
`
[environments.first]
store = "first.myshopify.com"
password = "token"
[environments.second]
store = "second.myshopify.com"
password = "token"
`,
)
const cwd = vi.spyOn(process, 'cwd').mockReturnValue(directory)
try {
await run()
} finally {
cwd.mockRestore()
}
})
}

describe('theme rename JSON output', () => {
test('exposes its schema and JSON flag in help', () => {
expect(Rename.jsonOutputSchema).toBe(themeRenameJsonOutputSchema)
expect(Rename.flags.json).toBeDefined()
expect(Rename.description).toContain('ThemeRenameResult')
})

test.each([undefined, '', 'https://example.com/theme.zip'])(
'returns the updated theme and omits missing src (%s)',
async (src) => {
vi.mocked(findOrSelectTheme).mockResolvedValue(originalTheme)
vi.mocked(themeUpdate).mockResolvedValue({...renamedTheme, src})
await withCapturedStandardStreams(async ({stdout, stderr}) => {
await run(['--store', store, '--theme', '1', '--name', 'Renamed Theme', '--json'])
expect(JSON.parse(stdout())).toEqual({
status: 'success',
originalName: 'Original',
theme: {...renamedTheme, ...(src === undefined ? {} : {src}), shop: store},
})
expect(stderr()).toBe('')
})
},
)

test('routes diagnostics to stderr', async () => {
vi.mocked(findOrSelectTheme).mockResolvedValue(originalTheme)
vi.mocked(themeUpdate).mockImplementation(async () => {
outputWarn('Retrying request')
return renamedTheme
})
await withCapturedStandardStreams(async ({stdout, stderr}) => {
await run(['--store', store, '--theme', '1', '--name', 'Renamed Theme', '--json'])
expect(JSON.parse(stdout()).theme.name).toBe('Renamed Theme')
expect(JSON.parse(stderr())).toMatchObject({type: 'diagnostic', level: 'warning', message: 'Retrying request'})
})
})

test('leaves execution failures to the shared error handler without writing a result', async () => {
vi.mocked(findOrSelectTheme).mockResolvedValue(originalTheme)
vi.mocked(themeUpdate).mockRejectedValue(new Error('Renameing failed'))
await withCapturedStandardStreams(async ({stdout}) => {
await expect(run(['--store', store, '--theme', '1', '--name', 'Renamed Theme', '--json'])).rejects.toThrow(
'Renameing failed',
)
expect(stdout()).toBe('')
})
})

test.each(['none', 'partial', 'all'])('collects one document in configured order (%s failures)', async (failures) => {
vi.mocked(findOrSelectTheme).mockResolvedValue(originalTheme)
vi.mocked(themeUpdate).mockImplementation(async (_id, _params, session) => {
if (failures === 'all' || (failures === 'partial' && session.storeFqdn.startsWith('first.'))) {
throw new Error('Renameing failed')
}
return renamedTheme
})
const exitCode = process.exitCode
await inEnvironments(async () => {
await withCapturedStandardStreams(async ({stdout, stderr}) => {
await run([
'--environment',
'first',
'--environment',
'second',
'--theme',
'1',
'--name',
'Renamed Theme',
'--json',
])
const environments: string[] = []
if (failures === 'none') environments.push('first')
if (failures !== 'all') environments.push('second')
expect(JSON.parse(stdout())).toEqual({
environments: environments.map((environment) => ({
environment,
result: {
status: 'success',
originalName: 'Original',
theme: {...renamedTheme, shop: `${environment}.myshopify.com`},
},
})),
})
if (failures === 'none') {
expect(stderr()).toBe('')
} else {
const errors = stderr()
.trim()
.split('\n')
.map((line) => JSON.parse(line))
expect(errors).toHaveLength(failures === 'all' ? 2 : 1)
expect(errors[0]).toMatchObject({type: 'diagnostic', level: 'error', code: 'theme-environment-failed'})
}
expect(process.exitCode).toBe(exitCode)
})
})
})

test.each([
{status: 'success', originalName: 'Original', theme: {...renamedTheme, id: '1', shop: store}},
{status: 'success', originalName: 'Original', theme: {...renamedTheme, shop: null}},
{status: 'success', originalName: 'Original', theme: {...renamedTheme, src: false, shop: store}},
{
environments: [
{
environment: 'first',
result: {status: 'success', originalName: 'Original', theme: {...renamedTheme, role: null, shop: store}},
},
],
},
])('rejects malformed results %#', (result) => {
expect(() => themeRenameJsonOutputSchema.validate(result)).toThrow()
})
})
Loading
Loading