diff --git a/packages/cli-kit/src/public/node/doctor/types.ts b/packages/cli-kit/src/public/node/doctor/types.ts index 6bd17e02d2..e56c588a1e 100644 --- a/packages/cli-kit/src/public/node/doctor/types.ts +++ b/packages/cli-kit/src/public/node/doctor/types.ts @@ -17,5 +17,6 @@ export interface TestResult { export interface DoctorContext { workingDirectory: string + cliCommand: string data: {[key: string]: unknown} } diff --git a/packages/cli/src/cli/commands/doctor-release/doctor-release.ts b/packages/cli/src/cli/commands/doctor-release/doctor-release.ts index b7e701877e..40ca1c4fc2 100644 --- a/packages/cli/src/cli/commands/doctor-release/doctor-release.ts +++ b/packages/cli/src/cli/commands/doctor-release/doctor-release.ts @@ -5,6 +5,7 @@ import {renderInfo} from '@shopify/cli-kit/node/ui' export default class DoctorRelease extends Command { static description = 'Run CLI doctor-release tests' static hidden = true + static handle = 'doctor-release' async run(): Promise { if (!canRunDoctorRelease()) { diff --git a/packages/cli/src/cli/commands/doctor-release/theme/index.ts b/packages/cli/src/cli/commands/doctor-release/theme/index.ts index 026a1f0eea..cb1bf86555 100644 --- a/packages/cli/src/cli/commands/doctor-release/theme/index.ts +++ b/packages/cli/src/cli/commands/doctor-release/theme/index.ts @@ -8,6 +8,7 @@ import {canRunDoctorRelease} from '@shopify/cli-kit/node/context/local' export default class DoctorReleaseTheme extends Command { static description = 'Run all theme command doctor-release tests' static hidden = true + static handle = 'doctor-release:theme' static flags = { ...globalFlags, @@ -42,12 +43,15 @@ export default class DoctorReleaseTheme extends Command { const {flags} = await this.parse(DoctorReleaseTheme) - const results = await runThemeDoctor({ - path: flags.path, - environment: flags.environment, - store: flags.store, - password: flags.password, - }) + const results = await runThemeDoctor( + { + path: flags.path, + environment: flags.environment, + store: flags.store, + password: flags.password, + }, + DoctorReleaseTheme.handle, + ) // Exit with error code if any tests failed const failed = results.some((result) => result.status === 'failed') diff --git a/packages/cli/src/cli/services/doctor-release/context.test.ts b/packages/cli/src/cli/services/doctor-release/context.test.ts new file mode 100644 index 0000000000..ddbaaede62 --- /dev/null +++ b/packages/cli/src/cli/services/doctor-release/context.test.ts @@ -0,0 +1,45 @@ +import {detectCliCommand} from './context.js' +import {describe, expect, test} from 'vitest' + +describe('detectCliCommand', () => { + const handle = 'doctor-release:theme' + + test('returns shopify when no handle is provided', () => { + expect(detectCliCommand(undefined, ['node', 'shopify', 'doctor-release', 'theme'])).toBe('shopify') + }) + + test('detects npx shopify', () => { + const argv = ['npx', 'shopify', 'doctor-release', 'theme'] + expect(detectCliCommand(handle, argv)).toBe('npx shopify') + }) + + test('detects direct shopify invocation', () => { + const argv = ['shopify', 'doctor-release', 'theme'] + expect(detectCliCommand(handle, argv)).toBe('shopify') + }) + + test('detects pnpm shopify', () => { + const argv = ['pnpm', 'shopify', 'doctor-release', 'theme'] + expect(detectCliCommand(handle, argv)).toBe('pnpm shopify') + }) + + test('detects node packages/cli/bin/dev.js', () => { + const argv = ['node', 'packages/cli/bin/dev.js', 'doctor-release', 'theme'] + expect(detectCliCommand(handle, argv)).toBe('node packages/cli/bin/dev.js') + }) + + test('returns shopify when handle has no colon', () => { + const argv = ['node', 'shopify', 'doctor-release', 'theme'] + expect(detectCliCommand('doctor-release', argv)).toBe('node shopify') + }) + + test('returns shopify when first topic is not found in argv', () => { + const argv = ['node', 'shopify', 'theme', 'init'] + expect(detectCliCommand(handle, argv)).toBe('shopify') + }) + + test('returns shopify when first topic is the first argv element', () => { + const argv = ['doctor-release', 'theme'] + expect(detectCliCommand(handle, argv)).toBe('shopify') + }) +}) diff --git a/packages/cli/src/cli/services/doctor-release/context.ts b/packages/cli/src/cli/services/doctor-release/context.ts index c4c35c8e40..be6a43e4db 100644 --- a/packages/cli/src/cli/services/doctor-release/context.ts +++ b/packages/cli/src/cli/services/doctor-release/context.ts @@ -27,9 +27,35 @@ export interface ThemeDoctorOptions { password?: string } -export function createDoctorContext(options: ThemeDoctorOptions): ThemeDoctorContext { +/** + * Detects the CLI command used to invoke the current process by finding the + * command's first topic in argv and returning everything before it. + * + * Examples: + * `npx shopify doctor-release theme` → `npx shopify` + * `shopify doctor-release theme` → `shopify` + * `pnpm shopify doctor-release theme` → `pnpm shopify` + * `node packages/cli/bin/dev.js doctor-release theme` → `node packages/cli/bin/dev.js` + */ +export function detectCliCommand(commandHandle?: string, argv: string[] = process.argv): string { + const defaultCommand = 'shopify' + + if (!commandHandle) return defaultCommand + + const firstTopic = commandHandle.split(':')[0] + if (!firstTopic) return defaultCommand + + const index = argv.findIndex((arg) => arg === firstTopic) + + if (index <= 0) return defaultCommand + + return argv.slice(0, index).join(' ') +} + +export function createDoctorContext(options: ThemeDoctorOptions, commandHandle?: string): ThemeDoctorContext { return { workingDirectory: options.path ?? cwd(), + cliCommand: detectCliCommand(commandHandle), environment: options.environment, store: options.store, password: options.password, diff --git a/packages/cli/src/cli/services/doctor-release/theme/runner.ts b/packages/cli/src/cli/services/doctor-release/theme/runner.ts index dfaf013cf4..90b8f0e323 100644 --- a/packages/cli/src/cli/services/doctor-release/theme/runner.ts +++ b/packages/cli/src/cli/services/doctor-release/theme/runner.ts @@ -19,9 +19,9 @@ const themeSuites: (new () => DoctorSuite)[] = [ThemeInitTes * Run all theme doctor tests. * Stops on first failure. */ -export async function runThemeDoctor(options: ThemeDoctorOptions): Promise { +export async function runThemeDoctor(options: ThemeDoctorOptions, commandHandle?: string): Promise { const results: TestResult[] = [] - const context = createDoctorContext(options) + const context = createDoctorContext(options, commandHandle) // Initialize reporter with working directory initReporter(context.workingDirectory) diff --git a/packages/cli/src/cli/services/doctor-release/theme/tests/init.ts b/packages/cli/src/cli/services/doctor-release/theme/tests/init.ts index b768fb9791..e583e9cce8 100644 --- a/packages/cli/src/cli/services/doctor-release/theme/tests/init.ts +++ b/packages/cli/src/cli/services/doctor-release/theme/tests/init.ts @@ -18,7 +18,7 @@ export default class ThemeInitTests extends DoctorSuite { this.themePath = joinPath(this.context.workingDirectory, this.themeName) const result = await this.runInteractive( - `shopify theme init ${this.themeName} --path ${this.context.workingDirectory}`, + `${this.context.cliCommand} theme init ${this.themeName} --path ${this.context.workingDirectory}`, ) this.assertSuccess(result) diff --git a/packages/cli/src/cli/services/doctor-release/theme/tests/push.ts b/packages/cli/src/cli/services/doctor-release/theme/tests/push.ts index 4bd4b0c806..c1188c6c15 100644 --- a/packages/cli/src/cli/services/doctor-release/theme/tests/push.ts +++ b/packages/cli/src/cli/services/doctor-release/theme/tests/push.ts @@ -29,7 +29,7 @@ export default class ThemePushTests extends DoctorSuite { // Build command const themeName = this.context.themeName ?? 'doctor-theme' - let cmd = `shopify theme push --unpublished --json --path ${this.context.themePath} -t ${themeName}` + let cmd = `${this.context.cliCommand} theme push --unpublished --json --path ${this.context.themePath} -t ${themeName}` if (this.context.environment) { cmd += ` -e ${this.context.environment}` diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 6b361f0aeb..d3ed09bd67 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -144,8 +144,8 @@ export const COMMANDS: any = { 'kitchen-sink:async': KitchenSinkAsync, 'kitchen-sink:prompts': KitchenSinkPrompts, 'kitchen-sink:static': KitchenSinkStatic, - 'doctor-release': Doctor, - 'doctor-release:theme': DoctorTheme, + [Doctor.handle]: Doctor, + [DoctorTheme.handle]: DoctorTheme, 'docs:generate': DocsGenerate, 'notifications:list': List, 'notifications:generate': Generate,