react-doctor 0.2.8 → 0.2.10

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.
@@ -22,6 +22,7 @@ import * as Option from "effect/Option";
22
22
  import * as Ref from "effect/Ref";
23
23
  import * as Stream from "effect/Stream";
24
24
  import * as Cache from "effect/Cache";
25
+ import { Worker } from "node:worker_threads";
25
26
  import * as NodeChildProcessSpawner from "@effect/platform-node-shared/NodeChildProcessSpawner";
26
27
  import * as NodeFileSystem from "@effect/platform-node-shared/NodeFileSystem";
27
28
  import * as NodePath from "@effect/platform-node-shared/NodePath";
@@ -2045,6 +2046,14 @@ var PackageJsonNotFoundError = class extends Error {
2045
2046
  this.directory = directory;
2046
2047
  }
2047
2048
  };
2049
+ var NotADirectoryError = class extends Error {
2050
+ name = "NotADirectoryError";
2051
+ resolvedPath;
2052
+ constructor(resolvedPath, options) {
2053
+ super(`Resolved scan target "${resolvedPath}" is not a directory. Ensure the path exists and points to a project directory, not a file.`, options);
2054
+ this.resolvedPath = resolvedPath;
2055
+ }
2056
+ };
2048
2057
  var AmbiguousProjectError = class extends Error {
2049
2058
  name = "AmbiguousProjectError";
2050
2059
  directory;
@@ -2264,11 +2273,13 @@ const FRAMEWORK_DISPLAY_NAMES = {
2264
2273
  gatsby: "Gatsby",
2265
2274
  expo: "Expo",
2266
2275
  "react-native": "React Native",
2276
+ preact: "Preact",
2267
2277
  unknown: "React"
2268
2278
  };
2269
2279
  const formatFrameworkName = (framework) => FRAMEWORK_DISPLAY_NAMES[framework];
2270
2280
  const detectFramework = (dependencies) => {
2271
2281
  for (const [packageName, frameworkName] of Object.entries(FRAMEWORK_PACKAGES)) if (dependencies[packageName]) return frameworkName;
2282
+ if (dependencies.preact && !dependencies.react) return "preact";
2272
2283
  return "unknown";
2273
2284
  };
2274
2285
  const UPPER_BOUND_COMPARATOR = /<\s*=?\s*\d+(?:\.\d+){0,2}(?:-[^\s,|]+)?/g;
@@ -2732,6 +2743,13 @@ const hasReactNativeWorkspaceAnywhere = (rootDirectory, rootPackageJson) => {
2732
2743
  }
2733
2744
  return false;
2734
2745
  };
2746
+ const hasPreact = (packageJson) => {
2747
+ return "preact" in {
2748
+ ...packageJson.peerDependencies,
2749
+ ...packageJson.dependencies,
2750
+ ...packageJson.devDependencies
2751
+ };
2752
+ };
2735
2753
  const TANSTACK_QUERY_PACKAGES = new Set([
2736
2754
  "@tanstack/react-query",
2737
2755
  "@tanstack/query-core",
@@ -2769,7 +2787,8 @@ const resolveEffectiveReactMajor = (reactVersion, packageJson) => {
2769
2787
  const REACT_DEPENDENCY_NAMES = new Set([
2770
2788
  "react",
2771
2789
  "react-native",
2772
- "next"
2790
+ "next",
2791
+ "preact"
2773
2792
  ]);
2774
2793
  const hasReactDependency = (packageJson) => {
2775
2794
  const allDependencies = {
@@ -2928,12 +2947,47 @@ const discoverProject = (directory) => {
2928
2947
  hasTypeScript,
2929
2948
  hasReactCompiler: detectReactCompiler(directory, packageJson),
2930
2949
  hasTanStackQuery: hasTanStackQuery(packageJson),
2950
+ hasPreact: hasPreact(packageJson),
2931
2951
  hasReactNativeWorkspace,
2932
2952
  sourceFileCount
2933
2953
  };
2934
2954
  cachedProjectInfos.set(directory, projectInfo);
2935
2955
  return projectInfo;
2936
2956
  };
2957
+ const MAJOR_MINOR_PATTERN = /(\d{1,4})\.(\d{1,4})/;
2958
+ const MAJOR_ONLY_PATTERN = /(\d{1,4})/;
2959
+ const UPPER_BOUND_COMPARATOR_PATTERN = /<=?\s{0,8}\d{1,4}(?:\.\d{1,4}){0,2}(?:-[^\s,|]+)?/g;
2960
+ const parseReactMajorMinor = (reactVersion) => {
2961
+ if (typeof reactVersion !== "string") return null;
2962
+ const trimmed = reactVersion.trim();
2963
+ if (trimmed.length === 0) return null;
2964
+ const lowerBoundsOnly = trimmed.replace(UPPER_BOUND_COMPARATOR_PATTERN, " ").trim();
2965
+ if (lowerBoundsOnly.length === 0) return null;
2966
+ const majorMinorMatch = lowerBoundsOnly.match(MAJOR_MINOR_PATTERN);
2967
+ if (majorMinorMatch) {
2968
+ const major = Number.parseInt(majorMinorMatch[1], 10);
2969
+ const minor = Number.parseInt(majorMinorMatch[2], 10);
2970
+ if (!Number.isFinite(major) || major <= 0) return null;
2971
+ if (!Number.isFinite(minor) || minor < 0) return null;
2972
+ return {
2973
+ major,
2974
+ minor
2975
+ };
2976
+ }
2977
+ const majorOnlyMatch = lowerBoundsOnly.match(MAJOR_ONLY_PATTERN);
2978
+ if (!majorOnlyMatch) return null;
2979
+ const major = Number.parseInt(majorOnlyMatch[1], 10);
2980
+ if (!Number.isFinite(major) || major <= 0) return null;
2981
+ return {
2982
+ major,
2983
+ minor: 0
2984
+ };
2985
+ };
2986
+ const isReactAtLeast = (detected, required) => {
2987
+ if (detected === null) return true;
2988
+ if (detected.major !== required.major) return detected.major > required.major;
2989
+ return detected.minor >= required.minor;
2990
+ };
2937
2991
  const parseTailwindMajorMinor = (tailwindVersion) => {
2938
2992
  if (typeof tailwindVersion !== "string") return null;
2939
2993
  const trimmed = tailwindVersion.trim();
@@ -2964,6 +3018,7 @@ const isTailwindAtLeast = (detected, required) => {
2964
3018
  return detected.minor >= required.minor;
2965
3019
  };
2966
3020
  const JSX_FILE_PATTERN = /\.(tsx|jsx)$/;
3021
+ const MILLISECONDS_PER_SECOND = 1e3;
2967
3022
  const SCORE_API_URL = "https://www.react.doctor/api/score";
2968
3023
  const SHARE_BASE_URL = "https://www.react.doctor/share";
2969
3024
  const FETCH_TIMEOUT_MS = 1e4;
@@ -4132,8 +4187,10 @@ const resolveScanTarget = (requestedDirectory) => {
4132
4187
  const configSourceDirectory = loadedConfig?.sourceDirectory ?? null;
4133
4188
  const redirectedDirectory = resolveConfigRootDir(userConfig, configSourceDirectory);
4134
4189
  const directoryAfterRedirect = redirectedDirectory ?? absoluteRequested;
4190
+ const resolvedDirectory = resolveDiagnoseTarget(directoryAfterRedirect) ?? directoryAfterRedirect;
4191
+ if (!isDirectory(resolvedDirectory)) throw existsSync(resolvedDirectory) ? new NotADirectoryError(resolvedDirectory) : new ProjectNotFoundError(resolvedDirectory);
4135
4192
  return {
4136
- resolvedDirectory: resolveDiagnoseTarget(directoryAfterRedirect) ?? directoryAfterRedirect,
4193
+ resolvedDirectory,
4137
4194
  requestedDirectory: absoluteRequested,
4138
4195
  userConfig,
4139
4196
  configSourceDirectory,
@@ -4481,6 +4538,49 @@ const collectIgnorePatterns = (rootDirectory) => {
4481
4538
  return patterns;
4482
4539
  };
4483
4540
  const TSCONFIG_FILENAMES$1 = ["tsconfig.json", "tsconfig.base.json"];
4541
+ const DEAD_CODE_WORKER_SCRIPT = `
4542
+ const { parentPort, workerData } = require("node:worker_threads");
4543
+
4544
+ const normalizeResult = (result) => ({
4545
+ unusedFiles: result.unusedFiles.map((unusedFile) => ({
4546
+ path: unusedFile.path,
4547
+ })),
4548
+ unusedExports: result.unusedExports.map((unusedExport) => ({
4549
+ path: unusedExport.path,
4550
+ name: unusedExport.name,
4551
+ line: unusedExport.line,
4552
+ column: unusedExport.column,
4553
+ isTypeOnly: unusedExport.isTypeOnly,
4554
+ })),
4555
+ unusedDependencies: result.unusedDependencies.map((unusedDependency) => ({
4556
+ name: unusedDependency.name,
4557
+ isDevDependency: unusedDependency.isDevDependency,
4558
+ })),
4559
+ circularDependencies: result.circularDependencies.map((cycle) => ({
4560
+ files: cycle.files,
4561
+ })),
4562
+ });
4563
+
4564
+ const serializeError = (error) =>
4565
+ error instanceof Error
4566
+ ? { name: error.name, message: error.message, stack: error.stack }
4567
+ : { message: String(error) };
4568
+
4569
+ (async () => {
4570
+ try {
4571
+ const { analyze, defineConfig } = await import(workerData.deslopJsModuleSpecifier);
4572
+ const config = {
4573
+ rootDir: workerData.rootDirectory,
4574
+ ...(workerData.tsConfigPath ? { tsConfigPath: workerData.tsConfigPath } : {}),
4575
+ ...(workerData.ignorePatterns.length > 0 ? { ignorePatterns: workerData.ignorePatterns } : {}),
4576
+ };
4577
+ const result = await analyze(defineConfig(config));
4578
+ parentPort.postMessage({ ok: true, result: normalizeResult(result) });
4579
+ } catch (error) {
4580
+ parentPort.postMessage({ ok: false, error: serializeError(error) });
4581
+ }
4582
+ })();
4583
+ `;
4484
4584
  const resolveTsConfigPath = (rootDirectory) => {
4485
4585
  for (const filename of TSCONFIG_FILENAMES$1) {
4486
4586
  const candidate = path.join(rootDirectory, filename);
@@ -4501,16 +4601,180 @@ const toRelativeFilePath = (rootDirectory, filePath) => {
4501
4601
  const relative = toRelativePath(filePath, rootDirectory);
4502
4602
  return relative.length > 0 ? relative : filePath.replace(/\\/g, "/");
4503
4603
  };
4604
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4605
+ const parseArray = (value, label) => {
4606
+ if (!Array.isArray(value)) throw new Error(`Dead-code worker returned invalid ${label}.`);
4607
+ return value;
4608
+ };
4609
+ const parseString = (value, label) => {
4610
+ if (typeof value !== "string") throw new Error(`Dead-code worker returned invalid ${label}.`);
4611
+ return value;
4612
+ };
4613
+ const parseNumber = (value, label) => {
4614
+ if (typeof value !== "number") throw new Error(`Dead-code worker returned invalid ${label}.`);
4615
+ return value;
4616
+ };
4617
+ const parseBoolean = (value, label) => {
4618
+ if (typeof value !== "boolean") throw new Error(`Dead-code worker returned invalid ${label}.`);
4619
+ return value;
4620
+ };
4621
+ const parseStringArray = (value, label) => {
4622
+ return parseArray(value, label).map((entry, index) => parseString(entry, `${label}[${index}]`));
4623
+ };
4624
+ const parseUnusedFiles = (value) => {
4625
+ const values = parseArray(value, "unusedFiles");
4626
+ const unusedFiles = [];
4627
+ for (const [index, entry] of values.entries()) {
4628
+ if (!isRecord(entry)) throw new Error(`Dead-code worker returned invalid unusedFiles[${index}].`);
4629
+ unusedFiles.push({ path: parseString(entry.path, `unusedFiles[${index}].path`) });
4630
+ }
4631
+ return unusedFiles;
4632
+ };
4633
+ const parseUnusedExports = (value) => {
4634
+ const values = parseArray(value, "unusedExports");
4635
+ const unusedExports = [];
4636
+ for (const [index, entry] of values.entries()) {
4637
+ if (!isRecord(entry)) throw new Error(`Dead-code worker returned invalid unusedExports[${index}].`);
4638
+ unusedExports.push({
4639
+ path: parseString(entry.path, `unusedExports[${index}].path`),
4640
+ name: parseString(entry.name, `unusedExports[${index}].name`),
4641
+ line: parseNumber(entry.line, `unusedExports[${index}].line`),
4642
+ column: parseNumber(entry.column, `unusedExports[${index}].column`),
4643
+ isTypeOnly: parseBoolean(entry.isTypeOnly, `unusedExports[${index}].isTypeOnly`)
4644
+ });
4645
+ }
4646
+ return unusedExports;
4647
+ };
4648
+ const parseUnusedDependencies = (value) => {
4649
+ const values = parseArray(value, "unusedDependencies");
4650
+ const unusedDependencies = [];
4651
+ for (const [index, entry] of values.entries()) {
4652
+ if (!isRecord(entry)) throw new Error(`Dead-code worker returned invalid unusedDependencies[${index}].`);
4653
+ unusedDependencies.push({
4654
+ name: parseString(entry.name, `unusedDependencies[${index}].name`),
4655
+ isDevDependency: parseBoolean(entry.isDevDependency, `unusedDependencies[${index}].isDevDependency`)
4656
+ });
4657
+ }
4658
+ return unusedDependencies;
4659
+ };
4660
+ const parseCircularDependencies = (value) => {
4661
+ const values = parseArray(value, "circularDependencies");
4662
+ const circularDependencies = [];
4663
+ for (const [index, entry] of values.entries()) {
4664
+ if (!isRecord(entry)) throw new Error(`Dead-code worker returned invalid circularDependencies[${index}].`);
4665
+ circularDependencies.push({ files: parseStringArray(entry.files, `circularDependencies[${index}].files`) });
4666
+ }
4667
+ return circularDependencies;
4668
+ };
4669
+ const parseDeadCodeWorkerResult = (value) => {
4670
+ if (!isRecord(value)) throw new Error("Dead-code worker returned an invalid result.");
4671
+ return {
4672
+ unusedFiles: parseUnusedFiles(value.unusedFiles),
4673
+ unusedExports: parseUnusedExports(value.unusedExports),
4674
+ unusedDependencies: parseUnusedDependencies(value.unusedDependencies),
4675
+ circularDependencies: parseCircularDependencies(value.circularDependencies)
4676
+ };
4677
+ };
4678
+ const parseDeadCodeWorkerError = (value) => {
4679
+ if (!isRecord(value) || typeof value.message !== "string") return { message: "Dead-code worker failed." };
4680
+ return {
4681
+ ...typeof value.name === "string" ? { name: value.name } : {},
4682
+ message: value.message,
4683
+ ...typeof value.stack === "string" ? { stack: value.stack } : {}
4684
+ };
4685
+ };
4686
+ const parseDeadCodeWorkerMessage = (value) => {
4687
+ if (!isRecord(value)) throw new Error("Dead-code worker returned an invalid message.");
4688
+ if (value.ok === true) return {
4689
+ ok: true,
4690
+ result: value.result
4691
+ };
4692
+ if (value.ok === false) return {
4693
+ ok: false,
4694
+ error: parseDeadCodeWorkerError(value.error)
4695
+ };
4696
+ throw new Error("Dead-code worker returned an invalid status.");
4697
+ };
4698
+ const buildDeadCodeWorkerError = (workerError) => {
4699
+ const error = new Error(workerError.message);
4700
+ if (workerError.name !== void 0) error.name = workerError.name;
4701
+ if (workerError.stack !== void 0) error.stack = workerError.stack;
4702
+ return error;
4703
+ };
4704
+ const createDeadCodeWorker = (input) => {
4705
+ const worker = new Worker(DEAD_CODE_WORKER_SCRIPT, {
4706
+ eval: true,
4707
+ workerData: input
4708
+ });
4709
+ let didSettle = false;
4710
+ return {
4711
+ result: new Promise((resolve, reject) => {
4712
+ const settle = (callback) => {
4713
+ if (didSettle) return;
4714
+ didSettle = true;
4715
+ worker.removeAllListeners();
4716
+ callback();
4717
+ };
4718
+ worker.once("message", (message) => {
4719
+ try {
4720
+ const parsedMessage = parseDeadCodeWorkerMessage(message);
4721
+ if (parsedMessage.ok) {
4722
+ settle(() => resolve(parsedMessage.result));
4723
+ return;
4724
+ }
4725
+ settle(() => reject(buildDeadCodeWorkerError(parsedMessage.error)));
4726
+ } catch (error) {
4727
+ settle(() => reject(error));
4728
+ }
4729
+ });
4730
+ worker.once("error", (error) => {
4731
+ settle(() => reject(error));
4732
+ });
4733
+ worker.once("exit", (exitCode) => {
4734
+ if (exitCode === 0) return;
4735
+ settle(() => reject(/* @__PURE__ */ new Error(`Dead-code worker exited with code ${exitCode}.`)));
4736
+ });
4737
+ }),
4738
+ terminate: () => {
4739
+ didSettle = true;
4740
+ worker.removeAllListeners();
4741
+ return worker.terminate();
4742
+ }
4743
+ };
4744
+ };
4745
+ const runDeadCodeWorkerWithTimeout = (handle, timeoutMs) => new Promise((resolve, reject) => {
4746
+ let didSettle = false;
4747
+ const timeoutHandle = setTimeout(() => {
4748
+ if (didSettle) return;
4749
+ didSettle = true;
4750
+ handle.terminate?.();
4751
+ reject(/* @__PURE__ */ new Error(`Dead-code worker timed out after ${timeoutMs / MILLISECONDS_PER_SECOND}s.`));
4752
+ }, timeoutMs);
4753
+ timeoutHandle.unref?.();
4754
+ handle.result.then((value) => {
4755
+ if (didSettle) return;
4756
+ didSettle = true;
4757
+ clearTimeout(timeoutHandle);
4758
+ handle.terminate?.();
4759
+ resolve(value);
4760
+ }, (error) => {
4761
+ if (didSettle) return;
4762
+ didSettle = true;
4763
+ clearTimeout(timeoutHandle);
4764
+ handle.terminate?.();
4765
+ reject(error);
4766
+ });
4767
+ });
4504
4768
  const checkDeadCode = async (options) => {
4505
4769
  const { rootDirectory, userConfig } = options;
4506
4770
  if (!fs.existsSync(path.join(rootDirectory, "package.json"))) return [];
4507
- const { analyze, defineConfig } = await import("deslop-js");
4508
4771
  const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory, userConfig);
4509
- const result = await analyze(defineConfig({
4510
- rootDir: rootDirectory,
4772
+ const result = parseDeadCodeWorkerResult(await runDeadCodeWorkerWithTimeout((options.createWorker ?? createDeadCodeWorker)({
4773
+ rootDirectory,
4511
4774
  tsConfigPath: resolveTsConfigPath(rootDirectory),
4512
- ...ignorePatterns.length > 0 ? { ignorePatterns } : {}
4513
- }));
4775
+ ignorePatterns,
4776
+ deslopJsModuleSpecifier: options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js")
4777
+ }), options.workerTimeoutMs ?? 12e4));
4514
4778
  const toRelative = (filePath) => toRelativeFilePath(rootDirectory, filePath);
4515
4779
  const diagnostics = [];
4516
4780
  for (const unusedFile of result.unusedFiles) diagnostics.push({
@@ -4614,7 +4878,7 @@ var Files = class Files extends Context.Service()("react-doctor/Files") {
4614
4878
  * pattern in react-doctor-evals' test layers.
4615
4879
  */
4616
4880
  static layerInMemory = (tree) => {
4617
- const resolveAbsolute = (filePath, rootDirectory) => Path.isAbsolute(filePath) ? filePath : Path.join(rootDirectory, filePath);
4881
+ const resolveAbsolute = (filePath, rootDirectory) => Path.isAbsolute(filePath) ? filePath : `${rootDirectory}/${filePath}`;
4618
4882
  return Layer.succeed(Files, Files.of({
4619
4883
  readLines: (input) => Effect.sync(() => {
4620
4884
  const absolute = resolveAbsolute(input.filePath, input.rootDirectory);
@@ -4622,17 +4886,17 @@ var Files = class Files extends Context.Service()("react-doctor/Files") {
4622
4886
  return content === void 0 ? null : content.split("\n");
4623
4887
  }),
4624
4888
  listSourceFiles: (rootDirectory) => Effect.sync(() => {
4625
- const prefix = rootDirectory.endsWith(Path.sep) ? rootDirectory : `${rootDirectory}${Path.sep}`;
4889
+ const prefix = rootDirectory.endsWith("/") ? rootDirectory : `${rootDirectory}/`;
4626
4890
  const files = [];
4627
4891
  for (const absolute of tree.keys()) {
4628
4892
  if (!absolute.startsWith(prefix)) continue;
4629
- files.push(absolute.slice(prefix.length).split(Path.sep).join("/"));
4893
+ files.push(absolute.slice(prefix.length));
4630
4894
  }
4631
4895
  return files;
4632
4896
  }),
4633
4897
  isFile: (filePath) => Effect.sync(() => tree.has(filePath)),
4634
4898
  isDirectory: (filePath) => Effect.sync(() => {
4635
- const prefix = filePath.endsWith(Path.sep) ? filePath : `${filePath}${Path.sep}`;
4899
+ const prefix = filePath.endsWith("/") ? filePath : `${filePath}/`;
4636
4900
  for (const absolute of tree.keys()) if (absolute.startsWith(prefix)) return true;
4637
4901
  return false;
4638
4902
  })
@@ -5134,7 +5398,15 @@ const buildCapabilities = (project) => {
5134
5398
  capabilities.add(project.framework);
5135
5399
  if (project.framework === "expo" || project.framework === "react-native" || project.hasReactNativeWorkspace) capabilities.add("react-native");
5136
5400
  const reactMajor = project.reactMajorVersion;
5137
- if (reactMajor !== null) for (let major = 17; major <= reactMajor; major++) capabilities.add(`react:${major}`);
5401
+ if (reactMajor !== null) {
5402
+ for (let major = 17; major <= reactMajor; major++) capabilities.add(`react:${major}`);
5403
+ if (reactMajor >= 19) {
5404
+ if (isReactAtLeast(parseReactMajorMinor(project.reactVersion), {
5405
+ major: 19,
5406
+ minor: 2
5407
+ })) capabilities.add("react:19.2");
5408
+ }
5409
+ }
5138
5410
  if (project.tailwindVersion !== null) {
5139
5411
  capabilities.add("tailwind");
5140
5412
  if (isTailwindAtLeast(parseTailwindMajorMinor(project.tailwindVersion), {
@@ -5145,6 +5417,10 @@ const buildCapabilities = (project) => {
5145
5417
  if (project.hasReactCompiler) capabilities.add("react-compiler");
5146
5418
  if (project.hasTanStackQuery) capabilities.add("tanstack-query");
5147
5419
  if (project.hasTypeScript) capabilities.add("typescript");
5420
+ if (project.hasPreact) {
5421
+ capabilities.add("preact");
5422
+ if (project.reactVersion === null) capabilities.add("pure-preact");
5423
+ }
5148
5424
  return capabilities;
5149
5425
  };
5150
5426
  const shouldEnableRule = (requires, tags, capabilities, ignoredTags, disabledBy) => {
@@ -5769,7 +6045,7 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
5769
6045
  const primaryLabel = diagnostic.labels[0];
5770
6046
  const cleaned = cleanDiagnosticMessage(diagnostic.message, diagnostic.help, plugin, rule, project);
5771
6047
  return {
5772
- filePath: diagnostic.filename,
6048
+ filePath: diagnostic.filename.replaceAll("\\", "/"),
5773
6049
  plugin,
5774
6050
  rule,
5775
6051
  severity: diagnostic.severity,
@@ -6474,17 +6750,6 @@ const runInspect = (input, hooks = {}) => Effect.gen(function* () {
6474
6750
  didFail: false,
6475
6751
  reason: null
6476
6752
  });
6477
- const shouldRunDeadCode = input.runDeadCode && !isDiffMode;
6478
- const deadCodeFiber = yield* Effect.forkChild(shouldRunDeadCode ? Stream.runCollect(applyPerElementPipeline(deadCodeService.run({
6479
- rootDirectory: scanDirectory,
6480
- userConfig: resolvedConfig.config
6481
- }).pipe(Stream.catchTag("ReactDoctorError", (error) => Stream.unwrap(Effect.gen(function* () {
6482
- yield* Ref.set(deadCodeFailure, {
6483
- didFail: true,
6484
- reason: error.message
6485
- });
6486
- return Stream.empty;
6487
- })))))) : Effect.succeed([]));
6488
6753
  const scanProgress = yield* progressService.start("Scanning...");
6489
6754
  const scanStartTime = Date.now();
6490
6755
  let lastReportedTotalFileCount = 0;
@@ -6514,11 +6779,18 @@ const runInspect = (input, hooks = {}) => Effect.gen(function* () {
6514
6779
  const lintCollected = yield* Stream.runCollect(applyPerElementPipeline(rawLintStream));
6515
6780
  const lintFailureState = yield* Ref.get(lintFailure);
6516
6781
  yield* afterLint(lintFailureState.didFail);
6517
- if (lintFailureState.didFail) {
6518
- yield* Fiber.interrupt(deadCodeFiber);
6519
- yield* scanProgress.fail(formatLintFailText(lintFailureState.reasonTag, process.version));
6520
- }
6521
- const deadCodeCollected = lintFailureState.didFail ? [] : yield* Fiber.join(deadCodeFiber);
6782
+ if (lintFailureState.didFail) yield* scanProgress.fail(formatLintFailText(lintFailureState.reasonTag, process.version));
6783
+ const shouldRunDeadCode = input.runDeadCode && !isDiffMode;
6784
+ const deadCodeCollected = lintFailureState.didFail || !shouldRunDeadCode ? [] : yield* scanProgress.update("Analyzing dead code...").pipe(Effect.andThen(Stream.runCollect(applyPerElementPipeline(deadCodeService.run({
6785
+ rootDirectory: scanDirectory,
6786
+ userConfig: resolvedConfig.config
6787
+ }).pipe(Stream.catchTag("ReactDoctorError", (error) => Stream.unwrap(Effect.gen(function* () {
6788
+ yield* Ref.set(deadCodeFailure, {
6789
+ didFail: true,
6790
+ reason: error.message
6791
+ });
6792
+ return Stream.empty;
6793
+ }))))))));
6522
6794
  const deadCodeFailureState = yield* Ref.get(deadCodeFailure);
6523
6795
  const scanElapsedSeconds = ((Date.now() - scanStartTime) / 1e3).toFixed(1);
6524
6796
  const totalFileCount = lastReportedTotalFileCount || (lintIncludePaths?.length ?? project.sourceFileCount);
@@ -7013,7 +7285,7 @@ var cli_logger_exports = /* @__PURE__ */ __exportAll({ cliLogger: () => cliLogge
7013
7285
  /**
7014
7286
  * Thin synchronous façade over Effect's `Console` module. Used by
7015
7287
  * the imperative CLI helper files (`select-projects`, `run-explain`,
7016
- * `install-skill`, the legacy paths in `cli/commands/inspect.ts`)
7288
+ * `install-react-doctor`, the legacy paths in `cli/commands/inspect.ts`)
7017
7289
  * that aren't yet Effect-typed. Every call drains into a single
7018
7290
  * `Console.*` Effect via `Effect.runSync`, so the underlying logging
7019
7291
  * pipeline is identical to the canonical `yield* Console.log(...)`
@@ -7046,4 +7318,4 @@ const cliLogger = {
7046
7318
  //#endregion
7047
7319
  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 };
7048
7320
 
7049
- //# sourceMappingURL=cli-logger-C35LXalM.js.map
7321
+ //# sourceMappingURL=cli-logger-BRBUS1pE.js.map