react-doctor 0.6.3-dev.8325343 → 0.6.3-dev.8c004f0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="2921b8ff-14cf-5604-8ae5-b65b4f1347fc")}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]="e34cb22b-7751-5b1a-8422-6c5eb384dbef")}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";
@@ -36128,6 +36128,7 @@ const SOCKET_PACKAGE_PAGE_BASE = "https://socket.dev/npm/package";
36128
36128
  const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
36129
36129
  const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
36130
36130
  const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
36131
+ const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
36131
36132
  const SUPPLY_CHAIN_PLUGIN = "socket";
36132
36133
  const SUPPLY_CHAIN_RULE = "low-supply-chain-score";
36133
36134
  const SUPPLY_CHAIN_CATEGORY = "Security";
@@ -38812,6 +38813,25 @@ var PerFileLintCacheEnabled = class extends Reference("react-doctor/PerFileLintC
38812
38813
  if (CACHE_DISABLED_VALUES$1.has(noFileCache)) return false;
38813
38814
  return true;
38814
38815
  } }) {};
38816
+ /**
38817
+ * Whether the whole-project dead-code result cache
38818
+ * (`dead-code/dead-code-result-cache.ts`) is active. Defaults ON — a rescan
38819
+ * whose inputs (source tree, manifests, configs, analyzer version) are
38820
+ * unchanged replays the stored diagnostics instead of re-running the
38821
+ * analysis worker. Opt-OUT, two knobs (matching the per-file lint cache):
38822
+ *
38823
+ * - `REACT_DOCTOR_NO_CACHE` — the global off-switch.
38824
+ * - `REACT_DOCTOR_NO_DEAD_CODE_CACHE` — granular: bust only this cache.
38825
+ *
38826
+ * Tests override via `Layer.succeed(DeadCodeResultCacheEnabled, false)`.
38827
+ */
38828
+ var DeadCodeResultCacheEnabled = class extends Reference("react-doctor/DeadCodeResultCacheEnabled", { defaultValue: () => {
38829
+ const noCache = process.env["REACT_DOCTOR_NO_CACHE"]?.toLowerCase() ?? "";
38830
+ const noDeadCodeCache = process.env["REACT_DOCTOR_NO_DEAD_CODE_CACHE"]?.toLowerCase() ?? "";
38831
+ if (CACHE_DISABLED_VALUES$1.has(noCache)) return false;
38832
+ if (CACHE_DISABLED_VALUES$1.has(noDeadCodeCache)) return false;
38833
+ return true;
38834
+ } }) {};
38815
38835
  const DIAGNOSTIC_SURFACES = [
38816
38836
  "cli",
38817
38837
  "prComment",
@@ -40544,6 +40564,108 @@ const collectDeadCodeIgnorePatterns = (rootDirectory) => {
40544
40564
  return [...seen].filter((pattern) => pattern.length > 0);
40545
40565
  };
40546
40566
  const collectDeadCodeEntryPatterns = (rootDirectory) => [...new Set(collectKnipPatterns(rootDirectory, "entry"))].filter((pattern) => pattern.length > 0);
40567
+ const atomicWriteJson = (filePath, value) => {
40568
+ try {
40569
+ NFS.mkdirSync(Path.dirname(filePath), { recursive: true });
40570
+ const temporaryPath = `${filePath}.${process.pid}.tmp`;
40571
+ NFS.writeFileSync(temporaryPath, JSON.stringify(value));
40572
+ NFS.renameSync(temporaryPath, filePath);
40573
+ } catch {
40574
+ return;
40575
+ }
40576
+ };
40577
+ const failOpenReadJson = (filePath, fallback) => {
40578
+ try {
40579
+ return JSON.parse(NFS.readFileSync(filePath, "utf8"));
40580
+ } catch {
40581
+ return fallback;
40582
+ }
40583
+ };
40584
+ const ANALYZED_FILE_EXTENSIONS = new Set([
40585
+ ".ts",
40586
+ ".tsx",
40587
+ ".js",
40588
+ ".jsx",
40589
+ ".mts",
40590
+ ".mjs",
40591
+ ".cts",
40592
+ ".cjs",
40593
+ ".mdx",
40594
+ ".astro",
40595
+ ".graphql",
40596
+ ".gql",
40597
+ ".css",
40598
+ ".scss",
40599
+ ".vue",
40600
+ ".svelte"
40601
+ ]);
40602
+ const ANALYZED_MANIFEST_NAMES = new Set([
40603
+ "package.json",
40604
+ "knip.json",
40605
+ "app.json",
40606
+ "pnpm-workspace.yaml",
40607
+ "lerna.json",
40608
+ "pnpm-lock.yaml",
40609
+ "package-lock.json",
40610
+ "yarn.lock",
40611
+ "bun.lock",
40612
+ "bun.lockb",
40613
+ "deno.lock",
40614
+ ".gitignore"
40615
+ ]);
40616
+ const isTsConfigLikeFile = (fileName) => (fileName.startsWith("tsconfig") || fileName.startsWith("jsconfig")) && fileName.endsWith(".json");
40617
+ const isFingerprintedFile = (fileName) => ANALYZED_FILE_EXTENSIONS.has(Path.extname(fileName).toLowerCase()) || ANALYZED_MANIFEST_NAMES.has(fileName) || isTsConfigLikeFile(fileName);
40618
+ const collectAnalyzedFileFingerprints = (rootDirectory) => {
40619
+ const fingerprints = [];
40620
+ for (const { absolutePath, name } of walkSourceTreeFiles(rootDirectory)) {
40621
+ if (!isFingerprintedFile(name)) continue;
40622
+ try {
40623
+ const fileStat = NFS.statSync(absolutePath);
40624
+ const relativePath = Path.relative(rootDirectory, absolutePath).replace(/\\/g, "/");
40625
+ fingerprints.push(`${relativePath}:${fileStat.mtimeMs}:${fileStat.size}`);
40626
+ } catch {}
40627
+ }
40628
+ return fingerprints.sort();
40629
+ };
40630
+ const bundledRequire$2 = createRequire(import.meta.url);
40631
+ const resolveDeslopVersion = () => {
40632
+ try {
40633
+ const packageJson = JSON.parse(NFS.readFileSync(bundledRequire$2.resolve("deslop-js/package.json"), "utf8"));
40634
+ return isRecord$1(packageJson) && typeof packageJson.version === "string" ? packageJson.version : "unknown";
40635
+ } catch {
40636
+ return "unknown";
40637
+ }
40638
+ };
40639
+ const computeDeadCodeCacheKey = (input) => crypto.createHash("sha1").update(JSON.stringify({
40640
+ schemaVersion: 1,
40641
+ deslopVersion: resolveDeslopVersion(),
40642
+ deslopJsModuleSpecifier: input.deslopJsModuleSpecifier,
40643
+ entryPatterns: input.entryPatterns,
40644
+ ignorePatterns: input.ignorePatterns,
40645
+ tsConfigFile: input.tsConfigPath === void 0 ? null : Path.relative(input.rootDirectory, input.tsConfigPath).replace(/\\/g, "/"),
40646
+ files: collectAnalyzedFileFingerprints(input.rootDirectory)
40647
+ })).digest("hex");
40648
+ const validateDiagnostic$1 = decodeUnknownSync(Diagnostic);
40649
+ const decodeCachedDiagnostics = (raw) => {
40650
+ try {
40651
+ for (const entry of raw) validateDiagnostic$1(entry);
40652
+ return raw;
40653
+ } catch {
40654
+ return null;
40655
+ }
40656
+ };
40657
+ const lookupDeadCodeResultCache = (cacheDirectory, cacheKey) => {
40658
+ const persisted = failOpenReadJson(Path.join(cacheDirectory, DEAD_CODE_CACHE_FILENAME), null);
40659
+ if (persisted === null || !isRecord$1(persisted) || persisted.version !== 1 || persisted.key !== cacheKey || !Array.isArray(persisted.diagnostics)) return null;
40660
+ return decodeCachedDiagnostics(persisted.diagnostics);
40661
+ };
40662
+ const storeDeadCodeResultCache = (cacheDirectory, cacheKey, diagnostics) => {
40663
+ atomicWriteJson(Path.join(cacheDirectory, DEAD_CODE_CACHE_FILENAME), {
40664
+ version: 1,
40665
+ key: cacheKey,
40666
+ diagnostics
40667
+ });
40668
+ };
40547
40669
  const readSystemFacts = () => ({
40548
40670
  availableCores: os.availableParallelism(),
40549
40671
  totalMemoryBytes: os.totalmem(),
@@ -40612,6 +40734,21 @@ const withDeadCodeWorkerSlot = async (task, abortSignal) => {
40612
40734
  releaseSlot();
40613
40735
  }
40614
40736
  };
40737
+ const projectCacheSubdir = (projectDirectory) => crypto.createHash("sha256").update(projectDirectory).digest("hex").slice(0, 16);
40738
+ const resolveReactDoctorCacheDir = (projectDirectory) => {
40739
+ const cacheDirOverride = process.env["REACT_DOCTOR_CACHE_DIR"]?.trim();
40740
+ if (cacheDirOverride) return Path.join(cacheDirOverride, projectCacheSubdir(projectDirectory));
40741
+ const nodeModulesDirectory = Path.join(projectDirectory, "node_modules");
40742
+ if (NFS.existsSync(nodeModulesDirectory)) return Path.join(nodeModulesDirectory, ".cache", "react-doctor");
40743
+ return Path.join(os.tmpdir(), `react-doctor-cache-${resolveUserCacheScope()}`, projectCacheSubdir(projectDirectory));
40744
+ };
40745
+ const resolveUserCacheScope = () => {
40746
+ try {
40747
+ return String(process.getuid?.() ?? os.userInfo().username);
40748
+ } catch {
40749
+ return "shared";
40750
+ }
40751
+ };
40615
40752
  /**
40616
40753
  * Resolves a path to its canonical, symlink-free form, falling back to
40617
40754
  * the input when it cannot be realpath'd (broken symlink, permission
@@ -40907,13 +41044,27 @@ const checkDeadCode = async (options) => {
40907
41044
  if (!NFS.existsSync(Path.join(rootDirectory, "package.json"))) return [];
40908
41045
  const entryPatterns = collectDeadCodeEntryPatterns(rootDirectory);
40909
41046
  const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory);
41047
+ const tsConfigPath = resolveTsConfigPath(rootDirectory);
41048
+ const deslopJsModuleSpecifier = options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js");
41049
+ const cacheKey = options.cacheEnabled ? computeDeadCodeCacheKey({
41050
+ rootDirectory,
41051
+ entryPatterns,
41052
+ ignorePatterns,
41053
+ tsConfigPath,
41054
+ deslopJsModuleSpecifier
41055
+ }) : null;
41056
+ if (cacheKey !== null) {
41057
+ const cachedDiagnostics = lookupDeadCodeResultCache(resolveReactDoctorCacheDir(rootDirectory), cacheKey);
41058
+ options.onCacheOutcome?.(cachedDiagnostics !== null);
41059
+ if (cachedDiagnostics !== null) return [...cachedDiagnostics];
41060
+ }
40910
41061
  const spawnAndRun = () => {
40911
41062
  return runDeadCodeWorkerWithTimeout((options.createWorker ?? createDeadCodeWorker)({
40912
41063
  rootDirectory,
40913
41064
  entryPatterns,
40914
- tsConfigPath: resolveTsConfigPath(rootDirectory),
41065
+ tsConfigPath,
40915
41066
  ignorePatterns,
40916
- deslopJsModuleSpecifier: options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js"),
41067
+ deslopJsModuleSpecifier,
40917
41068
  parseConcurrency: options.parseConcurrency
40918
41069
  }), options.workerTimeoutMs ?? 12e4, options.abortSignal);
40919
41070
  };
@@ -40974,6 +41125,7 @@ const checkDeadCode = async (options) => {
40974
41125
  category: DEAD_CODE_CATEGORY
40975
41126
  });
40976
41127
  }
41128
+ if (cacheKey !== null) storeDeadCodeResultCache(resolveReactDoctorCacheDir(rootDirectory), cacheKey, diagnostics);
40977
41129
  return diagnostics;
40978
41130
  };
40979
41131
  const DEAD_CODE_RULE_KEY_PREFIX = `${DEAD_CODE_PLUGIN}/`;
@@ -41076,12 +41228,15 @@ var Config = class Config extends Service()("react-doctor/Config") {
41076
41228
  */
41077
41229
  var DeadCode = class DeadCode extends Service()("react-doctor/DeadCode") {
41078
41230
  static layerNode = succeed$3(DeadCode, DeadCode.of({ run: (input) => unwrap(fn("DeadCode.run")(function* () {
41231
+ const cacheEnabled = yield* DeadCodeResultCacheEnabled;
41079
41232
  return yield* tryPromise({
41080
41233
  try: (signal) => checkDeadCode({
41081
41234
  rootDirectory: input.rootDirectory,
41082
41235
  parseConcurrency: input.parseConcurrency,
41083
41236
  workerTimeoutMs: input.workerTimeoutMs,
41084
- abortSignal: signal
41237
+ abortSignal: signal,
41238
+ cacheEnabled,
41239
+ onCacheOutcome: input.onCacheOutcome
41085
41240
  }),
41086
41241
  catch: (cause) => new ReactDoctorError({ reason: new DeadCodeAnalysisFailed({ cause }) })
41087
41242
  }).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)));
@@ -42007,23 +42162,6 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
42007
42162
  }
42008
42163
  };
42009
42164
  };
42010
- const atomicWriteJson = (filePath, value) => {
42011
- try {
42012
- NFS.mkdirSync(Path.dirname(filePath), { recursive: true });
42013
- const temporaryPath = `${filePath}.${process.pid}.tmp`;
42014
- NFS.writeFileSync(temporaryPath, JSON.stringify(value));
42015
- NFS.renameSync(temporaryPath, filePath);
42016
- } catch {
42017
- return;
42018
- }
42019
- };
42020
- const failOpenReadJson = (filePath, fallback) => {
42021
- try {
42022
- return JSON.parse(NFS.readFileSync(filePath, "utf8"));
42023
- } catch {
42024
- return fallback;
42025
- }
42026
- };
42027
42165
  const validateDiagnostic = decodeUnknownSync(Diagnostic);
42028
42166
  const decodeFileDiagnostics = (raw) => {
42029
42167
  if (!Array.isArray(raw)) return null;
@@ -42081,7 +42219,7 @@ const createFileLintCache = (cacheDirectory, rulesetHash) => {
42081
42219
  }
42082
42220
  };
42083
42221
  };
42084
- const bundledRequire$2 = createRequire(import.meta.url);
42222
+ const bundledRequire$3 = createRequire(import.meta.url);
42085
42223
  const childNodeVersionByBinaryPath = /* @__PURE__ */ new Map();
42086
42224
  const resolveChildNodeVersion = (nodeBinaryPath) => {
42087
42225
  if (nodeBinaryPath === process.execPath) return process.version;
@@ -42107,7 +42245,7 @@ const TOOLCHAIN_PACKAGE_SPECIFIERS$1 = [
42107
42245
  const resolveOxlintToolchainVersions = (nodeBinaryPath = process.execPath) => {
42108
42246
  const versions = [`node=${resolveChildNodeVersion(nodeBinaryPath)}`];
42109
42247
  for (const specifier of TOOLCHAIN_PACKAGE_SPECIFIERS$1) try {
42110
- const packageJson = bundledRequire$2(specifier);
42248
+ const packageJson = bundledRequire$3(specifier);
42111
42249
  const version = typeof packageJson.version === "string" ? packageJson.version : "unknown";
42112
42250
  versions.push(`${specifier}=${version}`);
42113
42251
  } catch {
@@ -43151,21 +43289,6 @@ const planLintBatches = ({ baseArgs, files, sizeByFile }) => {
43151
43289
  }
43152
43290
  return batches.filter((batch) => batch.fileIndexes.length > 0).map((batch) => batch.fileIndexes.sort((leftIndex, rightIndex) => leftIndex - rightIndex).map((fileIndex) => files[fileIndex]));
43153
43291
  };
43154
- const projectCacheSubdir = (projectDirectory) => crypto.createHash("sha256").update(projectDirectory).digest("hex").slice(0, 16);
43155
- const resolveReactDoctorCacheDir = (projectDirectory) => {
43156
- const cacheDirOverride = process.env["REACT_DOCTOR_CACHE_DIR"]?.trim();
43157
- if (cacheDirOverride) return Path.join(cacheDirOverride, projectCacheSubdir(projectDirectory));
43158
- const nodeModulesDirectory = Path.join(projectDirectory, "node_modules");
43159
- if (NFS.existsSync(nodeModulesDirectory)) return Path.join(nodeModulesDirectory, ".cache", "react-doctor");
43160
- return Path.join(os.tmpdir(), `react-doctor-cache-${resolveUserCacheScope()}`, projectCacheSubdir(projectDirectory));
43161
- };
43162
- const resolveUserCacheScope = () => {
43163
- try {
43164
- return String(process.getuid?.() ?? os.userInfo().username);
43165
- } catch {
43166
- return "shared";
43167
- }
43168
- };
43169
43292
  /**
43170
43293
  * Atomically (re)writes the generated oxlintrc.json. Used twice in
43171
43294
  * the runner: once for the primary scan, once for the
@@ -44292,7 +44415,10 @@ const runInspect = (input, hooks = {}) => gen(function* () {
44292
44415
  const buildCollectDeadCode = (deadCodeTimeout) => runCollect(applyPerElementPipeline(deadCodeService.run({
44293
44416
  rootDirectory: scanDirectory,
44294
44417
  parseConcurrency: deadCodeParseConcurrency,
44295
- workerTimeoutMs: deadCodeTimeout.workerTimeoutMs
44418
+ workerTimeoutMs: deadCodeTimeout.workerTimeoutMs,
44419
+ onCacheOutcome: (didHitCache) => {
44420
+ deadCodeCacheHit = didHitCache;
44421
+ }
44296
44422
  }).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
44297
44423
  yield* set(deadCodeFailure, {
44298
44424
  didFail: true,
@@ -44320,6 +44446,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
44320
44446
  let lastReportedTotalFileCount = 0;
44321
44447
  let lintCacheHitFileCount = null;
44322
44448
  let lintCacheTotalFileCount = null;
44449
+ let deadCodeCacheHit = null;
44323
44450
  const baseLintStream = linterService.run({
44324
44451
  rootDirectory: scanDirectory,
44325
44452
  project,
@@ -44449,6 +44576,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
44449
44576
  securityScanFailed,
44450
44577
  lintCacheHitFileCount,
44451
44578
  lintCacheTotalFileCount,
44579
+ deadCodeCacheHit: lintFailureState.didFail ? null : deadCodeCacheHit,
44452
44580
  suppressedRuleCounts: transform.summarizeSuppressions()
44453
44581
  };
44454
44582
  }).pipe(withSpan("runInspect", { attributes: {
@@ -45230,6 +45358,7 @@ const isNonInteractiveEnvironment = () => NON_INTERACTIVE_ENVIRONMENT_VARIABLES.
45230
45358
  const REACT_DOCTOR_CONFIG_PROJECT_NAME = "react-doctor";
45231
45359
  const STAGED_FILES_TEMP_DIR_PREFIX = "react-doctor-staged-";
45232
45360
  const BASELINE_FILES_TEMP_DIR_PREFIX = "react-doctor-baseline-";
45361
+ const RUN_GIT_MAX_BUFFER_BYTES = 64 * 1024 * 1024;
45233
45362
  const GH_DEFAULT_BRANCH_PROBE_TIMEOUT_MS = 5e3;
45234
45363
  const CI_TRUST_COMPANIES = "PayPal, Rippling, and Alibaba";
45235
45364
  const WELCOME_EXPLANATION_HOLD_MS = 1e3;
@@ -45320,7 +45449,7 @@ const makeNoopConsole = () => ({
45320
45449
  });
45321
45450
  //#endregion
45322
45451
  //#region src/cli/utils/version.ts
45323
- const VERSION = "0.6.3-dev.8325343";
45452
+ const VERSION = "0.6.3-dev.8c004f0";
45324
45453
  //#endregion
45325
45454
  //#region src/cli/utils/json-mode.ts
45326
45455
  let context = null;
@@ -45689,13 +45818,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
45689
45818
  * uploads source-map artifacts under, so stack frames symbolicate. Honors the
45690
45819
  * standard `SENTRY_RELEASE` override.
45691
45820
  */
45692
- const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.6.3-dev.8325343`;
45821
+ const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.6.3-dev.8c004f0`;
45693
45822
  /**
45694
45823
  * Deployment environment shown in Sentry's environment filter. Defaults to
45695
45824
  * `production` for tagged releases and `development` for dev/unbuilt versions,
45696
45825
  * overridable via the standard `SENTRY_ENVIRONMENT` env var.
45697
45826
  */
45698
- const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.6.3-dev.8325343") ? "development" : "production");
45827
+ const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.6.3-dev.8c004f0") ? "development" : "production");
45699
45828
  /**
45700
45829
  * Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
45701
45830
  * (set to `0` to disable tracing) and falls back to
@@ -45896,7 +46025,8 @@ const runGit = (projectRoot, args) => {
45896
46025
  "ignore",
45897
46026
  "pipe",
45898
46027
  "ignore"
45899
- ]
46028
+ ],
46029
+ maxBuffer: RUN_GIT_MAX_BUFFER_BYTES
45900
46030
  }).trim();
45901
46031
  } catch {
45902
46032
  return null;
@@ -57922,7 +58052,8 @@ const buildOutcomeAttributes = (input) => {
57922
58052
  }),
57923
58053
  ...withNamespace("deadCode", {
57924
58054
  failed: input.didDeadCodeFail ?? null,
57925
- overlapped: input.deadCodeOverlapped ?? null
58055
+ overlapped: input.deadCodeOverlapped ?? null,
58056
+ cacheHit: result.deadCodeCacheHit ?? null
57926
58057
  }),
57927
58058
  ...withNamespace("supplyChain", { overlapTimedOut: input.supplyChainOverlapTimedOut ?? null }),
57928
58059
  ...withNamespace("securityScan", { failed: input.securityScanFailed ?? null }),
@@ -59245,13 +59376,89 @@ const stringifyStableJson = (value) => {
59245
59376
  };
59246
59377
  const hashString = (value) => crypto.createHash("sha1").update(value).digest("hex");
59247
59378
  const readHeadSha = (projectDirectory) => runGit(projectDirectory, ["rev-parse", "HEAD"]);
59248
- const isWorktreeClean = (projectDirectory) => {
59249
- const status = runGit(projectDirectory, [
59379
+ const parseWorktreeStatusRecords = (statusOutput) => {
59380
+ const entries = [];
59381
+ const tokens = statusOutput.split("\0");
59382
+ for (let tokenIndex = 0; tokenIndex < tokens.length; tokenIndex += 1) {
59383
+ let record = tokens[tokenIndex];
59384
+ if (record.length === 0) continue;
59385
+ if (record.length < 3 || record[2] !== " ") {
59386
+ if (tokenIndex !== 0) return null;
59387
+ record = ` ${record}`;
59388
+ }
59389
+ if (record.length < 4 || record[2] !== " ") return null;
59390
+ const statusCode = record.slice(0, 2);
59391
+ const recordPath = record.slice(3);
59392
+ if (statusCode.includes("R") || statusCode.includes("C")) {
59393
+ tokenIndex += 1;
59394
+ const originPath = tokens[tokenIndex];
59395
+ if (originPath === void 0 || originPath.length === 0) return null;
59396
+ entries.push({
59397
+ statusCode,
59398
+ path: recordPath,
59399
+ originPath
59400
+ });
59401
+ continue;
59402
+ }
59403
+ entries.push({
59404
+ statusCode,
59405
+ path: recordPath
59406
+ });
59407
+ }
59408
+ return entries;
59409
+ };
59410
+ const buildDirtyPathContentFingerprint = (absolutePath, statusCode) => {
59411
+ let stats;
59412
+ try {
59413
+ stats = fs.lstatSync(absolutePath);
59414
+ } catch {
59415
+ return statusCode.includes("D") ? "deleted" : null;
59416
+ }
59417
+ if (stats.isSymbolicLink()) try {
59418
+ return `link:${fs.readlinkSync(absolutePath)}`;
59419
+ } catch {
59420
+ return null;
59421
+ }
59422
+ if (!stats.isFile()) return null;
59423
+ if (stats.size > 10485760) return `stat:${stats.mtimeMs}:${stats.size}`;
59424
+ return hashFileContents(absolutePath);
59425
+ };
59426
+ const buildWorktreeFingerprint = (projectDirectory) => {
59427
+ const statusOutput = runGit(projectDirectory, [
59250
59428
  "status",
59251
59429
  "--porcelain=v1",
59252
- "--untracked-files=normal"
59430
+ "-z",
59431
+ "--untracked-files=all"
59253
59432
  ]);
59254
- return status !== null && status.length === 0;
59433
+ if (statusOutput === null) return null;
59434
+ if (statusOutput.length === 0) return [];
59435
+ const statusEntries = parseWorktreeStatusRecords(statusOutput);
59436
+ if (statusEntries === null) return null;
59437
+ if (statusEntries.length > 300) return null;
59438
+ const repositoryRoot = runGit(projectDirectory, ["rev-parse", "--show-toplevel"]);
59439
+ if (repositoryRoot === null) return null;
59440
+ const dirtyEntries = [];
59441
+ for (const statusEntry of statusEntries) {
59442
+ const contentFingerprint = buildDirtyPathContentFingerprint(path.join(repositoryRoot, statusEntry.path), statusEntry.statusCode);
59443
+ if (contentFingerprint === null) return null;
59444
+ dirtyEntries.push({
59445
+ ...statusEntry,
59446
+ contentFingerprint
59447
+ });
59448
+ }
59449
+ return dirtyEntries.sort((firstEntry, secondEntry) => {
59450
+ const firstSortKey = `${firstEntry.path}${firstEntry.statusCode}`;
59451
+ const secondSortKey = `${secondEntry.path}${secondEntry.statusCode}`;
59452
+ return firstSortKey < secondSortKey ? -1 : firstSortKey > secondSortKey ? 1 : 0;
59453
+ });
59454
+ };
59455
+ const DOTENV_FILE_NAME_PATTERN = /^\.env(\.|$)/;
59456
+ const resolveDotenvFingerprint = (projectDirectory) => {
59457
+ try {
59458
+ return fs.readdirSync(projectDirectory).filter((entryName) => DOTENV_FILE_NAME_PATTERN.test(entryName)).sort().map((entryName) => `${entryName}=${fileFingerprint(path.join(projectDirectory, entryName)) ?? "unreadable"}`);
59459
+ } catch {
59460
+ return [];
59461
+ }
59255
59462
  };
59256
59463
  const hasHiddenTrackedFileState = (projectDirectory) => {
59257
59464
  const output = runGit(projectDirectory, ["ls-files", "-v"]);
@@ -59334,8 +59541,9 @@ const resolveToolchainFingerprint = (nodeBinaryPath) => {
59334
59541
  };
59335
59542
  const buildScanResultCacheKey = (input) => {
59336
59543
  if (isScanResultCacheDisabled()) return null;
59337
- if (!isWorktreeClean(input.projectDirectory)) return null;
59338
59544
  if (hasHiddenTrackedFileState(input.projectDirectory)) return null;
59545
+ const worktreeFingerprint = buildWorktreeFingerprint(input.projectDirectory);
59546
+ if (worktreeFingerprint === null) return null;
59339
59547
  const headSha = readHeadSha(input.projectDirectory);
59340
59548
  if (headSha === null) return null;
59341
59549
  if (stringifyStableJson(input.userConfig) === null) return null;
@@ -59343,6 +59551,8 @@ const buildScanResultCacheKey = (input) => {
59343
59551
  schemaVersion: 3,
59344
59552
  projectIdentity: resolveProjectIdentity(input.projectDirectory),
59345
59553
  headSha,
59554
+ worktreeFingerprint,
59555
+ dotenvFingerprint: resolveDotenvFingerprint(input.projectDirectory),
59346
59556
  reactDoctorVersion: input.version,
59347
59557
  nodeVersion: process.version,
59348
59558
  toolchainFingerprint: resolveToolchainFingerprint(input.nodeBinaryPath),
@@ -59735,7 +59945,8 @@ const runInspectWithRuntime = async (directory, options, userConfig, hasConfigOv
59735
59945
  scanMode: baselineDelta ? "baseline" : isDiffMode ? "diff" : "full",
59736
59946
  baselineDegraded,
59737
59947
  lintCacheHitFileCount: output.lintCacheHitFileCount,
59738
- lintCacheTotalFileCount: output.lintCacheTotalFileCount
59948
+ lintCacheTotalFileCount: output.lintCacheTotalFileCount,
59949
+ deadCodeCacheHit: output.deadCodeCacheHit
59739
59950
  });
59740
59951
  recordOnboardingCompletion(options);
59741
59952
  return result;
@@ -59770,6 +59981,7 @@ const renderAndRecordScan = async (input) => {
59770
59981
  scanElapsedMilliseconds: input.payload.scanElapsedMilliseconds,
59771
59982
  lintCacheHitFileCount: input.lintCacheHitFileCount ?? null,
59772
59983
  lintCacheTotalFileCount: input.lintCacheTotalFileCount ?? null,
59984
+ deadCodeCacheHit: input.deadCodeCacheHit ?? null,
59773
59985
  baselineDelta: input.payload.baselineDelta
59774
59986
  }), input.options.silent));
59775
59987
  const { workerCount: resolvedWorkerCount, parallel } = resolveWorkerTelemetry(input.payload.scanConcurrency, input.options.concurrency);
@@ -59808,7 +60020,7 @@ const renderAndRecordScan = async (input) => {
59808
60020
  return result;
59809
60021
  };
59810
60022
  const finalizeAndRender = (input) => gen(function* () {
59811
- const { options, elapsedMilliseconds, diagnostics, score, project, userConfig, didLintFail, lintFailureReason, lintPartialFailures, didDeadCodeFail, deadCodeFailureReason, directory, scannedFileCount, scannedFilePaths, scanElapsedMilliseconds, lintCacheHitFileCount, lintCacheTotalFileCount, baselineDelta } = input;
60023
+ const { options, elapsedMilliseconds, diagnostics, score, project, userConfig, didLintFail, lintFailureReason, lintPartialFailures, didDeadCodeFail, deadCodeFailureReason, directory, scannedFileCount, scannedFilePaths, scanElapsedMilliseconds, lintCacheHitFileCount, lintCacheTotalFileCount, deadCodeCacheHit, baselineDelta } = input;
59812
60024
  const { skippedChecks, skippedCheckReasons } = buildSkippedChecks({
59813
60025
  didLintFail,
59814
60026
  lintFailureReason,
@@ -59832,6 +60044,7 @@ const finalizeAndRender = (input) => gen(function* () {
59832
60044
  lintCacheHitFileCount,
59833
60045
  lintCacheTotalFileCount
59834
60046
  } : {},
60047
+ ...deadCodeCacheHit !== null ? { deadCodeCacheHit } : {},
59835
60048
  ...baselineDelta ? { baselineDelta } : {}
59836
60049
  });
59837
60050
  if (options.suppressRendering) return buildResult();
@@ -63678,4 +63891,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
63678
63891
  export {};
63679
63892
 
63680
63893
  //# sourceMappingURL=cli.js.map
63681
- //# debugId=2921b8ff-14cf-5604-8ae5-b65b4f1347fc
63894
+ //# debugId=e34cb22b-7751-5b1a-8422-6c5eb384dbef
package/dist/index.d.ts CHANGED
@@ -9743,6 +9743,14 @@ interface InspectResult {
9743
9743
  */
9744
9744
  lintCacheHitFileCount?: number | null;
9745
9745
  lintCacheTotalFileCount?: number | null;
9746
+ /**
9747
+ * Dead-code result cache outcome: `true` when the pass replayed a cached
9748
+ * result (the analysis never ran), `false` on a fresh analysis. Absent when
9749
+ * the pass never consulted the cache — dead-code skipped, the cache
9750
+ * disabled, or a whole-repo cache replay where no analysis ran. The CLI
9751
+ * projects it onto the Sentry wide event as `deadCode.cacheHit`.
9752
+ */
9753
+ deadCodeCacheHit?: boolean | null;
9746
9754
  /**
9747
9755
  * Present only for a baseline run (`InspectOptions.baseline` set). The
9748
9756
  * `diagnostics` above are then the *introduced* findings only; this
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]="8565c6ea-39f3-51e6-895a-ae9f7c026d0e")}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]="367db57b-e5c7-577e-9efa-ed68817a8271")}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";
@@ -32927,6 +32927,7 @@ const SOCKET_PACKAGE_PAGE_BASE = "https://socket.dev/npm/package";
32927
32927
  const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
32928
32928
  const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
32929
32929
  const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
32930
+ const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
32930
32931
  const SUPPLY_CHAIN_PLUGIN = "socket";
32931
32932
  const SUPPLY_CHAIN_RULE = "low-supply-chain-score";
32932
32933
  const SUPPLY_CHAIN_CATEGORY = "Security";
@@ -35613,6 +35614,25 @@ var PerFileLintCacheEnabled = class extends Reference("react-doctor/PerFileLintC
35613
35614
  if (CACHE_DISABLED_VALUES.has(noFileCache)) return false;
35614
35615
  return true;
35615
35616
  } }) {};
35617
+ /**
35618
+ * Whether the whole-project dead-code result cache
35619
+ * (`dead-code/dead-code-result-cache.ts`) is active. Defaults ON — a rescan
35620
+ * whose inputs (source tree, manifests, configs, analyzer version) are
35621
+ * unchanged replays the stored diagnostics instead of re-running the
35622
+ * analysis worker. Opt-OUT, two knobs (matching the per-file lint cache):
35623
+ *
35624
+ * - `REACT_DOCTOR_NO_CACHE` — the global off-switch.
35625
+ * - `REACT_DOCTOR_NO_DEAD_CODE_CACHE` — granular: bust only this cache.
35626
+ *
35627
+ * Tests override via `Layer.succeed(DeadCodeResultCacheEnabled, false)`.
35628
+ */
35629
+ var DeadCodeResultCacheEnabled = class extends Reference("react-doctor/DeadCodeResultCacheEnabled", { defaultValue: () => {
35630
+ const noCache = process.env["REACT_DOCTOR_NO_CACHE"]?.toLowerCase() ?? "";
35631
+ const noDeadCodeCache = process.env["REACT_DOCTOR_NO_DEAD_CODE_CACHE"]?.toLowerCase() ?? "";
35632
+ if (CACHE_DISABLED_VALUES.has(noCache)) return false;
35633
+ if (CACHE_DISABLED_VALUES.has(noDeadCodeCache)) return false;
35634
+ return true;
35635
+ } }) {};
35616
35636
  const DIAGNOSTIC_SURFACES = [
35617
35637
  "cli",
35618
35638
  "prComment",
@@ -37301,6 +37321,108 @@ const collectDeadCodeIgnorePatterns = (rootDirectory) => {
37301
37321
  return [...seen].filter((pattern) => pattern.length > 0);
37302
37322
  };
37303
37323
  const collectDeadCodeEntryPatterns = (rootDirectory) => [...new Set(collectKnipPatterns(rootDirectory, "entry"))].filter((pattern) => pattern.length > 0);
37324
+ const atomicWriteJson = (filePath, value) => {
37325
+ try {
37326
+ NFS.mkdirSync(Path.dirname(filePath), { recursive: true });
37327
+ const temporaryPath = `${filePath}.${process.pid}.tmp`;
37328
+ NFS.writeFileSync(temporaryPath, JSON.stringify(value));
37329
+ NFS.renameSync(temporaryPath, filePath);
37330
+ } catch {
37331
+ return;
37332
+ }
37333
+ };
37334
+ const failOpenReadJson = (filePath, fallback) => {
37335
+ try {
37336
+ return JSON.parse(NFS.readFileSync(filePath, "utf8"));
37337
+ } catch {
37338
+ return fallback;
37339
+ }
37340
+ };
37341
+ const ANALYZED_FILE_EXTENSIONS = new Set([
37342
+ ".ts",
37343
+ ".tsx",
37344
+ ".js",
37345
+ ".jsx",
37346
+ ".mts",
37347
+ ".mjs",
37348
+ ".cts",
37349
+ ".cjs",
37350
+ ".mdx",
37351
+ ".astro",
37352
+ ".graphql",
37353
+ ".gql",
37354
+ ".css",
37355
+ ".scss",
37356
+ ".vue",
37357
+ ".svelte"
37358
+ ]);
37359
+ const ANALYZED_MANIFEST_NAMES = new Set([
37360
+ "package.json",
37361
+ "knip.json",
37362
+ "app.json",
37363
+ "pnpm-workspace.yaml",
37364
+ "lerna.json",
37365
+ "pnpm-lock.yaml",
37366
+ "package-lock.json",
37367
+ "yarn.lock",
37368
+ "bun.lock",
37369
+ "bun.lockb",
37370
+ "deno.lock",
37371
+ ".gitignore"
37372
+ ]);
37373
+ const isTsConfigLikeFile = (fileName) => (fileName.startsWith("tsconfig") || fileName.startsWith("jsconfig")) && fileName.endsWith(".json");
37374
+ const isFingerprintedFile = (fileName) => ANALYZED_FILE_EXTENSIONS.has(Path.extname(fileName).toLowerCase()) || ANALYZED_MANIFEST_NAMES.has(fileName) || isTsConfigLikeFile(fileName);
37375
+ const collectAnalyzedFileFingerprints = (rootDirectory) => {
37376
+ const fingerprints = [];
37377
+ for (const { absolutePath, name } of walkSourceTreeFiles(rootDirectory)) {
37378
+ if (!isFingerprintedFile(name)) continue;
37379
+ try {
37380
+ const fileStat = NFS.statSync(absolutePath);
37381
+ const relativePath = Path.relative(rootDirectory, absolutePath).replace(/\\/g, "/");
37382
+ fingerprints.push(`${relativePath}:${fileStat.mtimeMs}:${fileStat.size}`);
37383
+ } catch {}
37384
+ }
37385
+ return fingerprints.sort();
37386
+ };
37387
+ const bundledRequire$2 = createRequire(import.meta.url);
37388
+ const resolveDeslopVersion = () => {
37389
+ try {
37390
+ const packageJson = JSON.parse(NFS.readFileSync(bundledRequire$2.resolve("deslop-js/package.json"), "utf8"));
37391
+ return isRecord(packageJson) && typeof packageJson.version === "string" ? packageJson.version : "unknown";
37392
+ } catch {
37393
+ return "unknown";
37394
+ }
37395
+ };
37396
+ const computeDeadCodeCacheKey = (input) => crypto.createHash("sha1").update(JSON.stringify({
37397
+ schemaVersion: 1,
37398
+ deslopVersion: resolveDeslopVersion(),
37399
+ deslopJsModuleSpecifier: input.deslopJsModuleSpecifier,
37400
+ entryPatterns: input.entryPatterns,
37401
+ ignorePatterns: input.ignorePatterns,
37402
+ tsConfigFile: input.tsConfigPath === void 0 ? null : Path.relative(input.rootDirectory, input.tsConfigPath).replace(/\\/g, "/"),
37403
+ files: collectAnalyzedFileFingerprints(input.rootDirectory)
37404
+ })).digest("hex");
37405
+ const validateDiagnostic$1 = decodeUnknownSync(Diagnostic);
37406
+ const decodeCachedDiagnostics = (raw) => {
37407
+ try {
37408
+ for (const entry of raw) validateDiagnostic$1(entry);
37409
+ return raw;
37410
+ } catch {
37411
+ return null;
37412
+ }
37413
+ };
37414
+ const lookupDeadCodeResultCache = (cacheDirectory, cacheKey) => {
37415
+ const persisted = failOpenReadJson(Path.join(cacheDirectory, DEAD_CODE_CACHE_FILENAME), null);
37416
+ if (persisted === null || !isRecord(persisted) || persisted.version !== 1 || persisted.key !== cacheKey || !Array.isArray(persisted.diagnostics)) return null;
37417
+ return decodeCachedDiagnostics(persisted.diagnostics);
37418
+ };
37419
+ const storeDeadCodeResultCache = (cacheDirectory, cacheKey, diagnostics) => {
37420
+ atomicWriteJson(Path.join(cacheDirectory, DEAD_CODE_CACHE_FILENAME), {
37421
+ version: 1,
37422
+ key: cacheKey,
37423
+ diagnostics
37424
+ });
37425
+ };
37304
37426
  const readSystemFacts = () => ({
37305
37427
  availableCores: os.availableParallelism(),
37306
37428
  totalMemoryBytes: os.totalmem(),
@@ -37369,6 +37491,21 @@ const withDeadCodeWorkerSlot = async (task, abortSignal) => {
37369
37491
  releaseSlot();
37370
37492
  }
37371
37493
  };
37494
+ const projectCacheSubdir = (projectDirectory) => crypto.createHash("sha256").update(projectDirectory).digest("hex").slice(0, 16);
37495
+ const resolveReactDoctorCacheDir = (projectDirectory) => {
37496
+ const cacheDirOverride = process.env["REACT_DOCTOR_CACHE_DIR"]?.trim();
37497
+ if (cacheDirOverride) return Path.join(cacheDirOverride, projectCacheSubdir(projectDirectory));
37498
+ const nodeModulesDirectory = Path.join(projectDirectory, "node_modules");
37499
+ if (NFS.existsSync(nodeModulesDirectory)) return Path.join(nodeModulesDirectory, ".cache", "react-doctor");
37500
+ return Path.join(os.tmpdir(), `react-doctor-cache-${resolveUserCacheScope()}`, projectCacheSubdir(projectDirectory));
37501
+ };
37502
+ const resolveUserCacheScope = () => {
37503
+ try {
37504
+ return String(process.getuid?.() ?? os.userInfo().username);
37505
+ } catch {
37506
+ return "shared";
37507
+ }
37508
+ };
37372
37509
  /**
37373
37510
  * Resolves a path to its canonical, symlink-free form, falling back to
37374
37511
  * the input when it cannot be realpath'd (broken symlink, permission
@@ -37664,13 +37801,27 @@ const checkDeadCode = async (options) => {
37664
37801
  if (!NFS.existsSync(Path.join(rootDirectory, "package.json"))) return [];
37665
37802
  const entryPatterns = collectDeadCodeEntryPatterns(rootDirectory);
37666
37803
  const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory);
37804
+ const tsConfigPath = resolveTsConfigPath(rootDirectory);
37805
+ const deslopJsModuleSpecifier = options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js");
37806
+ const cacheKey = options.cacheEnabled ? computeDeadCodeCacheKey({
37807
+ rootDirectory,
37808
+ entryPatterns,
37809
+ ignorePatterns,
37810
+ tsConfigPath,
37811
+ deslopJsModuleSpecifier
37812
+ }) : null;
37813
+ if (cacheKey !== null) {
37814
+ const cachedDiagnostics = lookupDeadCodeResultCache(resolveReactDoctorCacheDir(rootDirectory), cacheKey);
37815
+ options.onCacheOutcome?.(cachedDiagnostics !== null);
37816
+ if (cachedDiagnostics !== null) return [...cachedDiagnostics];
37817
+ }
37667
37818
  const spawnAndRun = () => {
37668
37819
  return runDeadCodeWorkerWithTimeout((options.createWorker ?? createDeadCodeWorker)({
37669
37820
  rootDirectory,
37670
37821
  entryPatterns,
37671
- tsConfigPath: resolveTsConfigPath(rootDirectory),
37822
+ tsConfigPath,
37672
37823
  ignorePatterns,
37673
- deslopJsModuleSpecifier: options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js"),
37824
+ deslopJsModuleSpecifier,
37674
37825
  parseConcurrency: options.parseConcurrency
37675
37826
  }), options.workerTimeoutMs ?? 12e4, options.abortSignal);
37676
37827
  };
@@ -37731,6 +37882,7 @@ const checkDeadCode = async (options) => {
37731
37882
  category: DEAD_CODE_CATEGORY
37732
37883
  });
37733
37884
  }
37885
+ if (cacheKey !== null) storeDeadCodeResultCache(resolveReactDoctorCacheDir(rootDirectory), cacheKey, diagnostics);
37734
37886
  return diagnostics;
37735
37887
  };
37736
37888
  const DEAD_CODE_RULE_KEY_PREFIX = `${DEAD_CODE_PLUGIN}/`;
@@ -37833,12 +37985,15 @@ var Config = class Config extends Service()("react-doctor/Config") {
37833
37985
  */
37834
37986
  var DeadCode = class DeadCode extends Service()("react-doctor/DeadCode") {
37835
37987
  static layerNode = succeed$3(DeadCode, DeadCode.of({ run: (input) => unwrap(fn("DeadCode.run")(function* () {
37988
+ const cacheEnabled = yield* DeadCodeResultCacheEnabled;
37836
37989
  return yield* tryPromise({
37837
37990
  try: (signal) => checkDeadCode({
37838
37991
  rootDirectory: input.rootDirectory,
37839
37992
  parseConcurrency: input.parseConcurrency,
37840
37993
  workerTimeoutMs: input.workerTimeoutMs,
37841
- abortSignal: signal
37994
+ abortSignal: signal,
37995
+ cacheEnabled,
37996
+ onCacheOutcome: input.onCacheOutcome
37842
37997
  }),
37843
37998
  catch: (cause) => new ReactDoctorError({ reason: new DeadCodeAnalysisFailed({ cause }) })
37844
37999
  }).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)));
@@ -38764,23 +38919,6 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
38764
38919
  }
38765
38920
  };
38766
38921
  };
38767
- const atomicWriteJson = (filePath, value) => {
38768
- try {
38769
- NFS.mkdirSync(Path.dirname(filePath), { recursive: true });
38770
- const temporaryPath = `${filePath}.${process.pid}.tmp`;
38771
- NFS.writeFileSync(temporaryPath, JSON.stringify(value));
38772
- NFS.renameSync(temporaryPath, filePath);
38773
- } catch {
38774
- return;
38775
- }
38776
- };
38777
- const failOpenReadJson = (filePath, fallback) => {
38778
- try {
38779
- return JSON.parse(NFS.readFileSync(filePath, "utf8"));
38780
- } catch {
38781
- return fallback;
38782
- }
38783
- };
38784
38922
  const validateDiagnostic = decodeUnknownSync(Diagnostic);
38785
38923
  const decodeFileDiagnostics = (raw) => {
38786
38924
  if (!Array.isArray(raw)) return null;
@@ -39908,21 +40046,6 @@ const planLintBatches = ({ baseArgs, files, sizeByFile }) => {
39908
40046
  }
39909
40047
  return batches.filter((batch) => batch.fileIndexes.length > 0).map((batch) => batch.fileIndexes.sort((leftIndex, rightIndex) => leftIndex - rightIndex).map((fileIndex) => files[fileIndex]));
39910
40048
  };
39911
- const projectCacheSubdir = (projectDirectory) => crypto.createHash("sha256").update(projectDirectory).digest("hex").slice(0, 16);
39912
- const resolveReactDoctorCacheDir = (projectDirectory) => {
39913
- const cacheDirOverride = process.env["REACT_DOCTOR_CACHE_DIR"]?.trim();
39914
- if (cacheDirOverride) return Path.join(cacheDirOverride, projectCacheSubdir(projectDirectory));
39915
- const nodeModulesDirectory = Path.join(projectDirectory, "node_modules");
39916
- if (NFS.existsSync(nodeModulesDirectory)) return Path.join(nodeModulesDirectory, ".cache", "react-doctor");
39917
- return Path.join(os.tmpdir(), `react-doctor-cache-${resolveUserCacheScope()}`, projectCacheSubdir(projectDirectory));
39918
- };
39919
- const resolveUserCacheScope = () => {
39920
- try {
39921
- return String(process.getuid?.() ?? os.userInfo().username);
39922
- } catch {
39923
- return "shared";
39924
- }
39925
- };
39926
40049
  /**
39927
40050
  * Atomically (re)writes the generated oxlintrc.json. Used twice in
39928
40051
  * the runner: once for the primary scan, once for the
@@ -41049,7 +41172,10 @@ const runInspect = (input, hooks = {}) => gen(function* () {
41049
41172
  const buildCollectDeadCode = (deadCodeTimeout) => runCollect(applyPerElementPipeline(deadCodeService.run({
41050
41173
  rootDirectory: scanDirectory,
41051
41174
  parseConcurrency: deadCodeParseConcurrency,
41052
- workerTimeoutMs: deadCodeTimeout.workerTimeoutMs
41175
+ workerTimeoutMs: deadCodeTimeout.workerTimeoutMs,
41176
+ onCacheOutcome: (didHitCache) => {
41177
+ deadCodeCacheHit = didHitCache;
41178
+ }
41053
41179
  }).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
41054
41180
  yield* set(deadCodeFailure, {
41055
41181
  didFail: true,
@@ -41077,6 +41203,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
41077
41203
  let lastReportedTotalFileCount = 0;
41078
41204
  let lintCacheHitFileCount = null;
41079
41205
  let lintCacheTotalFileCount = null;
41206
+ let deadCodeCacheHit = null;
41080
41207
  const baseLintStream = linterService.run({
41081
41208
  rootDirectory: scanDirectory,
41082
41209
  project,
@@ -41206,6 +41333,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
41206
41333
  securityScanFailed,
41207
41334
  lintCacheHitFileCount,
41208
41335
  lintCacheTotalFileCount,
41336
+ deadCodeCacheHit: lintFailureState.didFail ? null : deadCodeCacheHit,
41209
41337
  suppressedRuleCounts: transform.summarizeSuppressions()
41210
41338
  };
41211
41339
  }).pipe(withSpan("runInspect", { attributes: {
@@ -41846,4 +41974,4 @@ const toJsonReport = (result, options) => buildJsonReport({
41846
41974
  export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
41847
41975
 
41848
41976
  //# sourceMappingURL=index.js.map
41849
- //# debugId=8565c6ea-39f3-51e6-895a-ae9f7c026d0e
41977
+ //# debugId=367db57b-e5c7-577e-9efa-ed68817a8271
package/dist/lsp.js CHANGED
@@ -32986,6 +32986,7 @@ const SOCKET_PACKAGE_PAGE_BASE = "https://socket.dev/npm/package";
32986
32986
  const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
32987
32987
  const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
32988
32988
  const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
32989
+ const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
32989
32990
  const SUPPLY_CHAIN_PLUGIN = "socket";
32990
32991
  const SUPPLY_CHAIN_RULE = "low-supply-chain-score";
32991
32992
  const SUPPLY_CHAIN_CATEGORY = "Security";
@@ -35646,6 +35647,25 @@ var PerFileLintCacheEnabled = class extends Reference("react-doctor/PerFileLintC
35646
35647
  if (CACHE_DISABLED_VALUES.has(noFileCache)) return false;
35647
35648
  return true;
35648
35649
  } }) {};
35650
+ /**
35651
+ * Whether the whole-project dead-code result cache
35652
+ * (`dead-code/dead-code-result-cache.ts`) is active. Defaults ON — a rescan
35653
+ * whose inputs (source tree, manifests, configs, analyzer version) are
35654
+ * unchanged replays the stored diagnostics instead of re-running the
35655
+ * analysis worker. Opt-OUT, two knobs (matching the per-file lint cache):
35656
+ *
35657
+ * - `REACT_DOCTOR_NO_CACHE` — the global off-switch.
35658
+ * - `REACT_DOCTOR_NO_DEAD_CODE_CACHE` — granular: bust only this cache.
35659
+ *
35660
+ * Tests override via `Layer.succeed(DeadCodeResultCacheEnabled, false)`.
35661
+ */
35662
+ var DeadCodeResultCacheEnabled = class extends Reference("react-doctor/DeadCodeResultCacheEnabled", { defaultValue: () => {
35663
+ const noCache = process.env["REACT_DOCTOR_NO_CACHE"]?.toLowerCase() ?? "";
35664
+ const noDeadCodeCache = process.env["REACT_DOCTOR_NO_DEAD_CODE_CACHE"]?.toLowerCase() ?? "";
35665
+ if (CACHE_DISABLED_VALUES.has(noCache)) return false;
35666
+ if (CACHE_DISABLED_VALUES.has(noDeadCodeCache)) return false;
35667
+ return true;
35668
+ } }) {};
35649
35669
  const DIAGNOSTIC_SURFACES = [
35650
35670
  "cli",
35651
35671
  "prComment",
@@ -37286,6 +37306,108 @@ const collectDeadCodeIgnorePatterns = (rootDirectory) => {
37286
37306
  return [...seen].filter((pattern) => pattern.length > 0);
37287
37307
  };
37288
37308
  const collectDeadCodeEntryPatterns = (rootDirectory) => [...new Set(collectKnipPatterns(rootDirectory, "entry"))].filter((pattern) => pattern.length > 0);
37309
+ const atomicWriteJson = (filePath, value) => {
37310
+ try {
37311
+ NFS.mkdirSync(Path.dirname(filePath), { recursive: true });
37312
+ const temporaryPath = `${filePath}.${process.pid}.tmp`;
37313
+ NFS.writeFileSync(temporaryPath, JSON.stringify(value));
37314
+ NFS.renameSync(temporaryPath, filePath);
37315
+ } catch {
37316
+ return;
37317
+ }
37318
+ };
37319
+ const failOpenReadJson = (filePath, fallback) => {
37320
+ try {
37321
+ return JSON.parse(NFS.readFileSync(filePath, "utf8"));
37322
+ } catch {
37323
+ return fallback;
37324
+ }
37325
+ };
37326
+ const ANALYZED_FILE_EXTENSIONS = new Set([
37327
+ ".ts",
37328
+ ".tsx",
37329
+ ".js",
37330
+ ".jsx",
37331
+ ".mts",
37332
+ ".mjs",
37333
+ ".cts",
37334
+ ".cjs",
37335
+ ".mdx",
37336
+ ".astro",
37337
+ ".graphql",
37338
+ ".gql",
37339
+ ".css",
37340
+ ".scss",
37341
+ ".vue",
37342
+ ".svelte"
37343
+ ]);
37344
+ const ANALYZED_MANIFEST_NAMES = new Set([
37345
+ "package.json",
37346
+ "knip.json",
37347
+ "app.json",
37348
+ "pnpm-workspace.yaml",
37349
+ "lerna.json",
37350
+ "pnpm-lock.yaml",
37351
+ "package-lock.json",
37352
+ "yarn.lock",
37353
+ "bun.lock",
37354
+ "bun.lockb",
37355
+ "deno.lock",
37356
+ ".gitignore"
37357
+ ]);
37358
+ const isTsConfigLikeFile = (fileName) => (fileName.startsWith("tsconfig") || fileName.startsWith("jsconfig")) && fileName.endsWith(".json");
37359
+ const isFingerprintedFile = (fileName) => ANALYZED_FILE_EXTENSIONS.has(Path.extname(fileName).toLowerCase()) || ANALYZED_MANIFEST_NAMES.has(fileName) || isTsConfigLikeFile(fileName);
37360
+ const collectAnalyzedFileFingerprints = (rootDirectory) => {
37361
+ const fingerprints = [];
37362
+ for (const { absolutePath, name } of walkSourceTreeFiles(rootDirectory)) {
37363
+ if (!isFingerprintedFile(name)) continue;
37364
+ try {
37365
+ const fileStat = NFS.statSync(absolutePath);
37366
+ const relativePath = Path.relative(rootDirectory, absolutePath).replace(/\\/g, "/");
37367
+ fingerprints.push(`${relativePath}:${fileStat.mtimeMs}:${fileStat.size}`);
37368
+ } catch {}
37369
+ }
37370
+ return fingerprints.sort();
37371
+ };
37372
+ const bundledRequire$2 = createRequire(import.meta.url);
37373
+ const resolveDeslopVersion = () => {
37374
+ try {
37375
+ const packageJson = JSON.parse(NFS.readFileSync(bundledRequire$2.resolve("deslop-js/package.json"), "utf8"));
37376
+ return isRecord(packageJson) && typeof packageJson.version === "string" ? packageJson.version : "unknown";
37377
+ } catch {
37378
+ return "unknown";
37379
+ }
37380
+ };
37381
+ const computeDeadCodeCacheKey = (input) => crypto.createHash("sha1").update(JSON.stringify({
37382
+ schemaVersion: 1,
37383
+ deslopVersion: resolveDeslopVersion(),
37384
+ deslopJsModuleSpecifier: input.deslopJsModuleSpecifier,
37385
+ entryPatterns: input.entryPatterns,
37386
+ ignorePatterns: input.ignorePatterns,
37387
+ tsConfigFile: input.tsConfigPath === void 0 ? null : Path.relative(input.rootDirectory, input.tsConfigPath).replace(/\\/g, "/"),
37388
+ files: collectAnalyzedFileFingerprints(input.rootDirectory)
37389
+ })).digest("hex");
37390
+ const validateDiagnostic$1 = decodeUnknownSync(Diagnostic);
37391
+ const decodeCachedDiagnostics = (raw) => {
37392
+ try {
37393
+ for (const entry of raw) validateDiagnostic$1(entry);
37394
+ return raw;
37395
+ } catch {
37396
+ return null;
37397
+ }
37398
+ };
37399
+ const lookupDeadCodeResultCache = (cacheDirectory, cacheKey) => {
37400
+ const persisted = failOpenReadJson(Path.join(cacheDirectory, DEAD_CODE_CACHE_FILENAME), null);
37401
+ if (persisted === null || !isRecord(persisted) || persisted.version !== 1 || persisted.key !== cacheKey || !Array.isArray(persisted.diagnostics)) return null;
37402
+ return decodeCachedDiagnostics(persisted.diagnostics);
37403
+ };
37404
+ const storeDeadCodeResultCache = (cacheDirectory, cacheKey, diagnostics) => {
37405
+ atomicWriteJson(Path.join(cacheDirectory, DEAD_CODE_CACHE_FILENAME), {
37406
+ version: 1,
37407
+ key: cacheKey,
37408
+ diagnostics
37409
+ });
37410
+ };
37289
37411
  const readSystemFacts = () => ({
37290
37412
  availableCores: os.availableParallelism(),
37291
37413
  totalMemoryBytes: os.totalmem(),
@@ -37354,6 +37476,21 @@ const withDeadCodeWorkerSlot = async (task, abortSignal) => {
37354
37476
  releaseSlot();
37355
37477
  }
37356
37478
  };
37479
+ const projectCacheSubdir = (projectDirectory) => crypto.createHash("sha256").update(projectDirectory).digest("hex").slice(0, 16);
37480
+ const resolveReactDoctorCacheDir = (projectDirectory) => {
37481
+ const cacheDirOverride = process.env["REACT_DOCTOR_CACHE_DIR"]?.trim();
37482
+ if (cacheDirOverride) return Path.join(cacheDirOverride, projectCacheSubdir(projectDirectory));
37483
+ const nodeModulesDirectory = Path.join(projectDirectory, "node_modules");
37484
+ if (NFS.existsSync(nodeModulesDirectory)) return Path.join(nodeModulesDirectory, ".cache", "react-doctor");
37485
+ return Path.join(os.tmpdir(), `react-doctor-cache-${resolveUserCacheScope()}`, projectCacheSubdir(projectDirectory));
37486
+ };
37487
+ const resolveUserCacheScope = () => {
37488
+ try {
37489
+ return String(process.getuid?.() ?? os.userInfo().username);
37490
+ } catch {
37491
+ return "shared";
37492
+ }
37493
+ };
37357
37494
  /**
37358
37495
  * Resolves a path to its canonical, symlink-free form, falling back to
37359
37496
  * the input when it cannot be realpath'd (broken symlink, permission
@@ -37649,13 +37786,27 @@ const checkDeadCode = async (options) => {
37649
37786
  if (!NFS.existsSync(Path.join(rootDirectory, "package.json"))) return [];
37650
37787
  const entryPatterns = collectDeadCodeEntryPatterns(rootDirectory);
37651
37788
  const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory);
37789
+ const tsConfigPath = resolveTsConfigPath(rootDirectory);
37790
+ const deslopJsModuleSpecifier = options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js");
37791
+ const cacheKey = options.cacheEnabled ? computeDeadCodeCacheKey({
37792
+ rootDirectory,
37793
+ entryPatterns,
37794
+ ignorePatterns,
37795
+ tsConfigPath,
37796
+ deslopJsModuleSpecifier
37797
+ }) : null;
37798
+ if (cacheKey !== null) {
37799
+ const cachedDiagnostics = lookupDeadCodeResultCache(resolveReactDoctorCacheDir(rootDirectory), cacheKey);
37800
+ options.onCacheOutcome?.(cachedDiagnostics !== null);
37801
+ if (cachedDiagnostics !== null) return [...cachedDiagnostics];
37802
+ }
37652
37803
  const spawnAndRun = () => {
37653
37804
  return runDeadCodeWorkerWithTimeout((options.createWorker ?? createDeadCodeWorker)({
37654
37805
  rootDirectory,
37655
37806
  entryPatterns,
37656
- tsConfigPath: resolveTsConfigPath(rootDirectory),
37807
+ tsConfigPath,
37657
37808
  ignorePatterns,
37658
- deslopJsModuleSpecifier: options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js"),
37809
+ deslopJsModuleSpecifier,
37659
37810
  parseConcurrency: options.parseConcurrency
37660
37811
  }), options.workerTimeoutMs ?? 12e4, options.abortSignal);
37661
37812
  };
@@ -37716,6 +37867,7 @@ const checkDeadCode = async (options) => {
37716
37867
  category: DEAD_CODE_CATEGORY
37717
37868
  });
37718
37869
  }
37870
+ if (cacheKey !== null) storeDeadCodeResultCache(resolveReactDoctorCacheDir(rootDirectory), cacheKey, diagnostics);
37719
37871
  return diagnostics;
37720
37872
  };
37721
37873
  const DEAD_CODE_RULE_KEY_PREFIX = `${DEAD_CODE_PLUGIN}/`;
@@ -37818,12 +37970,15 @@ var Config = class Config extends Service()("react-doctor/Config") {
37818
37970
  */
37819
37971
  var DeadCode = class DeadCode extends Service()("react-doctor/DeadCode") {
37820
37972
  static layerNode = succeed$3(DeadCode, DeadCode.of({ run: (input) => unwrap(fn("DeadCode.run")(function* () {
37973
+ const cacheEnabled = yield* DeadCodeResultCacheEnabled;
37821
37974
  return yield* tryPromise({
37822
37975
  try: (signal) => checkDeadCode({
37823
37976
  rootDirectory: input.rootDirectory,
37824
37977
  parseConcurrency: input.parseConcurrency,
37825
37978
  workerTimeoutMs: input.workerTimeoutMs,
37826
- abortSignal: signal
37979
+ abortSignal: signal,
37980
+ cacheEnabled,
37981
+ onCacheOutcome: input.onCacheOutcome
37827
37982
  }),
37828
37983
  catch: (cause) => new ReactDoctorError({ reason: new DeadCodeAnalysisFailed({ cause }) })
37829
37984
  }).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)));
@@ -38749,23 +38904,6 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
38749
38904
  }
38750
38905
  };
38751
38906
  };
38752
- const atomicWriteJson = (filePath, value) => {
38753
- try {
38754
- NFS.mkdirSync(Path.dirname(filePath), { recursive: true });
38755
- const temporaryPath = `${filePath}.${process.pid}.tmp`;
38756
- NFS.writeFileSync(temporaryPath, JSON.stringify(value));
38757
- NFS.renameSync(temporaryPath, filePath);
38758
- } catch {
38759
- return;
38760
- }
38761
- };
38762
- const failOpenReadJson = (filePath, fallback) => {
38763
- try {
38764
- return JSON.parse(NFS.readFileSync(filePath, "utf8"));
38765
- } catch {
38766
- return fallback;
38767
- }
38768
- };
38769
38907
  const validateDiagnostic = decodeUnknownSync(Diagnostic);
38770
38908
  const decodeFileDiagnostics = (raw) => {
38771
38909
  if (!Array.isArray(raw)) return null;
@@ -39893,21 +40031,6 @@ const planLintBatches = ({ baseArgs, files, sizeByFile }) => {
39893
40031
  }
39894
40032
  return batches.filter((batch) => batch.fileIndexes.length > 0).map((batch) => batch.fileIndexes.sort((leftIndex, rightIndex) => leftIndex - rightIndex).map((fileIndex) => files[fileIndex]));
39895
40033
  };
39896
- const projectCacheSubdir = (projectDirectory) => crypto.createHash("sha256").update(projectDirectory).digest("hex").slice(0, 16);
39897
- const resolveReactDoctorCacheDir = (projectDirectory) => {
39898
- const cacheDirOverride = process.env["REACT_DOCTOR_CACHE_DIR"]?.trim();
39899
- if (cacheDirOverride) return Path.join(cacheDirOverride, projectCacheSubdir(projectDirectory));
39900
- const nodeModulesDirectory = Path.join(projectDirectory, "node_modules");
39901
- if (NFS.existsSync(nodeModulesDirectory)) return Path.join(nodeModulesDirectory, ".cache", "react-doctor");
39902
- return Path.join(os.tmpdir(), `react-doctor-cache-${resolveUserCacheScope()}`, projectCacheSubdir(projectDirectory));
39903
- };
39904
- const resolveUserCacheScope = () => {
39905
- try {
39906
- return String(process.getuid?.() ?? os.userInfo().username);
39907
- } catch {
39908
- return "shared";
39909
- }
39910
- };
39911
40034
  /**
39912
40035
  * Atomically (re)writes the generated oxlintrc.json. Used twice in
39913
40036
  * the runner: once for the primary scan, once for the
@@ -41034,7 +41157,10 @@ const runInspect = (input, hooks = {}) => gen(function* () {
41034
41157
  const buildCollectDeadCode = (deadCodeTimeout) => runCollect(applyPerElementPipeline(deadCodeService.run({
41035
41158
  rootDirectory: scanDirectory,
41036
41159
  parseConcurrency: deadCodeParseConcurrency,
41037
- workerTimeoutMs: deadCodeTimeout.workerTimeoutMs
41160
+ workerTimeoutMs: deadCodeTimeout.workerTimeoutMs,
41161
+ onCacheOutcome: (didHitCache) => {
41162
+ deadCodeCacheHit = didHitCache;
41163
+ }
41038
41164
  }).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
41039
41165
  yield* set(deadCodeFailure, {
41040
41166
  didFail: true,
@@ -41062,6 +41188,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
41062
41188
  let lastReportedTotalFileCount = 0;
41063
41189
  let lintCacheHitFileCount = null;
41064
41190
  let lintCacheTotalFileCount = null;
41191
+ let deadCodeCacheHit = null;
41065
41192
  const baseLintStream = linterService.run({
41066
41193
  rootDirectory: scanDirectory,
41067
41194
  project,
@@ -41191,6 +41318,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
41191
41318
  securityScanFailed,
41192
41319
  lintCacheHitFileCount,
41193
41320
  lintCacheTotalFileCount,
41321
+ deadCodeCacheHit: lintFailureState.didFail ? null : deadCodeCacheHit,
41194
41322
  suppressedRuleCounts: transform.summarizeSuppressions()
41195
41323
  };
41196
41324
  }).pipe(withSpan("runInspect", { attributes: {
@@ -43570,5 +43698,5 @@ const startLanguageServer = () => {
43570
43698
  };
43571
43699
  //#endregion
43572
43700
  export { startLanguageServer };
43573
- !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]="94a57ec3-d5a9-52ea-a743-e91d996065b8")}catch(e){}}();
43574
- //# debugId=94a57ec3-d5a9-52ea-a743-e91d996065b8
43701
+ !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]="46e851cc-7b86-5c73-afb0-e09296beab28")}catch(e){}}();
43702
+ //# debugId=46e851cc-7b86-5c73-afb0-e09296beab28
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-doctor",
3
- "version": "0.6.3-dev.8325343",
3
+ "version": "0.6.3-dev.8c004f0",
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.8325343"
69
+ "oxlint-plugin-react-doctor": "0.6.3-dev.8c004f0"
70
70
  },
71
71
  "devDependencies": {
72
72
  "@types/babel__code-frame": "^7.27.0",