react-doctor 0.2.9 → 0.2.11
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-BliQX9s8.js → cli-logger-pbFEieEc.js} +342 -40
- package/dist/cli.js +113 -85
- package/dist/index.d.ts +21 -2
- package/dist/index.js +340 -38
- package/package.json +4 -4
|
@@ -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
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
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 hasPreact = (packageJson) => {
|
|
2747
|
+
return "preact" in {
|
|
2748
|
+
...packageJson.peerDependencies,
|
|
2749
|
+
...packageJson.dependencies,
|
|
2750
|
+
...packageJson.devDependencies
|
|
2751
|
+
};
|
|
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 = {
|
|
@@ -2926,6 +2947,7 @@ const discoverProject = (directory) => {
|
|
|
2926
2947
|
const hasTypeScript = fs.existsSync(path.join(directory, "tsconfig.json"));
|
|
2927
2948
|
const sourceFileCount = countSourceFiles(directory);
|
|
2928
2949
|
const hasReactNativeWorkspace = framework === "expo" || framework === "react-native" || hasReactNativeWorkspaceAnywhere(directory, packageJson);
|
|
2950
|
+
const hasReanimated = hasReactNativeWorkspace && someWorkspacePackageJson(directory, packageJson, isPackageJsonReanimatedAware);
|
|
2929
2951
|
const projectInfo = {
|
|
2930
2952
|
rootDirectory: directory,
|
|
2931
2953
|
projectName,
|
|
@@ -2936,12 +2958,48 @@ const discoverProject = (directory) => {
|
|
|
2936
2958
|
hasTypeScript,
|
|
2937
2959
|
hasReactCompiler: detectReactCompiler(directory, packageJson),
|
|
2938
2960
|
hasTanStackQuery: hasTanStackQuery(packageJson),
|
|
2961
|
+
hasPreact: hasPreact(packageJson),
|
|
2939
2962
|
hasReactNativeWorkspace,
|
|
2963
|
+
hasReanimated,
|
|
2940
2964
|
sourceFileCount
|
|
2941
2965
|
};
|
|
2942
2966
|
cachedProjectInfos.set(directory, projectInfo);
|
|
2943
2967
|
return projectInfo;
|
|
2944
2968
|
};
|
|
2969
|
+
const MAJOR_MINOR_PATTERN = /(\d{1,4})\.(\d{1,4})/;
|
|
2970
|
+
const MAJOR_ONLY_PATTERN = /(\d{1,4})/;
|
|
2971
|
+
const UPPER_BOUND_COMPARATOR_PATTERN = /<=?\s{0,8}\d{1,4}(?:\.\d{1,4}){0,2}(?:-[^\s,|]+)?/g;
|
|
2972
|
+
const parseReactMajorMinor = (reactVersion) => {
|
|
2973
|
+
if (typeof reactVersion !== "string") return null;
|
|
2974
|
+
const trimmed = reactVersion.trim();
|
|
2975
|
+
if (trimmed.length === 0) return null;
|
|
2976
|
+
const lowerBoundsOnly = trimmed.replace(UPPER_BOUND_COMPARATOR_PATTERN, " ").trim();
|
|
2977
|
+
if (lowerBoundsOnly.length === 0) return null;
|
|
2978
|
+
const majorMinorMatch = lowerBoundsOnly.match(MAJOR_MINOR_PATTERN);
|
|
2979
|
+
if (majorMinorMatch) {
|
|
2980
|
+
const major = Number.parseInt(majorMinorMatch[1], 10);
|
|
2981
|
+
const minor = Number.parseInt(majorMinorMatch[2], 10);
|
|
2982
|
+
if (!Number.isFinite(major) || major <= 0) return null;
|
|
2983
|
+
if (!Number.isFinite(minor) || minor < 0) return null;
|
|
2984
|
+
return {
|
|
2985
|
+
major,
|
|
2986
|
+
minor
|
|
2987
|
+
};
|
|
2988
|
+
}
|
|
2989
|
+
const majorOnlyMatch = lowerBoundsOnly.match(MAJOR_ONLY_PATTERN);
|
|
2990
|
+
if (!majorOnlyMatch) return null;
|
|
2991
|
+
const major = Number.parseInt(majorOnlyMatch[1], 10);
|
|
2992
|
+
if (!Number.isFinite(major) || major <= 0) return null;
|
|
2993
|
+
return {
|
|
2994
|
+
major,
|
|
2995
|
+
minor: 0
|
|
2996
|
+
};
|
|
2997
|
+
};
|
|
2998
|
+
const isReactAtLeast = (detected, required) => {
|
|
2999
|
+
if (detected === null) return true;
|
|
3000
|
+
if (detected.major !== required.major) return detected.major > required.major;
|
|
3001
|
+
return detected.minor >= required.minor;
|
|
3002
|
+
};
|
|
2945
3003
|
const parseTailwindMajorMinor = (tailwindVersion) => {
|
|
2946
3004
|
if (typeof tailwindVersion !== "string") return null;
|
|
2947
3005
|
const trimmed = tailwindVersion.trim();
|
|
@@ -2972,6 +3030,7 @@ const isTailwindAtLeast = (detected, required) => {
|
|
|
2972
3030
|
return detected.minor >= required.minor;
|
|
2973
3031
|
};
|
|
2974
3032
|
const JSX_FILE_PATTERN = /\.(tsx|jsx)$/;
|
|
3033
|
+
const MILLISECONDS_PER_SECOND = 1e3;
|
|
2975
3034
|
const SCORE_API_URL = "https://www.react.doctor/api/score";
|
|
2976
3035
|
const SHARE_BASE_URL = "https://www.react.doctor/share";
|
|
2977
3036
|
const FETCH_TIMEOUT_MS = 1e4;
|
|
@@ -4491,6 +4550,59 @@ const collectIgnorePatterns = (rootDirectory) => {
|
|
|
4491
4550
|
return patterns;
|
|
4492
4551
|
};
|
|
4493
4552
|
const TSCONFIG_FILENAMES$1 = ["tsconfig.json", "tsconfig.base.json"];
|
|
4553
|
+
const DEAD_CODE_WORKER_SCRIPT = `
|
|
4554
|
+
const inputChunks = [];
|
|
4555
|
+
process.stdin.on("data", (chunk) => inputChunks.push(chunk));
|
|
4556
|
+
process.stdin.on("end", () => {
|
|
4557
|
+
const workerInput = JSON.parse(Buffer.concat(inputChunks).toString("utf8"));
|
|
4558
|
+
|
|
4559
|
+
const normalizeResult = (result) => ({
|
|
4560
|
+
unusedFiles: result.unusedFiles.map((unusedFile) => ({
|
|
4561
|
+
path: unusedFile.path,
|
|
4562
|
+
})),
|
|
4563
|
+
unusedExports: result.unusedExports.map((unusedExport) => ({
|
|
4564
|
+
path: unusedExport.path,
|
|
4565
|
+
name: unusedExport.name,
|
|
4566
|
+
line: unusedExport.line,
|
|
4567
|
+
column: unusedExport.column,
|
|
4568
|
+
isTypeOnly: unusedExport.isTypeOnly,
|
|
4569
|
+
})),
|
|
4570
|
+
unusedDependencies: result.unusedDependencies.map((unusedDependency) => ({
|
|
4571
|
+
name: unusedDependency.name,
|
|
4572
|
+
isDevDependency: unusedDependency.isDevDependency,
|
|
4573
|
+
})),
|
|
4574
|
+
circularDependencies: result.circularDependencies.map((cycle) => ({
|
|
4575
|
+
files: cycle.files,
|
|
4576
|
+
})),
|
|
4577
|
+
});
|
|
4578
|
+
|
|
4579
|
+
const serializeError = (error) =>
|
|
4580
|
+
error instanceof Error
|
|
4581
|
+
? { name: error.name, message: error.message, stack: error.stack }
|
|
4582
|
+
: { message: String(error) };
|
|
4583
|
+
|
|
4584
|
+
const emit = (message) => {
|
|
4585
|
+
process.stdout.write(JSON.stringify(message), () => process.exit(0));
|
|
4586
|
+
};
|
|
4587
|
+
|
|
4588
|
+
(async () => {
|
|
4589
|
+
try {
|
|
4590
|
+
const { analyze, defineConfig } = await import(workerInput.deslopJsModuleSpecifier);
|
|
4591
|
+
const config = {
|
|
4592
|
+
rootDir: workerInput.rootDirectory,
|
|
4593
|
+
...(workerInput.tsConfigPath ? { tsConfigPath: workerInput.tsConfigPath } : {}),
|
|
4594
|
+
...(workerInput.ignorePatterns.length > 0
|
|
4595
|
+
? { ignorePatterns: workerInput.ignorePatterns }
|
|
4596
|
+
: {}),
|
|
4597
|
+
};
|
|
4598
|
+
const result = await analyze(defineConfig(config));
|
|
4599
|
+
emit({ ok: true, result: normalizeResult(result) });
|
|
4600
|
+
} catch (error) {
|
|
4601
|
+
emit({ ok: false, error: serializeError(error) });
|
|
4602
|
+
}
|
|
4603
|
+
})();
|
|
4604
|
+
});
|
|
4605
|
+
`;
|
|
4494
4606
|
const resolveTsConfigPath = (rootDirectory) => {
|
|
4495
4607
|
for (const filename of TSCONFIG_FILENAMES$1) {
|
|
4496
4608
|
const candidate = path.join(rootDirectory, filename);
|
|
@@ -4511,16 +4623,191 @@ const toRelativeFilePath = (rootDirectory, filePath) => {
|
|
|
4511
4623
|
const relative = toRelativePath(filePath, rootDirectory);
|
|
4512
4624
|
return relative.length > 0 ? relative : filePath.replace(/\\/g, "/");
|
|
4513
4625
|
};
|
|
4626
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4627
|
+
const parseArray = (value, label) => {
|
|
4628
|
+
if (!Array.isArray(value)) throw new Error(`Dead-code worker returned invalid ${label}.`);
|
|
4629
|
+
return value;
|
|
4630
|
+
};
|
|
4631
|
+
const parseString = (value, label) => {
|
|
4632
|
+
if (typeof value !== "string") throw new Error(`Dead-code worker returned invalid ${label}.`);
|
|
4633
|
+
return value;
|
|
4634
|
+
};
|
|
4635
|
+
const parseNumber = (value, label) => {
|
|
4636
|
+
if (typeof value !== "number") throw new Error(`Dead-code worker returned invalid ${label}.`);
|
|
4637
|
+
return value;
|
|
4638
|
+
};
|
|
4639
|
+
const parseBoolean = (value, label) => {
|
|
4640
|
+
if (typeof value !== "boolean") throw new Error(`Dead-code worker returned invalid ${label}.`);
|
|
4641
|
+
return value;
|
|
4642
|
+
};
|
|
4643
|
+
const parseStringArray = (value, label) => {
|
|
4644
|
+
return parseArray(value, label).map((entry, index) => parseString(entry, `${label}[${index}]`));
|
|
4645
|
+
};
|
|
4646
|
+
const parseUnusedFiles = (value) => {
|
|
4647
|
+
const values = parseArray(value, "unusedFiles");
|
|
4648
|
+
const unusedFiles = [];
|
|
4649
|
+
for (const [index, entry] of values.entries()) {
|
|
4650
|
+
if (!isRecord(entry)) throw new Error(`Dead-code worker returned invalid unusedFiles[${index}].`);
|
|
4651
|
+
unusedFiles.push({ path: parseString(entry.path, `unusedFiles[${index}].path`) });
|
|
4652
|
+
}
|
|
4653
|
+
return unusedFiles;
|
|
4654
|
+
};
|
|
4655
|
+
const parseUnusedExports = (value) => {
|
|
4656
|
+
const values = parseArray(value, "unusedExports");
|
|
4657
|
+
const unusedExports = [];
|
|
4658
|
+
for (const [index, entry] of values.entries()) {
|
|
4659
|
+
if (!isRecord(entry)) throw new Error(`Dead-code worker returned invalid unusedExports[${index}].`);
|
|
4660
|
+
unusedExports.push({
|
|
4661
|
+
path: parseString(entry.path, `unusedExports[${index}].path`),
|
|
4662
|
+
name: parseString(entry.name, `unusedExports[${index}].name`),
|
|
4663
|
+
line: parseNumber(entry.line, `unusedExports[${index}].line`),
|
|
4664
|
+
column: parseNumber(entry.column, `unusedExports[${index}].column`),
|
|
4665
|
+
isTypeOnly: parseBoolean(entry.isTypeOnly, `unusedExports[${index}].isTypeOnly`)
|
|
4666
|
+
});
|
|
4667
|
+
}
|
|
4668
|
+
return unusedExports;
|
|
4669
|
+
};
|
|
4670
|
+
const parseUnusedDependencies = (value) => {
|
|
4671
|
+
const values = parseArray(value, "unusedDependencies");
|
|
4672
|
+
const unusedDependencies = [];
|
|
4673
|
+
for (const [index, entry] of values.entries()) {
|
|
4674
|
+
if (!isRecord(entry)) throw new Error(`Dead-code worker returned invalid unusedDependencies[${index}].`);
|
|
4675
|
+
unusedDependencies.push({
|
|
4676
|
+
name: parseString(entry.name, `unusedDependencies[${index}].name`),
|
|
4677
|
+
isDevDependency: parseBoolean(entry.isDevDependency, `unusedDependencies[${index}].isDevDependency`)
|
|
4678
|
+
});
|
|
4679
|
+
}
|
|
4680
|
+
return unusedDependencies;
|
|
4681
|
+
};
|
|
4682
|
+
const parseCircularDependencies = (value) => {
|
|
4683
|
+
const values = parseArray(value, "circularDependencies");
|
|
4684
|
+
const circularDependencies = [];
|
|
4685
|
+
for (const [index, entry] of values.entries()) {
|
|
4686
|
+
if (!isRecord(entry)) throw new Error(`Dead-code worker returned invalid circularDependencies[${index}].`);
|
|
4687
|
+
circularDependencies.push({ files: parseStringArray(entry.files, `circularDependencies[${index}].files`) });
|
|
4688
|
+
}
|
|
4689
|
+
return circularDependencies;
|
|
4690
|
+
};
|
|
4691
|
+
const parseDeadCodeWorkerResult = (value) => {
|
|
4692
|
+
if (!isRecord(value)) throw new Error("Dead-code worker returned an invalid result.");
|
|
4693
|
+
return {
|
|
4694
|
+
unusedFiles: parseUnusedFiles(value.unusedFiles),
|
|
4695
|
+
unusedExports: parseUnusedExports(value.unusedExports),
|
|
4696
|
+
unusedDependencies: parseUnusedDependencies(value.unusedDependencies),
|
|
4697
|
+
circularDependencies: parseCircularDependencies(value.circularDependencies)
|
|
4698
|
+
};
|
|
4699
|
+
};
|
|
4700
|
+
const parseDeadCodeWorkerError = (value) => {
|
|
4701
|
+
if (!isRecord(value) || typeof value.message !== "string") return { message: "Dead-code worker failed." };
|
|
4702
|
+
return {
|
|
4703
|
+
...typeof value.name === "string" ? { name: value.name } : {},
|
|
4704
|
+
message: value.message,
|
|
4705
|
+
...typeof value.stack === "string" ? { stack: value.stack } : {}
|
|
4706
|
+
};
|
|
4707
|
+
};
|
|
4708
|
+
const parseDeadCodeWorkerMessage = (value) => {
|
|
4709
|
+
if (!isRecord(value)) throw new Error("Dead-code worker returned an invalid message.");
|
|
4710
|
+
if (value.ok === true) return {
|
|
4711
|
+
ok: true,
|
|
4712
|
+
result: value.result
|
|
4713
|
+
};
|
|
4714
|
+
if (value.ok === false) return {
|
|
4715
|
+
ok: false,
|
|
4716
|
+
error: parseDeadCodeWorkerError(value.error)
|
|
4717
|
+
};
|
|
4718
|
+
throw new Error("Dead-code worker returned an invalid status.");
|
|
4719
|
+
};
|
|
4720
|
+
const buildDeadCodeWorkerError = (workerError) => {
|
|
4721
|
+
const error = new Error(workerError.message);
|
|
4722
|
+
if (workerError.name !== void 0) error.name = workerError.name;
|
|
4723
|
+
if (workerError.stack !== void 0) error.stack = workerError.stack;
|
|
4724
|
+
return error;
|
|
4725
|
+
};
|
|
4726
|
+
const createDeadCodeWorker = (input) => {
|
|
4727
|
+
const child = spawn(process.execPath, ["-e", DEAD_CODE_WORKER_SCRIPT], {
|
|
4728
|
+
stdio: [
|
|
4729
|
+
"pipe",
|
|
4730
|
+
"pipe",
|
|
4731
|
+
"pipe"
|
|
4732
|
+
],
|
|
4733
|
+
windowsHide: true
|
|
4734
|
+
});
|
|
4735
|
+
const stdoutChunks = [];
|
|
4736
|
+
const stderrChunks = [];
|
|
4737
|
+
child.stdout.on("data", (chunk) => stdoutChunks.push(chunk));
|
|
4738
|
+
child.stderr.on("data", (chunk) => stderrChunks.push(chunk));
|
|
4739
|
+
let didSettle = false;
|
|
4740
|
+
const result = new Promise((resolve, reject) => {
|
|
4741
|
+
const settle = (callback) => {
|
|
4742
|
+
if (didSettle) return;
|
|
4743
|
+
didSettle = true;
|
|
4744
|
+
callback();
|
|
4745
|
+
};
|
|
4746
|
+
child.once("error", (error) => {
|
|
4747
|
+
settle(() => reject(error));
|
|
4748
|
+
});
|
|
4749
|
+
child.once("close", (exitCode) => {
|
|
4750
|
+
const stdout = Buffer.concat(stdoutChunks).toString("utf8").trim();
|
|
4751
|
+
if (stdout.length === 0) {
|
|
4752
|
+
const stderr = Buffer.concat(stderrChunks).toString("utf8").trim();
|
|
4753
|
+
settle(() => reject(/* @__PURE__ */ new Error(`Dead-code worker exited with code ${exitCode ?? "null"}${stderr ? `: ${stderr}` : ""}.`)));
|
|
4754
|
+
return;
|
|
4755
|
+
}
|
|
4756
|
+
try {
|
|
4757
|
+
const parsedMessage = parseDeadCodeWorkerMessage(JSON.parse(stdout));
|
|
4758
|
+
if (parsedMessage.ok) {
|
|
4759
|
+
settle(() => resolve(parsedMessage.result));
|
|
4760
|
+
return;
|
|
4761
|
+
}
|
|
4762
|
+
settle(() => reject(buildDeadCodeWorkerError(parsedMessage.error)));
|
|
4763
|
+
} catch (error) {
|
|
4764
|
+
settle(() => reject(error));
|
|
4765
|
+
}
|
|
4766
|
+
});
|
|
4767
|
+
});
|
|
4768
|
+
child.stdin.on("error", () => {});
|
|
4769
|
+
child.stdin.end(JSON.stringify(input));
|
|
4770
|
+
return {
|
|
4771
|
+
result,
|
|
4772
|
+
terminate: () => {
|
|
4773
|
+
didSettle = true;
|
|
4774
|
+
child.kill("SIGKILL");
|
|
4775
|
+
}
|
|
4776
|
+
};
|
|
4777
|
+
};
|
|
4778
|
+
const runDeadCodeWorkerWithTimeout = (handle, timeoutMs) => new Promise((resolve, reject) => {
|
|
4779
|
+
let didSettle = false;
|
|
4780
|
+
const timeoutHandle = setTimeout(() => {
|
|
4781
|
+
if (didSettle) return;
|
|
4782
|
+
didSettle = true;
|
|
4783
|
+
handle.terminate?.();
|
|
4784
|
+
reject(/* @__PURE__ */ new Error(`Dead-code worker timed out after ${timeoutMs / MILLISECONDS_PER_SECOND}s.`));
|
|
4785
|
+
}, timeoutMs);
|
|
4786
|
+
timeoutHandle.unref?.();
|
|
4787
|
+
handle.result.then((value) => {
|
|
4788
|
+
if (didSettle) return;
|
|
4789
|
+
didSettle = true;
|
|
4790
|
+
clearTimeout(timeoutHandle);
|
|
4791
|
+
handle.terminate?.();
|
|
4792
|
+
resolve(value);
|
|
4793
|
+
}, (error) => {
|
|
4794
|
+
if (didSettle) return;
|
|
4795
|
+
didSettle = true;
|
|
4796
|
+
clearTimeout(timeoutHandle);
|
|
4797
|
+
handle.terminate?.();
|
|
4798
|
+
reject(error);
|
|
4799
|
+
});
|
|
4800
|
+
});
|
|
4514
4801
|
const checkDeadCode = async (options) => {
|
|
4515
4802
|
const { rootDirectory, userConfig } = options;
|
|
4516
4803
|
if (!fs.existsSync(path.join(rootDirectory, "package.json"))) return [];
|
|
4517
|
-
const { analyze, defineConfig } = await import("deslop-js");
|
|
4518
4804
|
const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory, userConfig);
|
|
4519
|
-
const result = await
|
|
4520
|
-
|
|
4805
|
+
const result = parseDeadCodeWorkerResult(await runDeadCodeWorkerWithTimeout((options.createWorker ?? createDeadCodeWorker)({
|
|
4806
|
+
rootDirectory,
|
|
4521
4807
|
tsConfigPath: resolveTsConfigPath(rootDirectory),
|
|
4522
|
-
|
|
4523
|
-
|
|
4808
|
+
ignorePatterns,
|
|
4809
|
+
deslopJsModuleSpecifier: options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js")
|
|
4810
|
+
}), options.workerTimeoutMs ?? 12e4));
|
|
4524
4811
|
const toRelative = (filePath) => toRelativeFilePath(rootDirectory, filePath);
|
|
4525
4812
|
const diagnostics = [];
|
|
4526
4813
|
for (const unusedFile of result.unusedFiles) diagnostics.push({
|
|
@@ -5144,7 +5431,15 @@ const buildCapabilities = (project) => {
|
|
|
5144
5431
|
capabilities.add(project.framework);
|
|
5145
5432
|
if (project.framework === "expo" || project.framework === "react-native" || project.hasReactNativeWorkspace) capabilities.add("react-native");
|
|
5146
5433
|
const reactMajor = project.reactMajorVersion;
|
|
5147
|
-
if (reactMajor !== null)
|
|
5434
|
+
if (reactMajor !== null) {
|
|
5435
|
+
for (let major = 17; major <= reactMajor; major++) capabilities.add(`react:${major}`);
|
|
5436
|
+
if (reactMajor >= 19) {
|
|
5437
|
+
if (isReactAtLeast(parseReactMajorMinor(project.reactVersion), {
|
|
5438
|
+
major: 19,
|
|
5439
|
+
minor: 2
|
|
5440
|
+
})) capabilities.add("react:19.2");
|
|
5441
|
+
}
|
|
5442
|
+
}
|
|
5148
5443
|
if (project.tailwindVersion !== null) {
|
|
5149
5444
|
capabilities.add("tailwind");
|
|
5150
5445
|
if (isTailwindAtLeast(parseTailwindMajorMinor(project.tailwindVersion), {
|
|
@@ -5155,6 +5450,10 @@ const buildCapabilities = (project) => {
|
|
|
5155
5450
|
if (project.hasReactCompiler) capabilities.add("react-compiler");
|
|
5156
5451
|
if (project.hasTanStackQuery) capabilities.add("tanstack-query");
|
|
5157
5452
|
if (project.hasTypeScript) capabilities.add("typescript");
|
|
5453
|
+
if (project.hasPreact) {
|
|
5454
|
+
capabilities.add("preact");
|
|
5455
|
+
if (project.reactVersion === null) capabilities.add("pure-preact");
|
|
5456
|
+
}
|
|
5158
5457
|
return capabilities;
|
|
5159
5458
|
};
|
|
5160
5459
|
const shouldEnableRule = (requires, tags, capabilities, ignoredTags, disabledBy) => {
|
|
@@ -5409,6 +5708,13 @@ const buildNoSecretsRecommendation = (project, fallbackRecommendation) => {
|
|
|
5409
5708
|
if (!publicEnvPrefix) return fallbackRecommendation;
|
|
5410
5709
|
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
5710
|
};
|
|
5711
|
+
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";
|
|
5712
|
+
const appendReanimatedSharedValueHint = (help, rule, project) => {
|
|
5713
|
+
if (rule !== "immutability") return help;
|
|
5714
|
+
if (!project.hasReanimated) return help;
|
|
5715
|
+
if (!help) return REANIMATED_SHARED_VALUE_HINT;
|
|
5716
|
+
return `${help}\n\n${REANIMATED_SHARED_VALUE_HINT}`;
|
|
5717
|
+
};
|
|
5412
5718
|
const REACT_MODULE_SOURCE = "react";
|
|
5413
5719
|
const REQUIRE_IDENTIFIER = "require";
|
|
5414
5720
|
const USE_IDENTIFIER = "use";
|
|
@@ -5732,7 +6038,7 @@ const getRuleCategory = (ruleName) => reactDoctorPlugin.rules[ruleName]?.categor
|
|
|
5732
6038
|
const cleanDiagnosticMessage = (message, help, plugin, rule, project) => {
|
|
5733
6039
|
if (plugin === "react-hooks-js") return {
|
|
5734
6040
|
message: REACT_COMPILER_MESSAGE,
|
|
5735
|
-
help: message.replace(FILEPATH_WITH_LOCATION_PATTERN, "").trim() || help
|
|
6041
|
+
help: appendReanimatedSharedValueHint(message.replace(FILEPATH_WITH_LOCATION_PATTERN, "").trim() || help, rule, project)
|
|
5736
6042
|
};
|
|
5737
6043
|
return {
|
|
5738
6044
|
message: message.replace(FILEPATH_WITH_LOCATION_PATTERN, "").trim() || message,
|
|
@@ -6484,17 +6790,6 @@ const runInspect = (input, hooks = {}) => Effect.gen(function* () {
|
|
|
6484
6790
|
didFail: false,
|
|
6485
6791
|
reason: null
|
|
6486
6792
|
});
|
|
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
6793
|
const scanProgress = yield* progressService.start("Scanning...");
|
|
6499
6794
|
const scanStartTime = Date.now();
|
|
6500
6795
|
let lastReportedTotalFileCount = 0;
|
|
@@ -6524,11 +6819,18 @@ const runInspect = (input, hooks = {}) => Effect.gen(function* () {
|
|
|
6524
6819
|
const lintCollected = yield* Stream.runCollect(applyPerElementPipeline(rawLintStream));
|
|
6525
6820
|
const lintFailureState = yield* Ref.get(lintFailure);
|
|
6526
6821
|
yield* afterLint(lintFailureState.didFail);
|
|
6527
|
-
if (lintFailureState.didFail)
|
|
6528
|
-
|
|
6529
|
-
|
|
6530
|
-
|
|
6531
|
-
|
|
6822
|
+
if (lintFailureState.didFail) yield* scanProgress.fail(formatLintFailText(lintFailureState.reasonTag, process.version));
|
|
6823
|
+
const shouldRunDeadCode = input.runDeadCode && !isDiffMode;
|
|
6824
|
+
const deadCodeCollected = lintFailureState.didFail || !shouldRunDeadCode ? [] : yield* scanProgress.update("Analyzing dead code...").pipe(Effect.andThen(Stream.runCollect(applyPerElementPipeline(deadCodeService.run({
|
|
6825
|
+
rootDirectory: scanDirectory,
|
|
6826
|
+
userConfig: resolvedConfig.config
|
|
6827
|
+
}).pipe(Stream.catchTag("ReactDoctorError", (error) => Stream.unwrap(Effect.gen(function* () {
|
|
6828
|
+
yield* Ref.set(deadCodeFailure, {
|
|
6829
|
+
didFail: true,
|
|
6830
|
+
reason: error.message
|
|
6831
|
+
});
|
|
6832
|
+
return Stream.empty;
|
|
6833
|
+
}))))))));
|
|
6532
6834
|
const deadCodeFailureState = yield* Ref.get(deadCodeFailure);
|
|
6533
6835
|
const scanElapsedSeconds = ((Date.now() - scanStartTime) / 1e3).toFixed(1);
|
|
6534
6836
|
const totalFileCount = lastReportedTotalFileCount || (lintIncludePaths?.length ?? project.sourceFileCount);
|
|
@@ -7023,7 +7325,7 @@ var cli_logger_exports = /* @__PURE__ */ __exportAll({ cliLogger: () => cliLogge
|
|
|
7023
7325
|
/**
|
|
7024
7326
|
* Thin synchronous façade over Effect's `Console` module. Used by
|
|
7025
7327
|
* the imperative CLI helper files (`select-projects`, `run-explain`,
|
|
7026
|
-
* `install-
|
|
7328
|
+
* `install-react-doctor`, the legacy paths in `cli/commands/inspect.ts`)
|
|
7027
7329
|
* that aren't yet Effect-typed. Every call drains into a single
|
|
7028
7330
|
* `Console.*` Effect via `Effect.runSync`, so the underlying logging
|
|
7029
7331
|
* pipeline is identical to the canonical `yield* Console.log(...)`
|
|
@@ -7056,4 +7358,4 @@ const cliLogger = {
|
|
|
7056
7358
|
//#endregion
|
|
7057
7359
|
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
7360
|
|
|
7059
|
-
//# sourceMappingURL=cli-logger-
|
|
7361
|
+
//# sourceMappingURL=cli-logger-pbFEieEc.js.map
|