react-doctor 0.7.1 → 0.7.2-dev.0eb5293
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1136 -654
- package/dist/index.d.ts +21 -2
- package/dist/index.js +1129 -645
- package/dist/lsp.js +1087 -632
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
2
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="63c65851-4fa5-5cd1-9337-0f2feefe37ad")}catch(e){}}();
|
|
3
3
|
import { r as __toESM$1, t as __commonJSMin$1 } from "./chunk-N93fKeF6.js";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import * as NFS from "node:fs";
|
|
@@ -9,7 +9,7 @@ import path from "node:path";
|
|
|
9
9
|
import * as NodeChildProcess from "node:child_process";
|
|
10
10
|
import { execFileSync, spawn, spawnSync } from "node:child_process";
|
|
11
11
|
import * as ts from "typescript";
|
|
12
|
-
import reactDoctorPlugin, { ALL_REACT_DOCTOR_RULE_KEYS, CROSS_FILE_DEPENDENCY_COLLECTORS, CROSS_FILE_RULE_IDS, FRAMEWORK_SPECIFIC_RULE_KEYS, MOTION_LIBRARY_PACKAGES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, classifySecurityScanFile, collectCrossFileDependencyProbes, shouldReadSecurityScanContent } from "oxlint-plugin-react-doctor";
|
|
12
|
+
import reactDoctorPlugin, { ALL_REACT_DOCTOR_RULE_KEYS, CROSS_FILE_DEPENDENCY_COLLECTORS, CROSS_FILE_RULE_IDS, FRAMEWORK_SPECIFIC_RULE_KEYS, MOTION_LIBRARY_PACKAGES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, classifySecurityScanFile, collectCrossFileDependencyProbes, resetManifestCaches, shouldReadSecurityScanContent } from "oxlint-plugin-react-doctor";
|
|
13
13
|
import * as OS from "node:os";
|
|
14
14
|
import os from "node:os";
|
|
15
15
|
import { parseJSON5 } from "confbox";
|
|
@@ -32791,6 +32791,25 @@ var AmbiguousProjectError = class extends Error {
|
|
|
32791
32791
|
}
|
|
32792
32792
|
};
|
|
32793
32793
|
const isProjectDiscoveryError = (value) => value instanceof ProjectNotFoundError || value instanceof NoReactDependencyError || value instanceof PackageJsonNotFoundError || value instanceof NotADirectoryError || value instanceof AmbiguousProjectError;
|
|
32794
|
+
const isErrnoException = (error) => error instanceof Error && "code" in error;
|
|
32795
|
+
const IGNORABLE_READDIR_ERROR_CODES = new Set([
|
|
32796
|
+
"EACCES",
|
|
32797
|
+
"EPERM",
|
|
32798
|
+
"ENOENT",
|
|
32799
|
+
"ENOTDIR",
|
|
32800
|
+
"EINVAL",
|
|
32801
|
+
"ELOOP",
|
|
32802
|
+
"ENAMETOOLONG"
|
|
32803
|
+
]);
|
|
32804
|
+
const isIgnorableReaddirError = (error) => isErrnoException(error) && typeof error.code === "string" && IGNORABLE_READDIR_ERROR_CODES.has(error.code);
|
|
32805
|
+
const readDirectoryEntries = (directoryPath) => {
|
|
32806
|
+
try {
|
|
32807
|
+
return NFS.readdirSync(directoryPath, { withFileTypes: true });
|
|
32808
|
+
} catch (error) {
|
|
32809
|
+
if (isIgnorableReaddirError(error)) return [];
|
|
32810
|
+
throw error;
|
|
32811
|
+
}
|
|
32812
|
+
};
|
|
32794
32813
|
const isFile = (filePath) => {
|
|
32795
32814
|
try {
|
|
32796
32815
|
return NFS.statSync(filePath).isFile();
|
|
@@ -32798,10 +32817,24 @@ const isFile = (filePath) => {
|
|
|
32798
32817
|
return false;
|
|
32799
32818
|
}
|
|
32800
32819
|
};
|
|
32820
|
+
const isDirectory = (directoryPath) => {
|
|
32821
|
+
try {
|
|
32822
|
+
return NFS.statSync(directoryPath).isDirectory();
|
|
32823
|
+
} catch {
|
|
32824
|
+
return false;
|
|
32825
|
+
}
|
|
32826
|
+
};
|
|
32827
|
+
const isPlainObject = (value) => {
|
|
32828
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
32829
|
+
const prototype = Object.getPrototypeOf(value);
|
|
32830
|
+
return prototype === null || prototype === Object.prototype;
|
|
32831
|
+
};
|
|
32801
32832
|
const SOURCE_FILE_PATTERN = /\.(tsx?|jsx?|mts|mjs)$/;
|
|
32802
32833
|
const GENERATED_BUNDLE_FILE_PATTERN = /\.(iife|umd|global|min)\.m?js$/i;
|
|
32834
|
+
const GENERATED_SOURCE_DIRECTORY_PATTERN = /(?:^|\/)__generated__\//;
|
|
32803
32835
|
const MINIFIED_SNIFF_BYTES = 65536;
|
|
32804
32836
|
const GIT_LS_FILES_MAX_BUFFER_BYTES = 50 * 1024 * 1024;
|
|
32837
|
+
const SCANNED_DOT_DIRECTORIES = new Set([".dumi", ".storybook"]);
|
|
32805
32838
|
const IGNORED_DIRECTORIES = new Set([
|
|
32806
32839
|
".angular",
|
|
32807
32840
|
".astro",
|
|
@@ -32943,7 +32976,7 @@ const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
|
|
|
32943
32976
|
const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
|
|
32944
32977
|
const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
|
|
32945
32978
|
const SIDECAR_LINT_CACHE_FILENAME = "sidecar-lint-cache.json";
|
|
32946
|
-
const CORE_PACKAGE_VERSION = "0.7.
|
|
32979
|
+
const CORE_PACKAGE_VERSION = "0.7.2";
|
|
32947
32980
|
const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
|
|
32948
32981
|
const DEAD_CODE_SUMMARY_CACHE_FILENAME = "dead-code-summaries.json";
|
|
32949
32982
|
const SUPPLY_CHAIN_PLUGIN = "socket";
|
|
@@ -32953,8 +32986,9 @@ const SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS = 9e4;
|
|
|
32953
32986
|
const SUPPLY_CHAIN_CACHE_TTL_MS = 864e5;
|
|
32954
32987
|
const SUPPLY_CHAIN_CACHE_SUBDIR = "supply-chain";
|
|
32955
32988
|
const SUPPLY_CHAIN_IGNORED_PACKAGES = new Set(["next"]);
|
|
32956
|
-
const
|
|
32957
|
-
const
|
|
32989
|
+
const isIgnoredDirectoryName = (directoryName) => IGNORED_DIRECTORIES.has(directoryName) || directoryName.startsWith(".") && !SCANNED_DOT_DIRECTORIES.has(directoryName);
|
|
32990
|
+
const hasIgnoredPathSegment = (relativePath) => relativePath.split(/[/\\]/).slice(0, -1).some((segment) => isIgnoredDirectoryName(segment));
|
|
32991
|
+
const isLintableSourceFile = (filePath) => SOURCE_FILE_PATTERN.test(filePath) && !GENERATED_BUNDLE_FILE_PATTERN.test(filePath) && !GENERATED_SOURCE_DIRECTORY_PATTERN.test(filePath);
|
|
32958
32992
|
const isMinifiedSource = (absolutePath) => {
|
|
32959
32993
|
let fileDescriptor;
|
|
32960
32994
|
try {
|
|
@@ -32991,25 +33025,6 @@ const isLargeMinifiedFile = (absolutePath, knownSizeBytes) => {
|
|
|
32991
33025
|
cachedIsLargeMinifiedByPath.set(absolutePath, result);
|
|
32992
33026
|
return result;
|
|
32993
33027
|
};
|
|
32994
|
-
const isErrnoException = (error) => error instanceof Error && "code" in error;
|
|
32995
|
-
const IGNORABLE_READDIR_ERROR_CODES = new Set([
|
|
32996
|
-
"EACCES",
|
|
32997
|
-
"EPERM",
|
|
32998
|
-
"ENOENT",
|
|
32999
|
-
"ENOTDIR",
|
|
33000
|
-
"EINVAL",
|
|
33001
|
-
"ELOOP",
|
|
33002
|
-
"ENAMETOOLONG"
|
|
33003
|
-
]);
|
|
33004
|
-
const isIgnorableReaddirError = (error) => isErrnoException(error) && typeof error.code === "string" && IGNORABLE_READDIR_ERROR_CODES.has(error.code);
|
|
33005
|
-
const readDirectoryEntries = (directoryPath) => {
|
|
33006
|
-
try {
|
|
33007
|
-
return NFS.readdirSync(directoryPath, { withFileTypes: true });
|
|
33008
|
-
} catch (error) {
|
|
33009
|
-
if (isIgnorableReaddirError(error)) return [];
|
|
33010
|
-
throw error;
|
|
33011
|
-
}
|
|
33012
|
-
};
|
|
33013
33028
|
function* walkSourceTreeFiles(rootDirectory) {
|
|
33014
33029
|
const stack = [rootDirectory];
|
|
33015
33030
|
while (stack.length > 0) {
|
|
@@ -33018,7 +33033,7 @@ function* walkSourceTreeFiles(rootDirectory) {
|
|
|
33018
33033
|
for (const entry of readDirectoryEntries(currentDirectory)) {
|
|
33019
33034
|
const absolutePath = Path.join(currentDirectory, entry.name);
|
|
33020
33035
|
if (entry.isDirectory()) {
|
|
33021
|
-
if (!
|
|
33036
|
+
if (!isIgnoredDirectoryName(entry.name)) stack.push(absolutePath);
|
|
33022
33037
|
continue;
|
|
33023
33038
|
}
|
|
33024
33039
|
if (entry.isFile()) yield {
|
|
@@ -33115,6 +33130,146 @@ const findMonorepoRoot = (startDirectory) => {
|
|
|
33115
33130
|
* byte-equivalent copy.
|
|
33116
33131
|
*/
|
|
33117
33132
|
const isProjectBoundary = (directory) => NFS.existsSync(Path.join(directory, ".git")) || isMonorepoRoot(directory);
|
|
33133
|
+
const TSCONFIG_FILENAME = "tsconfig.json";
|
|
33134
|
+
const isRelativeExtendsValue = (extendsValue) => extendsValue.startsWith("./") || extendsValue.startsWith("../") || Path.isAbsolute(extendsValue);
|
|
33135
|
+
const ensureJsonExtension = (filePath) => Path.extname(filePath) === "" ? `${filePath}.json` : filePath;
|
|
33136
|
+
const resolvePackageExtendsPath = (extendsValue, fromConfigDirectory) => {
|
|
33137
|
+
const requireFromConfig = createRequire(Path.join(fromConfigDirectory, "tsconfig.json"));
|
|
33138
|
+
const candidates = [
|
|
33139
|
+
extendsValue,
|
|
33140
|
+
ensureJsonExtension(extendsValue),
|
|
33141
|
+
`${extendsValue.replace(/\/$/, "")}/tsconfig.json`
|
|
33142
|
+
];
|
|
33143
|
+
for (const candidate of candidates) try {
|
|
33144
|
+
return requireFromConfig.resolve(candidate);
|
|
33145
|
+
} catch {
|
|
33146
|
+
continue;
|
|
33147
|
+
}
|
|
33148
|
+
return null;
|
|
33149
|
+
};
|
|
33150
|
+
const resolveExtendsPath = (extendsValue, fromConfigDirectory) => {
|
|
33151
|
+
if (isRelativeExtendsValue(extendsValue)) return ensureJsonExtension(Path.resolve(fromConfigDirectory, extendsValue));
|
|
33152
|
+
return resolvePackageExtendsPath(extendsValue, fromConfigDirectory);
|
|
33153
|
+
};
|
|
33154
|
+
const normalizeCompilerOptions = (compilerOptions) => {
|
|
33155
|
+
if (!isPlainObject(compilerOptions)) return { hasExplicitLib: false };
|
|
33156
|
+
const target = typeof compilerOptions.target === "string" ? compilerOptions.target : void 0;
|
|
33157
|
+
const hasExplicitLib = Object.hasOwn(compilerOptions, "lib");
|
|
33158
|
+
return {
|
|
33159
|
+
target,
|
|
33160
|
+
lib: Array.isArray(compilerOptions.lib) ? compilerOptions.lib.filter((entry) => typeof entry === "string") : void 0,
|
|
33161
|
+
hasExplicitLib
|
|
33162
|
+
};
|
|
33163
|
+
};
|
|
33164
|
+
const readTsConfig = (filePath) => {
|
|
33165
|
+
let content;
|
|
33166
|
+
try {
|
|
33167
|
+
content = NFS.readFileSync(filePath, "utf-8");
|
|
33168
|
+
} catch {
|
|
33169
|
+
return null;
|
|
33170
|
+
}
|
|
33171
|
+
const parsed = ts.parseConfigFileTextToJson(filePath, content);
|
|
33172
|
+
if (!isPlainObject(parsed.config)) return null;
|
|
33173
|
+
return {
|
|
33174
|
+
extends: typeof parsed.config.extends === "string" ? parsed.config.extends : void 0,
|
|
33175
|
+
referencePaths: normalizeReferencePaths(parsed.config.references),
|
|
33176
|
+
compilerOptions: normalizeCompilerOptions(parsed.config.compilerOptions)
|
|
33177
|
+
};
|
|
33178
|
+
};
|
|
33179
|
+
const normalizeReferencePaths = (references) => {
|
|
33180
|
+
if (!Array.isArray(references)) return [];
|
|
33181
|
+
return references.map((reference) => isPlainObject(reference) && typeof reference.path === "string" ? reference.path : null).filter((referencePath) => referencePath !== null);
|
|
33182
|
+
};
|
|
33183
|
+
const mergeCompilerOptions = (inherited, current) => {
|
|
33184
|
+
const target = current.target ?? inherited?.target;
|
|
33185
|
+
const hasExplicitLib = current.hasExplicitLib || Boolean(inherited?.hasExplicitLib);
|
|
33186
|
+
return {
|
|
33187
|
+
target,
|
|
33188
|
+
lib: current.hasExplicitLib ? current.lib : inherited?.lib,
|
|
33189
|
+
hasExplicitLib
|
|
33190
|
+
};
|
|
33191
|
+
};
|
|
33192
|
+
const readResolvedCompilerOptions = (tsConfigPath, extendsDepth, visitedPaths) => {
|
|
33193
|
+
const realPath = NFS.realpathSync.native(tsConfigPath);
|
|
33194
|
+
if (visitedPaths.has(realPath)) return null;
|
|
33195
|
+
const tsConfig = readTsConfig(realPath);
|
|
33196
|
+
if (!tsConfig) return null;
|
|
33197
|
+
const nextVisitedPaths = new Set(visitedPaths);
|
|
33198
|
+
nextVisitedPaths.add(realPath);
|
|
33199
|
+
if (tsConfig.extends && extendsDepth < 8) {
|
|
33200
|
+
const parentPath = resolveExtendsPath(tsConfig.extends, Path.dirname(realPath));
|
|
33201
|
+
if (parentPath && isFile(parentPath)) return mergeCompilerOptions(readResolvedCompilerOptions(parentPath, extendsDepth + 1, nextVisitedPaths), tsConfig.compilerOptions);
|
|
33202
|
+
}
|
|
33203
|
+
return tsConfig.compilerOptions;
|
|
33204
|
+
};
|
|
33205
|
+
const targetYearIsPreES2023 = (target) => {
|
|
33206
|
+
const year = ES_TARGET_YEAR_BY_NAME[target.toLowerCase()];
|
|
33207
|
+
return year !== void 0 && year < 2023;
|
|
33208
|
+
};
|
|
33209
|
+
const libEntryIncludesES2023Array = (entry) => {
|
|
33210
|
+
const normalizedEntry = entry.toLowerCase();
|
|
33211
|
+
if (normalizedEntry === "esnext" || normalizedEntry === "esnext.array") return true;
|
|
33212
|
+
const esYearMatch = /^es(\d{4})(?:\.(.+))?$/.exec(normalizedEntry);
|
|
33213
|
+
if (!esYearMatch) return false;
|
|
33214
|
+
if (Number(esYearMatch[1]) < 2023) return false;
|
|
33215
|
+
const component = esYearMatch[2];
|
|
33216
|
+
return component === void 0 || component === "array";
|
|
33217
|
+
};
|
|
33218
|
+
const libIncludesES2023 = (lib) => lib.some(libEntryIncludesES2023Array);
|
|
33219
|
+
const compilerOptionsArePreES2023 = (compilerOptions) => {
|
|
33220
|
+
if (compilerOptions.target) return targetYearIsPreES2023(compilerOptions.target);
|
|
33221
|
+
if (compilerOptions.hasExplicitLib) return !libIncludesES2023(compilerOptions.lib ?? []);
|
|
33222
|
+
return false;
|
|
33223
|
+
};
|
|
33224
|
+
const compilerOptionsDeclareTargetOrLib = (compilerOptions) => compilerOptions.hasExplicitLib || compilerOptions.target !== void 0;
|
|
33225
|
+
const detectPreES2023FromConfig = (tsConfigPath, visitedConfigPaths = /* @__PURE__ */ new Set()) => {
|
|
33226
|
+
if (visitedConfigPaths.has(tsConfigPath)) return false;
|
|
33227
|
+
const compilerOptions = readResolvedCompilerOptions(tsConfigPath, 0, /* @__PURE__ */ new Set());
|
|
33228
|
+
if (!compilerOptions) return false;
|
|
33229
|
+
if (!compilerOptionsDeclareTargetOrLib(compilerOptions)) {
|
|
33230
|
+
const tsConfig = readTsConfig(tsConfigPath);
|
|
33231
|
+
if (!tsConfig) return false;
|
|
33232
|
+
const nextVisitedConfigPaths = new Set(visitedConfigPaths);
|
|
33233
|
+
nextVisitedConfigPaths.add(tsConfigPath);
|
|
33234
|
+
const configDirectory = Path.dirname(tsConfigPath);
|
|
33235
|
+
return tsConfig.referencePaths.some((referencePath) => {
|
|
33236
|
+
const resolvedReferencePath = Path.resolve(configDirectory, referencePath);
|
|
33237
|
+
const referencedConfigPath = isFile(resolvedReferencePath) ? resolvedReferencePath : Path.join(resolvedReferencePath, TSCONFIG_FILENAME);
|
|
33238
|
+
return isFile(referencedConfigPath) && detectPreES2023FromConfig(referencedConfigPath, nextVisitedConfigPaths);
|
|
33239
|
+
});
|
|
33240
|
+
}
|
|
33241
|
+
return compilerOptionsArePreES2023(compilerOptions);
|
|
33242
|
+
};
|
|
33243
|
+
const detectPreES2023Target = (directory) => {
|
|
33244
|
+
const tsConfigPath = Path.join(directory, TSCONFIG_FILENAME);
|
|
33245
|
+
if (isFile(tsConfigPath)) return detectPreES2023FromConfig(tsConfigPath);
|
|
33246
|
+
for (const fallbackFilename of FALLBACK_TSCONFIG_FILENAMES) {
|
|
33247
|
+
const fallbackPath = Path.join(directory, fallbackFilename);
|
|
33248
|
+
if (isFile(fallbackPath)) return detectPreES2023FromConfig(fallbackPath);
|
|
33249
|
+
}
|
|
33250
|
+
return false;
|
|
33251
|
+
};
|
|
33252
|
+
const FALLBACK_TSCONFIG_FILENAMES = ["tsconfig.app.json", "tsconfig.build.json"];
|
|
33253
|
+
const FRAMEWORK_PACKAGES = {
|
|
33254
|
+
next: "nextjs",
|
|
33255
|
+
"@tanstack/react-start": "tanstack-start",
|
|
33256
|
+
vite: "vite",
|
|
33257
|
+
"react-scripts": "cra",
|
|
33258
|
+
"@remix-run/react": "remix",
|
|
33259
|
+
gatsby: "gatsby",
|
|
33260
|
+
expo: "expo",
|
|
33261
|
+
"react-native": "react-native"
|
|
33262
|
+
};
|
|
33263
|
+
const detectFramework = (dependencies) => {
|
|
33264
|
+
for (const [packageName, frameworkName] of Object.entries(FRAMEWORK_PACKAGES)) if (dependencies[packageName]) return frameworkName;
|
|
33265
|
+
if (dependencies.preact && !dependencies.react) return "preact";
|
|
33266
|
+
return "unknown";
|
|
33267
|
+
};
|
|
33268
|
+
const MOBILE_FRAMEWORKS = new Set(["expo", "react-native"]);
|
|
33269
|
+
const frameworkMergeRank = (framework) => {
|
|
33270
|
+
if (framework === "unknown") return 3;
|
|
33271
|
+
return MOBILE_FRAMEWORKS.has(framework) ? 2 : 1;
|
|
33272
|
+
};
|
|
33118
33273
|
const REACT_COMPILER_PACKAGES = new Set([
|
|
33119
33274
|
"babel-plugin-react-compiler",
|
|
33120
33275
|
"react-compiler-runtime",
|
|
@@ -33151,6 +33306,7 @@ const EXPO_APP_CONFIG_FILENAMES = [
|
|
|
33151
33306
|
];
|
|
33152
33307
|
const REACT_COMPILER_PACKAGE_REFERENCE_PATTERN = /babel-plugin-react-compiler|react-compiler-runtime|eslint-plugin-react-compiler|["']react-compiler["']/;
|
|
33153
33308
|
const REACT_COMPILER_ENABLED_FLAG_PATTERN = /["']?reactCompiler["']?\s*:\s*(?:true\b|\{)/;
|
|
33309
|
+
const STATIC_EXPORT_OUTPUT_PATTERN = /(?:^|[^.\w])["']?output["']?\s*:\s*["']export["']/m;
|
|
33154
33310
|
const hasCompilerPackage = (packageJson) => {
|
|
33155
33311
|
const allDependencies = {
|
|
33156
33312
|
...packageJson.peerDependencies,
|
|
@@ -33183,34 +33339,10 @@ const detectReactCompiler = (directory, packageJson) => {
|
|
|
33183
33339
|
}
|
|
33184
33340
|
return false;
|
|
33185
33341
|
};
|
|
33186
|
-
const
|
|
33187
|
-
|
|
33188
|
-
|
|
33189
|
-
|
|
33190
|
-
"react-scripts": "cra",
|
|
33191
|
-
"@remix-run/react": "remix",
|
|
33192
|
-
gatsby: "gatsby",
|
|
33193
|
-
expo: "expo",
|
|
33194
|
-
"react-native": "react-native"
|
|
33195
|
-
};
|
|
33196
|
-
const FRAMEWORK_DISPLAY_NAMES = {
|
|
33197
|
-
nextjs: "Next.js",
|
|
33198
|
-
"tanstack-start": "TanStack Start",
|
|
33199
|
-
vite: "Vite",
|
|
33200
|
-
cra: "Create React App",
|
|
33201
|
-
remix: "Remix",
|
|
33202
|
-
gatsby: "Gatsby",
|
|
33203
|
-
expo: "Expo",
|
|
33204
|
-
"react-native": "React Native",
|
|
33205
|
-
preact: "Preact",
|
|
33206
|
-
unknown: "React"
|
|
33207
|
-
};
|
|
33208
|
-
const formatFrameworkName = (framework) => FRAMEWORK_DISPLAY_NAMES[framework];
|
|
33209
|
-
const detectFramework = (dependencies) => {
|
|
33210
|
-
for (const [packageName, frameworkName] of Object.entries(FRAMEWORK_PACKAGES)) if (dependencies[packageName]) return frameworkName;
|
|
33211
|
-
if (dependencies.preact && !dependencies.react) return "preact";
|
|
33212
|
-
return "unknown";
|
|
33213
|
-
};
|
|
33342
|
+
const detectNextjsStaticExport = (directory) => NEXT_CONFIG_FILENAMES.some((filename) => {
|
|
33343
|
+
const filePath = Path.join(directory, filename);
|
|
33344
|
+
return isFile(filePath) && STATIC_EXPORT_OUTPUT_PATTERN.test(NFS.readFileSync(filePath, "utf-8"));
|
|
33345
|
+
});
|
|
33214
33346
|
const UNRESOLVABLE_PROTOCOL_VERSION = /^(?:file|git|github|https?|link|patch|portal|workspace|npm):/i;
|
|
33215
33347
|
const DIST_TAG_VERSION = /^[a-z][a-z0-9._-]*$/i;
|
|
33216
33348
|
const WILDCARD_VERSION = /^[*xX](?:\.[*xX])*$/;
|
|
@@ -33348,10 +33480,76 @@ const isConcreteDependencyVersion = (version) => {
|
|
|
33348
33480
|
const normalizedVersion = normalizeDependencyVersion(version);
|
|
33349
33481
|
return normalizedVersion !== null && /\d/.test(normalizedVersion);
|
|
33350
33482
|
};
|
|
33351
|
-
const
|
|
33352
|
-
if (
|
|
33353
|
-
|
|
33354
|
-
return
|
|
33483
|
+
const isMajorMinorAtLeast = (detected, required) => {
|
|
33484
|
+
if (detected === null) return true;
|
|
33485
|
+
if (detected.major !== required.major) return detected.major > required.major;
|
|
33486
|
+
return detected.minor >= required.minor;
|
|
33487
|
+
};
|
|
33488
|
+
const parseReactMajor = (reactVersion) => {
|
|
33489
|
+
if (typeof reactVersion !== "string") return null;
|
|
33490
|
+
return getLowestDependencyMajor(reactVersion);
|
|
33491
|
+
};
|
|
33492
|
+
const MAJOR_MINOR_PATTERN = /(\d{1,4})\.(\d{1,4})/;
|
|
33493
|
+
const MAJOR_ONLY_PATTERN = /(\d{1,4})/;
|
|
33494
|
+
const UPPER_BOUND_COMPARATOR_PATTERN = /<=?\s{0,8}\d{1,4}(?:\.\d{1,4}){0,2}(?:-[^\s,|]+)?/g;
|
|
33495
|
+
const parseReactMajorMinor = (reactVersion) => {
|
|
33496
|
+
if (typeof reactVersion !== "string") return null;
|
|
33497
|
+
const trimmed = reactVersion.trim();
|
|
33498
|
+
if (trimmed.length === 0) return null;
|
|
33499
|
+
const lowerBoundsOnly = trimmed.replace(UPPER_BOUND_COMPARATOR_PATTERN, " ").trim();
|
|
33500
|
+
if (lowerBoundsOnly.length === 0) return null;
|
|
33501
|
+
const majorMinorMatch = lowerBoundsOnly.match(MAJOR_MINOR_PATTERN);
|
|
33502
|
+
if (majorMinorMatch) {
|
|
33503
|
+
const major = Number.parseInt(majorMinorMatch[1], 10);
|
|
33504
|
+
const minor = Number.parseInt(majorMinorMatch[2], 10);
|
|
33505
|
+
if (!Number.isFinite(major) || major <= 0) return null;
|
|
33506
|
+
if (!Number.isFinite(minor) || minor < 0) return null;
|
|
33507
|
+
return {
|
|
33508
|
+
major,
|
|
33509
|
+
minor
|
|
33510
|
+
};
|
|
33511
|
+
}
|
|
33512
|
+
const majorOnlyMatch = lowerBoundsOnly.match(MAJOR_ONLY_PATTERN);
|
|
33513
|
+
if (!majorOnlyMatch) return null;
|
|
33514
|
+
const major = Number.parseInt(majorOnlyMatch[1], 10);
|
|
33515
|
+
if (!Number.isFinite(major) || major <= 0) return null;
|
|
33516
|
+
return {
|
|
33517
|
+
major,
|
|
33518
|
+
minor: 0
|
|
33519
|
+
};
|
|
33520
|
+
};
|
|
33521
|
+
const parseLowerBoundVersion = (versionSpec) => import_semver.validRange(versionSpec) !== null ? import_semver.minVersion(versionSpec) : import_semver.coerce(versionSpec);
|
|
33522
|
+
const parseTailwindMajorMinor = (tailwindVersion) => {
|
|
33523
|
+
if (typeof tailwindVersion !== "string") return null;
|
|
33524
|
+
const trimmed = tailwindVersion.trim();
|
|
33525
|
+
if (trimmed.length === 0) return null;
|
|
33526
|
+
const lowerBound = parseLowerBoundVersion(trimmed);
|
|
33527
|
+
if (lowerBound === null || lowerBound.major <= 0) return null;
|
|
33528
|
+
return {
|
|
33529
|
+
major: lowerBound.major,
|
|
33530
|
+
minor: lowerBound.minor
|
|
33531
|
+
};
|
|
33532
|
+
};
|
|
33533
|
+
const hasUpperBoundOnlyPeerRange = (range) => {
|
|
33534
|
+
if (typeof range !== "string") return false;
|
|
33535
|
+
const normalizedRange = normalizeDependencyVersion(range);
|
|
33536
|
+
if (normalizedRange === null) return false;
|
|
33537
|
+
return splitDependencyVersionBranches(normalizedRange).some((branch) => {
|
|
33538
|
+
const normalizedBranch = normalizeDependencyVersion(branch);
|
|
33539
|
+
return normalizedBranch !== null && getBranchLowestMajor(normalizedBranch) === null && hasUpperBoundComparator(normalizedBranch);
|
|
33540
|
+
});
|
|
33541
|
+
};
|
|
33542
|
+
const peerRangeMinMajor = (range) => {
|
|
33543
|
+
if (typeof range !== "string") return null;
|
|
33544
|
+
return getLowestDependencyMajor(range);
|
|
33545
|
+
};
|
|
33546
|
+
const resolveEffectiveReactMajor = (reactVersion, packageJson) => {
|
|
33547
|
+
const installedReactMajor = parseReactMajor(reactVersion);
|
|
33548
|
+
const peerReactRange = packageJson.peerDependencies?.react;
|
|
33549
|
+
if (typeof peerReactRange !== "string") return installedReactMajor;
|
|
33550
|
+
const peerFloor = peerRangeMinMajor(peerReactRange);
|
|
33551
|
+
if (peerFloor === null) return hasUpperBoundOnlyPeerRange(peerReactRange) ? null : installedReactMajor;
|
|
33552
|
+
return installedReactMajor !== null ? Math.min(installedReactMajor, peerFloor) : peerFloor;
|
|
33355
33553
|
};
|
|
33356
33554
|
const isCatalogReference = (version) => version.startsWith("catalog:");
|
|
33357
33555
|
const extractCatalogName = (version) => {
|
|
@@ -33493,6 +33691,16 @@ const resolveCatalogVersion = (packageJson, packageName, rootDirectory, explicit
|
|
|
33493
33691
|
}
|
|
33494
33692
|
return null;
|
|
33495
33693
|
};
|
|
33694
|
+
const REACT_SECTIONS = [
|
|
33695
|
+
"dependencies",
|
|
33696
|
+
"peerDependencies",
|
|
33697
|
+
"devDependencies"
|
|
33698
|
+
];
|
|
33699
|
+
const TAILWIND_ZOD_SECTIONS = [
|
|
33700
|
+
"dependencies",
|
|
33701
|
+
"devDependencies",
|
|
33702
|
+
"peerDependencies"
|
|
33703
|
+
];
|
|
33496
33704
|
const EMPTY_DEPENDENCY_INFO = {
|
|
33497
33705
|
reactVersion: null,
|
|
33498
33706
|
tailwindVersion: null,
|
|
@@ -33533,6 +33741,10 @@ const extractDependencyInfo = (packageJson) => {
|
|
|
33533
33741
|
framework: detectFramework(allDependencies)
|
|
33534
33742
|
};
|
|
33535
33743
|
};
|
|
33744
|
+
const getDependencySpec = (packageJson, packageName) => {
|
|
33745
|
+
const spec = packageJson.dependencies?.[packageName] ?? packageJson.devDependencies?.[packageName] ?? packageJson.peerDependencies?.[packageName] ?? packageJson.optionalDependencies?.[packageName];
|
|
33746
|
+
return typeof spec === "string" ? spec : null;
|
|
33747
|
+
};
|
|
33536
33748
|
const getDependencyDeclaration = ({ packageJson, packageName, sections }) => {
|
|
33537
33749
|
for (const section of sections) {
|
|
33538
33750
|
const version = packageJson[section]?.[packageName];
|
|
@@ -33549,222 +33761,91 @@ const getDependencyDeclaration = ({ packageJson, packageName, sections }) => {
|
|
|
33549
33761
|
version: null
|
|
33550
33762
|
};
|
|
33551
33763
|
};
|
|
33552
|
-
const
|
|
33553
|
-
|
|
33554
|
-
|
|
33555
|
-
|
|
33556
|
-
|
|
33557
|
-
|
|
33558
|
-
|
|
33559
|
-
const
|
|
33560
|
-
|
|
33561
|
-
|
|
33562
|
-
|
|
33563
|
-
|
|
33564
|
-
|
|
33565
|
-
if (!isFile(Path.join(rootDirectory, "nx.json"))) return [];
|
|
33566
|
-
const collected = [];
|
|
33567
|
-
for (const candidate of NX_PROJECT_DISCOVERY_DIRS) {
|
|
33568
|
-
const candidatePath = Path.join(rootDirectory, candidate);
|
|
33569
|
-
if (!isDirectory(candidatePath)) continue;
|
|
33570
|
-
for (const entry of readDirectoryEntries(candidatePath)) {
|
|
33571
|
-
if (!entry.isDirectory()) continue;
|
|
33572
|
-
const projectDirectory = Path.join(candidatePath, entry.name);
|
|
33573
|
-
if (isFile(Path.join(projectDirectory, "project.json")) || isFile(Path.join(projectDirectory, "package.json"))) collected.push(`${candidate}/${entry.name}`);
|
|
33574
|
-
}
|
|
33575
|
-
}
|
|
33576
|
-
return collected;
|
|
33577
|
-
};
|
|
33578
|
-
const parsePnpmWorkspacePatterns = (rootDirectory) => {
|
|
33579
|
-
const workspacePath = Path.join(rootDirectory, "pnpm-workspace.yaml");
|
|
33580
|
-
if (!isFile(workspacePath)) return [];
|
|
33581
|
-
const content = NFS.readFileSync(workspacePath, "utf-8");
|
|
33582
|
-
const patterns = [];
|
|
33583
|
-
let isInsidePackagesBlock = false;
|
|
33584
|
-
for (const line of content.split("\n")) {
|
|
33585
|
-
const trimmed = line.trim();
|
|
33586
|
-
if (trimmed === "packages:") {
|
|
33587
|
-
isInsidePackagesBlock = true;
|
|
33588
|
-
continue;
|
|
33589
|
-
}
|
|
33590
|
-
if (isInsidePackagesBlock && trimmed.startsWith("-")) patterns.push(trimmed.replace(/^-\s*/, "").replace(/["']/g, ""));
|
|
33591
|
-
else if (isInsidePackagesBlock && trimmed.length > 0 && !trimmed.startsWith("#")) isInsidePackagesBlock = false;
|
|
33592
|
-
}
|
|
33593
|
-
return patterns;
|
|
33594
|
-
};
|
|
33595
|
-
const getWorkspacePatterns = (rootDirectory, packageJson) => {
|
|
33596
|
-
const pnpmPatterns = parsePnpmWorkspacePatterns(rootDirectory);
|
|
33597
|
-
if (pnpmPatterns.length > 0) return pnpmPatterns;
|
|
33598
|
-
if (Array.isArray(packageJson.workspaces)) return packageJson.workspaces;
|
|
33599
|
-
if (packageJson.workspaces?.packages) return packageJson.workspaces.packages;
|
|
33600
|
-
const nxPatterns = getNxWorkspaceDirectories(rootDirectory);
|
|
33601
|
-
if (nxPatterns.length > 0) return nxPatterns;
|
|
33602
|
-
return [];
|
|
33603
|
-
};
|
|
33604
|
-
const parseReactMajor = (reactVersion) => {
|
|
33605
|
-
if (typeof reactVersion !== "string") return null;
|
|
33606
|
-
return getLowestDependencyMajor(reactVersion);
|
|
33764
|
+
const REACT_DEPENDENCY_NAMES = new Set([
|
|
33765
|
+
"react",
|
|
33766
|
+
"react-native",
|
|
33767
|
+
"next",
|
|
33768
|
+
"preact"
|
|
33769
|
+
]);
|
|
33770
|
+
const hasReactDependency = (packageJson) => {
|
|
33771
|
+
const allDependencies = {
|
|
33772
|
+
...packageJson.peerDependencies,
|
|
33773
|
+
...packageJson.dependencies,
|
|
33774
|
+
...packageJson.devDependencies
|
|
33775
|
+
};
|
|
33776
|
+
return Object.keys(allDependencies).some((packageName) => REACT_DEPENDENCY_NAMES.has(packageName));
|
|
33607
33777
|
};
|
|
33608
|
-
const
|
|
33609
|
-
|
|
33610
|
-
|
|
33611
|
-
|
|
33612
|
-
|
|
33613
|
-
|
|
33614
|
-
}
|
|
33615
|
-
const wildcardIndex = cleanPattern.indexOf("*");
|
|
33616
|
-
const baseDirectory = Path.join(rootDirectory, cleanPattern.slice(0, wildcardIndex));
|
|
33617
|
-
const suffixAfterWildcard = cleanPattern.slice(wildcardIndex + 1);
|
|
33618
|
-
if (!isDirectory(baseDirectory)) return [];
|
|
33619
|
-
const resolved = [];
|
|
33620
|
-
for (const entry of readDirectoryEntries(baseDirectory)) {
|
|
33621
|
-
const entryPath = Path.join(baseDirectory, entry.name, suffixAfterWildcard);
|
|
33622
|
-
if (isDirectory(entryPath) && isFile(Path.join(entryPath, "package.json"))) resolved.push(entryPath);
|
|
33623
|
-
}
|
|
33624
|
-
return resolved;
|
|
33778
|
+
const getPreactVersion = (packageJson) => {
|
|
33779
|
+
return {
|
|
33780
|
+
...packageJson.peerDependencies,
|
|
33781
|
+
...packageJson.dependencies,
|
|
33782
|
+
...packageJson.devDependencies
|
|
33783
|
+
}.preact ?? null;
|
|
33625
33784
|
};
|
|
33626
|
-
const
|
|
33627
|
-
|
|
33628
|
-
|
|
33629
|
-
|
|
33630
|
-
|
|
33631
|
-
|
|
33632
|
-
|
|
33633
|
-
|
|
33785
|
+
const TANSTACK_QUERY_PACKAGES = new Set([
|
|
33786
|
+
"@tanstack/react-query",
|
|
33787
|
+
"@tanstack/query-core",
|
|
33788
|
+
"react-query"
|
|
33789
|
+
]);
|
|
33790
|
+
const hasTanStackQuery = (packageJson) => {
|
|
33791
|
+
const allDependencies = {
|
|
33792
|
+
...packageJson.peerDependencies,
|
|
33793
|
+
...packageJson.dependencies,
|
|
33794
|
+
...packageJson.devDependencies
|
|
33795
|
+
};
|
|
33796
|
+
return Object.keys(allDependencies).some((packageName) => TANSTACK_QUERY_PACKAGES.has(packageName));
|
|
33634
33797
|
};
|
|
33635
|
-
const
|
|
33636
|
-
if (!
|
|
33637
|
-
const
|
|
33638
|
-
const
|
|
33639
|
-
if (
|
|
33640
|
-
|
|
33641
|
-
|
|
33798
|
+
const resolveCatalogBackedDependencyVersion = ({ rootDirectory, rootPackageJson, packageName, version }) => {
|
|
33799
|
+
if (version === null || !isCatalogReference(version)) return version;
|
|
33800
|
+
const catalogName = extractCatalogName(version);
|
|
33801
|
+
const resolvedLocalVersion = resolveCatalogVersion(rootPackageJson, packageName, rootDirectory, catalogName);
|
|
33802
|
+
if (resolvedLocalVersion) return resolvedLocalVersion;
|
|
33803
|
+
const monorepoRoot = findMonorepoRoot(rootDirectory);
|
|
33804
|
+
if (!monorepoRoot) return version;
|
|
33805
|
+
const monorepoPackageJsonPath = Path.join(monorepoRoot, "package.json");
|
|
33806
|
+
if (!isFile(monorepoPackageJsonPath)) return version;
|
|
33807
|
+
return resolveCatalogVersion(readPackageJson(monorepoPackageJsonPath), packageName, monorepoRoot, catalogName) ?? version;
|
|
33642
33808
|
};
|
|
33643
|
-
|
|
33644
|
-
|
|
33645
|
-
|
|
33646
|
-
|
|
33647
|
-
|
|
33648
|
-
|
|
33649
|
-
|
|
33650
|
-
|
|
33651
|
-
|
|
33652
|
-
|
|
33653
|
-
|
|
33654
|
-
|
|
33655
|
-
|
|
33656
|
-
|
|
33657
|
-
|
|
33658
|
-
"peerDependencies",
|
|
33659
|
-
"devDependencies"
|
|
33660
|
-
],
|
|
33661
|
-
workspaceDirectory,
|
|
33662
|
-
workspacePackageJson
|
|
33663
|
-
});
|
|
33664
|
-
const tailwindVersion = resolveWorkspaceDependencyVersion({
|
|
33665
|
-
concreteVersion: info.tailwindVersion,
|
|
33666
|
-
packageName: "tailwindcss",
|
|
33667
|
-
rootDirectory,
|
|
33668
|
-
rootPackageJson: packageJson,
|
|
33669
|
-
sections: [
|
|
33670
|
-
"dependencies",
|
|
33671
|
-
"devDependencies",
|
|
33672
|
-
"peerDependencies"
|
|
33673
|
-
],
|
|
33674
|
-
workspaceDirectory,
|
|
33675
|
-
workspacePackageJson
|
|
33676
|
-
});
|
|
33677
|
-
const zodVersion = resolveWorkspaceDependencyVersion({
|
|
33678
|
-
concreteVersion: info.zodVersion,
|
|
33679
|
-
packageName: "zod",
|
|
33680
|
-
rootDirectory,
|
|
33681
|
-
rootPackageJson: packageJson,
|
|
33682
|
-
sections: [
|
|
33683
|
-
"dependencies",
|
|
33684
|
-
"devDependencies",
|
|
33685
|
-
"peerDependencies"
|
|
33686
|
-
],
|
|
33687
|
-
workspaceDirectory,
|
|
33688
|
-
workspacePackageJson
|
|
33689
|
-
});
|
|
33690
|
-
if (reactVersion && shouldReplaceReactVersion(result.reactVersion, reactVersion)) result.reactVersion = reactVersion;
|
|
33691
|
-
if (tailwindVersion && !result.tailwindVersion) result.tailwindVersion = tailwindVersion;
|
|
33692
|
-
if (zodVersion && !result.zodVersion) result.zodVersion = zodVersion;
|
|
33693
|
-
if (info.framework !== "unknown" && result.framework === "unknown") result.framework = info.framework;
|
|
33694
|
-
const resultReactMajor = parseReactMajor(result.reactVersion);
|
|
33695
|
-
if (result.reactVersion && result.tailwindVersion && result.framework !== "unknown" && resultReactMajor !== null && resultReactMajor <= 17) return result;
|
|
33696
|
-
}
|
|
33809
|
+
/**
|
|
33810
|
+
* The chain of ancestor directories of `startDirectory`, nearest first, up to
|
|
33811
|
+
* and including the filesystem root. With `includeStart`, the chain begins at
|
|
33812
|
+
* `startDirectory` itself; otherwise at its parent. The one place the "walk up
|
|
33813
|
+
* the tree" traversal is expressed, so each caller adds only its own match /
|
|
33814
|
+
* stop policy instead of re-deriving the `path.dirname` loop.
|
|
33815
|
+
*/
|
|
33816
|
+
const ancestorDirectories = (startDirectory, { includeStart }) => {
|
|
33817
|
+
const directories = [];
|
|
33818
|
+
let currentDirectory = includeStart ? startDirectory : Path.dirname(startDirectory);
|
|
33819
|
+
while (true) {
|
|
33820
|
+
directories.push(currentDirectory);
|
|
33821
|
+
const parentDirectory = Path.dirname(currentDirectory);
|
|
33822
|
+
if (parentDirectory === currentDirectory) return directories;
|
|
33823
|
+
currentDirectory = parentDirectory;
|
|
33697
33824
|
}
|
|
33698
|
-
return result;
|
|
33699
33825
|
};
|
|
33700
|
-
|
|
33701
|
-
|
|
33702
|
-
|
|
33703
|
-
|
|
33704
|
-
|
|
33705
|
-
|
|
33706
|
-
|
|
33707
|
-
|
|
33708
|
-
|
|
33709
|
-
|
|
33710
|
-
|
|
33711
|
-
|
|
33712
|
-
|
|
33713
|
-
|
|
33714
|
-
|
|
33715
|
-
|
|
33716
|
-
|
|
33717
|
-
})
|
|
33718
|
-
|
|
33719
|
-
|
|
33720
|
-
packageName: "tailwindcss",
|
|
33721
|
-
sections: [
|
|
33722
|
-
"dependencies",
|
|
33723
|
-
"devDependencies",
|
|
33724
|
-
"peerDependencies"
|
|
33725
|
-
]
|
|
33726
|
-
}) : null;
|
|
33727
|
-
const leafZodDeclaration = leafPackageJson ? getDependencyDeclaration({
|
|
33728
|
-
packageJson: leafPackageJson,
|
|
33729
|
-
packageName: "zod",
|
|
33730
|
-
sections: [
|
|
33731
|
-
"dependencies",
|
|
33732
|
-
"devDependencies",
|
|
33733
|
-
"peerDependencies"
|
|
33734
|
-
]
|
|
33735
|
-
}) : null;
|
|
33736
|
-
const shouldUseReactFallback = !leafReactDeclaration?.hasDeclaration;
|
|
33737
|
-
const shouldUseTailwindFallback = leafTailwindDeclaration?.hasDeclaration ?? true;
|
|
33738
|
-
const shouldUseZodFallback = leafZodDeclaration?.hasDeclaration ?? true;
|
|
33739
|
-
const reactCatalogVersion = shouldUseReactFallback ? resolveCatalogVersion(rootPackageJson, "react", monorepoRoot, leafReactDeclaration?.catalogReference) : null;
|
|
33740
|
-
const tailwindCatalogVersion = shouldUseTailwindFallback ? resolveCatalogVersion(rootPackageJson, "tailwindcss", monorepoRoot, leafTailwindDeclaration?.catalogReference) : null;
|
|
33741
|
-
const zodCatalogVersion = shouldUseZodFallback ? resolveCatalogVersion(rootPackageJson, "zod", monorepoRoot, leafZodDeclaration?.catalogReference) : null;
|
|
33742
|
-
const workspaceInfo = findReactInWorkspaces(monorepoRoot, rootPackageJson);
|
|
33743
|
-
return {
|
|
33744
|
-
reactVersion: shouldUseReactFallback ? reactCatalogVersion ?? rootInfo.reactVersion ?? workspaceInfo.reactVersion : rootInfo.reactVersion ?? workspaceInfo.reactVersion,
|
|
33745
|
-
tailwindVersion: shouldUseTailwindFallback ? tailwindCatalogVersion ?? rootInfo.tailwindVersion ?? workspaceInfo.tailwindVersion : null,
|
|
33746
|
-
zodVersion: shouldUseZodFallback ? zodCatalogVersion ?? rootInfo.zodVersion ?? workspaceInfo.zodVersion : null,
|
|
33747
|
-
framework: rootInfo.framework !== "unknown" ? rootInfo.framework : workspaceInfo.framework
|
|
33748
|
-
};
|
|
33749
|
-
};
|
|
33750
|
-
const findInWorkspacePackageJsons = (rootDirectory, rootPackageJson, select) => {
|
|
33751
|
-
const rootValue = select(rootPackageJson);
|
|
33752
|
-
if (rootValue !== null) return rootValue;
|
|
33753
|
-
const patterns = getWorkspacePatterns(rootDirectory, rootPackageJson);
|
|
33754
|
-
if (patterns.length === 0) return null;
|
|
33755
|
-
const visitedDirectories = /* @__PURE__ */ new Set();
|
|
33756
|
-
for (const pattern of patterns) {
|
|
33757
|
-
const directories = [...resolveWorkspaceDirectories(rootDirectory, pattern)].sort();
|
|
33758
|
-
for (const workspaceDirectory of directories) {
|
|
33759
|
-
if (visitedDirectories.has(workspaceDirectory)) continue;
|
|
33760
|
-
visitedDirectories.add(workspaceDirectory);
|
|
33761
|
-
const value = select(readPackageJson(Path.join(workspaceDirectory, "package.json")));
|
|
33762
|
-
if (value !== null) return value;
|
|
33763
|
-
}
|
|
33826
|
+
/**
|
|
33827
|
+
* Walk up from `startDirectory` to the nearest ancestor that owns a
|
|
33828
|
+
* `package.json`, stopping at (and including) the enclosing project boundary
|
|
33829
|
+
* — the working tree's git root or a monorepo root. Lets a scan of a package
|
|
33830
|
+
* subfolder that has no `package.json` of its own adopt the nearest enclosing
|
|
33831
|
+
* package as its project root, so it inherits that package's dependency +
|
|
33832
|
+
* framework detection instead of synthesizing an empty (React-blind) project.
|
|
33833
|
+
*
|
|
33834
|
+
* Generalizes the older monorepo-root-only lookup: the nearest ancestor is the
|
|
33835
|
+
* most specific owning package (a leaf workspace, not just the repo root), so
|
|
33836
|
+
* a subfolder of one workspace no longer borrows a sibling's capabilities.
|
|
33837
|
+
*
|
|
33838
|
+
* Returns `null` when no ancestor `package.json` exists inside the project
|
|
33839
|
+
* boundary (a loose tree of files, or a subfolder of a package-less repo).
|
|
33840
|
+
*/
|
|
33841
|
+
const findNearestAncestorPackageJson = (startDirectory) => {
|
|
33842
|
+
if (isProjectBoundary(startDirectory)) return null;
|
|
33843
|
+
for (const directory of ancestorDirectories(startDirectory, { includeStart: false })) {
|
|
33844
|
+
if (isFile(Path.join(directory, "package.json"))) return directory;
|
|
33845
|
+
if (isProjectBoundary(directory)) return null;
|
|
33764
33846
|
}
|
|
33765
33847
|
return null;
|
|
33766
33848
|
};
|
|
33767
|
-
const someWorkspacePackageJson = (rootDirectory, rootPackageJson, predicate) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => predicate(packageJson) ? true : null) !== null;
|
|
33768
33849
|
const NAMES = new Set([
|
|
33769
33850
|
"react-native",
|
|
33770
33851
|
"react-native-tvos",
|
|
@@ -33797,47 +33878,7 @@ const isPackageJsonReactNativeAware = (packageJson) => {
|
|
|
33797
33878
|
if (containsAnyReactNativeDependency(packageJson.optionalDependencies)) return true;
|
|
33798
33879
|
return false;
|
|
33799
33880
|
};
|
|
33800
|
-
const
|
|
33801
|
-
const getDependencySpec = (packageJson, packageName) => {
|
|
33802
|
-
const spec = packageJson.dependencies?.[packageName] ?? packageJson.devDependencies?.[packageName] ?? packageJson.peerDependencies?.[packageName] ?? packageJson.optionalDependencies?.[packageName];
|
|
33803
|
-
return typeof spec === "string" ? spec : null;
|
|
33804
|
-
};
|
|
33805
|
-
const findExpoVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, "expo"));
|
|
33806
|
-
const SHOPIFY_FLASH_LIST_PACKAGE_NAME = "@shopify/flash-list";
|
|
33807
|
-
const findShopifyFlashListVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, SHOPIFY_FLASH_LIST_PACKAGE_NAME));
|
|
33808
|
-
const resolveCatalogBackedDependencyVersion = ({ rootDirectory, rootPackageJson, packageName, version }) => {
|
|
33809
|
-
if (version === null || !isCatalogReference(version)) return version;
|
|
33810
|
-
const catalogName = extractCatalogName(version);
|
|
33811
|
-
const resolvedLocalVersion = resolveCatalogVersion(rootPackageJson, packageName, rootDirectory, catalogName);
|
|
33812
|
-
if (resolvedLocalVersion) return resolvedLocalVersion;
|
|
33813
|
-
const monorepoRoot = findMonorepoRoot(rootDirectory);
|
|
33814
|
-
if (!monorepoRoot) return version;
|
|
33815
|
-
const monorepoPackageJsonPath = path.join(monorepoRoot, "package.json");
|
|
33816
|
-
if (!isFile(monorepoPackageJsonPath)) return version;
|
|
33817
|
-
return resolveCatalogVersion(readPackageJson(monorepoPackageJsonPath), packageName, monorepoRoot, catalogName) ?? version;
|
|
33818
|
-
};
|
|
33819
|
-
const findNextjsVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, "next"));
|
|
33820
|
-
const getPreactVersion = (packageJson) => {
|
|
33821
|
-
return {
|
|
33822
|
-
...packageJson.peerDependencies,
|
|
33823
|
-
...packageJson.dependencies,
|
|
33824
|
-
...packageJson.devDependencies
|
|
33825
|
-
}.preact ?? null;
|
|
33826
|
-
};
|
|
33827
|
-
const TANSTACK_QUERY_PACKAGES = new Set([
|
|
33828
|
-
"@tanstack/react-query",
|
|
33829
|
-
"@tanstack/query-core",
|
|
33830
|
-
"react-query"
|
|
33831
|
-
]);
|
|
33832
|
-
const hasTanStackQuery = (packageJson) => {
|
|
33833
|
-
const allDependencies = {
|
|
33834
|
-
...packageJson.peerDependencies,
|
|
33835
|
-
...packageJson.dependencies,
|
|
33836
|
-
...packageJson.devDependencies
|
|
33837
|
-
};
|
|
33838
|
-
return Object.keys(allDependencies).some((packageName) => TANSTACK_QUERY_PACKAGES.has(packageName));
|
|
33839
|
-
};
|
|
33840
|
-
const REANIMATED_DEPENDENCY_NAME = "react-native-reanimated";
|
|
33881
|
+
const REANIMATED_DEPENDENCY_NAME$1 = "react-native-reanimated";
|
|
33841
33882
|
const isPackageJsonReanimatedAware = (packageJson) => {
|
|
33842
33883
|
const allDependencies = {
|
|
33843
33884
|
...packageJson.peerDependencies,
|
|
@@ -33845,144 +33886,70 @@ const isPackageJsonReanimatedAware = (packageJson) => {
|
|
|
33845
33886
|
...packageJson.devDependencies,
|
|
33846
33887
|
...packageJson.optionalDependencies
|
|
33847
33888
|
};
|
|
33848
|
-
return Object.hasOwn(allDependencies, REANIMATED_DEPENDENCY_NAME);
|
|
33849
|
-
};
|
|
33850
|
-
const parseZodMajor = (zodVersion) => {
|
|
33851
|
-
if (typeof zodVersion !== "string") return null;
|
|
33852
|
-
return getLowestDependencyMajor(zodVersion);
|
|
33889
|
+
return Object.hasOwn(allDependencies, REANIMATED_DEPENDENCY_NAME$1);
|
|
33853
33890
|
};
|
|
33854
|
-
const
|
|
33855
|
-
|
|
33856
|
-
|
|
33857
|
-
if (
|
|
33858
|
-
|
|
33859
|
-
|
|
33860
|
-
|
|
33861
|
-
|
|
33862
|
-
};
|
|
33863
|
-
const peerRangeMinMajor = (range) => {
|
|
33864
|
-
if (typeof range !== "string") return null;
|
|
33865
|
-
return getLowestDependencyMajor(range);
|
|
33866
|
-
};
|
|
33867
|
-
const resolveEffectiveReactMajor = (reactVersion, packageJson) => {
|
|
33868
|
-
const installedReactMajor = parseReactMajor(reactVersion);
|
|
33869
|
-
const peerReactRange = packageJson.peerDependencies?.react;
|
|
33870
|
-
if (typeof peerReactRange !== "string") return installedReactMajor;
|
|
33871
|
-
const peerFloor = peerRangeMinMajor(peerReactRange);
|
|
33872
|
-
if (peerFloor === null) return hasUpperBoundOnlyPeerRange(peerReactRange) ? null : installedReactMajor;
|
|
33873
|
-
return installedReactMajor !== null ? Math.min(installedReactMajor, peerFloor) : peerFloor;
|
|
33891
|
+
const getWorkspacePatterns = (rootDirectory, packageJson) => {
|
|
33892
|
+
const pnpmPatterns = parsePnpmWorkspacePatterns(rootDirectory);
|
|
33893
|
+
if (pnpmPatterns.length > 0) return pnpmPatterns;
|
|
33894
|
+
if (Array.isArray(packageJson.workspaces)) return packageJson.workspaces;
|
|
33895
|
+
if (packageJson.workspaces?.packages) return packageJson.workspaces.packages;
|
|
33896
|
+
const nxPatterns = getNxWorkspaceDirectories(rootDirectory);
|
|
33897
|
+
if (nxPatterns.length > 0) return nxPatterns;
|
|
33898
|
+
return [];
|
|
33874
33899
|
};
|
|
33875
|
-
const
|
|
33876
|
-
const
|
|
33877
|
-
|
|
33878
|
-
const
|
|
33879
|
-
const
|
|
33880
|
-
|
|
33881
|
-
|
|
33882
|
-
|
|
33883
|
-
|
|
33884
|
-
|
|
33885
|
-
|
|
33886
|
-
|
|
33887
|
-
|
|
33888
|
-
|
|
33900
|
+
const parsePnpmWorkspacePatterns = (rootDirectory) => {
|
|
33901
|
+
const workspacePath = Path.join(rootDirectory, "pnpm-workspace.yaml");
|
|
33902
|
+
if (!isFile(workspacePath)) return [];
|
|
33903
|
+
const content = NFS.readFileSync(workspacePath, "utf-8");
|
|
33904
|
+
const patterns = [];
|
|
33905
|
+
let isInsidePackagesBlock = false;
|
|
33906
|
+
for (const line of content.split("\n")) {
|
|
33907
|
+
const trimmed = line.trim();
|
|
33908
|
+
if (trimmed === "packages:") {
|
|
33909
|
+
isInsidePackagesBlock = true;
|
|
33910
|
+
continue;
|
|
33911
|
+
}
|
|
33912
|
+
if (isInsidePackagesBlock && trimmed.startsWith("-")) patterns.push(trimmed.replace(/^-\s*/, "").replace(/["']/g, ""));
|
|
33913
|
+
else if (isInsidePackagesBlock && trimmed.length > 0 && !trimmed.startsWith("#")) isInsidePackagesBlock = false;
|
|
33889
33914
|
}
|
|
33890
|
-
return
|
|
33891
|
-
};
|
|
33892
|
-
const resolveExtendsPath = (extendsValue, fromConfigDirectory) => {
|
|
33893
|
-
if (isRelativeExtendsValue(extendsValue)) return ensureJsonExtension(Path.resolve(fromConfigDirectory, extendsValue));
|
|
33894
|
-
return resolvePackageExtendsPath(extendsValue, fromConfigDirectory);
|
|
33895
|
-
};
|
|
33896
|
-
const normalizeCompilerOptions = (compilerOptions) => {
|
|
33897
|
-
if (!isPlainObject(compilerOptions)) return { hasExplicitLib: false };
|
|
33898
|
-
const target = typeof compilerOptions.target === "string" ? compilerOptions.target : void 0;
|
|
33899
|
-
const hasExplicitLib = Object.hasOwn(compilerOptions, "lib");
|
|
33900
|
-
return {
|
|
33901
|
-
target,
|
|
33902
|
-
lib: Array.isArray(compilerOptions.lib) ? compilerOptions.lib.filter((entry) => typeof entry === "string") : void 0,
|
|
33903
|
-
hasExplicitLib
|
|
33904
|
-
};
|
|
33915
|
+
return patterns;
|
|
33905
33916
|
};
|
|
33906
|
-
const
|
|
33907
|
-
|
|
33908
|
-
|
|
33909
|
-
|
|
33910
|
-
|
|
33911
|
-
|
|
33917
|
+
const NX_PROJECT_DISCOVERY_DIRS = [
|
|
33918
|
+
"apps",
|
|
33919
|
+
"libs",
|
|
33920
|
+
"packages"
|
|
33921
|
+
];
|
|
33922
|
+
const getNxWorkspaceDirectories = (rootDirectory) => {
|
|
33923
|
+
if (!isFile(Path.join(rootDirectory, "nx.json"))) return [];
|
|
33924
|
+
const collected = [];
|
|
33925
|
+
for (const candidate of NX_PROJECT_DISCOVERY_DIRS) {
|
|
33926
|
+
const candidatePath = Path.join(rootDirectory, candidate);
|
|
33927
|
+
if (!isDirectory(candidatePath)) continue;
|
|
33928
|
+
for (const entry of readDirectoryEntries(candidatePath)) {
|
|
33929
|
+
if (!entry.isDirectory()) continue;
|
|
33930
|
+
const projectDirectory = Path.join(candidatePath, entry.name);
|
|
33931
|
+
if (isFile(Path.join(projectDirectory, "project.json")) || isFile(Path.join(projectDirectory, "package.json"))) collected.push(`${candidate}/${entry.name}`);
|
|
33932
|
+
}
|
|
33912
33933
|
}
|
|
33913
|
-
|
|
33914
|
-
if (!isPlainObject(parsed.config)) return null;
|
|
33915
|
-
return {
|
|
33916
|
-
extends: typeof parsed.config.extends === "string" ? parsed.config.extends : void 0,
|
|
33917
|
-
compilerOptions: normalizeCompilerOptions(parsed.config.compilerOptions)
|
|
33918
|
-
};
|
|
33919
|
-
};
|
|
33920
|
-
const mergeCompilerOptions = (inherited, current) => {
|
|
33921
|
-
const target = current.target ?? inherited?.target;
|
|
33922
|
-
const hasExplicitLib = current.hasExplicitLib || Boolean(inherited?.hasExplicitLib);
|
|
33923
|
-
return {
|
|
33924
|
-
target,
|
|
33925
|
-
lib: current.hasExplicitLib ? current.lib : inherited?.lib,
|
|
33926
|
-
hasExplicitLib
|
|
33927
|
-
};
|
|
33934
|
+
return collected;
|
|
33928
33935
|
};
|
|
33929
|
-
const
|
|
33930
|
-
const
|
|
33931
|
-
if (
|
|
33932
|
-
|
|
33933
|
-
|
|
33934
|
-
|
|
33935
|
-
nextVisitedPaths.add(realPath);
|
|
33936
|
-
if (tsConfig.extends && extendsDepth < 8) {
|
|
33937
|
-
const parentPath = resolveExtendsPath(tsConfig.extends, Path.dirname(realPath));
|
|
33938
|
-
if (parentPath && isFile(parentPath)) return mergeCompilerOptions(readResolvedCompilerOptions(parentPath, extendsDepth + 1, nextVisitedPaths), tsConfig.compilerOptions);
|
|
33936
|
+
const resolveWorkspaceDirectories = (rootDirectory, pattern) => {
|
|
33937
|
+
const cleanPattern = pattern.replace(/["']/g, "").replace(/\/\*\*$/, "/*");
|
|
33938
|
+
if (!cleanPattern.includes("*")) {
|
|
33939
|
+
const directoryPath = Path.join(rootDirectory, cleanPattern);
|
|
33940
|
+
if (isDirectory(directoryPath) && isFile(Path.join(directoryPath, "package.json"))) return [directoryPath];
|
|
33941
|
+
return [];
|
|
33939
33942
|
}
|
|
33940
|
-
|
|
33941
|
-
|
|
33942
|
-
const
|
|
33943
|
-
|
|
33944
|
-
|
|
33945
|
-
|
|
33946
|
-
const
|
|
33947
|
-
|
|
33948
|
-
|
|
33949
|
-
|
|
33950
|
-
if (!esYearMatch) return false;
|
|
33951
|
-
if (Number(esYearMatch[1]) < 2023) return false;
|
|
33952
|
-
const component = esYearMatch[2];
|
|
33953
|
-
return component === void 0 || component === "array";
|
|
33954
|
-
};
|
|
33955
|
-
const libIncludesES2023 = (lib) => lib.some(libEntryIncludesES2023Array);
|
|
33956
|
-
const compilerOptionsArePreES2023 = (compilerOptions) => {
|
|
33957
|
-
if (compilerOptions.hasExplicitLib) return !libIncludesES2023(compilerOptions.lib ?? []);
|
|
33958
|
-
if (compilerOptions.target) return targetYearIsPreES2023(compilerOptions.target);
|
|
33959
|
-
return false;
|
|
33960
|
-
};
|
|
33961
|
-
const compilerOptionsDeclareTargetOrLib = (compilerOptions) => compilerOptions.hasExplicitLib || compilerOptions.target !== void 0;
|
|
33962
|
-
const detectPreES2023FromConfig = (tsConfigPath) => {
|
|
33963
|
-
const compilerOptions = readResolvedCompilerOptions(tsConfigPath, 0, /* @__PURE__ */ new Set());
|
|
33964
|
-
if (!compilerOptions) return false;
|
|
33965
|
-
if (!compilerOptionsDeclareTargetOrLib(compilerOptions)) return false;
|
|
33966
|
-
return compilerOptionsArePreES2023(compilerOptions);
|
|
33967
|
-
};
|
|
33968
|
-
const detectPreES2023Target = (directory) => {
|
|
33969
|
-
const tsConfigPath = Path.join(directory, TSCONFIG_FILENAME);
|
|
33970
|
-
if (isFile(tsConfigPath)) return detectPreES2023FromConfig(tsConfigPath);
|
|
33971
|
-
return false;
|
|
33972
|
-
};
|
|
33973
|
-
const REACT_DEPENDENCY_NAMES = new Set([
|
|
33974
|
-
"react",
|
|
33975
|
-
"react-native",
|
|
33976
|
-
"next",
|
|
33977
|
-
"preact"
|
|
33978
|
-
]);
|
|
33979
|
-
const hasReactDependency = (packageJson) => {
|
|
33980
|
-
const allDependencies = {
|
|
33981
|
-
...packageJson.peerDependencies,
|
|
33982
|
-
...packageJson.dependencies,
|
|
33983
|
-
...packageJson.devDependencies
|
|
33984
|
-
};
|
|
33985
|
-
return Object.keys(allDependencies).some((packageName) => REACT_DEPENDENCY_NAMES.has(packageName));
|
|
33943
|
+
const wildcardIndex = cleanPattern.indexOf("*");
|
|
33944
|
+
const baseDirectory = Path.join(rootDirectory, cleanPattern.slice(0, wildcardIndex));
|
|
33945
|
+
const suffixAfterWildcard = cleanPattern.slice(wildcardIndex + 1);
|
|
33946
|
+
if (!isDirectory(baseDirectory)) return [];
|
|
33947
|
+
const resolved = [];
|
|
33948
|
+
for (const entry of readDirectoryEntries(baseDirectory)) {
|
|
33949
|
+
const entryPath = Path.join(baseDirectory, entry.name, suffixAfterWildcard);
|
|
33950
|
+
if (isDirectory(entryPath) && isFile(Path.join(entryPath, "package.json"))) resolved.push(entryPath);
|
|
33951
|
+
}
|
|
33952
|
+
return resolved;
|
|
33986
33953
|
};
|
|
33987
33954
|
const listWorkspacePackages = (rootDirectory) => {
|
|
33988
33955
|
const packageJsonPath = Path.join(rootDirectory, "package.json");
|
|
@@ -34014,6 +33981,208 @@ const listWorkspacePackages = (rootDirectory) => {
|
|
|
34014
33981
|
}
|
|
34015
33982
|
return packages;
|
|
34016
33983
|
};
|
|
33984
|
+
const REANIMATED_DEPENDENCY_NAME = "react-native-reanimated";
|
|
33985
|
+
const SHOPIFY_FLASH_LIST_PACKAGE_NAME = "@shopify/flash-list";
|
|
33986
|
+
const resolveWorkspaceDependencyVersion = ({ concreteVersion, packageName, rootDirectory, rootPackageJson, sections, workspaceDirectory, workspacePackageJson }) => {
|
|
33987
|
+
const dependencyDeclaration = getDependencyDeclaration({
|
|
33988
|
+
packageJson: workspacePackageJson,
|
|
33989
|
+
packageName,
|
|
33990
|
+
sections
|
|
33991
|
+
});
|
|
33992
|
+
if (!dependencyDeclaration.hasDeclaration) return null;
|
|
33993
|
+
return concreteVersion ?? resolveCatalogVersion(workspacePackageJson, packageName, workspaceDirectory, dependencyDeclaration.catalogReference) ?? resolveCatalogVersion(rootPackageJson, packageName, rootDirectory, dependencyDeclaration.catalogReference);
|
|
33994
|
+
};
|
|
33995
|
+
const shouldReplaceReactVersion = (currentVersion, nextVersion) => {
|
|
33996
|
+
if (!currentVersion) return true;
|
|
33997
|
+
const currentMajor = parseReactMajor(currentVersion);
|
|
33998
|
+
const nextMajor = parseReactMajor(nextVersion);
|
|
33999
|
+
if (currentMajor === null) return nextMajor !== null;
|
|
34000
|
+
if (nextMajor === null) return false;
|
|
34001
|
+
return nextMajor < currentMajor;
|
|
34002
|
+
};
|
|
34003
|
+
const evaluateManifestFacts = (facts, packageJson, directory) => {
|
|
34004
|
+
if (facts.expo.version === null) {
|
|
34005
|
+
const spec = getDependencySpec(packageJson, "expo");
|
|
34006
|
+
if (spec !== null) facts.expo = {
|
|
34007
|
+
version: spec,
|
|
34008
|
+
sourceDirectory: directory
|
|
34009
|
+
};
|
|
34010
|
+
}
|
|
34011
|
+
if (facts.next.version === null) {
|
|
34012
|
+
const spec = getDependencySpec(packageJson, "next");
|
|
34013
|
+
if (spec !== null) facts.next = {
|
|
34014
|
+
version: spec,
|
|
34015
|
+
sourceDirectory: directory
|
|
34016
|
+
};
|
|
34017
|
+
}
|
|
34018
|
+
if (facts.shopifyFlashList.version === null) {
|
|
34019
|
+
const spec = getDependencySpec(packageJson, SHOPIFY_FLASH_LIST_PACKAGE_NAME);
|
|
34020
|
+
if (spec !== null) facts.shopifyFlashList = {
|
|
34021
|
+
version: spec,
|
|
34022
|
+
sourceDirectory: directory
|
|
34023
|
+
};
|
|
34024
|
+
}
|
|
34025
|
+
if (facts.reanimatedVersion === null) {
|
|
34026
|
+
const spec = getDependencySpec(packageJson, REANIMATED_DEPENDENCY_NAME);
|
|
34027
|
+
if (spec !== null) facts.reanimatedVersion = spec;
|
|
34028
|
+
}
|
|
34029
|
+
facts.hasReactNativeAwarePackage = facts.hasReactNativeAwarePackage || isPackageJsonReactNativeAware(packageJson);
|
|
34030
|
+
facts.hasReanimatedAwarePackage = facts.hasReanimatedAwarePackage || isPackageJsonReanimatedAware(packageJson);
|
|
34031
|
+
};
|
|
34032
|
+
const collectWorkspaceFacts = (rootDirectory, rootPackageJson, { collectReactGroup }) => {
|
|
34033
|
+
const facts = {
|
|
34034
|
+
reactVersion: null,
|
|
34035
|
+
tailwindVersion: null,
|
|
34036
|
+
zodVersion: null,
|
|
34037
|
+
framework: "unknown",
|
|
34038
|
+
expo: {
|
|
34039
|
+
version: null,
|
|
34040
|
+
sourceDirectory: null
|
|
34041
|
+
},
|
|
34042
|
+
next: {
|
|
34043
|
+
version: null,
|
|
34044
|
+
sourceDirectory: null
|
|
34045
|
+
},
|
|
34046
|
+
shopifyFlashList: {
|
|
34047
|
+
version: null,
|
|
34048
|
+
sourceDirectory: null
|
|
34049
|
+
},
|
|
34050
|
+
hasReactNativeAwarePackage: false,
|
|
34051
|
+
hasReanimatedAwarePackage: false,
|
|
34052
|
+
reanimatedVersion: null
|
|
34053
|
+
};
|
|
34054
|
+
evaluateManifestFacts(facts, rootPackageJson, rootDirectory);
|
|
34055
|
+
let isReactGroupSettled = !collectReactGroup;
|
|
34056
|
+
const visitedDirectories = /* @__PURE__ */ new Set();
|
|
34057
|
+
for (const pattern of getWorkspacePatterns(rootDirectory, rootPackageJson)) {
|
|
34058
|
+
const directories = [...resolveWorkspaceDirectories(rootDirectory, pattern)].sort();
|
|
34059
|
+
for (const workspaceDirectory of directories) {
|
|
34060
|
+
if (visitedDirectories.has(workspaceDirectory)) continue;
|
|
34061
|
+
visitedDirectories.add(workspaceDirectory);
|
|
34062
|
+
const workspacePackageJson = readPackageJson(Path.join(workspaceDirectory, "package.json"));
|
|
34063
|
+
evaluateManifestFacts(facts, workspacePackageJson, workspaceDirectory);
|
|
34064
|
+
const info = extractDependencyInfo(workspacePackageJson);
|
|
34065
|
+
if (info.framework !== "unknown" && frameworkMergeRank(info.framework) < frameworkMergeRank(facts.framework)) facts.framework = info.framework;
|
|
34066
|
+
if (isReactGroupSettled) continue;
|
|
34067
|
+
const reactVersion = resolveWorkspaceDependencyVersion({
|
|
34068
|
+
concreteVersion: info.reactVersion,
|
|
34069
|
+
packageName: "react",
|
|
34070
|
+
rootDirectory,
|
|
34071
|
+
rootPackageJson,
|
|
34072
|
+
sections: REACT_SECTIONS,
|
|
34073
|
+
workspaceDirectory,
|
|
34074
|
+
workspacePackageJson
|
|
34075
|
+
});
|
|
34076
|
+
const tailwindVersion = resolveWorkspaceDependencyVersion({
|
|
34077
|
+
concreteVersion: info.tailwindVersion,
|
|
34078
|
+
packageName: "tailwindcss",
|
|
34079
|
+
rootDirectory,
|
|
34080
|
+
rootPackageJson,
|
|
34081
|
+
sections: TAILWIND_ZOD_SECTIONS,
|
|
34082
|
+
workspaceDirectory,
|
|
34083
|
+
workspacePackageJson
|
|
34084
|
+
});
|
|
34085
|
+
const zodVersion = resolveWorkspaceDependencyVersion({
|
|
34086
|
+
concreteVersion: info.zodVersion,
|
|
34087
|
+
packageName: "zod",
|
|
34088
|
+
rootDirectory,
|
|
34089
|
+
rootPackageJson,
|
|
34090
|
+
sections: TAILWIND_ZOD_SECTIONS,
|
|
34091
|
+
workspaceDirectory,
|
|
34092
|
+
workspacePackageJson
|
|
34093
|
+
});
|
|
34094
|
+
if (reactVersion && shouldReplaceReactVersion(facts.reactVersion, reactVersion)) facts.reactVersion = reactVersion;
|
|
34095
|
+
if (tailwindVersion && !facts.tailwindVersion) facts.tailwindVersion = tailwindVersion;
|
|
34096
|
+
if (zodVersion && !facts.zodVersion) facts.zodVersion = zodVersion;
|
|
34097
|
+
const settledReactMajor = parseReactMajor(facts.reactVersion);
|
|
34098
|
+
isReactGroupSettled = Boolean(facts.reactVersion && facts.tailwindVersion && facts.framework !== "unknown" && settledReactMajor !== null && settledReactMajor <= 17);
|
|
34099
|
+
}
|
|
34100
|
+
}
|
|
34101
|
+
return facts;
|
|
34102
|
+
};
|
|
34103
|
+
const findDependencyInfoFromMonorepoRoot = (directory) => {
|
|
34104
|
+
const monorepoRoot = findMonorepoRoot(directory);
|
|
34105
|
+
if (!monorepoRoot) return EMPTY_DEPENDENCY_INFO;
|
|
34106
|
+
const monorepoPackageJsonPath = Path.join(monorepoRoot, "package.json");
|
|
34107
|
+
if (!isFile(monorepoPackageJsonPath)) return EMPTY_DEPENDENCY_INFO;
|
|
34108
|
+
const rootPackageJson = readPackageJson(monorepoPackageJsonPath);
|
|
34109
|
+
const rootInfo = extractDependencyInfo(rootPackageJson);
|
|
34110
|
+
const leafPackageJsonPath = Path.join(directory, "package.json");
|
|
34111
|
+
const leafPackageJson = isFile(leafPackageJsonPath) ? readPackageJson(leafPackageJsonPath) : null;
|
|
34112
|
+
const leafReactDeclaration = leafPackageJson ? getDependencyDeclaration({
|
|
34113
|
+
packageJson: leafPackageJson,
|
|
34114
|
+
packageName: "react",
|
|
34115
|
+
sections: REACT_SECTIONS
|
|
34116
|
+
}) : null;
|
|
34117
|
+
const leafTailwindDeclaration = leafPackageJson ? getDependencyDeclaration({
|
|
34118
|
+
packageJson: leafPackageJson,
|
|
34119
|
+
packageName: "tailwindcss",
|
|
34120
|
+
sections: TAILWIND_ZOD_SECTIONS
|
|
34121
|
+
}) : null;
|
|
34122
|
+
const leafZodDeclaration = leafPackageJson ? getDependencyDeclaration({
|
|
34123
|
+
packageJson: leafPackageJson,
|
|
34124
|
+
packageName: "zod",
|
|
34125
|
+
sections: TAILWIND_ZOD_SECTIONS
|
|
34126
|
+
}) : null;
|
|
34127
|
+
const shouldUseReactFallback = !leafReactDeclaration?.hasDeclaration;
|
|
34128
|
+
const shouldUseTailwindFallback = leafTailwindDeclaration?.hasDeclaration ?? true;
|
|
34129
|
+
const shouldUseZodFallback = leafZodDeclaration?.hasDeclaration ?? true;
|
|
34130
|
+
const reactCatalogVersion = shouldUseReactFallback ? resolveCatalogVersion(rootPackageJson, "react", monorepoRoot, leafReactDeclaration?.catalogReference) : null;
|
|
34131
|
+
const tailwindCatalogVersion = shouldUseTailwindFallback ? resolveCatalogVersion(rootPackageJson, "tailwindcss", monorepoRoot, leafTailwindDeclaration?.catalogReference) : null;
|
|
34132
|
+
const zodCatalogVersion = shouldUseZodFallback ? resolveCatalogVersion(rootPackageJson, "zod", monorepoRoot, leafZodDeclaration?.catalogReference) : null;
|
|
34133
|
+
const workspaceFacts = collectWorkspaceFacts(monorepoRoot, rootPackageJson, { collectReactGroup: true });
|
|
34134
|
+
return {
|
|
34135
|
+
reactVersion: shouldUseReactFallback ? reactCatalogVersion ?? rootInfo.reactVersion ?? workspaceFacts.reactVersion : rootInfo.reactVersion ?? workspaceFacts.reactVersion,
|
|
34136
|
+
tailwindVersion: shouldUseTailwindFallback ? tailwindCatalogVersion ?? rootInfo.tailwindVersion ?? workspaceFacts.tailwindVersion : null,
|
|
34137
|
+
zodVersion: shouldUseZodFallback ? zodCatalogVersion ?? rootInfo.zodVersion ?? workspaceFacts.zodVersion : null,
|
|
34138
|
+
framework: rootInfo.framework !== "unknown" ? rootInfo.framework : workspaceFacts.framework
|
|
34139
|
+
};
|
|
34140
|
+
};
|
|
34141
|
+
/**
|
|
34142
|
+
* Last-resort React detection: locate React the way Node's `node_modules`
|
|
34143
|
+
* resolution would from `directory` — check each `node_modules/react` up the
|
|
34144
|
+
* tree — but bounded to the enclosing repo so a globally installed React can't
|
|
34145
|
+
* masquerade as the project's version. Stops at (and including) the nearest
|
|
34146
|
+
* project boundary — the git root or a monorepo root — and returns the first
|
|
34147
|
+
* installed `version`.
|
|
34148
|
+
*
|
|
34149
|
+
* Makes "React is installed and importable" ⇒ "React is detected" an invariant
|
|
34150
|
+
* for packages whose only React declaration is a version-less spec
|
|
34151
|
+
* (`workspace:*`, `*`, a dist-tag) or where React lives solely in a hoisted
|
|
34152
|
+
* `node_modules` the declaration walks never reach — the profile of a component
|
|
34153
|
+
* package inside a monorepo. Matching the `node_modules/react` entry rather than
|
|
34154
|
+
* its realpath means a project whose `node_modules` is symlinked elsewhere (a
|
|
34155
|
+
* Docker volume, a shared store) still counts, while a global install outside
|
|
34156
|
+
* the repo does not.
|
|
34157
|
+
*
|
|
34158
|
+
* Returns `null` when React isn't installed within the repo or its package.json
|
|
34159
|
+
* carries no version string. A tree with no boundary marker at all bounds the
|
|
34160
|
+
* search to `directory`, so a dependency hoisted above the scanned package there
|
|
34161
|
+
* is conservatively not adopted.
|
|
34162
|
+
*/
|
|
34163
|
+
/**
|
|
34164
|
+
* The nearest enclosing project boundary (git root or monorepo root), or
|
|
34165
|
+
* `directory` itself when the scan target sits outside any repo. Bounds the
|
|
34166
|
+
* node_modules walk so a React hoisted anywhere inside the repo counts, but a
|
|
34167
|
+
* React in an ancestor above the repo — a home-directory or global
|
|
34168
|
+
* `node_modules` — can't leak in.
|
|
34169
|
+
*/
|
|
34170
|
+
const findContainmentRoot = (directory) => {
|
|
34171
|
+
for (const ancestorDirectory of ancestorDirectories(directory, { includeStart: true })) if (isProjectBoundary(ancestorDirectory)) return ancestorDirectory;
|
|
34172
|
+
return directory;
|
|
34173
|
+
};
|
|
34174
|
+
const resolveInstalledReactVersion = (directory) => {
|
|
34175
|
+
const containmentRoot = findContainmentRoot(directory);
|
|
34176
|
+
for (const ancestorDirectory of ancestorDirectories(directory, { includeStart: true })) {
|
|
34177
|
+
const reactPackageJsonPath = Path.join(ancestorDirectory, "node_modules", "react", "package.json");
|
|
34178
|
+
if (isFile(reactPackageJsonPath)) {
|
|
34179
|
+
const installedVersion = readPackageJson(reactPackageJsonPath).version;
|
|
34180
|
+
return typeof installedVersion === "string" ? installedVersion : null;
|
|
34181
|
+
}
|
|
34182
|
+
if (ancestorDirectory === containmentRoot) return null;
|
|
34183
|
+
}
|
|
34184
|
+
return null;
|
|
34185
|
+
};
|
|
34017
34186
|
const toReactWorkspacePackages = (directories) => {
|
|
34018
34187
|
const packages = [];
|
|
34019
34188
|
for (const directory of directories) {
|
|
@@ -34097,8 +34266,8 @@ const clearProjectCache = () => {
|
|
|
34097
34266
|
const discoverProjectWithoutPackageJson = (directory) => {
|
|
34098
34267
|
const sourceFileCount = countSourceFiles(directory);
|
|
34099
34268
|
const hasOwnTsConfig = NFS.existsSync(Path.join(directory, "tsconfig.json"));
|
|
34100
|
-
const
|
|
34101
|
-
const enclosingProject =
|
|
34269
|
+
const enclosingProjectRoot = findNearestAncestorPackageJson(directory);
|
|
34270
|
+
const enclosingProject = enclosingProjectRoot !== null ? discoverProject(enclosingProjectRoot) : null;
|
|
34102
34271
|
if (enclosingProject !== null) return {
|
|
34103
34272
|
...enclosingProject,
|
|
34104
34273
|
rootDirectory: directory,
|
|
@@ -34128,7 +34297,9 @@ const discoverProjectWithoutPackageJson = (directory) => {
|
|
|
34128
34297
|
shopifyFlashListVersion: null,
|
|
34129
34298
|
shopifyFlashListMajorVersion: null,
|
|
34130
34299
|
hasReanimated: false,
|
|
34300
|
+
reanimatedVersion: null,
|
|
34131
34301
|
isPreES2023Target: hasOwnTsConfig && detectPreES2023Target(directory),
|
|
34302
|
+
isStaticExport: false,
|
|
34132
34303
|
sourceFileCount
|
|
34133
34304
|
};
|
|
34134
34305
|
};
|
|
@@ -34142,88 +34313,85 @@ const discoverProject = (directory) => {
|
|
|
34142
34313
|
return synthesized;
|
|
34143
34314
|
}
|
|
34144
34315
|
const packageJson = readPackageJson(packageJsonPath);
|
|
34145
|
-
|
|
34146
|
-
|
|
34147
|
-
|
|
34148
|
-
|
|
34149
|
-
|
|
34150
|
-
|
|
34151
|
-
|
|
34152
|
-
|
|
34153
|
-
|
|
34154
|
-
|
|
34155
|
-
|
|
34156
|
-
|
|
34157
|
-
|
|
34158
|
-
|
|
34159
|
-
|
|
34160
|
-
|
|
34161
|
-
|
|
34162
|
-
]
|
|
34163
|
-
});
|
|
34164
|
-
const zodDeclaration = getDependencyDeclaration({
|
|
34316
|
+
const rootInfo = extractDependencyInfo(packageJson);
|
|
34317
|
+
let framework = rootInfo.framework;
|
|
34318
|
+
const tracked = {
|
|
34319
|
+
react: {
|
|
34320
|
+
version: rootInfo.reactVersion,
|
|
34321
|
+
sections: REACT_SECTIONS
|
|
34322
|
+
},
|
|
34323
|
+
tailwindcss: {
|
|
34324
|
+
version: rootInfo.tailwindVersion,
|
|
34325
|
+
sections: TAILWIND_ZOD_SECTIONS
|
|
34326
|
+
},
|
|
34327
|
+
zod: {
|
|
34328
|
+
version: rootInfo.zodVersion,
|
|
34329
|
+
sections: TAILWIND_ZOD_SECTIONS
|
|
34330
|
+
}
|
|
34331
|
+
};
|
|
34332
|
+
const declarations = Object.fromEntries(Object.entries(tracked).map(([packageName, entry]) => [packageName, getDependencyDeclaration({
|
|
34165
34333
|
packageJson,
|
|
34166
|
-
packageName
|
|
34167
|
-
sections:
|
|
34168
|
-
|
|
34169
|
-
|
|
34170
|
-
|
|
34171
|
-
|
|
34172
|
-
|
|
34173
|
-
if (!
|
|
34174
|
-
if (!tailwindVersion && tailwindDeclaration.hasDeclaration) tailwindVersion = resolveCatalogVersion(packageJson, "tailwindcss", directory, tailwindDeclaration.catalogReference);
|
|
34175
|
-
if (!zodVersion && zodDeclaration.hasDeclaration) zodVersion = resolveCatalogVersion(packageJson, "zod", directory, zodDeclaration.catalogReference);
|
|
34176
|
-
if (!reactVersion || !tailwindVersion || !zodVersion) {
|
|
34334
|
+
packageName,
|
|
34335
|
+
sections: entry.sections
|
|
34336
|
+
})]));
|
|
34337
|
+
const fillFromCatalogs = (source, sourceDirectory) => {
|
|
34338
|
+
for (const [packageName, entry] of Object.entries(tracked)) if (!entry.version && declarations[packageName].hasDeclaration) entry.version = resolveCatalogVersion(source, packageName, sourceDirectory, declarations[packageName].catalogReference);
|
|
34339
|
+
};
|
|
34340
|
+
fillFromCatalogs(packageJson, directory);
|
|
34341
|
+
if (!tracked.react.version || !tracked.tailwindcss.version || !tracked.zod.version) {
|
|
34177
34342
|
const monorepoRoot = findMonorepoRoot(directory);
|
|
34178
34343
|
if (monorepoRoot) {
|
|
34179
34344
|
const monorepoPackageJsonPath = Path.join(monorepoRoot, "package.json");
|
|
34180
|
-
if (isFile(monorepoPackageJsonPath))
|
|
34181
|
-
const rootPackageJson = readPackageJson(monorepoPackageJsonPath);
|
|
34182
|
-
if (!reactVersion && reactDeclaration.hasDeclaration) reactVersion = resolveCatalogVersion(rootPackageJson, "react", monorepoRoot, reactDeclaration.catalogReference);
|
|
34183
|
-
if (!tailwindVersion && tailwindDeclaration.hasDeclaration) tailwindVersion = resolveCatalogVersion(rootPackageJson, "tailwindcss", monorepoRoot, tailwindDeclaration.catalogReference);
|
|
34184
|
-
if (!zodVersion && zodDeclaration.hasDeclaration) zodVersion = resolveCatalogVersion(rootPackageJson, "zod", monorepoRoot, zodDeclaration.catalogReference);
|
|
34185
|
-
}
|
|
34345
|
+
if (isFile(monorepoPackageJsonPath)) fillFromCatalogs(readPackageJson(monorepoPackageJsonPath), monorepoRoot);
|
|
34186
34346
|
}
|
|
34187
34347
|
}
|
|
34188
|
-
|
|
34189
|
-
|
|
34190
|
-
|
|
34191
|
-
|
|
34192
|
-
|
|
34193
|
-
|
|
34348
|
+
const shouldCollectReactGroup = !tracked.react.version || framework === "unknown";
|
|
34349
|
+
const workspaceFacts = collectWorkspaceFacts(directory, packageJson, { collectReactGroup: shouldCollectReactGroup });
|
|
34350
|
+
if (shouldCollectReactGroup) {
|
|
34351
|
+
tracked.react.version ||= workspaceFacts.reactVersion;
|
|
34352
|
+
tracked.tailwindcss.version ||= workspaceFacts.tailwindVersion;
|
|
34353
|
+
tracked.zod.version ||= workspaceFacts.zodVersion;
|
|
34354
|
+
if (framework === "unknown" && workspaceFacts.framework !== "unknown") framework = workspaceFacts.framework;
|
|
34194
34355
|
}
|
|
34195
|
-
if ((!
|
|
34356
|
+
if ((!tracked.react.version || framework === "unknown") && !isMonorepoRoot(directory)) {
|
|
34196
34357
|
const monorepoInfo = findDependencyInfoFromMonorepoRoot(directory);
|
|
34197
|
-
|
|
34198
|
-
|
|
34199
|
-
|
|
34358
|
+
tracked.react.version ||= monorepoInfo.reactVersion;
|
|
34359
|
+
tracked.tailwindcss.version ||= monorepoInfo.tailwindVersion;
|
|
34360
|
+
tracked.zod.version ||= monorepoInfo.zodVersion;
|
|
34200
34361
|
if (framework === "unknown") framework = monorepoInfo.framework;
|
|
34201
34362
|
}
|
|
34202
|
-
|
|
34203
|
-
|
|
34204
|
-
|
|
34363
|
+
for (const [packageName, entry] of Object.entries(tracked)) {
|
|
34364
|
+
const declaredVersion = declarations[packageName].version;
|
|
34365
|
+
if (!entry.version && declaredVersion && !isCatalogReference(declaredVersion)) entry.version = declaredVersion;
|
|
34366
|
+
}
|
|
34367
|
+
const { react, tailwindcss, zod } = tracked;
|
|
34368
|
+
let reactVersion = react.version;
|
|
34369
|
+
if (!reactVersion || parseReactMajor(reactVersion) === null) reactVersion = resolveInstalledReactVersion(directory) ?? reactVersion;
|
|
34370
|
+
const tailwindVersion = tailwindcss.version;
|
|
34371
|
+
const zodVersion = zod.version;
|
|
34205
34372
|
const projectName = packageJson.name ?? Path.basename(directory);
|
|
34206
34373
|
const hasTypeScript = NFS.existsSync(Path.join(directory, "tsconfig.json"));
|
|
34207
34374
|
const sourceFileCount = countSourceFiles(directory);
|
|
34208
|
-
const hasReactNativeWorkspace = framework === "expo" || framework === "react-native" ||
|
|
34375
|
+
const hasReactNativeWorkspace = framework === "expo" || framework === "react-native" || workspaceFacts.hasReactNativeAwarePackage;
|
|
34209
34376
|
const expoVersion = hasReactNativeWorkspace ? resolveCatalogBackedDependencyVersion({
|
|
34210
34377
|
rootDirectory: directory,
|
|
34211
34378
|
rootPackageJson: packageJson,
|
|
34212
34379
|
packageName: "expo",
|
|
34213
|
-
version:
|
|
34380
|
+
version: workspaceFacts.expo.version
|
|
34214
34381
|
}) : null;
|
|
34215
34382
|
const shopifyFlashListVersion = hasReactNativeWorkspace ? resolveCatalogBackedDependencyVersion({
|
|
34216
34383
|
rootDirectory: directory,
|
|
34217
34384
|
rootPackageJson: packageJson,
|
|
34218
34385
|
packageName: SHOPIFY_FLASH_LIST_PACKAGE_NAME,
|
|
34219
|
-
version:
|
|
34386
|
+
version: workspaceFacts.shopifyFlashList.version
|
|
34220
34387
|
}) : null;
|
|
34221
|
-
const hasReanimated = hasReactNativeWorkspace &&
|
|
34388
|
+
const hasReanimated = hasReactNativeWorkspace && workspaceFacts.hasReanimatedAwarePackage;
|
|
34389
|
+
const reanimatedVersion = hasReanimated ? workspaceFacts.reanimatedVersion : null;
|
|
34222
34390
|
const nextjsVersion = framework === "nextjs" ? resolveCatalogBackedDependencyVersion({
|
|
34223
34391
|
rootDirectory: directory,
|
|
34224
34392
|
rootPackageJson: packageJson,
|
|
34225
34393
|
packageName: "next",
|
|
34226
|
-
version:
|
|
34394
|
+
version: workspaceFacts.next.version
|
|
34227
34395
|
}) : null;
|
|
34228
34396
|
const preactVersion = getPreactVersion(packageJson);
|
|
34229
34397
|
const isPreES2023Target = hasTypeScript && detectPreES2023Target(directory);
|
|
@@ -34234,7 +34402,7 @@ const discoverProject = (directory) => {
|
|
|
34234
34402
|
reactMajorVersion: resolveEffectiveReactMajor(reactVersion, packageJson),
|
|
34235
34403
|
tailwindVersion,
|
|
34236
34404
|
zodVersion,
|
|
34237
|
-
zodMajorVersion:
|
|
34405
|
+
zodMajorVersion: zodVersion === null ? null : getLowestDependencyMajor(zodVersion),
|
|
34238
34406
|
framework,
|
|
34239
34407
|
hasTypeScript,
|
|
34240
34408
|
hasReactCompiler: detectReactCompiler(directory, packageJson),
|
|
@@ -34248,7 +34416,9 @@ const discoverProject = (directory) => {
|
|
|
34248
34416
|
shopifyFlashListVersion,
|
|
34249
34417
|
shopifyFlashListMajorVersion: shopifyFlashListVersion === null ? null : getLowestDependencyMajor(shopifyFlashListVersion),
|
|
34250
34418
|
hasReanimated,
|
|
34419
|
+
reanimatedVersion,
|
|
34251
34420
|
isPreES2023Target,
|
|
34421
|
+
isStaticExport: framework === "nextjs" && detectNextjsStaticExport(workspaceFacts.next.sourceDirectory ?? directory),
|
|
34252
34422
|
sourceFileCount
|
|
34253
34423
|
};
|
|
34254
34424
|
cachedProjectInfos.set(directory, projectInfo);
|
|
@@ -34264,57 +34434,6 @@ const discoverProject = (directory) => {
|
|
|
34264
34434
|
* single source file has nothing to analyze.
|
|
34265
34435
|
*/
|
|
34266
34436
|
const isAnalyzableProject = (project) => project.reactVersion !== null || project.preactVersion !== null || project.sourceFileCount > 0;
|
|
34267
|
-
const MAJOR_MINOR_PATTERN = /(\d{1,4})\.(\d{1,4})/;
|
|
34268
|
-
const MAJOR_ONLY_PATTERN = /(\d{1,4})/;
|
|
34269
|
-
const UPPER_BOUND_COMPARATOR_PATTERN = /<=?\s{0,8}\d{1,4}(?:\.\d{1,4}){0,2}(?:-[^\s,|]+)?/g;
|
|
34270
|
-
const parseReactMajorMinor = (reactVersion) => {
|
|
34271
|
-
if (typeof reactVersion !== "string") return null;
|
|
34272
|
-
const trimmed = reactVersion.trim();
|
|
34273
|
-
if (trimmed.length === 0) return null;
|
|
34274
|
-
const lowerBoundsOnly = trimmed.replace(UPPER_BOUND_COMPARATOR_PATTERN, " ").trim();
|
|
34275
|
-
if (lowerBoundsOnly.length === 0) return null;
|
|
34276
|
-
const majorMinorMatch = lowerBoundsOnly.match(MAJOR_MINOR_PATTERN);
|
|
34277
|
-
if (majorMinorMatch) {
|
|
34278
|
-
const major = Number.parseInt(majorMinorMatch[1], 10);
|
|
34279
|
-
const minor = Number.parseInt(majorMinorMatch[2], 10);
|
|
34280
|
-
if (!Number.isFinite(major) || major <= 0) return null;
|
|
34281
|
-
if (!Number.isFinite(minor) || minor < 0) return null;
|
|
34282
|
-
return {
|
|
34283
|
-
major,
|
|
34284
|
-
minor
|
|
34285
|
-
};
|
|
34286
|
-
}
|
|
34287
|
-
const majorOnlyMatch = lowerBoundsOnly.match(MAJOR_ONLY_PATTERN);
|
|
34288
|
-
if (!majorOnlyMatch) return null;
|
|
34289
|
-
const major = Number.parseInt(majorOnlyMatch[1], 10);
|
|
34290
|
-
if (!Number.isFinite(major) || major <= 0) return null;
|
|
34291
|
-
return {
|
|
34292
|
-
major,
|
|
34293
|
-
minor: 0
|
|
34294
|
-
};
|
|
34295
|
-
};
|
|
34296
|
-
const isReactAtLeast = (detected, required) => {
|
|
34297
|
-
if (detected === null) return true;
|
|
34298
|
-
if (detected.major !== required.major) return detected.major > required.major;
|
|
34299
|
-
return detected.minor >= required.minor;
|
|
34300
|
-
};
|
|
34301
|
-
const parseLowerBoundVersion = (versionSpec) => import_semver.validRange(versionSpec) !== null ? import_semver.minVersion(versionSpec) : import_semver.coerce(versionSpec);
|
|
34302
|
-
const parseTailwindMajorMinor = (tailwindVersion) => {
|
|
34303
|
-
if (typeof tailwindVersion !== "string") return null;
|
|
34304
|
-
const trimmed = tailwindVersion.trim();
|
|
34305
|
-
if (trimmed.length === 0) return null;
|
|
34306
|
-
const lowerBound = parseLowerBoundVersion(trimmed);
|
|
34307
|
-
if (lowerBound === null || lowerBound.major <= 0) return null;
|
|
34308
|
-
return {
|
|
34309
|
-
major: lowerBound.major,
|
|
34310
|
-
minor: lowerBound.minor
|
|
34311
|
-
};
|
|
34312
|
-
};
|
|
34313
|
-
const isTailwindAtLeast = (detected, required) => {
|
|
34314
|
-
if (detected === null) return true;
|
|
34315
|
-
if (detected.major !== required.major) return detected.major > required.major;
|
|
34316
|
-
return detected.minor >= required.minor;
|
|
34317
|
-
};
|
|
34318
34437
|
const messageFromUnknown = (error) => error instanceof Error ? error.message : String(error);
|
|
34319
34438
|
var InvalidGlobPatternError = class extends Error {
|
|
34320
34439
|
pattern;
|
|
@@ -34440,6 +34559,9 @@ const buildRuleSeverityControls = (config) => {
|
|
|
34440
34559
|
...config.buckets !== void 0 ? { buckets: config.buckets } : {}
|
|
34441
34560
|
};
|
|
34442
34561
|
};
|
|
34562
|
+
const FOREIGN_INLINE_DISABLE_PATTERN = /(?:\/\/|\/\*)[ \t]*(eslint|oxlint)-disable-(next-line|line)(?![\w-])([^\r\n]*)/;
|
|
34563
|
+
const FOREIGN_BLOCK_DISABLE_PATTERN = /\/\*[ \t]*(eslint|oxlint)-disable(?![\w-])([^*\r\n]*)/;
|
|
34564
|
+
const FOREIGN_BLOCK_ENABLE_PATTERN = /\/\*[ \t]*(?:eslint|oxlint)-enable(?![\w-])([^*\r\n]*)/;
|
|
34443
34565
|
const LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY = {
|
|
34444
34566
|
"effect/no-adjust-state-on-prop-change": "react-doctor/no-adjust-state-on-prop-change",
|
|
34445
34567
|
"effect/no-chain-state-updates": "react-doctor/no-chain-state-updates",
|
|
@@ -34517,6 +34639,7 @@ const LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY = {
|
|
|
34517
34639
|
"react/jsx-no-new-function-as-prop": "react-doctor/jsx-no-new-function-as-prop",
|
|
34518
34640
|
"react/jsx-no-new-object-as-prop": "react-doctor/jsx-no-new-object-as-prop",
|
|
34519
34641
|
"react/jsx-no-script-url": "react-doctor/jsx-no-script-url",
|
|
34642
|
+
"react/jsx-no-target-blank": "react-doctor/jsx-no-target-blank",
|
|
34520
34643
|
"react/jsx-no-undef": "react-doctor/jsx-no-undef",
|
|
34521
34644
|
"react/jsx-no-useless-fragment": "react-doctor/jsx-no-useless-fragment",
|
|
34522
34645
|
"react/jsx-pascal-case": "react-doctor/jsx-pascal-case",
|
|
@@ -34590,9 +34713,6 @@ const tokenizeRuleList = (ruleList) => {
|
|
|
34590
34713
|
if (!ruleSection) return [];
|
|
34591
34714
|
return ruleSection.split(/[,\s]+/).map((token) => token.trim()).filter(Boolean);
|
|
34592
34715
|
};
|
|
34593
|
-
const FOREIGN_INLINE_DISABLE_PATTERN = /(?:\/\/|\/\*)[ \t]*(eslint|oxlint)-disable-(next-line|line)(?![\w-])([^\r\n]*)/;
|
|
34594
|
-
const FOREIGN_BLOCK_DISABLE_PATTERN = /\/\*[ \t]*(eslint|oxlint)-disable(?![\w-])([^*\r\n]*)/;
|
|
34595
|
-
const FOREIGN_BLOCK_ENABLE_PATTERN = /\/\*[ \t]*(?:eslint|oxlint)-enable(?![\w-])([^*\r\n]*)/;
|
|
34596
34716
|
const buildHint = (tool, token, ruleId) => `oxlint matches plugin rules only by their full name, so \`${token}\` in your ${tool}-disable comment does not silence \`${ruleId}\` — add \`${ruleId}\` to the comment (e.g. \`${token}, ${ruleId}\`).`;
|
|
34597
34717
|
const tokenMisnamesRule = (token, ruleId) => token !== ruleId && isSameRuleKey(token, ruleId);
|
|
34598
34718
|
const detectInlineNearMiss = (lines, diagnosticLineIndex, ruleId) => {
|
|
@@ -34646,6 +34766,53 @@ const detectForeignDisableNearMiss = (lines, diagnosticLineIndex, ruleId) => {
|
|
|
34646
34766
|
if (!ruleId.startsWith("react-doctor/")) return null;
|
|
34647
34767
|
return detectInlineNearMiss(lines, diagnosticLineIndex, ruleId) ?? detectBlockNearMiss(lines, diagnosticLineIndex, ruleId);
|
|
34648
34768
|
};
|
|
34769
|
+
const ESLINT_REACT_HOOKS_PLUGIN_PREFIX = "react-hooks/";
|
|
34770
|
+
const buildAcceptedTokens = (ruleId) => {
|
|
34771
|
+
const shortRuleId = ruleId.slice(15);
|
|
34772
|
+
return new Set([ruleId, `${ESLINT_REACT_HOOKS_PLUGIN_PREFIX}${shortRuleId}`]);
|
|
34773
|
+
};
|
|
34774
|
+
const listNamesRule = (ruleList, acceptedTokens) => tokenizeRuleList(ruleList).some((token) => acceptedTokens.has(token));
|
|
34775
|
+
const isInlineDisabled = (lines, diagnosticLineIndex, acceptedTokens) => {
|
|
34776
|
+
const candidates = [{
|
|
34777
|
+
line: lines[diagnosticLineIndex],
|
|
34778
|
+
requiredScope: "line"
|
|
34779
|
+
}, {
|
|
34780
|
+
line: lines[diagnosticLineIndex - 1],
|
|
34781
|
+
requiredScope: "next-line"
|
|
34782
|
+
}];
|
|
34783
|
+
for (const { line, requiredScope } of candidates) {
|
|
34784
|
+
const match = line?.match(FOREIGN_INLINE_DISABLE_PATTERN);
|
|
34785
|
+
if (!match) continue;
|
|
34786
|
+
const [, , scope, ruleList] = match;
|
|
34787
|
+
if (scope !== requiredScope) continue;
|
|
34788
|
+
if (listNamesRule(ruleList, acceptedTokens)) return true;
|
|
34789
|
+
}
|
|
34790
|
+
return false;
|
|
34791
|
+
};
|
|
34792
|
+
const isBlockDisabled = (lines, diagnosticLineIndex, acceptedTokens) => {
|
|
34793
|
+
let isRangeOpen = false;
|
|
34794
|
+
const lastLineIndex = Math.min(diagnosticLineIndex, lines.length - 1);
|
|
34795
|
+
for (let lineIndex = 0; lineIndex <= lastLineIndex; lineIndex++) {
|
|
34796
|
+
const line = lines[lineIndex];
|
|
34797
|
+
if (line === void 0 || !line.includes("-disable") && !line.includes("-enable")) continue;
|
|
34798
|
+
const disableMatch = line.match(FOREIGN_BLOCK_DISABLE_PATTERN);
|
|
34799
|
+
if (disableMatch && listNamesRule(disableMatch[2], acceptedTokens)) {
|
|
34800
|
+
isRangeOpen = true;
|
|
34801
|
+
continue;
|
|
34802
|
+
}
|
|
34803
|
+
const enableMatch = line.match(FOREIGN_BLOCK_ENABLE_PATTERN);
|
|
34804
|
+
if (enableMatch) {
|
|
34805
|
+
const enabledRules = tokenizeRuleList(enableMatch[1]);
|
|
34806
|
+
if (enabledRules.length === 0 || enabledRules.some((rule) => acceptedTokens.has(rule))) isRangeOpen = false;
|
|
34807
|
+
}
|
|
34808
|
+
}
|
|
34809
|
+
return isRangeOpen;
|
|
34810
|
+
};
|
|
34811
|
+
const isCompilerRuleForeignDisabled = (lines, diagnosticLineIndex, ruleId) => {
|
|
34812
|
+
if (!ruleId.startsWith("react-hooks-js/")) return false;
|
|
34813
|
+
const acceptedTokens = buildAcceptedTokens(ruleId);
|
|
34814
|
+
return isInlineDisabled(lines, diagnosticLineIndex, acceptedTokens) || isBlockDisabled(lines, diagnosticLineIndex, acceptedTokens);
|
|
34815
|
+
};
|
|
34649
34816
|
const JSX_OPENER_TAG_PATTERN = /<[A-Za-z][\w.]*/g;
|
|
34650
34817
|
const JSX_TAG_NAME_FOLLOW = /[A-Za-z]/;
|
|
34651
34818
|
const isOpenerMatchInsideLineComment = (line, openerCharIndex) => {
|
|
@@ -34789,6 +34956,11 @@ const evaluateSuppression = (lines, diagnosticLineIndex, ruleId) => {
|
|
|
34789
34956
|
isSuppressed: true,
|
|
34790
34957
|
nearMissHint: null
|
|
34791
34958
|
};
|
|
34959
|
+
if (isCompilerRuleForeignDisabled(lines, diagnosticLineIndex, ruleId)) return {
|
|
34960
|
+
isSuppressed: true,
|
|
34961
|
+
nearMissHint: null,
|
|
34962
|
+
isForeignDirective: true
|
|
34963
|
+
};
|
|
34792
34964
|
const directComments = findStackedDisableCommentsAbove(lines, diagnosticLineIndex);
|
|
34793
34965
|
if (hasChainSuppressor(directComments, ruleId)) return {
|
|
34794
34966
|
isSuppressed: true,
|
|
@@ -35074,12 +35246,18 @@ const collectStringSet = (values) => {
|
|
|
35074
35246
|
return new Set(values.filter((value) => typeof value === "string"));
|
|
35075
35247
|
};
|
|
35076
35248
|
const JSX_NO_UNDEF_IDENTIFIER_PATTERN = /^`([^`]+)`/;
|
|
35249
|
+
const NODE_CLI_PATH_SEGMENTS = new Set([
|
|
35250
|
+
"scripts",
|
|
35251
|
+
"bin",
|
|
35252
|
+
"tools"
|
|
35253
|
+
]);
|
|
35254
|
+
const hasNodeCliPathSegment = (filePath) => filePath.split(/[\\/]/).slice(0, -1).some((segment) => NODE_CLI_PATH_SEGMENTS.has(segment));
|
|
35077
35255
|
/**
|
|
35078
35256
|
* Pre-compiles every stateful filter and returns a single
|
|
35079
35257
|
* `apply(diagnostic)` closure that runs (in order):
|
|
35080
35258
|
*
|
|
35081
35259
|
* 1. auto-suppress (test-noise rules in test files; `migration-hint`
|
|
35082
|
-
* wins over `test-noise`)
|
|
35260
|
+
* wins over `test-noise`; `no-console` on Node CLI scripts)
|
|
35083
35261
|
* 2. severity overrides (top-level `rules` / `categories`, with
|
|
35084
35262
|
* `"off"` dropping)
|
|
35085
35263
|
* 3. warning suppression (only when `showWarnings` is false: drops every
|
|
@@ -35177,6 +35355,12 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
35177
35355
|
if (hasRawTextWrappers && isInsideStringOnlyWrapper(lines, diagnostic.line, diagnostic.column, rawTextWrapperComponentNames)) return true;
|
|
35178
35356
|
return false;
|
|
35179
35357
|
};
|
|
35358
|
+
const isNoConsoleOnNodeCliScript = (diagnostic) => {
|
|
35359
|
+
if (diagnostic.rule !== "no-console") return false;
|
|
35360
|
+
if (hasNodeCliPathSegment(diagnostic.filePath)) return true;
|
|
35361
|
+
const lines = getFileLines(diagnostic.filePath);
|
|
35362
|
+
return lines !== null && Boolean(lines[0]?.startsWith("#!"));
|
|
35363
|
+
};
|
|
35180
35364
|
const isJsxNoUndefSuppressedByConfig = (diagnostic) => {
|
|
35181
35365
|
if (!hasRuntimeGlobals) return false;
|
|
35182
35366
|
if (diagnostic.rule !== "jsx-no-undef") return false;
|
|
@@ -35186,6 +35370,7 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
35186
35370
|
return {
|
|
35187
35371
|
apply: (diagnostic) => {
|
|
35188
35372
|
if (shouldAutoSuppress(diagnostic)) return null;
|
|
35373
|
+
if (isNoConsoleOnNodeCliScript(diagnostic)) return null;
|
|
35189
35374
|
let current = diagnostic;
|
|
35190
35375
|
let explicitSeverityOverride;
|
|
35191
35376
|
let explicitRuleOverride;
|
|
@@ -35215,7 +35400,7 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
35215
35400
|
if (lines) {
|
|
35216
35401
|
const ruleIdentifier = `${current.plugin}/${current.rule}`;
|
|
35217
35402
|
const evaluation = evaluateSuppression(lines, current.line - 1, ruleIdentifier);
|
|
35218
|
-
if (evaluation.isSuppressed) return suppress(current, "inline");
|
|
35403
|
+
if (evaluation.isSuppressed) return suppress(current, evaluation.isForeignDirective ? "foreign-inline" : "inline");
|
|
35219
35404
|
if (evaluation.nearMissHint) current = {
|
|
35220
35405
|
...current,
|
|
35221
35406
|
suppressionHint: evaluation.nearMissHint
|
|
@@ -37024,64 +37209,64 @@ function* collectSecurityScanFiles(rootDirectory) {
|
|
|
37024
37209
|
}
|
|
37025
37210
|
}
|
|
37026
37211
|
}
|
|
37027
|
-
|
|
37028
|
-
|
|
37029
|
-
|
|
37030
|
-
|
|
37031
|
-
|
|
37032
|
-
|
|
37033
|
-
|
|
37212
|
+
const CLIENT_ONLY_FRAMEWORKS = new Set([
|
|
37213
|
+
"vite",
|
|
37214
|
+
"cra",
|
|
37215
|
+
"gatsby",
|
|
37216
|
+
"react-native",
|
|
37217
|
+
"expo"
|
|
37218
|
+
]);
|
|
37219
|
+
const addMajorLadder = (capabilities, name, major, earliest, latest) => {
|
|
37220
|
+
if (major === null) return;
|
|
37221
|
+
const cappedMajor = Math.min(major, latest);
|
|
37222
|
+
for (let candidate = earliest; candidate <= cappedMajor; candidate += 1) capabilities.add(`${name}:${candidate}`);
|
|
37223
|
+
};
|
|
37034
37224
|
const buildCapabilities = (project) => {
|
|
37035
37225
|
const capabilities = /* @__PURE__ */ new Set();
|
|
37036
37226
|
capabilities.add(project.framework);
|
|
37037
|
-
if (
|
|
37227
|
+
if (project.reactVersion !== null || project.preactVersion !== null) capabilities.add("react");
|
|
37038
37228
|
if (project.framework === "expo" || project.framework === "react-native" || project.hasReactNativeWorkspace) capabilities.add("react-native");
|
|
37039
37229
|
if (project.expoVersion !== null) capabilities.add("expo");
|
|
37230
|
+
if ((project.framework === "nextjs" || project.framework === "tanstack-start" || project.framework === "remix") && !project.isStaticExport) capabilities.add("server-actions");
|
|
37231
|
+
if (CLIENT_ONLY_FRAMEWORKS.has(project.framework)) capabilities.add("client-only");
|
|
37232
|
+
if (project.isStaticExport) capabilities.add("nextjs:static-export");
|
|
37040
37233
|
if (project.nextjsMajorVersion !== null && project.nextjsMajorVersion >= 15) capabilities.add("nextjs:15");
|
|
37041
|
-
|
|
37042
|
-
if (
|
|
37043
|
-
|
|
37044
|
-
|
|
37045
|
-
|
|
37046
|
-
|
|
37047
|
-
|
|
37048
|
-
|
|
37049
|
-
|
|
37050
|
-
|
|
37051
|
-
|
|
37052
|
-
if (project.
|
|
37053
|
-
capabilities.add("tailwind");
|
|
37054
|
-
if (isTailwindAtLeast(parseTailwindMajorMinor(project.tailwindVersion), {
|
|
37055
|
-
major: 3,
|
|
37056
|
-
minor: 4
|
|
37057
|
-
})) capabilities.add("tailwind:3.4");
|
|
37058
|
-
}
|
|
37059
|
-
if (project.zodVersion !== null) {
|
|
37060
|
-
capabilities.add("zod");
|
|
37061
|
-
if (project.zodMajorVersion !== null && project.zodMajorVersion >= 4) capabilities.add("zod:4");
|
|
37062
|
-
}
|
|
37234
|
+
addMajorLadder(capabilities, "react", project.reactMajorVersion, 17, 30);
|
|
37235
|
+
if (project.reactMajorVersion !== null && project.reactMajorVersion >= 19 && isMajorMinorAtLeast(parseReactMajorMinor(project.reactVersion), {
|
|
37236
|
+
major: 19,
|
|
37237
|
+
minor: 2
|
|
37238
|
+
})) capabilities.add("react:19.2");
|
|
37239
|
+
if (project.tailwindVersion !== null) capabilities.add("tailwind");
|
|
37240
|
+
if (project.tailwindVersion !== null && isMajorMinorAtLeast(parseTailwindMajorMinor(project.tailwindVersion), {
|
|
37241
|
+
major: 3,
|
|
37242
|
+
minor: 4
|
|
37243
|
+
})) capabilities.add("tailwind:3.4");
|
|
37244
|
+
if (project.zodVersion !== null) capabilities.add("zod");
|
|
37245
|
+
if (project.zodMajorVersion !== null && project.zodMajorVersion >= 4) capabilities.add("zod:4");
|
|
37063
37246
|
if (project.isPreES2023Target) capabilities.add("pre-es2023");
|
|
37064
37247
|
if (project.hasReactCompiler) capabilities.add("react-compiler");
|
|
37065
37248
|
if (project.hasTanStackQuery) capabilities.add("tanstack-query");
|
|
37066
37249
|
if (project.hasTypeScript) capabilities.add("typescript");
|
|
37067
|
-
if (project.preactVersion !== null)
|
|
37068
|
-
|
|
37069
|
-
|
|
37070
|
-
|
|
37071
|
-
|
|
37072
|
-
|
|
37073
|
-
|
|
37074
|
-
|
|
37075
|
-
|
|
37250
|
+
if (project.preactVersion !== null) capabilities.add("preact");
|
|
37251
|
+
addMajorLadder(capabilities, "preact", project.preactMajorVersion, 10, 20);
|
|
37252
|
+
if (project.preactVersion !== null && project.reactVersion === null) capabilities.add("pure-preact");
|
|
37253
|
+
return capabilities;
|
|
37254
|
+
};
|
|
37255
|
+
const capabilitiesByProject = /* @__PURE__ */ new WeakMap();
|
|
37256
|
+
const getCapabilities = (project) => {
|
|
37257
|
+
const cached = capabilitiesByProject.get(project);
|
|
37258
|
+
if (cached !== void 0) return cached;
|
|
37259
|
+
const capabilities = buildCapabilities(project);
|
|
37260
|
+
capabilitiesByProject.set(project, capabilities);
|
|
37076
37261
|
return capabilities;
|
|
37077
37262
|
};
|
|
37078
|
-
const shouldEnableRule = (requires, tags, capabilities, ignoredTags,
|
|
37263
|
+
const shouldEnableRule = (requires, tags, capabilities, ignoredTags, disabledWhen) => {
|
|
37079
37264
|
if (requires) {
|
|
37080
37265
|
for (const capability of requires) if (!capabilities.has(capability)) return false;
|
|
37081
37266
|
}
|
|
37082
37267
|
if (tags?.includes("react-jsx-only") && !capabilities.has("react")) return false;
|
|
37083
|
-
if (
|
|
37084
|
-
for (const capability of
|
|
37268
|
+
if (disabledWhen) {
|
|
37269
|
+
for (const capability of disabledWhen) if (capabilities.has(capability)) return false;
|
|
37085
37270
|
}
|
|
37086
37271
|
if (tags) {
|
|
37087
37272
|
for (const tag of tags) if (ignoredTags.has(tag)) return false;
|
|
@@ -37092,14 +37277,14 @@ const yieldToEventLoop = () => new Promise((resolve) => {
|
|
|
37092
37277
|
setImmediate(resolve);
|
|
37093
37278
|
});
|
|
37094
37279
|
const createSecurityScanSession = (rootDirectory, options) => {
|
|
37095
|
-
const capabilities = options.project ?
|
|
37280
|
+
const capabilities = options.project ? getCapabilities(options.project) : /* @__PURE__ */ new Set();
|
|
37096
37281
|
const ignoredTags = options.ignoredTags ?? /* @__PURE__ */ new Set();
|
|
37097
37282
|
const enabledScanRules = REACT_DOCTOR_RULES.flatMap((entry) => {
|
|
37098
37283
|
const rule = entry.rule;
|
|
37099
37284
|
const scan = rule.scan;
|
|
37100
37285
|
if (typeof scan !== "function") return [];
|
|
37101
37286
|
if (rule.defaultEnabled === false) return [];
|
|
37102
|
-
if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.
|
|
37287
|
+
if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.disabledWhen)) return [];
|
|
37103
37288
|
return [{
|
|
37104
37289
|
entry,
|
|
37105
37290
|
scan,
|
|
@@ -37119,6 +37304,7 @@ const createSecurityScanSession = (rootDirectory, options) => {
|
|
|
37119
37304
|
return status === true;
|
|
37120
37305
|
};
|
|
37121
37306
|
const scanFileByRule = function* (file) {
|
|
37307
|
+
if (file === null) return;
|
|
37122
37308
|
for (const { entry, scan, committedFilesOnly } of enabledScanRules) {
|
|
37123
37309
|
for (const finding of scan(file)) {
|
|
37124
37310
|
if (committedFilesOnly && isFileGitIgnored(file)) continue;
|
|
@@ -38564,7 +38750,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
38564
38750
|
})),
|
|
38565
38751
|
showStagedContent: (directory, relativePath, options) => runCommand({
|
|
38566
38752
|
command: "git",
|
|
38567
|
-
args: ["show",
|
|
38753
|
+
args: ["show", `:./${relativePath}`],
|
|
38568
38754
|
directory,
|
|
38569
38755
|
maxStdoutBytes: options?.maxBufferBytes
|
|
38570
38756
|
}).pipe(map$3((result) => result.status === 0 ? result.stdout : null)),
|
|
@@ -38999,7 +39185,7 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
38999
39185
|
const reactCompilerRules = reactHooksJsPlugin ? applyRuleSeverityControls(filterRulesToAvailable(REACT_COMPILER_RULES, "react-hooks-js", reactHooksJsPlugin.availableRuleNames), severityControls) : {};
|
|
39000
39186
|
const jsPlugins = [];
|
|
39001
39187
|
if (reactHooksJsPlugin) jsPlugins.push(reactHooksJsPlugin.entry);
|
|
39002
|
-
const capabilities =
|
|
39188
|
+
const capabilities = getCapabilities(project);
|
|
39003
39189
|
const enabledReactDoctorRules = {};
|
|
39004
39190
|
for (const registryEntry of REACT_DOCTOR_RULES) {
|
|
39005
39191
|
const rule = reactDoctorPlugin.rules[registryEntry.id];
|
|
@@ -39010,7 +39196,7 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
39010
39196
|
if (rule.scan !== void 0) continue;
|
|
39011
39197
|
if (customRulesOnly && registryEntry.originallyExternal) continue;
|
|
39012
39198
|
if (rule.framework !== "global" && !rule.requires) continue;
|
|
39013
|
-
if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.
|
|
39199
|
+
if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.disabledWhen)) continue;
|
|
39014
39200
|
const explicitRuleOverride = resolveRuleSeverityOverride({ ruleKey: registryEntry.key }, severityControls);
|
|
39015
39201
|
if (rule.defaultEnabled === false && explicitRuleOverride === void 0) continue;
|
|
39016
39202
|
const severity = resolveRuleSeverityOverride({
|
|
@@ -39041,6 +39227,7 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
39041
39227
|
settings: { "react-doctor": {
|
|
39042
39228
|
framework: project.framework,
|
|
39043
39229
|
rootDirectory: resolveSettingsRootDirectory(project.rootDirectory),
|
|
39230
|
+
capabilities: [...capabilities].sort(),
|
|
39044
39231
|
...project.shopifyFlashListMajorVersion !== null ? { shopifyFlashListMajorVersion: project.shopifyFlashListMajorVersion } : {},
|
|
39045
39232
|
...serverAuthFunctionNames && serverAuthFunctionNames.length > 0 ? { serverAuthFunctionNames: [...serverAuthFunctionNames] } : {}
|
|
39046
39233
|
} },
|
|
@@ -39331,25 +39518,18 @@ const mapWithConcurrency = async (items, concurrency, task) => {
|
|
|
39331
39518
|
if (errors.length > 0) throw errors[0];
|
|
39332
39519
|
return results;
|
|
39333
39520
|
};
|
|
39334
|
-
const getPublicEnvPrefix = (framework) => {
|
|
39335
|
-
switch (framework) {
|
|
39336
|
-
case "nextjs": return "NEXT_PUBLIC_*";
|
|
39337
|
-
case "vite":
|
|
39338
|
-
case "tanstack-start": return "VITE_*";
|
|
39339
|
-
case "cra": return "REACT_APP_*";
|
|
39340
|
-
case "gatsby": return "GATSBY_*";
|
|
39341
|
-
default: return null;
|
|
39342
|
-
}
|
|
39343
|
-
};
|
|
39344
|
-
const buildNoSecretsRecommendation = (project, fallbackRecommendation) => {
|
|
39345
|
-
const publicEnvPrefix = getPublicEnvPrefix(project.framework);
|
|
39346
|
-
if (!publicEnvPrefix) return fallbackRecommendation;
|
|
39347
|
-
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`;
|
|
39348
|
-
};
|
|
39349
39521
|
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";
|
|
39522
|
+
const ACCESSORS_MIN_VERSION = "3.15.0";
|
|
39523
|
+
const supportsAccessors = (reanimatedVersion) => {
|
|
39524
|
+
if (reanimatedVersion === null) return true;
|
|
39525
|
+
const lowerBound = import_semver.validRange(reanimatedVersion) !== null ? import_semver.minVersion(reanimatedVersion) : import_semver.coerce(reanimatedVersion);
|
|
39526
|
+
if (lowerBound === null) return true;
|
|
39527
|
+
return import_semver.gte(lowerBound, ACCESSORS_MIN_VERSION);
|
|
39528
|
+
};
|
|
39350
39529
|
const appendReanimatedSharedValueHint = (help, rule, project) => {
|
|
39351
39530
|
if (rule !== "immutability") return help;
|
|
39352
39531
|
if (!project.hasReanimated) return help;
|
|
39532
|
+
if (!supportsAccessors(project.reanimatedVersion)) return help;
|
|
39353
39533
|
if (!help) return REANIMATED_SHARED_VALUE_HINT;
|
|
39354
39534
|
return `${help}\n\n${REANIMATED_SHARED_VALUE_HINT}`;
|
|
39355
39535
|
};
|
|
@@ -39511,13 +39691,13 @@ const REACT_USE_BINDING_RESOLUTION = {
|
|
|
39511
39691
|
isReactUseBinding: true,
|
|
39512
39692
|
isReactNamespaceBinding: false
|
|
39513
39693
|
};
|
|
39514
|
-
const getScriptKind = (filename) => {
|
|
39694
|
+
const getScriptKind$2 = (filename) => {
|
|
39515
39695
|
if (filename.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
39516
39696
|
if (filename.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
39517
39697
|
if (filename.endsWith(".ts")) return ts.ScriptKind.TS;
|
|
39518
39698
|
return ts.ScriptKind.JS;
|
|
39519
39699
|
};
|
|
39520
|
-
const getUtf16Offset = (sourceText, utf8Offset) => Buffer.from(sourceText).subarray(0, utf8Offset).toString("utf8").length;
|
|
39700
|
+
const getUtf16Offset$1 = (sourceText, utf8Offset) => Buffer.from(sourceText).subarray(0, utf8Offset).toString("utf8").length;
|
|
39521
39701
|
const unwrapExpression = (expression) => {
|
|
39522
39702
|
let currentExpression = expression;
|
|
39523
39703
|
while (ts.isParenthesizedExpression(currentExpression) || ts.isAsExpression(currentExpression) || ts.isSatisfiesExpression(currentExpression) || ts.isNonNullExpression(currentExpression) || ts.isTypeAssertionExpression(currentExpression)) currentExpression = currentExpression.expression;
|
|
@@ -39766,8 +39946,8 @@ const findUseCallIdentifier = (sourceFile, useOffset) => {
|
|
|
39766
39946
|
return matchedIdentifier;
|
|
39767
39947
|
};
|
|
39768
39948
|
const resolveUseCallBinding = (sourceText, filename, utf8Offset) => {
|
|
39769
|
-
const sourceFile = ts.createSourceFile(filename, sourceText, ts.ScriptTarget.Latest, true, getScriptKind(filename));
|
|
39770
|
-
const useIdentifier = findUseCallIdentifier(sourceFile, getUtf16Offset(sourceText, utf8Offset));
|
|
39949
|
+
const sourceFile = ts.createSourceFile(filename, sourceText, ts.ScriptTarget.Latest, true, getScriptKind$2(filename));
|
|
39950
|
+
const useIdentifier = findUseCallIdentifier(sourceFile, getUtf16Offset$1(sourceText, utf8Offset));
|
|
39771
39951
|
if (!useIdentifier) return null;
|
|
39772
39952
|
return resolveIdentifierBinding(useIdentifier, collectReactImportBindings(sourceFile), sourceFile);
|
|
39773
39953
|
};
|
|
@@ -39788,6 +39968,201 @@ const shouldSuppressLocalUseHookDiagnostic = (diagnostic, rootDirectory) => {
|
|
|
39788
39968
|
const bindingResolution = resolveUseCallBinding(sourceText, absolutePath, primaryLabel.span.offset);
|
|
39789
39969
|
return bindingResolution !== null && !bindingResolution.isReactUseBinding;
|
|
39790
39970
|
};
|
|
39971
|
+
const SUPPRESSED_COMPILER_CODES = new Set(["react-hooks-js(immutability)", "react-hooks-js(refs)"]);
|
|
39972
|
+
const WORKLET_DIRECTIVE = "worklet";
|
|
39973
|
+
const WORKLET_ACCEPTING_CALLEES = new Set([
|
|
39974
|
+
"useAnimatedStyle",
|
|
39975
|
+
"useAnimatedProps",
|
|
39976
|
+
"useDerivedValue",
|
|
39977
|
+
"useAnimatedReaction",
|
|
39978
|
+
"useAnimatedScrollHandler",
|
|
39979
|
+
"useAnimatedGestureHandler",
|
|
39980
|
+
"useFrameCallback",
|
|
39981
|
+
"runOnUI",
|
|
39982
|
+
"executeOnUIRuntimeSync"
|
|
39983
|
+
]);
|
|
39984
|
+
const GESTURE_ROOT_IDENTIFIER = "Gesture";
|
|
39985
|
+
const getScriptKind$1 = (filename) => {
|
|
39986
|
+
if (filename.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
39987
|
+
if (filename.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
39988
|
+
if (filename.endsWith(".ts")) return ts.ScriptKind.TS;
|
|
39989
|
+
return ts.ScriptKind.JS;
|
|
39990
|
+
};
|
|
39991
|
+
const getUtf16Offset = (sourceText, utf8Offset) => Buffer.from(sourceText).subarray(0, utf8Offset).toString("utf8").length;
|
|
39992
|
+
const hasWorkletDirective = (node) => {
|
|
39993
|
+
if (!ts.isFunctionDeclaration(node) && !ts.isFunctionExpression(node) && !ts.isArrowFunction(node) && !ts.isMethodDeclaration(node)) return false;
|
|
39994
|
+
const body = node.body;
|
|
39995
|
+
if (!body || !ts.isBlock(body)) return false;
|
|
39996
|
+
for (const statement of body.statements) {
|
|
39997
|
+
if (!ts.isExpressionStatement(statement)) return false;
|
|
39998
|
+
if (!ts.isStringLiteral(statement.expression)) return false;
|
|
39999
|
+
if (statement.expression.text === WORKLET_DIRECTIVE) return true;
|
|
40000
|
+
}
|
|
40001
|
+
return false;
|
|
40002
|
+
};
|
|
40003
|
+
const rootIdentifierOfCallChain = (expression) => {
|
|
40004
|
+
let currentExpression = expression;
|
|
40005
|
+
while (true) {
|
|
40006
|
+
if (ts.isIdentifier(currentExpression)) return currentExpression.text;
|
|
40007
|
+
if (ts.isPropertyAccessExpression(currentExpression)) {
|
|
40008
|
+
currentExpression = currentExpression.expression;
|
|
40009
|
+
continue;
|
|
40010
|
+
}
|
|
40011
|
+
if (ts.isCallExpression(currentExpression)) {
|
|
40012
|
+
currentExpression = currentExpression.expression;
|
|
40013
|
+
continue;
|
|
40014
|
+
}
|
|
40015
|
+
return null;
|
|
40016
|
+
}
|
|
40017
|
+
};
|
|
40018
|
+
const isWorkletAcceptingCall = (callExpression) => {
|
|
40019
|
+
const callee = callExpression.expression;
|
|
40020
|
+
if (ts.isIdentifier(callee)) return WORKLET_ACCEPTING_CALLEES.has(callee.text);
|
|
40021
|
+
if (ts.isPropertyAccessExpression(callee)) {
|
|
40022
|
+
if (WORKLET_ACCEPTING_CALLEES.has(callee.name.text)) return true;
|
|
40023
|
+
return rootIdentifierOfCallChain(callee) === GESTURE_ROOT_IDENTIFIER;
|
|
40024
|
+
}
|
|
40025
|
+
return false;
|
|
40026
|
+
};
|
|
40027
|
+
const isFunctionWorklet = (functionNode) => {
|
|
40028
|
+
if (hasWorkletDirective(functionNode)) return true;
|
|
40029
|
+
const parent = functionNode.parent;
|
|
40030
|
+
return ts.isCallExpression(parent) && parent.arguments.some((argument) => argument === functionNode) && isWorkletAcceptingCall(parent);
|
|
40031
|
+
};
|
|
40032
|
+
const findNodeAtOffset = (sourceFile, targetOffset) => {
|
|
40033
|
+
let matchedNode = null;
|
|
40034
|
+
const visit = (node) => {
|
|
40035
|
+
if (node.getStart(sourceFile) > targetOffset || node.getEnd() <= targetOffset) return;
|
|
40036
|
+
matchedNode = node;
|
|
40037
|
+
ts.forEachChild(node, visit);
|
|
40038
|
+
};
|
|
40039
|
+
visit(sourceFile);
|
|
40040
|
+
return matchedNode;
|
|
40041
|
+
};
|
|
40042
|
+
const isOffsetInsideWorklet = (sourceFile, targetOffset) => {
|
|
40043
|
+
let currentNode = findNodeAtOffset(sourceFile, targetOffset);
|
|
40044
|
+
while (currentNode) {
|
|
40045
|
+
if (ts.isFunctionLike(currentNode) && isFunctionWorklet(currentNode)) return true;
|
|
40046
|
+
currentNode = currentNode.parent;
|
|
40047
|
+
}
|
|
40048
|
+
return false;
|
|
40049
|
+
};
|
|
40050
|
+
const shouldSuppressCompilerFindingInWorklet = (diagnostic, project, rootDirectory) => {
|
|
40051
|
+
if (!SUPPRESSED_COMPILER_CODES.has(diagnostic.code)) return false;
|
|
40052
|
+
if (!project.hasReanimated) return false;
|
|
40053
|
+
const primaryLabel = diagnostic.labels[0];
|
|
40054
|
+
if (!primaryLabel) return false;
|
|
40055
|
+
const absolutePath = Path.isAbsolute(diagnostic.filename) ? diagnostic.filename : Path.join(rootDirectory, diagnostic.filename);
|
|
40056
|
+
let sourceText;
|
|
40057
|
+
try {
|
|
40058
|
+
sourceText = NFS.readFileSync(absolutePath, "utf-8");
|
|
40059
|
+
} catch {
|
|
40060
|
+
return false;
|
|
40061
|
+
}
|
|
40062
|
+
return isOffsetInsideWorklet(ts.createSourceFile(absolutePath, sourceText, ts.ScriptTarget.Latest, true, getScriptKind$1(absolutePath)), getUtf16Offset(sourceText, primaryLabel.span.offset));
|
|
40063
|
+
};
|
|
40064
|
+
const MANUAL_MEMOIZATION_PLUGIN = "react-doctor";
|
|
40065
|
+
const MANUAL_MEMOIZATION_RULE = "react-compiler-no-manual-memoization";
|
|
40066
|
+
const REACT_COMPILER_PLUGIN = "react-hooks-js";
|
|
40067
|
+
const getScriptKind = (filename) => {
|
|
40068
|
+
if (filename.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
40069
|
+
if (filename.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
40070
|
+
if (filename.endsWith(".ts")) return ts.ScriptKind.TS;
|
|
40071
|
+
return ts.ScriptKind.JS;
|
|
40072
|
+
};
|
|
40073
|
+
const isManualMemoizationDiagnostic = (diagnostic) => diagnostic.plugin === MANUAL_MEMOIZATION_PLUGIN && diagnostic.rule === MANUAL_MEMOIZATION_RULE;
|
|
40074
|
+
const nodeLineRange = (sourceFile, node) => ({
|
|
40075
|
+
startLine: sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1,
|
|
40076
|
+
endLine: sourceFile.getLineAndCharacterOfPosition(node.getEnd()).line + 1
|
|
40077
|
+
});
|
|
40078
|
+
const findOutermostEnclosingFunctionRange = (sourceFile, targetLine) => {
|
|
40079
|
+
let foundRange = null;
|
|
40080
|
+
const visit = (node) => {
|
|
40081
|
+
if (foundRange) return;
|
|
40082
|
+
if (ts.isFunctionLike(node)) {
|
|
40083
|
+
const range = nodeLineRange(sourceFile, node);
|
|
40084
|
+
if (range.startLine <= targetLine && targetLine <= range.endLine) foundRange = range;
|
|
40085
|
+
return;
|
|
40086
|
+
}
|
|
40087
|
+
ts.forEachChild(node, visit);
|
|
40088
|
+
};
|
|
40089
|
+
visit(sourceFile);
|
|
40090
|
+
return foundRange;
|
|
40091
|
+
};
|
|
40092
|
+
const NEWLINE_BYTE = 10;
|
|
40093
|
+
const lineOfUtf8Offset = (sourceBuffer, utf8Offset) => {
|
|
40094
|
+
let lineNumber = 1;
|
|
40095
|
+
const scanEnd = Math.min(utf8Offset, sourceBuffer.length);
|
|
40096
|
+
for (let byteIndex = 0; byteIndex < scanEnd; byteIndex++) if (sourceBuffer[byteIndex] === NEWLINE_BYTE) lineNumber++;
|
|
40097
|
+
return lineNumber;
|
|
40098
|
+
};
|
|
40099
|
+
/**
|
|
40100
|
+
* Drops `react-doctor/react-compiler-no-manual-memoization` diagnostics
|
|
40101
|
+
* whose enclosing function also carries a `react-hooks-js` (React
|
|
40102
|
+
* Compiler) diagnostic. Every react-hooks-js finding marks a function the
|
|
40103
|
+
* compiler could NOT compile (a bail-out / skip / preserve-manual-
|
|
40104
|
+
* memoization failure), so "React Compiler already caches every value
|
|
40105
|
+
* here" is false for that function and the manual memoization is
|
|
40106
|
+
* load-bearing, not dead weight.
|
|
40107
|
+
*
|
|
40108
|
+
* Matching is per enclosing function, not per file: a bailed-out
|
|
40109
|
+
* component must not silence redundant-memo findings in a sibling
|
|
40110
|
+
* component the compiler did optimize.
|
|
40111
|
+
*
|
|
40112
|
+
* Module-scope `memo(() => …)` wrappers have no enclosing function; for
|
|
40113
|
+
* those the memo call's own span is the containment range, so a bail-out
|
|
40114
|
+
* inside the inline-wrapped component still suppresses. Every failure
|
|
40115
|
+
* path (unreadable file, missing span) fails open and keeps the
|
|
40116
|
+
* diagnostic.
|
|
40117
|
+
*/
|
|
40118
|
+
const suppressMemoizationInBailedOutFunctions = (diagnostics, rootDirectory) => {
|
|
40119
|
+
const compilerDiagnosticLinesByFile = /* @__PURE__ */ new Map();
|
|
40120
|
+
let hasManualMemoizationDiagnostic = false;
|
|
40121
|
+
for (const diagnostic of diagnostics) {
|
|
40122
|
+
if (isManualMemoizationDiagnostic(diagnostic)) {
|
|
40123
|
+
hasManualMemoizationDiagnostic = true;
|
|
40124
|
+
continue;
|
|
40125
|
+
}
|
|
40126
|
+
if (diagnostic.plugin !== REACT_COMPILER_PLUGIN || diagnostic.line <= 0) continue;
|
|
40127
|
+
const lines = compilerDiagnosticLinesByFile.get(diagnostic.filePath);
|
|
40128
|
+
if (lines) lines.push(diagnostic.line);
|
|
40129
|
+
else compilerDiagnosticLinesByFile.set(diagnostic.filePath, [diagnostic.line]);
|
|
40130
|
+
}
|
|
40131
|
+
if (!hasManualMemoizationDiagnostic || compilerDiagnosticLinesByFile.size === 0) return diagnostics;
|
|
40132
|
+
const parsedFileCache = /* @__PURE__ */ new Map();
|
|
40133
|
+
const readAndParse = (filePath) => {
|
|
40134
|
+
const cached = parsedFileCache.get(filePath);
|
|
40135
|
+
if (cached !== void 0) return cached;
|
|
40136
|
+
const absolutePath = Path.isAbsolute(filePath) ? filePath : Path.resolve(rootDirectory || ".", filePath);
|
|
40137
|
+
let parsed;
|
|
40138
|
+
try {
|
|
40139
|
+
const buffer = NFS.readFileSync(absolutePath);
|
|
40140
|
+
parsed = {
|
|
40141
|
+
sourceFile: ts.createSourceFile(absolutePath, buffer.toString("utf8"), ts.ScriptTarget.Latest, true, getScriptKind(absolutePath)),
|
|
40142
|
+
buffer
|
|
40143
|
+
};
|
|
40144
|
+
} catch {
|
|
40145
|
+
parsed = null;
|
|
40146
|
+
}
|
|
40147
|
+
parsedFileCache.set(filePath, parsed);
|
|
40148
|
+
return parsed;
|
|
40149
|
+
};
|
|
40150
|
+
const isInsideBailedOutFunction = (diagnostic) => {
|
|
40151
|
+
const compilerLines = compilerDiagnosticLinesByFile.get(diagnostic.filePath);
|
|
40152
|
+
if (!compilerLines || diagnostic.line <= 0) return false;
|
|
40153
|
+
const parsed = readAndParse(diagnostic.filePath);
|
|
40154
|
+
if (!parsed) return false;
|
|
40155
|
+
let containmentRange = findOutermostEnclosingFunctionRange(parsed.sourceFile, diagnostic.line);
|
|
40156
|
+
if (!containmentRange && diagnostic.offset !== void 0 && diagnostic.length !== void 0) containmentRange = {
|
|
40157
|
+
startLine: diagnostic.line,
|
|
40158
|
+
endLine: lineOfUtf8Offset(parsed.buffer, diagnostic.offset + diagnostic.length)
|
|
40159
|
+
};
|
|
40160
|
+
if (!containmentRange) return false;
|
|
40161
|
+
const { startLine, endLine } = containmentRange;
|
|
40162
|
+
return compilerLines.some((compilerLine) => startLine <= compilerLine && compilerLine <= endLine);
|
|
40163
|
+
};
|
|
40164
|
+
return diagnostics.filter((diagnostic) => !isManualMemoizationDiagnostic(diagnostic) || !isInsideBailedOutFunction(diagnostic));
|
|
40165
|
+
};
|
|
39791
40166
|
const FILEPATH_WITH_LOCATION_PATTERN = /\S+\.\w+:\d+:\d+[\s\S]*$/;
|
|
39792
40167
|
const LEADING_SEVERITY_LABEL_PATTERN = /^(?:Error|Warning):\s*/;
|
|
39793
40168
|
const TRAILING_PERIOD_PATTERN = /\.$/;
|
|
@@ -39823,8 +40198,14 @@ const PLUGIN_CATEGORY_MAP = {
|
|
|
39823
40198
|
};
|
|
39824
40199
|
const lookupOwnString = (record, key) => Object.hasOwn(record, key) ? record[key] : void 0;
|
|
39825
40200
|
const getRuleRecommendation = (ruleName, project) => {
|
|
39826
|
-
|
|
39827
|
-
return
|
|
40201
|
+
const rule = reactDoctorPlugin.rules[ruleName];
|
|
40202
|
+
if (!rule) return void 0;
|
|
40203
|
+
if (rule.recommendationFor) {
|
|
40204
|
+
const capabilities = getCapabilities(project);
|
|
40205
|
+
const conditionalRecommendation = rule.recommendationFor((capability) => capabilities.has(capability));
|
|
40206
|
+
if (conditionalRecommendation !== void 0) return conditionalRecommendation;
|
|
40207
|
+
}
|
|
40208
|
+
return rule.recommendation;
|
|
39828
40209
|
};
|
|
39829
40210
|
const getRuleCategory = (ruleName) => reactDoctorPlugin.rules[ruleName]?.category;
|
|
39830
40211
|
const getRuleTitle = (ruleName) => reactDoctorPlugin.rules[ruleName]?.title;
|
|
@@ -39927,7 +40308,7 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
39927
40308
|
minifiedFileCache.set(absolutePath, minified);
|
|
39928
40309
|
return minified;
|
|
39929
40310
|
};
|
|
39930
|
-
return parsed.diagnostics.filter((diagnostic) => diagnostic.code && isLintableSourceFile(diagnostic.filename) && !isMinifiedDiagnosticFile(diagnostic.filename) && !shouldSuppressLocalUseHookDiagnostic(diagnostic, rootDirectory)).map((diagnostic) => {
|
|
40311
|
+
return suppressMemoizationInBailedOutFunctions(parsed.diagnostics.filter((diagnostic) => diagnostic.code && isLintableSourceFile(diagnostic.filename) && !isMinifiedDiagnosticFile(diagnostic.filename) && !shouldSuppressLocalUseHookDiagnostic(diagnostic, rootDirectory) && !shouldSuppressCompilerFindingInWorklet(diagnostic, project, rootDirectory)).map((diagnostic) => {
|
|
39931
40312
|
const { plugin, rule } = parseRuleCode(diagnostic.code);
|
|
39932
40313
|
const primaryLabel = diagnostic.labels[0];
|
|
39933
40314
|
const cleaned = cleanDiagnosticMessage(diagnostic.message, diagnostic.help, plugin, rule, project);
|
|
@@ -39954,7 +40335,7 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
39954
40335
|
...resolveMatchByOccurrence(rule, category) ? { matchByOccurrence: true } : {},
|
|
39955
40336
|
...relatedLocations.length > 0 ? { relatedLocations } : {}
|
|
39956
40337
|
};
|
|
39957
|
-
});
|
|
40338
|
+
}), rootDirectory);
|
|
39958
40339
|
};
|
|
39959
40340
|
const buildOxlintChildEnv = (sourceEnv) => {
|
|
39960
40341
|
const childEnv = {};
|
|
@@ -40465,6 +40846,7 @@ const runOxlint = async (options) => {
|
|
|
40465
40846
|
const serverAuthFunctionNames = Array.isArray(userConfig?.serverAuthFunctionNames) ? userConfig.serverAuthFunctionNames.filter((entry) => typeof entry === "string" && entry.length > 0) : void 0;
|
|
40466
40847
|
const severityControls = buildRuleSeverityControls(userConfig);
|
|
40467
40848
|
validateRuleRegistration();
|
|
40849
|
+
resetManifestCaches();
|
|
40468
40850
|
if (includePaths !== void 0 && includePaths.length === 0) return [];
|
|
40469
40851
|
const pluginPath = resolvePluginPath();
|
|
40470
40852
|
const extendsPaths = (adoptExistingLintConfig && !customRulesOnly ? detectUserLintConfigPaths(rootDirectory) : []).filter(canOxlintExtendConfig);
|
|
@@ -41127,9 +41509,10 @@ const CONTEXT_AXES = [
|
|
|
41127
41509
|
}
|
|
41128
41510
|
];
|
|
41129
41511
|
const SCORE_AXES = [...GATED_AXES, ...CONTEXT_AXES];
|
|
41130
|
-
const
|
|
41131
|
-
|
|
41132
|
-
|
|
41512
|
+
const gatedAxesForSection = (section) => section === "devDependencies" ? GATED_AXES.filter((axis) => axis.key !== "vulnerability") : GATED_AXES;
|
|
41513
|
+
const worstGatedAxis = (score, gatedAxes) => {
|
|
41514
|
+
let worst = gatedAxes[0];
|
|
41515
|
+
for (const axis of gatedAxes) if (score[axis.key] < score[worst.key]) worst = axis;
|
|
41133
41516
|
return worst;
|
|
41134
41517
|
};
|
|
41135
41518
|
const clampScore = (value) => {
|
|
@@ -41153,6 +41536,69 @@ const resolveConcreteVersion = (spec) => {
|
|
|
41153
41536
|
if (range === null || range === "*") return null;
|
|
41154
41537
|
return import_semver.minVersion(trimmed)?.version ?? null;
|
|
41155
41538
|
};
|
|
41539
|
+
const readTextFileOrNull = (filePath) => {
|
|
41540
|
+
try {
|
|
41541
|
+
return NFS.readFileSync(filePath, "utf-8");
|
|
41542
|
+
} catch {
|
|
41543
|
+
return null;
|
|
41544
|
+
}
|
|
41545
|
+
};
|
|
41546
|
+
const buildNpmLockLookup = (lockText) => {
|
|
41547
|
+
let parsed;
|
|
41548
|
+
try {
|
|
41549
|
+
parsed = JSON.parse(lockText);
|
|
41550
|
+
} catch {
|
|
41551
|
+
return null;
|
|
41552
|
+
}
|
|
41553
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
41554
|
+
const lock = parsed;
|
|
41555
|
+
return (name) => {
|
|
41556
|
+
const entry = lock.packages?.[`node_modules/${name}`] ?? lock.dependencies?.[name];
|
|
41557
|
+
return typeof entry?.version === "string" ? entry.version : null;
|
|
41558
|
+
};
|
|
41559
|
+
};
|
|
41560
|
+
const buildYarnLockLookup = (lockText) => {
|
|
41561
|
+
const lines = lockText.split(/\r?\n/);
|
|
41562
|
+
return (name, spec) => {
|
|
41563
|
+
const descriptor = `${name}@${spec}`;
|
|
41564
|
+
const berryDescriptor = `${name}@npm:${spec}`;
|
|
41565
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
41566
|
+
const line = lines[lineIndex];
|
|
41567
|
+
if (line.startsWith(" ") || line.startsWith("#") || !line.includes(":")) continue;
|
|
41568
|
+
const descriptors = line.replace(/:\s*$/, "").split(",").map((part) => part.trim().replace(/^"|"$/g, ""));
|
|
41569
|
+
if (!descriptors.includes(descriptor) && !descriptors.includes(berryDescriptor)) continue;
|
|
41570
|
+
for (let bodyIndex = lineIndex + 1; bodyIndex < lines.length && (lines[bodyIndex].startsWith(" ") || lines[bodyIndex] === ""); bodyIndex += 1) {
|
|
41571
|
+
const versionMatch = /^\s+version:?\s+"?([^"\s]+)"?/.exec(lines[bodyIndex]);
|
|
41572
|
+
if (versionMatch) return versionMatch[1];
|
|
41573
|
+
}
|
|
41574
|
+
return null;
|
|
41575
|
+
}
|
|
41576
|
+
return null;
|
|
41577
|
+
};
|
|
41578
|
+
};
|
|
41579
|
+
const buildPnpmLockLookup = (lockText) => {
|
|
41580
|
+
return (name) => {
|
|
41581
|
+
const entryPattern = new RegExp(`^ {4,8}['"]?${name.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&")}['"]?:\\s*$`);
|
|
41582
|
+
const lines = lockText.split(/\r?\n/);
|
|
41583
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
41584
|
+
if (!entryPattern.test(lines[lineIndex])) continue;
|
|
41585
|
+
for (let bodyIndex = lineIndex + 1; bodyIndex < lines.length && /^ {5,}\S/.test(lines[bodyIndex]); bodyIndex += 1) {
|
|
41586
|
+
const versionMatch = /^\s+version:\s+['"]?([^'"\s(]+)/.exec(lines[bodyIndex]);
|
|
41587
|
+
if (versionMatch) return versionMatch[1];
|
|
41588
|
+
}
|
|
41589
|
+
}
|
|
41590
|
+
return null;
|
|
41591
|
+
};
|
|
41592
|
+
};
|
|
41593
|
+
const buildLockedVersionLookup = (rootDirectory) => {
|
|
41594
|
+
const npmLockText = readTextFileOrNull(Path.join(rootDirectory, "package-lock.json"));
|
|
41595
|
+
if (npmLockText !== null) return buildNpmLockLookup(npmLockText);
|
|
41596
|
+
const yarnLockText = readTextFileOrNull(Path.join(rootDirectory, "yarn.lock"));
|
|
41597
|
+
if (yarnLockText !== null) return buildYarnLockLookup(yarnLockText);
|
|
41598
|
+
const pnpmLockText = readTextFileOrNull(Path.join(rootDirectory, "pnpm-lock.yaml"));
|
|
41599
|
+
if (pnpmLockText !== null) return buildPnpmLockLookup(pnpmLockText);
|
|
41600
|
+
return null;
|
|
41601
|
+
};
|
|
41156
41602
|
const locateDependencyKey = (packageJsonText, section, name) => {
|
|
41157
41603
|
const needle = `"${name}"`;
|
|
41158
41604
|
const sectionHeader = new RegExp(`"${section}"\\s*:\\s*\\{`);
|
|
@@ -41179,7 +41625,7 @@ const locateDependencyKey = (packageJsonText, section, name) => {
|
|
|
41179
41625
|
}
|
|
41180
41626
|
return null;
|
|
41181
41627
|
};
|
|
41182
|
-
const collectDependenciesToScore = (packageJson, packageJsonText, includeDevDependencies) => {
|
|
41628
|
+
const collectDependenciesToScore = (packageJson, packageJsonText, includeDevDependencies, lockedVersionLookup) => {
|
|
41183
41629
|
const sectionByName = /* @__PURE__ */ new Map();
|
|
41184
41630
|
for (const name of Object.keys(packageJson.dependencies ?? {})) sectionByName.set(name, "dependencies");
|
|
41185
41631
|
if (includeDevDependencies) {
|
|
@@ -41189,13 +41635,18 @@ const collectDependenciesToScore = (packageJson, packageJsonText, includeDevDepe
|
|
|
41189
41635
|
for (const [name, section] of sectionByName) {
|
|
41190
41636
|
if (SUPPLY_CHAIN_IGNORED_PACKAGES.has(name)) continue;
|
|
41191
41637
|
const spec = (packageJson[section] ?? {})[name] ?? "";
|
|
41192
|
-
const
|
|
41193
|
-
if (
|
|
41638
|
+
const floorVersion = resolveConcreteVersion(spec);
|
|
41639
|
+
if (floorVersion === null) continue;
|
|
41640
|
+
const lockedVersion = lockedVersionLookup?.(name, spec) ?? null;
|
|
41641
|
+
const lockSatisfiesSpec = lockedVersion !== null && import_semver.valid(lockedVersion) !== null && import_semver.satisfies(lockedVersion, spec);
|
|
41642
|
+
const isExactPin = import_semver.valid(spec.trim()) !== null;
|
|
41194
41643
|
const location = locateDependencyKey(packageJsonText, section, name);
|
|
41195
41644
|
dependencies.push({
|
|
41196
41645
|
name,
|
|
41197
|
-
version,
|
|
41646
|
+
version: lockSatisfiesSpec ? lockedVersion : floorVersion,
|
|
41198
41647
|
spec,
|
|
41648
|
+
section,
|
|
41649
|
+
resolution: isExactPin ? "pin" : lockSatisfiesSpec ? "locked" : "floor",
|
|
41199
41650
|
line: location?.line ?? 0,
|
|
41200
41651
|
column: location?.column ?? 0
|
|
41201
41652
|
});
|
|
@@ -41346,7 +41797,11 @@ const formatAlertReason = (topAlerts, totalCount) => {
|
|
|
41346
41797
|
}
|
|
41347
41798
|
return `Socket flagged ${totalCount} alerts (${topAlerts.map((alert) => friendlyAlertType(alert.type)).join(", ")}${totalCount > topAlerts.length ? ` (+${totalCount - topAlerts.length} more)` : ""}); most severe: ${displaySeverity(topAlerts[0].severity)}.`;
|
|
41348
41799
|
};
|
|
41349
|
-
const formatDependencyIdentity = (dependency) =>
|
|
41800
|
+
const formatDependencyIdentity = (dependency) => {
|
|
41801
|
+
if (dependency.resolution === "pin") return `${dependency.name}@${dependency.version}`;
|
|
41802
|
+
if (dependency.resolution === "locked") return `${dependency.name}@${dependency.version} (lockfile resolution of "${dependency.spec}")`;
|
|
41803
|
+
return `${dependency.name}@${dependency.version} (lowest version "${dependency.spec}" allows)`;
|
|
41804
|
+
};
|
|
41350
41805
|
const buildSupplyChainHelp = (dependency, failingAxis, topAlerts, packagePageUrl, options) => {
|
|
41351
41806
|
const hasCriticalAlert = topAlerts.some((alert) => alert.severity.toLowerCase() === "critical");
|
|
41352
41807
|
const entry = `\`"${dependency.name}": "${dependency.spec}"\``;
|
|
@@ -41389,7 +41844,7 @@ const buildLowScoreDiagnostic = (dependency, artifact, failingAxis, options) =>
|
|
|
41389
41844
|
const checkSupplyChain = (input) => gen(function* () {
|
|
41390
41845
|
const options = resolveOptions(input.userConfig);
|
|
41391
41846
|
const packageJsonPath = Path.join(input.rootDirectory, "package.json");
|
|
41392
|
-
const dependencies = collectDependenciesToScore(readPackageJson(packageJsonPath), readPackageJsonText(packageJsonPath), options.includeDevDependencies);
|
|
41847
|
+
const dependencies = collectDependenciesToScore(readPackageJson(packageJsonPath), readPackageJsonText(packageJsonPath), options.includeDevDependencies, buildLockedVersionLookup(input.rootDirectory));
|
|
41393
41848
|
if (dependencies.length === 0) return [];
|
|
41394
41849
|
const cacheDirectory = isSupplyChainCacheDisabled() ? null : resolveReactDoctorCacheDir(input.rootDirectory);
|
|
41395
41850
|
if (cacheDirectory !== null) pruneExpiredSocketCache(cacheDirectory);
|
|
@@ -41398,7 +41853,7 @@ const checkSupplyChain = (input) => gen(function* () {
|
|
|
41398
41853
|
for (let index = 0; index < dependencies.length; index += 1) {
|
|
41399
41854
|
const artifact = artifacts[index];
|
|
41400
41855
|
if (!artifact) continue;
|
|
41401
|
-
const worstAxis = worstGatedAxis(artifact.score);
|
|
41856
|
+
const worstAxis = worstGatedAxis(artifact.score, gatedAxesForSection(dependencies[index].section));
|
|
41402
41857
|
if (toHundred(artifact.score[worstAxis.key]) >= options.minScore) continue;
|
|
41403
41858
|
diagnostics.push(buildLowScoreDiagnostic(dependencies[index], artifact, worstAxis, options));
|
|
41404
41859
|
}
|
|
@@ -42075,6 +42530,13 @@ const summarizeDiagnostics = (diagnostics, worstScore = null, worstScoreLabel =
|
|
|
42075
42530
|
scoreLabel: worstScoreLabel
|
|
42076
42531
|
};
|
|
42077
42532
|
};
|
|
42533
|
+
/**
|
|
42534
|
+
* Whether project discovery resolved a React-compatible runtime (React or
|
|
42535
|
+
* Preact, which ships the same hooks + JSX model). This is the same
|
|
42536
|
+
* predicate that grants the `react` capability, so `false` means every
|
|
42537
|
+
* React-runtime rule family was gated off for the project.
|
|
42538
|
+
*/
|
|
42539
|
+
const hasReactRuntime = (project) => project.reactVersion !== null || project.preactVersion !== null;
|
|
42078
42540
|
const toJsonDiff = (diff) => {
|
|
42079
42541
|
if (!diff) return null;
|
|
42080
42542
|
return {
|
|
@@ -42309,17 +42771,20 @@ const warnIfAiTrainingEnvironment = () => {
|
|
|
42309
42771
|
didWarnAiTraining = true;
|
|
42310
42772
|
console.warn("[react-doctor] Use in an AI or ML pipeline requires written permission under the react-doctor license. Contact founders@million.dev to request access.");
|
|
42311
42773
|
};
|
|
42312
|
-
const
|
|
42313
|
-
|
|
42314
|
-
|
|
42315
|
-
|
|
42316
|
-
|
|
42774
|
+
const resolveShouldRunLint = (options, effectiveConfig) => options.lint ?? effectiveConfig?.lint ?? true;
|
|
42775
|
+
const resolveShouldRunDeadCode = (options, effectiveConfig) => options.deadCode ?? effectiveConfig?.deadCode ?? true;
|
|
42776
|
+
const buildDiagnoseLayer = (input) => {
|
|
42777
|
+
const configLayer = input.configOverrideTarget === void 0 ? Config.layerNode : Config.layerOf({
|
|
42778
|
+
config: input.config,
|
|
42779
|
+
resolvedDirectory: input.configOverrideTarget.resolvedDirectory,
|
|
42780
|
+
configSourceDirectory: input.configOverrideTarget.configSourceDirectory
|
|
42317
42781
|
});
|
|
42318
|
-
return mergeAll$1(Project.layerNode, configLayer, DeadCode.layerNode, Files.layerNode, Git.layerNode, Linter.layerOxlint, LintPartialFailures.layerLive, Progress.layerNoop, Reporter.layerNoop, Score.layerHttp, config?.supplyChain?.enabled !== false ? SupplyChain.layerNode : SupplyChain.layerOf([]));
|
|
42782
|
+
return mergeAll$1(Project.layerNode, configLayer, input.shouldRunDeadCode ? DeadCode.layerNode : DeadCode.layerOf([]), Files.layerNode, Git.layerNode, input.shouldRunLint ? Linter.layerOxlint : Linter.layerOf([]), LintPartialFailures.layerLive, Progress.layerNoop, Reporter.layerNoop, Score.layerHttp, input.config?.supplyChain?.enabled !== false ? SupplyChain.layerNode : SupplyChain.layerOf([]));
|
|
42319
42783
|
};
|
|
42320
42784
|
const buildInspectProgram = (scanTarget, options, configOverride) => {
|
|
42321
42785
|
const effectiveConfig = configOverride ?? scanTarget.userConfig;
|
|
42322
42786
|
const includePaths = options.includePaths ?? [];
|
|
42787
|
+
const shouldRunDeadCode = resolveShouldRunDeadCode(options, effectiveConfig);
|
|
42323
42788
|
return runInspect({
|
|
42324
42789
|
directory: scanTarget.resolvedDirectory,
|
|
42325
42790
|
includePaths,
|
|
@@ -42328,7 +42793,7 @@ const buildInspectProgram = (scanTarget, options, configOverride) => {
|
|
|
42328
42793
|
warnings: options.warnings ?? effectiveConfig?.warnings ?? true,
|
|
42329
42794
|
adoptExistingLintConfig: effectiveConfig?.adoptExistingLintConfig ?? true,
|
|
42330
42795
|
ignoredTags: new Set(effectiveConfig?.ignore?.tags ?? []),
|
|
42331
|
-
runDeadCode:
|
|
42796
|
+
runDeadCode: shouldRunDeadCode,
|
|
42332
42797
|
isCi: false,
|
|
42333
42798
|
resolveLocalGithubViewerPermission: true
|
|
42334
42799
|
});
|
|
@@ -42350,7 +42815,14 @@ const diagnoseDirectory = async (directory, options) => {
|
|
|
42350
42815
|
warnIfAiTrainingEnvironment();
|
|
42351
42816
|
const startTime = globalThis.performance.now();
|
|
42352
42817
|
const scanTarget = await resolveScanTarget(directory);
|
|
42353
|
-
|
|
42818
|
+
const program = buildInspectProgram(scanTarget, options);
|
|
42819
|
+
const shouldRunLint = resolveShouldRunLint(options, scanTarget.userConfig);
|
|
42820
|
+
const shouldRunDeadCode = resolveShouldRunDeadCode(options, scanTarget.userConfig);
|
|
42821
|
+
return outputToDiagnoseResult(await runPromise(restoreLegacyThrow(program.pipe(provide(buildDiagnoseLayer({
|
|
42822
|
+
config: scanTarget.userConfig,
|
|
42823
|
+
shouldRunLint,
|
|
42824
|
+
shouldRunDeadCode
|
|
42825
|
+
})), provide(layerOtlp)))), globalThis.performance.now() - startTime);
|
|
42354
42826
|
};
|
|
42355
42827
|
const findWorstScore = (projectResults) => {
|
|
42356
42828
|
let worstResult = null;
|
|
@@ -42371,15 +42843,27 @@ const diagnoseProject = async (projectDefinition, baseOptions, batchConfig) => {
|
|
|
42371
42843
|
const { directory: _, config: projectConfig, ...perProjectOptions } = projectDefinition;
|
|
42372
42844
|
const didOverrideConfig = batchConfig !== void 0 || projectConfig !== void 0;
|
|
42373
42845
|
const effectiveConfig = mergeReactDoctorConfigs(mergeReactDoctorConfigs(scanTarget.userConfig, batchConfig), projectConfig);
|
|
42374
|
-
const
|
|
42846
|
+
const mergedOptions = {
|
|
42375
42847
|
...baseOptions,
|
|
42376
42848
|
...perProjectOptions
|
|
42377
|
-
}
|
|
42849
|
+
};
|
|
42850
|
+
const shouldRunLint = resolveShouldRunLint(mergedOptions, effectiveConfig);
|
|
42851
|
+
const shouldRunDeadCode = resolveShouldRunDeadCode(mergedOptions, effectiveConfig);
|
|
42852
|
+
const program = buildInspectProgram(scanTarget, mergedOptions, effectiveConfig ?? void 0);
|
|
42378
42853
|
const didOverridePlugins = batchConfig?.plugins !== void 0 || projectConfig?.plugins !== void 0;
|
|
42379
|
-
const layer = buildDiagnoseLayer(
|
|
42380
|
-
|
|
42381
|
-
|
|
42382
|
-
|
|
42854
|
+
const layer = buildDiagnoseLayer(didOverrideConfig ? {
|
|
42855
|
+
config: effectiveConfig,
|
|
42856
|
+
shouldRunLint,
|
|
42857
|
+
shouldRunDeadCode,
|
|
42858
|
+
configOverrideTarget: {
|
|
42859
|
+
resolvedDirectory: scanTarget.resolvedDirectory,
|
|
42860
|
+
configSourceDirectory: didOverridePlugins ? null : scanTarget.configSourceDirectory
|
|
42861
|
+
}
|
|
42862
|
+
} : {
|
|
42863
|
+
config: effectiveConfig,
|
|
42864
|
+
shouldRunLint,
|
|
42865
|
+
shouldRunDeadCode
|
|
42866
|
+
});
|
|
42383
42867
|
return {
|
|
42384
42868
|
ok: true,
|
|
42385
42869
|
...outputToDiagnoseResult(await runPromise(restoreLegacyThrow(program.pipe(provide(layer), provide(layerOtlp)))), globalThis.performance.now() - startTime),
|
|
@@ -42441,4 +42925,4 @@ const toJsonReport = (result, options) => buildJsonReport({
|
|
|
42441
42925
|
export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, hasReactRuntime, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
|
|
42442
42926
|
|
|
42443
42927
|
//# sourceMappingURL=index.js.map
|
|
42444
|
-
//# debugId=
|
|
42928
|
+
//# debugId=63c65851-4fa5-5cd1-9337-0f2feefe37ad
|