react-doctor 0.6.3-dev.bdf8074 → 0.6.3-dev.ce49250

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="4d2b3e2c-ffb8-5da4-bf5d-833e608822bb")}catch(e){}}();
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]="cb06d1e4-d50b-5715-af77-13eb97c43e07")}catch(e){}}();
3
3
  import { createRequire } from "node:module";
4
4
  import * as NodeChildProcess from "node:child_process";
5
5
  import { execFile, execFileSync, spawn, spawnSync } from "node:child_process";
@@ -9,7 +9,7 @@ import * as NFS from "node:fs";
9
9
  import fs, { mkdtempSync, rmSync } from "node:fs";
10
10
  import process$1 from "node:process";
11
11
  import * as ts from "typescript";
12
- import reactDoctorPlugin, { ALL_REACT_DOCTOR_RULE_KEYS, CROSS_FILE_DEPENDENCY_COLLECTORS, CROSS_FILE_RULE_IDS, FRAMEWORK_SPECIFIC_RULE_KEYS, MOTION_LIBRARY_PACKAGES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, classifySecurityScanFile, collectCrossFileDependencyProbes, shouldReadSecurityScanContent } from "oxlint-plugin-react-doctor";
12
+ import reactDoctorPlugin, { ALL_REACT_DOCTOR_RULE_KEYS, CROSS_FILE_RULE_IDS, FRAMEWORK_SPECIFIC_RULE_KEYS, MOTION_LIBRARY_PACKAGES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, classifySecurityScanFile, shouldReadSecurityScanContent } from "oxlint-plugin-react-doctor";
13
13
  import * as OS from "node:os";
14
14
  import os, { tmpdir } from "node:os";
15
15
  import { parseJSON5 } from "confbox";
@@ -36129,7 +36129,6 @@ const SOCKET_PACKAGE_PAGE_BASE = "https://socket.dev/npm/package";
36129
36129
  const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
36130
36130
  const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
36131
36131
  const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
36132
- const SIDECAR_LINT_CACHE_FILENAME = "sidecar-lint-cache.json";
36133
36132
  const CORE_PACKAGE_VERSION = "0.6.3";
36134
36133
  const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
36135
36134
  const DEAD_CODE_SUMMARY_CACHE_FILENAME = "dead-code-summaries.json";
@@ -38818,29 +38817,6 @@ var PerFileLintCacheEnabled = class extends Reference("react-doctor/PerFileLintC
38818
38817
  return true;
38819
38818
  } }) {};
38820
38819
  /**
38821
- * Whether the sidecar lint cache (`runners/oxlint/sidecar-lint-cache.ts`) is
38822
- * active. Defaults ON — warm rescans replay the cross-file rules' cached
38823
- * diagnostics for every file whose dependency fingerprint (the probe set the
38824
- * plugin's collectors recorded) still matches the tree, and re-lint only the
38825
- * rest. Only reachable when the per-file lint cache itself is on (the
38826
- * sidecar covers its cache hits), so `REACT_DOCTOR_NO_CACHE` /
38827
- * `REACT_DOCTOR_NO_FILE_CACHE` implicitly disable it too. Opt-OUT knobs:
38828
- *
38829
- * - `REACT_DOCTOR_NO_CACHE` — the global off-switch.
38830
- * - `REACT_DOCTOR_NO_SIDECAR_CACHE` — granular rollback hatch: keep the
38831
- * per-file cache but run the always-fresh sidecar over every hit
38832
- * (the pre-cache behavior).
38833
- *
38834
- * Tests override via `Layer.succeed(SidecarLintCacheEnabled, false)`.
38835
- */
38836
- var SidecarLintCacheEnabled = class extends Reference("react-doctor/SidecarLintCacheEnabled", { defaultValue: () => {
38837
- const noCache = process.env["REACT_DOCTOR_NO_CACHE"]?.toLowerCase() ?? "";
38838
- const noSidecarCache = process.env["REACT_DOCTOR_NO_SIDECAR_CACHE"]?.toLowerCase() ?? "";
38839
- if (CACHE_DISABLED_VALUES$1.has(noCache)) return false;
38840
- if (CACHE_DISABLED_VALUES$1.has(noSidecarCache)) return false;
38841
- return true;
38842
- } }) {};
38843
- /**
38844
38820
  * Whether the whole-project dead-code result cache
38845
38821
  * (`dead-code/dead-code-result-cache.ts`) is active. Defaults ON — a rescan
38846
38822
  * whose inputs (source tree, manifests, configs, analyzer version) are
@@ -40644,10 +40620,10 @@ const computeDeadCodeCacheKey = (input) => crypto.createHash("sha1").update(JSON
40644
40620
  tsConfigFile: input.tsConfigPath === void 0 ? null : Path.relative(input.rootDirectory, input.tsConfigPath).replace(/\\/g, "/"),
40645
40621
  files: collectAnalyzedFileFingerprints(input.rootDirectory)
40646
40622
  })).digest("hex");
40647
- const validateDiagnostic$2 = decodeUnknownSync(Diagnostic);
40623
+ const validateDiagnostic$1 = decodeUnknownSync(Diagnostic);
40648
40624
  const decodeCachedDiagnostics = (raw) => {
40649
40625
  try {
40650
- for (const entry of raw) validateDiagnostic$2(entry);
40626
+ for (const entry of raw) validateDiagnostic$1(entry);
40651
40627
  return raw;
40652
40628
  } catch {
40653
40629
  return null;
@@ -42137,7 +42113,7 @@ const buildUserPluginRules = (userPlugin, severityControls) => {
42137
42113
  }
42138
42114
  return enabled;
42139
42115
  };
42140
- const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false, ruleSelection, sidecarRuleIdFilter }) => {
42116
+ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false, ruleSelection }) => {
42141
42117
  const reactHooksJsPlugin = disableReactHooksJsPlugin || ruleSelection === "sidecar" ? null : resolveReactHooksJsPlugin(project.hasReactCompiler, customRulesOnly);
42142
42118
  const reactCompilerRules = reactHooksJsPlugin ? applyRuleSeverityControls(filterRulesToAvailable(REACT_COMPILER_RULES, "react-hooks-js", reactHooksJsPlugin.availableRuleNames), severityControls) : {};
42143
42119
  const jsPlugins = [];
@@ -42149,7 +42125,6 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
42149
42125
  if (!rule) continue;
42150
42126
  if (ruleSelection === "cacheable" && CROSS_FILE_RULE_IDS.has(registryEntry.id)) continue;
42151
42127
  if (ruleSelection === "sidecar" && !CROSS_FILE_RULE_IDS.has(registryEntry.id)) continue;
42152
- if (ruleSelection === "sidecar" && sidecarRuleIdFilter !== void 0 && !sidecarRuleIdFilter.has(registryEntry.id)) continue;
42153
42128
  if (rule.scan !== void 0) continue;
42154
42129
  if (customRulesOnly && registryEntry.originallyExternal) continue;
42155
42130
  if (rule.framework !== "global" && !rule.requires) continue;
@@ -42194,23 +42169,23 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
42194
42169
  }
42195
42170
  };
42196
42171
  };
42197
- const validateDiagnostic$1 = decodeUnknownSync(Diagnostic);
42172
+ const validateDiagnostic = decodeUnknownSync(Diagnostic);
42198
42173
  const decodeFileDiagnostics = (raw) => {
42199
42174
  if (!Array.isArray(raw)) return null;
42200
42175
  try {
42201
- for (const entry of raw) validateDiagnostic$1(entry);
42176
+ for (const entry of raw) validateDiagnostic(entry);
42202
42177
  return raw;
42203
42178
  } catch {
42204
42179
  return null;
42205
42180
  }
42206
42181
  };
42207
- const emptyCache$1 = () => ({
42182
+ const emptyCache = () => ({
42208
42183
  version: 1,
42209
42184
  rulesets: {}
42210
42185
  });
42211
- const loadRulesetEntries$1 = (cacheFilePath, rulesetHash) => {
42186
+ const loadRulesetEntries = (cacheFilePath, rulesetHash) => {
42212
42187
  const entries = /* @__PURE__ */ new Map();
42213
- const persisted = failOpenReadJson(cacheFilePath, emptyCache$1());
42188
+ const persisted = failOpenReadJson(cacheFilePath, emptyCache());
42214
42189
  if (persisted.version !== 1 || !isRecord$1(persisted.rulesets)) return entries;
42215
42190
  const bucket = persisted.rulesets[rulesetHash];
42216
42191
  if (!isRecord$1(bucket) || !isRecord$1(bucket.files)) return entries;
@@ -42222,156 +42197,20 @@ const loadRulesetEntries$1 = (cacheFilePath, rulesetHash) => {
42222
42197
  };
