From 5a040b694938867c6b2d3f6dc20a2b21e9c4350e Mon Sep 17 00:00:00 2001 From: Julien Poitrin Date: Mon, 23 Jan 2023 13:58:18 +0100 Subject: [PATCH 01/12] Extract the ownership of development themes --- .changeset/nice-radios-lie.md | 6 ++ packages/cli-kit/src/store.ts | 30 +++++++ packages/theme/src/cli/commands/theme/dev.ts | 26 ++++-- packages/theme/src/cli/commands/theme/pull.ts | 16 +++- packages/theme/src/cli/commands/theme/push.ts | 16 +++- packages/theme/src/cli/models/theme.ts | 6 ++ .../theme/src/cli/services/delete.test.ts | 5 ++ packages/theme/src/cli/services/delete.ts | 19 ++++- packages/theme/src/cli/services/info.ts | 7 +- packages/theme/src/cli/services/list.test.ts | 15 +++- packages/theme/src/cli/services/list.ts | 21 +++-- packages/theme/src/cli/services/open.ts | 10 ++- .../development-theme-manager.test.ts | 83 +++++++++++++++++++ .../utilities/development-theme-manager.ts | 82 ++++++++++++++++++ .../generate-development-theme-name.test.ts | 20 +++++ .../generate-development-theme-name.ts | 15 ++++ .../replace-invalid-characters.test.ts | 14 ++++ .../utilities/replace-invalid-characters.ts | 8 ++ .../utilities/theme-selector/filter.test.ts | 16 +--- .../theme/src/cli/utilities/themes-api.ts | 5 ++ 20 files changed, 376 insertions(+), 44 deletions(-) create mode 100644 .changeset/nice-radios-lie.md create mode 100644 packages/theme/src/cli/utilities/development-theme-manager.test.ts create mode 100644 packages/theme/src/cli/utilities/development-theme-manager.ts create mode 100644 packages/theme/src/cli/utilities/generate-development-theme-name.test.ts create mode 100644 packages/theme/src/cli/utilities/generate-development-theme-name.ts create mode 100644 packages/theme/src/cli/utilities/replace-invalid-characters.test.ts create mode 100644 packages/theme/src/cli/utilities/replace-invalid-characters.ts diff --git a/.changeset/nice-radios-lie.md b/.changeset/nice-radios-lie.md new file mode 100644 index 00000000000..ecbd39e09b3 --- /dev/null +++ b/.changeset/nice-radios-lie.md @@ -0,0 +1,6 @@ +--- +'@shopify/cli-kit': patch +'@shopify/theme': patch +--- + +Extract the ownership of development themes diff --git a/packages/cli-kit/src/store.ts b/packages/cli-kit/src/store.ts index a7826b97897..435cba92e29 100644 --- a/packages/cli-kit/src/store.ts +++ b/packages/cli-kit/src/store.ts @@ -107,6 +107,21 @@ export function clearAllAppInfo(): void { store.clearAllAppInfo() } +export function getDevelopmentTheme(): string | undefined { + const store = cliKitStore() + return store.getDevelopmentTheme() +} + +export function setDevelopmentTheme(theme: string): void { + const store = cliKitStore() + store.setDevelopmentTheme(theme) +} + +export function removeDevelopmentTheme(): void { + const store = cliKitStore() + store.removeDevelopmentTheme() +} + export class CLIKitStore extends Conf { getAppInfo(directory: string): CachedAppInfo | undefined { debug(content`Reading cached app information for directory ${token.path(directory)}...`) @@ -182,4 +197,19 @@ export class CLIKitStore extends Conf { debug(content`Removing session store...`) this.set('sessionStore', '') } + + getDevelopmentTheme(): string { + debug(content`Getting development theme...`) + return this.get('developmentTheme') + } + + setDevelopmentTheme(theme: string): void { + debug(content`Setting development theme...`) + this.set('developmentTheme', theme) + } + + removeDevelopmentTheme(): void { + debug(content`Removing development theme...`) + this.set('developmentTheme', '') + } } diff --git a/packages/theme/src/cli/commands/theme/dev.ts b/packages/theme/src/cli/commands/theme/dev.ts index 5ae0e1a3396..0a2294d41dd 100644 --- a/packages/theme/src/cli/commands/theme/dev.ts +++ b/packages/theme/src/cli/commands/theme/dev.ts @@ -1,12 +1,13 @@ import {themeFlags} from '../../flags.js' import {getThemeStore} from '../../utilities/theme-store.js' import ThemeCommand from '../../utilities/theme-command.js' +import {DevelopmentThemeManager} from '../../utilities/development-theme-manager.js' import {Flags} from '@oclif/core' import {globalFlags} from '@shopify/cli-kit/node/cli' import {output} from '@shopify/cli-kit' import {execCLI2} from '@shopify/cli-kit/node/ruby' import {AbortController} from '@shopify/cli-kit/node/abort' -import {ensureAuthenticatedStorefront, ensureAuthenticatedThemes} from '@shopify/cli-kit/node/session' +import {AdminSession, ensureAuthenticatedStorefront, ensureAuthenticatedThemes} from '@shopify/cli-kit/node/session' import {sleep} from '@shopify/cli-kit/node/system' export default class Dev extends ThemeCommand { @@ -96,13 +97,18 @@ export default class Dev extends ThemeCommand { * Every 110 minutes, it will refresh the session token and restart the server. */ async run(): Promise { - const {flags} = await this.parse(Dev) + let {flags} = await this.parse(Dev) + const store = getThemeStore(flags) + const adminSession = await ensureAuthenticatedThemes(store, flags.password, [], true) + const theme = await new DevelopmentThemeManager(adminSession).findOrCreate() + flags = { + ...flags, + theme, + } const flagsToPass = this.passThroughFlags(flags, {allowedFlags: Dev.cli2Flags}) const command = ['theme', 'serve', flags.path, ...flagsToPass] - const store = getThemeStore(flags) - let controller = new AbortController() setInterval(() => { @@ -110,16 +116,20 @@ export default class Dev extends ThemeCommand { controller.abort() controller = new AbortController() // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.execute(store, flags.password, command, controller) + this.execute(adminSession, flags.password, command, controller) }, this.ThemeRefreshTimeoutInMs) // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.execute(store, flags.password, command, controller) + this.execute(adminSession, flags.password, command, controller) } - async execute(store: string, password: string | undefined, command: string[], controller: AbortController) { + async execute( + adminSession: AdminSession, + password: string | undefined, + command: string[], + controller: AbortController, + ) { await sleep(3) - const adminSession = await ensureAuthenticatedThemes(store, password, [], true) const storefrontToken = await ensureAuthenticatedStorefront([], password) return execCLI2(command, {adminSession, storefrontToken, signal: controller.signal}) } diff --git a/packages/theme/src/cli/commands/theme/pull.ts b/packages/theme/src/cli/commands/theme/pull.ts index 2e761d1c476..37cf06be6fc 100644 --- a/packages/theme/src/cli/commands/theme/pull.ts +++ b/packages/theme/src/cli/commands/theme/pull.ts @@ -1,6 +1,7 @@ import {themeFlags} from '../../flags.js' import {getThemeStore} from '../../utilities/theme-store.js' import ThemeCommand from '../../utilities/theme-command.js' +import {DevelopmentThemeManager} from '../../utilities/development-theme-manager.js' import {Flags} from '@oclif/core' import {globalFlags} from '@shopify/cli-kit/node/cli' import {execCLI2} from '@shopify/cli-kit/node/ruby' @@ -56,7 +57,18 @@ export default class Pull extends ThemeCommand { static cli2Flags = ['theme', 'development', 'live', 'nodelete', 'only', 'ignore', 'force'] async run(): Promise { - const {flags} = await this.parse(Pull) + let {flags} = await this.parse(Pull) + const store = getThemeStore(flags) + const adminSession = await ensureAuthenticatedThemes(store, flags.password) + + if (flags.development) { + const theme = await new DevelopmentThemeManager(adminSession).find() + flags = { + ...flags, + development: false, + theme: theme.id.toString(), + } + } let validPath = flags.path if (!isAbsolutePath(validPath)) { @@ -67,8 +79,6 @@ export default class Pull extends ThemeCommand { const command = ['theme', 'pull', validPath, ...flagsToPass] - const store = getThemeStore(flags) - const adminSession = await ensureAuthenticatedThemes(store, flags.password) await execCLI2(command, {adminSession}) } } diff --git a/packages/theme/src/cli/commands/theme/push.ts b/packages/theme/src/cli/commands/theme/push.ts index 4c33248a61b..3f2e9805e0f 100644 --- a/packages/theme/src/cli/commands/theme/push.ts +++ b/packages/theme/src/cli/commands/theme/push.ts @@ -1,6 +1,7 @@ import {themeFlags} from '../../flags.js' import {getThemeStore} from '../../utilities/theme-store.js' import ThemeCommand from '../../utilities/theme-command.js' +import {DevelopmentThemeManager} from '../../utilities/development-theme-manager.js' import {Flags} from '@oclif/core' import {globalFlags} from '@shopify/cli-kit/node/cli' import {execCLI2} from '@shopify/cli-kit/node/ruby' @@ -95,13 +96,22 @@ export default class Push extends ThemeCommand { ] async run(): Promise { - const {flags} = await this.parse(Push) + let {flags} = await this.parse(Push) + const store = getThemeStore(flags) + const adminSession = await ensureAuthenticatedThemes(store, flags.password) + + if (flags.development) { + const theme = await new DevelopmentThemeManager(adminSession).findOrCreate() + flags = { + ...flags, + development: false, + theme, + } + } const flagsToPass = this.passThroughFlags(flags, {allowedFlags: Push.cli2Flags}) const command = ['theme', 'push', flags.path, ...flagsToPass] - const store = getThemeStore(flags) - const adminSession = await ensureAuthenticatedThemes(store, flags.password) await execCLI2(command, {adminSession}) } } diff --git a/packages/theme/src/cli/models/theme.ts b/packages/theme/src/cli/models/theme.ts index 2f3ab221650..d1728ef2342 100644 --- a/packages/theme/src/cli/models/theme.ts +++ b/packages/theme/src/cli/models/theme.ts @@ -1,3 +1,5 @@ +export const DEVELOPMENT_THEME_ROLE = 'development' + export class Theme { constructor(public id: number, public name: string, private _role: string) {} @@ -16,4 +18,8 @@ export class Theme { this._role = _role } } + + get hasDevelopmentRole(): boolean { + return this.role === DEVELOPMENT_THEME_ROLE + } } diff --git a/packages/theme/src/cli/services/delete.test.ts b/packages/theme/src/cli/services/delete.test.ts index 1854fba2a45..20d60bb612e 100644 --- a/packages/theme/src/cli/services/delete.test.ts +++ b/packages/theme/src/cli/services/delete.test.ts @@ -8,6 +8,11 @@ import {renderConfirmationPrompt, renderSuccess, renderWarning} from '@shopify/c vi.mock('@shopify/cli-kit/node/ui') vi.mock('../utilities/themes-api.js') vi.mock('../utilities/theme-selector.js') +vi.mock('../utilities/development-theme-manager.js', () => { + const DevelopmentThemeManager = vi.fn() + DevelopmentThemeManager.prototype.find = () => theme1 + return {DevelopmentThemeManager} +}) const session = { token: 'token', diff --git a/packages/theme/src/cli/services/delete.ts b/packages/theme/src/cli/services/delete.ts index 3b12204c7c1..7f111507d8d 100644 --- a/packages/theme/src/cli/services/delete.ts +++ b/packages/theme/src/cli/services/delete.ts @@ -2,9 +2,11 @@ import {findOrSelectTheme, findThemes} from '../utilities/theme-selector.js' import {Theme} from '../models/theme.js' import {themeComponent, themesComponent} from '../utilities/theme-ui.js' import {deleteTheme} from '../utilities/themes-api.js' +import {DevelopmentThemeManager} from '../utilities/development-theme-manager.js' import {AdminSession} from '@shopify/cli-kit/node/session' import {renderConfirmationPrompt, renderSuccess, renderWarning} from '@shopify/cli-kit/node/ui' import {pluralize} from '@shopify/cli-kit/common/string' +import {store as storage} from '@shopify/cli-kit' export interface DeleteOptions { selectTheme: boolean @@ -14,14 +16,25 @@ export interface DeleteOptions { } export async function deleteThemes(adminSession: AdminSession, options: DeleteOptions) { + let themeIds = options.themes + if (options.development) { + const theme = await new DevelopmentThemeManager(adminSession).find() + themeIds = [theme.id.toString()] + } + const store = adminSession.storeFqdn - const themes = await findThemesByDeleteOptions(adminSession, options) + const themes = await findThemesByDeleteOptions(adminSession, {...options, themes: themeIds, development: false}) if (!options.force && !(await isConfirmed(themes, store))) { return } - themes.map((theme) => deleteTheme(theme.id, adminSession)) + themes.map((theme) => { + if (theme.hasDevelopmentRole) { + storage.removeDevelopmentTheme() + } + return deleteTheme(theme.id, adminSession) + }) renderSuccess({ headline: pluralize( @@ -33,7 +46,7 @@ export async function deleteThemes(adminSession: AdminSession, options: DeleteOp } async function findThemesByDeleteOptions(adminSession: AdminSession, options: DeleteOptions) { - const isSingleThemeSelection = options.selectTheme || options.development || options.themes.length <= 1 + const isSingleThemeSelection = options.selectTheme || options.themes.length <= 1 if (!isSingleThemeSelection) { return findThemes(adminSession, options) diff --git a/packages/theme/src/cli/services/info.ts b/packages/theme/src/cli/services/info.ts index 9f967696c9f..41801eee03b 100644 --- a/packages/theme/src/cli/services/info.ts +++ b/packages/theme/src/cli/services/info.ts @@ -13,7 +13,12 @@ export async function themeInfo(config: {cliVersion: string}): Promise { it('should call the table render function, with correctly formatted data', async () => { + const developmentThemeId = 5 vi.mocked(fetchStoreThemes).mockResolvedValue([ - {id: 1361, name: 'Dawn', role: 'live'}, - {id: 1363, name: 'Studio', role: ''}, + {id: 1, name: 'Theme 1', role: 'live'}, + {id: 2, name: 'Theme 2', role: ''}, + {id: 3, name: 'Theme 3', role: 'development'}, + {id: developmentThemeId, name: 'Theme 5', role: 'development'}, ] as Theme[]) + vi.spyOn(store, 'getDevelopmentTheme').mockReturnValue(developmentThemeId.toString()) await list(session, {}) expect(renderTable).toBeCalledWith({ rows: [ - {id: '#1361', name: 'Dawn', role: '[live]'}, - {id: '#1363', name: 'Studio', role: ''}, + {id: '#1', name: 'Theme 1', role: '[live]'}, + {id: '#2', name: 'Theme 2', role: ''}, + {id: '#3', name: 'Theme 3', role: '[development]'}, + {id: '#5', name: 'Theme 5', role: '[development] [yours]'}, ], columns, }) diff --git a/packages/theme/src/cli/services/list.ts b/packages/theme/src/cli/services/list.ts index 9a7f9127034..b7b51ee8a5d 100644 --- a/packages/theme/src/cli/services/list.ts +++ b/packages/theme/src/cli/services/list.ts @@ -3,6 +3,7 @@ import {ALLOWED_ROLES, fetchStoreThemes, Role} from '../utilities/theme-selector import {Filter, FilterProps, filterThemes} from '../utilities/theme-selector/filter.js' import {renderTable} from '@shopify/cli-kit/node/ui' import {AdminSession} from '@shopify/cli-kit/node/session.js' +import {store as storage} from '@shopify/cli-kit' export interface Options { role?: Role @@ -21,15 +22,25 @@ export async function list(adminSession: AdminSession, options: Options) { }) let storeThemes = await fetchStoreThemes(adminSession) + const developmentTheme = storage.getDevelopmentTheme() if (filter.any()) { storeThemes = filterThemes(store, storeThemes, filter) } - const themes = storeThemes.map(({id, name, role}) => ({ - id: `#${id}`, - name, - role: role ? `[${role}]` : '', - })) + const themes = storeThemes.map(({id, name, role}) => { + let formattedRole = '' + if (role) { + formattedRole = `[${role}]` + if (`${id}` === developmentTheme) { + formattedRole += ' [yours]' + } + } + return { + id: `#${id}`, + name, + role: formattedRole, + } + }) renderTable({rows: themes, columns}) } diff --git a/packages/theme/src/cli/services/open.ts b/packages/theme/src/cli/services/open.ts index ec702fd3672..dacfe09cd53 100644 --- a/packages/theme/src/cli/services/open.ts +++ b/packages/theme/src/cli/services/open.ts @@ -1,6 +1,7 @@ import {findOrSelectTheme} from '../utilities/theme-selector.js' import {themeEditorUrl, themePreviewUrl} from '../utilities/theme-urls.js' import {themeComponent} from '../utilities/theme-ui.js' +import {DevelopmentThemeManager} from '../utilities/development-theme-manager.js' import {openURL} from '@shopify/cli-kit/node/system' import {renderInfo} from '@shopify/cli-kit/node/ui' import {AdminSession} from '@shopify/cli-kit/node/session' @@ -9,12 +10,17 @@ export async function open( adminSession: AdminSession, options: {development: boolean; live: boolean; editor: boolean; theme: string | undefined}, ) { + let themeId = options.theme + if (options.development) { + const theme = await new DevelopmentThemeManager(adminSession).find() + themeId = theme.id.toString() + } + const theme = await findOrSelectTheme(adminSession, { header: 'Select a theme to open', filter: { - development: options.development, live: options.live, - theme: options.theme, + theme: themeId, }, }) diff --git a/packages/theme/src/cli/utilities/development-theme-manager.test.ts b/packages/theme/src/cli/utilities/development-theme-manager.test.ts new file mode 100644 index 00000000000..f966df7a6d8 --- /dev/null +++ b/packages/theme/src/cli/utilities/development-theme-manager.test.ts @@ -0,0 +1,83 @@ +import { + DevelopmentThemeManager, + NO_DEVELOPMENT_THEME_ID_SET, + DEVELOPMENT_THEME_NOT_FOUND, +} from './development-theme-manager.js' +import {Theme} from '../models/theme.js' +import {describe, expect, it, SpyInstance, vi} from 'vitest' + +describe('DevelopmentThemeManager', () => { + const existingId = 200 + const newThemeId = 201 + const onlyLocallyExistingId = 404 + const themeTestDatabase: {[id: number]: Theme | undefined} = { + [existingId]: {id: existingId} as Theme, + [onlyLocallyExistingId]: undefined, + } + let developmentThemeManager: DevelopmentThemeManager + let removeDevelopmentThemeSpy: SpyInstance + + function buildDevelopmentThemeManager(localDevelopmentThemeId?: string) { + const store = 'mystore.myshopify.com' + const token = 'token' + const storage = { + getDevelopmentTheme: () => localDevelopmentThemeId, + setDevelopmentTheme: (theme: string) => undefined, + removeDevelopmentTheme: () => undefined, + } + developmentThemeManager = new DevelopmentThemeManager( + { + storeFqdn: store, + token, + }, + storage, + { + fetchTheme: (id: number) => Promise.resolve(themeTestDatabase[id]), + createTheme: ({name, role}) => Promise.resolve(new Theme(newThemeId, name as string, role as string)), + }, + ) + removeDevelopmentThemeSpy = vi.spyOn(storage, 'removeDevelopmentTheme') + } + + describe('find', () => { + it('should throw Abort if no ID is locally stored', async () => { + buildDevelopmentThemeManager() + await expect(() => developmentThemeManager.find()).rejects.toThrowError(NO_DEVELOPMENT_THEME_ID_SET) + expect(removeDevelopmentThemeSpy).not.toHaveBeenCalled() + }) + + it('should remove locally stored ID and throw Abort if API could not return theme', async () => { + const theme = onlyLocallyExistingId.toString() + buildDevelopmentThemeManager(theme) + await expect(() => developmentThemeManager.find()).rejects.toThrowError(DEVELOPMENT_THEME_NOT_FOUND(theme)) + expect(removeDevelopmentThemeSpy).toHaveBeenCalledOnce() + }) + + it('should return theme if API returns theme with locally stored ID', async () => { + const theme = existingId.toString() + buildDevelopmentThemeManager(theme) + expect(await developmentThemeManager.find()).toEqual(themeTestDatabase[existingId]) + }) + }) + + describe('findOrCreate', () => { + it('should not create a new development theme if API returns theme with locally stored ID', async () => { + const theme = existingId.toString() + buildDevelopmentThemeManager(theme) + expect(await developmentThemeManager.findOrCreate()).toEqual(theme) + }) + + it('should create a new development theme if no ID is locally stored', async () => { + buildDevelopmentThemeManager() + expect(await developmentThemeManager.findOrCreate()).toEqual(newThemeId.toString()) + expect(removeDevelopmentThemeSpy).not.toHaveBeenCalled() + }) + + it('should create a new development theme if locally existing ID points to nowhere', async () => { + const theme = onlyLocallyExistingId.toString() + buildDevelopmentThemeManager(theme) + expect(await developmentThemeManager.findOrCreate()).toEqual(newThemeId.toString()) + expect(removeDevelopmentThemeSpy).toHaveBeenCalledOnce() + }) + }) +}) diff --git a/packages/theme/src/cli/utilities/development-theme-manager.ts b/packages/theme/src/cli/utilities/development-theme-manager.ts new file mode 100644 index 00000000000..76991c99683 --- /dev/null +++ b/packages/theme/src/cli/utilities/development-theme-manager.ts @@ -0,0 +1,82 @@ +import {fetchTheme, createTheme} from './themes-api.js' +import {generateDevelopmentThemeName} from './generate-development-theme-name.js' +import {DEVELOPMENT_THEME_ROLE} from '../models/theme.js' +import {AdminSession} from '@shopify/cli-kit/node/session' +import {AbortError, BugError} from '@shopify/cli-kit/node/error' +import {store as defaultStorage} from '@shopify/cli-kit' + +export const DEVELOPMENT_THEME_NOT_FOUND = (themeId: string) => + `Development theme #${themeId} could not be found. Please create a new development theme.` +export const NO_DEVELOPMENT_THEME_ID_SET = + 'No development theme ID has been set. Please create a development theme first.' + +interface Storage { + getDevelopmentTheme: () => string | undefined + setDevelopmentTheme: (theme: string) => void + removeDevelopmentTheme: () => void +} + +interface ThemesAPI { + fetchTheme: typeof fetchTheme + createTheme: typeof createTheme +} + +export class DevelopmentThemeManager { + private themeId: string | undefined + + constructor( + private adminSession: AdminSession, + private storage: Storage = defaultStorage, + private api: ThemesAPI = { + fetchTheme, + createTheme, + }, + private generateName = generateDevelopmentThemeName, + ) { + this.themeId = storage.getDevelopmentTheme() + } + + async find() { + const theme = await this.fetch() + if (!theme) { + throw new AbortError(this.themeId ? DEVELOPMENT_THEME_NOT_FOUND(this.themeId) : NO_DEVELOPMENT_THEME_ID_SET) + } + return theme + } + + async findOrCreate() { + let theme = await this.fetch() + if (!theme) { + theme = await this.create() + } + return theme.id.toString() + } + + private async fetch() { + if (!this.themeId) { + return + } + const theme = await this.api.fetchTheme(parseInt(this.themeId, 10), this.adminSession) + if (!theme) { + this.storage.removeDevelopmentTheme() + } + return theme + } + + private async create() { + const name = this.generateName() + const role = DEVELOPMENT_THEME_ROLE + const theme = await this.api.createTheme( + { + name, + role, + }, + this.adminSession, + ) + if (!theme) { + throw new BugError(`Could not create theme with name "${name}" and role "${role}"`) + } + this.storage.setDevelopmentTheme(theme.id.toString()) + return theme + } +} diff --git a/packages/theme/src/cli/utilities/generate-development-theme-name.test.ts b/packages/theme/src/cli/utilities/generate-development-theme-name.test.ts new file mode 100644 index 00000000000..69fe6aab102 --- /dev/null +++ b/packages/theme/src/cli/utilities/generate-development-theme-name.test.ts @@ -0,0 +1,20 @@ +import {generateDevelopmentThemeName} from './generate-development-theme-name.js' +import {describe, expect, it} from 'vitest' + +describe('generateName', () => { + function randomBytes() { + return Buffer.from([1, 2, 3]) + } + + it('should not truncate if the theme name is below the API limit', () => { + expect(generateDevelopmentThemeName(() => 'Mac-Book-Pro.My-Router', randomBytes)).toEqual( + 'Development (010203-Mac-Book-Pro)', + ) + }) + + it('should truncate if the theme name is above the API limit', () => { + expect( + generateDevelopmentThemeName(() => 'theme-dev-lan-very-long-name-that-will-be-truncated', randomBytes), + ).toEqual('Development (010203-theme-dev-lan-very-long-name-t)') + }) +}) diff --git a/packages/theme/src/cli/utilities/generate-development-theme-name.ts b/packages/theme/src/cli/utilities/generate-development-theme-name.ts new file mode 100644 index 00000000000..f1bd38e0310 --- /dev/null +++ b/packages/theme/src/cli/utilities/generate-development-theme-name.ts @@ -0,0 +1,15 @@ +import {replaceInvalidCharacters} from './replace-invalid-characters.js' +import {hostname as osHostName} from 'os' +import {randomBytes as cryptoRandomBytes} from 'crypto' + +const API_NAME_LIMIT = 50 + +export function generateDevelopmentThemeName(hostName = osHostName, randomBytes = cryptoRandomBytes): string { + const hostNameWithoutDomain = hostName().split('.')[0]! + const hash = randomBytes(3).toString('hex') + + const name = 'Development ()' + const hostNameCharacterLimit = API_NAME_LIMIT - name.length - hash.length + const identifier = replaceInvalidCharacters(`${hash}-${hostNameWithoutDomain.substring(0, hostNameCharacterLimit)}`) + return `Development (${identifier})` +} diff --git a/packages/theme/src/cli/utilities/replace-invalid-characters.test.ts b/packages/theme/src/cli/utilities/replace-invalid-characters.test.ts new file mode 100644 index 00000000000..f878780fdd0 --- /dev/null +++ b/packages/theme/src/cli/utilities/replace-invalid-characters.test.ts @@ -0,0 +1,14 @@ +import {replaceInvalidCharacters} from './replace-invalid-characters.js' +import {describe, expect, it} from 'vitest' + +describe('replaceInvalidCharacters', () => { + it('should replace unused ASCII characters', () => { + const asciiStringChar = '\x8F' + expect(replaceInvalidCharacters(`theme-dev-${asciiStringChar}.lan`)).toEqual('theme-dev---lan') + }) + + it('should not replace non-latin letters and marks', () => { + const hostName = 'ÇaVaこんにちはПривіт' + expect(replaceInvalidCharacters(hostName)).toEqual(hostName) + }) +}) diff --git a/packages/theme/src/cli/utilities/replace-invalid-characters.ts b/packages/theme/src/cli/utilities/replace-invalid-characters.ts new file mode 100644 index 00000000000..0d4b09be948 --- /dev/null +++ b/packages/theme/src/cli/utilities/replace-invalid-characters.ts @@ -0,0 +1,8 @@ +export function replaceInvalidCharacters(identifier: string) { + const findAllMatches = 'g' + const enablesUnicodeSupport = 'u' + return identifier.replace( + new RegExp(/[^\p{Letter}\p{Number}\p{Mark}-]/, `${findAllMatches}${enablesUnicodeSupport}`), + '-', + ) +} diff --git a/packages/theme/src/cli/utilities/theme-selector/filter.test.ts b/packages/theme/src/cli/utilities/theme-selector/filter.test.ts index ae4b4e1440a..3378bb5bc67 100644 --- a/packages/theme/src/cli/utilities/theme-selector/filter.test.ts +++ b/packages/theme/src/cli/utilities/theme-selector/filter.test.ts @@ -30,21 +30,7 @@ describe('filterThemes', () => { expect(filtered[0]!.name).toBe('theme (3)') }) - test('filters the development theme', async () => { - /** - * TODO: Return _your_ development theme. - * - * CLI2 creates the development theme and persists the ID - * on a PStore file. Thus, only the CLI2 can differentiate - * between _your_ development theme and the others. - * - * Currently, this filter just returns all development - * themes, but it should only return _yours_. - * - * As soon as the development gets created at the CLI3 level - * this issue must be fixed. - */ - + test('filters by role', async () => { // Given const filter = new Filter({ development: true, diff --git a/packages/theme/src/cli/utilities/themes-api.ts b/packages/theme/src/cli/utilities/themes-api.ts index 5a57baed983..f038a7cfec2 100644 --- a/packages/theme/src/cli/utilities/themes-api.ts +++ b/packages/theme/src/cli/utilities/themes-api.ts @@ -9,6 +9,11 @@ import {AbortError} from '@shopify/cli-kit/node/error' export type ThemeParams = Partial> +export async function fetchTheme(id: number, session: AdminSession): Promise { + const response = await request('GET', `/themes/${id}`, session, undefined, {fields: 'id'}) + return buildTheme(response.json.theme) +} + export async function fetchThemes(session: AdminSession): Promise { const response = await request('GET', '/themes', session, undefined, {fields: 'id,name,role'}) return buildThemes(response) From b4b3db85d7cf65f1742bd456cbc7b9018637dc2f Mon Sep 17 00:00:00 2001 From: Julien Poitrin Date: Tue, 24 Jan 2023 11:09:55 +0100 Subject: [PATCH 02/12] Changes after code review --- .../development-theme-manager.test.ts | 78 ++++++++++--------- .../utilities/development-theme-manager.ts | 35 ++------- .../generate-development-theme-name.test.ts | 23 +++--- .../generate-development-theme-name.ts | 8 +- 4 files changed, 65 insertions(+), 79 deletions(-) diff --git a/packages/theme/src/cli/utilities/development-theme-manager.test.ts b/packages/theme/src/cli/utilities/development-theme-manager.test.ts index f966df7a6d8..2c9e97adc5a 100644 --- a/packages/theme/src/cli/utilities/development-theme-manager.test.ts +++ b/packages/theme/src/cli/utilities/development-theme-manager.test.ts @@ -3,10 +3,17 @@ import { NO_DEVELOPMENT_THEME_ID_SET, DEVELOPMENT_THEME_NOT_FOUND, } from './development-theme-manager.js' +import {createTheme, fetchTheme} from './themes-api.js' import {Theme} from '../models/theme.js' -import {describe, expect, it, SpyInstance, vi} from 'vitest' +import {beforeEach, describe, expect, it, vi} from 'vitest' +import {store} from '@shopify/cli-kit' + +vi.mock('./themes-api.js') +vi.mock('@shopify/cli-kit') describe('DevelopmentThemeManager', () => { + const storeFqdn = 'mystore.myshopify.com' + const token = 'token' const existingId = 200 const newThemeId = 201 const onlyLocallyExistingId = 404 @@ -14,70 +21,65 @@ describe('DevelopmentThemeManager', () => { [existingId]: {id: existingId} as Theme, [onlyLocallyExistingId]: undefined, } - let developmentThemeManager: DevelopmentThemeManager - let removeDevelopmentThemeSpy: SpyInstance + let localDevelopmentThemeId: string | undefined + + beforeEach(() => { + vi.mocked(store.getDevelopmentTheme).mockImplementation(() => localDevelopmentThemeId) + vi.mocked(store.setDevelopmentTheme).mockImplementation(() => undefined) + vi.mocked(store.removeDevelopmentTheme).mockImplementation(() => undefined) - function buildDevelopmentThemeManager(localDevelopmentThemeId?: string) { - const store = 'mystore.myshopify.com' - const token = 'token' - const storage = { - getDevelopmentTheme: () => localDevelopmentThemeId, - setDevelopmentTheme: (theme: string) => undefined, - removeDevelopmentTheme: () => undefined, - } - developmentThemeManager = new DevelopmentThemeManager( - { - storeFqdn: store, - token, - }, - storage, - { - fetchTheme: (id: number) => Promise.resolve(themeTestDatabase[id]), - createTheme: ({name, role}) => Promise.resolve(new Theme(newThemeId, name as string, role as string)), - }, + vi.mocked(fetchTheme).mockImplementation((id: number) => Promise.resolve(themeTestDatabase[id])) + vi.mocked(createTheme).mockImplementation(({name, role}) => + Promise.resolve(new Theme(newThemeId, name as string, role as string)), ) - removeDevelopmentThemeSpy = vi.spyOn(storage, 'removeDevelopmentTheme') + }) + + function buildDevelopmentThemeManager() { + return new DevelopmentThemeManager({ + storeFqdn, + token, + }) } describe('find', () => { it('should throw Abort if no ID is locally stored', async () => { - buildDevelopmentThemeManager() - await expect(() => developmentThemeManager.find()).rejects.toThrowError(NO_DEVELOPMENT_THEME_ID_SET) - expect(removeDevelopmentThemeSpy).not.toHaveBeenCalled() + localDevelopmentThemeId = undefined + await expect(() => buildDevelopmentThemeManager().find()).rejects.toThrowError(NO_DEVELOPMENT_THEME_ID_SET) + expect(store.removeDevelopmentTheme).not.toHaveBeenCalled() }) it('should remove locally stored ID and throw Abort if API could not return theme', async () => { const theme = onlyLocallyExistingId.toString() - buildDevelopmentThemeManager(theme) - await expect(() => developmentThemeManager.find()).rejects.toThrowError(DEVELOPMENT_THEME_NOT_FOUND(theme)) - expect(removeDevelopmentThemeSpy).toHaveBeenCalledOnce() + localDevelopmentThemeId = theme + await expect(() => buildDevelopmentThemeManager().find()).rejects.toThrowError(DEVELOPMENT_THEME_NOT_FOUND(theme)) + expect(store.removeDevelopmentTheme).toHaveBeenCalledOnce() }) it('should return theme if API returns theme with locally stored ID', async () => { const theme = existingId.toString() - buildDevelopmentThemeManager(theme) - expect(await developmentThemeManager.find()).toEqual(themeTestDatabase[existingId]) + localDevelopmentThemeId = theme + expect(await buildDevelopmentThemeManager().find()).toEqual(themeTestDatabase[existingId]) }) }) describe('findOrCreate', () => { it('should not create a new development theme if API returns theme with locally stored ID', async () => { const theme = existingId.toString() - buildDevelopmentThemeManager(theme) - expect(await developmentThemeManager.findOrCreate()).toEqual(theme) + localDevelopmentThemeId = theme + expect(await buildDevelopmentThemeManager().findOrCreate()).toEqual(theme) }) it('should create a new development theme if no ID is locally stored', async () => { - buildDevelopmentThemeManager() - expect(await developmentThemeManager.findOrCreate()).toEqual(newThemeId.toString()) - expect(removeDevelopmentThemeSpy).not.toHaveBeenCalled() + localDevelopmentThemeId = undefined + expect(await buildDevelopmentThemeManager().findOrCreate()).toEqual(newThemeId.toString()) + expect(store.removeDevelopmentTheme).not.toHaveBeenCalled() }) it('should create a new development theme if locally existing ID points to nowhere', async () => { const theme = onlyLocallyExistingId.toString() - buildDevelopmentThemeManager(theme) - expect(await developmentThemeManager.findOrCreate()).toEqual(newThemeId.toString()) - expect(removeDevelopmentThemeSpy).toHaveBeenCalledOnce() + localDevelopmentThemeId = theme + expect(await buildDevelopmentThemeManager().findOrCreate()).toEqual(newThemeId.toString()) + expect(store.removeDevelopmentTheme).toHaveBeenCalledOnce() }) }) }) diff --git a/packages/theme/src/cli/utilities/development-theme-manager.ts b/packages/theme/src/cli/utilities/development-theme-manager.ts index 76991c99683..2a261986707 100644 --- a/packages/theme/src/cli/utilities/development-theme-manager.ts +++ b/packages/theme/src/cli/utilities/development-theme-manager.ts @@ -3,37 +3,18 @@ import {generateDevelopmentThemeName} from './generate-development-theme-name.js import {DEVELOPMENT_THEME_ROLE} from '../models/theme.js' import {AdminSession} from '@shopify/cli-kit/node/session' import {AbortError, BugError} from '@shopify/cli-kit/node/error' -import {store as defaultStorage} from '@shopify/cli-kit' +import {store} from '@shopify/cli-kit' export const DEVELOPMENT_THEME_NOT_FOUND = (themeId: string) => `Development theme #${themeId} could not be found. Please create a new development theme.` export const NO_DEVELOPMENT_THEME_ID_SET = 'No development theme ID has been set. Please create a development theme first.' -interface Storage { - getDevelopmentTheme: () => string | undefined - setDevelopmentTheme: (theme: string) => void - removeDevelopmentTheme: () => void -} - -interface ThemesAPI { - fetchTheme: typeof fetchTheme - createTheme: typeof createTheme -} - export class DevelopmentThemeManager { private themeId: string | undefined - constructor( - private adminSession: AdminSession, - private storage: Storage = defaultStorage, - private api: ThemesAPI = { - fetchTheme, - createTheme, - }, - private generateName = generateDevelopmentThemeName, - ) { - this.themeId = storage.getDevelopmentTheme() + constructor(private adminSession: AdminSession) { + this.themeId = store.getDevelopmentTheme() } async find() { @@ -56,17 +37,17 @@ export class DevelopmentThemeManager { if (!this.themeId) { return } - const theme = await this.api.fetchTheme(parseInt(this.themeId, 10), this.adminSession) + const theme = await fetchTheme(parseInt(this.themeId, 10), this.adminSession) if (!theme) { - this.storage.removeDevelopmentTheme() + store.removeDevelopmentTheme() } return theme } private async create() { - const name = this.generateName() + const name = generateDevelopmentThemeName() const role = DEVELOPMENT_THEME_ROLE - const theme = await this.api.createTheme( + const theme = await createTheme( { name, role, @@ -76,7 +57,7 @@ export class DevelopmentThemeManager { if (!theme) { throw new BugError(`Could not create theme with name "${name}" and role "${role}"`) } - this.storage.setDevelopmentTheme(theme.id.toString()) + store.setDevelopmentTheme(theme.id.toString()) return theme } } diff --git a/packages/theme/src/cli/utilities/generate-development-theme-name.test.ts b/packages/theme/src/cli/utilities/generate-development-theme-name.test.ts index 69fe6aab102..04962c669b7 100644 --- a/packages/theme/src/cli/utilities/generate-development-theme-name.test.ts +++ b/packages/theme/src/cli/utilities/generate-development-theme-name.test.ts @@ -1,20 +1,23 @@ import {generateDevelopmentThemeName} from './generate-development-theme-name.js' -import {describe, expect, it} from 'vitest' +import {beforeEach, describe, expect, it, vi} from 'vitest' +import {hostname} from 'os' +import {randomBytes} from 'crypto' + +vi.mock('os') +vi.mock('crypto') describe('generateName', () => { - function randomBytes() { - return Buffer.from([1, 2, 3]) - } + beforeEach(() => { + vi.mocked(randomBytes).mockImplementation(() => Buffer.from([1, 2, 3])) + }) it('should not truncate if the theme name is below the API limit', () => { - expect(generateDevelopmentThemeName(() => 'Mac-Book-Pro.My-Router', randomBytes)).toEqual( - 'Development (010203-Mac-Book-Pro)', - ) + vi.mocked(hostname).mockReturnValue('Mac-Book-Pro.My-Router') + expect(generateDevelopmentThemeName()).toEqual('Development (010203-Mac-Book-Pro)') }) it('should truncate if the theme name is above the API limit', () => { - expect( - generateDevelopmentThemeName(() => 'theme-dev-lan-very-long-name-that-will-be-truncated', randomBytes), - ).toEqual('Development (010203-theme-dev-lan-very-long-name-t)') + vi.mocked(hostname).mockReturnValue('theme-dev-lan-very-long-name-that-will-be-truncated') + expect(generateDevelopmentThemeName()).toEqual('Development (010203-theme-dev-lan-very-long-name-t)') }) }) diff --git a/packages/theme/src/cli/utilities/generate-development-theme-name.ts b/packages/theme/src/cli/utilities/generate-development-theme-name.ts index f1bd38e0310..f7eba8caa54 100644 --- a/packages/theme/src/cli/utilities/generate-development-theme-name.ts +++ b/packages/theme/src/cli/utilities/generate-development-theme-name.ts @@ -1,11 +1,11 @@ import {replaceInvalidCharacters} from './replace-invalid-characters.js' -import {hostname as osHostName} from 'os' -import {randomBytes as cryptoRandomBytes} from 'crypto' +import {hostname} from 'os' +import {randomBytes} from 'crypto' const API_NAME_LIMIT = 50 -export function generateDevelopmentThemeName(hostName = osHostName, randomBytes = cryptoRandomBytes): string { - const hostNameWithoutDomain = hostName().split('.')[0]! +export function generateDevelopmentThemeName(): string { + const hostNameWithoutDomain = hostname().split('.')[0]! const hash = randomBytes(3).toString('hex') const name = 'Development ()' From 0cbd918dfe6e89e45815ce136f979b210860d4cf Mon Sep 17 00:00:00 2001 From: Julien Poitrin Date: Thu, 26 Jan 2023 11:51:53 +0100 Subject: [PATCH 03/12] Resolve merge conflicts after latest changes on `main` --- packages/theme/src/cli/services/conf.ts | 22 +++++++++++++++++++ packages/theme/src/cli/services/delete.ts | 4 ++-- packages/theme/src/cli/services/info.ts | 3 ++- packages/theme/src/cli/services/list.test.ts | 5 +++-- packages/theme/src/cli/services/list.ts | 4 ++-- .../development-theme-manager.test.ts | 18 +++++++-------- .../utilities/development-theme-manager.ts | 8 +++---- 7 files changed, 44 insertions(+), 20 deletions(-) diff --git a/packages/theme/src/cli/services/conf.ts b/packages/theme/src/cli/services/conf.ts index 50b4f672a8c..36f29e7b890 100644 --- a/packages/theme/src/cli/services/conf.ts +++ b/packages/theme/src/cli/services/conf.ts @@ -1,7 +1,11 @@ import {Conf} from '@shopify/cli-kit/node/conf' +import {outputDebug, outputContent} from '@shopify/cli-kit/node/output' + +const DEVELOPMENT_THEME_KEY = 'developmentTheme' export interface ThemeConfSchema { themeStore: string + developmentTheme: string } let _instance: Conf | undefined @@ -12,3 +16,21 @@ export function themeConf() { } return _instance } + +export function getDevelopmentTheme(): string | undefined { + outputDebug(outputContent`Getting development theme...`) + const config = themeConf() + return config.get(DEVELOPMENT_THEME_KEY) +} + +export function setDevelopmentTheme(theme: string): void { + outputDebug(outputContent`Setting development theme...`) + const config = themeConf() + config.set(DEVELOPMENT_THEME_KEY, theme) +} + +export function removeDevelopmentTheme(): void { + outputDebug(outputContent`Removing development theme...`) + const config = themeConf() + config.reset(DEVELOPMENT_THEME_KEY) +} diff --git a/packages/theme/src/cli/services/delete.ts b/packages/theme/src/cli/services/delete.ts index d2328d85653..27a9aa78820 100644 --- a/packages/theme/src/cli/services/delete.ts +++ b/packages/theme/src/cli/services/delete.ts @@ -1,3 +1,4 @@ +import {removeDevelopmentTheme} from './conf.js' import {findOrSelectTheme, findThemes} from '../utilities/theme-selector.js' import {Theme} from '../models/theme.js' import {themeComponent, themesComponent} from '../utilities/theme-ui.js' @@ -6,7 +7,6 @@ import {DevelopmentThemeManager} from '../utilities/development-theme-manager.js import {AdminSession} from '@shopify/cli-kit/node/session' import {renderConfirmationPrompt, renderSuccess, renderWarning} from '@shopify/cli-kit/node/ui' import {pluralize} from '@shopify/cli-kit/common/string' -import {store as storage} from '@shopify/cli-kit' export interface DeleteOptions { selectTheme: boolean @@ -31,7 +31,7 @@ export async function deleteThemes(adminSession: AdminSession, options: DeleteOp themes.map((theme) => { if (theme.hasDevelopmentRole) { - storage.removeDevelopmentTheme() + removeDevelopmentTheme() } return deleteTheme(theme.id, adminSession) }) diff --git a/packages/theme/src/cli/services/info.ts b/packages/theme/src/cli/services/info.ts index 8226ad30a52..9103c323226 100644 --- a/packages/theme/src/cli/services/info.ts +++ b/packages/theme/src/cli/services/info.ts @@ -1,3 +1,4 @@ +import {getDevelopmentTheme} from './conf.js' import {getThemeStore} from '../utilities/theme-store.js' import {platformAndArch} from '@shopify/cli-kit/node/os' import {version as rubyVersion} from '@shopify/cli-kit/node/ruby' @@ -14,7 +15,7 @@ export async function themeInfo(config: {cliVersion: string}): Promise { {id: 3, name: 'Theme 3', role: 'development'}, {id: developmentThemeId, name: 'Theme 5', role: 'development'}, ] as Theme[]) - vi.spyOn(store, 'getDevelopmentTheme').mockReturnValue(developmentThemeId.toString()) + vi.mocked(getDevelopmentTheme).mockReturnValue(developmentThemeId.toString()) await list(session, {}) diff --git a/packages/theme/src/cli/services/list.ts b/packages/theme/src/cli/services/list.ts index b7b51ee8a5d..2b935d0d14d 100644 --- a/packages/theme/src/cli/services/list.ts +++ b/packages/theme/src/cli/services/list.ts @@ -1,9 +1,9 @@ import {columns} from './list.columns.js' +import {getDevelopmentTheme} from './conf.js' import {ALLOWED_ROLES, fetchStoreThemes, Role} from '../utilities/theme-selector/fetch.js' import {Filter, FilterProps, filterThemes} from '../utilities/theme-selector/filter.js' import {renderTable} from '@shopify/cli-kit/node/ui' import {AdminSession} from '@shopify/cli-kit/node/session.js' -import {store as storage} from '@shopify/cli-kit' export interface Options { role?: Role @@ -22,7 +22,7 @@ export async function list(adminSession: AdminSession, options: Options) { }) let storeThemes = await fetchStoreThemes(adminSession) - const developmentTheme = storage.getDevelopmentTheme() + const developmentTheme = getDevelopmentTheme() if (filter.any()) { storeThemes = filterThemes(store, storeThemes, filter) } diff --git a/packages/theme/src/cli/utilities/development-theme-manager.test.ts b/packages/theme/src/cli/utilities/development-theme-manager.test.ts index 2c9e97adc5a..ac0ca01014d 100644 --- a/packages/theme/src/cli/utilities/development-theme-manager.test.ts +++ b/packages/theme/src/cli/utilities/development-theme-manager.test.ts @@ -5,11 +5,11 @@ import { } from './development-theme-manager.js' import {createTheme, fetchTheme} from './themes-api.js' import {Theme} from '../models/theme.js' +import {getDevelopmentTheme, setDevelopmentTheme, removeDevelopmentTheme} from '../services/conf.js' import {beforeEach, describe, expect, it, vi} from 'vitest' -import {store} from '@shopify/cli-kit' vi.mock('./themes-api.js') -vi.mock('@shopify/cli-kit') +vi.mock('../services/conf.js') describe('DevelopmentThemeManager', () => { const storeFqdn = 'mystore.myshopify.com' @@ -24,9 +24,9 @@ describe('DevelopmentThemeManager', () => { let localDevelopmentThemeId: string | undefined beforeEach(() => { - vi.mocked(store.getDevelopmentTheme).mockImplementation(() => localDevelopmentThemeId) - vi.mocked(store.setDevelopmentTheme).mockImplementation(() => undefined) - vi.mocked(store.removeDevelopmentTheme).mockImplementation(() => undefined) + vi.mocked(getDevelopmentTheme).mockImplementation(() => localDevelopmentThemeId) + vi.mocked(setDevelopmentTheme).mockImplementation(() => undefined) + vi.mocked(removeDevelopmentTheme).mockImplementation(() => undefined) vi.mocked(fetchTheme).mockImplementation((id: number) => Promise.resolve(themeTestDatabase[id])) vi.mocked(createTheme).mockImplementation(({name, role}) => @@ -45,14 +45,14 @@ describe('DevelopmentThemeManager', () => { it('should throw Abort if no ID is locally stored', async () => { localDevelopmentThemeId = undefined await expect(() => buildDevelopmentThemeManager().find()).rejects.toThrowError(NO_DEVELOPMENT_THEME_ID_SET) - expect(store.removeDevelopmentTheme).not.toHaveBeenCalled() + expect(removeDevelopmentTheme).not.toHaveBeenCalled() }) it('should remove locally stored ID and throw Abort if API could not return theme', async () => { const theme = onlyLocallyExistingId.toString() localDevelopmentThemeId = theme await expect(() => buildDevelopmentThemeManager().find()).rejects.toThrowError(DEVELOPMENT_THEME_NOT_FOUND(theme)) - expect(store.removeDevelopmentTheme).toHaveBeenCalledOnce() + expect(removeDevelopmentTheme).toHaveBeenCalledOnce() }) it('should return theme if API returns theme with locally stored ID', async () => { @@ -72,14 +72,14 @@ describe('DevelopmentThemeManager', () => { it('should create a new development theme if no ID is locally stored', async () => { localDevelopmentThemeId = undefined expect(await buildDevelopmentThemeManager().findOrCreate()).toEqual(newThemeId.toString()) - expect(store.removeDevelopmentTheme).not.toHaveBeenCalled() + expect(removeDevelopmentTheme).not.toHaveBeenCalled() }) it('should create a new development theme if locally existing ID points to nowhere', async () => { const theme = onlyLocallyExistingId.toString() localDevelopmentThemeId = theme expect(await buildDevelopmentThemeManager().findOrCreate()).toEqual(newThemeId.toString()) - expect(store.removeDevelopmentTheme).toHaveBeenCalledOnce() + expect(removeDevelopmentTheme).toHaveBeenCalledOnce() }) }) }) diff --git a/packages/theme/src/cli/utilities/development-theme-manager.ts b/packages/theme/src/cli/utilities/development-theme-manager.ts index 2a261986707..c12db836e82 100644 --- a/packages/theme/src/cli/utilities/development-theme-manager.ts +++ b/packages/theme/src/cli/utilities/development-theme-manager.ts @@ -1,9 +1,9 @@ import {fetchTheme, createTheme} from './themes-api.js' import {generateDevelopmentThemeName} from './generate-development-theme-name.js' import {DEVELOPMENT_THEME_ROLE} from '../models/theme.js' +import {getDevelopmentTheme, setDevelopmentTheme, removeDevelopmentTheme} from '../services/conf.js' import {AdminSession} from '@shopify/cli-kit/node/session' import {AbortError, BugError} from '@shopify/cli-kit/node/error' -import {store} from '@shopify/cli-kit' export const DEVELOPMENT_THEME_NOT_FOUND = (themeId: string) => `Development theme #${themeId} could not be found. Please create a new development theme.` @@ -14,7 +14,7 @@ export class DevelopmentThemeManager { private themeId: string | undefined constructor(private adminSession: AdminSession) { - this.themeId = store.getDevelopmentTheme() + this.themeId = getDevelopmentTheme() } async find() { @@ -39,7 +39,7 @@ export class DevelopmentThemeManager { } const theme = await fetchTheme(parseInt(this.themeId, 10), this.adminSession) if (!theme) { - store.removeDevelopmentTheme() + removeDevelopmentTheme() } return theme } @@ -57,7 +57,7 @@ export class DevelopmentThemeManager { if (!theme) { throw new BugError(`Could not create theme with name "${name}" and role "${role}"`) } - store.setDevelopmentTheme(theme.id.toString()) + setDevelopmentTheme(theme.id.toString()) return theme } } From ffd39cf7b21041055a28003de106db9c8f8e05b9 Mon Sep 17 00:00:00 2001 From: Julien Poitrin Date: Fri, 27 Jan 2023 10:20:04 +0100 Subject: [PATCH 04/12] Pass `--overwrite-json` flag to Ruby CLI --- packages/theme/src/cli/commands/theme/dev.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/theme/src/cli/commands/theme/dev.ts b/packages/theme/src/cli/commands/theme/dev.ts index 57631f49d24..3886d029152 100644 --- a/packages/theme/src/cli/commands/theme/dev.ts +++ b/packages/theme/src/cli/commands/theme/dev.ts @@ -81,6 +81,7 @@ export default class Dev extends ThemeCommand { 'live-reload', 'poll', 'theme-editor-sync', + 'overwrite-json', 'port', 'theme', 'only', @@ -104,6 +105,7 @@ export default class Dev extends ThemeCommand { flags = { ...flags, theme, + 'overwrite-json': Boolean(flags['theme-editor-sync']), } const flagsToPass = this.passThroughFlags(flags, {allowedFlags: Dev.cli2Flags}) From c6ee55b5eed44d42d2f7ec8913e648852bd6cd6f Mon Sep 17 00:00:00 2001 From: Julien Poitrin Date: Fri, 27 Jan 2023 10:36:56 +0100 Subject: [PATCH 05/12] Update manifests --- packages/theme/oclif.manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/theme/oclif.manifest.json b/packages/theme/oclif.manifest.json index 4ccb8087e53..b10395aa765 100644 --- a/packages/theme/oclif.manifest.json +++ b/packages/theme/oclif.manifest.json @@ -1 +1 @@ -{"version":"3.38.0","commands":{"theme:check":{"id":"theme:check","description":"Validate the theme.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"path":{"name":"path","type":"option","description":"The path to your theme directory.","hidden":false,"multiple":false,"default":"."},"auto-correct":{"name":"auto-correct","type":"boolean","char":"a","description":"Automatically fix offenses","required":false,"allowNo":false},"category":{"name":"category","type":"option","char":"c","description":"Only run this category of checks\nRuns checks matching all categories when specified more than once","required":false,"multiple":false},"config":{"name":"config","type":"option","char":"C","description":"Use the config provided, overriding .theme-check.yml if present\nUse :theme_app_extension to use default checks for theme app extensions","required":false,"multiple":false},"exclude-category":{"name":"exclude-category","type":"option","char":"x","description":"Exclude this category of checks\nExcludes checks matching any category when specified more than once","required":false,"multiple":false},"fail-level":{"name":"fail-level","type":"option","description":"Minimum severity for exit with error code","required":false,"multiple":false,"options":["error","suggestion","style"]},"init":{"name":"init","type":"boolean","description":"Generate a .theme-check.yml file","required":false,"allowNo":false},"list":{"name":"list","type":"boolean","description":"List enabled checks","required":false,"allowNo":false},"output":{"name":"output","type":"option","char":"o","description":"The output format to use","required":false,"multiple":false,"options":["text","json"],"default":"text"},"print":{"name":"print","type":"boolean","description":"Output active config to STDOUT","required":false,"allowNo":false},"version":{"name":"version","type":"boolean","char":"v","description":"Print Theme Check version","required":false,"allowNo":false}},"args":[],"cli2Flags":["auto-correct","category","config","exclude-category","fail-level","init","list","output","print","version"]},"theme:delete":{"id":"theme:delete","description":"Delete remote themes from the connected store. This command can't be undone.","strict":false,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false},"development":{"name":"development","type":"boolean","char":"d","description":"Delete your development theme.","allowNo":false},"show-all":{"name":"show-all","type":"boolean","char":"a","description":"Include others development themes in theme list.","allowNo":false},"force":{"name":"force","type":"boolean","char":"f","description":"Skip confirmation.","allowNo":false},"theme":{"name":"theme","type":"option","char":"t","description":"Theme ID or name of the remote theme.","multiple":true},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false}},"args":[]},"theme:dev":{"id":"theme:dev","description":"Uploads the current theme as a development theme to the connected store, then prints theme editor and preview URLs to your terminal. While running, changes will push to the store in real time.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"path":{"name":"path","type":"option","description":"The path to your theme directory.","hidden":false,"multiple":false,"default":"."},"host":{"name":"host","type":"option","description":"Set which network interface the web server listens on. The default value is 127.0.0.1.","multiple":false},"live-reload":{"name":"live-reload","type":"option","description":"The live reload mode switches the server behavior when a file is modified:\n- hot-reload Hot reloads local changes to CSS and sections (default)\n- full-page Always refreshes the entire page\n- off Deactivate live reload","multiple":false,"options":["hot-reload","full-page","off"],"default":"hot-reload"},"poll":{"name":"poll","type":"boolean","description":"Force polling to detect file changes.","allowNo":false},"theme-editor-sync":{"name":"theme-editor-sync","type":"boolean","char":"e","description":"Synchronize Theme Editor updates in the local theme files.","allowNo":false},"port":{"name":"port","type":"option","description":"Local port to serve theme preview from.","multiple":false},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false},"theme":{"name":"theme","type":"option","char":"t","description":"Theme ID or name of the remote theme.","multiple":false},"only":{"name":"only","type":"option","char":"o","description":"Hot reload only files that match the specified pattern.","multiple":true},"ignore":{"name":"ignore","type":"option","char":"x","description":"Skip hot reloading any files that match the specified pattern.","multiple":true},"stable":{"name":"stable","type":"boolean","description":"Performs the upload by relying in the legacy upload approach (slower, but it might be more stable in some scenarios)","hidden":true,"allowNo":false},"force":{"name":"force","type":"boolean","char":"f","description":"Proceed without confirmation, if current directory does not seem to be theme directory.","hidden":true,"allowNo":false},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false}},"args":[],"cli2Flags":["host","live-reload","poll","theme-editor-sync","port","theme","only","ignore","stable","force"]},"theme:help-old":{"id":"theme:help-old","description":"Show help from Ruby CLI","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","hidden":true,"aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"command":{"name":"command","type":"option","description":"The command for which to show CLI2 help.","multiple":false}},"args":[]},"theme:info":{"id":"theme:info","description":"Print basic information about your theme environment.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false}},"args":[]},"theme:init":{"id":"theme:init","description":"Clones a Git repository to use as a starting point for building a new theme.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"path":{"name":"path","type":"option","description":"The path to your theme directory.","hidden":false,"multiple":false,"default":"."},"clone-url":{"name":"clone-url","type":"option","char":"u","description":"The Git URL to clone from. Defaults to Shopify's example theme, Dawn: https://github.com/Shopify/dawn.git","multiple":false,"default":"https://github.com/Shopify/dawn.git"},"latest":{"name":"latest","type":"boolean","char":"l","description":"Downloads the latest release of the `clone-url`","allowNo":false}},"args":[{"name":"name","description":"Name of the new theme","required":false}]},"theme:language-server":{"id":"theme:language-server","description":"Start a Language Server Protocol server.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false}},"args":[]},"theme:list":{"id":"theme:list","description":"Lists your remote themes.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false},"role":{"name":"role","type":"option","description":"Only list themes with the given role.","helpValue":"(live|unpublished|development)","multiple":false,"options":["live","unpublished","development"]},"name":{"name":"name","type":"option","description":"Only list themes that contain the given name.","multiple":false},"id":{"name":"id","type":"option","description":"Only list theme with the given ID.","multiple":false}},"args":[]},"theme:open":{"id":"theme:open","description":"Opens the preview of your remote theme.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false},"development":{"name":"development","type":"boolean","char":"d","description":"Delete your development theme.","allowNo":false},"editor":{"name":"editor","type":"boolean","char":"e","description":"Open the theme editor for the specified theme in the browser.","allowNo":false},"live":{"name":"live","type":"boolean","char":"l","description":"Pull theme files from your remote live theme.","allowNo":false},"theme":{"name":"theme","type":"option","char":"t","description":"Theme ID or name of the remote theme.","multiple":false},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false}},"args":[]},"theme:package":{"id":"theme:package","description":"Package your theme into a .zip file, ready to upload to the Online Store.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"path":{"name":"path","type":"option","description":"The path to your theme directory.","hidden":false,"multiple":false,"default":"."}},"args":[]},"theme:publish":{"id":"theme:publish","description":"Set a remote theme as the live theme.","strict":false,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false},"force":{"name":"force","type":"boolean","char":"f","description":"Skip confirmation.","allowNo":false},"theme":{"name":"theme","type":"option","char":"t","description":"Theme ID or name of the remote theme.","multiple":false},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false}},"args":[]},"theme:pull":{"id":"theme:pull","description":"Download your remote theme files locally.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"path":{"name":"path","type":"option","description":"The path to your theme directory.","hidden":false,"multiple":false,"default":"."},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false},"theme":{"name":"theme","type":"option","char":"t","description":"Theme ID or name of the remote theme.","multiple":false},"development":{"name":"development","type":"boolean","char":"d","description":"Pull theme files from your remote development theme.","allowNo":false},"live":{"name":"live","type":"boolean","char":"l","description":"Pull theme files from your remote live theme.","allowNo":false},"nodelete":{"name":"nodelete","type":"boolean","char":"n","description":"Runs the pull command without deleting local files.","allowNo":false},"only":{"name":"only","type":"option","char":"o","description":"Download only the specified files (Multiple flags allowed).","multiple":true},"ignore":{"name":"ignore","type":"option","char":"x","description":"Skip downloading the specified files (Multiple flags allowed).","multiple":true},"force":{"name":"force","type":"boolean","char":"f","description":"Proceed without confirmation, if current directory does not seem to be theme directory.","hidden":true,"allowNo":false}},"args":[],"cli2Flags":["theme","development","live","nodelete","only","ignore","force"]},"theme:push":{"id":"theme:push","description":"Uploads your local theme files to the connected store, overwriting the remote version if specified.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"path":{"name":"path","type":"option","description":"The path to your theme directory.","hidden":false,"multiple":false,"default":"."},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false},"theme":{"name":"theme","type":"option","char":"t","description":"Theme ID or name of the remote theme.","multiple":false},"development":{"name":"development","type":"boolean","char":"d","description":"Push theme files from your remote development theme.","allowNo":false},"live":{"name":"live","type":"boolean","char":"l","description":"Push theme files from your remote live theme.","allowNo":false},"unpublished":{"name":"unpublished","type":"boolean","char":"u","description":"Create a new unpublished theme and push to it.","allowNo":false},"nodelete":{"name":"nodelete","type":"boolean","char":"n","description":"Runs the push command without deleting local files.","allowNo":false},"only":{"name":"only","type":"option","char":"o","description":"Download only the specified files (Multiple flags allowed).","multiple":true},"ignore":{"name":"ignore","type":"option","char":"x","description":"Skip downloading the specified files (Multiple flags allowed).","multiple":true},"json":{"name":"json","type":"boolean","char":"j","description":"Output JSON instead of a UI.","allowNo":false},"allow-live":{"name":"allow-live","type":"boolean","char":"a","description":"Allow push to a live theme.","allowNo":false},"publish":{"name":"publish","type":"boolean","char":"p","description":"Publish as the live theme after uploading.","allowNo":false},"stable":{"name":"stable","type":"boolean","description":"Performs the upload by relying in the legacy upload approach (slower, but it might be more stable in some scenarios)","hidden":true,"allowNo":false},"force":{"name":"force","type":"boolean","char":"f","description":"Proceed without confirmation, if current directory does not seem to be theme directory.","hidden":true,"allowNo":false}},"args":[],"cli2Flags":["theme","development","live","unpublished","nodelete","only","ignore","json","allow-live","publish","stable","force"]},"theme:share":{"id":"theme:share","description":"Creates a shareable, unpublished, and new theme on your theme library with a randomized name. Works like an alias to {{command:theme push -u -t=RANDOMIZED_NAME}}.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"path":{"name":"path","type":"option","description":"The path to your theme directory.","hidden":false,"multiple":false,"default":"."},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false},"force":{"name":"force","type":"boolean","char":"f","description":"Proceed without confirmation, if current directory does not seem to be theme directory.","hidden":true,"allowNo":false}},"args":[],"cli2Flags":["force"]}}} \ No newline at end of file +{"version":"3.38.0","commands":{"theme:check":{"id":"theme:check","description":"Validate the theme.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"path":{"name":"path","type":"option","description":"The path to your theme directory.","hidden":false,"multiple":false,"default":"."},"auto-correct":{"name":"auto-correct","type":"boolean","char":"a","description":"Automatically fix offenses","required":false,"allowNo":false},"category":{"name":"category","type":"option","char":"c","description":"Only run this category of checks\nRuns checks matching all categories when specified more than once","required":false,"multiple":false},"config":{"name":"config","type":"option","char":"C","description":"Use the config provided, overriding .theme-check.yml if present\nUse :theme_app_extension to use default checks for theme app extensions","required":false,"multiple":false},"exclude-category":{"name":"exclude-category","type":"option","char":"x","description":"Exclude this category of checks\nExcludes checks matching any category when specified more than once","required":false,"multiple":false},"fail-level":{"name":"fail-level","type":"option","description":"Minimum severity for exit with error code","required":false,"multiple":false,"options":["error","suggestion","style"]},"init":{"name":"init","type":"boolean","description":"Generate a .theme-check.yml file","required":false,"allowNo":false},"list":{"name":"list","type":"boolean","description":"List enabled checks","required":false,"allowNo":false},"output":{"name":"output","type":"option","char":"o","description":"The output format to use","required":false,"multiple":false,"options":["text","json"],"default":"text"},"print":{"name":"print","type":"boolean","description":"Output active config to STDOUT","required":false,"allowNo":false},"version":{"name":"version","type":"boolean","char":"v","description":"Print Theme Check version","required":false,"allowNo":false}},"args":[],"cli2Flags":["auto-correct","category","config","exclude-category","fail-level","init","list","output","print","version"]},"theme:delete":{"id":"theme:delete","description":"Delete remote themes from the connected store. This command can't be undone.","strict":false,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false},"development":{"name":"development","type":"boolean","char":"d","description":"Delete your development theme.","allowNo":false},"show-all":{"name":"show-all","type":"boolean","char":"a","description":"Include others development themes in theme list.","allowNo":false},"force":{"name":"force","type":"boolean","char":"f","description":"Skip confirmation.","allowNo":false},"theme":{"name":"theme","type":"option","char":"t","description":"Theme ID or name of the remote theme.","multiple":true},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false}},"args":[]},"theme:dev":{"id":"theme:dev","description":"Uploads the current theme as a development theme to the connected store, then prints theme editor and preview URLs to your terminal. While running, changes will push to the store in real time.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"path":{"name":"path","type":"option","description":"The path to your theme directory.","hidden":false,"multiple":false,"default":"."},"host":{"name":"host","type":"option","description":"Set which network interface the web server listens on. The default value is 127.0.0.1.","multiple":false},"live-reload":{"name":"live-reload","type":"option","description":"The live reload mode switches the server behavior when a file is modified:\n- hot-reload Hot reloads local changes to CSS and sections (default)\n- full-page Always refreshes the entire page\n- off Deactivate live reload","multiple":false,"options":["hot-reload","full-page","off"],"default":"hot-reload"},"poll":{"name":"poll","type":"boolean","description":"Force polling to detect file changes.","allowNo":false},"theme-editor-sync":{"name":"theme-editor-sync","type":"boolean","char":"e","description":"Synchronize Theme Editor updates in the local theme files.","allowNo":false},"port":{"name":"port","type":"option","description":"Local port to serve theme preview from.","multiple":false},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false},"theme":{"name":"theme","type":"option","char":"t","description":"Theme ID or name of the remote theme.","multiple":false},"only":{"name":"only","type":"option","char":"o","description":"Hot reload only files that match the specified pattern.","multiple":true},"ignore":{"name":"ignore","type":"option","char":"x","description":"Skip hot reloading any files that match the specified pattern.","multiple":true},"stable":{"name":"stable","type":"boolean","description":"Performs the upload by relying in the legacy upload approach (slower, but it might be more stable in some scenarios)","hidden":true,"allowNo":false},"force":{"name":"force","type":"boolean","char":"f","description":"Proceed without confirmation, if current directory does not seem to be theme directory.","hidden":true,"allowNo":false},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false}},"args":[],"cli2Flags":["host","live-reload","poll","theme-editor-sync","overwrite-json","port","theme","only","ignore","stable","force"]},"theme:help-old":{"id":"theme:help-old","description":"Show help from Ruby CLI","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","hidden":true,"aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"command":{"name":"command","type":"option","description":"The command for which to show CLI2 help.","multiple":false}},"args":[]},"theme:info":{"id":"theme:info","description":"Print basic information about your theme environment.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false}},"args":[]},"theme:init":{"id":"theme:init","description":"Clones a Git repository to use as a starting point for building a new theme.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"path":{"name":"path","type":"option","description":"The path to your theme directory.","hidden":false,"multiple":false,"default":"."},"clone-url":{"name":"clone-url","type":"option","char":"u","description":"The Git URL to clone from. Defaults to Shopify's example theme, Dawn: https://github.com/Shopify/dawn.git","multiple":false,"default":"https://github.com/Shopify/dawn.git"},"latest":{"name":"latest","type":"boolean","char":"l","description":"Downloads the latest release of the `clone-url`","allowNo":false}},"args":[{"name":"name","description":"Name of the new theme","required":false}]},"theme:language-server":{"id":"theme:language-server","description":"Start a Language Server Protocol server.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false}},"args":[]},"theme:list":{"id":"theme:list","description":"Lists your remote themes.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false},"role":{"name":"role","type":"option","description":"Only list themes with the given role.","helpValue":"(live|unpublished|development)","multiple":false,"options":["live","unpublished","development"]},"name":{"name":"name","type":"option","description":"Only list themes that contain the given name.","multiple":false},"id":{"name":"id","type":"option","description":"Only list theme with the given ID.","multiple":false}},"args":[]},"theme:open":{"id":"theme:open","description":"Opens the preview of your remote theme.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false},"development":{"name":"development","type":"boolean","char":"d","description":"Delete your development theme.","allowNo":false},"editor":{"name":"editor","type":"boolean","char":"e","description":"Open the theme editor for the specified theme in the browser.","allowNo":false},"live":{"name":"live","type":"boolean","char":"l","description":"Pull theme files from your remote live theme.","allowNo":false},"theme":{"name":"theme","type":"option","char":"t","description":"Theme ID or name of the remote theme.","multiple":false},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false}},"args":[]},"theme:package":{"id":"theme:package","description":"Package your theme into a .zip file, ready to upload to the Online Store.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"path":{"name":"path","type":"option","description":"The path to your theme directory.","hidden":false,"multiple":false,"default":"."}},"args":[]},"theme:publish":{"id":"theme:publish","description":"Set a remote theme as the live theme.","strict":false,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false},"force":{"name":"force","type":"boolean","char":"f","description":"Skip confirmation.","allowNo":false},"theme":{"name":"theme","type":"option","char":"t","description":"Theme ID or name of the remote theme.","multiple":false},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false}},"args":[]},"theme:pull":{"id":"theme:pull","description":"Download your remote theme files locally.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"path":{"name":"path","type":"option","description":"The path to your theme directory.","hidden":false,"multiple":false,"default":"."},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false},"theme":{"name":"theme","type":"option","char":"t","description":"Theme ID or name of the remote theme.","multiple":false},"development":{"name":"development","type":"boolean","char":"d","description":"Pull theme files from your remote development theme.","allowNo":false},"live":{"name":"live","type":"boolean","char":"l","description":"Pull theme files from your remote live theme.","allowNo":false},"nodelete":{"name":"nodelete","type":"boolean","char":"n","description":"Runs the pull command without deleting local files.","allowNo":false},"only":{"name":"only","type":"option","char":"o","description":"Download only the specified files (Multiple flags allowed).","multiple":true},"ignore":{"name":"ignore","type":"option","char":"x","description":"Skip downloading the specified files (Multiple flags allowed).","multiple":true},"force":{"name":"force","type":"boolean","char":"f","description":"Proceed without confirmation, if current directory does not seem to be theme directory.","hidden":true,"allowNo":false}},"args":[],"cli2Flags":["theme","development","live","nodelete","only","ignore","force"]},"theme:push":{"id":"theme:push","description":"Uploads your local theme files to the connected store, overwriting the remote version if specified.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"path":{"name":"path","type":"option","description":"The path to your theme directory.","hidden":false,"multiple":false,"default":"."},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false},"theme":{"name":"theme","type":"option","char":"t","description":"Theme ID or name of the remote theme.","multiple":false},"development":{"name":"development","type":"boolean","char":"d","description":"Push theme files from your remote development theme.","allowNo":false},"live":{"name":"live","type":"boolean","char":"l","description":"Push theme files from your remote live theme.","allowNo":false},"unpublished":{"name":"unpublished","type":"boolean","char":"u","description":"Create a new unpublished theme and push to it.","allowNo":false},"nodelete":{"name":"nodelete","type":"boolean","char":"n","description":"Runs the push command without deleting local files.","allowNo":false},"only":{"name":"only","type":"option","char":"o","description":"Download only the specified files (Multiple flags allowed).","multiple":true},"ignore":{"name":"ignore","type":"option","char":"x","description":"Skip downloading the specified files (Multiple flags allowed).","multiple":true},"json":{"name":"json","type":"boolean","char":"j","description":"Output JSON instead of a UI.","allowNo":false},"allow-live":{"name":"allow-live","type":"boolean","char":"a","description":"Allow push to a live theme.","allowNo":false},"publish":{"name":"publish","type":"boolean","char":"p","description":"Publish as the live theme after uploading.","allowNo":false},"stable":{"name":"stable","type":"boolean","description":"Performs the upload by relying in the legacy upload approach (slower, but it might be more stable in some scenarios)","hidden":true,"allowNo":false},"force":{"name":"force","type":"boolean","char":"f","description":"Proceed without confirmation, if current directory does not seem to be theme directory.","hidden":true,"allowNo":false}},"args":[],"cli2Flags":["theme","development","live","unpublished","nodelete","only","ignore","json","allow-live","publish","stable","force"]},"theme:share":{"id":"theme:share","description":"Creates a shareable, unpublished, and new theme on your theme library with a randomized name. Works like an alias to {{command:theme push -u -t=RANDOMIZED_NAME}}.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"path":{"name":"path","type":"option","description":"The path to your theme directory.","hidden":false,"multiple":false,"default":"."},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false},"force":{"name":"force","type":"boolean","char":"f","description":"Proceed without confirmation, if current directory does not seem to be theme directory.","hidden":true,"allowNo":false}},"args":[],"cli2Flags":["force"]}}} \ No newline at end of file From ebce87fdbac0fdaaf9aec78b84edef38721a8873 Mon Sep 17 00:00:00 2001 From: Julien Poitrin Date: Fri, 27 Jan 2023 14:19:46 +0100 Subject: [PATCH 06/12] Store one development theme ID per store --- packages/theme/src/cli/services/conf.ts | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/packages/theme/src/cli/services/conf.ts b/packages/theme/src/cli/services/conf.ts index 36f29e7b890..e15d7dac716 100644 --- a/packages/theme/src/cli/services/conf.ts +++ b/packages/theme/src/cli/services/conf.ts @@ -1,36 +1,34 @@ +import {getThemeStore} from '../utilities/theme-store.js' import {Conf} from '@shopify/cli-kit/node/conf' import {outputDebug, outputContent} from '@shopify/cli-kit/node/output' -const DEVELOPMENT_THEME_KEY = 'developmentTheme' +type DevelopmentThemeId = string export interface ThemeConfSchema { themeStore: string - developmentTheme: string + [developmentThemeStore: string]: DevelopmentThemeId } -let _instance: Conf | undefined +let _themeConfInstance: Conf | undefined export function themeConf() { - if (!_instance) { - _instance = new Conf({projectName: 'shopify-cli-theme-conf'}) + if (!_themeConfInstance) { + _themeConfInstance = new Conf({projectName: 'shopify-cli-theme-conf'}) } - return _instance + return _themeConfInstance } export function getDevelopmentTheme(): string | undefined { outputDebug(outputContent`Getting development theme...`) - const config = themeConf() - return config.get(DEVELOPMENT_THEME_KEY) + return themeConf().get(getThemeStore({store: undefined})) } export function setDevelopmentTheme(theme: string): void { outputDebug(outputContent`Setting development theme...`) - const config = themeConf() - config.set(DEVELOPMENT_THEME_KEY, theme) + themeConf().set(getThemeStore({store: undefined}), theme) } export function removeDevelopmentTheme(): void { outputDebug(outputContent`Removing development theme...`) - const config = themeConf() - config.reset(DEVELOPMENT_THEME_KEY) + themeConf().reset(getThemeStore({store: undefined})) } From 1a206a2e3e32b04c359a6db42cbc454e7b3063e6 Mon Sep 17 00:00:00 2001 From: Julien Poitrin Date: Mon, 30 Jan 2023 10:01:55 +0100 Subject: [PATCH 07/12] Move Development Theme ID Store into separate store, for storing host themes more easily --- packages/theme/src/cli/services/conf.ts | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/packages/theme/src/cli/services/conf.ts b/packages/theme/src/cli/services/conf.ts index e15d7dac716..10e28d5135c 100644 --- a/packages/theme/src/cli/services/conf.ts +++ b/packages/theme/src/cli/services/conf.ts @@ -2,14 +2,18 @@ import {getThemeStore} from '../utilities/theme-store.js' import {Conf} from '@shopify/cli-kit/node/conf' import {outputDebug, outputContent} from '@shopify/cli-kit/node/output' -type DevelopmentThemeId = string +type DevelopmentOrHostThemeId = string export interface ThemeConfSchema { themeStore: string - [developmentThemeStore: string]: DevelopmentThemeId +} + +interface DevelopmentThemeConfSchema { + [themeStore: string]: DevelopmentOrHostThemeId } let _themeConfInstance: Conf | undefined +let _developmentThemeConfInstance: Conf | undefined export function themeConf() { if (!_themeConfInstance) { @@ -18,17 +22,26 @@ export function themeConf() { return _themeConfInstance } +export function developmentThemeConf() { + if (!_developmentThemeConfInstance) { + _developmentThemeConfInstance = new Conf({ + projectName: 'shopify-cli-development-theme-conf', + }) + } + return _developmentThemeConfInstance +} + export function getDevelopmentTheme(): string | undefined { outputDebug(outputContent`Getting development theme...`) - return themeConf().get(getThemeStore({store: undefined})) + return developmentThemeConf().get(getThemeStore({store: undefined})) } export function setDevelopmentTheme(theme: string): void { outputDebug(outputContent`Setting development theme...`) - themeConf().set(getThemeStore({store: undefined}), theme) + developmentThemeConf().set(getThemeStore({store: undefined}), theme) } export function removeDevelopmentTheme(): void { outputDebug(outputContent`Removing development theme...`) - themeConf().reset(getThemeStore({store: undefined})) + developmentThemeConf().reset(getThemeStore({store: undefined})) } From 76804ce8bcf8bb299bba3a8f8975cd9bdd8846b7 Mon Sep 17 00:00:00 2001 From: Julien Poitrin Date: Tue, 31 Jan 2023 13:45:46 +0100 Subject: [PATCH 08/12] Update oclif.manifest.json --- packages/theme/oclif.manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/theme/oclif.manifest.json b/packages/theme/oclif.manifest.json index 1bcc4298943..5adf5398766 100644 --- a/packages/theme/oclif.manifest.json +++ b/packages/theme/oclif.manifest.json @@ -1 +1 @@ -{"version":"3.39.0","commands":{"theme:check":{"id":"theme:check","description":"Validate the theme.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"path":{"name":"path","type":"option","description":"The path to your theme directory.","hidden":false,"multiple":false,"default":"."},"auto-correct":{"name":"auto-correct","type":"boolean","char":"a","description":"Automatically fix offenses","required":false,"allowNo":false},"category":{"name":"category","type":"option","char":"c","description":"Only run this category of checks\nRuns checks matching all categories when specified more than once","required":false,"multiple":false},"config":{"name":"config","type":"option","char":"C","description":"Use the config provided, overriding .theme-check.yml if present\nUse :theme_app_extension to use default checks for theme app extensions","required":false,"multiple":false},"exclude-category":{"name":"exclude-category","type":"option","char":"x","description":"Exclude this category of checks\nExcludes checks matching any category when specified more than once","required":false,"multiple":false},"fail-level":{"name":"fail-level","type":"option","description":"Minimum severity for exit with error code","required":false,"multiple":false,"options":["error","suggestion","style"]},"init":{"name":"init","type":"boolean","description":"Generate a .theme-check.yml file","required":false,"allowNo":false},"list":{"name":"list","type":"boolean","description":"List enabled checks","required":false,"allowNo":false},"output":{"name":"output","type":"option","char":"o","description":"The output format to use","required":false,"multiple":false,"options":["text","json"],"default":"text"},"print":{"name":"print","type":"boolean","description":"Output active config to STDOUT","required":false,"allowNo":false},"version":{"name":"version","type":"boolean","char":"v","description":"Print Theme Check version","required":false,"allowNo":false}},"args":[],"cli2Flags":["auto-correct","category","config","exclude-category","fail-level","init","list","output","print","version"]},"theme:delete":{"id":"theme:delete","description":"Delete remote themes from the connected store. This command can't be undone.","strict":false,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false},"development":{"name":"development","type":"boolean","char":"d","description":"Delete your development theme.","allowNo":false},"show-all":{"name":"show-all","type":"boolean","char":"a","description":"Include others development themes in theme list.","allowNo":false},"force":{"name":"force","type":"boolean","char":"f","description":"Skip confirmation.","allowNo":false},"theme":{"name":"theme","type":"option","char":"t","description":"Theme ID or name of the remote theme.","multiple":true},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false}},"args":[]},"theme:dev":{"id":"theme:dev","description":"Uploads the current theme as a development theme to the connected store, then prints theme editor and preview URLs to your terminal. While running, changes will push to the store in real time.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"path":{"name":"path","type":"option","description":"The path to your theme directory.","hidden":false,"multiple":false,"default":"."},"host":{"name":"host","type":"option","description":"Set which network interface the web server listens on. The default value is 127.0.0.1.","multiple":false},"live-reload":{"name":"live-reload","type":"option","description":"The live reload mode switches the server behavior when a file is modified:\n- hot-reload Hot reloads local changes to CSS and sections (default)\n- full-page Always refreshes the entire page\n- off Deactivate live reload","multiple":false,"options":["hot-reload","full-page","off"],"default":"hot-reload"},"poll":{"name":"poll","type":"boolean","description":"Force polling to detect file changes.","allowNo":false},"theme-editor-sync":{"name":"theme-editor-sync","type":"boolean","char":"e","description":"Synchronize Theme Editor updates in the local theme files.","allowNo":false},"port":{"name":"port","type":"option","description":"Local port to serve theme preview from.","multiple":false},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false},"theme":{"name":"theme","type":"option","char":"t","description":"Theme ID or name of the remote theme.","multiple":false},"only":{"name":"only","type":"option","char":"o","description":"Hot reload only files that match the specified pattern.","multiple":true},"ignore":{"name":"ignore","type":"option","char":"x","description":"Skip hot reloading any files that match the specified pattern.","multiple":true},"stable":{"name":"stable","type":"boolean","description":"Performs the upload by relying in the legacy upload approach (slower, but it might be more stable in some scenarios)","hidden":true,"allowNo":false},"force":{"name":"force","type":"boolean","char":"f","description":"Proceed without confirmation, if current directory does not seem to be theme directory.","hidden":true,"allowNo":false},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false}},"args":[],"cli2Flags":["host","live-reload","poll","theme-editor-sync","port","theme","only","ignore","stable","force"]},"theme:help-old":{"id":"theme:help-old","description":"Show help from Ruby CLI","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","hidden":true,"aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"command":{"name":"command","type":"option","description":"The command for which to show CLI2 help.","multiple":false}},"args":[]},"theme:info":{"id":"theme:info","description":"Print basic information about your theme environment.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false}},"args":[]},"theme:init":{"id":"theme:init","description":"Clones a Git repository to use as a starting point for building a new theme.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"path":{"name":"path","type":"option","description":"The path to your theme directory.","hidden":false,"multiple":false,"default":"."},"clone-url":{"name":"clone-url","type":"option","char":"u","description":"The Git URL to clone from. Defaults to Shopify's example theme, Dawn: https://github.com/Shopify/dawn.git","multiple":false,"default":"https://github.com/Shopify/dawn.git"},"latest":{"name":"latest","type":"boolean","char":"l","description":"Downloads the latest release of the `clone-url`","allowNo":false}},"args":[{"name":"name","description":"Name of the new theme","required":false}]},"theme:language-server":{"id":"theme:language-server","description":"Start a Language Server Protocol server.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false}},"args":[]},"theme:list":{"id":"theme:list","description":"Lists your remote themes.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false},"role":{"name":"role","type":"option","description":"Only list themes with the given role.","helpValue":"(live|unpublished|development)","multiple":false,"options":["live","unpublished","development"]},"name":{"name":"name","type":"option","description":"Only list themes that contain the given name.","multiple":false},"id":{"name":"id","type":"option","description":"Only list theme with the given ID.","multiple":false}},"args":[]},"theme:open":{"id":"theme:open","description":"Opens the preview of your remote theme.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false},"development":{"name":"development","type":"boolean","char":"d","description":"Delete your development theme.","allowNo":false},"editor":{"name":"editor","type":"boolean","char":"e","description":"Open the theme editor for the specified theme in the browser.","allowNo":false},"live":{"name":"live","type":"boolean","char":"l","description":"Pull theme files from your remote live theme.","allowNo":false},"theme":{"name":"theme","type":"option","char":"t","description":"Theme ID or name of the remote theme.","multiple":false},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false}},"args":[]},"theme:package":{"id":"theme:package","description":"Package your theme into a .zip file, ready to upload to the Online Store.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"path":{"name":"path","type":"option","description":"The path to your theme directory.","hidden":false,"multiple":false,"default":"."}},"args":[]},"theme:publish":{"id":"theme:publish","description":"Set a remote theme as the live theme.","strict":false,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false},"force":{"name":"force","type":"boolean","char":"f","description":"Skip confirmation.","allowNo":false},"theme":{"name":"theme","type":"option","char":"t","description":"Theme ID or name of the remote theme.","multiple":false},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false}},"args":[]},"theme:pull":{"id":"theme:pull","description":"Download your remote theme files locally.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"path":{"name":"path","type":"option","description":"The path to your theme directory.","hidden":false,"multiple":false,"default":"."},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false},"theme":{"name":"theme","type":"option","char":"t","description":"Theme ID or name of the remote theme.","multiple":false},"development":{"name":"development","type":"boolean","char":"d","description":"Pull theme files from your remote development theme.","allowNo":false},"live":{"name":"live","type":"boolean","char":"l","description":"Pull theme files from your remote live theme.","allowNo":false},"nodelete":{"name":"nodelete","type":"boolean","char":"n","description":"Runs the pull command without deleting local files.","allowNo":false},"only":{"name":"only","type":"option","char":"o","description":"Download only the specified files (Multiple flags allowed).","multiple":true},"ignore":{"name":"ignore","type":"option","char":"x","description":"Skip downloading the specified files (Multiple flags allowed).","multiple":true},"force":{"name":"force","type":"boolean","char":"f","description":"Proceed without confirmation, if current directory does not seem to be theme directory.","hidden":true,"allowNo":false}},"args":[],"cli2Flags":["theme","development","live","nodelete","only","ignore","force"]},"theme:push":{"id":"theme:push","description":"Uploads your local theme files to the connected store, overwriting the remote version if specified.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"path":{"name":"path","type":"option","description":"The path to your theme directory.","hidden":false,"multiple":false,"default":"."},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false},"theme":{"name":"theme","type":"option","char":"t","description":"Theme ID or name of the remote theme.","multiple":false},"development":{"name":"development","type":"boolean","char":"d","description":"Push theme files from your remote development theme.","allowNo":false},"live":{"name":"live","type":"boolean","char":"l","description":"Push theme files from your remote live theme.","allowNo":false},"unpublished":{"name":"unpublished","type":"boolean","char":"u","description":"Create a new unpublished theme and push to it.","allowNo":false},"nodelete":{"name":"nodelete","type":"boolean","char":"n","description":"Runs the push command without deleting local files.","allowNo":false},"only":{"name":"only","type":"option","char":"o","description":"Download only the specified files (Multiple flags allowed).","multiple":true},"ignore":{"name":"ignore","type":"option","char":"x","description":"Skip downloading the specified files (Multiple flags allowed).","multiple":true},"json":{"name":"json","type":"boolean","char":"j","description":"Output JSON instead of a UI.","allowNo":false},"allow-live":{"name":"allow-live","type":"boolean","char":"a","description":"Allow push to a live theme.","allowNo":false},"publish":{"name":"publish","type":"boolean","char":"p","description":"Publish as the live theme after uploading.","allowNo":false},"stable":{"name":"stable","type":"boolean","description":"Performs the upload by relying in the legacy upload approach (slower, but it might be more stable in some scenarios)","hidden":true,"allowNo":false},"force":{"name":"force","type":"boolean","char":"f","description":"Proceed without confirmation, if current directory does not seem to be theme directory.","hidden":true,"allowNo":false}},"args":[],"cli2Flags":["theme","development","live","unpublished","nodelete","only","ignore","json","allow-live","publish","stable","force"]},"theme:share":{"id":"theme:share","description":"Creates a shareable, unpublished, and new theme on your theme library with a randomized name. Works like an alias to {{command:theme push -u -t=RANDOMIZED_NAME}}.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"path":{"name":"path","type":"option","description":"The path to your theme directory.","hidden":false,"multiple":false,"default":"."},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false},"force":{"name":"force","type":"boolean","char":"f","description":"Proceed without confirmation, if current directory does not seem to be theme directory.","hidden":true,"allowNo":false}},"args":[],"cli2Flags":["force"]}}} \ No newline at end of file +{"version":"3.39.0","commands":{"theme:check":{"id":"theme:check","description":"Validate the theme.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"path":{"name":"path","type":"option","description":"The path to your theme directory.","hidden":false,"multiple":false,"default":"."},"auto-correct":{"name":"auto-correct","type":"boolean","char":"a","description":"Automatically fix offenses","required":false,"allowNo":false},"category":{"name":"category","type":"option","char":"c","description":"Only run this category of checks\nRuns checks matching all categories when specified more than once","required":false,"multiple":false},"config":{"name":"config","type":"option","char":"C","description":"Use the config provided, overriding .theme-check.yml if present\nUse :theme_app_extension to use default checks for theme app extensions","required":false,"multiple":false},"exclude-category":{"name":"exclude-category","type":"option","char":"x","description":"Exclude this category of checks\nExcludes checks matching any category when specified more than once","required":false,"multiple":false},"fail-level":{"name":"fail-level","type":"option","description":"Minimum severity for exit with error code","required":false,"multiple":false,"options":["error","suggestion","style"]},"init":{"name":"init","type":"boolean","description":"Generate a .theme-check.yml file","required":false,"allowNo":false},"list":{"name":"list","type":"boolean","description":"List enabled checks","required":false,"allowNo":false},"output":{"name":"output","type":"option","char":"o","description":"The output format to use","required":false,"multiple":false,"options":["text","json"],"default":"text"},"print":{"name":"print","type":"boolean","description":"Output active config to STDOUT","required":false,"allowNo":false},"version":{"name":"version","type":"boolean","char":"v","description":"Print Theme Check version","required":false,"allowNo":false}},"args":[],"cli2Flags":["auto-correct","category","config","exclude-category","fail-level","init","list","output","print","version"]},"theme:delete":{"id":"theme:delete","description":"Delete remote themes from the connected store. This command can't be undone.","strict":false,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false},"development":{"name":"development","type":"boolean","char":"d","description":"Delete your development theme.","allowNo":false},"show-all":{"name":"show-all","type":"boolean","char":"a","description":"Include others development themes in theme list.","allowNo":false},"force":{"name":"force","type":"boolean","char":"f","description":"Skip confirmation.","allowNo":false},"theme":{"name":"theme","type":"option","char":"t","description":"Theme ID or name of the remote theme.","multiple":true},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false}},"args":[]},"theme:dev":{"id":"theme:dev","description":"Uploads the current theme as a development theme to the connected store, then prints theme editor and preview URLs to your terminal. While running, changes will push to the store in real time.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"path":{"name":"path","type":"option","description":"The path to your theme directory.","hidden":false,"multiple":false,"default":"."},"host":{"name":"host","type":"option","description":"Set which network interface the web server listens on. The default value is 127.0.0.1.","multiple":false},"live-reload":{"name":"live-reload","type":"option","description":"The live reload mode switches the server behavior when a file is modified:\n- hot-reload Hot reloads local changes to CSS and sections (default)\n- full-page Always refreshes the entire page\n- off Deactivate live reload","multiple":false,"options":["hot-reload","full-page","off"],"default":"hot-reload"},"poll":{"name":"poll","type":"boolean","description":"Force polling to detect file changes.","allowNo":false},"theme-editor-sync":{"name":"theme-editor-sync","type":"boolean","char":"e","description":"Synchronize Theme Editor updates in the local theme files.","allowNo":false},"port":{"name":"port","type":"option","description":"Local port to serve theme preview from.","multiple":false},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false},"theme":{"name":"theme","type":"option","char":"t","description":"Theme ID or name of the remote theme.","multiple":false},"only":{"name":"only","type":"option","char":"o","description":"Hot reload only files that match the specified pattern.","multiple":true},"ignore":{"name":"ignore","type":"option","char":"x","description":"Skip hot reloading any files that match the specified pattern.","multiple":true},"stable":{"name":"stable","type":"boolean","description":"Performs the upload by relying in the legacy upload approach (slower, but it might be more stable in some scenarios)","hidden":true,"allowNo":false},"force":{"name":"force","type":"boolean","char":"f","description":"Proceed without confirmation, if current directory does not seem to be theme directory.","hidden":true,"allowNo":false},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false}},"args":[],"cli2Flags":["host","live-reload","poll","theme-editor-sync","overwrite-json","port","theme","only","ignore","stable","force"]},"theme:help-old":{"id":"theme:help-old","description":"Show help from Ruby CLI","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","hidden":true,"aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"command":{"name":"command","type":"option","description":"The command for which to show CLI2 help.","multiple":false}},"args":[]},"theme:info":{"id":"theme:info","description":"Print basic information about your theme environment.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false}},"args":[]},"theme:init":{"id":"theme:init","description":"Clones a Git repository to use as a starting point for building a new theme.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"path":{"name":"path","type":"option","description":"The path to your theme directory.","hidden":false,"multiple":false,"default":"."},"clone-url":{"name":"clone-url","type":"option","char":"u","description":"The Git URL to clone from. Defaults to Shopify's example theme, Dawn: https://github.com/Shopify/dawn.git","multiple":false,"default":"https://github.com/Shopify/dawn.git"},"latest":{"name":"latest","type":"boolean","char":"l","description":"Downloads the latest release of the `clone-url`","allowNo":false}},"args":[{"name":"name","description":"Name of the new theme","required":false}]},"theme:language-server":{"id":"theme:language-server","description":"Start a Language Server Protocol server.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false}},"args":[]},"theme:list":{"id":"theme:list","description":"Lists your remote themes.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false},"role":{"name":"role","type":"option","description":"Only list themes with the given role.","helpValue":"(live|unpublished|development)","multiple":false,"options":["live","unpublished","development"]},"name":{"name":"name","type":"option","description":"Only list themes that contain the given name.","multiple":false},"id":{"name":"id","type":"option","description":"Only list theme with the given ID.","multiple":false}},"args":[]},"theme:open":{"id":"theme:open","description":"Opens the preview of your remote theme.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false},"development":{"name":"development","type":"boolean","char":"d","description":"Delete your development theme.","allowNo":false},"editor":{"name":"editor","type":"boolean","char":"e","description":"Open the theme editor for the specified theme in the browser.","allowNo":false},"live":{"name":"live","type":"boolean","char":"l","description":"Pull theme files from your remote live theme.","allowNo":false},"theme":{"name":"theme","type":"option","char":"t","description":"Theme ID or name of the remote theme.","multiple":false},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false}},"args":[]},"theme:package":{"id":"theme:package","description":"Package your theme into a .zip file, ready to upload to the Online Store.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"path":{"name":"path","type":"option","description":"The path to your theme directory.","hidden":false,"multiple":false,"default":"."}},"args":[]},"theme:publish":{"id":"theme:publish","description":"Set a remote theme as the live theme.","strict":false,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false},"force":{"name":"force","type":"boolean","char":"f","description":"Skip confirmation.","allowNo":false},"theme":{"name":"theme","type":"option","char":"t","description":"Theme ID or name of the remote theme.","multiple":false},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false}},"args":[]},"theme:pull":{"id":"theme:pull","description":"Download your remote theme files locally.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"path":{"name":"path","type":"option","description":"The path to your theme directory.","hidden":false,"multiple":false,"default":"."},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false},"theme":{"name":"theme","type":"option","char":"t","description":"Theme ID or name of the remote theme.","multiple":false},"development":{"name":"development","type":"boolean","char":"d","description":"Pull theme files from your remote development theme.","allowNo":false},"live":{"name":"live","type":"boolean","char":"l","description":"Pull theme files from your remote live theme.","allowNo":false},"nodelete":{"name":"nodelete","type":"boolean","char":"n","description":"Runs the pull command without deleting local files.","allowNo":false},"only":{"name":"only","type":"option","char":"o","description":"Download only the specified files (Multiple flags allowed).","multiple":true},"ignore":{"name":"ignore","type":"option","char":"x","description":"Skip downloading the specified files (Multiple flags allowed).","multiple":true},"force":{"name":"force","type":"boolean","char":"f","description":"Proceed without confirmation, if current directory does not seem to be theme directory.","hidden":true,"allowNo":false}},"args":[],"cli2Flags":["theme","development","live","nodelete","only","ignore","force"]},"theme:push":{"id":"theme:push","description":"Uploads your local theme files to the connected store, overwriting the remote version if specified.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"path":{"name":"path","type":"option","description":"The path to your theme directory.","hidden":false,"multiple":false,"default":"."},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false},"theme":{"name":"theme","type":"option","char":"t","description":"Theme ID or name of the remote theme.","multiple":false},"development":{"name":"development","type":"boolean","char":"d","description":"Push theme files from your remote development theme.","allowNo":false},"live":{"name":"live","type":"boolean","char":"l","description":"Push theme files from your remote live theme.","allowNo":false},"unpublished":{"name":"unpublished","type":"boolean","char":"u","description":"Create a new unpublished theme and push to it.","allowNo":false},"nodelete":{"name":"nodelete","type":"boolean","char":"n","description":"Runs the push command without deleting local files.","allowNo":false},"only":{"name":"only","type":"option","char":"o","description":"Download only the specified files (Multiple flags allowed).","multiple":true},"ignore":{"name":"ignore","type":"option","char":"x","description":"Skip downloading the specified files (Multiple flags allowed).","multiple":true},"json":{"name":"json","type":"boolean","char":"j","description":"Output JSON instead of a UI.","allowNo":false},"allow-live":{"name":"allow-live","type":"boolean","char":"a","description":"Allow push to a live theme.","allowNo":false},"publish":{"name":"publish","type":"boolean","char":"p","description":"Publish as the live theme after uploading.","allowNo":false},"stable":{"name":"stable","type":"boolean","description":"Performs the upload by relying in the legacy upload approach (slower, but it might be more stable in some scenarios)","hidden":true,"allowNo":false},"force":{"name":"force","type":"boolean","char":"f","description":"Proceed without confirmation, if current directory does not seem to be theme directory.","hidden":true,"allowNo":false}},"args":[],"cli2Flags":["theme","development","live","unpublished","nodelete","only","ignore","json","allow-live","publish","stable","force"]},"theme:share":{"id":"theme:share","description":"Creates a shareable, unpublished, and new theme on your theme library with a randomized name. Works like an alias to {{command:theme push -u -t=RANDOMIZED_NAME}}.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"preset":{"name":"preset","type":"option","description":"The preset to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"path":{"name":"path","type":"option","description":"The path to your theme directory.","hidden":false,"multiple":false,"default":"."},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false},"force":{"name":"force","type":"boolean","char":"f","description":"Proceed without confirmation, if current directory does not seem to be theme directory.","hidden":true,"allowNo":false}},"args":[],"cli2Flags":["force"]}}} \ No newline at end of file From fd652767b059e85ecff87df64fea866c43137f33 Mon Sep 17 00:00:00 2001 From: Julien Poitrin Date: Tue, 31 Jan 2023 14:07:31 +0100 Subject: [PATCH 09/12] Use `getThemeStore` --- packages/theme/src/cli/services/conf.ts | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/packages/theme/src/cli/services/conf.ts b/packages/theme/src/cli/services/conf.ts index 0a8dcd2ef46..9f0c8f8f11c 100644 --- a/packages/theme/src/cli/services/conf.ts +++ b/packages/theme/src/cli/services/conf.ts @@ -1,4 +1,3 @@ -import {ensureThemeStore} from '../utilities/theme-store.js' import {Conf} from '@shopify/cli-kit/node/conf' import {outputDebug, outputContent} from '@shopify/cli-kit/node/output' @@ -22,14 +21,6 @@ export function themeConf() { return _themeConfInstance } -export function getThemeStore() { - return themeConf().get('themeStore') -} - -export function setThemeStore(store: string) { - themeConf().set('themeStore', store) -} - export function developmentThemeConf() { if (!_developmentThemeConfInstance) { _developmentThemeConfInstance = new Conf({ @@ -39,17 +30,25 @@ export function developmentThemeConf() { return _developmentThemeConfInstance } +export function getThemeStore() { + return themeConf().get('themeStore') +} + +export function setThemeStore(store: string) { + themeConf().set('themeStore', store) +} + export function getDevelopmentTheme(): string | undefined { outputDebug(outputContent`Getting development theme...`) - return developmentThemeConf().get(ensureThemeStore({store: undefined})) + return developmentThemeConf().get(getThemeStore()) } export function setDevelopmentTheme(theme: string): void { outputDebug(outputContent`Setting development theme...`) - developmentThemeConf().set(ensureThemeStore({store: undefined}), theme) + developmentThemeConf().set(getThemeStore(), theme) } export function removeDevelopmentTheme(): void { outputDebug(outputContent`Removing development theme...`) - developmentThemeConf().reset(ensureThemeStore({store: undefined})) + developmentThemeConf().reset(getThemeStore()) } From 150749bfbad1117c7e49e45aec9cd46656a14d00 Mon Sep 17 00:00:00 2001 From: Julien Poitrin Date: Fri, 3 Feb 2023 10:20:07 +0100 Subject: [PATCH 10/12] Do not let `shopify theme dev --theme-editor-sync` always override local JSON files --- packages/theme/src/cli/commands/theme/dev.ts | 4 ++-- packages/theme/src/cli/commands/theme/push.ts | 2 +- packages/theme/src/cli/models/theme.ts | 2 +- .../src/cli/utilities/development-theme-manager.test.ts | 6 +++--- .../theme/src/cli/utilities/development-theme-manager.ts | 8 ++++---- packages/theme/src/cli/utilities/themes-api.ts | 4 ++-- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/theme/src/cli/commands/theme/dev.ts b/packages/theme/src/cli/commands/theme/dev.ts index 99a506406cf..fcd32a7d42c 100644 --- a/packages/theme/src/cli/commands/theme/dev.ts +++ b/packages/theme/src/cli/commands/theme/dev.ts @@ -104,8 +104,8 @@ export default class Dev extends ThemeCommand { const theme = await new DevelopmentThemeManager(adminSession).findOrCreate() flags = { ...flags, - theme, - 'overwrite-json': Boolean(flags['theme-editor-sync']), + theme: theme.id.toString(), + 'overwrite-json': Boolean(flags['theme-editor-sync']) && theme.createdAtRuntime, } const flagsToPass = this.passThroughFlags(flags, {allowedFlags: Dev.cli2Flags}) diff --git a/packages/theme/src/cli/commands/theme/push.ts b/packages/theme/src/cli/commands/theme/push.ts index 788c9a6e4af..4fd35af529b 100644 --- a/packages/theme/src/cli/commands/theme/push.ts +++ b/packages/theme/src/cli/commands/theme/push.ts @@ -105,7 +105,7 @@ export default class Push extends ThemeCommand { flags = { ...flags, development: false, - theme, + theme: theme.id.toString(), } } diff --git a/packages/theme/src/cli/models/theme.ts b/packages/theme/src/cli/models/theme.ts index d1728ef2342..df8a68fe03a 100644 --- a/packages/theme/src/cli/models/theme.ts +++ b/packages/theme/src/cli/models/theme.ts @@ -1,7 +1,7 @@ export const DEVELOPMENT_THEME_ROLE = 'development' export class Theme { - constructor(public id: number, public name: string, private _role: string) {} + constructor(public id: number, public name: string, private _role: string, public createdAtRuntime = false) {} get role(): string { if (this._role === 'main') { diff --git a/packages/theme/src/cli/utilities/development-theme-manager.test.ts b/packages/theme/src/cli/utilities/development-theme-manager.test.ts index ac0ca01014d..6c43d38c2d3 100644 --- a/packages/theme/src/cli/utilities/development-theme-manager.test.ts +++ b/packages/theme/src/cli/utilities/development-theme-manager.test.ts @@ -66,19 +66,19 @@ describe('DevelopmentThemeManager', () => { it('should not create a new development theme if API returns theme with locally stored ID', async () => { const theme = existingId.toString() localDevelopmentThemeId = theme - expect(await buildDevelopmentThemeManager().findOrCreate()).toEqual(theme) + expect((await buildDevelopmentThemeManager().findOrCreate()).id.toString()).toEqual(theme) }) it('should create a new development theme if no ID is locally stored', async () => { localDevelopmentThemeId = undefined - expect(await buildDevelopmentThemeManager().findOrCreate()).toEqual(newThemeId.toString()) + expect((await buildDevelopmentThemeManager().findOrCreate()).id.toString()).toEqual(newThemeId.toString()) expect(removeDevelopmentTheme).not.toHaveBeenCalled() }) it('should create a new development theme if locally existing ID points to nowhere', async () => { const theme = onlyLocallyExistingId.toString() localDevelopmentThemeId = theme - expect(await buildDevelopmentThemeManager().findOrCreate()).toEqual(newThemeId.toString()) + expect((await buildDevelopmentThemeManager().findOrCreate()).id.toString()).toEqual(newThemeId.toString()) expect(removeDevelopmentTheme).toHaveBeenCalledOnce() }) }) diff --git a/packages/theme/src/cli/utilities/development-theme-manager.ts b/packages/theme/src/cli/utilities/development-theme-manager.ts index c12db836e82..4d67240f334 100644 --- a/packages/theme/src/cli/utilities/development-theme-manager.ts +++ b/packages/theme/src/cli/utilities/development-theme-manager.ts @@ -1,6 +1,6 @@ import {fetchTheme, createTheme} from './themes-api.js' import {generateDevelopmentThemeName} from './generate-development-theme-name.js' -import {DEVELOPMENT_THEME_ROLE} from '../models/theme.js' +import {DEVELOPMENT_THEME_ROLE, Theme} from '../models/theme.js' import {getDevelopmentTheme, setDevelopmentTheme, removeDevelopmentTheme} from '../services/conf.js' import {AdminSession} from '@shopify/cli-kit/node/session' import {AbortError, BugError} from '@shopify/cli-kit/node/error' @@ -17,7 +17,7 @@ export class DevelopmentThemeManager { this.themeId = getDevelopmentTheme() } - async find() { + async find(): Promise { const theme = await this.fetch() if (!theme) { throw new AbortError(this.themeId ? DEVELOPMENT_THEME_NOT_FOUND(this.themeId) : NO_DEVELOPMENT_THEME_ID_SET) @@ -25,12 +25,12 @@ export class DevelopmentThemeManager { return theme } - async findOrCreate() { + async findOrCreate(): Promise { let theme = await this.fetch() if (!theme) { theme = await this.create() } - return theme.id.toString() + return theme } private async fetch() { diff --git a/packages/theme/src/cli/utilities/themes-api.ts b/packages/theme/src/cli/utilities/themes-api.ts index 59df2ceaffe..591801acbc3 100644 --- a/packages/theme/src/cli/utilities/themes-api.ts +++ b/packages/theme/src/cli/utilities/themes-api.ts @@ -21,7 +21,7 @@ export async function fetchThemes(session: AdminSession): Promise { export async function createTheme(params: ThemeParams, session: AdminSession): Promise { const response = await request('POST', '/themes', session, {theme: {...params}}) - return buildTheme(response.json.theme) + return buildTheme({...response.json.theme, createdAtRuntime: true}) } export async function updateTheme(id: number, params: ThemeParams, session: AdminSession): Promise { @@ -92,7 +92,7 @@ function buildTheme(themeJson: any): Theme | undefined { return undefined } - return new Theme(themeJson.id, themeJson.name, themeJson.role) + return new Theme(themeJson.id, themeJson.name, themeJson.role, themeJson.createdAtRuntime) } function handleForbiddenError(session: AdminSession): never { From c1af0754862fc4940e4325a53a64d784e4e5d0f3 Mon Sep 17 00:00:00 2001 From: Julien Poitrin Date: Fri, 3 Feb 2023 10:29:04 +0100 Subject: [PATCH 11/12] Move refactoring from fix-532 into fix-509 --- .eslintrc.cjs | 2 + .../node/themes/generate-theme-name.test.ts} | 10 ++-- .../node/themes/generate-theme-name.ts} | 8 +-- .../src/public/node/themes}/models/theme.ts | 0 .../replace-invalid-characters.test.ts | 0 .../themes}/replace-invalid-characters.ts | 0 .../src/public/node/themes/theme-manager.ts | 50 +++++++++++++++++++ .../public/node/themes}/theme-urls.test.ts | 2 +- .../src/public/node/themes}/theme-urls.ts | 2 +- .../public/node/themes}/themes-api.test.ts | 0 .../src/public/node/themes}/themes-api.ts | 2 +- .../node/themes}/themes-api/headers.test.ts | 0 .../public/node/themes}/themes-api/headers.ts | 0 .../public/node/themes}/themes-api/retry.ts | 0 .../node/themes}/themes-api/throttler.ts | 0 packages/theme/src/cli/services/conf.ts | 4 +- .../theme/src/cli/services/delete.test.ts | 6 +-- packages/theme/src/cli/services/delete.ts | 4 +- packages/theme/src/cli/services/list.test.ts | 2 +- packages/theme/src/cli/services/list.ts | 2 +- packages/theme/src/cli/services/open.test.ts | 2 +- packages/theme/src/cli/services/open.ts | 2 +- .../theme/src/cli/services/publish.test.ts | 6 +-- packages/theme/src/cli/services/publish.ts | 6 +-- .../development-theme-manager.test.ts | 6 +-- .../utilities/development-theme-manager.ts | 50 ++++--------------- .../src/cli/utilities/theme-selector.test.ts | 2 +- .../utilities/theme-selector/fetch.test.ts | 6 +-- .../src/cli/utilities/theme-selector/fetch.ts | 4 +- .../utilities/theme-selector/filter.test.ts | 2 +- .../cli/utilities/theme-selector/filter.ts | 2 +- .../theme/src/cli/utilities/theme-ui.test.ts | 2 +- packages/theme/src/cli/utilities/theme-ui.ts | 2 +- 33 files changed, 106 insertions(+), 80 deletions(-) rename packages/{theme/src/cli/utilities/generate-development-theme-name.test.ts => cli-kit/src/public/node/themes/generate-theme-name.test.ts} (64%) rename packages/{theme/src/cli/utilities/generate-development-theme-name.ts => cli-kit/src/public/node/themes/generate-theme-name.ts} (78%) rename packages/{theme/src/cli => cli-kit/src/public/node/themes}/models/theme.ts (100%) rename packages/{theme/src/cli/utilities => cli-kit/src/public/node/themes}/replace-invalid-characters.test.ts (100%) rename packages/{theme/src/cli/utilities => cli-kit/src/public/node/themes}/replace-invalid-characters.ts (100%) create mode 100644 packages/cli-kit/src/public/node/themes/theme-manager.ts rename packages/{theme/src/cli/utilities => cli-kit/src/public/node/themes}/theme-urls.test.ts (96%) rename packages/{theme/src/cli/utilities => cli-kit/src/public/node/themes}/theme-urls.ts (90%) rename packages/{theme/src/cli/utilities => cli-kit/src/public/node/themes}/themes-api.test.ts (100%) rename packages/{theme/src/cli/utilities => cli-kit/src/public/node/themes}/themes-api.ts (99%) rename packages/{theme/src/cli/utilities => cli-kit/src/public/node/themes}/themes-api/headers.test.ts (100%) rename packages/{theme/src/cli/utilities => cli-kit/src/public/node/themes}/themes-api/headers.ts (100%) rename packages/{theme/src/cli/utilities => cli-kit/src/public/node/themes}/themes-api/retry.ts (100%) rename packages/{theme/src/cli/utilities => cli-kit/src/public/node/themes}/themes-api/throttler.ts (100%) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index f40c3e7379b..11dd8d7957c 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -188,6 +188,7 @@ module.exports = { '**/public/node/plugins/tunnel.ts', '**/public/node/presets.ts', '**/public/node/result.ts', + '**/public/node/themes/**/*', ], rules: { 'jsdoc/check-access': 'error', @@ -249,6 +250,7 @@ module.exports = { rules: { '@typescript-eslint/explicit-module-boundary-types': 'error', }, + excludedFiles: ['**/public/node/themes/**/*'], }, ], } diff --git a/packages/theme/src/cli/utilities/generate-development-theme-name.test.ts b/packages/cli-kit/src/public/node/themes/generate-theme-name.test.ts similarity index 64% rename from packages/theme/src/cli/utilities/generate-development-theme-name.test.ts rename to packages/cli-kit/src/public/node/themes/generate-theme-name.test.ts index 04962c669b7..f8c1195a702 100644 --- a/packages/theme/src/cli/utilities/generate-development-theme-name.test.ts +++ b/packages/cli-kit/src/public/node/themes/generate-theme-name.test.ts @@ -1,4 +1,4 @@ -import {generateDevelopmentThemeName} from './generate-development-theme-name.js' +import {generateThemeName} from './generate-theme-name.js' import {beforeEach, describe, expect, it, vi} from 'vitest' import {hostname} from 'os' import {randomBytes} from 'crypto' @@ -6,18 +6,20 @@ import {randomBytes} from 'crypto' vi.mock('os') vi.mock('crypto') -describe('generateName', () => { +describe('generateThemeName', () => { + const context = 'Development' + beforeEach(() => { vi.mocked(randomBytes).mockImplementation(() => Buffer.from([1, 2, 3])) }) it('should not truncate if the theme name is below the API limit', () => { vi.mocked(hostname).mockReturnValue('Mac-Book-Pro.My-Router') - expect(generateDevelopmentThemeName()).toEqual('Development (010203-Mac-Book-Pro)') + expect(generateThemeName(context)).toEqual('Development (010203-Mac-Book-Pro)') }) it('should truncate if the theme name is above the API limit', () => { vi.mocked(hostname).mockReturnValue('theme-dev-lan-very-long-name-that-will-be-truncated') - expect(generateDevelopmentThemeName()).toEqual('Development (010203-theme-dev-lan-very-long-name-t)') + expect(generateThemeName(context)).toEqual('Development (010203-theme-dev-lan-very-long-name-t)') }) }) diff --git a/packages/theme/src/cli/utilities/generate-development-theme-name.ts b/packages/cli-kit/src/public/node/themes/generate-theme-name.ts similarity index 78% rename from packages/theme/src/cli/utilities/generate-development-theme-name.ts rename to packages/cli-kit/src/public/node/themes/generate-theme-name.ts index f7eba8caa54..34adf137ac9 100644 --- a/packages/theme/src/cli/utilities/generate-development-theme-name.ts +++ b/packages/cli-kit/src/public/node/themes/generate-theme-name.ts @@ -1,15 +1,15 @@ import {replaceInvalidCharacters} from './replace-invalid-characters.js' -import {hostname} from 'os' import {randomBytes} from 'crypto' +import {hostname} from 'os' const API_NAME_LIMIT = 50 -export function generateDevelopmentThemeName(): string { +export function generateThemeName(context: string): string { const hostNameWithoutDomain = hostname().split('.')[0]! const hash = randomBytes(3).toString('hex') - const name = 'Development ()' + const name = `${context} ()` const hostNameCharacterLimit = API_NAME_LIMIT - name.length - hash.length const identifier = replaceInvalidCharacters(`${hash}-${hostNameWithoutDomain.substring(0, hostNameCharacterLimit)}`) - return `Development (${identifier})` + return `${context} (${identifier})` } diff --git a/packages/theme/src/cli/models/theme.ts b/packages/cli-kit/src/public/node/themes/models/theme.ts similarity index 100% rename from packages/theme/src/cli/models/theme.ts rename to packages/cli-kit/src/public/node/themes/models/theme.ts diff --git a/packages/theme/src/cli/utilities/replace-invalid-characters.test.ts b/packages/cli-kit/src/public/node/themes/replace-invalid-characters.test.ts similarity index 100% rename from packages/theme/src/cli/utilities/replace-invalid-characters.test.ts rename to packages/cli-kit/src/public/node/themes/replace-invalid-characters.test.ts diff --git a/packages/theme/src/cli/utilities/replace-invalid-characters.ts b/packages/cli-kit/src/public/node/themes/replace-invalid-characters.ts similarity index 100% rename from packages/theme/src/cli/utilities/replace-invalid-characters.ts rename to packages/cli-kit/src/public/node/themes/replace-invalid-characters.ts diff --git a/packages/cli-kit/src/public/node/themes/theme-manager.ts b/packages/cli-kit/src/public/node/themes/theme-manager.ts new file mode 100644 index 00000000000..baa70618622 --- /dev/null +++ b/packages/cli-kit/src/public/node/themes/theme-manager.ts @@ -0,0 +1,50 @@ +import {fetchTheme, createTheme} from './themes-api.js' +import {DEVELOPMENT_THEME_ROLE, Theme} from './models/theme.js' +import {generateThemeName} from './generate-theme-name.js' +import {AdminSession} from '@shopify/cli-kit/node/session' +import {BugError} from '@shopify/cli-kit/node/error' + +export abstract class ThemeManager { + protected themeId: string | undefined + protected abstract setTheme(themeId: string): void + protected abstract removeTheme(): void + protected abstract context: string + + constructor(protected adminSession: AdminSession) {} + + async findOrCreate(): Promise { + let theme = await this.fetch() + if (!theme) { + theme = await this.create() + } + return theme + } + + protected async fetch() { + if (!this.themeId) { + return + } + const theme = await fetchTheme(parseInt(this.themeId, 10), this.adminSession) + if (!theme) { + this.removeTheme() + } + return theme + } + + private async create() { + const name = generateThemeName(this.context) + const role = DEVELOPMENT_THEME_ROLE + const theme = await createTheme( + { + name, + role, + }, + this.adminSession, + ) + if (!theme) { + throw new BugError(`Could not create theme with name "${name}" and role "${role}"`) + } + this.setTheme(theme.id.toString()) + return theme + } +} diff --git a/packages/theme/src/cli/utilities/theme-urls.test.ts b/packages/cli-kit/src/public/node/themes/theme-urls.test.ts similarity index 96% rename from packages/theme/src/cli/utilities/theme-urls.test.ts rename to packages/cli-kit/src/public/node/themes/theme-urls.test.ts index c9129fb8013..fdc0deda9bb 100644 --- a/packages/theme/src/cli/utilities/theme-urls.test.ts +++ b/packages/cli-kit/src/public/node/themes/theme-urls.test.ts @@ -1,5 +1,5 @@ import {storeAdminUrl, themeEditorUrl, themePreviewUrl} from './theme-urls.js' -import {Theme} from '../models/theme.js' +import {Theme} from './models/theme.js' import {test, describe, expect} from 'vitest' const session = {token: 'token', storeFqdn: 'my-shop.myshopify.com'} diff --git a/packages/theme/src/cli/utilities/theme-urls.ts b/packages/cli-kit/src/public/node/themes/theme-urls.ts similarity index 90% rename from packages/theme/src/cli/utilities/theme-urls.ts rename to packages/cli-kit/src/public/node/themes/theme-urls.ts index 520f8962fbb..cc331e91d79 100644 --- a/packages/theme/src/cli/utilities/theme-urls.ts +++ b/packages/cli-kit/src/public/node/themes/theme-urls.ts @@ -1,4 +1,4 @@ -import {Theme} from '../models/theme.js' +import {Theme} from '@shopify/cli-kit/node/themes/models/theme' import {AdminSession} from '@shopify/cli-kit/node/session' export function themePreviewUrl(theme: Theme, session: AdminSession) { diff --git a/packages/theme/src/cli/utilities/themes-api.test.ts b/packages/cli-kit/src/public/node/themes/themes-api.test.ts similarity index 100% rename from packages/theme/src/cli/utilities/themes-api.test.ts rename to packages/cli-kit/src/public/node/themes/themes-api.test.ts diff --git a/packages/theme/src/cli/utilities/themes-api.ts b/packages/cli-kit/src/public/node/themes/themes-api.ts similarity index 99% rename from packages/theme/src/cli/utilities/themes-api.ts rename to packages/cli-kit/src/public/node/themes/themes-api.ts index 591801acbc3..611deba2690 100644 --- a/packages/theme/src/cli/utilities/themes-api.ts +++ b/packages/cli-kit/src/public/node/themes/themes-api.ts @@ -2,7 +2,7 @@ import * as throttler from './themes-api/throttler.js' import {apiCallLimit, retryAfter} from './themes-api/headers.js' import {retry} from './themes-api/retry.js' import {storeAdminUrl} from './theme-urls.js' -import {Theme} from '../models/theme.js' +import {Theme} from './models/theme.js' import {restRequest, RestResponse} from '@shopify/cli-kit/node/api/admin' import {AdminSession} from '@shopify/cli-kit/node/session' import {AbortError} from '@shopify/cli-kit/node/error' diff --git a/packages/theme/src/cli/utilities/themes-api/headers.test.ts b/packages/cli-kit/src/public/node/themes/themes-api/headers.test.ts similarity index 100% rename from packages/theme/src/cli/utilities/themes-api/headers.test.ts rename to packages/cli-kit/src/public/node/themes/themes-api/headers.test.ts diff --git a/packages/theme/src/cli/utilities/themes-api/headers.ts b/packages/cli-kit/src/public/node/themes/themes-api/headers.ts similarity index 100% rename from packages/theme/src/cli/utilities/themes-api/headers.ts rename to packages/cli-kit/src/public/node/themes/themes-api/headers.ts diff --git a/packages/theme/src/cli/utilities/themes-api/retry.ts b/packages/cli-kit/src/public/node/themes/themes-api/retry.ts similarity index 100% rename from packages/theme/src/cli/utilities/themes-api/retry.ts rename to packages/cli-kit/src/public/node/themes/themes-api/retry.ts diff --git a/packages/theme/src/cli/utilities/themes-api/throttler.ts b/packages/cli-kit/src/public/node/themes/themes-api/throttler.ts similarity index 100% rename from packages/theme/src/cli/utilities/themes-api/throttler.ts rename to packages/cli-kit/src/public/node/themes/themes-api/throttler.ts diff --git a/packages/theme/src/cli/services/conf.ts b/packages/theme/src/cli/services/conf.ts index 9f0c8f8f11c..97e34ff7e13 100644 --- a/packages/theme/src/cli/services/conf.ts +++ b/packages/theme/src/cli/services/conf.ts @@ -1,14 +1,14 @@ import {Conf} from '@shopify/cli-kit/node/conf' import {outputDebug, outputContent} from '@shopify/cli-kit/node/output' -type DevelopmentOrHostThemeId = string +type DevelopmentThemeId = string export interface ThemeConfSchema { themeStore: string } interface DevelopmentThemeConfSchema { - [themeStore: string]: DevelopmentOrHostThemeId + [themeStore: string]: DevelopmentThemeId } let _themeConfInstance: Conf | undefined diff --git a/packages/theme/src/cli/services/delete.test.ts b/packages/theme/src/cli/services/delete.test.ts index 1d662183cbe..e25f72699d6 100644 --- a/packages/theme/src/cli/services/delete.test.ts +++ b/packages/theme/src/cli/services/delete.test.ts @@ -1,12 +1,12 @@ import {deleteThemes, renderDeprecatedArgsWarning} from './delete.js' -import {Theme} from '../models/theme.js' -import {deleteTheme} from '../utilities/themes-api.js' import {findOrSelectTheme, findThemes} from '../utilities/theme-selector.js' +import {deleteTheme} from '@shopify/cli-kit/node/themes/themes-api' +import {Theme} from '@shopify/cli-kit/node/themes/models/theme' import {test, describe, expect, vi} from 'vitest' import {renderConfirmationPrompt, renderSuccess, renderWarning} from '@shopify/cli-kit/node/ui' vi.mock('@shopify/cli-kit/node/ui') -vi.mock('../utilities/themes-api.js') +vi.mock('@shopify/cli-kit/node/themes/themes-api') vi.mock('../utilities/theme-selector.js') vi.mock('../utilities/development-theme-manager.js', () => { const DevelopmentThemeManager = vi.fn() diff --git a/packages/theme/src/cli/services/delete.ts b/packages/theme/src/cli/services/delete.ts index 201078a4ad4..6ce4755f8a1 100644 --- a/packages/theme/src/cli/services/delete.ts +++ b/packages/theme/src/cli/services/delete.ts @@ -1,9 +1,9 @@ import {removeDevelopmentTheme} from './conf.js' import {findOrSelectTheme, findThemes} from '../utilities/theme-selector.js' -import {Theme} from '../models/theme.js' import {themeComponent, themesComponent} from '../utilities/theme-ui.js' -import {deleteTheme} from '../utilities/themes-api.js' import {DevelopmentThemeManager} from '../utilities/development-theme-manager.js' +import {deleteTheme} from '@shopify/cli-kit/node/themes/themes-api' +import {Theme} from '@shopify/cli-kit/node/themes/models/theme' import {AdminSession} from '@shopify/cli-kit/node/session' import { renderConfirmationPrompt, diff --git a/packages/theme/src/cli/services/list.test.ts b/packages/theme/src/cli/services/list.test.ts index 1ee2d1b819e..7ea6cb969c0 100644 --- a/packages/theme/src/cli/services/list.test.ts +++ b/packages/theme/src/cli/services/list.test.ts @@ -2,7 +2,7 @@ import {list} from './list.js' import {columns} from './list.columns.js' import {getDevelopmentTheme} from './conf.js' import {fetchStoreThemes} from '../utilities/theme-selector/fetch.js' -import {Theme} from '../models/theme.js' +import {Theme} from '@shopify/cli-kit/node/themes/models/theme' import {renderTable} from '@shopify/cli-kit/node/ui' import {describe, expect, it, vi} from 'vitest' diff --git a/packages/theme/src/cli/services/list.ts b/packages/theme/src/cli/services/list.ts index 0c86622daf8..ad2befae0de 100644 --- a/packages/theme/src/cli/services/list.ts +++ b/packages/theme/src/cli/services/list.ts @@ -31,7 +31,7 @@ export async function list(adminSession: AdminSession, options: Options) { let formattedRole = '' if (role) { formattedRole = `[${role}]` - if (`${id}` === developmentTheme) { + if ([developmentTheme].includes(`${id}`)) { formattedRole += ' [yours]' } } diff --git a/packages/theme/src/cli/services/open.test.ts b/packages/theme/src/cli/services/open.test.ts index d3a4068f1f2..e5ba4aacb50 100644 --- a/packages/theme/src/cli/services/open.test.ts +++ b/packages/theme/src/cli/services/open.test.ts @@ -1,6 +1,6 @@ import {open} from './open.js' import {findOrSelectTheme} from '../utilities/theme-selector.js' -import {Theme} from '../models/theme.js' +import {Theme} from '@shopify/cli-kit/node/themes/models/theme' import {test, describe, expect, vi} from 'vitest' import {openURL} from '@shopify/cli-kit/node/system' import {renderInfo} from '@shopify/cli-kit/node/ui' diff --git a/packages/theme/src/cli/services/open.ts b/packages/theme/src/cli/services/open.ts index 7edbf3d8798..ef1547cf84d 100644 --- a/packages/theme/src/cli/services/open.ts +++ b/packages/theme/src/cli/services/open.ts @@ -1,7 +1,7 @@ import {findOrSelectTheme} from '../utilities/theme-selector.js' -import {themeEditorUrl, themePreviewUrl} from '../utilities/theme-urls.js' import {themeComponent} from '../utilities/theme-ui.js' import {DevelopmentThemeManager} from '../utilities/development-theme-manager.js' +import {themeEditorUrl, themePreviewUrl} from '@shopify/cli-kit/node/themes/theme-urls' import {openURL} from '@shopify/cli-kit/node/system' import {renderInfo} from '@shopify/cli-kit/node/ui' import {AdminSession} from '@shopify/cli-kit/node/session' diff --git a/packages/theme/src/cli/services/publish.test.ts b/packages/theme/src/cli/services/publish.test.ts index 756194f23b8..373231d5b67 100644 --- a/packages/theme/src/cli/services/publish.test.ts +++ b/packages/theme/src/cli/services/publish.test.ts @@ -1,14 +1,14 @@ import {publish} from './publish.js' import {findOrSelectTheme} from '../utilities/theme-selector.js' -import {Theme} from '../models/theme.js' -import {publishTheme} from '../utilities/themes-api.js' +import {publishTheme} from '@shopify/cli-kit/node/themes/themes-api' +import {Theme} from '@shopify/cli-kit/node/themes/models/theme' import {renderSuccess, renderConfirmationPrompt} from '@shopify/cli-kit/node/ui' import {test, describe, expect, vi} from 'vitest' vi.mock('@shopify/cli-kit/node/system') vi.mock('@shopify/cli-kit/node/ui') +vi.mock('@shopify/cli-kit/node/themes/themes-api') vi.mock('../utilities/theme-selector.js') -vi.mock('../utilities/themes-api') const session = { token: 'token', diff --git a/packages/theme/src/cli/services/publish.ts b/packages/theme/src/cli/services/publish.ts index 980a583dedf..9807fd5d365 100644 --- a/packages/theme/src/cli/services/publish.ts +++ b/packages/theme/src/cli/services/publish.ts @@ -1,8 +1,8 @@ -import {Theme} from '../models/theme.js' import {findOrSelectTheme} from '../utilities/theme-selector.js' -import {themePreviewUrl} from '../utilities/theme-urls.js' -import {publishTheme} from '../utilities/themes-api.js' import {themeComponent} from '../utilities/theme-ui.js' +import {publishTheme} from '@shopify/cli-kit/node/themes/themes-api' +import {themePreviewUrl} from '@shopify/cli-kit/node/themes/theme-urls' +import {Theme} from '@shopify/cli-kit/node/themes/models/theme' import {renderConfirmationPrompt, renderSuccess, renderWarning} from '@shopify/cli-kit/node/ui' import {AdminSession} from '@shopify/cli-kit/node/session' diff --git a/packages/theme/src/cli/utilities/development-theme-manager.test.ts b/packages/theme/src/cli/utilities/development-theme-manager.test.ts index 6c43d38c2d3..f0f8a90fec4 100644 --- a/packages/theme/src/cli/utilities/development-theme-manager.test.ts +++ b/packages/theme/src/cli/utilities/development-theme-manager.test.ts @@ -3,12 +3,12 @@ import { NO_DEVELOPMENT_THEME_ID_SET, DEVELOPMENT_THEME_NOT_FOUND, } from './development-theme-manager.js' -import {createTheme, fetchTheme} from './themes-api.js' -import {Theme} from '../models/theme.js' import {getDevelopmentTheme, setDevelopmentTheme, removeDevelopmentTheme} from '../services/conf.js' +import {createTheme, fetchTheme} from '@shopify/cli-kit/node/themes/themes-api' +import {Theme} from '@shopify/cli-kit/node/themes/models/theme' import {beforeEach, describe, expect, it, vi} from 'vitest' -vi.mock('./themes-api.js') +vi.mock('@shopify/cli-kit/node/themes/themes-api') vi.mock('../services/conf.js') describe('DevelopmentThemeManager', () => { diff --git a/packages/theme/src/cli/utilities/development-theme-manager.ts b/packages/theme/src/cli/utilities/development-theme-manager.ts index 4d67240f334..d704e23a281 100644 --- a/packages/theme/src/cli/utilities/development-theme-manager.ts +++ b/packages/theme/src/cli/utilities/development-theme-manager.ts @@ -1,19 +1,19 @@ -import {fetchTheme, createTheme} from './themes-api.js' -import {generateDevelopmentThemeName} from './generate-development-theme-name.js' -import {DEVELOPMENT_THEME_ROLE, Theme} from '../models/theme.js' import {getDevelopmentTheme, setDevelopmentTheme, removeDevelopmentTheme} from '../services/conf.js' +import {ThemeManager} from '@shopify/cli-kit/node/themes/theme-manager' import {AdminSession} from '@shopify/cli-kit/node/session' -import {AbortError, BugError} from '@shopify/cli-kit/node/error' +import {AbortError} from '@shopify/cli-kit/node/error' +import {Theme} from '@shopify/cli-kit/node/themes/models/theme' export const DEVELOPMENT_THEME_NOT_FOUND = (themeId: string) => `Development theme #${themeId} could not be found. Please create a new development theme.` export const NO_DEVELOPMENT_THEME_ID_SET = 'No development theme ID has been set. Please create a development theme first.' -export class DevelopmentThemeManager { - private themeId: string | undefined +export class DevelopmentThemeManager extends ThemeManager { + protected context = 'Development' - constructor(private adminSession: AdminSession) { + constructor(adminSession: AdminSession) { + super(adminSession) this.themeId = getDevelopmentTheme() } @@ -25,39 +25,11 @@ export class DevelopmentThemeManager { return theme } - async findOrCreate(): Promise { - let theme = await this.fetch() - if (!theme) { - theme = await this.create() - } - return theme - } - - private async fetch() { - if (!this.themeId) { - return - } - const theme = await fetchTheme(parseInt(this.themeId, 10), this.adminSession) - if (!theme) { - removeDevelopmentTheme() - } - return theme + protected setTheme(themeId: string): void { + setDevelopmentTheme(themeId) } - private async create() { - const name = generateDevelopmentThemeName() - const role = DEVELOPMENT_THEME_ROLE - const theme = await createTheme( - { - name, - role, - }, - this.adminSession, - ) - if (!theme) { - throw new BugError(`Could not create theme with name "${name}" and role "${role}"`) - } - setDevelopmentTheme(theme.id.toString()) - return theme + protected removeTheme(): void { + removeDevelopmentTheme() } } diff --git a/packages/theme/src/cli/utilities/theme-selector.test.ts b/packages/theme/src/cli/utilities/theme-selector.test.ts index 41e07b3fd36..6f14fab3d9a 100644 --- a/packages/theme/src/cli/utilities/theme-selector.test.ts +++ b/packages/theme/src/cli/utilities/theme-selector.test.ts @@ -1,6 +1,6 @@ import {fetchStoreThemes} from './theme-selector/fetch.js' import {findOrSelectTheme, findThemes} from './theme-selector.js' -import {Theme} from '../models/theme.js' +import {Theme} from '@shopify/cli-kit/node/themes/models/theme' import {test, describe, vi, expect} from 'vitest' import {renderSelectPrompt} from '@shopify/cli-kit/node/ui' diff --git a/packages/theme/src/cli/utilities/theme-selector/fetch.test.ts b/packages/theme/src/cli/utilities/theme-selector/fetch.test.ts index 26705dd33b0..3a913645a1f 100644 --- a/packages/theme/src/cli/utilities/theme-selector/fetch.test.ts +++ b/packages/theme/src/cli/utilities/theme-selector/fetch.test.ts @@ -1,12 +1,12 @@ import {fetchStoreThemes} from './fetch.js' -import {fetchThemes} from '../themes-api.js' -import {Theme} from '../../models/theme.js' +import {fetchThemes} from '@shopify/cli-kit/node/themes/themes-api' +import {Theme} from '@shopify/cli-kit/node/themes/models/theme' import {test, vi, describe, expect} from 'vitest' import {AbortError} from '@shopify/cli-kit/node/error' const session = {token: 'token', storeFqdn: 'my-shop.myshopify.com'} -vi.mock('../themes-api.js') +vi.mock('@shopify/cli-kit/node/themes/themes-api') describe('fetchStoreThemes', () => { test('returns only allowed themes', async () => { diff --git a/packages/theme/src/cli/utilities/theme-selector/fetch.ts b/packages/theme/src/cli/utilities/theme-selector/fetch.ts index d2845768afb..0642550a539 100644 --- a/packages/theme/src/cli/utilities/theme-selector/fetch.ts +++ b/packages/theme/src/cli/utilities/theme-selector/fetch.ts @@ -1,5 +1,5 @@ -import {fetchThemes} from '../themes-api.js' -import {Theme} from '../../models/theme.js' +import {fetchThemes} from '@shopify/cli-kit/node/themes/themes-api' +import {Theme} from '@shopify/cli-kit/node/themes/models/theme' import {AdminSession} from '@shopify/cli-kit/node/session' import {AbortError} from '@shopify/cli-kit/node/error' diff --git a/packages/theme/src/cli/utilities/theme-selector/filter.test.ts b/packages/theme/src/cli/utilities/theme-selector/filter.test.ts index 3378bb5bc67..acf0f8f1e0f 100644 --- a/packages/theme/src/cli/utilities/theme-selector/filter.test.ts +++ b/packages/theme/src/cli/utilities/theme-selector/filter.test.ts @@ -1,5 +1,5 @@ import {Filter, filterThemes} from './filter.js' -import {Theme} from '../../models/theme.js' +import {Theme} from '@shopify/cli-kit/node/themes/models/theme' import {test, describe, expect} from 'vitest' const store = 'my-shop.myshopify.com' diff --git a/packages/theme/src/cli/utilities/theme-selector/filter.ts b/packages/theme/src/cli/utilities/theme-selector/filter.ts index fa7ac4fba28..ba2c1ed7f70 100644 --- a/packages/theme/src/cli/utilities/theme-selector/filter.ts +++ b/packages/theme/src/cli/utilities/theme-selector/filter.ts @@ -1,5 +1,5 @@ import {ALLOWED_ROLES} from './fetch.js' -import {Theme} from '../../models/theme.js' +import {Theme} from '@shopify/cli-kit/node/themes/models/theme' import {AbortError} from '@shopify/cli-kit/node/error' export function filterThemes(store: string, themes: Theme[], filter: Filter): Theme[] { diff --git a/packages/theme/src/cli/utilities/theme-ui.test.ts b/packages/theme/src/cli/utilities/theme-ui.test.ts index f8afe2a4e47..c32268100dd 100644 --- a/packages/theme/src/cli/utilities/theme-ui.test.ts +++ b/packages/theme/src/cli/utilities/theme-ui.test.ts @@ -1,5 +1,5 @@ import {themeComponent, themesComponent} from './theme-ui.js' -import {Theme} from '../models/theme.js' +import {Theme} from '@shopify/cli-kit/node/themes/models/theme' import {test, describe, expect} from 'vitest' describe('themeComponent', () => { diff --git a/packages/theme/src/cli/utilities/theme-ui.ts b/packages/theme/src/cli/utilities/theme-ui.ts index ef1ce3bad7e..0ee3e46952b 100644 --- a/packages/theme/src/cli/utilities/theme-ui.ts +++ b/packages/theme/src/cli/utilities/theme-ui.ts @@ -1,4 +1,4 @@ -import {Theme} from '../models/theme.js' +import {Theme} from '@shopify/cli-kit/node/themes/models/theme' export function themeComponent(theme: Theme) { return [ From 0ec8ec81c579d3b0338ec3fcfc7268bb9b8afce1 Mon Sep 17 00:00:00 2001 From: Julien Poitrin Date: Mon, 6 Feb 2023 10:19:03 +0100 Subject: [PATCH 12/12] Refresh manifests --- packages/theme/oclif.manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/theme/oclif.manifest.json b/packages/theme/oclif.manifest.json index d7668777666..d6199455162 100644 --- a/packages/theme/oclif.manifest.json +++ b/packages/theme/oclif.manifest.json @@ -1 +1 @@ -{"version":"3.39.0","commands":{"theme:check":{"id":"theme:check","description":"Validate the theme.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"environment":{"name":"environment","type":"option","char":"e","description":"The environment to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"path":{"name":"path","type":"option","description":"The path to your theme directory.","hidden":false,"multiple":false,"default":"."},"auto-correct":{"name":"auto-correct","type":"boolean","char":"a","description":"Automatically fix offenses","required":false,"allowNo":false},"category":{"name":"category","type":"option","char":"c","description":"Only run this category of checks\nRuns checks matching all categories when specified more than once","required":false,"multiple":false},"config":{"name":"config","type":"option","char":"C","description":"Use the config provided, overriding .theme-check.yml if present\nUse :theme_app_extension to use default checks for theme app extensions","required":false,"multiple":false},"exclude-category":{"name":"exclude-category","type":"option","char":"x","description":"Exclude this category of checks\nExcludes checks matching any category when specified more than once","required":false,"multiple":false},"fail-level":{"name":"fail-level","type":"option","description":"Minimum severity for exit with error code","required":false,"multiple":false,"options":["error","suggestion","style"]},"init":{"name":"init","type":"boolean","description":"Generate a .theme-check.yml file","required":false,"allowNo":false},"list":{"name":"list","type":"boolean","description":"List enabled checks","required":false,"allowNo":false},"output":{"name":"output","type":"option","char":"o","description":"The output format to use","required":false,"multiple":false,"options":["text","json"],"default":"text"},"print":{"name":"print","type":"boolean","description":"Output active config to STDOUT","required":false,"allowNo":false},"version":{"name":"version","type":"boolean","char":"v","description":"Print Theme Check version","required":false,"allowNo":false}},"args":[],"cli2Flags":["auto-correct","category","config","exclude-category","fail-level","init","list","output","print","version"]},"theme:delete":{"id":"theme:delete","description":"Delete remote themes from the connected store. This command can't be undone.","strict":false,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"environment":{"name":"environment","type":"option","char":"e","description":"The environment to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false},"development":{"name":"development","type":"boolean","char":"d","description":"Delete your development theme.","allowNo":false},"show-all":{"name":"show-all","type":"boolean","char":"a","description":"Include others development themes in theme list.","allowNo":false},"force":{"name":"force","type":"boolean","char":"f","description":"Skip confirmation.","allowNo":false},"theme":{"name":"theme","type":"option","char":"t","description":"Theme ID or name of the remote theme.","multiple":true},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false}},"args":[]},"theme:dev":{"id":"theme:dev","description":"Uploads the current theme as a development theme to the connected store, then prints theme editor and preview URLs to your terminal. While running, changes will push to the store in real time.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"environment":{"name":"environment","type":"option","char":"e","description":"The environment to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"path":{"name":"path","type":"option","description":"The path to your theme directory.","hidden":false,"multiple":false,"default":"."},"host":{"name":"host","type":"option","description":"Set which network interface the web server listens on. The default value is 127.0.0.1.","multiple":false},"live-reload":{"name":"live-reload","type":"option","description":"The live reload mode switches the server behavior when a file is modified:\n- hot-reload Hot reloads local changes to CSS and sections (default)\n- full-page Always refreshes the entire page\n- off Deactivate live reload","multiple":false,"options":["hot-reload","full-page","off"],"default":"hot-reload"},"poll":{"name":"poll","type":"boolean","description":"Force polling to detect file changes.","allowNo":false},"theme-editor-sync":{"name":"theme-editor-sync","type":"boolean","char":"e","description":"Synchronize Theme Editor updates in the local theme files.","allowNo":false},"port":{"name":"port","type":"option","description":"Local port to serve theme preview from.","multiple":false},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false},"theme":{"name":"theme","type":"option","char":"t","description":"Theme ID or name of the remote theme.","multiple":false},"only":{"name":"only","type":"option","char":"o","description":"Hot reload only files that match the specified pattern.","multiple":true},"ignore":{"name":"ignore","type":"option","char":"x","description":"Skip hot reloading any files that match the specified pattern.","multiple":true},"stable":{"name":"stable","type":"boolean","description":"Performs the upload by relying in the legacy upload approach (slower, but it might be more stable in some scenarios)","hidden":true,"allowNo":false},"force":{"name":"force","type":"boolean","char":"f","description":"Proceed without confirmation, if current directory does not seem to be theme directory.","hidden":true,"allowNo":false},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false}},"args":[],"cli2Flags":["host","live-reload","poll","theme-editor-sync","port","theme","only","ignore","stable","force"]},"theme:help-old":{"id":"theme:help-old","description":"Show help from Ruby CLI","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","hidden":true,"aliases":[],"flags":{"environment":{"name":"environment","type":"option","char":"e","description":"The environment to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"command":{"name":"command","type":"option","description":"The command for which to show CLI2 help.","multiple":false}},"args":[]},"theme:info":{"id":"theme:info","description":"Print basic information about your theme environment.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"environment":{"name":"environment","type":"option","char":"e","description":"The environment to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false}},"args":[]},"theme:init":{"id":"theme:init","description":"Clones a Git repository to use as a starting point for building a new theme.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"environment":{"name":"environment","type":"option","char":"e","description":"The environment to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"path":{"name":"path","type":"option","description":"The path to your theme directory.","hidden":false,"multiple":false,"default":"."},"clone-url":{"name":"clone-url","type":"option","char":"u","description":"The Git URL to clone from. Defaults to Shopify's example theme, Dawn: https://github.com/Shopify/dawn.git","multiple":false,"default":"https://github.com/Shopify/dawn.git"},"latest":{"name":"latest","type":"boolean","char":"l","description":"Downloads the latest release of the `clone-url`","allowNo":false}},"args":[{"name":"name","description":"Name of the new theme","required":false}]},"theme:language-server":{"id":"theme:language-server","description":"Start a Language Server Protocol server.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"environment":{"name":"environment","type":"option","char":"e","description":"The environment to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false}},"args":[]},"theme:list":{"id":"theme:list","description":"Lists your remote themes.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"environment":{"name":"environment","type":"option","char":"e","description":"The environment to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false},"role":{"name":"role","type":"option","description":"Only list themes with the given role.","helpValue":"(live|unpublished|development)","multiple":false,"options":["live","unpublished","development"]},"name":{"name":"name","type":"option","description":"Only list themes that contain the given name.","multiple":false},"id":{"name":"id","type":"option","description":"Only list theme with the given ID.","multiple":false}},"args":[]},"theme:open":{"id":"theme:open","description":"Opens the preview of your remote theme.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"environment":{"name":"environment","type":"option","char":"e","description":"The environment to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false},"development":{"name":"development","type":"boolean","char":"d","description":"Delete your development theme.","allowNo":false},"editor":{"name":"editor","type":"boolean","char":"e","description":"Open the theme editor for the specified theme in the browser.","allowNo":false},"live":{"name":"live","type":"boolean","char":"l","description":"Pull theme files from your remote live theme.","allowNo":false},"theme":{"name":"theme","type":"option","char":"t","description":"Theme ID or name of the remote theme.","multiple":false},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false}},"args":[]},"theme:package":{"id":"theme:package","description":"Package your theme into a .zip file, ready to upload to the Online Store.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"environment":{"name":"environment","type":"option","char":"e","description":"The environment to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"path":{"name":"path","type":"option","description":"The path to your theme directory.","hidden":false,"multiple":false,"default":"."}},"args":[]},"theme:publish":{"id":"theme:publish","description":"Set a remote theme as the live theme.","strict":false,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"environment":{"name":"environment","type":"option","char":"e","description":"The environment to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false},"force":{"name":"force","type":"boolean","char":"f","description":"Skip confirmation.","allowNo":false},"theme":{"name":"theme","type":"option","char":"t","description":"Theme ID or name of the remote theme.","multiple":false},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false}},"args":[]},"theme:pull":{"id":"theme:pull","description":"Download your remote theme files locally.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"environment":{"name":"environment","type":"option","char":"e","description":"The environment to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"path":{"name":"path","type":"option","description":"The path to your theme directory.","hidden":false,"multiple":false,"default":"."},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false},"theme":{"name":"theme","type":"option","char":"t","description":"Theme ID or name of the remote theme.","multiple":false},"development":{"name":"development","type":"boolean","char":"d","description":"Pull theme files from your remote development theme.","allowNo":false},"live":{"name":"live","type":"boolean","char":"l","description":"Pull theme files from your remote live theme.","allowNo":false},"nodelete":{"name":"nodelete","type":"boolean","char":"n","description":"Runs the pull command without deleting local files.","allowNo":false},"only":{"name":"only","type":"option","char":"o","description":"Download only the specified files (Multiple flags allowed).","multiple":true},"ignore":{"name":"ignore","type":"option","char":"x","description":"Skip downloading the specified files (Multiple flags allowed).","multiple":true},"force":{"name":"force","type":"boolean","char":"f","description":"Proceed without confirmation, if current directory does not seem to be theme directory.","hidden":true,"allowNo":false}},"args":[],"cli2Flags":["theme","development","live","nodelete","only","ignore","force"]},"theme:push":{"id":"theme:push","description":"Uploads your local theme files to the connected store, overwriting the remote version if specified.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"environment":{"name":"environment","type":"option","char":"e","description":"The environment to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"path":{"name":"path","type":"option","description":"The path to your theme directory.","hidden":false,"multiple":false,"default":"."},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false},"theme":{"name":"theme","type":"option","char":"t","description":"Theme ID or name of the remote theme.","multiple":false},"development":{"name":"development","type":"boolean","char":"d","description":"Push theme files from your remote development theme.","allowNo":false},"live":{"name":"live","type":"boolean","char":"l","description":"Push theme files from your remote live theme.","allowNo":false},"unpublished":{"name":"unpublished","type":"boolean","char":"u","description":"Create a new unpublished theme and push to it.","allowNo":false},"nodelete":{"name":"nodelete","type":"boolean","char":"n","description":"Runs the push command without deleting local files.","allowNo":false},"only":{"name":"only","type":"option","char":"o","description":"Download only the specified files (Multiple flags allowed).","multiple":true},"ignore":{"name":"ignore","type":"option","char":"x","description":"Skip downloading the specified files (Multiple flags allowed).","multiple":true},"json":{"name":"json","type":"boolean","char":"j","description":"Output JSON instead of a UI.","allowNo":false},"allow-live":{"name":"allow-live","type":"boolean","char":"a","description":"Allow push to a live theme.","allowNo":false},"publish":{"name":"publish","type":"boolean","char":"p","description":"Publish as the live theme after uploading.","allowNo":false},"stable":{"name":"stable","type":"boolean","description":"Performs the upload by relying in the legacy upload approach (slower, but it might be more stable in some scenarios)","hidden":true,"allowNo":false},"force":{"name":"force","type":"boolean","char":"f","description":"Proceed without confirmation, if current directory does not seem to be theme directory.","hidden":true,"allowNo":false}},"args":[],"cli2Flags":["theme","development","live","unpublished","nodelete","only","ignore","json","allow-live","publish","stable","force"]},"theme:serve":{"id":"theme:serve","description":"Uploads the current theme as a development theme to the connected store, then prints theme editor and preview URLs to your terminal. While running, changes will push to the store in real time.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","hidden":true,"aliases":[],"flags":{"environment":{"name":"environment","type":"option","char":"e","description":"The environment to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"path":{"name":"path","type":"option","description":"The path to your theme directory.","hidden":false,"multiple":false,"default":"."},"host":{"name":"host","type":"option","description":"Set which network interface the web server listens on. The default value is 127.0.0.1.","multiple":false},"live-reload":{"name":"live-reload","type":"option","description":"The live reload mode switches the server behavior when a file is modified:\n- hot-reload Hot reloads local changes to CSS and sections (default)\n- full-page Always refreshes the entire page\n- off Deactivate live reload","multiple":false,"options":["hot-reload","full-page","off"],"default":"hot-reload"},"poll":{"name":"poll","type":"boolean","description":"Force polling to detect file changes.","allowNo":false},"theme-editor-sync":{"name":"theme-editor-sync","type":"boolean","char":"e","description":"Synchronize Theme Editor updates in the local theme files.","allowNo":false},"port":{"name":"port","type":"option","description":"Local port to serve theme preview from.","multiple":false},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false},"theme":{"name":"theme","type":"option","char":"t","description":"Theme ID or name of the remote theme.","multiple":false},"only":{"name":"only","type":"option","char":"o","description":"Hot reload only files that match the specified pattern.","multiple":true},"ignore":{"name":"ignore","type":"option","char":"x","description":"Skip hot reloading any files that match the specified pattern.","multiple":true},"stable":{"name":"stable","type":"boolean","description":"Performs the upload by relying in the legacy upload approach (slower, but it might be more stable in some scenarios)","hidden":true,"allowNo":false},"force":{"name":"force","type":"boolean","char":"f","description":"Proceed without confirmation, if current directory does not seem to be theme directory.","hidden":true,"allowNo":false},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false}},"args":[]},"theme:share":{"id":"theme:share","description":"Creates a shareable, unpublished, and new theme on your theme library with a randomized name. Works like an alias to {{command:theme push -u -t=RANDOMIZED_NAME}}.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"environment":{"name":"environment","type":"option","char":"e","description":"The environment to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"path":{"name":"path","type":"option","description":"The path to your theme directory.","hidden":false,"multiple":false,"default":"."},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false},"force":{"name":"force","type":"boolean","char":"f","description":"Proceed without confirmation, if current directory does not seem to be theme directory.","hidden":true,"allowNo":false}},"args":[],"cli2Flags":["force"]}}} \ No newline at end of file +{"version":"3.39.0","commands":{"theme:check":{"id":"theme:check","description":"Validate the theme.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"environment":{"name":"environment","type":"option","char":"e","description":"The environment to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"path":{"name":"path","type":"option","description":"The path to your theme directory.","hidden":false,"multiple":false,"default":"."},"auto-correct":{"name":"auto-correct","type":"boolean","char":"a","description":"Automatically fix offenses","required":false,"allowNo":false},"category":{"name":"category","type":"option","char":"c","description":"Only run this category of checks\nRuns checks matching all categories when specified more than once","required":false,"multiple":false},"config":{"name":"config","type":"option","char":"C","description":"Use the config provided, overriding .theme-check.yml if present\nUse :theme_app_extension to use default checks for theme app extensions","required":false,"multiple":false},"exclude-category":{"name":"exclude-category","type":"option","char":"x","description":"Exclude this category of checks\nExcludes checks matching any category when specified more than once","required":false,"multiple":false},"fail-level":{"name":"fail-level","type":"option","description":"Minimum severity for exit with error code","required":false,"multiple":false,"options":["error","suggestion","style"]},"init":{"name":"init","type":"boolean","description":"Generate a .theme-check.yml file","required":false,"allowNo":false},"list":{"name":"list","type":"boolean","description":"List enabled checks","required":false,"allowNo":false},"output":{"name":"output","type":"option","char":"o","description":"The output format to use","required":false,"multiple":false,"options":["text","json"],"default":"text"},"print":{"name":"print","type":"boolean","description":"Output active config to STDOUT","required":false,"allowNo":false},"version":{"name":"version","type":"boolean","char":"v","description":"Print Theme Check version","required":false,"allowNo":false}},"args":[],"cli2Flags":["auto-correct","category","config","exclude-category","fail-level","init","list","output","print","version"]},"theme:delete":{"id":"theme:delete","description":"Delete remote themes from the connected store. This command can't be undone.","strict":false,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"environment":{"name":"environment","type":"option","char":"e","description":"The environment to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false},"development":{"name":"development","type":"boolean","char":"d","description":"Delete your development theme.","allowNo":false},"show-all":{"name":"show-all","type":"boolean","char":"a","description":"Include others development themes in theme list.","allowNo":false},"force":{"name":"force","type":"boolean","char":"f","description":"Skip confirmation.","allowNo":false},"theme":{"name":"theme","type":"option","char":"t","description":"Theme ID or name of the remote theme.","multiple":true},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false}},"args":[]},"theme:dev":{"id":"theme:dev","description":"Uploads the current theme as a development theme to the connected store, then prints theme editor and preview URLs to your terminal. While running, changes will push to the store in real time.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"environment":{"name":"environment","type":"option","char":"e","description":"The environment to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"path":{"name":"path","type":"option","description":"The path to your theme directory.","hidden":false,"multiple":false,"default":"."},"host":{"name":"host","type":"option","description":"Set which network interface the web server listens on. The default value is 127.0.0.1.","multiple":false},"live-reload":{"name":"live-reload","type":"option","description":"The live reload mode switches the server behavior when a file is modified:\n- hot-reload Hot reloads local changes to CSS and sections (default)\n- full-page Always refreshes the entire page\n- off Deactivate live reload","multiple":false,"options":["hot-reload","full-page","off"],"default":"hot-reload"},"poll":{"name":"poll","type":"boolean","description":"Force polling to detect file changes.","allowNo":false},"theme-editor-sync":{"name":"theme-editor-sync","type":"boolean","char":"e","description":"Synchronize Theme Editor updates in the local theme files.","allowNo":false},"port":{"name":"port","type":"option","description":"Local port to serve theme preview from.","multiple":false},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false},"theme":{"name":"theme","type":"option","char":"t","description":"Theme ID or name of the remote theme.","multiple":false},"only":{"name":"only","type":"option","char":"o","description":"Hot reload only files that match the specified pattern.","multiple":true},"ignore":{"name":"ignore","type":"option","char":"x","description":"Skip hot reloading any files that match the specified pattern.","multiple":true},"stable":{"name":"stable","type":"boolean","description":"Performs the upload by relying in the legacy upload approach (slower, but it might be more stable in some scenarios)","hidden":true,"allowNo":false},"force":{"name":"force","type":"boolean","char":"f","description":"Proceed without confirmation, if current directory does not seem to be theme directory.","hidden":true,"allowNo":false},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false}},"args":[],"cli2Flags":["host","live-reload","poll","theme-editor-sync","overwrite-json","port","theme","only","ignore","stable","force"]},"theme:help-old":{"id":"theme:help-old","description":"Show help from Ruby CLI","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","hidden":true,"aliases":[],"flags":{"environment":{"name":"environment","type":"option","char":"e","description":"The environment to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"command":{"name":"command","type":"option","description":"The command for which to show CLI2 help.","multiple":false}},"args":[]},"theme:info":{"id":"theme:info","description":"Print basic information about your theme environment.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"environment":{"name":"environment","type":"option","char":"e","description":"The environment to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false}},"args":[]},"theme:init":{"id":"theme:init","description":"Clones a Git repository to use as a starting point for building a new theme.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"environment":{"name":"environment","type":"option","char":"e","description":"The environment to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"path":{"name":"path","type":"option","description":"The path to your theme directory.","hidden":false,"multiple":false,"default":"."},"clone-url":{"name":"clone-url","type":"option","char":"u","description":"The Git URL to clone from. Defaults to Shopify's example theme, Dawn: https://github.com/Shopify/dawn.git","multiple":false,"default":"https://github.com/Shopify/dawn.git"},"latest":{"name":"latest","type":"boolean","char":"l","description":"Downloads the latest release of the `clone-url`","allowNo":false}},"args":[{"name":"name","description":"Name of the new theme","required":false}]},"theme:language-server":{"id":"theme:language-server","description":"Start a Language Server Protocol server.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"environment":{"name":"environment","type":"option","char":"e","description":"The environment to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false}},"args":[]},"theme:list":{"id":"theme:list","description":"Lists your remote themes.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"environment":{"name":"environment","type":"option","char":"e","description":"The environment to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false},"role":{"name":"role","type":"option","description":"Only list themes with the given role.","helpValue":"(live|unpublished|development)","multiple":false,"options":["live","unpublished","development"]},"name":{"name":"name","type":"option","description":"Only list themes that contain the given name.","multiple":false},"id":{"name":"id","type":"option","description":"Only list theme with the given ID.","multiple":false}},"args":[]},"theme:open":{"id":"theme:open","description":"Opens the preview of your remote theme.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"environment":{"name":"environment","type":"option","char":"e","description":"The environment to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false},"development":{"name":"development","type":"boolean","char":"d","description":"Delete your development theme.","allowNo":false},"editor":{"name":"editor","type":"boolean","char":"e","description":"Open the theme editor for the specified theme in the browser.","allowNo":false},"live":{"name":"live","type":"boolean","char":"l","description":"Pull theme files from your remote live theme.","allowNo":false},"theme":{"name":"theme","type":"option","char":"t","description":"Theme ID or name of the remote theme.","multiple":false},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false}},"args":[]},"theme:package":{"id":"theme:package","description":"Package your theme into a .zip file, ready to upload to the Online Store.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"environment":{"name":"environment","type":"option","char":"e","description":"The environment to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"path":{"name":"path","type":"option","description":"The path to your theme directory.","hidden":false,"multiple":false,"default":"."}},"args":[]},"theme:publish":{"id":"theme:publish","description":"Set a remote theme as the live theme.","strict":false,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"environment":{"name":"environment","type":"option","char":"e","description":"The environment to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false},"force":{"name":"force","type":"boolean","char":"f","description":"Skip confirmation.","allowNo":false},"theme":{"name":"theme","type":"option","char":"t","description":"Theme ID or name of the remote theme.","multiple":false},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false}},"args":[]},"theme:pull":{"id":"theme:pull","description":"Download your remote theme files locally.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"environment":{"name":"environment","type":"option","char":"e","description":"The environment to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"path":{"name":"path","type":"option","description":"The path to your theme directory.","hidden":false,"multiple":false,"default":"."},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false},"theme":{"name":"theme","type":"option","char":"t","description":"Theme ID or name of the remote theme.","multiple":false},"development":{"name":"development","type":"boolean","char":"d","description":"Pull theme files from your remote development theme.","allowNo":false},"live":{"name":"live","type":"boolean","char":"l","description":"Pull theme files from your remote live theme.","allowNo":false},"nodelete":{"name":"nodelete","type":"boolean","char":"n","description":"Runs the pull command without deleting local files.","allowNo":false},"only":{"name":"only","type":"option","char":"o","description":"Download only the specified files (Multiple flags allowed).","multiple":true},"ignore":{"name":"ignore","type":"option","char":"x","description":"Skip downloading the specified files (Multiple flags allowed).","multiple":true},"force":{"name":"force","type":"boolean","char":"f","description":"Proceed without confirmation, if current directory does not seem to be theme directory.","hidden":true,"allowNo":false}},"args":[],"cli2Flags":["theme","development","live","nodelete","only","ignore","force"]},"theme:push":{"id":"theme:push","description":"Uploads your local theme files to the connected store, overwriting the remote version if specified.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"environment":{"name":"environment","type":"option","char":"e","description":"The environment to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"path":{"name":"path","type":"option","description":"The path to your theme directory.","hidden":false,"multiple":false,"default":"."},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false},"theme":{"name":"theme","type":"option","char":"t","description":"Theme ID or name of the remote theme.","multiple":false},"development":{"name":"development","type":"boolean","char":"d","description":"Push theme files from your remote development theme.","allowNo":false},"live":{"name":"live","type":"boolean","char":"l","description":"Push theme files from your remote live theme.","allowNo":false},"unpublished":{"name":"unpublished","type":"boolean","char":"u","description":"Create a new unpublished theme and push to it.","allowNo":false},"nodelete":{"name":"nodelete","type":"boolean","char":"n","description":"Runs the push command without deleting local files.","allowNo":false},"only":{"name":"only","type":"option","char":"o","description":"Download only the specified files (Multiple flags allowed).","multiple":true},"ignore":{"name":"ignore","type":"option","char":"x","description":"Skip downloading the specified files (Multiple flags allowed).","multiple":true},"json":{"name":"json","type":"boolean","char":"j","description":"Output JSON instead of a UI.","allowNo":false},"allow-live":{"name":"allow-live","type":"boolean","char":"a","description":"Allow push to a live theme.","allowNo":false},"publish":{"name":"publish","type":"boolean","char":"p","description":"Publish as the live theme after uploading.","allowNo":false},"stable":{"name":"stable","type":"boolean","description":"Performs the upload by relying in the legacy upload approach (slower, but it might be more stable in some scenarios)","hidden":true,"allowNo":false},"force":{"name":"force","type":"boolean","char":"f","description":"Proceed without confirmation, if current directory does not seem to be theme directory.","hidden":true,"allowNo":false}},"args":[],"cli2Flags":["theme","development","live","unpublished","nodelete","only","ignore","json","allow-live","publish","stable","force"]},"theme:serve":{"id":"theme:serve","description":"Uploads the current theme as a development theme to the connected store, then prints theme editor and preview URLs to your terminal. While running, changes will push to the store in real time.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","hidden":true,"aliases":[],"flags":{"environment":{"name":"environment","type":"option","char":"e","description":"The environment to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"path":{"name":"path","type":"option","description":"The path to your theme directory.","hidden":false,"multiple":false,"default":"."},"host":{"name":"host","type":"option","description":"Set which network interface the web server listens on. The default value is 127.0.0.1.","multiple":false},"live-reload":{"name":"live-reload","type":"option","description":"The live reload mode switches the server behavior when a file is modified:\n- hot-reload Hot reloads local changes to CSS and sections (default)\n- full-page Always refreshes the entire page\n- off Deactivate live reload","multiple":false,"options":["hot-reload","full-page","off"],"default":"hot-reload"},"poll":{"name":"poll","type":"boolean","description":"Force polling to detect file changes.","allowNo":false},"theme-editor-sync":{"name":"theme-editor-sync","type":"boolean","char":"e","description":"Synchronize Theme Editor updates in the local theme files.","allowNo":false},"port":{"name":"port","type":"option","description":"Local port to serve theme preview from.","multiple":false},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false},"theme":{"name":"theme","type":"option","char":"t","description":"Theme ID or name of the remote theme.","multiple":false},"only":{"name":"only","type":"option","char":"o","description":"Hot reload only files that match the specified pattern.","multiple":true},"ignore":{"name":"ignore","type":"option","char":"x","description":"Skip hot reloading any files that match the specified pattern.","multiple":true},"stable":{"name":"stable","type":"boolean","description":"Performs the upload by relying in the legacy upload approach (slower, but it might be more stable in some scenarios)","hidden":true,"allowNo":false},"force":{"name":"force","type":"boolean","char":"f","description":"Proceed without confirmation, if current directory does not seem to be theme directory.","hidden":true,"allowNo":false},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false}},"args":[]},"theme:share":{"id":"theme:share","description":"Creates a shareable, unpublished, and new theme on your theme library with a randomized name. Works like an alias to {{command:theme push -u -t=RANDOMIZED_NAME}}.","strict":true,"pluginName":"@shopify/theme","pluginAlias":"@shopify/theme","pluginType":"core","aliases":[],"flags":{"environment":{"name":"environment","type":"option","char":"e","description":"The environment to apply to the current command.","hidden":true,"multiple":false},"verbose":{"name":"verbose","type":"boolean","description":"Increase the verbosity of the logs.","hidden":false,"allowNo":false},"path":{"name":"path","type":"option","description":"The path to your theme directory.","hidden":false,"multiple":false,"default":"."},"password":{"name":"password","type":"option","description":"Password generated from the Theme Access app.","hidden":false,"multiple":false},"store":{"name":"store","type":"option","char":"s","description":"Store URL. It can be the store prefix (johns-apparel) or the full myshopify.com URL (johns-apparel.myshopify.com, https://johns-apparel.myshopify.com).","multiple":false},"force":{"name":"force","type":"boolean","char":"f","description":"Proceed without confirmation, if current directory does not seem to be theme directory.","hidden":true,"allowNo":false}},"args":[],"cli2Flags":["force"]}}} \ No newline at end of file