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.
- package/dist/{cli-logger-C35LXalM.js → cli-logger-BRBUS1pE.js} +303 -31
- package/dist/cli.js +224 -90
- package/dist/index.d.ts +64 -7
- package/dist/index.js +321 -58
- package/package.json +5 -5
|
@@ -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
|
|
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
|
|
4510
|
-
|
|
4772
|
+
const result = parseDeadCodeWorkerResult(await runDeadCodeWorkerWithTimeout((options.createWorker ?? createDeadCodeWorker)({
|
|
4773
|
+
rootDirectory,
|
|
4511
4774
|
tsConfigPath: resolveTsConfigPath(rootDirectory),
|
|
4512
|
-
|
|
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 :
|
|
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(
|
|
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)
|
|
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(
|
|
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)
|
|
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
|
-
|
|
6519
|
-
|
|
6520
|
-
|
|
6521
|
-
|
|
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-
|
|
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-
|
|
7321
|
+
//# sourceMappingURL=cli-logger-BRBUS1pE.js.map
|