react-doctor 0.2.10 → 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-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
|
@@ -22,7 +22,6 @@ import * as Option from "effect/Option";
|
|
|
22
22
|
import * as Ref from "effect/Ref";
|
|
23
23
|
import * as Stream from "effect/Stream";
|
|
24
24
|
import * as Cache from "effect/Cache";
|
|
25
|
-
import { Worker } from "node:worker_threads";
|
|
26
25
|
import * as NodeChildProcessSpawner from "@effect/platform-node-shared/NodeChildProcessSpawner";
|
|
27
26
|
import * as NodeFileSystem from "@effect/platform-node-shared/NodeFileSystem";
|
|
28
27
|
import * as NodePath from "@effect/platform-node-shared/NodePath";
|
|
@@ -2282,15 +2281,91 @@ const detectFramework = (dependencies) => {
|
|
|
2282
2281
|
if (dependencies.preact && !dependencies.react) return "preact";
|
|
2283
2282
|
return "unknown";
|
|
2284
2283
|
};
|
|
2285
|
-
const UPPER_BOUND_COMPARATOR = /<\s*=?\s*\d+(?:\.\d+){0,2}(?:-[^\s,|]+)?/g;
|
|
2286
|
-
const HAS_UPPER_BOUND_COMPARATOR = /<\s*=?\s*\d+(?:\.\d+){0,2}(?:-[^\s,|]+)?/;
|
|
2287
|
-
const OR_SEPARATOR = /\s*\|\|\s*/;
|
|
2288
2284
|
const UNRESOLVABLE_PROTOCOL_VERSION = /^(?:file|git|github|https?|link|patch|portal|workspace|npm):/i;
|
|
2289
2285
|
const DIST_TAG_VERSION = /^[a-z][a-z0-9._-]*$/i;
|
|
2290
2286
|
const WILDCARD_VERSION = /^[*xX](?:\.[*xX])*$/;
|
|
2291
|
-
const NON_LOWER_BOUND_COMPARATOR = /(?:^|[\s,|])(?:>(?!=)|!={0,2})\s*\d/;
|
|
2292
|
-
const LOWER_BOUND_MAJOR = /(?:^|[\s,|])(?:>=\s*|[~^=v]\s*)?(\d+)(?=$|[\s,|.*xX-])/g;
|
|
2293
2287
|
const NPM_ALIAS_VERSION = /^npm:(?:@[^/]+\/[^@]+|[^@]+)@(.+)$/i;
|
|
2288
|
+
const isDigit = (value) => value !== void 0 && value >= "0" && value <= "9";
|
|
2289
|
+
const isWhitespace = (value) => value === " " || value === " " || value === "\n" || value === "\r" || value === "\f" || value === "\v";
|
|
2290
|
+
const isSeparator = (value) => isWhitespace(value) || value === "," || value === "|";
|
|
2291
|
+
const skipWhitespace = (value, start) => {
|
|
2292
|
+
let index = start;
|
|
2293
|
+
while (isWhitespace(value[index])) index += 1;
|
|
2294
|
+
return index;
|
|
2295
|
+
};
|
|
2296
|
+
const skipSeparators = (value, start) => {
|
|
2297
|
+
let index = start;
|
|
2298
|
+
while (isSeparator(value[index])) index += 1;
|
|
2299
|
+
return index;
|
|
2300
|
+
};
|
|
2301
|
+
const readDigits = (value, start) => {
|
|
2302
|
+
let index = start;
|
|
2303
|
+
while (isDigit(value[index])) index += 1;
|
|
2304
|
+
return index;
|
|
2305
|
+
};
|
|
2306
|
+
const getUpperBoundComparatorEnd = (version, start) => {
|
|
2307
|
+
if (version[start] !== "<") return null;
|
|
2308
|
+
let index = skipWhitespace(version, start + 1);
|
|
2309
|
+
if (version[index] === "=") index = skipWhitespace(version, index + 1);
|
|
2310
|
+
const majorStart = index;
|
|
2311
|
+
index = readDigits(version, index);
|
|
2312
|
+
if (index === majorStart) return null;
|
|
2313
|
+
for (let segments = 0; segments < 2 && version[index] === "."; segments += 1) {
|
|
2314
|
+
const segmentStart = index + 1;
|
|
2315
|
+
const segmentEnd = readDigits(version, segmentStart);
|
|
2316
|
+
if (segmentEnd === segmentStart) break;
|
|
2317
|
+
index = segmentEnd;
|
|
2318
|
+
}
|
|
2319
|
+
if (version[index] === "-") {
|
|
2320
|
+
index += 1;
|
|
2321
|
+
while (index < version.length && !isSeparator(version[index])) index += 1;
|
|
2322
|
+
}
|
|
2323
|
+
return index;
|
|
2324
|
+
};
|
|
2325
|
+
const stripUpperBoundComparators = (version) => {
|
|
2326
|
+
let stripped = "";
|
|
2327
|
+
let index = 0;
|
|
2328
|
+
while (index < version.length) {
|
|
2329
|
+
const comparatorEnd = getUpperBoundComparatorEnd(version, index);
|
|
2330
|
+
if (comparatorEnd === null) {
|
|
2331
|
+
stripped += version[index];
|
|
2332
|
+
index += 1;
|
|
2333
|
+
continue;
|
|
2334
|
+
}
|
|
2335
|
+
stripped += " ";
|
|
2336
|
+
index = comparatorEnd;
|
|
2337
|
+
}
|
|
2338
|
+
return stripped;
|
|
2339
|
+
};
|
|
2340
|
+
const hasNonLowerBoundComparator = (branch) => {
|
|
2341
|
+
for (let index = 0; index < branch.length; index += 1) {
|
|
2342
|
+
if (index > 0 && !isSeparator(branch[index - 1])) continue;
|
|
2343
|
+
if (branch[index] === ">" && branch[index + 1] !== "=") {
|
|
2344
|
+
if (isDigit(branch[skipWhitespace(branch, index + 1)])) return true;
|
|
2345
|
+
continue;
|
|
2346
|
+
}
|
|
2347
|
+
if (branch[index] !== "!") continue;
|
|
2348
|
+
let valueIndex = index + 1;
|
|
2349
|
+
if (branch[valueIndex] === "=") valueIndex += 1;
|
|
2350
|
+
if (branch[valueIndex] === "=") valueIndex += 1;
|
|
2351
|
+
valueIndex = skipWhitespace(branch, valueIndex);
|
|
2352
|
+
if (isDigit(branch[valueIndex])) return true;
|
|
2353
|
+
}
|
|
2354
|
+
return false;
|
|
2355
|
+
};
|
|
2356
|
+
const isMajorTerminator = (value) => value === void 0 || isSeparator(value) || value === "." || value === "*" || value === "x" || value === "X" || value === "-";
|
|
2357
|
+
const getLowerBoundMajorAt = (branch, start) => {
|
|
2358
|
+
let index = start;
|
|
2359
|
+
if (branch[index] === ">" && branch[index + 1] === "=") index = skipWhitespace(branch, index + 2);
|
|
2360
|
+
else if (branch[index] === "~" || branch[index] === "^" || branch[index] === "=" || branch[index] === "v") index = skipWhitespace(branch, index + 1);
|
|
2361
|
+
const majorStart = index;
|
|
2362
|
+
const majorEnd = readDigits(branch, majorStart);
|
|
2363
|
+
if (majorEnd === majorStart || !isMajorTerminator(branch[majorEnd])) return null;
|
|
2364
|
+
return {
|
|
2365
|
+
end: majorEnd,
|
|
2366
|
+
major: Number.parseInt(branch.slice(majorStart, majorEnd), 10)
|
|
2367
|
+
};
|
|
2368
|
+
};
|
|
2294
2369
|
const normalizeDependencyVersion = (version) => {
|
|
2295
2370
|
const trimmed = version.trim();
|
|
2296
2371
|
if (trimmed.length === 0) return null;
|
|
@@ -2300,17 +2375,29 @@ const normalizeDependencyVersion = (version) => {
|
|
|
2300
2375
|
if (WILDCARD_VERSION.test(normalizedVersion)) return null;
|
|
2301
2376
|
return normalizedVersion;
|
|
2302
2377
|
};
|
|
2303
|
-
const splitDependencyVersionBranches = (version) => version.split(
|
|
2304
|
-
const hasUpperBoundComparator = (version) =>
|
|
2378
|
+
const splitDependencyVersionBranches = (version) => version.split("||").map((branch) => branch.trim()).filter(Boolean);
|
|
2379
|
+
const hasUpperBoundComparator = (version) => {
|
|
2380
|
+
for (let index = 0; index < version.length; index += 1) if (getUpperBoundComparatorEnd(version, index) !== null) return true;
|
|
2381
|
+
return false;
|
|
2382
|
+
};
|
|
2305
2383
|
const getBranchLowestMajor = (branch) => {
|
|
2306
|
-
if (
|
|
2307
|
-
const lowerBoundComparators = branch
|
|
2384
|
+
if (hasNonLowerBoundComparator(branch)) return null;
|
|
2385
|
+
const lowerBoundComparators = stripUpperBoundComparators(branch).trim();
|
|
2308
2386
|
if (lowerBoundComparators.length === 0) return null;
|
|
2309
2387
|
let branchLowestMajor = null;
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
if (
|
|
2388
|
+
let index = 0;
|
|
2389
|
+
while (index < lowerBoundComparators.length) {
|
|
2390
|
+
const lowerBoundStart = skipSeparators(lowerBoundComparators, index);
|
|
2391
|
+
if (lowerBoundStart > 0 && !isSeparator(lowerBoundComparators[lowerBoundStart - 1])) {
|
|
2392
|
+
index = lowerBoundStart + 1;
|
|
2393
|
+
continue;
|
|
2394
|
+
}
|
|
2395
|
+
const lowerBoundMajor = getLowerBoundMajorAt(lowerBoundComparators, lowerBoundStart);
|
|
2396
|
+
if (lowerBoundMajor !== null && Number.isFinite(lowerBoundMajor.major) && lowerBoundMajor.major > 0) {
|
|
2397
|
+
const major = lowerBoundMajor.major;
|
|
2398
|
+
if (branchLowestMajor === null || major < branchLowestMajor) branchLowestMajor = major;
|
|
2399
|
+
}
|
|
2400
|
+
index = lowerBoundMajor?.end ?? lowerBoundStart + 1;
|
|
2314
2401
|
}
|
|
2315
2402
|
return branchLowestMajor;
|
|
2316
2403
|
};
|
|
@@ -2479,6 +2566,7 @@ const resolveCatalogVersion = (packageJson, packageName, rootDirectory, explicit
|
|
|
2479
2566
|
const EMPTY_DEPENDENCY_INFO = {
|
|
2480
2567
|
reactVersion: null,
|
|
2481
2568
|
tailwindVersion: null,
|
|
2569
|
+
zodVersion: null,
|
|
2482
2570
|
framework: "unknown"
|
|
2483
2571
|
};
|
|
2484
2572
|
const pickConcreteVersion = (packageJson, packageName, sections) => {
|
|
@@ -2507,6 +2595,11 @@ const extractDependencyInfo = (packageJson) => {
|
|
|
2507
2595
|
"devDependencies",
|
|
2508
2596
|
"peerDependencies"
|
|
2509
2597
|
]),
|
|
2598
|
+
zodVersion: pickConcreteVersion(packageJson, "zod", [
|
|
2599
|
+
"dependencies",
|
|
2600
|
+
"devDependencies",
|
|
2601
|
+
"peerDependencies"
|
|
2602
|
+
]),
|
|
2510
2603
|
framework: detectFramework(allDependencies)
|
|
2511
2604
|
};
|
|
2512
2605
|
};
|
|
@@ -2651,8 +2744,22 @@ const findReactInWorkspaces = (rootDirectory, packageJson) => {
|
|
|
2651
2744
|
workspaceDirectory,
|
|
2652
2745
|
workspacePackageJson
|
|
2653
2746
|
});
|
|
2747
|
+
const zodVersion = resolveWorkspaceDependencyVersion({
|
|
2748
|
+
concreteVersion: info.zodVersion,
|
|
2749
|
+
packageName: "zod",
|
|
2750
|
+
rootDirectory,
|
|
2751
|
+
rootPackageJson: packageJson,
|
|
2752
|
+
sections: [
|
|
2753
|
+
"dependencies",
|
|
2754
|
+
"devDependencies",
|
|
2755
|
+
"peerDependencies"
|
|
2756
|
+
],
|
|
2757
|
+
workspaceDirectory,
|
|
2758
|
+
workspacePackageJson
|
|
2759
|
+
});
|
|
2654
2760
|
if (reactVersion && shouldReplaceReactVersion(result.reactVersion, reactVersion)) result.reactVersion = reactVersion;
|
|
2655
2761
|
if (tailwindVersion && !result.tailwindVersion) result.tailwindVersion = tailwindVersion;
|
|
2762
|
+
if (zodVersion && !result.zodVersion) result.zodVersion = zodVersion;
|
|
2656
2763
|
if (info.framework !== "unknown" && result.framework === "unknown") result.framework = info.framework;
|
|
2657
2764
|
const resultReactMajor = parseReactMajor(result.reactVersion);
|
|
2658
2765
|
if (result.reactVersion && result.tailwindVersion && result.framework !== "unknown" && resultReactMajor !== null && resultReactMajor <= 17) return result;
|
|
@@ -2687,17 +2794,44 @@ const findDependencyInfoFromMonorepoRoot = (directory) => {
|
|
|
2687
2794
|
"peerDependencies"
|
|
2688
2795
|
]
|
|
2689
2796
|
}) : null;
|
|
2797
|
+
const leafZodDeclaration = leafPackageJson ? getDependencyDeclaration({
|
|
2798
|
+
packageJson: leafPackageJson,
|
|
2799
|
+
packageName: "zod",
|
|
2800
|
+
sections: [
|
|
2801
|
+
"dependencies",
|
|
2802
|
+
"devDependencies",
|
|
2803
|
+
"peerDependencies"
|
|
2804
|
+
]
|
|
2805
|
+
}) : null;
|
|
2690
2806
|
const shouldUseReactFallback = !leafReactDeclaration?.hasDeclaration;
|
|
2691
2807
|
const shouldUseTailwindFallback = leafTailwindDeclaration?.hasDeclaration ?? true;
|
|
2808
|
+
const shouldUseZodFallback = leafZodDeclaration?.hasDeclaration ?? true;
|
|
2692
2809
|
const reactCatalogVersion = shouldUseReactFallback ? resolveCatalogVersion(rootPackageJson, "react", monorepoRoot, leafReactDeclaration?.catalogReference) : null;
|
|
2693
2810
|
const tailwindCatalogVersion = shouldUseTailwindFallback ? resolveCatalogVersion(rootPackageJson, "tailwindcss", monorepoRoot, leafTailwindDeclaration?.catalogReference) : null;
|
|
2811
|
+
const zodCatalogVersion = shouldUseZodFallback ? resolveCatalogVersion(rootPackageJson, "zod", monorepoRoot, leafZodDeclaration?.catalogReference) : null;
|
|
2694
2812
|
const workspaceInfo = findReactInWorkspaces(monorepoRoot, rootPackageJson);
|
|
2695
2813
|
return {
|
|
2696
2814
|
reactVersion: shouldUseReactFallback ? reactCatalogVersion ?? rootInfo.reactVersion ?? workspaceInfo.reactVersion : rootInfo.reactVersion ?? workspaceInfo.reactVersion,
|
|
2697
2815
|
tailwindVersion: shouldUseTailwindFallback ? tailwindCatalogVersion ?? rootInfo.tailwindVersion ?? workspaceInfo.tailwindVersion : null,
|
|
2816
|
+
zodVersion: shouldUseZodFallback ? zodCatalogVersion ?? rootInfo.zodVersion ?? workspaceInfo.zodVersion : null,
|
|
2698
2817
|
framework: rootInfo.framework !== "unknown" ? rootInfo.framework : workspaceInfo.framework
|
|
2699
2818
|
};
|
|
2700
2819
|
};
|
|
2820
|
+
const someWorkspacePackageJson = (rootDirectory, rootPackageJson, predicate) => {
|
|
2821
|
+
if (predicate(rootPackageJson)) return true;
|
|
2822
|
+
const patterns = getWorkspacePatterns(rootDirectory, rootPackageJson);
|
|
2823
|
+
if (patterns.length === 0) return false;
|
|
2824
|
+
const visitedDirectories = /* @__PURE__ */ new Set();
|
|
2825
|
+
for (const pattern of patterns) {
|
|
2826
|
+
const directories = resolveWorkspaceDirectories(rootDirectory, pattern);
|
|
2827
|
+
for (const workspaceDirectory of directories) {
|
|
2828
|
+
if (visitedDirectories.has(workspaceDirectory)) continue;
|
|
2829
|
+
visitedDirectories.add(workspaceDirectory);
|
|
2830
|
+
if (predicate(readPackageJson(path.join(workspaceDirectory, "package.json")))) return true;
|
|
2831
|
+
}
|
|
2832
|
+
}
|
|
2833
|
+
return false;
|
|
2834
|
+
};
|
|
2701
2835
|
const NAMES = new Set([
|
|
2702
2836
|
"react-native",
|
|
2703
2837
|
"react-native-tvos",
|
|
@@ -2728,27 +2862,13 @@ const isPackageJsonReactNativeAware = (packageJson) => {
|
|
|
2728
2862
|
if (containsAnyReactNativeDependency(packageJson.optionalDependencies)) return true;
|
|
2729
2863
|
return false;
|
|
2730
2864
|
};
|
|
2731
|
-
const hasReactNativeWorkspaceAnywhere = (rootDirectory, rootPackageJson) =>
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
if (patterns.length === 0) return false;
|
|
2735
|
-
const visitedDirectories = /* @__PURE__ */ new Set();
|
|
2736
|
-
for (const pattern of patterns) {
|
|
2737
|
-
const directories = resolveWorkspaceDirectories(rootDirectory, pattern);
|
|
2738
|
-
for (const workspaceDirectory of directories) {
|
|
2739
|
-
if (visitedDirectories.has(workspaceDirectory)) continue;
|
|
2740
|
-
visitedDirectories.add(workspaceDirectory);
|
|
2741
|
-
if (isPackageJsonReactNativeAware(readPackageJson(path.join(workspaceDirectory, "package.json")))) return true;
|
|
2742
|
-
}
|
|
2743
|
-
}
|
|
2744
|
-
return false;
|
|
2745
|
-
};
|
|
2746
|
-
const hasPreact = (packageJson) => {
|
|
2747
|
-
return "preact" in {
|
|
2865
|
+
const hasReactNativeWorkspaceAnywhere = (rootDirectory, rootPackageJson) => someWorkspacePackageJson(rootDirectory, rootPackageJson, isPackageJsonReactNativeAware);
|
|
2866
|
+
const getPreactVersion = (packageJson) => {
|
|
2867
|
+
return {
|
|
2748
2868
|
...packageJson.peerDependencies,
|
|
2749
2869
|
...packageJson.dependencies,
|
|
2750
2870
|
...packageJson.devDependencies
|
|
2751
|
-
};
|
|
2871
|
+
}.preact ?? null;
|
|
2752
2872
|
};
|
|
2753
2873
|
const TANSTACK_QUERY_PACKAGES = new Set([
|
|
2754
2874
|
"@tanstack/react-query",
|
|
@@ -2763,6 +2883,20 @@ const hasTanStackQuery = (packageJson) => {
|
|
|
2763
2883
|
};
|
|
2764
2884
|
return Object.keys(allDependencies).some((packageName) => TANSTACK_QUERY_PACKAGES.has(packageName));
|
|
2765
2885
|
};
|
|
2886
|
+
const REANIMATED_DEPENDENCY_NAME = "react-native-reanimated";
|
|
2887
|
+
const isPackageJsonReanimatedAware = (packageJson) => {
|
|
2888
|
+
const allDependencies = {
|
|
2889
|
+
...packageJson.peerDependencies,
|
|
2890
|
+
...packageJson.dependencies,
|
|
2891
|
+
...packageJson.devDependencies,
|
|
2892
|
+
...packageJson.optionalDependencies
|
|
2893
|
+
};
|
|
2894
|
+
return Object.hasOwn(allDependencies, REANIMATED_DEPENDENCY_NAME);
|
|
2895
|
+
};
|
|
2896
|
+
const parseZodMajor = (zodVersion) => {
|
|
2897
|
+
if (typeof zodVersion !== "string") return null;
|
|
2898
|
+
return getLowestDependencyMajor(zodVersion);
|
|
2899
|
+
};
|
|
2766
2900
|
const hasUpperBoundOnlyPeerRange = (range) => {
|
|
2767
2901
|
if (typeof range !== "string") return false;
|
|
2768
2902
|
const normalizedRange = normalizeDependencyVersion(range);
|
|
@@ -2849,12 +2983,22 @@ const listManifestWorkspacePackages = (rootDirectory) => {
|
|
|
2849
2983
|
const nxPatterns = patterns.length > 0 ? [] : getNxWorkspaceDirectories(rootDirectory);
|
|
2850
2984
|
return toReactWorkspacePackages((patterns.length > 0 ? patterns : nxPatterns).flatMap((pattern) => resolveWorkspaceDirectories(rootDirectory, pattern)));
|
|
2851
2985
|
};
|
|
2986
|
+
const NON_PROJECT_DIRECTORIES = new Set([
|
|
2987
|
+
"AppData",
|
|
2988
|
+
"Application Data",
|
|
2989
|
+
"Library"
|
|
2990
|
+
]);
|
|
2991
|
+
const MAX_SCAN_DEPTH = 6;
|
|
2852
2992
|
const discoverReactSubprojectsByFilesystem = (rootDirectory) => {
|
|
2853
2993
|
const packages = [];
|
|
2854
|
-
const pendingDirectories = [
|
|
2994
|
+
const pendingDirectories = [{
|
|
2995
|
+
directory: rootDirectory,
|
|
2996
|
+
depth: 0
|
|
2997
|
+
}];
|
|
2855
2998
|
while (pendingDirectories.length > 0) {
|
|
2856
|
-
const
|
|
2857
|
-
if (!
|
|
2999
|
+
const current = pendingDirectories.pop();
|
|
3000
|
+
if (!current) continue;
|
|
3001
|
+
const { directory: currentDirectory, depth } = current;
|
|
2858
3002
|
const packageJsonPath = path.join(currentDirectory, "package.json");
|
|
2859
3003
|
if (isFile(packageJsonPath)) {
|
|
2860
3004
|
const packageJson = readPackageJson(packageJsonPath);
|
|
@@ -2866,10 +3010,14 @@ const discoverReactSubprojectsByFilesystem = (rootDirectory) => {
|
|
|
2866
3010
|
});
|
|
2867
3011
|
}
|
|
2868
3012
|
}
|
|
3013
|
+
if (depth >= MAX_SCAN_DEPTH) continue;
|
|
2869
3014
|
const entries = readDirectoryEntries(currentDirectory).toSorted((firstEntry, secondEntry) => firstEntry.name.localeCompare(secondEntry.name));
|
|
2870
3015
|
for (const entry of entries) {
|
|
2871
|
-
if (!entry.isDirectory() || entry.name.startsWith(".") || IGNORED_DIRECTORIES.has(entry.name)) continue;
|
|
2872
|
-
pendingDirectories.push(
|
|
3016
|
+
if (!entry.isDirectory() || entry.name.startsWith(".") || IGNORED_DIRECTORIES.has(entry.name) || NON_PROJECT_DIRECTORIES.has(entry.name)) continue;
|
|
3017
|
+
pendingDirectories.push({
|
|
3018
|
+
directory: path.join(currentDirectory, entry.name),
|
|
3019
|
+
depth: depth + 1
|
|
3020
|
+
});
|
|
2873
3021
|
}
|
|
2874
3022
|
}
|
|
2875
3023
|
return packages;
|
|
@@ -2887,7 +3035,7 @@ const discoverProject = (directory) => {
|
|
|
2887
3035
|
const packageJsonPath = path.join(directory, "package.json");
|
|
2888
3036
|
if (!isFile(packageJsonPath)) throw new PackageJsonNotFoundError(directory);
|
|
2889
3037
|
const packageJson = readPackageJson(packageJsonPath);
|
|
2890
|
-
let { reactVersion, tailwindVersion, framework } = extractDependencyInfo(packageJson);
|
|
3038
|
+
let { reactVersion, tailwindVersion, zodVersion, framework } = extractDependencyInfo(packageJson);
|
|
2891
3039
|
const reactDeclaration = getDependencyDeclaration({
|
|
2892
3040
|
packageJson,
|
|
2893
3041
|
packageName: "react",
|
|
@@ -2906,9 +3054,19 @@ const discoverProject = (directory) => {
|
|
|
2906
3054
|
"peerDependencies"
|
|
2907
3055
|
]
|
|
2908
3056
|
});
|
|
3057
|
+
const zodDeclaration = getDependencyDeclaration({
|
|
3058
|
+
packageJson,
|
|
3059
|
+
packageName: "zod",
|
|
3060
|
+
sections: [
|
|
3061
|
+
"dependencies",
|
|
3062
|
+
"devDependencies",
|
|
3063
|
+
"peerDependencies"
|
|
3064
|
+
]
|
|
3065
|
+
});
|
|
2909
3066
|
if (!reactVersion && reactDeclaration.hasDeclaration) reactVersion = resolveCatalogVersion(packageJson, "react", directory, reactDeclaration.catalogReference);
|
|
2910
3067
|
if (!tailwindVersion && tailwindDeclaration.hasDeclaration) tailwindVersion = resolveCatalogVersion(packageJson, "tailwindcss", directory, tailwindDeclaration.catalogReference);
|
|
2911
|
-
if (!
|
|
3068
|
+
if (!zodVersion && zodDeclaration.hasDeclaration) zodVersion = resolveCatalogVersion(packageJson, "zod", directory, zodDeclaration.catalogReference);
|
|
3069
|
+
if (!reactVersion || !tailwindVersion || !zodVersion) {
|
|
2912
3070
|
const monorepoRoot = findMonorepoRoot(directory);
|
|
2913
3071
|
if (monorepoRoot) {
|
|
2914
3072
|
const monorepoPackageJsonPath = path.join(monorepoRoot, "package.json");
|
|
@@ -2916,6 +3074,7 @@ const discoverProject = (directory) => {
|
|
|
2916
3074
|
const rootPackageJson = readPackageJson(monorepoPackageJsonPath);
|
|
2917
3075
|
if (!reactVersion && reactDeclaration.hasDeclaration) reactVersion = resolveCatalogVersion(rootPackageJson, "react", monorepoRoot, reactDeclaration.catalogReference);
|
|
2918
3076
|
if (!tailwindVersion && tailwindDeclaration.hasDeclaration) tailwindVersion = resolveCatalogVersion(rootPackageJson, "tailwindcss", monorepoRoot, tailwindDeclaration.catalogReference);
|
|
3077
|
+
if (!zodVersion && zodDeclaration.hasDeclaration) zodVersion = resolveCatalogVersion(rootPackageJson, "zod", monorepoRoot, zodDeclaration.catalogReference);
|
|
2919
3078
|
}
|
|
2920
3079
|
}
|
|
2921
3080
|
}
|
|
@@ -2923,37 +3082,47 @@ const discoverProject = (directory) => {
|
|
|
2923
3082
|
const workspaceInfo = findReactInWorkspaces(directory, packageJson);
|
|
2924
3083
|
if (!reactVersion && workspaceInfo.reactVersion) reactVersion = workspaceInfo.reactVersion;
|
|
2925
3084
|
if (!tailwindVersion && workspaceInfo.tailwindVersion) tailwindVersion = workspaceInfo.tailwindVersion;
|
|
3085
|
+
if (!zodVersion && workspaceInfo.zodVersion) zodVersion = workspaceInfo.zodVersion;
|
|
2926
3086
|
if (framework === "unknown" && workspaceInfo.framework !== "unknown") framework = workspaceInfo.framework;
|
|
2927
3087
|
}
|
|
2928
3088
|
if ((!reactVersion || framework === "unknown") && !isMonorepoRoot(directory)) {
|
|
2929
3089
|
const monorepoInfo = findDependencyInfoFromMonorepoRoot(directory);
|
|
2930
3090
|
if (!reactVersion) reactVersion = monorepoInfo.reactVersion;
|
|
2931
3091
|
if (!tailwindVersion) tailwindVersion = monorepoInfo.tailwindVersion;
|
|
3092
|
+
if (!zodVersion) zodVersion = monorepoInfo.zodVersion;
|
|
2932
3093
|
if (framework === "unknown") framework = monorepoInfo.framework;
|
|
2933
3094
|
}
|
|
2934
3095
|
if (!reactVersion && reactDeclaration.version && !isCatalogReference(reactDeclaration.version)) reactVersion = reactDeclaration.version;
|
|
2935
3096
|
if (!tailwindVersion && tailwindDeclaration.version && !isCatalogReference(tailwindDeclaration.version)) tailwindVersion = tailwindDeclaration.version;
|
|
3097
|
+
if (!zodVersion && zodDeclaration.version && !isCatalogReference(zodDeclaration.version)) zodVersion = zodDeclaration.version;
|
|
2936
3098
|
const projectName = packageJson.name ?? path.basename(directory);
|
|
2937
3099
|
const hasTypeScript = fs.existsSync(path.join(directory, "tsconfig.json"));
|
|
2938
3100
|
const sourceFileCount = countSourceFiles(directory);
|
|
2939
3101
|
const hasReactNativeWorkspace = framework === "expo" || framework === "react-native" || hasReactNativeWorkspaceAnywhere(directory, packageJson);
|
|
3102
|
+
const hasReanimated = hasReactNativeWorkspace && someWorkspacePackageJson(directory, packageJson, isPackageJsonReanimatedAware);
|
|
3103
|
+
const preactVersion = getPreactVersion(packageJson);
|
|
2940
3104
|
const projectInfo = {
|
|
2941
3105
|
rootDirectory: directory,
|
|
2942
3106
|
projectName,
|
|
2943
3107
|
reactVersion,
|
|
2944
3108
|
reactMajorVersion: resolveEffectiveReactMajor(reactVersion, packageJson),
|
|
2945
3109
|
tailwindVersion,
|
|
3110
|
+
zodVersion,
|
|
3111
|
+
zodMajorVersion: parseZodMajor(zodVersion),
|
|
2946
3112
|
framework,
|
|
2947
3113
|
hasTypeScript,
|
|
2948
3114
|
hasReactCompiler: detectReactCompiler(directory, packageJson),
|
|
2949
3115
|
hasTanStackQuery: hasTanStackQuery(packageJson),
|
|
2950
|
-
|
|
3116
|
+
preactVersion,
|
|
3117
|
+
preactMajorVersion: parseReactMajor(preactVersion),
|
|
2951
3118
|
hasReactNativeWorkspace,
|
|
3119
|
+
hasReanimated,
|
|
2952
3120
|
sourceFileCount
|
|
2953
3121
|
};
|
|
2954
3122
|
cachedProjectInfos.set(directory, projectInfo);
|
|
2955
3123
|
return projectInfo;
|
|
2956
3124
|
};
|
|
3125
|
+
const isAnalyzableProject = (project) => project.reactVersion !== null || project.preactVersion !== null;
|
|
2957
3126
|
const MAJOR_MINOR_PATTERN = /(\d{1,4})\.(\d{1,4})/;
|
|
2958
3127
|
const MAJOR_ONLY_PATTERN = /(\d{1,4})/;
|
|
2959
3128
|
const UPPER_BOUND_COMPARATOR_PATTERN = /<=?\s{0,8}\d{1,4}(?:\.\d{1,4}){0,2}(?:-[^\s,|]+)?/g;
|
|
@@ -3021,6 +3190,7 @@ const JSX_FILE_PATTERN = /\.(tsx|jsx)$/;
|
|
|
3021
3190
|
const MILLISECONDS_PER_SECOND = 1e3;
|
|
3022
3191
|
const SCORE_API_URL = "https://www.react.doctor/api/score";
|
|
3023
3192
|
const SHARE_BASE_URL = "https://www.react.doctor/share";
|
|
3193
|
+
const PROMPTS_RULES_BASE_URL = "https://www.react.doctor/prompts/rules";
|
|
3024
3194
|
const FETCH_TIMEOUT_MS = 1e4;
|
|
3025
3195
|
const GITHUB_VIEWER_PERMISSION_TIMEOUT_MS = 2e3;
|
|
3026
3196
|
const DEFAULT_BRANCH_CANDIDATES = ["main", "master"];
|
|
@@ -3923,17 +4093,26 @@ const layerOtlp = Layer.unwrap(Effect.gen(function* () {
|
|
|
3923
4093
|
headers
|
|
3924
4094
|
}).pipe(Layer.provide(FetchHttpClient.layer));
|
|
3925
4095
|
}).pipe(Effect.orDie));
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
4096
|
+
/**
|
|
4097
|
+
* Per-batch oxlint wall-clock budget. Reads from the env var on
|
|
4098
|
+
* startup so the eval harness can raise the budget under sandbox
|
|
4099
|
+
* microVMs without recompiling react-doctor. Tests override via
|
|
4100
|
+
* `Layer.succeed(OxlintSpawnTimeoutMs, ...)`.
|
|
4101
|
+
*/
|
|
4102
|
+
var OxlintSpawnTimeoutMs = class extends Context.Reference("react-doctor/OxlintSpawnTimeoutMs", { defaultValue: () => {
|
|
3929
4103
|
const raw = process.env["REACT_DOCTOR_OXLINT_SPAWN_TIMEOUT_MS"];
|
|
3930
4104
|
if (raw === void 0) return OXLINT_SPAWN_TIMEOUT_MS;
|
|
3931
4105
|
const parsed = Number(raw);
|
|
3932
4106
|
if (!Number.isFinite(parsed) || parsed <= 0) return OXLINT_SPAWN_TIMEOUT_MS;
|
|
3933
4107
|
return parsed;
|
|
3934
|
-
} });
|
|
3935
|
-
|
|
3936
|
-
|
|
4108
|
+
} }) {};
|
|
4109
|
+
/**
|
|
4110
|
+
* Hard cap on combined stdout+stderr bytes per oxlint batch. The
|
|
4111
|
+
* subprocess gets SIGKILL'd if it produces more; the recovery path
|
|
4112
|
+
* suggests narrowing the scan with --diff. Override via Layer in
|
|
4113
|
+
* tests that exercise the cap behavior.
|
|
4114
|
+
*/
|
|
4115
|
+
var OxlintOutputMaxBytes = class extends Context.Reference("react-doctor/OxlintOutputMaxBytes", { defaultValue: () => OXLINT_OUTPUT_MAX_BYTES }) {};
|
|
3937
4116
|
const DIAGNOSTIC_SURFACES = [
|
|
3938
4117
|
"cli",
|
|
3939
4118
|
"prComment",
|
|
@@ -4539,47 +4718,57 @@ const collectIgnorePatterns = (rootDirectory) => {
|
|
|
4539
4718
|
};
|
|
4540
4719
|
const TSCONFIG_FILENAMES$1 = ["tsconfig.json", "tsconfig.base.json"];
|
|
4541
4720
|
const DEAD_CODE_WORKER_SCRIPT = `
|
|
4542
|
-
const
|
|
4721
|
+
const inputChunks = [];
|
|
4722
|
+
process.stdin.on("data", (chunk) => inputChunks.push(chunk));
|
|
4723
|
+
process.stdin.on("end", () => {
|
|
4724
|
+
const workerInput = JSON.parse(Buffer.concat(inputChunks).toString("utf8"));
|
|
4543
4725
|
|
|
4544
|
-
const normalizeResult = (result) => ({
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
});
|
|
4726
|
+
const normalizeResult = (result) => ({
|
|
4727
|
+
unusedFiles: result.unusedFiles.map((unusedFile) => ({
|
|
4728
|
+
path: unusedFile.path,
|
|
4729
|
+
})),
|
|
4730
|
+
unusedExports: result.unusedExports.map((unusedExport) => ({
|
|
4731
|
+
path: unusedExport.path,
|
|
4732
|
+
name: unusedExport.name,
|
|
4733
|
+
line: unusedExport.line,
|
|
4734
|
+
column: unusedExport.column,
|
|
4735
|
+
isTypeOnly: unusedExport.isTypeOnly,
|
|
4736
|
+
})),
|
|
4737
|
+
unusedDependencies: result.unusedDependencies.map((unusedDependency) => ({
|
|
4738
|
+
name: unusedDependency.name,
|
|
4739
|
+
isDevDependency: unusedDependency.isDevDependency,
|
|
4740
|
+
})),
|
|
4741
|
+
circularDependencies: result.circularDependencies.map((cycle) => ({
|
|
4742
|
+
files: cycle.files,
|
|
4743
|
+
})),
|
|
4744
|
+
});
|
|
4563
4745
|
|
|
4564
|
-
const serializeError = (error) =>
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4746
|
+
const serializeError = (error) =>
|
|
4747
|
+
error instanceof Error
|
|
4748
|
+
? { name: error.name, message: error.message, stack: error.stack }
|
|
4749
|
+
: { message: String(error) };
|
|
4568
4750
|
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
}
|
|
4751
|
+
const emit = (message) => {
|
|
4752
|
+
process.stdout.write(JSON.stringify(message), () => process.exit(0));
|
|
4753
|
+
};
|
|
4754
|
+
|
|
4755
|
+
(async () => {
|
|
4756
|
+
try {
|
|
4757
|
+
const { analyze, defineConfig } = await import(workerInput.deslopJsModuleSpecifier);
|
|
4758
|
+
const config = {
|
|
4759
|
+
rootDir: workerInput.rootDirectory,
|
|
4760
|
+
...(workerInput.tsConfigPath ? { tsConfigPath: workerInput.tsConfigPath } : {}),
|
|
4761
|
+
...(workerInput.ignorePatterns.length > 0
|
|
4762
|
+
? { ignorePatterns: workerInput.ignorePatterns }
|
|
4763
|
+
: {}),
|
|
4764
|
+
};
|
|
4765
|
+
const result = await analyze(defineConfig(config));
|
|
4766
|
+
emit({ ok: true, result: normalizeResult(result) });
|
|
4767
|
+
} catch (error) {
|
|
4768
|
+
emit({ ok: false, error: serializeError(error) });
|
|
4769
|
+
}
|
|
4770
|
+
})();
|
|
4771
|
+
});
|
|
4583
4772
|
`;
|
|
4584
4773
|
const resolveTsConfigPath = (rootDirectory) => {
|
|
4585
4774
|
for (const filename of TSCONFIG_FILENAMES$1) {
|
|
@@ -4702,43 +4891,54 @@ const buildDeadCodeWorkerError = (workerError) => {
|
|
|
4702
4891
|
return error;
|
|
4703
4892
|
};
|
|
4704
4893
|
const createDeadCodeWorker = (input) => {
|
|
4705
|
-
const
|
|
4706
|
-
|
|
4707
|
-
|
|
4894
|
+
const child = spawn(process.execPath, ["-e", DEAD_CODE_WORKER_SCRIPT], {
|
|
4895
|
+
stdio: [
|
|
4896
|
+
"pipe",
|
|
4897
|
+
"pipe",
|
|
4898
|
+
"pipe"
|
|
4899
|
+
],
|
|
4900
|
+
windowsHide: true
|
|
4708
4901
|
});
|
|
4902
|
+
const stdoutChunks = [];
|
|
4903
|
+
const stderrChunks = [];
|
|
4904
|
+
child.stdout.on("data", (chunk) => stdoutChunks.push(chunk));
|
|
4905
|
+
child.stderr.on("data", (chunk) => stderrChunks.push(chunk));
|
|
4709
4906
|
let didSettle = false;
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4907
|
+
const result = new Promise((resolve, reject) => {
|
|
4908
|
+
const settle = (callback) => {
|
|
4909
|
+
if (didSettle) return;
|
|
4910
|
+
didSettle = true;
|
|
4911
|
+
callback();
|
|
4912
|
+
};
|
|
4913
|
+
child.once("error", (error) => {
|
|
4914
|
+
settle(() => reject(error));
|
|
4915
|
+
});
|
|
4916
|
+
child.once("close", (exitCode) => {
|
|
4917
|
+
const stdout = Buffer.concat(stdoutChunks).toString("utf8").trim();
|
|
4918
|
+
if (stdout.length === 0) {
|
|
4919
|
+
const stderr = Buffer.concat(stderrChunks).toString("utf8").trim();
|
|
4920
|
+
settle(() => reject(/* @__PURE__ */ new Error(`Dead-code worker exited with code ${exitCode ?? "null"}${stderr ? `: ${stderr}` : ""}.`)));
|
|
4921
|
+
return;
|
|
4922
|
+
}
|
|
4923
|
+
try {
|
|
4924
|
+
const parsedMessage = parseDeadCodeWorkerMessage(JSON.parse(stdout));
|
|
4925
|
+
if (parsedMessage.ok) {
|
|
4926
|
+
settle(() => resolve(parsedMessage.result));
|
|
4927
|
+
return;
|
|
4728
4928
|
}
|
|
4729
|
-
|
|
4730
|
-
|
|
4929
|
+
settle(() => reject(buildDeadCodeWorkerError(parsedMessage.error)));
|
|
4930
|
+
} catch (error) {
|
|
4731
4931
|
settle(() => reject(error));
|
|
4732
|
-
}
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4932
|
+
}
|
|
4933
|
+
});
|
|
4934
|
+
});
|
|
4935
|
+
child.stdin.on("error", () => {});
|
|
4936
|
+
child.stdin.end(JSON.stringify(input));
|
|
4937
|
+
return {
|
|
4938
|
+
result,
|
|
4738
4939
|
terminate: () => {
|
|
4739
4940
|
didSettle = true;
|
|
4740
|
-
|
|
4741
|
-
return worker.terminate();
|
|
4941
|
+
child.kill("SIGKILL");
|
|
4742
4942
|
}
|
|
4743
4943
|
};
|
|
4744
4944
|
};
|
|
@@ -4980,8 +5180,15 @@ var Git = class Git extends Context.Service()("react-doctor/Git") {
|
|
|
4980
5180
|
env: input.env,
|
|
4981
5181
|
extendEnv: true
|
|
4982
5182
|
}));
|
|
5183
|
+
const maxStdoutBytes = input.maxStdoutBytes;
|
|
5184
|
+
const stdoutByteCount = yield* Ref.make(0);
|
|
5185
|
+
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({
|
|
5186
|
+
args: [...input.args],
|
|
5187
|
+
directory: input.directory,
|
|
5188
|
+
cause: /* @__PURE__ */ new Error(`git stdout exceeded ${maxStdoutBytes} bytes`)
|
|
5189
|
+
}) })) : Effect.void))));
|
|
4983
5190
|
const [stdout, stderr, status] = yield* Effect.all([
|
|
4984
|
-
Stream.mkString(Stream.decodeText(
|
|
5191
|
+
Stream.mkString(Stream.decodeText(stdoutStream)),
|
|
4985
5192
|
Stream.mkString(Stream.decodeText(handle.stderr)),
|
|
4986
5193
|
handle.exitCode
|
|
4987
5194
|
], { concurrency: 3 });
|
|
@@ -5143,7 +5350,12 @@ var Git = class Git extends Context.Service()("react-doctor/Git") {
|
|
|
5143
5350
|
if (result.status !== 0) return [];
|
|
5144
5351
|
return splitNullSeparated(result.stdout);
|
|
5145
5352
|
})),
|
|
5146
|
-
showStagedContent: (directory, relativePath) =>
|
|
5353
|
+
showStagedContent: (directory, relativePath, options) => runCommand({
|
|
5354
|
+
command: "git",
|
|
5355
|
+
args: ["show", `:${relativePath}`],
|
|
5356
|
+
directory,
|
|
5357
|
+
maxStdoutBytes: options?.maxBufferBytes
|
|
5358
|
+
}).pipe(Effect.map((result) => result.status === 0 ? result.stdout : null)),
|
|
5147
5359
|
grep: (input) => Effect.gen(function* () {
|
|
5148
5360
|
const args = ["grep"];
|
|
5149
5361
|
if (input.listMatchingFiles ?? true) args.push("-l");
|
|
@@ -5151,7 +5363,12 @@ var Git = class Git extends Context.Service()("react-doctor/Git") {
|
|
|
5151
5363
|
if (input.extendedRegexp ?? false) args.push("-E");
|
|
5152
5364
|
args.push(input.pattern);
|
|
5153
5365
|
if (input.includePaths && input.includePaths.length > 0) args.push("--", ...input.includePaths);
|
|
5154
|
-
const result = yield*
|
|
5366
|
+
const result = yield* runCommand({
|
|
5367
|
+
command: "git",
|
|
5368
|
+
args,
|
|
5369
|
+
directory: input.directory,
|
|
5370
|
+
maxStdoutBytes: input.maxBufferBytes
|
|
5371
|
+
});
|
|
5155
5372
|
if (result.status === 128) return null;
|
|
5156
5373
|
return {
|
|
5157
5374
|
status: result.status,
|
|
@@ -5399,7 +5616,8 @@ const buildCapabilities = (project) => {
|
|
|
5399
5616
|
if (project.framework === "expo" || project.framework === "react-native" || project.hasReactNativeWorkspace) capabilities.add("react-native");
|
|
5400
5617
|
const reactMajor = project.reactMajorVersion;
|
|
5401
5618
|
if (reactMajor !== null) {
|
|
5402
|
-
|
|
5619
|
+
const cappedReactMajor = Math.min(reactMajor, 30);
|
|
5620
|
+
for (let major = 17; major <= cappedReactMajor; major++) capabilities.add(`react:${major}`);
|
|
5403
5621
|
if (reactMajor >= 19) {
|
|
5404
5622
|
if (isReactAtLeast(parseReactMajorMinor(project.reactVersion), {
|
|
5405
5623
|
major: 19,
|
|
@@ -5414,11 +5632,20 @@ const buildCapabilities = (project) => {
|
|
|
5414
5632
|
minor: 4
|
|
5415
5633
|
})) capabilities.add("tailwind:3.4");
|
|
5416
5634
|
}
|
|
5635
|
+
if (project.zodVersion !== null) {
|
|
5636
|
+
capabilities.add("zod");
|
|
5637
|
+
if (project.zodMajorVersion !== null && project.zodMajorVersion >= 4) capabilities.add("zod:4");
|
|
5638
|
+
}
|
|
5417
5639
|
if (project.hasReactCompiler) capabilities.add("react-compiler");
|
|
5418
5640
|
if (project.hasTanStackQuery) capabilities.add("tanstack-query");
|
|
5419
5641
|
if (project.hasTypeScript) capabilities.add("typescript");
|
|
5420
|
-
if (project.
|
|
5642
|
+
if (project.preactVersion !== null) {
|
|
5421
5643
|
capabilities.add("preact");
|
|
5644
|
+
const preactMajor = project.preactMajorVersion;
|
|
5645
|
+
if (preactMajor !== null) {
|
|
5646
|
+
const cappedPreactMajor = Math.min(preactMajor, 20);
|
|
5647
|
+
for (let major = 10; major <= cappedPreactMajor; major++) capabilities.add(`preact:${major}`);
|
|
5648
|
+
}
|
|
5422
5649
|
if (project.reactVersion === null) capabilities.add("pure-preact");
|
|
5423
5650
|
}
|
|
5424
5651
|
return capabilities;
|
|
@@ -5675,6 +5902,13 @@ const buildNoSecretsRecommendation = (project, fallbackRecommendation) => {
|
|
|
5675
5902
|
if (!publicEnvPrefix) return fallbackRecommendation;
|
|
5676
5903
|
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`;
|
|
5677
5904
|
};
|
|
5905
|
+
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";
|
|
5906
|
+
const appendReanimatedSharedValueHint = (help, rule, project) => {
|
|
5907
|
+
if (rule !== "immutability") return help;
|
|
5908
|
+
if (!project.hasReanimated) return help;
|
|
5909
|
+
if (!help) return REANIMATED_SHARED_VALUE_HINT;
|
|
5910
|
+
return `${help}\n\n${REANIMATED_SHARED_VALUE_HINT}`;
|
|
5911
|
+
};
|
|
5678
5912
|
const REACT_MODULE_SOURCE = "react";
|
|
5679
5913
|
const REQUIRE_IDENTIFIER = "require";
|
|
5680
5914
|
const USE_IDENTIFIER = "use";
|
|
@@ -5998,7 +6232,7 @@ const getRuleCategory = (ruleName) => reactDoctorPlugin.rules[ruleName]?.categor
|
|
|
5998
6232
|
const cleanDiagnosticMessage = (message, help, plugin, rule, project) => {
|
|
5999
6233
|
if (plugin === "react-hooks-js") return {
|
|
6000
6234
|
message: REACT_COMPILER_MESSAGE,
|
|
6001
|
-
help: message.replace(FILEPATH_WITH_LOCATION_PATTERN, "").trim() || help
|
|
6235
|
+
help: appendReanimatedSharedValueHint(message.replace(FILEPATH_WITH_LOCATION_PATTERN, "").trim() || help, rule, project)
|
|
6002
6236
|
};
|
|
6003
6237
|
return {
|
|
6004
6238
|
message: message.replace(FILEPATH_WITH_LOCATION_PATTERN, "").trim() || message,
|
|
@@ -6067,13 +6301,6 @@ const SANITIZED_ENV = (() => {
|
|
|
6067
6301
|
}
|
|
6068
6302
|
return sanitized;
|
|
6069
6303
|
})();
|
|
6070
|
-
const OXLINT_SPAWN_TIMEOUT_MS$1 = (() => {
|
|
6071
|
-
const raw = process.env["REACT_DOCTOR_OXLINT_SPAWN_TIMEOUT_MS"];
|
|
6072
|
-
if (raw === void 0) return OXLINT_SPAWN_TIMEOUT_MS;
|
|
6073
|
-
const parsed = Number(raw);
|
|
6074
|
-
if (!Number.isFinite(parsed) || parsed <= 0) return OXLINT_SPAWN_TIMEOUT_MS;
|
|
6075
|
-
return parsed;
|
|
6076
|
-
})();
|
|
6077
6304
|
/**
|
|
6078
6305
|
* Spawn one oxlint subprocess with hard ceilings on wall time and
|
|
6079
6306
|
* output size. Returns stdout on success; raises a tagged
|
|
@@ -6090,7 +6317,7 @@ const OXLINT_SPAWN_TIMEOUT_MS$1 = (() => {
|
|
|
6090
6317
|
* The first three are splittable (the caller's binary-split retry
|
|
6091
6318
|
* shrinks the batch and re-spawns); the fourth isn't.
|
|
6092
6319
|
*/
|
|
6093
|
-
const spawnOxlint = (args, rootDirectory, nodeBinaryPath) => new Promise((resolve, reject) => {
|
|
6320
|
+
const spawnOxlint = (args, rootDirectory, nodeBinaryPath, spawnTimeoutMs = OXLINT_SPAWN_TIMEOUT_MS, outputMaxBytes = OXLINT_OUTPUT_MAX_BYTES) => new Promise((resolve, reject) => {
|
|
6094
6321
|
const child = spawn(nodeBinaryPath, args, {
|
|
6095
6322
|
cwd: rootDirectory,
|
|
6096
6323
|
env: SANITIZED_ENV
|
|
@@ -6099,9 +6326,9 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath) => new Promise((resolv
|
|
|
6099
6326
|
child.kill("SIGKILL");
|
|
6100
6327
|
reject(new ReactDoctorError({ reason: new OxlintBatchExceeded({
|
|
6101
6328
|
kind: "timeout",
|
|
6102
|
-
detail: `${
|
|
6329
|
+
detail: `${spawnTimeoutMs / 1e3}s budget exceeded`
|
|
6103
6330
|
}) }));
|
|
6104
|
-
},
|
|
6331
|
+
}, spawnTimeoutMs);
|
|
6105
6332
|
timeoutHandle.unref?.();
|
|
6106
6333
|
const stdoutBuffers = [];
|
|
6107
6334
|
const stderrBuffers = [];
|
|
@@ -6111,7 +6338,7 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath) => new Promise((resolv
|
|
|
6111
6338
|
const killIfTooLarge = (incomingBytes, isStdout) => {
|
|
6112
6339
|
if (isStdout) stdoutByteCount += incomingBytes;
|
|
6113
6340
|
else stderrByteCount += incomingBytes;
|
|
6114
|
-
if (stdoutByteCount + stderrByteCount >
|
|
6341
|
+
if (stdoutByteCount + stderrByteCount > outputMaxBytes && !didKillForSize) {
|
|
6115
6342
|
didKillForSize = true;
|
|
6116
6343
|
child.kill("SIGKILL");
|
|
6117
6344
|
return true;
|
|
@@ -6137,7 +6364,7 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath) => new Promise((resolv
|
|
|
6137
6364
|
if (didKillForSize) {
|
|
6138
6365
|
reject(new ReactDoctorError({ reason: new OxlintBatchExceeded({
|
|
6139
6366
|
kind: "output-too-large",
|
|
6140
|
-
detail: `exceeded ${
|
|
6367
|
+
detail: `exceeded ${outputMaxBytes} bytes — scan a smaller subset with --diff or --staged`
|
|
6141
6368
|
}) }));
|
|
6142
6369
|
return;
|
|
6143
6370
|
}
|
|
@@ -6178,7 +6405,7 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath) => new Promise((resolv
|
|
|
6178
6405
|
* with a slimmer config in that case.
|
|
6179
6406
|
*/
|
|
6180
6407
|
const spawnLintBatches = async (input) => {
|
|
6181
|
-
const { baseArgs, fileBatches, rootDirectory, nodeBinaryPath, project, onPartialFailure, onFileProgress } = input;
|
|
6408
|
+
const { baseArgs, fileBatches, rootDirectory, nodeBinaryPath, project, onPartialFailure, onFileProgress, spawnTimeoutMs, outputMaxBytes } = input;
|
|
6182
6409
|
const totalFileCount = fileBatches.reduce((sum, batch) => sum + batch.length, 0);
|
|
6183
6410
|
const allDiagnostics = [];
|
|
6184
6411
|
const droppedFiles = [];
|
|
@@ -6186,7 +6413,7 @@ const spawnLintBatches = async (input) => {
|
|
|
6186
6413
|
const spawnLintBatch = async (batch) => {
|
|
6187
6414
|
const batchArgs = [...baseArgs, ...batch];
|
|
6188
6415
|
try {
|
|
6189
|
-
return parseOxlintOutput(await spawnOxlint(batchArgs, rootDirectory, nodeBinaryPath), project, rootDirectory);
|
|
6416
|
+
return parseOxlintOutput(await spawnOxlint(batchArgs, rootDirectory, nodeBinaryPath, spawnTimeoutMs, outputMaxBytes), project, rootDirectory);
|
|
6190
6417
|
} catch (error) {
|
|
6191
6418
|
if (!isSplittableReactDoctorError(error)) throw error;
|
|
6192
6419
|
if (batch.length <= 1) {
|
|
@@ -6289,13 +6516,11 @@ const writeOxlintConfig = (configPath, configToWrite) => {
|
|
|
6289
6516
|
* 6. always restore disable directives + clean up the temp dir
|
|
6290
6517
|
*/
|
|
6291
6518
|
const runOxlint = async (options) => {
|
|
6292
|
-
const { rootDirectory, project, includePaths, nodeBinaryPath = process.execPath, customRulesOnly = false, respectInlineDisables = true, adoptExistingLintConfig = true, ignoredTags = /* @__PURE__ */ new Set(), userConfig, configSourceDirectory = rootDirectory, onPartialFailure } = options;
|
|
6519
|
+
const { rootDirectory, project, includePaths, nodeBinaryPath = process.execPath, customRulesOnly = false, respectInlineDisables = true, adoptExistingLintConfig = true, ignoredTags = /* @__PURE__ */ new Set(), userConfig, configSourceDirectory = rootDirectory, onPartialFailure, spawnTimeoutMs, outputMaxBytes } = options;
|
|
6293
6520
|
const serverAuthFunctionNames = Array.isArray(userConfig?.serverAuthFunctionNames) ? userConfig.serverAuthFunctionNames.filter((entry) => typeof entry === "string" && entry.length > 0) : void 0;
|
|
6294
6521
|
const severityControls = buildRuleSeverityControls(userConfig);
|
|
6295
6522
|
validateRuleRegistration();
|
|
6296
6523
|
if (includePaths !== void 0 && includePaths.length === 0) return [];
|
|
6297
|
-
const configDirectory = fs.mkdtempSync(path.join(os.tmpdir(), "react-doctor-oxlintrc-"));
|
|
6298
|
-
const configPath = path.join(configDirectory, "oxlintrc.json");
|
|
6299
6524
|
const pluginPath = resolvePluginPath();
|
|
6300
6525
|
const extendsPaths = (adoptExistingLintConfig && !customRulesOnly ? detectUserLintConfigPaths(rootDirectory) : []).filter(canOxlintExtendConfig);
|
|
6301
6526
|
const userPlugins = resolveUserPlugins(userConfig?.plugins, configSourceDirectory);
|
|
@@ -6310,6 +6535,8 @@ const runOxlint = async (options) => {
|
|
|
6310
6535
|
userPlugins
|
|
6311
6536
|
});
|
|
6312
6537
|
const restoreDisableDirectives = respectInlineDisables ? () => {} : await neutralizeDisableDirectives(rootDirectory, includePaths);
|
|
6538
|
+
const configDirectory = fs.mkdtempSync(path.join(os.tmpdir(), "react-doctor-oxlintrc-"));
|
|
6539
|
+
const configPath = path.join(configDirectory, "oxlintrc.json");
|
|
6313
6540
|
try {
|
|
6314
6541
|
const baseArgs = [
|
|
6315
6542
|
resolveOxlintBinary(),
|
|
@@ -6336,7 +6563,9 @@ const runOxlint = async (options) => {
|
|
|
6336
6563
|
nodeBinaryPath,
|
|
6337
6564
|
project,
|
|
6338
6565
|
onPartialFailure,
|
|
6339
|
-
onFileProgress: options.onFileProgress
|
|
6566
|
+
onFileProgress: options.onFileProgress,
|
|
6567
|
+
spawnTimeoutMs,
|
|
6568
|
+
outputMaxBytes
|
|
6340
6569
|
});
|
|
6341
6570
|
writeOxlintConfig(configPath, buildConfig(extendsPaths));
|
|
6342
6571
|
try {
|
|
@@ -6402,6 +6631,8 @@ var Linter = class Linter extends Context.Service()("react-doctor/Linter") {
|
|
|
6402
6631
|
*/
|
|
6403
6632
|
static layerOxlint = Layer.succeed(Linter, Linter.of({ run: (input) => Stream.unwrap(Effect.fn("Linter.run")(function* () {
|
|
6404
6633
|
const partialFailures = yield* LintPartialFailures;
|
|
6634
|
+
const spawnTimeoutMs = yield* OxlintSpawnTimeoutMs;
|
|
6635
|
+
const outputMaxBytes = yield* OxlintOutputMaxBytes;
|
|
6405
6636
|
const collectedFailures = [];
|
|
6406
6637
|
const diagnostics = yield* Effect.tryPromise({
|
|
6407
6638
|
try: () => runOxlint({
|
|
@@ -6418,7 +6649,9 @@ var Linter = class Linter extends Context.Service()("react-doctor/Linter") {
|
|
|
6418
6649
|
onPartialFailure: (reason) => {
|
|
6419
6650
|
collectedFailures.push(reason);
|
|
6420
6651
|
},
|
|
6421
|
-
onFileProgress: input.onFileProgress
|
|
6652
|
+
onFileProgress: input.onFileProgress,
|
|
6653
|
+
spawnTimeoutMs,
|
|
6654
|
+
outputMaxBytes
|
|
6422
6655
|
}),
|
|
6423
6656
|
catch: ensureReactDoctorError
|
|
6424
6657
|
});
|
|
@@ -6716,7 +6949,7 @@ const runInspect = (input, hooks = {}) => Effect.gen(function* () {
|
|
|
6716
6949
|
const resolvedConfig = yield* configService.resolve(input.directory);
|
|
6717
6950
|
const scanDirectory = resolvedConfig.resolvedDirectory;
|
|
6718
6951
|
const project = yield* projectService.discover(scanDirectory);
|
|
6719
|
-
if (project
|
|
6952
|
+
if (!isAnalyzableProject(project)) return yield* new ReactDoctorError({ reason: new NoReactDependency({ directory: scanDirectory }) });
|
|
6720
6953
|
const [repo, sha, defaultBranch] = yield* Effect.all([
|
|
6721
6954
|
gitService.githubRepo(scanDirectory).pipe(Effect.orElseSucceed(() => null)),
|
|
6722
6955
|
gitService.headSha(scanDirectory).pipe(Effect.orElseSucceed(() => null)),
|
|
@@ -6744,7 +6977,8 @@ const runInspect = (input, hooks = {}) => Effect.gen(function* () {
|
|
|
6744
6977
|
const lintFailure = yield* Ref.make({
|
|
6745
6978
|
didFail: false,
|
|
6746
6979
|
reason: null,
|
|
6747
|
-
reasonTag: null
|
|
6980
|
+
reasonTag: null,
|
|
6981
|
+
reasonKind: null
|
|
6748
6982
|
});
|
|
6749
6983
|
const deadCodeFailure = yield* Ref.make({
|
|
6750
6984
|
didFail: false,
|
|
@@ -6766,13 +7000,14 @@ const runInspect = (input, hooks = {}) => Effect.gen(function* () {
|
|
|
6766
7000
|
configSourceDirectory: resolvedConfig.configSourceDirectory ?? void 0,
|
|
6767
7001
|
onFileProgress: (scannedFileCount, totalFileCount) => {
|
|
6768
7002
|
lastReportedTotalFileCount = totalFileCount;
|
|
6769
|
-
Effect.runSync(scanProgress.update(`Scanning (${scannedFileCount}/${totalFileCount})...`));
|
|
7003
|
+
Effect.runSync(scanProgress.update(`Scanning files (${scannedFileCount}/${totalFileCount})...`));
|
|
6770
7004
|
}
|
|
6771
7005
|
}).pipe(Stream.catchTag("ReactDoctorError", (error) => Stream.unwrap(Effect.gen(function* () {
|
|
6772
7006
|
yield* Ref.set(lintFailure, {
|
|
6773
7007
|
didFail: true,
|
|
6774
7008
|
reason: error.message,
|
|
6775
|
-
reasonTag: error.reason._tag
|
|
7009
|
+
reasonTag: error.reason._tag,
|
|
7010
|
+
reasonKind: error.reason._tag === "OxlintUnavailable" ? error.reason.kind : null
|
|
6776
7011
|
});
|
|
6777
7012
|
return Stream.empty;
|
|
6778
7013
|
}))));
|
|
@@ -6832,6 +7067,7 @@ const runInspect = (input, hooks = {}) => Effect.gen(function* () {
|
|
|
6832
7067
|
didLintFail: lintFailureState.didFail,
|
|
6833
7068
|
lintFailureReason: lintFailureState.reason,
|
|
6834
7069
|
lintFailureReasonTag: lintFailureState.reasonTag,
|
|
7070
|
+
lintFailureReasonKind: lintFailureState.reasonKind,
|
|
6835
7071
|
lintPartialFailures,
|
|
6836
7072
|
didDeadCodeFail: deadCodeFailureState.didFail,
|
|
6837
7073
|
deadCodeFailureReason: deadCodeFailureState.reason
|
|
@@ -7269,6 +7505,13 @@ const highlighter = {
|
|
|
7269
7505
|
gray: import_picocolors.default.gray,
|
|
7270
7506
|
bold: import_picocolors.default.bold
|
|
7271
7507
|
};
|
|
7508
|
+
/**
|
|
7509
|
+
* Canonical URL for a rule's reviewer-tested fix recipe, served at
|
|
7510
|
+
* `https://www.react.doctor/prompts/rules/<plugin>/<rule>.md`. The
|
|
7511
|
+
* `/doctor` playbook fetches it on demand so each fix follows the
|
|
7512
|
+
* canonical recipe instead of being improvised per diagnostic.
|
|
7513
|
+
*/
|
|
7514
|
+
const buildRulePromptUrl = (plugin, rule) => `${PROMPTS_RULES_BASE_URL}/${plugin}/${rule}.md`;
|
|
7272
7515
|
const groupBy = (items, keyFn) => {
|
|
7273
7516
|
const groups = /* @__PURE__ */ new Map();
|
|
7274
7517
|
for (const item of items) {
|
|
@@ -7316,6 +7559,6 @@ const cliLogger = {
|
|
|
7316
7559
|
}
|
|
7317
7560
|
};
|
|
7318
7561
|
//#endregion
|
|
7319
|
-
export {
|
|
7562
|
+
export { isMonorepoRoot as A, filterDiagnosticsForSurface as C, getDiffInfo as D, formatReactDoctorError as E, restoreLegacyThrow as F, runInspect as I, toRelativePath as L, layerOtlp as M, listWorkspacePackages as N, groupBy as O, resolveScanTarget as P, discoverReactSubprojects as S, formatErrorChain as T, Score as _, DeadCode as a, buildJsonReportError as b, LintPartialFailures as c, OXLINT_NODE_REQUIREMENT as d, Progress as f, SKILL_NAME as g, SHARE_BASE_URL as h, Config as i, isReactDoctorError as j, highlighter as k, Linter as l, Reporter as m, cli_logger_exports as n, Files as o, Project as p, CANONICAL_GITHUB_URL as r, Git as s, cliLogger as t, NodeResolver as u, StagedFiles as v, filterSourceFiles as w, buildRulePromptUrl as x, buildJsonReport as y };
|
|
7320
7563
|
|
|
7321
|
-
//# sourceMappingURL=cli-logger-
|
|
7564
|
+
//# sourceMappingURL=cli-logger-CSZagq1E.js.map
|