react-doctor 0.2.11-dev.f4035fc → 0.2.12-dev.269ca17
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 +1 -1
- package/dist/{cli-logger-Df45H6Lw.js → cli-logger-CSZagq1E.js} +169 -17
- package/dist/cli.js +24 -13
- package/dist/index.d.ts +3 -0
- package/dist/index.js +159 -15
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -64,7 +64,7 @@ jobs:
|
|
|
64
64
|
runs-on: ubuntu-latest
|
|
65
65
|
steps:
|
|
66
66
|
- uses: actions/checkout@v5
|
|
67
|
-
- uses: millionco/react-doctor@
|
|
67
|
+
- uses: millionco/react-doctor@main
|
|
68
68
|
```
|
|
69
69
|
|
|
70
70
|
React Doctor scans the files changed in the pull request, emits inline annotations, blocks on error-level findings, and updates one sticky PR comment with the score and issue summary. The built-in GitHub token is used automatically; no secret or PAT is required. On forked PRs where GitHub withholds write permissions, the scan and annotations still run, but the sticky comment may be skipped.
|
|
@@ -2281,15 +2281,91 @@ const detectFramework = (dependencies) => {
|
|
|
2281
2281
|
if (dependencies.preact && !dependencies.react) return "preact";
|
|
2282
2282
|
return "unknown";
|
|
2283
2283
|
};
|
|
2284
|
-
const UPPER_BOUND_COMPARATOR = /<\s*=?\s*\d+(?:\.\d+){0,2}(?:-[^\s,|]+)?/g;
|
|
2285
|
-
const HAS_UPPER_BOUND_COMPARATOR = /<\s*=?\s*\d+(?:\.\d+){0,2}(?:-[^\s,|]+)?/;
|
|
2286
|
-
const OR_SEPARATOR = /\s*\|\|\s*/;
|
|
2287
2284
|
const UNRESOLVABLE_PROTOCOL_VERSION = /^(?:file|git|github|https?|link|patch|portal|workspace|npm):/i;
|
|
2288
2285
|
const DIST_TAG_VERSION = /^[a-z][a-z0-9._-]*$/i;
|
|
2289
2286
|
const WILDCARD_VERSION = /^[*xX](?:\.[*xX])*$/;
|
|
2290
|
-
const NON_LOWER_BOUND_COMPARATOR = /(?:^|[\s,|])(?:>(?!=)|!={0,2})\s*\d/;
|
|
2291
|
-
const LOWER_BOUND_MAJOR = /(?:^|[\s,|])(?:>=\s*|[~^=v]\s*)?(\d+)(?=$|[\s,|.*xX-])/g;
|
|
2292
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
|
+
};
|
|
2293
2369
|
const normalizeDependencyVersion = (version) => {
|
|
2294
2370
|
const trimmed = version.trim();
|
|
2295
2371
|
if (trimmed.length === 0) return null;
|
|
@@ -2299,17 +2375,29 @@ const normalizeDependencyVersion = (version) => {
|
|
|
2299
2375
|
if (WILDCARD_VERSION.test(normalizedVersion)) return null;
|
|
2300
2376
|
return normalizedVersion;
|
|
2301
2377
|
};
|
|
2302
|
-
const splitDependencyVersionBranches = (version) => version.split(
|
|
2303
|
-
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
|
+
};
|
|
2304
2383
|
const getBranchLowestMajor = (branch) => {
|
|
2305
|
-
if (
|
|
2306
|
-
const lowerBoundComparators = branch
|
|
2384
|
+
if (hasNonLowerBoundComparator(branch)) return null;
|
|
2385
|
+
const lowerBoundComparators = stripUpperBoundComparators(branch).trim();
|
|
2307
2386
|
if (lowerBoundComparators.length === 0) return null;
|
|
2308
2387
|
let branchLowestMajor = null;
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
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;
|
|
2313
2401
|
}
|
|
2314
2402
|
return branchLowestMajor;
|
|
2315
2403
|
};
|
|
@@ -2478,6 +2566,7 @@ const resolveCatalogVersion = (packageJson, packageName, rootDirectory, explicit
|
|
|
2478
2566
|
const EMPTY_DEPENDENCY_INFO = {
|
|
2479
2567
|
reactVersion: null,
|
|
2480
2568
|
tailwindVersion: null,
|
|
2569
|
+
zodVersion: null,
|
|
2481
2570
|
framework: "unknown"
|
|
2482
2571
|
};
|
|
2483
2572
|
const pickConcreteVersion = (packageJson, packageName, sections) => {
|
|
@@ -2506,6 +2595,11 @@ const extractDependencyInfo = (packageJson) => {
|
|
|
2506
2595
|
"devDependencies",
|
|
2507
2596
|
"peerDependencies"
|
|
2508
2597
|
]),
|
|
2598
|
+
zodVersion: pickConcreteVersion(packageJson, "zod", [
|
|
2599
|
+
"dependencies",
|
|
2600
|
+
"devDependencies",
|
|
2601
|
+
"peerDependencies"
|
|
2602
|
+
]),
|
|
2509
2603
|
framework: detectFramework(allDependencies)
|
|
2510
2604
|
};
|
|
2511
2605
|
};
|
|
@@ -2650,8 +2744,22 @@ const findReactInWorkspaces = (rootDirectory, packageJson) => {
|
|
|
2650
2744
|
workspaceDirectory,
|
|
2651
2745
|
workspacePackageJson
|
|
2652
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
|
+
});
|
|
2653
2760
|
if (reactVersion && shouldReplaceReactVersion(result.reactVersion, reactVersion)) result.reactVersion = reactVersion;
|
|
2654
2761
|
if (tailwindVersion && !result.tailwindVersion) result.tailwindVersion = tailwindVersion;
|
|
2762
|
+
if (zodVersion && !result.zodVersion) result.zodVersion = zodVersion;
|
|
2655
2763
|
if (info.framework !== "unknown" && result.framework === "unknown") result.framework = info.framework;
|
|
2656
2764
|
const resultReactMajor = parseReactMajor(result.reactVersion);
|
|
2657
2765
|
if (result.reactVersion && result.tailwindVersion && result.framework !== "unknown" && resultReactMajor !== null && resultReactMajor <= 17) return result;
|
|
@@ -2686,14 +2794,26 @@ const findDependencyInfoFromMonorepoRoot = (directory) => {
|
|
|
2686
2794
|
"peerDependencies"
|
|
2687
2795
|
]
|
|
2688
2796
|
}) : null;
|
|
2797
|
+
const leafZodDeclaration = leafPackageJson ? getDependencyDeclaration({
|
|
2798
|
+
packageJson: leafPackageJson,
|
|
2799
|
+
packageName: "zod",
|
|
2800
|
+
sections: [
|
|
2801
|
+
"dependencies",
|
|
2802
|
+
"devDependencies",
|
|
2803
|
+
"peerDependencies"
|
|
2804
|
+
]
|
|
2805
|
+
}) : null;
|
|
2689
2806
|
const shouldUseReactFallback = !leafReactDeclaration?.hasDeclaration;
|
|
2690
2807
|
const shouldUseTailwindFallback = leafTailwindDeclaration?.hasDeclaration ?? true;
|
|
2808
|
+
const shouldUseZodFallback = leafZodDeclaration?.hasDeclaration ?? true;
|
|
2691
2809
|
const reactCatalogVersion = shouldUseReactFallback ? resolveCatalogVersion(rootPackageJson, "react", monorepoRoot, leafReactDeclaration?.catalogReference) : null;
|
|
2692
2810
|
const tailwindCatalogVersion = shouldUseTailwindFallback ? resolveCatalogVersion(rootPackageJson, "tailwindcss", monorepoRoot, leafTailwindDeclaration?.catalogReference) : null;
|
|
2811
|
+
const zodCatalogVersion = shouldUseZodFallback ? resolveCatalogVersion(rootPackageJson, "zod", monorepoRoot, leafZodDeclaration?.catalogReference) : null;
|
|
2693
2812
|
const workspaceInfo = findReactInWorkspaces(monorepoRoot, rootPackageJson);
|
|
2694
2813
|
return {
|
|
2695
2814
|
reactVersion: shouldUseReactFallback ? reactCatalogVersion ?? rootInfo.reactVersion ?? workspaceInfo.reactVersion : rootInfo.reactVersion ?? workspaceInfo.reactVersion,
|
|
2696
2815
|
tailwindVersion: shouldUseTailwindFallback ? tailwindCatalogVersion ?? rootInfo.tailwindVersion ?? workspaceInfo.tailwindVersion : null,
|
|
2816
|
+
zodVersion: shouldUseZodFallback ? zodCatalogVersion ?? rootInfo.zodVersion ?? workspaceInfo.zodVersion : null,
|
|
2697
2817
|
framework: rootInfo.framework !== "unknown" ? rootInfo.framework : workspaceInfo.framework
|
|
2698
2818
|
};
|
|
2699
2819
|
};
|
|
@@ -2773,6 +2893,10 @@ const isPackageJsonReanimatedAware = (packageJson) => {
|
|
|
2773
2893
|
};
|
|
2774
2894
|
return Object.hasOwn(allDependencies, REANIMATED_DEPENDENCY_NAME);
|
|
2775
2895
|
};
|
|
2896
|
+
const parseZodMajor = (zodVersion) => {
|
|
2897
|
+
if (typeof zodVersion !== "string") return null;
|
|
2898
|
+
return getLowestDependencyMajor(zodVersion);
|
|
2899
|
+
};
|
|
2776
2900
|
const hasUpperBoundOnlyPeerRange = (range) => {
|
|
2777
2901
|
if (typeof range !== "string") return false;
|
|
2778
2902
|
const normalizedRange = normalizeDependencyVersion(range);
|
|
@@ -2911,7 +3035,7 @@ const discoverProject = (directory) => {
|
|
|
2911
3035
|
const packageJsonPath = path.join(directory, "package.json");
|
|
2912
3036
|
if (!isFile(packageJsonPath)) throw new PackageJsonNotFoundError(directory);
|
|
2913
3037
|
const packageJson = readPackageJson(packageJsonPath);
|
|
2914
|
-
let { reactVersion, tailwindVersion, framework } = extractDependencyInfo(packageJson);
|
|
3038
|
+
let { reactVersion, tailwindVersion, zodVersion, framework } = extractDependencyInfo(packageJson);
|
|
2915
3039
|
const reactDeclaration = getDependencyDeclaration({
|
|
2916
3040
|
packageJson,
|
|
2917
3041
|
packageName: "react",
|
|
@@ -2930,9 +3054,19 @@ const discoverProject = (directory) => {
|
|
|
2930
3054
|
"peerDependencies"
|
|
2931
3055
|
]
|
|
2932
3056
|
});
|
|
3057
|
+
const zodDeclaration = getDependencyDeclaration({
|
|
3058
|
+
packageJson,
|
|
3059
|
+
packageName: "zod",
|
|
3060
|
+
sections: [
|
|
3061
|
+
"dependencies",
|
|
3062
|
+
"devDependencies",
|
|
3063
|
+
"peerDependencies"
|
|
3064
|
+
]
|
|
3065
|
+
});
|
|
2933
3066
|
if (!reactVersion && reactDeclaration.hasDeclaration) reactVersion = resolveCatalogVersion(packageJson, "react", directory, reactDeclaration.catalogReference);
|
|
2934
3067
|
if (!tailwindVersion && tailwindDeclaration.hasDeclaration) tailwindVersion = resolveCatalogVersion(packageJson, "tailwindcss", directory, tailwindDeclaration.catalogReference);
|
|
2935
|
-
if (!
|
|
3068
|
+
if (!zodVersion && zodDeclaration.hasDeclaration) zodVersion = resolveCatalogVersion(packageJson, "zod", directory, zodDeclaration.catalogReference);
|
|
3069
|
+
if (!reactVersion || !tailwindVersion || !zodVersion) {
|
|
2936
3070
|
const monorepoRoot = findMonorepoRoot(directory);
|
|
2937
3071
|
if (monorepoRoot) {
|
|
2938
3072
|
const monorepoPackageJsonPath = path.join(monorepoRoot, "package.json");
|
|
@@ -2940,6 +3074,7 @@ const discoverProject = (directory) => {
|
|
|
2940
3074
|
const rootPackageJson = readPackageJson(monorepoPackageJsonPath);
|
|
2941
3075
|
if (!reactVersion && reactDeclaration.hasDeclaration) reactVersion = resolveCatalogVersion(rootPackageJson, "react", monorepoRoot, reactDeclaration.catalogReference);
|
|
2942
3076
|
if (!tailwindVersion && tailwindDeclaration.hasDeclaration) tailwindVersion = resolveCatalogVersion(rootPackageJson, "tailwindcss", monorepoRoot, tailwindDeclaration.catalogReference);
|
|
3077
|
+
if (!zodVersion && zodDeclaration.hasDeclaration) zodVersion = resolveCatalogVersion(rootPackageJson, "zod", monorepoRoot, zodDeclaration.catalogReference);
|
|
2943
3078
|
}
|
|
2944
3079
|
}
|
|
2945
3080
|
}
|
|
@@ -2947,16 +3082,19 @@ const discoverProject = (directory) => {
|
|
|
2947
3082
|
const workspaceInfo = findReactInWorkspaces(directory, packageJson);
|
|
2948
3083
|
if (!reactVersion && workspaceInfo.reactVersion) reactVersion = workspaceInfo.reactVersion;
|
|
2949
3084
|
if (!tailwindVersion && workspaceInfo.tailwindVersion) tailwindVersion = workspaceInfo.tailwindVersion;
|
|
3085
|
+
if (!zodVersion && workspaceInfo.zodVersion) zodVersion = workspaceInfo.zodVersion;
|
|
2950
3086
|
if (framework === "unknown" && workspaceInfo.framework !== "unknown") framework = workspaceInfo.framework;
|
|
2951
3087
|
}
|
|
2952
3088
|
if ((!reactVersion || framework === "unknown") && !isMonorepoRoot(directory)) {
|
|
2953
3089
|
const monorepoInfo = findDependencyInfoFromMonorepoRoot(directory);
|
|
2954
3090
|
if (!reactVersion) reactVersion = monorepoInfo.reactVersion;
|
|
2955
3091
|
if (!tailwindVersion) tailwindVersion = monorepoInfo.tailwindVersion;
|
|
3092
|
+
if (!zodVersion) zodVersion = monorepoInfo.zodVersion;
|
|
2956
3093
|
if (framework === "unknown") framework = monorepoInfo.framework;
|
|
2957
3094
|
}
|
|
2958
3095
|
if (!reactVersion && reactDeclaration.version && !isCatalogReference(reactDeclaration.version)) reactVersion = reactDeclaration.version;
|
|
2959
3096
|
if (!tailwindVersion && tailwindDeclaration.version && !isCatalogReference(tailwindDeclaration.version)) tailwindVersion = tailwindDeclaration.version;
|
|
3097
|
+
if (!zodVersion && zodDeclaration.version && !isCatalogReference(zodDeclaration.version)) zodVersion = zodDeclaration.version;
|
|
2960
3098
|
const projectName = packageJson.name ?? path.basename(directory);
|
|
2961
3099
|
const hasTypeScript = fs.existsSync(path.join(directory, "tsconfig.json"));
|
|
2962
3100
|
const sourceFileCount = countSourceFiles(directory);
|
|
@@ -2969,6 +3107,8 @@ const discoverProject = (directory) => {
|
|
|
2969
3107
|
reactVersion,
|
|
2970
3108
|
reactMajorVersion: resolveEffectiveReactMajor(reactVersion, packageJson),
|
|
2971
3109
|
tailwindVersion,
|
|
3110
|
+
zodVersion,
|
|
3111
|
+
zodMajorVersion: parseZodMajor(zodVersion),
|
|
2972
3112
|
framework,
|
|
2973
3113
|
hasTypeScript,
|
|
2974
3114
|
hasReactCompiler: detectReactCompiler(directory, packageJson),
|
|
@@ -3050,6 +3190,7 @@ const JSX_FILE_PATTERN = /\.(tsx|jsx)$/;
|
|
|
3050
3190
|
const MILLISECONDS_PER_SECOND = 1e3;
|
|
3051
3191
|
const SCORE_API_URL = "https://www.react.doctor/api/score";
|
|
3052
3192
|
const SHARE_BASE_URL = "https://www.react.doctor/share";
|
|
3193
|
+
const PROMPTS_RULES_BASE_URL = "https://www.react.doctor/prompts/rules";
|
|
3053
3194
|
const FETCH_TIMEOUT_MS = 1e4;
|
|
3054
3195
|
const GITHUB_VIEWER_PERMISSION_TIMEOUT_MS = 2e3;
|
|
3055
3196
|
const DEFAULT_BRANCH_CANDIDATES = ["main", "master"];
|
|
@@ -5491,6 +5632,10 @@ const buildCapabilities = (project) => {
|
|
|
5491
5632
|
minor: 4
|
|
5492
5633
|
})) capabilities.add("tailwind:3.4");
|
|
5493
5634
|
}
|
|
5635
|
+
if (project.zodVersion !== null) {
|
|
5636
|
+
capabilities.add("zod");
|
|
5637
|
+
if (project.zodMajorVersion !== null && project.zodMajorVersion >= 4) capabilities.add("zod:4");
|
|
5638
|
+
}
|
|
5494
5639
|
if (project.hasReactCompiler) capabilities.add("react-compiler");
|
|
5495
5640
|
if (project.hasTanStackQuery) capabilities.add("tanstack-query");
|
|
5496
5641
|
if (project.hasTypeScript) capabilities.add("typescript");
|
|
@@ -7360,6 +7505,13 @@ const highlighter = {
|
|
|
7360
7505
|
gray: import_picocolors.default.gray,
|
|
7361
7506
|
bold: import_picocolors.default.bold
|
|
7362
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`;
|
|
7363
7515
|
const groupBy = (items, keyFn) => {
|
|
7364
7516
|
const groups = /* @__PURE__ */ new Map();
|
|
7365
7517
|
for (const item of items) {
|
|
@@ -7407,6 +7559,6 @@ const cliLogger = {
|
|
|
7407
7559
|
}
|
|
7408
7560
|
};
|
|
7409
7561
|
//#endregion
|
|
7410
|
-
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 };
|
|
7411
7563
|
|
|
7412
|
-
//# sourceMappingURL=cli-logger-
|
|
7564
|
+
//# sourceMappingURL=cli-logger-CSZagq1E.js.map
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { i as __toESM, n as __exportAll, r as __require, t as __commonJSMin } from "./rolldown-runtime-uZX_iqCz.js";
|
|
2
|
-
import { A as
|
|
2
|
+
import { A as isMonorepoRoot, C as filterDiagnosticsForSurface, D as getDiffInfo, E as formatReactDoctorError, F as restoreLegacyThrow, I as runInspect, L as toRelativePath, M as layerOtlp, N as listWorkspacePackages, O as groupBy, P as resolveScanTarget, S as discoverReactSubprojects, T as formatErrorChain, _ as Score, a as DeadCode, b as buildJsonReportError, c as LintPartialFailures, d as OXLINT_NODE_REQUIREMENT, f as Progress, g as SKILL_NAME, h as SHARE_BASE_URL, i as Config, j as isReactDoctorError, k as highlighter, l as Linter, m as Reporter, o as Files, p as Project, r as CANONICAL_GITHUB_URL, s as Git, t as cliLogger, u as NodeResolver, v as StagedFiles, w as filterSourceFiles, x as buildRulePromptUrl, y as buildJsonReport } from "./cli-logger-CSZagq1E.js";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { execFileSync, execSync } from "node:child_process";
|
|
5
5
|
import path, { join } from "node:path";
|
|
@@ -6220,6 +6220,8 @@ const padRuleNameToColumn = (ruleName, columnWidth) => {
|
|
|
6220
6220
|
return ruleName + " ".repeat(columnWidth - ruleName.length);
|
|
6221
6221
|
};
|
|
6222
6222
|
const grayLine = (text) => highlighter.gray(text);
|
|
6223
|
+
const FETCH_FIX_RECIPE_LABEL = "Fetch & follow the canonical fix recipe before fixing";
|
|
6224
|
+
const formatFixRecipeLine = (diagnostic) => `${FETCH_FIX_RECIPE_LABEL}: ${buildRulePromptUrl(diagnostic.plugin, diagnostic.rule)}`;
|
|
6223
6225
|
const buildCompactRuleGroupLine = (ruleKey, ruleDiagnostics, ruleNameColumnWidth) => {
|
|
6224
6226
|
const firstDiagnostic = ruleDiagnostics[0];
|
|
6225
6227
|
const icon = colorizeBySeverity(firstDiagnostic.severity === "error" ? "✗" : "⚠", firstDiagnostic.severity);
|
|
@@ -6255,6 +6257,7 @@ const buildVerboseRuleGroupLines = (ruleKey, ruleDiagnostics, ruleNameColumnWidt
|
|
|
6255
6257
|
const firstDiagnostic = ruleDiagnostics[0];
|
|
6256
6258
|
lines.push(grayLine(indentMultilineText(firstDiagnostic.message, " ")));
|
|
6257
6259
|
if (firstDiagnostic.help) lines.push(grayLine(indentMultilineText(`→ ${firstDiagnostic.help}`, " ")));
|
|
6260
|
+
lines.push(grayLine(` ${formatFixRecipeLine(firstDiagnostic)}`));
|
|
6258
6261
|
const fileSites = buildVerboseSiteMap(ruleDiagnostics);
|
|
6259
6262
|
for (const [filePath, sites] of fileSites) if (sites.length > 0) for (const site of sites) {
|
|
6260
6263
|
lines.push(grayLine(` ${filePath}:${site.line}`));
|
|
@@ -6299,6 +6302,7 @@ const formatRuleSummary = (ruleKey, ruleDiagnostics) => {
|
|
|
6299
6302
|
];
|
|
6300
6303
|
if (firstDiagnostic.help) sections.push("", `Suggestion: ${firstDiagnostic.help}`);
|
|
6301
6304
|
if (firstDiagnostic.url) sections.push("", `Docs: ${firstDiagnostic.url}`);
|
|
6305
|
+
sections.push("", formatFixRecipeLine(firstDiagnostic));
|
|
6302
6306
|
sections.push("", "Files:");
|
|
6303
6307
|
const fileSites = buildVerboseSiteMap(ruleDiagnostics);
|
|
6304
6308
|
for (const [filePath, sites] of fileSites) if (sites.length > 0) for (const site of sites) {
|
|
@@ -6539,7 +6543,11 @@ const printCountsSummaryLine = (diagnostics, isVerbose) => Effect.gen(function*
|
|
|
6539
6543
|
const warningCount = diagnostics.filter((diagnostic) => diagnostic.severity === "warning").length;
|
|
6540
6544
|
const issueText = (errorCount > 0 ? highlighter.error : warningCount > 0 ? highlighter.warn : highlighter.dim)(`${totalIssueCount} ${totalIssueCount === 1 ? "issue" : "issues"}`);
|
|
6541
6545
|
yield* Console.log(` ${issueText}`);
|
|
6542
|
-
if (!isVerbose && totalIssueCount > 0)
|
|
6546
|
+
if (!isVerbose && totalIssueCount > 0) {
|
|
6547
|
+
const exampleDiagnostic = diagnostics.find((diagnostic) => diagnostic.severity === "error") ?? diagnostics[0];
|
|
6548
|
+
yield* Console.log(highlighter.dim(` Run ${highlighter.info("npx react-doctor@latest --verbose")} to list every issue with its fix-recipe URL`));
|
|
6549
|
+
yield* Console.log(highlighter.dim(` Each rule links a canonical fix recipe to fetch & follow before fixing, e.g. ${highlighter.info(buildRulePromptUrl(exampleDiagnostic.plugin, exampleDiagnostic.rule))}`));
|
|
6550
|
+
}
|
|
6543
6551
|
});
|
|
6544
6552
|
const printSummary = (input) => Effect.gen(function* () {
|
|
6545
6553
|
if (input.scoreResult) yield* printScoreHeader(input.scoreResult, input.projectName);
|
|
@@ -6666,7 +6674,7 @@ const resolveOxlintNodeEffect = (isLintEnabled, isQuiet) => Effect.gen(function*
|
|
|
6666
6674
|
const resolveOxlintNode = (isLintEnabled, isQuiet) => Effect.runPromise(resolveOxlintNodeEffect(isLintEnabled, isQuiet).pipe(Effect.provide(NodeResolver.layerNode)));
|
|
6667
6675
|
//#endregion
|
|
6668
6676
|
//#region src/cli/utils/version.ts
|
|
6669
|
-
const VERSION = "0.2.
|
|
6677
|
+
const VERSION = "0.2.12-dev.269ca17";
|
|
6670
6678
|
//#endregion
|
|
6671
6679
|
//#region src/inspect.ts
|
|
6672
6680
|
const silentConsole = makeNoopConsole();
|
|
@@ -7066,15 +7074,16 @@ const buildIssuesSummary = (input) => {
|
|
|
7066
7074
|
if (input.score) lines.push(`Score: ${input.score.score}/100`);
|
|
7067
7075
|
lines.push(`${input.diagnostics.length} issues found`);
|
|
7068
7076
|
lines.push("");
|
|
7069
|
-
const sortedRules = [...groupBy([...input.diagnostics], (diagnostic) => diagnostic.rule).entries()].sort(([, diagnosticsA], [, diagnosticsB]) => diagnosticsB.length - diagnosticsA.length);
|
|
7077
|
+
const sortedRules = [...groupBy([...input.diagnostics], (diagnostic) => `${diagnostic.plugin}/${diagnostic.rule}`).entries()].sort(([, diagnosticsA], [, diagnosticsB]) => diagnosticsB.length - diagnosticsA.length);
|
|
7070
7078
|
const visibleRules = sortedRules.slice(0, MAX_RULES_SHOWN);
|
|
7071
|
-
for (const [
|
|
7079
|
+
for (const [ruleKey, ruleDiagnostics] of visibleRules) {
|
|
7072
7080
|
const severity = ruleDiagnostics[0].severity;
|
|
7073
7081
|
const uniqueFiles = [...new Set(ruleDiagnostics.map((diagnostic) => diagnostic.filePath))];
|
|
7074
7082
|
const shownFiles = uniqueFiles.slice(0, MAX_FILES_PER_RULE);
|
|
7075
7083
|
const remainingFileCount = uniqueFiles.length - shownFiles.length;
|
|
7076
|
-
lines.push(`${severity === "error" ? "ERROR" : "WARN"} ${
|
|
7084
|
+
lines.push(`${severity === "error" ? "ERROR" : "WARN"} ${ruleKey} (×${ruleDiagnostics.length})`);
|
|
7077
7085
|
lines.push(` ${ruleDiagnostics[0].message}`);
|
|
7086
|
+
lines.push(` ${formatFixRecipeLine(ruleDiagnostics[0])}`);
|
|
7078
7087
|
for (const filePath of shownFiles) {
|
|
7079
7088
|
const firstSite = ruleDiagnostics.find((diagnostic) => diagnostic.filePath === filePath && diagnostic.line > 0);
|
|
7080
7089
|
lines.push(` - ${filePath}${firstSite ? `:${firstSite.line}` : ""}`);
|
|
@@ -7094,11 +7103,12 @@ const buildIssuesSummary = (input) => {
|
|
|
7094
7103
|
lines.push("");
|
|
7095
7104
|
lines.push("## How to fix");
|
|
7096
7105
|
lines.push("1. Run `npx react-doctor@latest --verbose` to see full details");
|
|
7097
|
-
lines.push("2.
|
|
7098
|
-
lines.push("3.
|
|
7099
|
-
lines.push("4.
|
|
7100
|
-
lines.push("5.
|
|
7101
|
-
lines.push("6.
|
|
7106
|
+
lines.push("2. For each rule above, fetch & follow its canonical fix recipe URL before fixing.");
|
|
7107
|
+
lines.push("3. Fix errors first, then warnings. Start with high-count rules.");
|
|
7108
|
+
lines.push("4. Read the code before acting. Treat findings as hypotheses, not commands.");
|
|
7109
|
+
lines.push("5. Fix root causes, not symptoms. Don't suppress rules without evidence.");
|
|
7110
|
+
lines.push("6. Run `npx react-doctor@latest --verbose --diff` after changes to verify.");
|
|
7111
|
+
lines.push("7. Split unrelated fixes into separate PRs.");
|
|
7102
7112
|
return lines.join("\n");
|
|
7103
7113
|
};
|
|
7104
7114
|
const copyToClipboard = (text) => {
|
|
@@ -7238,6 +7248,7 @@ const printMultiProjectSummary = (input) => Effect.gen(function* () {
|
|
|
7238
7248
|
};
|
|
7239
7249
|
});
|
|
7240
7250
|
const longestProjectNameLength = Math.max(...entries.map((entry) => entry.projectName.length));
|
|
7251
|
+
yield* Console.log("");
|
|
7241
7252
|
for (const entry of entries) yield* Console.log(buildSummaryLine(entry, longestProjectNameLength));
|
|
7242
7253
|
yield* Console.log("");
|
|
7243
7254
|
});
|
|
@@ -7484,7 +7495,7 @@ const warnSetupPromptFailure = async (options, error) => {
|
|
|
7484
7495
|
return;
|
|
7485
7496
|
}
|
|
7486
7497
|
try {
|
|
7487
|
-
const { cliLogger } = await import("./cli-logger-
|
|
7498
|
+
const { cliLogger } = await import("./cli-logger-CSZagq1E.js").then((n) => n.n);
|
|
7488
7499
|
cliLogger.warn(message);
|
|
7489
7500
|
} catch {}
|
|
7490
7501
|
};
|
|
@@ -8883,7 +8894,7 @@ const buildWorkflowContent = () => [
|
|
|
8883
8894
|
" runs-on: ubuntu-latest",
|
|
8884
8895
|
" steps:",
|
|
8885
8896
|
" - uses: actions/checkout@v5",
|
|
8886
|
-
" - uses: millionco/react-doctor@
|
|
8897
|
+
" - uses: millionco/react-doctor@main",
|
|
8887
8898
|
""
|
|
8888
8899
|
].join("\n");
|
|
8889
8900
|
const runInstallReactDoctor = async (options = {}) => {
|
package/dist/index.d.ts
CHANGED
|
@@ -305,6 +305,9 @@ interface ProjectInfo {
|
|
|
305
305
|
reactVersion: string | null;
|
|
306
306
|
reactMajorVersion: number | null;
|
|
307
307
|
tailwindVersion: string | null;
|
|
308
|
+
zodVersion: string | null;
|
|
309
|
+
/** Parsed major from `zodVersion`, or `null` when absent/unparseable. Mirrors `reactMajorVersion`. */
|
|
310
|
+
zodMajorVersion: number | null;
|
|
308
311
|
framework: Framework;
|
|
309
312
|
hasTypeScript: boolean;
|
|
310
313
|
hasReactCompiler: boolean;
|
package/dist/index.js
CHANGED
|
@@ -2307,15 +2307,91 @@ const detectFramework = (dependencies) => {
|
|
|
2307
2307
|
if (dependencies.preact && !dependencies.react) return "preact";
|
|
2308
2308
|
return "unknown";
|
|
2309
2309
|
};
|
|
2310
|
-
const UPPER_BOUND_COMPARATOR = /<\s*=?\s*\d+(?:\.\d+){0,2}(?:-[^\s,|]+)?/g;
|
|
2311
|
-
const HAS_UPPER_BOUND_COMPARATOR = /<\s*=?\s*\d+(?:\.\d+){0,2}(?:-[^\s,|]+)?/;
|
|
2312
|
-
const OR_SEPARATOR = /\s*\|\|\s*/;
|
|
2313
2310
|
const UNRESOLVABLE_PROTOCOL_VERSION = /^(?:file|git|github|https?|link|patch|portal|workspace|npm):/i;
|
|
2314
2311
|
const DIST_TAG_VERSION = /^[a-z][a-z0-9._-]*$/i;
|
|
2315
2312
|
const WILDCARD_VERSION = /^[*xX](?:\.[*xX])*$/;
|
|
2316
|
-
const NON_LOWER_BOUND_COMPARATOR = /(?:^|[\s,|])(?:>(?!=)|!={0,2})\s*\d/;
|
|
2317
|
-
const LOWER_BOUND_MAJOR = /(?:^|[\s,|])(?:>=\s*|[~^=v]\s*)?(\d+)(?=$|[\s,|.*xX-])/g;
|
|
2318
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
|
+
};
|
|
2319
2395
|
const normalizeDependencyVersion = (version) => {
|
|
2320
2396
|
const trimmed = version.trim();
|
|
2321
2397
|
if (trimmed.length === 0) return null;
|
|
@@ -2325,17 +2401,29 @@ const normalizeDependencyVersion = (version) => {
|
|
|
2325
2401
|
if (WILDCARD_VERSION.test(normalizedVersion)) return null;
|
|
2326
2402
|
return normalizedVersion;
|
|
2327
2403
|
};
|
|
2328
|
-
const splitDependencyVersionBranches = (version) => version.split(
|
|
2329
|
-
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
|
+
};
|
|
2330
2409
|
const getBranchLowestMajor = (branch) => {
|
|
2331
|
-
if (
|
|
2332
|
-
const lowerBoundComparators = branch
|
|
2410
|
+
if (hasNonLowerBoundComparator(branch)) return null;
|
|
2411
|
+
const lowerBoundComparators = stripUpperBoundComparators(branch).trim();
|
|
2333
2412
|
if (lowerBoundComparators.length === 0) return null;
|
|
2334
2413
|
let branchLowestMajor = null;
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
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;
|
|
2339
2427
|
}
|
|
2340
2428
|
return branchLowestMajor;
|
|
2341
2429
|
};
|
|
@@ -2504,6 +2592,7 @@ const resolveCatalogVersion = (packageJson, packageName, rootDirectory, explicit
|
|
|
2504
2592
|
const EMPTY_DEPENDENCY_INFO = {
|
|
2505
2593
|
reactVersion: null,
|
|
2506
2594
|
tailwindVersion: null,
|
|
2595
|
+
zodVersion: null,
|
|
2507
2596
|
framework: "unknown"
|
|
2508
2597
|
};
|
|
2509
2598
|
const pickConcreteVersion = (packageJson, packageName, sections) => {
|
|
@@ -2532,6 +2621,11 @@ const extractDependencyInfo = (packageJson) => {
|
|
|
2532
2621
|
"devDependencies",
|
|
2533
2622
|
"peerDependencies"
|
|
2534
2623
|
]),
|
|
2624
|
+
zodVersion: pickConcreteVersion(packageJson, "zod", [
|
|
2625
|
+
"dependencies",
|
|
2626
|
+
"devDependencies",
|
|
2627
|
+
"peerDependencies"
|
|
2628
|
+
]),
|
|
2535
2629
|
framework: detectFramework(allDependencies)
|
|
2536
2630
|
};
|
|
2537
2631
|
};
|
|
@@ -2676,8 +2770,22 @@ const findReactInWorkspaces = (rootDirectory, packageJson) => {
|
|
|
2676
2770
|
workspaceDirectory,
|
|
2677
2771
|
workspacePackageJson
|
|
2678
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
|
+
});
|
|
2679
2786
|
if (reactVersion && shouldReplaceReactVersion(result.reactVersion, reactVersion)) result.reactVersion = reactVersion;
|
|
2680
2787
|
if (tailwindVersion && !result.tailwindVersion) result.tailwindVersion = tailwindVersion;
|
|
2788
|
+
if (zodVersion && !result.zodVersion) result.zodVersion = zodVersion;
|
|
2681
2789
|
if (info.framework !== "unknown" && result.framework === "unknown") result.framework = info.framework;
|
|
2682
2790
|
const resultReactMajor = parseReactMajor(result.reactVersion);
|
|
2683
2791
|
if (result.reactVersion && result.tailwindVersion && result.framework !== "unknown" && resultReactMajor !== null && resultReactMajor <= 17) return result;
|
|
@@ -2712,14 +2820,26 @@ const findDependencyInfoFromMonorepoRoot = (directory) => {
|
|
|
2712
2820
|
"peerDependencies"
|
|
2713
2821
|
]
|
|
2714
2822
|
}) : null;
|
|
2823
|
+
const leafZodDeclaration = leafPackageJson ? getDependencyDeclaration({
|
|
2824
|
+
packageJson: leafPackageJson,
|
|
2825
|
+
packageName: "zod",
|
|
2826
|
+
sections: [
|
|
2827
|
+
"dependencies",
|
|
2828
|
+
"devDependencies",
|
|
2829
|
+
"peerDependencies"
|
|
2830
|
+
]
|
|
2831
|
+
}) : null;
|
|
2715
2832
|
const shouldUseReactFallback = !leafReactDeclaration?.hasDeclaration;
|
|
2716
2833
|
const shouldUseTailwindFallback = leafTailwindDeclaration?.hasDeclaration ?? true;
|
|
2834
|
+
const shouldUseZodFallback = leafZodDeclaration?.hasDeclaration ?? true;
|
|
2717
2835
|
const reactCatalogVersion = shouldUseReactFallback ? resolveCatalogVersion(rootPackageJson, "react", monorepoRoot, leafReactDeclaration?.catalogReference) : null;
|
|
2718
2836
|
const tailwindCatalogVersion = shouldUseTailwindFallback ? resolveCatalogVersion(rootPackageJson, "tailwindcss", monorepoRoot, leafTailwindDeclaration?.catalogReference) : null;
|
|
2837
|
+
const zodCatalogVersion = shouldUseZodFallback ? resolveCatalogVersion(rootPackageJson, "zod", monorepoRoot, leafZodDeclaration?.catalogReference) : null;
|
|
2719
2838
|
const workspaceInfo = findReactInWorkspaces(monorepoRoot, rootPackageJson);
|
|
2720
2839
|
return {
|
|
2721
2840
|
reactVersion: shouldUseReactFallback ? reactCatalogVersion ?? rootInfo.reactVersion ?? workspaceInfo.reactVersion : rootInfo.reactVersion ?? workspaceInfo.reactVersion,
|
|
2722
2841
|
tailwindVersion: shouldUseTailwindFallback ? tailwindCatalogVersion ?? rootInfo.tailwindVersion ?? workspaceInfo.tailwindVersion : null,
|
|
2842
|
+
zodVersion: shouldUseZodFallback ? zodCatalogVersion ?? rootInfo.zodVersion ?? workspaceInfo.zodVersion : null,
|
|
2723
2843
|
framework: rootInfo.framework !== "unknown" ? rootInfo.framework : workspaceInfo.framework
|
|
2724
2844
|
};
|
|
2725
2845
|
};
|
|
@@ -2799,6 +2919,10 @@ const isPackageJsonReanimatedAware = (packageJson) => {
|
|
|
2799
2919
|
};
|
|
2800
2920
|
return Object.hasOwn(allDependencies, REANIMATED_DEPENDENCY_NAME);
|
|
2801
2921
|
};
|
|
2922
|
+
const parseZodMajor = (zodVersion) => {
|
|
2923
|
+
if (typeof zodVersion !== "string") return null;
|
|
2924
|
+
return getLowestDependencyMajor(zodVersion);
|
|
2925
|
+
};
|
|
2802
2926
|
const hasUpperBoundOnlyPeerRange = (range) => {
|
|
2803
2927
|
if (typeof range !== "string") return false;
|
|
2804
2928
|
const normalizedRange = normalizeDependencyVersion(range);
|
|
@@ -2940,7 +3064,7 @@ const discoverProject = (directory) => {
|
|
|
2940
3064
|
const packageJsonPath = path.join(directory, "package.json");
|
|
2941
3065
|
if (!isFile(packageJsonPath)) throw new PackageJsonNotFoundError(directory);
|
|
2942
3066
|
const packageJson = readPackageJson(packageJsonPath);
|
|
2943
|
-
let { reactVersion, tailwindVersion, framework } = extractDependencyInfo(packageJson);
|
|
3067
|
+
let { reactVersion, tailwindVersion, zodVersion, framework } = extractDependencyInfo(packageJson);
|
|
2944
3068
|
const reactDeclaration = getDependencyDeclaration({
|
|
2945
3069
|
packageJson,
|
|
2946
3070
|
packageName: "react",
|
|
@@ -2959,9 +3083,19 @@ const discoverProject = (directory) => {
|
|
|
2959
3083
|
"peerDependencies"
|
|
2960
3084
|
]
|
|
2961
3085
|
});
|
|
3086
|
+
const zodDeclaration = getDependencyDeclaration({
|
|
3087
|
+
packageJson,
|
|
3088
|
+
packageName: "zod",
|
|
3089
|
+
sections: [
|
|
3090
|
+
"dependencies",
|
|
3091
|
+
"devDependencies",
|
|
3092
|
+
"peerDependencies"
|
|
3093
|
+
]
|
|
3094
|
+
});
|
|
2962
3095
|
if (!reactVersion && reactDeclaration.hasDeclaration) reactVersion = resolveCatalogVersion(packageJson, "react", directory, reactDeclaration.catalogReference);
|
|
2963
3096
|
if (!tailwindVersion && tailwindDeclaration.hasDeclaration) tailwindVersion = resolveCatalogVersion(packageJson, "tailwindcss", directory, tailwindDeclaration.catalogReference);
|
|
2964
|
-
if (!
|
|
3097
|
+
if (!zodVersion && zodDeclaration.hasDeclaration) zodVersion = resolveCatalogVersion(packageJson, "zod", directory, zodDeclaration.catalogReference);
|
|
3098
|
+
if (!reactVersion || !tailwindVersion || !zodVersion) {
|
|
2965
3099
|
const monorepoRoot = findMonorepoRoot(directory);
|
|
2966
3100
|
if (monorepoRoot) {
|
|
2967
3101
|
const monorepoPackageJsonPath = path.join(monorepoRoot, "package.json");
|
|
@@ -2969,6 +3103,7 @@ const discoverProject = (directory) => {
|
|
|
2969
3103
|
const rootPackageJson = readPackageJson(monorepoPackageJsonPath);
|
|
2970
3104
|
if (!reactVersion && reactDeclaration.hasDeclaration) reactVersion = resolveCatalogVersion(rootPackageJson, "react", monorepoRoot, reactDeclaration.catalogReference);
|
|
2971
3105
|
if (!tailwindVersion && tailwindDeclaration.hasDeclaration) tailwindVersion = resolveCatalogVersion(rootPackageJson, "tailwindcss", monorepoRoot, tailwindDeclaration.catalogReference);
|
|
3106
|
+
if (!zodVersion && zodDeclaration.hasDeclaration) zodVersion = resolveCatalogVersion(rootPackageJson, "zod", monorepoRoot, zodDeclaration.catalogReference);
|
|
2972
3107
|
}
|
|
2973
3108
|
}
|
|
2974
3109
|
}
|
|
@@ -2976,16 +3111,19 @@ const discoverProject = (directory) => {
|
|
|
2976
3111
|
const workspaceInfo = findReactInWorkspaces(directory, packageJson);
|
|
2977
3112
|
if (!reactVersion && workspaceInfo.reactVersion) reactVersion = workspaceInfo.reactVersion;
|
|
2978
3113
|
if (!tailwindVersion && workspaceInfo.tailwindVersion) tailwindVersion = workspaceInfo.tailwindVersion;
|
|
3114
|
+
if (!zodVersion && workspaceInfo.zodVersion) zodVersion = workspaceInfo.zodVersion;
|
|
2979
3115
|
if (framework === "unknown" && workspaceInfo.framework !== "unknown") framework = workspaceInfo.framework;
|
|
2980
3116
|
}
|
|
2981
3117
|
if ((!reactVersion || framework === "unknown") && !isMonorepoRoot(directory)) {
|
|
2982
3118
|
const monorepoInfo = findDependencyInfoFromMonorepoRoot(directory);
|
|
2983
3119
|
if (!reactVersion) reactVersion = monorepoInfo.reactVersion;
|
|
2984
3120
|
if (!tailwindVersion) tailwindVersion = monorepoInfo.tailwindVersion;
|
|
3121
|
+
if (!zodVersion) zodVersion = monorepoInfo.zodVersion;
|
|
2985
3122
|
if (framework === "unknown") framework = monorepoInfo.framework;
|
|
2986
3123
|
}
|
|
2987
3124
|
if (!reactVersion && reactDeclaration.version && !isCatalogReference(reactDeclaration.version)) reactVersion = reactDeclaration.version;
|
|
2988
3125
|
if (!tailwindVersion && tailwindDeclaration.version && !isCatalogReference(tailwindDeclaration.version)) tailwindVersion = tailwindDeclaration.version;
|
|
3126
|
+
if (!zodVersion && zodDeclaration.version && !isCatalogReference(zodDeclaration.version)) zodVersion = zodDeclaration.version;
|
|
2989
3127
|
const projectName = packageJson.name ?? path.basename(directory);
|
|
2990
3128
|
const hasTypeScript = fs.existsSync(path.join(directory, "tsconfig.json"));
|
|
2991
3129
|
const sourceFileCount = countSourceFiles(directory);
|
|
@@ -2998,6 +3136,8 @@ const discoverProject = (directory) => {
|
|
|
2998
3136
|
reactVersion,
|
|
2999
3137
|
reactMajorVersion: resolveEffectiveReactMajor(reactVersion, packageJson),
|
|
3000
3138
|
tailwindVersion,
|
|
3139
|
+
zodVersion,
|
|
3140
|
+
zodMajorVersion: parseZodMajor(zodVersion),
|
|
3001
3141
|
framework,
|
|
3002
3142
|
hasTypeScript,
|
|
3003
3143
|
hasReactCompiler: detectReactCompiler(directory, packageJson),
|
|
@@ -5522,6 +5662,10 @@ const buildCapabilities = (project) => {
|
|
|
5522
5662
|
minor: 4
|
|
5523
5663
|
})) capabilities.add("tailwind:3.4");
|
|
5524
5664
|
}
|
|
5665
|
+
if (project.zodVersion !== null) {
|
|
5666
|
+
capabilities.add("zod");
|
|
5667
|
+
if (project.zodMajorVersion !== null && project.zodMajorVersion >= 4) capabilities.add("zod:4");
|
|
5668
|
+
}
|
|
5525
5669
|
if (project.hasReactCompiler) capabilities.add("react-compiler");
|
|
5526
5670
|
if (project.hasTanStackQuery) capabilities.add("tanstack-query");
|
|
5527
5671
|
if (project.hasTypeScript) capabilities.add("typescript");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-doctor",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.12-dev.269ca17",
|
|
4
4
|
"description": "Diagnose and fix React codebases for security, performance, correctness, accessibility, bundle-size, and architecture issues",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"accessibility",
|
|
@@ -58,14 +58,14 @@
|
|
|
58
58
|
"oxlint": "^1.66.0",
|
|
59
59
|
"prompts": "^2.4.2",
|
|
60
60
|
"typescript": ">=5.0.4 <7",
|
|
61
|
-
"oxlint-plugin-react-doctor": "0.2.
|
|
61
|
+
"oxlint-plugin-react-doctor": "0.2.12-dev.269ca17"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
64
|
"@types/prompts": "^2.4.9",
|
|
65
65
|
"commander": "^14.0.3",
|
|
66
66
|
"ora": "^9.4.0",
|
|
67
|
-
"@react-doctor/
|
|
68
|
-
"@react-doctor/
|
|
67
|
+
"@react-doctor/api": "0.2.12",
|
|
68
|
+
"@react-doctor/core": "0.2.12"
|
|
69
69
|
},
|
|
70
70
|
"engines": {
|
|
71
71
|
"node": "^20.19.0 || >=22.12.0"
|