react-doctor 0.9.12-dev.28a1a9f → 0.9.12-dev.3ff37f3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +27 -0
  2. package/dist/active-run-trace-dlIZeEvb.js +18 -0
  3. package/dist/active-scan-abort-registry-DwG6tSPy.js +28 -0
  4. package/dist/{build-section-divider-CdnczCsb.js → build-section-divider-Cj0nyrG1.js} +8 -22
  5. package/dist/{ci-CvrWkFyG.js → ci-C9w_Lhh6.js} +12 -11
  6. package/dist/cli.js +76 -24
  7. package/dist/{design-C6QoD6j5.js → design-CDE4LTuo.js} +4 -4
  8. package/dist/{devtools-D8NTVj81.js → devtools-BiC-AP3B.js} +3 -3
  9. package/dist/{exit-gracefully-dQBRJxyW.js → exit-gracefully-C7wG2TfE.js} +15 -9
  10. package/dist/get-east-asian-width-Cglw13G7.js +444 -0
  11. package/dist/{report-error-DspMW6A2.js → handle-error-DiCapepQ.js} +6 -61
  12. package/dist/index.d.ts +32 -0
  13. package/dist/index.js +76 -2
  14. package/dist/{inspect-CEYHtLuK.js → inspect-CoLAR93r.js} +16 -15
  15. package/dist/{install-BPQG8X-S.js → install-BJSIzCYw.js} +9 -8
  16. package/dist/{install-doctor-script-BZJlaCgz.js → install-doctor-script-CNGuoU8S.js} +6 -441
  17. package/dist/{install-github-workflow-DQIDvKig.js → install-github-workflow-BKVWkCTq.js} +6 -6
  18. package/dist/{install-react-doctor-C3iiSm4r.js → install-react-doctor-BWRYj-Fs.js} +8 -8
  19. package/dist/{run-scan-app-lYmDIiT4.js → jsx-runtime-B5Qxm0Ti.js} +280 -2774
  20. package/dist/{print-branded-header-B594Zejh.js → print-branded-header-DeYaSsGm.js} +4 -4
  21. package/dist/prompt-runtime-scan-url-BAhpWXsp.js +240 -0
  22. package/dist/{read-object-file-CIVH-Cg-.js → read-object-file-erb2VkBf.js} +4 -4
  23. package/dist/{record-metric-CFPfyOEW.js → record-metric-Da2M9WzV.js} +95 -8
  24. package/dist/report-error-UwwTVzhj.js +64 -0
  25. package/dist/{report-workflow-result-GxDOhexG.js → report-workflow-result-o2TuEe07.js} +5 -5
  26. package/dist/{resolve-scope-CNV3HRNo.js → resolve-scope-5IZ9ty12.js} +6 -6
  27. package/dist/{rules-C1Nt63By.js → rules-EVjd4fgi.js} +6 -6
  28. package/dist/run-scan-app-D6y6mTuD.js +2508 -0
  29. package/dist/runtime-scan/browser-probe.iife.js +855 -0
  30. package/dist/runtime-scan-OII2kzLp.js +770 -0
  31. package/dist/sanitize-runtime-url-B5UAaMft.js +76 -0
  32. package/dist/{scan-D-Y1Qifl.js → scan-Dbvtv4ys.js} +10 -22
  33. package/dist/{should-fail-scan-gate-XdfNPXf9.js → should-fail-scan-gate-ClMRBbO-.js} +12 -111
  34. package/dist/{should-skip-prompts-DXyhnzjP.js → should-skip-prompts-BtU4MJNK.js} +4 -4
  35. package/dist/should-use-tui-CWIewB4u.js +25 -0
  36. package/dist/skills/improve-threejs/SKILL.md +102 -0
  37. package/dist/skills/performance/SKILL.md +113 -0
  38. package/dist/skills/react-doctor/SKILL.md +6 -0
  39. package/dist/{unref-stdin-CwYEtpri.js → telemetry-runtime-6u7KYjVy.js} +5 -23
  40. package/dist/unref-stdin-BKT_s38a.js +12 -0
  41. package/dist/{version-tT7fsrFN.js → version-BxpgV1hk.js} +4 -4
  42. package/dist/{why-4B0HPeQG.js → why-vAAkjgbL.js} +4 -4
  43. package/dist/with-run-span-_U591pbM.js +110 -0
  44. package/package.json +3 -2
package/README.md CHANGED
@@ -55,6 +55,33 @@ You can configure which rules to run and how to run them in `doctor.config.ts`.
55
55
 
