react-doctor 0.9.12-dev.ffc2d14 → 0.9.13-dev.1239043

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 (47) hide show
  1. package/README.md +33 -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-BuOtLrzK.js → build-section-divider-C09RcJrc.js} +8 -22
  5. package/dist/{ci-DVVsf25h.js → ci-CUgNjCr-.js} +12 -11
  6. package/dist/cli.js +116 -35
  7. package/dist/{design-Y1pcA2uj.js → design-CX2j8ubS.js} +4 -4
  8. package/dist/{devtools-D8NTVj81.js → devtools-CC2PAZ85.js} +3 -3
  9. package/dist/{exit-gracefully-DnXGONjj.js → exit-gracefully-cGJM2g3O.js} +15 -9
  10. package/dist/get-east-asian-width-Bv2TD3Xj.js +444 -0
  11. package/dist/{report-error-MGKyeIns.js → handle-error-BCI-Nv3P.js} +18 -68
  12. package/dist/index.d.ts +32 -0
  13. package/dist/index.js +628 -301
  14. package/dist/{inspect-g3_lzs3l.js → inspect-Dq5krKWG.js} +41 -29
  15. package/dist/{install-02Lq6wKC.js → install-C0aiZKgf.js} +9 -8
  16. package/dist/{install-doctor-script-DmEWQpVJ.js → install-doctor-script-CaTIms0r.js} +7 -449
  17. package/dist/{install-github-workflow-Dx6Pl9Wl.js → install-github-workflow-DjPgXVqg.js} +10 -6
  18. package/dist/{install-react-doctor-CMCXgenA.js → install-react-doctor-C-PVDWXv.js} +8 -8
  19. package/dist/{run-scan-app-CDDu7P32.js → jsx-runtime-D_A4d39v.js} +280 -2774
  20. package/dist/path-format-DnHpmJwG.js +9 -0
  21. package/dist/{print-branded-header-kbvWYuGv.js → print-branded-header-Dz0SwyHQ.js} +4 -4
  22. package/dist/project-analysis-worker.js +59 -54
  23. package/dist/prompt-runtime-scan-url-BXdY2Opw.js +240 -0
  24. package/dist/{read-object-file-Cwhr9OIi.js → read-object-file-CmMGK1uu.js} +4 -4
  25. package/dist/{record-metric-CFfsgcVs.js → record-metric-B6T_Ej33.js} +592 -250
  26. package/dist/report-error-DwL3b99q.js +64 -0
  27. package/dist/{report-workflow-result-DGg9xopH.js → report-workflow-result-5PueDIAr.js} +5 -5
  28. package/dist/{resolve-scope-CXS-5jLR.js → resolve-scope-CTlLl7e6.js} +8 -10
  29. package/dist/{rules-CThlosPx.js → rules-DEbHKRag.js} +6 -6
  30. package/dist/run-scan-app-D3huvmXJ.js +2517 -0
  31. package/dist/runtime-scan/browser-probe.iife.js +855 -0
  32. package/dist/runtime-scan-CHFAEYg3.js +770 -0
  33. package/dist/sanitize-runtime-url-Bbq40iIp.js +76 -0
  34. package/dist/scan-C7sCm7wi.js +47 -0
  35. package/dist/{should-fail-scan-gate-C0Bo5Gqy.js → should-fail-scan-gate-CfG_ppAV.js} +50 -117
  36. package/dist/{should-skip-prompts-BqrtVX4G.js → should-skip-prompts-BuNtAVEs.js} +4 -4
  37. package/dist/should-use-tui-B-lCjXVG.js +25 -0
  38. package/dist/skills/improve-threejs/SKILL.md +102 -0
  39. package/dist/skills/performance/SKILL.md +113 -0
  40. package/dist/skills/react-doctor/SKILL.md +6 -0
  41. package/dist/{unref-stdin-BxyvqsOy.js → telemetry-runtime-BF83aBFL.js} +55 -25
  42. package/dist/unref-stdin-SK3WfwxS.js +12 -0
  43. package/dist/{version-S-8vvWFi.js → version-C27gr4Qj.js} +4 -4
  44. package/dist/{why-DXfx0oL9.js → why-CYRkZ2VY.js} +4 -4
  45. package/dist/with-run-span-CdsXSJgE.js +110 -0
  46. package/package.json +9 -6
  47. package/dist/scan-4I1mvxBO.js +0 -53
@@ -0,0 +1,113 @@
1
+ ---
2
+ name: performance
3
+ description: Diagnose React runtime performance with React Doctor traces, live render outlines, Long Animation Frames, interaction timing, and component render evidence. Use when invoked as `/performance` for a slow interaction, unexpected re-renders, or a measured before-and-after comparison.
4
+ disable-model-invocation: true
5
+ ---
6
+
7
+ # Diagnose React runtime performance
8
+
9
+ Measure one reproducible interaction, connect browser work to React renders, and report only conclusions supported by the trace.
10
+
11
+ ## Define the interaction
12
+
13
+ Before recording:
14
+
15
+ 1. Identify the target URL
16
+ 2. Write the exact actions to reproduce
17
+ 3. Choose the expected result
18
+ 4. Confirm whether authentication is required
19
+
20
+ Use a production build when available. Development builds add framework work that can distort render and script timings. If you must measure a development build, label that limitation in the report.
21
+
22
+ ## Record the trace
23
+
24
+ Run the scan in an interactive terminal:
25
+
26
+ ```bash
27
+ npx react-doctor@latest scan http://localhost:3000 --format json
28
+ ```
29
+
30
+ React Doctor opens an isolated Chrome profile. Perform the planned interaction while purple outlines identify rendered components. Press Enter after the interaction settles; recordings stop automatically after five minutes.
31
+
32
+ Interactive users can omit the URL and choose a detected localhost app or enter another URL. Agents must always pass the explicit URL so automated runs never wait for input.
33
+
34
+ For an authenticated session, connect through the Chrome DevTools Protocol (CDP):
35
+
36
+ ```bash
37
+ npx react-doctor@latest scan https://app.example.com \
38
+ --cdp http://127.0.0.1:9222 \
39
+ --format json
40
+ ```
41
+
42
+ Use a dedicated debug profile for CDP because Chrome tracing is browser-wide. Sign in, close every non-blank tab, and then start the scan. React Doctor closes leftover blank tabs before tracing. Never request cookies, copy a browser profile, or close an externally managed browser.
43
+
44
+ The compressed `.json.gz` trace can contain URLs, source paths, and application behavior. Keep it local unless an upload is explicitly approved.
45
+
46
+ ## Read the report
47
+
48
+ Evaluate the report in this order:
49
+
50
+ 1. **Capture support**: confirm React detection, build type, React tracks, and Long Animation Frame support
51
+ 2. **User impact**: inspect the worst interaction, total blocking duration, Largest Contentful Paint (LCP), and Cumulative Layout Shift (CLS)
52
+ 3. **Browser work**: inspect long frames and script hotspots for event handling, JavaScript, style, layout, and paint cost
53
+ 4. **React work**: inspect component render count, total self time, and maximum self time
54
+ 5. **Capture limits**: read every warning, especially dropped event counts
55
+
56
+ Follow these interpretation rules:
57
+
58
+ - A high render count is evidence, not a defect. Pair it with duration and user impact
59
+ - Generated chunk names identify browser work, not the owning source component
60
+ - A slow interaction can be browser-bound even when every component render is cheap
61
+ - Repeated Event Timing records with one interaction identifier represent one interaction
62
+ - Dropped component events make hotspot totals incomplete
63
+ - Use purple labels to identify the active subtree, then use recorded timings to set severity
64
+
65
+ ## Connect measurements to source
66
+
67
+ Search the repository for measured component display names and event handlers. Confirm that each candidate runs in the recorded flow before reporting it.
68
+
69
+ Open the DevTools trace when the summary cannot explain a long frame. Correlate the interaction timestamp with script tasks, style or layout work, React tracks, and paint. Do not infer causality from neighboring timestamps alone.
70
+
71
+ ## Report findings
72
+
73
+ Use this structure:
74
+
75
+ ```markdown
76
+ ## Flow tested
77
+
78
+ tested_url, build_type, and exact_interaction
79
+
80
+ ## Verdict
81
+
82
+ one_evidence_backed_paragraph
83
+
84
+ ## Evidence
85
+
86
+ | Signal | Measurement | Interpretation |
87
+ | ----------------- | ---------------------------: | --------------- |
88
+ | Worst interaction | duration_ms | measured_cause |
89
+ | Total blocking | duration_ms | measured_scope |
90
+ | Top component | render_count and duration_ms | measured_impact |
91
+
92
+ ## Findings
93
+
94
+ 1. `path/to/component.tsx:42`: measured_problem, evidence, and smallest_fix
95
+
96
+ ## Limits
97
+
98
+ capture_warnings, missing_support, or environmental_caveats
99
+ ```
100
+
101
+ Do not pad the report with static lint findings. Include source findings only when runtime evidence connects them to the tested flow.
102
+
103
+ ## Validate a fix
104
+
105
+ Do not edit code unless code changes are requested. After a fix:
106
+
107
+ 1. Rebuild with the same mode
108
+ 2. Record the same interaction at the same viewport
109
+ 3. Run three before and three after samples when timing variance could change the conclusion
110
+ 4. Compare medians for interaction, blocking, and component duration
111
+ 5. Confirm behavior and accessibility did not regress
112
+
113
+ Reject improvements that only move work outside the recorded window or disable useful behavior.
@@ -22,6 +22,12 @@ Run `npx react-doctor@latest --verbose` (the default `--scope full`) to scan the
22
22
 
23
23
  Run `npx react-doctor@latest design --verbose`. This selects only design-tagged UI composition, typography, interaction, accessibility, and motion rules, including focused rules that remain opt-in during a general health scan.
24
24
 
25
+ ## For runtime performance problems:
26
+
27
+ Run `npx react-doctor@latest scan <url> --format json` in an interactive terminal. React Doctor opens an isolated system Chrome profile, records a DevTools trace while the user reproduces the slow interaction, and flashes purple outlines with component names as React renders. It stops when they press Enter. Read the structured summary first, then inspect the returned local `.json.gz` trace for CPU, browser, and React component evidence.
28
+
29
+ If the user needs their authenticated browser state, use `--cdp <remote-debugging-url>`. This requires Chrome to already be running with remote debugging. Never ask for cookies or copy the user's browser profile. Treat the trace as sensitive local application data and never upload it without explicit permission.
30
+
25
31
  ## /doctor — full local triage workflow
26
32
 
27
33
  When the user types `/doctor`, says "run react doctor", or asks for a full triage / cleanup pass (not just a regression check), fetch the canonical local-triage playbook and follow every step in it:
@@ -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]="2bee1c15-4924-512e-8abd-f6f764de1963")}catch(e){}}();
3
- import { Hn as buildWithScope, Jt as isProjectDiscoveryError, Kn as close, Ln as runPromise, On as make, Qt as layerObservability, R as CliInputError, Rn as runSync, Sr as messageFromUnknown, Yn as void_, Yt as isReactDoctorError, g as VERSION, i as isTelemetryEnabled, qn as makeUnsafe, yr as isErrnoException } from "./record-metric-CFfsgcVs.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]="f658c385-8ef1-58af-8f8f-a72607a16be2")}catch(e){}}();
3
+ import { $t as layerObservability, Cr as messageFromUnknown, Jn as makeUnsafe, Rn as runPromise, Un as buildWithScope, Xn as void_, Xt as isReactDoctorError, Yt as isProjectDiscoveryError, _ as VERSION, br as isErrnoException, i as isTelemetryEnabled, kn as make, qn as close, z as CliInputError, zn as runSync } from "./record-metric-B6T_Ej33.js";
4
4
  //#region src/cli/utils/is-environment-error.ts
