react-doctor 0.9.7 → 0.9.8-dev.7f028ea
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/build-section-divider-DKzi1sn2.js +312 -0
- package/dist/ci-BKAwxTDf.js +7501 -0
- package/dist/cli.js +96 -10780
- package/dist/design-ROS4n3k3.js +14 -0
- package/dist/{devtools-BQATi33M.js → devtools-DPRVRu3S.js} +3 -3
- package/dist/exit-gracefully-De1Nzy3z.js +47 -0
- package/dist/index.d.ts +4703 -4081
- package/dist/index.js +6070 -4387
- package/dist/inspect-Z_e5RkQs.js +1443 -0
- package/dist/install-BDS2ISd_.js +32 -0
- package/dist/install-doctor-script-J-zvzVqR.js +3251 -0
- package/dist/install-github-workflow-CAo3dNh1.js +267 -0
- package/dist/install-react-doctor-C_5DWtTC.js +1154 -0
- package/dist/lsp.js +6183 -4512
- package/dist/print-branded-header-UfYgFqhb.js +22 -0
- package/dist/read-object-file-BDLV3R4X.js +24 -0
- package/dist/{exit-gracefully-BJRsqdaK.js → record-metric-BGxpNcR0.js} +6252 -11955
- package/dist/report-error-Wat6_MDs.js +158 -0
- package/dist/report-workflow-result-CULZXneJ.js +279 -0
- package/dist/resolve-scope-BbndLz4E.js +339 -0
- package/dist/rules-BSF6erEQ.js +581 -0
- package/dist/{run-scan-app-DawD5rhU.js → run-scan-app-D1yyCXe2.js} +36 -29
- package/dist/scan-BTYPKfS9.js +53 -0
- package/dist/should-fail-scan-gate-CgCFA8pU.js +3075 -0
- package/dist/should-skip-prompts-CZbr6sjQ.js +10 -0
- package/dist/unref-stdin-noiOzC5n.js +120 -0
- package/dist/version-BAsYYPLc.js +21 -0
- package/dist/why-DZ9AgG5O.js +21 -0
- package/package.json +6 -7
|
@@ -0,0 +1,312 @@
|
|
|
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]="bd01af5d-c94c-56b0-8823-3601ceecd3b3")}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-BGxpNcR0.js";
|
|
4
|
+
import { o as isExpectedUserError } from "./unref-stdin-noiOzC5n.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.8-dev.7f028ea`;
|
|
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.8-dev.7f028ea") ? "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-DKzi1sn2.js.map
|
|
312
|
+
//# debugId=bd01af5d-c94c-56b0-8823-3601ceecd3b3
|