Skip to content

Add doctor command#6798

Open
EvilGenius13 wants to merge 4 commits intomainfrom
jf-audit-proto
Open

Add doctor command#6798
EvilGenius13 wants to merge 4 commits intomainfrom
jf-audit-proto

Conversation

@EvilGenius13
Copy link
Contributor

@EvilGenius13 EvilGenius13 commented Jan 28, 2026

WHY are these changes introduced?

We need an automated way to validate that CLI commands work correctly after changes or new releases. Currently, there's no easy way to:

  • Test commands end-to-end before releasing to npm
  • Catch regressions when changes affect command behavior
  • Verify that a fresh install works as expected
  • Run QA workflows consistently across different scenarios

This doctor command provides the foundation for automated command testing.

WHAT is this pull request doing?

Introduces the shopify doctor command with initial theme command tests:
Core Framework: - Adds shopify doctor theme command for testing theme-related commands - Implements test framework with suites, assertions, and context management - Sets up structure for future expansion (app commands)
Initial Tests: - We are testing theme init and theme push commands. Follow up PR's will add the remaining commands and update existing tests and assertions.
Note: - Tests run in order. If a test relies on another command, that command should run first (Ex: theme init to create a theme and then running theme push.

How to test your changes?

Pull down the branch
Build the branch
Run shopify doctor theme -e <your env> in a folder that has a shopify.theme.toml file.

  • Check to see a new theme folder is initialized in the folder you ran the doctor command
  • Check your store to see if that same theme was pushed to your store

Post-release steps

Much like the CLI multi-env command work, the doctor command will be built up over many additional PR's.

Measuring impact

How do we know this change was effective? Please choose one:

  • n/a - this doesn't need measurement, e.g. a linting rule or a bug-fix
  • Existing analytics will cater for this addition
  • PR includes analytics changes to measure impact

Checklist

  • I've considered possible cross-platform impacts (Mac, Linux, Windows)
  • I've considered possible documentation changes

@github-actions
Copy link
Contributor

github-actions bot commented Jan 29, 2026

Coverage report

St.
Category Percentage Covered / Total
🟡 Statements
78.81% (-0.42% 🔻)
14437/18319
🟡 Branches
73.13% (+0.02% 🔼)
7149/9776
🟡 Functions
78.99% (-0.38% 🔻)
3685/4665
🟡 Lines
79.16% (-0.42% 🔻)
13649/17242
Show new covered files 🐣
St.
File Statements Branches Functions Lines
🟢
... / admin-as-app.ts
100% 100% 100% 100%
🟢
... / metafield_definitions.ts
100% 100% 100% 100%
🟢
... / metaobject_definitions.ts
100% 100% 100% 100%
🟢
... / bulk-operation-cancel.ts
100% 100% 100% 100%
🟢
... / bulk-operation-run-mutation.ts
100% 100% 100% 100%
🟢
... / bulk-operation-run-query.ts
100% 100% 100% 100%
🟢
... / get-bulk-operation-by-id.ts
100% 100% 100% 100%
🟢
... / list-bulk-operations.ts
100% 100% 100% 100%
🟢
... / staged-uploads-create.ts
100% 100% 100% 100%
🟢
... / fetch_store_by_domain.ts
100% 100% 100% 100%
🟢
... / organization_exp_flags.ts
100% 100% 100% 100%
🔴
... / import-custom-data-definitions.ts
0% 100% 0% 0%
🔴
... / cancel.ts
0% 100% 0% 0%
🔴
... / execute.ts
0% 0% 0% 0%
🔴
... / status.ts
0% 0% 0% 0%
🔴
... / pull.ts
0% 100% 0% 0%
🟡
... / execute-operation.ts
75% 50% 100% 75%
🔴
... / pull.ts
0% 0% 0% 0%
🟢
... / bulk-operation-status.ts
96.55% 92.11% 100% 100%
🟢
... / cancel-bulk-operation.ts
100% 100% 100% 100%
🟢
... / constants.ts
100% 100% 100% 100%
🟢
... / download-bulk-operation-results.ts
100% 100% 100% 100%
🟢
... / execute-bulk-operation.ts
86.76% 83.67% 100% 88.06%
🟢
... / format-bulk-operation-status.ts
100% 100% 100% 100%
🟢
... / run-mutation.ts
100% 100% 100% 100%
🟢
... / run-query.ts
100% 100% 100% 100%
🟡
... / stage-file.ts
73.53% 62.5% 85.71% 72.73%
🟢
... / watch-bulk-operation.ts
100% 94.74% 100% 100%
🟢
... / utilities.ts
100% 100% 100% 100%
🟢
... / declarative-definitions.ts
98.54% 93.18% 100% 98.51%
🟢
... / common.ts
97.62% 95% 100% 97.06%
🟢
... / execute-command-helpers.ts
100% 100% 100% 100%
🟢
... / file-formatter.ts
100% 100% 100% 100%
🔴
... / doctor.ts
0% 100% 0% 0%
🔴
... / index.ts
0% 0% 0% 0%
🔴
... / context.ts
0% 0% 0% 0%
🔴
... / framework.ts
27.4% 5.88% 41.18% 26.39%
🔴
... / reporter.ts
0% 0% 0% 0%
🔴
... / runner.ts
0% 0% 0% 0%
🔴
... / init.ts
0% 100% 0% 0%
🔴
... / push.ts
0% 0% 0% 0%
🔴
... / promiseWithResolvers.ts
33.33% 50% 50% 33.33%
Show files with reduced coverage 🔻
St.
File Statements Branches Functions Lines
🔴
... / execute.ts
0%
0% (-100% 🔻)
0% 0%
🟢
... / loader.ts
94.06% (+0.2% 🔼)
86.41% (-0.42% 🔻)
97.17% (+0.11% 🔼)
94.85% (+0.18% 🔼)
🟢
... / extension-instance.ts
84.8% (+0.23% 🔼)
77.6% (-0.91% 🔻)
92.06% (+0.13% 🔼)
85.11% (+0.24% 🔼)
🟡
... / specification.ts
69.64% (+0.55% 🔼)
75.61% (+2.44% 🔼)
76.47% (-1.31% 🔻)
69.39% (+0.64% 🔼)
🟢
... / ui_extension.ts
88.61% (-6.22% 🔻)
78.57% (-2.68% 🔻)
85.19% (-14.81% 🔻)
90.79% (-5.67% 🔻)
🟢
... / store-context.ts
100%
82.35% (-0.98% 🔻)
100% 100%
🟢
... / Logs.tsx
90%
90.91% (-5.97% 🔻)
100% 90%
🟢
... / fetch.ts
84.21% (+0.88% 🔼)
82.35% (-0.98% 🔻)
75%
85.29% (+1.42% 🔼)
🟢
... / app-event-watcher-handler.ts
86.36% (-4.11% 🔻)
75% 86.67%
85.71% (-5.19% 🔻)
🟡
... / middlewares.ts
77.33% (-0.87% 🔻)
75%
68.42% (-1.58% 🔻)
76.39% (-0.94% 🔻)
🔴
... / server.ts
1.23% (-0.02% 🔻)
0% 0%
1.3% (-0.02% 🔻)
🟢
... / bundle.ts
93.22%
63.33% (-3.33% 🔻)
94.12% (+5.88% 🔼)
96.3%
🟢
... / developer-platform-client.ts
84.62% (-1.5% 🔻)
71.43% (+0.84% 🔼)
81.82% (+1.82% 🔼)
93.75% (+0.42% 🔼)
🔴
... / http-reverse-proxy.ts
58.97% (-4.91% 🔻)
37.04% (-2.96% 🔻)
58.33% (-5.3% 🔻)
59.46% (-5.25% 🔻)
🟢
... / api.ts
87.07% (-0.43% 🔻)
76.71% (-0.1% 🔻)
100%
86.49% (-0.43% 🔻)
🟢
... / device-authorization.ts
88.24% (-0.83% 🔻)
76.47% (-2.94% 🔻)
100%
88.24% (-0.83% 🔻)
🟢
... / ConcurrentOutput.tsx
98.36% (-1.64% 🔻)
92% (-4% 🔻)
100%
98.33% (-1.67% 🔻)
🟢
... / SingleTask.tsx
84.21% (-15.79% 🔻)
50% (-50% 🔻)
80% (-20% 🔻)
84.21% (-15.79% 🔻)
🔴
... / environment.ts
35% (-5% 🔻)
41.18%
40% (-10% 🔻)
36.84% (-5.26% 🔻)
🔴
... / ui.tsx
50.82% (-0.79% 🔻)
42.86% (-5.53% 🔻)
54.55% (+1.42% 🔼)
50% (-0.82% 🔻)
🟡
... / local.ts
68.63% (-1.37% 🔻)
56.25% (-1.2% 🔻)
54.55% (-2.6% 🔻)
68.63% (-1.37% 🔻)
🟢
... / console.ts
81.82% (+15.15% 🔼)
75% (-25% 🔻)
100% (+33.33% 🔼)
81.82% (+15.15% 🔼)
🟢
... / init.ts
88% (-0.89% 🔻)
71.43% (+4.76% 🔼)
86.67% (+4.85% 🔼)
88% (-0.89% 🔻)
🟢
... / storefront-renderer.ts
90.2% (-0.54% 🔻)
78.95%
81.82% (-1.52% 🔻)
90.2% (-0.54% 🔻)
🟡
... / theme-polling.ts
67.57% (-0.49% 🔻)
68.75% 78.57%
66.18% (-1.47% 🔻)

Test suite run success

3724 tests passing in 1443 suites.

Report generated by 🧪jest coverage report action from 18a9b6d

@EvilGenius13 EvilGenius13 force-pushed the jf-audit-proto branch 5 times, most recently from 9ecb998 to 440b7c9 Compare January 29, 2026 20:11
@EvilGenius13 EvilGenius13 marked this pull request as ready for review January 30, 2026 16:12
@EvilGenius13 EvilGenius13 requested review from a team as code owners January 30, 2026 16:12
@github-actions
Copy link
Contributor

We detected some changes at packages/*/src and there are no updates in the .changeset.
If the changes are user-facing, run pnpm changeset add to track your changes and include them in the next release CHANGELOG.

Caution

DO NOT create changesets for features which you do not wish to be included in the public changelog of the next CLI release.

@EvilGenius13 EvilGenius13 requested a review from karreiro January 30, 2026 16:12
@EvilGenius13 EvilGenius13 changed the title Jf audit proto Add audit command Jan 30, 2026
Copy link
Contributor

@karreiro karreiro left a comment

Choose a reason for hiding this comment

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

Thank you for this PR, @EvilGenius13!

I’ve left a few comments and suggestions. I also think it’d be worth adding unit tests around the test framework to make sure the modules behave as intended.

Thanks again for the PR!

command: string,
options?: {cwd?: string; env?: {[key: string]: string}},
): Promise<CommandResult> {
const parts = command.split(' ').filter(Boolean)
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems like we have an issue here for flags with spaces. For example:

const command = 'shopify theme push --theme "My Theme Name"';
const parts = command.split(' ').filter(Boolean);
// Expected: ['shopify', 'theme', 'push', '--theme', 'My Theme Name']
// Actual:   ['shopify', 'theme', 'push', '--theme', '"My', 'Theme', 'Name"']

In the packages/cli/src/cli/services/audit/theme/tests/init.ts file, tests stop working if the API user updates the theme name to this:

this.themeName = `My audit-theme-${getRandomName('creative')}`

Since the theme name can include spaces, the current split(' ') parsing ends up breaking the --theme value into multiple parts.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed parsing to gather themes with spaces.

private themeName = ''
private themePath = ''

// eslint-disable-next-line @typescript-eslint/naming-convention
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think we should promote the pattern of always eslint-ignoring all test cases.

We may solve this by adopting a pattern like this for test definition:

test('test init creates theme directory', async () => {
  
})

Or, we may ignore this warning globally in this module (still, I personally prefer the test(...) API as it follows a more well-established convention when it comes to unit tests in JS/TS frameworks).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

switched to the test method!

if (error instanceof Error) {
stderr = error.message
// Try to extract exit code from error
const match = /exit code (\d+)/i.exec(error.message)
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems a bit fragile to extract the error code based on the output string.

The captureOutput function calls buildExec that results the output and the exit code.

I wonder if we may update packages/cli-kit/src/public/node/system.ts with a new function that returns the output and the exit status code.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

@EvilGenius13 EvilGenius13 changed the title Add audit command Add doctor command Feb 3, 2026
@EvilGenius13 EvilGenius13 requested a review from karreiro February 3, 2026 19:27
@karreiro
Copy link
Contributor

karreiro commented Feb 4, 2026

/snapit

@github-actions
Copy link
Contributor

github-actions bot commented Feb 4, 2026

🫰✨ Thanks @karreiro! Your snapshot has been published to npm.

Test the snapshot by installing your package globally:

npm i -g --@shopify:registry=https://registry.npmjs.org @shopify/[email protected]

Caution

After installing, validate the version by running just shopify in your terminal.
If the versions don't match, you might have multiple global instances installed.
Use which shopify to find out which one you are running and uninstall it.

Copy link
Contributor

@karreiro karreiro left a comment

Choose a reason for hiding this comment

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

Thank you for this PR, @EvilGenius13!

I left a few minor comments as next steps, but I think we’re in a good spot to merge this as a first iteration 🚀

Comment on lines +11 to +18
body: [
'Available doctor commands:',
'',
' shopify doctor theme -e <environment> Run all theme command tests',
'',
'The -e/--environment flag is required to specify the store configuration.',
'Use --help with any command for more options.',
],
Copy link
Contributor

Choose a reason for hiding this comment

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

It doesn't seem like the output here is formatted as expected (we can use TokenItem to break lines and highlight commands):

Image Image

(we may handle this in follow-up PR)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes absolutely, will handle in a follow up PR.

Comment on lines 32 to 35
for (const file of essentialFiles) {
// eslint-disable-next-line no-await-in-loop
await this.assertFile(joinPath(this.themePath, file))
}
Copy link
Contributor

Choose a reason for hiding this comment

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

May we resolve promises here to avoid the the eslint ignore?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Originally I had them this way in case of consistent failures, the results would come back in order (such as missing files) but considering this is handling the order of a missing file or folder on an error, I switched to Promise.all

const directories = ['sections', 'snippets', 'assets', 'locales']

for (const dir of directories) {
// eslint-disable-next-line no-await-in-loop
Copy link
Contributor

Choose a reason for hiding this comment

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

May we resolve promises here too?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same as above comment

Comment on lines +33 to +41
if (this.context.environment) {
cmd += ` -e ${this.context.environment}`
}
if (this.context.store) {
cmd += ` -s ${this.context.store}`
}
if (this.context.password) {
cmd += ` --password ${this.context.password}`
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Given the framework requires the -e flag to run the test suite, and that this is something that's going to happen with all commands that require authentication, I believe we should try to extract this early to another place.

(we may apply this in a follow-up PR tho)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can add this in a follow up.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 4, 2026

Differences in type declarations

We detected differences in the type declarations generated by Typescript for this branch compared to the baseline ('main' branch). Please, review them to ensure they are backward-compatible. Here are some important things to keep in mind:

  • Some seemingly private modules might be re-exported through public modules.
  • If the branch is behind main you might see odd diffs, rebase main into this branch.

New type declarations

We found no new type declarations in this PR

Existing type declarations

packages/cli-kit/dist/public/node/system.d.ts
@@ -30,6 +30,50 @@ export declare function openURL(url: string): Promise<boolean>;
  * @returns A promise that resolves with the aggregatted stdout of the command.
  */
 export declare function captureOutput(command: string, args: string[], options?: ExecOptions): Promise<string>;
+/**
+ * Result from running a command with captureOutputWithExitCode.
+ */
+export interface CaptureOutputResult {
+    /** Standard output. */
+    stdout: string;
+    /** Standard error. */
+    stderr: string;
+    /** Exit code (0 = success). */
+    exitCode: number;
+}
+/**
+ * Runs a command asynchronously and returns stdout, stderr, and exit code.
+ * Unlike captureOutput, this function does NOT throw on non-zero exit codes.
+ *
+ * @param command - Command to be executed.
+ * @param args - Arguments to pass to the command.
+ * @param options - Optional settings for how to run the command.
+ * @returns A promise that resolves with stdout, stderr, and exitCode.
+ *
+ * @example
+ * 
+ */
+export declare function captureOutputWithExitCode(command: string, args: string[], options?: ExecOptions): Promise<CaptureOutputResult>;
+/**
+ * Runs a command string asynchronously and returns stdout, stderr, and exit code.
+ * Parses the command string into command and arguments (handles quoted strings).
+ * Unlike captureOutput, this function does NOT throw on non-zero exit codes.
+ *
+ * @param command - Full command string to be executed (e.g., 'ls -la "my folder"').
+ * @param options - Optional settings for how to run the command.
+ * @returns A promise that resolves with stdout, stderr, and exitCode.
+ *
+ * @example
+ * 
+ */
+export declare function captureCommandWithExitCode(command: string, options?: ExecOptions): Promise<CaptureOutputResult>;
+/**
+ * Runs a command string asynchronously (parses command and arguments from the string).
+ *
+ * @param command - Full command string to be executed (e.g., 'ls -la "my folder"').
+ * @param options - Optional settings for how to run the command.
+ */
+export declare function execCommand(command: string, options?: ExecOptions): Promise<void>;
 /**
  * Runs a command asynchronously.
  *

@EvilGenius13
Copy link
Contributor Author

/snapit

@github-actions
Copy link
Contributor

github-actions bot commented Feb 4, 2026

🫰✨ Thanks @EvilGenius13! Your snapshot has been published to npm.

Test the snapshot by installing your package globally:

npm i -g --@shopify:registry=https://registry.npmjs.org @shopify/[email protected]

Caution

After installing, validate the version by running just shopify in your terminal.
If the versions don't match, you might have multiple global instances installed.
Use which shopify to find out which one you are running and uninstall it.

@EvilGenius13
Copy link
Contributor Author

Added an extra commit changing how we handle filepaths in the command as it would fail on windows. The latest commit is working cross OS
image

@nickwesselman
Copy link
Contributor

hmm, should we consider some command naming that is more aligned with its internal use, even though it is hidden? People do discover these things and I'm not sure we want people running our tests?

Any additional knee-high walls? Requiring 1P login, or setting an environment variable for the tests to execute?

@EvilGenius13
Copy link
Contributor Author

hmm, should we consider some command naming that is more aligned with its internal use, even though it is hidden? People do discover these things and I'm not sure we want people running our tests?

Any additional knee-high walls? Requiring 1P login, or setting an environment variable for the tests to execute?

Absolutely. We will rename to doctor-release and set behind an ENV var.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants