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

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 +133 -55
  2. package/dist/index.js +117 -30
  3. package/dist/lsp.js +120 -35
  4. package/package.json +3 -3
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]="1722becf-6e91-56ae-afc9-1e1410647bc3")}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,6 +36137,7 @@ 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;
36140
36141
  const SUPPLY_CHAIN_CACHE_SUBDIR = "supply-chain";
36141
36142
  const SUPPLY_CHAIN_IGNORED_PACKAGES = new Set(["next"]);
36142
36143
  const hasIgnoredPathSegment = (relativePath) => relativePath.split(/[/\\]/).slice(0, -1).some((segment) => IGNORED_DIRECTORIES.has(segment));
@@ -40608,22 +40609,37 @@ const failOpenReadJson = (filePath, fallback) => {
40608
40609
  return fallback;
40609
40610
  }
40610
40611
  };
40612
+ const hashFileContents = (filePath) => {
40613
+ try {
40614
+ return crypto.createHash("sha1").update(NFS.readFileSync(filePath)).digest("hex");
40615
+ } catch {
40616
+ return null;
40617
+ }
40618
+ };
40611
40619
  const ANALYZED_FILE_EXTENSIONS = new Set(DEFAULT_EXTENSIONS);
40612
40620
  const CORE_SIDE_MANIFEST_NAMES = ["knip.json", "deno.lock"];
40613
40621
  const ANALYZED_MANIFEST_NAMES = new Set([...ANALYZED_MANIFEST_FILENAMES, ...CORE_SIDE_MANIFEST_NAMES]);
40614
40622
  const isTsConfigLikeFile = (fileName) => (fileName.startsWith("tsconfig") || fileName.startsWith("jsconfig")) && fileName.endsWith(".json");
40615
40623
  const isFingerprintedFile = (fileName) => ANALYZED_FILE_EXTENSIONS.has(Path.extname(fileName).toLowerCase()) || ANALYZED_MANIFEST_NAMES.has(fileName) || isTsConfigLikeFile(fileName);
40616
- const collectAnalyzedFileFingerprints = (rootDirectory) => {
40617
- const fingerprints = [];
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();
40618
40631
  for (const { absolutePath, name } of walkSourceTreeFiles(rootDirectory)) {
40619
40632
  if (!isFingerprintedFile(name)) continue;
40620
40633
  try {
40621
40634
  const fileStat = NFS.statSync(absolutePath);
40622
40635
  const relativePath = Path.relative(rootDirectory, absolutePath).replace(/\\/g, "/");
40623
- fingerprints.push(`${relativePath}:${fileStat.mtimeMs}:${fileStat.size}`);
40636
+ statByRelativePath.set(relativePath, {
40637
+ mtimeMs: fileStat.mtimeMs,
40638
+ size: fileStat.size
40639
+ });
40624
40640
  } catch {}
40625
40641
  }
40626
- return fingerprints.sort();
40642
+ return statByRelativePath;
40627
40643
  };
40628
40644
  const bundledRequire$2 = createRequire(import.meta.url);
40629
40645
  const resolveDeslopVersion = () => {
@@ -40635,14 +40651,13 @@ const resolveDeslopVersion = () => {
40635
40651
  }
40636
40652
  };
40637
40653
  const computeDeadCodeCacheKey = (input) => crypto.createHash("sha1").update(JSON.stringify({
40638
- schemaVersion: 1,
40654
+ schemaVersion: 2,
40639
40655
  coreVersion: input.coreVersion,
40640
40656
  deslopVersion: resolveDeslopVersion(),
40641
40657
  deslopJsModuleSpecifier: input.deslopJsModuleSpecifier,
40642
40658
  entryPatterns: input.entryPatterns,
40643
40659
  ignorePatterns: input.ignorePatterns,
40644
- tsConfigFile: input.tsConfigPath === void 0 ? null : Path.relative(input.rootDirectory, input.tsConfigPath).replace(/\\/g, "/"),
40645
- files: collectAnalyzedFileFingerprints(input.rootDirectory)
40660
+ tsConfigFile: input.tsConfigPath === void 0 ? null : Path.relative(input.rootDirectory, input.tsConfigPath).replace(/\\/g, "/")
40646
40661
  })).digest("hex");
40647
40662
  const validateDiagnostic$2 = decodeUnknownSync(Diagnostic);
40648
40663
  const decodeCachedDiagnostics = (raw) => {
@@ -40653,16 +40668,68 @@ const decodeCachedDiagnostics = (raw) => {
40653
40668
  return null;
40654
40669
  }
40655
40670
  };
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);
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;
40660
40708
  };
40661
- const storeDeadCodeResultCache = (cacheDirectory, cacheKey, diagnostics) => {
40662
- atomicWriteJson(Path.join(cacheDirectory, DEAD_CODE_CACHE_FILENAME), {
40663
- version: 1,
40664
- key: cacheKey,
40665
- diagnostics
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
40666
40733
  });
40667
40734
  };
40668
40735
  const readSystemFacts = () => ({
@@ -41072,16 +41139,22 @@ const checkDeadCode = async (options) => {
41072
41139
  const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory);
41073
41140
  const tsConfigPath = resolveTsConfigPath(rootDirectory);
41074
41141
  const deslopJsModuleSpecifier = options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js");
41075
- const cacheKey = options.cacheEnabled ? computeDeadCodeCacheKey({
41142
+ const fileStatsSnapshot = options.cacheEnabled ? collectAnalyzedFileStats(rootDirectory) : null;
41143
+ const cacheKey = fileStatsSnapshot === null ? null : computeDeadCodeCacheKey({
41076
41144
  rootDirectory,
41077
41145
  entryPatterns,
41078
41146
  ignorePatterns,
41079
41147
  tsConfigPath,
41080
41148
  deslopJsModuleSpecifier,
41081
41149
  coreVersion: CORE_PACKAGE_VERSION
41082
- }) : null;
41083
- if (cacheKey !== null) {
41084
- const cachedDiagnostics = lookupDeadCodeResultCache(resolveReactDoctorCacheDir(rootDirectory), cacheKey);
41150
+ });
41151
+ if (cacheKey !== null && fileStatsSnapshot !== null) {
41152
+ const cachedDiagnostics = lookupDeadCodeResultCache({
41153
+ cacheDirectory: resolveReactDoctorCacheDir(rootDirectory),
41154
+ cacheKey,
41155
+ rootDirectory,
41156
+ currentFileStats: fileStatsSnapshot
41157
+ });
41085
41158
  options.onCacheOutcome?.(cachedDiagnostics !== null);
41086
41159
  if (cachedDiagnostics !== null) return [...cachedDiagnostics];
41087
41160
  }
@@ -41155,7 +41228,13 @@ const checkDeadCode = async (options) => {
41155
41228
  category: DEAD_CODE_CATEGORY
41156
41229
  });
41157
41230
  }
41158
- if (cacheKey !== null) storeDeadCodeResultCache(resolveReactDoctorCacheDir(rootDirectory), cacheKey, diagnostics);
41231
+ if (cacheKey !== null && fileStatsSnapshot !== null) storeDeadCodeResultCache({
41232
+ cacheDirectory: resolveReactDoctorCacheDir(rootDirectory),
41233
+ cacheKey,
41234
+ rootDirectory,
41235
+ snapshotFileStats: fileStatsSnapshot,
41236
+ diagnostics
41237
+ });
41159
41238
  return diagnostics;
41160
41239
  };
41161
41240
  const DEAD_CODE_RULE_KEY_PREFIX = `${DEAD_CODE_PLUGIN}/`;
@@ -42251,13 +42330,6 @@ const createFileLintCache = (cacheDirectory, rulesetHash) => {
42251
42330
  }
42252
42331
  };
42253
42332
  };
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
42333
  const ABSENT_CONTENT_ANSWER = "absent";
42262
42334
  /**
42263
42335
  * Answers dependency probes for the sidecar lint cache — used both when
@@ -44398,6 +44470,20 @@ const writeCachedSocketBody = (cacheFile, body) => {
44398
44470
  }));
44399
44471
  } catch {}
44400
44472
  };
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
+ };
44401
44487
  const fetchSocketArtifact = (dependency, cacheDirectory) => tryPromise(async (signal) => {
44402
44488
  const cacheFile = cacheDirectory === null ? null : supplyChainCacheFile(cacheDirectory, dependency);
44403
44489
  if (cacheFile !== null) {
@@ -44525,6 +44611,7 @@ const checkSupplyChain = (input) => gen(function* () {
44525
44611
  const dependencies = collectDependenciesToScore(readPackageJson$1(packageJsonPath), readPackageJsonText(packageJsonPath), options.includeDevDependencies);
44526
44612
  if (dependencies.length === 0) return [];
44527
44613
  const cacheDirectory = isSupplyChainCacheDisabled() ? null : resolveReactDoctorCacheDir(input.rootDirectory);
44614
+ if (cacheDirectory !== null) pruneExpiredSocketCache(cacheDirectory);
44528
44615
  const artifacts = yield* forEach$1(dependencies, (dependency) => fetchSocketArtifact(dependency, cacheDirectory), { concurrency: 8 }).pipe(timeoutOption(input.totalTimeoutMs ?? 9e4), map$3((maybeArtifacts) => getOrElse$1(maybeArtifacts, () => [])));
44529
44616
  const diagnostics = [];
44530
44617
  for (let index = 0; index < dependencies.length; index += 1) {
@@ -45410,11 +45497,9 @@ const computeConfigFingerprint = (projectDirectory, version) => {
45410
45497
  const parts = [`v=${version}`];
45411
45498
  let directory = projectDirectory;
45412
45499
  for (;;) {
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;
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}`);
45418
45503
  }
45419
45504
  const parent = path.dirname(directory);
45420
45505
  if (parent === directory) break;
@@ -45710,6 +45795,7 @@ const isNonInteractiveEnvironment = () => NON_INTERACTIVE_ENVIRONMENT_VARIABLES.
45710
45795
  const REACT_DOCTOR_CONFIG_PROJECT_NAME = "react-doctor";
45711
45796
  const STAGED_FILES_TEMP_DIR_PREFIX = "react-doctor-staged-";
45712
45797
  const BASELINE_FILES_TEMP_DIR_PREFIX = "react-doctor-baseline-";
45798
+ const SCAN_RESULT_CACHE_FILENAME = "scan-cache.json";
45713
45799
  const RUN_GIT_MAX_BUFFER_BYTES = 64 * 1024 * 1024;
45714
45800
  const GH_DEFAULT_BRANCH_PROBE_TIMEOUT_MS = 5e3;
45715
45801
  const CI_TRUST_COMPANIES = "PayPal, Rippling, and Alibaba";
@@ -45801,7 +45887,7 @@ const makeNoopConsole = () => ({
45801
45887
  });
45802
45888
  //#endregion
45803
45889
  //#region src/cli/utils/version.ts
45804
- const VERSION = "0.6.3-dev.bdf8074";
45890
+ const VERSION = "0.6.3-dev.ced746f";
45805
45891
  //#endregion
45806
45892
  //#region src/cli/utils/json-mode.ts
45807
45893
  let context = null;
@@ -46170,13 +46256,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
46170
46256
  * uploads source-map artifacts under, so stack frames symbolicate. Honors the
46171
46257
  * standard `SENTRY_RELEASE` override.
46172
46258
  */
46173
- const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.6.3-dev.bdf8074`;
46259
+ const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.6.3-dev.ced746f`;
46174
46260
  /**
46175
46261
  * Deployment environment shown in Sentry's environment filter. Defaults to
46176
46262
  * `production` for tagged releases and `development` for dev/unbuilt versions,
46177
46263
  * overridable via the standard `SENTRY_ENVIRONMENT` env var.
46178
46264
  */
46179
- const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.6.3-dev.bdf8074") ? "development" : "production");
46265
+ const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.6.3-dev.ced746f") ? "development" : "production");
46180
46266
  /**
46181
46267
  * Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
46182
46268
  * (set to `0` to disable tracing) and falls back to
@@ -58257,9 +58343,8 @@ const findMigrationScaleBuckets = (diagnostics) => buildRuleBlastRadii(diagnosti
58257
58343
  //#region src/cli/utils/scan-result-cache.ts
58258
58344
  const CACHE_DISABLED_VALUES = new Set(["1", "true"]);
58259
58345
  const TOOLCHAIN_PACKAGE_SPECIFIERS = [
58260
- "oxlint",
58261
58346
  "oxlint/package.json",
58262
- "oxlint-plugin-react-doctor",
58347
+ "oxlint-plugin-react-doctor/package.json",
58263
58348
  "deslop-js/package.json",
58264
58349
  "eslint-plugin-react-hooks/package.json"
58265
58350
  ];
@@ -58372,7 +58457,7 @@ const buildWorktreeFingerprint = (projectDirectory) => {
58372
58457
  const DOTENV_FILE_NAME_PATTERN = /^\.env(\.|$)/;
58373
58458
  const resolveDotenvFingerprint = (projectDirectory) => {
58374
58459
  try {
58375
- return fs.readdirSync(projectDirectory).filter((entryName) => DOTENV_FILE_NAME_PATTERN.test(entryName)).sort().map((entryName) => `${entryName}=${fileFingerprint(path.join(projectDirectory, entryName)) ?? "unreadable"}`);
58460
+ return fs.readdirSync(projectDirectory).filter((entryName) => DOTENV_FILE_NAME_PATTERN.test(entryName)).sort().map((entryName) => `${entryName}=${hashFileContents(path.join(projectDirectory, entryName)) ?? "unreadable"}`);
58376
58461
  } catch {
58377
58462
  return [];
58378
58463
  }
@@ -58382,12 +58467,7 @@ const hasHiddenTrackedFileState = (projectDirectory) => {
58382
58467
  if (output === null) return true;
58383
58468
  return output.split("\n").some((line) => line.length > 0 && line[0] !== "H");
58384
58469
  };
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
- };
58470
+ const resolveCacheFilePath = (projectDirectory) => path.join(resolveReactDoctorCacheDir(projectDirectory), SCAN_RESULT_CACHE_FILENAME);
58391
58471
  const readPersistedCache = (cacheFilePath) => {
58392
58472
  try {
58393
58473
  const parsed = JSON.parse(fs.readFileSync(cacheFilePath, "utf8"));
@@ -58453,15 +58533,13 @@ const fileFingerprint = (filePath) => {
58453
58533
  };
58454
58534
  const resolveToolchainFingerprint = (nodeBinaryPath) => {
58455
58535
  const fingerprints = [];
58456
- if (nodeBinaryPath !== null) {
58457
- const fingerprint = fileFingerprint(nodeBinaryPath);
58458
- if (fingerprint !== null) fingerprints.push(`node=${fingerprint}`);
58459
- }
58536
+ if (nodeBinaryPath !== null) fingerprints.push(nodeBinaryPath === process.execPath ? `node=${process.version}` : `node=${fileFingerprint(nodeBinaryPath) ?? "unreadable"}`);
58460
58537
  for (const specifier of TOOLCHAIN_PACKAGE_SPECIFIERS) try {
58461
- const fingerprint = fileFingerprint(bundledRequire.resolve(specifier));
58462
- if (fingerprint !== null) fingerprints.push(`${specifier}=${fingerprint}`);
58538
+ const packageJson = bundledRequire(specifier);
58539
+ const version = typeof packageJson.version === "string" ? packageJson.version : "unknown";
58540
+ fingerprints.push(`${specifier}=${version}`);
58463
58541
  } catch {
58464
- continue;
58542
+ fingerprints.push(`${specifier}=missing`);
58465
58543
  }
58466
58544
  return fingerprints;
58467
58545
  };
@@ -60206,7 +60284,7 @@ const runBaselineComparison = async (params) => {
60206
60284
  suppressScanSummary: true,
60207
60285
  supplyChainManifestChanged: params.options.supplyChainManifestChanged,
60208
60286
  deadlineEpochMs: params.deadlineEpochMs ?? void 0
60209
- }, {}).pipe(provide(baseLayers), provideService$2(Console, silentConsole))));
60287
+ }, {}).pipe(provide(baseLayers), provideService$2(PerFileLintCacheEnabled, false), provideService$2(SidecarLintCacheEnabled, false), provideService$2(Console, silentConsole))));
60210
60288
  if (baseOutput.didLintFail || countIncompleteLintFiles(baseOutput.lintPartialFailures) > 0) return null;
60211
60289
  const delta = computeDiagnosticDelta({
60212
60290
  headDiagnostics: params.headDiagnostics,
@@ -64334,4 +64412,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
64334
64412
  export {};
64335
64413
 
64336
64414
  //# sourceMappingURL=cli.js.map
64337
- //# debugId=4d2b3e2c-ffb8-5da4-bf5d-833e608822bb
64415
+ //# debugId=1722becf-6e91-56ae-afc9-1e1410647bc3
package/dist/index.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]="b55ef8b2-8e9d-512b-81de-a01f6ce3e60e")}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]="b81c51d5-0ead-51a4-a9f9-ae499fd19a52")}catch(e){}}();
3
3
  import { r as __toESM$1, t as __commonJSMin$1 } from "./chunk-N93fKeF6.js";
4
4
  import { createRequire } from "node:module";
5
5
  import * as NFS from "node:fs";
@@ -32936,6 +32936,7 @@ const SUPPLY_CHAIN_PLUGIN = "socket";
32936
32936
  const SUPPLY_CHAIN_RULE = "low-supply-chain-score";
32937
32937
  const SUPPLY_CHAIN_CATEGORY = "Security";
32938
32938
  const SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS = 9e4;
32939
+ const SUPPLY_CHAIN_CACHE_TTL_MS = 864e5;
32939
32940
  const SUPPLY_CHAIN_CACHE_SUBDIR = "supply-chain";
32940
32941
  const SUPPLY_CHAIN_IGNORED_PACKAGES = new Set(["next"]);
32941
32942
  const hasIgnoredPathSegment = (relativePath) => relativePath.split(/[/\\]/).slice(0, -1).some((segment) => IGNORED_DIRECTORIES.has(segment));
@@ -37365,22 +37366,37 @@ const failOpenReadJson = (filePath, fallback) => {
37365
37366
  return fallback;
37366
37367
  }
37367
37368
  };
37369
+ const hashFileContents = (filePath) => {
37370
+ try {
37371
+ return crypto.createHash("sha1").update(NFS.readFileSync(filePath)).digest("hex");
37372
+ } catch {
37373
+ return null;
37374
+ }
37375
+ };
37368
37376
  const ANALYZED_FILE_EXTENSIONS = new Set(DEFAULT_EXTENSIONS);
37369
37377
  const CORE_SIDE_MANIFEST_NAMES = ["knip.json", "deno.lock"];
37370
37378
  const ANALYZED_MANIFEST_NAMES = new Set([...ANALYZED_MANIFEST_FILENAMES, ...CORE_SIDE_MANIFEST_NAMES]);
37371
37379
  const isTsConfigLikeFile = (fileName) => (fileName.startsWith("tsconfig") || fileName.startsWith("jsconfig")) && fileName.endsWith(".json");
37372
37380
  const isFingerprintedFile = (fileName) => ANALYZED_FILE_EXTENSIONS.has(Path.extname(fileName).toLowerCase()) || ANALYZED_MANIFEST_NAMES.has(fileName) || isTsConfigLikeFile(fileName);
37373
- const collectAnalyzedFileFingerprints = (rootDirectory) => {
37374
- const fingerprints = [];
37381
+ /**
37382
+ * Stat snapshot of every file the analysis reads, keyed by root-relative
37383
+ * `/`-separated path. Taken BEFORE the (long) analysis so a stored result is
37384
+ * verified against the tree it started from.
37385
+ */
37386
+ const collectAnalyzedFileStats = (rootDirectory) => {
37387
+ const statByRelativePath = /* @__PURE__ */ new Map();
37375
37388
  for (const { absolutePath, name } of walkSourceTreeFiles(rootDirectory)) {
37376
37389
  if (!isFingerprintedFile(name)) continue;
37377
37390
  try {
37378
37391
  const fileStat = NFS.statSync(absolutePath);
37379
37392
  const relativePath = Path.relative(rootDirectory, absolutePath).replace(/\\/g, "/");
37380
- fingerprints.push(`${relativePath}:${fileStat.mtimeMs}:${fileStat.size}`);
37393
+ statByRelativePath.set(relativePath, {
37394
+ mtimeMs: fileStat.mtimeMs,
37395
+ size: fileStat.size
37396
+ });
37381
37397
  } catch {}
37382
37398
  }
37383
- return fingerprints.sort();
37399
+ return statByRelativePath;
37384
37400
  };
