react-doctor 0.9.12 → 0.9.13-dev.fa72869
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/README.md +34 -1
- package/bin/react-doctor.js +1 -9
- package/dist/active-run-trace-dlIZeEvb.js +18 -0
- package/dist/active-scan-abort-registry-DwG6tSPy.js +28 -0
- package/dist/{build-section-divider-BsQvH-7I.js → build-section-divider-DPs8DOje.js} +10 -25
- package/dist/{ci-B4x4RjKD.js → ci-DJX0d-zr.js} +13 -12
- package/dist/cli.js +121 -41
- package/dist/{design-A48PNCf_.js → design-16noMOyv.js} +4 -4
- package/dist/{devtools-DPRVRu3S.js → devtools-CC2PAZ85.js} +4 -4
- package/dist/{exit-gracefully-Bk531kS-.js → exit-gracefully-CmnLSIyy.js} +15 -9
- package/dist/get-east-asian-width-Bv2TD3Xj.js +444 -0
- package/dist/{report-error-Pb2uO8EG.js → handle-error-BoWVnsAX.js} +18 -68
- package/dist/index.d.ts +53 -42
- package/dist/index.js +55119 -54689
- package/dist/{inspect-CwH84aSA.js → inspect-CQjmfc6o.js} +44 -33
- package/dist/{install-sFN_w425.js → install-BI45HWXE.js} +9 -8
- package/dist/{install-doctor-script-DLayXS7q.js → install-doctor-script-Czibd55T.js} +7 -449
- package/dist/{install-github-workflow-Goz-KFqb.js → install-github-workflow-DAW5HCoZ.js} +10 -6
- package/dist/{install-react-doctor-Bq7dfEBI.js → install-react-doctor-0EPCiZSM.js} +47 -45
- package/dist/{run-scan-app-DXBMW54J.js → jsx-runtime-CpHGdrl2.js} +281 -2759
- package/dist/path-format-DnHpmJwG.js +9 -0
- package/dist/{print-branded-header-D_J8Qdsj.js → print-branded-header-D99_D1jG.js} +4 -4
- package/dist/project-analysis-worker.d.ts +1 -0
- package/dist/project-analysis-worker.js +51905 -0
- package/dist/prompt-runtime-scan-url-BXbCor1e.js +240 -0
- package/dist/{read-object-file-Hk79zI91.js → read-object-file-B74hRgMl.js} +4 -4
- package/dist/{record-metric-CsQXJewH.js → record-metric-BcJN2xOB.js} +3257 -6019
- package/dist/report-error-OirqwZb5.js +64 -0
- package/dist/{report-workflow-result-6n_wSqzA.js → report-workflow-result-C4EMosul.js} +5 -5
- package/dist/{resolve-scope-DP6rYlNO.js → resolve-scope-CKnF1u7B.js} +8 -10
- package/dist/{rules-BnIZ4Fq_.js → rules-D-04wsFx.js} +6 -6
- package/dist/run-scan-app-hjGd9Nt9.js +2511 -0
- package/dist/runtime-scan/browser-probe.iife.js +855 -0
- package/dist/runtime-scan-5ucpiIRF.js +770 -0
- package/dist/sanitize-runtime-url-KJQEVCa5.js +76 -0
- package/dist/scan-Busv3qJ_.js +47 -0
- package/dist/semver-DFtnEmN-.js +3219 -0
- package/dist/{should-fail-scan-gate-DzZuTxim.js → should-fail-scan-gate-D1mS86Ff.js} +160 -184
- package/dist/{should-skip-prompts-D1GWG2qN.js → should-skip-prompts-DkasjzpQ.js} +4 -4
- package/dist/should-use-tui-B5bj9qhm.js +25 -0
- package/dist/skills/improve-threejs/SKILL.md +102 -0
- package/dist/skills/performance/SKILL.md +113 -0
- package/dist/skills/react-doctor/SKILL.md +6 -0
- package/dist/{unref-stdin-0a_0uwC6.js → telemetry-runtime-DFNjke5Y.js} +57 -33
- package/dist/unref-stdin-SK3WfwxS.js +12 -0
- package/dist/{version-Bo5QDp7F.js → version-DKEaWdQZ.js} +4 -4
- package/dist/{why-BFYbtnm8.js → why-BP622crq.js} +4 -4
- package/dist/with-run-span-B9bjgvks.js +110 -0
- package/package.json +9 -10
- package/dist/chunk-q7NCDQ7-.js +0 -28
- package/dist/lsp.js +0 -81145
- package/dist/scan-CnnXz78m.js +0 -53
|
@@ -1,21 +1,25 @@
|
|
|
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]="
|
|
3
|
-
import { $
|
|
4
|
-
import {
|
|
5
|
-
import { n as buildFooterLinkLines,
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { a as
|
|
9
|
-
import {
|
|
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]="c6a4440d-8008-5253-b2d5-e7f83df2b0a4")}catch(e){}}();
|
|
3
|
+
import { $ as Linter, A as SCAN_RESULT_CACHE_FILENAME, An as gen, At as discoverSupportedSubprojects, B as isNonInteractiveEnvironment, Bn as sync, Cn as error, Ct as collectSourceFileCountsByDirectory, Dn as Diagnostic, Dt as createNodeReadFileLinesSync, En as warn, Et as countOptInProjectRuleSelections, Fn as provide, G as isCiEnvironment, Gt as hashFileContents, Ht as groupBy, In as provideContext, J as Config, Jt as isPathInsideDirectory, K as isCiOrCodingAgentEnvironment, Kn as succeed, Kt as highlighter, Ln as provideService, Mn as map, Mt as filterPathsOutsideDirectories, Nn as orElseSucceed, Nt as filterSourceFiles, On as decodeUnknownSync, Ot as createOxlintSpawnSlots, Pn as promise, Q as LintPartialFailures, Qt as isScanComplete, Rn as runPromise, S as BASELINE_FILES_TEMP_DIR_PREFIX, Sn as Console, T as METRIC, Tn as log, Tt as computeDiagnosticDelta, U as ACTION_INPUT_ENVIRONMENT_VARIABLES, Ut as hasPublishedFixRecipe, Vn as try_, W as detectRunnerOs, Wn as mergeAll, X as Git, Xt as isReactDoctorError, Y as Files, Zn as ParentSpan, _ as VERSION, _n as shouldUseMaintainabilityLayer, _t as buildRuleSeverityControls, an as materializeSourceTree, ar as DIAGNOSTIC_CATEGORY_BUCKETS, at as Progress, bt as canonicalizeUserRuleKey, cn as resolveGithubActionsScoreMetadata, ct as Score, d as isJsonModeActive, dn as resolveScanConcurrency, dr as JSX_DUPLICATION_SOURCE_FILE_PATTERN, dt as SupplyChain, en as listSourceFiles, et as Maintainability, fn as resolveScanTarget, fr as JSX_FILE_PATTERN, gt as buildRuleDocsUrl, hr as SHARE_BASE_URL, in as mapWithConcurrency, it as PerFileLintCacheEnabled, jt as filterDiagnosticsForSurface, l as getRunId, ln as resolveLintBatchOrdering, lr as GIT_SHOW_MAX_BUFFER_BYTES, lt as SidecarLintCacheEnabled, mn as runInspect, mr as OXLINT_NODE_REQUIREMENT, mt as atomicWriteJson, n as recordDistribution, nn as listWorkspacePackages, nt as OxlintConcurrency, on as mergeReactDoctorConfigs, ot as Project, pn as restoreLegacyThrow, pr as MILLISECONDS_PER_SECOND, qt as isMonorepoRoot, r as toSpanAttributes, rt as OxlintSpawnSlots, sn as remainingDeadlineBudgetMs, sr as ENTERPRISE_CONTACT_URL, st as Reporter, t as recordCount, tn as listSourceFilesCooperative, tr as CI_URL, tt as NodeResolver, un as resolveReactDoctorCacheDir, ur as HTML_FILE_PATTERN, v as makeNoopConsole, vn as summarizeDiagnostics, vt as buildSkippedChecks, wt as computeConfigFingerprint, xn as yieldToEventLoop, xr as isFile, xt as classifyFileContext, yr as isDirectory, yt as calculateScore, z as CliInputError, zn as runSync, zt as getDiagnosticRuleIdentity } from "./record-metric-BcJN2xOB.js";
|
|
4
|
+
import { t as getTelemetryContext } from "./telemetry-runtime-DFNjke5Y.js";
|
|
5
|
+
import { n as buildFooterLinkLines, t as buildSectionDivider } from "./build-section-divider-DPs8DOje.js";
|
|
6
|
+
import { t as activeScanAbortRegistry } from "./active-scan-abort-registry-DwG6tSPy.js";
|
|
7
|
+
import { t as toForwardSlashes } from "./path-format-DnHpmJwG.js";
|
|
8
|
+
import { C as isRecord, M as cliLogger, S as getPackageJsonPath, a as installReactDoctorScriptStep, c as spinner, k as runGit, n as findNearestPackageDirectory, o as isSpinnerSilent, s as setSpinnerSilent } from "./install-doctor-script-Czibd55T.js";
|
|
9
|
+
import { d as detectDefaultBranch, l as prompts, r as installReactDoctorWorkflow, u as isCommandAvailable } from "./install-github-workflow-DAW5HCoZ.js";
|
|
10
|
+
import { a as isValidBlockingLevel, c as openWorkflowPullRequest, l as stageWorkflowFile, o as pickBlockingLevel } from "./resolve-scope-CKnF1u7B.js";
|
|
11
|
+
import { n as resetSentryRunState, r as withRunSpan, t as recordSentryProjectContext } from "./with-run-span-B9bjgvks.js";
|
|
12
|
+
import { h as detectAvailableAgents, t as reportWorkflowResult } from "./report-workflow-result-C4EMosul.js";
|
|
10
13
|
import { createRequire } from "node:module";
|
|
11
14
|
import { spawn } from "node:child_process";
|
|
12
15
|
import * as path$1 from "node:path";
|
|
13
16
|
import path from "node:path";
|
|
14
|
-
import * as fs$
|
|
17
|
+
import * as fs$2 from "node:fs";
|
|
15
18
|
import fs, { mkdtempSync, rmSync } from "node:fs";
|
|
16
|
-
import crypto, { randomUUID } from "node:crypto";
|
|
17
19
|
import ts from "typescript";
|
|
20
|
+
import crypto, { randomUUID } from "node:crypto";
|
|
18
21
|
import { tmpdir } from "node:os";
|
|
22
|
+
import * as fs$1 from "node:fs/promises";
|
|
19
23
|
import { performance } from "node:perf_hooks";
|
|
20
24
|
//#region src/cli/utils/apply-observability.ts
|
|
21
25
|
/**
|
|
@@ -83,7 +87,10 @@ const buildSpinnerProgressHandle = (text) => {
|
|
|
83
87
|
*/
|
|
84
88
|
const buildRuntimeLayers = (input) => {
|
|
85
89
|
const linterLayer = input.shouldSkipLint ? Linter.layerOf([]) : Linter.layerOxlint;
|
|
86
|
-
const
|
|
90
|
+
const maintainabilityLayer = shouldUseMaintainabilityLayer({
|
|
91
|
+
shouldRunDuplicateJsx: input.shouldRunDeadCode,
|
|
92
|
+
userConfig: input.userConfig
|
|
93
|
+
}) ? Maintainability.layerNode : Maintainability.layerOf([]);
|
|
87
94
|
const scoreLayer = input.shouldComputeScore ? Score.layerHttp : Score.layerOf(null);
|
|
88
95
|
const supplyChainLayer = input.shouldRunSupplyChain ? SupplyChain.layerNode : SupplyChain.layerOf([]);
|
|
89
96
|
const projectLayer = input.projectInfoOverride === void 0 ? Project.layerNode : Project.layerOf(input.projectInfoOverride);
|
|
@@ -93,112 +100,11 @@ const buildRuntimeLayers = (input) => {
|
|
|
93
100
|
config: input.userConfig,
|
|
94
101
|
resolvedDirectory: input.directory,
|
|
95
102
|
configSourceDirectory: input.configSourceDirectory
|
|
96
|
-
}) : Config.layerNode, Files.layerNode, Git.layerNode, linterLayer, LintPartialFailures.layerLive,
|
|
103
|
+
}) : Config.layerNode, Files.layerNode, Git.layerNode, linterLayer, LintPartialFailures.layerLive, maintainabilityLayer, progressLayer, reporterLayer, scoreLayer, supplyChainLayer);
|
|
97
104
|
const layersWithConcurrency = input.oxlintConcurrency === void 0 ? baseLayers : mergeAll(baseLayers, succeed(OxlintConcurrency, input.oxlintConcurrency));
|
|
98
105
|
return input.oxlintSpawnSlots === void 0 ? layersWithConcurrency : mergeAll(layersWithConcurrency, succeed(OxlintSpawnSlots, input.oxlintSpawnSlots));
|
|
99
106
|
};
|
|
100
107
|
//#endregion
|
|
101
|
-
//#region src/cli/utils/with-run-span.ts
|
|
102
|
-
/**
|
|
103
|
-
* Ends the run span without letting a telemetry failure change the run's
|
|
104
|
-
* outcome — the invariant every other emit site here already holds.
|
|
105
|
-
*
|
|
106
|
-
* The failure path is the reason this matters most: a throw from `end` there
|
|
107
|
-
* would replace the user's original scan error, so a project-config mistake
|
|
108
|
-
* could surface as an exporter stack trace, be reclassified as a crash, and get
|
|
109
|
-
* reported as one.
|
|
110
|
-
*/
|
|
111
|
-
const endSpanSafely = (span, exit) => {
|
|
112
|
-
try {
|
|
113
|
-
span.end(BigInt(Date.now()) * NANOSECONDS_PER_MILLISECOND, exit);
|
|
114
|
-
} catch {}
|
|
115
|
-
};
|
|
116
|
-
/**
|
|
117
|
-
* Clears the module-level run-scoped telemetry state — the current scanned
|
|
118
|
-
* project and the active run trace. `inspect()` calls this at the start of every
|
|
119
|
-
* run and again after a clean one, so a prior or just-finished scan can't attach
|
|
120
|
-
* its project tags / trace to a later run or to a non-scan error (e.g.
|
|
121
|
-
* inspectAction's post-loop finalize/handoff steps). A thrown scan error skips
|
|
122
|
-
* the post-run reset, leaving the state for the command catch to attribute and
|
|
123
|
-
* link the crash. Concurrent batch members (`concurrentScan`) never touch this
|
|
124
|
-
* state — they neither write nor reset it. Safe to call when telemetry is off
|
|
125
|
-
* (the refs are read only when an event is built).
|
|
126
|
-
*/
|
|
127
|
-
const resetSentryRunState = () => {
|
|
128
|
-
setSentryProjectInfo(null);
|
|
129
|
-
setActiveRunTrace(null);
|
|
130
|
-
};
|
|
131
|
-
/**
|
|
132
|
-
* Runs an inspect invocation inside a root span so each `react-doctor` run is a
|
|
133
|
-
* first-class trace with timing and the run snapshot as attributes. The span is
|
|
134
|
-
* handed to `run` so the scan's Effect spans parent under it and so the wide
|
|
135
|
-
* event can be stamped onto it once the outcome is known.
|
|
136
|
-
*
|
|
137
|
-
* A no-op pass-through when telemetry is off (`--no-telemetry`, tests, or an
|
|
138
|
-
* unconfigured build) — `run` receives `undefined` and no span is created, so
|
|
139
|
-
* there's no added exit latency.
|
|
140
|
-
*
|
|
141
|
-
* The span is created against the shared telemetry context rather than a
|
|
142
|
-
* freshly-provided layer, so it lives in the same tracer as the scan program's
|
|
143
|
-
* spans; see `telemetry-runtime.ts` for why that matters. It is started and
|
|
144
|
-
* ended manually because the body it wraps is a promise-returning imperative
|
|
145
|
-
* function, not an Effect — the same shape the Sentry inactive span had.
|
|
146
|
-
*
|
|
147
|
-
* While the span runs, its trace context is recorded as the active run trace so
|
|
148
|
-
* `reportErrorToSentry` can attach a crash thrown during the scan back to this
|
|
149
|
-
* trace (errors surface in the command catch, after the span has ended).
|
|
150
|
-
*
|
|
151
|
-
* A `concurrentScan` (one member of the CLI's multi-project pool) still gets its
|
|
152
|
-
* own root span, but skips recording the active run trace — the module-level
|
|
153
|
-
* handle has single-scan semantics, and overlapping writers would link a crash
|
|
154
|
-
* to an arbitrary sibling's trace.
|
|
155
|
-
*/
|
|
156
|
-
const withRunSpan = async (run, options = {}) => {
|
|
157
|
-
const telemetryContext = getTelemetryContext();
|
|
158
|
-
if (telemetryContext === null) return run(void 0);
|
|
159
|
-
const { tags } = buildSentryScope();
|
|
160
|
-
const command = typeof tags.command === "string" ? tags.command : "inspect";
|
|
161
|
-
let rootSpan;
|
|
162
|
-
try {
|
|
163
|
-
rootSpan = runSync(makeSpan(`react-doctor ${command}`, { attributes: toSpanAttributes(tags) }).pipe(provideContext(telemetryContext)));
|
|
164
|
-
} catch {
|
|
165
|
-
return run(void 0);
|
|
166
|
-
}
|
|
167
|
-
recordRunTraceId(rootSpan.traceId);
|
|
168
|
-
if (options.concurrentScan !== true) setActiveRunTrace({
|
|
169
|
-
traceId: rootSpan.traceId,
|
|
170
|
-
spanId: rootSpan.spanId,
|
|
171
|
-
sampled: rootSpan.sampled
|
|
172
|
-
});
|
|
173
|
-
try {
|
|
174
|
-
const result = await run(rootSpan);
|
|
175
|
-
endSpanSafely(rootSpan, void_);
|
|
176
|
-
return result;
|
|
177
|
-
} catch (error) {
|
|
178
|
-
endSpanSafely(rootSpan, fail(error));
|
|
179
|
-
throw error;
|
|
180
|
-
}
|
|
181
|
-
};
|
|
182
|
-
/**
|
|
183
|
-
* Records the scanned project (discovered in the `beforeLint` hook): remembers
|
|
184
|
-
* it for the lazy Sentry error-capture path (`buildSentryScope` folds it into
|
|
185
|
-
* exception events) and for per-emit metric attributes, and sets it on the run's
|
|
186
|
-
* root span so the trace carries the project shape too. Always cheap — the span
|
|
187
|
-
* attributes are skipped when `rootSpan` is absent (telemetry off), and storing
|
|
188
|
-
* the info is a plain assignment.
|
|
189
|
-
*
|
|
190
|
-
* A `concurrentScan` only sets the span attributes: the module-level project ref
|
|
191
|
-
* has single-scan semantics, and overlapping writers would stamp events and
|
|
192
|
-
* metrics with an arbitrary sibling's project. Wide events keep full attribution
|
|
193
|
-
* (they ride the span); per-emit metrics simply omit the project shape during a
|
|
194
|
-
* concurrent batch (absent, never wrong).
|
|
195
|
-
*/
|
|
196
|
-
const recordSentryProjectContext = (projectInfo, rootSpan, options = {}) => {
|
|
197
|
-
if (options.concurrentScan !== true) setSentryProjectInfo(projectInfo);
|
|
198
|
-
if (rootSpan === void 0) return;
|
|
199
|
-
for (const [key, value] of Object.entries(toSpanAttributes(buildSentryProjectContext(projectInfo).tags))) rootSpan.attribute(key, value);
|
|
200
|
-
};
|
|
201
|
-
//#endregion
|
|
202
108
|
//#region src/cli/utils/diagnostic-grouping.ts
|
|
203
109
|
const buildRulePriorityMap = (scores) => {
|
|
204
110
|
const rulePriority = /* @__PURE__ */ new Map();
|
|
@@ -309,7 +215,7 @@ const recordScanMetrics = (input) => {
|
|
|
309
215
|
recordCount(METRIC.scanCompleted, 1, {
|
|
310
216
|
mode: input.mode,
|
|
311
217
|
lint: input.lint,
|
|
312
|
-
|
|
218
|
+
maintainability: input.deadCode,
|
|
313
219
|
parallel: input.parallel,
|
|
314
220
|
scoreOnly: input.scoreOnly,
|
|
315
221
|
didLintFail: input.didLintFail,
|
|
@@ -349,7 +255,7 @@ const recordScanMetrics = (input) => {
|
|
|
349
255
|
else if (!input.noScore && !input.didLintFail && !input.didDeadCodeFail) recordCount(METRIC.scoreUnavailable, 1, { mode: input.mode });
|
|
350
256
|
if (input.baselineDegraded) recordCount(METRIC.baselineDegraded, 1);
|
|
351
257
|
if (input.didLintFail) recordCount(METRIC.lintFailed, 1, { reasonKind: input.lintFailureReasonKind });
|
|
352
|
-
if (input.didDeadCodeFail) recordCount(METRIC.
|
|
258
|
+
if (input.didDeadCodeFail) recordCount(METRIC.maintainabilityFailed, 1);
|
|
353
259
|
for (const check of result.skippedChecks) recordCount(METRIC.scanCheckSkipped, 1, {
|
|
354
260
|
check,
|
|
355
261
|
reason: result.skippedCheckReasons?.[check] ?? null
|
|
@@ -493,10 +399,16 @@ const CACHE_DISABLED_VALUES = new Set(["1", "true"]);
|
|
|
493
399
|
const TOOLCHAIN_PACKAGE_SPECIFIERS = [
|
|
494
400
|
"oxlint/package.json",
|
|
495
401
|
"oxlint-plugin-react-doctor/package.json",
|
|
496
|
-
"deslop-js/package.json",
|
|
497
402
|
"eslint-plugin-react-hooks/package.json"
|
|
498
403
|
];
|
|
499
404
|
const bundledRequire = createRequire(import.meta.url);
|
|
405
|
+
const RULE_PLUGIN_CONTENT_FINGERPRINT = (() => {
|
|
406
|
+
try {
|
|
407
|
+
return `oxlint-plugin-react-doctor#fingerprint=${hashFileContents(bundledRequire.resolve("oxlint-plugin-react-doctor")) ?? "unreadable"}`;
|
|
408
|
+
} catch {
|
|
409
|
+
return "oxlint-plugin-react-doctor#fingerprint=unresolved";
|
|
410
|
+
}
|
|
411
|
+
})();
|
|
500
412
|
const normalizeForStableJson = (value) => {
|
|
501
413
|
if (value === null) return null;
|
|
502
414
|
if (value === void 0) return void 0;
|
|
@@ -632,12 +544,12 @@ const resolveCacheFilePath = (projectDirectory) => path.join(resolveReactDoctorC
|
|
|
632
544
|
const readPersistedCache = (cacheFilePath) => {
|
|
633
545
|
try {
|
|
634
546
|
const parsed = JSON.parse(fs.readFileSync(cacheFilePath, "utf8"));
|
|
635
|
-
if (!isRecord(parsed) || parsed.version !==
|
|
636
|
-
version:
|
|
547
|
+
if (!isRecord(parsed) || parsed.version !== 7) return {
|
|
548
|
+
version: 7,
|
|
637
549
|
entries: []
|
|
638
550
|
};
|
|
639
551
|
if (!Array.isArray(parsed.entries)) return {
|
|
640
|
-
version:
|
|
552
|
+
version: 7,
|
|
641
553
|
entries: []
|
|
642
554
|
};
|
|
643
555
|
const entries = [];
|
|
@@ -652,12 +564,12 @@ const readPersistedCache = (cacheFilePath) => {
|
|
|
652
564
|
});
|
|
653
565
|
}
|
|
654
566
|
return {
|
|
655
|
-
version:
|
|
567
|
+
version: 7,
|
|
656
568
|
entries
|
|
657
569
|
};
|
|
658
570
|
} catch {
|
|
659
571
|
return {
|
|
660
|
-
version:
|
|
572
|
+
version: 7,
|
|
661
573
|
entries: []
|
|
662
574
|
};
|
|
663
575
|
}
|
|
@@ -665,7 +577,7 @@ const readPersistedCache = (cacheFilePath) => {
|
|
|
665
577
|
/**
|
|
666
578
|
* The global cache off-switch (`REACT_DOCTOR_NO_CACHE`), which disables every
|
|
667
579
|
* cache subsystem: this whole-repo scan cache plus core's per-file lint,
|
|
668
|
-
* sidecar
|
|
580
|
+
* sidecar caches (their `Context.Reference` defaults read the
|
|
669
581
|
* same variable). Exported for the wide event's `cache.temperature`
|
|
670
582
|
* derivation, which reports `"disabled"` instead of `"cold"` when the switch
|
|
671
583
|
* is on. Granular knobs (`REACT_DOCTOR_NO_FILE_CACHE`, …) are deliberately
|
|
@@ -688,7 +600,7 @@ const fileFingerprint = (filePath) => {
|
|
|
688
600
|
return null;
|
|
689
601
|
}
|
|
690
602
|
};
|
|
691
|
-
const
|
|
603
|
+
const resolveScanResultToolchainFingerprint = (nodeBinaryPath) => {
|
|
692
604
|
const fingerprints = [];
|
|
693
605
|
if (nodeBinaryPath !== null) fingerprints.push(nodeBinaryPath === process.execPath ? `node=${process.version}` : `node=${fileFingerprint(nodeBinaryPath) ?? "unreadable"}`);
|
|
694
606
|
for (const specifier of TOOLCHAIN_PACKAGE_SPECIFIERS) try {
|
|
@@ -698,6 +610,7 @@ const resolveToolchainFingerprint = (nodeBinaryPath) => {
|
|
|
698
610
|
} catch {
|
|
699
611
|
fingerprints.push(`${specifier}=missing`);
|
|
700
612
|
}
|
|
613
|
+
fingerprints.push(RULE_PLUGIN_CONTENT_FINGERPRINT);
|
|
701
614
|
return fingerprints;
|
|
702
615
|
};
|
|
703
616
|
const buildScanResultCacheKey = (input) => {
|
|
@@ -707,14 +620,14 @@ const buildScanResultCacheKey = (input) => {
|
|
|
707
620
|
if (repositoryIdentity === null) return null;
|
|
708
621
|
if (stringifyStableJson(input.userConfig) === null) return null;
|
|
709
622
|
const cacheKeyJson = stringifyStableJson({
|
|
710
|
-
schemaVersion:
|
|
623
|
+
schemaVersion: 7,
|
|
711
624
|
projectIdentity: resolveProjectIdentity(input.projectDirectory),
|
|
712
625
|
headSha: repositoryIdentity.headSha,
|
|
713
626
|
worktreeFingerprint: repositoryIdentity.worktreeFingerprint,
|
|
714
627
|
dotenvFingerprint: resolveDotenvFingerprint(input.projectDirectory),
|
|
715
628
|
reactDoctorVersion: input.version,
|
|
716
629
|
nodeVersion: process.version,
|
|
717
|
-
toolchainFingerprint:
|
|
630
|
+
toolchainFingerprint: resolveScanResultToolchainFingerprint(input.nodeBinaryPath),
|
|
718
631
|
configFingerprint: computeConfigFingerprint(input.projectDirectory, input.version),
|
|
719
632
|
hasConfigOverride: input.hasConfigOverride,
|
|
720
633
|
configSourceDirectory: input.configSourceDirectory,
|
|
@@ -755,7 +668,7 @@ const createScanResultCache = (projectDirectory) => {
|
|
|
755
668
|
for (const entry of readPersistedCache(cacheFilePath).entries) mergedEntries.set(entry.key, entry);
|
|
756
669
|
for (const entry of entries.values()) mergedEntries.set(entry.key, entry);
|
|
757
670
|
atomicWriteJson(cacheFilePath, {
|
|
758
|
-
version:
|
|
671
|
+
version: 7,
|
|
759
672
|
entries: [...mergedEntries.values()].sort((firstEntry, secondEntry) => secondEntry.createdAtMs - firstEntry.createdAtMs).slice(0, 20)
|
|
760
673
|
});
|
|
761
674
|
};
|
|
@@ -790,8 +703,7 @@ const shouldBlockCi = (diagnostics, blockingLevel) => {
|
|
|
790
703
|
/**
|
|
791
704
|
* Lowercase, key-safe form of a rule category for the `diag.category.*`
|
|
792
705
|
* telemetry attribute namespace (categories carry spaces / capitals, e.g.
|
|
793
|
-
* "Performance" → `performance`, "Dead Code" → `dead_code`).
|
|
794
|
-
* CLI run event and the editor LSP wide event so both namespace identically.
|
|
706
|
+
* "Performance" → `performance`, "Dead Code" → `dead_code`).
|
|
795
707
|
*/
|
|
796
708
|
const toCategoryKey = (category) => category.toLowerCase().replace(/[^a-z0-9]+/g, "_");
|
|
797
709
|
//#endregion
|
|
@@ -821,12 +733,6 @@ const readEnvBoolean = (name) => {
|
|
|
821
733
|
return value.toLowerCase() === "true" || value === "1";
|
|
822
734
|
};
|
|
823
735
|
const ratioOf = (numerator, denominator) => denominator != null && denominator > 0 ? (numerator ?? 0) / denominator : null;
|
|
824
|
-
const resolveDeadCodeReuseRatio = (result) => {
|
|
825
|
-
if (result.deadCodeCacheHit === true) return 1;
|
|
826
|
-
const summaryTotal = (result.deadCodeSummaryCacheHits ?? 0) + (result.deadCodeSummaryCacheMisses ?? 0);
|
|
827
|
-
if (summaryTotal > 0) return (result.deadCodeSummaryCacheHits ?? 0) / summaryTotal;
|
|
828
|
-
return result.deadCodeCacheHit === false ? 0 : null;
|
|
829
|
-
};
|
|
830
736
|
/**
|
|
831
737
|
* One queryable cache temperature per scan, derived from the whole stack:
|
|
832
738
|
*
|
|
@@ -834,8 +740,7 @@ const resolveDeadCodeReuseRatio = (result) => {
|
|
|
834
740
|
* `wholeRepoCacheHit` flag from the CLI's cachedPayload
|
|
835
741
|
* branch; no scan work ran).
|
|
836
742
|
* - `"warm"` — any incremental reuse: per-file lint hits, sidecar
|
|
837
|
-
* replays
|
|
838
|
-
* summary-cache hits.
|
|
743
|
+
* replays.
|
|
839
744
|
* - `"disabled"` — zero reuse because the global `REACT_DOCTOR_NO_CACHE`
|
|
840
745
|
* off-switch is on. Granular per-cache opt-outs
|
|
841
746
|
* (`REACT_DOCTOR_NO_FILE_CACHE`, …) still read warm/cold,
|
|
@@ -844,7 +749,7 @@ const resolveDeadCodeReuseRatio = (result) => {
|
|
|
844
749
|
*
|
|
845
750
|
* `cache.warmth` is the headline reuse magnitude in [0, 1]: the plain mean of
|
|
846
751
|
* the subsystem reuse fractions known this run (lint hit ratio, sidecar
|
|
847
|
-
* replay ratio
|
|
752
|
+
* replay ratio), skipping subsystems that never consulted a
|
|
848
753
|
* cache; `1` on turbo, dropped when nothing consulted any cache. Deliberately
|
|
849
754
|
* unweighted — the per-subsystem dims stay the precise signal; warmth is the
|
|
850
755
|
* p50/p90-able summary. Emitted only on the success path.
|
|
@@ -857,11 +762,7 @@ const buildCacheAttributes = (input) => {
|
|
|
857
762
|
warmth: 1
|
|
858
763
|
});
|
|
859
764
|
const result = input.result;
|
|
860
|
-
const subsystemReuseRatios = [
|
|
861
|
-
ratioOf(result.lintCacheHitFileCount, result.lintCacheTotalFileCount),
|
|
862
|
-
ratioOf(result.lintSidecarReplayedFileCount, result.lintSidecarTotalFileCount),
|
|
863
|
-
resolveDeadCodeReuseRatio(result)
|
|
864
|
-
];
|
|
765
|
+
const subsystemReuseRatios = [ratioOf(result.lintCacheHitFileCount, result.lintCacheTotalFileCount), ratioOf(result.lintSidecarReplayedFileCount, result.lintSidecarTotalFileCount)];
|
|
865
766
|
let knownSubsystemCount = 0;
|
|
866
767
|
let reuseRatioSum = 0;
|
|
867
768
|
for (const reuseRatio of subsystemReuseRatios) {
|
|
@@ -1013,13 +914,7 @@ const buildOutcomeAttributes = (input) => {
|
|
|
1013
914
|
sidecarTotalFiles: result.lintSidecarTotalFileCount,
|
|
1014
915
|
sidecarReplayRatio: ratioOf(result.lintSidecarReplayedFileCount, result.lintSidecarTotalFileCount)
|
|
1015
916
|
}),
|
|
1016
|
-
...withNamespace("
|
|
1017
|
-
failed: input.didDeadCodeFail,
|
|
1018
|
-
overlapped: input.deadCodeOverlapped,
|
|
1019
|
-
cacheHit: result.deadCodeCacheHit,
|
|
1020
|
-
summaryCacheHits: result.deadCodeSummaryCacheHits,
|
|
1021
|
-
summaryCacheMisses: result.deadCodeSummaryCacheMisses
|
|
1022
|
-
}),
|
|
917
|
+
...withNamespace("maintainability", { failed: input.didDeadCodeFail }),
|
|
1023
918
|
...withNamespace("supplyChain", { overlapTimedOut: input.supplyChainOverlapTimedOut }),
|
|
1024
919
|
...withNamespace("securityScan", { failed: input.securityScanFailed }),
|
|
1025
920
|
...withNamespace("timing", {
|
|
@@ -1062,7 +957,7 @@ const buildScanAttributes = (input) => {
|
|
|
1062
957
|
workerCount: input.workerCount,
|
|
1063
958
|
maxDurationMs: input.maxDurationMs,
|
|
1064
959
|
lint: input.lint,
|
|
1065
|
-
|
|
960
|
+
maintainability: input.deadCode,
|
|
1066
961
|
supplyChain: input.supplyChain,
|
|
1067
962
|
scoreOnly: input.scoreOnly,
|
|
1068
963
|
noScore: input.noScore,
|
|
@@ -1073,6 +968,7 @@ const buildScanAttributes = (input) => {
|
|
|
1073
968
|
hasCustomConfig: input.hasCustomConfig,
|
|
1074
969
|
rulesConfigured: ruleKeys.length,
|
|
1075
970
|
rulesDisabled: ruleKeys.filter((key) => ruleOverrides[key] === "off").length,
|
|
971
|
+
projectAnalysisRuleCount: countOptInProjectRuleSelections(buildRuleSeverityControls(input.userConfig)),
|
|
1076
972
|
fileCount: input.result?.scannedFileCount,
|
|
1077
973
|
nonJsxFileCount: input.result?.analyzedFiles?.filter((filePath) => !JSX_FILE_PATTERN.test(filePath)).length,
|
|
1078
974
|
htmlFileCount: input.result?.analyzedFiles?.filter((filePath) => HTML_FILE_PATTERN.test(filePath)).length,
|
|
@@ -1083,7 +979,7 @@ const buildScanAttributes = (input) => {
|
|
|
1083
979
|
* Projects a scan into the namespaced attribute set for its root span — the
|
|
1084
980
|
* canonical per-scan "wide event". Every attribute carries a dotted namespace
|
|
1085
981
|
* that groups it by concept (`scan.*` config, `action.*` CI knobs, `outcome.*`
|
|
1086
|
-
* verdict, `diag.*` findings, `score.*`, `lint.*`, `
|
|
982
|
+
* verdict, `diag.*` findings, `score.*`, `lint.*`, `maintainability.*`, `cache.*`,
|
|
1087
983
|
* `supplyChain.*`, `timing.*`, `migration.*`, `baseline.*`) so the attributes
|
|
1088
984
|
* tree up in Sentry's attribute browser and stay filter-/group-/aggregate-able
|
|
1089
985
|
* in the Spans dataset. Pure and exported so the projection (outcome
|
|
@@ -1322,7 +1218,7 @@ const buildNoScoreMessage = (input) => {
|
|
|
1322
1218
|
reason = input.disabledMessage ?? "Score disabled by --no-score.";
|
|
1323
1219
|
break;
|
|
1324
1220
|
case "analysis-incomplete":
|
|
1325
|
-
reason = "Score not shown because lint or
|
|
1221
|
+
reason = "Score not shown because lint or maintainability analysis could not complete.";
|
|
1326
1222
|
break;
|
|
1327
1223
|
case "api-unavailable":
|
|
1328
1224
|
reason = "Score unavailable (could not reach the score API).";
|
|
@@ -1370,7 +1266,7 @@ const formatRuleSummary = (ruleKey, ruleDiagnostics) => {
|
|
|
1370
1266
|
const ruleDumpFileName = (ruleKey) => ruleKey.replace(/\//g, "--") + ".txt";
|
|
1371
1267
|
const readPreviousRuleDumpFileNames = (directory) => {
|
|
1372
1268
|
try {
|
|
1373
|
-
const previous = JSON.parse(fs$
|
|
1269
|
+
const previous = JSON.parse(fs$2.readFileSync(path$1.join(directory, "diagnostics.json"), "utf8"));
|
|
1374
1270
|
return new Set(previous.filter((d) => typeof d?.plugin === "string" && typeof d?.rule === "string").map((d) => ruleDumpFileName(`${d.plugin}/${d.rule}`)));
|
|
1375
1271
|
} catch {
|
|
1376
1272
|
return /* @__PURE__ */ new Set();
|
|
@@ -1378,10 +1274,10 @@ const readPreviousRuleDumpFileNames = (directory) => {
|
|
|
1378
1274
|
};
|
|
1379
1275
|
const writeDiagnosticsDirectory = (diagnostics, outputDirectory) => {
|
|
1380
1276
|
const directory = outputDirectory ? path$1.resolve(outputDirectory) : path$1.join(tmpdir(), `react-doctor-${randomUUID()}`);
|
|
1381
|
-
if (outputDirectory) for (const fileName of readPreviousRuleDumpFileNames(directory)) fs$
|
|
1382
|
-
fs$
|
|
1383
|
-
for (const [ruleKey, ruleDiagnostics] of buildSortedRuleGroups(diagnostics)) fs$
|
|
1384
|
-
fs$
|
|
1277
|
+
if (outputDirectory) for (const fileName of readPreviousRuleDumpFileNames(directory)) fs$2.rmSync(path$1.join(directory, fileName), { force: true });
|
|
1278
|
+
fs$2.mkdirSync(directory, { recursive: true });
|
|
1279
|
+
for (const [ruleKey, ruleDiagnostics] of buildSortedRuleGroups(diagnostics)) fs$2.writeFileSync(path$1.join(directory, ruleDumpFileName(ruleKey)), formatRuleSummary(ruleKey, ruleDiagnostics));
|
|
1280
|
+
fs$2.writeFileSync(path$1.join(directory, "diagnostics.json"), JSON.stringify(diagnostics));
|
|
1385
1281
|
return directory;
|
|
1386
1282
|
};
|
|
1387
1283
|
//#endregion
|
|
@@ -1425,6 +1321,9 @@ const formatElapsedTime = (elapsedMilliseconds) => {
|
|
|
1425
1321
|
return `${(elapsedMilliseconds / MILLISECONDS_PER_SECOND).toFixed(1)}s`;
|
|
1426
1322
|
};
|
|
1427
1323
|
//#endregion
|
|
1324
|
+
//#region src/cli/utils/format-skipped-check-label.ts
|
|
1325
|
+
const formatSkippedCheckLabel = (skippedCheck) => skippedCheck === "dead-code" ? "maintainability" : skippedCheck;
|
|
1326
|
+
//#endregion
|
|
1428
1327
|
//#region src/cli/utils/pluralize.ts
|
|
1429
1328
|
const pluralize = (count, singularNoun) => `${count} ${singularNoun}${count === 1 ? "" : "s"}`;
|
|
1430
1329
|
//#endregion
|
|
@@ -1473,7 +1372,7 @@ const printHeadlessReport = (input) => gen(function* () {
|
|
|
1473
1372
|
}
|
|
1474
1373
|
if (input.skippedChecks.length > 0) {
|
|
1475
1374
|
yield* log("");
|
|
1476
|
-
yield* warn(highlighter.warn(`Results are incomplete: ${input.skippedChecks.join(" and ")} checks failed.`));
|
|
1375
|
+
yield* warn(highlighter.warn(`Results are incomplete: ${input.skippedChecks.map(formatSkippedCheckLabel).join(" and ")} checks failed.`));
|
|
1477
1376
|
}
|
|
1478
1377
|
});
|
|
1479
1378
|
//#endregion
|
|
@@ -1673,12 +1572,39 @@ const renderAndRecordScan = async (input) => {
|
|
|
1673
1572
|
didDeadCodeFail: input.payload.didDeadCodeFail,
|
|
1674
1573
|
supplyChainOverlapTimedOut: input.payload.supplyChainOverlapTimedOut,
|
|
1675
1574
|
securityScanFailed: input.payload.securityScanFailed,
|
|
1676
|
-
deadCodeOverlapped: input.payload.deadCodeOverlapped,
|
|
1677
1575
|
suppressedRuleCounts: input.payload.suppressedRuleCounts ?? []
|
|
1678
1576
|
});
|
|
1679
1577
|
return result;
|
|
1680
1578
|
};
|
|
1681
1579
|
//#endregion
|
|
1580
|
+
//#region src/cli/utils/copy-unchanged-baseline-sources.ts
|
|
1581
|
+
const copyUnchangedBaselineSources = async (input) => {
|
|
1582
|
+
const baseMaterializedFiles = new Set(input.baseMaterializedFiles.map(toForwardSlashes));
|
|
1583
|
+
const headChangedFiles = new Set(input.headChangedFiles.map(toForwardSlashes));
|
|
1584
|
+
const untrackedFiles = new Set(input.untrackedFiles.map(toForwardSlashes));
|
|
1585
|
+
const sourceDirectory = path$1.resolve(input.directory);
|
|
1586
|
+
const targetDirectory = path$1.resolve(input.tempDirectory);
|
|
1587
|
+
return (await mapWithConcurrency(input.sourceFiles.filter((filePath) => {
|
|
1588
|
+
const normalizedPath = toForwardSlashes(filePath);
|
|
1589
|
+
return !baseMaterializedFiles.has(normalizedPath) && !headChangedFiles.has(normalizedPath) && !untrackedFiles.has(normalizedPath);
|
|
1590
|
+
}), 32, async (filePath) => {
|
|
1591
|
+
input.signal?.throwIfAborted();
|
|
1592
|
+
if (input.deadlineEpochMs !== null && Date.now() >= input.deadlineEpochMs) return false;
|
|
1593
|
+
const sourcePath = path$1.resolve(sourceDirectory, filePath);
|
|
1594
|
+
const targetPath = path$1.resolve(targetDirectory, filePath);
|
|
1595
|
+
if (!isPathInsideDirectory(sourcePath, sourceDirectory) || !isPathInsideDirectory(targetPath, targetDirectory)) return false;
|
|
1596
|
+
try {
|
|
1597
|
+
if (!(await fs$1.lstat(sourcePath)).isFile()) return false;
|
|
1598
|
+
await fs$1.mkdir(path$1.dirname(targetPath), { recursive: true });
|
|
1599
|
+
await fs$1.copyFile(sourcePath, targetPath);
|
|
1600
|
+
return true;
|
|
1601
|
+
} catch {
|
|
1602
|
+
input.signal?.throwIfAborted();
|
|
1603
|
+
return false;
|
|
1604
|
+
}
|
|
1605
|
+
})).every(Boolean);
|
|
1606
|
+
};
|
|
1607
|
+
//#endregion
|
|
1682
1608
|
//#region src/cli/utils/read-diagnostic-evidence.ts
|
|
1683
1609
|
const SOURCE_EXTENSIONS = [
|
|
1684
1610
|
".ts",
|
|
@@ -2096,7 +2022,6 @@ const resolveMergeBaseRef = async (directory, ref) => {
|
|
|
2096
2022
|
const silentConsole$1 = makeNoopConsole();
|
|
2097
2023
|
const countIncompleteLintFiles = (lintPartialFailures) => countDroppedLintFiles(lintPartialFailures) + countDeadlineSkippedFiles(lintPartialFailures);
|
|
2098
2024
|
const runBaselineComparison = async (input) => {
|
|
2099
|
-
const temporaryDirectory = mkdtempSync(path.join(tmpdir(), BASELINE_FILES_TEMP_DIR_PREFIX));
|
|
2100
2025
|
const baselineIncludePaths = filterPathsOutsideDirectories({
|
|
2101
2026
|
rootDirectory: input.directory,
|
|
2102
2027
|
relativePaths: input.options.includePaths,
|
|
@@ -2112,12 +2037,24 @@ const runBaselineComparison = async (input) => {
|
|
|
2112
2037
|
relativePaths: input.headFiles,
|
|
2113
2038
|
excludedDirectories: input.options.excludedProjectDirectories
|
|
2114
2039
|
}) : void 0;
|
|
2040
|
+
const remainingBaselineBudgetMs = input.deadlineEpochMs === null ? null : remainingDeadlineBudgetMs(input.deadlineEpochMs);
|
|
2041
|
+
if (remainingBaselineBudgetMs === 0) return null;
|
|
2042
|
+
const baselineDeadlineSignal = remainingBaselineBudgetMs === null ? void 0 : AbortSignal.timeout(remainingBaselineBudgetMs);
|
|
2043
|
+
const baselineListingSignal = baselineDeadlineSignal === void 0 ? input.oxlintRuntime.abortSignal : input.oxlintRuntime.abortSignal === void 0 ? baselineDeadlineSignal : AbortSignal.any([input.oxlintRuntime.abortSignal, baselineDeadlineSignal]);
|
|
2044
|
+
let maintainabilitySourceFiles = [];
|
|
2045
|
+
if (input.options.deadCode) try {
|
|
2046
|
+
maintainabilitySourceFiles = (await listSourceFilesCooperative(input.directory, baselineListingSignal)).filter((filePath) => JSX_DUPLICATION_SOURCE_FILE_PATTERN.test(filePath) && classifyFileContext(filePath) === "production");
|
|
2047
|
+
} catch (error) {
|
|
2048
|
+
if (baselineDeadlineSignal?.aborted) return null;
|
|
2049
|
+
throw error;
|
|
2050
|
+
}
|
|
2051
|
+
const temporaryDirectory = mkdtempSync(path.join(tmpdir(), BASELINE_FILES_TEMP_DIR_PREFIX));
|
|
2115
2052
|
const snapshot = await materializeBaselineFiles({
|
|
2116
2053
|
directory: input.directory,
|
|
2117
2054
|
ref: input.baselineRef,
|
|
2118
|
-
files: baselineIncludePaths,
|
|
2119
|
-
baseFiles: baselineBaseFiles,
|
|
2120
|
-
headFiles: baselineHeadFiles,
|
|
2055
|
+
files: input.options.deadCode ? input.options.includePaths : baselineIncludePaths,
|
|
2056
|
+
baseFiles: input.options.deadCode ? input.baseFiles : baselineBaseFiles,
|
|
2057
|
+
headFiles: input.options.deadCode ? input.headFiles : baselineHeadFiles,
|
|
2121
2058
|
tempDirectory: temporaryDirectory
|
|
2122
2059
|
}).catch((error) => {
|
|
2123
2060
|
rmSync(temporaryDirectory, {
|
|
@@ -2135,22 +2072,47 @@ const runBaselineComparison = async (input) => {
|
|
|
2135
2072
|
}
|
|
2136
2073
|
try {
|
|
2137
2074
|
if (!snapshot.isComplete) return null;
|
|
2075
|
+
if (input.options.deadCode && !await copyUnchangedBaselineSources({
|
|
2076
|
+
directory: input.directory,
|
|
2077
|
+
sourceFiles: maintainabilitySourceFiles,
|
|
2078
|
+
baseMaterializedFiles: snapshot.materializedFiles,
|
|
2079
|
+
headChangedFiles: snapshot.headFiles,
|
|
2080
|
+
untrackedFiles: snapshot.untrackedFiles,
|
|
2081
|
+
tempDirectory: snapshot.tempDirectory,
|
|
2082
|
+
deadlineEpochMs: input.deadlineEpochMs,
|
|
2083
|
+
signal: input.oxlintRuntime.abortSignal
|
|
2084
|
+
})) return null;
|
|
2085
|
+
const filteredSnapshotBaseFiles = filterPathsOutsideDirectories({
|
|
2086
|
+
rootDirectory: input.directory,
|
|
2087
|
+
relativePaths: snapshot.baseFiles,
|
|
2088
|
+
excludedDirectories: input.options.excludedProjectDirectories
|
|
2089
|
+
});
|
|
2090
|
+
const filteredSnapshotHeadFiles = filterPathsOutsideDirectories({
|
|
2091
|
+
rootDirectory: input.directory,
|
|
2092
|
+
relativePaths: snapshot.headFiles,
|
|
2093
|
+
excludedDirectories: input.options.excludedProjectDirectories
|
|
2094
|
+
});
|
|
2138
2095
|
const analyzedHeadFiles = new Set(input.headAnalyzedFiles.map(toForwardSlashes));
|
|
2139
|
-
const baseFiles = new Set(
|
|
2140
|
-
const expectedHeadFiles = new Set(
|
|
2096
|
+
const baseFiles = new Set((baselineBaseFiles ?? filteredSnapshotBaseFiles).map(toForwardSlashes));
|
|
2097
|
+
const expectedHeadFiles = new Set((baselineHeadFiles ?? filteredSnapshotHeadFiles).map(toForwardSlashes));
|
|
2141
2098
|
for (const filePath of baselineIncludePaths) {
|
|
2142
2099
|
const normalizedFilePath = toForwardSlashes(filePath);
|
|
2143
2100
|
if (!baseFiles.has(normalizedFilePath)) expectedHeadFiles.add(normalizedFilePath);
|
|
2144
2101
|
}
|
|
2145
|
-
if (filterSourceFiles([...expectedHeadFiles]).some((filePath) => !analyzedHeadFiles.has(filePath))) return null;
|
|
2102
|
+
if (input.options.lint && filterSourceFiles([...expectedHeadFiles]).some((filePath) => !analyzedHeadFiles.has(filePath))) return null;
|
|
2103
|
+
const baselineLintPaths = new Set([...baselineIncludePaths, ...filteredSnapshotBaseFiles].map(toForwardSlashes));
|
|
2104
|
+
const materializedLintPaths = snapshot.materializedFiles.filter((filePath) => baselineLintPaths.has(toForwardSlashes(filePath)));
|
|
2105
|
+
const maintainabilityFocusPaths = [...new Set([...input.options.includePaths, ...snapshot.baseFiles].map(toForwardSlashes))];
|
|
2106
|
+
const baselineExcludedProjectDirectories = input.options.excludedProjectDirectories.map((excludedDirectory) => path.resolve(excludedDirectory)).filter((excludedDirectory) => isPathInsideDirectory(excludedDirectory, input.directory)).map((excludedDirectory) => path.resolve(snapshot.tempDirectory, path.relative(input.directory, excludedDirectory)));
|
|
2107
|
+
const baseIncludePaths = materializedLintPaths.length > 0 ? materializedLintPaths : maintainabilityFocusPaths;
|
|
2146
2108
|
const runtimeLayers = buildRuntimeLayers({
|
|
2147
2109
|
directory: snapshot.tempDirectory,
|
|
2148
2110
|
hasConfigOverride: true,
|
|
2149
2111
|
userConfig: input.userConfig,
|
|
2150
2112
|
configSourceDirectory: input.configSourceDirectory,
|
|
2151
2113
|
projectInfoOverride: input.headProjectInfo,
|
|
2152
|
-
shouldSkipLint: !input.options.lint || !input.resolvedNodeBinaryPath,
|
|
2153
|
-
shouldRunDeadCode:
|
|
2114
|
+
shouldSkipLint: !input.options.lint || !input.resolvedNodeBinaryPath || materializedLintPaths.length === 0,
|
|
2115
|
+
shouldRunDeadCode: input.options.deadCode,
|
|
2154
2116
|
shouldRunSupplyChain: input.options.supplyChain,
|
|
2155
2117
|
shouldComputeScore: false,
|
|
2156
2118
|
shouldShowProgressSpinners: false,
|
|
@@ -2159,7 +2121,8 @@ const runBaselineComparison = async (input) => {
|
|
|
2159
2121
|
});
|
|
2160
2122
|
const baseOutput = await runPromise(restoreLegacyThrow(runInspect({
|
|
2161
2123
|
directory: snapshot.tempDirectory,
|
|
2162
|
-
includePaths:
|
|
2124
|
+
includePaths: baseIncludePaths,
|
|
2125
|
+
maintainabilityFocusPaths,
|
|
2163
2126
|
customRulesOnly: input.options.customRulesOnly,
|
|
2164
2127
|
respectInlineDisables: input.options.respectInlineDisables,
|
|
2165
2128
|
warnings: input.options.warnings,
|
|
@@ -2168,7 +2131,7 @@ const runBaselineComparison = async (input) => {
|
|
|
2168
2131
|
includedTags: input.options.includedTags,
|
|
2169
2132
|
includeTagDefaults: input.options.includeTagDefaults,
|
|
2170
2133
|
nodeBinaryPath: input.resolvedNodeBinaryPath ?? void 0,
|
|
2171
|
-
runDeadCode:
|
|
2134
|
+
runDeadCode: input.options.deadCode,
|
|
2172
2135
|
isCi: input.options.isCi,
|
|
2173
2136
|
doctorVersion: VERSION,
|
|
2174
2137
|
runId: getRunId(),
|
|
@@ -2176,10 +2139,16 @@ const runBaselineComparison = async (input) => {
|
|
|
2176
2139
|
suppressScanSummary: true,
|
|
2177
2140
|
supplyChainManifestChanged: input.options.supplyChainManifestChanged,
|
|
2178
2141
|
deadlineEpochMs: input.deadlineEpochMs ?? void 0,
|
|
2179
|
-
signal: input.oxlintRuntime.abortSignal
|
|
2142
|
+
signal: input.oxlintRuntime.abortSignal,
|
|
2143
|
+
excludedProjectDirectories: baselineExcludedProjectDirectories,
|
|
2144
|
+
retainExcludedProjectDeadCodeDiagnostics: input.options.retainExcludedProjectDeadCodeDiagnostics
|
|
2180
2145
|
}, {}).pipe(provide(runtimeLayers), provideService(PerFileLintCacheEnabled, false), provideService(SidecarLintCacheEnabled, false), provideService(Console, silentConsole$1))), { signal: input.oxlintRuntime.abortSignal });
|
|
2181
|
-
if (baseOutput.didLintFail || countIncompleteLintFiles(baseOutput.lintPartialFailures) > 0) return null;
|
|
2182
|
-
const hasUnscannedUntrackedSourceFiles = filterSourceFiles(
|
|
2146
|
+
if (baseOutput.didLintFail || baseOutput.didDeadCodeFail || countIncompleteLintFiles(baseOutput.lintPartialFailures) > 0) return null;
|
|
2147
|
+
const hasUnscannedUntrackedSourceFiles = filterSourceFiles(filterPathsOutsideDirectories({
|
|
2148
|
+
rootDirectory: input.directory,
|
|
2149
|
+
relativePaths: snapshot.untrackedFiles,
|
|
2150
|
+
excludedDirectories: input.options.excludedProjectDirectories
|
|
2151
|
+
}).map(toForwardSlashes)).some((filePath) => !analyzedHeadFiles.has(filePath));
|
|
2183
2152
|
const diagnosticDelta = computeDiagnosticDelta({
|
|
2184
2153
|
headDiagnostics: input.headDiagnostics,
|
|
2185
2154
|
baseDiagnostics: baseOutput.diagnostics,
|
|
@@ -2352,6 +2321,7 @@ const runInspectWithRuntime = async (directory, options, userConfig, hasConfigOv
|
|
|
2352
2321
|
directory,
|
|
2353
2322
|
precomputedSourceFileCount: options.precomputedSourceFileCount,
|
|
2354
2323
|
includePaths: options.includePaths,
|
|
2324
|
+
changedLineRanges: options.changedLineRanges ?? void 0,
|
|
2355
2325
|
customRulesOnly: options.customRulesOnly,
|
|
2356
2326
|
respectInlineDisables: options.respectInlineDisables,
|
|
2357
2327
|
warnings: options.warnings,
|
|
@@ -2382,7 +2352,7 @@ const runInspectWithRuntime = async (directory, options, userConfig, hasConfigOv
|
|
|
2382
2352
|
else runSync(error(highlighter.error(lintFailureReason)));
|
|
2383
2353
|
let inspectDiagnostics = output.diagnostics;
|
|
2384
2354
|
let baselineDelta;
|
|
2385
|
-
if (options.baseline && isDiffMode && !didLintFail && countIncompleteLintFiles(output.lintPartialFailures) === 0) {
|
|
2355
|
+
if (options.baseline && isDiffMode && !didLintFail && !output.didDeadCodeFail && countIncompleteLintFiles(output.lintPartialFailures) === 0) {
|
|
2386
2356
|
const comparison = await runBaselineComparison({
|
|
2387
2357
|
directory,
|
|
2388
2358
|
options,
|
|
@@ -2535,13 +2505,13 @@ const resolveCursorBundledNode = () => {
|
|
|
2535
2505
|
if (!localAppData) return null;
|
|
2536
2506
|
const cursorAgentRoot = path$1.join(localAppData, "cursor-agent", "versions");
|
|
2537
2507
|
try {
|
|
2538
|
-
const versions = fs$
|
|
2508
|
+
const versions = fs$2.readdirSync(cursorAgentRoot);
|
|
2539
2509
|
if (versions.length === 0) return null;
|
|
2540
2510
|
const sortedVersions = versions.sort((a, b) => b.localeCompare(a, void 0, { numeric: true }));
|
|
2541
2511
|
for (const version of sortedVersions) try {
|
|
2542
2512
|
const bundledNodePath = path$1.join(cursorAgentRoot, version, "node.exe");
|
|
2543
2513
|
const entryScriptPath = path$1.join(cursorAgentRoot, version, "index.js");
|
|
2544
|
-
if (fs$
|
|
2514
|
+
if (fs$2.statSync(bundledNodePath).isFile() && fs$2.statSync(entryScriptPath).isFile()) return {
|
|
2545
2515
|
command: bundledNodePath,
|
|
2546
2516
|
args: [entryScriptPath]
|
|
2547
2517
|
};
|
|
@@ -2555,15 +2525,15 @@ const resolveWindowsCmdEntryScript = (command) => {
|
|
|
2555
2525
|
for (const directory of pathDirectories) {
|
|
2556
2526
|
const cmdFilePath = path$1.join(directory, `${command}.cmd`);
|
|
2557
2527
|
try {
|
|
2558
|
-
if (!fs$
|
|
2528
|
+
if (!fs$2.statSync(cmdFilePath).isFile()) continue;
|
|
2559
2529
|
} catch {
|
|
2560
2530
|
continue;
|
|
2561
2531
|
}
|
|
2562
2532
|
try {
|
|
2563
|
-
const entryScriptMatch = fs$
|
|
2533
|
+
const entryScriptMatch = fs$2.readFileSync(cmdFilePath, "utf8").match(/"%(?:~dp0|dp0%)\\([^"]+\.(?:m?js|cjs))"/);
|
|
2564
2534
|
if (!entryScriptMatch) continue;
|
|
2565
2535
|
const resolvedScriptPath = path$1.resolve(directory, entryScriptMatch[1]);
|
|
2566
|
-
if (fs$
|
|
2536
|
+
if (fs$2.statSync(resolvedScriptPath).isFile()) return resolvedScriptPath;
|
|
2567
2537
|
} catch {}
|
|
2568
2538
|
}
|
|
2569
2539
|
return null;
|
|
@@ -2889,7 +2859,7 @@ const buildCodeFrame = (input) => {
|
|
|
2889
2859
|
const absolutePath = resolveAbsolutePath(input.filePath, input.rootDirectory);
|
|
2890
2860
|
let source;
|
|
2891
2861
|
try {
|
|
2892
|
-
source = fs$
|
|
2862
|
+
source = fs$2.readFileSync(absolutePath, "utf8");
|
|
2893
2863
|
} catch {
|
|
2894
2864
|
return null;
|
|
2895
2865
|
}
|
|
@@ -2910,7 +2880,7 @@ const buildCodeFrame = (input) => {
|
|
|
2910
2880
|
const discoverWorkspacePackages = (rootDirectory) => {
|
|
2911
2881
|
const hasRootPackageJson = isFile(path$1.join(rootDirectory, "package.json"));
|
|
2912
2882
|
const packages = listWorkspacePackages(rootDirectory);
|
|
2913
|
-
if (packages.length === 0 && (!hasRootPackageJson || isMonorepoRoot(rootDirectory))) return
|
|
2883
|
+
if (packages.length === 0 && (!hasRootPackageJson || isMonorepoRoot(rootDirectory))) return discoverSupportedSubprojects(rootDirectory);
|
|
2914
2884
|
return packages;
|
|
2915
2885
|
};
|
|
2916
2886
|
const selectProjects = async (rootDirectory, projectFlag, skipPrompts, configProjects) => {
|
|
@@ -3002,6 +2972,12 @@ const validateIncludeUntrackedScope = (includeUntracked, scope) => {
|
|
|
3002
2972
|
if (!includeUntracked || scope !== void 0 && scope !== "full") return;
|
|
3003
2973
|
throw new CliInputError("--include-untracked requires the files, changed, or lines scope (via --scope, --diff, or config).");
|
|
3004
2974
|
};
|
|
2975
|
+
const validateFilePathSelectionFlags = (flags) => {
|
|
2976
|
+
if (flags.staged || flags.changedFilesFrom !== void 0) throw new CliInputError("Cannot combine file path arguments with --staged or --changed-files-from. Use one file source.");
|
|
2977
|
+
if (usedDiffAlias(flags)) throw new CliInputError("Cannot combine file path arguments with --diff. File path arguments use the files scope.");
|
|
2978
|
+
if (usedScope(flags) && flags.scope !== "files") throw new CliInputError(`Cannot combine file path arguments with --scope ${flags.scope}. File path arguments use --scope files.`);
|
|
2979
|
+
if (flags.base !== void 0) throw new CliInputError("Cannot combine file path arguments with --base. The selected files do not need a Git base.");
|
|
2980
|
+
};
|
|
3005
2981
|
const validateModeFlags = (flags) => {
|
|
3006
2982
|
if (usedScope(flags) && usedDiffAlias(flags)) throw new CliInputError("Cannot combine --scope and --diff; --diff is the deprecated alias.");
|
|
3007
2983
|
if (flags.staged && usedDiffAlias(flags)) throw new CliInputError("Cannot combine --staged and --diff; pick one mode.");
|
|
@@ -3026,7 +3002,7 @@ const shouldFailScanGate = (input) => {
|
|
|
3026
3002
|
return shouldBlockCi(filterScansForSurface(input.scans, "ciFailure"), input.blockingLevel);
|
|
3027
3003
|
};
|
|
3028
3004
|
//#endregion
|
|
3029
|
-
export {
|
|
3005
|
+
export { inspect as A, filterDiagnosticsByCategories as B, CLI_AGENT_BINARIES as C, collectProjectSourceFileCounts as D, buildHandoffPayload as E, formatElapsedTime as F, buildSortedRuleGroups as G, buildEmptyReportMessage as H, printFooter as I, formatFixRecipeLine as K, printDiagnosticsDump as L, printHeadlessReport as M, pluralize as N, deduplicateProjectScans as O, formatSkippedCheckLabel as P, formatDiagnosticSite as R, detectLaunchableAgents as S, launchCliAgent as T, hasLintHardFailure as U, buildNoScoreMessage as V, buildRulePriorityMap as W, resolveWorkspaceDeadCodeOwner as _, validateModeFlags as a, countUniqueScannedFiles as b, discoverWorkspacePackages as c, resolveProjectScan as d, resolveProjectChangedLineRanges as f, retryMissingProjectScores as g, resolveProjectRelativeDirectory as h, validateIncludeUntrackedScope as i, resolveMergeBaseRef as j, createInvocationInspect as k, selectProjects as l, resolveProjectSourceFilePaths as m, formatSkippedProjectsMessage as n, projectManifestChanged as o, resolveProjectDiffIncludePaths as p, formatLearnMoreLine as q, validateFilePathSelectionFlags as r, partitionProjectScanOutcomes as s, shouldFailScanGate as t, buildCodeFrame as u, resolveCliInspectOptions as v, copyToClipboard as w, setUpGitHubActions as x, filterScansForSurface as y, hasIncompleteScoreAnalysis as z };
|
|
3030
3006
|
|
|
3031
|
-
//# sourceMappingURL=should-fail-scan-gate-
|
|
3032
|
-
//# debugId=
|
|
3007
|
+
//# sourceMappingURL=should-fail-scan-gate-D1mS86Ff.js.map
|
|
3008
|
+
//# debugId=c6a4440d-8008-5253-b2d5-e7f83df2b0a4
|