Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/cli-kit/src/public/node/doctor/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ export interface TestResult {

export interface DoctorContext {
workingDirectory: string
cliCommand: string
data: {[key: string]: unknown}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> {
if (!canRunDoctorRelease()) {
Expand Down
16 changes: 10 additions & 6 deletions packages/cli/src/cli/commands/doctor-release/theme/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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')
Expand Down
45 changes: 45 additions & 0 deletions packages/cli/src/cli/services/doctor-release/context.test.ts
Original file line number Diff line number Diff line change
@@ -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')
})
})
28 changes: 27 additions & 1 deletion packages/cli/src/cli/services/doctor-release/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(' ')
}
Comment on lines +40 to +53
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@EvilGenius13 do you think this might be too fragile? I am not sure if we should just make a new arg passed in from the user.

If i were to steelman this approach, this would work in CI if we'd launch theme-doctor theme from a CI environment if it used npx shopify instead of shopify

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey so I console logged the input of running the command and I didn't get an argument setup of something like npx, shopify, i got

process.argv: ["/opt/homebrew/Cellar/node/23.9.0/bin/node","/Users/joshuafaigan/bin/shopify","doctor-release","theme","-e","evil"]

So I think we could just simplify the function to return argv.slice(0, 2).join(' '), and then we only pass detectCliCommand through createDoctorContext. Let me know if I'm wrong!


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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ const themeSuites: (new () => DoctorSuite<ThemeDoctorContext>)[] = [ThemeInitTes
* Run all theme doctor tests.
* Stops on first failure.
*/
export async function runThemeDoctor(options: ThemeDoctorOptions): Promise<TestResult[]> {
export async function runThemeDoctor(options: ThemeDoctorOptions, commandHandle?: string): Promise<TestResult[]> {
const results: TestResult[] = []
const context = createDoctorContext(options)
const context = createDoctorContext(options, commandHandle)

// Initialize reporter with working directory
initReporter(context.workingDirectory)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export default class ThemeInitTests extends DoctorSuite<ThemeDoctorContext> {
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)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export default class ThemePushTests extends DoctorSuite<ThemeDoctorContext> {

// 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}`
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading