react-doctor 0.6.3-dev.ced746f → 0.6.3-dev.ea9a775

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.
Files changed (4) hide show
  1. package/dist/cli.js +313 -461
  2. package/dist/index.js +30 -117
  3. package/dist/lsp.js +35 -120
  4. package/package.json +4 -4
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]="1722becf-6e91-56ae-afc9-1e1410647bc3")}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]="d9c72b21-3e94-5923-b692-077f137a0078")}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";
@@ -36137,7 +36137,6 @@ const SUPPLY_CHAIN_PLUGIN = "socket";
36137
36137
  const SUPPLY_CHAIN_RULE = "low-supply-chain-score";
36138
36138
  const SUPPLY_CHAIN_CATEGORY = "Security";
36139
36139
  const SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS = 9e4;
36140
- const SUPPLY_CHAIN_CACHE_TTL_MS = 864e5;
36141
36140
  const SUPPLY_CHAIN_CACHE_SUBDIR = "supply-chain";
36142
36141
  const SUPPLY_CHAIN_IGNORED_PACKAGES = new Set(["next"]);
36143
36142
  const hasIgnoredPathSegment = (relativePath) => relativePath.split(/[/\\]/).slice(0, -1).some((segment) => IGNORED_DIRECTORIES.has(segment));
@@ -40609,37 +40608,22 @@ const failOpenReadJson = (filePath, fallback) => {
40609
40608
  return fallback;
40610
40609
  }
40611
40610
  };
40612
- const hashFileContents = (filePath) => {
40613
- try {
40614
- return crypto.createHash("sha1").update(NFS.readFileSync(filePath)).digest("hex");
40615
- } catch {
40616
- return null;
40617
- }
40618
- };
40619
40611
  const ANALYZED_FILE_EXTENSIONS = new Set(DEFAULT_EXTENSIONS);
40620
40612
  const CORE_SIDE_MANIFEST_NAMES = ["knip.json", "deno.lock"];
40621
40613
  const ANALYZED_MANIFEST_NAMES = new Set([...ANALYZED_MANIFEST_FILENAMES, ...CORE_SIDE_MANIFEST_NAMES]);
40622
40614
  const isTsConfigLikeFile = (fileName) => (fileName.startsWith("tsconfig") || fileName.startsWith("jsconfig")) && fileName.endsWith(".json");
40623
40615
  const isFingerprintedFile = (fileName) => ANALYZED_FILE_EXTENSIONS.has(Path.extname(fileName).toLowerCase()) || ANALYZED_MANIFEST_NAMES.has(fileName) || isTsConfigLikeFile(fileName);