42223
42198
  const createFileLintCache = (cacheDirectory, rulesetHash) => {
42224
42199
  const cacheFilePath = Path.join(cacheDirectory, FILE_LINT_CACHE_FILENAME);
42225
- const entries = loadRulesetEntries$1(cacheFilePath, rulesetHash);
42200
+ const entries = loadRulesetEntries(cacheFilePath, rulesetHash);
42226
42201
  return {
42227
42202
  lookup: (fileKey) => entries.get(fileKey) ?? null,
42228
42203
  store: (fileKey, diagnostics) => {
42229
42204
  entries.delete(fileKey);
42230
42205
  entries.set(fileKey, diagnostics);
42231
42206
  },
42232
- persist: () => {
42233
- const onDisk = failOpenReadJson(cacheFilePath, emptyCache$1());
42234
- const rulesets = onDisk.version === 1 && isRecord$1(onDisk.rulesets) ? { ...onDisk.rulesets } : {};
42235
- const existingBucket = rulesets[rulesetHash];
42236
- const existingFiles = isRecord$1(existingBucket) && isRecord$1(existingBucket.files) ? existingBucket.files : {};
42237
- const ourFiles = {};
42238
- for (const [fileKey, diagnostics] of entries) ourFiles[fileKey] = diagnostics;
42239
- const cappedEntries = Object.entries({
42240
- ...existingFiles,
42241
- ...ourFiles
42242
- }).slice(-FILE_LINT_CACHE_MAX_FILE_COUNT);
42243
- rulesets[rulesetHash] = {
42244
- updatedAtMs: Date.now(),
42245
- files: Object.fromEntries(cappedEntries)
42246
- };
42247
- atomicWriteJson(cacheFilePath, {
42248
- version: 1,
42249
- rulesets: Object.fromEntries(Object.entries(rulesets).filter(([, bucket]) => isRecord$1(bucket) && typeof bucket.updatedAtMs === "number").sort(([, first], [, second]) => second.updatedAtMs - first.updatedAtMs).slice(0, 8))
42250
- });
42251
- }
42252
- };
42253
- };
42254
- const hashFileContents = (filePath) => {
42255
- try {
42256
- return crypto.createHash("sha1").update(NFS.readFileSync(filePath)).digest("hex");
42257
- } catch {
42258
- return null;
42259
- }
42260
- };
42261
- const ABSENT_CONTENT_ANSWER = "absent";
42262
- /**
42263
- * Answers dependency probes for the sidecar lint cache — used both when
42264
- * STORING an entry (turn the collector's probe paths into answers) and when
42265
- * VERIFYING one (re-answer each stored probe against the current tree).
42266
- * Store and verify must agree exactly, so both go through this one resolver.
42267
- *
42268
- * - `content` — SHA-1 of the file's bytes, `"absent"` when unreadable
42269
- * (missing, a directory, permission failure). Source files reuse the
42270
- * hashes the cache-partition loop already computed
42271
- * (`contentHashByRelativePath`); everything else (package.json,
42272
- * tsconfig, ignored files) is hashed on demand.
42273
- * - `exists` — the existence class a module-resolution candidate probe
42274
- * sees: `"file"`, `"dir"`, or `"none"` (missing or an exotic entry —
42275
- * `statSync` follows symlinks, matching the resolvers).
42276
- *
42277
- * Answers are memoized per scan: the filesystem is frozen for the run, and
42278
- * hot dependency paths (a barrel imported by hundreds of files, the shared
42279
- * tsconfig chain) repeat across many files' probe sets.
42280
- */
42281
- const createSidecarProbeAnswerResolver = (input) => {
42282
- const contentAnswerByPath = /* @__PURE__ */ new Map();
42283
- const existsAnswerByPath = /* @__PURE__ */ new Map();
42284
- const toRelativePath = (absolutePath) => Path.relative(input.rootDirectory, absolutePath).replaceAll("\\", "/");
42285
- const contentAnswer = (relativePath) => {
42286
- const memoized = contentAnswerByPath.get(relativePath);
42287
- if (memoized !== void 0) return memoized;
42288
- const answer = input.contentHashByRelativePath.get(relativePath) ?? hashFileContents(Path.resolve(input.rootDirectory, relativePath)) ?? ABSENT_CONTENT_ANSWER;
42289
- contentAnswerByPath.set(relativePath, answer);
42290
- return answer;
42291
- };
42292
- const existsAnswer = (relativePath) => {
42293
- const memoized = existsAnswerByPath.get(relativePath);
42294
- if (memoized !== void 0) return memoized;
42295
- let answer;
42296
- try {
42297
- const stat = NFS.statSync(Path.resolve(input.rootDirectory, relativePath));
42298
- answer = stat.isFile() ? "file" : stat.isDirectory() ? "dir" : "none";
42299
- } catch {
42300
- answer = "none";
42301
- }
42302
- existsAnswerByPath.set(relativePath, answer);
42303
- return answer;
42304
- };
42305
- return {
42306
- answerFor: (kind, relativePath) => kind === "content" ? contentAnswer(relativePath) : existsAnswer(relativePath),
42307
- toRelativePath
42308
- };
42309
- };
42310
- const validateDiagnostic = decodeUnknownSync(Diagnostic);
42311
- const isProbeKind = (value) => value === "content" || value === "exists";
42312
- const decodeSidecarFileEntry = (raw) => {
42313
- if (!isRecord$1(raw) || !Array.isArray(raw.probes) || !Array.isArray(raw.diagnostics)) return null;
42314
- const probes = [];
42315
- for (const rawProbe of raw.probes) {
42316
- if (!Array.isArray(rawProbe) || rawProbe.length !== 3) return null;
42317
- const [kind, probePath, answer] = rawProbe;
42318
- if (!isProbeKind(kind) || typeof probePath !== "string" || typeof answer !== "string") return null;
42319
- probes.push({
42320
- kind,
42321
- path: probePath,
42322
- answer
42323
- });
42324
- }
42325
- try {
42326
- for (const entry of raw.diagnostics) validateDiagnostic(entry);
42327
- } catch {
42328
- return null;
42329
- }
42330
- return {
42331
- probes,
42332
- diagnostics: raw.diagnostics
42333
- };
42334
- };
42335
- const encodeSidecarFileEntry = (entry) => ({
42336
- probes: entry.probes.map((probe) => [
42337
- probe.kind,
42338
- probe.path,
42339
- probe.answer
42340
- ]),
42341
- diagnostics: entry.diagnostics
42342
- });
42343
- const emptyCache = () => ({
42344
- version: 1,
42345
- rulesets: {}
42346
- });
42347
- const loadRulesetEntries = (cacheFilePath, rulesetHash) => {
42348
- const entries = /* @__PURE__ */ new Map();
42349
- const persisted = failOpenReadJson(cacheFilePath, emptyCache());
42350
- if (persisted.version !== 1 || !isRecord$1(persisted.rulesets)) return entries;
42351
- const bucket = persisted.rulesets[rulesetHash];
42352
- if (!isRecord$1(bucket) || !isRecord$1(bucket.files)) return entries;
42353
- for (const [fileKey, rawEntry] of Object.entries(bucket.files)) {
42354
- const decoded = decodeSidecarFileEntry(rawEntry);
42355
- if (decoded !== null) entries.set(fileKey, decoded);
42356
- }
42357
- return entries;
42358
- };
42359
- const createSidecarLintCache = (cacheDirectory, rulesetHash) => {
42360
- const cacheFilePath = Path.join(cacheDirectory, SIDECAR_LINT_CACHE_FILENAME);
42361
- const entries = loadRulesetEntries(cacheFilePath, rulesetHash);
42362
- return {
42363
- lookup: (fileKey) => entries.get(fileKey) ?? null,
42364
- store: (fileKey, entry) => {
42365
- entries.delete(fileKey);
42366
- entries.set(fileKey, entry);
42367
- },
42368
42207
  persist: () => {
42369
42208
  const onDisk = failOpenReadJson(cacheFilePath, emptyCache());
42370
42209
  const rulesets = onDisk.version === 1 && isRecord$1(onDisk.rulesets) ? { ...onDisk.rulesets } : {};
42371
42210
  const existingBucket = rulesets[rulesetHash];
42372
42211
  const existingFiles = isRecord$1(existingBucket) && isRecord$1(existingBucket.files) ? existingBucket.files : {};
42373
42212
  const ourFiles = {};
42374
- for (const [fileKey, entry] of entries) ourFiles[fileKey] = encodeSidecarFileEntry(entry);
42213
+ for (const [fileKey, diagnostics] of entries) ourFiles[fileKey] = diagnostics;
42375
42214
  const cappedEntries = Object.entries({
42376
42215
  ...existingFiles,
42377
42216
  ...ourFiles
@@ -43386,6 +43225,13 @@ const validateRuleRegistration = () => {
43386
43225
  ].filter((entry) => entry !== null).join("; ");
43387
43226
  console.warn(`[react-doctor] rule-registration drift: ${detail}`);
43388
43227
  };
43228
+ const hashFileContents = (filePath) => {
43229
+ try {
43230
+ return crypto.createHash("sha1").update(NFS.readFileSync(filePath)).digest("hex");
43231
+ } catch {
43232
+ return null;
43233
+ }
43234
+ };
43389
43235
  /**
43390
43236
  * Balanced LPT (longest-processing-time-first) lint batch planner. Where
43391
43237
  * `batchIncludePaths` greedily fills 100-file batches and leaves a small
@@ -43467,106 +43313,6 @@ const writeOxlintConfig = (configPath, configToWrite) => {
43467
43313
  NFS.closeSync(fileHandle);
43468
43314
  }
43469
43315
  };
43470
- /**
43471
- * Attributes diagnostics back to the file that produced them by the
43472
- * normalized path oxlint echoes. Returns `null` when ANY diagnostic can't be
43473
- * attributed — the path forms don't align, so the caller must skip its cache
43474
- * store rather than risk caching a wrong empty result for a file that
43475
- * actually had diagnostics.
43476
- */
43477
- const attributeDiagnosticsToFiles = (diagnostics, files) => {
43478
- const fileByNormalizedPath = /* @__PURE__ */ new Map();
43479
- for (const file of files) fileByNormalizedPath.set(file.replaceAll("\\", "/"), file);
43480
- const diagnosticsByFile = /* @__PURE__ */ new Map();
43481
- for (const diagnostic of diagnostics) {
43482
- const file = fileByNormalizedPath.get(diagnostic.filePath);
43483
- if (file === void 0) return null;
43484
- const fileDiagnostics = diagnosticsByFile.get(file) ?? [];
43485
- fileDiagnostics.push(diagnostic);
43486
- diagnosticsByFile.set(file, fileDiagnostics);
43487
- }
43488
- return diagnosticsByFile;
43489
- };
43490
- /**
43491
- * Collects each file's cross-file dependency probes (via the plugin's
43492
- * collectors) with their current answers. Fail-open per file: an unreadable /
43493
- * unparseable / collector-failing file maps to `null` (no entry stored — it
43494
- * re-lints next scan). The filesystem is frozen for the scan, so this can run
43495
- * CONCURRENTLY with an oxlint child-process await — the caller starts it
43496
- * unawaited before a lint pass and awaits it at store time, hiding the
43497
- * in-process parse + resolution cost under the subprocess wait. The loop
43498
- * yields on the cooperative time budget so the child's stdout handling (and
43499
- * sibling fibers) keep flowing; each per-file collection is synchronous, so
43500
- * the module-level probe recorder never sees interleaved use. Never rejects.
43501
- */
43502
- const collectSidecarProbesForFiles = async (input) => {
43503
- const buildProbes = (file) => {
43504
- const absoluteFilePath = Path.resolve(input.rootDirectory, file);
43505
- let trace;
43506
- try {
43507
- trace = collectCrossFileDependencyProbes({
43508
- absoluteFilePath,
43509
- sourceText: NFS.readFileSync(absoluteFilePath, "utf8"),
43510
- ruleIds: input.boundedSidecarRuleIds
43511
- });
43512
- } catch {
43513
- return null;
43514
- }
43515
- if (trace === null) return null;
43516
- const probes = [];
43517
- for (const contentPath of trace.contentPaths) {
43518
- const relativePath = input.probeAnswers.toRelativePath(contentPath);
43519
- probes.push({
43520
- kind: "content",
43521
- path: relativePath,
43522
- answer: input.probeAnswers.answerFor("content", relativePath)
43523
- });
43524
- }
43525
- for (const existencePath of trace.existencePaths) {
43526
- const relativePath = input.probeAnswers.toRelativePath(existencePath);
43527
- probes.push({
43528
- kind: "exists",
43529
- path: relativePath,
43530
- answer: input.probeAnswers.answerFor("exists", relativePath)
43531
- });
43532
- }
43533
- return probes;
43534
- };
43535
- const probesByFile = /* @__PURE__ */ new Map();
43536
- let collectSliceStartedAt = performance.now();
43537
- for (const file of input.files) {
43538
- probesByFile.set(file, buildProbes(file));
43539
- if (performance.now() - collectSliceStartedAt >= 12) {
43540
- await yieldToEventLoop();
43541
- collectSliceStartedAt = performance.now();
43542
- }
43543
- }
43544
- return probesByFile;
43545
- };
43546
- /**
43547
- * Stores each freshly-linted file's probe set + cross-file diagnostics so
43548
- * the next scan can replay instead of re-linting.
43549
- */
43550
- const storeSidecarEntries = (input) => {
43551
- let didStoreAnyEntry = false;
43552
- for (const pass of input.storablePasses) {
43553
- if (!pass.isTrusted) continue;
43554
- const diagnosticsByFile = attributeDiagnosticsToFiles(pass.diagnostics, pass.files);
43555
- if (diagnosticsByFile === null) continue;
43556
- for (const file of pass.files) {
43557
- const cacheKey = input.cacheKeyByFile.get(file);
43558
- if (cacheKey === void 0) continue;
43559
- const probes = pass.probesByFile.get(file);
43560
- if (probes === void 0 || probes === null) continue;
43561
- input.sidecarCache.store(cacheKey, {
43562
- probes,
43563
- diagnostics: diagnosticsByFile.get(file) ?? []
43564
- });
43565
- didStoreAnyEntry = true;
43566
- }
43567
- }
43568
- if (didStoreAnyEntry) input.sidecarCache.persist();
43569
- };
43570
43316
  const REACT_HOOKS_JS_DROP_PREFIX = "React Compiler rules (react-hooks-js/*) skipped — eslint-plugin-react-hooks failed to load in this environment";
43571
43317
  /**
43572
43318
  * Detects an oxlint config-load crash caused by the optional
@@ -43608,7 +43354,7 @@ const reactHooksJsPluginDropNote = (error) => {
43608
43354
  * 6. always restore disable directives + clean up the temp dir
43609
43355
  */
43610
43356
  const runOxlint = async (options) => {
43611
- const { rootDirectory, project, includePaths, nodeBinaryPath = process.execPath, customRulesOnly = false, respectInlineDisables = true, adoptExistingLintConfig = true, ignoredTags = /* @__PURE__ */ new Set(), userConfig, configSourceDirectory = rootDirectory, onPartialFailure, perFileLintCacheEnabled = false, sidecarLintCacheEnabled = false, onCacheStats, onSidecarStats, spawnTimeoutMs, outputMaxBytes, lintBatchOrdering = "cost" } = options;
43357
+ const { rootDirectory, project, includePaths, nodeBinaryPath = process.execPath, customRulesOnly = false, respectInlineDisables = true, adoptExistingLintConfig = true, ignoredTags = /* @__PURE__ */ new Set(), userConfig, configSourceDirectory = rootDirectory, onPartialFailure, perFileLintCacheEnabled = false, onCacheStats, spawnTimeoutMs, outputMaxBytes, lintBatchOrdering = "cost" } = options;
43612
43358
  const serverAuthFunctionNames = Array.isArray(userConfig?.serverAuthFunctionNames) ? userConfig.serverAuthFunctionNames.filter((entry) => typeof entry === "string" && entry.length > 0) : void 0;
43613
43359
  const severityControls = buildRuleSeverityControls(userConfig);
43614
43360
  validateRuleRegistration();
@@ -43626,8 +43372,7 @@ const runOxlint = async (options) => {
43626
43372
  severityControls,
43627
43373
  userPlugins,
43628
43374
  disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin,
43629
- ruleSelection: overrides.ruleSelection,
43630
- sidecarRuleIdFilter: overrides.sidecarRuleIdFilter
43375
+ ruleSelection: overrides.ruleSelection
43631
43376
  });
43632
43377
  const restoreDisableDirectives = respectInlineDisables ? () => {} : await neutralizeDisableDirectives(rootDirectory, includePaths);
43633
43378
  const configDirectory = NFS.mkdtempSync(Path.join(os.tmpdir(), "react-doctor-oxlintrc-"));
@@ -43730,18 +43475,15 @@ const runOxlint = async (options) => {
43730
43475
  });
43731
43476
  const cache = createFileLintCache(resolveReactDoctorCacheDir(rootDirectory), rulesetHash);
43732
43477
  const cacheKeyByFile = /* @__PURE__ */ new Map();
43733
- const contentHashByRelativePath = /* @__PURE__ */ new Map();
43734
43478
  const missFiles = [];
43735
43479
  const replayedDiagnostics = [];
43736
43480
  let hashSliceStartedAt = performance.now();
43737
43481
  for (const candidateFile of candidateFiles) {
43738
- const absoluteCandidatePath = Path.resolve(rootDirectory, candidateFile);
43739
- const contentHash = hashFileContents(absoluteCandidatePath);
43482
+ const contentHash = hashFileContents(Path.resolve(rootDirectory, candidateFile));
43740
43483
  if (contentHash === null) {
43741
43484
  missFiles.push(candidateFile);
43742
43485
  continue;
43743
43486
  }
43744
- contentHashByRelativePath.set(Path.relative(rootDirectory, absoluteCandidatePath).replaceAll("\\", "/"), contentHash);
43745
43487
  const cacheKey = `${candidateFile.replaceAll("\\", "/")}\u0000${contentHash}`;
43746
43488
  cacheKeyByFile.set(candidateFile, cacheKey);
43747
43489
  const cachedDiagnostics = cache.lookup(cacheKey);
@@ -43753,111 +43495,43 @@ const runOxlint = async (options) => {
43753
43495
  }
43754
43496
  }
43755
43497
  const cacheHitFileCount = candidateFiles.length - missFiles.length;
43756
- const enabledSidecarRuleIds = Object.keys(buildConfig({
43757
- extendsPaths: [],
43758
- ruleSelection: "sidecar"
43759
- }).rules).map((ruleKey) => ruleKey.slice(ruleKey.lastIndexOf("/") + 1)).filter((ruleId) => CROSS_FILE_RULE_IDS.has(ruleId));
43760
- const boundedSidecarRuleIds = enabledSidecarRuleIds.filter((ruleId) => CROSS_FILE_DEPENDENCY_COLLECTORS.has(ruleId));
43761
- const boundedSidecarRuleIdSet = new Set(boundedSidecarRuleIds);
43762
- const unboundedSidecarRuleIds = enabledSidecarRuleIds.filter((ruleId) => !CROSS_FILE_DEPENDENCY_COLLECTORS.has(ruleId));
43763
- const useSidecarCache = sidecarLintCacheEnabled && boundedSidecarRuleIds.length > 0;
43764
- const sidecarCache = useSidecarCache ? createSidecarLintCache(resolveReactDoctorCacheDir(rootDirectory), computeRulesetHash({
43765
- config: buildConfig({
43766
- extendsPaths: [],
43767
- ruleSelection: "sidecar",
43768
- sidecarRuleIdFilter: boundedSidecarRuleIdSet
43769
- }),
43770
- toolchainVersions: resolveOxlintToolchainVersions(nodeBinaryPath),
43771
- ignorePatterns: combinedPatterns,
43772
- tsconfigContent
43773
- })) : null;
43774
- const probeAnswers = createSidecarProbeAnswerResolver({
43775
- rootDirectory,
43776
- contentHashByRelativePath
43777
- });
43778
- const missProbesTask = sidecarCache === null ? null : collectSidecarProbesForFiles({
43779
- files: missFiles,
43780
- rootDirectory,
43781
- boundedSidecarRuleIds,
43782
- probeAnswers
43783
- });
43784
43498
  const fullResult = await runConfigOverFiles((overrides) => buildConfig({
43785
43499
  extendsPaths: [],
43786
43500
  disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin
43787
43501
  }), "oxlintrc.full.json", missFiles, options.onFileProgress && ((scannedFileCount) => options.onFileProgress?.(scannedFileCount, candidateFiles.length)));
43788
43502
  const missFileSet = new Set(missFiles);
43789
- const hitFiles = candidateFiles.filter((candidateFile) => !missFileSet.has(candidateFile));
43790
- const sidecarReplayedDiagnostics = [];
43791
- const sidecarLintFiles = [];
43792
- if (sidecarCache === null) sidecarLintFiles.push(...hitFiles);
43793
- else {
43794
- let verifySliceStartedAt = performance.now();
43795
- for (const hitFile of hitFiles) {
43796
- const cacheKey = cacheKeyByFile.get(hitFile);
43797
- const entry = cacheKey === void 0 ? null : sidecarCache.lookup(cacheKey);
43798
- if (entry !== null && entry.probes.every((probe) => probeAnswers.answerFor(probe.kind, probe.path) === probe.answer)) sidecarReplayedDiagnostics.push(...entry.diagnostics);
43799
- else sidecarLintFiles.push(hitFile);
43800
- if (performance.now() - verifySliceStartedAt >= 12) {
43801
- await yieldToEventLoop();
43802
- verifySliceStartedAt = performance.now();
43803
- }
43804
- }
43805
- }
43806
- const sidecarProbesTask = sidecarCache === null ? null : collectSidecarProbesForFiles({
43807
- files: sidecarLintFiles,
43808
- rootDirectory,
43809
- boundedSidecarRuleIds,
43810
- probeAnswers
43811
- });
43812
- const sidecarReplayedFileCount = hitFiles.length - sidecarLintFiles.length;
43813
- if (sidecarReplayedFileCount > 0) options.onFileProgress?.(missFiles.length + sidecarReplayedFileCount, candidateFiles.length);
43814
- const boundedSidecarResult = await runConfigOverFiles(() => buildConfig({
43815
- extendsPaths: [],
43816
- ruleSelection: "sidecar",
43817
- ...useSidecarCache ? { sidecarRuleIdFilter: boundedSidecarRuleIdSet } : {}
43818
- }), "oxlintrc.sidecar.json", sidecarLintFiles, options.onFileProgress && ((scannedFileCount) => options.onFileProgress?.(missFiles.length + sidecarReplayedFileCount + scannedFileCount, candidateFiles.length)));
43819
- const unboundedSidecarResult = useSidecarCache && unboundedSidecarRuleIds.length > 0 ? await runConfigOverFiles(() => buildConfig({
43503
+ const sidecarResult = await runConfigOverFiles(() => buildConfig({
43820
43504
  extendsPaths: [],
43821
- ruleSelection: "sidecar",
43822
- sidecarRuleIdFilter: new Set(unboundedSidecarRuleIds)
43823
- }), "oxlintrc.sidecar-unbounded.json", hitFiles, void 0) : {
43824
- diagnostics: [],
43825
- didDropReactHooksJsPlugin: false,
43826
- hadPartialFailure: false
43827
- };
43505
+ ruleSelection: "sidecar"
43506
+ }), "oxlintrc.sidecar.json", candidateFiles.filter((candidateFile) => !missFileSet.has(candidateFile)), options.onFileProgress && ((scannedFileCount) => options.onFileProgress?.(missFiles.length + scannedFileCount, candidateFiles.length)));
43828
43507
  const freshCacheableDiagnostics = [];
43829
43508
  for (const diagnostic of fullResult.diagnostics) if (!CROSS_FILE_RULE_IDS.has(diagnostic.rule)) freshCacheableDiagnostics.push(diagnostic);
43830
43509
  onCacheStats?.(cacheHitFileCount, candidateFiles.length);
43831
- if (sidecarCache !== null) onSidecarStats?.(hitFiles.length - sidecarLintFiles.length, hitFiles.length);
43832
- const freshDiagnosticsByFile = attributeDiagnosticsToFiles(freshCacheableDiagnostics, missFiles);
43833
- if (!fullResult.didDropReactHooksJsPlugin && !fullResult.hadPartialFailure && freshDiagnosticsByFile !== null) {
43510
+ const missFileByNormalizedPath = /* @__PURE__ */ new Map();
43511
+ for (const missFile of missFiles) missFileByNormalizedPath.set(missFile.replaceAll("\\", "/"), missFile);
43512
+ const freshDiagnosticsByFile = /* @__PURE__ */ new Map();
43513
+ let isAttributionSound = true;
43514
+ for (const diagnostic of freshCacheableDiagnostics) {
43515
+ const missFile = missFileByNormalizedPath.get(diagnostic.filePath);
43516
+ if (missFile === void 0) {
43517
+ isAttributionSound = false;
43518
+ break;
43519
+ }
43520
+ const fileDiagnostics = freshDiagnosticsByFile.get(missFile) ?? [];
43521
+ fileDiagnostics.push(diagnostic);
43522
+ freshDiagnosticsByFile.set(missFile, fileDiagnostics);
43523
+ }
43524
+ if (!fullResult.didDropReactHooksJsPlugin && !fullResult.hadPartialFailure && isAttributionSound) {
43834
43525
  for (const missFile of missFiles) {
43835
43526
  const cacheKey = cacheKeyByFile.get(missFile);
43836
43527
  if (cacheKey !== void 0) cache.store(cacheKey, freshDiagnosticsByFile.get(missFile) ?? []);
43837
43528
  }
43838
43529
  cache.persist();
43839
43530
  }
43840
- if (sidecarCache !== null && missProbesTask !== null && sidecarProbesTask !== null) storeSidecarEntries({
43841
- sidecarCache,
43842
- cacheKeyByFile,
43843
- storablePasses: [{
43844
- files: sidecarLintFiles,
43845
- diagnostics: boundedSidecarResult.diagnostics,
43846
- isTrusted: !boundedSidecarResult.hadPartialFailure,
43847
- probesByFile: await sidecarProbesTask
43848
- }, {
43849
- files: missFiles,
43850
- diagnostics: fullResult.diagnostics.filter((diagnostic) => boundedSidecarRuleIdSet.has(diagnostic.rule)),
43851
- isTrusted: !fullResult.didDropReactHooksJsPlugin && !fullResult.hadPartialFailure,
43852
- probesByFile: await missProbesTask
43853
- }]
43854
- });
43855
43531
  return dedupeDiagnostics([
43856
43532
  ...replayedDiagnostics,
43857
43533
  ...fullResult.diagnostics,
43858
- ...sidecarReplayedDiagnostics,
43859
- ...boundedSidecarResult.diagnostics,
43860
- ...unboundedSidecarResult.diagnostics
43534
+ ...sidecarResult.diagnostics
43861
43535
  ]);
43862
43536
  }
43863
43537
  const baseArgs = makeBaseArgs(configPath);
@@ -43955,7 +43629,6 @@ var Linter = class Linter extends Service()("react-doctor/Linter") {
43955
43629
  const concurrency = yield* OxlintConcurrency;
43956
43630
  const lintBatchOrdering = yield* LintBatchOrdering;
43957
43631
  const perFileLintCacheEnabled = yield* PerFileLintCacheEnabled;
43958
- const sidecarLintCacheEnabled = yield* SidecarLintCacheEnabled;
43959
43632
  const collectedFailures = [];
43960
43633
  const diagnostics = yield* tryPromise({
43961
43634
  try: (signal) => runOxlint({
@@ -43974,9 +43647,7 @@ var Linter = class Linter extends Service()("react-doctor/Linter") {
43974
43647
  },
43975
43648
  onFileProgress: input.onFileProgress,
43976
43649
  perFileLintCacheEnabled,
43977
- sidecarLintCacheEnabled,
43978
43650
  onCacheStats: input.onCacheStats,
43979
- onSidecarStats: input.onSidecarStats,
43980
43651
  spawnTimeoutMs,
43981
43652
  outputMaxBytes,
43982
43653
  concurrency,
@@ -44786,8 +44457,6 @@ const runInspect = (input, hooks = {}) => gen(function* () {
44786
44457
  let lastReportedTotalFileCount = 0;
44787
44458
  let lintCacheHitFileCount = null;
44788
44459
  let lintCacheTotalFileCount = null;
44789
- let lintSidecarReplayedFileCount = null;
44790
- let lintSidecarTotalFileCount = null;
44791
44460
  let deadCodeCacheHit = null;
44792
44461
  let deadCodeSummaryCacheHits = null;
44793
44462
  let deadCodeSummaryCacheMisses = null;
@@ -44810,10 +44479,6 @@ const runInspect = (input, hooks = {}) => gen(function* () {
44810
44479
  lintCacheHitFileCount = cacheHitFileCount;
44811
44480
  lintCacheTotalFileCount = totalConsideredFileCount;
44812
44481
  },
44813
- onSidecarStats: (sidecarReplayedFileCount, sidecarConsideredFileCount) => {
44814
- lintSidecarReplayedFileCount = sidecarReplayedFileCount;
44815
- lintSidecarTotalFileCount = sidecarConsideredFileCount;
44816
- },
44817
44482
  deadlineEpochMs: input.deadlineEpochMs
44818
44483
  }).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
44819
44484
  yield* set(lintFailure, {
@@ -44924,8 +44589,6 @@ const runInspect = (input, hooks = {}) => gen(function* () {
44924
44589
  securityScanFailed,
44925
44590
  lintCacheHitFileCount,
44926
44591
  lintCacheTotalFileCount,
44927
- lintSidecarReplayedFileCount,
44928
- lintSidecarTotalFileCount,
44929
44592
  deadCodeCacheHit: lintFailureState.didFail ? null : deadCodeCacheHit,
44930
44593
  deadCodeSummaryCacheHits: lintFailureState.didFail ? null : deadCodeSummaryCacheHits,
44931
44594
  deadCodeSummaryCacheMisses: lintFailureState.didFail ? null : deadCodeSummaryCacheMisses,
@@ -45801,7 +45464,7 @@ const makeNoopConsole = () => ({
45801
45464
  });
45802
45465
  //#endregion
45803
45466
  //#region src/cli/utils/version.ts
45804
- const VERSION = "0.6.3-dev.bdf8074";
45467
+ const VERSION = "0.6.3-dev.ce49250";
45805
45468
  //#endregion
45806
45469
  //#region src/cli/utils/json-mode.ts
45807
45470
  let context = null;
@@ -46170,13 +45833,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
46170
45833
  * uploads source-map artifacts under, so stack frames symbolicate. Honors the
46171
45834
  * standard `SENTRY_RELEASE` override.
46172
45835
  */
46173
- const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.6.3-dev.bdf8074`;
45836
+ const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.6.3-dev.ce49250`;
46174
45837
  /**
46175
45838
  * Deployment environment shown in Sentry's environment filter. Defaults to
46176
45839
  * `production` for tagged releases and `development` for dev/unbuilt versions,
46177
45840
  * overridable via the standard `SENTRY_ENVIRONMENT` env var.
46178
45841
  */
46179
- const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.6.3-dev.bdf8074") ? "development" : "production");
45842
+ const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.6.3-dev.ce49250") ? "development" : "production");
46180
45843
  /**
46181
45844
  * Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
46182
45845
  * (set to `0` to disable tracing) and falls back to
@@ -58254,284 +57917,6 @@ const buildRuleBlastRadii = (diagnostics) => buildSortedRuleGroups(diagnostics).
58254
57917
  })).toSorted((left, right) => right.fileCount - left.fileCount);
58255
57918
  const findMigrationScaleBuckets = (diagnostics) => buildRuleBlastRadii(diagnostics).filter((bucket) => bucket.fileCount >= 40);
58256
57919
  //#endregion
58257
- //#region src/cli/utils/scan-result-cache.ts
58258
- const CACHE_DISABLED_VALUES = new Set(["1", "true"]);
58259
- const TOOLCHAIN_PACKAGE_SPECIFIERS = [
58260
- "oxlint",
58261
- "oxlint/package.json",
58262
- "oxlint-plugin-react-doctor",
58263
- "deslop-js/package.json",
58264
- "eslint-plugin-react-hooks/package.json"
58265
- ];
58266
- const bundledRequire = createRequire(import.meta.url);
58267
- const normalizeForStableJson = (value) => {
58268
- if (value === null) return null;
58269
- if (value === void 0) return void 0;
58270
- const valueType = typeof value;
58271
- if (valueType === "string" || valueType === "number" || valueType === "boolean") return value;
58272
- if (valueType === "bigint" || valueType === "function" || valueType === "symbol") throw new Error("Unsupported cache key value");
58273
- if (Array.isArray(value)) return value.map((item) => {
58274
- const normalized = normalizeForStableJson(item);
58275
- return normalized === void 0 ? null : normalized;
58276
- });
58277
- const prototype = Object.getPrototypeOf(value);
58278
- if (prototype !== Object.prototype && prototype !== null) throw new Error("Unsupported cache key object");
58279
- if (!isRecord(value)) throw new Error("Unsupported cache key object");
58280
- const normalizedRecord = {};
58281
- for (const key of Object.keys(value).sort()) {
58282
- const normalized = normalizeForStableJson(value[key]);
58283
- if (normalized !== void 0) normalizedRecord[key] = normalized;
58284
- }
58285
- return normalizedRecord;
58286
- };
58287
- const stringifyStableJson = (value) => {
58288
- try {
58289
- return JSON.stringify(normalizeForStableJson(value));
58290
- } catch {
58291
- return null;
58292
- }
58293
- };
58294
- const hashString = (value) => crypto.createHash("sha1").update(value).digest("hex");
58295
- const readHeadSha = (projectDirectory) => runGit(projectDirectory, ["rev-parse", "HEAD"]);
58296
- const parseWorktreeStatusRecords = (statusOutput) => {
58297
- const entries = [];
58298
- const tokens = statusOutput.split("\0");
58299
- for (let tokenIndex = 0; tokenIndex < tokens.length; tokenIndex += 1) {
58300
- let record = tokens[tokenIndex];
58301
- if (record.length === 0) continue;
58302
- if (record.length < 3 || record[2] !== " ") {
58303
- if (tokenIndex !== 0) return null;
58304
- record = ` ${record}`;
58305
- }
58306
- if (record.length < 4 || record[2] !== " ") return null;
58307
- const statusCode = record.slice(0, 2);
58308
- const recordPath = record.slice(3);
58309
- if (statusCode.includes("R") || statusCode.includes("C")) {
58310
- tokenIndex += 1;
58311
- const originPath = tokens[tokenIndex];
58312
- if (originPath === void 0 || originPath.length === 0) return null;
58313
- entries.push({
58314
- statusCode,
58315
- path: recordPath,
58316
- originPath
58317
- });
58318
- continue;
58319
- }
58320
- entries.push({
58321
- statusCode,
58322
- path: recordPath
58323
- });
58324
- }
58325
- return entries;
58326
- };
58327
- const buildDirtyPathContentFingerprint = (absolutePath, statusCode) => {
58328
- let stats;
58329
- try {
58330
- stats = fs.lstatSync(absolutePath);
58331
- } catch {
58332
- return statusCode.includes("D") ? "deleted" : null;
58333
- }
58334
- if (stats.isSymbolicLink()) try {
58335
- return `link:${fs.readlinkSync(absolutePath)}`;
58336
- } catch {
58337
- return null;
58338
- }
58339
- if (!stats.isFile()) return null;
58340
- if (stats.size > 10485760) return `stat:${stats.mtimeMs}:${stats.size}`;
58341
- return hashFileContents(absolutePath);
58342
- };
58343
- const buildWorktreeFingerprint = (projectDirectory) => {
58344
- const statusOutput = runGit(projectDirectory, [
58345
- "status",
58346
- "--porcelain=v1",
58347
- "-z",
58348
- "--untracked-files=all"
58349
- ]);
58350
- if (statusOutput === null) return null;
58351
- if (statusOutput.length === 0) return [];
58352
- const statusEntries = parseWorktreeStatusRecords(statusOutput);
58353
- if (statusEntries === null) return null;
58354
- if (statusEntries.length > 300) return null;
58355
- const repositoryRoot = runGit(projectDirectory, ["rev-parse", "--show-toplevel"]);
58356
- if (repositoryRoot === null) return null;
58357
- const dirtyEntries = [];
58358
- for (const statusEntry of statusEntries) {
58359
- const contentFingerprint = buildDirtyPathContentFingerprint(path.join(repositoryRoot, statusEntry.path), statusEntry.statusCode);
58360
- if (contentFingerprint === null) return null;
58361
- dirtyEntries.push({
58362
- ...statusEntry,
58363
- contentFingerprint
58364
- });
58365
- }
58366
- return dirtyEntries.sort((firstEntry, secondEntry) => {
58367
- const firstSortKey = `${firstEntry.path}${firstEntry.statusCode}`;
58368
- const secondSortKey = `${secondEntry.path}${secondEntry.statusCode}`;
58369
- return firstSortKey < secondSortKey ? -1 : firstSortKey > secondSortKey ? 1 : 0;
58370
- });
58371
- };
58372
- const DOTENV_FILE_NAME_PATTERN = /^\.env(\.|$)/;
58373
- const resolveDotenvFingerprint = (projectDirectory) => {
58374
- try {
58375
- return fs.readdirSync(projectDirectory).filter((entryName) => DOTENV_FILE_NAME_PATTERN.test(entryName)).sort().map((entryName) => `${entryName}=${fileFingerprint(path.join(projectDirectory, entryName)) ?? "unreadable"}`);
58376
- } catch {
58377
- return [];
58378
- }
58379
- };
58380
- const hasHiddenTrackedFileState = (projectDirectory) => {
58381
- const output = runGit(projectDirectory, ["ls-files", "-v"]);
58382
- if (output === null) return true;
58383
- return output.split("\n").some((line) => line.length > 0 && line[0] !== "H");
58384
- };
58385
- const resolveCacheFilePath = (projectDirectory) => {
58386
- const nodeModulesDirectory = path.join(projectDirectory, "node_modules");
58387
- if (fs.existsSync(nodeModulesDirectory)) return path.join(nodeModulesDirectory, ".cache", "react-doctor", "scan-cache.json");
58388
- const projectHash = hashString(projectDirectory).slice(0, 16);
58389
- return path.join(os.tmpdir(), "react-doctor-cache", `scan-${projectHash}.json`);
58390
- };
58391
- const readPersistedCache = (cacheFilePath) => {
58392
- try {
58393
- const parsed = JSON.parse(fs.readFileSync(cacheFilePath, "utf8"));
58394
- if (!isRecord(parsed) || parsed.version !== 3) return {
58395
- version: 3,
58396
- entries: []
58397
- };
58398
- if (!Array.isArray(parsed.entries)) return {
58399
- version: 3,
58400
- entries: []
58401
- };
58402
- const entries = [];
58403
- for (const entry of parsed.entries) {
58404
- if (!isRecord(entry) || typeof entry.key !== "string" || typeof entry.createdAtMs !== "number") continue;
58405
- if (!isRecord(entry.payload) || !Array.isArray(entry.payload.diagnostics)) continue;
58406
- entries.push(entry);
58407
- }
58408
- return {
58409
- version: 3,
58410
- entries
58411
- };
58412
- } catch {
58413
- return {
58414
- version: 3,
58415
- entries: []
58416
- };
58417
- }
58418
- };
58419
- const writePersistedCache = (cacheFilePath, cache) => {
58420
- try {
58421
- fs.mkdirSync(path.dirname(cacheFilePath), { recursive: true });
58422
- const tempPath = `${cacheFilePath}.${process.pid}.tmp`;
58423
- fs.writeFileSync(tempPath, JSON.stringify(cache));
58424
- fs.renameSync(tempPath, cacheFilePath);
58425
- } catch {
58426
- return;
58427
- }
58428
- };
58429
- /**
58430
- * The global cache off-switch (`REACT_DOCTOR_NO_CACHE`), which disables every
58431
- * cache subsystem: this whole-repo scan cache plus core's per-file lint,
58432
- * sidecar, and dead-code caches (their `Context.Reference` defaults read the
58433
- * same variable). Exported for the wide event's `cache.temperature`
58434
- * derivation, which reports `"disabled"` instead of `"cold"` when the switch
58435
- * is on. Granular knobs (`REACT_DOCTOR_NO_FILE_CACHE`, …) are deliberately
58436
- * not consulted — they leave the other subsystems live.
58437
- */
58438
- const isCacheGloballyDisabled = () => CACHE_DISABLED_VALUES.has(process.env.REACT_DOCTOR_NO_CACHE?.toLowerCase() ?? "");
58439
- const resolveProjectIdentity = (projectDirectory) => {
58440
- try {
58441
- return fs.realpathSync.native(projectDirectory);
58442
- } catch {
58443
- return path.resolve(projectDirectory);
58444
- }
58445
- };
58446
- const fileFingerprint = (filePath) => {
58447
- try {
58448
- const stat = fs.statSync(filePath);
58449
- return `${filePath}:${stat.mtimeMs}:${stat.size}`;
58450
- } catch {
58451
- return null;
58452
- }
58453
- };
58454
- const resolveToolchainFingerprint = (nodeBinaryPath) => {
58455
- const fingerprints = [];
58456
- if (nodeBinaryPath !== null) {
58457
- const fingerprint = fileFingerprint(nodeBinaryPath);
58458
- if (fingerprint !== null) fingerprints.push(`node=${fingerprint}`);
58459
- }
58460
- for (const specifier of TOOLCHAIN_PACKAGE_SPECIFIERS) try {
58461
- const fingerprint = fileFingerprint(bundledRequire.resolve(specifier));
58462
- if (fingerprint !== null) fingerprints.push(`${specifier}=${fingerprint}`);
58463
- } catch {
58464
- continue;
58465
- }
58466
- return fingerprints;
58467
- };
58468
- const buildScanResultCacheKey = (input) => {
58469
- if (isCacheGloballyDisabled()) return null;
58470
- if (hasHiddenTrackedFileState(input.projectDirectory)) return null;
58471
- const worktreeFingerprint = buildWorktreeFingerprint(input.projectDirectory);
58472
- if (worktreeFingerprint === null) return null;
58473
- const headSha = readHeadSha(input.projectDirectory);
58474
- if (headSha === null) return null;
58475
- if (stringifyStableJson(input.userConfig) === null) return null;
58476
- const cacheKeyJson = stringifyStableJson({
58477
- schemaVersion: 3,
58478
- projectIdentity: resolveProjectIdentity(input.projectDirectory),
58479
- headSha,
58480
- worktreeFingerprint,
58481
- dotenvFingerprint: resolveDotenvFingerprint(input.projectDirectory),
58482
- reactDoctorVersion: input.version,
58483
- nodeVersion: process.version,
58484
- toolchainFingerprint: resolveToolchainFingerprint(input.nodeBinaryPath),
58485
- configFingerprint: computeConfigFingerprint(input.projectDirectory, input.version),
58486
- hasConfigOverride: input.hasConfigOverride,
58487
- configSourceDirectory: input.configSourceDirectory,
58488
- userConfig: input.userConfig,
58489
- engineOptions: {
58490
- lint: input.options.lint,
58491
- deadCode: input.options.deadCode,
58492
- includePaths: [...input.options.includePaths].sort(),
58493
- customRulesOnly: input.options.customRulesOnly,
58494
- respectInlineDisables: input.options.respectInlineDisables,
58495
- warnings: input.options.warnings,
58496
- adoptExistingLintConfig: input.options.adoptExistingLintConfig,
58497
- ignoredTags: [...input.options.ignoredTags].sort(),
58498
- concurrency: input.options.concurrency,
58499
- lintBatchOrdering: resolveLintBatchOrdering(),
58500
- baselineRef: input.options.baseline?.ref,
58501
- changedLineRanges: input.options.changedLineRanges ?? void 0,
58502
- noScore: input.options.noScore,
58503
- isCi: input.options.isCi,
58504
- suppressRendering: input.options.suppressRendering,
58505
- supplyChainManifestChanged: input.options.supplyChainManifestChanged
58506
- }
58507
- });
58508
- return cacheKeyJson === null ? null : hashString(cacheKeyJson);
58509
- };
58510
- const createScanResultCache = (projectDirectory) => {
58511
- const cacheFilePath = resolveCacheFilePath(projectDirectory);
58512
- const persistedCache = readPersistedCache(cacheFilePath);
58513
- const entries = /* @__PURE__ */ new Map();
58514
- for (const entry of persistedCache.entries) entries.set(entry.key, entry);
58515
- const persist = () => {
58516
- writePersistedCache(cacheFilePath, {
58517
- version: 3,
58518
- entries: [...entries.values()].sort((firstEntry, secondEntry) => secondEntry.createdAtMs - firstEntry.createdAtMs).slice(0, 20)
58519
- });
58520
- };
58521
- return {
58522
- lookup: (key) => entries.get(key)?.payload ?? null,
58523
- store: (key, payload) => {
58524
- entries.set(key, {
58525
- key,
58526
- createdAtMs: Date.now(),
58527
- payload
58528
- });
58529
- persist();
58530
- }
58531
- };
58532
- };
58533
- const shouldStoreScanPayload = (payload) => !payload.didLintFail && !payload.didDeadCodeFail && payload.lintPartialFailures.length === 0 && !payload.supplyChainOverlapTimedOut && payload.securityScanFailed !== true;
58534
- //#endregion
58535
57920
  //#region src/cli/utils/should-block-ci.ts
58536
57921
  const shouldBlockCi = (diagnostics, blockingLevel) => {
58537
57922
  if (blockingLevel === "none") return false;
@@ -58573,63 +57958,6 @@ const readEnvBoolean = (name) => {
58573
57958
  if (value === void 0) return null;
58574
57959
  return value.toLowerCase() === "true" || value === "1";
58575
57960
  };
58576
- const ratioOf = (numerator, denominator) => denominator != null && denominator > 0 ? (numerator ?? 0) / denominator : null;
58577
- const resolveDeadCodeReuseRatio = (result) => {
58578
- if (result.deadCodeCacheHit === true) return 1;
58579
- const summaryTotal = (result.deadCodeSummaryCacheHits ?? 0) + (result.deadCodeSummaryCacheMisses ?? 0);
58580
- if (summaryTotal > 0) return (result.deadCodeSummaryCacheHits ?? 0) / summaryTotal;
58581
- return result.deadCodeCacheHit === false ? 0 : null;
58582
- };
58583
- /**
58584
- * One queryable cache temperature per scan, derived from the whole stack:
58585
- *
58586
- * - `"turbo"` — whole-repo scan-result replay (the explicit
58587
- * `wholeRepoCacheHit` flag from the CLI's cachedPayload
58588
- * branch; no scan work ran).
58589
- * - `"warm"` — any incremental reuse: per-file lint hits, sidecar
58590
- * replays, a dead-code whole-result hit, or dead-code
58591
- * summary-cache hits.
58592
- * - `"disabled"` — zero reuse because the global `REACT_DOCTOR_NO_CACHE`
58593
- * off-switch is on. Granular per-cache opt-outs
58594
- * (`REACT_DOCTOR_NO_FILE_CACHE`, …) still read warm/cold,
58595
- * since the other subsystems stay live.
58596
- * - `"cold"` — caches on, zero reuse (first scan / everything changed).
58597
- *
58598
- * `cache.warmth` is the headline reuse magnitude in [0, 1]: the plain mean of
58599
- * the subsystem reuse fractions known this run (lint hit ratio, sidecar
58600
- * replay ratio, dead-code reuse), skipping subsystems that never consulted a
58601
- * cache; `1` on turbo, dropped when nothing consulted any cache. Deliberately
58602
- * unweighted — the per-subsystem dims stay the precise signal; warmth is the
58603
- * p50/p90-able summary. Emitted only on the success path.
58604
- */
58605
- const buildCacheAttributes = (input) => {
58606
- if (input.result === void 0) return {};
58607
- if (input.wholeRepoCacheHit) return withNamespace("cache", {
58608
- wholeRepoHit: true,
58609
- temperature: "turbo",
58610
- warmth: 1
58611
- });
58612
- const result = input.result;
58613
- const subsystemReuseRatios = [
58614
- ratioOf(result.lintCacheHitFileCount, result.lintCacheTotalFileCount),
58615
- ratioOf(result.lintSidecarReplayedFileCount, result.lintSidecarTotalFileCount),
58616
- resolveDeadCodeReuseRatio(result)
58617
- ];
58618
- let knownSubsystemCount = 0;
58619
- let reuseRatioSum = 0;
58620
- for (const reuseRatio of subsystemReuseRatios) {
58621
- if (reuseRatio === null) continue;
58622
- knownSubsystemCount += 1;
58623
- reuseRatioSum += reuseRatio;
58624
- }
58625
- const warmth = knownSubsystemCount > 0 ? reuseRatioSum / knownSubsystemCount : null;
58626
- const temperature = warmth !== null && warmth > 0 ? "warm" : isCacheGloballyDisabled() ? "disabled" : "cold";
58627
- return withNamespace("cache", {
58628
- wholeRepoHit: input.wholeRepoCacheHit ?? null,
58629
- temperature,
58630
- warmth
58631
- });
58632
- };
58633
57961
  const resolveVersionPin = (versionInput) => {
58634
57962
  if (versionInput === void 0 || versionInput.trim() === "") return null;
58635
57963
  if (versionInput === "latest") return "latest";
@@ -58735,10 +58063,7 @@ const buildOutcomeAttributes = (input) => {
58735
58063
  deadlineSkippedFileCount: input.lintDeadlineSkippedFileCount ?? null,
58736
58064
  cacheHitFiles: result.lintCacheHitFileCount ?? null,
58737
58065
  cacheTotalFiles: result.lintCacheTotalFileCount ?? null,
58738
- cacheHitRatio: ratioOf(result.lintCacheHitFileCount, result.lintCacheTotalFileCount),
58739
- sidecarReplayedFiles: result.lintSidecarReplayedFileCount ?? null,
58740
- sidecarTotalFiles: result.lintSidecarTotalFileCount ?? null,
58741
- sidecarReplayRatio: ratioOf(result.lintSidecarReplayedFileCount, result.lintSidecarTotalFileCount)
58066
+ cacheHitRatio: result.lintCacheTotalFileCount != null && result.lintCacheTotalFileCount > 0 ? (result.lintCacheHitFileCount ?? 0) / result.lintCacheTotalFileCount : null
58742
58067
  }),
58743
58068
  ...withNamespace("deadCode", {
58744
58069
  failed: input.didDeadCodeFail ?? null,
@@ -58805,7 +58130,7 @@ const buildScanAttributes = (input) => {
58805
58130
  * Projects a scan into the namespaced attribute set for its root span — the
58806
58131
  * canonical per-scan "wide event". Every attribute carries a dotted namespace
58807
58132
  * that groups it by concept (`scan.*` config, `action.*` CI knobs, `outcome.*`
58808
- * verdict, `diag.*` findings, `score.*`, `lint.*`, `deadCode.*`, `cache.*`,
58133
+ * verdict, `diag.*` findings, `score.*`, `lint.*`, `deadCode.*`,
58809
58134
  * `supplyChain.*`, `timing.*`, `migration.*`, `baseline.*`) so the attributes
58810
58135
  * tree up in Sentry's attribute browser and stay filter-/group-/aggregate-able
58811
58136
  * in the Spans dataset. Pure and exported so the projection (outcome
@@ -58819,8 +58144,7 @@ const buildScanAttributes = (input) => {
58819
58144
  const buildRunEventAttributes = (input) => toSpanAttributes({
58820
58145
  ...buildScanAttributes(input),
58821
58146
  ...buildActionAttributes(),
58822
- ...buildOutcomeAttributes(input),
58823
- ...buildCacheAttributes(input)
58147
+ ...buildOutcomeAttributes(input)
58824
58148
  });
58825
58149
  /**
58826
58150
  * Stamps the wide-event attributes onto the run's root span. A guarded no-op
@@ -60030,6 +59354,275 @@ const resolveCliCategories = (categoryFlag) => {
60030
59354
  return resolvedCategories.length > 0 ? resolvedCategories : void 0;
60031
59355
  };
60032
59356
  //#endregion
59357
+ //#region src/cli/utils/scan-result-cache.ts
59358
+ const CACHE_DISABLED_VALUES = new Set(["1", "true"]);
59359
+ const TOOLCHAIN_PACKAGE_SPECIFIERS = [
59360
+ "oxlint",
59361
+ "oxlint/package.json",
59362
+ "oxlint-plugin-react-doctor",
59363
+ "deslop-js/package.json",
59364
+ "eslint-plugin-react-hooks/package.json"
59365
+ ];
59366
+ const bundledRequire = createRequire(import.meta.url);
59367
+ const normalizeForStableJson = (value) => {
59368
+ if (value === null) return null;
59369
+ if (value === void 0) return void 0;
59370
+ const valueType = typeof value;
59371
+ if (valueType === "string" || valueType === "number" || valueType === "boolean") return value;
59372
+ if (valueType === "bigint" || valueType === "function" || valueType === "symbol") throw new Error("Unsupported cache key value");
59373
+ if (Array.isArray(value)) return value.map((item) => {
59374
+ const normalized = normalizeForStableJson(item);
59375
+ return normalized === void 0 ? null : normalized;
59376
+ });
59377
+ const prototype = Object.getPrototypeOf(value);
59378
+ if (prototype !== Object.prototype && prototype !== null) throw new Error("Unsupported cache key object");
59379
+ if (!isRecord(value)) throw new Error("Unsupported cache key object");
59380
+ const normalizedRecord = {};
59381
+ for (const key of Object.keys(value).sort()) {
59382
+ const normalized = normalizeForStableJson(value[key]);
59383
+ if (normalized !== void 0) normalizedRecord[key] = normalized;
59384
+ }
59385
+ return normalizedRecord;
59386
+ };
59387
+ const stringifyStableJson = (value) => {
59388
+ try {
59389
+ return JSON.stringify(normalizeForStableJson(value));
59390
+ } catch {
59391
+ return null;
59392
+ }
59393
+ };
59394
+ const hashString = (value) => crypto.createHash("sha1").update(value).digest("hex");
59395
+ const readHeadSha = (projectDirectory) => runGit(projectDirectory, ["rev-parse", "HEAD"]);
59396
+ const parseWorktreeStatusRecords = (statusOutput) => {
59397
+ const entries = [];
59398
+ const tokens = statusOutput.split("\0");
59399
+ for (let tokenIndex = 0; tokenIndex < tokens.length; tokenIndex += 1) {
59400
+ let record = tokens[tokenIndex];
59401
+ if (record.length === 0) continue;
59402
+ if (record.length < 3 || record[2] !== " ") {
59403
+ if (tokenIndex !== 0) return null;
59404
+ record = ` ${record}`;
59405
+ }
59406
+ if (record.length < 4 || record[2] !== " ") return null;
59407
+ const statusCode = record.slice(0, 2);
59408
+ const recordPath = record.slice(3);
59409
+ if (statusCode.includes("R") || statusCode.includes("C")) {
59410
+ tokenIndex += 1;
59411
+ const originPath = tokens[tokenIndex];
59412
+ if (originPath === void 0 || originPath.length === 0) return null;
59413
+ entries.push({
59414
+ statusCode,
59415
+ path: recordPath,
59416
+ originPath
59417
+ });
59418
+ continue;
59419
+ }
59420
+ entries.push({
59421
+ statusCode,
59422
+ path: recordPath
59423
+ });
59424
+ }
59425
+ return entries;
59426
+ };
59427
+ const buildDirtyPathContentFingerprint = (absolutePath, statusCode) => {
59428
+ let stats;
59429
+ try {
59430
+ stats = fs.lstatSync(absolutePath);
59431
+ } catch {
59432
+ return statusCode.includes("D") ? "deleted" : null;
59433
+ }
59434
+ if (stats.isSymbolicLink()) try {
59435
+ return `link:${fs.readlinkSync(absolutePath)}`;
59436
+ } catch {
59437
+ return null;
59438
+ }
59439
+ if (!stats.isFile()) return null;
59440
+ if (stats.size > 10485760) return `stat:${stats.mtimeMs}:${stats.size}`;
59441
+ return hashFileContents(absolutePath);
59442
+ };
59443
+ const buildWorktreeFingerprint = (projectDirectory) => {
59444
+ const statusOutput = runGit(projectDirectory, [
59445
+ "status",
59446
+ "--porcelain=v1",
59447
+ "-z",
59448
+ "--untracked-files=all"
59449
+ ]);
59450
+ if (statusOutput === null) return null;
59451
+ if (statusOutput.length === 0) return [];
59452
+ const statusEntries = parseWorktreeStatusRecords(statusOutput);
59453
+ if (statusEntries === null) return null;
59454
+ if (statusEntries.length > 300) return null;
59455
+ const repositoryRoot = runGit(projectDirectory, ["rev-parse", "--show-toplevel"]);
59456
+ if (repositoryRoot === null) return null;
59457
+ const dirtyEntries = [];
59458
+ for (const statusEntry of statusEntries) {
59459
+ const contentFingerprint = buildDirtyPathContentFingerprint(path.join(repositoryRoot, statusEntry.path), statusEntry.statusCode);
59460
+ if (contentFingerprint === null) return null;
59461
+ dirtyEntries.push({
59462
+ ...statusEntry,
59463
+ contentFingerprint
59464
+ });
59465
+ }
59466
+ return dirtyEntries.sort((firstEntry, secondEntry) => {
59467
+ const firstSortKey = `${firstEntry.path}${firstEntry.statusCode}`;
59468
+ const secondSortKey = `${secondEntry.path}${secondEntry.statusCode}`;
59469
+ return firstSortKey < secondSortKey ? -1 : firstSortKey > secondSortKey ? 1 : 0;
59470
+ });
59471
+ };
59472
+ const DOTENV_FILE_NAME_PATTERN = /^\.env(\.|$)/;
59473
+ const resolveDotenvFingerprint = (projectDirectory) => {
59474
+ try {
59475
+ return fs.readdirSync(projectDirectory).filter((entryName) => DOTENV_FILE_NAME_PATTERN.test(entryName)).sort().map((entryName) => `${entryName}=${fileFingerprint(path.join(projectDirectory, entryName)) ?? "unreadable"}`);
59476
+ } catch {
59477
+ return [];
59478
+ }
59479
+ };
59480
+ const hasHiddenTrackedFileState = (projectDirectory) => {
59481
+ const output = runGit(projectDirectory, ["ls-files", "-v"]);
59482
+ if (output === null) return true;
59483
+ return output.split("\n").some((line) => line.length > 0 && line[0] !== "H");
59484
+ };
59485
+ const resolveCacheFilePath = (projectDirectory) => {
59486
+ const nodeModulesDirectory = path.join(projectDirectory, "node_modules");
59487
+ if (fs.existsSync(nodeModulesDirectory)) return path.join(nodeModulesDirectory, ".cache", "react-doctor", "scan-cache.json");
59488
+ const projectHash = hashString(projectDirectory).slice(0, 16);
59489
+ return path.join(os.tmpdir(), "react-doctor-cache", `scan-${projectHash}.json`);
59490
+ };
59491
+ const readPersistedCache = (cacheFilePath) => {
59492
+ try {
59493
+ const parsed = JSON.parse(fs.readFileSync(cacheFilePath, "utf8"));
59494
+ if (!isRecord(parsed) || parsed.version !== 3) return {
59495
+ version: 3,
59496
+ entries: []
59497
+ };
59498
+ if (!Array.isArray(parsed.entries)) return {
59499
+ version: 3,
59500
+ entries: []
59501
+ };
59502
+ const entries = [];
59503
+ for (const entry of parsed.entries) {
59504
+ if (!isRecord(entry) || typeof entry.key !== "string" || typeof entry.createdAtMs !== "number") continue;
59505
+ if (!isRecord(entry.payload) || !Array.isArray(entry.payload.diagnostics)) continue;
59506
+ entries.push(entry);
59507
+ }
59508
+ return {
59509
+ version: 3,
59510
+ entries
59511
+ };
59512
+ } catch {
59513
+ return {
59514
+ version: 3,
59515
+ entries: []
59516
+ };
59517
+ }
59518
+ };
59519
+ const writePersistedCache = (cacheFilePath, cache) => {
59520
+ try {
59521
+ fs.mkdirSync(path.dirname(cacheFilePath), { recursive: true });
59522
+ const tempPath = `${cacheFilePath}.${process.pid}.tmp`;
59523
+ fs.writeFileSync(tempPath, JSON.stringify(cache));
59524
+ fs.renameSync(tempPath, cacheFilePath);
59525
+ } catch {
59526
+ return;
59527
+ }
59528
+ };
59529
+ const isScanResultCacheDisabled = () => CACHE_DISABLED_VALUES.has(process.env.REACT_DOCTOR_NO_CACHE?.toLowerCase() ?? "");
59530
+ const resolveProjectIdentity = (projectDirectory) => {
59531
+ try {
59532
+ return fs.realpathSync.native(projectDirectory);
59533
+ } catch {
59534
+ return path.resolve(projectDirectory);
59535
+ }
59536
+ };
59537
+ const fileFingerprint = (filePath) => {
59538
+ try {
59539
+ const stat = fs.statSync(filePath);
59540
+ return `${filePath}:${stat.mtimeMs}:${stat.size}`;
59541
+ } catch {
59542
+ return null;
59543
+ }
59544
+ };
59545
+ const resolveToolchainFingerprint = (nodeBinaryPath) => {
59546
+ const fingerprints = [];
59547
+ if (nodeBinaryPath !== null) {
59548
+ const fingerprint = fileFingerprint(nodeBinaryPath);
59549
+ if (fingerprint !== null) fingerprints.push(`node=${fingerprint}`);
59550
+ }
59551
+ for (const specifier of TOOLCHAIN_PACKAGE_SPECIFIERS) try {
59552
+ const fingerprint = fileFingerprint(bundledRequire.resolve(specifier));
59553
+ if (fingerprint !== null) fingerprints.push(`${specifier}=${fingerprint}`);
59554
+ } catch {
59555
+ continue;
59556
+ }
59557
+ return fingerprints;
59558
+ };
59559
+ const buildScanResultCacheKey = (input) => {
59560
+ if (isScanResultCacheDisabled()) return null;
59561
+ if (hasHiddenTrackedFileState(input.projectDirectory)) return null;
59562
+ const worktreeFingerprint = buildWorktreeFingerprint(input.projectDirectory);
59563
+ if (worktreeFingerprint === null) return null;
59564
+ const headSha = readHeadSha(input.projectDirectory);
59565
+ if (headSha === null) return null;
59566
+ if (stringifyStableJson(input.userConfig) === null) return null;
59567
+ const cacheKeyJson = stringifyStableJson({
59568
+ schemaVersion: 3,
59569
+ projectIdentity: resolveProjectIdentity(input.projectDirectory),
59570
+ headSha,
59571
+ worktreeFingerprint,
59572
+ dotenvFingerprint: resolveDotenvFingerprint(input.projectDirectory),
59573
+ reactDoctorVersion: input.version,
59574
+ nodeVersion: process.version,
59575
+ toolchainFingerprint: resolveToolchainFingerprint(input.nodeBinaryPath),
59576
+ configFingerprint: computeConfigFingerprint(input.projectDirectory, input.version),
59577
+ hasConfigOverride: input.hasConfigOverride,
59578
+ configSourceDirectory: input.configSourceDirectory,
59579
+ userConfig: input.userConfig,
59580
+ engineOptions: {
59581
+ lint: input.options.lint,
59582
+ deadCode: input.options.deadCode,
59583
+ includePaths: [...input.options.includePaths].sort(),
59584
+ customRulesOnly: input.options.customRulesOnly,
59585
+ respectInlineDisables: input.options.respectInlineDisables,
59586
+ warnings: input.options.warnings,
59587
+ adoptExistingLintConfig: input.options.adoptExistingLintConfig,
59588
+ ignoredTags: [...input.options.ignoredTags].sort(),
59589
+ concurrency: input.options.concurrency,
59590
+ lintBatchOrdering: resolveLintBatchOrdering(),
59591
+ baselineRef: input.options.baseline?.ref,
59592
+ changedLineRanges: input.options.changedLineRanges ?? void 0,
59593
+ noScore: input.options.noScore,
59594
+ isCi: input.options.isCi,
59595
+ suppressRendering: input.options.suppressRendering,
59596
+ supplyChainManifestChanged: input.options.supplyChainManifestChanged
59597
+ }
59598
+ });
59599
+ return cacheKeyJson === null ? null : hashString(cacheKeyJson);
59600
+ };
59601
+ const createScanResultCache = (projectDirectory) => {
59602
+ const cacheFilePath = resolveCacheFilePath(projectDirectory);
59603
+ const persistedCache = readPersistedCache(cacheFilePath);
59604
+ const entries = /* @__PURE__ */ new Map();
59605
+ for (const entry of persistedCache.entries) entries.set(entry.key, entry);
59606
+ const persist = () => {
59607
+ writePersistedCache(cacheFilePath, {
59608
+ version: 3,
59609
+ entries: [...entries.values()].sort((firstEntry, secondEntry) => secondEntry.createdAtMs - firstEntry.createdAtMs).slice(0, 20)
59610
+ });
59611
+ };
59612
+ return {
59613
+ lookup: (key) => entries.get(key)?.payload ?? null,
59614
+ store: (key, payload) => {
59615
+ entries.set(key, {
59616
+ key,
59617
+ createdAtMs: Date.now(),
59618
+ payload
59619
+ });
59620
+ persist();
59621
+ }
59622
+ };
59623
+ };
59624
+ const shouldStoreScanPayload = (payload) => !payload.didLintFail && !payload.didDeadCodeFail && payload.lintPartialFailures.length === 0 && !payload.supplyChainOverlapTimedOut && payload.securityScanFailed !== true;
59625
+ //#endregion
60033
59626
  //#region src/inspect.ts
60034
59627
  const silentConsole = makeNoopConsole();
60035
59628
  const runConsole = (effect) => {
@@ -60259,8 +59852,7 @@ const runInspectWithRuntime = async (directory, options, userConfig, hasConfigOv
60259
59852
  startTime,
60260
59853
  rootSentrySpan,
60261
59854
  scanMode: cachedPayload.baselineDelta ? "baseline" : isDiffMode ? "diff" : "full",
60262
- baselineDegraded,
60263
- wholeRepoCacheHit: true
59855
+ baselineDegraded
60264
59856
  });
60265
59857
  recordOnboardingCompletion(options);
60266
59858
  return result;
@@ -60369,11 +59961,8 @@ const runInspectWithRuntime = async (directory, options, userConfig, hasConfigOv
60369
59961
  rootSentrySpan,
60370
59962
  scanMode: baselineDelta ? "baseline" : isDiffMode ? "diff" : "full",
60371
59963
  baselineDegraded,
60372
- wholeRepoCacheHit: false,
60373
59964
  lintCacheHitFileCount: output.lintCacheHitFileCount,
60374
59965
  lintCacheTotalFileCount: output.lintCacheTotalFileCount,
60375
- lintSidecarReplayedFileCount: output.lintSidecarReplayedFileCount,
60376
- lintSidecarTotalFileCount: output.lintSidecarTotalFileCount,
60377
59966
  deadCodeCacheHit: output.deadCodeCacheHit,
60378
59967
  deadCodeSummaryCacheHits: output.deadCodeSummaryCacheHits,
60379
59968
  deadCodeSummaryCacheMisses: output.deadCodeSummaryCacheMisses
@@ -60411,8 +60000,6 @@ const renderAndRecordScan = async (input) => {
60411
60000
  scanElapsedMilliseconds: input.payload.scanElapsedMilliseconds,
60412
60001
  lintCacheHitFileCount: input.lintCacheHitFileCount ?? null,
60413
60002
  lintCacheTotalFileCount: input.lintCacheTotalFileCount ?? null,
60414
- lintSidecarReplayedFileCount: input.lintSidecarReplayedFileCount ?? null,
60415
- lintSidecarTotalFileCount: input.lintSidecarTotalFileCount ?? null,
60416
60003
  deadCodeCacheHit: input.deadCodeCacheHit ?? null,
60417
60004
  deadCodeSummaryCacheHits: input.deadCodeSummaryCacheHits ?? null,
60418
60005
  deadCodeSummaryCacheMisses: input.deadCodeSummaryCacheMisses ?? null,
@@ -60440,7 +60027,6 @@ const renderAndRecordScan = async (input) => {
60440
60027
  result,
60441
60028
  mode: input.scanMode,
60442
60029
  gateExempt: input.baselineDegraded,
60443
- wholeRepoCacheHit: input.wholeRepoCacheHit,
60444
60030
  didLintFail: input.payload.didLintFail,
60445
60031
  lintFailureReasonKind: input.payload.lintFailureReasonKind,
60446
60032
  lintPartialFailureCount: input.payload.lintPartialFailures.length,
@@ -60455,7 +60041,7 @@ const renderAndRecordScan = async (input) => {
60455
60041
  return result;
60456
60042
  };
60457
60043
  const finalizeAndRender = (input) => gen(function* () {
60458
- const { options, elapsedMilliseconds, diagnostics, score, project, userConfig, didLintFail, lintFailureReason, lintPartialFailures, didDeadCodeFail, deadCodeFailureReason, directory, scannedFileCount, scannedFilePaths, scanElapsedMilliseconds, lintCacheHitFileCount, lintCacheTotalFileCount, lintSidecarReplayedFileCount, lintSidecarTotalFileCount, deadCodeCacheHit, deadCodeSummaryCacheHits, deadCodeSummaryCacheMisses, baselineDelta } = input;
60044
+ const { options, elapsedMilliseconds, diagnostics, score, project, userConfig, didLintFail, lintFailureReason, lintPartialFailures, didDeadCodeFail, deadCodeFailureReason, directory, scannedFileCount, scannedFilePaths, scanElapsedMilliseconds, lintCacheHitFileCount, lintCacheTotalFileCount, deadCodeCacheHit, deadCodeSummaryCacheHits, deadCodeSummaryCacheMisses, baselineDelta } = input;
60459
60045
  const { skippedChecks, skippedCheckReasons } = buildSkippedChecks({
60460
60046
  didLintFail,
60461
60047
  lintFailureReason,
@@ -60479,10 +60065,6 @@ const finalizeAndRender = (input) => gen(function* () {
60479
60065
  lintCacheHitFileCount,
60480
60066
  lintCacheTotalFileCount
60481
60067
  } : {},
60482
- ...lintSidecarTotalFileCount !== null ? {
60483
- lintSidecarReplayedFileCount,
60484
- lintSidecarTotalFileCount
60485
- } : {},
60486
60068
  ...deadCodeCacheHit !== null ? { deadCodeCacheHit } : {},
60487
60069
  ...deadCodeSummaryCacheHits !== null && deadCodeSummaryCacheMisses !== null ? {
60488
60070
  deadCodeSummaryCacheHits,
@@ -64334,4 +63916,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
64334
63916
  export {};
64335
63917
 
64336
63918
  //# sourceMappingURL=cli.js.map
64337
- //# debugId=4d2b3e2c-ffb8-5da4-bf5d-833e608822bb
63919
+ //# debugId=cb06d1e4-d50b-5715-af77-13eb97c43e07