react-doctor 0.9.12 → 0.9.13-dev.1239043
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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-C09RcJrc.js} +10 -25
- package/dist/{ci-B4x4RjKD.js → ci-CUgNjCr-.js} +13 -12
- package/dist/cli.js +121 -41
- package/dist/{design-A48PNCf_.js → design-CX2j8ubS.js} +4 -4
- package/dist/{devtools-DPRVRu3S.js → devtools-CC2PAZ85.js} +4 -4
- package/dist/{exit-gracefully-Bk531kS-.js → exit-gracefully-cGJM2g3O.js} +15 -9
- package/dist/get-east-asian-width-Bv2TD3Xj.js +444 -0
- package/dist/{report-error-Pb2uO8EG.js → handle-error-BCI-Nv3P.js} +18 -68
- package/dist/index.d.ts +53 -42
- package/dist/index.js +55146 -54688
- package/dist/{inspect-CwH84aSA.js → inspect-Dq5krKWG.js} +47 -34
- package/dist/{install-sFN_w425.js → install-C0aiZKgf.js} +9 -8
- package/dist/{install-doctor-script-DLayXS7q.js → install-doctor-script-CaTIms0r.js} +7 -449
- package/dist/{install-github-workflow-Goz-KFqb.js → install-github-workflow-DjPgXVqg.js} +10 -6
- package/dist/{install-react-doctor-Bq7dfEBI.js → install-react-doctor-C-PVDWXv.js} +47 -45
- package/dist/{run-scan-app-DXBMW54J.js → jsx-runtime-D_A4d39v.js} +281 -2759
- package/dist/path-format-DnHpmJwG.js +9 -0
- package/dist/{print-branded-header-D_J8Qdsj.js → print-branded-header-Dz0SwyHQ.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-BXdY2Opw.js +240 -0
- package/dist/{read-object-file-Hk79zI91.js → read-object-file-CmMGK1uu.js} +4 -4
- package/dist/{record-metric-CsQXJewH.js → record-metric-B6T_Ej33.js} +3285 -6018
- package/dist/report-error-DwL3b99q.js +64 -0
- package/dist/{report-workflow-result-6n_wSqzA.js → report-workflow-result-5PueDIAr.js} +5 -5
- package/dist/{resolve-scope-DP6rYlNO.js → resolve-scope-CTlLl7e6.js} +8 -10
- package/dist/{rules-BnIZ4Fq_.js → rules-DEbHKRag.js} +6 -6
- package/dist/run-scan-app-D3huvmXJ.js +2517 -0
- package/dist/runtime-scan/browser-probe.iife.js +855 -0
- package/dist/runtime-scan-CHFAEYg3.js +770 -0
- package/dist/sanitize-runtime-url-Bbq40iIp.js +76 -0
- package/dist/scan-C7sCm7wi.js +47 -0
- package/dist/semver-DFtnEmN-.js +3219 -0
- package/dist/{should-fail-scan-gate-DzZuTxim.js → should-fail-scan-gate-CfG_ppAV.js} +188 -187
- package/dist/{should-skip-prompts-D1GWG2qN.js → should-skip-prompts-BuNtAVEs.js} +4 -4
- package/dist/should-use-tui-B-lCjXVG.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-BF83aBFL.js} +57 -33
- package/dist/unref-stdin-SK3WfwxS.js +12 -0
- package/dist/{version-Bo5QDp7F.js → version-C27gr4Qj.js} +4 -4
- package/dist/{why-BFYbtnm8.js → why-CYRkZ2VY.js} +4 -4
- package/dist/with-run-span-CdsXSJgE.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]="159aa887-619a-5d42-9d32-79fce37783d6")}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_FILE_PATTERN, dt as SupplyChain, en as listSourceFiles, et as Maintainability, fn as resolveScanTarget, fr as MAINTAINABILITY_SOURCE_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-B6T_Ej33.js";
|
|
4
|
+
import { t as getTelemetryContext } from "./telemetry-runtime-BF83aBFL.js";
|
|
5
|
+
import { n as buildFooterLinkLines, t as buildSectionDivider } from "./build-section-divider-C09RcJrc.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-CaTIms0r.js";
|
|
9
|
+
import { d as detectDefaultBranch, l as prompts, r as installReactDoctorWorkflow, u as isCommandAvailable } from "./install-github-workflow-DjPgXVqg.js";
|
|
10
|
+
import { a as isValidBlockingLevel, c as openWorkflowPullRequest, l as stageWorkflowFile, o as pickBlockingLevel } from "./resolve-scope-CTlLl7e6.js";
|
|
11
|
+
import { n as resetSentryRunState, r as withRunSpan, t as recordSentryProjectContext } from "./with-run-span-CdsXSJgE.js";
|
|
12
|
+
import { h as detectAvailableAgents, t as reportWorkflowResult } from "./report-workflow-result-5PueDIAr.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",
|
|
@@ -2040,8 +1966,10 @@ const isMaterializableGitSource = (content) => !content.includes("\0") && !conte
|
|
|
2040
1966
|
* `tempDirectory`, mirroring the project layout plus head's config files so
|
|
2041
1967
|
* both sides lint under the same rules. Git rename heuristics are disabled by
|
|
2042
1968
|
* the diff planner, so an old path is retained as a base deletion while its
|
|
2043
|
-
* new path is a head addition. Missing head-only additions are expected
|
|
2044
|
-
*
|
|
1969
|
+
* new path is a head addition. Missing head-only additions are expected, and a
|
|
1970
|
+
* base-only deletion that cannot be read has nothing at head to compare
|
|
1971
|
+
* against; only a missing path that the plan says exists on both sides makes
|
|
1972
|
+
* `isComplete` false.
|
|
2045
1973
|
*/
|
|
2046
1974
|
const materializeBaselineFiles = (input) => runPromise(gen(function* () {
|
|
2047
1975
|
const git = yield* Git;
|
|
@@ -2066,11 +1994,12 @@ const materializeBaselineFiles = (input) => runPromise(gen(function* () {
|
|
|
2066
1994
|
}).pipe(map((content) => content !== null && isMaterializableGitSource(content) ? content : null))
|
|
2067
1995
|
});
|
|
2068
1996
|
const materializedFiles = new Set(tree.materializedFiles);
|
|
1997
|
+
const headFileSet = new Set(headFiles);
|
|
2069
1998
|
return {
|
|
2070
1999
|
...tree,
|
|
2071
2000
|
baseFiles,
|
|
2072
2001
|
headFiles,
|
|
2073
|
-
isComplete: baseFiles.every((filePath) => materializedFiles.has(filePath)),
|
|
2002
|
+
isComplete: baseFiles.every((filePath) => materializedFiles.has(filePath) || !headFileSet.has(filePath)),
|
|
2074
2003
|
untrackedFiles
|
|
2075
2004
|
};
|
|
2076
2005
|
}).pipe(provide(Git.layerNode)));
|
|
@@ -2092,11 +2021,16 @@ const resolveMergeBaseRef = async (directory, ref) => {
|
|
|
2092
2021
|
}
|
|
2093
2022
|
};
|
|
2094
2023
|
//#endregion
|
|
2024
|
+
//#region src/cli/utils/has-unlinted-surviving-base-file.ts
|
|
2025
|
+
const hasUnlintedSurvivingBaseFile = (input) => {
|
|
2026
|
+
const analyzedBaseFiles = new Set(input.analyzedBaseFiles.map(toForwardSlashes));
|
|
2027
|
+
return filterSourceFiles(input.baseLintPaths.map(toForwardSlashes)).some((filePath) => input.headFiles.has(filePath) && !analyzedBaseFiles.has(filePath));
|
|
2028
|
+
};
|
|
2029
|
+
//#endregion
|
|
2095
2030
|
//#region src/cli/utils/run-baseline-comparison.ts
|
|
2096
2031
|
const silentConsole$1 = makeNoopConsole();
|
|
2097
2032
|
const countIncompleteLintFiles = (lintPartialFailures) => countDroppedLintFiles(lintPartialFailures) + countDeadlineSkippedFiles(lintPartialFailures);
|
|
2098
2033
|
const runBaselineComparison = async (input) => {
|
|
2099
|
-
const temporaryDirectory = mkdtempSync(path.join(tmpdir(), BASELINE_FILES_TEMP_DIR_PREFIX));
|
|
2100
2034
|
const baselineIncludePaths = filterPathsOutsideDirectories({
|
|
2101
2035
|
rootDirectory: input.directory,
|
|
2102
2036
|
relativePaths: input.options.includePaths,
|
|
@@ -2112,12 +2046,24 @@ const runBaselineComparison = async (input) => {
|
|
|
2112
2046
|
relativePaths: input.headFiles,
|
|
2113
2047
|
excludedDirectories: input.options.excludedProjectDirectories
|
|
2114
2048
|
}) : void 0;
|
|
2049
|
+
const remainingBaselineBudgetMs = input.deadlineEpochMs === null ? null : remainingDeadlineBudgetMs(input.deadlineEpochMs);
|
|
2050
|
+
if (remainingBaselineBudgetMs === 0) return null;
|
|
2051
|
+
const baselineDeadlineSignal = remainingBaselineBudgetMs === null ? void 0 : AbortSignal.timeout(remainingBaselineBudgetMs);
|
|
2052
|
+
const baselineListingSignal = baselineDeadlineSignal === void 0 ? input.oxlintRuntime.abortSignal : input.oxlintRuntime.abortSignal === void 0 ? baselineDeadlineSignal : AbortSignal.any([input.oxlintRuntime.abortSignal, baselineDeadlineSignal]);
|
|
2053
|
+
let maintainabilitySourceFiles = [];
|
|
2054
|
+
if (input.options.deadCode) try {
|
|
2055
|
+
maintainabilitySourceFiles = (await listSourceFilesCooperative(input.directory, baselineListingSignal)).filter((filePath) => MAINTAINABILITY_SOURCE_FILE_PATTERN.test(filePath) && classifyFileContext(filePath) === "production");
|
|
2056
|
+
} catch (error) {
|
|
2057
|
+
if (baselineDeadlineSignal?.aborted) return null;
|
|
2058
|
+
throw error;
|
|
2059
|
+
}
|
|
2060
|
+
const temporaryDirectory = mkdtempSync(path.join(tmpdir(), BASELINE_FILES_TEMP_DIR_PREFIX));
|
|
2115
2061
|
const snapshot = await materializeBaselineFiles({
|
|
2116
2062
|
directory: input.directory,
|
|
2117
2063
|
ref: input.baselineRef,
|
|
2118
|
-
files: baselineIncludePaths,
|
|
2119
|
-
baseFiles: baselineBaseFiles,
|
|
2120
|
-
headFiles: baselineHeadFiles,
|
|
2064
|
+
files: input.options.deadCode ? input.options.includePaths : baselineIncludePaths,
|
|
2065
|
+
baseFiles: input.options.deadCode ? input.baseFiles : baselineBaseFiles,
|
|
2066
|
+
headFiles: input.options.deadCode ? input.headFiles : baselineHeadFiles,
|
|
2121
2067
|
tempDirectory: temporaryDirectory
|
|
2122
2068
|
}).catch((error) => {
|
|
2123
2069
|
rmSync(temporaryDirectory, {
|
|
@@ -2135,22 +2081,47 @@ const runBaselineComparison = async (input) => {
|
|
|
2135
2081
|
}
|
|
2136
2082
|
try {
|
|
2137
2083
|
if (!snapshot.isComplete) return null;
|
|
2084
|
+
if (input.options.deadCode && !await copyUnchangedBaselineSources({
|
|
2085
|
+
directory: input.directory,
|
|
2086
|
+
sourceFiles: maintainabilitySourceFiles,
|
|
2087
|
+
baseMaterializedFiles: snapshot.materializedFiles,
|
|
2088
|
+
headChangedFiles: snapshot.headFiles,
|
|
2089
|
+
untrackedFiles: snapshot.untrackedFiles,
|
|
2090
|
+
tempDirectory: snapshot.tempDirectory,
|
|
2091
|
+
deadlineEpochMs: input.deadlineEpochMs,
|
|
2092
|
+
signal: input.oxlintRuntime.abortSignal
|
|
2093
|
+
})) return null;
|
|
2094
|
+
const filteredSnapshotBaseFiles = filterPathsOutsideDirectories({
|
|
2095
|
+
rootDirectory: input.directory,
|
|
2096
|
+
relativePaths: snapshot.baseFiles,
|
|
2097
|
+
excludedDirectories: input.options.excludedProjectDirectories
|
|
2098
|
+
});
|
|
2099
|
+
const filteredSnapshotHeadFiles = filterPathsOutsideDirectories({
|
|
2100
|
+
rootDirectory: input.directory,
|
|
2101
|
+
relativePaths: snapshot.headFiles,
|
|
2102
|
+
excludedDirectories: input.options.excludedProjectDirectories
|
|
2103
|
+
});
|
|
2138
2104
|
const analyzedHeadFiles = new Set(input.headAnalyzedFiles.map(toForwardSlashes));
|
|
2139
|
-
const baseFiles = new Set(
|
|
2140
|
-
const expectedHeadFiles = new Set(
|
|
2105
|
+
const baseFiles = new Set((baselineBaseFiles ?? filteredSnapshotBaseFiles).map(toForwardSlashes));
|
|
2106
|
+
const expectedHeadFiles = new Set((baselineHeadFiles ?? filteredSnapshotHeadFiles).map(toForwardSlashes));
|
|
2141
2107
|
for (const filePath of baselineIncludePaths) {
|
|
2142
2108
|
const normalizedFilePath = toForwardSlashes(filePath);
|
|
2143
2109
|
if (!baseFiles.has(normalizedFilePath)) expectedHeadFiles.add(normalizedFilePath);
|
|
2144
2110
|
}
|
|
2145
|
-
if (filterSourceFiles([...expectedHeadFiles]).some((filePath) => !analyzedHeadFiles.has(filePath))) return null;
|
|
2111
|
+
if (input.options.lint && filterSourceFiles([...expectedHeadFiles]).some((filePath) => !analyzedHeadFiles.has(filePath))) return null;
|
|
2112
|
+
const baselineLintPaths = new Set([...baselineIncludePaths, ...filteredSnapshotBaseFiles].map(toForwardSlashes));
|
|
2113
|
+
const materializedLintPaths = snapshot.materializedFiles.filter((filePath) => baselineLintPaths.has(toForwardSlashes(filePath)));
|
|
2114
|
+
const maintainabilityFocusPaths = [...new Set([...input.options.includePaths, ...snapshot.baseFiles].map(toForwardSlashes))];
|
|
2115
|
+
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)));
|
|
2116
|
+
const baseIncludePaths = materializedLintPaths.length > 0 ? materializedLintPaths : maintainabilityFocusPaths;
|
|
2146
2117
|
const runtimeLayers = buildRuntimeLayers({
|
|
2147
2118
|
directory: snapshot.tempDirectory,
|
|
2148
2119
|
hasConfigOverride: true,
|
|
2149
2120
|
userConfig: input.userConfig,
|
|
2150
2121
|
configSourceDirectory: input.configSourceDirectory,
|
|
2151
2122
|
projectInfoOverride: input.headProjectInfo,
|
|
2152
|
-
shouldSkipLint: !input.options.lint || !input.resolvedNodeBinaryPath,
|
|
2153
|
-
shouldRunDeadCode:
|
|
2123
|
+
shouldSkipLint: !input.options.lint || !input.resolvedNodeBinaryPath || materializedLintPaths.length === 0,
|
|
2124
|
+
shouldRunDeadCode: input.options.deadCode,
|
|
2154
2125
|
shouldRunSupplyChain: input.options.supplyChain,
|
|
2155
2126
|
shouldComputeScore: false,
|
|
2156
2127
|
shouldShowProgressSpinners: false,
|
|
@@ -2159,7 +2130,8 @@ const runBaselineComparison = async (input) => {
|
|
|
2159
2130
|
});
|
|
2160
2131
|
const baseOutput = await runPromise(restoreLegacyThrow(runInspect({
|
|
2161
2132
|
directory: snapshot.tempDirectory,
|
|
2162
|
-
includePaths:
|
|
2133
|
+
includePaths: baseIncludePaths,
|
|
2134
|
+
maintainabilityFocusPaths,
|
|
2163
2135
|
customRulesOnly: input.options.customRulesOnly,
|
|
2164
2136
|
respectInlineDisables: input.options.respectInlineDisables,
|
|
2165
2137
|
warnings: input.options.warnings,
|
|
@@ -2168,7 +2140,7 @@ const runBaselineComparison = async (input) => {
|
|
|
2168
2140
|
includedTags: input.options.includedTags,
|
|
2169
2141
|
includeTagDefaults: input.options.includeTagDefaults,
|
|
2170
2142
|
nodeBinaryPath: input.resolvedNodeBinaryPath ?? void 0,
|
|
2171
|
-
runDeadCode:
|
|
2143
|
+
runDeadCode: input.options.deadCode,
|
|
2172
2144
|
isCi: input.options.isCi,
|
|
2173
2145
|
doctorVersion: VERSION,
|
|
2174
2146
|
runId: getRunId(),
|
|
@@ -2176,10 +2148,21 @@ const runBaselineComparison = async (input) => {
|
|
|
2176
2148
|
suppressScanSummary: true,
|
|
2177
2149
|
supplyChainManifestChanged: input.options.supplyChainManifestChanged,
|
|
2178
2150
|
deadlineEpochMs: input.deadlineEpochMs ?? void 0,
|
|
2179
|
-
signal: input.oxlintRuntime.abortSignal
|
|
2151
|
+
signal: input.oxlintRuntime.abortSignal,
|
|
2152
|
+
excludedProjectDirectories: baselineExcludedProjectDirectories,
|
|
2153
|
+
retainExcludedProjectDeadCodeDiagnostics: input.options.retainExcludedProjectDeadCodeDiagnostics
|
|
2180
2154
|
}, {}).pipe(provide(runtimeLayers), provideService(PerFileLintCacheEnabled, false), provideService(SidecarLintCacheEnabled, false), provideService(Console, silentConsole$1))), { signal: input.oxlintRuntime.abortSignal });
|
|
2181
|
-
if (baseOutput.didLintFail ||
|
|
2182
|
-
|
|
2155
|
+
if (baseOutput.didLintFail || baseOutput.didDeadCodeFail) return null;
|
|
2156
|
+
if (countIncompleteLintFiles(baseOutput.lintPartialFailures) > 0 && hasUnlintedSurvivingBaseFile({
|
|
2157
|
+
baseLintPaths: materializedLintPaths,
|
|
2158
|
+
headFiles: expectedHeadFiles,
|
|
2159
|
+
analyzedBaseFiles: baseOutput.analyzedFiles
|
|
2160
|
+
})) return null;
|
|
2161
|
+
const hasUnscannedUntrackedSourceFiles = filterSourceFiles(filterPathsOutsideDirectories({
|
|
2162
|
+
rootDirectory: input.directory,
|
|
2163
|
+
relativePaths: snapshot.untrackedFiles,
|
|
2164
|
+
excludedDirectories: input.options.excludedProjectDirectories
|
|
2165
|
+
}).map(toForwardSlashes)).some((filePath) => !analyzedHeadFiles.has(filePath));
|
|
2183
2166
|
const diagnosticDelta = computeDiagnosticDelta({
|
|
2184
2167
|
headDiagnostics: input.headDiagnostics,
|
|
2185
2168
|
baseDiagnostics: baseOutput.diagnostics,
|
|
@@ -2352,6 +2335,7 @@ const runInspectWithRuntime = async (directory, options, userConfig, hasConfigOv
|
|
|
2352
2335
|
directory,
|
|
2353
2336
|
precomputedSourceFileCount: options.precomputedSourceFileCount,
|
|
2354
2337
|
includePaths: options.includePaths,
|
|
2338
|
+
changedLineRanges: options.changedLineRanges ?? void 0,
|
|
2355
2339
|
customRulesOnly: options.customRulesOnly,
|
|
2356
2340
|
respectInlineDisables: options.respectInlineDisables,
|
|
2357
2341
|
warnings: options.warnings,
|
|
@@ -2382,7 +2366,7 @@ const runInspectWithRuntime = async (directory, options, userConfig, hasConfigOv
|
|
|
2382
2366
|
else runSync(error(highlighter.error(lintFailureReason)));
|
|
2383
2367
|
let inspectDiagnostics = output.diagnostics;
|
|
2384
2368
|
let baselineDelta;
|
|
2385
|
-
if (options.baseline && isDiffMode && !didLintFail && countIncompleteLintFiles(output.lintPartialFailures) === 0) {
|
|
2369
|
+
if (options.baseline && isDiffMode && !didLintFail && !output.didDeadCodeFail && countIncompleteLintFiles(output.lintPartialFailures) === 0) {
|
|
2386
2370
|
const comparison = await runBaselineComparison({
|
|
2387
2371
|
directory,
|
|
2388
2372
|
options,
|
|
@@ -2535,13 +2519,13 @@ const resolveCursorBundledNode = () => {
|
|
|
2535
2519
|
if (!localAppData) return null;
|
|
2536
2520
|
const cursorAgentRoot = path$1.join(localAppData, "cursor-agent", "versions");
|
|
2537
2521
|
try {
|
|
2538
|
-
const versions = fs$
|
|
2522
|
+
const versions = fs$2.readdirSync(cursorAgentRoot);
|
|
2539
2523
|
if (versions.length === 0) return null;
|
|
2540
2524
|
const sortedVersions = versions.sort((a, b) => b.localeCompare(a, void 0, { numeric: true }));
|
|
2541
2525
|
for (const version of sortedVersions) try {
|
|
2542
2526
|
const bundledNodePath = path$1.join(cursorAgentRoot, version, "node.exe");
|
|
2543
2527
|
const entryScriptPath = path$1.join(cursorAgentRoot, version, "index.js");
|
|
2544
|
-
if (fs$
|
|
2528
|
+
if (fs$2.statSync(bundledNodePath).isFile() && fs$2.statSync(entryScriptPath).isFile()) return {
|
|
2545
2529
|
command: bundledNodePath,
|
|
2546
2530
|
args: [entryScriptPath]
|
|
2547
2531
|
};
|
|
@@ -2555,15 +2539,15 @@ const resolveWindowsCmdEntryScript = (command) => {
|
|
|
2555
2539
|
for (const directory of pathDirectories) {
|
|
2556
2540
|
const cmdFilePath = path$1.join(directory, `${command}.cmd`);
|
|
2557
2541
|
try {
|
|
2558
|
-
if (!fs$
|
|
2542
|
+
if (!fs$2.statSync(cmdFilePath).isFile()) continue;
|
|
2559
2543
|
} catch {
|
|
2560
2544
|
continue;
|
|
2561
2545
|
}
|
|
2562
2546
|
try {
|
|
2563
|
-
const entryScriptMatch = fs$
|
|
2547
|
+
const entryScriptMatch = fs$2.readFileSync(cmdFilePath, "utf8").match(/"%(?:~dp0|dp0%)\\([^"]+\.(?:m?js|cjs))"/);
|
|
2564
2548
|
if (!entryScriptMatch) continue;
|
|
2565
2549
|
const resolvedScriptPath = path$1.resolve(directory, entryScriptMatch[1]);
|
|
2566
|
-
if (fs$
|
|
2550
|
+
if (fs$2.statSync(resolvedScriptPath).isFile()) return resolvedScriptPath;
|
|
2567
2551
|
} catch {}
|
|
2568
2552
|
}
|
|
2569
2553
|
return null;
|
|
@@ -2889,7 +2873,7 @@ const buildCodeFrame = (input) => {
|
|
|
2889
2873
|
const absolutePath = resolveAbsolutePath(input.filePath, input.rootDirectory);
|
|
2890
2874
|
let source;
|
|
2891
2875
|
try {
|
|
2892
|
-
source = fs$
|
|
2876
|
+
source = fs$2.readFileSync(absolutePath, "utf8");
|
|
2893
2877
|
} catch {
|
|
2894
2878
|
return null;
|
|
2895
2879
|
}
|
|
@@ -2910,7 +2894,7 @@ const buildCodeFrame = (input) => {
|
|
|
2910
2894
|
const discoverWorkspacePackages = (rootDirectory) => {
|
|
2911
2895
|
const hasRootPackageJson = isFile(path$1.join(rootDirectory, "package.json"));
|
|
2912
2896
|
const packages = listWorkspacePackages(rootDirectory);
|
|
2913
|
-
if (packages.length === 0 && (!hasRootPackageJson || isMonorepoRoot(rootDirectory))) return
|
|
2897
|
+
if (packages.length === 0 && (!hasRootPackageJson || isMonorepoRoot(rootDirectory))) return discoverSupportedSubprojects(rootDirectory);
|
|
2914
2898
|
return packages;
|
|
2915
2899
|
};
|
|
2916
2900
|
const selectProjects = async (rootDirectory, projectFlag, skipPrompts, configProjects) => {
|
|
@@ -2995,6 +2979,17 @@ const projectManifestChanged = (rootDirectory, projectDirectory, diffInfo) => {
|
|
|
2995
2979
|
return diffInfo.changedFiles.some((filePath) => toForwardSlashes(filePath) === manifestPath);
|
|
2996
2980
|
};
|
|
2997
2981
|
//#endregion
|
|
2982
|
+
//#region src/cli/utils/resolve-excluded-project-directories.ts
|
|
2983
|
+
const resolveExcludedProjectDirectories = (scanDirectory, projectDirectories) => {
|
|
2984
|
+
const excludedDirectories = /* @__PURE__ */ new Map();
|
|
2985
|
+
for (const projectDirectory of projectDirectories) {
|
|
2986
|
+
if (!isPathInsideDirectory(projectDirectory, scanDirectory)) continue;
|
|
2987
|
+
const resolvedDirectory = path$1.resolve(projectDirectory);
|
|
2988
|
+
if (!excludedDirectories.has(resolvedDirectory)) excludedDirectories.set(resolvedDirectory, projectDirectory);
|
|
2989
|
+
}
|
|
2990
|
+
return [...excludedDirectories.values()];
|
|
2991
|
+
};
|
|
2992
|
+
//#endregion
|
|
2998
2993
|
//#region src/cli/utils/validate-mode-flags.ts
|
|
2999
2994
|
const usedDiffAlias = (flags) => flags.diff !== void 0 && flags.diff !== false && flags.diff !== "false" && flags.diff !== "";
|
|
3000
2995
|
const usedScope = (flags) => typeof flags.scope === "string" && flags.scope.length > 0;
|
|
@@ -3002,6 +2997,12 @@ const validateIncludeUntrackedScope = (includeUntracked, scope) => {
|
|
|
3002
2997
|
if (!includeUntracked || scope !== void 0 && scope !== "full") return;
|
|
3003
2998
|
throw new CliInputError("--include-untracked requires the files, changed, or lines scope (via --scope, --diff, or config).");
|
|
3004
2999
|
};
|
|
3000
|
+
const validateFilePathSelectionFlags = (flags) => {
|
|
3001
|
+
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.");
|
|
3002
|
+
if (usedDiffAlias(flags)) throw new CliInputError("Cannot combine file path arguments with --diff. File path arguments use the files scope.");
|
|
3003
|
+
if (usedScope(flags) && flags.scope !== "files") throw new CliInputError(`Cannot combine file path arguments with --scope ${flags.scope}. File path arguments use --scope files.`);
|
|
3004
|
+
if (flags.base !== void 0) throw new CliInputError("Cannot combine file path arguments with --base. The selected files do not need a Git base.");
|
|
3005
|
+
};
|
|
3005
3006
|
const validateModeFlags = (flags) => {
|
|
3006
3007
|
if (usedScope(flags) && usedDiffAlias(flags)) throw new CliInputError("Cannot combine --scope and --diff; --diff is the deprecated alias.");
|
|
3007
3008
|
if (flags.staged && usedDiffAlias(flags)) throw new CliInputError("Cannot combine --staged and --diff; pick one mode.");
|
|
@@ -3026,7 +3027,7 @@ const shouldFailScanGate = (input) => {
|
|
|
3026
3027
|
return shouldBlockCi(filterScansForSurface(input.scans, "ciFailure"), input.blockingLevel);
|
|
3027
3028
|
};
|
|
3028
3029
|
//#endregion
|
|
3029
|
-
export {
|
|
3030
|
+
export { createInvocationInspect as A, hasIncompleteScoreAnalysis as B, detectLaunchableAgents as C, buildHandoffPayload as D, launchCliAgent as E, formatSkippedCheckLabel as F, buildRulePriorityMap as G, buildNoScoreMessage as H, formatElapsedTime as I, formatLearnMoreLine as J, buildSortedRuleGroups as K, printFooter as L, resolveMergeBaseRef as M, printHeadlessReport as N, collectProjectSourceFileCounts as O, pluralize as P, printDiagnosticsDump as R, setUpGitHubActions as S, copyToClipboard as T, buildEmptyReportMessage as U, filterDiagnosticsByCategories as V, hasLintHardFailure as W, retryMissingProjectScores as _, validateModeFlags as a, filterScansForSurface as b, partitionProjectScanOutcomes as c, buildCodeFrame as d, resolveProjectScan as f, resolveProjectRelativeDirectory as g, resolveProjectSourceFilePaths as h, validateIncludeUntrackedScope as i, inspect as j, deduplicateProjectScans as k, discoverWorkspacePackages as l, resolveProjectDiffIncludePaths as m, formatSkippedProjectsMessage as n, resolveExcludedProjectDirectories as o, resolveProjectChangedLineRanges as p, formatFixRecipeLine as q, validateFilePathSelectionFlags as r, projectManifestChanged as s, shouldFailScanGate as t, selectProjects as u, resolveWorkspaceDeadCodeOwner as v, CLI_AGENT_BINARIES as w, countUniqueScannedFiles as x, resolveCliInspectOptions as y, formatDiagnosticSite as z };
|
|
3030
3031
|
|
|
3031
|
-
//# sourceMappingURL=should-fail-scan-gate-
|
|
3032
|
-
//# debugId=
|
|
3032
|
+
//# sourceMappingURL=should-fail-scan-gate-CfG_ppAV.js.map
|
|
3033
|
+
//# debugId=159aa887-619a-5d42-9d32-79fce37783d6
|