react-doctor 0.2.9 → 0.2.11-dev.15e5fec
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/README.md +26 -0
- package/dist/{cli-logger-BliQX9s8.js → cli-logger-CSZagq1E.js} +598 -93
- package/dist/cli.js +194 -110
- package/dist/index.d.ts +26 -2
- package/dist/index.js +592 -94
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -2298,22 +2298,100 @@ const FRAMEWORK_DISPLAY_NAMES = {
|
|
|
2298
2298
|
gatsby: "Gatsby",
|
|
2299
2299
|
expo: "Expo",
|
|
2300
2300
|
"react-native": "React Native",
|
|
2301
|
+
preact: "Preact",
|
|
2301
2302
|
unknown: "React"
|
|
2302
2303
|
};
|
|
2303
2304
|
const formatFrameworkName = (framework) => FRAMEWORK_DISPLAY_NAMES[framework];
|
|
2304
2305
|
const detectFramework = (dependencies) => {
|
|
2305
2306
|
for (const [packageName, frameworkName] of Object.entries(FRAMEWORK_PACKAGES)) if (dependencies[packageName]) return frameworkName;
|
|
2307
|
+
if (dependencies.preact && !dependencies.react) return "preact";
|
|
2306
2308
|
return "unknown";
|
|
2307
2309
|
};
|
|
2308
|
-
const UPPER_BOUND_COMPARATOR = /<\s*=?\s*\d+(?:\.\d+){0,2}(?:-[^\s,|]+)?/g;
|
|
2309
|
-
const HAS_UPPER_BOUND_COMPARATOR = /<\s*=?\s*\d+(?:\.\d+){0,2}(?:-[^\s,|]+)?/;
|
|
2310
|
-
const OR_SEPARATOR = /\s*\|\|\s*/;
|
|
2311
2310
|
const UNRESOLVABLE_PROTOCOL_VERSION = /^(?:file|git|github|https?|link|patch|portal|workspace|npm):/i;
|
|
2312
2311
|
const DIST_TAG_VERSION = /^[a-z][a-z0-9._-]*$/i;
|
|
2313
2312
|
const WILDCARD_VERSION = /^[*xX](?:\.[*xX])*$/;
|
|
2314
|
-
const NON_LOWER_BOUND_COMPARATOR = /(?:^|[\s,|])(?:>(?!=)|!={0,2})\s*\d/;
|
|
2315
|
-
const LOWER_BOUND_MAJOR = /(?:^|[\s,|])(?:>=\s*|[~^=v]\s*)?(\d+)(?=$|[\s,|.*xX-])/g;
|
|
2316
2313
|
const NPM_ALIAS_VERSION = /^npm:(?:@[^/]+\/[^@]+|[^@]+)@(.+)$/i;
|
|
2314
|
+
const isDigit = (value) => value !== void 0 && value >= "0" && value <= "9";
|
|
2315
|
+
const isWhitespace = (value) => value === " " || value === " " || value === "\n" || value === "\r" || value === "\f" || value === "\v";
|
|
2316
|
+
const isSeparator = (value) => isWhitespace(value) || value === "," || value === "|";
|
|
2317
|
+
const skipWhitespace = (value, start) => {
|
|
2318
|
+
let index = start;
|
|
2319
|
+
while (isWhitespace(value[index])) index += 1;
|
|
2320
|
+
return index;
|
|
2321
|
+
};
|
|
2322
|
+
const skipSeparators = (value, start) => {
|
|
2323
|
+
let index = start;
|
|
2324
|
+
while (isSeparator(value[index])) index += 1;
|
|
2325
|
+
return index;
|
|
2326
|
+
};
|
|
2327
|
+
const readDigits = (value, start) => {
|
|
2328
|
+
let index = start;
|
|
2329
|
+
while (isDigit(value[index])) index += 1;
|
|
2330
|
+
return index;
|
|
2331
|
+
};
|
|
2332
|
+
const getUpperBoundComparatorEnd = (version, start) => {
|
|
2333
|
+
if (version[start] !== "<") return null;
|
|
2334
|
+
let index = skipWhitespace(version, start + 1);
|
|
2335
|
+
if (version[index] === "=") index = skipWhitespace(version, index + 1);
|
|
2336
|
+
const majorStart = index;
|
|
2337
|
+
index = readDigits(version, index);
|
|
2338
|
+
if (index === majorStart) return null;
|
|
2339
|
+
for (let segments = 0; segments < 2 && version[index] === "."; segments += 1) {
|
|
2340
|
+
const segmentStart = index + 1;
|
|
2341
|
+
const segmentEnd = readDigits(version, segmentStart);
|
|
2342
|
+
if (segmentEnd === segmentStart) break;
|
|
2343
|
+
index = segmentEnd;
|
|
2344
|
+
}
|
|
2345
|
+
if (version[index] === "-") {
|
|
2346
|
+
index += 1;
|
|
2347
|
+
while (index < version.length && !isSeparator(version[index])) index += 1;
|
|
2348
|
+
}
|
|
2349
|
+
return index;
|
|
2350
|
+
};
|
|
2351
|
+
const stripUpperBoundComparators = (version) => {
|
|
2352
|
+
let stripped = "";
|
|
2353
|
+
let index = 0;
|
|
2354
|
+
while (index < version.length) {
|
|
2355
|
+
const comparatorEnd = getUpperBoundComparatorEnd(version, index);
|
|
2356
|
+
if (comparatorEnd === null) {
|
|
2357
|
+
stripped += version[index];
|
|
2358
|
+
index += 1;
|
|
2359
|
+
continue;
|
|
2360
|
+
}
|
|
2361
|
+
stripped += " ";
|
|
2362
|
+
index = comparatorEnd;
|
|
2363
|
+
}
|
|
2364
|
+
return stripped;
|
|
2365
|
+
};
|
|
2366
|
+
const hasNonLowerBoundComparator = (branch) => {
|
|
2367
|
+
for (let index = 0; index < branch.length; index += 1) {
|
|
2368
|
+
if (index > 0 && !isSeparator(branch[index - 1])) continue;
|
|
2369
|
+
if (branch[index] === ">" && branch[index + 1] !== "=") {
|
|
2370
|
+
if (isDigit(branch[skipWhitespace(branch, index + 1)])) return true;
|
|
2371
|
+
continue;
|
|
2372
|
+
}
|
|
2373
|
+
if (branch[index] !== "!") continue;
|
|
2374
|
+
let valueIndex = index + 1;
|
|
2375
|
+
if (branch[valueIndex] === "=") valueIndex += 1;
|
|
2376
|
+
if (branch[valueIndex] === "=") valueIndex += 1;
|
|
2377
|
+
valueIndex = skipWhitespace(branch, valueIndex);
|
|
2378
|
+
if (isDigit(branch[valueIndex])) return true;
|
|
2379
|
+
}
|
|
2380
|
+
return false;
|
|
2381
|
+
};
|
|
2382
|
+
const isMajorTerminator = (value) => value === void 0 || isSeparator(value) || value === "." || value === "*" || value === "x" || value === "X" || value === "-";
|
|
2383
|
+
const getLowerBoundMajorAt = (branch, start) => {
|
|
2384
|
+
let index = start;
|
|
2385
|
+
if (branch[index] === ">" && branch[index + 1] === "=") index = skipWhitespace(branch, index + 2);
|
|
2386
|
+
else if (branch[index] === "~" || branch[index] === "^" || branch[index] === "=" || branch[index] === "v") index = skipWhitespace(branch, index + 1);
|
|
2387
|
+
const majorStart = index;
|
|
2388
|
+
const majorEnd = readDigits(branch, majorStart);
|
|
2389
|
+
if (majorEnd === majorStart || !isMajorTerminator(branch[majorEnd])) return null;
|
|
2390
|
+
return {
|
|
2391
|
+
end: majorEnd,
|
|
2392
|
+
major: Number.parseInt(branch.slice(majorStart, majorEnd), 10)
|
|
2393
|
+
};
|
|
2394
|
+
};
|
|
2317
2395
|
const normalizeDependencyVersion = (version) => {
|
|
2318
2396
|
const trimmed = version.trim();
|
|
2319
2397
|
if (trimmed.length === 0) return null;
|
|
@@ -2323,17 +2401,29 @@ const normalizeDependencyVersion = (version) => {
|
|
|
2323
2401
|
if (WILDCARD_VERSION.test(normalizedVersion)) return null;
|
|
2324
2402
|
return normalizedVersion;
|
|
2325
2403
|
};
|
|
2326
|
-
const splitDependencyVersionBranches = (version) => version.split(
|
|
2327
|
-
const hasUpperBoundComparator = (version) =>
|
|
2404
|
+
const splitDependencyVersionBranches = (version) => version.split("||").map((branch) => branch.trim()).filter(Boolean);
|
|
2405
|
+
const hasUpperBoundComparator = (version) => {
|
|
2406
|
+
for (let index = 0; index < version.length; index += 1) if (getUpperBoundComparatorEnd(version, index) !== null) return true;
|
|
2407
|
+
return false;
|
|
2408
|
+
};
|
|
2328
2409
|
const getBranchLowestMajor = (branch) => {
|
|
2329
|
-
if (
|
|
2330
|
-
const lowerBoundComparators = branch
|
|
2410
|
+
if (hasNonLowerBoundComparator(branch)) return null;
|
|
2411
|
+
const lowerBoundComparators = stripUpperBoundComparators(branch).trim();
|
|
2331
2412
|
if (lowerBoundComparators.length === 0) return null;
|
|
2332
2413
|
let branchLowestMajor = null;
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
if (
|
|
2414
|
+
let index = 0;
|
|
2415
|
+
while (index < lowerBoundComparators.length) {
|
|
2416
|
+
const lowerBoundStart = skipSeparators(lowerBoundComparators, index);
|
|
2417
|
+
if (lowerBoundStart > 0 && !isSeparator(lowerBoundComparators[lowerBoundStart - 1])) {
|
|
2418
|
+
index = lowerBoundStart + 1;
|
|
2419
|
+
continue;
|
|
2420
|
+
}
|
|
2421
|
+
const lowerBoundMajor = getLowerBoundMajorAt(lowerBoundComparators, lowerBoundStart);
|
|
2422
|
+
if (lowerBoundMajor !== null && Number.isFinite(lowerBoundMajor.major) && lowerBoundMajor.major > 0) {
|
|
2423
|
+
const major = lowerBoundMajor.major;
|
|
2424
|
+
if (branchLowestMajor === null || major < branchLowestMajor) branchLowestMajor = major;
|
|
2425
|
+
}
|
|
2426
|
+
index = lowerBoundMajor?.end ?? lowerBoundStart + 1;
|
|
2337
2427
|
}
|
|
2338
2428
|
return branchLowestMajor;
|
|
2339
2429
|
};
|
|
@@ -2502,6 +2592,7 @@ const resolveCatalogVersion = (packageJson, packageName, rootDirectory, explicit
|
|
|
2502
2592
|
const EMPTY_DEPENDENCY_INFO = {
|
|
2503
2593
|
reactVersion: null,
|
|
2504
2594
|
tailwindVersion: null,
|
|
2595
|
+
zodVersion: null,
|
|
2505
2596
|
framework: "unknown"
|
|
2506
2597
|
};
|
|
2507
2598
|
const pickConcreteVersion = (packageJson, packageName, sections) => {
|
|
@@ -2530,6 +2621,11 @@ const extractDependencyInfo = (packageJson) => {
|
|
|
2530
2621
|
"devDependencies",
|
|
2531
2622
|
"peerDependencies"
|
|
2532
2623
|
]),
|
|
2624
|
+
zodVersion: pickConcreteVersion(packageJson, "zod", [
|
|
2625
|
+
"dependencies",
|
|
2626
|
+
"devDependencies",
|
|
2627
|
+
"peerDependencies"
|
|
2628
|
+
]),
|
|
2533
2629
|
framework: detectFramework(allDependencies)
|
|
2534
2630
|
};
|
|
2535
2631
|
};
|
|
@@ -2674,8 +2770,22 @@ const findReactInWorkspaces = (rootDirectory, packageJson) => {
|
|
|
2674
2770
|
workspaceDirectory,
|
|
2675
2771
|
workspacePackageJson
|
|
2676
2772
|
});
|
|
2773
|
+
const zodVersion = resolveWorkspaceDependencyVersion({
|
|
2774
|
+
concreteVersion: info.zodVersion,
|
|
2775
|
+
packageName: "zod",
|
|
2776
|
+
rootDirectory,
|
|
2777
|
+
rootPackageJson: packageJson,
|
|
2778
|
+
sections: [
|
|
2779
|
+
"dependencies",
|
|
2780
|
+
"devDependencies",
|
|
2781
|
+
"peerDependencies"
|
|
2782
|
+
],
|
|
2783
|
+
workspaceDirectory,
|
|
2784
|
+
workspacePackageJson
|
|
2785
|
+
});
|
|
2677
2786
|
if (reactVersion && shouldReplaceReactVersion(result.reactVersion, reactVersion)) result.reactVersion = reactVersion;
|
|
2678
2787
|
if (tailwindVersion && !result.tailwindVersion) result.tailwindVersion = tailwindVersion;
|
|
2788
|
+
if (zodVersion && !result.zodVersion) result.zodVersion = zodVersion;
|
|
2679
2789
|
if (info.framework !== "unknown" && result.framework === "unknown") result.framework = info.framework;
|
|
2680
2790
|
const resultReactMajor = parseReactMajor(result.reactVersion);
|
|
2681
2791
|
if (result.reactVersion && result.tailwindVersion && result.framework !== "unknown" && resultReactMajor !== null && resultReactMajor <= 17) return result;
|
|
@@ -2710,17 +2820,44 @@ const findDependencyInfoFromMonorepoRoot = (directory) => {
|
|
|
2710
2820
|
"peerDependencies"
|
|
2711
2821
|
]
|
|
2712
2822
|
}) : null;
|
|
2823
|
+
const leafZodDeclaration = leafPackageJson ? getDependencyDeclaration({
|
|
2824
|
+
packageJson: leafPackageJson,
|
|
2825
|
+
packageName: "zod",
|
|
2826
|
+
sections: [
|
|
2827
|
+
"dependencies",
|
|
2828
|
+
"devDependencies",
|
|
2829
|
+
"peerDependencies"
|
|
2830
|
+
]
|
|
2831
|
+
}) : null;
|
|
2713
2832
|
const shouldUseReactFallback = !leafReactDeclaration?.hasDeclaration;
|
|
2714
2833
|
const shouldUseTailwindFallback = leafTailwindDeclaration?.hasDeclaration ?? true;
|
|
2834
|
+
const shouldUseZodFallback = leafZodDeclaration?.hasDeclaration ?? true;
|
|
2715
2835
|
const reactCatalogVersion = shouldUseReactFallback ? resolveCatalogVersion(rootPackageJson, "react", monorepoRoot, leafReactDeclaration?.catalogReference) : null;
|
|
2716
2836
|
const tailwindCatalogVersion = shouldUseTailwindFallback ? resolveCatalogVersion(rootPackageJson, "tailwindcss", monorepoRoot, leafTailwindDeclaration?.catalogReference) : null;
|
|
2837
|
+
const zodCatalogVersion = shouldUseZodFallback ? resolveCatalogVersion(rootPackageJson, "zod", monorepoRoot, leafZodDeclaration?.catalogReference) : null;
|
|
2717
2838
|
const workspaceInfo = findReactInWorkspaces(monorepoRoot, rootPackageJson);
|
|
2718
2839
|
return {
|
|
2719
2840
|
reactVersion: shouldUseReactFallback ? reactCatalogVersion ?? rootInfo.reactVersion ?? workspaceInfo.reactVersion : rootInfo.reactVersion ?? workspaceInfo.reactVersion,
|
|
2720
2841
|
tailwindVersion: shouldUseTailwindFallback ? tailwindCatalogVersion ?? rootInfo.tailwindVersion ?? workspaceInfo.tailwindVersion : null,
|
|
2842
|
+
zodVersion: shouldUseZodFallback ? zodCatalogVersion ?? rootInfo.zodVersion ?? workspaceInfo.zodVersion : null,
|
|
2721
2843
|
framework: rootInfo.framework !== "unknown" ? rootInfo.framework : workspaceInfo.framework
|
|
2722
2844
|
};
|
|
2723
2845
|
};
|
|
2846
|
+
const someWorkspacePackageJson = (rootDirectory, rootPackageJson, predicate) => {
|
|
2847
|
+
if (predicate(rootPackageJson)) return true;
|
|
2848
|
+
const patterns = getWorkspacePatterns(rootDirectory, rootPackageJson);
|
|
2849
|
+
if (patterns.length === 0) return false;
|
|
2850
|
+
const visitedDirectories = /* @__PURE__ */ new Set();
|
|
2851
|
+
for (const pattern of patterns) {
|
|
2852
|
+
const directories = resolveWorkspaceDirectories(rootDirectory, pattern);
|
|
2853
|
+
for (const workspaceDirectory of directories) {
|
|
2854
|
+
if (visitedDirectories.has(workspaceDirectory)) continue;
|
|
2855
|
+
visitedDirectories.add(workspaceDirectory);
|
|
2856
|
+
if (predicate(readPackageJson(path.join(workspaceDirectory, "package.json")))) return true;
|
|
2857
|
+
}
|
|
2858
|
+
}
|
|
2859
|
+
return false;
|
|
2860
|
+
};
|
|
2724
2861
|
const NAMES = new Set([
|
|
2725
2862
|
"react-native",
|
|
2726
2863
|
"react-native-tvos",
|
|
@@ -2751,20 +2888,13 @@ const isPackageJsonReactNativeAware = (packageJson) => {
|
|
|
2751
2888
|
if (containsAnyReactNativeDependency(packageJson.optionalDependencies)) return true;
|
|
2752
2889
|
return false;
|
|
2753
2890
|
};
|
|
2754
|
-
const hasReactNativeWorkspaceAnywhere = (rootDirectory, rootPackageJson) =>
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
for (const workspaceDirectory of directories) {
|
|
2762
|
-
if (visitedDirectories.has(workspaceDirectory)) continue;
|
|
2763
|
-
visitedDirectories.add(workspaceDirectory);
|
|
2764
|
-
if (isPackageJsonReactNativeAware(readPackageJson(path.join(workspaceDirectory, "package.json")))) return true;
|
|
2765
|
-
}
|
|
2766
|
-
}
|
|
2767
|
-
return false;
|
|
2891
|
+
const hasReactNativeWorkspaceAnywhere = (rootDirectory, rootPackageJson) => someWorkspacePackageJson(rootDirectory, rootPackageJson, isPackageJsonReactNativeAware);
|
|
2892
|
+
const getPreactVersion = (packageJson) => {
|
|
2893
|
+
return {
|
|
2894
|
+
...packageJson.peerDependencies,
|
|
2895
|
+
...packageJson.dependencies,
|
|
2896
|
+
...packageJson.devDependencies
|
|
2897
|
+
}.preact ?? null;
|
|
2768
2898
|
};
|
|
2769
2899
|
const TANSTACK_QUERY_PACKAGES = new Set([
|
|
2770
2900
|
"@tanstack/react-query",
|
|
@@ -2779,6 +2909,20 @@ const hasTanStackQuery = (packageJson) => {
|
|
|
2779
2909
|
};
|
|
2780
2910
|
return Object.keys(allDependencies).some((packageName) => TANSTACK_QUERY_PACKAGES.has(packageName));
|
|
2781
2911
|
};
|
|
2912
|
+
const REANIMATED_DEPENDENCY_NAME = "react-native-reanimated";
|
|
2913
|
+
const isPackageJsonReanimatedAware = (packageJson) => {
|
|
2914
|
+
const allDependencies = {
|
|
2915
|
+
...packageJson.peerDependencies,
|
|
2916
|
+
...packageJson.dependencies,
|
|
2917
|
+
...packageJson.devDependencies,
|
|
2918
|
+
...packageJson.optionalDependencies
|
|
2919
|
+
};
|
|
2920
|
+
return Object.hasOwn(allDependencies, REANIMATED_DEPENDENCY_NAME);
|
|
2921
|
+
};
|
|
2922
|
+
const parseZodMajor = (zodVersion) => {
|
|
2923
|
+
if (typeof zodVersion !== "string") return null;
|
|
2924
|
+
return getLowestDependencyMajor(zodVersion);
|
|
2925
|
+
};
|
|
2782
2926
|
const hasUpperBoundOnlyPeerRange = (range) => {
|
|
2783
2927
|
if (typeof range !== "string") return false;
|
|
2784
2928
|
const normalizedRange = normalizeDependencyVersion(range);
|
|
@@ -2803,7 +2947,8 @@ const resolveEffectiveReactMajor = (reactVersion, packageJson) => {
|
|
|
2803
2947
|
const REACT_DEPENDENCY_NAMES = new Set([
|
|
2804
2948
|
"react",
|
|
2805
2949
|
"react-native",
|
|
2806
|
-
"next"
|
|
2950
|
+
"next",
|
|
2951
|
+
"preact"
|
|
2807
2952
|
]);
|
|
2808
2953
|
const hasReactDependency = (packageJson) => {
|
|
2809
2954
|
const allDependencies = {
|
|
@@ -2864,12 +3009,22 @@ const listManifestWorkspacePackages = (rootDirectory) => {
|
|
|
2864
3009
|
const nxPatterns = patterns.length > 0 ? [] : getNxWorkspaceDirectories(rootDirectory);
|
|
2865
3010
|
return toReactWorkspacePackages((patterns.length > 0 ? patterns : nxPatterns).flatMap((pattern) => resolveWorkspaceDirectories(rootDirectory, pattern)));
|
|
2866
3011
|
};
|
|
3012
|
+
const NON_PROJECT_DIRECTORIES = new Set([
|
|
3013
|
+
"AppData",
|
|
3014
|
+
"Application Data",
|
|
3015
|
+
"Library"
|
|
3016
|
+
]);
|
|
3017
|
+
const MAX_SCAN_DEPTH = 6;
|
|
2867
3018
|
const discoverReactSubprojectsByFilesystem = (rootDirectory) => {
|
|
2868
3019
|
const packages = [];
|
|
2869
|
-
const pendingDirectories = [
|
|
3020
|
+
const pendingDirectories = [{
|
|
3021
|
+
directory: rootDirectory,
|
|
3022
|
+
depth: 0
|
|
3023
|
+
}];
|
|
2870
3024
|
while (pendingDirectories.length > 0) {
|
|
2871
|
-
const
|
|
2872
|
-
if (!
|
|
3025
|
+
const current = pendingDirectories.pop();
|
|
3026
|
+
if (!current) continue;
|
|
3027
|
+
const { directory: currentDirectory, depth } = current;
|
|
2873
3028
|
const packageJsonPath = path.join(currentDirectory, "package.json");
|
|
2874
3029
|
if (isFile(packageJsonPath)) {
|
|
2875
3030
|
const packageJson = readPackageJson(packageJsonPath);
|
|
@@ -2881,10 +3036,14 @@ const discoverReactSubprojectsByFilesystem = (rootDirectory) => {
|
|
|
2881
3036
|
});
|
|
2882
3037
|
}
|
|
2883
3038
|
}
|
|
3039
|
+
if (depth >= MAX_SCAN_DEPTH) continue;
|
|
2884
3040
|
const entries = readDirectoryEntries(currentDirectory).toSorted((firstEntry, secondEntry) => firstEntry.name.localeCompare(secondEntry.name));
|
|
2885
3041
|
for (const entry of entries) {
|
|
2886
|
-
if (!entry.isDirectory() || entry.name.startsWith(".") || IGNORED_DIRECTORIES.has(entry.name)) continue;
|
|
2887
|
-
pendingDirectories.push(
|
|
3042
|
+
if (!entry.isDirectory() || entry.name.startsWith(".") || IGNORED_DIRECTORIES.has(entry.name) || NON_PROJECT_DIRECTORIES.has(entry.name)) continue;
|
|
3043
|
+
pendingDirectories.push({
|
|
3044
|
+
directory: path.join(currentDirectory, entry.name),
|
|
3045
|
+
depth: depth + 1
|
|
3046
|
+
});
|
|
2888
3047
|
}
|
|
2889
3048
|
}
|
|
2890
3049
|
return packages;
|
|
@@ -2905,7 +3064,7 @@ const discoverProject = (directory) => {
|
|
|
2905
3064
|
const packageJsonPath = path.join(directory, "package.json");
|
|
2906
3065
|
if (!isFile(packageJsonPath)) throw new PackageJsonNotFoundError(directory);
|
|
2907
3066
|
const packageJson = readPackageJson(packageJsonPath);
|
|
2908
|
-
let { reactVersion, tailwindVersion, framework } = extractDependencyInfo(packageJson);
|
|
3067
|
+
let { reactVersion, tailwindVersion, zodVersion, framework } = extractDependencyInfo(packageJson);
|
|
2909
3068
|
const reactDeclaration = getDependencyDeclaration({
|
|
2910
3069
|
packageJson,
|
|
2911
3070
|
packageName: "react",
|
|
@@ -2924,9 +3083,19 @@ const discoverProject = (directory) => {
|
|
|
2924
3083
|
"peerDependencies"
|
|
2925
3084
|
]
|
|
2926
3085
|
});
|
|
3086
|
+
const zodDeclaration = getDependencyDeclaration({
|
|
3087
|
+
packageJson,
|
|
3088
|
+
packageName: "zod",
|
|
3089
|
+
sections: [
|
|
3090
|
+
"dependencies",
|
|
3091
|
+
"devDependencies",
|
|
3092
|
+
"peerDependencies"
|
|
3093
|
+
]
|
|
3094
|
+
});
|
|
2927
3095
|
if (!reactVersion && reactDeclaration.hasDeclaration) reactVersion = resolveCatalogVersion(packageJson, "react", directory, reactDeclaration.catalogReference);
|
|
2928
3096
|
if (!tailwindVersion && tailwindDeclaration.hasDeclaration) tailwindVersion = resolveCatalogVersion(packageJson, "tailwindcss", directory, tailwindDeclaration.catalogReference);
|
|
2929
|
-
if (!
|
|
3097
|
+
if (!zodVersion && zodDeclaration.hasDeclaration) zodVersion = resolveCatalogVersion(packageJson, "zod", directory, zodDeclaration.catalogReference);
|
|
3098
|
+
if (!reactVersion || !tailwindVersion || !zodVersion) {
|
|
2930
3099
|
const monorepoRoot = findMonorepoRoot(directory);
|
|
2931
3100
|
if (monorepoRoot) {
|
|
2932
3101
|
const monorepoPackageJsonPath = path.join(monorepoRoot, "package.json");
|
|
@@ -2934,6 +3103,7 @@ const discoverProject = (directory) => {
|
|
|
2934
3103
|
const rootPackageJson = readPackageJson(monorepoPackageJsonPath);
|
|
2935
3104
|
if (!reactVersion && reactDeclaration.hasDeclaration) reactVersion = resolveCatalogVersion(rootPackageJson, "react", monorepoRoot, reactDeclaration.catalogReference);
|
|
2936
3105
|
if (!tailwindVersion && tailwindDeclaration.hasDeclaration) tailwindVersion = resolveCatalogVersion(rootPackageJson, "tailwindcss", monorepoRoot, tailwindDeclaration.catalogReference);
|
|
3106
|
+
if (!zodVersion && zodDeclaration.hasDeclaration) zodVersion = resolveCatalogVersion(rootPackageJson, "zod", monorepoRoot, zodDeclaration.catalogReference);
|
|
2937
3107
|
}
|
|
2938
3108
|
}
|
|
2939
3109
|
}
|
|
@@ -2941,36 +3111,81 @@ const discoverProject = (directory) => {
|
|
|
2941
3111
|
const workspaceInfo = findReactInWorkspaces(directory, packageJson);
|
|
2942
3112
|
if (!reactVersion && workspaceInfo.reactVersion) reactVersion = workspaceInfo.reactVersion;
|
|
2943
3113
|
if (!tailwindVersion && workspaceInfo.tailwindVersion) tailwindVersion = workspaceInfo.tailwindVersion;
|
|
3114
|
+
if (!zodVersion && workspaceInfo.zodVersion) zodVersion = workspaceInfo.zodVersion;
|
|
2944
3115
|
if (framework === "unknown" && workspaceInfo.framework !== "unknown") framework = workspaceInfo.framework;
|
|
2945
3116
|
}
|
|
2946
3117
|
if ((!reactVersion || framework === "unknown") && !isMonorepoRoot(directory)) {
|
|
2947
3118
|
const monorepoInfo = findDependencyInfoFromMonorepoRoot(directory);
|
|
2948
3119
|
if (!reactVersion) reactVersion = monorepoInfo.reactVersion;
|
|
2949
3120
|
if (!tailwindVersion) tailwindVersion = monorepoInfo.tailwindVersion;
|
|
3121
|
+
if (!zodVersion) zodVersion = monorepoInfo.zodVersion;
|
|
2950
3122
|
if (framework === "unknown") framework = monorepoInfo.framework;
|
|
2951
3123
|
}
|
|
2952
3124
|
if (!reactVersion && reactDeclaration.version && !isCatalogReference(reactDeclaration.version)) reactVersion = reactDeclaration.version;
|
|
2953
3125
|
if (!tailwindVersion && tailwindDeclaration.version && !isCatalogReference(tailwindDeclaration.version)) tailwindVersion = tailwindDeclaration.version;
|
|
3126
|
+
if (!zodVersion && zodDeclaration.version && !isCatalogReference(zodDeclaration.version)) zodVersion = zodDeclaration.version;
|
|
2954
3127
|
const projectName = packageJson.name ?? path.basename(directory);
|
|
2955
3128
|
const hasTypeScript = fs.existsSync(path.join(directory, "tsconfig.json"));
|
|
2956
3129
|
const sourceFileCount = countSourceFiles(directory);
|
|
2957
3130
|
const hasReactNativeWorkspace = framework === "expo" || framework === "react-native" || hasReactNativeWorkspaceAnywhere(directory, packageJson);
|
|
3131
|
+
const hasReanimated = hasReactNativeWorkspace && someWorkspacePackageJson(directory, packageJson, isPackageJsonReanimatedAware);
|
|
3132
|
+
const preactVersion = getPreactVersion(packageJson);
|
|
2958
3133
|
const projectInfo = {
|
|
2959
3134
|
rootDirectory: directory,
|
|
2960
3135
|
projectName,
|
|
2961
3136
|
reactVersion,
|
|
2962
3137
|
reactMajorVersion: resolveEffectiveReactMajor(reactVersion, packageJson),
|
|
2963
3138
|
tailwindVersion,
|
|
3139
|
+
zodVersion,
|
|
3140
|
+
zodMajorVersion: parseZodMajor(zodVersion),
|
|
2964
3141
|
framework,
|
|
2965
3142
|
hasTypeScript,
|
|
2966
3143
|
hasReactCompiler: detectReactCompiler(directory, packageJson),
|
|
2967
3144
|
hasTanStackQuery: hasTanStackQuery(packageJson),
|
|
3145
|
+
preactVersion,
|
|
3146
|
+
preactMajorVersion: parseReactMajor(preactVersion),
|
|
2968
3147
|
hasReactNativeWorkspace,
|
|
3148
|
+
hasReanimated,
|
|
2969
3149
|
sourceFileCount
|
|
2970
3150
|
};
|
|
2971
3151
|
cachedProjectInfos.set(directory, projectInfo);
|
|
2972
3152
|
return projectInfo;
|
|
2973
3153
|
};
|
|
3154
|
+
const isAnalyzableProject = (project) => project.reactVersion !== null || project.preactVersion !== null;
|
|
3155
|
+
const MAJOR_MINOR_PATTERN = /(\d{1,4})\.(\d{1,4})/;
|
|
3156
|
+
const MAJOR_ONLY_PATTERN = /(\d{1,4})/;
|
|
3157
|
+
const UPPER_BOUND_COMPARATOR_PATTERN = /<=?\s{0,8}\d{1,4}(?:\.\d{1,4}){0,2}(?:-[^\s,|]+)?/g;
|
|
3158
|
+
const parseReactMajorMinor = (reactVersion) => {
|
|
3159
|
+
if (typeof reactVersion !== "string") return null;
|
|
3160
|
+
const trimmed = reactVersion.trim();
|
|
3161
|
+
if (trimmed.length === 0) return null;
|
|
3162
|
+
const lowerBoundsOnly = trimmed.replace(UPPER_BOUND_COMPARATOR_PATTERN, " ").trim();
|
|
3163
|
+
if (lowerBoundsOnly.length === 0) return null;
|
|
3164
|
+
const majorMinorMatch = lowerBoundsOnly.match(MAJOR_MINOR_PATTERN);
|
|
3165
|
+
if (majorMinorMatch) {
|
|
3166
|
+
const major = Number.parseInt(majorMinorMatch[1], 10);
|
|
3167
|
+
const minor = Number.parseInt(majorMinorMatch[2], 10);
|
|
3168
|
+
if (!Number.isFinite(major) || major <= 0) return null;
|
|
3169
|
+
if (!Number.isFinite(minor) || minor < 0) return null;
|
|
3170
|
+
return {
|
|
3171
|
+
major,
|
|
3172
|
+
minor
|
|
3173
|
+
};
|
|
3174
|
+
}
|
|
3175
|
+
const majorOnlyMatch = lowerBoundsOnly.match(MAJOR_ONLY_PATTERN);
|
|
3176
|
+
if (!majorOnlyMatch) return null;
|
|
3177
|
+
const major = Number.parseInt(majorOnlyMatch[1], 10);
|
|
3178
|
+
if (!Number.isFinite(major) || major <= 0) return null;
|
|
3179
|
+
return {
|
|
3180
|
+
major,
|
|
3181
|
+
minor: 0
|
|
3182
|
+
};
|
|
3183
|
+
};
|
|
3184
|
+
const isReactAtLeast = (detected, required) => {
|
|
3185
|
+
if (detected === null) return true;
|
|
3186
|
+
if (detected.major !== required.major) return detected.major > required.major;
|
|
3187
|
+
return detected.minor >= required.minor;
|
|
3188
|
+
};
|
|
2974
3189
|
const parseTailwindMajorMinor = (tailwindVersion) => {
|
|
2975
3190
|
if (typeof tailwindVersion !== "string") return null;
|
|
2976
3191
|
const trimmed = tailwindVersion.trim();
|
|
@@ -3001,6 +3216,7 @@ const isTailwindAtLeast = (detected, required) => {
|
|
|
3001
3216
|
return detected.minor >= required.minor;
|
|
3002
3217
|
};
|
|
3003
3218
|
const JSX_FILE_PATTERN = /\.(tsx|jsx)$/;
|
|
3219
|
+
const MILLISECONDS_PER_SECOND = 1e3;
|
|
3004
3220
|
const SCORE_API_URL = "https://www.react.doctor/api/score";
|
|
3005
3221
|
const FETCH_TIMEOUT_MS = 1e4;
|
|
3006
3222
|
const GITHUB_VIEWER_PERMISSION_TIMEOUT_MS = 2e3;
|
|
@@ -3901,17 +4117,26 @@ const layerOtlp = Layer.unwrap(Effect.gen(function* () {
|
|
|
3901
4117
|
headers
|
|
3902
4118
|
}).pipe(Layer.provide(FetchHttpClient.layer));
|
|
3903
4119
|
}).pipe(Effect.orDie));
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
4120
|
+
/**
|
|
4121
|
+
* Per-batch oxlint wall-clock budget. Reads from the env var on
|
|
4122
|
+
* startup so the eval harness can raise the budget under sandbox
|
|
4123
|
+
* microVMs without recompiling react-doctor. Tests override via
|
|
4124
|
+
* `Layer.succeed(OxlintSpawnTimeoutMs, ...)`.
|
|
4125
|
+
*/
|
|
4126
|
+
var OxlintSpawnTimeoutMs = class extends Context.Reference("react-doctor/OxlintSpawnTimeoutMs", { defaultValue: () => {
|
|
3907
4127
|
const raw = process.env["REACT_DOCTOR_OXLINT_SPAWN_TIMEOUT_MS"];
|
|
3908
4128
|
if (raw === void 0) return OXLINT_SPAWN_TIMEOUT_MS;
|
|
3909
4129
|
const parsed = Number(raw);
|
|
3910
4130
|
if (!Number.isFinite(parsed) || parsed <= 0) return OXLINT_SPAWN_TIMEOUT_MS;
|
|
3911
4131
|
return parsed;
|
|
3912
|
-
} });
|
|
3913
|
-
|
|
3914
|
-
|
|
4132
|
+
} }) {};
|
|
4133
|
+
/**
|
|
4134
|
+
* Hard cap on combined stdout+stderr bytes per oxlint batch. The
|
|
4135
|
+
* subprocess gets SIGKILL'd if it produces more; the recovery path
|
|
4136
|
+
* suggests narrowing the scan with --diff. Override via Layer in
|
|
4137
|
+
* tests that exercise the cap behavior.
|
|
4138
|
+
*/
|
|
4139
|
+
var OxlintOutputMaxBytes = class extends Context.Reference("react-doctor/OxlintOutputMaxBytes", { defaultValue: () => OXLINT_OUTPUT_MAX_BYTES }) {};
|
|
3915
4140
|
const DIAGNOSTIC_SURFACES = [
|
|
3916
4141
|
"cli",
|
|
3917
4142
|
"prComment",
|
|
@@ -4522,6 +4747,59 @@ const collectIgnorePatterns = (rootDirectory) => {
|
|
|
4522
4747
|
return patterns;
|
|
4523
4748
|
};
|
|
4524
4749
|
const TSCONFIG_FILENAMES$1 = ["tsconfig.json", "tsconfig.base.json"];
|
|
4750
|
+
const DEAD_CODE_WORKER_SCRIPT = `
|
|
4751
|
+
const inputChunks = [];
|
|
4752
|
+
process.stdin.on("data", (chunk) => inputChunks.push(chunk));
|
|
4753
|
+
process.stdin.on("end", () => {
|
|
4754
|
+
const workerInput = JSON.parse(Buffer.concat(inputChunks).toString("utf8"));
|
|
4755
|
+
|
|
4756
|
+
const normalizeResult = (result) => ({
|
|
4757
|
+
unusedFiles: result.unusedFiles.map((unusedFile) => ({
|
|
4758
|
+
path: unusedFile.path,
|
|
4759
|
+
})),
|
|
4760
|
+
unusedExports: result.unusedExports.map((unusedExport) => ({
|
|
4761
|
+
path: unusedExport.path,
|
|
4762
|
+
name: unusedExport.name,
|
|
4763
|
+
line: unusedExport.line,
|
|
4764
|
+
column: unusedExport.column,
|
|
4765
|
+
isTypeOnly: unusedExport.isTypeOnly,
|
|
4766
|
+
})),
|
|
4767
|
+
unusedDependencies: result.unusedDependencies.map((unusedDependency) => ({
|
|
4768
|
+
name: unusedDependency.name,
|
|
4769
|
+
isDevDependency: unusedDependency.isDevDependency,
|
|
4770
|
+
})),
|
|
4771
|
+
circularDependencies: result.circularDependencies.map((cycle) => ({
|
|
4772
|
+
files: cycle.files,
|
|
4773
|
+
})),
|
|
4774
|
+
});
|
|
4775
|
+
|
|
4776
|
+
const serializeError = (error) =>
|
|
4777
|
+
error instanceof Error
|
|
4778
|
+
? { name: error.name, message: error.message, stack: error.stack }
|
|
4779
|
+
: { message: String(error) };
|
|
4780
|
+
|
|
4781
|
+
const emit = (message) => {
|
|
4782
|
+
process.stdout.write(JSON.stringify(message), () => process.exit(0));
|
|
4783
|
+
};
|
|
4784
|
+
|
|
4785
|
+
(async () => {
|
|
4786
|
+
try {
|
|
4787
|
+
const { analyze, defineConfig } = await import(workerInput.deslopJsModuleSpecifier);
|
|
4788
|
+
const config = {
|
|
4789
|
+
rootDir: workerInput.rootDirectory,
|
|
4790
|
+
...(workerInput.tsConfigPath ? { tsConfigPath: workerInput.tsConfigPath } : {}),
|
|
4791
|
+
...(workerInput.ignorePatterns.length > 0
|
|
4792
|
+
? { ignorePatterns: workerInput.ignorePatterns }
|
|
4793
|
+
: {}),
|
|
4794
|
+
};
|
|
4795
|
+
const result = await analyze(defineConfig(config));
|
|
4796
|
+
emit({ ok: true, result: normalizeResult(result) });
|
|
4797
|
+
} catch (error) {
|
|
4798
|
+
emit({ ok: false, error: serializeError(error) });
|
|
4799
|
+
}
|
|
4800
|
+
})();
|
|
4801
|
+
});
|
|
4802
|
+
`;
|
|
4525
4803
|
const resolveTsConfigPath = (rootDirectory) => {
|
|
4526
4804
|
for (const filename of TSCONFIG_FILENAMES$1) {
|
|
4527
4805
|
const candidate = path.join(rootDirectory, filename);
|
|
@@ -4542,16 +4820,191 @@ const toRelativeFilePath = (rootDirectory, filePath) => {
|
|
|
4542
4820
|
const relative = toRelativePath(filePath, rootDirectory);
|
|
4543
4821
|
return relative.length > 0 ? relative : filePath.replace(/\\/g, "/");
|
|
4544
4822
|
};
|
|
4823
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4824
|
+
const parseArray = (value, label) => {
|
|
4825
|
+
if (!Array.isArray(value)) throw new Error(`Dead-code worker returned invalid ${label}.`);
|
|
4826
|
+
return value;
|
|
4827
|
+
};
|
|
4828
|
+
const parseString = (value, label) => {
|
|
4829
|
+
if (typeof value !== "string") throw new Error(`Dead-code worker returned invalid ${label}.`);
|
|
4830
|
+
return value;
|
|
4831
|
+
};
|
|
4832
|
+
const parseNumber = (value, label) => {
|
|
4833
|
+
if (typeof value !== "number") throw new Error(`Dead-code worker returned invalid ${label}.`);
|
|
4834
|
+
return value;
|
|
4835
|
+
};
|
|
4836
|
+
const parseBoolean = (value, label) => {
|
|
4837
|
+
if (typeof value !== "boolean") throw new Error(`Dead-code worker returned invalid ${label}.`);
|
|
4838
|
+
return value;
|
|
4839
|
+
};
|
|
4840
|
+
const parseStringArray = (value, label) => {
|
|
4841
|
+
return parseArray(value, label).map((entry, index) => parseString(entry, `${label}[${index}]`));
|
|
4842
|
+
};
|
|
4843
|
+
const parseUnusedFiles = (value) => {
|
|
4844
|
+
const values = parseArray(value, "unusedFiles");
|
|
4845
|
+
const unusedFiles = [];
|
|
4846
|
+
for (const [index, entry] of values.entries()) {
|
|
4847
|
+
if (!isRecord(entry)) throw new Error(`Dead-code worker returned invalid unusedFiles[${index}].`);
|
|
4848
|
+
unusedFiles.push({ path: parseString(entry.path, `unusedFiles[${index}].path`) });
|
|
4849
|
+
}
|
|
4850
|
+
return unusedFiles;
|
|
4851
|
+
};
|
|
4852
|
+
const parseUnusedExports = (value) => {
|
|
4853
|
+
const values = parseArray(value, "unusedExports");
|
|
4854
|
+
const unusedExports = [];
|
|
4855
|
+
for (const [index, entry] of values.entries()) {
|
|
4856
|
+
if (!isRecord(entry)) throw new Error(`Dead-code worker returned invalid unusedExports[${index}].`);
|
|
4857
|
+
unusedExports.push({
|
|
4858
|
+
path: parseString(entry.path, `unusedExports[${index}].path`),
|
|
4859
|
+
name: parseString(entry.name, `unusedExports[${index}].name`),
|
|
4860
|
+
line: parseNumber(entry.line, `unusedExports[${index}].line`),
|
|
4861
|
+
column: parseNumber(entry.column, `unusedExports[${index}].column`),
|
|
4862
|
+
isTypeOnly: parseBoolean(entry.isTypeOnly, `unusedExports[${index}].isTypeOnly`)
|
|
4863
|
+
});
|
|
4864
|
+
}
|
|
4865
|
+
return unusedExports;
|
|
4866
|
+
};
|
|
4867
|
+
const parseUnusedDependencies = (value) => {
|
|
4868
|
+
const values = parseArray(value, "unusedDependencies");
|
|
4869
|
+
const unusedDependencies = [];
|
|
4870
|
+
for (const [index, entry] of values.entries()) {
|
|
4871
|
+
if (!isRecord(entry)) throw new Error(`Dead-code worker returned invalid unusedDependencies[${index}].`);
|
|
4872
|
+
unusedDependencies.push({
|
|
4873
|
+
name: parseString(entry.name, `unusedDependencies[${index}].name`),
|
|
4874
|
+
isDevDependency: parseBoolean(entry.isDevDependency, `unusedDependencies[${index}].isDevDependency`)
|
|
4875
|
+
});
|
|
4876
|
+
}
|
|
4877
|
+
return unusedDependencies;
|
|
4878
|
+
};
|
|
4879
|
+
const parseCircularDependencies = (value) => {
|
|
4880
|
+
const values = parseArray(value, "circularDependencies");
|
|
4881
|
+
const circularDependencies = [];
|
|
4882
|
+
for (const [index, entry] of values.entries()) {
|
|
4883
|
+
if (!isRecord(entry)) throw new Error(`Dead-code worker returned invalid circularDependencies[${index}].`);
|
|
4884
|
+
circularDependencies.push({ files: parseStringArray(entry.files, `circularDependencies[${index}].files`) });
|
|
4885
|
+
}
|
|
4886
|
+
return circularDependencies;
|
|
4887
|
+
};
|
|
4888
|
+
const parseDeadCodeWorkerResult = (value) => {
|
|
4889
|
+
if (!isRecord(value)) throw new Error("Dead-code worker returned an invalid result.");
|
|
4890
|
+
return {
|
|
4891
|
+
unusedFiles: parseUnusedFiles(value.unusedFiles),
|
|
4892
|
+
unusedExports: parseUnusedExports(value.unusedExports),
|
|
4893
|
+
unusedDependencies: parseUnusedDependencies(value.unusedDependencies),
|
|
4894
|
+
circularDependencies: parseCircularDependencies(value.circularDependencies)
|
|
4895
|
+
};
|
|
4896
|
+
};
|
|
4897
|
+
const parseDeadCodeWorkerError = (value) => {
|
|
4898
|
+
if (!isRecord(value) || typeof value.message !== "string") return { message: "Dead-code worker failed." };
|
|
4899
|
+
return {
|
|
4900
|
+
...typeof value.name === "string" ? { name: value.name } : {},
|
|
4901
|
+
message: value.message,
|
|
4902
|
+
...typeof value.stack === "string" ? { stack: value.stack } : {}
|
|
4903
|
+
};
|
|
4904
|
+
};
|
|
4905
|
+
const parseDeadCodeWorkerMessage = (value) => {
|
|
4906
|
+
if (!isRecord(value)) throw new Error("Dead-code worker returned an invalid message.");
|
|
4907
|
+
if (value.ok === true) return {
|
|
4908
|
+
ok: true,
|
|
4909
|
+
result: value.result
|
|
4910
|
+
};
|
|
4911
|
+
if (value.ok === false) return {
|
|
4912
|
+
ok: false,
|
|
4913
|
+
error: parseDeadCodeWorkerError(value.error)
|
|
4914
|
+
};
|
|
4915
|
+
throw new Error("Dead-code worker returned an invalid status.");
|
|
4916
|
+
};
|
|
4917
|
+
const buildDeadCodeWorkerError = (workerError) => {
|
|
4918
|
+
const error = new Error(workerError.message);
|
|
4919
|
+
if (workerError.name !== void 0) error.name = workerError.name;
|
|
4920
|
+
if (workerError.stack !== void 0) error.stack = workerError.stack;
|
|
4921
|
+
return error;
|
|
4922
|
+
};
|
|
4923
|
+
const createDeadCodeWorker = (input) => {
|
|
4924
|
+
const child = spawn(process.execPath, ["-e", DEAD_CODE_WORKER_SCRIPT], {
|
|
4925
|
+
stdio: [
|
|
4926
|
+
"pipe",
|
|
4927
|
+
"pipe",
|
|
4928
|
+
"pipe"
|
|
4929
|
+
],
|
|
4930
|
+
windowsHide: true
|
|
4931
|
+
});
|
|
4932
|
+
const stdoutChunks = [];
|
|
4933
|
+
const stderrChunks = [];
|
|
4934
|
+
child.stdout.on("data", (chunk) => stdoutChunks.push(chunk));
|
|
4935
|
+
child.stderr.on("data", (chunk) => stderrChunks.push(chunk));
|
|
4936
|
+
let didSettle = false;
|
|
4937
|
+
const result = new Promise((resolve, reject) => {
|
|
4938
|
+
const settle = (callback) => {
|
|
4939
|
+
if (didSettle) return;
|
|
4940
|
+
didSettle = true;
|
|
4941
|
+
callback();
|
|
4942
|
+
};
|
|
4943
|
+
child.once("error", (error) => {
|
|
4944
|
+
settle(() => reject(error));
|
|
4945
|
+
});
|
|
4946
|
+
child.once("close", (exitCode) => {
|
|
4947
|
+
const stdout = Buffer.concat(stdoutChunks).toString("utf8").trim();
|
|
4948
|
+
if (stdout.length === 0) {
|
|
4949
|
+
const stderr = Buffer.concat(stderrChunks).toString("utf8").trim();
|
|
4950
|
+
settle(() => reject(/* @__PURE__ */ new Error(`Dead-code worker exited with code ${exitCode ?? "null"}${stderr ? `: ${stderr}` : ""}.`)));
|
|
4951
|
+
return;
|
|
4952
|
+
}
|
|
4953
|
+
try {
|
|
4954
|
+
const parsedMessage = parseDeadCodeWorkerMessage(JSON.parse(stdout));
|
|
4955
|
+
if (parsedMessage.ok) {
|
|
4956
|
+
settle(() => resolve(parsedMessage.result));
|
|
4957
|
+
return;
|
|
4958
|
+
}
|
|
4959
|
+
settle(() => reject(buildDeadCodeWorkerError(parsedMessage.error)));
|
|
4960
|
+
} catch (error) {
|
|
4961
|
+
settle(() => reject(error));
|
|
4962
|
+
}
|
|
4963
|
+
});
|
|
4964
|
+
});
|
|
4965
|
+
child.stdin.on("error", () => {});
|
|
4966
|
+
child.stdin.end(JSON.stringify(input));
|
|
4967
|
+
return {
|
|
4968
|
+
result,
|
|
4969
|
+
terminate: () => {
|
|
4970
|
+
didSettle = true;
|
|
4971
|
+
child.kill("SIGKILL");
|
|
4972
|
+
}
|
|
4973
|
+
};
|
|
4974
|
+
};
|
|
4975
|
+
const runDeadCodeWorkerWithTimeout = (handle, timeoutMs) => new Promise((resolve, reject) => {
|
|
4976
|
+
let didSettle = false;
|
|
4977
|
+
const timeoutHandle = setTimeout(() => {
|
|
4978
|
+
if (didSettle) return;
|
|
4979
|
+
didSettle = true;
|
|
4980
|
+
handle.terminate?.();
|
|
4981
|
+
reject(/* @__PURE__ */ new Error(`Dead-code worker timed out after ${timeoutMs / MILLISECONDS_PER_SECOND}s.`));
|
|
4982
|
+
}, timeoutMs);
|
|
4983
|
+
timeoutHandle.unref?.();
|
|
4984
|
+
handle.result.then((value) => {
|
|
4985
|
+
if (didSettle) return;
|
|
4986
|
+
didSettle = true;
|
|
4987
|
+
clearTimeout(timeoutHandle);
|
|
4988
|
+
handle.terminate?.();
|
|
4989
|
+
resolve(value);
|
|
4990
|
+
}, (error) => {
|
|
4991
|
+
if (didSettle) return;
|
|
4992
|
+
didSettle = true;
|
|
4993
|
+
clearTimeout(timeoutHandle);
|
|
4994
|
+
handle.terminate?.();
|
|
4995
|
+
reject(error);
|
|
4996
|
+
});
|
|
4997
|
+
});
|
|
4545
4998
|
const checkDeadCode = async (options) => {
|
|
4546
4999
|
const { rootDirectory, userConfig } = options;
|
|
4547
5000
|
if (!fs.existsSync(path.join(rootDirectory, "package.json"))) return [];
|
|
4548
|
-
const { analyze, defineConfig } = await import("deslop-js");
|
|
4549
5001
|
const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory, userConfig);
|
|
4550
|
-
const result = await
|
|
4551
|
-
|
|
5002
|
+
const result = parseDeadCodeWorkerResult(await runDeadCodeWorkerWithTimeout((options.createWorker ?? createDeadCodeWorker)({
|
|
5003
|
+
rootDirectory,
|
|
4552
5004
|
tsConfigPath: resolveTsConfigPath(rootDirectory),
|
|
4553
|
-
|
|
4554
|
-
|
|
5005
|
+
ignorePatterns,
|
|
5006
|
+
deslopJsModuleSpecifier: options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js")
|
|
5007
|
+
}), options.workerTimeoutMs ?? 12e4));
|
|
4555
5008
|
const toRelative = (filePath) => toRelativeFilePath(rootDirectory, filePath);
|
|
4556
5009
|
const diagnostics = [];
|
|
4557
5010
|
for (const unusedFile of result.unusedFiles) diagnostics.push({
|
|
@@ -4757,8 +5210,15 @@ var Git = class Git extends Context.Service()("react-doctor/Git") {
|
|
|
4757
5210
|
env: input.env,
|
|
4758
5211
|
extendEnv: true
|
|
4759
5212
|
}));
|
|
5213
|
+
const maxStdoutBytes = input.maxStdoutBytes;
|
|
5214
|
+
const stdoutByteCount = yield* Ref.make(0);
|
|
5215
|
+
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({
|
|
5216
|
+
args: [...input.args],
|
|
5217
|
+
directory: input.directory,
|
|
5218
|
+
cause: /* @__PURE__ */ new Error(`git stdout exceeded ${maxStdoutBytes} bytes`)
|
|
5219
|
+
}) })) : Effect.void))));
|
|
4760
5220
|
const [stdout, stderr, status] = yield* Effect.all([
|
|
4761
|
-
Stream.mkString(Stream.decodeText(
|
|
5221
|
+
Stream.mkString(Stream.decodeText(stdoutStream)),
|
|
4762
5222
|
Stream.mkString(Stream.decodeText(handle.stderr)),
|
|
4763
5223
|
handle.exitCode
|
|
4764
5224
|
], { concurrency: 3 });
|
|
@@ -4920,7 +5380,12 @@ var Git = class Git extends Context.Service()("react-doctor/Git") {
|
|
|
4920
5380
|
if (result.status !== 0) return [];
|
|
4921
5381
|
return splitNullSeparated(result.stdout);
|
|
4922
5382
|
})),
|
|
4923
|
-
showStagedContent: (directory, relativePath) =>
|
|
5383
|
+
showStagedContent: (directory, relativePath, options) => runCommand({
|
|
5384
|
+
command: "git",
|
|
5385
|
+
args: ["show", `:${relativePath}`],
|
|
5386
|
+
directory,
|
|
5387
|
+
maxStdoutBytes: options?.maxBufferBytes
|
|
5388
|
+
}).pipe(Effect.map((result) => result.status === 0 ? result.stdout : null)),
|
|
4924
5389
|
grep: (input) => Effect.gen(function* () {
|
|
4925
5390
|
const args = ["grep"];
|
|
4926
5391
|
if (input.listMatchingFiles ?? true) args.push("-l");
|
|
@@ -4928,7 +5393,12 @@ var Git = class Git extends Context.Service()("react-doctor/Git") {
|
|
|
4928
5393
|
if (input.extendedRegexp ?? false) args.push("-E");
|
|
4929
5394
|
args.push(input.pattern);
|
|
4930
5395
|
if (input.includePaths && input.includePaths.length > 0) args.push("--", ...input.includePaths);
|
|
4931
|
-
const result = yield*
|
|
5396
|
+
const result = yield* runCommand({
|
|
5397
|
+
command: "git",
|
|
5398
|
+
args,
|
|
5399
|
+
directory: input.directory,
|
|
5400
|
+
maxStdoutBytes: input.maxBufferBytes
|
|
5401
|
+
});
|
|
4932
5402
|
if (result.status === 128) return null;
|
|
4933
5403
|
return {
|
|
4934
5404
|
status: result.status,
|
|
@@ -5175,7 +5645,16 @@ const buildCapabilities = (project) => {
|
|
|
5175
5645
|
capabilities.add(project.framework);
|
|
5176
5646
|
if (project.framework === "expo" || project.framework === "react-native" || project.hasReactNativeWorkspace) capabilities.add("react-native");
|
|
5177
5647
|
const reactMajor = project.reactMajorVersion;
|
|
5178
|
-
if (reactMajor !== null)
|
|
5648
|
+
if (reactMajor !== null) {
|
|
5649
|
+
const cappedReactMajor = Math.min(reactMajor, 30);
|
|
5650
|
+
for (let major = 17; major <= cappedReactMajor; major++) capabilities.add(`react:${major}`);
|
|
5651
|
+
if (reactMajor >= 19) {
|
|
5652
|
+
if (isReactAtLeast(parseReactMajorMinor(project.reactVersion), {
|
|
5653
|
+
major: 19,
|
|
5654
|
+
minor: 2
|
|
5655
|
+
})) capabilities.add("react:19.2");
|
|
5656
|
+
}
|
|
5657
|
+
}
|
|
5179
5658
|
if (project.tailwindVersion !== null) {
|
|
5180
5659
|
capabilities.add("tailwind");
|
|
5181
5660
|
if (isTailwindAtLeast(parseTailwindMajorMinor(project.tailwindVersion), {
|
|
@@ -5183,9 +5662,22 @@ const buildCapabilities = (project) => {
|
|
|
5183
5662
|
minor: 4
|
|
5184
5663
|
})) capabilities.add("tailwind:3.4");
|
|
5185
5664
|
}
|
|
5665
|
+
if (project.zodVersion !== null) {
|
|
5666
|
+
capabilities.add("zod");
|
|
5667
|
+
if (project.zodMajorVersion !== null && project.zodMajorVersion >= 4) capabilities.add("zod:4");
|
|
5668
|
+
}
|
|
5186
5669
|
if (project.hasReactCompiler) capabilities.add("react-compiler");
|
|
5187
5670
|
if (project.hasTanStackQuery) capabilities.add("tanstack-query");
|
|
5188
5671
|
if (project.hasTypeScript) capabilities.add("typescript");
|
|
5672
|
+
if (project.preactVersion !== null) {
|
|
5673
|
+
capabilities.add("preact");
|
|
5674
|
+
const preactMajor = project.preactMajorVersion;
|
|
5675
|
+
if (preactMajor !== null) {
|
|
5676
|
+
const cappedPreactMajor = Math.min(preactMajor, 20);
|
|
5677
|
+
for (let major = 10; major <= cappedPreactMajor; major++) capabilities.add(`preact:${major}`);
|
|
5678
|
+
}
|
|
5679
|
+
if (project.reactVersion === null) capabilities.add("pure-preact");
|
|
5680
|
+
}
|
|
5189
5681
|
return capabilities;
|
|
5190
5682
|
};
|
|
5191
5683
|
const shouldEnableRule = (requires, tags, capabilities, ignoredTags, disabledBy) => {
|
|
@@ -5440,6 +5932,13 @@ const buildNoSecretsRecommendation = (project, fallbackRecommendation) => {
|
|
|
5440
5932
|
if (!publicEnvPrefix) return fallbackRecommendation;
|
|
5441
5933
|
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`;
|
|
5442
5934
|
};
|
|
5935
|
+
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";
|
|
5936
|
+
const appendReanimatedSharedValueHint = (help, rule, project) => {
|
|
5937
|
+
if (rule !== "immutability") return help;
|
|
5938
|
+
if (!project.hasReanimated) return help;
|
|
5939
|
+
if (!help) return REANIMATED_SHARED_VALUE_HINT;
|
|
5940
|
+
return `${help}\n\n${REANIMATED_SHARED_VALUE_HINT}`;
|
|
5941
|
+
};
|
|
5443
5942
|
const REACT_MODULE_SOURCE = "react";
|
|
5444
5943
|
const REQUIRE_IDENTIFIER = "require";
|
|
5445
5944
|
const USE_IDENTIFIER = "use";
|
|
@@ -5763,7 +6262,7 @@ const getRuleCategory = (ruleName) => reactDoctorPlugin.rules[ruleName]?.categor
|
|
|
5763
6262
|
const cleanDiagnosticMessage = (message, help, plugin, rule, project) => {
|
|
5764
6263
|
if (plugin === "react-hooks-js") return {
|
|
5765
6264
|
message: REACT_COMPILER_MESSAGE,
|
|
5766
|
-
help: message.replace(FILEPATH_WITH_LOCATION_PATTERN, "").trim() || help
|
|
6265
|
+
help: appendReanimatedSharedValueHint(message.replace(FILEPATH_WITH_LOCATION_PATTERN, "").trim() || help, rule, project)
|
|
5767
6266
|
};
|
|
5768
6267
|
return {
|
|
5769
6268
|
message: message.replace(FILEPATH_WITH_LOCATION_PATTERN, "").trim() || message,
|
|
@@ -5832,13 +6331,6 @@ const SANITIZED_ENV = (() => {
|
|
|
5832
6331
|
}
|
|
5833
6332
|
return sanitized;
|
|
5834
6333
|
})();
|
|
5835
|
-
const OXLINT_SPAWN_TIMEOUT_MS$1 = (() => {
|
|
5836
|
-
const raw = process.env["REACT_DOCTOR_OXLINT_SPAWN_TIMEOUT_MS"];
|
|
5837
|
-
if (raw === void 0) return OXLINT_SPAWN_TIMEOUT_MS;
|
|
5838
|
-
const parsed = Number(raw);
|
|
5839
|
-
if (!Number.isFinite(parsed) || parsed <= 0) return OXLINT_SPAWN_TIMEOUT_MS;
|
|
5840
|
-
return parsed;
|
|
5841
|
-
})();
|
|
5842
6334
|
/**
|
|
5843
6335
|
* Spawn one oxlint subprocess with hard ceilings on wall time and
|
|
5844
6336
|
* output size. Returns stdout on success; raises a tagged
|
|
@@ -5855,7 +6347,7 @@ const OXLINT_SPAWN_TIMEOUT_MS$1 = (() => {
|
|
|
5855
6347
|
* The first three are splittable (the caller's binary-split retry
|
|
5856
6348
|
* shrinks the batch and re-spawns); the fourth isn't.
|
|
5857
6349
|
*/
|
|
5858
|
-
const spawnOxlint = (args, rootDirectory, nodeBinaryPath) => new Promise((resolve, reject) => {
|
|
6350
|
+
const spawnOxlint = (args, rootDirectory, nodeBinaryPath, spawnTimeoutMs = OXLINT_SPAWN_TIMEOUT_MS, outputMaxBytes = OXLINT_OUTPUT_MAX_BYTES) => new Promise((resolve, reject) => {
|
|
5859
6351
|
const child = spawn(nodeBinaryPath, args, {
|
|
5860
6352
|
cwd: rootDirectory,
|
|
5861
6353
|
env: SANITIZED_ENV
|
|
@@ -5864,9 +6356,9 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath) => new Promise((resolv
|
|
|
5864
6356
|
child.kill("SIGKILL");
|
|
5865
6357
|
reject(new ReactDoctorError({ reason: new OxlintBatchExceeded({
|
|
5866
6358
|
kind: "timeout",
|
|
5867
|
-
detail: `${
|
|
6359
|
+
detail: `${spawnTimeoutMs / 1e3}s budget exceeded`
|
|
5868
6360
|
}) }));
|
|
5869
|
-
},
|
|
6361
|
+
}, spawnTimeoutMs);
|
|
5870
6362
|
timeoutHandle.unref?.();
|
|
5871
6363
|
const stdoutBuffers = [];
|
|
5872
6364
|
const stderrBuffers = [];
|
|
@@ -5876,7 +6368,7 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath) => new Promise((resolv
|
|
|
5876
6368
|
const killIfTooLarge = (incomingBytes, isStdout) => {
|
|
5877
6369
|
if (isStdout) stdoutByteCount += incomingBytes;
|
|
5878
6370
|
else stderrByteCount += incomingBytes;
|
|
5879
|
-
if (stdoutByteCount + stderrByteCount >
|
|
6371
|
+
if (stdoutByteCount + stderrByteCount > outputMaxBytes && !didKillForSize) {
|
|
5880
6372
|
didKillForSize = true;
|
|
5881
6373
|
child.kill("SIGKILL");
|
|
5882
6374
|
return true;
|
|
@@ -5902,7 +6394,7 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath) => new Promise((resolv
|
|
|
5902
6394
|
if (didKillForSize) {
|
|
5903
6395
|
reject(new ReactDoctorError({ reason: new OxlintBatchExceeded({
|
|
5904
6396
|
kind: "output-too-large",
|
|
5905
|
-
detail: `exceeded ${
|
|
6397
|
+
detail: `exceeded ${outputMaxBytes} bytes — scan a smaller subset with --diff or --staged`
|
|
5906
6398
|
}) }));
|
|
5907
6399
|
return;
|
|
5908
6400
|
}
|
|
@@ -5943,7 +6435,7 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath) => new Promise((resolv
|
|
|
5943
6435
|
* with a slimmer config in that case.
|
|
5944
6436
|
*/
|
|
5945
6437
|
const spawnLintBatches = async (input) => {
|
|
5946
|
-
const { baseArgs, fileBatches, rootDirectory, nodeBinaryPath, project, onPartialFailure, onFileProgress } = input;
|
|
6438
|
+
const { baseArgs, fileBatches, rootDirectory, nodeBinaryPath, project, onPartialFailure, onFileProgress, spawnTimeoutMs, outputMaxBytes } = input;
|
|
5947
6439
|
const totalFileCount = fileBatches.reduce((sum, batch) => sum + batch.length, 0);
|
|
5948
6440
|
const allDiagnostics = [];
|
|
5949
6441
|
const droppedFiles = [];
|
|
@@ -5951,7 +6443,7 @@ const spawnLintBatches = async (input) => {
|
|
|
5951
6443
|
const spawnLintBatch = async (batch) => {
|
|
5952
6444
|
const batchArgs = [...baseArgs, ...batch];
|
|
5953
6445
|
try {
|
|
5954
|
-
return parseOxlintOutput(await spawnOxlint(batchArgs, rootDirectory, nodeBinaryPath), project, rootDirectory);
|
|
6446
|
+
return parseOxlintOutput(await spawnOxlint(batchArgs, rootDirectory, nodeBinaryPath, spawnTimeoutMs, outputMaxBytes), project, rootDirectory);
|
|
5955
6447
|
} catch (error) {
|
|
5956
6448
|
if (!isSplittableReactDoctorError(error)) throw error;
|
|
5957
6449
|
if (batch.length <= 1) {
|
|
@@ -6054,13 +6546,11 @@ const writeOxlintConfig = (configPath, configToWrite) => {
|
|
|
6054
6546
|
* 6. always restore disable directives + clean up the temp dir
|
|
6055
6547
|
*/
|
|
6056
6548
|
const runOxlint = async (options) => {
|
|
6057
|
-
const { rootDirectory, project, includePaths, nodeBinaryPath = process.execPath, customRulesOnly = false, respectInlineDisables = true, adoptExistingLintConfig = true, ignoredTags = /* @__PURE__ */ new Set(), userConfig, configSourceDirectory = rootDirectory, onPartialFailure } = options;
|
|
6549
|
+
const { rootDirectory, project, includePaths, nodeBinaryPath = process.execPath, customRulesOnly = false, respectInlineDisables = true, adoptExistingLintConfig = true, ignoredTags = /* @__PURE__ */ new Set(), userConfig, configSourceDirectory = rootDirectory, onPartialFailure, spawnTimeoutMs, outputMaxBytes } = options;
|
|
6058
6550
|
const serverAuthFunctionNames = Array.isArray(userConfig?.serverAuthFunctionNames) ? userConfig.serverAuthFunctionNames.filter((entry) => typeof entry === "string" && entry.length > 0) : void 0;
|
|
6059
6551
|
const severityControls = buildRuleSeverityControls(userConfig);
|
|
6060
6552
|
validateRuleRegistration();
|
|
6061
6553
|
if (includePaths !== void 0 && includePaths.length === 0) return [];
|
|
6062
|
-
const configDirectory = fs.mkdtempSync(path.join(os.tmpdir(), "react-doctor-oxlintrc-"));
|
|
6063
|
-
const configPath = path.join(configDirectory, "oxlintrc.json");
|
|
6064
6554
|
const pluginPath = resolvePluginPath();
|
|
6065
6555
|
const extendsPaths = (adoptExistingLintConfig && !customRulesOnly ? detectUserLintConfigPaths(rootDirectory) : []).filter(canOxlintExtendConfig);
|
|
6066
6556
|
const userPlugins = resolveUserPlugins(userConfig?.plugins, configSourceDirectory);
|
|
@@ -6075,6 +6565,8 @@ const runOxlint = async (options) => {
|
|
|
6075
6565
|
userPlugins
|
|
6076
6566
|
});
|
|
6077
6567
|
const restoreDisableDirectives = respectInlineDisables ? () => {} : await neutralizeDisableDirectives(rootDirectory, includePaths);
|
|
6568
|
+
const configDirectory = fs.mkdtempSync(path.join(os.tmpdir(), "react-doctor-oxlintrc-"));
|
|
6569
|
+
const configPath = path.join(configDirectory, "oxlintrc.json");
|
|
6078
6570
|
try {
|
|
6079
6571
|
const baseArgs = [
|
|
6080
6572
|
resolveOxlintBinary(),
|
|
@@ -6101,7 +6593,9 @@ const runOxlint = async (options) => {
|
|
|
6101
6593
|
nodeBinaryPath,
|
|
6102
6594
|
project,
|
|
6103
6595
|
onPartialFailure,
|
|
6104
|
-
onFileProgress: options.onFileProgress
|
|
6596
|
+
onFileProgress: options.onFileProgress,
|
|
6597
|
+
spawnTimeoutMs,
|
|
6598
|
+
outputMaxBytes
|
|
6105
6599
|
});
|
|
6106
6600
|
writeOxlintConfig(configPath, buildConfig(extendsPaths));
|
|
6107
6601
|
try {
|
|
@@ -6167,6 +6661,8 @@ var Linter = class Linter extends Context.Service()("react-doctor/Linter") {
|
|
|
6167
6661
|
*/
|
|
6168
6662
|
static layerOxlint = Layer.succeed(Linter, Linter.of({ run: (input) => Stream.unwrap(Effect.fn("Linter.run")(function* () {
|
|
6169
6663
|
const partialFailures = yield* LintPartialFailures;
|
|
6664
|
+
const spawnTimeoutMs = yield* OxlintSpawnTimeoutMs;
|
|
6665
|
+
const outputMaxBytes = yield* OxlintOutputMaxBytes;
|
|
6170
6666
|
const collectedFailures = [];
|
|
6171
6667
|
const diagnostics = yield* Effect.tryPromise({
|
|
6172
6668
|
try: () => runOxlint({
|
|
@@ -6183,7 +6679,9 @@ var Linter = class Linter extends Context.Service()("react-doctor/Linter") {
|
|
|
6183
6679
|
onPartialFailure: (reason) => {
|
|
6184
6680
|
collectedFailures.push(reason);
|
|
6185
6681
|
},
|
|
6186
|
-
onFileProgress: input.onFileProgress
|
|
6682
|
+
onFileProgress: input.onFileProgress,
|
|
6683
|
+
spawnTimeoutMs,
|
|
6684
|
+
outputMaxBytes
|
|
6187
6685
|
}),
|
|
6188
6686
|
catch: ensureReactDoctorError
|
|
6189
6687
|
});
|
|
@@ -6481,7 +6979,7 @@ const runInspect = (input, hooks = {}) => Effect.gen(function* () {
|
|
|
6481
6979
|
const resolvedConfig = yield* configService.resolve(input.directory);
|
|
6482
6980
|
const scanDirectory = resolvedConfig.resolvedDirectory;
|
|
6483
6981
|
const project = yield* projectService.discover(scanDirectory);
|
|
6484
|
-
if (project
|
|
6982
|
+
if (!isAnalyzableProject(project)) return yield* new ReactDoctorError({ reason: new NoReactDependency({ directory: scanDirectory }) });
|
|
6485
6983
|
const [repo, sha, defaultBranch] = yield* Effect.all([
|
|
6486
6984
|
gitService.githubRepo(scanDirectory).pipe(Effect.orElseSucceed(() => null)),
|
|
6487
6985
|
gitService.headSha(scanDirectory).pipe(Effect.orElseSucceed(() => null)),
|
|
@@ -6509,23 +7007,13 @@ const runInspect = (input, hooks = {}) => Effect.gen(function* () {
|
|
|
6509
7007
|
const lintFailure = yield* Ref.make({
|
|
6510
7008
|
didFail: false,
|
|
6511
7009
|
reason: null,
|
|
6512
|
-
reasonTag: null
|
|
7010
|
+
reasonTag: null,
|
|
7011
|
+
reasonKind: null
|
|
6513
7012
|
});
|
|
6514
7013
|
const deadCodeFailure = yield* Ref.make({
|
|
6515
7014
|
didFail: false,
|
|
6516
7015
|
reason: null
|
|
6517
7016
|
});
|
|
6518
|
-
const shouldRunDeadCode = input.runDeadCode && !isDiffMode;
|
|
6519
|
-
const deadCodeFiber = yield* Effect.forkChild(shouldRunDeadCode ? Stream.runCollect(applyPerElementPipeline(deadCodeService.run({
|
|
6520
|
-
rootDirectory: scanDirectory,
|
|
6521
|
-
userConfig: resolvedConfig.config
|
|
6522
|
-
}).pipe(Stream.catchTag("ReactDoctorError", (error) => Stream.unwrap(Effect.gen(function* () {
|
|
6523
|
-
yield* Ref.set(deadCodeFailure, {
|
|
6524
|
-
didFail: true,
|
|
6525
|
-
reason: error.message
|
|
6526
|
-
});
|
|
6527
|
-
return Stream.empty;
|
|
6528
|
-
})))))) : Effect.succeed([]));
|
|
6529
7017
|
const scanProgress = yield* progressService.start("Scanning...");
|
|
6530
7018
|
const scanStartTime = Date.now();
|
|
6531
7019
|
let lastReportedTotalFileCount = 0;
|
|
@@ -6542,24 +7030,32 @@ const runInspect = (input, hooks = {}) => Effect.gen(function* () {
|
|
|
6542
7030
|
configSourceDirectory: resolvedConfig.configSourceDirectory ?? void 0,
|
|
6543
7031
|
onFileProgress: (scannedFileCount, totalFileCount) => {
|
|
6544
7032
|
lastReportedTotalFileCount = totalFileCount;
|
|
6545
|
-
Effect.runSync(scanProgress.update(`Scanning (${scannedFileCount}/${totalFileCount})...`));
|
|
7033
|
+
Effect.runSync(scanProgress.update(`Scanning files (${scannedFileCount}/${totalFileCount})...`));
|
|
6546
7034
|
}
|
|
6547
7035
|
}).pipe(Stream.catchTag("ReactDoctorError", (error) => Stream.unwrap(Effect.gen(function* () {
|
|
6548
7036
|
yield* Ref.set(lintFailure, {
|
|
6549
7037
|
didFail: true,
|
|
6550
7038
|
reason: error.message,
|
|
6551
|
-
reasonTag: error.reason._tag
|
|
7039
|
+
reasonTag: error.reason._tag,
|
|
7040
|
+
reasonKind: error.reason._tag === "OxlintUnavailable" ? error.reason.kind : null
|
|
6552
7041
|
});
|
|
6553
7042
|
return Stream.empty;
|
|
6554
7043
|
}))));
|
|
6555
7044
|
const lintCollected = yield* Stream.runCollect(applyPerElementPipeline(rawLintStream));
|
|
6556
7045
|
const lintFailureState = yield* Ref.get(lintFailure);
|
|
6557
7046
|
yield* afterLint(lintFailureState.didFail);
|
|
6558
|
-
if (lintFailureState.didFail)
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
|
|
7047
|
+
if (lintFailureState.didFail) yield* scanProgress.fail(formatLintFailText(lintFailureState.reasonTag, process.version));
|
|
7048
|
+
const shouldRunDeadCode = input.runDeadCode && !isDiffMode;
|
|
7049
|
+
const deadCodeCollected = lintFailureState.didFail || !shouldRunDeadCode ? [] : yield* scanProgress.update("Analyzing dead code...").pipe(Effect.andThen(Stream.runCollect(applyPerElementPipeline(deadCodeService.run({
|
|
7050
|
+
rootDirectory: scanDirectory,
|
|
7051
|
+
userConfig: resolvedConfig.config
|
|
7052
|
+
}).pipe(Stream.catchTag("ReactDoctorError", (error) => Stream.unwrap(Effect.gen(function* () {
|
|
7053
|
+
yield* Ref.set(deadCodeFailure, {
|
|
7054
|
+
didFail: true,
|
|
7055
|
+
reason: error.message
|
|
7056
|
+
});
|
|
7057
|
+
return Stream.empty;
|
|
7058
|
+
}))))))));
|
|
6563
7059
|
const deadCodeFailureState = yield* Ref.get(deadCodeFailure);
|
|
6564
7060
|
const scanElapsedSeconds = ((Date.now() - scanStartTime) / 1e3).toFixed(1);
|
|
6565
7061
|
const totalFileCount = lastReportedTotalFileCount || (lintIncludePaths?.length ?? project.sourceFileCount);
|
|
@@ -6601,6 +7097,7 @@ const runInspect = (input, hooks = {}) => Effect.gen(function* () {
|
|
|
6601
7097
|
didLintFail: lintFailureState.didFail,
|
|
6602
7098
|
lintFailureReason: lintFailureState.reason,
|
|
6603
7099
|
lintFailureReasonTag: lintFailureState.reasonTag,
|
|
7100
|
+
lintFailureReasonKind: lintFailureState.reasonKind,
|
|
6604
7101
|
lintPartialFailures,
|
|
6605
7102
|
didDeadCodeFail: deadCodeFailureState.didFail,
|
|
6606
7103
|
deadCodeFailureReason: deadCodeFailureState.reason
|
|
@@ -7035,11 +7532,12 @@ const buildInspectProgram = (scanTarget, options, configOverride) => {
|
|
|
7035
7532
|
const outputToDiagnoseResult = (output, elapsedMilliseconds) => {
|
|
7036
7533
|
if (output.didLintFail && output.lintFailureReason !== null) console.error("Lint failed:", output.lintFailureReason);
|
|
7037
7534
|
const skippedChecks = [];
|
|
7535
|
+
if (output.didLintFail) skippedChecks.push("lint");
|
|
7536
|
+
if (output.didDeadCodeFail) skippedChecks.push("dead-code");
|
|
7038
7537
|
const skippedCheckReasons = {};
|
|
7039
|
-
if (output.
|
|
7040
|
-
|
|
7041
|
-
|
|
7042
|
-
}
|
|
7538
|
+
if (output.didLintFail && output.lintFailureReason !== null) skippedCheckReasons.lint = output.lintFailureReason;
|
|
7539
|
+
else if (output.lintPartialFailures.length > 0) skippedCheckReasons["lint:partial"] = output.lintPartialFailures.join("; ");
|
|
7540
|
+
if (output.didDeadCodeFail && output.deadCodeFailureReason !== null) skippedCheckReasons["dead-code"] = output.deadCodeFailureReason;
|
|
7043
7541
|
return {
|
|
7044
7542
|
diagnostics: [...output.diagnostics],
|
|
7045
7543
|
score: output.score,
|