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/lsp.js
CHANGED
|
@@ -8,7 +8,7 @@ import path from "node:path";
|
|
|
8
8
|
import * as NodeChildProcess from "node:child_process";
|
|
9
9
|
import { execFileSync, spawn, spawnSync } from "node:child_process";
|
|
10
10
|
import * as ts from "typescript";
|
|
11
|
-
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";
|
|
11
|
+
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";
|
|
12
12
|
import { parseJSON5 } from "confbox";
|
|
13
13
|
import * as NodeUrl from "node:url";
|
|
14
14
|
import { fileURLToPath } from "node:url";
|
|
@@ -32827,6 +32827,25 @@ var AmbiguousProjectError = class extends Error {
|
|
|
32827
32827
|
}
|
|
32828
32828
|
};
|
|
32829
32829
|
const isProjectDiscoveryError = (value) => value instanceof ProjectNotFoundError || value instanceof NoReactDependencyError || value instanceof PackageJsonNotFoundError || value instanceof NotADirectoryError || value instanceof AmbiguousProjectError;
|
|
32830
|
+
const isErrnoException = (error) => error instanceof Error && "code" in error;
|
|
32831
|
+
const IGNORABLE_READDIR_ERROR_CODES = new Set([
|
|
32832
|
+
"EACCES",
|
|
32833
|
+
"EPERM",
|
|
32834
|
+
"ENOENT",
|
|
32835
|
+
"ENOTDIR",
|
|
32836
|
+
"EINVAL",
|
|
32837
|
+
"ELOOP",
|
|
32838
|
+
"ENAMETOOLONG"
|
|
32839
|
+
]);
|
|
32840
|
+
const isIgnorableReaddirError = (error) => isErrnoException(error) && typeof error.code === "string" && IGNORABLE_READDIR_ERROR_CODES.has(error.code);
|
|
32841
|
+
const readDirectoryEntries = (directoryPath) => {
|
|
32842
|
+
try {
|
|
32843
|
+
return NFS.readdirSync(directoryPath, { withFileTypes: true });
|
|
32844
|
+
} catch (error) {
|
|
32845
|
+
if (isIgnorableReaddirError(error)) return [];
|
|
32846
|
+
throw error;
|
|
32847
|
+
}
|
|
32848
|
+
};
|
|
32830
32849
|
const isFile = (filePath) => {
|
|
32831
32850
|
try {
|
|
32832
32851
|
return NFS.statSync(filePath).isFile();
|
|
@@ -32834,10 +32853,24 @@ const isFile = (filePath) => {
|
|
|
32834
32853
|
return false;
|
|
32835
32854
|
}
|
|
32836
32855
|
};
|
|
32856
|
+
const isDirectory = (directoryPath) => {
|
|
32857
|
+
try {
|
|
32858
|
+
return NFS.statSync(directoryPath).isDirectory();
|
|
32859
|
+
} catch {
|
|
32860
|
+
return false;
|
|
32861
|
+
}
|
|
32862
|
+
};
|
|
32863
|
+
const isPlainObject = (value) => {
|
|
32864
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
32865
|
+
const prototype = Object.getPrototypeOf(value);
|
|
32866
|
+
return prototype === null || prototype === Object.prototype;
|
|
32867
|
+
};
|
|
32837
32868
|
const SOURCE_FILE_PATTERN = /\.(tsx?|jsx?|mts|mjs)$/;
|
|
32838
32869
|
const GENERATED_BUNDLE_FILE_PATTERN = /\.(iife|umd|global|min)\.m?js$/i;
|
|
32870
|
+
const GENERATED_SOURCE_DIRECTORY_PATTERN = /(?:^|\/)__generated__\//;
|
|
32839
32871
|
const MINIFIED_SNIFF_BYTES = 65536;
|
|
32840
32872
|
const GIT_LS_FILES_MAX_BUFFER_BYTES = 50 * 1024 * 1024;
|
|
32873
|
+
const SCANNED_DOT_DIRECTORIES = new Set([".dumi", ".storybook"]);
|
|
32841
32874
|
const IGNORED_DIRECTORIES = new Set([
|
|
32842
32875
|
".angular",
|
|
32843
32876
|
".astro",
|
|
@@ -33002,7 +33035,7 @@ const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
|
|
|
33002
33035
|
const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
|
|
33003
33036
|
const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
|
|
33004
33037
|
const SIDECAR_LINT_CACHE_FILENAME = "sidecar-lint-cache.json";
|
|
33005
|
-
const CORE_PACKAGE_VERSION = "0.7.
|
|
33038
|
+
const CORE_PACKAGE_VERSION = "0.7.2";
|
|
33006
33039
|
const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
|
|
33007
33040
|
const DEAD_CODE_SUMMARY_CACHE_FILENAME = "dead-code-summaries.json";
|
|
33008
33041
|
const SUPPLY_CHAIN_PLUGIN = "socket";
|
|
@@ -33012,8 +33045,9 @@ const SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS = 9e4;
|
|
|
33012
33045
|
const SUPPLY_CHAIN_CACHE_TTL_MS = 864e5;
|
|
33013
33046
|
const SUPPLY_CHAIN_CACHE_SUBDIR = "supply-chain";
|
|
33014
33047
|
const SUPPLY_CHAIN_IGNORED_PACKAGES = new Set(["next"]);
|
|
33015
|
-
const
|
|
33016
|
-
const
|
|
33048
|
+
const isIgnoredDirectoryName = (directoryName) => IGNORED_DIRECTORIES.has(directoryName) || directoryName.startsWith(".") && !SCANNED_DOT_DIRECTORIES.has(directoryName);
|
|
33049
|
+
const hasIgnoredPathSegment = (relativePath) => relativePath.split(/[/\\]/).slice(0, -1).some((segment) => isIgnoredDirectoryName(segment));
|
|
33050
|
+
const isLintableSourceFile = (filePath) => SOURCE_FILE_PATTERN.test(filePath) && !GENERATED_BUNDLE_FILE_PATTERN.test(filePath) && !GENERATED_SOURCE_DIRECTORY_PATTERN.test(filePath);
|
|
33017
33051
|
const isMinifiedSource = (absolutePath) => {
|
|
33018
33052
|
let fileDescriptor;
|
|
33019
33053
|
try {
|
|
@@ -33050,25 +33084,6 @@ const isLargeMinifiedFile = (absolutePath, knownSizeBytes) => {
|
|
|
33050
33084
|
cachedIsLargeMinifiedByPath.set(absolutePath, result);
|
|
33051
33085
|
return result;
|
|
33052
33086
|
};
|
|
33053
|
-
const isErrnoException = (error) => error instanceof Error && "code" in error;
|
|
33054
|
-
const IGNORABLE_READDIR_ERROR_CODES = new Set([
|
|
33055
|
-
"EACCES",
|
|
33056
|
-
"EPERM",
|
|
33057
|
-
"ENOENT",
|
|
33058
|
-
"ENOTDIR",
|
|
33059
|
-
"EINVAL",
|
|
33060
|
-
"ELOOP",
|
|
33061
|
-
"ENAMETOOLONG"
|
|
33062
|
-
]);
|
|
33063
|
-
const isIgnorableReaddirError = (error) => isErrnoException(error) && typeof error.code === "string" && IGNORABLE_READDIR_ERROR_CODES.has(error.code);
|
|
33064
|
-
const readDirectoryEntries = (directoryPath) => {
|
|
33065
|
-
try {
|
|
33066
|
-
return NFS.readdirSync(directoryPath, { withFileTypes: true });
|
|
33067
|
-
} catch (error) {
|
|
33068
|
-
if (isIgnorableReaddirError(error)) return [];
|
|
33069
|
-
throw error;
|
|
33070
|
-
}
|
|
33071
|
-
};
|
|
33072
33087
|
function* walkSourceTreeFiles(rootDirectory) {
|
|
33073
33088
|
const stack = [rootDirectory];
|
|
33074
33089
|
while (stack.length > 0) {
|
|
@@ -33077,7 +33092,7 @@ function* walkSourceTreeFiles(rootDirectory) {
|
|
|
33077
33092
|
for (const entry of readDirectoryEntries(currentDirectory)) {
|
|
33078
33093
|
const absolutePath = Path.join(currentDirectory, entry.name);
|
|
33079
33094
|
if (entry.isDirectory()) {
|
|
33080
|
-
if (!
|
|
33095
|
+
if (!isIgnoredDirectoryName(entry.name)) stack.push(absolutePath);
|
|
33081
33096
|
continue;
|
|
33082
33097
|
}
|
|
33083
33098
|
if (entry.isFile()) yield {
|
|
@@ -33174,6 +33189,146 @@ const findMonorepoRoot = (startDirectory) => {
|
|
|
33174
33189
|
* byte-equivalent copy.
|
|
33175
33190
|
*/
|
|
33176
33191
|
const isProjectBoundary = (directory) => NFS.existsSync(Path.join(directory, ".git")) || isMonorepoRoot(directory);
|
|
33192
|
+
const TSCONFIG_FILENAME = "tsconfig.json";
|
|
33193
|
+
const isRelativeExtendsValue = (extendsValue) => extendsValue.startsWith("./") || extendsValue.startsWith("../") || Path.isAbsolute(extendsValue);
|
|
33194
|
+
const ensureJsonExtension = (filePath) => Path.extname(filePath) === "" ? `${filePath}.json` : filePath;
|
|
33195
|
+
const resolvePackageExtendsPath = (extendsValue, fromConfigDirectory) => {
|
|
33196
|
+
const requireFromConfig = createRequire(Path.join(fromConfigDirectory, "tsconfig.json"));
|
|
33197
|
+
const candidates = [
|
|
33198
|
+
extendsValue,
|
|
33199
|
+
ensureJsonExtension(extendsValue),
|
|
33200
|
+
`${extendsValue.replace(/\/$/, "")}/tsconfig.json`
|
|
33201
|
+
];
|
|
33202
|
+
for (const candidate of candidates) try {
|
|
33203
|
+
return requireFromConfig.resolve(candidate);
|
|
33204
|
+
} catch {
|
|
33205
|
+
continue;
|
|
33206
|
+
}
|
|
33207
|
+
return null;
|
|
33208
|
+
};
|
|
33209
|
+
const resolveExtendsPath = (extendsValue, fromConfigDirectory) => {
|
|
33210
|
+
if (isRelativeExtendsValue(extendsValue)) return ensureJsonExtension(Path.resolve(fromConfigDirectory, extendsValue));
|
|
33211
|
+
return resolvePackageExtendsPath(extendsValue, fromConfigDirectory);
|
|
33212
|
+
};
|
|
33213
|
+
const normalizeCompilerOptions = (compilerOptions) => {
|
|
33214
|
+
if (!isPlainObject(compilerOptions)) return { hasExplicitLib: false };
|
|
33215
|
+
const target = typeof compilerOptions.target === "string" ? compilerOptions.target : void 0;
|
|
33216
|
+
const hasExplicitLib = Object.hasOwn(compilerOptions, "lib");
|
|
33217
|
+
return {
|
|
33218
|
+
target,
|
|
33219
|
+
lib: Array.isArray(compilerOptions.lib) ? compilerOptions.lib.filter((entry) => typeof entry === "string") : void 0,
|
|
33220
|
+
hasExplicitLib
|
|
33221
|
+
};
|
|
33222
|
+
};
|
|
33223
|
+
const readTsConfig = (filePath) => {
|
|
33224
|
+
let content;
|
|
33225
|
+
try {
|
|
33226
|
+
content = NFS.readFileSync(filePath, "utf-8");
|
|
33227
|
+
} catch {
|
|
33228
|
+
return null;
|
|
33229
|
+
}
|
|
33230
|
+
const parsed = ts.parseConfigFileTextToJson(filePath, content);
|
|
33231
|
+
if (!isPlainObject(parsed.config)) return null;
|
|
33232
|
+
return {
|
|
33233
|
+
extends: typeof parsed.config.extends === "string" ? parsed.config.extends : void 0,
|
|
33234
|
+
referencePaths: normalizeReferencePaths(parsed.config.references),
|
|
33235
|
+
compilerOptions: normalizeCompilerOptions(parsed.config.compilerOptions)
|
|
33236
|
+
};
|
|
33237
|
+
};
|
|
33238
|
+
const normalizeReferencePaths = (references) => {
|
|
33239
|
+
if (!Array.isArray(references)) return [];
|
|
33240
|
+
return references.map((reference) => isPlainObject(reference) && typeof reference.path === "string" ? reference.path : null).filter((referencePath) => referencePath !== null);
|
|
33241
|
+
};
|
|
33242
|
+
const mergeCompilerOptions = (inherited, current) => {
|
|
33243
|
+
const target = current.target ?? inherited?.target;
|
|
33244
|
+
const hasExplicitLib = current.hasExplicitLib || Boolean(inherited?.hasExplicitLib);
|
|
33245
|
+
return {
|
|
33246
|
+
target,
|
|
33247
|
+
lib: current.hasExplicitLib ? current.lib : inherited?.lib,
|
|
33248
|
+
hasExplicitLib
|
|
33249
|
+
};
|
|
33250
|
+
};
|
|
33251
|
+
const readResolvedCompilerOptions = (tsConfigPath, extendsDepth, visitedPaths) => {
|
|
33252
|
+
const realPath = NFS.realpathSync.native(tsConfigPath);
|
|
33253
|
+
if (visitedPaths.has(realPath)) return null;
|
|
33254
|
+
const tsConfig = readTsConfig(realPath);
|
|
33255
|
+
if (!tsConfig) return null;
|
|
33256
|
+
const nextVisitedPaths = new Set(visitedPaths);
|
|
33257
|
+
nextVisitedPaths.add(realPath);
|
|
33258
|
+
if (tsConfig.extends && extendsDepth < 8) {
|
|
33259
|
+
const parentPath = resolveExtendsPath(tsConfig.extends, Path.dirname(realPath));
|
|
33260
|
+
if (parentPath && isFile(parentPath)) return mergeCompilerOptions(readResolvedCompilerOptions(parentPath, extendsDepth + 1, nextVisitedPaths), tsConfig.compilerOptions);
|
|
33261
|
+
}
|
|
33262
|
+
return tsConfig.compilerOptions;
|
|
33263
|
+
};
|
|
33264
|
+
const targetYearIsPreES2023 = (target) => {
|
|
33265
|
+
const year = ES_TARGET_YEAR_BY_NAME[target.toLowerCase()];
|
|
33266
|
+
return year !== void 0 && year < 2023;
|
|
33267
|
+
};
|
|
33268
|
+
const libEntryIncludesES2023Array = (entry) => {
|
|
33269
|
+
const normalizedEntry = entry.toLowerCase();
|
|
33270
|
+
if (normalizedEntry === "esnext" || normalizedEntry === "esnext.array") return true;
|
|
33271
|
+
const esYearMatch = /^es(\d{4})(?:\.(.+))?$/.exec(normalizedEntry);
|
|
33272
|
+
if (!esYearMatch) return false;
|
|
33273
|
+
if (Number(esYearMatch[1]) < 2023) return false;
|
|
33274
|
+
const component = esYearMatch[2];
|
|
33275
|
+
return component === void 0 || component === "array";
|
|
33276
|
+
};
|
|
33277
|
+
const libIncludesES2023 = (lib) => lib.some(libEntryIncludesES2023Array);
|
|
33278
|
+
const compilerOptionsArePreES2023 = (compilerOptions) => {
|
|
33279
|
+
if (compilerOptions.target) return targetYearIsPreES2023(compilerOptions.target);
|
|
33280
|
+
if (compilerOptions.hasExplicitLib) return !libIncludesES2023(compilerOptions.lib ?? []);
|
|
33281
|
+
return false;
|
|
33282
|
+
};
|
|
33283
|
+
const compilerOptionsDeclareTargetOrLib = (compilerOptions) => compilerOptions.hasExplicitLib || compilerOptions.target !== void 0;
|
|
33284
|
+
const detectPreES2023FromConfig = (tsConfigPath, visitedConfigPaths = /* @__PURE__ */ new Set()) => {
|
|
33285
|
+
if (visitedConfigPaths.has(tsConfigPath)) return false;
|
|
33286
|
+
const compilerOptions = readResolvedCompilerOptions(tsConfigPath, 0, /* @__PURE__ */ new Set());
|
|
33287
|
+
if (!compilerOptions) return false;
|
|
33288
|
+
if (!compilerOptionsDeclareTargetOrLib(compilerOptions)) {
|
|
33289
|
+
const tsConfig = readTsConfig(tsConfigPath);
|
|
33290
|
+
if (!tsConfig) return false;
|
|
33291
|
+
const nextVisitedConfigPaths = new Set(visitedConfigPaths);
|
|
33292
|
+
nextVisitedConfigPaths.add(tsConfigPath);
|
|
33293
|
+
const configDirectory = Path.dirname(tsConfigPath);
|
|
33294
|
+
return tsConfig.referencePaths.some((referencePath) => {
|
|
33295
|
+
const resolvedReferencePath = Path.resolve(configDirectory, referencePath);
|
|
33296
|
+
const referencedConfigPath = isFile(resolvedReferencePath) ? resolvedReferencePath : Path.join(resolvedReferencePath, TSCONFIG_FILENAME);
|
|
33297
|
+
return isFile(referencedConfigPath) && detectPreES2023FromConfig(referencedConfigPath, nextVisitedConfigPaths);
|
|
33298
|
+
});
|
|
33299
|
+
}
|
|
33300
|
+
return compilerOptionsArePreES2023(compilerOptions);
|
|
33301
|
+
};
|
|
33302
|
+
const detectPreES2023Target = (directory) => {
|
|
33303
|
+
const tsConfigPath = Path.join(directory, TSCONFIG_FILENAME);
|
|
33304
|
+
if (isFile(tsConfigPath)) return detectPreES2023FromConfig(tsConfigPath);
|
|
33305
|
+
for (const fallbackFilename of FALLBACK_TSCONFIG_FILENAMES) {
|
|
33306
|
+
const fallbackPath = Path.join(directory, fallbackFilename);
|
|
33307
|
+
if (isFile(fallbackPath)) return detectPreES2023FromConfig(fallbackPath);
|
|
33308
|
+
}
|
|
33309
|
+
return false;
|
|
33310
|
+
};
|
|
33311
|
+
const FALLBACK_TSCONFIG_FILENAMES = ["tsconfig.app.json", "tsconfig.build.json"];
|
|
33312
|
+
const FRAMEWORK_PACKAGES = {
|
|
33313
|
+
next: "nextjs",
|
|
33314
|
+
"@tanstack/react-start": "tanstack-start",
|
|
33315
|
+
vite: "vite",
|
|
33316
|
+
"react-scripts": "cra",
|
|
33317
|
+
"@remix-run/react": "remix",
|
|
33318
|
+
gatsby: "gatsby",
|
|
33319
|
+
expo: "expo",
|
|
33320
|
+
"react-native": "react-native"
|
|
33321
|
+
};
|
|
33322
|
+
const detectFramework = (dependencies) => {
|
|
33323
|
+
for (const [packageName, frameworkName] of Object.entries(FRAMEWORK_PACKAGES)) if (dependencies[packageName]) return frameworkName;
|
|
33324
|
+
if (dependencies.preact && !dependencies.react) return "preact";
|
|
33325
|
+
return "unknown";
|
|
33326
|
+
};
|
|
33327
|
+
const MOBILE_FRAMEWORKS = new Set(["expo", "react-native"]);
|
|
33328
|
+
const frameworkMergeRank = (framework) => {
|
|
33329
|
+
if (framework === "unknown") return 3;
|
|
33330
|
+
return MOBILE_FRAMEWORKS.has(framework) ? 2 : 1;
|
|
33331
|
+
};
|
|
33177
33332
|
const REACT_COMPILER_PACKAGES = new Set([
|
|
33178
33333
|
"babel-plugin-react-compiler",
|
|
33179
33334
|
"react-compiler-runtime",
|
|
@@ -33210,6 +33365,7 @@ const EXPO_APP_CONFIG_FILENAMES = [
|
|
|
33210
33365
|
];
|
|
33211
33366
|
const REACT_COMPILER_PACKAGE_REFERENCE_PATTERN = /babel-plugin-react-compiler|react-compiler-runtime|eslint-plugin-react-compiler|["']react-compiler["']/;
|
|
33212
33367
|
const REACT_COMPILER_ENABLED_FLAG_PATTERN = /["']?reactCompiler["']?\s*:\s*(?:true\b|\{)/;
|
|
33368
|
+
const STATIC_EXPORT_OUTPUT_PATTERN = /(?:^|[^.\w])["']?output["']?\s*:\s*["']export["']/m;
|
|
33213
33369
|
const hasCompilerPackage = (packageJson) => {
|
|
33214
33370
|
const allDependencies = {
|
|
33215
33371
|
...packageJson.peerDependencies,
|
|
@@ -33242,34 +33398,10 @@ const detectReactCompiler = (directory, packageJson) => {
|
|
|
33242
33398
|
}
|
|
33243
33399
|
return false;
|
|
33244
33400
|
};
|
|
33245
|
-
const
|
|
33246
|
-
|
|
33247
|
-
|
|
33248
|
-
|
|
33249
|
-
"react-scripts": "cra",
|
|
33250
|
-
"@remix-run/react": "remix",
|
|
33251
|
-
gatsby: "gatsby",
|
|
33252
|
-
expo: "expo",
|
|
33253
|
-
"react-native": "react-native"
|
|
33254
|
-
};
|
|
33255
|
-
const FRAMEWORK_DISPLAY_NAMES = {
|
|
33256
|
-
nextjs: "Next.js",
|
|
33257
|
-
"tanstack-start": "TanStack Start",
|
|
33258
|
-
vite: "Vite",
|
|
33259
|
-
cra: "Create React App",
|
|
33260
|
-
remix: "Remix",
|
|
33261
|
-
gatsby: "Gatsby",
|
|
33262
|
-
expo: "Expo",
|
|
33263
|
-
"react-native": "React Native",
|
|
33264
|
-
preact: "Preact",
|
|
33265
|
-
unknown: "React"
|
|
33266
|
-
};
|
|
33267
|
-
const formatFrameworkName = (framework) => FRAMEWORK_DISPLAY_NAMES[framework];
|
|
33268
|
-
const detectFramework = (dependencies) => {
|
|
33269
|
-
for (const [packageName, frameworkName] of Object.entries(FRAMEWORK_PACKAGES)) if (dependencies[packageName]) return frameworkName;
|
|
33270
|
-
if (dependencies.preact && !dependencies.react) return "preact";
|
|
33271
|
-
return "unknown";
|
|
33272
|
-
};
|
|
33401
|
+
const detectNextjsStaticExport = (directory) => NEXT_CONFIG_FILENAMES.some((filename) => {
|
|
33402
|
+
const filePath = Path.join(directory, filename);
|
|
33403
|
+
return isFile(filePath) && STATIC_EXPORT_OUTPUT_PATTERN.test(NFS.readFileSync(filePath, "utf-8"));
|
|
33404
|
+
});
|
|
33273
33405
|
const UNRESOLVABLE_PROTOCOL_VERSION = /^(?:file|git|github|https?|link|patch|portal|workspace|npm):/i;
|
|
33274
33406
|
const DIST_TAG_VERSION = /^[a-z][a-z0-9._-]*$/i;
|
|
33275
33407
|
const WILDCARD_VERSION = /^[*xX](?:\.[*xX])*$/;
|
|
@@ -33407,10 +33539,76 @@ const isConcreteDependencyVersion = (version) => {
|
|
|
33407
33539
|
const normalizedVersion = normalizeDependencyVersion(version);
|
|
33408
33540
|
return normalizedVersion !== null && /\d/.test(normalizedVersion);
|
|
33409
33541
|
};
|
|
33410
|
-
const
|
|
33411
|
-
if (
|
|
33412
|
-
|
|
33413
|
-
return
|
|
33542
|
+
const isMajorMinorAtLeast = (detected, required) => {
|
|
33543
|
+
if (detected === null) return true;
|
|
33544
|
+
if (detected.major !== required.major) return detected.major > required.major;
|
|
33545
|
+
return detected.minor >= required.minor;
|
|
33546
|
+
};
|
|
33547
|
+
const parseReactMajor = (reactVersion) => {
|
|
33548
|
+
if (typeof reactVersion !== "string") return null;
|
|
33549
|
+
return getLowestDependencyMajor(reactVersion);
|
|
33550
|
+
};
|
|
33551
|
+
const MAJOR_MINOR_PATTERN = /(\d{1,4})\.(\d{1,4})/;
|
|
33552
|
+
const MAJOR_ONLY_PATTERN = /(\d{1,4})/;
|
|
33553
|
+
const UPPER_BOUND_COMPARATOR_PATTERN = /<=?\s{0,8}\d{1,4}(?:\.\d{1,4}){0,2}(?:-[^\s,|]+)?/g;
|
|
33554
|
+
const parseReactMajorMinor = (reactVersion) => {
|
|
33555
|
+
if (typeof reactVersion !== "string") return null;
|
|
33556
|
+
const trimmed = reactVersion.trim();
|
|
33557
|
+
if (trimmed.length === 0) return null;
|
|
33558
|
+
const lowerBoundsOnly = trimmed.replace(UPPER_BOUND_COMPARATOR_PATTERN, " ").trim();
|
|
33559
|
+
if (lowerBoundsOnly.length === 0) return null;
|
|
33560
|
+
const majorMinorMatch = lowerBoundsOnly.match(MAJOR_MINOR_PATTERN);
|
|
33561
|
+
if (majorMinorMatch) {
|
|
33562
|
+
const major = Number.parseInt(majorMinorMatch[1], 10);
|
|
33563
|
+
const minor = Number.parseInt(majorMinorMatch[2], 10);
|
|
33564
|
+
if (!Number.isFinite(major) || major <= 0) return null;
|
|
33565
|
+
if (!Number.isFinite(minor) || minor < 0) return null;
|
|
33566
|
+
return {
|
|
33567
|
+
major,
|
|
33568
|
+
minor
|
|
33569
|
+
};
|
|
33570
|
+
}
|
|
33571
|
+
const majorOnlyMatch = lowerBoundsOnly.match(MAJOR_ONLY_PATTERN);
|
|
33572
|
+
if (!majorOnlyMatch) return null;
|
|
33573
|
+
const major = Number.parseInt(majorOnlyMatch[1], 10);
|
|
33574
|
+
if (!Number.isFinite(major) || major <= 0) return null;
|
|
33575
|
+
return {
|
|
33576
|
+
major,
|
|
33577
|
+
minor: 0
|
|
33578
|
+
};
|
|
33579
|
+
};
|
|
33580
|
+
const parseLowerBoundVersion = (versionSpec) => import_semver.validRange(versionSpec) !== null ? import_semver.minVersion(versionSpec) : import_semver.coerce(versionSpec);
|
|
33581
|
+
const parseTailwindMajorMinor = (tailwindVersion) => {
|
|
33582
|
+
if (typeof tailwindVersion !== "string") return null;
|
|
33583
|
+
const trimmed = tailwindVersion.trim();
|
|
33584
|
+
if (trimmed.length === 0) return null;
|
|
33585
|
+
const lowerBound = parseLowerBoundVersion(trimmed);
|
|
33586
|
+
if (lowerBound === null || lowerBound.major <= 0) return null;
|
|
33587
|
+
return {
|
|
33588
|
+
major: lowerBound.major,
|
|
33589
|
+
minor: lowerBound.minor
|
|
33590
|
+
};
|
|
33591
|
+
};
|
|
33592
|
+
const hasUpperBoundOnlyPeerRange = (range) => {
|
|
33593
|
+
if (typeof range !== "string") return false;
|
|
33594
|
+
const normalizedRange = normalizeDependencyVersion(range);
|
|
33595
|
+
if (normalizedRange === null) return false;
|
|
33596
|
+
return splitDependencyVersionBranches(normalizedRange).some((branch) => {
|
|
33597
|
+
const normalizedBranch = normalizeDependencyVersion(branch);
|
|
33598
|
+
return normalizedBranch !== null && getBranchLowestMajor(normalizedBranch) === null && hasUpperBoundComparator(normalizedBranch);
|
|
33599
|
+
});
|
|
33600
|
+
};
|
|
33601
|
+
const peerRangeMinMajor = (range) => {
|
|
33602
|
+
if (typeof range !== "string") return null;
|
|
33603
|
+
return getLowestDependencyMajor(range);
|
|
33604
|
+
};
|
|
33605
|
+
const resolveEffectiveReactMajor = (reactVersion, packageJson) => {
|
|
33606
|
+
const installedReactMajor = parseReactMajor(reactVersion);
|
|
33607
|
+
const peerReactRange = packageJson.peerDependencies?.react;
|
|
33608
|
+
if (typeof peerReactRange !== "string") return installedReactMajor;
|
|
33609
|
+
const peerFloor = peerRangeMinMajor(peerReactRange);
|
|
33610
|
+
if (peerFloor === null) return hasUpperBoundOnlyPeerRange(peerReactRange) ? null : installedReactMajor;
|
|
33611
|
+
return installedReactMajor !== null ? Math.min(installedReactMajor, peerFloor) : peerFloor;
|
|
33414
33612
|
};
|
|
33415
33613
|
const isCatalogReference = (version) => version.startsWith("catalog:");
|
|
33416
33614
|
const extractCatalogName = (version) => {
|
|
@@ -33552,6 +33750,16 @@ const resolveCatalogVersion = (packageJson, packageName, rootDirectory, explicit
|
|
|
33552
33750
|
}
|
|
33553
33751
|
return null;
|
|
33554
33752
|
};
|
|
33753
|
+
const REACT_SECTIONS = [
|
|
33754
|
+
"dependencies",
|
|
33755
|
+
"peerDependencies",
|
|
33756
|
+
"devDependencies"
|
|
33757
|
+
];
|
|
33758
|
+
const TAILWIND_ZOD_SECTIONS = [
|
|
33759
|
+
"dependencies",
|
|
33760
|
+
"devDependencies",
|
|
33761
|
+
"peerDependencies"
|
|
33762
|
+
];
|
|
33555
33763
|
const EMPTY_DEPENDENCY_INFO = {
|
|
33556
33764
|
reactVersion: null,
|
|
33557
33765
|
tailwindVersion: null,
|
|
@@ -33592,6 +33800,10 @@ const extractDependencyInfo = (packageJson) => {
|
|
|
33592
33800
|
framework: detectFramework(allDependencies)
|
|
33593
33801
|
};
|
|
33594
33802
|
};
|
|
33803
|
+
const getDependencySpec = (packageJson, packageName) => {
|
|
33804
|
+
const spec = packageJson.dependencies?.[packageName] ?? packageJson.devDependencies?.[packageName] ?? packageJson.peerDependencies?.[packageName] ?? packageJson.optionalDependencies?.[packageName];
|
|
33805
|
+
return typeof spec === "string" ? spec : null;
|
|
33806
|
+
};
|
|
33595
33807
|
const getDependencyDeclaration = ({ packageJson, packageName, sections }) => {
|
|
33596
33808
|
for (const section of sections) {
|
|
33597
33809
|
const version = packageJson[section]?.[packageName];
|
|
@@ -33608,222 +33820,91 @@ const getDependencyDeclaration = ({ packageJson, packageName, sections }) => {
|
|
|
33608
33820
|
version: null
|
|
33609
33821
|
};
|
|
33610
33822
|
};
|
|
33611
|
-
const
|
|
33612
|
-
|
|
33613
|
-
|
|
33614
|
-
|
|
33615
|
-
|
|
33616
|
-
|
|
33617
|
-
|
|
33618
|
-
const
|
|
33619
|
-
|
|
33620
|
-
|
|
33621
|
-
|
|
33622
|
-
|
|
33623
|
-
|
|
33624
|
-
if (!isFile(Path.join(rootDirectory, "nx.json"))) return [];
|
|
33625
|
-
const collected = [];
|
|
33626
|
-
for (const candidate of NX_PROJECT_DISCOVERY_DIRS) {
|
|
33627
|
-
const candidatePath = Path.join(rootDirectory, candidate);
|
|
33628
|
-
if (!isDirectory(candidatePath)) continue;
|
|
33629
|
-
for (const entry of readDirectoryEntries(candidatePath)) {
|
|
33630
|
-
if (!entry.isDirectory()) continue;
|
|
33631
|
-
const projectDirectory = Path.join(candidatePath, entry.name);
|
|
33632
|
-
if (isFile(Path.join(projectDirectory, "project.json")) || isFile(Path.join(projectDirectory, "package.json"))) collected.push(`${candidate}/${entry.name}`);
|
|
33633
|
-
}
|
|
33634
|
-
}
|
|
33635
|
-
return collected;
|
|
33636
|
-
};
|
|
33637
|
-
const parsePnpmWorkspacePatterns = (rootDirectory) => {
|
|
33638
|
-
const workspacePath = Path.join(rootDirectory, "pnpm-workspace.yaml");
|
|
33639
|
-
if (!isFile(workspacePath)) return [];
|
|
33640
|
-
const content = NFS.readFileSync(workspacePath, "utf-8");
|
|
33641
|
-
const patterns = [];
|
|
33642
|
-
let isInsidePackagesBlock = false;
|
|
33643
|
-
for (const line of content.split("\n")) {
|
|
33644
|
-
const trimmed = line.trim();
|
|
33645
|
-
if (trimmed === "packages:") {
|
|
33646
|
-
isInsidePackagesBlock = true;
|
|
33647
|
-
continue;
|
|
33648
|
-
}
|
|
33649
|
-
if (isInsidePackagesBlock && trimmed.startsWith("-")) patterns.push(trimmed.replace(/^-\s*/, "").replace(/["']/g, ""));
|
|
33650
|
-
else if (isInsidePackagesBlock && trimmed.length > 0 && !trimmed.startsWith("#")) isInsidePackagesBlock = false;
|
|
33651
|
-
}
|
|
33652
|
-
return patterns;
|
|
33653
|
-
};
|
|
33654
|
-
const getWorkspacePatterns = (rootDirectory, packageJson) => {
|
|
33655
|
-
const pnpmPatterns = parsePnpmWorkspacePatterns(rootDirectory);
|
|
33656
|
-
if (pnpmPatterns.length > 0) return pnpmPatterns;
|
|
33657
|
-
if (Array.isArray(packageJson.workspaces)) return packageJson.workspaces;
|
|
33658
|
-
if (packageJson.workspaces?.packages) return packageJson.workspaces.packages;
|
|
33659
|
-
const nxPatterns = getNxWorkspaceDirectories(rootDirectory);
|
|
33660
|
-
if (nxPatterns.length > 0) return nxPatterns;
|
|
33661
|
-
return [];
|
|
33662
|
-
};
|
|
33663
|
-
const parseReactMajor = (reactVersion) => {
|
|
33664
|
-
if (typeof reactVersion !== "string") return null;
|
|
33665
|
-
return getLowestDependencyMajor(reactVersion);
|
|
33823
|
+
const REACT_DEPENDENCY_NAMES = new Set([
|
|
33824
|
+
"react",
|
|
33825
|
+
"react-native",
|
|
33826
|
+
"next",
|
|
33827
|
+
"preact"
|
|
33828
|
+
]);
|
|
33829
|
+
const hasReactDependency = (packageJson) => {
|
|
33830
|
+
const allDependencies = {
|
|
33831
|
+
...packageJson.peerDependencies,
|
|
33832
|
+
...packageJson.dependencies,
|
|
33833
|
+
...packageJson.devDependencies
|
|
33834
|
+
};
|
|
33835
|
+
return Object.keys(allDependencies).some((packageName) => REACT_DEPENDENCY_NAMES.has(packageName));
|
|
33666
33836
|
};
|
|
33667
|
-
const
|
|
33668
|
-
|
|
33669
|
-
|
|
33670
|
-
|
|
33671
|
-
|
|
33672
|
-
|
|
33673
|
-
}
|
|
33674
|
-
const wildcardIndex = cleanPattern.indexOf("*");
|
|
33675
|
-
const baseDirectory = Path.join(rootDirectory, cleanPattern.slice(0, wildcardIndex));
|
|
33676
|
-
const suffixAfterWildcard = cleanPattern.slice(wildcardIndex + 1);
|
|
33677
|
-
if (!isDirectory(baseDirectory)) return [];
|
|
33678
|
-
const resolved = [];
|
|
33679
|
-
for (const entry of readDirectoryEntries(baseDirectory)) {
|
|
33680
|
-
const entryPath = Path.join(baseDirectory, entry.name, suffixAfterWildcard);
|
|
33681
|
-
if (isDirectory(entryPath) && isFile(Path.join(entryPath, "package.json"))) resolved.push(entryPath);
|
|
33682
|
-
}
|
|
33683
|
-
return resolved;
|
|
33837
|
+
const getPreactVersion = (packageJson) => {
|
|
33838
|
+
return {
|
|
33839
|
+
...packageJson.peerDependencies,
|
|
33840
|
+
...packageJson.dependencies,
|
|
33841
|
+
...packageJson.devDependencies
|
|
33842
|
+
}.preact ?? null;
|
|
33684
33843
|
};
|
|
33685
|
-
const
|
|
33686
|
-
|
|
33687
|
-
|
|
33688
|
-
|
|
33689
|
-
|
|
33690
|
-
|
|
33691
|
-
|
|
33692
|
-
|
|
33844
|
+
const TANSTACK_QUERY_PACKAGES = new Set([
|
|
33845
|
+
"@tanstack/react-query",
|
|
33846
|
+
"@tanstack/query-core",
|
|
33847
|
+
"react-query"
|
|
33848
|
+
]);
|
|
33849
|
+
const hasTanStackQuery = (packageJson) => {
|
|
33850
|
+
const allDependencies = {
|
|
33851
|
+
...packageJson.peerDependencies,
|
|
33852
|
+
...packageJson.dependencies,
|
|
33853
|
+
...packageJson.devDependencies
|
|
33854
|
+
};
|
|
33855
|
+
return Object.keys(allDependencies).some((packageName) => TANSTACK_QUERY_PACKAGES.has(packageName));
|
|
33693
33856
|
};
|
|
33694
|
-
const
|
|
33695
|
-
if (!
|
|
33696
|
-
const
|
|
33697
|
-
const
|
|
33698
|
-
if (
|
|
33699
|
-
|
|
33700
|
-
|
|
33857
|
+
const resolveCatalogBackedDependencyVersion = ({ rootDirectory, rootPackageJson, packageName, version }) => {
|
|
33858
|
+
if (version === null || !isCatalogReference(version)) return version;
|
|
33859
|
+
const catalogName = extractCatalogName(version);
|
|
33860
|
+
const resolvedLocalVersion = resolveCatalogVersion(rootPackageJson, packageName, rootDirectory, catalogName);
|
|
33861
|
+
if (resolvedLocalVersion) return resolvedLocalVersion;
|
|
33862
|
+
const monorepoRoot = findMonorepoRoot(rootDirectory);
|
|
33863
|
+
if (!monorepoRoot) return version;
|
|
33864
|
+
const monorepoPackageJsonPath = Path.join(monorepoRoot, "package.json");
|
|
33865
|
+
if (!isFile(monorepoPackageJsonPath)) return version;
|
|
33866
|
+
return resolveCatalogVersion(readPackageJson(monorepoPackageJsonPath), packageName, monorepoRoot, catalogName) ?? version;
|
|
33701
33867
|
};
|
|
33702
|
-
|
|
33703
|
-
|
|
33704
|
-
|
|
33705
|
-
|
|
33706
|
-
|
|
33707
|
-
|
|
33708
|
-
|
|
33709
|
-
|
|
33710
|
-
|
|
33711
|
-
|
|
33712
|
-
|
|
33713
|
-
|
|
33714
|
-
|
|
33715
|
-
|
|
33716
|
-
|
|
33717
|
-
"peerDependencies",
|
|
33718
|
-
"devDependencies"
|
|
33719
|
-
],
|
|
33720
|
-
workspaceDirectory,
|
|
33721
|
-
workspacePackageJson
|
|
33722
|
-
});
|
|
33723
|
-
const tailwindVersion = resolveWorkspaceDependencyVersion({
|
|
33724
|
-
concreteVersion: info.tailwindVersion,
|
|
33725
|
-
packageName: "tailwindcss",
|
|
33726
|
-
rootDirectory,
|
|
33727
|
-
rootPackageJson: packageJson,
|
|
33728
|
-
sections: [
|
|
33729
|
-
"dependencies",
|
|
33730
|
-
"devDependencies",
|
|
33731
|
-
"peerDependencies"
|
|
33732
|
-
],
|
|
33733
|
-
workspaceDirectory,
|
|
33734
|
-
workspacePackageJson
|
|
33735
|
-
});
|
|
33736
|
-
const zodVersion = resolveWorkspaceDependencyVersion({
|
|
33737
|
-
concreteVersion: info.zodVersion,
|
|
33738
|
-
packageName: "zod",
|
|
33739
|
-
rootDirectory,
|
|
33740
|
-
rootPackageJson: packageJson,
|
|
33741
|
-
sections: [
|
|
33742
|
-
"dependencies",
|
|
33743
|
-
"devDependencies",
|
|
33744
|
-
"peerDependencies"
|
|
33745
|
-
],
|
|
33746
|
-
workspaceDirectory,
|
|
33747
|
-
workspacePackageJson
|
|
33748
|
-
});
|
|
33749
|
-
if (reactVersion && shouldReplaceReactVersion(result.reactVersion, reactVersion)) result.reactVersion = reactVersion;
|
|
33750
|
-
if (tailwindVersion && !result.tailwindVersion) result.tailwindVersion = tailwindVersion;
|
|
33751
|
-
if (zodVersion && !result.zodVersion) result.zodVersion = zodVersion;
|
|
33752
|
-
if (info.framework !== "unknown" && result.framework === "unknown") result.framework = info.framework;
|
|
33753
|
-
const resultReactMajor = parseReactMajor(result.reactVersion);
|
|
33754
|
-
if (result.reactVersion && result.tailwindVersion && result.framework !== "unknown" && resultReactMajor !== null && resultReactMajor <= 17) return result;
|
|
33755
|
-
}
|
|
33868
|
+
/**
|
|
33869
|
+
* The chain of ancestor directories of `startDirectory`, nearest first, up to
|
|
33870
|
+
* and including the filesystem root. With `includeStart`, the chain begins at
|
|
33871
|
+
* `startDirectory` itself; otherwise at its parent. The one place the "walk up
|
|
33872
|
+
* the tree" traversal is expressed, so each caller adds only its own match /
|
|
33873
|
+
* stop policy instead of re-deriving the `path.dirname` loop.
|
|
33874
|
+
*/
|
|
33875
|
+
const ancestorDirectories = (startDirectory, { includeStart }) => {
|
|
33876
|
+
const directories = [];
|
|
33877
|
+
let currentDirectory = includeStart ? startDirectory : Path.dirname(startDirectory);
|
|
33878
|
+
while (true) {
|
|
33879
|
+
directories.push(currentDirectory);
|
|
33880
|
+
const parentDirectory = Path.dirname(currentDirectory);
|
|
33881
|
+
if (parentDirectory === currentDirectory) return directories;
|
|
33882
|
+
currentDirectory = parentDirectory;
|
|
33756
33883
|
}
|
|
33757
|
-
return result;
|
|
33758
33884
|
};
|
|
33759
|
-
|
|
33760
|
-
|
|
33761
|
-
|
|
33762
|
-
|
|
33763
|
-
|
|
33764
|
-
|
|
33765
|
-
|
|
33766
|
-
|
|
33767
|
-
|
|
33768
|
-
|
|
33769
|
-
|
|
33770
|
-
|
|
33771
|
-
|
|
33772
|
-
|
|
33773
|
-
|
|
33774
|
-
|
|
33775
|
-
|
|
33776
|
-
})
|
|
33777
|
-
|
|
33778
|
-
|
|
33779
|
-
packageName: "tailwindcss",
|
|
33780
|
-
sections: [
|
|
33781
|
-
"dependencies",
|
|
33782
|
-
"devDependencies",
|
|
33783
|
-
"peerDependencies"
|
|
33784
|
-
]
|
|
33785
|
-
}) : null;
|
|
33786
|
-
const leafZodDeclaration = leafPackageJson ? getDependencyDeclaration({
|
|
33787
|
-
packageJson: leafPackageJson,
|
|
33788
|
-
packageName: "zod",
|
|
33789
|
-
sections: [
|
|
33790
|
-
"dependencies",
|
|
33791
|
-
"devDependencies",
|
|
33792
|
-
"peerDependencies"
|
|
33793
|
-
]
|
|
33794
|
-
}) : null;
|
|
33795
|
-
const shouldUseReactFallback = !leafReactDeclaration?.hasDeclaration;
|
|
33796
|
-
const shouldUseTailwindFallback = leafTailwindDeclaration?.hasDeclaration ?? true;
|
|
33797
|
-
const shouldUseZodFallback = leafZodDeclaration?.hasDeclaration ?? true;
|
|
33798
|
-
const reactCatalogVersion = shouldUseReactFallback ? resolveCatalogVersion(rootPackageJson, "react", monorepoRoot, leafReactDeclaration?.catalogReference) : null;
|
|
33799
|
-
const tailwindCatalogVersion = shouldUseTailwindFallback ? resolveCatalogVersion(rootPackageJson, "tailwindcss", monorepoRoot, leafTailwindDeclaration?.catalogReference) : null;
|
|
33800
|
-
const zodCatalogVersion = shouldUseZodFallback ? resolveCatalogVersion(rootPackageJson, "zod", monorepoRoot, leafZodDeclaration?.catalogReference) : null;
|
|
33801
|
-
const workspaceInfo = findReactInWorkspaces(monorepoRoot, rootPackageJson);
|
|
33802
|
-
return {
|
|
33803
|
-
reactVersion: shouldUseReactFallback ? reactCatalogVersion ?? rootInfo.reactVersion ?? workspaceInfo.reactVersion : rootInfo.reactVersion ?? workspaceInfo.reactVersion,
|
|
33804
|
-
tailwindVersion: shouldUseTailwindFallback ? tailwindCatalogVersion ?? rootInfo.tailwindVersion ?? workspaceInfo.tailwindVersion : null,
|
|
33805
|
-
zodVersion: shouldUseZodFallback ? zodCatalogVersion ?? rootInfo.zodVersion ?? workspaceInfo.zodVersion : null,
|
|
33806
|
-
framework: rootInfo.framework !== "unknown" ? rootInfo.framework : workspaceInfo.framework
|
|
33807
|
-
};
|
|
33808
|
-
};
|
|
33809
|
-
const findInWorkspacePackageJsons = (rootDirectory, rootPackageJson, select) => {
|
|
33810
|
-
const rootValue = select(rootPackageJson);
|
|
33811
|
-
if (rootValue !== null) return rootValue;
|
|
33812
|
-
const patterns = getWorkspacePatterns(rootDirectory, rootPackageJson);
|
|
33813
|
-
if (patterns.length === 0) return null;
|
|
33814
|
-
const visitedDirectories = /* @__PURE__ */ new Set();
|
|
33815
|
-
for (const pattern of patterns) {
|
|
33816
|
-
const directories = [...resolveWorkspaceDirectories(rootDirectory, pattern)].sort();
|
|
33817
|
-
for (const workspaceDirectory of directories) {
|
|
33818
|
-
if (visitedDirectories.has(workspaceDirectory)) continue;
|
|
33819
|
-
visitedDirectories.add(workspaceDirectory);
|
|
33820
|
-
const value = select(readPackageJson(Path.join(workspaceDirectory, "package.json")));
|
|
33821
|
-
if (value !== null) return value;
|
|
33822
|
-
}
|
|
33885
|
+
/**
|
|
33886
|
+
* Walk up from `startDirectory` to the nearest ancestor that owns a
|
|
33887
|
+
* `package.json`, stopping at (and including) the enclosing project boundary
|
|
33888
|
+
* — the working tree's git root or a monorepo root. Lets a scan of a package
|
|
33889
|
+
* subfolder that has no `package.json` of its own adopt the nearest enclosing
|
|
33890
|
+
* package as its project root, so it inherits that package's dependency +
|
|
33891
|
+
* framework detection instead of synthesizing an empty (React-blind) project.
|
|
33892
|
+
*
|
|
33893
|
+
* Generalizes the older monorepo-root-only lookup: the nearest ancestor is the
|
|
33894
|
+
* most specific owning package (a leaf workspace, not just the repo root), so
|
|
33895
|
+
* a subfolder of one workspace no longer borrows a sibling's capabilities.
|
|
33896
|
+
*
|
|
33897
|
+
* Returns `null` when no ancestor `package.json` exists inside the project
|
|
33898
|
+
* boundary (a loose tree of files, or a subfolder of a package-less repo).
|
|
33899
|
+
*/
|
|
33900
|
+
const findNearestAncestorPackageJson = (startDirectory) => {
|
|
33901
|
+
if (isProjectBoundary(startDirectory)) return null;
|
|
33902
|
+
for (const directory of ancestorDirectories(startDirectory, { includeStart: false })) {
|
|
33903
|
+
if (isFile(Path.join(directory, "package.json"))) return directory;
|
|
33904
|
+
if (isProjectBoundary(directory)) return null;
|
|
33823
33905
|
}
|
|
33824
33906
|
return null;
|
|
33825
33907
|
};
|
|
33826
|
-
const someWorkspacePackageJson = (rootDirectory, rootPackageJson, predicate) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => predicate(packageJson) ? true : null) !== null;
|
|
33827
33908
|
const NAMES = new Set([
|
|
33828
33909
|
"react-native",
|
|
33829
33910
|
"react-native-tvos",
|
|
@@ -33856,47 +33937,7 @@ const isPackageJsonReactNativeAware = (packageJson) => {
|
|
|
33856
33937
|
if (containsAnyReactNativeDependency(packageJson.optionalDependencies)) return true;
|
|
33857
33938
|
return false;
|
|
33858
33939
|
};
|
|
33859
|
-
const
|
|
33860
|
-
const getDependencySpec = (packageJson, packageName) => {
|
|
33861
|
-
const spec = packageJson.dependencies?.[packageName] ?? packageJson.devDependencies?.[packageName] ?? packageJson.peerDependencies?.[packageName] ?? packageJson.optionalDependencies?.[packageName];
|
|
33862
|
-
return typeof spec === "string" ? spec : null;
|
|
33863
|
-
};
|
|
33864
|
-
const findExpoVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, "expo"));
|
|
33865
|
-
const SHOPIFY_FLASH_LIST_PACKAGE_NAME = "@shopify/flash-list";
|
|
33866
|
-
const findShopifyFlashListVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, SHOPIFY_FLASH_LIST_PACKAGE_NAME));
|
|
33867
|
-
const resolveCatalogBackedDependencyVersion = ({ rootDirectory, rootPackageJson, packageName, version }) => {
|
|
33868
|
-
if (version === null || !isCatalogReference(version)) return version;
|
|
33869
|
-
const catalogName = extractCatalogName(version);
|
|
33870
|
-
const resolvedLocalVersion = resolveCatalogVersion(rootPackageJson, packageName, rootDirectory, catalogName);
|
|
33871
|
-
if (resolvedLocalVersion) return resolvedLocalVersion;
|
|
33872
|
-
const monorepoRoot = findMonorepoRoot(rootDirectory);
|
|
33873
|
-
if (!monorepoRoot) return version;
|
|
33874
|
-
const monorepoPackageJsonPath = path.join(monorepoRoot, "package.json");
|
|
33875
|
-
if (!isFile(monorepoPackageJsonPath)) return version;
|
|
33876
|
-
return resolveCatalogVersion(readPackageJson(monorepoPackageJsonPath), packageName, monorepoRoot, catalogName) ?? version;
|
|
33877
|
-
};
|
|
33878
|
-
const findNextjsVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, "next"));
|
|
33879
|
-
const getPreactVersion = (packageJson) => {
|
|
33880
|
-
return {
|
|
33881
|
-
...packageJson.peerDependencies,
|
|
33882
|
-
...packageJson.dependencies,
|
|
33883
|
-
...packageJson.devDependencies
|
|
33884
|
-
}.preact ?? null;
|
|
33885
|
-
};
|
|
33886
|
-
const TANSTACK_QUERY_PACKAGES = new Set([
|
|
33887
|
-
"@tanstack/react-query",
|
|
33888
|
-
"@tanstack/query-core",
|
|
33889
|
-
"react-query"
|
|
33890
|
-
]);
|
|
33891
|
-
const hasTanStackQuery = (packageJson) => {
|
|
33892
|
-
const allDependencies = {
|
|
33893
|
-
...packageJson.peerDependencies,
|
|
33894
|
-
...packageJson.dependencies,
|
|
33895
|
-
...packageJson.devDependencies
|
|
33896
|
-
};
|
|
33897
|
-
return Object.keys(allDependencies).some((packageName) => TANSTACK_QUERY_PACKAGES.has(packageName));
|
|
33898
|
-
};
|
|
33899
|
-
const REANIMATED_DEPENDENCY_NAME = "react-native-reanimated";
|
|
33940
|
+
const REANIMATED_DEPENDENCY_NAME$1 = "react-native-reanimated";
|
|
33900
33941
|
const isPackageJsonReanimatedAware = (packageJson) => {
|
|
33901
33942
|
const allDependencies = {
|
|
33902
33943
|
...packageJson.peerDependencies,
|
|
@@ -33904,144 +33945,70 @@ const isPackageJsonReanimatedAware = (packageJson) => {
|
|
|
33904
33945
|
...packageJson.devDependencies,
|
|
33905
33946
|
...packageJson.optionalDependencies
|
|
33906
33947
|
};
|
|
33907
|
-
return Object.hasOwn(allDependencies, REANIMATED_DEPENDENCY_NAME);
|
|
33908
|
-
};
|
|
33909
|
-
const parseZodMajor = (zodVersion) => {
|
|
33910
|
-
if (typeof zodVersion !== "string") return null;
|
|
33911
|
-
return getLowestDependencyMajor(zodVersion);
|
|
33948
|
+
return Object.hasOwn(allDependencies, REANIMATED_DEPENDENCY_NAME$1);
|
|
33912
33949
|
};
|
|
33913
|
-
const
|
|
33914
|
-
|
|
33915
|
-
|
|
33916
|
-
if (
|
|
33917
|
-
|
|
33918
|
-
|
|
33919
|
-
|
|
33920
|
-
|
|
33921
|
-
};
|
|
33922
|
-
const peerRangeMinMajor = (range) => {
|
|
33923
|
-
if (typeof range !== "string") return null;
|
|
33924
|
-
return getLowestDependencyMajor(range);
|
|
33925
|
-
};
|
|
33926
|
-
const resolveEffectiveReactMajor = (reactVersion, packageJson) => {
|
|
33927
|
-
const installedReactMajor = parseReactMajor(reactVersion);
|
|
33928
|
-
const peerReactRange = packageJson.peerDependencies?.react;
|
|
33929
|
-
if (typeof peerReactRange !== "string") return installedReactMajor;
|
|
33930
|
-
const peerFloor = peerRangeMinMajor(peerReactRange);
|
|
33931
|
-
if (peerFloor === null) return hasUpperBoundOnlyPeerRange(peerReactRange) ? null : installedReactMajor;
|
|
33932
|
-
return installedReactMajor !== null ? Math.min(installedReactMajor, peerFloor) : peerFloor;
|
|
33950
|
+
const getWorkspacePatterns = (rootDirectory, packageJson) => {
|
|
33951
|
+
const pnpmPatterns = parsePnpmWorkspacePatterns(rootDirectory);
|
|
33952
|
+
if (pnpmPatterns.length > 0) return pnpmPatterns;
|
|
33953
|
+
if (Array.isArray(packageJson.workspaces)) return packageJson.workspaces;
|
|
33954
|
+
if (packageJson.workspaces?.packages) return packageJson.workspaces.packages;
|
|
33955
|
+
const nxPatterns = getNxWorkspaceDirectories(rootDirectory);
|
|
33956
|
+
if (nxPatterns.length > 0) return nxPatterns;
|
|
33957
|
+
return [];
|
|
33933
33958
|
};
|
|
33934
|
-
const
|
|
33935
|
-
const
|
|
33936
|
-
|
|
33937
|
-
const
|
|
33938
|
-
const
|
|
33939
|
-
|
|
33940
|
-
|
|
33941
|
-
|
|
33942
|
-
|
|
33943
|
-
|
|
33944
|
-
|
|
33945
|
-
|
|
33946
|
-
|
|
33947
|
-
|
|
33959
|
+
const parsePnpmWorkspacePatterns = (rootDirectory) => {
|
|
33960
|
+
const workspacePath = Path.join(rootDirectory, "pnpm-workspace.yaml");
|
|
33961
|
+
if (!isFile(workspacePath)) return [];
|
|
33962
|
+
const content = NFS.readFileSync(workspacePath, "utf-8");
|
|
33963
|
+
const patterns = [];
|
|
33964
|
+
let isInsidePackagesBlock = false;
|
|
33965
|
+
for (const line of content.split("\n")) {
|
|
33966
|
+
const trimmed = line.trim();
|
|
33967
|
+
if (trimmed === "packages:") {
|
|
33968
|
+
isInsidePackagesBlock = true;
|
|
33969
|
+
continue;
|
|
33970
|
+
}
|
|
33971
|
+
if (isInsidePackagesBlock && trimmed.startsWith("-")) patterns.push(trimmed.replace(/^-\s*/, "").replace(/["']/g, ""));
|
|
33972
|
+
else if (isInsidePackagesBlock && trimmed.length > 0 && !trimmed.startsWith("#")) isInsidePackagesBlock = false;
|
|
33948
33973
|
}
|
|
33949
|
-
return
|
|
33950
|
-
};
|
|
33951
|
-
const resolveExtendsPath = (extendsValue, fromConfigDirectory) => {
|
|
33952
|
-
if (isRelativeExtendsValue(extendsValue)) return ensureJsonExtension(Path.resolve(fromConfigDirectory, extendsValue));
|
|
33953
|
-
return resolvePackageExtendsPath(extendsValue, fromConfigDirectory);
|
|
33954
|
-
};
|
|
33955
|
-
const normalizeCompilerOptions = (compilerOptions) => {
|
|
33956
|
-
if (!isPlainObject(compilerOptions)) return { hasExplicitLib: false };
|
|
33957
|
-
const target = typeof compilerOptions.target === "string" ? compilerOptions.target : void 0;
|
|
33958
|
-
const hasExplicitLib = Object.hasOwn(compilerOptions, "lib");
|
|
33959
|
-
return {
|
|
33960
|
-
target,
|
|
33961
|
-
lib: Array.isArray(compilerOptions.lib) ? compilerOptions.lib.filter((entry) => typeof entry === "string") : void 0,
|
|
33962
|
-
hasExplicitLib
|
|
33963
|
-
};
|
|
33974
|
+
return patterns;
|
|
33964
33975
|
};
|
|
33965
|
-
const
|
|
33966
|
-
|
|
33967
|
-
|
|
33968
|
-
|
|
33969
|
-
|
|
33970
|
-
|
|
33976
|
+
const NX_PROJECT_DISCOVERY_DIRS = [
|
|
33977
|
+
"apps",
|
|
33978
|
+
"libs",
|
|
33979
|
+
"packages"
|
|
33980
|
+
];
|
|
33981
|
+
const getNxWorkspaceDirectories = (rootDirectory) => {
|
|
33982
|
+
if (!isFile(Path.join(rootDirectory, "nx.json"))) return [];
|
|
33983
|
+
const collected = [];
|
|
33984
|
+
for (const candidate of NX_PROJECT_DISCOVERY_DIRS) {
|
|
33985
|
+
const candidatePath = Path.join(rootDirectory, candidate);
|
|
33986
|
+
if (!isDirectory(candidatePath)) continue;
|
|
33987
|
+
for (const entry of readDirectoryEntries(candidatePath)) {
|
|
33988
|
+
if (!entry.isDirectory()) continue;
|
|
33989
|
+
const projectDirectory = Path.join(candidatePath, entry.name);
|
|
33990
|
+
if (isFile(Path.join(projectDirectory, "project.json")) || isFile(Path.join(projectDirectory, "package.json"))) collected.push(`${candidate}/${entry.name}`);
|
|
33991
|
+
}
|
|
33971
33992
|
}
|
|
33972
|
-
|
|
33973
|
-
if (!isPlainObject(parsed.config)) return null;
|
|
33974
|
-
return {
|
|
33975
|
-
extends: typeof parsed.config.extends === "string" ? parsed.config.extends : void 0,
|
|
33976
|
-
compilerOptions: normalizeCompilerOptions(parsed.config.compilerOptions)
|
|
33977
|
-
};
|
|
33978
|
-
};
|
|
33979
|
-
const mergeCompilerOptions = (inherited, current) => {
|
|
33980
|
-
const target = current.target ?? inherited?.target;
|
|
33981
|
-
const hasExplicitLib = current.hasExplicitLib || Boolean(inherited?.hasExplicitLib);
|
|
33982
|
-
return {
|
|
33983
|
-
target,
|
|
33984
|
-
lib: current.hasExplicitLib ? current.lib : inherited?.lib,
|
|
33985
|
-
hasExplicitLib
|
|
33986
|
-
};
|
|
33993
|
+
return collected;
|
|
33987
33994
|
};
|
|
33988
|
-
const
|
|
33989
|
-
const
|
|
33990
|
-
if (
|
|
33991
|
-
|
|
33992
|
-
|
|
33993
|
-
|
|
33994
|
-
nextVisitedPaths.add(realPath);
|
|
33995
|
-
if (tsConfig.extends && extendsDepth < 8) {
|
|
33996
|
-
const parentPath = resolveExtendsPath(tsConfig.extends, Path.dirname(realPath));
|
|
33997
|
-
if (parentPath && isFile(parentPath)) return mergeCompilerOptions(readResolvedCompilerOptions(parentPath, extendsDepth + 1, nextVisitedPaths), tsConfig.compilerOptions);
|
|
33995
|
+
const resolveWorkspaceDirectories = (rootDirectory, pattern) => {
|
|
33996
|
+
const cleanPattern = pattern.replace(/["']/g, "").replace(/\/\*\*$/, "/*");
|
|
33997
|
+
if (!cleanPattern.includes("*")) {
|
|
33998
|
+
const directoryPath = Path.join(rootDirectory, cleanPattern);
|
|
33999
|
+
if (isDirectory(directoryPath) && isFile(Path.join(directoryPath, "package.json"))) return [directoryPath];
|
|
34000
|
+
return [];
|
|
33998
34001
|
}
|
|
33999
|
-
|
|
34000
|
-
|
|
34001
|
-
const
|
|
34002
|
-
|
|
34003
|
-
|
|
34004
|
-
|
|
34005
|
-
const
|
|
34006
|
-
|
|
34007
|
-
|
|
34008
|
-
|
|
34009
|
-
if (!esYearMatch) return false;
|
|
34010
|
-
if (Number(esYearMatch[1]) < 2023) return false;
|
|
34011
|
-
const component = esYearMatch[2];
|
|
34012
|
-
return component === void 0 || component === "array";
|
|
34013
|
-
};
|
|
34014
|
-
const libIncludesES2023 = (lib) => lib.some(libEntryIncludesES2023Array);
|
|
34015
|
-
const compilerOptionsArePreES2023 = (compilerOptions) => {
|
|
34016
|
-
if (compilerOptions.hasExplicitLib) return !libIncludesES2023(compilerOptions.lib ?? []);
|
|
34017
|
-
if (compilerOptions.target) return targetYearIsPreES2023(compilerOptions.target);
|
|
34018
|
-
return false;
|
|
34019
|
-
};
|
|
34020
|
-
const compilerOptionsDeclareTargetOrLib = (compilerOptions) => compilerOptions.hasExplicitLib || compilerOptions.target !== void 0;
|
|
34021
|
-
const detectPreES2023FromConfig = (tsConfigPath) => {
|
|
34022
|
-
const compilerOptions = readResolvedCompilerOptions(tsConfigPath, 0, /* @__PURE__ */ new Set());
|
|
34023
|
-
if (!compilerOptions) return false;
|
|
34024
|
-
if (!compilerOptionsDeclareTargetOrLib(compilerOptions)) return false;
|
|
34025
|
-
return compilerOptionsArePreES2023(compilerOptions);
|
|
34026
|
-
};
|
|
34027
|
-
const detectPreES2023Target = (directory) => {
|
|
34028
|
-
const tsConfigPath = Path.join(directory, TSCONFIG_FILENAME);
|
|
34029
|
-
if (isFile(tsConfigPath)) return detectPreES2023FromConfig(tsConfigPath);
|
|
34030
|
-
return false;
|
|
34031
|
-
};
|
|
34032
|
-
const REACT_DEPENDENCY_NAMES = new Set([
|
|
34033
|
-
"react",
|
|
34034
|
-
"react-native",
|
|
34035
|
-
"next",
|
|
34036
|
-
"preact"
|
|
34037
|
-
]);
|
|
34038
|
-
const hasReactDependency = (packageJson) => {
|
|
34039
|
-
const allDependencies = {
|
|
34040
|
-
...packageJson.peerDependencies,
|
|
34041
|
-
...packageJson.dependencies,
|
|
34042
|
-
...packageJson.devDependencies
|
|
34043
|
-
};
|
|
34044
|
-
return Object.keys(allDependencies).some((packageName) => REACT_DEPENDENCY_NAMES.has(packageName));
|
|
34002
|
+
const wildcardIndex = cleanPattern.indexOf("*");
|
|
34003
|
+
const baseDirectory = Path.join(rootDirectory, cleanPattern.slice(0, wildcardIndex));
|
|
34004
|
+
const suffixAfterWildcard = cleanPattern.slice(wildcardIndex + 1);
|
|
34005
|
+
if (!isDirectory(baseDirectory)) return [];
|
|
34006
|
+
const resolved = [];
|
|
34007
|
+
for (const entry of readDirectoryEntries(baseDirectory)) {
|
|
34008
|
+
const entryPath = Path.join(baseDirectory, entry.name, suffixAfterWildcard);
|
|
34009
|
+
if (isDirectory(entryPath) && isFile(Path.join(entryPath, "package.json"))) resolved.push(entryPath);
|
|
34010
|
+
}
|
|
34011
|
+
return resolved;
|
|
34045
34012
|
};
|
|
34046
34013
|
const listWorkspacePackages = (rootDirectory) => {
|
|
34047
34014
|
const packageJsonPath = Path.join(rootDirectory, "package.json");
|
|
@@ -34073,6 +34040,208 @@ const listWorkspacePackages = (rootDirectory) => {
|
|
|
34073
34040
|
}
|
|
34074
34041
|
return packages;
|
|
34075
34042
|
};
|
|
34043
|
+
const REANIMATED_DEPENDENCY_NAME = "react-native-reanimated";
|
|
34044
|
+
const SHOPIFY_FLASH_LIST_PACKAGE_NAME = "@shopify/flash-list";
|
|
34045
|
+
const resolveWorkspaceDependencyVersion = ({ concreteVersion, packageName, rootDirectory, rootPackageJson, sections, workspaceDirectory, workspacePackageJson }) => {
|
|
34046
|
+
const dependencyDeclaration = getDependencyDeclaration({
|
|
34047
|
+
packageJson: workspacePackageJson,
|
|
34048
|
+
packageName,
|
|
34049
|
+
sections
|
|
34050
|
+
});
|
|
34051
|
+
if (!dependencyDeclaration.hasDeclaration) return null;
|
|
34052
|
+
return concreteVersion ?? resolveCatalogVersion(workspacePackageJson, packageName, workspaceDirectory, dependencyDeclaration.catalogReference) ?? resolveCatalogVersion(rootPackageJson, packageName, rootDirectory, dependencyDeclaration.catalogReference);
|
|
34053
|
+
};
|
|
34054
|
+
const shouldReplaceReactVersion = (currentVersion, nextVersion) => {
|
|
34055
|
+
if (!currentVersion) return true;
|
|
34056
|
+
const currentMajor = parseReactMajor(currentVersion);
|
|
34057
|
+
const nextMajor = parseReactMajor(nextVersion);
|
|
34058
|
+
if (currentMajor === null) return nextMajor !== null;
|
|
34059
|
+
if (nextMajor === null) return false;
|
|
34060
|
+
return nextMajor < currentMajor;
|
|
34061
|
+
};
|
|
34062
|
+
const evaluateManifestFacts = (facts, packageJson, directory) => {
|
|
34063
|
+
if (facts.expo.version === null) {
|
|
34064
|
+
const spec = getDependencySpec(packageJson, "expo");
|
|
34065
|
+
if (spec !== null) facts.expo = {
|
|
34066
|
+
version: spec,
|
|
34067
|
+
sourceDirectory: directory
|
|
34068
|
+
};
|
|
34069
|
+
}
|
|
34070
|
+
if (facts.next.version === null) {
|
|
34071
|
+
const spec = getDependencySpec(packageJson, "next");
|
|
34072
|
+
if (spec !== null) facts.next = {
|
|
34073
|
+
version: spec,
|
|
34074
|
+
sourceDirectory: directory
|
|
34075
|
+
};
|
|
34076
|
+
}
|
|
34077
|
+
if (facts.shopifyFlashList.version === null) {
|
|
34078
|
+
const spec = getDependencySpec(packageJson, SHOPIFY_FLASH_LIST_PACKAGE_NAME);
|
|
34079
|
+
if (spec !== null) facts.shopifyFlashList = {
|
|
34080
|
+
version: spec,
|
|
34081
|
+
sourceDirectory: directory
|
|
34082
|
+
};
|
|
34083
|
+
}
|
|
34084
|
+
if (facts.reanimatedVersion === null) {
|
|
34085
|
+
const spec = getDependencySpec(packageJson, REANIMATED_DEPENDENCY_NAME);
|
|
34086
|
+
if (spec !== null) facts.reanimatedVersion = spec;
|
|
34087
|
+
}
|
|
34088
|
+
facts.hasReactNativeAwarePackage = facts.hasReactNativeAwarePackage || isPackageJsonReactNativeAware(packageJson);
|
|
34089
|
+
facts.hasReanimatedAwarePackage = facts.hasReanimatedAwarePackage || isPackageJsonReanimatedAware(packageJson);
|
|
34090
|
+
};
|
|
34091
|
+
const collectWorkspaceFacts = (rootDirectory, rootPackageJson, { collectReactGroup }) => {
|
|
34092
|
+
const facts = {
|
|
34093
|
+
reactVersion: null,
|
|
34094
|
+
tailwindVersion: null,
|
|
34095
|
+
zodVersion: null,
|
|
34096
|
+
framework: "unknown",
|
|
34097
|
+
expo: {
|
|
34098
|
+
version: null,
|
|
34099
|
+
sourceDirectory: null
|
|
34100
|
+
},
|
|
34101
|
+
next: {
|
|
34102
|
+
version: null,
|
|
34103
|
+
sourceDirectory: null
|
|
34104
|
+
},
|
|
34105
|
+
shopifyFlashList: {
|
|
34106
|
+
version: null,
|
|
34107
|
+
sourceDirectory: null
|
|
34108
|
+
},
|
|
34109
|
+
hasReactNativeAwarePackage: false,
|
|
34110
|
+
hasReanimatedAwarePackage: false,
|
|
34111
|
+
reanimatedVersion: null
|
|
34112
|
+
};
|
|
34113
|
+
evaluateManifestFacts(facts, rootPackageJson, rootDirectory);
|
|
34114
|
+
let isReactGroupSettled = !collectReactGroup;
|
|
34115
|
+
const visitedDirectories = /* @__PURE__ */ new Set();
|
|
34116
|
+
for (const pattern of getWorkspacePatterns(rootDirectory, rootPackageJson)) {
|
|
34117
|
+
const directories = [...resolveWorkspaceDirectories(rootDirectory, pattern)].sort();
|
|
34118
|
+
for (const workspaceDirectory of directories) {
|
|
34119
|
+
if (visitedDirectories.has(workspaceDirectory)) continue;
|
|
34120
|
+
visitedDirectories.add(workspaceDirectory);
|
|
34121
|
+
const workspacePackageJson = readPackageJson(Path.join(workspaceDirectory, "package.json"));
|
|
34122
|
+
evaluateManifestFacts(facts, workspacePackageJson, workspaceDirectory);
|
|
34123
|
+
const info = extractDependencyInfo(workspacePackageJson);
|
|
34124
|
+
if (info.framework !== "unknown" && frameworkMergeRank(info.framework) < frameworkMergeRank(facts.framework)) facts.framework = info.framework;
|
|
34125
|
+
if (isReactGroupSettled) continue;
|
|
34126
|
+
const reactVersion = resolveWorkspaceDependencyVersion({
|
|
34127
|
+
concreteVersion: info.reactVersion,
|
|
34128
|
+
packageName: "react",
|
|
34129
|
+
rootDirectory,
|
|
34130
|
+
rootPackageJson,
|
|
34131
|
+
sections: REACT_SECTIONS,
|
|
34132
|
+
workspaceDirectory,
|
|
34133
|
+
workspacePackageJson
|
|
34134
|
+
});
|
|
34135
|
+
const tailwindVersion = resolveWorkspaceDependencyVersion({
|
|
34136
|
+
concreteVersion: info.tailwindVersion,
|
|
34137
|
+
packageName: "tailwindcss",
|
|
34138
|
+
rootDirectory,
|
|
34139
|
+
rootPackageJson,
|
|
34140
|
+
sections: TAILWIND_ZOD_SECTIONS,
|
|
34141
|
+
workspaceDirectory,
|
|
34142
|
+
workspacePackageJson
|
|
34143
|
+
});
|
|
34144
|
+
const zodVersion = resolveWorkspaceDependencyVersion({
|
|
34145
|
+
concreteVersion: info.zodVersion,
|
|
34146
|
+
packageName: "zod",
|
|
34147
|
+
rootDirectory,
|
|
34148
|
+
rootPackageJson,
|
|
34149
|
+
sections: TAILWIND_ZOD_SECTIONS,
|
|
34150
|
+
workspaceDirectory,
|
|
34151
|
+
workspacePackageJson
|
|
34152
|
+
});
|
|
34153
|
+
if (reactVersion && shouldReplaceReactVersion(facts.reactVersion, reactVersion)) facts.reactVersion = reactVersion;
|
|
34154
|
+
if (tailwindVersion && !facts.tailwindVersion) facts.tailwindVersion = tailwindVersion;
|
|
34155
|
+
if (zodVersion && !facts.zodVersion) facts.zodVersion = zodVersion;
|
|
34156
|
+
const settledReactMajor = parseReactMajor(facts.reactVersion);
|
|
34157
|
+
isReactGroupSettled = Boolean(facts.reactVersion && facts.tailwindVersion && facts.framework !== "unknown" && settledReactMajor !== null && settledReactMajor <= 17);
|
|
34158
|
+
}
|
|
34159
|
+
}
|
|
34160
|
+
return facts;
|
|
34161
|
+
};
|
|
34162
|
+
const findDependencyInfoFromMonorepoRoot = (directory) => {
|
|
34163
|
+
const monorepoRoot = findMonorepoRoot(directory);
|
|
34164
|
+
if (!monorepoRoot) return EMPTY_DEPENDENCY_INFO;
|
|
34165
|
+
const monorepoPackageJsonPath = Path.join(monorepoRoot, "package.json");
|
|
34166
|
+
if (!isFile(monorepoPackageJsonPath)) return EMPTY_DEPENDENCY_INFO;
|
|
34167
|
+
const rootPackageJson = readPackageJson(monorepoPackageJsonPath);
|
|
34168
|
+
const rootInfo = extractDependencyInfo(rootPackageJson);
|
|
34169
|
+
const leafPackageJsonPath = Path.join(directory, "package.json");
|
|
34170
|
+
const leafPackageJson = isFile(leafPackageJsonPath) ? readPackageJson(leafPackageJsonPath) : null;
|
|
34171
|
+
const leafReactDeclaration = leafPackageJson ? getDependencyDeclaration({
|
|
34172
|
+
packageJson: leafPackageJson,
|
|
34173
|
+
packageName: "react",
|
|
34174
|
+
sections: REACT_SECTIONS
|
|
34175
|
+
}) : null;
|
|
34176
|
+
const leafTailwindDeclaration = leafPackageJson ? getDependencyDeclaration({
|
|
34177
|
+
packageJson: leafPackageJson,
|
|
34178
|
+
packageName: "tailwindcss",
|
|
34179
|
+
sections: TAILWIND_ZOD_SECTIONS
|
|
34180
|
+
}) : null;
|
|
34181
|
+
const leafZodDeclaration = leafPackageJson ? getDependencyDeclaration({
|
|
34182
|
+
packageJson: leafPackageJson,
|
|
34183
|
+
packageName: "zod",
|
|
34184
|
+
sections: TAILWIND_ZOD_SECTIONS
|
|
34185
|
+
}) : null;
|
|
34186
|
+
const shouldUseReactFallback = !leafReactDeclaration?.hasDeclaration;
|
|
34187
|
+
const shouldUseTailwindFallback = leafTailwindDeclaration?.hasDeclaration ?? true;
|
|
34188
|
+
const shouldUseZodFallback = leafZodDeclaration?.hasDeclaration ?? true;
|
|
34189
|
+
const reactCatalogVersion = shouldUseReactFallback ? resolveCatalogVersion(rootPackageJson, "react", monorepoRoot, leafReactDeclaration?.catalogReference) : null;
|
|
34190
|
+
const tailwindCatalogVersion = shouldUseTailwindFallback ? resolveCatalogVersion(rootPackageJson, "tailwindcss", monorepoRoot, leafTailwindDeclaration?.catalogReference) : null;
|
|
34191
|
+
const zodCatalogVersion = shouldUseZodFallback ? resolveCatalogVersion(rootPackageJson, "zod", monorepoRoot, leafZodDeclaration?.catalogReference) : null;
|
|
34192
|
+
const workspaceFacts = collectWorkspaceFacts(monorepoRoot, rootPackageJson, { collectReactGroup: true });
|
|
34193
|
+
return {
|
|
34194
|
+
reactVersion: shouldUseReactFallback ? reactCatalogVersion ?? rootInfo.reactVersion ?? workspaceFacts.reactVersion : rootInfo.reactVersion ?? workspaceFacts.reactVersion,
|
|
34195
|
+
tailwindVersion: shouldUseTailwindFallback ? tailwindCatalogVersion ?? rootInfo.tailwindVersion ?? workspaceFacts.tailwindVersion : null,
|
|
34196
|
+
zodVersion: shouldUseZodFallback ? zodCatalogVersion ?? rootInfo.zodVersion ?? workspaceFacts.zodVersion : null,
|
|
34197
|
+
framework: rootInfo.framework !== "unknown" ? rootInfo.framework : workspaceFacts.framework
|
|
34198
|
+
};
|
|
34199
|
+
};
|
|
34200
|
+
/**
|
|
34201
|
+
* Last-resort React detection: locate React the way Node's `node_modules`
|
|
34202
|
+
* resolution would from `directory` — check each `node_modules/react` up the
|
|
34203
|
+
* tree — but bounded to the enclosing repo so a globally installed React can't
|
|
34204
|
+
* masquerade as the project's version. Stops at (and including) the nearest
|
|
34205
|
+
* project boundary — the git root or a monorepo root — and returns the first
|
|
34206
|
+
* installed `version`.
|
|
34207
|
+
*
|
|
34208
|
+
* Makes "React is installed and importable" ⇒ "React is detected" an invariant
|
|
34209
|
+
* for packages whose only React declaration is a version-less spec
|
|
34210
|
+
* (`workspace:*`, `*`, a dist-tag) or where React lives solely in a hoisted
|
|
34211
|
+
* `node_modules` the declaration walks never reach — the profile of a component
|
|
34212
|
+
* package inside a monorepo. Matching the `node_modules/react` entry rather than
|
|
34213
|
+
* its realpath means a project whose `node_modules` is symlinked elsewhere (a
|
|
34214
|
+
* Docker volume, a shared store) still counts, while a global install outside
|
|
34215
|
+
* the repo does not.
|
|
34216
|
+
*
|
|
34217
|
+
* Returns `null` when React isn't installed within the repo or its package.json
|
|
34218
|
+
* carries no version string. A tree with no boundary marker at all bounds the
|
|
34219
|
+
* search to `directory`, so a dependency hoisted above the scanned package there
|
|
34220
|
+
* is conservatively not adopted.
|
|
34221
|
+
*/
|
|
34222
|
+
/**
|
|
34223
|
+
* The nearest enclosing project boundary (git root or monorepo root), or
|
|
34224
|
+
* `directory` itself when the scan target sits outside any repo. Bounds the
|
|
34225
|
+
* node_modules walk so a React hoisted anywhere inside the repo counts, but a
|
|
34226
|
+
* React in an ancestor above the repo — a home-directory or global
|
|
34227
|
+
* `node_modules` — can't leak in.
|
|
34228
|
+
*/
|
|
34229
|
+
const findContainmentRoot = (directory) => {
|
|
34230
|
+
for (const ancestorDirectory of ancestorDirectories(directory, { includeStart: true })) if (isProjectBoundary(ancestorDirectory)) return ancestorDirectory;
|
|
34231
|
+
return directory;
|
|
34232
|
+
};
|
|
34233
|
+
const resolveInstalledReactVersion = (directory) => {
|
|
34234
|
+
const containmentRoot = findContainmentRoot(directory);
|
|
34235
|
+
for (const ancestorDirectory of ancestorDirectories(directory, { includeStart: true })) {
|
|
34236
|
+
const reactPackageJsonPath = Path.join(ancestorDirectory, "node_modules", "react", "package.json");
|
|
34237
|
+
if (isFile(reactPackageJsonPath)) {
|
|
34238
|
+
const installedVersion = readPackageJson(reactPackageJsonPath).version;
|
|
34239
|
+
return typeof installedVersion === "string" ? installedVersion : null;
|
|
34240
|
+
}
|
|
34241
|
+
if (ancestorDirectory === containmentRoot) return null;
|
|
34242
|
+
}
|
|
34243
|
+
return null;
|
|
34244
|
+
};
|
|
34076
34245
|
const toReactWorkspacePackages = (directories) => {
|
|
34077
34246
|
const packages = [];
|
|
34078
34247
|
for (const directory of directories) {
|
|
@@ -34156,8 +34325,8 @@ const clearProjectCache = () => {
|
|
|
34156
34325
|
const discoverProjectWithoutPackageJson = (directory) => {
|
|
34157
34326
|
const sourceFileCount = countSourceFiles(directory);
|
|
34158
34327
|
const hasOwnTsConfig = NFS.existsSync(Path.join(directory, "tsconfig.json"));
|
|
34159
|
-
const
|
|
34160
|
-
const enclosingProject =
|
|
34328
|
+
const enclosingProjectRoot = findNearestAncestorPackageJson(directory);
|
|
34329
|
+
const enclosingProject = enclosingProjectRoot !== null ? discoverProject(enclosingProjectRoot) : null;
|
|
34161
34330
|
if (enclosingProject !== null) return {
|
|
34162
34331
|
...enclosingProject,
|
|
34163
34332
|
rootDirectory: directory,
|
|
@@ -34187,7 +34356,9 @@ const discoverProjectWithoutPackageJson = (directory) => {
|
|
|
34187
34356
|
shopifyFlashListVersion: null,
|
|
34188
34357
|
shopifyFlashListMajorVersion: null,
|
|
34189
34358
|
hasReanimated: false,
|
|
34359
|
+
reanimatedVersion: null,
|
|
34190
34360
|
isPreES2023Target: hasOwnTsConfig && detectPreES2023Target(directory),
|
|
34361
|
+
isStaticExport: false,
|
|
34191
34362
|
sourceFileCount
|
|
34192
34363
|
};
|
|
34193
34364
|
};
|
|
@@ -34201,88 +34372,85 @@ const discoverProject = (directory) => {
|
|
|
34201
34372
|
return synthesized;
|
|
34202
34373
|
}
|
|
34203
34374
|
const packageJson = readPackageJson(packageJsonPath);
|
|
34204
|
-
|
|
34205
|
-
|
|
34206
|
-
|
|
34207
|
-
|
|
34208
|
-
|
|
34209
|
-
|
|
34210
|
-
|
|
34211
|
-
|
|
34212
|
-
|
|
34213
|
-
|
|
34214
|
-
|
|
34215
|
-
|
|
34216
|
-
|
|
34217
|
-
|
|
34218
|
-
|
|
34219
|
-
|
|
34220
|
-
|
|
34221
|
-
]
|
|
34222
|
-
});
|
|
34223
|
-
const zodDeclaration = getDependencyDeclaration({
|
|
34375
|
+
const rootInfo = extractDependencyInfo(packageJson);
|
|
34376
|
+
let framework = rootInfo.framework;
|
|
34377
|
+
const tracked = {
|
|
34378
|
+
react: {
|
|
34379
|
+
version: rootInfo.reactVersion,
|
|
34380
|
+
sections: REACT_SECTIONS
|
|
34381
|
+
},
|
|
34382
|
+
tailwindcss: {
|
|
34383
|
+
version: rootInfo.tailwindVersion,
|
|
34384
|
+
sections: TAILWIND_ZOD_SECTIONS
|
|
34385
|
+
},
|
|
34386
|
+
zod: {
|
|
34387
|
+
version: rootInfo.zodVersion,
|
|
34388
|
+
sections: TAILWIND_ZOD_SECTIONS
|
|
34389
|
+
}
|
|
34390
|
+
};
|
|
34391
|
+
const declarations = Object.fromEntries(Object.entries(tracked).map(([packageName, entry]) => [packageName, getDependencyDeclaration({
|
|
34224
34392
|
packageJson,
|
|
34225
|
-
packageName
|
|
34226
|
-
sections:
|
|
34227
|
-
|
|
34228
|
-
|
|
34229
|
-
|
|
34230
|
-
|
|
34231
|
-
|
|
34232
|
-
if (!
|
|
34233
|
-
if (!tailwindVersion && tailwindDeclaration.hasDeclaration) tailwindVersion = resolveCatalogVersion(packageJson, "tailwindcss", directory, tailwindDeclaration.catalogReference);
|
|
34234
|
-
if (!zodVersion && zodDeclaration.hasDeclaration) zodVersion = resolveCatalogVersion(packageJson, "zod", directory, zodDeclaration.catalogReference);
|
|
34235
|
-
if (!reactVersion || !tailwindVersion || !zodVersion) {
|
|
34393
|
+
packageName,
|
|
34394
|
+
sections: entry.sections
|
|
34395
|
+
})]));
|
|
34396
|
+
const fillFromCatalogs = (source, sourceDirectory) => {
|
|
34397
|
+
for (const [packageName, entry] of Object.entries(tracked)) if (!entry.version && declarations[packageName].hasDeclaration) entry.version = resolveCatalogVersion(source, packageName, sourceDirectory, declarations[packageName].catalogReference);
|
|
34398
|
+
};
|
|
34399
|
+
fillFromCatalogs(packageJson, directory);
|
|
34400
|
+
if (!tracked.react.version || !tracked.tailwindcss.version || !tracked.zod.version) {
|
|
34236
34401
|
const monorepoRoot = findMonorepoRoot(directory);
|
|
34237
34402
|
if (monorepoRoot) {
|
|
34238
34403
|
const monorepoPackageJsonPath = Path.join(monorepoRoot, "package.json");
|
|
34239
|
-
if (isFile(monorepoPackageJsonPath))
|
|
34240
|
-
const rootPackageJson = readPackageJson(monorepoPackageJsonPath);
|
|
34241
|
-
if (!reactVersion && reactDeclaration.hasDeclaration) reactVersion = resolveCatalogVersion(rootPackageJson, "react", monorepoRoot, reactDeclaration.catalogReference);
|
|
34242
|
-
if (!tailwindVersion && tailwindDeclaration.hasDeclaration) tailwindVersion = resolveCatalogVersion(rootPackageJson, "tailwindcss", monorepoRoot, tailwindDeclaration.catalogReference);
|
|
34243
|
-
if (!zodVersion && zodDeclaration.hasDeclaration) zodVersion = resolveCatalogVersion(rootPackageJson, "zod", monorepoRoot, zodDeclaration.catalogReference);
|
|
34244
|
-
}
|
|
34404
|
+
if (isFile(monorepoPackageJsonPath)) fillFromCatalogs(readPackageJson(monorepoPackageJsonPath), monorepoRoot);
|
|
34245
34405
|
}
|
|
34246
34406
|
}
|
|
34247
|
-
|
|
34248
|
-
|
|
34249
|
-
|
|
34250
|
-
|
|
34251
|
-
|
|
34252
|
-
|
|
34407
|
+
const shouldCollectReactGroup = !tracked.react.version || framework === "unknown";
|
|
34408
|
+
const workspaceFacts = collectWorkspaceFacts(directory, packageJson, { collectReactGroup: shouldCollectReactGroup });
|
|
34409
|
+
if (shouldCollectReactGroup) {
|
|
34410
|
+
tracked.react.version ||= workspaceFacts.reactVersion;
|
|
34411
|
+
tracked.tailwindcss.version ||= workspaceFacts.tailwindVersion;
|
|
34412
|
+
tracked.zod.version ||= workspaceFacts.zodVersion;
|
|
34413
|
+
if (framework === "unknown" && workspaceFacts.framework !== "unknown") framework = workspaceFacts.framework;
|
|
34253
34414
|
}
|
|
34254
|
-
if ((!
|
|
34415
|
+
if ((!tracked.react.version || framework === "unknown") && !isMonorepoRoot(directory)) {
|
|
34255
34416
|
const monorepoInfo = findDependencyInfoFromMonorepoRoot(directory);
|
|
34256
|
-
|
|
34257
|
-
|
|
34258
|
-
|
|
34417
|
+
tracked.react.version ||= monorepoInfo.reactVersion;
|
|
34418
|
+
tracked.tailwindcss.version ||= monorepoInfo.tailwindVersion;
|
|
34419
|
+
tracked.zod.version ||= monorepoInfo.zodVersion;
|
|
34259
34420
|
if (framework === "unknown") framework = monorepoInfo.framework;
|
|
34260
34421
|
}
|
|
34261
|
-
|
|
34262
|
-
|
|
34263
|
-
|
|
34422
|
+
for (const [packageName, entry] of Object.entries(tracked)) {
|
|
34423
|
+
const declaredVersion = declarations[packageName].version;
|
|
34424
|
+
if (!entry.version && declaredVersion && !isCatalogReference(declaredVersion)) entry.version = declaredVersion;
|
|
34425
|
+
}
|
|
34426
|
+
const { react, tailwindcss, zod } = tracked;
|
|
34427
|
+
let reactVersion = react.version;
|
|
34428
|
+
if (!reactVersion || parseReactMajor(reactVersion) === null) reactVersion = resolveInstalledReactVersion(directory) ?? reactVersion;
|
|
34429
|
+
const tailwindVersion = tailwindcss.version;
|
|
34430
|
+
const zodVersion = zod.version;
|
|
34264
34431
|
const projectName = packageJson.name ?? Path.basename(directory);
|
|
34265
34432
|
const hasTypeScript = NFS.existsSync(Path.join(directory, "tsconfig.json"));
|
|
34266
34433
|
const sourceFileCount = countSourceFiles(directory);
|
|
34267
|
-
const hasReactNativeWorkspace = framework === "expo" || framework === "react-native" ||
|
|
34434
|
+
const hasReactNativeWorkspace = framework === "expo" || framework === "react-native" || workspaceFacts.hasReactNativeAwarePackage;
|
|
34268
34435
|
const expoVersion = hasReactNativeWorkspace ? resolveCatalogBackedDependencyVersion({
|
|
34269
34436
|
rootDirectory: directory,
|
|
34270
34437
|
rootPackageJson: packageJson,
|
|
34271
34438
|
packageName: "expo",
|
|
34272
|
-
version:
|
|
34439
|
+
version: workspaceFacts.expo.version
|
|
34273
34440
|
}) : null;
|
|
34274
34441
|
const shopifyFlashListVersion = hasReactNativeWorkspace ? resolveCatalogBackedDependencyVersion({
|
|
34275
34442
|
rootDirectory: directory,
|
|
34276
34443
|
rootPackageJson: packageJson,
|
|
34277
34444
|
packageName: SHOPIFY_FLASH_LIST_PACKAGE_NAME,
|
|
34278
|
-
version:
|
|
34445
|
+
version: workspaceFacts.shopifyFlashList.version
|
|
34279
34446
|
}) : null;
|
|
34280
|
-
const hasReanimated = hasReactNativeWorkspace &&
|
|
34447
|
+
const hasReanimated = hasReactNativeWorkspace && workspaceFacts.hasReanimatedAwarePackage;
|
|
34448
|
+
const reanimatedVersion = hasReanimated ? workspaceFacts.reanimatedVersion : null;
|
|
34281
34449
|
const nextjsVersion = framework === "nextjs" ? resolveCatalogBackedDependencyVersion({
|
|
34282
34450
|
rootDirectory: directory,
|
|
34283
34451
|
rootPackageJson: packageJson,
|
|
34284
34452
|
packageName: "next",
|
|
34285
|
-
version:
|
|
34453
|
+
version: workspaceFacts.next.version
|
|
34286
34454
|
}) : null;
|
|
34287
34455
|
const preactVersion = getPreactVersion(packageJson);
|
|
34288
34456
|
const isPreES2023Target = hasTypeScript && detectPreES2023Target(directory);
|
|
@@ -34293,7 +34461,7 @@ const discoverProject = (directory) => {
|
|
|
34293
34461
|
reactMajorVersion: resolveEffectiveReactMajor(reactVersion, packageJson),
|
|
34294
34462
|
tailwindVersion,
|
|
34295
34463
|
zodVersion,
|
|
34296
|
-
zodMajorVersion:
|
|
34464
|
+
zodMajorVersion: zodVersion === null ? null : getLowestDependencyMajor(zodVersion),
|
|
34297
34465
|
framework,
|
|
34298
34466
|
hasTypeScript,
|
|
34299
34467
|
hasReactCompiler: detectReactCompiler(directory, packageJson),
|
|
@@ -34307,7 +34475,9 @@ const discoverProject = (directory) => {
|
|
|
34307
34475
|
shopifyFlashListVersion,
|
|
34308
34476
|
shopifyFlashListMajorVersion: shopifyFlashListVersion === null ? null : getLowestDependencyMajor(shopifyFlashListVersion),
|
|
34309
34477
|
hasReanimated,
|
|
34478
|
+
reanimatedVersion,
|
|
34310
34479
|
isPreES2023Target,
|
|
34480
|
+
isStaticExport: framework === "nextjs" && detectNextjsStaticExport(workspaceFacts.next.sourceDirectory ?? directory),
|
|
34311
34481
|
sourceFileCount
|
|
34312
34482
|
};
|
|
34313
34483
|
cachedProjectInfos.set(directory, projectInfo);
|
|
@@ -34323,57 +34493,6 @@ const discoverProject = (directory) => {
|
|
|
34323
34493
|
* single source file has nothing to analyze.
|
|
34324
34494
|
*/
|
|
34325
34495
|
const isAnalyzableProject = (project) => project.reactVersion !== null || project.preactVersion !== null || project.sourceFileCount > 0;
|
|
34326
|
-
const MAJOR_MINOR_PATTERN = /(\d{1,4})\.(\d{1,4})/;
|
|
34327
|
-
const MAJOR_ONLY_PATTERN = /(\d{1,4})/;
|
|
34328
|
-
const UPPER_BOUND_COMPARATOR_PATTERN = /<=?\s{0,8}\d{1,4}(?:\.\d{1,4}){0,2}(?:-[^\s,|]+)?/g;
|
|
34329
|
-
const parseReactMajorMinor = (reactVersion) => {
|
|
34330
|
-
if (typeof reactVersion !== "string") return null;
|
|
34331
|
-
const trimmed = reactVersion.trim();
|
|
34332
|
-
if (trimmed.length === 0) return null;
|
|
34333
|
-
const lowerBoundsOnly = trimmed.replace(UPPER_BOUND_COMPARATOR_PATTERN, " ").trim();
|
|
34334
|
-
if (lowerBoundsOnly.length === 0) return null;
|
|
34335
|
-
const majorMinorMatch = lowerBoundsOnly.match(MAJOR_MINOR_PATTERN);
|
|
34336
|
-
if (majorMinorMatch) {
|
|
34337
|
-
const major = Number.parseInt(majorMinorMatch[1], 10);
|
|
34338
|
-
const minor = Number.parseInt(majorMinorMatch[2], 10);
|
|
34339
|
-
if (!Number.isFinite(major) || major <= 0) return null;
|
|
34340
|
-
if (!Number.isFinite(minor) || minor < 0) return null;
|
|
34341
|
-
return {
|
|
34342
|
-
major,
|
|
34343
|
-
minor
|
|
34344
|
-
};
|
|
34345
|
-
}
|
|
34346
|
-
const majorOnlyMatch = lowerBoundsOnly.match(MAJOR_ONLY_PATTERN);
|
|
34347
|
-
if (!majorOnlyMatch) return null;
|
|
34348
|
-
const major = Number.parseInt(majorOnlyMatch[1], 10);
|
|
34349
|
-
if (!Number.isFinite(major) || major <= 0) return null;
|
|
34350
|
-
return {
|
|
34351
|
-
major,
|
|
34352
|
-
minor: 0
|
|
34353
|
-
};
|
|
34354
|
-
};
|
|
34355
|
-
const isReactAtLeast = (detected, required) => {
|
|
34356
|
-
if (detected === null) return true;
|
|
34357
|
-
if (detected.major !== required.major) return detected.major > required.major;
|
|
34358
|
-
return detected.minor >= required.minor;
|
|
34359
|
-
};
|
|
34360
|
-
const parseLowerBoundVersion = (versionSpec) => import_semver.validRange(versionSpec) !== null ? import_semver.minVersion(versionSpec) : import_semver.coerce(versionSpec);
|
|
34361
|
-
const parseTailwindMajorMinor = (tailwindVersion) => {
|
|
34362
|
-
if (typeof tailwindVersion !== "string") return null;
|
|
34363
|
-
const trimmed = tailwindVersion.trim();
|
|
34364
|
-
if (trimmed.length === 0) return null;
|
|
34365
|
-
const lowerBound = parseLowerBoundVersion(trimmed);
|
|
34366
|
-
if (lowerBound === null || lowerBound.major <= 0) return null;
|
|
34367
|
-
return {
|
|
34368
|
-
major: lowerBound.major,
|
|
34369
|
-
minor: lowerBound.minor
|
|
34370
|
-
};
|
|
34371
|
-
};
|
|
34372
|
-
const isTailwindAtLeast = (detected, required) => {
|
|
34373
|
-
if (detected === null) return true;
|
|
34374
|
-
if (detected.major !== required.major) return detected.major > required.major;
|
|
34375
|
-
return detected.minor >= required.minor;
|
|
34376
|
-
};
|
|
34377
34496
|
const messageFromUnknown = (error) => error instanceof Error ? error.message : String(error);
|
|
34378
34497
|
var InvalidGlobPatternError = class extends Error {
|
|
34379
34498
|
pattern;
|
|
@@ -34499,6 +34618,9 @@ const buildRuleSeverityControls = (config) => {
|
|
|
34499
34618
|
...config.buckets !== void 0 ? { buckets: config.buckets } : {}
|
|
34500
34619
|
};
|
|
34501
34620
|
};
|
|
34621
|
+
const FOREIGN_INLINE_DISABLE_PATTERN = /(?:\/\/|\/\*)[ \t]*(eslint|oxlint)-disable-(next-line|line)(?![\w-])([^\r\n]*)/;
|
|
34622
|
+
const FOREIGN_BLOCK_DISABLE_PATTERN = /\/\*[ \t]*(eslint|oxlint)-disable(?![\w-])([^*\r\n]*)/;
|
|
34623
|
+
const FOREIGN_BLOCK_ENABLE_PATTERN = /\/\*[ \t]*(?:eslint|oxlint)-enable(?![\w-])([^*\r\n]*)/;
|
|
34502
34624
|
const LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY = {
|
|
34503
34625
|
"effect/no-adjust-state-on-prop-change": "react-doctor/no-adjust-state-on-prop-change",
|
|
34504
34626
|
"effect/no-chain-state-updates": "react-doctor/no-chain-state-updates",
|
|
@@ -34576,6 +34698,7 @@ const LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY = {
|
|
|
34576
34698
|
"react/jsx-no-new-function-as-prop": "react-doctor/jsx-no-new-function-as-prop",
|
|
34577
34699
|
"react/jsx-no-new-object-as-prop": "react-doctor/jsx-no-new-object-as-prop",
|
|
34578
34700
|
"react/jsx-no-script-url": "react-doctor/jsx-no-script-url",
|
|
34701
|
+
"react/jsx-no-target-blank": "react-doctor/jsx-no-target-blank",
|
|
34579
34702
|
"react/jsx-no-undef": "react-doctor/jsx-no-undef",
|
|
34580
34703
|
"react/jsx-no-useless-fragment": "react-doctor/jsx-no-useless-fragment",
|
|
34581
34704
|
"react/jsx-pascal-case": "react-doctor/jsx-pascal-case",
|
|
@@ -34649,9 +34772,6 @@ const tokenizeRuleList = (ruleList) => {
|
|
|
34649
34772
|
if (!ruleSection) return [];
|
|
34650
34773
|
return ruleSection.split(/[,\s]+/).map((token) => token.trim()).filter(Boolean);
|
|
34651
34774
|
};
|
|
34652
|
-
const FOREIGN_INLINE_DISABLE_PATTERN = /(?:\/\/|\/\*)[ \t]*(eslint|oxlint)-disable-(next-line|line)(?![\w-])([^\r\n]*)/;
|
|
34653
|
-
const FOREIGN_BLOCK_DISABLE_PATTERN = /\/\*[ \t]*(eslint|oxlint)-disable(?![\w-])([^*\r\n]*)/;
|
|
34654
|
-
const FOREIGN_BLOCK_ENABLE_PATTERN = /\/\*[ \t]*(?:eslint|oxlint)-enable(?![\w-])([^*\r\n]*)/;
|
|
34655
34775
|
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}\`).`;
|
|
34656
34776
|
const tokenMisnamesRule = (token, ruleId) => token !== ruleId && isSameRuleKey(token, ruleId);
|
|
34657
34777
|
const detectInlineNearMiss = (lines, diagnosticLineIndex, ruleId) => {
|
|
@@ -34705,6 +34825,53 @@ const detectForeignDisableNearMiss = (lines, diagnosticLineIndex, ruleId) => {
|
|
|
34705
34825
|
if (!ruleId.startsWith("react-doctor/")) return null;
|
|
34706
34826
|
return detectInlineNearMiss(lines, diagnosticLineIndex, ruleId) ?? detectBlockNearMiss(lines, diagnosticLineIndex, ruleId);
|
|
34707
34827
|
};
|
|
34828
|
+
const ESLINT_REACT_HOOKS_PLUGIN_PREFIX = "react-hooks/";
|
|
34829
|
+
const buildAcceptedTokens = (ruleId) => {
|
|
34830
|
+
const shortRuleId = ruleId.slice(15);
|
|
34831
|
+
return new Set([ruleId, `${ESLINT_REACT_HOOKS_PLUGIN_PREFIX}${shortRuleId}`]);
|
|
34832
|
+
};
|
|
34833
|
+
const listNamesRule = (ruleList, acceptedTokens) => tokenizeRuleList(ruleList).some((token) => acceptedTokens.has(token));
|
|
34834
|
+
const isInlineDisabled = (lines, diagnosticLineIndex, acceptedTokens) => {
|
|
34835
|
+
const candidates = [{
|
|
34836
|
+
line: lines[diagnosticLineIndex],
|
|
34837
|
+
requiredScope: "line"
|
|
34838
|
+
}, {
|
|
34839
|
+
line: lines[diagnosticLineIndex - 1],
|
|
34840
|
+
requiredScope: "next-line"
|
|
34841
|
+
}];
|
|
34842
|
+
for (const { line, requiredScope } of candidates) {
|
|
34843
|
+
const match = line?.match(FOREIGN_INLINE_DISABLE_PATTERN);
|
|
34844
|
+
if (!match) continue;
|
|
34845
|
+
const [, , scope, ruleList] = match;
|
|
34846
|
+
if (scope !== requiredScope) continue;
|
|
34847
|
+
if (listNamesRule(ruleList, acceptedTokens)) return true;
|
|
34848
|
+
}
|
|
34849
|
+
return false;
|
|
34850
|
+
};
|
|
34851
|
+
const isBlockDisabled = (lines, diagnosticLineIndex, acceptedTokens) => {
|
|
34852
|
+
let isRangeOpen = false;
|
|
34853
|
+
const lastLineIndex = Math.min(diagnosticLineIndex, lines.length - 1);
|
|
34854
|
+
for (let lineIndex = 0; lineIndex <= lastLineIndex; lineIndex++) {
|
|
34855
|
+
const line = lines[lineIndex];
|
|
34856
|
+
if (line === void 0 || !line.includes("-disable") && !line.includes("-enable")) continue;
|
|
34857
|
+
const disableMatch = line.match(FOREIGN_BLOCK_DISABLE_PATTERN);
|
|
34858
|
+
if (disableMatch && listNamesRule(disableMatch[2], acceptedTokens)) {
|
|
34859
|
+
isRangeOpen = true;
|
|
34860
|
+
continue;
|
|
34861
|
+
}
|
|
34862
|
+
const enableMatch = line.match(FOREIGN_BLOCK_ENABLE_PATTERN);
|
|
34863
|
+
if (enableMatch) {
|
|
34864
|
+
const enabledRules = tokenizeRuleList(enableMatch[1]);
|
|
34865
|
+
if (enabledRules.length === 0 || enabledRules.some((rule) => acceptedTokens.has(rule))) isRangeOpen = false;
|
|
34866
|
+
}
|
|
34867
|
+
}
|
|
34868
|
+
return isRangeOpen;
|
|
34869
|
+
};
|
|
34870
|
+
const isCompilerRuleForeignDisabled = (lines, diagnosticLineIndex, ruleId) => {
|
|
34871
|
+
if (!ruleId.startsWith("react-hooks-js/")) return false;
|
|
34872
|
+
const acceptedTokens = buildAcceptedTokens(ruleId);
|
|
34873
|
+
return isInlineDisabled(lines, diagnosticLineIndex, acceptedTokens) || isBlockDisabled(lines, diagnosticLineIndex, acceptedTokens);
|
|
34874
|
+
};
|
|
34708
34875
|
const JSX_OPENER_TAG_PATTERN = /<[A-Za-z][\w.]*/g;
|
|
34709
34876
|
const JSX_TAG_NAME_FOLLOW = /[A-Za-z]/;
|
|
34710
34877
|
const isOpenerMatchInsideLineComment = (line, openerCharIndex) => {
|
|
@@ -34848,6 +35015,11 @@ const evaluateSuppression = (lines, diagnosticLineIndex, ruleId) => {
|
|
|
34848
35015
|
isSuppressed: true,
|
|
34849
35016
|
nearMissHint: null
|
|
34850
35017
|
};
|
|
35018
|
+
if (isCompilerRuleForeignDisabled(lines, diagnosticLineIndex, ruleId)) return {
|
|
35019
|
+
isSuppressed: true,
|
|
35020
|
+
nearMissHint: null,
|
|
35021
|
+
isForeignDirective: true
|
|
35022
|
+
};
|
|
34851
35023
|
const directComments = findStackedDisableCommentsAbove(lines, diagnosticLineIndex);
|
|
34852
35024
|
if (hasChainSuppressor(directComments, ruleId)) return {
|
|
34853
35025
|
isSuppressed: true,
|
|
@@ -35129,12 +35301,18 @@ const collectStringSet = (values) => {
|
|
|
35129
35301
|
return new Set(values.filter((value) => typeof value === "string"));
|
|
35130
35302
|
};
|
|
35131
35303
|
const JSX_NO_UNDEF_IDENTIFIER_PATTERN = /^`([^`]+)`/;
|
|
35304
|
+
const NODE_CLI_PATH_SEGMENTS = new Set([
|
|
35305
|
+
"scripts",
|
|
35306
|
+
"bin",
|
|
35307
|
+
"tools"
|
|
35308
|
+
]);
|
|
35309
|
+
const hasNodeCliPathSegment = (filePath) => filePath.split(/[\\/]/).slice(0, -1).some((segment) => NODE_CLI_PATH_SEGMENTS.has(segment));
|
|
35132
35310
|
/**
|
|
35133
35311
|
* Pre-compiles every stateful filter and returns a single
|
|
35134
35312
|
* `apply(diagnostic)` closure that runs (in order):
|
|
35135
35313
|
*
|
|
35136
35314
|
* 1. auto-suppress (test-noise rules in test files; `migration-hint`
|
|
35137
|
-
* wins over `test-noise`)
|
|
35315
|
+
* wins over `test-noise`; `no-console` on Node CLI scripts)
|
|
35138
35316
|
* 2. severity overrides (top-level `rules` / `categories`, with
|
|
35139
35317
|
* `"off"` dropping)
|
|
35140
35318
|
* 3. warning suppression (only when `showWarnings` is false: drops every
|
|
@@ -35232,6 +35410,12 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
35232
35410
|
if (hasRawTextWrappers && isInsideStringOnlyWrapper(lines, diagnostic.line, diagnostic.column, rawTextWrapperComponentNames)) return true;
|
|
35233
35411
|
return false;
|
|
35234
35412
|
};
|
|
35413
|
+
const isNoConsoleOnNodeCliScript = (diagnostic) => {
|
|
35414
|
+
if (diagnostic.rule !== "no-console") return false;
|
|
35415
|
+
if (hasNodeCliPathSegment(diagnostic.filePath)) return true;
|
|
35416
|
+
const lines = getFileLines(diagnostic.filePath);
|
|
35417
|
+
return lines !== null && Boolean(lines[0]?.startsWith("#!"));
|
|
35418
|
+
};
|
|
35235
35419
|
const isJsxNoUndefSuppressedByConfig = (diagnostic) => {
|
|
35236
35420
|
if (!hasRuntimeGlobals) return false;
|
|
35237
35421
|
if (diagnostic.rule !== "jsx-no-undef") return false;
|
|
@@ -35241,6 +35425,7 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
35241
35425
|
return {
|
|
35242
35426
|
apply: (diagnostic) => {
|
|
35243
35427
|
if (shouldAutoSuppress(diagnostic)) return null;
|
|
35428
|
+
if (isNoConsoleOnNodeCliScript(diagnostic)) return null;
|
|
35244
35429
|
let current = diagnostic;
|
|
35245
35430
|
let explicitSeverityOverride;
|
|
35246
35431
|
let explicitRuleOverride;
|
|
@@ -35270,7 +35455,7 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
35270
35455
|
if (lines) {
|
|
35271
35456
|
const ruleIdentifier = `${current.plugin}/${current.rule}`;
|
|
35272
35457
|
const evaluation = evaluateSuppression(lines, current.line - 1, ruleIdentifier);
|
|
35273
|
-
if (evaluation.isSuppressed) return suppress(current, "inline");
|
|
35458
|
+
if (evaluation.isSuppressed) return suppress(current, evaluation.isForeignDirective ? "foreign-inline" : "inline");
|
|
35274
35459
|
if (evaluation.nearMissHint) current = {
|
|
35275
35460
|
...current,
|
|
35276
35461
|
suppressionHint: evaluation.nearMissHint
|
|
@@ -37009,64 +37194,64 @@ function* collectSecurityScanFiles(rootDirectory) {
|
|
|
37009
37194
|
}
|
|
37010
37195
|
}
|
|
37011
37196
|
}
|
|
37012
|
-
|
|
37013
|
-
|
|
37014
|
-
|
|
37015
|
-
|
|
37016
|
-
|
|
37017
|
-
|
|
37018
|
-
|
|
37197
|
+
const CLIENT_ONLY_FRAMEWORKS = new Set([
|
|
37198
|
+
"vite",
|
|
37199
|
+
"cra",
|
|
37200
|
+
"gatsby",
|
|
37201
|
+
"react-native",
|
|
37202
|
+
"expo"
|
|
37203
|
+
]);
|
|
37204
|
+
const addMajorLadder = (capabilities, name, major, earliest, latest) => {
|
|
37205
|
+
if (major === null) return;
|
|
37206
|
+
const cappedMajor = Math.min(major, latest);
|
|
37207
|
+
for (let candidate = earliest; candidate <= cappedMajor; candidate += 1) capabilities.add(`${name}:${candidate}`);
|
|
37208
|
+
};
|
|
37019
37209
|
const buildCapabilities = (project) => {
|
|
37020
37210
|
const capabilities = /* @__PURE__ */ new Set();
|
|
37021
37211
|
capabilities.add(project.framework);
|
|
37022
|
-
if (
|
|
37212
|
+
if (project.reactVersion !== null || project.preactVersion !== null) capabilities.add("react");
|
|
37023
37213
|
if (project.framework === "expo" || project.framework === "react-native" || project.hasReactNativeWorkspace) capabilities.add("react-native");
|
|
37024
37214
|
if (project.expoVersion !== null) capabilities.add("expo");
|
|
37215
|
+
if ((project.framework === "nextjs" || project.framework === "tanstack-start" || project.framework === "remix") && !project.isStaticExport) capabilities.add("server-actions");
|
|
37216
|
+
if (CLIENT_ONLY_FRAMEWORKS.has(project.framework)) capabilities.add("client-only");
|
|
37217
|
+
if (project.isStaticExport) capabilities.add("nextjs:static-export");
|
|
37025
37218
|
if (project.nextjsMajorVersion !== null && project.nextjsMajorVersion >= 15) capabilities.add("nextjs:15");
|
|
37026
|
-
|
|
37027
|
-
if (
|
|
37028
|
-
|
|
37029
|
-
|
|
37030
|
-
|
|
37031
|
-
|
|
37032
|
-
|
|
37033
|
-
|
|
37034
|
-
|
|
37035
|
-
|
|
37036
|
-
|
|
37037
|
-
if (project.
|
|
37038
|
-
capabilities.add("tailwind");
|
|
37039
|
-
if (isTailwindAtLeast(parseTailwindMajorMinor(project.tailwindVersion), {
|
|
37040
|
-
major: 3,
|
|
37041
|
-
minor: 4
|
|
37042
|
-
})) capabilities.add("tailwind:3.4");
|
|
37043
|
-
}
|
|
37044
|
-
if (project.zodVersion !== null) {
|
|
37045
|
-
capabilities.add("zod");
|
|
37046
|
-
if (project.zodMajorVersion !== null && project.zodMajorVersion >= 4) capabilities.add("zod:4");
|
|
37047
|
-
}
|
|
37219
|
+
addMajorLadder(capabilities, "react", project.reactMajorVersion, 17, 30);
|
|
37220
|
+
if (project.reactMajorVersion !== null && project.reactMajorVersion >= 19 && isMajorMinorAtLeast(parseReactMajorMinor(project.reactVersion), {
|
|
37221
|
+
major: 19,
|
|
37222
|
+
minor: 2
|
|
37223
|
+
})) capabilities.add("react:19.2");
|
|
37224
|
+
if (project.tailwindVersion !== null) capabilities.add("tailwind");
|
|
37225
|
+
if (project.tailwindVersion !== null && isMajorMinorAtLeast(parseTailwindMajorMinor(project.tailwindVersion), {
|
|
37226
|
+
major: 3,
|
|
37227
|
+
minor: 4
|
|
37228
|
+
})) capabilities.add("tailwind:3.4");
|
|
37229
|
+
if (project.zodVersion !== null) capabilities.add("zod");
|
|
37230
|
+
if (project.zodMajorVersion !== null && project.zodMajorVersion >= 4) capabilities.add("zod:4");
|
|
37048
37231
|
if (project.isPreES2023Target) capabilities.add("pre-es2023");
|
|
37049
37232
|
if (project.hasReactCompiler) capabilities.add("react-compiler");
|
|
37050
37233
|
if (project.hasTanStackQuery) capabilities.add("tanstack-query");
|
|
37051
37234
|
if (project.hasTypeScript) capabilities.add("typescript");
|
|
37052
|
-
if (project.preactVersion !== null)
|
|
37053
|
-
|
|
37054
|
-
|
|
37055
|
-
if (preactMajor !== null) {
|
|
37056
|
-
const cappedPreactMajor = Math.min(preactMajor, 20);
|
|
37057
|
-
for (let major = 10; major <= cappedPreactMajor; major++) capabilities.add(`preact:${major}`);
|
|
37058
|
-
}
|
|
37059
|
-
if (project.reactVersion === null) capabilities.add("pure-preact");
|
|
37060
|
-
}
|
|
37235
|
+
if (project.preactVersion !== null) capabilities.add("preact");
|
|
37236
|
+
addMajorLadder(capabilities, "preact", project.preactMajorVersion, 10, 20);
|
|
37237
|
+
if (project.preactVersion !== null && project.reactVersion === null) capabilities.add("pure-preact");
|
|
37061
37238
|
return capabilities;
|
|
37062
37239
|
};
|
|
37063
|
-
const
|
|
37240
|
+
const capabilitiesByProject = /* @__PURE__ */ new WeakMap();
|
|
37241
|
+
const getCapabilities = (project) => {
|
|
37242
|
+
const cached = capabilitiesByProject.get(project);
|
|
37243
|
+
if (cached !== void 0) return cached;
|
|
37244
|
+
const capabilities = buildCapabilities(project);
|
|
37245
|
+
capabilitiesByProject.set(project, capabilities);
|
|
37246
|
+
return capabilities;
|
|
37247
|
+
};
|
|
37248
|
+
const shouldEnableRule = (requires, tags, capabilities, ignoredTags, disabledWhen) => {
|
|
37064
37249
|
if (requires) {
|
|
37065
37250
|
for (const capability of requires) if (!capabilities.has(capability)) return false;
|
|
37066
37251
|
}
|
|
37067
37252
|
if (tags?.includes("react-jsx-only") && !capabilities.has("react")) return false;
|
|
37068
|
-
if (
|
|
37069
|
-
for (const capability of
|
|
37253
|
+
if (disabledWhen) {
|
|
37254
|
+
for (const capability of disabledWhen) if (capabilities.has(capability)) return false;
|
|
37070
37255
|
}
|
|
37071
37256
|
if (tags) {
|
|
37072
37257
|
for (const tag of tags) if (ignoredTags.has(tag)) return false;
|
|
@@ -37077,14 +37262,14 @@ const yieldToEventLoop = () => new Promise((resolve) => {
|
|
|
37077
37262
|
setImmediate(resolve);
|
|
37078
37263
|
});
|
|
37079
37264
|
const createSecurityScanSession = (rootDirectory, options) => {
|
|
37080
|
-
const capabilities = options.project ?
|
|
37265
|
+
const capabilities = options.project ? getCapabilities(options.project) : /* @__PURE__ */ new Set();
|
|
37081
37266
|
const ignoredTags = options.ignoredTags ?? /* @__PURE__ */ new Set();
|
|
37082
37267
|
const enabledScanRules = REACT_DOCTOR_RULES.flatMap((entry) => {
|
|
37083
37268
|
const rule = entry.rule;
|
|
37084
37269
|
const scan = rule.scan;
|
|
37085
37270
|
if (typeof scan !== "function") return [];
|
|
37086
37271
|
if (rule.defaultEnabled === false) return [];
|
|
37087
|
-
if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.
|
|
37272
|
+
if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.disabledWhen)) return [];
|
|
37088
37273
|
return [{
|
|
37089
37274
|
entry,
|
|
37090
37275
|
scan,
|
|
@@ -37104,6 +37289,7 @@ const createSecurityScanSession = (rootDirectory, options) => {
|
|
|
37104
37289
|
return status === true;
|
|
37105
37290
|
};
|
|
37106
37291
|
const scanFileByRule = function* (file) {
|
|
37292
|
+
if (file === null) return;
|
|
37107
37293
|
for (const { entry, scan, committedFilesOnly } of enabledScanRules) {
|
|
37108
37294
|
for (const finding of scan(file)) {
|
|
37109
37295
|
if (committedFilesOnly && isFileGitIgnored(file)) continue;
|
|
@@ -38549,7 +38735,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
38549
38735
|
})),
|
|
38550
38736
|
showStagedContent: (directory, relativePath, options) => runCommand({
|
|
38551
38737
|
command: "git",
|
|
38552
|
-
args: ["show",
|
|
38738
|
+
args: ["show", `:./${relativePath}`],
|
|
38553
38739
|
directory,
|
|
38554
38740
|
maxStdoutBytes: options?.maxBufferBytes
|
|
38555
38741
|
}).pipe(map$3((result) => result.status === 0 ? result.stdout : null)),
|
|
@@ -38984,7 +39170,7 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
38984
39170
|
const reactCompilerRules = reactHooksJsPlugin ? applyRuleSeverityControls(filterRulesToAvailable(REACT_COMPILER_RULES, "react-hooks-js", reactHooksJsPlugin.availableRuleNames), severityControls) : {};
|
|
38985
39171
|
const jsPlugins = [];
|
|
38986
39172
|
if (reactHooksJsPlugin) jsPlugins.push(reactHooksJsPlugin.entry);
|
|
38987
|
-
const capabilities =
|
|
39173
|
+
const capabilities = getCapabilities(project);
|
|
38988
39174
|
const enabledReactDoctorRules = {};
|
|
38989
39175
|
for (const registryEntry of REACT_DOCTOR_RULES) {
|
|
38990
39176
|
const rule = reactDoctorPlugin.rules[registryEntry.id];
|
|
@@ -38995,7 +39181,7 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
38995
39181
|
if (rule.scan !== void 0) continue;
|
|
38996
39182
|
if (customRulesOnly && registryEntry.originallyExternal) continue;
|
|
38997
39183
|
if (rule.framework !== "global" && !rule.requires) continue;
|
|
38998
|
-
if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.
|
|
39184
|
+
if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.disabledWhen)) continue;
|
|
38999
39185
|
const explicitRuleOverride = resolveRuleSeverityOverride({ ruleKey: registryEntry.key }, severityControls);
|
|
39000
39186
|
if (rule.defaultEnabled === false && explicitRuleOverride === void 0) continue;
|
|
39001
39187
|
const severity = resolveRuleSeverityOverride({
|
|
@@ -39026,6 +39212,7 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
39026
39212
|
settings: { "react-doctor": {
|
|
39027
39213
|
framework: project.framework,
|
|
39028
39214
|
rootDirectory: resolveSettingsRootDirectory(project.rootDirectory),
|
|
39215
|
+
capabilities: [...capabilities].sort(),
|
|
39029
39216
|
...project.shopifyFlashListMajorVersion !== null ? { shopifyFlashListMajorVersion: project.shopifyFlashListMajorVersion } : {},
|
|
39030
39217
|
...serverAuthFunctionNames && serverAuthFunctionNames.length > 0 ? { serverAuthFunctionNames: [...serverAuthFunctionNames] } : {}
|
|
39031
39218
|
} },
|
|
@@ -39316,25 +39503,18 @@ const mapWithConcurrency = async (items, concurrency, task) => {
|
|
|
39316
39503
|
if (errors.length > 0) throw errors[0];
|
|
39317
39504
|
return results;
|
|
39318
39505
|
};
|
|
39319
|
-
const getPublicEnvPrefix = (framework) => {
|
|
39320
|
-
switch (framework) {
|
|
39321
|
-
case "nextjs": return "NEXT_PUBLIC_*";
|
|
39322
|
-
case "vite":
|
|
39323
|
-
case "tanstack-start": return "VITE_*";
|
|
39324
|
-
case "cra": return "REACT_APP_*";
|
|
39325
|
-
case "gatsby": return "GATSBY_*";
|
|
39326
|
-
default: return null;
|
|
39327
|
-
}
|
|
39328
|
-
};
|
|
39329
|
-
const buildNoSecretsRecommendation = (project, fallbackRecommendation) => {
|
|
39330
|
-
const publicEnvPrefix = getPublicEnvPrefix(project.framework);
|
|
39331
|
-
if (!publicEnvPrefix) return fallbackRecommendation;
|
|
39332
|
-
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`;
|
|
39333
|
-
};
|
|
39334
39506
|
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";
|
|
39507
|
+
const ACCESSORS_MIN_VERSION = "3.15.0";
|
|
39508
|
+
const supportsAccessors = (reanimatedVersion) => {
|
|
39509
|
+
if (reanimatedVersion === null) return true;
|
|
39510
|
+
const lowerBound = import_semver.validRange(reanimatedVersion) !== null ? import_semver.minVersion(reanimatedVersion) : import_semver.coerce(reanimatedVersion);
|
|
39511
|
+
if (lowerBound === null) return true;
|
|
39512
|
+
return import_semver.gte(lowerBound, ACCESSORS_MIN_VERSION);
|
|
39513
|
+
};
|
|
39335
39514
|
const appendReanimatedSharedValueHint = (help, rule, project) => {
|
|
39336
39515
|
if (rule !== "immutability") return help;
|
|
39337
39516
|
if (!project.hasReanimated) return help;
|
|
39517
|
+
if (!supportsAccessors(project.reanimatedVersion)) return help;
|
|
39338
39518
|
if (!help) return REANIMATED_SHARED_VALUE_HINT;
|
|
39339
39519
|
return `${help}\n\n${REANIMATED_SHARED_VALUE_HINT}`;
|
|
39340
39520
|
};
|
|
@@ -39496,13 +39676,13 @@ const REACT_USE_BINDING_RESOLUTION = {
|
|
|
39496
39676
|
isReactUseBinding: true,
|
|
39497
39677
|
isReactNamespaceBinding: false
|
|
39498
39678
|
};
|
|
39499
|
-
const getScriptKind = (filename) => {
|
|
39679
|
+
const getScriptKind$2 = (filename) => {
|
|
39500
39680
|
if (filename.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
39501
39681
|
if (filename.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
39502
39682
|
if (filename.endsWith(".ts")) return ts.ScriptKind.TS;
|
|
39503
39683
|
return ts.ScriptKind.JS;
|
|
39504
39684
|
};
|
|
39505
|
-
const getUtf16Offset = (sourceText, utf8Offset) => Buffer.from(sourceText).subarray(0, utf8Offset).toString("utf8").length;
|
|
39685
|
+
const getUtf16Offset$1 = (sourceText, utf8Offset) => Buffer.from(sourceText).subarray(0, utf8Offset).toString("utf8").length;
|
|
39506
39686
|
const unwrapExpression = (expression) => {
|
|
39507
39687
|
let currentExpression = expression;
|
|
39508
39688
|
while (ts.isParenthesizedExpression(currentExpression) || ts.isAsExpression(currentExpression) || ts.isSatisfiesExpression(currentExpression) || ts.isNonNullExpression(currentExpression) || ts.isTypeAssertionExpression(currentExpression)) currentExpression = currentExpression.expression;
|
|
@@ -39751,8 +39931,8 @@ const findUseCallIdentifier = (sourceFile, useOffset) => {
|
|
|
39751
39931
|
return matchedIdentifier;
|
|
39752
39932
|
};
|
|
39753
39933
|
const resolveUseCallBinding = (sourceText, filename, utf8Offset) => {
|
|
39754
|
-
const sourceFile = ts.createSourceFile(filename, sourceText, ts.ScriptTarget.Latest, true, getScriptKind(filename));
|
|
39755
|
-
const useIdentifier = findUseCallIdentifier(sourceFile, getUtf16Offset(sourceText, utf8Offset));
|
|
39934
|
+
const sourceFile = ts.createSourceFile(filename, sourceText, ts.ScriptTarget.Latest, true, getScriptKind$2(filename));
|
|
39935
|
+
const useIdentifier = findUseCallIdentifier(sourceFile, getUtf16Offset$1(sourceText, utf8Offset));
|
|
39756
39936
|
if (!useIdentifier) return null;
|
|
39757
39937
|
return resolveIdentifierBinding(useIdentifier, collectReactImportBindings(sourceFile), sourceFile);
|
|
39758
39938
|
};
|
|
@@ -39773,6 +39953,201 @@ const shouldSuppressLocalUseHookDiagnostic = (diagnostic, rootDirectory) => {
|
|
|
39773
39953
|
const bindingResolution = resolveUseCallBinding(sourceText, absolutePath, primaryLabel.span.offset);
|
|
39774
39954
|
return bindingResolution !== null && !bindingResolution.isReactUseBinding;
|
|
39775
39955
|
};
|
|
39956
|
+
const SUPPRESSED_COMPILER_CODES = new Set(["react-hooks-js(immutability)", "react-hooks-js(refs)"]);
|
|
39957
|
+
const WORKLET_DIRECTIVE = "worklet";
|
|
39958
|
+
const WORKLET_ACCEPTING_CALLEES = new Set([
|
|
39959
|
+
"useAnimatedStyle",
|
|
39960
|
+
"useAnimatedProps",
|
|
39961
|
+
"useDerivedValue",
|
|
39962
|
+
"useAnimatedReaction",
|
|
39963
|
+
"useAnimatedScrollHandler",
|
|
39964
|
+
"useAnimatedGestureHandler",
|
|
39965
|
+
"useFrameCallback",
|
|
39966
|
+
"runOnUI",
|
|
39967
|
+
"executeOnUIRuntimeSync"
|
|
39968
|
+
]);
|
|
39969
|
+
const GESTURE_ROOT_IDENTIFIER = "Gesture";
|
|
39970
|
+
const getScriptKind$1 = (filename) => {
|
|
39971
|
+
if (filename.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
39972
|
+
if (filename.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
39973
|
+
if (filename.endsWith(".ts")) return ts.ScriptKind.TS;
|
|
39974
|
+
return ts.ScriptKind.JS;
|
|
39975
|
+
};
|
|
39976
|
+
const getUtf16Offset = (sourceText, utf8Offset) => Buffer.from(sourceText).subarray(0, utf8Offset).toString("utf8").length;
|
|
39977
|
+
const hasWorkletDirective = (node) => {
|
|
39978
|
+
if (!ts.isFunctionDeclaration(node) && !ts.isFunctionExpression(node) && !ts.isArrowFunction(node) && !ts.isMethodDeclaration(node)) return false;
|
|
39979
|
+
const body = node.body;
|
|
39980
|
+
if (!body || !ts.isBlock(body)) return false;
|
|
39981
|
+
for (const statement of body.statements) {
|
|
39982
|
+
if (!ts.isExpressionStatement(statement)) return false;
|
|
39983
|
+
if (!ts.isStringLiteral(statement.expression)) return false;
|
|
39984
|
+
if (statement.expression.text === WORKLET_DIRECTIVE) return true;
|
|
39985
|
+
}
|
|
39986
|
+
return false;
|
|
39987
|
+
};
|
|
39988
|
+
const rootIdentifierOfCallChain = (expression) => {
|
|
39989
|
+
let currentExpression = expression;
|
|
39990
|
+
while (true) {
|
|
39991
|
+
if (ts.isIdentifier(currentExpression)) return currentExpression.text;
|
|
39992
|
+
if (ts.isPropertyAccessExpression(currentExpression)) {
|
|
39993
|
+
currentExpression = currentExpression.expression;
|
|
39994
|
+
continue;
|
|
39995
|
+
}
|
|
39996
|
+
if (ts.isCallExpression(currentExpression)) {
|
|
39997
|
+
currentExpression = currentExpression.expression;
|
|
39998
|
+
continue;
|
|
39999
|
+
}
|
|
40000
|
+
return null;
|
|
40001
|
+
}
|
|
40002
|
+
};
|
|
40003
|
+
const isWorkletAcceptingCall = (callExpression) => {
|
|
40004
|
+
const callee = callExpression.expression;
|
|
40005
|
+
if (ts.isIdentifier(callee)) return WORKLET_ACCEPTING_CALLEES.has(callee.text);
|
|
40006
|
+
if (ts.isPropertyAccessExpression(callee)) {
|
|
40007
|
+
if (WORKLET_ACCEPTING_CALLEES.has(callee.name.text)) return true;
|
|
40008
|
+
return rootIdentifierOfCallChain(callee) === GESTURE_ROOT_IDENTIFIER;
|
|
40009
|
+
}
|
|
40010
|
+
return false;
|
|
40011
|
+
};
|
|
40012
|
+
const isFunctionWorklet = (functionNode) => {
|
|
40013
|
+
if (hasWorkletDirective(functionNode)) return true;
|
|
40014
|
+
const parent = functionNode.parent;
|
|
40015
|
+
return ts.isCallExpression(parent) && parent.arguments.some((argument) => argument === functionNode) && isWorkletAcceptingCall(parent);
|
|
40016
|
+
};
|
|
40017
|
+
const findNodeAtOffset = (sourceFile, targetOffset) => {
|
|
40018
|
+
let matchedNode = null;
|
|
40019
|
+
const visit = (node) => {
|
|
40020
|
+
if (node.getStart(sourceFile) > targetOffset || node.getEnd() <= targetOffset) return;
|
|
40021
|
+
matchedNode = node;
|
|
40022
|
+
ts.forEachChild(node, visit);
|
|
40023
|
+
};
|
|
40024
|
+
visit(sourceFile);
|
|
40025
|
+
return matchedNode;
|
|
40026
|
+
};
|
|
40027
|
+
const isOffsetInsideWorklet = (sourceFile, targetOffset) => {
|
|
40028
|
+
let currentNode = findNodeAtOffset(sourceFile, targetOffset);
|
|
40029
|
+
while (currentNode) {
|
|
40030
|
+
if (ts.isFunctionLike(currentNode) && isFunctionWorklet(currentNode)) return true;
|
|
40031
|
+
currentNode = currentNode.parent;
|
|
40032
|
+
}
|
|
40033
|
+
return false;
|
|
40034
|
+
};
|
|
40035
|
+
const shouldSuppressCompilerFindingInWorklet = (diagnostic, project, rootDirectory) => {
|
|
40036
|
+
if (!SUPPRESSED_COMPILER_CODES.has(diagnostic.code)) return false;
|
|
40037
|
+
if (!project.hasReanimated) return false;
|
|
40038
|
+
const primaryLabel = diagnostic.labels[0];
|
|
40039
|
+
if (!primaryLabel) return false;
|
|
40040
|
+
const absolutePath = Path.isAbsolute(diagnostic.filename) ? diagnostic.filename : Path.join(rootDirectory, diagnostic.filename);
|
|
40041
|
+
let sourceText;
|
|
40042
|
+
try {
|
|
40043
|
+
sourceText = NFS.readFileSync(absolutePath, "utf-8");
|
|
40044
|
+
} catch {
|
|
40045
|
+
return false;
|
|
40046
|
+
}
|
|
40047
|
+
return isOffsetInsideWorklet(ts.createSourceFile(absolutePath, sourceText, ts.ScriptTarget.Latest, true, getScriptKind$1(absolutePath)), getUtf16Offset(sourceText, primaryLabel.span.offset));
|
|
40048
|
+
};
|
|
40049
|
+
const MANUAL_MEMOIZATION_PLUGIN = "react-doctor";
|
|
40050
|
+
const MANUAL_MEMOIZATION_RULE = "react-compiler-no-manual-memoization";
|
|
40051
|
+
const REACT_COMPILER_PLUGIN = "react-hooks-js";
|
|
40052
|
+
const getScriptKind = (filename) => {
|
|
40053
|
+
if (filename.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
40054
|
+
if (filename.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
40055
|
+
if (filename.endsWith(".ts")) return ts.ScriptKind.TS;
|
|
40056
|
+
return ts.ScriptKind.JS;
|
|
40057
|
+
};
|
|
40058
|
+
const isManualMemoizationDiagnostic = (diagnostic) => diagnostic.plugin === MANUAL_MEMOIZATION_PLUGIN && diagnostic.rule === MANUAL_MEMOIZATION_RULE;
|
|
40059
|
+
const nodeLineRange = (sourceFile, node) => ({
|
|
40060
|
+
startLine: sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1,
|
|
40061
|
+
endLine: sourceFile.getLineAndCharacterOfPosition(node.getEnd()).line + 1
|
|
40062
|
+
});
|
|
40063
|
+
const findOutermostEnclosingFunctionRange = (sourceFile, targetLine) => {
|
|
40064
|
+
let foundRange = null;
|
|
40065
|
+
const visit = (node) => {
|
|
40066
|
+
if (foundRange) return;
|
|
40067
|
+
if (ts.isFunctionLike(node)) {
|
|
40068
|
+
const range = nodeLineRange(sourceFile, node);
|
|
40069
|
+
if (range.startLine <= targetLine && targetLine <= range.endLine) foundRange = range;
|
|
40070
|
+
return;
|
|
40071
|
+
}
|
|
40072
|
+
ts.forEachChild(node, visit);
|
|
40073
|
+
};
|
|
40074
|
+
visit(sourceFile);
|
|
40075
|
+
return foundRange;
|
|
40076
|
+
};
|
|
40077
|
+
const NEWLINE_BYTE = 10;
|
|
40078
|
+
const lineOfUtf8Offset = (sourceBuffer, utf8Offset) => {
|
|
40079
|
+
let lineNumber = 1;
|
|
40080
|
+
const scanEnd = Math.min(utf8Offset, sourceBuffer.length);
|
|
40081
|
+
for (let byteIndex = 0; byteIndex < scanEnd; byteIndex++) if (sourceBuffer[byteIndex] === NEWLINE_BYTE) lineNumber++;
|
|
40082
|
+
return lineNumber;
|
|
40083
|
+
};
|
|
40084
|
+
/**
|
|
40085
|
+
* Drops `react-doctor/react-compiler-no-manual-memoization` diagnostics
|
|
40086
|
+
* whose enclosing function also carries a `react-hooks-js` (React
|
|
40087
|
+
* Compiler) diagnostic. Every react-hooks-js finding marks a function the
|
|
40088
|
+
* compiler could NOT compile (a bail-out / skip / preserve-manual-
|
|
40089
|
+
* memoization failure), so "React Compiler already caches every value
|
|
40090
|
+
* here" is false for that function and the manual memoization is
|
|
40091
|
+
* load-bearing, not dead weight.
|
|
40092
|
+
*
|
|
40093
|
+
* Matching is per enclosing function, not per file: a bailed-out
|
|
40094
|
+
* component must not silence redundant-memo findings in a sibling
|
|
40095
|
+
* component the compiler did optimize.
|
|
40096
|
+
*
|
|
40097
|
+
* Module-scope `memo(() => …)` wrappers have no enclosing function; for
|
|
40098
|
+
* those the memo call's own span is the containment range, so a bail-out
|
|
40099
|
+
* inside the inline-wrapped component still suppresses. Every failure
|
|
40100
|
+
* path (unreadable file, missing span) fails open and keeps the
|
|
40101
|
+
* diagnostic.
|
|
40102
|
+
*/
|
|
40103
|
+
const suppressMemoizationInBailedOutFunctions = (diagnostics, rootDirectory) => {
|
|
40104
|
+
const compilerDiagnosticLinesByFile = /* @__PURE__ */ new Map();
|
|
40105
|
+
let hasManualMemoizationDiagnostic = false;
|
|
40106
|
+
for (const diagnostic of diagnostics) {
|
|
40107
|
+
if (isManualMemoizationDiagnostic(diagnostic)) {
|
|
40108
|
+
hasManualMemoizationDiagnostic = true;
|
|
40109
|
+
continue;
|
|
40110
|
+
}
|
|
40111
|
+
if (diagnostic.plugin !== REACT_COMPILER_PLUGIN || diagnostic.line <= 0) continue;
|
|
40112
|
+
const lines = compilerDiagnosticLinesByFile.get(diagnostic.filePath);
|
|
40113
|
+
if (lines) lines.push(diagnostic.line);
|
|
40114
|
+
else compilerDiagnosticLinesByFile.set(diagnostic.filePath, [diagnostic.line]);
|
|
40115
|
+
}
|
|
40116
|
+
if (!hasManualMemoizationDiagnostic || compilerDiagnosticLinesByFile.size === 0) return diagnostics;
|
|
40117
|
+
const parsedFileCache = /* @__PURE__ */ new Map();
|
|
40118
|
+
const readAndParse = (filePath) => {
|
|
40119
|
+
const cached = parsedFileCache.get(filePath);
|
|
40120
|
+
if (cached !== void 0) return cached;
|
|
40121
|
+
const absolutePath = Path.isAbsolute(filePath) ? filePath : Path.resolve(rootDirectory || ".", filePath);
|
|
40122
|
+
let parsed;
|
|
40123
|
+
try {
|
|
40124
|
+
const buffer = NFS.readFileSync(absolutePath);
|
|
40125
|
+
parsed = {
|
|
40126
|
+
sourceFile: ts.createSourceFile(absolutePath, buffer.toString("utf8"), ts.ScriptTarget.Latest, true, getScriptKind(absolutePath)),
|
|
40127
|
+
buffer
|
|
40128
|
+
};
|
|
40129
|
+
} catch {
|
|
40130
|
+
parsed = null;
|
|
40131
|
+
}
|
|
40132
|
+
parsedFileCache.set(filePath, parsed);
|
|
40133
|
+
return parsed;
|
|
40134
|
+
};
|
|
40135
|
+
const isInsideBailedOutFunction = (diagnostic) => {
|
|
40136
|
+
const compilerLines = compilerDiagnosticLinesByFile.get(diagnostic.filePath);
|
|
40137
|
+
if (!compilerLines || diagnostic.line <= 0) return false;
|
|
40138
|
+
const parsed = readAndParse(diagnostic.filePath);
|
|
40139
|
+
if (!parsed) return false;
|
|
40140
|
+
let containmentRange = findOutermostEnclosingFunctionRange(parsed.sourceFile, diagnostic.line);
|
|
40141
|
+
if (!containmentRange && diagnostic.offset !== void 0 && diagnostic.length !== void 0) containmentRange = {
|
|
40142
|
+
startLine: diagnostic.line,
|
|
40143
|
+
endLine: lineOfUtf8Offset(parsed.buffer, diagnostic.offset + diagnostic.length)
|
|
40144
|
+
};
|
|
40145
|
+
if (!containmentRange) return false;
|
|
40146
|
+
const { startLine, endLine } = containmentRange;
|
|
40147
|
+
return compilerLines.some((compilerLine) => startLine <= compilerLine && compilerLine <= endLine);
|
|
40148
|
+
};
|
|
40149
|
+
return diagnostics.filter((diagnostic) => !isManualMemoizationDiagnostic(diagnostic) || !isInsideBailedOutFunction(diagnostic));
|
|
40150
|
+
};
|
|
39776
40151
|
const FILEPATH_WITH_LOCATION_PATTERN = /\S+\.\w+:\d+:\d+[\s\S]*$/;
|
|
39777
40152
|
const LEADING_SEVERITY_LABEL_PATTERN = /^(?:Error|Warning):\s*/;
|
|
39778
40153
|
const TRAILING_PERIOD_PATTERN = /\.$/;
|
|
@@ -39808,8 +40183,14 @@ const PLUGIN_CATEGORY_MAP = {
|
|
|
39808
40183
|
};
|
|
39809
40184
|
const lookupOwnString = (record, key) => Object.hasOwn(record, key) ? record[key] : void 0;
|
|
39810
40185
|
const getRuleRecommendation = (ruleName, project) => {
|
|
39811
|
-
|
|
39812
|
-
return
|
|
40186
|
+
const rule = reactDoctorPlugin.rules[ruleName];
|
|
40187
|
+
if (!rule) return void 0;
|
|
40188
|
+
if (rule.recommendationFor) {
|
|
40189
|
+
const capabilities = getCapabilities(project);
|
|
40190
|
+
const conditionalRecommendation = rule.recommendationFor((capability) => capabilities.has(capability));
|
|
40191
|
+
if (conditionalRecommendation !== void 0) return conditionalRecommendation;
|
|
40192
|
+
}
|
|
40193
|
+
return rule.recommendation;
|
|
39813
40194
|
};
|
|
39814
40195
|
const getRuleCategory = (ruleName) => reactDoctorPlugin.rules[ruleName]?.category;
|
|
39815
40196
|
const getRuleTitle = (ruleName) => reactDoctorPlugin.rules[ruleName]?.title;
|
|
@@ -39912,7 +40293,7 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
39912
40293
|
minifiedFileCache.set(absolutePath, minified);
|
|
39913
40294
|
return minified;
|
|
39914
40295
|
};
|
|
39915
|
-
return parsed.diagnostics.filter((diagnostic) => diagnostic.code && isLintableSourceFile(diagnostic.filename) && !isMinifiedDiagnosticFile(diagnostic.filename) && !shouldSuppressLocalUseHookDiagnostic(diagnostic, rootDirectory)).map((diagnostic) => {
|
|
40296
|
+
return suppressMemoizationInBailedOutFunctions(parsed.diagnostics.filter((diagnostic) => diagnostic.code && isLintableSourceFile(diagnostic.filename) && !isMinifiedDiagnosticFile(diagnostic.filename) && !shouldSuppressLocalUseHookDiagnostic(diagnostic, rootDirectory) && !shouldSuppressCompilerFindingInWorklet(diagnostic, project, rootDirectory)).map((diagnostic) => {
|
|
39916
40297
|
const { plugin, rule } = parseRuleCode(diagnostic.code);
|
|
39917
40298
|
const primaryLabel = diagnostic.labels[0];
|
|
39918
40299
|
const cleaned = cleanDiagnosticMessage(diagnostic.message, diagnostic.help, plugin, rule, project);
|
|
@@ -39939,7 +40320,7 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
39939
40320
|
...resolveMatchByOccurrence(rule, category) ? { matchByOccurrence: true } : {},
|
|
39940
40321
|
...relatedLocations.length > 0 ? { relatedLocations } : {}
|
|
39941
40322
|
};
|
|
39942
|
-
});
|
|
40323
|
+
}), rootDirectory);
|
|
39943
40324
|
};
|
|
39944
40325
|
const buildOxlintChildEnv = (sourceEnv) => {
|
|
39945
40326
|
const childEnv = {};
|
|
@@ -40450,6 +40831,7 @@ const runOxlint = async (options) => {
|
|
|
40450
40831
|
const serverAuthFunctionNames = Array.isArray(userConfig?.serverAuthFunctionNames) ? userConfig.serverAuthFunctionNames.filter((entry) => typeof entry === "string" && entry.length > 0) : void 0;
|
|
40451
40832
|
const severityControls = buildRuleSeverityControls(userConfig);
|
|
40452
40833
|
validateRuleRegistration();
|
|
40834
|
+
resetManifestCaches();
|
|
40453
40835
|
if (includePaths !== void 0 && includePaths.length === 0) return [];
|
|
40454
40836
|
const pluginPath = resolvePluginPath();
|
|
40455
40837
|
const extendsPaths = (adoptExistingLintConfig && !customRulesOnly ? detectUserLintConfigPaths(rootDirectory) : []).filter(canOxlintExtendConfig);
|
|
@@ -41112,9 +41494,10 @@ const CONTEXT_AXES = [
|
|
|
41112
41494
|
}
|
|
41113
41495
|
];
|
|
41114
41496
|
const SCORE_AXES = [...GATED_AXES, ...CONTEXT_AXES];
|
|
41115
|
-
const
|
|
41116
|
-
|
|
41117
|
-
|
|
41497
|
+
const gatedAxesForSection = (section) => section === "devDependencies" ? GATED_AXES.filter((axis) => axis.key !== "vulnerability") : GATED_AXES;
|
|
41498
|
+
const worstGatedAxis = (score, gatedAxes) => {
|
|
41499
|
+
let worst = gatedAxes[0];
|
|
41500
|
+
for (const axis of gatedAxes) if (score[axis.key] < score[worst.key]) worst = axis;
|
|
41118
41501
|
return worst;
|
|
41119
41502
|
};
|
|
41120
41503
|
const clampScore = (value) => {
|
|
@@ -41138,6 +41521,69 @@ const resolveConcreteVersion = (spec) => {
|
|
|
41138
41521
|
if (range === null || range === "*") return null;
|
|
41139
41522
|
return import_semver.minVersion(trimmed)?.version ?? null;
|
|
41140
41523
|
};
|
|
41524
|
+
const readTextFileOrNull = (filePath) => {
|
|
41525
|
+
try {
|
|
41526
|
+
return NFS.readFileSync(filePath, "utf-8");
|
|
41527
|
+
} catch {
|
|
41528
|
+
return null;
|
|
41529
|
+
}
|
|
41530
|
+
};
|
|
41531
|
+
const buildNpmLockLookup = (lockText) => {
|
|
41532
|
+
let parsed;
|
|
41533
|
+
try {
|
|
41534
|
+
parsed = JSON.parse(lockText);
|
|
41535
|
+
} catch {
|
|
41536
|
+
return null;
|
|
41537
|
+
}
|
|
41538
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
41539
|
+
const lock = parsed;
|
|
41540
|
+
return (name) => {
|
|
41541
|
+
const entry = lock.packages?.[`node_modules/${name}`] ?? lock.dependencies?.[name];
|
|
41542
|
+
return typeof entry?.version === "string" ? entry.version : null;
|
|
41543
|
+
};
|
|
41544
|
+
};
|
|
41545
|
+
const buildYarnLockLookup = (lockText) => {
|
|
41546
|
+
const lines = lockText.split(/\r?\n/);
|
|
41547
|
+
return (name, spec) => {
|
|
41548
|
+
const descriptor = `${name}@${spec}`;
|
|
41549
|
+
const berryDescriptor = `${name}@npm:${spec}`;
|
|
41550
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
41551
|
+
const line = lines[lineIndex];
|
|
41552
|
+
if (line.startsWith(" ") || line.startsWith("#") || !line.includes(":")) continue;
|
|
41553
|
+
const descriptors = line.replace(/:\s*$/, "").split(",").map((part) => part.trim().replace(/^"|"$/g, ""));
|
|
41554
|
+
if (!descriptors.includes(descriptor) && !descriptors.includes(berryDescriptor)) continue;
|
|
41555
|
+
for (let bodyIndex = lineIndex + 1; bodyIndex < lines.length && (lines[bodyIndex].startsWith(" ") || lines[bodyIndex] === ""); bodyIndex += 1) {
|
|
41556
|
+
const versionMatch = /^\s+version:?\s+"?([^"\s]+)"?/.exec(lines[bodyIndex]);
|
|
41557
|
+
if (versionMatch) return versionMatch[1];
|
|
41558
|
+
}
|
|
41559
|
+
return null;
|
|
41560
|
+
}
|
|
41561
|
+
return null;
|
|
41562
|
+
};
|
|
41563
|
+
};
|
|
41564
|
+
const buildPnpmLockLookup = (lockText) => {
|
|
41565
|
+
return (name) => {
|
|
41566
|
+
const entryPattern = new RegExp(`^ {4,8}['"]?${name.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&")}['"]?:\\s*$`);
|
|
41567
|
+
const lines = lockText.split(/\r?\n/);
|
|
41568
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
41569
|
+
if (!entryPattern.test(lines[lineIndex])) continue;
|
|
41570
|
+
for (let bodyIndex = lineIndex + 1; bodyIndex < lines.length && /^ {5,}\S/.test(lines[bodyIndex]); bodyIndex += 1) {
|
|
41571
|
+
const versionMatch = /^\s+version:\s+['"]?([^'"\s(]+)/.exec(lines[bodyIndex]);
|
|
41572
|
+
if (versionMatch) return versionMatch[1];
|
|
41573
|
+
}
|
|
41574
|
+
}
|
|
41575
|
+
return null;
|
|
41576
|
+
};
|
|
41577
|
+
};
|
|
41578
|
+
const buildLockedVersionLookup = (rootDirectory) => {
|
|
41579
|
+
const npmLockText = readTextFileOrNull(Path.join(rootDirectory, "package-lock.json"));
|
|
41580
|
+
if (npmLockText !== null) return buildNpmLockLookup(npmLockText);
|
|
41581
|
+
const yarnLockText = readTextFileOrNull(Path.join(rootDirectory, "yarn.lock"));
|
|
41582
|
+
if (yarnLockText !== null) return buildYarnLockLookup(yarnLockText);
|
|
41583
|
+
const pnpmLockText = readTextFileOrNull(Path.join(rootDirectory, "pnpm-lock.yaml"));
|
|
41584
|
+
if (pnpmLockText !== null) return buildPnpmLockLookup(pnpmLockText);
|
|
41585
|
+
return null;
|
|
41586
|
+
};
|
|
41141
41587
|
const locateDependencyKey = (packageJsonText, section, name) => {
|
|
41142
41588
|
const needle = `"${name}"`;
|
|
41143
41589
|
const sectionHeader = new RegExp(`"${section}"\\s*:\\s*\\{`);
|
|
@@ -41164,7 +41610,7 @@ const locateDependencyKey = (packageJsonText, section, name) => {
|
|
|
41164
41610
|
}
|
|
41165
41611
|
return null;
|
|
41166
41612
|
};
|
|
41167
|
-
const collectDependenciesToScore = (packageJson, packageJsonText, includeDevDependencies) => {
|
|
41613
|
+
const collectDependenciesToScore = (packageJson, packageJsonText, includeDevDependencies, lockedVersionLookup) => {
|
|
41168
41614
|
const sectionByName = /* @__PURE__ */ new Map();
|
|
41169
41615
|
for (const name of Object.keys(packageJson.dependencies ?? {})) sectionByName.set(name, "dependencies");
|
|
41170
41616
|
if (includeDevDependencies) {
|
|
@@ -41174,13 +41620,18 @@ const collectDependenciesToScore = (packageJson, packageJsonText, includeDevDepe
|
|
|
41174
41620
|
for (const [name, section] of sectionByName) {
|
|
41175
41621
|
if (SUPPLY_CHAIN_IGNORED_PACKAGES.has(name)) continue;
|
|
41176
41622
|
const spec = (packageJson[section] ?? {})[name] ?? "";
|
|
41177
|
-
const
|
|
41178
|
-
if (
|
|
41623
|
+
const floorVersion = resolveConcreteVersion(spec);
|
|
41624
|
+
if (floorVersion === null) continue;
|
|
41625
|
+
const lockedVersion = lockedVersionLookup?.(name, spec) ?? null;
|
|
41626
|
+
const lockSatisfiesSpec = lockedVersion !== null && import_semver.valid(lockedVersion) !== null && import_semver.satisfies(lockedVersion, spec);
|
|
41627
|
+
const isExactPin = import_semver.valid(spec.trim()) !== null;
|
|
41179
41628
|
const location = locateDependencyKey(packageJsonText, section, name);
|
|
41180
41629
|
dependencies.push({
|
|
41181
41630
|
name,
|
|
41182
|
-
version,
|
|
41631
|
+
version: lockSatisfiesSpec ? lockedVersion : floorVersion,
|
|
41183
41632
|
spec,
|
|
41633
|
+
section,
|
|
41634
|
+
resolution: isExactPin ? "pin" : lockSatisfiesSpec ? "locked" : "floor",
|
|
41184
41635
|
line: location?.line ?? 0,
|
|
41185
41636
|
column: location?.column ?? 0
|
|
41186
41637
|
});
|
|
@@ -41331,7 +41782,11 @@ const formatAlertReason = (topAlerts, totalCount) => {
|
|
|
41331
41782
|
}
|
|
41332
41783
|
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)}.`;
|
|
41333
41784
|
};
|
|
41334
|
-
const formatDependencyIdentity = (dependency) =>
|
|
41785
|
+
const formatDependencyIdentity = (dependency) => {
|
|
41786
|
+
if (dependency.resolution === "pin") return `${dependency.name}@${dependency.version}`;
|
|
41787
|
+
if (dependency.resolution === "locked") return `${dependency.name}@${dependency.version} (lockfile resolution of "${dependency.spec}")`;
|
|
41788
|
+
return `${dependency.name}@${dependency.version} (lowest version "${dependency.spec}" allows)`;
|
|
41789
|
+
};
|
|
41335
41790
|
const buildSupplyChainHelp = (dependency, failingAxis, topAlerts, packagePageUrl, options) => {
|
|
41336
41791
|
const hasCriticalAlert = topAlerts.some((alert) => alert.severity.toLowerCase() === "critical");
|
|
41337
41792
|
const entry = `\`"${dependency.name}": "${dependency.spec}"\``;
|
|
@@ -41374,7 +41829,7 @@ const buildLowScoreDiagnostic = (dependency, artifact, failingAxis, options) =>
|
|
|
41374
41829
|
const checkSupplyChain = (input) => gen(function* () {
|
|
41375
41830
|
const options = resolveOptions(input.userConfig);
|
|
41376
41831
|
const packageJsonPath = Path.join(input.rootDirectory, "package.json");
|
|
41377
|
-
const dependencies = collectDependenciesToScore(readPackageJson(packageJsonPath), readPackageJsonText(packageJsonPath), options.includeDevDependencies);
|
|
41832
|
+
const dependencies = collectDependenciesToScore(readPackageJson(packageJsonPath), readPackageJsonText(packageJsonPath), options.includeDevDependencies, buildLockedVersionLookup(input.rootDirectory));
|
|
41378
41833
|
if (dependencies.length === 0) return [];
|
|
41379
41834
|
const cacheDirectory = isSupplyChainCacheDisabled() ? null : resolveReactDoctorCacheDir(input.rootDirectory);
|
|
41380
41835
|
if (cacheDirectory !== null) pruneExpiredSocketCache(cacheDirectory);
|
|
@@ -41383,7 +41838,7 @@ const checkSupplyChain = (input) => gen(function* () {
|
|
|
41383
41838
|
for (let index = 0; index < dependencies.length; index += 1) {
|
|
41384
41839
|
const artifact = artifacts[index];
|
|
41385
41840
|
if (!artifact) continue;
|
|
41386
|
-
const worstAxis = worstGatedAxis(artifact.score);
|
|
41841
|
+
const worstAxis = worstGatedAxis(artifact.score, gatedAxesForSection(dependencies[index].section));
|
|
41387
41842
|
if (toHundred(artifact.score[worstAxis.key]) >= options.minScore) continue;
|
|
41388
41843
|
diagnostics.push(buildLowScoreDiagnostic(dependencies[index], artifact, worstAxis, options));
|
|
41389
41844
|
}
|
|
@@ -42109,7 +42564,7 @@ const computeConfigFingerprint = (projectDirectory, version) => {
|
|
|
42109
42564
|
/** Display name used in client-facing messages and progress titles. */
|
|
42110
42565
|
const SERVER_DISPLAY_NAME = "React Doctor";
|
|
42111
42566
|
/** Server version reported in `serverInfo`; injected at build, `dev` from source. */
|
|
42112
|
-
const SERVER_VERSION = "0.7.
|
|
42567
|
+
const SERVER_VERSION = "0.7.2";
|
|
42113
42568
|
/** `Diagnostic.source` shown next to every published diagnostic. */
|
|
42114
42569
|
const DIAGNOSTIC_SOURCE = "react-doctor";
|
|
42115
42570
|
/**
|
|
@@ -44160,5 +44615,5 @@ const startLanguageServer = () => {
|
|
|
44160
44615
|
};
|
|
44161
44616
|
//#endregion
|
|
44162
44617
|
export { startLanguageServer };
|
|
44163
|
-
!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]="
|
|
44164
|
-
//# debugId=
|
|
44618
|
+
!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]="94aee89d-9f35-5b59-a911-566ac2ef5a0a")}catch(e){}}();
|
|
44619
|
+
//# debugId=94aee89d-9f35-5b59-a911-566ac2ef5a0a
|