react-doctor 0.2.10 → 0.2.11-dev.2e369e2
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-BRBUS1pE.js → cli-logger-CSZagq1E.js} +389 -146
- package/dist/cli.js +89 -33
- package/dist/index.d.ts +20 -8
- package/dist/index.js +384 -148
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -21,7 +21,6 @@ import * as Option from "effect/Option";
|
|
|
21
21
|
import * as Ref from "effect/Ref";
|
|
22
22
|
import * as Stream from "effect/Stream";
|
|
23
23
|
import * as Cache from "effect/Cache";
|
|
24
|
-
import { Worker } from "node:worker_threads";
|
|
25
24
|
import * as NodeChildProcessSpawner from "@effect/platform-node-shared/NodeChildProcessSpawner";
|
|
26
25
|
import * as NodeFileSystem from "@effect/platform-node-shared/NodeFileSystem";
|
|
27
26
|
import * as NodePath from "@effect/platform-node-shared/NodePath";
|
|
@@ -2308,15 +2307,91 @@ const detectFramework = (dependencies) => {
|
|
|
2308
2307
|
if (dependencies.preact && !dependencies.react) return "preact";
|
|
2309
2308
|
return "unknown";
|
|
2310
2309
|
};
|
|
2311
|
-
const UPPER_BOUND_COMPARATOR = /<\s*=?\s*\d+(?:\.\d+){0,2}(?:-[^\s,|]+)?/g;
|
|
2312
|
-
const HAS_UPPER_BOUND_COMPARATOR = /<\s*=?\s*\d+(?:\.\d+){0,2}(?:-[^\s,|]+)?/;
|
|
2313
|
-
const OR_SEPARATOR = /\s*\|\|\s*/;
|
|
2314
2310
|
const UNRESOLVABLE_PROTOCOL_VERSION = /^(?:file|git|github|https?|link|patch|portal|workspace|npm):/i;
|
|
2315
2311
|
const DIST_TAG_VERSION = /^[a-z][a-z0-9._-]*$/i;
|
|
2316
2312
|
const WILDCARD_VERSION = /^[*xX](?:\.[*xX])*$/;
|
|
2317
|
-
const NON_LOWER_BOUND_COMPARATOR = /(?:^|[\s,|])(?:>(?!=)|!={0,2})\s*\d/;
|
|
2318
|
-
const LOWER_BOUND_MAJOR = /(?:^|[\s,|])(?:>=\s*|[~^=v]\s*)?(\d+)(?=$|[\s,|.*xX-])/g;
|
|
2319
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
|
+
};
|
|
2320
2395
|
const normalizeDependencyVersion = (version) => {
|
|
2321
2396
|
const trimmed = version.trim();
|
|
2322
2397
|
if (trimmed.length === 0) return null;
|
|
@@ -2326,17 +2401,29 @@ const normalizeDependencyVersion = (version) => {
|
|
|
2326
2401
|
if (WILDCARD_VERSION.test(normalizedVersion)) return null;
|
|
2327
2402
|
return normalizedVersion;
|
|
2328
2403
|
};
|
|
2329
|
-
const splitDependencyVersionBranches = (version) => version.split(
|
|
2330
|
-
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
|
+
};
|
|
2331
2409
|
const getBranchLowestMajor = (branch) => {
|
|
2332
|
-
if (
|
|
2333
|
-
const lowerBoundComparators = branch
|
|
2410
|
+
if (hasNonLowerBoundComparator(branch)) return null;
|
|
2411
|
+
const lowerBoundComparators = stripUpperBoundComparators(branch).trim();
|
|
2334
2412
|
if (lowerBoundComparators.length === 0) return null;
|
|
2335
2413
|
let branchLowestMajor = null;
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
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;
|
|
2340
2427
|
}
|
|
2341
2428
|
return branchLowestMajor;
|
|
2342
2429
|
};
|
|
@@ -2505,6 +2592,7 @@ const resolveCatalogVersion = (packageJson, packageName, rootDirectory, explicit
|
|
|
2505
2592
|
const EMPTY_DEPENDENCY_INFO = {
|
|
2506
2593
|
reactVersion: null,
|
|
2507
2594
|
tailwindVersion: null,
|
|
2595
|
+
zodVersion: null,
|
|
2508
2596
|
framework: "unknown"
|
|
2509
2597
|
};
|
|
2510
2598
|
const pickConcreteVersion = (packageJson, packageName, sections) => {
|
|
@@ -2533,6 +2621,11 @@ const extractDependencyInfo = (packageJson) => {
|
|
|
2533
2621
|
"devDependencies",
|
|
2534
2622
|
"peerDependencies"
|
|
2535
2623
|
]),
|
|
2624
|
+
zodVersion: pickConcreteVersion(packageJson, "zod", [
|
|
2625
|
+
"dependencies",
|
|
2626
|
+
"devDependencies",
|
|
2627
|
+
"peerDependencies"
|
|
2628
|
+
]),
|
|
2536
2629
|
framework: detectFramework(allDependencies)
|
|
2537
2630
|
};
|
|
2538
2631
|
};
|
|
@@ -2677,8 +2770,22 @@ const findReactInWorkspaces = (rootDirectory, packageJson) => {
|
|
|
2677
2770
|
workspaceDirectory,
|
|
2678
2771
|
workspacePackageJson
|
|
2679
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
|
+
});
|
|
2680
2786
|
if (reactVersion && shouldReplaceReactVersion(result.reactVersion, reactVersion)) result.reactVersion = reactVersion;
|
|
2681
2787
|
if (tailwindVersion && !result.tailwindVersion) result.tailwindVersion = tailwindVersion;
|
|
2788
|
+
if (zodVersion && !result.zodVersion) result.zodVersion = zodVersion;
|
|
2682
2789
|
if (info.framework !== "unknown" && result.framework === "unknown") result.framework = info.framework;
|
|
2683
2790
|
const resultReactMajor = parseReactMajor(result.reactVersion);
|
|
2684
2791
|
if (result.reactVersion && result.tailwindVersion && result.framework !== "unknown" && resultReactMajor !== null && resultReactMajor <= 17) return result;
|
|
@@ -2713,17 +2820,44 @@ const findDependencyInfoFromMonorepoRoot = (directory) => {
|
|
|
2713
2820
|
"peerDependencies"
|
|
2714
2821
|
]
|
|
2715
2822
|
}) : null;
|
|
2823
|
+
const leafZodDeclaration = leafPackageJson ? getDependencyDeclaration({
|
|
2824
|
+
packageJson: leafPackageJson,
|
|
2825
|
+
packageName: "zod",
|
|
2826
|
+
sections: [
|
|
2827
|
+
"dependencies",
|
|
2828
|
+
"devDependencies",
|
|
2829
|
+
"peerDependencies"
|
|
2830
|
+
]
|
|
2831
|
+
}) : null;
|
|
2716
2832
|
const shouldUseReactFallback = !leafReactDeclaration?.hasDeclaration;
|
|
2717
2833
|
const shouldUseTailwindFallback = leafTailwindDeclaration?.hasDeclaration ?? true;
|
|
2834
|
+
const shouldUseZodFallback = leafZodDeclaration?.hasDeclaration ?? true;
|
|
2718
2835
|
const reactCatalogVersion = shouldUseReactFallback ? resolveCatalogVersion(rootPackageJson, "react", monorepoRoot, leafReactDeclaration?.catalogReference) : null;
|
|
2719
2836
|
const tailwindCatalogVersion = shouldUseTailwindFallback ? resolveCatalogVersion(rootPackageJson, "tailwindcss", monorepoRoot, leafTailwindDeclaration?.catalogReference) : null;
|
|
2837
|
+
const zodCatalogVersion = shouldUseZodFallback ? resolveCatalogVersion(rootPackageJson, "zod", monorepoRoot, leafZodDeclaration?.catalogReference) : null;
|
|
2720
2838
|
const workspaceInfo = findReactInWorkspaces(monorepoRoot, rootPackageJson);
|
|
2721
2839
|
return {
|
|
2722
2840
|
reactVersion: shouldUseReactFallback ? reactCatalogVersion ?? rootInfo.reactVersion ?? workspaceInfo.reactVersion : rootInfo.reactVersion ?? workspaceInfo.reactVersion,
|
|
2723
2841
|
tailwindVersion: shouldUseTailwindFallback ? tailwindCatalogVersion ?? rootInfo.tailwindVersion ?? workspaceInfo.tailwindVersion : null,
|
|
2842
|
+
zodVersion: shouldUseZodFallback ? zodCatalogVersion ?? rootInfo.zodVersion ?? workspaceInfo.zodVersion : null,
|
|
2724
2843
|
framework: rootInfo.framework !== "unknown" ? rootInfo.framework : workspaceInfo.framework
|
|
2725
2844
|
};
|
|
2726
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
|
+
};
|
|
2727
2861
|
const NAMES = new Set([
|
|
2728
2862
|
"react-native",
|
|
2729
2863
|
"react-native-tvos",
|
|
@@ -2754,27 +2888,13 @@ const isPackageJsonReactNativeAware = (packageJson) => {
|
|
|
2754
2888
|
if (containsAnyReactNativeDependency(packageJson.optionalDependencies)) return true;
|
|
2755
2889
|
return false;
|
|
2756
2890
|
};
|
|
2757
|
-
const hasReactNativeWorkspaceAnywhere = (rootDirectory, rootPackageJson) =>
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
if (patterns.length === 0) return false;
|
|
2761
|
-
const visitedDirectories = /* @__PURE__ */ new Set();
|
|
2762
|
-
for (const pattern of patterns) {
|
|
2763
|
-
const directories = resolveWorkspaceDirectories(rootDirectory, pattern);
|
|
2764
|
-
for (const workspaceDirectory of directories) {
|
|
2765
|
-
if (visitedDirectories.has(workspaceDirectory)) continue;
|
|
2766
|
-
visitedDirectories.add(workspaceDirectory);
|
|
2767
|
-
if (isPackageJsonReactNativeAware(readPackageJson(path.join(workspaceDirectory, "package.json")))) return true;
|
|
2768
|
-
}
|
|
2769
|
-
}
|
|
2770
|
-
return false;
|
|
2771
|
-
};
|
|
2772
|
-
const hasPreact = (packageJson) => {
|
|
2773
|
-
return "preact" in {
|
|
2891
|
+
const hasReactNativeWorkspaceAnywhere = (rootDirectory, rootPackageJson) => someWorkspacePackageJson(rootDirectory, rootPackageJson, isPackageJsonReactNativeAware);
|
|
2892
|
+
const getPreactVersion = (packageJson) => {
|
|
2893
|
+
return {
|
|
2774
2894
|
...packageJson.peerDependencies,
|
|
2775
2895
|
...packageJson.dependencies,
|
|
2776
2896
|
...packageJson.devDependencies
|
|
2777
|
-
};
|
|
2897
|
+
}.preact ?? null;
|
|
2778
2898
|
};
|
|
2779
2899
|
const TANSTACK_QUERY_PACKAGES = new Set([
|
|
2780
2900
|
"@tanstack/react-query",
|
|
@@ -2789,6 +2909,20 @@ const hasTanStackQuery = (packageJson) => {
|
|
|
2789
2909
|
};
|
|
2790
2910
|
return Object.keys(allDependencies).some((packageName) => TANSTACK_QUERY_PACKAGES.has(packageName));
|
|
2791
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
|
+
};
|
|
2792
2926
|
const hasUpperBoundOnlyPeerRange = (range) => {
|
|
2793
2927
|
if (typeof range !== "string") return false;
|
|
2794
2928
|
const normalizedRange = normalizeDependencyVersion(range);
|
|
@@ -2875,12 +3009,22 @@ const listManifestWorkspacePackages = (rootDirectory) => {
|
|
|
2875
3009
|
const nxPatterns = patterns.length > 0 ? [] : getNxWorkspaceDirectories(rootDirectory);
|
|
2876
3010
|
return toReactWorkspacePackages((patterns.length > 0 ? patterns : nxPatterns).flatMap((pattern) => resolveWorkspaceDirectories(rootDirectory, pattern)));
|
|
2877
3011
|
};
|
|
3012
|
+
const NON_PROJECT_DIRECTORIES = new Set([
|
|
3013
|
+
"AppData",
|
|
3014
|
+
"Application Data",
|
|
3015
|
+
"Library"
|
|
3016
|
+
]);
|
|
3017
|
+
const MAX_SCAN_DEPTH = 6;
|
|
2878
3018
|
const discoverReactSubprojectsByFilesystem = (rootDirectory) => {
|
|
2879
3019
|
const packages = [];
|
|
2880
|
-
const pendingDirectories = [
|
|
3020
|
+
const pendingDirectories = [{
|
|
3021
|
+
directory: rootDirectory,
|
|
3022
|
+
depth: 0
|
|
3023
|
+
}];
|
|
2881
3024
|
while (pendingDirectories.length > 0) {
|
|
2882
|
-
const
|
|
2883
|
-
if (!
|
|
3025
|
+
const current = pendingDirectories.pop();
|
|
3026
|
+
if (!current) continue;
|
|
3027
|
+
const { directory: currentDirectory, depth } = current;
|
|
2884
3028
|
const packageJsonPath = path.join(currentDirectory, "package.json");
|
|
2885
3029
|
if (isFile(packageJsonPath)) {
|
|
2886
3030
|
const packageJson = readPackageJson(packageJsonPath);
|
|
@@ -2892,10 +3036,14 @@ const discoverReactSubprojectsByFilesystem = (rootDirectory) => {
|
|
|
2892
3036
|
});
|
|
2893
3037
|
}
|
|
2894
3038
|
}
|
|
3039
|
+
if (depth >= MAX_SCAN_DEPTH) continue;
|
|
2895
3040
|
const entries = readDirectoryEntries(currentDirectory).toSorted((firstEntry, secondEntry) => firstEntry.name.localeCompare(secondEntry.name));
|
|
2896
3041
|
for (const entry of entries) {
|
|
2897
|
-
if (!entry.isDirectory() || entry.name.startsWith(".") || IGNORED_DIRECTORIES.has(entry.name)) continue;
|
|
2898
|
-
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
|
+
});
|
|
2899
3047
|
}
|
|
2900
3048
|
}
|
|
2901
3049
|
return packages;
|
|
@@ -2916,7 +3064,7 @@ const discoverProject = (directory) => {
|
|
|
2916
3064
|
const packageJsonPath = path.join(directory, "package.json");
|
|
2917
3065
|
if (!isFile(packageJsonPath)) throw new PackageJsonNotFoundError(directory);
|
|
2918
3066
|
const packageJson = readPackageJson(packageJsonPath);
|
|
2919
|
-
let { reactVersion, tailwindVersion, framework } = extractDependencyInfo(packageJson);
|
|
3067
|
+
let { reactVersion, tailwindVersion, zodVersion, framework } = extractDependencyInfo(packageJson);
|
|
2920
3068
|
const reactDeclaration = getDependencyDeclaration({
|
|
2921
3069
|
packageJson,
|
|
2922
3070
|
packageName: "react",
|
|
@@ -2935,9 +3083,19 @@ const discoverProject = (directory) => {
|
|
|
2935
3083
|
"peerDependencies"
|
|
2936
3084
|
]
|
|
2937
3085
|
});
|
|
3086
|
+
const zodDeclaration = getDependencyDeclaration({
|
|
3087
|
+
packageJson,
|
|
3088
|
+
packageName: "zod",
|
|
3089
|
+
sections: [
|
|
3090
|
+
"dependencies",
|
|
3091
|
+
"devDependencies",
|
|
3092
|
+
"peerDependencies"
|
|
3093
|
+
]
|
|
3094
|
+
});
|
|
2938
3095
|
if (!reactVersion && reactDeclaration.hasDeclaration) reactVersion = resolveCatalogVersion(packageJson, "react", directory, reactDeclaration.catalogReference);
|
|
2939
3096
|
if (!tailwindVersion && tailwindDeclaration.hasDeclaration) tailwindVersion = resolveCatalogVersion(packageJson, "tailwindcss", directory, tailwindDeclaration.catalogReference);
|
|
2940
|
-
if (!
|
|
3097
|
+
if (!zodVersion && zodDeclaration.hasDeclaration) zodVersion = resolveCatalogVersion(packageJson, "zod", directory, zodDeclaration.catalogReference);
|
|
3098
|
+
if (!reactVersion || !tailwindVersion || !zodVersion) {
|
|
2941
3099
|
const monorepoRoot = findMonorepoRoot(directory);
|
|
2942
3100
|
if (monorepoRoot) {
|
|
2943
3101
|
const monorepoPackageJsonPath = path.join(monorepoRoot, "package.json");
|
|
@@ -2945,6 +3103,7 @@ const discoverProject = (directory) => {
|
|
|
2945
3103
|
const rootPackageJson = readPackageJson(monorepoPackageJsonPath);
|
|
2946
3104
|
if (!reactVersion && reactDeclaration.hasDeclaration) reactVersion = resolveCatalogVersion(rootPackageJson, "react", monorepoRoot, reactDeclaration.catalogReference);
|
|
2947
3105
|
if (!tailwindVersion && tailwindDeclaration.hasDeclaration) tailwindVersion = resolveCatalogVersion(rootPackageJson, "tailwindcss", monorepoRoot, tailwindDeclaration.catalogReference);
|
|
3106
|
+
if (!zodVersion && zodDeclaration.hasDeclaration) zodVersion = resolveCatalogVersion(rootPackageJson, "zod", monorepoRoot, zodDeclaration.catalogReference);
|
|
2948
3107
|
}
|
|
2949
3108
|
}
|
|
2950
3109
|
}
|
|
@@ -2952,37 +3111,47 @@ const discoverProject = (directory) => {
|
|
|
2952
3111
|
const workspaceInfo = findReactInWorkspaces(directory, packageJson);
|
|
2953
3112
|
if (!reactVersion && workspaceInfo.reactVersion) reactVersion = workspaceInfo.reactVersion;
|
|
2954
3113
|
if (!tailwindVersion && workspaceInfo.tailwindVersion) tailwindVersion = workspaceInfo.tailwindVersion;
|
|
3114
|
+
if (!zodVersion && workspaceInfo.zodVersion) zodVersion = workspaceInfo.zodVersion;
|
|
2955
3115
|
if (framework === "unknown" && workspaceInfo.framework !== "unknown") framework = workspaceInfo.framework;
|
|
2956
3116
|
}
|
|
2957
3117
|
if ((!reactVersion || framework === "unknown") && !isMonorepoRoot(directory)) {
|
|
2958
3118
|
const monorepoInfo = findDependencyInfoFromMonorepoRoot(directory);
|
|
2959
3119
|
if (!reactVersion) reactVersion = monorepoInfo.reactVersion;
|
|
2960
3120
|
if (!tailwindVersion) tailwindVersion = monorepoInfo.tailwindVersion;
|
|
3121
|
+
if (!zodVersion) zodVersion = monorepoInfo.zodVersion;
|
|
2961
3122
|
if (framework === "unknown") framework = monorepoInfo.framework;
|
|
2962
3123
|
}
|
|
2963
3124
|
if (!reactVersion && reactDeclaration.version && !isCatalogReference(reactDeclaration.version)) reactVersion = reactDeclaration.version;
|
|
2964
3125
|
if (!tailwindVersion && tailwindDeclaration.version && !isCatalogReference(tailwindDeclaration.version)) tailwindVersion = tailwindDeclaration.version;
|
|
3126
|
+
if (!zodVersion && zodDeclaration.version && !isCatalogReference(zodDeclaration.version)) zodVersion = zodDeclaration.version;
|
|
2965
3127
|
const projectName = packageJson.name ?? path.basename(directory);
|
|
2966
3128
|
const hasTypeScript = fs.existsSync(path.join(directory, "tsconfig.json"));
|
|
2967
3129
|
const sourceFileCount = countSourceFiles(directory);
|
|
2968
3130
|
const hasReactNativeWorkspace = framework === "expo" || framework === "react-native" || hasReactNativeWorkspaceAnywhere(directory, packageJson);
|
|
3131
|
+
const hasReanimated = hasReactNativeWorkspace && someWorkspacePackageJson(directory, packageJson, isPackageJsonReanimatedAware);
|
|
3132
|
+
const preactVersion = getPreactVersion(packageJson);
|
|
2969
3133
|
const projectInfo = {
|
|
2970
3134
|
rootDirectory: directory,
|
|
2971
3135
|
projectName,
|
|
2972
3136
|
reactVersion,
|
|
2973
3137
|
reactMajorVersion: resolveEffectiveReactMajor(reactVersion, packageJson),
|
|
2974
3138
|
tailwindVersion,
|
|
3139
|
+
zodVersion,
|
|
3140
|
+
zodMajorVersion: parseZodMajor(zodVersion),
|
|
2975
3141
|
framework,
|
|
2976
3142
|
hasTypeScript,
|
|
2977
3143
|
hasReactCompiler: detectReactCompiler(directory, packageJson),
|
|
2978
3144
|
hasTanStackQuery: hasTanStackQuery(packageJson),
|
|
2979
|
-
|
|
3145
|
+
preactVersion,
|
|
3146
|
+
preactMajorVersion: parseReactMajor(preactVersion),
|
|
2980
3147
|
hasReactNativeWorkspace,
|
|
3148
|
+
hasReanimated,
|
|
2981
3149
|
sourceFileCount
|
|
2982
3150
|
};
|
|
2983
3151
|
cachedProjectInfos.set(directory, projectInfo);
|
|
2984
3152
|
return projectInfo;
|
|
2985
3153
|
};
|
|
3154
|
+
const isAnalyzableProject = (project) => project.reactVersion !== null || project.preactVersion !== null;
|
|
2986
3155
|
const MAJOR_MINOR_PATTERN = /(\d{1,4})\.(\d{1,4})/;
|
|
2987
3156
|
const MAJOR_ONLY_PATTERN = /(\d{1,4})/;
|
|
2988
3157
|
const UPPER_BOUND_COMPARATOR_PATTERN = /<=?\s{0,8}\d{1,4}(?:\.\d{1,4}){0,2}(?:-[^\s,|]+)?/g;
|
|
@@ -3948,17 +4117,26 @@ const layerOtlp = Layer.unwrap(Effect.gen(function* () {
|
|
|
3948
4117
|
headers
|
|
3949
4118
|
}).pipe(Layer.provide(FetchHttpClient.layer));
|
|
3950
4119
|
}).pipe(Effect.orDie));
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
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: () => {
|
|
3954
4127
|
const raw = process.env["REACT_DOCTOR_OXLINT_SPAWN_TIMEOUT_MS"];
|
|
3955
4128
|
if (raw === void 0) return OXLINT_SPAWN_TIMEOUT_MS;
|
|
3956
4129
|
const parsed = Number(raw);
|
|
3957
4130
|
if (!Number.isFinite(parsed) || parsed <= 0) return OXLINT_SPAWN_TIMEOUT_MS;
|
|
3958
4131
|
return parsed;
|
|
3959
|
-
} });
|
|
3960
|
-
|
|
3961
|
-
|
|
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 }) {};
|
|
3962
4140
|
const DIAGNOSTIC_SURFACES = [
|
|
3963
4141
|
"cli",
|
|
3964
4142
|
"prComment",
|
|
@@ -4570,47 +4748,57 @@ const collectIgnorePatterns = (rootDirectory) => {
|
|
|
4570
4748
|
};
|
|
4571
4749
|
const TSCONFIG_FILENAMES$1 = ["tsconfig.json", "tsconfig.base.json"];
|
|
4572
4750
|
const DEAD_CODE_WORKER_SCRIPT = `
|
|
4573
|
-
const
|
|
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"));
|
|
4574
4755
|
|
|
4575
|
-
const normalizeResult = (result) => ({
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
});
|
|
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
|
+
});
|
|
4594
4775
|
|
|
4595
|
-
const serializeError = (error) =>
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4776
|
+
const serializeError = (error) =>
|
|
4777
|
+
error instanceof Error
|
|
4778
|
+
? { name: error.name, message: error.message, stack: error.stack }
|
|
4779
|
+
: { message: String(error) };
|
|
4599
4780
|
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
}
|
|
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
|
+
});
|
|
4614
4802
|
`;
|
|
4615
4803
|
const resolveTsConfigPath = (rootDirectory) => {
|
|
4616
4804
|
for (const filename of TSCONFIG_FILENAMES$1) {
|
|
@@ -4733,43 +4921,54 @@ const buildDeadCodeWorkerError = (workerError) => {
|
|
|
4733
4921
|
return error;
|
|
4734
4922
|
};
|
|
4735
4923
|
const createDeadCodeWorker = (input) => {
|
|
4736
|
-
const
|
|
4737
|
-
|
|
4738
|
-
|
|
4924
|
+
const child = spawn(process.execPath, ["-e", DEAD_CODE_WORKER_SCRIPT], {
|
|
4925
|
+
stdio: [
|
|
4926
|
+
"pipe",
|
|
4927
|
+
"pipe",
|
|
4928
|
+
"pipe"
|
|
4929
|
+
],
|
|
4930
|
+
windowsHide: true
|
|
4739
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));
|
|
4740
4936
|
let didSettle = false;
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
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;
|
|
4759
4958
|
}
|
|
4760
|
-
|
|
4761
|
-
|
|
4959
|
+
settle(() => reject(buildDeadCodeWorkerError(parsedMessage.error)));
|
|
4960
|
+
} catch (error) {
|
|
4762
4961
|
settle(() => reject(error));
|
|
4763
|
-
}
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4962
|
+
}
|
|
4963
|
+
});
|
|
4964
|
+
});
|
|
4965
|
+
child.stdin.on("error", () => {});
|
|
4966
|
+
child.stdin.end(JSON.stringify(input));
|
|
4967
|
+
return {
|
|
4968
|
+
result,
|
|
4769
4969
|
terminate: () => {
|
|
4770
4970
|
didSettle = true;
|
|
4771
|
-
|
|
4772
|
-
return worker.terminate();
|
|
4971
|
+
child.kill("SIGKILL");
|
|
4773
4972
|
}
|
|
4774
4973
|
};
|
|
4775
4974
|
};
|
|
@@ -5011,8 +5210,15 @@ var Git = class Git extends Context.Service()("react-doctor/Git") {
|
|
|
5011
5210
|
env: input.env,
|
|
5012
5211
|
extendEnv: true
|
|
5013
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))));
|
|
5014
5220
|
const [stdout, stderr, status] = yield* Effect.all([
|
|
5015
|
-
Stream.mkString(Stream.decodeText(
|
|
5221
|
+
Stream.mkString(Stream.decodeText(stdoutStream)),
|
|
5016
5222
|
Stream.mkString(Stream.decodeText(handle.stderr)),
|
|
5017
5223
|
handle.exitCode
|
|
5018
5224
|
], { concurrency: 3 });
|
|
@@ -5174,7 +5380,12 @@ var Git = class Git extends Context.Service()("react-doctor/Git") {
|
|
|
5174
5380
|
if (result.status !== 0) return [];
|
|
5175
5381
|
return splitNullSeparated(result.stdout);
|
|
5176
5382
|
})),
|
|
5177
|
-
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)),
|
|
5178
5389
|
grep: (input) => Effect.gen(function* () {
|
|
5179
5390
|
const args = ["grep"];
|
|
5180
5391
|
if (input.listMatchingFiles ?? true) args.push("-l");
|
|
@@ -5182,7 +5393,12 @@ var Git = class Git extends Context.Service()("react-doctor/Git") {
|
|
|
5182
5393
|
if (input.extendedRegexp ?? false) args.push("-E");
|
|
5183
5394
|
args.push(input.pattern);
|
|
5184
5395
|
if (input.includePaths && input.includePaths.length > 0) args.push("--", ...input.includePaths);
|
|
5185
|
-
const result = yield*
|
|
5396
|
+
const result = yield* runCommand({
|
|
5397
|
+
command: "git",
|
|
5398
|
+
args,
|
|
5399
|
+
directory: input.directory,
|
|
5400
|
+
maxStdoutBytes: input.maxBufferBytes
|
|
5401
|
+
});
|
|
5186
5402
|
if (result.status === 128) return null;
|
|
5187
5403
|
return {
|
|
5188
5404
|
status: result.status,
|
|
@@ -5430,7 +5646,8 @@ const buildCapabilities = (project) => {
|
|
|
5430
5646
|
if (project.framework === "expo" || project.framework === "react-native" || project.hasReactNativeWorkspace) capabilities.add("react-native");
|
|
5431
5647
|
const reactMajor = project.reactMajorVersion;
|
|
5432
5648
|
if (reactMajor !== null) {
|
|
5433
|
-
|
|
5649
|
+
const cappedReactMajor = Math.min(reactMajor, 30);
|
|
5650
|
+
for (let major = 17; major <= cappedReactMajor; major++) capabilities.add(`react:${major}`);
|
|
5434
5651
|
if (reactMajor >= 19) {
|
|
5435
5652
|
if (isReactAtLeast(parseReactMajorMinor(project.reactVersion), {
|
|
5436
5653
|
major: 19,
|
|
@@ -5445,11 +5662,20 @@ const buildCapabilities = (project) => {
|
|
|
5445
5662
|
minor: 4
|
|
5446
5663
|
})) capabilities.add("tailwind:3.4");
|
|
5447
5664
|
}
|
|
5665
|
+
if (project.zodVersion !== null) {
|
|
5666
|
+
capabilities.add("zod");
|
|
5667
|
+
if (project.zodMajorVersion !== null && project.zodMajorVersion >= 4) capabilities.add("zod:4");
|
|
5668
|
+
}
|
|
5448
5669
|
if (project.hasReactCompiler) capabilities.add("react-compiler");
|
|
5449
5670
|
if (project.hasTanStackQuery) capabilities.add("tanstack-query");
|
|
5450
5671
|
if (project.hasTypeScript) capabilities.add("typescript");
|
|
5451
|
-
if (project.
|
|
5672
|
+
if (project.preactVersion !== null) {
|
|
5452
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
|
+
}
|
|
5453
5679
|
if (project.reactVersion === null) capabilities.add("pure-preact");
|
|
5454
5680
|
}
|
|
5455
5681
|
return capabilities;
|
|
@@ -5706,6 +5932,13 @@ const buildNoSecretsRecommendation = (project, fallbackRecommendation) => {
|
|
|
5706
5932
|
if (!publicEnvPrefix) return fallbackRecommendation;
|
|
5707
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`;
|
|
5708
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
|
+
};
|
|
5709
5942
|
const REACT_MODULE_SOURCE = "react";
|
|
5710
5943
|
const REQUIRE_IDENTIFIER = "require";
|
|
5711
5944
|
const USE_IDENTIFIER = "use";
|
|
@@ -6029,7 +6262,7 @@ const getRuleCategory = (ruleName) => reactDoctorPlugin.rules[ruleName]?.categor
|
|
|
6029
6262
|
const cleanDiagnosticMessage = (message, help, plugin, rule, project) => {
|
|
6030
6263
|
if (plugin === "react-hooks-js") return {
|
|
6031
6264
|
message: REACT_COMPILER_MESSAGE,
|
|
6032
|
-
help: message.replace(FILEPATH_WITH_LOCATION_PATTERN, "").trim() || help
|
|
6265
|
+
help: appendReanimatedSharedValueHint(message.replace(FILEPATH_WITH_LOCATION_PATTERN, "").trim() || help, rule, project)
|
|
6033
6266
|
};
|
|
6034
6267
|
return {
|
|
6035
6268
|
message: message.replace(FILEPATH_WITH_LOCATION_PATTERN, "").trim() || message,
|
|
@@ -6098,13 +6331,6 @@ const SANITIZED_ENV = (() => {
|
|
|
6098
6331
|
}
|
|
6099
6332
|
return sanitized;
|
|
6100
6333
|
})();
|
|
6101
|
-
const OXLINT_SPAWN_TIMEOUT_MS$1 = (() => {
|
|
6102
|
-
const raw = process.env["REACT_DOCTOR_OXLINT_SPAWN_TIMEOUT_MS"];
|
|
6103
|
-
if (raw === void 0) return OXLINT_SPAWN_TIMEOUT_MS;
|
|
6104
|
-
const parsed = Number(raw);
|
|
6105
|
-
if (!Number.isFinite(parsed) || parsed <= 0) return OXLINT_SPAWN_TIMEOUT_MS;
|
|
6106
|
-
return parsed;
|
|
6107
|
-
})();
|
|
6108
6334
|
/**
|
|
6109
6335
|
* Spawn one oxlint subprocess with hard ceilings on wall time and
|
|
6110
6336
|
* output size. Returns stdout on success; raises a tagged
|
|
@@ -6121,7 +6347,7 @@ const OXLINT_SPAWN_TIMEOUT_MS$1 = (() => {
|
|
|
6121
6347
|
* The first three are splittable (the caller's binary-split retry
|
|
6122
6348
|
* shrinks the batch and re-spawns); the fourth isn't.
|
|
6123
6349
|
*/
|
|
6124
|
-
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) => {
|
|
6125
6351
|
const child = spawn(nodeBinaryPath, args, {
|
|
6126
6352
|
cwd: rootDirectory,
|
|
6127
6353
|
env: SANITIZED_ENV
|
|
@@ -6130,9 +6356,9 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath) => new Promise((resolv
|
|
|
6130
6356
|
child.kill("SIGKILL");
|
|
6131
6357
|
reject(new ReactDoctorError({ reason: new OxlintBatchExceeded({
|
|
6132
6358
|
kind: "timeout",
|
|
6133
|
-
detail: `${
|
|
6359
|
+
detail: `${spawnTimeoutMs / 1e3}s budget exceeded`
|
|
6134
6360
|
}) }));
|
|
6135
|
-
},
|
|
6361
|
+
}, spawnTimeoutMs);
|
|
6136
6362
|
timeoutHandle.unref?.();
|
|
6137
6363
|
const stdoutBuffers = [];
|
|
6138
6364
|
const stderrBuffers = [];
|
|
@@ -6142,7 +6368,7 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath) => new Promise((resolv
|
|
|
6142
6368
|
const killIfTooLarge = (incomingBytes, isStdout) => {
|
|
6143
6369
|
if (isStdout) stdoutByteCount += incomingBytes;
|
|
6144
6370
|
else stderrByteCount += incomingBytes;
|
|
6145
|
-
if (stdoutByteCount + stderrByteCount >
|
|
6371
|
+
if (stdoutByteCount + stderrByteCount > outputMaxBytes && !didKillForSize) {
|
|
6146
6372
|
didKillForSize = true;
|
|
6147
6373
|
child.kill("SIGKILL");
|
|
6148
6374
|
return true;
|
|
@@ -6168,7 +6394,7 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath) => new Promise((resolv
|
|
|
6168
6394
|
if (didKillForSize) {
|
|
6169
6395
|
reject(new ReactDoctorError({ reason: new OxlintBatchExceeded({
|
|
6170
6396
|
kind: "output-too-large",
|
|
6171
|
-
detail: `exceeded ${
|
|
6397
|
+
detail: `exceeded ${outputMaxBytes} bytes — scan a smaller subset with --diff or --staged`
|
|
6172
6398
|
}) }));
|
|
6173
6399
|
return;
|
|
6174
6400
|
}
|
|
@@ -6209,7 +6435,7 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath) => new Promise((resolv
|
|
|
6209
6435
|
* with a slimmer config in that case.
|
|
6210
6436
|
*/
|
|
6211
6437
|
const spawnLintBatches = async (input) => {
|
|
6212
|
-
const { baseArgs, fileBatches, rootDirectory, nodeBinaryPath, project, onPartialFailure, onFileProgress } = input;
|
|
6438
|
+
const { baseArgs, fileBatches, rootDirectory, nodeBinaryPath, project, onPartialFailure, onFileProgress, spawnTimeoutMs, outputMaxBytes } = input;
|
|
6213
6439
|
const totalFileCount = fileBatches.reduce((sum, batch) => sum + batch.length, 0);
|
|
6214
6440
|
const allDiagnostics = [];
|
|
6215
6441
|
const droppedFiles = [];
|
|
@@ -6217,7 +6443,7 @@ const spawnLintBatches = async (input) => {
|
|
|
6217
6443
|
const spawnLintBatch = async (batch) => {
|
|
6218
6444
|
const batchArgs = [...baseArgs, ...batch];
|
|
6219
6445
|
try {
|
|
6220
|
-
return parseOxlintOutput(await spawnOxlint(batchArgs, rootDirectory, nodeBinaryPath), project, rootDirectory);
|
|
6446
|
+
return parseOxlintOutput(await spawnOxlint(batchArgs, rootDirectory, nodeBinaryPath, spawnTimeoutMs, outputMaxBytes), project, rootDirectory);
|
|
6221
6447
|
} catch (error) {
|
|
6222
6448
|
if (!isSplittableReactDoctorError(error)) throw error;
|
|
6223
6449
|
if (batch.length <= 1) {
|
|
@@ -6320,13 +6546,11 @@ const writeOxlintConfig = (configPath, configToWrite) => {
|
|
|
6320
6546
|
* 6. always restore disable directives + clean up the temp dir
|
|
6321
6547
|
*/
|
|
6322
6548
|
const runOxlint = async (options) => {
|
|
6323
|
-
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;
|
|
6324
6550
|
const serverAuthFunctionNames = Array.isArray(userConfig?.serverAuthFunctionNames) ? userConfig.serverAuthFunctionNames.filter((entry) => typeof entry === "string" && entry.length > 0) : void 0;
|
|
6325
6551
|
const severityControls = buildRuleSeverityControls(userConfig);
|
|
6326
6552
|
validateRuleRegistration();
|
|
6327
6553
|
if (includePaths !== void 0 && includePaths.length === 0) return [];
|
|
6328
|
-
const configDirectory = fs.mkdtempSync(path.join(os.tmpdir(), "react-doctor-oxlintrc-"));
|
|
6329
|
-
const configPath = path.join(configDirectory, "oxlintrc.json");
|
|
6330
6554
|
const pluginPath = resolvePluginPath();
|
|
6331
6555
|
const extendsPaths = (adoptExistingLintConfig && !customRulesOnly ? detectUserLintConfigPaths(rootDirectory) : []).filter(canOxlintExtendConfig);
|
|
6332
6556
|
const userPlugins = resolveUserPlugins(userConfig?.plugins, configSourceDirectory);
|
|
@@ -6341,6 +6565,8 @@ const runOxlint = async (options) => {
|
|
|
6341
6565
|
userPlugins
|
|
6342
6566
|
});
|
|
6343
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");
|
|
6344
6570
|
try {
|
|
6345
6571
|
const baseArgs = [
|
|
6346
6572
|
resolveOxlintBinary(),
|
|
@@ -6367,7 +6593,9 @@ const runOxlint = async (options) => {
|
|
|
6367
6593
|
nodeBinaryPath,
|
|
6368
6594
|
project,
|
|
6369
6595
|
onPartialFailure,
|
|
6370
|
-
onFileProgress: options.onFileProgress
|
|
6596
|
+
onFileProgress: options.onFileProgress,
|
|
6597
|
+
spawnTimeoutMs,
|
|
6598
|
+
outputMaxBytes
|
|
6371
6599
|
});
|
|
6372
6600
|
writeOxlintConfig(configPath, buildConfig(extendsPaths));
|
|
6373
6601
|
try {
|
|
@@ -6433,6 +6661,8 @@ var Linter = class Linter extends Context.Service()("react-doctor/Linter") {
|
|
|
6433
6661
|
*/
|
|
6434
6662
|
static layerOxlint = Layer.succeed(Linter, Linter.of({ run: (input) => Stream.unwrap(Effect.fn("Linter.run")(function* () {
|
|
6435
6663
|
const partialFailures = yield* LintPartialFailures;
|
|
6664
|
+
const spawnTimeoutMs = yield* OxlintSpawnTimeoutMs;
|
|
6665
|
+
const outputMaxBytes = yield* OxlintOutputMaxBytes;
|
|
6436
6666
|
const collectedFailures = [];
|
|
6437
6667
|
const diagnostics = yield* Effect.tryPromise({
|
|
6438
6668
|
try: () => runOxlint({
|
|
@@ -6449,7 +6679,9 @@ var Linter = class Linter extends Context.Service()("react-doctor/Linter") {
|
|
|
6449
6679
|
onPartialFailure: (reason) => {
|
|
6450
6680
|
collectedFailures.push(reason);
|
|
6451
6681
|
},
|
|
6452
|
-
onFileProgress: input.onFileProgress
|
|
6682
|
+
onFileProgress: input.onFileProgress,
|
|
6683
|
+
spawnTimeoutMs,
|
|
6684
|
+
outputMaxBytes
|
|
6453
6685
|
}),
|
|
6454
6686
|
catch: ensureReactDoctorError
|
|
6455
6687
|
});
|
|
@@ -6747,7 +6979,7 @@ const runInspect = (input, hooks = {}) => Effect.gen(function* () {
|
|
|
6747
6979
|
const resolvedConfig = yield* configService.resolve(input.directory);
|
|
6748
6980
|
const scanDirectory = resolvedConfig.resolvedDirectory;
|
|
6749
6981
|
const project = yield* projectService.discover(scanDirectory);
|
|
6750
|
-
if (project
|
|
6982
|
+
if (!isAnalyzableProject(project)) return yield* new ReactDoctorError({ reason: new NoReactDependency({ directory: scanDirectory }) });
|
|
6751
6983
|
const [repo, sha, defaultBranch] = yield* Effect.all([
|
|
6752
6984
|
gitService.githubRepo(scanDirectory).pipe(Effect.orElseSucceed(() => null)),
|
|
6753
6985
|
gitService.headSha(scanDirectory).pipe(Effect.orElseSucceed(() => null)),
|
|
@@ -6775,7 +7007,8 @@ const runInspect = (input, hooks = {}) => Effect.gen(function* () {
|
|
|
6775
7007
|
const lintFailure = yield* Ref.make({
|
|
6776
7008
|
didFail: false,
|
|
6777
7009
|
reason: null,
|
|
6778
|
-
reasonTag: null
|
|
7010
|
+
reasonTag: null,
|
|
7011
|
+
reasonKind: null
|
|
6779
7012
|
});
|
|
6780
7013
|
const deadCodeFailure = yield* Ref.make({
|
|
6781
7014
|
didFail: false,
|
|
@@ -6797,13 +7030,14 @@ const runInspect = (input, hooks = {}) => Effect.gen(function* () {
|
|
|
6797
7030
|
configSourceDirectory: resolvedConfig.configSourceDirectory ?? void 0,
|
|
6798
7031
|
onFileProgress: (scannedFileCount, totalFileCount) => {
|
|
6799
7032
|
lastReportedTotalFileCount = totalFileCount;
|
|
6800
|
-
Effect.runSync(scanProgress.update(`Scanning (${scannedFileCount}/${totalFileCount})...`));
|
|
7033
|
+
Effect.runSync(scanProgress.update(`Scanning files (${scannedFileCount}/${totalFileCount})...`));
|
|
6801
7034
|
}
|
|
6802
7035
|
}).pipe(Stream.catchTag("ReactDoctorError", (error) => Stream.unwrap(Effect.gen(function* () {
|
|
6803
7036
|
yield* Ref.set(lintFailure, {
|
|
6804
7037
|
didFail: true,
|
|
6805
7038
|
reason: error.message,
|
|
6806
|
-
reasonTag: error.reason._tag
|
|
7039
|
+
reasonTag: error.reason._tag,
|
|
7040
|
+
reasonKind: error.reason._tag === "OxlintUnavailable" ? error.reason.kind : null
|
|
6807
7041
|
});
|
|
6808
7042
|
return Stream.empty;
|
|
6809
7043
|
}))));
|
|
@@ -6863,6 +7097,7 @@ const runInspect = (input, hooks = {}) => Effect.gen(function* () {
|
|
|
6863
7097
|
didLintFail: lintFailureState.didFail,
|
|
6864
7098
|
lintFailureReason: lintFailureState.reason,
|
|
6865
7099
|
lintFailureReasonTag: lintFailureState.reasonTag,
|
|
7100
|
+
lintFailureReasonKind: lintFailureState.reasonKind,
|
|
6866
7101
|
lintPartialFailures,
|
|
6867
7102
|
didDeadCodeFail: deadCodeFailureState.didFail,
|
|
6868
7103
|
deadCodeFailureReason: deadCodeFailureState.reason
|
|
@@ -7297,11 +7532,12 @@ const buildInspectProgram = (scanTarget, options, configOverride) => {
|
|
|
7297
7532
|
const outputToDiagnoseResult = (output, elapsedMilliseconds) => {
|
|
7298
7533
|
if (output.didLintFail && output.lintFailureReason !== null) console.error("Lint failed:", output.lintFailureReason);
|
|
7299
7534
|
const skippedChecks = [];
|
|
7535
|
+
if (output.didLintFail) skippedChecks.push("lint");
|
|
7536
|
+
if (output.didDeadCodeFail) skippedChecks.push("dead-code");
|
|
7300
7537
|
const skippedCheckReasons = {};
|
|
7301
|
-
if (output.
|
|
7302
|
-
|
|
7303
|
-
|
|
7304
|
-
}
|
|
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;
|
|
7305
7541
|
return {
|
|
7306
7542
|
diagnostics: [...output.diagnostics],
|
|
7307
7543
|
score: output.score,
|