react-doctor 0.9.7-dev.13138a4 → 0.9.7
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.
- package/dist/cli.js +10780 -96
- package/dist/{devtools-DPRVRu3S.js → devtools-BQATi33M.js} +3 -3
- package/dist/{record-metric-CtlQma6P.js → exit-gracefully-BJRsqdaK.js} +11954 -6251
- package/dist/index.d.ts +4082 -4704
- package/dist/index.js +4386 -6069
- package/dist/lsp.js +4510 -6181
- package/dist/{run-scan-app-Drlqxtzl.js → run-scan-app-DawD5rhU.js} +29 -36
- package/package.json +3 -2
- package/dist/build-section-divider-QykRnrlR.js +0 -312
- package/dist/ci-BvGqcDKc.js +0 -7501
- package/dist/design-SGX69YQq.js +0 -14
- package/dist/exit-gracefully-CddufLS0.js +0 -47
- package/dist/inspect-DBJtHure.js +0 -1443
- package/dist/install-doctor-script-DxrteuEw.js +0 -3251
- package/dist/install-ffJ108LC.js +0 -32
- package/dist/install-github-workflow-BP6t3YBA.js +0 -267
- package/dist/install-react-doctor-BhfvyNZB.js +0 -1154
- package/dist/print-branded-header-Cw5AlicW.js +0 -22
- package/dist/read-object-file-ITGVjFKp.js +0 -24
- package/dist/report-error-DUoFKR7d.js +0 -158
- package/dist/report-workflow-result-D3GCowCN.js +0 -279
- package/dist/resolve-scope-gy72JH3h.js +0 -339
- package/dist/rules-6-d19pN1.js +0 -581
- package/dist/scan-C3Io41Dq.js +0 -53
- package/dist/should-fail-scan-gate-DImKUuu-.js +0 -3075
- package/dist/should-skip-prompts-CbIIaU8b.js +0 -10
- package/dist/unref-stdin-B5RnVeLa.js +0 -120
- package/dist/version-BFKBxkKV.js +0 -21
- package/dist/why-DO4CTn-R.js +0 -21
|
@@ -1,13 +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]="
|
|
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]="adb1b48c-bc53-5c98-982a-aeab790b4798")}catch(e){}}();
|
|
3
3
|
import { n as __require, r as __toESM, t as __commonJSMin } from "./chunk-q7NCDQ7-.js";
|
|
4
|
-
import { Et as
|
|
5
|
-
import { n as registerActiveTuiRenderer, t as exitGracefully } from "./exit-gracefully-CddufLS0.js";
|
|
6
|
-
import { L as cliLogger, d as stripAnsi, f as isUnicodeSupported, l as isFullWidth$1, n as findNearestPackageDirectory, p as chalk, u as isWide$1 } from "./install-doctor-script-DxrteuEw.js";
|
|
7
|
-
import { i as isReactDoctorWorkflowInstalled } from "./install-github-workflow-BP6t3YBA.js";
|
|
8
|
-
import { r as resolveScope, s as resolveBlockingLevel, t as finalizeScope, u as toForwardSlashes } from "./resolve-scope-gy72JH3h.js";
|
|
9
|
-
import { A as resolveMergeBaseRef, B as buildEmptyReportMessage, C as copyToClipboard, D as deduplicateProjectScans, E as collectProjectSourceFileCounts, F as printDiagnosticsDump, G as formatLearnMoreLine, H as buildRulePriorityMap, I as formatDiagnosticSite, L as hasIncompleteScoreAnalysis, M as pluralize, N as formatElapsedTime, O as createInvocationInspect, P as printFooter, R as filterDiagnosticsByCategories, S as CLI_AGENT_BINARIES, T as buildHandoffPayload, U as buildSortedRuleGroups, V as hasLintHardFailure, W as formatFixRecipeLine, _ as resolveCliInspectOptions, a as projectManifestChanged, b as setUpGitHubActions, c as selectProjects, d as resolveProjectChangedLineRanges, f as resolveProjectDiffIncludePaths, g as resolveWorkspaceDeadCodeOwner, h as retryMissingProjectScores, l as buildCodeFrame, n as formatSkippedProjectsMessage, o as partitionProjectScanOutcomes, p as resolveProjectSourceFilePaths, r as validateIncludeUntrackedScope, s as discoverWorkspacePackages, t as shouldFailScanGate, u as resolveProjectScan, v as filterScansForSurface, w as launchCliAgent, x as detectLaunchableAgents, y as countUniqueScannedFiles, z as buildNoScoreMessage } from "./should-fail-scan-gate-DImKUuu-.js";
|
|
10
|
-
import { a as recordGate, f as ONBOARDING_EVENT, n as openUrl, r as isGatePending } from "./report-workflow-result-D3GCowCN.js";
|
|
4
|
+
import { $ as filterDiagnosticsByCategories, $n as GITHUB_ACTIONS_SETUP_URL, B as buildHandoffPayload, C as isGatePending, Cr as highlighter, Dt as recordDistribution, Et as recordCount, F as CLI_AGENT_BINARIES, Gr as sync, Gt as chalk, H as collectProjectSourceFileCounts, Ht as isWide$1, I as copyToClipboard, J as formatElapsedTime, Jr as succeed, Kr as void_, L as launchCliAgent, Ln as TUI_REPORT_COMPACT_MAX_ROWS, M as ONBOARDING_EVENT, Mn as METRIC, Mt as toForwardSlashes, Nr as remainingDeadlineBudgetMs, P as detectLaunchableAgents, Pr as resolveScanTarget, Pt as findNearestPackageDirectory, Q as formatDiagnosticSite, Qn as DIAGNOSTIC_CATEGORY_BUCKETS, Rn as TUI_REPORT_DETAIL_WIDTH_FRACTION, Rr as yieldToEventLoop, S as openUrl, T as recordGate, Tr as isPathInsideDirectory, U as deduplicateProjectScans, Un as isCiEnvironment, Ur as runPromise, Ut as stripAnsi, V as formatSkippedProjectsMessage, Vn as isGitHookEnvironment, Vt as isFullWidth$1, W as createInvocationInspect, Wt as isUnicodeSupported, Y as printFooter, Z as printDiagnosticsDump, _ as resolveCliInspectOptions, _n as cliLogger, _r as getCategoryImpact, _t as resolveBlockingLevel, a as shouldFailScanGate, at as buildRulePriorityMap, b as setUpGitHubActions, c as selectProjects, cr as buildRuleDocsUrl, ct as formatLearnMoreLine, d as resolveProjectChangedLineRanges, et as buildEmptyReportMessage, f as resolveProjectDiffIncludePaths, ft as finalizeScope, g as resolveWorkspaceDeadCodeOwner, gr as getBaselineDiffPlan, h as retryMissingProjectScores, it as hasLintHardFailure, jn as CI_TRUST_COMPANIES, jr as mapWithConcurrency, l as buildCodeFrame, lr as calculateScore, mt as resolveScope, n as registerActiveTuiRenderer, nr as Progress, nt as buildNoScoreMessage, o as projectManifestChanged, ot as buildSortedRuleGroups, p as resolveProjectSourceFilePaths, q as pluralize, r as validateIncludeUntrackedScope, rr as Reporter, rt as resolveMergeBaseRef, s as discoverWorkspacePackages, st as formatFixRecipeLine, t as exitGracefully, tt as hasIncompleteScoreAnalysis, u as resolveProjectScan, v as filterScansForSurface, vr as getChangedLineRanges, xr as hasPublishedFixRecipe, xt as isReactDoctorWorkflowInstalled, y as countUniqueScannedFiles, yr as getDiffInfo } from "./exit-gracefully-BJRsqdaK.js";
|
|
11
5
|
import { EventEmitter } from "node:events";
|
|
12
6
|
import { execFileSync } from "node:child_process";
|
|
13
7
|
import path from "node:path";
|
|
@@ -20448,7 +20442,7 @@ if (process$1.env["DEV"] === "true") {
|
|
|
20448
20442
|
import.meta.resolve("react-devtools-core");
|
|
20449
20443
|
isDevtoolsInstalled = true;
|
|
20450
20444
|
} catch {}
|
|
20451
|
-
if (isDevtoolsInstalled) await import("./devtools-
|
|
20445
|
+
if (isDevtoolsInstalled) await import("./devtools-BQATi33M.js");
|
|
20452
20446
|
}
|
|
20453
20447
|
const diff = (before, after) => {
|
|
20454
20448
|
if (before === after) return;
|
|
@@ -27726,7 +27720,7 @@ const ScanApp = ({ store, displayMode = "report", launchableAgents, onHandoff, c
|
|
|
27726
27720
|
onQuit?.();
|
|
27727
27721
|
exit();
|
|
27728
27722
|
};
|
|
27729
|
-
if (displayMode === "report" && snapshot.phase === "summary") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Summary, {
|
|
27723
|
+
if (displayMode === "report" && snapshot.phase === "summary" && snapshot.summary) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Summary, {
|
|
27730
27724
|
summary: snapshot.summary,
|
|
27731
27725
|
launchableAgents,
|
|
27732
27726
|
onHandoff,
|
|
@@ -27735,7 +27729,7 @@ const ScanApp = ({ store, displayMode = "report", launchableAgents, onHandoff, c
|
|
|
27735
27729
|
onExit: exit,
|
|
27736
27730
|
onQuit: handleQuit
|
|
27737
27731
|
});
|
|
27738
|
-
if (displayMode === "report" && snapshot.phase === "report") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Report, {
|
|
27732
|
+
if (displayMode === "report" && snapshot.phase === "report" && snapshot.report) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Report, {
|
|
27739
27733
|
report: snapshot.report,
|
|
27740
27734
|
launchableAgents,
|
|
27741
27735
|
onHandoff,
|
|
@@ -27771,7 +27765,9 @@ const progressLayerForStore = (store, options = {}) => Progress.layerOra(progres
|
|
|
27771
27765
|
const INITIAL_SNAPSHOT = {
|
|
27772
27766
|
phase: "scanning",
|
|
27773
27767
|
liveDiagnostics: [],
|
|
27774
|
-
progress: null
|
|
27768
|
+
progress: null,
|
|
27769
|
+
report: null,
|
|
27770
|
+
summary: null
|
|
27775
27771
|
};
|
|
27776
27772
|
const createScanStore = () => {
|
|
27777
27773
|
let snapshot = INITIAL_SNAPSHOT;
|
|
@@ -27780,7 +27776,7 @@ const createScanStore = () => {
|
|
|
27780
27776
|
let progressTimer = null;
|
|
27781
27777
|
const commit = (next) => {
|
|
27782
27778
|
snapshot = next;
|
|
27783
|
-
for (const listener of
|
|
27779
|
+
for (const listener of listeners) listener();
|
|
27784
27780
|
};
|
|
27785
27781
|
const cancelPendingProgress = () => {
|
|
27786
27782
|
pendingProgress = null;
|
|
@@ -27825,19 +27821,17 @@ const createScanStore = () => {
|
|
|
27825
27821
|
setReport: (report) => {
|
|
27826
27822
|
cancelPendingProgress();
|
|
27827
27823
|
commit({
|
|
27828
|
-
|
|
27829
|
-
|
|
27830
|
-
|
|
27831
|
-
report
|
|
27824
|
+
...snapshot,
|
|
27825
|
+
report,
|
|
27826
|
+
phase: "report"
|
|
27832
27827
|
});
|
|
27833
27828
|
},
|
|
27834
27829
|
setSummary: (summary) => {
|
|
27835
27830
|
cancelPendingProgress();
|
|
27836
27831
|
commit({
|
|
27837
|
-
|
|
27838
|
-
|
|
27839
|
-
|
|
27840
|
-
summary
|
|
27832
|
+
...snapshot,
|
|
27833
|
+
summary,
|
|
27834
|
+
phase: "summary"
|
|
27841
27835
|
});
|
|
27842
27836
|
}
|
|
27843
27837
|
};
|
|
@@ -28164,16 +28158,14 @@ const runMultiProjectScan = async (rootScanTarget, directories, input, scopePlan
|
|
|
28164
28158
|
context.store.setProgress(`Scanning ${projectCount} projects…`);
|
|
28165
28159
|
await yieldToEventLoop();
|
|
28166
28160
|
const precomputedSourceFileCounts = scopePlan.scope === "full" ? await collectProjectSourceFileCounts(rootDirectory, projectScans.map(({ projectScan }) => projectScan.directory)) : null;
|
|
28167
|
-
const
|
|
28161
|
+
const scanOutcomes = await mapWithConcurrency(projectScans, 4, async ({ projectScan, scopeOptions }) => {
|
|
28168
28162
|
if (input.options?.deadlineEpochMs !== void 0 && remainingDeadlineBudgetMs(input.options.deadlineEpochMs) === 0) {
|
|
28169
28163
|
finishedCount += 1;
|
|
28170
28164
|
context.store.setProgress(`Scanning ${projectCount} projects… (${finishedCount}/${projectCount})`);
|
|
28171
28165
|
return {
|
|
28172
28166
|
status: "skipped",
|
|
28173
|
-
|
|
28174
|
-
|
|
28175
|
-
reason: "max-duration"
|
|
28176
|
-
}
|
|
28167
|
+
directory: projectScan.directory,
|
|
28168
|
+
reason: "max-duration"
|
|
28177
28169
|
};
|
|
28178
28170
|
}
|
|
28179
28171
|
const projectLabel = path.relative(rootDirectory, projectScan.directory) || path.basename(rootDirectory);
|
|
@@ -28204,18 +28196,19 @@ const runMultiProjectScan = async (rootScanTarget, directories, input, scopePlan
|
|
|
28204
28196
|
await yieldToEventLoop();
|
|
28205
28197
|
return {
|
|
28206
28198
|
status: "completed",
|
|
28207
|
-
|
|
28208
|
-
|
|
28209
|
-
|
|
28210
|
-
config: projectScan.config
|
|
28211
|
-
}
|
|
28199
|
+
directory: projectScan.directory,
|
|
28200
|
+
result,
|
|
28201
|
+
config: projectScan.config
|
|
28212
28202
|
};
|
|
28213
|
-
})
|
|
28214
|
-
const results = await retryMissingProjectScores(
|
|
28203
|
+
});
|
|
28204
|
+
const results = await retryMissingProjectScores(scanOutcomes.filter((scanOutcome) => scanOutcome.status === "completed").map((completedScan) => ({
|
|
28215
28205
|
...completedScan,
|
|
28216
28206
|
isScoreDisabled: input.options?.noScore ?? completedScan.config?.noScore ?? false
|
|
28217
28207
|
})));
|
|
28218
|
-
const skippedProjects =
|
|
28208
|
+
const skippedProjects = scanOutcomes.filter((scanOutcome) => scanOutcome.status === "skipped").map(({ directory, reason }) => ({
|
|
28209
|
+
directory,
|
|
28210
|
+
reason
|
|
28211
|
+
})).sort((left, right) => left.directory.localeCompare(right.directory));
|
|
28219
28212
|
if (skippedProjects.length > 0) recordCount(METRIC.scanProjectSkipped, skippedProjects.length, { reason: "max-duration" });
|
|
28220
28213
|
const projectEntries = results.map(({ directory, result, config }) => {
|
|
28221
28214
|
const reportSelection = selectReportDiagnostics({
|
|
@@ -28314,5 +28307,5 @@ const runScanApp = async (input) => {
|
|
|
28314
28307
|
//#endregion
|
|
28315
28308
|
export { runScanApp };
|
|
28316
28309
|
|
|
28317
|
-
//# sourceMappingURL=run-scan-app-
|
|
28318
|
-
//# debugId=
|
|
28310
|
+
//# sourceMappingURL=run-scan-app-DawD5rhU.js.map
|
|
28311
|
+
//# debugId=adb1b48c-bc53-5c98-982a-aeab790b4798
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-doctor",
|
|
3
|
-
"version": "0.9.7
|
|
3
|
+
"version": "0.9.7",
|
|
4
4
|
"description": "Your agent writes bad React. This catches it",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"accessibility",
|
|
@@ -70,12 +70,13 @@
|
|
|
70
70
|
"yaml": "^2.9.0",
|
|
71
71
|
"yoga-layout": "~3.2.1",
|
|
72
72
|
"deslop-js": "0.9.7",
|
|
73
|
-
"oxlint-plugin-react-doctor": "0.9.7
|
|
73
|
+
"oxlint-plugin-react-doctor": "0.9.7"
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
76
|
"@types/babel__code-frame": "^7.27.0",
|
|
77
77
|
"@types/prompts": "^2.4.9",
|
|
78
78
|
"@types/react": "^19.2.14",
|
|
79
|
+
"@xterm/headless": "^6.0.0",
|
|
79
80
|
"commander": "^14.0.3",
|
|
80
81
|
"ink": "^7.1.0",
|
|
81
82
|
"ink-spinner": "^5.0.0",
|
|
@@ -1,312 +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]="8e93a83f-d2d8-5db5-9431-c9699a4d2a7d")}catch(e){}}();
|
|
3
|
-
import { D as SENTRY_FLUSH_TIMEOUT_MS, I as isDebugFlagEnabled, On as scrubSensitivePaths, U as CANONICAL_GITHUB_URL, X as DOCS_URL, en as highlighter, i as buildSentryScope, on as isPlainObject, yn as redactSensitiveText } from "./record-metric-CtlQma6P.js";
|
|
4
|
-
import { o as isExpectedUserError } from "./unref-stdin-B5RnVeLa.js";
|
|
5
|
-
import * as Sentry from "@sentry/node";
|
|
6
|
-
//#region src/cli/utils/is-env-flag-enabled.ts
|
|
7
|
-
const isEnvFlagEnabled = (value) => value === "1" || value?.toLowerCase() === "true";
|
|
8
|
-
//#endregion
|
|
9
|
-
//#region src/cli/utils/anonymize-text.ts
|
|
10
|
-
/**
|
|
11
|
-
* Free-text fields can carry both a home-directory path (the OS username) and a
|
|
12
|
-
* secret/email echoed from user code, so run both scrubbers: strip the username
|
|
13
|
-
* from paths, then mask any known credential/PII shape. Shared by the Sentry
|
|
14
|
-
* event scrubber ({@link scrubSentryEvent}) and metric scrubber
|
|
15
|
-
* ({@link scrubSentryMetric}).
|
|
16
|
-
*/
|
|
17
|
-
const anonymizeText = (text) => redactSensitiveText(scrubSensitivePaths(text));
|
|
18
|
-
/**
|
|
19
|
-
* Recursively rewrites every string within an arbitrary value (object / array /
|
|
20
|
-
* primitive) through {@link anonymizeText}, mutating in place. Used to sweep the
|
|
21
|
-
* unstructured corners of a Sentry payload (event contexts/extra/tags, breadcrumb
|
|
22
|
-
* data, span attributes, metric attributes) where a path or secret could hide.
|
|
23
|
-
*/
|
|
24
|
-
const anonymizeInPlace = (value) => {
|
|
25
|
-
if (Array.isArray(value)) {
|
|
26
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
27
|
-
const item = value[index];
|
|
28
|
-
if (typeof item === "string") value[index] = anonymizeText(item);
|
|
29
|
-
else anonymizeInPlace(item);
|
|
30
|
-
}
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
if (!isPlainObject(value)) return;
|
|
34
|
-
for (const key of Object.keys(value)) {
|
|
35
|
-
const inner = value[key];
|
|
36
|
-
if (typeof inner === "string") value[key] = anonymizeText(inner);
|
|
37
|
-
else anonymizeInPlace(inner);
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
//#endregion
|
|
41
|
-
//#region src/cli/utils/scrub-sentry-event.ts
|
|
42
|
-
/**
|
|
43
|
-
* Anonymizes a Sentry event (error or transaction) before it leaves the
|
|
44
|
-
* machine. Strips identity the SDK attaches automatically — the IP-bearing
|
|
45
|
-
* `user`, the `server_name`, and the device `name` (all hostnames) — drops
|
|
46
|
-
* captured local variables (unbounded, un-anonymizable user data), and scrubs
|
|
47
|
-
* home-directory paths + known secrets/emails from every remaining string:
|
|
48
|
-
* messages, stack frames, breadcrumbs, contexts/extra/tags, and span
|
|
49
|
-
* attributes (e.g. the `inspect.directory` path on the bridged `runInspect`
|
|
50
|
-
* span).
|
|
51
|
-
*
|
|
52
|
-
* Wired into both `beforeSend` and `beforeSendTransaction`. If scrubbing ever
|
|
53
|
-
* throws, the event is dropped (`null`) rather than risk sending un-anonymized
|
|
54
|
-
* data — telemetry is best-effort, privacy is not.
|
|
55
|
-
*/
|
|
56
|
-
const scrubSentryEvent = (event) => {
|
|
57
|
-
try {
|
|
58
|
-
delete event.server_name;
|
|
59
|
-
delete event.user;
|
|
60
|
-
const device = event.contexts?.device;
|
|
61
|
-
if (device) delete device.name;
|
|
62
|
-
if (event.contexts) anonymizeInPlace(event.contexts);
|
|
63
|
-
if (event.extra) anonymizeInPlace(event.extra);
|
|
64
|
-
if (event.tags) anonymizeInPlace(event.tags);
|
|
65
|
-
if (typeof event.message === "string") event.message = anonymizeText(event.message);
|
|
66
|
-
for (const breadcrumb of event.breadcrumbs ?? []) {
|
|
67
|
-
if (typeof breadcrumb.message === "string") breadcrumb.message = anonymizeText(breadcrumb.message);
|
|
68
|
-
if (breadcrumb.data) anonymizeInPlace(breadcrumb.data);
|
|
69
|
-
}
|
|
70
|
-
for (const exception of event.exception?.values ?? []) {
|
|
71
|
-
if (typeof exception.value === "string") exception.value = anonymizeText(exception.value);
|
|
72
|
-
for (const frame of exception.stacktrace?.frames ?? []) {
|
|
73
|
-
delete frame.vars;
|
|
74
|
-
if (typeof frame.filename === "string") frame.filename = scrubSensitivePaths(frame.filename);
|
|
75
|
-
if (typeof frame.abs_path === "string") frame.abs_path = scrubSensitivePaths(frame.abs_path);
|
|
76
|
-
if (typeof frame.module === "string") frame.module = scrubSensitivePaths(frame.module);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
for (const span of event.spans ?? []) {
|
|
80
|
-
if (typeof span.description === "string") span.description = anonymizeText(span.description);
|
|
81
|
-
if (span.data) anonymizeInPlace(span.data);
|
|
82
|
-
}
|
|
83
|
-
return event;
|
|
84
|
-
} catch {
|
|
85
|
-
return null;
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
//#endregion
|
|
89
|
-
//#region src/cli/utils/scrub-sentry-metric.ts
|
|
90
|
-
/**
|
|
91
|
-
* Anonymizes a Sentry metric before it leaves the machine, mirroring
|
|
92
|
-
* {@link scrubSentryEvent}. Drops the `server.address` default attribute (the
|
|
93
|
-
* hostname) and scrubs home-directory paths + known secrets/emails from every
|
|
94
|
-
* remaining attribute value (metric names are our own constants, so they're
|
|
95
|
-
* left intact to avoid splitting a series). Returns `null` on any failure so an
|
|
96
|
-
* un-anonymized metric is never sent. Wired into `beforeSendMetric`.
|
|
97
|
-
*/
|
|
98
|
-
const scrubSentryMetric = (metric) => {
|
|
99
|
-
try {
|
|
100
|
-
if (metric.attributes) {
|
|
101
|
-
delete metric.attributes["server.address"];
|
|
102
|
-
anonymizeInPlace(metric.attributes);
|
|
103
|
-
}
|
|
104
|
-
return metric;
|
|
105
|
-
} catch {
|
|
106
|
-
return null;
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
//#endregion
|
|
110
|
-
//#region src/cli/utils/sentry-config.ts
|
|
111
|
-
/**
|
|
112
|
-
* Shared Sentry configuration resolution — release, environment, and tracing
|
|
113
|
-
* sample rate — derived from `VERSION` and the standard `SENTRY_*` env
|
|
114
|
-
* overrides. Lives apart from `instrument.ts` (the CLI's run-context-aware
|
|
115
|
-
* init) so both the CLI and the editor language server can resolve identical
|
|
116
|
-
* release/environment values without the LSP pulling in CLI run-context code.
|
|
117
|
-
*/
|
|
118
|
-
/**
|
|
119
|
-
* A version is a "dev" build when it's the unbuilt placeholder (`0.0.0`) or
|
|
120
|
-
* carries a prerelease suffix (e.g. the `-dev.<sha>` snapshots published from
|
|
121
|
-
* CI). Everything else is a real, tagged release.
|
|
122
|
-
*/
|
|
123
|
-
const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
|
|
124
|
-
/**
|
|
125
|
-
* Sentry release identifier. `react-doctor@<version>` keeps it unique within
|
|
126
|
-
* the org and — crucially — matches the value `scripts/sentry-sourcemaps.mjs`
|
|
127
|
-
* uploads source-map artifacts under, so stack frames symbolicate. Honors the
|
|
128
|
-
* standard `SENTRY_RELEASE` override.
|
|
129
|
-
*/
|
|
130
|
-
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.9.7-dev.13138a4`;
|
|
131
|
-
/**
|
|
132
|
-
* Deployment environment shown in Sentry's environment filter. Defaults to
|
|
133
|
-
* `production` for tagged releases and `development` for dev/unbuilt versions,
|
|
134
|
-
* overridable via the standard `SENTRY_ENVIRONMENT` env var.
|
|
135
|
-
*/
|
|
136
|
-
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.9.7-dev.13138a4") ? "development" : "production");
|
|
137
|
-
/**
|
|
138
|
-
* Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
|
|
139
|
-
* (set to `0` to disable tracing) and falls back to
|
|
140
|
-
* {@link SENTRY_DEFAULT_TRACES_SAMPLE_RATE}. Invalid / out-of-range values fall
|
|
141
|
-
* back to the default rather than silently disabling tracing.
|
|
142
|
-
*/
|
|
143
|
-
const resolveTracesSampleRate = () => {
|
|
144
|
-
const raw = process.env.SENTRY_TRACES_SAMPLE_RATE;
|
|
145
|
-
if (raw === void 0 || raw.trim() === "") return 1;
|
|
146
|
-
const parsed = Number(raw);
|
|
147
|
-
if (Number.isNaN(parsed) || parsed < 0 || parsed > 1) return 1;
|
|
148
|
-
return parsed;
|
|
149
|
-
};
|
|
150
|
-
//#endregion
|
|
151
|
-
//#region src/instrument.ts
|
|
152
|
-
let isInitialized = false;
|
|
153
|
-
let resolvedTracesSampleRate = 0;
|
|
154
|
-
const shouldEnableSentry = () => {
|
|
155
|
-
if (process.argv.includes("--no-score") || process.argv.includes("--no-telemetry")) return false;
|
|
156
|
-
if (process.env.VITEST || process.env.NODE_ENV === "test") return false;
|
|
157
|
-
return true;
|
|
158
|
-
};
|
|
159
|
-
/**
|
|
160
|
-
* Whether performance traces will actually be recorded — Sentry is live and the
|
|
161
|
-
* resolved sample rate is above zero. Used to gate the per-run root span and
|
|
162
|
-
* the Effect→Sentry tracer bridge so they're true no-ops when tracing is off.
|
|
163
|
-
*/
|
|
164
|
-
const isSentryTracingEnabled = () => Sentry.isInitialized() && resolvedTracesSampleRate > 0;
|
|
165
|
-
/**
|
|
166
|
-
* Flushes queued Sentry events (errors + transactions) before the CLI exits, so
|
|
167
|
-
* the success-path transaction is delivered. A no-op when Sentry was never
|
|
168
|
-
* initialized, and it swallows transport failures so telemetry can never mask
|
|
169
|
-
* the user's result.
|
|
170
|
-
*/
|
|
171
|
-
const flushSentry = async () => {
|
|
172
|
-
if (!Sentry.isInitialized()) return;
|
|
173
|
-
try {
|
|
174
|
-
await Sentry.flush(SENTRY_FLUSH_TIMEOUT_MS);
|
|
175
|
-
} catch {}
|
|
176
|
-
};
|
|
177
|
-
/**
|
|
178
|
-
* Initializes the Sentry Node SDK for CLI crash reporting and performance
|
|
179
|
-
* tracing. Invoked as the first statement of the CLI entry (`cli/index.ts`) so
|
|
180
|
-
* the SDK's global `uncaughtException` / `unhandledRejection` handlers and OTel
|
|
181
|
-
* auto-instrumentation are armed before any command runs.
|
|
182
|
-
*
|
|
183
|
-
* Exported as a function rather than a bare side-effecting import because the
|
|
184
|
-
* package declares `"sideEffects": false`, which lets the bundler tree-shake
|
|
185
|
-
* side-effect-only modules. An explicit call keeps the initialization in the
|
|
186
|
-
* published `dist/cli.js`.
|
|
187
|
-
*
|
|
188
|
-
* Scoped to the CLI application only — the programmatic `@react-doctor/api`
|
|
189
|
-
* library never initializes Sentry, so importing `diagnose()` into a consumer
|
|
190
|
-
* app can't hijack their telemetry.
|
|
191
|
-
*
|
|
192
|
-
* Configuration is environment-overridable for self-hosting and tuning:
|
|
193
|
-
* `SENTRY_DSN`, `SENTRY_ENVIRONMENT`, `SENTRY_RELEASE`,
|
|
194
|
-
* `SENTRY_TRACES_SAMPLE_RATE` (`0` disables tracing), and `SENTRY_DEBUG`.
|
|
195
|
-
*/
|
|
196
|
-
const initializeSentry = () => {
|
|
197
|
-
if (isInitialized || !shouldEnableSentry()) return;
|
|
198
|
-
isInitialized = true;
|
|
199
|
-
resolvedTracesSampleRate = isDebugFlagEnabled() ? 1 : resolveTracesSampleRate();
|
|
200
|
-
const { tags, contexts } = buildSentryScope();
|
|
201
|
-
try {
|
|
202
|
-
Sentry.init({
|
|
203
|
-
dsn: process.env.SENTRY_DSN || "https://f253d570240a59b8dbd77b7a548ef133@o4510226365743104.ingest.us.sentry.io/4511487817809920",
|
|
204
|
-
release: resolveSentryRelease(),
|
|
205
|
-
environment: resolveSentryEnvironment(),
|
|
206
|
-
sendDefaultPii: false,
|
|
207
|
-
tracesSampleRate: resolvedTracesSampleRate,
|
|
208
|
-
debug: isEnvFlagEnabled(process.env.SENTRY_DEBUG),
|
|
209
|
-
initialScope: {
|
|
210
|
-
tags,
|
|
211
|
-
contexts
|
|
212
|
-
},
|
|
213
|
-
beforeSend: (event, hint) => isExpectedUserError(hint.originalException) ? null : scrubSentryEvent(event),
|
|
214
|
-
beforeSendTransaction: (event) => scrubSentryEvent(event),
|
|
215
|
-
beforeSendMetric: (metric) => scrubSentryMetric(metric)
|
|
216
|
-
});
|
|
217
|
-
} catch {
|
|
218
|
-
resolvedTracesSampleRate = 0;
|
|
219
|
-
}
|
|
220
|
-
};
|
|
221
|
-
//#endregion
|
|
222
|
-
//#region src/cli/utils/active-scan-abort-registry.ts
|
|
223
|
-
const activeScanAbortControllers = /* @__PURE__ */ new Set();
|
|
224
|
-
const activeScanAbortRegistry = {
|
|
225
|
-
register: (controller) => {
|
|
226
|
-
activeScanAbortControllers.add(controller);
|
|
227
|
-
return () => activeScanAbortControllers.delete(controller);
|
|
228
|
-
},
|
|
229
|
-
abortAll: () => {
|
|
230
|
-
const controllers = [...activeScanAbortControllers];
|
|
231
|
-
activeScanAbortControllers.clear();
|
|
232
|
-
for (const controller of controllers) controller.abort();
|
|
233
|
-
}
|
|
234
|
-
};
|
|
235
|
-
//#endregion
|
|
236
|
-
//#region src/cli/utils/resolve-measure-width.ts
|
|
237
|
-
const resolveClampedWidth = (input) => {
|
|
238
|
-
const terminalColumns = process.stdout.columns;
|
|
239
|
-
if (!terminalColumns || terminalColumns <= 0) return input.fullWidth;
|
|
240
|
-
const availableColumns = terminalColumns - input.reservedColumns;
|
|
241
|
-
return Math.max(input.minWidth, Math.min(input.fullWidth, availableColumns));
|
|
242
|
-
};
|
|
243
|
-
const resolveMeasureWidth = (reservedColumns = 0) => resolveClampedWidth({
|
|
244
|
-
reservedColumns,
|
|
245
|
-
fullWidth: 60,
|
|
246
|
-
minWidth: 24
|
|
247
|
-
});
|
|
248
|
-
//#endregion
|
|
249
|
-
//#region src/cli/utils/wrap-indented-text.ts
|
|
250
|
-
const wrapLine = (lineText, width, breakLongWords) => {
|
|
251
|
-
if (lineText.length <= width) return [lineText];
|
|
252
|
-
const wrappedLines = [];
|
|
253
|
-
let remainingText = lineText.trim();
|
|
254
|
-
while (remainingText.length > width) {
|
|
255
|
-
const candidateText = remainingText.slice(0, width);
|
|
256
|
-
const breakIndex = candidateText.lastIndexOf(" ");
|
|
257
|
-
if (breakIndex <= 0) {
|
|
258
|
-
if (breakLongWords) {
|
|
259
|
-
wrappedLines.push(candidateText);
|
|
260
|
-
remainingText = remainingText.slice(width).trimStart();
|
|
261
|
-
continue;
|
|
262
|
-
}
|
|
263
|
-
const nextSpace = remainingText.indexOf(" ");
|
|
264
|
-
if (nextSpace === -1) break;
|
|
265
|
-
wrappedLines.push(remainingText.slice(0, nextSpace));
|
|
266
|
-
remainingText = remainingText.slice(nextSpace + 1).trimStart();
|
|
267
|
-
continue;
|
|
268
|
-
}
|
|
269
|
-
wrappedLines.push(remainingText.slice(0, breakIndex));
|
|
270
|
-
remainingText = remainingText.slice(breakIndex + 1).trimStart();
|
|
271
|
-
}
|
|
272
|
-
if (remainingText.length > 0) wrappedLines.push(remainingText);
|
|
273
|
-
return wrappedLines;
|
|
274
|
-
};
|
|
275
|
-
/**
|
|
276
|
-
* Greedy word-wrap to a character width, returning one string per line
|
|
277
|
-
* (no indentation applied). Preserves existing `\n` boundaries.
|
|
278
|
-
*/
|
|
279
|
-
const wrapTextToWidth = (text, width, options = {}) => {
|
|
280
|
-
if (width <= 0) return text.split("\n");
|
|
281
|
-
const breakLongWords = options.breakLongWords ?? true;
|
|
282
|
-
return text.split("\n").flatMap((lineText) => wrapLine(lineText, width, breakLongWords));
|
|
283
|
-
};
|
|
284
|
-
//#endregion
|
|
285
|
-
//#region src/cli/utils/build-footer-description-lines.ts
|
|
286
|
-
const FOOTER_DESCRIPTION_INDENT = " ";
|
|
287
|
-
const buildFooterDescriptionLines = (description) => {
|
|
288
|
-
return wrapTextToWidth(description, resolveMeasureWidth(2)).map((line) => highlighter.dim(`${FOOTER_DESCRIPTION_INDENT}${line}`));
|
|
289
|
-
};
|
|
290
|
-
//#endregion
|
|
291
|
-
//#region src/cli/utils/build-footer-link-lines.ts
|
|
292
|
-
const buildFooterLinkLines = ({ shareUrl }) => [
|
|
293
|
-
...shareUrl === null ? [] : [
|
|
294
|
-
` ${highlighter.bold("Share:")} ${highlighter.info(shareUrl)}`,
|
|
295
|
-
...buildFooterDescriptionLines("Tell others how you did on socials"),
|
|
296
|
-
""
|
|
297
|
-
],
|
|
298
|
-
` ${highlighter.bold("Docs:")} ${highlighter.info(DOCS_URL)}`,
|
|
299
|
-
...buildFooterDescriptionLines("Learn more about fixing issues, setting up CI/CD, and configuring rules with a config file"),
|
|
300
|
-
"",
|
|
301
|
-
` ${highlighter.bold("GitHub:")} ${highlighter.info(CANONICAL_GITHUB_URL)}`,
|
|
302
|
-
...buildFooterDescriptionLines("Report issues and star the repository!")
|
|
303
|
-
];
|
|
304
|
-
//#endregion
|
|
305
|
-
//#region src/cli/utils/build-section-divider.ts
|
|
306
|
-
const DIVIDER_INDENT = " ";
|
|
307
|
-
const buildSectionDivider = () => highlighter.dim(`${DIVIDER_INDENT}${"─".repeat(resolveMeasureWidth(2))}`);
|
|
308
|
-
//#endregion
|
|
309
|
-
export { initializeSentry as a, flushSentry as i, buildFooterLinkLines as n, isSentryTracingEnabled as o, activeScanAbortRegistry as r, buildSectionDivider as t };
|
|
310
|
-
|
|
311
|
-
//# sourceMappingURL=build-section-divider-QykRnrlR.js.map
|
|
312
|
-
//# debugId=8e93a83f-d2d8-5db5-9431-c9699a4d2a7d
|