56
56
  [Learn more →](https://react.doctor/docs/configuration/config-files)
57
57
 
58
+ ## Runtime performance traces
59
+
60
+ Record a Chrome DevTools performance trace while you interact with a running React app:
61
+
62
+ ```bash
63
+ npx react-doctor@latest scan http://localhost:3000
64
+ ```
65
+
66
+ React Doctor opens system Chrome in a temporary isolated profile, records until you press Enter
67
+ (up to five minutes), and flashes purple outlines with component names as React renders. It then
68
+ returns a readable summary plus the path to a compressed DevTools trace. Use `--format json` or
69
+ `--format jsonl` for coding agents. In an interactive terminal, you can run `react-doctor scan`
70
+ without a URL and choose a detected localhost app or enter another URL; coding agents and CI must
71
+ pass the URL explicitly.
72
+
73
+ An already-open normal browser is left alone. To reuse an authenticated session, start a dedicated
74
+ Chrome profile with remote debugging, sign in, close its non-blank tabs, and pass its endpoint:
75
+
76
+ ```bash
77
+ npx react-doctor@latest scan https://app.example.com --cdp http://127.0.0.1:9222
78
+ ```
79
+
80
+ Chrome performance tracing is browser-wide, so React Doctor rejects attached profiles with open
81
+ pages. It closes blank startup tabs before tracing and closes its scan tab afterward; the attached
82
+ browser stays open. The trace is stored locally and is never uploaded, but it can contain page
83
+ URLs, source paths, and React profiling details. Treat it as sensitive application data.
84
+
58
85
  ## Telemetry
59
86
 
60
87
  The CLI reports crashes, basic run traces, and anonymous usage counters to [Sentry](https://sentry.io/) to help us fix bugs and prioritize work.
@@ -0,0 +1,18 @@
1
+ //#region src/cli/utils/active-run-trace.ts
2
+
3
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="38d6312b-a431-5dea-8088-ac88f4110ce8")}catch(e){}}();
4
+ let activeRunTrace = null;
5
+ const setActiveRunTrace = (trace) => {
6
+ activeRunTrace = trace;
7
+ };
8
+ const getActiveRunTrace = () => activeRunTrace;
9
+ let lastRunTraceId = null;
10
+ const recordRunTraceId = (traceId) => {
11
+ lastRunTraceId = traceId;
12
+ };
13
+ const getLastRunTraceId = () => lastRunTraceId;
14
+ //#endregion
15
+ export { setActiveRunTrace as i, getLastRunTraceId as n, recordRunTraceId as r, getActiveRunTrace as t };
16
+
17
+ //# sourceMappingURL=active-run-trace-dlIZeEvb.js.map
18
+ //# debugId=38d6312b-a431-5dea-8088-ac88f4110ce8
@@ -0,0 +1,28 @@
1
+ //#region src/cli/utils/active-scan-abort-registry.ts
2
+
3
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="464e40c7-b98a-555d-a847-5ddb886227d3")}catch(e){}}();
4
+ const activeScanAbortControllers = /* @__PURE__ */ new Set();
5
+ const activeScanCleanups = /* @__PURE__ */ new Set();
6
+ const activeScanAbortRegistry = {
7
+ register: (controller) => {
8
+ activeScanAbortControllers.add(controller);
9
+ return () => activeScanAbortControllers.delete(controller);
10
+ },
11
+ registerCleanup: (cleanup) => {
12
+ activeScanCleanups.add(cleanup);
13
+ return () => activeScanCleanups.delete(cleanup);
14
+ },
15
+ abortAll: async () => {
16
+ const controllers = [...activeScanAbortControllers];
17
+ const cleanups = [...activeScanCleanups];
18
+ activeScanAbortControllers.clear();
19
+ activeScanCleanups.clear();
20
+ for (const controller of controllers) controller.abort();
21
+ await Promise.allSettled(cleanups.map((cleanup) => cleanup()));
22
+ }
23
+ };
24
+ //#endregion
25
+ export { activeScanAbortRegistry as t };
26
+
27
+ //# sourceMappingURL=active-scan-abort-registry-DwG6tSPy.js.map
28
+ //# debugId=464e40c7-b98a-555d-a847-5ddb886227d3
@@ -1,7 +1,7 @@
1
1
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="a510ef38-c335-5207-aa80-ce74d4d590a8")}catch(e){}}();
3
- import { $n as CANONICAL_GITHUB_URL, Gt as highlighter, M as SENTRY_FLUSH_TIMEOUT_MS, a as isEnvFlagEnabled, ar as DOCS_URL, dt as anonymizeInPlace, ft as anonymizeText, i as isTelemetryEnabled, mn as scrubSensitivePaths, o as buildSentryScope } from "./record-metric-CFPfyOEW.js";
4
- import { c as isExpectedUserError } from "./unref-stdin-CwYEtpri.js";
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="359e0cb9-5a71-5497-821d-bc3d775b40c5")}catch(e){}}();
3
+ import { Kt as highlighter, N as SENTRY_FLUSH_TIMEOUT_MS, a as isEnvFlagEnabled, er as CANONICAL_GITHUB_URL, ft as anonymizeInPlace, hn as scrubSensitivePaths, i as isTelemetryEnabled, o as buildSentryScope, or as DOCS_URL, pt as anonymizeText } from "./record-metric-Da2M9WzV.js";
4
+ import { r as isExpectedUserError } from "./telemetry-runtime-6u7KYjVy.js";
5
5
  import * as Sentry from "@sentry/node";
6
6
  //#region src/cli/utils/scrub-sentry-event.ts
7
7
  /**
@@ -70,13 +70,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
70
70
  * uploads source-map artifacts under, so stack frames symbolicate. Honors the
71
71
  * standard `SENTRY_RELEASE` override.
72
72
  */
73
- const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.9.12-dev.28a1a9f`;
73
+ const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.9.12-dev.3ff37f3`;
74
74
  /**
75
75
  * Deployment environment shown in Sentry's environment filter. Defaults to
76
76
  * `production` for tagged releases and `development` for dev/unbuilt versions,
77
77
  * overridable via the standard `SENTRY_ENVIRONMENT` env var.
78
78
  */
79
- const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.9.12-dev.28a1a9f") ? "development" : "production");
79
+ const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.9.12-dev.3ff37f3") ? "development" : "production");
80
80
  //#endregion
81
81
  //#region src/instrument.ts
82
82
  let isInitialized = false;
@@ -137,20 +137,6 @@ const initializeSentry = () => {
137
137
  } catch {}
138
138
  };
139
139
  //#endregion
140
- //#region src/cli/utils/active-scan-abort-registry.ts
141
- const activeScanAbortControllers = /* @__PURE__ */ new Set();
142
- const activeScanAbortRegistry = {
143
- register: (controller) => {
144
- activeScanAbortControllers.add(controller);
145
- return () => activeScanAbortControllers.delete(controller);
146
- },
147
- abortAll: () => {
148
- const controllers = [...activeScanAbortControllers];
149
- activeScanAbortControllers.clear();
150
- for (const controller of controllers) controller.abort();
151
- }
152
- };
153
- //#endregion
154
140
  //#region src/cli/utils/resolve-measure-width.ts
155
141
  const resolveClampedWidth = (input) => {
156
142
  const terminalColumns = process.stdout.columns;
@@ -224,7 +210,7 @@ const buildFooterLinkLines = ({ shareUrl }) => [
224
210
  const DIVIDER_INDENT = " ";
225
211
  const buildSectionDivider = () => highlighter.dim(`${DIVIDER_INDENT}${"─".repeat(resolveMeasureWidth(2))}`);
226
212
  //#endregion
227
- export { initializeSentry as a, flushSentry as i, buildFooterLinkLines as n, activeScanAbortRegistry as r, buildSectionDivider as t };
213
+ export { initializeSentry as i, buildFooterLinkLines as n, flushSentry as r, buildSectionDivider as t };
228
214
 
229
- //# sourceMappingURL=build-section-divider-CdnczCsb.js.map
230
- //# debugId=a510ef38-c335-5207-aa80-ce74d4d590a8
215
+ //# sourceMappingURL=build-section-divider-Cj0nyrG1.js.map
216
+ //# debugId=359e0cb9-5a71-5497-821d-bc3d775b40c5
@@ -1,14 +1,15 @@
1
1
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="154e0951-d688-5d8f-ba77-8bb4b3f19acc")}catch(e){}}();
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="df882d81-e2b3-5b5c-9d91-2bb658683e9a")}catch(e){}}();
3
3
  import { i as __toESM, r as __require, t as __commonJSMin } from "./chunk-ChKA3wi6.js";
4
- import { Gt as highlighter, Rn as runSync, er as CI_URL, sr as GITHUB_ACTIONS_SETUP_URL, t as recordCount, w as METRIC } from "./record-metric-CFPfyOEW.js";
5
- import { c as isExpectedUserError } from "./unref-stdin-CwYEtpri.js";
6
- import { n as handleError, r as handleUserError, t as reportErrorToSentry } from "./report-error-DspMW6A2.js";
7
- import { L as cliLogger, c as spinner, n as findNearestPackageDirectory } from "./install-doctor-script-BZJlaCgz.js";
8
- import { a as readReactDoctorWorkflow, d as detectDefaultBranch, f as runCommand, l as prompts, n as getReactDoctorWorkflowPath, s as upgradeWorkflowActionToV2, t as buildWorkflowContent } from "./install-github-workflow-DQIDvKig.js";
9
- import { a as isValidBlockingLevel, c as openWorkflowPullRequest, l as stageWorkflowFile, n as isScopeValue } from "./resolve-scope-CNV3HRNo.js";
10
- import { t as shouldSkipPrompts } from "./should-skip-prompts-DXyhnzjP.js";
11
- import { t as printBrandedHeader } from "./print-branded-header-B594Zejh.js";
4
+ import { Kt as highlighter, T as METRIC, cr as GITHUB_ACTIONS_SETUP_URL, t as recordCount, tr as CI_URL, zn as runSync } from "./record-metric-Da2M9WzV.js";
5
+ import { r as isExpectedUserError } from "./telemetry-runtime-6u7KYjVy.js";
6
+ import { n as handleUserError, t as handleError } from "./handle-error-DiCapepQ.js";
7
+ import { t as reportErrorToSentry } from "./report-error-UwwTVzhj.js";
8
+ import { N as cliLogger, c as spinner, n as findNearestPackageDirectory } from "./install-doctor-script-CNGuoU8S.js";
9
+ import { a as readReactDoctorWorkflow, d as detectDefaultBranch, f as runCommand, l as prompts, n as getReactDoctorWorkflowPath, s as upgradeWorkflowActionToV2, t as buildWorkflowContent } from "./install-github-workflow-BKVWkCTq.js";
10
+ import { a as isValidBlockingLevel, c as openWorkflowPullRequest, l as stageWorkflowFile, n as isScopeValue } from "./resolve-scope-5IZ9ty12.js";
11
+ import { t as shouldSkipPrompts } from "./should-skip-prompts-BtU4MJNK.js";
12
+ import { t as printBrandedHeader } from "./print-branded-header-DeYaSsGm.js";
12
13
  import * as path$1 from "node:path";
13
14
  import * as fs$1 from "node:fs";
14
15
  //#region src/cli/utils/ci/ci-provider.ts
@@ -7497,5 +7498,5 @@ const ciConfigAction = (options) => runCiCommand("config", runCiConfig, options)
7497
7498
  //#endregion
7498
7499
  export { ciConfigAction, ciInstallAction, ciUpgradeAction };
7499
7500
 
7500
- //# sourceMappingURL=ci-CvrWkFyG.js.map
7501
- //# debugId=154e0951-d688-5d8f-ba77-8bb4b3f19acc
7501
+ //# sourceMappingURL=ci-C9w_Lhh6.js.map
7502
+ //# debugId=df882d81-e2b3-5b5c-9d91-2bb658683e9a
package/dist/cli.js CHANGED
@@ -1,11 +1,14 @@
1
1
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="03462e9a-cc61-5815-b641-9bce568efeb2")}catch(e){}}();
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="060d9fda-090f-56c0-a01c-3ce99371376f")}catch(e){}}();
3
3
  import { i as __toESM, r as __require, t as __commonJSMin } from "./chunk-ChKA3wi6.js";
4
- import { $n as CANONICAL_GITHUB_URL, Gt as highlighter, R as CliInputError, V as isDebugFlagEnabled, d as isJsonModeActive, er as CI_URL, g as VERSION, hn as setColorEnabled, i as isTelemetryEnabled, m as writeJsonErrorReport } from "./record-metric-CFPfyOEW.js";
5
- import { c as isExpectedUserError, r as getLastRunTraceId, s as shutdownTelemetry, t as unrefStdin } from "./unref-stdin-CwYEtpri.js";
6
- import { a as initializeSentry, i as flushSentry } from "./build-section-divider-CdnczCsb.js";
7
- import { t as exitGracefully } from "./exit-gracefully-dQBRJxyW.js";
8
- import { n as handleError, r as handleUserError, t as reportErrorToSentry } from "./report-error-DspMW6A2.js";
4
+ import { H as isDebugFlagEnabled, Kt as highlighter, _ as VERSION, d as isJsonModeActive, er as CANONICAL_GITHUB_URL, gn as setColorEnabled, h as writeJsonErrorReport, i as isTelemetryEnabled, tr as CI_URL, z as CliInputError } from "./record-metric-Da2M9WzV.js";
5
+ import { n as shutdownTelemetry, r as isExpectedUserError } from "./telemetry-runtime-6u7KYjVy.js";
6
+ import { i as initializeSentry, r as flushSentry } from "./build-section-divider-Cj0nyrG1.js";
7
+ import { t as exitGracefully } from "./exit-gracefully-C7wG2TfE.js";
8
+ import { n as handleUserError, t as handleError } from "./handle-error-DiCapepQ.js";
9
+ import { n as getLastRunTraceId } from "./active-run-trace-dlIZeEvb.js";
10
+ import { t as reportErrorToSentry } from "./report-error-UwwTVzhj.js";
11
+ import { t as unrefStdin } from "./unref-stdin-BKT_s38a.js";
9
12
  import { execFileSync } from "node:child_process";
10
13
  //#region ../../node_modules/.pnpm/commander@14.0.3/node_modules/commander/lib/error.js
11
14
  var require_error = /* @__PURE__ */ __commonJSMin(((exports) => {
@@ -3291,6 +3294,24 @@ const COMMAND_FLAG_SPECS = new Map([
3291
3294
  shortOptionsWithoutValues: new Set(["-h", "-y"]),
3292
3295
  shortOptionsWithRequiredValues: new Set(["-c"])
3293
3296
  }],
3297
+ ["scan", {
3298
+ longOptionsWithoutValues: new Set([
3299
+ "--color",
3300
+ "--debug",
3301
+ "--help",
3302
+ "--no-color",
3303
+ "--no-score",
3304
+ "--no-telemetry"
3305
+ ]),
3306
+ longOptionsWithRequiredValues: new Set([
3307
+ "--cdp",
3308
+ "--format",
3309
+ "--trace-out"
3310
+ ]),
3311
+ longOptionsWithOptionalValues: /* @__PURE__ */ new Set(),
3312
+ shortOptionsWithoutValues: new Set(["-h"]),
3313
+ shortOptionsWithRequiredValues: new Set(["-f"])
3314
+ }],
3294
3315
  ["why", {
3295
3316
  longOptionsWithoutValues: new Set([
3296
3317
  "--color",
@@ -3408,6 +3429,7 @@ ${highlighter.dim("Examples:")}
3408
3429
  ${formatExampleLines([
3409
3430
  ["react-doctor", "scan the current project"],
3410
3431
  ["react-doctor ./apps/web", "scan a specific directory"],
3432
+ ["react-doctor scan http://localhost:3000", "profile one interaction in a running React app"],
3411
3433
  ["react-doctor --scope changed --base main", "scan only new issues vs. main"],
3412
3434
  ["react-doctor --project modules/a,modules/b", "score each module separately (names or paths)"],
3413
3435
  ["react-doctor --staged", "scan staged files (pre-commit hook)"],
@@ -3459,6 +3481,32 @@ ${highlighter.dim("Scope:")}
3459
3481
  Whole-project maintainability, supply-chain, external lint-config, custom-plugin, and health-score passes are skipped.
3460
3482
  Standard scan flags such as ${highlighter.info("--scope")}, ${highlighter.info("--project")}, ${highlighter.info("--verbose")}, and ${highlighter.info("--json")} still work.
3461
3483
  `;
3484
+ const renderRuntimeScanHelpEpilog = () => `
3485
+ ${highlighter.dim("Examples:")}
3486
+ ${formatExampleLines([
3487
+ ["react-doctor scan", "choose a detected local app or enter its URL"],
3488
+ ["react-doctor scan http://localhost:3000", "scan one interaction in isolated Chrome"],
3489
+ ["react-doctor scan http://localhost:3000 --format json", "print a structured report for an agent"],
3490
+ ["react-doctor scan http://localhost:3000 --trace-out ./trace.json.gz", "choose where the private trace is saved"],
3491
+ ["react-doctor scan https://app.example.com --cdp http://127.0.0.1:9222", "reuse a dedicated debug-enabled Chrome profile"]
3492
+ ])}
3493
+
3494
+ ${highlighter.dim("How scanning works:")}
3495
+ 1. Start the app. Prefer a production build for representative timings.
3496
+ 2. Run this command. If you omit the URL, React Doctor suggests running localhost apps.
3497
+ 3. Reproduce one slow interaction within five minutes. Purple labels show component renders.
3498
+ 4. Return here and press Enter. React Doctor prints the report and trace path.
3499
+
3500
+ ${highlighter.dim("Authenticated apps:")}
3501
+ The default temporary profile starts signed out. Start a separate Chrome profile with remote
3502
+ debugging, sign in there, close its non-blank tabs, then pass ${highlighter.info("--cdp <url>")}.
3503
+ React Doctor closes blank startup tabs and its scan tab. The attached browser stays open.
3504
+
3505
+ ${highlighter.dim("Output and privacy:")}
3506
+ ${highlighter.info("text")} summarizes the evidence for people. ${highlighter.info("json")} returns one report; ${highlighter.info("jsonl")} returns one record per line.
3507
+ Chrome tracing is browser-wide, so ${highlighter.info("--cdp")} rejects profiles with open pages. The compressed
3508
+ ${highlighter.info(".json.gz")} trace stays local and can contain page URLs, source paths, and profiling data.
3509
+ `;
3462
3510
  const MAX_DURATION_OPTION_DESCRIPTION = "scan time budget for the whole run, shared across workspace projects: past it, queued projects, remaining lint batches, and maintainability analysis are skipped and partial results are reported (skipped files and projects are listed in the JSON report)";
3463
3511
  const renderCiHelpEpilog = () => `
3464
3512
  ${highlighter.dim("Examples:")}
@@ -3481,7 +3529,7 @@ ${highlighter.dim("Learn more:")}
3481
3529
  const collectCategoryOption = (value, previousValues) => [...previousValues ?? [], value];
3482
3530
  const program = new Command().name("react-doctor").description("Diagnose React codebase health").version(VERSION, "-v, --version", "display the version number").argument("[directory]", "project directory to scan", ".").option("--lint", "enable linting").option("--no-lint", "skip linting").addOption(new Option("--dead-code").hideHelp()).addOption(new Option("--no-dead-code").hideHelp()).option("--supply-chain", "enable the dependency supply-chain scan (default)").option("--no-supply-chain", "skip the dependency supply-chain scan (Socket.dev dependency health checks)").option("--verbose", "show every rule and per-file details (default shows top 3 rules)").option("--debug", "force a Sentry trace and print its id at the end (paste it into a bug report)").option("--output-dir <dir>", "directory for the full diagnostics dump (default: a temp folder)").option("--score", "output only the score").option("--json", "output a single structured JSON report (suppresses other output)").option("--json-compact", "with --json, emit compact JSON (no indentation)").option("--json-out <path>", "with --json, write the report to a file instead of stdout").option("--no-cache", "disable all scan caches for this run").option("-y, --yes", "skip prompts, scan all workspace projects").option("--no-parallel", "lint serially with one worker (default: parallel across CPU cores; set the worker count with REACT_DOCTOR_PARALLEL)").option("--project <name>", "select projects: workspace names or directory paths (comma-separated for multiple); overrides the `projects` config field").option("--scope <value>", "how much supported JS/TS and inline HTML script source to scan/report: full (default), files, changed (only new issues vs base), or lines (issues whose source spans touch changed lines)").option("--base <ref>", "base git ref for files/changed/lines scope (auto-detected when omitted)").option("--include-untracked", "with --scope files/changed/lines, also scan ordinary untracked files (respects .gitignore)").addOption(new Option("--diff [base]", "[deprecated] alias for --scope changed (pass `false` to force a full scan)").hideHelp()).addOption(new Option("--changed-files-from <file>", "scan source files listed in a newline-delimited changed-files file").hideHelp()).option("--no-score", "skip the score API, the share URL, and crash reporting").addOption(new Option("--category <category>", "only show diagnostics in a category (repeatable; e.g. Security)").argParser(collectCategoryOption)).option("--no-telemetry", "alias for --no-score (skip the score API, share URL, and crash reporting)").option("--staged", "scan only staged (git index) files for pre-commit hooks (honors --project and config `projects`; exits 0 when nothing is staged)").option("--max-duration <seconds>", MAX_DURATION_OPTION_DESCRIPTION).option("--blocking <level>", "severity that fails CI: error (default), warning, or none (advisory)").addOption(new Option("--fail-on <level>", "[deprecated] alias for --blocking <level>").hideHelp()).option("--no-respect-inline-disables", "audit mode: neutralize inline lint suppressions before scanning").option("--warnings", "show warning-severity diagnostics (default)").option("--no-warnings", "hide warning-severity diagnostics (errors only)").option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").addHelpText("after", renderRootHelpEpilog);
3483
3531
  program.action(async (directory = ".", flags) => {
3484
- const { runScanCommand } = await import("./scan-D-Y1Qifl.js");
3532
+ const { runScanCommand } = await import("./scan-Dbvtv4ys.js");
3485
3533
  return runScanCommand({
3486
3534
  directory,
3487
3535
  flags,
@@ -3489,71 +3537,75 @@ program.action(async (directory = ".", flags) => {
3489
3537
  });
3490
3538
  });
3491
3539
  program.command("design [directory]").description("Run only the focused UI design diagnostics").addHelpText("after", renderDesignHelpEpilog).action(async (directory, _options, command) => {
3492
- const { designAction } = await import("./design-C6QoD6j5.js");
3540
+ const { designAction } = await import("./design-CDE4LTuo.js");
3493
3541
  return designAction(directory ?? ".", command.optsWithGlobals());
3494
3542
  });
3543
+ program.command("scan").description("Scan a React interaction in Chrome until you press Enter").argument("[url]", "HTTP(S) URL of the running React app; detects local apps when omitted").option("-f, --format <format>", "report format: text, json, or jsonl", "text").option("--cdp <url>", "reuse Chrome at this remote-debugging endpoint").option("--trace-out <path>", "save the compressed .json.gz DevTools trace at this path").addHelpText("after", renderRuntimeScanHelpEpilog).action(async (url, options) => {
3544
+ const { runtimeScanAction } = await import("./runtime-scan-OII2kzLp.js");
3545
+ return runtimeScanAction(url, options);
3546
+ });
3495
3547
  program.command("why <location>").description("Explain why a rule fired (or why a suppression didn't apply) at a file:line").option("--project <name>", "select projects: workspace names or directory paths (comma-separated for multiple)").option("-c, --cwd <cwd>", "working directory", process.cwd()).option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").action(async (location, options) => {
3496
- const { whyAction } = await import("./why-4B0HPeQG.js");
3548
+ const { whyAction } = await import("./why-vAAkjgbL.js");
3497
3549
  return whyAction(location, options);
3498
3550
  });
3499
3551
  program.command("install").alias("setup").description("Install the react-doctor skill into your coding agents and optional git hook").option("-y, --yes", "skip prompts, install for all detected agents").option("--dry-run", "show what would be installed without writing files").option("--agent-hooks", "install end-of-turn agent hooks for Claude Code and Cursor").option("-c, --cwd <cwd>", "working directory", process.cwd()).option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").addHelpText("after", renderInstallHelpEpilog).action(async (options, command) => {
3500
- const { installAction } = await import("./install-BPQG8X-S.js");
3552
+ const { installAction } = await import("./install-BJSIzCYw.js");
3501
3553
  return installAction(options, command);
3502
3554
  });
3503
3555
  const providerOption = ["--provider <name>", "ci provider: github-actions or gitlab-ci (auto-detected by default)"];
3504
3556
  const prOption = ["--pr", "open a pull request with the change instead of writing it to the working tree"];
3505
3557
  const ci = program.command("ci").description("Set up, upgrade, and configure React Doctor in your CI");
3506
3558
  ci.command("install").description("Add a CI workflow that scans every pull request").option(...providerOption).option(...prOption).option("--blocking <level>", "gate level: none (advisory, default), warning, or error").option("--scope <value>", "what a pull-request scan reports: changed (default), files, lines, full").option("--comment", "post a summary comment on each pull request").option("--no-comment", "don't post a summary comment").option("--review-comments", "add inline review comments on changed lines").option("--no-review-comments", "don't add inline review comments").option("--commit-status", "report a commit status with the health score").option("--no-commit-status", "don't report a commit status").option("-y, --yes", "skip prompts; use the defaults").option("-c, --cwd <cwd>", "working directory", process.cwd()).option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").addHelpText("after", renderCiHelpEpilog).action(async (_options, command) => {
3507
- const { ciInstallAction } = await import("./ci-CvrWkFyG.js");
3559
+ const { ciInstallAction } = await import("./ci-C9w_Lhh6.js");
3508
3560
  return ciInstallAction(command.optsWithGlobals());
3509
3561
  });
3510
3562
  ci.command("config").description("Change the gate, scan scope, and pull-request reporting").option(...providerOption).option("--blocking <level>", "gate level: none (advisory), warning, or error").option("--scope <value>", "what a pull-request scan reports: changed, files, lines, or full").option("--comment", "post a summary comment on each pull request").option("--no-comment", "don't post a summary comment").option("--review-comments", "add inline review comments on changed lines").option("--no-review-comments", "don't add inline review comments").option("--commit-status", "report a commit status with the health score").option("--no-commit-status", "don't report a commit status").option("-y, --yes", "skip prompts; print the current settings").option("-c, --cwd <cwd>", "working directory", process.cwd()).option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").action(async (_options, command) => {
3511
- const { ciConfigAction } = await import("./ci-CvrWkFyG.js");
3563
+ const { ciConfigAction } = await import("./ci-C9w_Lhh6.js");
3512
3564
  return ciConfigAction(command.optsWithGlobals());
3513
3565
  });
3514
3566
  ci.command("upgrade").description("Upgrade the CI workflow to the action's current major").option(...providerOption).option(...prOption).option("-y, --yes", "skip prompts").option("-c, --cwd <cwd>", "working directory", process.cwd()).option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").action(async (_options, command) => {
3515
- const { ciUpgradeAction } = await import("./ci-CvrWkFyG.js");
3567
+ const { ciUpgradeAction } = await import("./ci-C9w_Lhh6.js");
3516
3568
  return ciUpgradeAction(command.optsWithGlobals());
3517
3569
  });
3518
3570
  program.command("version").description("show the version with Node and platform info").option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").action(async () => {
3519
- const { versionAction } = await import("./version-tT7fsrFN.js");
3571
+ const { versionAction } = await import("./version-BxpgV1hk.js");
3520
3572
  return versionAction();
3521
3573
  });
3522
3574
  const rules = program.command("rules").description("List, explain, and configure which React Doctor rules run");
3523
3575
  rules.command("list").description("List rules and the severity they run at under your config").option("--category <name>", "only show rules in a category (e.g. Performance)").option("--tag <name>", "only show rules with a tag (e.g. design, test-noise)").option("--framework <name>", "only show rules for a framework (e.g. global, nextjs)").option("--configured", "only show rules your config has changed from the default").option("--json", "output a structured JSON array").option("-c, --cwd <cwd>", "working directory", process.cwd()).action(async (_options, command) => {
3524
- const { rulesListAction } = await import("./rules-C1Nt63By.js");
3576
+ const { rulesListAction } = await import("./rules-EVjd4fgi.js");
3525
3577
  return rulesListAction(command.optsWithGlobals());
3526
3578
  });
3527
3579
  rules.command("explain <rule>").description("Explain why a rule matters, its current severity, and how to configure it").option("--json", "output a structured JSON object").option("-c, --cwd <cwd>", "working directory", process.cwd()).action(async (rule, _options, command) => {
3528
- const { rulesExplainAction } = await import("./rules-C1Nt63By.js");
3580
+ const { rulesExplainAction } = await import("./rules-EVjd4fgi.js");
3529
3581
  return rulesExplainAction(rule, command.optsWithGlobals());
3530
3582
  });
3531
3583
  rules.command("set <rule> <severity>").description("Set a rule's severity: off, warn, or error").option("-c, --cwd <cwd>", "working directory", process.cwd()).action(async (rule, severity, _options, command) => {
3532
- const { rulesSetAction } = await import("./rules-C1Nt63By.js");
3584
+ const { rulesSetAction } = await import("./rules-EVjd4fgi.js");
3533
3585
  return rulesSetAction(rule, severity, command.optsWithGlobals());
3534
3586
  });
3535
3587
  rules.command("enable <rule>").description("Enable a rule at its recommended severity (or pass --severity)").option("--severity <level>", "severity to enable at: warn or error").option("-c, --cwd <cwd>", "working directory", process.cwd()).action(async (rule, _options, command) => {
3536
- const { rulesEnableAction } = await import("./rules-C1Nt63By.js");
3588
+ const { rulesEnableAction } = await import("./rules-EVjd4fgi.js");
3537
3589
  return rulesEnableAction(rule, command.optsWithGlobals());
3538
3590
  });
3539
3591
  rules.command("disable <rule>").description("Disable a rule so it never runs").option("-c, --cwd <cwd>", "working directory", process.cwd()).action(async (rule, _options, command) => {
3540
- const { rulesDisableAction } = await import("./rules-C1Nt63By.js");
3592
+ const { rulesDisableAction } = await import("./rules-EVjd4fgi.js");
3541
3593
  return rulesDisableAction(rule, command.optsWithGlobals());
3542
3594
  });
3543
3595
  rules.command("category <category> <severity>").description("Set the severity for a whole category (off, warn, error)").option("-c, --cwd <cwd>", "working directory", process.cwd()).action(async (category, severity, _options, command) => {
3544
- const { rulesCategoryAction } = await import("./rules-C1Nt63By.js");
3596
+ const { rulesCategoryAction } = await import("./rules-EVjd4fgi.js");
3545
3597
  return rulesCategoryAction(category, severity, command.optsWithGlobals());
3546
3598
  });
3547
3599
  rules.command("ignore-tag <tag>").description("Skip a whole rule family by tag before linting (e.g. design)").option("-c, --cwd <cwd>", "working directory", process.cwd()).action(async (tag, _options, command) => {
3548
- const { rulesIgnoreTagAction } = await import("./rules-C1Nt63By.js");
3600
+ const { rulesIgnoreTagAction } = await import("./rules-EVjd4fgi.js");
3549
3601
  return rulesIgnoreTagAction(tag, command.optsWithGlobals());
3550
3602
  });
3551
3603
  rules.command("unignore-tag <tag>").description("Stop ignoring a tag previously skipped via ignore-tag").option("-c, --cwd <cwd>", "working directory", process.cwd()).action(async (tag, _options, command) => {
3552
- const { rulesUnignoreTagAction } = await import("./rules-C1Nt63By.js");
3604
+ const { rulesUnignoreTagAction } = await import("./rules-EVjd4fgi.js");
3553
3605
  return rulesUnignoreTagAction(tag, command.optsWithGlobals());
3554
3606
  });
3555
3607
  program.command("experimental-tui [directory]", { hidden: true }).description("[experimental] interactive, scrollable scan report").option("--blocking <level>", "severity that fails CI: error (default), warning, or none (advisory)").option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").addOption(new Option("--no-dead-code").hideHelp()).option("--no-supply-chain", "skip the dependency supply-chain scan").option("--score", "only print the numeric score (for scripts and CI)").option("--no-score", "skip the score API, the share URL, and crash reporting").option("--no-cache", "disable all scan caches for this run").option("--max-duration <seconds>", MAX_DURATION_OPTION_DESCRIPTION).option("-p, --project <names>", "scan specific workspace projects (comma-separated, or *)").option("-y, --yes", "skip the project prompt and scan every discovered project").action(async (directory = ".", _localOptions, command) => {