40624
- /**
40625
- * Stat snapshot of every file the analysis reads, keyed by root-relative
40626
- * `/`-separated path. Taken BEFORE the (long) analysis so a stored result is
40627
- * verified against the tree it started from.
40628
- */
40629
- const collectAnalyzedFileStats = (rootDirectory) => {
40630
- const statByRelativePath = /* @__PURE__ */ new Map();
40616
+ const collectAnalyzedFileFingerprints = (rootDirectory) => {
40617
+ const fingerprints = [];
40631
40618
  for (const { absolutePath, name } of walkSourceTreeFiles(rootDirectory)) {
40632
40619
  if (!isFingerprintedFile(name)) continue;
40633
40620
  try {
40634
40621
  const fileStat = NFS.statSync(absolutePath);
40635
40622
  const relativePath = Path.relative(rootDirectory, absolutePath).replace(/\\/g, "/");
40636
- statByRelativePath.set(relativePath, {
40637
- mtimeMs: fileStat.mtimeMs,
40638
- size: fileStat.size
40639
- });
40623
+ fingerprints.push(`${relativePath}:${fileStat.mtimeMs}:${fileStat.size}`);
40640
40624
  } catch {}
40641
40625
  }
40642
- return statByRelativePath;
40626
+ return fingerprints.sort();
40643
40627
  };
40644
40628
  const bundledRequire$2 = createRequire(import.meta.url);
40645
40629
  const resolveDeslopVersion = () => {
@@ -40651,13 +40635,14 @@ const resolveDeslopVersion = () => {
40651
40635
  }
40652
40636
  };
40653
40637
  const computeDeadCodeCacheKey = (input) => crypto.createHash("sha1").update(JSON.stringify({
40654
- schemaVersion: 2,
40638
+ schemaVersion: 1,
40655
40639
  coreVersion: input.coreVersion,
40656
40640
  deslopVersion: resolveDeslopVersion(),
40657
40641
  deslopJsModuleSpecifier: input.deslopJsModuleSpecifier,
40658
40642
  entryPatterns: input.entryPatterns,
40659
40643
  ignorePatterns: input.ignorePatterns,
40660
- tsConfigFile: input.tsConfigPath === void 0 ? null : Path.relative(input.rootDirectory, input.tsConfigPath).replace(/\\/g, "/")
40644
+ tsConfigFile: input.tsConfigPath === void 0 ? null : Path.relative(input.rootDirectory, input.tsConfigPath).replace(/\\/g, "/"),
40645
+ files: collectAnalyzedFileFingerprints(input.rootDirectory)
40661
40646
  })).digest("hex");
40662
40647
  const validateDiagnostic$2 = decodeUnknownSync(Diagnostic);
40663
40648
  const decodeCachedDiagnostics = (raw) => {
@@ -40668,68 +40653,16 @@ const decodeCachedDiagnostics = (raw) => {
40668
40653
  return null;
40669
40654
  }
40670
40655
  };
40671
- const isPersistedFileIdentity = (value) => Array.isArray(value) && value.length === 3 && typeof value[0] === "number" && typeof value[1] === "number" && typeof value[2] === "string";
40672
- const lookupDeadCodeResultCache = (input) => {
40673
- const cacheFilePath = Path.join(input.cacheDirectory, DEAD_CODE_CACHE_FILENAME);
40674
- const persisted = failOpenReadJson(cacheFilePath, null);
40675
- if (persisted === null || !isRecord$1(persisted) || persisted.version !== 2 || persisted.key !== input.cacheKey || !isRecord$1(persisted.files) || !Array.isArray(persisted.diagnostics)) return null;
40676
- const storedFileEntries = Object.entries(persisted.files);
40677
- if (storedFileEntries.length !== input.currentFileStats.size) return null;
40678
- const repairedFiles = {};
40679
- let repairedCount = 0;
40680
- for (const [relativePath, storedIdentity] of storedFileEntries) {
40681
- if (!isPersistedFileIdentity(storedIdentity)) return null;
40682
- const currentStat = input.currentFileStats.get(relativePath);
40683
- if (currentStat === void 0) return null;
40684
- const [storedMtimeMs, storedSize, storedContentHash] = storedIdentity;
40685
- if (currentStat.mtimeMs === storedMtimeMs && currentStat.size === storedSize) {
40686
- repairedFiles[relativePath] = storedIdentity;
40687
- continue;
40688
- }
40689
- if (currentStat.size !== storedSize) return null;
40690
- const currentContentHash = hashFileContents(Path.join(input.rootDirectory, relativePath));
40691
- if (currentContentHash === null || currentContentHash !== storedContentHash) return null;
40692
- repairedFiles[relativePath] = [
40693
- currentStat.mtimeMs,
40694
- currentStat.size,
40695
- storedContentHash
40696
- ];
40697
- repairedCount += 1;
40698
- }
40699
- const diagnostics = decodeCachedDiagnostics(persisted.diagnostics);
40700
- if (diagnostics === null) return null;
40701
- if (repairedCount > 0) atomicWriteJson(cacheFilePath, {
40702
- version: 2,
40703
- key: input.cacheKey,
40704
- files: repairedFiles,
40705
- diagnostics: persisted.diagnostics
40706
- });
40707
- return diagnostics;
40656
+ const lookupDeadCodeResultCache = (cacheDirectory, cacheKey) => {
40657
+ const persisted = failOpenReadJson(Path.join(cacheDirectory, DEAD_CODE_CACHE_FILENAME), null);
40658
+ if (persisted === null || !isRecord$1(persisted) || persisted.version !== 1 || persisted.key !== cacheKey || !Array.isArray(persisted.diagnostics)) return null;
40659
+ return decodeCachedDiagnostics(persisted.diagnostics);
40708
40660
  };
40709
- const storeDeadCodeResultCache = (input) => {
40710
- const persistedFiles = {};
40711
- for (const [relativePath, snapshotStat] of input.snapshotFileStats) {
40712
- const absolutePath = Path.join(input.rootDirectory, relativePath);
40713
- const contentHash = hashFileContents(absolutePath);
40714
- if (contentHash === null) return;
40715
- let currentStat;
40716
- try {
40717
- currentStat = NFS.statSync(absolutePath);
40718
- } catch {
40719
- return;
40720
- }
40721
- if (currentStat.mtimeMs !== snapshotStat.mtimeMs || currentStat.size !== snapshotStat.size) return;
40722
- persistedFiles[relativePath] = [
40723
- snapshotStat.mtimeMs,
40724
- snapshotStat.size,
40725
- contentHash
40726
- ];
40727
- }
40728
- atomicWriteJson(Path.join(input.cacheDirectory, DEAD_CODE_CACHE_FILENAME), {
40729
- version: 2,
40730
- key: input.cacheKey,
40731
- files: persistedFiles,
40732
- diagnostics: input.diagnostics
40661
+ const storeDeadCodeResultCache = (cacheDirectory, cacheKey, diagnostics) => {
40662
+ atomicWriteJson(Path.join(cacheDirectory, DEAD_CODE_CACHE_FILENAME), {
40663
+ version: 1,
40664
+ key: cacheKey,
40665
+ diagnostics
40733
40666
  });
40734
40667
  };
40735
40668
  const readSystemFacts = () => ({
@@ -41139,22 +41072,16 @@ const checkDeadCode = async (options) => {
41139
41072
  const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory);
41140
41073
  const tsConfigPath = resolveTsConfigPath(rootDirectory);
41141
41074
  const deslopJsModuleSpecifier = options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js");
41142
- const fileStatsSnapshot = options.cacheEnabled ? collectAnalyzedFileStats(rootDirectory) : null;
41143
- const cacheKey = fileStatsSnapshot === null ? null : computeDeadCodeCacheKey({
41075
+ const cacheKey = options.cacheEnabled ? computeDeadCodeCacheKey({
41144
41076
  rootDirectory,
41145
41077
  entryPatterns,
41146
41078
  ignorePatterns,
41147
41079
  tsConfigPath,
41148
41080
  deslopJsModuleSpecifier,
41149
41081
  coreVersion: CORE_PACKAGE_VERSION
41150
- });
41151
- if (cacheKey !== null && fileStatsSnapshot !== null) {
41152
- const cachedDiagnostics = lookupDeadCodeResultCache({
41153
- cacheDirectory: resolveReactDoctorCacheDir(rootDirectory),
41154
- cacheKey,
41155
- rootDirectory,
41156
- currentFileStats: fileStatsSnapshot
41157
- });
41082
+ }) : null;
41083
+ if (cacheKey !== null) {
41084
+ const cachedDiagnostics = lookupDeadCodeResultCache(resolveReactDoctorCacheDir(rootDirectory), cacheKey);
41158
41085
  options.onCacheOutcome?.(cachedDiagnostics !== null);
41159
41086
  if (cachedDiagnostics !== null) return [...cachedDiagnostics];
41160
41087
  }
@@ -41228,13 +41155,7 @@ const checkDeadCode = async (options) => {
41228
41155
  category: DEAD_CODE_CATEGORY
41229
41156
  });
41230
41157
  }
41231
- if (cacheKey !== null && fileStatsSnapshot !== null) storeDeadCodeResultCache({
41232
- cacheDirectory: resolveReactDoctorCacheDir(rootDirectory),
41233
- cacheKey,
41234
- rootDirectory,
41235
- snapshotFileStats: fileStatsSnapshot,
41236
- diagnostics
41237
- });
41158
+ if (cacheKey !== null) storeDeadCodeResultCache(resolveReactDoctorCacheDir(rootDirectory), cacheKey, diagnostics);
41238
41159
  return diagnostics;
41239
41160
  };
41240
41161
  const DEAD_CODE_RULE_KEY_PREFIX = `${DEAD_CODE_PLUGIN}/`;
@@ -42330,6 +42251,13 @@ const createFileLintCache = (cacheDirectory, rulesetHash) => {
42330
42251
  }
42331
42252
  };
42332
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
+ };
42333
42261
  const ABSENT_CONTENT_ANSWER = "absent";
42334
42262
  /**
42335
42263
  * Answers dependency probes for the sidecar lint cache — used both when
@@ -44470,20 +44398,6 @@ const writeCachedSocketBody = (cacheFile, body) => {
44470
44398
  }));
44471
44399
  } catch {}
44472
44400
  };
44473
- const pruneExpiredSocketCache = (cacheDirectory) => {
44474
- try {
44475
- const supplyChainCacheDirectory = Path.join(cacheDirectory, SUPPLY_CHAIN_CACHE_SUBDIR);
44476
- const expiryThresholdMs = Date.now() - SUPPLY_CHAIN_CACHE_TTL_MS;
44477
- for (const entryName of NFS.readdirSync(supplyChainCacheDirectory)) {
44478
- const entryPath = Path.join(supplyChainCacheDirectory, entryName);
44479
- try {
44480
- if (NFS.statSync(entryPath).mtimeMs < expiryThresholdMs) NFS.rmSync(entryPath);
44481
- } catch {
44482
- continue;
44483
- }
44484
- }
44485
- } catch {}
44486
- };
44487
44401
  const fetchSocketArtifact = (dependency, cacheDirectory) => tryPromise(async (signal) => {
44488
44402
  const cacheFile = cacheDirectory === null ? null : supplyChainCacheFile(cacheDirectory, dependency);
44489
44403
  if (cacheFile !== null) {
@@ -44611,7 +44525,6 @@ const checkSupplyChain = (input) => gen(function* () {
44611
44525
  const dependencies = collectDependenciesToScore(readPackageJson$1(packageJsonPath), readPackageJsonText(packageJsonPath), options.includeDevDependencies);
44612
44526
  if (dependencies.length === 0) return [];
44613
44527
  const cacheDirectory = isSupplyChainCacheDisabled() ? null : resolveReactDoctorCacheDir(input.rootDirectory);
44614
- if (cacheDirectory !== null) pruneExpiredSocketCache(cacheDirectory);
44615
44528
  const artifacts = yield* forEach$1(dependencies, (dependency) => fetchSocketArtifact(dependency, cacheDirectory), { concurrency: 8 }).pipe(timeoutOption(input.totalTimeoutMs ?? 9e4), map$3((maybeArtifacts) => getOrElse$1(maybeArtifacts, () => [])));
44616
44529
  const diagnostics = [];
44617
44530
  for (let index = 0; index < dependencies.length; index += 1) {
@@ -45497,9 +45410,11 @@ const computeConfigFingerprint = (projectDirectory, version) => {
45497
45410
  const parts = [`v=${version}`];
45498
45411
  let directory = projectDirectory;
45499
45412
  for (;;) {
45500
- for (const filename of CONFIG_FINGERPRINT_FILENAMES) {
45501
- const contentHash = hashFileContents(path.join(directory, filename));
45502
- if (contentHash !== null) parts.push(`${directory}/${filename}=${contentHash}`);
45413
+ for (const filename of CONFIG_FINGERPRINT_FILENAMES) try {
45414
+ const stat = fs.statSync(path.join(directory, filename));
45415
+ parts.push(`${directory}/${filename}=${stat.mtimeMs}:${stat.size}`);
45416
+ } catch {
45417
+ continue;
45503
45418
  }
45504
45419
  const parent = path.dirname(directory);
45505
45420
  if (parent === directory) break;
@@ -45795,7 +45710,6 @@ const isNonInteractiveEnvironment = () => NON_INTERACTIVE_ENVIRONMENT_VARIABLES.
45795
45710
  const REACT_DOCTOR_CONFIG_PROJECT_NAME = "react-doctor";
45796
45711
  const STAGED_FILES_TEMP_DIR_PREFIX = "react-doctor-staged-";
45797
45712
  const BASELINE_FILES_TEMP_DIR_PREFIX = "react-doctor-baseline-";
45798
- const SCAN_RESULT_CACHE_FILENAME = "scan-cache.json";
45799
45713
  const RUN_GIT_MAX_BUFFER_BYTES = 64 * 1024 * 1024;
45800
45714
  const GH_DEFAULT_BRANCH_PROBE_TIMEOUT_MS = 5e3;
45801
45715
  const CI_TRUST_COMPANIES = "PayPal, Rippling, and Alibaba";
@@ -45887,7 +45801,7 @@ const makeNoopConsole = () => ({
45887
45801
  });
45888
45802
  //#endregion
45889
45803
  //#region src/cli/utils/version.ts
45890
- const VERSION = "0.6.3-dev.ced746f";
45804
+ const VERSION = "0.6.3-dev.ea9a775";
45891
45805
  //#endregion
45892
45806
  //#region src/cli/utils/json-mode.ts
45893
45807
  let context = null;
@@ -46256,13 +46170,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
46256
46170
  * uploads source-map artifacts under, so stack frames symbolicate. Honors the
46257
46171
  * standard `SENTRY_RELEASE` override.
46258
46172
  */
46259
- const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.6.3-dev.ced746f`;
46173
+ const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.6.3-dev.ea9a775`;
46260
46174
  /**
46261
46175
  * Deployment environment shown in Sentry's environment filter. Defaults to
46262
46176
  * `production` for tagged releases and `development` for dev/unbuilt versions,
46263
46177
  * overridable via the standard `SENTRY_ENVIRONMENT` env var.
46264
46178
  */
46265
- const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.6.3-dev.ced746f") ? "development" : "production");
46179
+ const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.6.3-dev.ea9a775") ? "development" : "production");
46266
46180
  /**
46267
46181
  * Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
46268
46182
  * (set to `0` to disable tracing) and falls back to
@@ -58340,276 +58254,6 @@ const buildRuleBlastRadii = (diagnostics) => buildSortedRuleGroups(diagnostics).
58340
58254
  })).toSorted((left, right) => right.fileCount - left.fileCount);
58341
58255
  const findMigrationScaleBuckets = (diagnostics) => buildRuleBlastRadii(diagnostics).filter((bucket) => bucket.fileCount >= 40);
58342
58256
  //#endregion
58343
- //#region src/cli/utils/scan-result-cache.ts
58344
- const CACHE_DISABLED_VALUES = new Set(["1", "true"]);
58345
- const TOOLCHAIN_PACKAGE_SPECIFIERS = [
58346
- "oxlint/package.json",
58347
- "oxlint-plugin-react-doctor/package.json",
58348
- "deslop-js/package.json",
58349
- "eslint-plugin-react-hooks/package.json"
58350
- ];
58351
- const bundledRequire = createRequire(import.meta.url);
58352
- const normalizeForStableJson = (value) => {
58353
- if (value === null) return null;
58354
- if (value === void 0) return void 0;
58355
- const valueType = typeof value;
58356
- if (valueType === "string" || valueType === "number" || valueType === "boolean") return value;
58357
- if (valueType === "bigint" || valueType === "function" || valueType === "symbol") throw new Error("Unsupported cache key value");
58358
- if (Array.isArray(value)) return value.map((item) => {
58359
- const normalized = normalizeForStableJson(item);
58360
- return normalized === void 0 ? null : normalized;
58361
- });
58362
- const prototype = Object.getPrototypeOf(value);
58363
- if (prototype !== Object.prototype && prototype !== null) throw new Error("Unsupported cache key object");
58364
- if (!isRecord(value)) throw new Error("Unsupported cache key object");
58365
- const normalizedRecord = {};
58366
- for (const key of Object.keys(value).sort()) {
58367
- const normalized = normalizeForStableJson(value[key]);
58368
- if (normalized !== void 0) normalizedRecord[key] = normalized;
58369
- }
58370
- return normalizedRecord;
58371
- };
58372
- const stringifyStableJson = (value) => {
58373
- try {
58374
- return JSON.stringify(normalizeForStableJson(value));
58375
- } catch {
58376
- return null;
58377
- }
58378
- };
58379
- const hashString = (value) => crypto.createHash("sha1").update(value).digest("hex");
58380
- const readHeadSha = (projectDirectory) => runGit(projectDirectory, ["rev-parse", "HEAD"]);
58381
- const parseWorktreeStatusRecords = (statusOutput) => {
58382
- const entries = [];
58383
- const tokens = statusOutput.split("\0");
58384
- for (let tokenIndex = 0; tokenIndex < tokens.length; tokenIndex += 1) {
58385
- let record = tokens[tokenIndex];
58386
- if (record.length === 0) continue;
58387
- if (record.length < 3 || record[2] !== " ") {
58388
- if (tokenIndex !== 0) return null;
58389
- record = ` ${record}`;
58390
- }
58391
- if (record.length < 4 || record[2] !== " ") return null;
58392
- const statusCode = record.slice(0, 2);
58393
- const recordPath = record.slice(3);
58394
- if (statusCode.includes("R") || statusCode.includes("C")) {
58395
- tokenIndex += 1;
58396
- const originPath = tokens[tokenIndex];
58397
- if (originPath === void 0 || originPath.length === 0) return null;
58398
- entries.push({
58399
- statusCode,
58400
- path: recordPath,
58401
- originPath
58402
- });
58403
- continue;
58404
- }
58405
- entries.push({
58406
- statusCode,
58407
- path: recordPath
58408
- });
58409
- }
58410
- return entries;
58411
- };
58412
- const buildDirtyPathContentFingerprint = (absolutePath, statusCode) => {
58413
- let stats;
58414
- try {
58415
- stats = fs.lstatSync(absolutePath);
58416
- } catch {
58417
- return statusCode.includes("D") ? "deleted" : null;
58418
- }
58419
- if (stats.isSymbolicLink()) try {
58420
- return `link:${fs.readlinkSync(absolutePath)}`;
58421
- } catch {
58422
- return null;
58423
- }
58424
- if (!stats.isFile()) return null;
58425
- if (stats.size > 10485760) return `stat:${stats.mtimeMs}:${stats.size}`;
58426
- return hashFileContents(absolutePath);
58427
- };
58428
- const buildWorktreeFingerprint = (projectDirectory) => {
58429
- const statusOutput = runGit(projectDirectory, [
58430
- "status",
58431
- "--porcelain=v1",
58432
- "-z",
58433
- "--untracked-files=all"
58434
- ]);
58435
- if (statusOutput === null) return null;
58436
- if (statusOutput.length === 0) return [];
58437
- const statusEntries = parseWorktreeStatusRecords(statusOutput);
58438
- if (statusEntries === null) return null;
58439
- if (statusEntries.length > 300) return null;
58440
- const repositoryRoot = runGit(projectDirectory, ["rev-parse", "--show-toplevel"]);
58441
- if (repositoryRoot === null) return null;
58442
- const dirtyEntries = [];
58443
- for (const statusEntry of statusEntries) {
58444
- const contentFingerprint = buildDirtyPathContentFingerprint(path.join(repositoryRoot, statusEntry.path), statusEntry.statusCode);
58445
- if (contentFingerprint === null) return null;
58446
- dirtyEntries.push({
58447
- ...statusEntry,
58448
- contentFingerprint
58449
- });
58450
- }
58451
- return dirtyEntries.sort((firstEntry, secondEntry) => {
58452
- const firstSortKey = `${firstEntry.path}${firstEntry.statusCode}`;
58453
- const secondSortKey = `${secondEntry.path}${secondEntry.statusCode}`;
58454
- return firstSortKey < secondSortKey ? -1 : firstSortKey > secondSortKey ? 1 : 0;
58455
- });
58456
- };
58457
- const DOTENV_FILE_NAME_PATTERN = /^\.env(\.|$)/;
58458
- const resolveDotenvFingerprint = (projectDirectory) => {
58459
- try {
58460
- return fs.readdirSync(projectDirectory).filter((entryName) => DOTENV_FILE_NAME_PATTERN.test(entryName)).sort().map((entryName) => `${entryName}=${hashFileContents(path.join(projectDirectory, entryName)) ?? "unreadable"}`);
58461
- } catch {
58462
- return [];
58463
- }
58464
- };
58465
- const hasHiddenTrackedFileState = (projectDirectory) => {
58466
- const output = runGit(projectDirectory, ["ls-files", "-v"]);
58467
- if (output === null) return true;
58468
- return output.split("\n").some((line) => line.length > 0 && line[0] !== "H");
58469
- };
58470
- const resolveCacheFilePath = (projectDirectory) => path.join(resolveReactDoctorCacheDir(projectDirectory), SCAN_RESULT_CACHE_FILENAME);
58471
- const readPersistedCache = (cacheFilePath) => {
58472
- try {
58473
- const parsed = JSON.parse(fs.readFileSync(cacheFilePath, "utf8"));
58474
- if (!isRecord(parsed) || parsed.version !== 3) return {
58475
- version: 3,
58476
- entries: []
58477
- };
58478
- if (!Array.isArray(parsed.entries)) return {
58479
- version: 3,
58480
- entries: []
58481
- };
58482
- const entries = [];
58483
- for (const entry of parsed.entries) {
58484
- if (!isRecord(entry) || typeof entry.key !== "string" || typeof entry.createdAtMs !== "number") continue;
58485
- if (!isRecord(entry.payload) || !Array.isArray(entry.payload.diagnostics)) continue;
58486
- entries.push(entry);
58487
- }
58488
- return {
58489
- version: 3,
58490
- entries
58491
- };
58492
- } catch {
58493
- return {
58494
- version: 3,
58495
- entries: []
58496
- };
58497
- }
58498
- };
58499
- const writePersistedCache = (cacheFilePath, cache) => {
58500
- try {
58501
- fs.mkdirSync(path.dirname(cacheFilePath), { recursive: true });
58502
- const tempPath = `${cacheFilePath}.${process.pid}.tmp`;
58503
- fs.writeFileSync(tempPath, JSON.stringify(cache));
58504
- fs.renameSync(tempPath, cacheFilePath);
58505
- } catch {
58506
- return;
58507
- }
58508
- };
58509
- /**
58510
- * The global cache off-switch (`REACT_DOCTOR_NO_CACHE`), which disables every
58511
- * cache subsystem: this whole-repo scan cache plus core's per-file lint,
58512
- * sidecar, and dead-code caches (their `Context.Reference` defaults read the
58513
- * same variable). Exported for the wide event's `cache.temperature`
58514
- * derivation, which reports `"disabled"` instead of `"cold"` when the switch
58515
- * is on. Granular knobs (`REACT_DOCTOR_NO_FILE_CACHE`, …) are deliberately
58516
- * not consulted — they leave the other subsystems live.
58517
- */
58518
- const isCacheGloballyDisabled = () => CACHE_DISABLED_VALUES.has(process.env.REACT_DOCTOR_NO_CACHE?.toLowerCase() ?? "");
58519
- const resolveProjectIdentity = (projectDirectory) => {
58520
- try {
58521
- return fs.realpathSync.native(projectDirectory);
58522
- } catch {
58523
- return path.resolve(projectDirectory);
58524
- }
58525
- };
58526
- const fileFingerprint = (filePath) => {
58527
- try {
58528
- const stat = fs.statSync(filePath);
58529
- return `${filePath}:${stat.mtimeMs}:${stat.size}`;
58530
- } catch {
58531
- return null;
58532
- }
58533
- };
58534
- const resolveToolchainFingerprint = (nodeBinaryPath) => {
58535
- const fingerprints = [];
58536
- if (nodeBinaryPath !== null) fingerprints.push(nodeBinaryPath === process.execPath ? `node=${process.version}` : `node=${fileFingerprint(nodeBinaryPath) ?? "unreadable"}`);
58537
- for (const specifier of TOOLCHAIN_PACKAGE_SPECIFIERS) try {
58538
- const packageJson = bundledRequire(specifier);
58539
- const version = typeof packageJson.version === "string" ? packageJson.version : "unknown";
58540
- fingerprints.push(`${specifier}=${version}`);
58541
- } catch {
58542
- fingerprints.push(`${specifier}=missing`);
58543
- }
58544
- return fingerprints;
58545
- };
58546
- const buildScanResultCacheKey = (input) => {
58547
- if (isCacheGloballyDisabled()) return null;
58548
- if (hasHiddenTrackedFileState(input.projectDirectory)) return null;
58549
- const worktreeFingerprint = buildWorktreeFingerprint(input.projectDirectory);
58550
- if (worktreeFingerprint === null) return null;
58551
- const headSha = readHeadSha(input.projectDirectory);
58552
- if (headSha === null) return null;
58553
- if (stringifyStableJson(input.userConfig) === null) return null;
58554
- const cacheKeyJson = stringifyStableJson({
58555
- schemaVersion: 3,
58556
- projectIdentity: resolveProjectIdentity(input.projectDirectory),
58557
- headSha,
58558
- worktreeFingerprint,
58559
- dotenvFingerprint: resolveDotenvFingerprint(input.projectDirectory),
58560
- reactDoctorVersion: input.version,
58561
- nodeVersion: process.version,
58562
- toolchainFingerprint: resolveToolchainFingerprint(input.nodeBinaryPath),
58563
- configFingerprint: computeConfigFingerprint(input.projectDirectory, input.version),
58564
- hasConfigOverride: input.hasConfigOverride,
58565
- configSourceDirectory: input.configSourceDirectory,
58566
- userConfig: input.userConfig,
58567
- engineOptions: {
58568
- lint: input.options.lint,
58569
- deadCode: input.options.deadCode,
58570
- includePaths: [...input.options.includePaths].sort(),
58571
- customRulesOnly: input.options.customRulesOnly,
58572
- respectInlineDisables: input.options.respectInlineDisables,
58573
- warnings: input.options.warnings,
58574
- adoptExistingLintConfig: input.options.adoptExistingLintConfig,
58575
- ignoredTags: [...input.options.ignoredTags].sort(),
58576
- concurrency: input.options.concurrency,
58577
- lintBatchOrdering: resolveLintBatchOrdering(),
58578
- baselineRef: input.options.baseline?.ref,
58579
- changedLineRanges: input.options.changedLineRanges ?? void 0,
58580
- noScore: input.options.noScore,
58581
- isCi: input.options.isCi,
58582
- suppressRendering: input.options.suppressRendering,
58583
- supplyChainManifestChanged: input.options.supplyChainManifestChanged
58584
- }
58585
- });
58586
- return cacheKeyJson === null ? null : hashString(cacheKeyJson);
58587
- };
58588
- const createScanResultCache = (projectDirectory) => {
58589
- const cacheFilePath = resolveCacheFilePath(projectDirectory);
58590
- const persistedCache = readPersistedCache(cacheFilePath);
58591
- const entries = /* @__PURE__ */ new Map();
58592
- for (const entry of persistedCache.entries) entries.set(entry.key, entry);
58593
- const persist = () => {
58594
- writePersistedCache(cacheFilePath, {
58595
- version: 3,
58596
- entries: [...entries.values()].sort((firstEntry, secondEntry) => secondEntry.createdAtMs - firstEntry.createdAtMs).slice(0, 20)
58597
- });
58598
- };
58599
- return {
58600
- lookup: (key) => entries.get(key)?.payload ?? null,
58601
- store: (key, payload) => {
58602
- entries.set(key, {
58603
- key,
58604
- createdAtMs: Date.now(),
58605
- payload
58606
- });
58607
- persist();
58608
- }
58609
- };
58610
- };
58611
- const shouldStoreScanPayload = (payload) => !payload.didLintFail && !payload.didDeadCodeFail && payload.lintPartialFailures.length === 0 && !payload.supplyChainOverlapTimedOut && payload.securityScanFailed !== true;
58612
- //#endregion
58613
58257
  //#region src/cli/utils/should-block-ci.ts
58614
58258
  const shouldBlockCi = (diagnostics, blockingLevel) => {
58615
58259
  if (blockingLevel === "none") return false;
@@ -58651,63 +58295,6 @@ const readEnvBoolean = (name) => {
58651
58295
  if (value === void 0) return null;
58652
58296
  return value.toLowerCase() === "true" || value === "1";
58653
58297
  };
58654
- const ratioOf = (numerator, denominator) => denominator != null && denominator > 0 ? (numerator ?? 0) / denominator : null;
58655
- const resolveDeadCodeReuseRatio = (result) => {
58656
- if (result.deadCodeCacheHit === true) return 1;
58657
- const summaryTotal = (result.deadCodeSummaryCacheHits ?? 0) + (result.deadCodeSummaryCacheMisses ?? 0);
58658
- if (summaryTotal > 0) return (result.deadCodeSummaryCacheHits ?? 0) / summaryTotal;
58659
- return result.deadCodeCacheHit === false ? 0 : null;
58660
- };
58661
- /**
58662
- * One queryable cache temperature per scan, derived from the whole stack:
58663
- *
58664
- * - `"turbo"` — whole-repo scan-result replay (the explicit
58665
- * `wholeRepoCacheHit` flag from the CLI's cachedPayload
58666
- * branch; no scan work ran).
58667
- * - `"warm"` — any incremental reuse: per-file lint hits, sidecar
58668
- * replays, a dead-code whole-result hit, or dead-code
58669
- * summary-cache hits.
58670
- * - `"disabled"` — zero reuse because the global `REACT_DOCTOR_NO_CACHE`
58671
- * off-switch is on. Granular per-cache opt-outs
58672
- * (`REACT_DOCTOR_NO_FILE_CACHE`, …) still read warm/cold,
58673
- * since the other subsystems stay live.
58674
- * - `"cold"` — caches on, zero reuse (first scan / everything changed).
58675
- *
58676
- * `cache.warmth` is the headline reuse magnitude in [0, 1]: the plain mean of
58677
- * the subsystem reuse fractions known this run (lint hit ratio, sidecar
58678
- * replay ratio, dead-code reuse), skipping subsystems that never consulted a
58679
- * cache; `1` on turbo, dropped when nothing consulted any cache. Deliberately
58680
- * unweighted — the per-subsystem dims stay the precise signal; warmth is the
58681
- * p50/p90-able summary. Emitted only on the success path.
58682
- */
58683
- const buildCacheAttributes = (input) => {
58684
- if (input.result === void 0) return {};
58685
- if (input.wholeRepoCacheHit) return withNamespace("cache", {
58686
- wholeRepoHit: true,
58687
- temperature: "turbo",
58688
- warmth: 1
58689
- });
58690
- const result = input.result;
58691
- const subsystemReuseRatios = [
58692
- ratioOf(result.lintCacheHitFileCount, result.lintCacheTotalFileCount),
58693
- ratioOf(result.lintSidecarReplayedFileCount, result.lintSidecarTotalFileCount),
58694
- resolveDeadCodeReuseRatio(result)
58695
- ];
58696
- let knownSubsystemCount = 0;
58697
- let reuseRatioSum = 0;
58698
- for (const reuseRatio of subsystemReuseRatios) {
58699
- if (reuseRatio === null) continue;
58700
- knownSubsystemCount += 1;
58701
- reuseRatioSum += reuseRatio;
58702
- }
58703
- const warmth = knownSubsystemCount > 0 ? reuseRatioSum / knownSubsystemCount : null;
58704
- const temperature = warmth !== null && warmth > 0 ? "warm" : isCacheGloballyDisabled() ? "disabled" : "cold";
58705
- return withNamespace("cache", {
58706
- wholeRepoHit: input.wholeRepoCacheHit ?? null,
58707
- temperature,
58708
- warmth
58709
- });
58710
- };
58711
58298
  const resolveVersionPin = (versionInput) => {
58712
58299
  if (versionInput === void 0 || versionInput.trim() === "") return null;
58713
58300
  if (versionInput === "latest") return "latest";
@@ -58813,10 +58400,10 @@ const buildOutcomeAttributes = (input) => {
58813
58400
  deadlineSkippedFileCount: input.lintDeadlineSkippedFileCount ?? null,
58814
58401
  cacheHitFiles: result.lintCacheHitFileCount ?? null,
58815
58402
  cacheTotalFiles: result.lintCacheTotalFileCount ?? null,
58816
- cacheHitRatio: ratioOf(result.lintCacheHitFileCount, result.lintCacheTotalFileCount),
58403
+ cacheHitRatio: result.lintCacheTotalFileCount != null && result.lintCacheTotalFileCount > 0 ? (result.lintCacheHitFileCount ?? 0) / result.lintCacheTotalFileCount : null,
58817
58404
  sidecarReplayedFiles: result.lintSidecarReplayedFileCount ?? null,
58818
58405
  sidecarTotalFiles: result.lintSidecarTotalFileCount ?? null,
58819
- sidecarReplayRatio: ratioOf(result.lintSidecarReplayedFileCount, result.lintSidecarTotalFileCount)
58406
+ sidecarReplayRatio: result.lintSidecarTotalFileCount != null && result.lintSidecarTotalFileCount > 0 ? (result.lintSidecarReplayedFileCount ?? 0) / result.lintSidecarTotalFileCount : null
58820
58407
  }),
58821
58408
  ...withNamespace("deadCode", {
58822
58409
  failed: input.didDeadCodeFail ?? null,
@@ -58883,7 +58470,7 @@ const buildScanAttributes = (input) => {
58883
58470
  * Projects a scan into the namespaced attribute set for its root span — the
58884
58471
  * canonical per-scan "wide event". Every attribute carries a dotted namespace
58885
58472
  * that groups it by concept (`scan.*` config, `action.*` CI knobs, `outcome.*`
58886
- * verdict, `diag.*` findings, `score.*`, `lint.*`, `deadCode.*`, `cache.*`,
58473
+ * verdict, `diag.*` findings, `score.*`, `lint.*`, `deadCode.*`,
58887
58474
  * `supplyChain.*`, `timing.*`, `migration.*`, `baseline.*`) so the attributes
58888
58475
  * tree up in Sentry's attribute browser and stay filter-/group-/aggregate-able
58889
58476
  * in the Spans dataset. Pure and exported so the projection (outcome
@@ -58897,8 +58484,7 @@ const buildScanAttributes = (input) => {
58897
58484
  const buildRunEventAttributes = (input) => toSpanAttributes({
58898
58485
  ...buildScanAttributes(input),
58899
58486
  ...buildActionAttributes(),
58900
- ...buildOutcomeAttributes(input),
58901
- ...buildCacheAttributes(input)
58487
+ ...buildOutcomeAttributes(input)
58902
58488
  });
58903
58489
  /**
58904
58490
  * Stamps the wide-event attributes onto the run's root span. A guarded no-op
@@ -60108,6 +59694,275 @@ const resolveCliCategories = (categoryFlag) => {
60108
59694
  return resolvedCategories.length > 0 ? resolvedCategories : void 0;
60109
59695
  };
60110
59696
  //#endregion
59697
+ //#region src/cli/utils/scan-result-cache.ts
59698
+ const CACHE_DISABLED_VALUES = new Set(["1", "true"]);
59699
+ const TOOLCHAIN_PACKAGE_SPECIFIERS = [
59700
+ "oxlint",
59701
+ "oxlint/package.json",
59702
+ "oxlint-plugin-react-doctor",
59703
+ "deslop-js/package.json",
59704
+ "eslint-plugin-react-hooks/package.json"
59705
+ ];
59706
+ const bundledRequire = createRequire(import.meta.url);
59707
+ const normalizeForStableJson = (value) => {
59708
+ if (value === null) return null;
59709
+ if (value === void 0) return void 0;
59710
+ const valueType = typeof value;
59711
+ if (valueType === "string" || valueType === "number" || valueType === "boolean") return value;
59712
+ if (valueType === "bigint" || valueType === "function" || valueType === "symbol") throw new Error("Unsupported cache key value");
59713
+ if (Array.isArray(value)) return value.map((item) => {
59714
+ const normalized = normalizeForStableJson(item);
59715
+ return normalized === void 0 ? null : normalized;
59716
+ });
59717
+ const prototype = Object.getPrototypeOf(value);
59718
+ if (prototype !== Object.prototype && prototype !== null) throw new Error("Unsupported cache key object");
59719
+ if (!isRecord(value)) throw new Error("Unsupported cache key object");
59720
+ const normalizedRecord = {};
59721
+ for (const key of Object.keys(value).sort()) {
59722
+ const normalized = normalizeForStableJson(value[key]);
59723
+ if (normalized !== void 0) normalizedRecord[key] = normalized;
59724
+ }
59725
+ return normalizedRecord;
59726
+ };
59727
+ const stringifyStableJson = (value) => {
59728
+ try {
59729
+ return JSON.stringify(normalizeForStableJson(value));
59730
+ } catch {
59731
+ return null;
59732
+ }
59733
+ };
59734
+ const hashString = (value) => crypto.createHash("sha1").update(value).digest("hex");
59735
+ const readHeadSha = (projectDirectory) => runGit(projectDirectory, ["rev-parse", "HEAD"]);
59736
+ const parseWorktreeStatusRecords = (statusOutput) => {
59737
+ const entries = [];
59738
+ const tokens = statusOutput.split("\0");
59739
+ for (let tokenIndex = 0; tokenIndex < tokens.length; tokenIndex += 1) {
59740
+ let record = tokens[tokenIndex];
59741
+ if (record.length === 0) continue;
59742
+ if (record.length < 3 || record[2] !== " ") {
59743
+ if (tokenIndex !== 0) return null;
59744
+ record = ` ${record}`;
59745
+ }
59746
+ if (record.length < 4 || record[2] !== " ") return null;
59747
+ const statusCode = record.slice(0, 2);
59748
+ const recordPath = record.slice(3);
59749
+ if (statusCode.includes("R") || statusCode.includes("C")) {
59750
+ tokenIndex += 1;
59751
+ const originPath = tokens[tokenIndex];
59752
+ if (originPath === void 0 || originPath.length === 0) return null;
59753
+ entries.push({
59754
+ statusCode,
59755
+ path: recordPath,
59756
+ originPath
59757
+ });
59758
+ continue;
59759
+ }
59760
+ entries.push({
59761
+ statusCode,
59762
+ path: recordPath
59763
+ });
59764
+ }
59765
+ return entries;
59766
+ };
59767
+ const buildDirtyPathContentFingerprint = (absolutePath, statusCode) => {
59768
+ let stats;
59769
+ try {
59770
+ stats = fs.lstatSync(absolutePath);
59771
+ } catch {
59772
+ return statusCode.includes("D") ? "deleted" : null;
59773
+ }
59774
+ if (stats.isSymbolicLink()) try {
59775
+ return `link:${fs.readlinkSync(absolutePath)}`;
59776
+ } catch {
59777
+ return null;
59778
+ }
59779
+ if (!stats.isFile()) return null;
59780
+ if (stats.size > 10485760) return `stat:${stats.mtimeMs}:${stats.size}`;
59781
+ return hashFileContents(absolutePath);
59782
+ };
59783
+ const buildWorktreeFingerprint = (projectDirectory) => {
59784
+ const statusOutput = runGit(projectDirectory, [
59785
+ "status",
59786
+ "--porcelain=v1",
59787
+ "-z",
59788
+ "--untracked-files=all"
59789
+ ]);
59790
+ if (statusOutput === null) return null;
59791
+ if (statusOutput.length === 0) return [];
59792
+ const statusEntries = parseWorktreeStatusRecords(statusOutput);
59793
+ if (statusEntries === null) return null;
59794
+ if (statusEntries.length > 300) return null;
59795
+ const repositoryRoot = runGit(projectDirectory, ["rev-parse", "--show-toplevel"]);
59796
+ if (repositoryRoot === null) return null;
59797
+ const dirtyEntries = [];
59798
+ for (const statusEntry of statusEntries) {
59799
+ const contentFingerprint = buildDirtyPathContentFingerprint(path.join(repositoryRoot, statusEntry.path), statusEntry.statusCode);
59800
+ if (contentFingerprint === null) return null;
59801
+ dirtyEntries.push({
59802
+ ...statusEntry,
59803
+ contentFingerprint
59804
+ });
59805
+ }
59806
+ return dirtyEntries.sort((firstEntry, secondEntry) => {
59807
+ const firstSortKey = `${firstEntry.path}${firstEntry.statusCode}`;
59808
+ const secondSortKey = `${secondEntry.path}${secondEntry.statusCode}`;
59809
+ return firstSortKey < secondSortKey ? -1 : firstSortKey > secondSortKey ? 1 : 0;
59810
+ });
59811
+ };
59812
+ const DOTENV_FILE_NAME_PATTERN = /^\.env(\.|$)/;
59813
+ const resolveDotenvFingerprint = (projectDirectory) => {
59814
+ try {
59815
+ return fs.readdirSync(projectDirectory).filter((entryName) => DOTENV_FILE_NAME_PATTERN.test(entryName)).sort().map((entryName) => `${entryName}=${fileFingerprint(path.join(projectDirectory, entryName)) ?? "unreadable"}`);
59816
+ } catch {
59817
+ return [];
59818
+ }
59819
+ };
59820
+ const hasHiddenTrackedFileState = (projectDirectory) => {
59821
+ const output = runGit(projectDirectory, ["ls-files", "-v"]);
59822
+ if (output === null) return true;
59823
+ return output.split("\n").some((line) => line.length > 0 && line[0] !== "H");
59824
+ };
59825
+ const resolveCacheFilePath = (projectDirectory) => {
59826
+ const nodeModulesDirectory = path.join(projectDirectory, "node_modules");
59827
+ if (fs.existsSync(nodeModulesDirectory)) return path.join(nodeModulesDirectory, ".cache", "react-doctor", "scan-cache.json");
59828
+ const projectHash = hashString(projectDirectory).slice(0, 16);
59829
+ return path.join(os.tmpdir(), "react-doctor-cache", `scan-${projectHash}.json`);
59830
+ };
59831
+ const readPersistedCache = (cacheFilePath) => {
59832
+ try {
59833
+ const parsed = JSON.parse(fs.readFileSync(cacheFilePath, "utf8"));
59834
+ if (!isRecord(parsed) || parsed.version !== 3) return {
59835
+ version: 3,
59836
+ entries: []
59837
+ };
59838
+ if (!Array.isArray(parsed.entries)) return {
59839
+ version: 3,
59840
+ entries: []
59841
+ };
59842
+ const entries = [];
59843
+ for (const entry of parsed.entries) {
59844
+ if (!isRecord(entry) || typeof entry.key !== "string" || typeof entry.createdAtMs !== "number") continue;
59845
+ if (!isRecord(entry.payload) || !Array.isArray(entry.payload.diagnostics)) continue;
59846
+ entries.push(entry);
59847
+ }
59848
+ return {
59849
+ version: 3,
59850
+ entries
59851
+ };
59852
+ } catch {
59853
+ return {
59854
+ version: 3,
59855
+ entries: []
59856
+ };
59857
+ }
59858
+ };
59859
+ const writePersistedCache = (cacheFilePath, cache) => {
59860
+ try {
59861
+ fs.mkdirSync(path.dirname(cacheFilePath), { recursive: true });
59862
+ const tempPath = `${cacheFilePath}.${process.pid}.tmp`;
59863
+ fs.writeFileSync(tempPath, JSON.stringify(cache));
59864
+ fs.renameSync(tempPath, cacheFilePath);
59865
+ } catch {
59866
+ return;
59867
+ }
59868
+ };
59869
+ const isScanResultCacheDisabled = () => CACHE_DISABLED_VALUES.has(process.env.REACT_DOCTOR_NO_CACHE?.toLowerCase() ?? "");
59870
+ const resolveProjectIdentity = (projectDirectory) => {
59871
+ try {
59872
+ return fs.realpathSync.native(projectDirectory);
59873
+ } catch {
59874
+ return path.resolve(projectDirectory);
59875
+ }
59876
+ };
59877
+ const fileFingerprint = (filePath) => {
59878
+ try {
59879
+ const stat = fs.statSync(filePath);
59880
+ return `${filePath}:${stat.mtimeMs}:${stat.size}`;
59881
+ } catch {
59882
+ return null;
59883
+ }
59884
+ };
59885
+ const resolveToolchainFingerprint = (nodeBinaryPath) => {
59886
+ const fingerprints = [];
59887
+ if (nodeBinaryPath !== null) {
59888
+ const fingerprint = fileFingerprint(nodeBinaryPath);
59889
+ if (fingerprint !== null) fingerprints.push(`node=${fingerprint}`);
59890
+ }
59891
+ for (const specifier of TOOLCHAIN_PACKAGE_SPECIFIERS) try {
59892
+ const fingerprint = fileFingerprint(bundledRequire.resolve(specifier));
59893
+ if (fingerprint !== null) fingerprints.push(`${specifier}=${fingerprint}`);
59894
+ } catch {
59895
+ continue;
59896
+ }
59897
+ return fingerprints;
59898
+ };
59899
+ const buildScanResultCacheKey = (input) => {
59900
+ if (isScanResultCacheDisabled()) return null;
59901
+ if (hasHiddenTrackedFileState(input.projectDirectory)) return null;
59902
+ const worktreeFingerprint = buildWorktreeFingerprint(input.projectDirectory);
59903
+ if (worktreeFingerprint === null) return null;
59904
+ const headSha = readHeadSha(input.projectDirectory);
59905
+ if (headSha === null) return null;
59906
+ if (stringifyStableJson(input.userConfig) === null) return null;
59907
+ const cacheKeyJson = stringifyStableJson({
59908
+ schemaVersion: 3,
59909
+ projectIdentity: resolveProjectIdentity(input.projectDirectory),
59910
+ headSha,
59911
+ worktreeFingerprint,
59912
+ dotenvFingerprint: resolveDotenvFingerprint(input.projectDirectory),
59913
+ reactDoctorVersion: input.version,
59914
+ nodeVersion: process.version,
59915
+ toolchainFingerprint: resolveToolchainFingerprint(input.nodeBinaryPath),
59916
+ configFingerprint: computeConfigFingerprint(input.projectDirectory, input.version),
59917
+ hasConfigOverride: input.hasConfigOverride,
59918
+ configSourceDirectory: input.configSourceDirectory,
59919
+ userConfig: input.userConfig,
59920
+ engineOptions: {
59921
+ lint: input.options.lint,
59922
+ deadCode: input.options.deadCode,
59923
+ includePaths: [...input.options.includePaths].sort(),
59924
+ customRulesOnly: input.options.customRulesOnly,
59925
+ respectInlineDisables: input.options.respectInlineDisables,
59926
+ warnings: input.options.warnings,
59927
+ adoptExistingLintConfig: input.options.adoptExistingLintConfig,
59928
+ ignoredTags: [...input.options.ignoredTags].sort(),
59929
+ concurrency: input.options.concurrency,
59930
+ lintBatchOrdering: resolveLintBatchOrdering(),
59931
+ baselineRef: input.options.baseline?.ref,
59932
+ changedLineRanges: input.options.changedLineRanges ?? void 0,
59933
+ noScore: input.options.noScore,
59934
+ isCi: input.options.isCi,
59935
+ suppressRendering: input.options.suppressRendering,
59936
+ supplyChainManifestChanged: input.options.supplyChainManifestChanged
59937
+ }
59938
+ });
59939
+ return cacheKeyJson === null ? null : hashString(cacheKeyJson);
59940
+ };
59941
+ const createScanResultCache = (projectDirectory) => {
59942
+ const cacheFilePath = resolveCacheFilePath(projectDirectory);
59943
+ const persistedCache = readPersistedCache(cacheFilePath);
59944
+ const entries = /* @__PURE__ */ new Map();
59945
+ for (const entry of persistedCache.entries) entries.set(entry.key, entry);
59946
+ const persist = () => {
59947
+ writePersistedCache(cacheFilePath, {
59948
+ version: 3,
59949
+ entries: [...entries.values()].sort((firstEntry, secondEntry) => secondEntry.createdAtMs - firstEntry.createdAtMs).slice(0, 20)
59950
+ });
59951
+ };
59952
+ return {
59953
+ lookup: (key) => entries.get(key)?.payload ?? null,
59954
+ store: (key, payload) => {
59955
+ entries.set(key, {
59956
+ key,
59957
+ createdAtMs: Date.now(),
59958
+ payload
59959
+ });
59960
+ persist();
59961
+ }
59962
+ };
59963
+ };
59964
+ const shouldStoreScanPayload = (payload) => !payload.didLintFail && !payload.didDeadCodeFail && payload.lintPartialFailures.length === 0 && !payload.supplyChainOverlapTimedOut && payload.securityScanFailed !== true;
59965
+ //#endregion
60111
59966
  //#region src/inspect.ts
60112
59967
  const silentConsole = makeNoopConsole();
60113
59968
  const runConsole = (effect) => {
@@ -60284,7 +60139,7 @@ const runBaselineComparison = async (params) => {
60284
60139
  suppressScanSummary: true,
60285
60140
  supplyChainManifestChanged: params.options.supplyChainManifestChanged,
60286
60141
  deadlineEpochMs: params.deadlineEpochMs ?? void 0
60287
- }, {}).pipe(provide(baseLayers), provideService$2(PerFileLintCacheEnabled, false), provideService$2(SidecarLintCacheEnabled, false), provideService$2(Console, silentConsole))));
60142
+ }, {}).pipe(provide(baseLayers), provideService$2(Console, silentConsole))));
60288
60143
  if (baseOutput.didLintFail || countIncompleteLintFiles(baseOutput.lintPartialFailures) > 0) return null;
60289
60144
  const delta = computeDiagnosticDelta({
60290
60145
  headDiagnostics: params.headDiagnostics,
@@ -60337,8 +60192,7 @@ const runInspectWithRuntime = async (directory, options, userConfig, hasConfigOv
60337
60192
  startTime,
60338
60193
  rootSentrySpan,
60339
60194
  scanMode: cachedPayload.baselineDelta ? "baseline" : isDiffMode ? "diff" : "full",
60340
- baselineDegraded,
60341
- wholeRepoCacheHit: true
60195
+ baselineDegraded
60342
60196
  });
60343
60197
  recordOnboardingCompletion(options);
60344
60198
  return result;
@@ -60447,7 +60301,6 @@ const runInspectWithRuntime = async (directory, options, userConfig, hasConfigOv
60447
60301
  rootSentrySpan,
60448
60302
  scanMode: baselineDelta ? "baseline" : isDiffMode ? "diff" : "full",
60449
60303
  baselineDegraded,
60450
- wholeRepoCacheHit: false,
60451
60304
  lintCacheHitFileCount: output.lintCacheHitFileCount,
60452
60305
  lintCacheTotalFileCount: output.lintCacheTotalFileCount,
60453
60306
  lintSidecarReplayedFileCount: output.lintSidecarReplayedFileCount,
@@ -60518,7 +60371,6 @@ const renderAndRecordScan = async (input) => {
60518
60371
  result,
60519
60372
  mode: input.scanMode,
60520
60373
  gateExempt: input.baselineDegraded,
60521
- wholeRepoCacheHit: input.wholeRepoCacheHit,
60522
60374
  didLintFail: input.payload.didLintFail,
60523
60375
  lintFailureReasonKind: input.payload.lintFailureReasonKind,
60524
60376
  lintPartialFailureCount: input.payload.lintPartialFailures.length,
@@ -64412,4 +64264,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
64412
64264
  export {};
64413
64265
 
64414
64266
  //# sourceMappingURL=cli.js.map
64415
- //# debugId=1722becf-6e91-56ae-afc9-1e1410647bc3
64267
+ //# debugId=d9c72b21-3e94-5923-b692-077f137a0078