5
5
  const ENVIRONMENT_ERROR_CODES = new Set([
6
6
  "EACCES",
@@ -61,6 +61,51 @@ const formatEnvironmentError = (error) => {
61
61
  }
62
62
  };
63
63
  //#endregion
64
+ //#region src/cli/utils/is-npm-cache-corruption-error.ts
65
+ const MISSING_AJV_META_SCHEMA_MESSAGES = new Set(["Cannot find module './meta/unevaluated.json'", "Cannot find module './meta/validation.json'"]);
66
+ const NPX_AJV_META_SCHEMA_REQUIRE_PATH_PATTERN = /(?:^|\/)_npx\/(?<cacheKey>[a-f0-9]{16})\/node_modules\/ajv\/dist\/refs\/json-schema-2020-12\/index\.js$/;
67
+ const isModuleNotFoundError = (error) => error instanceof Error && "code" in error && error.code === "MODULE_NOT_FOUND";
68
+ const findNpmCacheCorruptionError = (error) => {
69
+ const pendingErrors = [error];
70
+ const visitedErrors = /* @__PURE__ */ new Set();
71
+ while (pendingErrors.length > 0) {
72
+ const currentError = pendingErrors.pop();
73
+ if (typeof currentError !== "object" || currentError === null) continue;
74
+ if (visitedErrors.has(currentError)) continue;
75
+ visitedErrors.add(currentError);
76
+ if (isModuleNotFoundError(currentError)) {
77
+ const [message] = currentError.message.split("\n");
78
+ if (message && MISSING_AJV_META_SCHEMA_MESSAGES.has(message)) {
79
+ const requireStack = Array.isArray(currentError.requireStack) ? currentError.requireStack : [];
80
+ for (const requirePath of requireStack) {
81
+ if (typeof requirePath !== "string") continue;
82
+ const cacheKey = requirePath.replaceAll("\\", "/").match(NPX_AJV_META_SCHEMA_REQUIRE_PATH_PATTERN)?.groups?.cacheKey;
83
+ if (cacheKey !== void 0) return {
84
+ cacheKey,
85
+ moduleNotFoundError: currentError
86
+ };
87
+ }
88
+ }
89
+ }
90
+ if (currentError instanceof Error && currentError.cause !== void 0) pendingErrors.push(currentError.cause);
91
+ }
92
+ return null;
93
+ };
94
+ const isNpmCacheCorruptionError = (error) => findNpmCacheCorruptionError(error) !== null;
95
+ const formatNpmCacheCorruptionError = (error) => {
96
+ const corruptionMatch = findNpmCacheCorruptionError(error);
97
+ if (!corruptionMatch) return messageFromUnknown(error);
98
+ return [
99
+ "The npx cache has an incomplete Ajv installation.",
100
+ "",
101
+ "Remove this cache entry, then run React Doctor again:",
102
+ ` npm cache npx rm ${corruptionMatch.cacheKey}`,
103
+ " npx react-doctor@latest",
104
+ "",
105
+ `Original error: ${corruptionMatch.moduleNotFoundError.message}`
106
+ ].join("\n");
107
+ };
108
+ //#endregion
64
109
  //#region src/cli/utils/is-expected-user-error.ts
65
110
  /**
66
111
  * Whether `error` is an expected, user-actionable failure — the user's project
@@ -69,7 +114,7 @@ const formatEnvironmentError = (error) => {
69
114
  * `handleUserError` (a plain message — no "Something went wrong", prefilled
70
115
  * issue, Discord link, or Sentry reference), since there is no bug to report.
71
116
  *
72
- * Four distinct shapes reach the CLI's catch blocks:
117
+ * Five distinct shapes reach the CLI's catch blocks:
73
118
  *
74
119
  * - **Project-discovery failures** (`NoReactDependencyError`,
75
120
  * `ProjectNotFoundError`, `PackageJsonNotFoundError`, `NotADirectoryError`,
@@ -89,12 +134,15 @@ const formatEnvironmentError = (error) => {
89
134
  * actionable message instead of crashing. See `is-environment-error.ts` for
90
135
  * why the set stays narrow (codes that usually mean our bug keep reaching
91
136
  * Sentry).
137
+ * - **npm cache corruption** (missing Ajv meta-schema files inside `_npx`) —
138
+ * an incomplete npx package installation that the user can remove and
139
+ * reinstall.
92
140
  *
93
141
  * This composes the existing core narrowers rather than introducing a new
94
142
  * error-shape helper (AGENTS.md): it encodes CLI-layer reporting policy, not
95
143
  * knowledge of the `ReactDoctorError` shape.
96
144
  */
97
- const isExpectedUserError = (error) => error instanceof CliInputError || isProjectDiscoveryError(error) || isEnvironmentError(error) || isReactDoctorError(error) && (error.reason._tag === "GitBaseBranchInvalid" || error.reason._tag === "GitBaseBranchMissing");
145
+ const isExpectedUserError = (error) => error instanceof CliInputError || isProjectDiscoveryError(error) || isEnvironmentError(error) || isNpmCacheCorruptionError(error) || isReactDoctorError(error) && (error.reason._tag === "GitBaseBranchInvalid" || error.reason._tag === "GitBaseBranchMissing");
98
146
  //#endregion
99
147
  //#region src/cli/utils/resolve-axiom-telemetry-options.ts
100
148
  /**
@@ -197,25 +245,7 @@ const shutdownTelemetry = async () => {
197
245
  return pendingShutdown;
198
246
  };
199
247
  //#endregion
200
- //#region src/cli/utils/active-run-trace.ts
201
- let activeRunTrace = null;
202
- const setActiveRunTrace = (trace) => {
203
- activeRunTrace = trace;
204
- };
205
- const getActiveRunTrace = () => activeRunTrace;
206
- let lastRunTraceId = null;
207
- const recordRunTraceId = (traceId) => {
208
- lastRunTraceId = traceId;
209
- };
210
- const getLastRunTraceId = () => lastRunTraceId;
211
- //#endregion
212
- //#region src/cli/utils/unref-stdin.ts
213
- const unrefStdin = () => {
214
- if (process.stdin.isTTY) return;
215
- process.stdin.unref?.();
216
- };
217
- //#endregion
218
- export { setActiveRunTrace as a, isExpectedUserError as c, recordRunTraceId as i, formatEnvironmentError as l, getActiveRunTrace as n, getTelemetryContext as o, getLastRunTraceId as r, shutdownTelemetry as s, unrefStdin as t, isEnvironmentError as u };
248
+ export { isNpmCacheCorruptionError as a, formatNpmCacheCorruptionError as i, shutdownTelemetry as n, formatEnvironmentError as o, isExpectedUserError as r, isEnvironmentError as s, getTelemetryContext as t };
219
249
 
220
- //# sourceMappingURL=unref-stdin-BxyvqsOy.js.map
221
- //# debugId=2bee1c15-4924-512e-8abd-f6f764de1963
250
+ //# sourceMappingURL=telemetry-runtime-BF83aBFL.js.map
251
+ //# debugId=f658c385-8ef1-58af-8f8f-a72607a16be2
@@ -0,0 +1,12 @@
1
+ //#region src/cli/utils/unref-stdin.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]="8f48a2ed-e79f-5438-90a2-8ef096b5fd64")}catch(e){}}();
4
+ const unrefStdin = () => {
5
+ if (process.stdin.isTTY) return;
6
+ process.stdin.unref?.();
7
+ };
8
+ //#endregion
9
+ export { unrefStdin as t };
10
+
11
+ //# sourceMappingURL=unref-stdin-SK3WfwxS.js.map
12
+ //# debugId=8f48a2ed-e79f-5438-90a2-8ef096b5fd64
@@ -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]="eed18eb6-7b08-5c0f-9206-65fc4aadea43")}catch(e){}}();
3
- import { g as VERSION, t as recordCount, w as METRIC } from "./record-metric-CFfsgcVs.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]="c82a3ace-63f2-583f-bb13-d8f22e52b61f")}catch(e){}}();
3
+ import { T as METRIC, _ as VERSION, t as recordCount } from "./record-metric-B6T_Ej33.js";
4
4
  //#region src/cli/commands/version.ts
5
5
  /**
6
6
  * oclif-style version line. 12-factor CLI Apps (#3, "What version am I
@@ -17,5 +17,5 @@ const versionAction = () => {
17
17
  //#endregion
18
18
  export { versionAction };
19
19
 
20
- //# sourceMappingURL=version-S-8vvWFi.js.map
21
- //# debugId=eed18eb6-7b08-5c0f-9206-65fc4aadea43
20
+ //# sourceMappingURL=version-C27gr4Qj.js.map
21
+ //# debugId=c82a3ace-63f2-583f-bb13-d8f22e52b61f
@@ -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]="fd7f3a9f-2a0e-58d7-b42a-f5ded8bef85d")}catch(e){}}();
3
- import { t as inspectAction } from "./inspect-g3_lzs3l.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]="2153fea8-7916-5747-9a20-62730c7c2d54")}catch(e){}}();
3
+ import { t as inspectAction } from "./inspect-Dq5krKWG.js";
4
4
  //#region src/cli/commands/why.ts
5
5
  /**
6
6
  * `why <file:line>` — explains why a rule fired (or why a suppression didn't
@@ -17,5 +17,5 @@ const whyAction = async (location, options) => {
17
17
  //#endregion
18
18
  export { whyAction };
19
19
 
20
- //# sourceMappingURL=why-DXfx0oL9.js.map
21
- //# debugId=fd7f3a9f-2a0e-58d7-b42a-f5ded8bef85d
20
+ //# sourceMappingURL=why-CYRkZ2VY.js.map
21
+ //# debugId=2153fea8-7916-5747-9a20-62730c7c2d54
@@ -0,0 +1,110 @@
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]="4354cbc4-7bd8-5c31-abea-8943b6196d6e")}catch(e){}}();
3
+ import { D as NANOSECONDS_PER_MILLISECOND, In as provideContext, Xn as void_, Yn as fail, c as setSentryProjectInfo, jn as makeSpan, o as buildSentryScope, r as toSpanAttributes, s as buildSentryProjectContext, zn as runSync } from "./record-metric-B6T_Ej33.js";
4
+ import { t as getTelemetryContext } from "./telemetry-runtime-BF83aBFL.js";
5
+ import { i as setActiveRunTrace, r as recordRunTraceId } from "./active-run-trace-dlIZeEvb.js";
6
+ //#region src/cli/utils/with-run-span.ts
7
+ /**
8
+ * Ends the run span without letting a telemetry failure change the run's
9
+ * outcome — the invariant every other emit site here already holds.
10
+ *
11
+ * The failure path is the reason this matters most: a throw from `end` there
12
+ * would replace the user's original scan error, so a project-config mistake
13
+ * could surface as an exporter stack trace, be reclassified as a crash, and get
14
+ * reported as one.
15
+ */
16
+ const endSpanSafely = (span, exit) => {
17
+ try {
18
+ span.end(BigInt(Date.now()) * NANOSECONDS_PER_MILLISECOND, exit);
19
+ } catch {}
20
+ };
21
+ /**
22
+ * Clears the module-level run-scoped telemetry state — the current scanned
23
+ * project and the active run trace. `inspect()` calls this at the start of every
24
+ * run and again after a clean one, so a prior or just-finished scan can't attach
25
+ * its project tags / trace to a later run or to a non-scan error (e.g.
26
+ * inspectAction's post-loop finalize/handoff steps). A thrown scan error skips
27
+ * the post-run reset, leaving the state for the command catch to attribute and
28
+ * link the crash. Concurrent batch members (`concurrentScan`) never touch this
29
+ * state — they neither write nor reset it. Safe to call when telemetry is off
30
+ * (the refs are read only when an event is built).
31
+ */
32
+ const resetSentryRunState = () => {
33
+ setSentryProjectInfo(null);
34
+ setActiveRunTrace(null);
35
+ };
36
+ /**
37
+ * Runs an inspect invocation inside a root span so each `react-doctor` run is a
38
+ * first-class trace with timing and the run snapshot as attributes. The span is
39
+ * handed to `run` so the scan's Effect spans parent under it and so the wide
40
+ * event can be stamped onto it once the outcome is known.
41
+ *
42
+ * A no-op pass-through when telemetry is off (`--no-telemetry`, tests, or an
43
+ * unconfigured build) — `run` receives `undefined` and no span is created, so
44
+ * there's no added exit latency.
45
+ *
46
+ * The span is created against the shared telemetry context rather than a
47
+ * freshly-provided layer, so it lives in the same tracer as the scan program's
48
+ * spans; see `telemetry-runtime.ts` for why that matters. It is started and
49
+ * ended manually because the body it wraps is a promise-returning imperative
50
+ * function, not an Effect — the same shape the Sentry inactive span had.
51
+ *
52
+ * While the span runs, its trace context is recorded as the active run trace so
53
+ * `reportErrorToSentry` can attach a crash thrown during the scan back to this
54
+ * trace (errors surface in the command catch, after the span has ended).
55
+ *
56
+ * A `concurrentScan` (one member of the CLI's multi-project pool) still gets its
57
+ * own root span, but skips recording the active run trace — the module-level
58
+ * handle has single-scan semantics, and overlapping writers would link a crash
59
+ * to an arbitrary sibling's trace.
60
+ */
61
+ const withRunSpan = async (run, options = {}) => {
62
+ const telemetryContext = getTelemetryContext();
63
+ if (telemetryContext === null) return run(void 0);
64
+ const { tags } = buildSentryScope();
65
+ const command = typeof tags.command === "string" ? tags.command : "inspect";
66
+ let rootSpan;
67
+ try {
68
+ rootSpan = runSync(makeSpan(`react-doctor ${command}`, { attributes: toSpanAttributes(tags) }).pipe(provideContext(telemetryContext)));
69
+ } catch {
70
+ return run(void 0);
71
+ }
72
+ recordRunTraceId(rootSpan.traceId);
73
+ if (options.concurrentScan !== true) setActiveRunTrace({
74
+ traceId: rootSpan.traceId,
75
+ spanId: rootSpan.spanId,
76
+ sampled: rootSpan.sampled
77
+ });
78
+ try {
79
+ const result = await run(rootSpan);
80
+ endSpanSafely(rootSpan, void_);
81
+ return result;
82
+ } catch (error) {
83
+ endSpanSafely(rootSpan, fail(options.mapErrorForSpan?.(error) ?? error));
84
+ throw error;
85
+ }
86
+ };
87
+ /**
88
+ * Records the scanned project (discovered in the `beforeLint` hook): remembers
89
+ * it for the lazy Sentry error-capture path (`buildSentryScope` folds it into
90
+ * exception events) and for per-emit metric attributes, and sets it on the run's
91
+ * root span so the trace carries the project shape too. Always cheap — the span
92
+ * attributes are skipped when `rootSpan` is absent (telemetry off), and storing
93
+ * the info is a plain assignment.
94
+ *
95
+ * A `concurrentScan` only sets the span attributes: the module-level project ref
96
+ * has single-scan semantics, and overlapping writers would stamp events and
97
+ * metrics with an arbitrary sibling's project. Wide events keep full attribution
98
+ * (they ride the span); per-emit metrics simply omit the project shape during a
99
+ * concurrent batch (absent, never wrong).
100
+ */
101
+ const recordSentryProjectContext = (projectInfo, rootSpan, options = {}) => {
102
+ if (options.concurrentScan !== true) setSentryProjectInfo(projectInfo);
103
+ if (rootSpan === void 0) return;
104
+ for (const [key, value] of Object.entries(toSpanAttributes(buildSentryProjectContext(projectInfo).tags))) rootSpan.attribute(key, value);
105
+ };
106
+ //#endregion
107
+ export { resetSentryRunState as n, withRunSpan as r, recordSentryProjectContext as t };
108
+
109
+ //# sourceMappingURL=with-run-span-CdsXSJgE.js.map
110
+ //# debugId=4354cbc4-7bd8-5c31-abea-8943b6196d6e
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-doctor",
3
- "version": "0.9.12-dev.ffc2d14",
3
+ "version": "0.9.13-dev.1239043",
4
4
  "description": "Your agent writes bad React. This catches it",
5
5
  "keywords": [
6
6
  "accessibility",
@@ -53,19 +53,22 @@
53
53
  "@astrojs/compiler": "^4.0.0",
54
54
  "@sentry/node": "^10.54.0",
55
55
  "agent-install": "0.0.5",
56
+ "bippy": "^0.6.1",
56
57
  "conf": "^15.1.0",
57
58
  "confbox": "^0.2.4",
58
59
  "eslint-plugin-react-hooks": "^7.1.1",
60
+ "is-unicode-supported": "^2.1.0",
59
61
  "jiti": "^2.7.0",
60
62
  "magicast": "^0.5.3",
61
- "oxc-parser": "^0.144.0",
63
+ "oxc-parser": "^0.148.0",
62
64
  "oxc-resolver": "^11.24.2",
63
- "oxlint": ">=1.78.0 <1.79.0",
65
+ "oxlint": ">=1.81.0 <1.82.0",
66
+ "playwright-core": "^1.62.1",
64
67
  "prompts": "^2.4.2",
65
68
  "typescript": ">=5.0.4 <6",
66
69
  "yaml": "^2.9.0",
67
70
  "yoga-layout": "~3.2.1",
68
- "oxlint-plugin-react-doctor": "0.9.12-dev.ffc2d14"
71
+ "oxlint-plugin-react-doctor": "0.9.13-dev.1239043"
69
72
  },
70
73
  "devDependencies": {
71
74
  "@types/babel__code-frame": "^7.27.0",
@@ -78,8 +81,8 @@
78
81
  "ora": "^9.4.0",
79
82
  "react": "19.2.5",
80
83
  "react-devtools-core": "^7.0.1",
81
- "@react-doctor/core": "0.9.12",
82
- "@react-doctor/api": "0.9.12"
84
+ "@react-doctor/api": "0.9.13",
85
+ "@react-doctor/core": "0.9.13"
83
86
  },
84
87
  "engines": {
85
88
  "node": "^20.19.0 || >=22.13.0"
@@ -1,53 +0,0 @@
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]="51399d27-1dd4-55db-9fa5-7f925bd3ec8f")}catch(e){}}();
3
- import { dn as resolveScanTarget, t as recordCount, w as METRIC, z as isNonInteractiveEnvironment } from "./record-metric-CFfsgcVs.js";
4
- import { i as warnDeprecatedDiff } from "./resolve-scope-CXS-5jLR.js";
5
- import { _ as resolveCliInspectOptions, i as validateModeFlags } from "./should-fail-scan-gate-C0Bo5Gqy.js";
6
- import { n as warnDeprecatedFailOn, r as runProjectMigrations, t as inspectAction } from "./inspect-g3_lzs3l.js";
7
- import path from "node:path";
8
- //#region src/cli/utils/should-use-tui.ts
9
- const shouldUseTui = (input) => {
10
- if (input.isNonInteractiveEnvironment || !input.stdinIsTty || !input.stdoutIsTty || !input.supportsRawMode || input.nodeMajorVersion < 22 || input.terminalName === "dumb") return false;
11
- const flags = input.flags;
12
- return !(flags.score === true || flags.json === true || flags.jsonCompact === true || flags.jsonOut !== void 0 || flags.staged === true || flags.changedFilesFrom !== void 0);
13
- };
14
- //#endregion
15
- //#region src/cli/commands/scan.ts
16
- const runScanCommand = async (input) => {
17
- if (input.flags.cache === false) process.env.REACT_DOCTOR_NO_CACHE = "1";
18
- const nodeMajorVersion = Number(process.versions.node.split(".")[0]);
19
- if (!shouldUseTui({
20
- flags: input.flags,
21
- isNonInteractiveEnvironment: isNonInteractiveEnvironment(),
22
- nodeMajorVersion,
23
- stdinIsTty: process.stdin.isTTY === true,
24
- stdoutIsTty: process.stdout.isTTY === true,
25
- supportsRawMode: typeof process.stdin.setRawMode === "function",
26
- terminalName: process.env.TERM
27
- })) {
28
- await inspectAction(input.directory, input.flags, input.invocationCommand);
29
- return;
30
- }
31
- await runProjectMigrations(path.resolve(input.directory));
32
- const scanTarget = await resolveScanTarget(input.directory, { allowAmbiguous: true });
33
- validateModeFlags(input.flags);
34
- warnDeprecatedFailOn(input.flags, scanTarget.userConfig);
35
- warnDeprecatedDiff(input.flags, scanTarget.userConfig);
36
- recordCount(METRIC.cliInvoked, 1, { command: input.invocationCommand });
37
- const { runScanApp } = await import("./run-scan-app-CDDu7P32.js");
38
- const { shouldFail } = await runScanApp({
39
- directory: input.directory,
40
- scanTarget,
41
- options: resolveCliInspectOptions(input.flags, null),
42
- projectFlag: input.flags.project,
43
- skipPrompts: input.flags.yes ?? false,
44
- blocking: input.flags.blocking ?? input.flags.failOn,
45
- flags: input.flags
46
- });
47
- if (shouldFail) process.exitCode = 1;
48
- };
49
- //#endregion
50
- export { runScanCommand };
51
-
52
- //# sourceMappingURL=scan-4I1mvxBO.js.map
53
- //# debugId=51399d27-1dd4-55db-9fa5-7f925bd3ec8f