react-doctor 0.2.9 → 0.2.11-dev.f036b0f

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.
@@ -2272,11 +2272,13 @@ const FRAMEWORK_DISPLAY_NAMES = {
2272
2272
  gatsby: "Gatsby",
2273
2273
  expo: "Expo",
2274
2274
  "react-native": "React Native",
2275
+ preact: "Preact",
2275
2276
  unknown: "React"
2276
2277
  };
2277
2278
  const formatFrameworkName = (framework) => FRAMEWORK_DISPLAY_NAMES[framework];
2278
2279
  const detectFramework = (dependencies) => {
2279
2280
  for (const [packageName, frameworkName] of Object.entries(FRAMEWORK_PACKAGES)) if (dependencies[packageName]) return frameworkName;
2281
+ if (dependencies.preact && !dependencies.react) return "preact";
2280
2282
  return "unknown";
2281
2283
  };
2282
2284
  const UPPER_BOUND_COMPARATOR = /<\s*=?\s*\d+(?:\.\d+){0,2}(?:-[^\s,|]+)?/g;
@@ -2695,6 +2697,21 @@ const findDependencyInfoFromMonorepoRoot = (directory) => {
2695
2697
  framework: rootInfo.framework !== "unknown" ? rootInfo.framework : workspaceInfo.framework
2696
2698
  };
2697
2699
  };