3556
- const { runScanCommand } = await import("./scan-D-Y1Qifl.js");
3608
+ const { runScanCommand } = await import("./scan-Dbvtv4ys.js");
3557
3609
  return runScanCommand({
3558
3610
  directory,
3559
3611
  flags: command.optsWithGlobals(),
@@ -3592,4 +3644,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
3592
3644
  export {};
3593
3645
 
3594
3646
  //# sourceMappingURL=cli.js.map
3595
- //# debugId=03462e9a-cc61-5815-b641-9bce568efeb2
3647
+ //# debugId=060d9fda-090f-56c0-a01c-3ce99371376f
@@ -1,6 +1,6 @@
1
1
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="8e58ad56-88ef-508c-9d44-a0034346b9cd")}catch(e){}}();
3
- import { t as inspectAction } from "./inspect-CEYHtLuK.js";
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="ea9260a7-b195-58ef-a22a-2041fab9990b")}catch(e){}}();
3
+ import { t as inspectAction } from "./inspect-CoLAR93r.js";
4
4
  //#region src/cli/commands/design.ts
5
5
  const designAction = async (directory, flags) => inspectAction(directory, {
6
6
  ...flags,
@@ -10,5 +10,5 @@ const designAction = async (directory, flags) => inspectAction(directory, {
10
10
  //#endregion
11
11
  export { designAction };
12
12
 
13
- //# sourceMappingURL=design-C6QoD6j5.js.map
14
- //# debugId=8e58ad56-88ef-508c-9d44-a0034346b9cd
13
+ //# sourceMappingURL=design-CDE4LTuo.js.map
14
+ //# debugId=ea9260a7-b195-58ef-a22a-2041fab9990b
@@ -1,5 +1,5 @@
1
1
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="9bd200c1-4571-57fb-8b9f-f59503d08d1d")}catch(e){}}();
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="27aef5e9-9c40-5fb8-a52c-e639ba9a5c8b")}catch(e){}}();
3
3
  import { i as __toESM, r as __require, t as __commonJSMin } from "./chunk-ChKA3wi6.js";
4
4
  //#region ../../node_modules/.pnpm/ws@8.20.0/node_modules/ws/lib/constants.js
5
5
  var require_constants = /* @__PURE__ */ __commonJSMin(((exports, module) => {
@@ -16999,5 +16999,5 @@ if (await isDevToolsReachable()) {
16999
16999
  //#endregion
17000
17000
  export {};
17001
17001
 
17002
- //# sourceMappingURL=devtools-D8NTVj81.js.map
17003
- //# debugId=9bd200c1-4571-57fb-8b9f-f59503d08d1d
17002
+ //# sourceMappingURL=devtools-BiC-AP3B.js.map
17003
+ //# debugId=27aef5e9-9c40-5fb8-a52c-e639ba9a5c8b
@@ -1,8 +1,9 @@
1
1
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="35e0cca5-cdf2-53c1-ae27-a8ad6483dd63")}catch(e){}}();
3
- import { d as isJsonModeActive, m as writeJsonErrorReport } from "./record-metric-CFPfyOEW.js";
4
- import { s as shutdownTelemetry } from "./unref-stdin-CwYEtpri.js";
5
- import { i as flushSentry, n as buildFooterLinkLines, r as activeScanAbortRegistry, t as buildSectionDivider } from "./build-section-divider-CdnczCsb.js";
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="707bd6fa-0d6d-5c16-a90d-18877744e4f1")}catch(e){}}();
3
+ import { d as isJsonModeActive, m as writeJsonCancellationReport } from "./record-metric-Da2M9WzV.js";
4
+ import { n as shutdownTelemetry } from "./telemetry-runtime-6u7KYjVy.js";
5
+ import { n as buildFooterLinkLines, r as flushSentry, t as buildSectionDivider } from "./build-section-divider-Cj0nyrG1.js";
6
+ import { t as activeScanAbortRegistry } from "./active-scan-abort-registry-DwG6tSPy.js";
6
7
  //#region src/cli/utils/active-tui-renderer.ts
7
8
  let activeTuiRenderer = null;
8
9
  const registerActiveTuiRenderer = (renderer) => {
@@ -22,14 +23,15 @@ let didStartExiting = false;
22
23
  const exitGracefully = () => {
23
24
  if (didStartExiting) return process.exit(130);
24
25
  didStartExiting = true;
26
+ let activeScanCleanup = Promise.resolve();
25
27
  try {
26
- activeScanAbortRegistry.abortAll();
28
+ activeScanCleanup = activeScanAbortRegistry.abortAll();
27
29
  } catch {}
28
30
  try {
29
31
  preserveActiveTuiRendererOutput();
30
32
  } catch {}
31
33
  try {
32
- if (isJsonModeActive()) writeJsonErrorReport(/* @__PURE__ */ new Error("Scan cancelled by user (SIGINT/SIGTERM)"));
34
+ if (isJsonModeActive()) writeJsonCancellationReport();
33
35
  else console.log([
34
36
  "",
35
37
  "Cancelled.",
@@ -39,10 +41,14 @@ const exitGracefully = () => {
39
41
  ...buildFooterLinkLines({ shareUrl: null })
40
42
  ].join("\n"));
41
43
  } catch {}
42
- Promise.all([flushSentry(), shutdownTelemetry()]).finally(() => process.exit(130));
44
+ Promise.all([
45
+ activeScanCleanup,
46
+ flushSentry(),
47
+ shutdownTelemetry()
48
+ ]).finally(() => process.exit(130));
43
49
  };
44
50
  //#endregion
45
51
  export { registerActiveTuiRenderer as n, exitGracefully as t };
46
52
 
47
- //# sourceMappingURL=exit-gracefully-dQBRJxyW.js.map
48
- //# debugId=35e0cca5-cdf2-53c1-ae27-a8ad6483dd63
53
+ //# sourceMappingURL=exit-gracefully-C7wG2TfE.js.map
54
+ //# debugId=707bd6fa-0d6d-5c16-a90d-18877744e4f1