37385
37401
  const bundledRequire$2 = createRequire(import.meta.url);
37386
37402
  const resolveDeslopVersion = () => {
@@ -37392,14 +37408,13 @@ const resolveDeslopVersion = () => {
37392
37408
  }
37393
37409
  };
37394
37410
  const computeDeadCodeCacheKey = (input) => crypto.createHash("sha1").update(JSON.stringify({
37395
- schemaVersion: 1,
37411
+ schemaVersion: 2,
37396
37412
  coreVersion: input.coreVersion,
37397
37413
  deslopVersion: resolveDeslopVersion(),
37398
37414
  deslopJsModuleSpecifier: input.deslopJsModuleSpecifier,
37399
37415
  entryPatterns: input.entryPatterns,
37400
37416
  ignorePatterns: input.ignorePatterns,
37401
- tsConfigFile: input.tsConfigPath === void 0 ? null : Path.relative(input.rootDirectory, input.tsConfigPath).replace(/\\/g, "/"),
37402
- files: collectAnalyzedFileFingerprints(input.rootDirectory)
37417
+ tsConfigFile: input.tsConfigPath === void 0 ? null : Path.relative(input.rootDirectory, input.tsConfigPath).replace(/\\/g, "/")
37403
37418
  })).digest("hex");
37404
37419
  const validateDiagnostic$2 = decodeUnknownSync(Diagnostic);
37405
37420
  const decodeCachedDiagnostics = (raw) => {
@@ -37410,16 +37425,68 @@ const decodeCachedDiagnostics = (raw) => {
37410
37425
  return null;
37411
37426
  }
37412
37427
  };