2700
+ const someWorkspacePackageJson = (rootDirectory, rootPackageJson, predicate) => {
2701
+ if (predicate(rootPackageJson)) return true;
2702
+ const patterns = getWorkspacePatterns(rootDirectory, rootPackageJson);
2703
+ if (patterns.length === 0) return false;
2704
+ const visitedDirectories = /* @__PURE__ */ new Set();
2705
+ for (const pattern of patterns) {
2706
+ const directories = resolveWorkspaceDirectories(rootDirectory, pattern);
2707
+ for (const workspaceDirectory of directories) {
2708
+ if (visitedDirectories.has(workspaceDirectory)) continue;
2709
+ visitedDirectories.add(workspaceDirectory);
2710
+ if (predicate(readPackageJson(path.join(workspaceDirectory, "package.json")))) return true;
2711
+ }
2712
+ }
2713
+ return false;
2714
+ };
2698
2715
  const NAMES = new Set([
2699
2716
  "react-native",
2700
2717
  "react-native-tvos",
@@ -2725,20 +2742,13 @@ const isPackageJsonReactNativeAware = (packageJson) => {
2725
2742
  if (containsAnyReactNativeDependency(packageJson.optionalDependencies)) return true;
2726
2743
  return false;
2727
2744
  };
2728
- const hasReactNativeWorkspaceAnywhere = (rootDirectory, rootPackageJson) => {
2729
- if (isPackageJsonReactNativeAware(rootPackageJson)) return true;
2730
- const patterns = getWorkspacePatterns(rootDirectory, rootPackageJson);
2731
- if (patterns.length === 0) return false;
2732
- const visitedDirectories = /* @__PURE__ */ new Set();
2733
- for (const pattern of patterns) {
2734
- const directories = resolveWorkspaceDirectories(rootDirectory, pattern);
2735
- for (const workspaceDirectory of directories) {
2736
- if (visitedDirectories.has(workspaceDirectory)) continue;
2737
- visitedDirectories.add(workspaceDirectory);
2738
- if (isPackageJsonReactNativeAware(readPackageJson(path.join(workspaceDirectory, "package.json")))) return true;
2739
- }
2740
- }
2741
- return false;
2745
+ const hasReactNativeWorkspaceAnywhere = (rootDirectory, rootPackageJson) => someWorkspacePackageJson(rootDirectory, rootPackageJson, isPackageJsonReactNativeAware);
2746
+ const getPreactVersion = (packageJson) => {
2747
+ return {
2748
+ ...packageJson.peerDependencies,
2749
+ ...packageJson.dependencies,
2750
+ ...packageJson.devDependencies
2751
+ }.preact ?? null;
2742
2752
  };
2743
2753
  const TANSTACK_QUERY_PACKAGES = new Set([
2744
2754
  "@tanstack/react-query",
@@ -2753,6 +2763,16 @@ const hasTanStackQuery = (packageJson) => {
2753
2763
  };
2754
2764
  return Object.keys(allDependencies).some((packageName) => TANSTACK_QUERY_PACKAGES.has(packageName));
2755
2765
  };
2766
+ const REANIMATED_DEPENDENCY_NAME = "react-native-reanimated";
2767
+ const isPackageJsonReanimatedAware = (packageJson) => {
2768
+ const allDependencies = {
2769
+ ...packageJson.peerDependencies,
2770
+ ...packageJson.dependencies,
2771
+ ...packageJson.devDependencies,
2772
+ ...packageJson.optionalDependencies
2773
+ };
2774
+ return Object.hasOwn(allDependencies, REANIMATED_DEPENDENCY_NAME);
2775
+ };
2756
2776
  const hasUpperBoundOnlyPeerRange = (range) => {
2757
2777
  if (typeof range !== "string") return false;
2758
2778
  const normalizedRange = normalizeDependencyVersion(range);
@@ -2777,7 +2797,8 @@ const resolveEffectiveReactMajor = (reactVersion, packageJson) => {
2777
2797
  const REACT_DEPENDENCY_NAMES = new Set([
2778
2798
  "react",
2779
2799
  "react-native",
2780
- "next"
2800
+ "next",
2801
+ "preact"
2781
2802
  ]);
2782
2803
  const hasReactDependency = (packageJson) => {
2783
2804
  const allDependencies = {
@@ -2838,12 +2859,22 @@ const listManifestWorkspacePackages = (rootDirectory) => {
2838
2859
  const nxPatterns = patterns.length > 0 ? [] : getNxWorkspaceDirectories(rootDirectory);
2839
2860
  return toReactWorkspacePackages((patterns.length > 0 ? patterns : nxPatterns).flatMap((pattern) => resolveWorkspaceDirectories(rootDirectory, pattern)));
2840
2861
  };
2862
+ const NON_PROJECT_DIRECTORIES = new Set([
2863
+ "AppData",
2864
+ "Application Data",
2865
+ "Library"
2866
+ ]);
2867
+ const MAX_SCAN_DEPTH = 6;
2841
2868
  const discoverReactSubprojectsByFilesystem = (rootDirectory) => {
2842
2869
  const packages = [];
2843
- const pendingDirectories = [rootDirectory];
2870
+ const pendingDirectories = [{
2871
+ directory: rootDirectory,
2872
+ depth: 0
2873
+ }];
2844
2874
  while (pendingDirectories.length > 0) {
2845
- const currentDirectory = pendingDirectories.pop();
2846
- if (!currentDirectory) continue;
2875
+ const current = pendingDirectories.pop();
2876
+ if (!current) continue;
2877
+ const { directory: currentDirectory, depth } = current;
2847
2878
  const packageJsonPath = path.join(currentDirectory, "package.json");
2848
2879
  if (isFile(packageJsonPath)) {
2849
2880
  const packageJson = readPackageJson(packageJsonPath);
@@ -2855,10 +2886,14 @@ const discoverReactSubprojectsByFilesystem = (rootDirectory) => {
2855
2886
  });
2856
2887
  }
2857
2888
  }
2889
+ if (depth >= MAX_SCAN_DEPTH) continue;
2858
2890
  const entries = readDirectoryEntries(currentDirectory).toSorted((firstEntry, secondEntry) => firstEntry.name.localeCompare(secondEntry.name));
2859
2891
  for (const entry of entries) {
2860
- if (!entry.isDirectory() || entry.name.startsWith(".") || IGNORED_DIRECTORIES.has(entry.name)) continue;
2861
- pendingDirectories.push(path.join(currentDirectory, entry.name));
2892
+ if (!entry.isDirectory() || entry.name.startsWith(".") || IGNORED_DIRECTORIES.has(entry.name) || NON_PROJECT_DIRECTORIES.has(entry.name)) continue;
2893
+ pendingDirectories.push({
2894
+ directory: path.join(currentDirectory, entry.name),
2895
+ depth: depth + 1
2896
+ });
2862
2897
  }
2863
2898
  }
2864
2899
  return packages;
@@ -2926,6 +2961,8 @@ const discoverProject = (directory) => {
2926
2961
  const hasTypeScript = fs.existsSync(path.join(directory, "tsconfig.json"));
2927
2962
  const sourceFileCount = countSourceFiles(directory);
2928
2963
  const hasReactNativeWorkspace = framework === "expo" || framework === "react-native" || hasReactNativeWorkspaceAnywhere(directory, packageJson);
2964
+ const hasReanimated = hasReactNativeWorkspace && someWorkspacePackageJson(directory, packageJson, isPackageJsonReanimatedAware);
2965
+ const preactVersion = getPreactVersion(packageJson);
2929
2966
  const projectInfo = {
2930
2967
  rootDirectory: directory,
2931
2968
  projectName,
@@ -2936,12 +2973,50 @@ const discoverProject = (directory) => {
2936
2973
  hasTypeScript,
2937
2974
  hasReactCompiler: detectReactCompiler(directory, packageJson),
2938
2975
  hasTanStackQuery: hasTanStackQuery(packageJson),
2976
+ preactVersion,
2977
+ preactMajorVersion: parseReactMajor(preactVersion),
2939
2978
  hasReactNativeWorkspace,
2979
+ hasReanimated,
2940
2980
  sourceFileCount
2941
2981
  };
2942
2982
  cachedProjectInfos.set(directory, projectInfo);
2943
2983
  return projectInfo;
2944
2984
  };
2985
+ const isAnalyzableProject = (project) => project.reactVersion !== null || project.preactVersion !== null;
2986
+ const MAJOR_MINOR_PATTERN = /(\d{1,4})\.(\d{1,4})/;
2987
+ const MAJOR_ONLY_PATTERN = /(\d{1,4})/;
2988
+ const UPPER_BOUND_COMPARATOR_PATTERN = /<=?\s{0,8}\d{1,4}(?:\.\d{1,4}){0,2}(?:-[^\s,|]+)?/g;
2989
+ const parseReactMajorMinor = (reactVersion) => {
2990
+ if (typeof reactVersion !== "string") return null;
2991
+ const trimmed = reactVersion.trim();
2992
+ if (trimmed.length === 0) return null;
2993
+ const lowerBoundsOnly = trimmed.replace(UPPER_BOUND_COMPARATOR_PATTERN, " ").trim();
2994
+ if (lowerBoundsOnly.length === 0) return null;
2995
+ const majorMinorMatch = lowerBoundsOnly.match(MAJOR_MINOR_PATTERN);
2996
+ if (majorMinorMatch) {
2997
+ const major = Number.parseInt(majorMinorMatch[1], 10);
2998
+ const minor = Number.parseInt(majorMinorMatch[2], 10);
2999
+ if (!Number.isFinite(major) || major <= 0) return null;
3000
+ if (!Number.isFinite(minor) || minor < 0) return null;
3001
+ return {
3002
+ major,
3003
+ minor
3004
+ };
3005
+ }
3006
+ const majorOnlyMatch = lowerBoundsOnly.match(MAJOR_ONLY_PATTERN);
3007
+ if (!majorOnlyMatch) return null;
3008
+ const major = Number.parseInt(majorOnlyMatch[1], 10);
3009
+ if (!Number.isFinite(major) || major <= 0) return null;
3010
+ return {
3011
+ major,
3012
+ minor: 0
3013
+ };
3014
+ };
3015
+ const isReactAtLeast = (detected, required) => {
3016
+ if (detected === null) return true;
3017
+ if (detected.major !== required.major) return detected.major > required.major;
3018
+ return detected.minor >= required.minor;
3019
+ };
2945
3020
  const parseTailwindMajorMinor = (tailwindVersion) => {
2946
3021
  if (typeof tailwindVersion !== "string") return null;
2947
3022
  const trimmed = tailwindVersion.trim();
@@ -2972,6 +3047,7 @@ const isTailwindAtLeast = (detected, required) => {
2972
3047
  return detected.minor >= required.minor;
2973
3048
  };
2974
3049
  const JSX_FILE_PATTERN = /\.(tsx|jsx)$/;
3050
+ const MILLISECONDS_PER_SECOND = 1e3;
2975
3051
  const SCORE_API_URL = "https://www.react.doctor/api/score";
2976
3052
  const SHARE_BASE_URL = "https://www.react.doctor/share";
2977
3053
  const FETCH_TIMEOUT_MS = 1e4;
@@ -3876,17 +3952,26 @@ const layerOtlp = Layer.unwrap(Effect.gen(function* () {
3876
3952
  headers
3877
3953
  }).pipe(Layer.provide(FetchHttpClient.layer));
3878
3954
  }).pipe(Effect.orDie));
3879
- Schema.String.pipe(Schema.brand("OxlintBinaryPath"));
3880
- Schema.String.pipe(Schema.brand("NodeBinaryPath"));
3881
- Context.Reference("react-doctor/OxlintSpawnTimeoutMs", { defaultValue: () => {
3955
+ /**
3956
+ * Per-batch oxlint wall-clock budget. Reads from the env var on
3957
+ * startup so the eval harness can raise the budget under sandbox
3958
+ * microVMs without recompiling react-doctor. Tests override via
3959
+ * `Layer.succeed(OxlintSpawnTimeoutMs, ...)`.
3960
+ */
3961
+ var OxlintSpawnTimeoutMs = class extends Context.Reference("react-doctor/OxlintSpawnTimeoutMs", { defaultValue: () => {
3882
3962
  const raw = process.env["REACT_DOCTOR_OXLINT_SPAWN_TIMEOUT_MS"];
3883
3963
  if (raw === void 0) return OXLINT_SPAWN_TIMEOUT_MS;
3884
3964
  const parsed = Number(raw);
3885
3965
  if (!Number.isFinite(parsed) || parsed <= 0) return OXLINT_SPAWN_TIMEOUT_MS;
3886
3966
  return parsed;
3887
- } });
3888
- Context.Reference("react-doctor/OxlintOutputMaxBytes", { defaultValue: () => OXLINT_OUTPUT_MAX_BYTES });
3889
- Context.Reference("react-doctor/StagedFilesTempDirPrefix", { defaultValue: () => "react-doctor-staged-" });
3967
+ } }) {};
3968
+ /**
3969
+ * Hard cap on combined stdout+stderr bytes per oxlint batch. The
3970
+ * subprocess gets SIGKILL'd if it produces more; the recovery path
3971
+ * suggests narrowing the scan with --diff. Override via Layer in
3972
+ * tests that exercise the cap behavior.
3973
+ */
3974
+ var OxlintOutputMaxBytes = class extends Context.Reference("react-doctor/OxlintOutputMaxBytes", { defaultValue: () => OXLINT_OUTPUT_MAX_BYTES }) {};
3890
3975
  const DIAGNOSTIC_SURFACES = [
3891
3976
  "cli",
3892
3977
  "prComment",
@@ -4491,6 +4576,59 @@ const collectIgnorePatterns = (rootDirectory) => {
4491
4576
  return patterns;
4492
4577
  };
4493
4578
  const TSCONFIG_FILENAMES$1 = ["tsconfig.json", "tsconfig.base.json"];
4579
+ const DEAD_CODE_WORKER_SCRIPT = `
4580
+ const inputChunks = [];
4581
+ process.stdin.on("data", (chunk) => inputChunks.push(chunk));
4582
+ process.stdin.on("end", () => {
4583
+ const workerInput = JSON.parse(Buffer.concat(inputChunks).toString("utf8"));
4584
+
4585
+ const normalizeResult = (result) => ({
4586
+ unusedFiles: result.unusedFiles.map((unusedFile) => ({
4587
+ path: unusedFile.path,
4588
+ })),
4589
+ unusedExports: result.unusedExports.map((unusedExport) => ({
4590
+ path: unusedExport.path,
4591
+ name: unusedExport.name,
4592
+ line: unusedExport.line,
4593
+ column: unusedExport.column,
4594
+ isTypeOnly: unusedExport.isTypeOnly,
4595
+ })),
4596
+ unusedDependencies: result.unusedDependencies.map((unusedDependency) => ({
4597
+ name: unusedDependency.name,
4598
+ isDevDependency: unusedDependency.isDevDependency,
4599
+ })),
4600
+ circularDependencies: result.circularDependencies.map((cycle) => ({
4601
+ files: cycle.files,
4602
+ })),
4603
+ });
4604
+
4605
+ const serializeError = (error) =>
4606
+ error instanceof Error
4607
+ ? { name: error.name, message: error.message, stack: error.stack }
4608
+ : { message: String(error) };
4609
+
4610
+ const emit = (message) => {
4611
+ process.stdout.write(JSON.stringify(message), () => process.exit(0));
4612
+ };
4613
+
4614
+ (async () => {
4615
+ try {
4616
+ const { analyze, defineConfig } = await import(workerInput.deslopJsModuleSpecifier);
4617
+ const config = {
4618
+ rootDir: workerInput.rootDirectory,
4619
+ ...(workerInput.tsConfigPath ? { tsConfigPath: workerInput.tsConfigPath } : {}),
4620
+ ...(workerInput.ignorePatterns.length > 0
4621
+ ? { ignorePatterns: workerInput.ignorePatterns }
4622
+ : {}),
4623
+ };
4624
+ const result = await analyze(defineConfig(config));
4625
+ emit({ ok: true, result: normalizeResult(result) });
4626
+ } catch (error) {
4627
+ emit({ ok: false, error: serializeError(error) });
4628
+ }
4629
+ })();
4630
+ });
4631
+ `;
4494
4632
  const resolveTsConfigPath = (rootDirectory) => {
4495
4633
  for (const filename of TSCONFIG_FILENAMES$1) {
4496
4634
  const candidate = path.join(rootDirectory, filename);
@@ -4511,16 +4649,191 @@ const toRelativeFilePath = (rootDirectory, filePath) => {
4511
4649
  const relative = toRelativePath(filePath, rootDirectory);
4512
4650
  return relative.length > 0 ? relative : filePath.replace(/\\/g, "/");
4513
4651
  };
4652
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4653
+ const parseArray = (value, label) => {
4654
+ if (!Array.isArray(value)) throw new Error(`Dead-code worker returned invalid ${label}.`);
4655
+ return value;
4656
+ };
4657
+ const parseString = (value, label) => {
4658
+ if (typeof value !== "string") throw new Error(`Dead-code worker returned invalid ${label}.`);
4659
+ return value;
4660
+ };
4661
+ const parseNumber = (value, label) => {
4662
+ if (typeof value !== "number") throw new Error(`Dead-code worker returned invalid ${label}.`);
4663
+ return value;
4664
+ };
4665
+ const parseBoolean = (value, label) => {
4666
+ if (typeof value !== "boolean") throw new Error(`Dead-code worker returned invalid ${label}.`);
4667
+ return value;
4668
+ };
4669
+ const parseStringArray = (value, label) => {
4670
+ return parseArray(value, label).map((entry, index) => parseString(entry, `${label}[${index}]`));
4671
+ };
4672
+ const parseUnusedFiles = (value) => {
4673
+ const values = parseArray(value, "unusedFiles");
4674
+ const unusedFiles = [];
4675
+ for (const [index, entry] of values.entries()) {
4676
+ if (!isRecord(entry)) throw new Error(`Dead-code worker returned invalid unusedFiles[${index}].`);
4677
+ unusedFiles.push({ path: parseString(entry.path, `unusedFiles[${index}].path`) });
4678
+ }
4679
+ return unusedFiles;
4680
+ };
4681
+ const parseUnusedExports = (value) => {
4682
+ const values = parseArray(value, "unusedExports");
4683
+ const unusedExports = [];
4684
+ for (const [index, entry] of values.entries()) {
4685
+ if (!isRecord(entry)) throw new Error(`Dead-code worker returned invalid unusedExports[${index}].`);
4686
+ unusedExports.push({
4687
+ path: parseString(entry.path, `unusedExports[${index}].path`),
4688
+ name: parseString(entry.name, `unusedExports[${index}].name`),
4689
+ line: parseNumber(entry.line, `unusedExports[${index}].line`),
4690
+ column: parseNumber(entry.column, `unusedExports[${index}].column`),
4691
+ isTypeOnly: parseBoolean(entry.isTypeOnly, `unusedExports[${index}].isTypeOnly`)
4692
+ });
4693
+ }
4694
+ return unusedExports;
4695
+ };
4696
+ const parseUnusedDependencies = (value) => {
4697
+ const values = parseArray(value, "unusedDependencies");
4698
+ const unusedDependencies = [];
4699
+ for (const [index, entry] of values.entries()) {
4700
+ if (!isRecord(entry)) throw new Error(`Dead-code worker returned invalid unusedDependencies[${index}].`);
4701
+ unusedDependencies.push({
4702
+ name: parseString(entry.name, `unusedDependencies[${index}].name`),
4703
+ isDevDependency: parseBoolean(entry.isDevDependency, `unusedDependencies[${index}].isDevDependency`)
4704
+ });
4705
+ }
4706
+ return unusedDependencies;
4707
+ };
4708
+ const parseCircularDependencies = (value) => {
4709
+ const values = parseArray(value, "circularDependencies");
4710
+ const circularDependencies = [];
4711
+ for (const [index, entry] of values.entries()) {
4712
+ if (!isRecord(entry)) throw new Error(`Dead-code worker returned invalid circularDependencies[${index}].`);
4713
+ circularDependencies.push({ files: parseStringArray(entry.files, `circularDependencies[${index}].files`) });
4714
+ }
4715
+ return circularDependencies;
4716
+ };
4717
+ const parseDeadCodeWorkerResult = (value) => {
4718
+ if (!isRecord(value)) throw new Error("Dead-code worker returned an invalid result.");
4719
+ return {
4720
+ unusedFiles: parseUnusedFiles(value.unusedFiles),
4721
+ unusedExports: parseUnusedExports(value.unusedExports),
4722
+ unusedDependencies: parseUnusedDependencies(value.unusedDependencies),
4723
+ circularDependencies: parseCircularDependencies(value.circularDependencies)
4724
+ };
4725
+ };
4726
+ const parseDeadCodeWorkerError = (value) => {
4727
+ if (!isRecord(value) || typeof value.message !== "string") return { message: "Dead-code worker failed." };
4728
+ return {
4729
+ ...typeof value.name === "string" ? { name: value.name } : {},
4730
+ message: value.message,
4731
+ ...typeof value.stack === "string" ? { stack: value.stack } : {}
4732
+ };
4733
+ };
4734
+ const parseDeadCodeWorkerMessage = (value) => {
4735
+ if (!isRecord(value)) throw new Error("Dead-code worker returned an invalid message.");
4736
+ if (value.ok === true) return {
4737
+ ok: true,
4738
+ result: value.result
4739
+ };
4740
+ if (value.ok === false) return {
4741
+ ok: false,
4742
+ error: parseDeadCodeWorkerError(value.error)
4743
+ };
4744
+ throw new Error("Dead-code worker returned an invalid status.");
4745
+ };
4746
+ const buildDeadCodeWorkerError = (workerError) => {
4747
+ const error = new Error(workerError.message);
4748
+ if (workerError.name !== void 0) error.name = workerError.name;
4749
+ if (workerError.stack !== void 0) error.stack = workerError.stack;
4750
+ return error;
4751
+ };
4752
+ const createDeadCodeWorker = (input) => {
4753
+ const child = spawn(process.execPath, ["-e", DEAD_CODE_WORKER_SCRIPT], {
4754
+ stdio: [
4755
+ "pipe",
4756
+ "pipe",
4757
+ "pipe"
4758
+ ],
4759
+ windowsHide: true
4760
+ });
4761
+ const stdoutChunks = [];
4762
+ const stderrChunks = [];
4763
+ child.stdout.on("data", (chunk) => stdoutChunks.push(chunk));
4764
+ child.stderr.on("data", (chunk) => stderrChunks.push(chunk));
4765
+ let didSettle = false;
4766
+ const result = new Promise((resolve, reject) => {
4767
+ const settle = (callback) => {
4768
+ if (didSettle) return;
4769
+ didSettle = true;
4770
+ callback();
4771
+ };
4772
+ child.once("error", (error) => {
4773
+ settle(() => reject(error));
4774
+ });
4775
+ child.once("close", (exitCode) => {
4776
+ const stdout = Buffer.concat(stdoutChunks).toString("utf8").trim();
4777
+ if (stdout.length === 0) {
4778
+ const stderr = Buffer.concat(stderrChunks).toString("utf8").trim();
4779
+ settle(() => reject(/* @__PURE__ */ new Error(`Dead-code worker exited with code ${exitCode ?? "null"}${stderr ? `: ${stderr}` : ""}.`)));
4780
+ return;
4781
+ }
4782
+ try {
4783
+ const parsedMessage = parseDeadCodeWorkerMessage(JSON.parse(stdout));
4784
+ if (parsedMessage.ok) {
4785
+ settle(() => resolve(parsedMessage.result));
4786
+ return;
4787
+ }
4788
+ settle(() => reject(buildDeadCodeWorkerError(parsedMessage.error)));
4789
+ } catch (error) {
4790
+ settle(() => reject(error));
4791
+ }
4792
+ });
4793
+ });
4794
+ child.stdin.on("error", () => {});
4795
+ child.stdin.end(JSON.stringify(input));
4796
+ return {
4797
+ result,
4798
+ terminate: () => {
4799
+ didSettle = true;
4800
+ child.kill("SIGKILL");
4801
+ }
4802
+ };
4803
+ };
4804
+ const runDeadCodeWorkerWithTimeout = (handle, timeoutMs) => new Promise((resolve, reject) => {
4805
+ let didSettle = false;
4806
+ const timeoutHandle = setTimeout(() => {
4807
+ if (didSettle) return;
4808
+ didSettle = true;
4809
+ handle.terminate?.();
4810
+ reject(/* @__PURE__ */ new Error(`Dead-code worker timed out after ${timeoutMs / MILLISECONDS_PER_SECOND}s.`));
4811
+ }, timeoutMs);
4812
+ timeoutHandle.unref?.();
4813
+ handle.result.then((value) => {
4814
+ if (didSettle) return;
4815
+ didSettle = true;
4816
+ clearTimeout(timeoutHandle);
4817
+ handle.terminate?.();
4818
+ resolve(value);
4819
+ }, (error) => {
4820
+ if (didSettle) return;
4821
+ didSettle = true;
4822
+ clearTimeout(timeoutHandle);
4823
+ handle.terminate?.();
4824
+ reject(error);
4825
+ });
4826
+ });
4514
4827
  const checkDeadCode = async (options) => {
4515
4828
  const { rootDirectory, userConfig } = options;
4516
4829
  if (!fs.existsSync(path.join(rootDirectory, "package.json"))) return [];
4517
- const { analyze, defineConfig } = await import("deslop-js");
4518
4830
  const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory, userConfig);
4519
- const result = await analyze(defineConfig({
4520
- rootDir: rootDirectory,
4831
+ const result = parseDeadCodeWorkerResult(await runDeadCodeWorkerWithTimeout((options.createWorker ?? createDeadCodeWorker)({
4832
+ rootDirectory,
4521
4833
  tsConfigPath: resolveTsConfigPath(rootDirectory),
4522
- ...ignorePatterns.length > 0 ? { ignorePatterns } : {}
4523
- }));
4834
+ ignorePatterns,
4835
+ deslopJsModuleSpecifier: options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js")
4836
+ }), options.workerTimeoutMs ?? 12e4));
4524
4837
  const toRelative = (filePath) => toRelativeFilePath(rootDirectory, filePath);
4525
4838
  const diagnostics = [];
4526
4839
  for (const unusedFile of result.unusedFiles) diagnostics.push({
@@ -4726,8 +5039,15 @@ var Git = class Git extends Context.Service()("react-doctor/Git") {
4726
5039
  env: input.env,
4727
5040
  extendEnv: true
4728
5041
  }));
5042
+ const maxStdoutBytes = input.maxStdoutBytes;
5043
+ const stdoutByteCount = yield* Ref.make(0);
5044
+ const stdoutStream = maxStdoutBytes === void 0 ? handle.stdout : handle.stdout.pipe(Stream.tap((chunk) => Ref.updateAndGet(stdoutByteCount, (total) => total + chunk.length).pipe(Effect.flatMap((total) => total > maxStdoutBytes ? Effect.fail(new ReactDoctorError({ reason: new GitInvocationFailed({
5045
+ args: [...input.args],
5046
+ directory: input.directory,
5047
+ cause: /* @__PURE__ */ new Error(`git stdout exceeded ${maxStdoutBytes} bytes`)
5048
+ }) })) : Effect.void))));
4729
5049
  const [stdout, stderr, status] = yield* Effect.all([
4730
- Stream.mkString(Stream.decodeText(handle.stdout)),
5050
+ Stream.mkString(Stream.decodeText(stdoutStream)),
4731
5051
  Stream.mkString(Stream.decodeText(handle.stderr)),
4732
5052
  handle.exitCode
4733
5053
  ], { concurrency: 3 });
@@ -4889,7 +5209,12 @@ var Git = class Git extends Context.Service()("react-doctor/Git") {
4889
5209
  if (result.status !== 0) return [];
4890
5210
  return splitNullSeparated(result.stdout);
4891
5211
  })),
4892
- showStagedContent: (directory, relativePath) => runGit(directory, ["show", `:${relativePath}`]).pipe(Effect.map((result) => result.status === 0 ? result.stdout : null)),
5212
+ showStagedContent: (directory, relativePath, options) => runCommand({
5213
+ command: "git",
5214
+ args: ["show", `:${relativePath}`],
5215
+ directory,
5216
+ maxStdoutBytes: options?.maxBufferBytes
5217
+ }).pipe(Effect.map((result) => result.status === 0 ? result.stdout : null)),
4893
5218
  grep: (input) => Effect.gen(function* () {
4894
5219
  const args = ["grep"];
4895
5220
  if (input.listMatchingFiles ?? true) args.push("-l");
@@ -4897,7 +5222,12 @@ var Git = class Git extends Context.Service()("react-doctor/Git") {
4897
5222
  if (input.extendedRegexp ?? false) args.push("-E");
4898
5223
  args.push(input.pattern);
4899
5224
  if (input.includePaths && input.includePaths.length > 0) args.push("--", ...input.includePaths);
4900
- const result = yield* runGit(input.directory, args);
5225
+ const result = yield* runCommand({
5226
+ command: "git",
5227
+ args,
5228
+ directory: input.directory,
5229
+ maxStdoutBytes: input.maxBufferBytes
5230
+ });
4901
5231
  if (result.status === 128) return null;
4902
5232
  return {
4903
5233
  status: result.status,
@@ -5144,7 +5474,16 @@ const buildCapabilities = (project) => {
5144
5474
  capabilities.add(project.framework);
5145
5475
  if (project.framework === "expo" || project.framework === "react-native" || project.hasReactNativeWorkspace) capabilities.add("react-native");
5146
5476
  const reactMajor = project.reactMajorVersion;
5147
- if (reactMajor !== null) for (let major = 17; major <= reactMajor; major++) capabilities.add(`react:${major}`);
5477
+ if (reactMajor !== null) {
5478
+ const cappedReactMajor = Math.min(reactMajor, 30);
5479
+ for (let major = 17; major <= cappedReactMajor; major++) capabilities.add(`react:${major}`);
5480
+ if (reactMajor >= 19) {
5481
+ if (isReactAtLeast(parseReactMajorMinor(project.reactVersion), {
5482
+ major: 19,
5483
+ minor: 2
5484
+ })) capabilities.add("react:19.2");
5485
+ }
5486
+ }
5148
5487
  if (project.tailwindVersion !== null) {
5149
5488
  capabilities.add("tailwind");
5150
5489
  if (isTailwindAtLeast(parseTailwindMajorMinor(project.tailwindVersion), {
@@ -5155,6 +5494,15 @@ const buildCapabilities = (project) => {
5155
5494
  if (project.hasReactCompiler) capabilities.add("react-compiler");
5156
5495
  if (project.hasTanStackQuery) capabilities.add("tanstack-query");
5157
5496
  if (project.hasTypeScript) capabilities.add("typescript");
5497
+ if (project.preactVersion !== null) {
5498
+ capabilities.add("preact");
5499
+ const preactMajor = project.preactMajorVersion;
5500
+ if (preactMajor !== null) {
5501
+ const cappedPreactMajor = Math.min(preactMajor, 20);
5502
+ for (let major = 10; major <= cappedPreactMajor; major++) capabilities.add(`preact:${major}`);
5503
+ }
5504
+ if (project.reactVersion === null) capabilities.add("pure-preact");
5505
+ }
5158
5506
  return capabilities;
5159
5507
  };
5160
5508
  const shouldEnableRule = (requires, tags, capabilities, ignoredTags, disabledBy) => {
@@ -5409,6 +5757,13 @@ const buildNoSecretsRecommendation = (project, fallbackRecommendation) => {
5409
5757
  if (!publicEnvPrefix) return fallbackRecommendation;
5410
5758
  return `Move secrets to server-only code. In ${formatFrameworkName(project.framework)}, only \`${publicEnvPrefix}\` env vars are exposed to the browser, and they must not contain secrets`;
5411
5759
  };
5760
+ const REANIMATED_SHARED_VALUE_HINT = "If this is a Reanimated shared value, prefer its React Compiler-compatible `.get()` / `.set()` accessors over `.value` — https://docs.swmansion.com/react-native-reanimated/docs/core/useSharedValue/#react-compiler-support";
5761
+ const appendReanimatedSharedValueHint = (help, rule, project) => {
5762
+ if (rule !== "immutability") return help;
5763
+ if (!project.hasReanimated) return help;
5764
+ if (!help) return REANIMATED_SHARED_VALUE_HINT;
5765
+ return `${help}\n\n${REANIMATED_SHARED_VALUE_HINT}`;
5766
+ };
5412
5767
  const REACT_MODULE_SOURCE = "react";
5413
5768
  const REQUIRE_IDENTIFIER = "require";
5414
5769
  const USE_IDENTIFIER = "use";
@@ -5732,7 +6087,7 @@ const getRuleCategory = (ruleName) => reactDoctorPlugin.rules[ruleName]?.categor
5732
6087
  const cleanDiagnosticMessage = (message, help, plugin, rule, project) => {
5733
6088
  if (plugin === "react-hooks-js") return {
5734
6089
  message: REACT_COMPILER_MESSAGE,
5735
- help: message.replace(FILEPATH_WITH_LOCATION_PATTERN, "").trim() || help
6090
+ help: appendReanimatedSharedValueHint(message.replace(FILEPATH_WITH_LOCATION_PATTERN, "").trim() || help, rule, project)
5736
6091
  };
5737
6092
  return {
5738
6093
  message: message.replace(FILEPATH_WITH_LOCATION_PATTERN, "").trim() || message,
@@ -5801,13 +6156,6 @@ const SANITIZED_ENV = (() => {
5801
6156
  }
5802
6157
  return sanitized;
5803
6158
  })();
5804
- const OXLINT_SPAWN_TIMEOUT_MS$1 = (() => {
5805
- const raw = process.env["REACT_DOCTOR_OXLINT_SPAWN_TIMEOUT_MS"];
5806
- if (raw === void 0) return OXLINT_SPAWN_TIMEOUT_MS;
5807
- const parsed = Number(raw);
5808
- if (!Number.isFinite(parsed) || parsed <= 0) return OXLINT_SPAWN_TIMEOUT_MS;
5809
- return parsed;
5810
- })();
5811
6159
  /**
5812
6160
  * Spawn one oxlint subprocess with hard ceilings on wall time and
5813
6161
  * output size. Returns stdout on success; raises a tagged
@@ -5824,7 +6172,7 @@ const OXLINT_SPAWN_TIMEOUT_MS$1 = (() => {
5824
6172
  * The first three are splittable (the caller's binary-split retry
5825
6173
  * shrinks the batch and re-spawns); the fourth isn't.
5826
6174
  */
5827
- const spawnOxlint = (args, rootDirectory, nodeBinaryPath) => new Promise((resolve, reject) => {
6175
+ const spawnOxlint = (args, rootDirectory, nodeBinaryPath, spawnTimeoutMs = OXLINT_SPAWN_TIMEOUT_MS, outputMaxBytes = OXLINT_OUTPUT_MAX_BYTES) => new Promise((resolve, reject) => {
5828
6176
  const child = spawn(nodeBinaryPath, args, {
5829
6177
  cwd: rootDirectory,
5830
6178
  env: SANITIZED_ENV
@@ -5833,9 +6181,9 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath) => new Promise((resolv
5833
6181
  child.kill("SIGKILL");
5834
6182
  reject(new ReactDoctorError({ reason: new OxlintBatchExceeded({
5835
6183
  kind: "timeout",
5836
- detail: `${OXLINT_SPAWN_TIMEOUT_MS$1 / 1e3}s budget exceeded`
6184
+ detail: `${spawnTimeoutMs / 1e3}s budget exceeded`
5837
6185
  }) }));
5838
- }, OXLINT_SPAWN_TIMEOUT_MS$1);
6186
+ }, spawnTimeoutMs);
5839
6187
  timeoutHandle.unref?.();
5840
6188
  const stdoutBuffers = [];
5841
6189
  const stderrBuffers = [];
@@ -5845,7 +6193,7 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath) => new Promise((resolv
5845
6193
  const killIfTooLarge = (incomingBytes, isStdout) => {
5846
6194
  if (isStdout) stdoutByteCount += incomingBytes;
5847
6195
  else stderrByteCount += incomingBytes;
5848
- if (stdoutByteCount + stderrByteCount > 52428800 && !didKillForSize) {
6196
+ if (stdoutByteCount + stderrByteCount > outputMaxBytes && !didKillForSize) {
5849
6197
  didKillForSize = true;
5850
6198
  child.kill("SIGKILL");
5851
6199
  return true;
@@ -5871,7 +6219,7 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath) => new Promise((resolv
5871
6219
  if (didKillForSize) {
5872
6220
  reject(new ReactDoctorError({ reason: new OxlintBatchExceeded({
5873
6221
  kind: "output-too-large",
5874
- detail: `exceeded ${OXLINT_OUTPUT_MAX_BYTES} bytes — scan a smaller subset with --diff or --staged`
6222
+ detail: `exceeded ${outputMaxBytes} bytes — scan a smaller subset with --diff or --staged`
5875
6223
  }) }));
5876
6224
  return;
5877
6225
  }
@@ -5912,7 +6260,7 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath) => new Promise((resolv
5912
6260
  * with a slimmer config in that case.
5913
6261
  */
5914
6262
  const spawnLintBatches = async (input) => {
5915
- const { baseArgs, fileBatches, rootDirectory, nodeBinaryPath, project, onPartialFailure, onFileProgress } = input;
6263
+ const { baseArgs, fileBatches, rootDirectory, nodeBinaryPath, project, onPartialFailure, onFileProgress, spawnTimeoutMs, outputMaxBytes } = input;
5916
6264
  const totalFileCount = fileBatches.reduce((sum, batch) => sum + batch.length, 0);
5917
6265
  const allDiagnostics = [];
5918
6266
  const droppedFiles = [];
@@ -5920,7 +6268,7 @@ const spawnLintBatches = async (input) => {
5920
6268
  const spawnLintBatch = async (batch) => {
5921
6269
  const batchArgs = [...baseArgs, ...batch];
5922
6270
  try {
5923
- return parseOxlintOutput(await spawnOxlint(batchArgs, rootDirectory, nodeBinaryPath), project, rootDirectory);
6271
+ return parseOxlintOutput(await spawnOxlint(batchArgs, rootDirectory, nodeBinaryPath, spawnTimeoutMs, outputMaxBytes), project, rootDirectory);
5924
6272
  } catch (error) {
5925
6273
  if (!isSplittableReactDoctorError(error)) throw error;
5926
6274
  if (batch.length <= 1) {
@@ -6023,13 +6371,11 @@ const writeOxlintConfig = (configPath, configToWrite) => {
6023
6371
  * 6. always restore disable directives + clean up the temp dir
6024
6372
  */
6025
6373
  const runOxlint = async (options) => {
6026
- const { rootDirectory, project, includePaths, nodeBinaryPath = process.execPath, customRulesOnly = false, respectInlineDisables = true, adoptExistingLintConfig = true, ignoredTags = /* @__PURE__ */ new Set(), userConfig, configSourceDirectory = rootDirectory, onPartialFailure } = options;
6374
+ const { rootDirectory, project, includePaths, nodeBinaryPath = process.execPath, customRulesOnly = false, respectInlineDisables = true, adoptExistingLintConfig = true, ignoredTags = /* @__PURE__ */ new Set(), userConfig, configSourceDirectory = rootDirectory, onPartialFailure, spawnTimeoutMs, outputMaxBytes } = options;
6027
6375
  const serverAuthFunctionNames = Array.isArray(userConfig?.serverAuthFunctionNames) ? userConfig.serverAuthFunctionNames.filter((entry) => typeof entry === "string" && entry.length > 0) : void 0;
6028
6376
  const severityControls = buildRuleSeverityControls(userConfig);
6029
6377
  validateRuleRegistration();
6030
6378
  if (includePaths !== void 0 && includePaths.length === 0) return [];
6031
- const configDirectory = fs.mkdtempSync(path.join(os.tmpdir(), "react-doctor-oxlintrc-"));
6032
- const configPath = path.join(configDirectory, "oxlintrc.json");
6033
6379
  const pluginPath = resolvePluginPath();
6034
6380
  const extendsPaths = (adoptExistingLintConfig && !customRulesOnly ? detectUserLintConfigPaths(rootDirectory) : []).filter(canOxlintExtendConfig);
6035
6381
  const userPlugins = resolveUserPlugins(userConfig?.plugins, configSourceDirectory);
@@ -6044,6 +6390,8 @@ const runOxlint = async (options) => {
6044
6390
  userPlugins
6045
6391
  });
6046
6392
  const restoreDisableDirectives = respectInlineDisables ? () => {} : await neutralizeDisableDirectives(rootDirectory, includePaths);
6393
+ const configDirectory = fs.mkdtempSync(path.join(os.tmpdir(), "react-doctor-oxlintrc-"));
6394
+ const configPath = path.join(configDirectory, "oxlintrc.json");
6047
6395
  try {
6048
6396
  const baseArgs = [
6049
6397
  resolveOxlintBinary(),
@@ -6070,7 +6418,9 @@ const runOxlint = async (options) => {
6070
6418
  nodeBinaryPath,
6071
6419
  project,
6072
6420
  onPartialFailure,
6073
- onFileProgress: options.onFileProgress
6421
+ onFileProgress: options.onFileProgress,
6422
+ spawnTimeoutMs,
6423
+ outputMaxBytes
6074
6424
  });
6075
6425
  writeOxlintConfig(configPath, buildConfig(extendsPaths));
6076
6426
  try {
@@ -6136,6 +6486,8 @@ var Linter = class Linter extends Context.Service()("react-doctor/Linter") {
6136
6486
  */
6137
6487
  static layerOxlint = Layer.succeed(Linter, Linter.of({ run: (input) => Stream.unwrap(Effect.fn("Linter.run")(function* () {
6138
6488
  const partialFailures = yield* LintPartialFailures;
6489
+ const spawnTimeoutMs = yield* OxlintSpawnTimeoutMs;
6490
+ const outputMaxBytes = yield* OxlintOutputMaxBytes;
6139
6491
  const collectedFailures = [];
6140
6492
  const diagnostics = yield* Effect.tryPromise({
6141
6493
  try: () => runOxlint({
@@ -6152,7 +6504,9 @@ var Linter = class Linter extends Context.Service()("react-doctor/Linter") {
6152
6504
  onPartialFailure: (reason) => {
6153
6505
  collectedFailures.push(reason);
6154
6506
  },
6155
- onFileProgress: input.onFileProgress
6507
+ onFileProgress: input.onFileProgress,
6508
+ spawnTimeoutMs,
6509
+ outputMaxBytes
6156
6510
  }),
6157
6511
  catch: ensureReactDoctorError
6158
6512
  });
@@ -6450,7 +6804,7 @@ const runInspect = (input, hooks = {}) => Effect.gen(function* () {
6450
6804
  const resolvedConfig = yield* configService.resolve(input.directory);
6451
6805
  const scanDirectory = resolvedConfig.resolvedDirectory;
6452
6806
  const project = yield* projectService.discover(scanDirectory);
6453
- if (project.reactVersion === null) return yield* new ReactDoctorError({ reason: new NoReactDependency({ directory: scanDirectory }) });
6807
+ if (!isAnalyzableProject(project)) return yield* new ReactDoctorError({ reason: new NoReactDependency({ directory: scanDirectory }) });
6454
6808
  const [repo, sha, defaultBranch] = yield* Effect.all([
6455
6809
  gitService.githubRepo(scanDirectory).pipe(Effect.orElseSucceed(() => null)),
6456
6810
  gitService.headSha(scanDirectory).pipe(Effect.orElseSucceed(() => null)),
@@ -6478,23 +6832,13 @@ const runInspect = (input, hooks = {}) => Effect.gen(function* () {
6478
6832
  const lintFailure = yield* Ref.make({
6479
6833
  didFail: false,
6480
6834
  reason: null,
6481
- reasonTag: null
6835
+ reasonTag: null,
6836
+ reasonKind: null
6482
6837
  });
6483
6838
  const deadCodeFailure = yield* Ref.make({
6484
6839
  didFail: false,
6485
6840
  reason: null
6486
6841
  });
6487
- const shouldRunDeadCode = input.runDeadCode && !isDiffMode;
6488
- const deadCodeFiber = yield* Effect.forkChild(shouldRunDeadCode ? Stream.runCollect(applyPerElementPipeline(deadCodeService.run({
6489
- rootDirectory: scanDirectory,
6490
- userConfig: resolvedConfig.config
6491
- }).pipe(Stream.catchTag("ReactDoctorError", (error) => Stream.unwrap(Effect.gen(function* () {
6492
- yield* Ref.set(deadCodeFailure, {
6493
- didFail: true,
6494
- reason: error.message
6495
- });
6496
- return Stream.empty;
6497
- })))))) : Effect.succeed([]));
6498
6842
  const scanProgress = yield* progressService.start("Scanning...");
6499
6843
  const scanStartTime = Date.now();
6500
6844
  let lastReportedTotalFileCount = 0;
@@ -6511,24 +6855,32 @@ const runInspect = (input, hooks = {}) => Effect.gen(function* () {
6511
6855
  configSourceDirectory: resolvedConfig.configSourceDirectory ?? void 0,
6512
6856
  onFileProgress: (scannedFileCount, totalFileCount) => {
6513
6857
  lastReportedTotalFileCount = totalFileCount;
6514
- Effect.runSync(scanProgress.update(`Scanning (${scannedFileCount}/${totalFileCount})...`));
6858
+ Effect.runSync(scanProgress.update(`Scanning files (${scannedFileCount}/${totalFileCount})...`));
6515
6859
  }
6516
6860
  }).pipe(Stream.catchTag("ReactDoctorError", (error) => Stream.unwrap(Effect.gen(function* () {
6517
6861
  yield* Ref.set(lintFailure, {
6518
6862
  didFail: true,
6519
6863
  reason: error.message,
6520
- reasonTag: error.reason._tag
6864
+ reasonTag: error.reason._tag,
6865
+ reasonKind: error.reason._tag === "OxlintUnavailable" ? error.reason.kind : null
6521
6866
  });
6522
6867
  return Stream.empty;
6523
6868
  }))));
6524
6869
  const lintCollected = yield* Stream.runCollect(applyPerElementPipeline(rawLintStream));
6525
6870
  const lintFailureState = yield* Ref.get(lintFailure);
6526
6871
  yield* afterLint(lintFailureState.didFail);
6527
- if (lintFailureState.didFail) {
6528
- yield* Fiber.interrupt(deadCodeFiber);
6529
- yield* scanProgress.fail(formatLintFailText(lintFailureState.reasonTag, process.version));
6530
- }
6531
- const deadCodeCollected = lintFailureState.didFail ? [] : yield* Fiber.join(deadCodeFiber);
6872
+ if (lintFailureState.didFail) yield* scanProgress.fail(formatLintFailText(lintFailureState.reasonTag, process.version));
6873
+ const shouldRunDeadCode = input.runDeadCode && !isDiffMode;
6874
+ const deadCodeCollected = lintFailureState.didFail || !shouldRunDeadCode ? [] : yield* scanProgress.update("Analyzing dead code...").pipe(Effect.andThen(Stream.runCollect(applyPerElementPipeline(deadCodeService.run({
6875
+ rootDirectory: scanDirectory,
6876
+ userConfig: resolvedConfig.config
6877
+ }).pipe(Stream.catchTag("ReactDoctorError", (error) => Stream.unwrap(Effect.gen(function* () {
6878
+ yield* Ref.set(deadCodeFailure, {
6879
+ didFail: true,
6880
+ reason: error.message
6881
+ });
6882
+ return Stream.empty;
6883
+ }))))))));
6532
6884
  const deadCodeFailureState = yield* Ref.get(deadCodeFailure);
6533
6885
  const scanElapsedSeconds = ((Date.now() - scanStartTime) / 1e3).toFixed(1);
6534
6886
  const totalFileCount = lastReportedTotalFileCount || (lintIncludePaths?.length ?? project.sourceFileCount);
@@ -6570,6 +6922,7 @@ const runInspect = (input, hooks = {}) => Effect.gen(function* () {
6570
6922
  didLintFail: lintFailureState.didFail,
6571
6923
  lintFailureReason: lintFailureState.reason,
6572
6924
  lintFailureReasonTag: lintFailureState.reasonTag,
6925
+ lintFailureReasonKind: lintFailureState.reasonKind,
6573
6926
  lintPartialFailures,
6574
6927
  didDeadCodeFail: deadCodeFailureState.didFail,
6575
6928
  deadCodeFailureReason: deadCodeFailureState.reason
@@ -7023,7 +7376,7 @@ var cli_logger_exports = /* @__PURE__ */ __exportAll({ cliLogger: () => cliLogge
7023
7376
  /**
7024
7377
  * Thin synchronous façade over Effect's `Console` module. Used by
7025
7378
  * the imperative CLI helper files (`select-projects`, `run-explain`,
7026
- * `install-skill`, the legacy paths in `cli/commands/inspect.ts`)
7379
+ * `install-react-doctor`, the legacy paths in `cli/commands/inspect.ts`)
7027
7380
  * that aren't yet Effect-typed. Every call drains into a single
7028
7381
  * `Console.*` Effect via `Effect.runSync`, so the underlying logging
7029
7382
  * pipeline is identical to the canonical `yield* Console.log(...)`
@@ -7056,4 +7409,4 @@ const cliLogger = {
7056
7409
  //#endregion
7057
7410
  export { isReactDoctorError as A, filterSourceFiles as C, groupBy as D, getDiffInfo as E, runInspect as F, toRelativePath as I, listWorkspacePackages as M, resolveScanTarget as N, highlighter as O, restoreLegacyThrow as P, filterDiagnosticsForSurface as S, formatReactDoctorError as T, Score as _, DeadCode as a, buildJsonReportError as b, LintPartialFailures as c, OXLINT_NODE_REQUIREMENT as d, Progress as f, SKILL_NAME as g, SHARE_BASE_URL as h, Config as i, layerOtlp as j, isMonorepoRoot as k, Linter as l, Reporter as m, cli_logger_exports as n, Files as o, Project as p, CANONICAL_GITHUB_URL as r, Git as s, cliLogger as t, NodeResolver as u, StagedFiles as v, formatErrorChain as w, discoverReactSubprojects as x, buildJsonReport as y };
7058
7411
 
7059
- //# sourceMappingURL=cli-logger-BliQX9s8.js.map
7412
+ //# sourceMappingURL=cli-logger-Df45H6Lw.js.map