react-doctor 0.5.8-dev.970babc → 0.5.8-dev.99f2417
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/LICENSE +14 -1
- package/dist/cli.js +487 -187
- package/dist/index.d.ts +11 -2
- package/dist/index.js +211 -32
- package/dist/lsp.js +151 -33
- package/package.json +8 -7
package/dist/cli.js
CHANGED
|
@@ -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]="
|
|
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]="cf52ec59-cee0-5503-b8f2-a4f98bf77ea4")}catch(e){}}();
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import * as NodeChildProcess from "node:child_process";
|
|
5
5
|
import { execFile, execFileSync, spawn, spawnSync } from "node:child_process";
|
|
@@ -9041,7 +9041,7 @@ const composePassthrough = /* @__PURE__ */ dual(2, (left, right) => (input) => {
|
|
|
9041
9041
|
* @since 2.0.0
|
|
9042
9042
|
*/
|
|
9043
9043
|
const Scheduler = /* @__PURE__ */ Reference("effect/Scheduler", { defaultValue: () => new MixedScheduler() });
|
|
9044
|
-
const setImmediate = "setImmediate" in globalThis ? (f) => {
|
|
9044
|
+
const setImmediate$1 = "setImmediate" in globalThis ? (f) => {
|
|
9045
9045
|
const timer = globalThis.setImmediate(f);
|
|
9046
9046
|
return () => globalThis.clearImmediate(timer);
|
|
9047
9047
|
} : (f) => {
|
|
@@ -9085,7 +9085,7 @@ var PriorityBuckets = class {
|
|
|
9085
9085
|
var MixedScheduler = class {
|
|
9086
9086
|
executionMode;
|
|
9087
9087
|
setImmediate;
|
|
9088
|
-
constructor(executionMode = "async", setImmediateFn = setImmediate) {
|
|
9088
|
+
constructor(executionMode = "async", setImmediateFn = setImmediate$1) {
|
|
9089
9089
|
this.executionMode = executionMode;
|
|
9090
9090
|
this.setImmediate = setImmediateFn;
|
|
9091
9091
|
}
|
|
@@ -9110,7 +9110,7 @@ var MixedSchedulerDispatcher = class {
|
|
|
9110
9110
|
tasks = /* @__PURE__ */ new PriorityBuckets();
|
|
9111
9111
|
running = void 0;
|
|
9112
9112
|
setImmediate;
|
|
9113
|
-
constructor(setImmediateFn = setImmediate) {
|
|
9113
|
+
constructor(setImmediateFn = setImmediate$1) {
|
|
9114
9114
|
this.setImmediate = setImmediateFn;
|
|
9115
9115
|
}
|
|
9116
9116
|
/**
|
|
@@ -36947,6 +36947,7 @@ const DEAD_CODE_PHASE_TIMEOUT_MS = 15e4;
|
|
|
36947
36947
|
const LINT_PHASE_TIMEOUT_MS = 3e5;
|
|
36948
36948
|
const SCAN_TOTAL_DEADLINE_MS = 9e5;
|
|
36949
36949
|
const DEAD_CODE_WORKER_MAX_OLD_SPACE_MB = 8192;
|
|
36950
|
+
const DEAD_CODE_WORKER_MEM_BUDGET_BYTES = 2 * 1024 * 1024 * 1024;
|
|
36950
36951
|
const DEAD_CODE_TIMEOUT_CEILING_MS = 6e5;
|
|
36951
36952
|
const DEAD_CODE_PHASE_TIMEOUT_OVER_WORKER_MS = 3e4;
|
|
36952
36953
|
const DEAD_CODE_OVERLAP_PARSE_SHARE = .4;
|
|
@@ -38186,6 +38187,7 @@ const collectStringSet = (values) => {
|
|
|
38186
38187
|
if (!Array.isArray(values)) return /* @__PURE__ */ new Set();
|
|
38187
38188
|
return new Set(values.filter((value) => typeof value === "string"));
|
|
38188
38189
|
};
|
|
38190
|
+
const JSX_NO_UNDEF_IDENTIFIER_PATTERN = /^`([^`]+)`/;
|
|
38189
38191
|
/**
|
|
38190
38192
|
* Pre-compiles every stateful filter and returns a single
|
|
38191
38193
|
* `apply(diagnostic)` closure that runs (in order):
|
|
@@ -38219,8 +38221,10 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
38219
38221
|
const compiledOverrides = compileIgnoreOverrides(userConfig);
|
|
38220
38222
|
const textComponentNames = collectStringSet(userConfig?.textComponents);
|
|
38221
38223
|
const rawTextWrapperComponentNames = collectStringSet(userConfig?.rawTextWrapperComponents);
|
|
38224
|
+
const runtimeGlobalNames = collectStringSet(userConfig?.runtimeGlobals);
|
|
38222
38225
|
const hasTextComponents = textComponentNames.size > 0;
|
|
38223
38226
|
const hasRawTextWrappers = rawTextWrapperComponentNames.size > 0;
|
|
38227
|
+
const hasRuntimeGlobals = runtimeGlobalNames.size > 0;
|
|
38224
38228
|
const fileLinesCache = /* @__PURE__ */ new Map();
|
|
38225
38229
|
const fileContextCache = /* @__PURE__ */ new Map();
|
|
38226
38230
|
const libraryFileCache = /* @__PURE__ */ new Map();
|
|
@@ -38272,6 +38276,12 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
38272
38276
|
if (hasRawTextWrappers && isInsideStringOnlyWrapper(lines, diagnostic.line, diagnostic.column, rawTextWrapperComponentNames)) return true;
|
|
38273
38277
|
return false;
|
|
38274
38278
|
};
|
|
38279
|
+
const isJsxNoUndefSuppressedByConfig = (diagnostic) => {
|
|
38280
|
+
if (!hasRuntimeGlobals) return false;
|
|
38281
|
+
if (diagnostic.rule !== "jsx-no-undef") return false;
|
|
38282
|
+
const identifierMatch = JSX_NO_UNDEF_IDENTIFIER_PATTERN.exec(diagnostic.message);
|
|
38283
|
+
return identifierMatch !== null && runtimeGlobalNames.has(identifierMatch[1]);
|
|
38284
|
+
};
|
|
38275
38285
|
return { apply: (diagnostic) => {
|
|
38276
38286
|
if (shouldAutoSuppress(diagnostic)) return null;
|
|
38277
38287
|
let current = diagnostic;
|
|
@@ -38296,6 +38306,7 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
38296
38306
|
if (isFileIgnoredByPatterns(current.filePath, rootDirectory, ignoredFilePatterns)) return null;
|
|
38297
38307
|
if (isDiagnosticIgnoredByOverrides(current, rootDirectory, compiledOverrides)) return null;
|
|
38298
38308
|
if (isRnRawTextSuppressedByConfig(current)) return null;
|
|
38309
|
+
if (isJsxNoUndefSuppressedByConfig(current)) return null;
|
|
38299
38310
|
}
|
|
38300
38311
|
if (respectInlineDisables && current.line > 0) {
|
|
38301
38312
|
const lines = getFileLines(current.filePath);
|
|
@@ -38557,7 +38568,7 @@ const resolveScanConcurrency = (requested) => {
|
|
|
38557
38568
|
if (!Number.isFinite(requested) || requested < 1) return 1;
|
|
38558
38569
|
return Math.min(Math.floor(requested), 32);
|
|
38559
38570
|
};
|
|
38560
|
-
const readSystemFacts = () => ({
|
|
38571
|
+
const readSystemFacts$1 = () => ({
|
|
38561
38572
|
availableCores: os.availableParallelism(),
|
|
38562
38573
|
totalMemoryBytes: os.totalmem(),
|
|
38563
38574
|
cgroupMemoryLimitBytes: readCgroupMemoryLimitBytes()
|
|
@@ -38578,7 +38589,7 @@ const readSystemFacts = () => ({
|
|
|
38578
38589
|
* `facts` is injectable so tests exercise core-bound, memory-bound, cgroup-
|
|
38579
38590
|
* limited, and ceiling cases without mocking `os` or the filesystem.
|
|
38580
38591
|
*/
|
|
38581
|
-
const resolveAutoScanConcurrency = (facts = readSystemFacts()) => {
|
|
38592
|
+
const resolveAutoScanConcurrency = (facts = readSystemFacts$1()) => {
|
|
38582
38593
|
const availableMemoryBytes = Math.min(facts.totalMemoryBytes, facts.cgroupMemoryLimitBytes ?? Number.POSITIVE_INFINITY);
|
|
38583
38594
|
const memoryBoundedWorkers = Math.floor(availableMemoryBytes / PER_WORKER_MEM_BUDGET_BYTES);
|
|
38584
38595
|
return resolveScanConcurrency(Math.min(facts.availableCores, memoryBoundedWorkers));
|
|
@@ -38773,6 +38784,7 @@ const STRING_ARRAY_FIELD_NAMES = [
|
|
|
38773
38784
|
"projects",
|
|
38774
38785
|
"textComponents",
|
|
38775
38786
|
"rawTextWrapperComponents",
|
|
38787
|
+
"runtimeGlobals",
|
|
38776
38788
|
"serverAuthFunctionNames"
|
|
38777
38789
|
];
|
|
38778
38790
|
const SURFACE_CONTROL_FIELD_NAMES = [
|
|
@@ -40139,7 +40151,10 @@ const shouldEnableRule = (requires, tags, capabilities, ignoredTags, disabledBy)
|
|
|
40139
40151
|
}
|
|
40140
40152
|
return true;
|
|
40141
40153
|
};
|
|
40142
|
-
const
|
|
40154
|
+
const yieldToEventLoop = () => new Promise((resolve) => {
|
|
40155
|
+
setImmediate(resolve);
|
|
40156
|
+
});
|
|
40157
|
+
const createSecurityScanSession = (rootDirectory, options) => {
|
|
40143
40158
|
const capabilities = options.project ? buildCapabilities(options.project) : /* @__PURE__ */ new Set();
|
|
40144
40159
|
const ignoredTags = options.ignoredTags ?? /* @__PURE__ */ new Set();
|
|
40145
40160
|
const enabledScanRules = REACT_DOCTOR_RULES.flatMap((entry) => {
|
|
@@ -40154,7 +40169,7 @@ const checkSecurityScan = (rootDirectory, options = {}) => {
|
|
|
40154
40169
|
committedFilesOnly: rule.committedFilesOnly === true
|
|
40155
40170
|
}];
|
|
40156
40171
|
});
|
|
40157
|
-
if (enabledScanRules.length === 0) return
|
|
40172
|
+
if (enabledScanRules.length === 0) return null;
|
|
40158
40173
|
const diagnostics = [];
|
|
40159
40174
|
const seen = /* @__PURE__ */ new Set();
|
|
40160
40175
|
const gitIgnoredCache = /* @__PURE__ */ new Map();
|
|
@@ -40166,15 +40181,34 @@ const checkSecurityScan = (rootDirectory, options = {}) => {
|
|
|
40166
40181
|
}
|
|
40167
40182
|
return status === true;
|
|
40168
40183
|
};
|
|
40169
|
-
|
|
40170
|
-
|
|
40171
|
-
|
|
40172
|
-
|
|
40173
|
-
|
|
40174
|
-
|
|
40175
|
-
|
|
40184
|
+
const scanFile = (file) => {
|
|
40185
|
+
for (const { entry, scan, committedFilesOnly } of enabledScanRules) for (const finding of scan(file)) {
|
|
40186
|
+
if (committedFilesOnly && isFileGitIgnored(file)) continue;
|
|
40187
|
+
const diagnostic = buildSecurityScanDiagnostic(finding, entry, file.relativePath);
|
|
40188
|
+
const key = `${diagnostic.rule}:${diagnostic.filePath}:${diagnostic.line}:${diagnostic.column}:${diagnostic.message}`;
|
|
40189
|
+
if (seen.has(key)) continue;
|
|
40190
|
+
seen.add(key);
|
|
40191
|
+
diagnostics.push(diagnostic);
|
|
40192
|
+
}
|
|
40193
|
+
};
|
|
40194
|
+
return {
|
|
40195
|
+
scanFile,
|
|
40196
|
+
diagnostics
|
|
40197
|
+
};
|
|
40198
|
+
};
|
|
40199
|
+
const checkSecurityScanCooperative = async (rootDirectory, options = {}) => {
|
|
40200
|
+
const session = createSecurityScanSession(rootDirectory, options);
|
|
40201
|
+
if (session === null) return [];
|
|
40202
|
+
let filesSinceYield = 0;
|
|
40203
|
+
for (const file of collectSecurityScanFiles(rootDirectory)) {
|
|
40204
|
+
session.scanFile(file);
|
|
40205
|
+
filesSinceYield += 1;
|
|
40206
|
+
if (filesSinceYield >= 16) {
|
|
40207
|
+
filesSinceYield = 0;
|
|
40208
|
+
await yieldToEventLoop();
|
|
40209
|
+
}
|
|
40176
40210
|
}
|
|
40177
|
-
return diagnostics;
|
|
40211
|
+
return session.diagnostics;
|
|
40178
40212
|
};
|
|
40179
40213
|
var import_picocolors = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
40180
40214
|
let p = process || {}, argv = p.argv || [], env = p.env || {};
|
|
@@ -40407,6 +40441,74 @@ const collectDeadCodeIgnorePatterns = (rootDirectory) => {
|
|
|
40407
40441
|
return [...seen].filter((pattern) => pattern.length > 0);
|
|
40408
40442
|
};
|
|
40409
40443
|
const collectDeadCodeEntryPatterns = (rootDirectory) => [...new Set(collectKnipPatterns(rootDirectory, "entry"))].filter((pattern) => pattern.length > 0);
|
|
40444
|
+
const readSystemFacts = () => ({
|
|
40445
|
+
availableCores: os.availableParallelism(),
|
|
40446
|
+
totalMemoryBytes: os.totalmem(),
|
|
40447
|
+
cgroupMemoryLimitBytes: readCgroupMemoryLimitBytes()
|
|
40448
|
+
});
|
|
40449
|
+
/**
|
|
40450
|
+
* How many real deslop dead-code child processes may run at once, across the
|
|
40451
|
+
* concurrent per-project `runInspect` fibers of one CLI run. The cap is the
|
|
40452
|
+
* smaller of the core count and the number of `DEAD_CODE_WORKER_MEM_BUDGET_BYTES`
|
|
40453
|
+
* workers that fit in available memory, floored at 1.
|
|
40454
|
+
*
|
|
40455
|
+
* On a roomy dev box / CI runner this resolves high enough that every
|
|
40456
|
+
* concurrently-scanned project still spawns its own worker (no serialization vs
|
|
40457
|
+
* the prior uncapped behavior); on a memory-constrained runner it collapses
|
|
40458
|
+
* toward 1, so the `withDeadCodeWorkerSlot` semaphore serializes the spawns
|
|
40459
|
+
* instead of oversubscribing memory with N simultaneous children — the global
|
|
40460
|
+
* cap the per-project spawn path lacked.
|
|
40461
|
+
*
|
|
40462
|
+
* Mirrors `resolveAutoScanConcurrency` (lint), but budgets memory per the
|
|
40463
|
+
* heavier dead-code worker. `facts` is injectable for tests.
|
|
40464
|
+
*/
|
|
40465
|
+
const resolveDeadCodeConcurrency = (facts = readSystemFacts()) => {
|
|
40466
|
+
const availableMemoryBytes = Math.min(facts.totalMemoryBytes, facts.cgroupMemoryLimitBytes ?? Number.POSITIVE_INFINITY);
|
|
40467
|
+
const memoryBoundedWorkers = Math.floor(availableMemoryBytes / DEAD_CODE_WORKER_MEM_BUDGET_BYTES);
|
|
40468
|
+
return Math.max(1, Math.min(facts.availableCores, memoryBoundedWorkers));
|
|
40469
|
+
};
|
|
40470
|
+
let availableSlots = -1;
|
|
40471
|
+
const waiters = [];
|
|
40472
|
+
const releaseSlot = () => {
|
|
40473
|
+
const nextWaiter = waiters.shift();
|
|
40474
|
+
if (nextWaiter !== void 0) nextWaiter();
|
|
40475
|
+
else availableSlots += 1;
|
|
40476
|
+
};
|
|
40477
|
+
/**
|
|
40478
|
+
* Runs `task` once a dead-code worker slot is free, releasing the slot when the
|
|
40479
|
+
* task settles (success or failure). With a high cap (roomy machine) every
|
|
40480
|
+
* caller proceeds immediately; with a low cap (constrained runner) callers
|
|
40481
|
+
* queue and run as slots free.
|
|
40482
|
+
*
|
|
40483
|
+
* `abortSignal` short-circuits the WAIT: if it's already aborted, or fires while
|
|
40484
|
+
* this caller is queued, the call rejects without acquiring a slot or running
|
|
40485
|
+
* `task` — so a cancelled scan (e.g. lint failed) doesn't sit in the queue and
|
|
40486
|
+
* then spawn a child only to tear it down. A queued caller that aborts removes
|
|
40487
|
+
* its own waiter so a later release never hands a slot to a dead request.
|
|
40488
|
+
*/
|
|
40489
|
+
const withDeadCodeWorkerSlot = async (task, abortSignal) => {
|
|
40490
|
+
if (abortSignal?.aborted) throw new Error("Dead-code worker aborted.");
|
|
40491
|
+
if (availableSlots < 0) availableSlots = resolveDeadCodeConcurrency();
|
|
40492
|
+
if (availableSlots > 0) availableSlots -= 1;
|
|
40493
|
+
else await new Promise((resolve, reject) => {
|
|
40494
|
+
const waiter = () => {
|
|
40495
|
+
abortSignal?.removeEventListener("abort", onAbort);
|
|
40496
|
+
resolve();
|
|
40497
|
+
};
|
|
40498
|
+
const onAbort = () => {
|
|
40499
|
+
const queuedIndex = waiters.indexOf(waiter);
|
|
40500
|
+
if (queuedIndex !== -1) waiters.splice(queuedIndex, 1);
|
|
40501
|
+
reject(/* @__PURE__ */ new Error("Dead-code worker aborted."));
|
|
40502
|
+
};
|
|
40503
|
+
waiters.push(waiter);
|
|
40504
|
+
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
40505
|
+
});
|
|
40506
|
+
try {
|
|
40507
|
+
return await task();
|
|
40508
|
+
} finally {
|
|
40509
|
+
releaseSlot();
|
|
40510
|
+
}
|
|
40511
|
+
};
|
|
40410
40512
|
/**
|
|
40411
40513
|
* Resolves a path to its canonical, symlink-free form, falling back to
|
|
40412
40514
|
* the input when it cannot be realpath'd (broken symlink, permission
|
|
@@ -40431,6 +40533,11 @@ const toCanonicalPath = (filePath) => {
|
|
|
40431
40533
|
};
|
|
40432
40534
|
const DEAD_CODE_PLUGIN = "deslop";
|
|
40433
40535
|
const DEAD_CODE_CATEGORY = "Maintainability";
|
|
40536
|
+
const REACT_DOCTOR_TOOLCHAIN_PACKAGES = new Set([
|
|
40537
|
+
"react-doctor",
|
|
40538
|
+
"eslint-plugin-react-doctor",
|
|
40539
|
+
"oxlint-plugin-react-doctor"
|
|
40540
|
+
]);
|
|
40434
40541
|
const DEAD_CODE_WORKER_SCRIPT = `
|
|
40435
40542
|
const inputChunks = [];
|
|
40436
40543
|
process.stdin.on("data", (chunk) => inputChunks.push(chunk));
|
|
@@ -40697,14 +40804,17 @@ const checkDeadCode = async (options) => {
|
|
|
40697
40804
|
if (!NFS.existsSync(Path.join(rootDirectory, "package.json"))) return [];
|
|
40698
40805
|
const entryPatterns = collectDeadCodeEntryPatterns(rootDirectory);
|
|
40699
40806
|
const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory);
|
|
40700
|
-
const
|
|
40701
|
-
|
|
40702
|
-
|
|
40703
|
-
|
|
40704
|
-
|
|
40705
|
-
|
|
40706
|
-
|
|
40707
|
-
|
|
40807
|
+
const spawnAndRun = () => {
|
|
40808
|
+
return runDeadCodeWorkerWithTimeout((options.createWorker ?? createDeadCodeWorker)({
|
|
40809
|
+
rootDirectory,
|
|
40810
|
+
entryPatterns,
|
|
40811
|
+
tsConfigPath: resolveTsConfigPath(rootDirectory),
|
|
40812
|
+
ignorePatterns,
|
|
40813
|
+
deslopJsModuleSpecifier: options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js"),
|
|
40814
|
+
parseConcurrency: options.parseConcurrency
|
|
40815
|
+
}), options.workerTimeoutMs ?? 12e4, options.abortSignal);
|
|
40816
|
+
};
|
|
40817
|
+
const result = parseDeadCodeWorkerResult(options.createWorker === void 0 ? await withDeadCodeWorkerSlot(spawnAndRun, options.abortSignal) : await spawnAndRun());
|
|
40708
40818
|
const toRelative = (filePath) => toRelativeFilePath(rootDirectory, filePath);
|
|
40709
40819
|
const diagnostics = [];
|
|
40710
40820
|
for (const unusedFile of result.unusedFiles) diagnostics.push({
|
|
@@ -40733,6 +40843,7 @@ const checkDeadCode = async (options) => {
|
|
|
40733
40843
|
});
|
|
40734
40844
|
}
|
|
40735
40845
|
for (const unusedDependency of result.unusedDependencies) {
|
|
40846
|
+
if (REACT_DOCTOR_TOOLCHAIN_PACKAGES.has(unusedDependency.name)) continue;
|
|
40736
40847
|
const label = unusedDependency.isDevDependency ? "devDependency" : "dependency";
|
|
40737
40848
|
diagnostics.push({
|
|
40738
40849
|
filePath: "package.json",
|
|
@@ -43868,7 +43979,10 @@ const formatLintFailText = (reasonTag, nodeVersion) => {
|
|
|
43868
43979
|
* diagnostics).
|
|
43869
43980
|
* 2. beforeLint hook (e.g. CLI renders the project-detection block)
|
|
43870
43981
|
* 3. environment checks (reduced-motion + pnpm hardening +
|
|
43871
|
-
* expo/react-native
|
|
43982
|
+
* expo/react-native), collected synchronously. The heavier
|
|
43983
|
+
* content-regex security scan is forked instead (like supply-chain
|
|
43984
|
+
* below) and joined before the concat, so its CPU overlaps lint
|
|
43985
|
+
* rather than blocking the event loop before it.
|
|
43872
43986
|
* 4. The supply-chain check (Socket.dev) is forked onto a background
|
|
43873
43987
|
* fiber so its ~100% network-bound time overlaps the ~100%
|
|
43874
43988
|
* CPU/subprocess-bound lint pass below, collapsing two serial
|
|
@@ -43888,7 +44002,7 @@ const formatLintFailText = (reasonTag, nodeVersion) => {
|
|
|
43888
44002
|
* order, so terminal output is identical either way; supply-chain
|
|
43889
44003
|
* rides alongside without a spinner.
|
|
43890
44004
|
* 6. Join the supply-chain fiber, then assemble the diagnostics in a
|
|
43891
|
-
* FIXED order (env, supply-chain, lint, dead-code) so the output is
|
|
44005
|
+
* FIXED order (env, security-scan, supply-chain, lint, dead-code) so the output is
|
|
43892
44006
|
* byte-identical regardless of which fiber settled first. The
|
|
43893
44007
|
* viewer-permission fiber is joined later, during score-metadata
|
|
43894
44008
|
* assembly (it feeds score metadata, not diagnostics). The per-element
|
|
@@ -43949,12 +44063,12 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
43949
44063
|
...checkPnpmHardening(scanDirectory),
|
|
43950
44064
|
...checkReactServerComponentsAdvisory(scanDirectory, project),
|
|
43951
44065
|
...checkExpoProject(scanDirectory, project),
|
|
43952
|
-
...checkReactNativeProject(scanDirectory, project)
|
|
43953
|
-
...checkSecurityScan(scanDirectory, {
|
|
43954
|
-
project,
|
|
43955
|
-
ignoredTags: input.ignoredTags
|
|
43956
|
-
})
|
|
44066
|
+
...checkReactNativeProject(scanDirectory, project)
|
|
43957
44067
|
])));
|
|
44068
|
+
const securityScanFiber = yield* forkChild(runCollect(applyPerElementPipeline(isDiffMode ? empty$4 : unwrap(promise(() => checkSecurityScanCooperative(scanDirectory, {
|
|
44069
|
+
project,
|
|
44070
|
+
ignoredTags: input.ignoredTags
|
|
44071
|
+
})).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)))))).pipe(withSpan("SecurityScan.run")));
|
|
43958
44072
|
const shouldRunSupplyChain = !isDiffMode || (input.supplyChainManifestChanged ?? false);
|
|
43959
44073
|
const supplyChainOverlapTimeout = yield* SupplyChainOverlapTimeoutMs;
|
|
43960
44074
|
const supplyChainFiber = yield* forkChild(shouldRunSupplyChain ? runCollect(applyPerElementPipeline(supplyChainService.run({
|
|
@@ -44090,9 +44204,11 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
44090
44204
|
else yield* scanProgress.succeed(`Scanned ${scannedFilesLabel} in ${scanElapsedSeconds}s${workerCountSuffix}`);
|
|
44091
44205
|
const supplyChainResult = yield* join(supplyChainFiber);
|
|
44092
44206
|
const supplyChainCollected = supplyChainResult.diagnostics;
|
|
44207
|
+
const securityScanCollected = yield* join(securityScanFiber);
|
|
44093
44208
|
yield* reporterService.finalize;
|
|
44094
44209
|
const finalDiagnostics = sortDiagnosticsStable(assignFixGroups([
|
|
44095
44210
|
...envCollected,
|
|
44211
|
+
...securityScanCollected,
|
|
44096
44212
|
...supplyChainCollected,
|
|
44097
44213
|
...lintCollected,
|
|
44098
44214
|
...deadCodeCollected
|
|
@@ -44631,6 +44747,61 @@ const computeConfigFingerprint = (projectDirectory, version) => {
|
|
|
44631
44747
|
}
|
|
44632
44748
|
return crypto.createHash("sha1").update(parts.join("|")).digest("hex");
|
|
44633
44749
|
};
|
|
44750
|
+
const AI_TRAINING_ENV_VARS = [
|
|
44751
|
+
["HF_DATASETS_CACHE", "huggingface"],
|
|
44752
|
+
["HF_HOME", "huggingface"],
|
|
44753
|
+
["HUGGINGFACE_HUB_CACHE", "huggingface"],
|
|
44754
|
+
["SPACE_ID", "huggingface-spaces"],
|
|
44755
|
+
["CUDA_VISIBLE_DEVICES", "cuda"],
|
|
44756
|
+
["NVIDIA_VISIBLE_DEVICES", "nvidia"],
|
|
44757
|
+
["ROCR_VISIBLE_DEVICES", "rocm"],
|
|
44758
|
+
["TPU_NAME", "google-tpu"],
|
|
44759
|
+
["WANDB_RUN_ID", "wandb"],
|
|
44760
|
+
["WANDB_SWEEP_ID", "wandb"],
|
|
44761
|
+
["MLFLOW_RUN_ID", "mlflow"],
|
|
44762
|
+
["MLFLOW_TRACKING_URI", "mlflow"],
|
|
44763
|
+
["COMET_EXPERIMENT_KEY", "comet"],
|
|
44764
|
+
["NEPTUNE_RUN_ID", "neptune"],
|
|
44765
|
+
["CLEARML_TASK_ID", "clearml"],
|
|
44766
|
+
["DVC_STAGE", "dvc"],
|
|
44767
|
+
["RAY_WORKER_PROCESS", "ray"],
|
|
44768
|
+
["RAY_ADDRESS", "ray"],
|
|
44769
|
+
["MUJOCO_GL", "mujoco"],
|
|
44770
|
+
["MUJOCO_PATH", "mujoco"],
|
|
44771
|
+
["GYM_DISABLE_ENV_CHECKER", "gymnasium"],
|
|
44772
|
+
["SM_TRAINING_ENV", "sagemaker"],
|
|
44773
|
+
["TRAINING_JOB_ARN", "sagemaker"],
|
|
44774
|
+
["SAGEMAKER_BASE_DIR", "sagemaker"],
|
|
44775
|
+
["AZUREML_RUN_ID", "azure-ml"],
|
|
44776
|
+
["AZURE_ML_MODEL_DIR", "azure-ml"],
|
|
44777
|
+
["CLOUD_ML_PROJECT_ID", "vertex-ai"],
|
|
44778
|
+
["VERTEX_AI_LOG_LEVEL", "vertex-ai"],
|
|
44779
|
+
["DET_MASTER", "determined-ai"],
|
|
44780
|
+
["LIGHTNING_USER_ID", "lightning-ai"],
|
|
44781
|
+
["FLYTE_INTERNAL_EXECUTION_ID", "flyte"],
|
|
44782
|
+
["ARGO_WORKFLOW_NAME", "argo-workflows"],
|
|
44783
|
+
["KFP_POD_NAME", "kubeflow-pipelines"],
|
|
44784
|
+
["KAGGLE_KERNEL_RUN_TYPE", "kaggle"],
|
|
44785
|
+
["COLAB_BACKEND_VERSION", "google-colab"],
|
|
44786
|
+
["DATABRICKS_RUNTIME_VERSION", "databricks"],
|
|
44787
|
+
["DAYTONA_WS_ID", "daytona"],
|
|
44788
|
+
["DAYTONA_WS_NAME", "daytona"],
|
|
44789
|
+
["E2B_SANDBOX_ID", "e2b"],
|
|
44790
|
+
["MODAL_FUNCTION_ID", "modal"],
|
|
44791
|
+
["MODAL_TASK_ID", "modal"],
|
|
44792
|
+
["RUNPOD_POD_ID", "runpod"],
|
|
44793
|
+
["REPLICATE_USERNAME", "replicate"],
|
|
44794
|
+
["VAST_CONTAINERLABEL", "vast-ai"],
|
|
44795
|
+
["HARBOR_URL", "harbor"],
|
|
44796
|
+
["HARBOR_HOSTNAME", "harbor"],
|
|
44797
|
+
["SWE_BENCH_TASK", "swe-bench"],
|
|
44798
|
+
["SWEBENCH_TASK", "swe-bench"],
|
|
44799
|
+
["SWE_AGENT_MODEL", "swe-agent"]
|
|
44800
|
+
];
|
|
44801
|
+
const detectAiTrainingEnvironment = () => {
|
|
44802
|
+
for (const [envVar, label] of AI_TRAINING_ENV_VARS) if (process.env[envVar]) return label;
|
|
44803
|
+
return null;
|
|
44804
|
+
};
|
|
44634
44805
|
const groupBy = (items, keyFn) => {
|
|
44635
44806
|
const groups = /* @__PURE__ */ new Map();
|
|
44636
44807
|
for (const item of items) {
|
|
@@ -44716,7 +44887,25 @@ const CI_PROVIDER_BY_ENVIRONMENT_VARIABLE = [
|
|
|
44716
44887
|
["TEAMCITY_VERSION", "teamcity"],
|
|
44717
44888
|
["BITBUCKET_BUILD_NUMBER", "bitbucket"],
|
|
44718
44889
|
["TRAVIS", "travis"],
|
|
44719
|
-
["DRONE", "drone"]
|
|
44890
|
+
["DRONE", "drone"],
|
|
44891
|
+
["SEMAPHORE", "semaphore"],
|
|
44892
|
+
["APPVEYOR", "appveyor"],
|
|
44893
|
+
["HARNESS_BUILD_ID", "harness"],
|
|
44894
|
+
["BUDDY", "buddy"],
|
|
44895
|
+
["CF_BUILD_ID", "codefresh"],
|
|
44896
|
+
["NETLIFY", "netlify"],
|
|
44897
|
+
["RAILWAY_SERVICE_ID", "railway"],
|
|
44898
|
+
["VERCEL", "vercel"],
|
|
44899
|
+
["CM_BUILD_ID", "codemagic"],
|
|
44900
|
+
["PROW_JOB_ID", "prow"],
|
|
44901
|
+
["AGOLA_GIT_REF", "agola"],
|
|
44902
|
+
["CIRRUS_CI", "cirrus-ci"],
|
|
44903
|
+
["BLACKSMITH_STICKYDISK_TOKEN", "blacksmith"],
|
|
44904
|
+
["WARPBUILD_RUNNER_VERIFICATION_TOKEN", "warpbuild"],
|
|
44905
|
+
["NSC_CACHE_PATH", "namespace"],
|
|
44906
|
+
["UBICLOUD_CACHE_URL", "ubicloud"],
|
|
44907
|
+
["RENDER", "render"],
|
|
44908
|
+
["FLY_APP_NAME", "fly-io"]
|
|
44720
44909
|
];
|
|
44721
44910
|
const GITHUB_ACTION_MARKER_ENVIRONMENT_VARIABLE = "REACT_DOCTOR_GITHUB_ACTION";
|
|
44722
44911
|
const ACTION_INPUT_ENVIRONMENT_VARIABLES = {
|
|
@@ -44734,7 +44923,10 @@ const CODING_AGENT_BY_ENVIRONMENT_VARIABLE = [
|
|
|
44734
44923
|
["CODEX_SANDBOX_NETWORK_DISABLED", "codex"],
|
|
44735
44924
|
["OPENCODE", "opencode"],
|
|
44736
44925
|
["GOOSE_TERMINAL", "goose"],
|
|
44737
|
-
["AMP_THREAD_ID", "amp"]
|
|
44926
|
+
["AMP_THREAD_ID", "amp"],
|
|
44927
|
+
["CLINE_ACTIVE", "cline"],
|
|
44928
|
+
["AUGMENT_AGENT", "augment"],
|
|
44929
|
+
["TRAE_AI_SHELL_ID", "trae-ai"]
|
|
44738
44930
|
];
|
|
44739
44931
|
const GENERIC_CODING_AGENT_ENVIRONMENT_VARIABLES = ["AGENT_SESSION_ID", "AGENT_THREAD_ID"];
|
|
44740
44932
|
const CODING_AGENT_ENVIRONMENT_VALUE_VARIABLES = ["AGENT"];
|
|
@@ -44883,7 +45075,9 @@ const METRIC = {
|
|
|
44883
45075
|
lspSessionStarted: "lsp.session.started",
|
|
44884
45076
|
lspScanCompleted: "lsp.scan.completed",
|
|
44885
45077
|
lspScanDuration: "lsp.scan.duration",
|
|
44886
|
-
lspScanDiagnostics: "lsp.scan.diagnostics"
|
|
45078
|
+
lspScanDiagnostics: "lsp.scan.diagnostics",
|
|
45079
|
+
aiTrainingWarningShown: "ai.training.warning_shown",
|
|
45080
|
+
jsonOutUsed: "json.out_used"
|
|
44887
45081
|
};
|
|
44888
45082
|
//#endregion
|
|
44889
45083
|
//#region src/cli/utils/noop-console.ts
|
|
@@ -44926,7 +45120,7 @@ const makeNoopConsole = () => ({
|
|
|
44926
45120
|
});
|
|
44927
45121
|
//#endregion
|
|
44928
45122
|
//#region src/cli/utils/version.ts
|
|
44929
|
-
const VERSION = "0.5.8-dev.
|
|
45123
|
+
const VERSION = "0.5.8-dev.99f2417";
|
|
44930
45124
|
//#endregion
|
|
44931
45125
|
//#region src/cli/utils/json-mode.ts
|
|
44932
45126
|
let context = null;
|
|
@@ -44964,12 +45158,13 @@ const installSilentConsole = () => {
|
|
|
44964
45158
|
"trace"
|
|
44965
45159
|
]) target[key] = source[key];
|
|
44966
45160
|
};
|
|
44967
|
-
const enableJsonMode = ({ compact, directory }) => {
|
|
45161
|
+
const enableJsonMode = ({ compact, directory, outputFile }) => {
|
|
44968
45162
|
context = {
|
|
44969
45163
|
compact,
|
|
44970
45164
|
directory,
|
|
44971
45165
|
startTime: performance$1.now(),
|
|
44972
|
-
mode: "full"
|
|
45166
|
+
mode: "full",
|
|
45167
|
+
outputFile: outputFile ?? null
|
|
44973
45168
|
};
|
|
44974
45169
|
installSilentConsole();
|
|
44975
45170
|
};
|
|
@@ -44982,7 +45177,11 @@ const setJsonReportMode = (mode) => {
|
|
|
44982
45177
|
};
|
|
44983
45178
|
const writeJsonReport = (report) => {
|
|
44984
45179
|
const serialized = context?.compact ? JSON.stringify(report) : JSON.stringify(report, null, 2);
|
|
44985
|
-
|
|
45180
|
+
if (context?.outputFile) {
|
|
45181
|
+
const resolvedPath = Path.resolve(context.outputFile);
|
|
45182
|
+
NFS.mkdirSync(Path.dirname(resolvedPath), { recursive: true });
|
|
45183
|
+
NFS.writeFileSync(resolvedPath, `${serialized}\n`);
|
|
45184
|
+
} else process.stdout.write(`${serialized}\n`);
|
|
44986
45185
|
};
|
|
44987
45186
|
const writeJsonErrorReport = (error, sentryEventId) => {
|
|
44988
45187
|
if (!context) return;
|
|
@@ -45290,13 +45489,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
|
|
|
45290
45489
|
* uploads source-map artifacts under, so stack frames symbolicate. Honors the
|
|
45291
45490
|
* standard `SENTRY_RELEASE` override.
|
|
45292
45491
|
*/
|
|
45293
|
-
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.5.8-dev.
|
|
45492
|
+
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.5.8-dev.99f2417`;
|
|
45294
45493
|
/**
|
|
45295
45494
|
* Deployment environment shown in Sentry's environment filter. Defaults to
|
|
45296
45495
|
* `production` for tagged releases and `development` for dev/unbuilt versions,
|
|
45297
45496
|
* overridable via the standard `SENTRY_ENVIRONMENT` env var.
|
|
45298
45497
|
*/
|
|
45299
|
-
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.5.8-dev.
|
|
45498
|
+
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.5.8-dev.99f2417") ? "development" : "production");
|
|
45300
45499
|
/**
|
|
45301
45500
|
* Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
|
|
45302
45501
|
* (set to `0` to disable tracing) and falls back to
|
|
@@ -48892,6 +49091,25 @@ const shouldBlockCi = (diagnostics, blockingLevel) => {
|
|
|
48892
49091
|
*/
|
|
48893
49092
|
const toCategoryKey = (category) => category.toLowerCase().replace(/[^a-z0-9]+/g, "_");
|
|
48894
49093
|
//#endregion
|
|
49094
|
+
//#region src/cli/utils/with-namespace.ts
|
|
49095
|
+
/**
|
|
49096
|
+
* Prefixes every key in a flat attribute group with `namespace.`, so a logical
|
|
49097
|
+
* group (scan config, diagnostics rollup, score, …) carries one dotted
|
|
49098
|
+
* namespace applied in a single place rather than hand-spelled into each key —
|
|
49099
|
+
* which is how the wide event drifted into a flat, half-namespaced soup. The
|
|
49100
|
+
* dotted prefix is what makes the attributes tree up in Sentry's attribute
|
|
49101
|
+
* browser and stay group-/filter-/aggregate-able in the Spans dataset.
|
|
49102
|
+
*
|
|
49103
|
+
* Value types are preserved verbatim (numbers stay numbers so Sentry infers a
|
|
49104
|
+
* numeric attribute and `p75(...)`/`avg(...)` work; `null` is kept so
|
|
49105
|
+
* `toSpanAttributes` can drop absent signals rather than coerce them).
|
|
49106
|
+
*/
|
|
49107
|
+
const withNamespace = (namespace, attributes) => {
|
|
49108
|
+
const namespaced = {};
|
|
49109
|
+
for (const [key, value] of Object.entries(attributes)) namespaced[`${namespace}.${key}`] = value;
|
|
49110
|
+
return namespaced;
|
|
49111
|
+
};
|
|
49112
|
+
//#endregion
|
|
48895
49113
|
//#region src/cli/utils/build-run-event.ts
|
|
48896
49114
|
const readEnvBoolean = (name) => {
|
|
48897
49115
|
const value = process.env[name];
|
|
@@ -48913,12 +49131,12 @@ const buildOutcomeAttributes = (input) => {
|
|
|
48913
49131
|
if (input.result === void 0) {
|
|
48914
49132
|
const error = input.error;
|
|
48915
49133
|
const known = isReactDoctorError(error);
|
|
48916
|
-
return {
|
|
48917
|
-
|
|
49134
|
+
return withNamespace("outcome", {
|
|
49135
|
+
status: "error",
|
|
48918
49136
|
exitCode: 1,
|
|
48919
49137
|
knownError: known,
|
|
48920
49138
|
errorTag: known ? error.reason._tag : error instanceof Error ? error.name : null
|
|
48921
|
-
};
|
|
49139
|
+
});
|
|
48922
49140
|
}
|
|
48923
49141
|
const result = input.result;
|
|
48924
49142
|
const summary = summarizeDiagnostics(result.diagnostics);
|
|
@@ -48952,66 +49170,82 @@ const buildOutcomeAttributes = (input) => {
|
|
|
48952
49170
|
}
|
|
48953
49171
|
let fixGroupedFindings = 0;
|
|
48954
49172
|
for (const count of findingsPerFixGroup.values()) fixGroupedFindings += count;
|
|
49173
|
+
const categoryRollup = {};
|
|
49174
|
+
for (const [category, count] of countByCategory) categoryRollup[`category.${toCategoryKey(category)}`] = count;
|
|
48955
49175
|
const attributes = {
|
|
48956
|
-
outcome,
|
|
48957
|
-
|
|
48958
|
-
|
|
48959
|
-
|
|
48960
|
-
|
|
48961
|
-
|
|
48962
|
-
|
|
48963
|
-
|
|
48964
|
-
|
|
48965
|
-
|
|
48966
|
-
|
|
48967
|
-
|
|
48968
|
-
|
|
48969
|
-
|
|
48970
|
-
|
|
48971
|
-
|
|
48972
|
-
|
|
48973
|
-
|
|
48974
|
-
|
|
48975
|
-
|
|
48976
|
-
|
|
48977
|
-
|
|
48978
|
-
|
|
48979
|
-
|
|
48980
|
-
|
|
48981
|
-
|
|
48982
|
-
|
|
48983
|
-
|
|
48984
|
-
|
|
48985
|
-
|
|
48986
|
-
|
|
48987
|
-
|
|
48988
|
-
|
|
48989
|
-
|
|
48990
|
-
|
|
48991
|
-
|
|
48992
|
-
|
|
48993
|
-
|
|
48994
|
-
|
|
48995
|
-
|
|
48996
|
-
|
|
48997
|
-
|
|
48998
|
-
|
|
49176
|
+
...withNamespace("outcome", {
|
|
49177
|
+
status: outcome,
|
|
49178
|
+
exitCode: wouldBlock ? 1 : 0,
|
|
49179
|
+
wouldBlock,
|
|
49180
|
+
blocking: blockingLevel,
|
|
49181
|
+
clean: isClean,
|
|
49182
|
+
skippedChecks: result.skippedChecks.length
|
|
49183
|
+
}),
|
|
49184
|
+
...withNamespace("diag", {
|
|
49185
|
+
total: summary.totalDiagnosticCount,
|
|
49186
|
+
errors: summary.errorCount,
|
|
49187
|
+
warnings: summary.warningCount,
|
|
49188
|
+
affectedFiles: summary.affectedFileCount,
|
|
49189
|
+
inTestFiles: diagnosticsInTestFiles,
|
|
49190
|
+
inStoryFiles: diagnosticsInStoryFiles,
|
|
49191
|
+
distinctRules: countByRule.size,
|
|
49192
|
+
topRule,
|
|
49193
|
+
fixGroups: findingsPerFixGroup.size,
|
|
49194
|
+
fixGroupedFindings,
|
|
49195
|
+
...categoryRollup
|
|
49196
|
+
}),
|
|
49197
|
+
...withNamespace("score", {
|
|
49198
|
+
value: result.score ? result.score.score : null,
|
|
49199
|
+
label: result.score ? result.score.label : null,
|
|
49200
|
+
available: result.score !== null
|
|
49201
|
+
}),
|
|
49202
|
+
...withNamespace("lint", {
|
|
49203
|
+
failed: input.didLintFail ?? null,
|
|
49204
|
+
failureReasonKind: input.lintFailureReasonKind ?? null,
|
|
49205
|
+
partialFailureCount: input.lintPartialFailureCount ?? null,
|
|
49206
|
+
droppedFileCount: input.lintDroppedFileCount ?? null,
|
|
49207
|
+
cacheHitFiles: result.lintCacheHitFileCount ?? null,
|
|
49208
|
+
cacheTotalFiles: result.lintCacheTotalFileCount ?? null,
|
|
49209
|
+
cacheHitRatio: result.lintCacheTotalFileCount != null && result.lintCacheTotalFileCount > 0 ? (result.lintCacheHitFileCount ?? 0) / result.lintCacheTotalFileCount : null
|
|
49210
|
+
}),
|
|
49211
|
+
...withNamespace("deadCode", {
|
|
49212
|
+
failed: input.didDeadCodeFail ?? null,
|
|
49213
|
+
overlapped: input.deadCodeOverlapped ?? null
|
|
49214
|
+
}),
|
|
49215
|
+
...withNamespace("supplyChain", { overlapTimedOut: input.supplyChainOverlapTimedOut ?? null }),
|
|
49216
|
+
...withNamespace("timing", {
|
|
49217
|
+
elapsedMs: result.elapsedMilliseconds,
|
|
49218
|
+
scanMs: result.scanElapsedMilliseconds ?? null
|
|
49219
|
+
}),
|
|
49220
|
+
...withNamespace("migration", {
|
|
49221
|
+
largestRuleBucketFiles: largestRuleBucket ? largestRuleBucket.fileCount : null,
|
|
49222
|
+
largestRuleBucketSites: largestRuleBucket ? largestRuleBucket.siteCount : null,
|
|
49223
|
+
largestRuleBucketRule: largestRuleBucket ? largestRuleBucket.ruleKey : null
|
|
49224
|
+
})
|
|
49225
|
+
};
|
|
49226
|
+
if (result.baselineDelta) Object.assign(attributes, withNamespace("baseline", {
|
|
49227
|
+
new: summary.totalDiagnosticCount,
|
|
49228
|
+
fixed: result.baselineDelta.fixedCount,
|
|
49229
|
+
baseTotal: result.baselineDelta.baseTotalCount,
|
|
49230
|
+
degraded: false
|
|
49231
|
+
}));
|
|
49232
|
+
else if (input.gateExempt) attributes["baseline.degraded"] = true;
|
|
48999
49233
|
return attributes;
|
|
49000
49234
|
};
|
|
49001
|
-
const
|
|
49235
|
+
const buildActionAttributes = () => {
|
|
49002
49236
|
const { githubActorAssociation } = resolveGithubActionsScoreMetadata();
|
|
49003
|
-
return {
|
|
49237
|
+
return withNamespace("action", {
|
|
49004
49238
|
actorAssociation: githubActorAssociation ?? null,
|
|
49005
49239
|
runnerOs: detectRunnerOs(),
|
|
49006
49240
|
comment: readEnvBoolean(ACTION_INPUT_ENVIRONMENT_VARIABLES.comment),
|
|
49007
49241
|
reviewComments: readEnvBoolean(ACTION_INPUT_ENVIRONMENT_VARIABLES.reviewComments),
|
|
49008
49242
|
versionPin: resolveVersionPin(process.env[ACTION_INPUT_ENVIRONMENT_VARIABLES.version])
|
|
49009
|
-
};
|
|
49243
|
+
});
|
|
49010
49244
|
};
|
|
49011
|
-
const
|
|
49245
|
+
const buildScanAttributes = (input) => {
|
|
49012
49246
|
const ruleOverrides = input.userConfig?.rules ?? {};
|
|
49013
49247
|
const ruleKeys = Object.keys(ruleOverrides);
|
|
49014
|
-
return {
|
|
49248
|
+
return withNamespace("scan", {
|
|
49015
49249
|
mode: input.mode,
|
|
49016
49250
|
scope: input.scope,
|
|
49017
49251
|
parallel: input.parallel,
|
|
@@ -49026,12 +49260,18 @@ const buildConfigAttributes = (input) => {
|
|
|
49026
49260
|
ignoredTagCount: input.ignoredTagCount,
|
|
49027
49261
|
hasCustomConfig: input.hasCustomConfig,
|
|
49028
49262
|
rulesConfigured: ruleKeys.length,
|
|
49029
|
-
rulesDisabled: ruleKeys.filter((key) => ruleOverrides[key] === "off").length
|
|
49030
|
-
|
|
49263
|
+
rulesDisabled: ruleKeys.filter((key) => ruleOverrides[key] === "off").length,
|
|
49264
|
+
fileCount: input.result?.scannedFileCount ?? null
|
|
49265
|
+
});
|
|
49031
49266
|
};
|
|
49032
49267
|
/**
|
|
49033
|
-
* Projects a scan into the
|
|
49034
|
-
* per-scan "wide event".
|
|
49268
|
+
* Projects a scan into the namespaced attribute set for its root span — the
|
|
49269
|
+
* canonical per-scan "wide event". Every attribute carries a dotted namespace
|
|
49270
|
+
* that groups it by concept (`scan.*` config, `action.*` CI knobs, `outcome.*`
|
|
49271
|
+
* verdict, `diag.*` findings, `score.*`, `lint.*`, `deadCode.*`,
|
|
49272
|
+
* `supplyChain.*`, `timing.*`, `migration.*`, `baseline.*`) so the attributes
|
|
49273
|
+
* tree up in Sentry's attribute browser and stay filter-/group-/aggregate-able
|
|
49274
|
+
* in the Spans dataset. Pure and exported so the projection (outcome
|
|
49035
49275
|
* precedence, rule/category rollups, CI knobs, config shape) is unit-testable
|
|
49036
49276
|
* without a live Sentry client. `null` values are dropped so absent signals
|
|
49037
49277
|
* never become misleading `"null"` attributes. The run + project base context
|
|
@@ -49040,8 +49280,8 @@ const buildConfigAttributes = (input) => {
|
|
|
49040
49280
|
* those don't carry.
|
|
49041
49281
|
*/
|
|
49042
49282
|
const buildRunEventAttributes = (input) => toSpanAttributes({
|
|
49043
|
-
...
|
|
49044
|
-
...
|
|
49283
|
+
...buildScanAttributes(input),
|
|
49284
|
+
...buildActionAttributes(),
|
|
49045
49285
|
...buildOutcomeAttributes(input)
|
|
49046
49286
|
});
|
|
49047
49287
|
/**
|
|
@@ -50339,6 +50579,7 @@ const NON_BLOCKING_REACT_DOCTOR_COMMAND = [
|
|
|
50339
50579
|
`if ${REACT_DOCTOR_COMMAND} > "$react_doctor_output" 2>&1; then`,
|
|
50340
50580
|
"rm -f \"$react_doctor_output\";",
|
|
50341
50581
|
"else",
|
|
50582
|
+
"cat \"$react_doctor_output\" >&2;",
|
|
50342
50583
|
"rm -f \"$react_doctor_output\";",
|
|
50343
50584
|
`printf "%s\\n" "React Doctor found staged regressions." "Run ${REACT_DOCTOR_COMMAND} to inspect." "Want them fixed? Ask your agent to run that command and resolve the findings." >&2;`,
|
|
50344
50585
|
"fi"
|
|
@@ -52036,10 +52277,11 @@ const rememberInstallAgents = (agents, options = {}) => writePreference(INSTALL_
|
|
|
52036
52277
|
const CLAUDE_AGENT = "claude-code";
|
|
52037
52278
|
const CURSOR_AGENT = "cursor";
|
|
52038
52279
|
const CLAUDE_SETTINGS_RELATIVE_PATH = ".claude/settings.json";
|
|
52039
|
-
const CLAUDE_HOOK_RELATIVE_PATH = ".claude/hooks/react-doctor.
|
|
52040
|
-
const CLAUDE_HOOK_COMMAND = "
|
|
52280
|
+
const CLAUDE_HOOK_RELATIVE_PATH = ".claude/hooks/react-doctor.mjs";
|
|
52281
|
+
const CLAUDE_HOOK_COMMAND = "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/react-doctor.mjs\"";
|
|
52041
52282
|
const CURSOR_HOOKS_RELATIVE_PATH = ".cursor/hooks.json";
|
|
52042
|
-
const CURSOR_HOOK_RELATIVE_PATH = ".cursor/hooks/react-doctor.
|
|
52283
|
+
const CURSOR_HOOK_RELATIVE_PATH = ".cursor/hooks/react-doctor.mjs";
|
|
52284
|
+
const CURSOR_HOOK_COMMAND = "node .cursor/hooks/react-doctor.mjs";
|
|
52043
52285
|
const CURSOR_HOOK_MATCHER = "Write|Edit|MultiEdit|ApplyPatch";
|
|
52044
52286
|
const CURSOR_HOOKS_SCHEMA_VERSION = 1;
|
|
52045
52287
|
const isSupportedAgent = (agent) => agent === CLAUDE_AGENT || agent === CURSOR_AGENT;
|
|
@@ -52071,7 +52313,6 @@ const writeJsonFileWithDirectoryCheck = (filePath, value) => {
|
|
|
52071
52313
|
const writeHookScript = (filePath) => {
|
|
52072
52314
|
ensureDirectoryExists(Path.dirname(filePath));
|
|
52073
52315
|
NFS.writeFileSync(filePath, buildAgentHookScript());
|
|
52074
|
-
NFS.chmodSync(filePath, 493);
|
|
52075
52316
|
};
|
|
52076
52317
|
const hasClaudeHookCommand = (groups) => groups.some((group) => (group.hooks ?? []).some((hook) => hook.command === CLAUDE_HOOK_COMMAND));
|
|
52077
52318
|
const installClaudeHook = (projectRoot) => {
|
|
@@ -52092,7 +52333,7 @@ const installClaudeHook = (projectRoot) => {
|
|
|
52092
52333
|
writeHookScript(hookPath);
|
|
52093
52334
|
return [settingsPath, hookPath];
|
|
52094
52335
|
};
|
|
52095
|
-
const hasCursorHookCommand = (handlers) => handlers.some((handler) => handler.command ===
|
|
52336
|
+
const hasCursorHookCommand = (handlers) => handlers.some((handler) => handler.command === CURSOR_HOOK_COMMAND);
|
|
52096
52337
|
const installCursorHook = (projectRoot) => {
|
|
52097
52338
|
const configPath = Path.join(projectRoot, CURSOR_HOOKS_RELATIVE_PATH);
|
|
52098
52339
|
const hookPath = Path.join(projectRoot, CURSOR_HOOK_RELATIVE_PATH);
|
|
@@ -52100,7 +52341,7 @@ const installCursorHook = (projectRoot) => {
|
|
|
52100
52341
|
const hooks = { ...config.hooks ?? {} };
|
|
52101
52342
|
const postToolUseHooks = [...hooks.postToolUse ?? []];
|
|
52102
52343
|
if (!hasCursorHookCommand(postToolUseHooks)) postToolUseHooks.push({
|
|
52103
|
-
command:
|
|
52344
|
+
command: CURSOR_HOOK_COMMAND,
|
|
52104
52345
|
matcher: CURSOR_HOOK_MATCHER,
|
|
52105
52346
|
timeout: 120
|
|
52106
52347
|
});
|
|
@@ -52114,102 +52355,111 @@ const installCursorHook = (projectRoot) => {
|
|
|
52114
52355
|
return [configPath, hookPath];
|
|
52115
52356
|
};
|
|
52116
52357
|
const buildAgentHookScript = () => [
|
|
52117
|
-
"
|
|
52118
|
-
"
|
|
52358
|
+
"import { readFileSync, writeFileSync, unlinkSync } from 'node:fs';",
|
|
52359
|
+
"import { tmpdir } from 'node:os';",
|
|
52360
|
+
"import { join, dirname } from 'node:path';",
|
|
52361
|
+
"import { fileURLToPath } from 'node:url';",
|
|
52362
|
+
"import { spawnSync } from 'node:child_process';",
|
|
52119
52363
|
"",
|
|
52120
|
-
"
|
|
52121
|
-
"
|
|
52122
|
-
"trap 'rm -f \"$input_file\" \"$output_file\"' EXIT",
|
|
52123
|
-
"cat > \"$input_file\"",
|
|
52364
|
+
"const __filename = fileURLToPath(import.meta.url);",
|
|
52365
|
+
"const __dirname = dirname(__filename);",
|
|
52124
52366
|
"",
|
|
52125
|
-
"
|
|
52126
|
-
"project_root=${CLAUDE_PROJECT_DIR:-}",
|
|
52127
|
-
"if [ -z \"$project_root\" ]; then",
|
|
52128
|
-
" project_root=$(CDPATH= cd \"$script_dir/../..\" && pwd)",
|
|
52129
|
-
"fi",
|
|
52130
|
-
"if ! cd \"$project_root\"; then",
|
|
52131
|
-
" exit 0",
|
|
52132
|
-
"fi",
|
|
52367
|
+
"const EDIT_TOOL_NAMES = new Set(['Edit', 'Write', 'MultiEdit', 'NotebookEdit', 'ApplyPatch']);",
|
|
52133
52368
|
"",
|
|
52134
|
-
"
|
|
52135
|
-
"
|
|
52136
|
-
" return 0",
|
|
52137
|
-
"
|
|
52369
|
+
"const readStdin = () => {",
|
|
52370
|
+
" try {",
|
|
52371
|
+
" return readFileSync(0, 'utf8');",
|
|
52372
|
+
" } catch {",
|
|
52373
|
+
" return '';",
|
|
52374
|
+
" }",
|
|
52375
|
+
"};",
|
|
52138
52376
|
"",
|
|
52139
|
-
"
|
|
52140
|
-
"const fs = require('node:fs');",
|
|
52141
|
-
"const inputPath = process.argv[2];",
|
|
52142
|
-
"const editToolNames = new Set(['Edit', 'Write', 'MultiEdit', 'NotebookEdit', 'ApplyPatch']);",
|
|
52143
|
-
"try {",
|
|
52144
|
-
" const input = JSON.parse(fs.readFileSync(inputPath, 'utf8') || '{}');",
|
|
52377
|
+
"const shouldScan = (input) => {",
|
|
52145
52378
|
" const eventName = input.hook_event_name || input.eventName || input.event_name;",
|
|
52146
52379
|
" if (eventName === 'PostToolBatch') {",
|
|
52147
52380
|
" const toolCalls = Array.isArray(input.tool_calls) ? input.tool_calls : [];",
|
|
52148
|
-
"
|
|
52381
|
+
" return toolCalls.some((toolCall) => EDIT_TOOL_NAMES.has(toolCall.tool_name));",
|
|
52149
52382
|
" }",
|
|
52150
52383
|
" const toolName = input.tool_name || input.toolName || input.tool;",
|
|
52151
|
-
"
|
|
52152
|
-
"}
|
|
52153
|
-
" process.exit(0);",
|
|
52154
|
-
"}",
|
|
52155
|
-
"NODE",
|
|
52156
|
-
"}",
|
|
52384
|
+
" return !toolName || EDIT_TOOL_NAMES.has(toolName);",
|
|
52385
|
+
"};",
|
|
52157
52386
|
"",
|
|
52158
|
-
"
|
|
52159
|
-
"
|
|
52160
|
-
"
|
|
52161
|
-
"
|
|
52162
|
-
"
|
|
52387
|
+
"const runReactDoctor = (outputPath) => {",
|
|
52388
|
+
" // Each candidate is a single shell command string (not an args array):",
|
|
52389
|
+
" // `shell: true` is required to run the Windows `.cmd` shims, and an args",
|
|
52390
|
+
" // array with `shell: true` trips Node's DEP0190. A missing command exits",
|
|
52391
|
+
" // 127 via the shell (no ENOENT error), so fall through on that. With no",
|
|
52392
|
+
" // runner found, exit 0 silently — stdout is parsed as the hook's JSON.",
|
|
52393
|
+
" const commands = [",
|
|
52394
|
+
" './node_modules/.bin/react-doctor --verbose --scope changed --blocking warning --no-score',",
|
|
52395
|
+
" 'react-doctor --verbose --scope changed --blocking warning --no-score',",
|
|
52396
|
+
" 'pnpm dlx react-doctor@latest --verbose --scope changed --blocking warning --no-score',",
|
|
52397
|
+
" 'npx --yes react-doctor@latest --verbose --scope changed --blocking warning --no-score',",
|
|
52398
|
+
" ];",
|
|
52163
52399
|
"",
|
|
52164
|
-
"
|
|
52165
|
-
"
|
|
52166
|
-
"
|
|
52167
|
-
"
|
|
52400
|
+
" for (const command of commands) {",
|
|
52401
|
+
" const result = spawnSync(command, { encoding: 'utf8', shell: true });",
|
|
52402
|
+
" if (result.error?.code === 'ENOENT' || result.status === 127) continue;",
|
|
52403
|
+
" try {",
|
|
52404
|
+
" writeFileSync(outputPath, (result.stdout || '') + (result.stderr || ''));",
|
|
52405
|
+
" } catch {}",
|
|
52406
|
+
" return result.status;",
|
|
52407
|
+
" }",
|
|
52168
52408
|
"",
|
|
52169
|
-
"
|
|
52170
|
-
"
|
|
52171
|
-
" return",
|
|
52172
|
-
" fi",
|
|
52409
|
+
" return 0;",
|
|
52410
|
+
"};",
|
|
52173
52411
|
"",
|
|
52174
|
-
"
|
|
52175
|
-
"
|
|
52176
|
-
"
|
|
52177
|
-
"
|
|
52412
|
+
"const cleanup = (...paths) => {",
|
|
52413
|
+
" for (const path of paths) {",
|
|
52414
|
+
" try { unlinkSync(path); } catch {}",
|
|
52415
|
+
" }",
|
|
52416
|
+
"};",
|
|
52178
52417
|
"",
|
|
52179
|
-
"
|
|
52180
|
-
"
|
|
52181
|
-
"
|
|
52418
|
+
"const main = () => {",
|
|
52419
|
+
" let input;",
|
|
52420
|
+
" try {",
|
|
52421
|
+
" const stdinContent = readStdin();",
|
|
52422
|
+
" input = JSON.parse(stdinContent || '{}');",
|
|
52423
|
+
" } catch {",
|
|
52424
|
+
" input = {};",
|
|
52425
|
+
" }",
|
|
52182
52426
|
"",
|
|
52183
|
-
"if !
|
|
52184
|
-
"
|
|
52185
|
-
"
|
|
52427
|
+
" if (!shouldScan(input)) {",
|
|
52428
|
+
" process.exit(0);",
|
|
52429
|
+
" }",
|
|
52186
52430
|
"",
|
|
52187
|
-
"
|
|
52188
|
-
"
|
|
52189
|
-
"fi",
|
|
52431
|
+
" const projectRoot = process.env.CLAUDE_PROJECT_DIR || join(__dirname, '../..');",
|
|
52432
|
+
" const outputPath = join(tmpdir(), `react-doctor-agent-hook-output-${process.pid}.txt`);",
|
|
52190
52433
|
"",
|
|
52191
|
-
"node - \"$input_file\" \"$output_file\" <<'NODE'",
|
|
52192
|
-
"const fs = require('node:fs');",
|
|
52193
|
-
"const inputPath = process.argv[2];",
|
|
52194
|
-
"const outputPath = process.argv[3];",
|
|
52195
|
-
"const readInput = () => {",
|
|
52196
52434
|
" try {",
|
|
52197
|
-
"
|
|
52435
|
+
" process.chdir(projectRoot);",
|
|
52198
52436
|
" } catch {",
|
|
52199
|
-
"
|
|
52437
|
+
" process.exit(0);",
|
|
52438
|
+
" }",
|
|
52439
|
+
"",
|
|
52440
|
+
" const scanResult = runReactDoctor(outputPath);",
|
|
52441
|
+
" if (scanResult === 0) {",
|
|
52442
|
+
" cleanup(outputPath);",
|
|
52443
|
+
" process.exit(0);",
|
|
52444
|
+
" }",
|
|
52445
|
+
"",
|
|
52446
|
+
" const scanOutput = readFileSync(outputPath, 'utf8').trim();",
|
|
52447
|
+
" cleanup(outputPath);",
|
|
52448
|
+
"",
|
|
52449
|
+
" if (!scanOutput) {",
|
|
52450
|
+
" process.exit(0);",
|
|
52451
|
+
" }",
|
|
52452
|
+
"",
|
|
52453
|
+
" const message = `React Doctor found issues in the changed files. Review this output and fix the regressions before finishing. For confirmed issues that cannot be fixed now, create GitHub issues with the rule, file/line, confidence, impact, and proposed fix.\\n\\n${scanOutput}`;",
|
|
52454
|
+
"",
|
|
52455
|
+
" if (input.hook_event_name === 'PostToolBatch') {",
|
|
52456
|
+
" console.log(JSON.stringify({ hookSpecificOutput: { hookEventName: 'PostToolBatch', additionalContext: message } }));",
|
|
52457
|
+
" } else {",
|
|
52458
|
+
" console.log(JSON.stringify({ additional_context: message }));",
|
|
52200
52459
|
" }",
|
|
52201
52460
|
"};",
|
|
52202
|
-
"
|
|
52203
|
-
"
|
|
52204
|
-
"if (!scanOutput) process.exit(0);",
|
|
52205
|
-
"const message = `React Doctor found issues in the changed files. Review this output and fix the regressions before finishing. For confirmed issues that cannot be fixed now, create GitHub issues with the rule, file/line, confidence, impact, and proposed fix.\\n\\n${scanOutput}`;",
|
|
52206
|
-
"if (input.hook_event_name === 'PostToolBatch') {",
|
|
52207
|
-
" console.log(JSON.stringify({ hookSpecificOutput: { hookEventName: 'PostToolBatch', additionalContext: message } }));",
|
|
52208
|
-
"} else {",
|
|
52209
|
-
" console.log(JSON.stringify({ additional_context: message }));",
|
|
52210
|
-
"}",
|
|
52211
|
-
"NODE",
|
|
52212
|
-
""
|
|
52461
|
+
"",
|
|
52462
|
+
"main();"
|
|
52213
52463
|
].join("\n");
|
|
52214
52464
|
const installReactDoctorAgentHooks = (options) => {
|
|
52215
52465
|
const installedAgents = [];
|
|
@@ -52270,6 +52520,7 @@ const buildReactDoctorHookBlock = () => [
|
|
|
52270
52520
|
"if react_doctor_scan_staged_files > \"$react_doctor_output\" 2>&1; then",
|
|
52271
52521
|
" rm -f \"$react_doctor_output\"",
|
|
52272
52522
|
"else",
|
|
52523
|
+
" cat \"$react_doctor_output\" >&2",
|
|
52273
52524
|
" rm -f \"$react_doctor_output\"",
|
|
52274
52525
|
` printf '%s\\n' "React Doctor found staged regressions." "Run ${REACT_DOCTOR_COMMAND} to inspect." "Want them fixed? Ask your agent to run that command and resolve the findings." >&2`,
|
|
52275
52526
|
"fi",
|
|
@@ -53035,6 +53286,26 @@ const CLI_AGENT_AUTO_FLAGS = {
|
|
|
53035
53286
|
codex: ["--yolo"],
|
|
53036
53287
|
cursor: ["--force"]
|
|
53037
53288
|
};
|
|
53289
|
+
const resolveCursorBundledNode = () => {
|
|
53290
|
+
if (!isWindows) return null;
|
|
53291
|
+
const localAppData = process.env.LOCALAPPDATA;
|
|
53292
|
+
if (!localAppData) return null;
|
|
53293
|
+
const cursorAgentRoot = Path.join(localAppData, "cursor-agent", "versions");
|
|
53294
|
+
try {
|
|
53295
|
+
const versions = NFS.readdirSync(cursorAgentRoot);
|
|
53296
|
+
if (versions.length === 0) return null;
|
|
53297
|
+
const sortedVersions = versions.sort((a, b) => b.localeCompare(a, void 0, { numeric: true }));
|
|
53298
|
+
for (const version of sortedVersions) try {
|
|
53299
|
+
const bundledNodePath = Path.join(cursorAgentRoot, version, "node.exe");
|
|
53300
|
+
const entryScriptPath = Path.join(cursorAgentRoot, version, "index.js");
|
|
53301
|
+
if (NFS.statSync(bundledNodePath).isFile() && NFS.statSync(entryScriptPath).isFile()) return {
|
|
53302
|
+
command: bundledNodePath,
|
|
53303
|
+
args: [entryScriptPath]
|
|
53304
|
+
};
|
|
53305
|
+
} catch {}
|
|
53306
|
+
} catch {}
|
|
53307
|
+
return null;
|
|
53308
|
+
};
|
|
53038
53309
|
const resolveWindowsCmdEntryScript = (command) => {
|
|
53039
53310
|
if (!isWindows) return null;
|
|
53040
53311
|
const pathDirectories = (process.env.PATH ?? "").split(Path.delimiter).filter(Boolean);
|
|
@@ -53054,11 +53325,10 @@ const resolveWindowsCmdEntryScript = (command) => {
|
|
|
53054
53325
|
}
|
|
53055
53326
|
return null;
|
|
53056
53327
|
};
|
|
53057
|
-
const spawnAgent = (command, args, cwd
|
|
53328
|
+
const spawnAgent = (command, args, cwd) => new Promise((resolve, reject) => {
|
|
53058
53329
|
const child = spawn(command, [...args], {
|
|
53059
53330
|
cwd,
|
|
53060
|
-
stdio: "inherit"
|
|
53061
|
-
shell
|
|
53331
|
+
stdio: "inherit"
|
|
53062
53332
|
});
|
|
53063
53333
|
child.on("error", reject);
|
|
53064
53334
|
child.on("close", (code) => resolve(code ?? 0));
|
|
@@ -53067,6 +53337,10 @@ const launchCliAgent = async (agentId, prompt, cwd) => {
|
|
|
53067
53337
|
const binary = CLI_AGENT_BINARIES[agentId];
|
|
53068
53338
|
const agentArgs = [...CLI_AGENT_AUTO_FLAGS[agentId], prompt];
|
|
53069
53339
|
if (isWindows) {
|
|
53340
|
+
if (agentId === "cursor") {
|
|
53341
|
+
const cursorResolution = resolveCursorBundledNode();
|
|
53342
|
+
if (cursorResolution) return spawnAgent(cursorResolution.command, [...cursorResolution.args, ...agentArgs], cwd);
|
|
53343
|
+
}
|
|
53070
53344
|
const entryScript = resolveWindowsCmdEntryScript(binary);
|
|
53071
53345
|
if (entryScript) return spawnAgent(process.execPath, [entryScript, ...agentArgs], cwd);
|
|
53072
53346
|
}
|
|
@@ -54085,6 +54359,17 @@ const warnDeprecatedFailOn = (flags, userConfig) => {
|
|
|
54085
54359
|
cliLogger.warn(`${source} is deprecated; rename it to ${replacement}.`);
|
|
54086
54360
|
};
|
|
54087
54361
|
//#endregion
|
|
54362
|
+
//#region src/cli/utils/warn-ai-training-environment.ts
|
|
54363
|
+
let didWarnAiTraining = false;
|
|
54364
|
+
const warnIfAiTrainingEnvironment = () => {
|
|
54365
|
+
if (didWarnAiTraining) return;
|
|
54366
|
+
const detected = detectAiTrainingEnvironment();
|
|
54367
|
+
if (detected === null) return;
|
|
54368
|
+
didWarnAiTraining = true;
|
|
54369
|
+
process.stderr.write(`${highlighter.warn("react-doctor detected use in an AI or ML pipeline. This use requires written permission under the react-doctor license — contact founders@million.dev to request access.")}\n`);
|
|
54370
|
+
recordCount(METRIC.aiTrainingWarningShown, 1, { environment: detected });
|
|
54371
|
+
};
|
|
54372
|
+
//#endregion
|
|
54088
54373
|
//#region src/cli/utils/validate-mode-flags.ts
|
|
54089
54374
|
const usedDiffAlias = (flags) => flags.diff !== void 0 && flags.diff !== false && flags.diff !== "false" && flags.diff !== "";
|
|
54090
54375
|
const usedScope = (flags) => typeof flags.scope === "string" && flags.scope.length > 0;
|
|
@@ -54168,10 +54453,14 @@ const inspectAction = async (directory, flags) => {
|
|
|
54168
54453
|
const isQuiet = isScoreOnly || isJsonMode;
|
|
54169
54454
|
const requestedDirectory = Path.resolve(directory);
|
|
54170
54455
|
const startTime = performance$1.now();
|
|
54171
|
-
if (isJsonMode)
|
|
54172
|
-
|
|
54173
|
-
|
|
54174
|
-
|
|
54456
|
+
if (isJsonMode) {
|
|
54457
|
+
enableJsonMode({
|
|
54458
|
+
compact: Boolean(flags.jsonCompact),
|
|
54459
|
+
directory: requestedDirectory,
|
|
54460
|
+
outputFile: flags.jsonOut
|
|
54461
|
+
});
|
|
54462
|
+
if (flags.jsonOut) recordCount(METRIC.jsonOutUsed, 1);
|
|
54463
|
+
}
|
|
54175
54464
|
recordCount(METRIC.cliInvoked, 1, { command: "inspect" });
|
|
54176
54465
|
try {
|
|
54177
54466
|
validateModeFlags(flags);
|
|
@@ -54185,6 +54474,7 @@ const inspectAction = async (directory, flags) => {
|
|
|
54185
54474
|
setJsonReportDirectory(resolvedDirectory);
|
|
54186
54475
|
warnDeprecatedFailOn(flags, userConfig);
|
|
54187
54476
|
warnDeprecatedDiff(flags, userConfig);
|
|
54477
|
+
warnIfAiTrainingEnvironment();
|
|
54188
54478
|
if (scanTarget.didRedirectViaRootDir && !isQuiet) {
|
|
54189
54479
|
cliLogger.dim(`Redirected to ${highlighter.info(toRelativePath(resolvedDirectory, requestedDirectory))} via react-doctor config "rootDir".`);
|
|
54190
54480
|
cliLogger.break();
|
|
@@ -55108,6 +55398,14 @@ const applyColorPreference = (argv, env = process.env) => {
|
|
|
55108
55398
|
setColorEnabled(enabled);
|
|
55109
55399
|
};
|
|
55110
55400
|
//#endregion
|
|
55401
|
+
//#region src/cli/utils/ensure-windows-utf8-console.ts
|
|
55402
|
+
const ensureWindowsUtf8Console = () => {
|
|
55403
|
+
if (process.platform !== "win32" || !process.stdout.isTTY) return;
|
|
55404
|
+
try {
|
|
55405
|
+
execFileSync("chcp.com", ["65001"], { stdio: "ignore" });
|
|
55406
|
+
} catch {}
|
|
55407
|
+
};
|
|
55408
|
+
//#endregion
|
|
55111
55409
|
//#region src/cli/utils/exit-gracefully.ts
|
|
55112
55410
|
const exitGracefully = () => {
|
|
55113
55411
|
try {
|
|
@@ -55250,6 +55548,7 @@ const ROOT_FLAG_SPEC = {
|
|
|
55250
55548
|
"--category",
|
|
55251
55549
|
"--changed-files-from",
|
|
55252
55550
|
"--blocking",
|
|
55551
|
+
"--json-out",
|
|
55253
55552
|
"--output-dir",
|
|
55254
55553
|
"--fail-on",
|
|
55255
55554
|
"--project",
|
|
@@ -55440,7 +55739,7 @@ ${highlighter.dim("Learn more:")}
|
|
|
55440
55739
|
${highlighter.info(CANONICAL_GITHUB_URL)}
|
|
55441
55740
|
`;
|
|
55442
55741
|
const collectCategoryOption = (value, previousValues) => [...previousValues ?? [], value];
|
|
55443
|
-
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").option("--dead-code", "enable dead-code analysis (default)").option("--no-dead-code", "skip dead-code analysis (unused files / exports / dependencies, circular imports)").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("-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 to scan/report: full (default), files, changed (only new issues vs base), or lines (only changed lines)").option("--base <ref>", "base git ref for files/changed/lines scope (auto-detected when omitted)").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").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);
|
|
55742
|
+
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").option("--dead-code", "enable dead-code analysis (default)").option("--no-dead-code", "skip dead-code analysis (unused files / exports / dependencies, circular imports)").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("-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 to scan/report: full (default), files, changed (only new issues vs base), or lines (only changed lines)").option("--base <ref>", "base git ref for files/changed/lines scope (auto-detected when omitted)").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").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);
|
|
55444
55743
|
program.action(inspectAction);
|
|
55445
55744
|
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((location, options) => whyAction(location, options));
|
|
55446
55745
|
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 native non-blocking 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(installAction);
|
|
@@ -55465,6 +55764,7 @@ if (process.argv.includes("-V") && !strippedArgv.includes("-V")) {
|
|
|
55465
55764
|
process.exit(0);
|
|
55466
55765
|
}
|
|
55467
55766
|
applyColorPreference(strippedArgv);
|
|
55767
|
+
ensureWindowsUtf8Console();
|
|
55468
55768
|
const argv = normalizeHelpInvocation(strippedArgv, knownCommands);
|
|
55469
55769
|
Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => program.parseAsync(argv)).then(() => flushSentry()).catch(async (error) => {
|
|
55470
55770
|
const isUserError = isExpectedUserError(error);
|
|
@@ -55483,4 +55783,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
|
|
|
55483
55783
|
export {};
|
|
55484
55784
|
|
|
55485
55785
|
//# sourceMappingURL=cli.js.map
|
|
55486
|
-
//# debugId=
|
|
55786
|
+
//# debugId=cf52ec59-cee0-5503-b8f2-a4f98bf77ea4
|