37413
- const lookupDeadCodeResultCache = (cacheDirectory, cacheKey) => {
37414
- const persisted = failOpenReadJson(Path.join(cacheDirectory, DEAD_CODE_CACHE_FILENAME), null);
37415
- if (persisted === null || !isRecord(persisted) || persisted.version !== 1 || persisted.key !== cacheKey || !Array.isArray(persisted.diagnostics)) return null;
37416
- return decodeCachedDiagnostics(persisted.diagnostics);
37428
+ const isPersistedFileIdentity = (value) => Array.isArray(value) && value.length === 3 && typeof value[0] === "number" && typeof value[1] === "number" && typeof value[2] === "string";
37429
+ const lookupDeadCodeResultCache = (input) => {
37430
+ const cacheFilePath = Path.join(input.cacheDirectory, DEAD_CODE_CACHE_FILENAME);
37431
+ const persisted = failOpenReadJson(cacheFilePath, null);
37432
+ if (persisted === null || !isRecord(persisted) || persisted.version !== 2 || persisted.key !== input.cacheKey || !isRecord(persisted.files) || !Array.isArray(persisted.diagnostics)) return null;
37433
+ const storedFileEntries = Object.entries(persisted.files);
37434
+ if (storedFileEntries.length !== input.currentFileStats.size) return null;
37435
+ const repairedFiles = {};
37436
+ let repairedCount = 0;
37437
+ for (const [relativePath, storedIdentity] of storedFileEntries) {
37438
+ if (!isPersistedFileIdentity(storedIdentity)) return null;
37439
+ const currentStat = input.currentFileStats.get(relativePath);
37440
+ if (currentStat === void 0) return null;
37441
+ const [storedMtimeMs, storedSize, storedContentHash] = storedIdentity;
37442
+ if (currentStat.mtimeMs === storedMtimeMs && currentStat.size === storedSize) {
37443
+ repairedFiles[relativePath] = storedIdentity;
37444
+ continue;
37445
+ }
37446
+ if (currentStat.size !== storedSize) return null;
37447
+ const currentContentHash = hashFileContents(Path.join(input.rootDirectory, relativePath));
37448
+ if (currentContentHash === null || currentContentHash !== storedContentHash) return null;
37449
+ repairedFiles[relativePath] = [
37450
+ currentStat.mtimeMs,
37451
+ currentStat.size,
37452
+ storedContentHash
37453
+ ];
37454
+ repairedCount += 1;
37455
+ }
37456
+ const diagnostics = decodeCachedDiagnostics(persisted.diagnostics);
37457
+ if (diagnostics === null) return null;
37458
+ if (repairedCount > 0) atomicWriteJson(cacheFilePath, {
37459
+ version: 2,
37460
+ key: input.cacheKey,
37461
+ files: repairedFiles,
37462
+ diagnostics: persisted.diagnostics
37463
+ });
37464
+ return diagnostics;
37417
37465
  };
37418
- const storeDeadCodeResultCache = (cacheDirectory, cacheKey, diagnostics) => {
37419
- atomicWriteJson(Path.join(cacheDirectory, DEAD_CODE_CACHE_FILENAME), {
37420
- version: 1,
37421
- key: cacheKey,
37422
- diagnostics
37466
+ const storeDeadCodeResultCache = (input) => {
37467
+ const persistedFiles = {};
37468
+ for (const [relativePath, snapshotStat] of input.snapshotFileStats) {
37469
+ const absolutePath = Path.join(input.rootDirectory, relativePath);
37470
+ const contentHash = hashFileContents(absolutePath);
37471
+ if (contentHash === null) return;
37472
+ let currentStat;
37473
+ try {
37474
+ currentStat = NFS.statSync(absolutePath);
37475
+ } catch {
37476
+ return;
37477
+ }
37478
+ if (currentStat.mtimeMs !== snapshotStat.mtimeMs || currentStat.size !== snapshotStat.size) return;
37479
+ persistedFiles[relativePath] = [
37480
+ snapshotStat.mtimeMs,
37481
+ snapshotStat.size,
37482
+ contentHash
37483
+ ];
37484
+ }
37485
+ atomicWriteJson(Path.join(input.cacheDirectory, DEAD_CODE_CACHE_FILENAME), {
37486
+ version: 2,
37487
+ key: input.cacheKey,
37488
+ files: persistedFiles,
37489
+ diagnostics: input.diagnostics
37423
37490
  });
37424
37491
  };
37425
37492
  const readSystemFacts = () => ({
@@ -37829,16 +37896,22 @@ const checkDeadCode = async (options) => {
37829
37896
  const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory);
37830
37897
  const tsConfigPath = resolveTsConfigPath(rootDirectory);
37831
37898
  const deslopJsModuleSpecifier = options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js");
37832
- const cacheKey = options.cacheEnabled ? computeDeadCodeCacheKey({
37899
+ const fileStatsSnapshot = options.cacheEnabled ? collectAnalyzedFileStats(rootDirectory) : null;
37900
+ const cacheKey = fileStatsSnapshot === null ? null : computeDeadCodeCacheKey({
37833
37901
  rootDirectory,
37834
37902
  entryPatterns,
37835
37903
  ignorePatterns,
37836
37904
  tsConfigPath,
37837
37905
  deslopJsModuleSpecifier,
37838
37906
  coreVersion: CORE_PACKAGE_VERSION
37839
- }) : null;
37840
- if (cacheKey !== null) {
37841
- const cachedDiagnostics = lookupDeadCodeResultCache(resolveReactDoctorCacheDir(rootDirectory), cacheKey);
37907
+ });
37908
+ if (cacheKey !== null && fileStatsSnapshot !== null) {
37909
+ const cachedDiagnostics = lookupDeadCodeResultCache({
37910
+ cacheDirectory: resolveReactDoctorCacheDir(rootDirectory),
37911
+ cacheKey,
37912
+ rootDirectory,
37913
+ currentFileStats: fileStatsSnapshot
37914
+ });
37842
37915
  options.onCacheOutcome?.(cachedDiagnostics !== null);
37843
37916
  if (cachedDiagnostics !== null) return [...cachedDiagnostics];
37844
37917
  }
@@ -37912,7 +37985,13 @@ const checkDeadCode = async (options) => {
37912
37985
  category: DEAD_CODE_CATEGORY
37913
37986
  });
37914
37987
  }
37915
- if (cacheKey !== null) storeDeadCodeResultCache(resolveReactDoctorCacheDir(rootDirectory), cacheKey, diagnostics);
37988
+ if (cacheKey !== null && fileStatsSnapshot !== null) storeDeadCodeResultCache({
37989
+ cacheDirectory: resolveReactDoctorCacheDir(rootDirectory),
37990
+ cacheKey,
37991
+ rootDirectory,
37992
+ snapshotFileStats: fileStatsSnapshot,
37993
+ diagnostics
37994
+ });
37916
37995
  return diagnostics;
37917
37996
  };
37918
37997
  const DEAD_CODE_RULE_KEY_PREFIX = `${DEAD_CODE_PLUGIN}/`;
@@ -39008,13 +39087,6 @@ const createFileLintCache = (cacheDirectory, rulesetHash) => {
39008
39087
  }
39009
39088
  };
39010
39089
  };
39011
- const hashFileContents = (filePath) => {
39012
- try {
39013
- return crypto.createHash("sha1").update(NFS.readFileSync(filePath)).digest("hex");
39014
- } catch {
39015
- return null;
39016
- }
39017
- };
39018
39090
  const ABSENT_CONTENT_ANSWER = "absent";
39019
39091
  /**
39020
39092
  * Answers dependency probes for the sidecar lint cache — used both when
@@ -41155,6 +41227,20 @@ const writeCachedSocketBody = (cacheFile, body) => {
41155
41227
  }));
41156
41228
  } catch {}
41157
41229
  };
41230
+ const pruneExpiredSocketCache = (cacheDirectory) => {
41231
+ try {
41232
+ const supplyChainCacheDirectory = Path.join(cacheDirectory, SUPPLY_CHAIN_CACHE_SUBDIR);
41233
+ const expiryThresholdMs = Date.now() - SUPPLY_CHAIN_CACHE_TTL_MS;
41234
+ for (const entryName of NFS.readdirSync(supplyChainCacheDirectory)) {
41235
+ const entryPath = Path.join(supplyChainCacheDirectory, entryName);
41236
+ try {
41237
+ if (NFS.statSync(entryPath).mtimeMs < expiryThresholdMs) NFS.rmSync(entryPath);
41238
+ } catch {
41239
+ continue;
41240
+ }
41241
+ }
41242
+ } catch {}
41243
+ };
41158
41244
  const fetchSocketArtifact = (dependency, cacheDirectory) => tryPromise(async (signal) => {
41159
41245
  const cacheFile = cacheDirectory === null ? null : supplyChainCacheFile(cacheDirectory, dependency);
41160
41246
  if (cacheFile !== null) {
@@ -41282,6 +41368,7 @@ const checkSupplyChain = (input) => gen(function* () {
41282
41368
  const dependencies = collectDependenciesToScore(readPackageJson(packageJsonPath), readPackageJsonText(packageJsonPath), options.includeDevDependencies);
41283
41369
  if (dependencies.length === 0) return [];
41284
41370
  const cacheDirectory = isSupplyChainCacheDisabled() ? null : resolveReactDoctorCacheDir(input.rootDirectory);
41371
+ if (cacheDirectory !== null) pruneExpiredSocketCache(cacheDirectory);
41285
41372
  const artifacts = yield* forEach$1(dependencies, (dependency) => fetchSocketArtifact(dependency, cacheDirectory), { concurrency: 8 }).pipe(timeoutOption(input.totalTimeoutMs ?? 9e4), map$3((maybeArtifacts) => getOrElse$1(maybeArtifacts, () => [])));
41286
41373
  const diagnostics = [];
41287
41374
  for (let index = 0; index < dependencies.length; index += 1) {
@@ -42326,4 +42413,4 @@ const toJsonReport = (result, options) => buildJsonReport({
42326
42413
  export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
42327
42414
 
42328
42415
  //# sourceMappingURL=index.js.map
42329
- //# debugId=b55ef8b2-8e9d-512b-81de-a01f6ce3e60e
42416
+ //# debugId=b81c51d5-0ead-51a4-a9f9-ae499fd19a52
package/dist/lsp.js CHANGED
@@ -32995,6 +32995,7 @@ const SUPPLY_CHAIN_PLUGIN = "socket";
32995
32995
  const SUPPLY_CHAIN_RULE = "low-supply-chain-score";
32996
32996
  const SUPPLY_CHAIN_CATEGORY = "Security";
32997
32997
  const SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS = 9e4;
32998
+ const SUPPLY_CHAIN_CACHE_TTL_MS = 864e5;
32998
32999
  const SUPPLY_CHAIN_CACHE_SUBDIR = "supply-chain";
32999
33000
  const SUPPLY_CHAIN_IGNORED_PACKAGES = new Set(["next"]);
33000
33001
  const hasIgnoredPathSegment = (relativePath) => relativePath.split(/[/\\]/).slice(0, -1).some((segment) => IGNORED_DIRECTORIES.has(segment));
@@ -37350,22 +37351,37 @@ const failOpenReadJson = (filePath, fallback) => {
37350
37351
  return fallback;
37351
37352
  }
37352
37353
  };
37354
+ const hashFileContents = (filePath) => {
37355
+ try {
37356
+ return crypto.createHash("sha1").update(NFS.readFileSync(filePath)).digest("hex");
37357
+ } catch {
37358
+ return null;
37359
+ }
37360
+ };
37353
37361
  const ANALYZED_FILE_EXTENSIONS = new Set(DEFAULT_EXTENSIONS);
37354
37362
  const CORE_SIDE_MANIFEST_NAMES = ["knip.json", "deno.lock"];
37355
37363
  const ANALYZED_MANIFEST_NAMES = new Set([...ANALYZED_MANIFEST_FILENAMES, ...CORE_SIDE_MANIFEST_NAMES]);
37356
37364
  const isTsConfigLikeFile = (fileName) => (fileName.startsWith("tsconfig") || fileName.startsWith("jsconfig")) && fileName.endsWith(".json");
37357
37365
  const isFingerprintedFile = (fileName) => ANALYZED_FILE_EXTENSIONS.has(Path.extname(fileName).toLowerCase()) || ANALYZED_MANIFEST_NAMES.has(fileName) || isTsConfigLikeFile(fileName);
37358
- const collectAnalyzedFileFingerprints = (rootDirectory) => {
37359
- const fingerprints = [];
37366
+ /**
37367
+ * Stat snapshot of every file the analysis reads, keyed by root-relative
37368
+ * `/`-separated path. Taken BEFORE the (long) analysis so a stored result is
37369
+ * verified against the tree it started from.
37370
+ */
37371
+ const collectAnalyzedFileStats = (rootDirectory) => {
37372
+ const statByRelativePath = /* @__PURE__ */ new Map();
37360
37373
  for (const { absolutePath, name } of walkSourceTreeFiles(rootDirectory)) {
37361
37374
  if (!isFingerprintedFile(name)) continue;
37362
37375
  try {
37363
37376
  const fileStat = NFS.statSync(absolutePath);
37364
37377
  const relativePath = Path.relative(rootDirectory, absolutePath).replace(/\\/g, "/");
37365
- fingerprints.push(`${relativePath}:${fileStat.mtimeMs}:${fileStat.size}`);
37378
+ statByRelativePath.set(relativePath, {
37379
+ mtimeMs: fileStat.mtimeMs,
37380
+ size: fileStat.size
37381
+ });
37366
37382
  } catch {}
37367
37383
  }
37368
- return fingerprints.sort();
37384
+ return statByRelativePath;
37369
37385
  };
37370
37386
  const bundledRequire$2 = createRequire(import.meta.url);
37371
37387
  const resolveDeslopVersion = () => {
@@ -37377,14 +37393,13 @@ const resolveDeslopVersion = () => {
37377
37393
  }
37378
37394
  };
37379
37395
  const computeDeadCodeCacheKey = (input) => crypto.createHash("sha1").update(JSON.stringify({
37380
- schemaVersion: 1,
37396
+ schemaVersion: 2,
37381
37397
  coreVersion: input.coreVersion,
37382
37398
  deslopVersion: resolveDeslopVersion(),
37383
37399
  deslopJsModuleSpecifier: input.deslopJsModuleSpecifier,
37384
37400
  entryPatterns: input.entryPatterns,
37385
37401
  ignorePatterns: input.ignorePatterns,
37386
- tsConfigFile: input.tsConfigPath === void 0 ? null : Path.relative(input.rootDirectory, input.tsConfigPath).replace(/\\/g, "/"),
37387
- files: collectAnalyzedFileFingerprints(input.rootDirectory)
37402
+ tsConfigFile: input.tsConfigPath === void 0 ? null : Path.relative(input.rootDirectory, input.tsConfigPath).replace(/\\/g, "/")
37388
37403
  })).digest("hex");
37389
37404
  const validateDiagnostic$2 = decodeUnknownSync(Diagnostic);
37390
37405
  const decodeCachedDiagnostics = (raw) => {
@@ -37395,16 +37410,68 @@ const decodeCachedDiagnostics = (raw) => {
37395
37410
  return null;
37396
37411
  }
37397
37412
  };
37398
- const lookupDeadCodeResultCache = (cacheDirectory, cacheKey) => {
37399
- const persisted = failOpenReadJson(Path.join(cacheDirectory, DEAD_CODE_CACHE_FILENAME), null);
37400
- if (persisted === null || !isRecord(persisted) || persisted.version !== 1 || persisted.key !== cacheKey || !Array.isArray(persisted.diagnostics)) return null;
37401
- return decodeCachedDiagnostics(persisted.diagnostics);
37413
+ const isPersistedFileIdentity = (value) => Array.isArray(value) && value.length === 3 && typeof value[0] === "number" && typeof value[1] === "number" && typeof value[2] === "string";
37414
+ const lookupDeadCodeResultCache = (input) => {
37415
+ const cacheFilePath = Path.join(input.cacheDirectory, DEAD_CODE_CACHE_FILENAME);
37416
+ const persisted = failOpenReadJson(cacheFilePath, null);
37417
+ if (persisted === null || !isRecord(persisted) || persisted.version !== 2 || persisted.key !== input.cacheKey || !isRecord(persisted.files) || !Array.isArray(persisted.diagnostics)) return null;
37418
+ const storedFileEntries = Object.entries(persisted.files);
37419
+ if (storedFileEntries.length !== input.currentFileStats.size) return null;
37420
+ const repairedFiles = {};
37421
+ let repairedCount = 0;
37422
+ for (const [relativePath, storedIdentity] of storedFileEntries) {
37423
+ if (!isPersistedFileIdentity(storedIdentity)) return null;
37424
+ const currentStat = input.currentFileStats.get(relativePath);
37425
+ if (currentStat === void 0) return null;
37426
+ const [storedMtimeMs, storedSize, storedContentHash] = storedIdentity;
37427
+ if (currentStat.mtimeMs === storedMtimeMs && currentStat.size === storedSize) {
37428
+ repairedFiles[relativePath] = storedIdentity;
37429
+ continue;
37430
+ }
37431
+ if (currentStat.size !== storedSize) return null;
37432
+ const currentContentHash = hashFileContents(Path.join(input.rootDirectory, relativePath));
37433
+ if (currentContentHash === null || currentContentHash !== storedContentHash) return null;
37434
+ repairedFiles[relativePath] = [
37435
+ currentStat.mtimeMs,
37436
+ currentStat.size,
37437
+ storedContentHash
37438
+ ];
37439
+ repairedCount += 1;
37440
+ }
37441
+ const diagnostics = decodeCachedDiagnostics(persisted.diagnostics);
37442
+ if (diagnostics === null) return null;
37443
+ if (repairedCount > 0) atomicWriteJson(cacheFilePath, {
37444
+ version: 2,
37445
+ key: input.cacheKey,
37446
+ files: repairedFiles,
37447
+ diagnostics: persisted.diagnostics
37448
+ });
37449
+ return diagnostics;
37402
37450
  };
37403
- const storeDeadCodeResultCache = (cacheDirectory, cacheKey, diagnostics) => {
37404
- atomicWriteJson(Path.join(cacheDirectory, DEAD_CODE_CACHE_FILENAME), {
37405
- version: 1,
37406
- key: cacheKey,
37407
- diagnostics
37451
+ const storeDeadCodeResultCache = (input) => {
37452
+ const persistedFiles = {};
37453
+ for (const [relativePath, snapshotStat] of input.snapshotFileStats) {
37454
+ const absolutePath = Path.join(input.rootDirectory, relativePath);
37455
+ const contentHash = hashFileContents(absolutePath);
37456
+ if (contentHash === null) return;
37457
+ let currentStat;
37458
+ try {
37459
+ currentStat = NFS.statSync(absolutePath);
37460
+ } catch {
37461
+ return;
37462
+ }
37463
+ if (currentStat.mtimeMs !== snapshotStat.mtimeMs || currentStat.size !== snapshotStat.size) return;
37464
+ persistedFiles[relativePath] = [
37465
+ snapshotStat.mtimeMs,
37466
+ snapshotStat.size,
37467
+ contentHash
37468
+ ];
37469
+ }
37470
+ atomicWriteJson(Path.join(input.cacheDirectory, DEAD_CODE_CACHE_FILENAME), {
37471
+ version: 2,
37472
+ key: input.cacheKey,
37473
+ files: persistedFiles,
37474
+ diagnostics: input.diagnostics
37408
37475
  });
37409
37476
  };
37410
37477
  const readSystemFacts = () => ({
@@ -37814,16 +37881,22 @@ const checkDeadCode = async (options) => {
37814
37881
  const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory);
37815
37882
  const tsConfigPath = resolveTsConfigPath(rootDirectory);
37816
37883
  const deslopJsModuleSpecifier = options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js");
37817
- const cacheKey = options.cacheEnabled ? computeDeadCodeCacheKey({
37884
+ const fileStatsSnapshot = options.cacheEnabled ? collectAnalyzedFileStats(rootDirectory) : null;
37885
+ const cacheKey = fileStatsSnapshot === null ? null : computeDeadCodeCacheKey({
37818
37886
  rootDirectory,
37819
37887
  entryPatterns,
37820
37888
  ignorePatterns,
37821
37889
  tsConfigPath,
37822
37890
  deslopJsModuleSpecifier,
37823
37891
  coreVersion: CORE_PACKAGE_VERSION
37824
- }) : null;
37825
- if (cacheKey !== null) {
37826
- const cachedDiagnostics = lookupDeadCodeResultCache(resolveReactDoctorCacheDir(rootDirectory), cacheKey);
37892
+ });
37893
+ if (cacheKey !== null && fileStatsSnapshot !== null) {
37894
+ const cachedDiagnostics = lookupDeadCodeResultCache({
37895
+ cacheDirectory: resolveReactDoctorCacheDir(rootDirectory),
37896
+ cacheKey,
37897
+ rootDirectory,
37898
+ currentFileStats: fileStatsSnapshot
37899
+ });
37827
37900
  options.onCacheOutcome?.(cachedDiagnostics !== null);
37828
37901
  if (cachedDiagnostics !== null) return [...cachedDiagnostics];
37829
37902
  }
@@ -37897,7 +37970,13 @@ const checkDeadCode = async (options) => {
37897
37970
  category: DEAD_CODE_CATEGORY
37898
37971
  });
37899
37972
  }
37900
- if (cacheKey !== null) storeDeadCodeResultCache(resolveReactDoctorCacheDir(rootDirectory), cacheKey, diagnostics);
37973
+ if (cacheKey !== null && fileStatsSnapshot !== null) storeDeadCodeResultCache({
37974
+ cacheDirectory: resolveReactDoctorCacheDir(rootDirectory),
37975
+ cacheKey,
37976
+ rootDirectory,
37977
+ snapshotFileStats: fileStatsSnapshot,
37978
+ diagnostics
37979
+ });
37901
37980
  return diagnostics;
37902
37981
  };
37903
37982
  const DEAD_CODE_RULE_KEY_PREFIX = `${DEAD_CODE_PLUGIN}/`;
@@ -38993,13 +39072,6 @@ const createFileLintCache = (cacheDirectory, rulesetHash) => {
38993
39072
  }
38994
39073
  };
38995
39074
  };
38996
- const hashFileContents = (filePath) => {
38997
- try {
38998
- return crypto.createHash("sha1").update(NFS.readFileSync(filePath)).digest("hex");
38999
- } catch {
39000
- return null;
39001
- }
39002
- };
39003
39075
  const ABSENT_CONTENT_ANSWER = "absent";
39004
39076
  /**
39005
39077
  * Answers dependency probes for the sidecar lint cache — used both when
@@ -41140,6 +41212,20 @@ const writeCachedSocketBody = (cacheFile, body) => {
41140
41212
  }));
41141
41213
  } catch {}
41142
41214
  };
41215
+ const pruneExpiredSocketCache = (cacheDirectory) => {
41216
+ try {
41217
+ const supplyChainCacheDirectory = Path.join(cacheDirectory, SUPPLY_CHAIN_CACHE_SUBDIR);
41218
+ const expiryThresholdMs = Date.now() - SUPPLY_CHAIN_CACHE_TTL_MS;
41219
+ for (const entryName of NFS.readdirSync(supplyChainCacheDirectory)) {
41220
+ const entryPath = Path.join(supplyChainCacheDirectory, entryName);
41221
+ try {
41222
+ if (NFS.statSync(entryPath).mtimeMs < expiryThresholdMs) NFS.rmSync(entryPath);
41223
+ } catch {
41224
+ continue;
41225
+ }
41226
+ }
41227
+ } catch {}
41228
+ };
41143
41229
  const fetchSocketArtifact = (dependency, cacheDirectory) => tryPromise(async (signal) => {
41144
41230
  const cacheFile = cacheDirectory === null ? null : supplyChainCacheFile(cacheDirectory, dependency);
41145
41231
  if (cacheFile !== null) {
@@ -41267,6 +41353,7 @@ const checkSupplyChain = (input) => gen(function* () {
41267
41353
  const dependencies = collectDependenciesToScore(readPackageJson(packageJsonPath), readPackageJsonText(packageJsonPath), options.includeDevDependencies);
41268
41354
  if (dependencies.length === 0) return [];
41269
41355
  const cacheDirectory = isSupplyChainCacheDisabled() ? null : resolveReactDoctorCacheDir(input.rootDirectory);
41356
+ if (cacheDirectory !== null) pruneExpiredSocketCache(cacheDirectory);
41270
41357
  const artifacts = yield* forEach$1(dependencies, (dependency) => fetchSocketArtifact(dependency, cacheDirectory), { concurrency: 8 }).pipe(timeoutOption(input.totalTimeoutMs ?? 9e4), map$3((maybeArtifacts) => getOrElse$1(maybeArtifacts, () => [])));
41271
41358
  const diagnostics = [];
41272
41359
  for (let index = 0; index < dependencies.length; index += 1) {
@@ -41983,11 +42070,9 @@ const computeConfigFingerprint = (projectDirectory, version) => {
41983
42070
  const parts = [`v=${version}`];
41984
42071
  let directory = projectDirectory;
41985
42072
  for (;;) {
41986
- for (const filename of CONFIG_FINGERPRINT_FILENAMES) try {
41987
- const stat = fs.statSync(path.join(directory, filename));
41988
- parts.push(`${directory}/${filename}=${stat.mtimeMs}:${stat.size}`);
41989
- } catch {
41990
- continue;
42073
+ for (const filename of CONFIG_FINGERPRINT_FILENAMES) {
42074
+ const contentHash = hashFileContents(path.join(directory, filename));
42075
+ if (contentHash !== null) parts.push(`${directory}/${filename}=${contentHash}`);
41991
42076
  }
41992
42077
  const parent = path.dirname(directory);
41993
42078
  if (parent === directory) break;
@@ -44050,5 +44135,5 @@ const startLanguageServer = () => {
44050
44135
  };
44051
44136
  //#endregion
44052
44137
  export { startLanguageServer };
44053
- !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]="17a3f533-8248-511a-859b-584197cefba6")}catch(e){}}();
44054
- //# debugId=17a3f533-8248-511a-859b-584197cefba6
44138
+ !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]="35802bcf-f0e9-56fb-8cd9-6aee4dd99007")}catch(e){}}();
44139
+ //# debugId=35802bcf-f0e9-56fb-8cd9-6aee4dd99007
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-doctor",
3
- "version": "0.6.3-dev.bdf8074",
3
+ "version": "0.6.3-dev.ced746f",
4
4
  "description": "Your agent writes bad React. This catches it",
5
5
  "keywords": [
6
6
  "accessibility",
@@ -66,7 +66,7 @@
66
66
  "vscode-uri": "^3.1.0",
67
67
  "yaml": "^2.9.0",
68
68
  "deslop-js": "0.6.3",
69
- "oxlint-plugin-react-doctor": "0.6.3-dev.bdf8074"
69
+ "oxlint-plugin-react-doctor": "0.6.3-dev.ced746f"
70
70
  },
71
71
  "devDependencies": {
72
72
  "@types/babel__code-frame": "^7.27.0",
@@ -74,8 +74,8 @@
74
74
  "@xterm/headless": "^6.0.0",
75
75
  "commander": "^14.0.3",
76
76
  "ora": "^9.4.0",
77
- "@react-doctor/api": "0.6.3",
78
77
  "@react-doctor/core": "0.6.3",
78
+ "@react-doctor/api": "0.6.3",
79
79
  "@react-doctor/language-server": "0.6.3"
80
80
  },
81
81
  "engines": {