Skip to content

Commit c24eece

Browse files
committed
Add ability to set theme name on push
1 parent 8966243 commit c24eece

10 files changed

Lines changed: 95 additions & 10 deletions

File tree

.changeset/puny-plants-allow.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@shopify/cli-kit': minor
3+
'@shopify/theme': minor
4+
'@shopify/cli': minor
5+
---
6+
7+
Add `--development-context` flag to `theme push`

docs-shopify.dev/commands/interfaces/theme-push.interface.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ export interface themepush {
4242
*/
4343
'-l, --live'?: ''
4444

45+
/**
46+
* The name for the theme. Will always create a new theme.
47+
* @environment SHOPIFY_FLAG_NAME
48+
*/
49+
'--name <value>'?: string
50+
4551
/**
4652
* Disable color output.
4753
* @environment SHOPIFY_FLAG_NO_COLOR

docs-shopify.dev/generated/generated_docs_data.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6884,6 +6884,15 @@
68846884
"isOptional": true,
68856885
"environmentValue": "SHOPIFY_FLAG_LISTING"
68866886
},
6887+
{
6888+
"filePath": "docs-shopify.dev/commands/interfaces/theme-push.interface.ts",
6889+
"syntaxKind": "PropertySignature",
6890+
"name": "--name <value>",
6891+
"value": "string",
6892+
"description": "The name for the theme. Will always create a new theme.",
6893+
"isOptional": true,
6894+
"environmentValue": "SHOPIFY_FLAG_NAME"
6895+
},
68876896
{
68886897
"filePath": "docs-shopify.dev/commands/interfaces/theme-push.interface.ts",
68896898
"syntaxKind": "PropertySignature",
@@ -7038,7 +7047,7 @@
70387047
"environmentValue": "SHOPIFY_FLAG_IGNORE"
70397048
}
70407049
],
7041-
"value": "export interface themepush {\n /**\n * Allow push to a live theme.\n * @environment SHOPIFY_FLAG_ALLOW_LIVE\n */\n '-a, --allow-live'?: ''\n\n /**\n * Push theme files from your remote development theme.\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 uploading 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.\n * @environment SHOPIFY_FLAG_JSON\n */\n '-j, --json'?: ''\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 <value>'?: string\n\n /**\n * Push theme files from your remote live theme.\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 remote files that don't exist locally.\n * @environment SHOPIFY_FLAG_NODELETE\n */\n '-n, --nodelete'?: ''\n\n /**\n * Upload 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 * Publish as the live theme after uploading.\n * @environment SHOPIFY_FLAG_PUBLISH\n */\n '-p, --publish'?: ''\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 * Require theme check to pass without errors before pushing. Warnings are allowed.\n * @environment SHOPIFY_FLAG_STRICT_PUSH\n */\n '--strict'?: ''\n\n /**\n * Theme ID or name of the remote theme.\n * @environment SHOPIFY_FLAG_THEME_ID\n */\n '-t, --theme <value>'?: string\n\n /**\n * Create a new unpublished theme and push to it.\n * @environment SHOPIFY_FLAG_UNPUBLISHED\n */\n '-u, --unpublished'?: ''\n\n /**\n * Increase the verbosity of the output.\n * @environment SHOPIFY_FLAG_VERBOSE\n */\n '--verbose'?: ''\n}"
7050+
"value": "export interface themepush {\n /**\n * Allow push to a live theme.\n * @environment SHOPIFY_FLAG_ALLOW_LIVE\n */\n '-a, --allow-live'?: ''\n\n /**\n * Push theme files from your remote development theme.\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 uploading 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.\n * @environment SHOPIFY_FLAG_JSON\n */\n '-j, --json'?: ''\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 <value>'?: string\n\n /**\n * Push theme files from your remote live theme.\n * @environment SHOPIFY_FLAG_LIVE\n */\n '-l, --live'?: ''\n\n /**\n * The name for the theme. Will always create a new theme.\n * @environment SHOPIFY_FLAG_NAME\n */\n '--name <value>'?: string\n\n /**\n * Disable color output.\n * @environment SHOPIFY_FLAG_NO_COLOR\n */\n '--no-color'?: ''\n\n /**\n * Prevent deleting remote files that don't exist locally.\n * @environment SHOPIFY_FLAG_NODELETE\n */\n '-n, --nodelete'?: ''\n\n /**\n * Upload 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 * Publish as the live theme after uploading.\n * @environment SHOPIFY_FLAG_PUBLISH\n */\n '-p, --publish'?: ''\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 * Require theme check to pass without errors before pushing. Warnings are allowed.\n * @environment SHOPIFY_FLAG_STRICT_PUSH\n */\n '--strict'?: ''\n\n /**\n * Theme ID or name of the remote theme.\n * @environment SHOPIFY_FLAG_THEME_ID\n */\n '-t, --theme <value>'?: string\n\n /**\n * Create a new unpublished theme and push to it.\n * @environment SHOPIFY_FLAG_UNPUBLISHED\n */\n '-u, --unpublished'?: ''\n\n /**\n * Increase the verbosity of the output.\n * @environment SHOPIFY_FLAG_VERBOSE\n */\n '--verbose'?: ''\n}"
70427051
}
70437052
}
70447053
}

packages/cli-kit/src/public/node/themes/theme-manager.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,28 @@ describe('ThemeManager', () => {
9292
expect(result).toEqual(mockTheme)
9393
expect(manager.getStoredThemeId()).toBe('123')
9494
})
95+
96+
test('always creates a new theme when name is provided', async () => {
97+
// Given
98+
manager.setThemeId('123')
99+
const customTheme = {...mockTheme, name: 'Custom name'}
100+
vi.mocked(themeCreate).mockResolvedValue(customTheme)
101+
102+
// When
103+
const result = await manager.findOrCreate('Custom name')
104+
105+
// Then
106+
expect(fetchTheme).not.toHaveBeenCalled()
107+
expect(themeCreate).toHaveBeenCalledWith(
108+
{
109+
name: 'Custom name',
110+
role: DEVELOPMENT_THEME_ROLE,
111+
},
112+
session,
113+
)
114+
expect(result).toEqual(customTheme)
115+
expect(manager.getStoredThemeId()).toBe('123')
116+
})
95117
})
96118

97119
describe('fetch', () => {

packages/cli-kit/src/public/node/themes/theme-manager.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {fetchTheme, themeCreate} from './api.js'
1+
import {fetchTheme, findThemeByName, themeCreate} from './api.js'
22
import {Theme} from './types.js'
33
import {DEVELOPMENT_THEME_ROLE, Role} from './utils.js'
44
import {generateThemeName} from '../../../private/node/themes/generate-theme-name.js'
@@ -13,19 +13,24 @@ export abstract class ThemeManager {
1313

1414
constructor(protected adminSession: AdminSession) {}
1515

16-
async findOrCreate(): Promise<Theme> {
17-
let theme = await this.fetch()
16+
async findOrCreate(name?: string): Promise<Theme> {
17+
let theme = await this.fetch(name)
1818
if (!theme) {
19-
theme = await this.create()
19+
theme = await this.create(DEVELOPMENT_THEME_ROLE, name)
2020
}
2121
return theme
2222
}
2323

24-
async fetch() {
25-
if (!this.themeId) {
24+
async fetch(name?: string) {
25+
if (!this.themeId && !name) {
2626
return
2727
}
28-
const theme = await fetchTheme(parseInt(this.themeId, 10), this.adminSession)
28+
29+
const theme = name
30+
? await findThemeByName(name, this.adminSession)
31+
: // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
32+
await fetchTheme(parseInt(this.themeId!, 10), this.adminSession)
33+
2934
if (!theme) {
3035
this.removeTheme()
3136
}

packages/cli/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2400,6 +2400,7 @@ FLAGS
24002400
quotes if you're using wildcards.
24012401
--listing=<value> The listing preset to use for multi-preset themes. Applies preset files from
24022402
listings/[preset-name] directory.
2403+
--name=<value> The name for the theme. Will always create a new theme.
24032404
--no-color Disable color output.
24042405
--password=<value> Password generated from the Theme Access app or an Admin API token.
24052406
--path=<value> The path where you want to run the command. Defaults to the current working directory.

packages/cli/oclif.manifest.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7134,6 +7134,17 @@
71347134
"name": "live",
71357135
"type": "boolean"
71367136
},
7137+
"name": {
7138+
"description": "The name for the theme. Will always create a new theme.",
7139+
"env": "SHOPIFY_FLAG_NAME",
7140+
"exclusive": [
7141+
"theme"
7142+
],
7143+
"hasDynamicHelp": false,
7144+
"multiple": false,
7145+
"name": "name",
7146+
"type": "option"
7147+
},
71377148
"no-color": {
71387149
"allowNo": false,
71397150
"description": "Disable color output.",

packages/theme/src/cli/commands/theme/push.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,13 @@ export default class Push extends ThemeCommand {
102102
'The listing preset to use for multi-preset themes. Applies preset files from listings/[preset-name] directory.',
103103
env: 'SHOPIFY_FLAG_LISTING',
104104
}),
105+
'development-context': Flags.string({
106+
description:
107+
'Unique identifier for a development theme context (e.g., PR number, branch name). Reuses an existing development theme with this context name, or creates one if none exists.',
108+
env: 'SHOPIFY_FLAG_DEVELOPMENT_CONTEXT',
109+
dependsOn: ['development'],
110+
exclusive: ['theme'],
111+
}),
105112
environment: themeFlags.environment,
106113
}
107114

packages/theme/src/cli/services/push.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,20 @@ describe('createOrSelectTheme', async () => {
292292
expect(setDevelopmentTheme).toHaveBeenCalled()
293293
})
294294

295+
test('creates development theme when development and development-context flags are provided', async () => {
296+
// Given
297+
vi.mocked(themeCreate).mockResolvedValue(buildTheme({id: 1, name: 'Custom name', role: DEVELOPMENT_THEME_ROLE}))
298+
vi.mocked(fetchTheme).mockResolvedValue(undefined)
299+
const flags: PushFlags = {development: true, developmentContext: 'Custom name'}
300+
301+
// When
302+
const theme = await createOrSelectTheme(adminSession, flags)
303+
304+
// Then
305+
expect(theme).toMatchObject({role: DEVELOPMENT_THEME_ROLE, name: 'Custom name'})
306+
expect(setDevelopmentTheme).toHaveBeenCalled()
307+
})
308+
295309
test('creates development theme when development and unpublished flags are provided', async () => {
296310
// Given
297311
vi.mocked(themeCreate).mockResolvedValue(buildTheme({id: 1, name: 'Theme', role: DEVELOPMENT_THEME_ROLE}))

packages/theme/src/cli/services/push.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,9 @@ export interface PushFlags {
113113

114114
/** The listing preset to use for multi-preset themes. */
115115
listing?: string
116+
117+
/** Unique identifier for a development theme context. Reuses an existing development theme with this context name, or creates one if none exists. */
118+
developmentContext?: string
116119
}
117120

118121
/**
@@ -379,11 +382,11 @@ export async function createOrSelectTheme(
379382
flags: PushFlags,
380383
multiEnvironment?: boolean,
381384
): Promise<Theme | undefined> {
382-
const {live, development, unpublished, theme, environment} = flags
385+
const {live, development, unpublished, theme, environment, developmentContext} = flags
383386

384387
if (development) {
385388
const themeManager = new DevelopmentThemeManager(session)
386-
return themeManager.findOrCreate()
389+
return themeManager.findOrCreate(developmentContext)
387390
} else if (unpublished) {
388391
const themeName = theme ?? (await promptThemeName('Name of the new theme'))
389392
return themeCreate(

0 commit comments

Comments
 (0)