react-doctor 0.7.1-dev.d353dad → 0.7.2-dev.08b768b
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 +1171 -742
- package/dist/index.d.ts +21 -2
- package/dist/index.js +1147 -698
- package/dist/lsp.js +1158 -738
- 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",
|
|
@@ -32956,6 +32989,8 @@ const OXLINT_SPAWN_TIMEOUT_MS = 6e4;
|
|
|
32956
32989
|
const NODE_COMPILE_CACHE_DIR_NAME = "node-compile-cache";
|
|
32957
32990
|
const DEAD_CODE_WORKER_TIMEOUT_MS = 12e4;
|
|
32958
32991
|
const OXLINT_SPLIT_TOTAL_BUDGET_MS = 18e4;
|
|
32992
|
+
const ABORT_EXIT_CODES = new Set([134, 3221226505]);
|
|
32993
|
+
const OXLINT_OOM_RESCUE_BUDGET_MS = 6e4;
|
|
32959
32994
|
const DEAD_CODE_PHASE_TIMEOUT_MS = 15e4;
|
|
32960
32995
|
const LINT_PHASE_TIMEOUT_MS = 3e5;
|
|
32961
32996
|
const SCAN_TOTAL_DEADLINE_MS = 9e5;
|
|
@@ -33002,7 +33037,7 @@ const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
|
|
|
33002
33037
|
const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
|
|
33003
33038
|
const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
|
|
33004
33039
|
const SIDECAR_LINT_CACHE_FILENAME = "sidecar-lint-cache.json";
|
|
33005
|
-
const CORE_PACKAGE_VERSION = "0.7.
|
|
33040
|
+
const CORE_PACKAGE_VERSION = "0.7.2";
|
|
33006
33041
|
const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
|
|
33007
33042
|
const DEAD_CODE_SUMMARY_CACHE_FILENAME = "dead-code-summaries.json";
|
|
33008
33043
|
const SUPPLY_CHAIN_PLUGIN = "socket";
|
|
@@ -33012,8 +33047,9 @@ const SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS = 9e4;
|
|
|
33012
33047
|
const SUPPLY_CHAIN_CACHE_TTL_MS = 864e5;
|
|
33013
33048
|
const SUPPLY_CHAIN_CACHE_SUBDIR = "supply-chain";
|
|
33014
33049
|
const SUPPLY_CHAIN_IGNORED_PACKAGES = new Set(["next"]);
|
|
33015
|
-
const
|
|
33016
|
-
const
|
|
33050
|
+
const isIgnoredDirectoryName = (directoryName) => IGNORED_DIRECTORIES.has(directoryName) || directoryName.startsWith(".") && !SCANNED_DOT_DIRECTORIES.has(directoryName);
|
|
33051
|
+
const hasIgnoredPathSegment = (relativePath) => relativePath.split(/[/\\]/).slice(0, -1).some((segment) => isIgnoredDirectoryName(segment));
|
|
33052
|
+
const isLintableSourceFile = (filePath) => SOURCE_FILE_PATTERN.test(filePath) && !GENERATED_BUNDLE_FILE_PATTERN.test(filePath) && !GENERATED_SOURCE_DIRECTORY_PATTERN.test(filePath);
|
|
33017
33053
|
const isMinifiedSource = (absolutePath) => {
|
|
33018
33054
|
let fileDescriptor;
|
|
33019
33055
|
try {
|
|
@@ -33050,25 +33086,6 @@ const isLargeMinifiedFile = (absolutePath, knownSizeBytes) => {
|
|
|
33050
33086
|
cachedIsLargeMinifiedByPath.set(absolutePath, result);
|
|
33051
33087
|
return result;
|
|
33052
33088
|
};
|
|
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
33089
|
function* walkSourceTreeFiles(rootDirectory) {
|
|
33073
33090
|
const stack = [rootDirectory];
|
|
33074
33091
|
while (stack.length > 0) {
|
|
@@ -33077,7 +33094,7 @@ function* walkSourceTreeFiles(rootDirectory) {
|
|
|
33077
33094
|
for (const entry of readDirectoryEntries(currentDirectory)) {
|
|
33078
33095
|
const absolutePath = Path.join(currentDirectory, entry.name);
|
|
33079
33096
|
if (entry.isDirectory()) {
|
|
33080
|
-
if (!
|
|
33097
|
+
if (!isIgnoredDirectoryName(entry.name)) stack.push(absolutePath);
|
|
33081
33098
|
continue;
|
|
33082
33099
|
}
|
|
33083
33100
|
if (entry.isFile()) yield {
|
|
@@ -33123,23 +33140,6 @@ const listSourceFilesViaFilesystem = (rootDirectory) => {
|
|
|
33123
33140
|
const listSourceFilesWithSize = (rootDirectory) => collectSizedSourceFiles(rootDirectory, (listSourceFilesViaGit(rootDirectory) ?? listSourceFilesViaFilesystem(rootDirectory)).sort());
|
|
33124
33141
|
const listSourceFiles = (rootDirectory) => listSourceFilesWithSize(rootDirectory).map((entry) => entry.path);
|
|
33125
33142
|
const countSourceFiles = (rootDirectory) => listSourceFilesWithSize(rootDirectory).length;
|
|
33126
|
-
/**
|
|
33127
|
-
* The chain of ancestor directories of `startDirectory`, nearest first, up to
|
|
33128
|
-
* and including the filesystem root. With `includeStart`, the chain begins at
|
|
33129
|
-
* `startDirectory` itself; otherwise at its parent. The one place the "walk up
|
|
33130
|
-
* the tree" traversal is expressed, so each caller adds only its own match /
|
|
33131
|
-
* stop policy instead of re-deriving the `path.dirname` loop.
|
|
33132
|
-
*/
|
|
33133
|
-
const ancestorDirectories = (startDirectory, { includeStart }) => {
|
|
33134
|
-
const directories = [];
|
|
33135
|
-
let currentDirectory = includeStart ? startDirectory : Path.dirname(startDirectory);
|
|
33136
|
-
while (true) {
|
|
33137
|
-
directories.push(currentDirectory);
|
|
33138
|
-
const parentDirectory = Path.dirname(currentDirectory);
|
|
33139
|
-
if (parentDirectory === currentDirectory) return directories;
|
|
33140
|
-
currentDirectory = parentDirectory;
|
|
33141
|
-
}
|
|
33142
|
-
};
|
|
33143
33143
|
const cachedPackageJsons = /* @__PURE__ */ new Map();
|
|
33144
33144
|
const clearPackageJsonCache = () => {
|
|
33145
33145
|
cachedPackageJsons.clear();
|
|
@@ -33173,7 +33173,11 @@ const isMonorepoRoot = (directory) => {
|
|
|
33173
33173
|
return Array.isArray(packageJson.workspaces) || Boolean(packageJson.workspaces?.packages);
|
|
33174
33174
|
};
|
|
33175
33175
|
const findMonorepoRoot = (startDirectory) => {
|
|
33176
|
-
|
|
33176
|
+
let currentDirectory = Path.dirname(startDirectory);
|
|
33177
|
+
while (currentDirectory !== Path.dirname(currentDirectory)) {
|
|
33178
|
+
if (isMonorepoRoot(currentDirectory)) return currentDirectory;
|
|
33179
|
+
currentDirectory = Path.dirname(currentDirectory);
|
|
33180
|
+
}
|
|
33177
33181
|
return null;
|
|
33178
33182
|
};
|
|
33179
33183
|
/**
|
|
@@ -33187,6 +33191,146 @@ const findMonorepoRoot = (startDirectory) => {
|
|
|
33187
33191
|
* byte-equivalent copy.
|
|
33188
33192
|
*/
|
|
33189
33193
|
const isProjectBoundary = (directory) => NFS.existsSync(Path.join(directory, ".git")) || isMonorepoRoot(directory);
|
|
33194
|
+
const TSCONFIG_FILENAME = "tsconfig.json";
|
|
33195
|
+
const isRelativeExtendsValue = (extendsValue) => extendsValue.startsWith("./") || extendsValue.startsWith("../") || Path.isAbsolute(extendsValue);
|
|
33196
|
+
const ensureJsonExtension = (filePath) => Path.extname(filePath) === "" ? `${filePath}.json` : filePath;
|
|
33197
|
+
const resolvePackageExtendsPath = (extendsValue, fromConfigDirectory) => {
|
|
33198
|
+
const requireFromConfig = createRequire(Path.join(fromConfigDirectory, "tsconfig.json"));
|
|
33199
|
+
const candidates = [
|
|
33200
|
+
extendsValue,
|
|
33201
|
+
ensureJsonExtension(extendsValue),
|
|
33202
|
+
`${extendsValue.replace(/\/$/, "")}/tsconfig.json`
|
|
33203
|
+
];
|
|
33204
|
+
for (const candidate of candidates) try {
|
|
33205
|
+
return requireFromConfig.resolve(candidate);
|
|
33206
|
+
} catch {
|
|
33207
|
+
continue;
|
|
33208
|
+
}
|
|
33209
|
+
return null;
|
|
33210
|
+
};
|
|
33211
|
+
const resolveExtendsPath = (extendsValue, fromConfigDirectory) => {
|
|
33212
|
+
if (isRelativeExtendsValue(extendsValue)) return ensureJsonExtension(Path.resolve(fromConfigDirectory, extendsValue));
|
|
33213
|
+
return resolvePackageExtendsPath(extendsValue, fromConfigDirectory);
|
|
33214
|
+
};
|
|
33215
|
+
const normalizeCompilerOptions = (compilerOptions) => {
|
|
33216
|
+
if (!isPlainObject(compilerOptions)) return { hasExplicitLib: false };
|
|
33217
|
+
const target = typeof compilerOptions.target === "string" ? compilerOptions.target : void 0;
|
|
33218
|
+
const hasExplicitLib = Object.hasOwn(compilerOptions, "lib");
|
|
33219
|
+
return {
|
|
33220
|
+
target,
|
|
33221
|
+
lib: Array.isArray(compilerOptions.lib) ? compilerOptions.lib.filter((entry) => typeof entry === "string") : void 0,
|
|
33222
|
+
hasExplicitLib
|
|
33223
|
+
};
|
|
33224
|
+
};
|
|
33225
|
+
const readTsConfig = (filePath) => {
|
|
33226
|
+
let content;
|
|
33227
|
+
try {
|
|
33228
|
+
content = NFS.readFileSync(filePath, "utf-8");
|
|
33229
|
+
} catch {
|
|
33230
|
+
return null;
|
|
33231
|
+
}
|
|
33232
|
+
const parsed = ts.parseConfigFileTextToJson(filePath, content);
|
|
33233
|
+
if (!isPlainObject(parsed.config)) return null;
|
|
33234
|
+
return {
|
|
33235
|
+
extends: typeof parsed.config.extends === "string" ? parsed.config.extends : void 0,
|
|
33236
|
+
referencePaths: normalizeReferencePaths(parsed.config.references),
|
|
33237
|
+
compilerOptions: normalizeCompilerOptions(parsed.config.compilerOptions)
|
|
33238
|
+
};
|
|
33239
|
+
};
|
|
33240
|
+
const normalizeReferencePaths = (references) => {
|
|
33241
|
+
if (!Array.isArray(references)) return [];
|
|
33242
|
+
return references.map((reference) => isPlainObject(reference) && typeof reference.path === "string" ? reference.path : null).filter((referencePath) => referencePath !== null);
|
|
33243
|
+
};
|
|
33244
|
+
const mergeCompilerOptions = (inherited, current) => {
|
|
33245
|
+
const target = current.target ?? inherited?.target;
|
|
33246
|
+
const hasExplicitLib = current.hasExplicitLib || Boolean(inherited?.hasExplicitLib);
|
|
33247
|
+
return {
|
|
33248
|
+
target,
|
|
33249
|
+
lib: current.hasExplicitLib ? current.lib : inherited?.lib,
|
|
33250
|
+
hasExplicitLib
|
|
33251
|
+
};
|
|
33252
|
+
};
|
|
33253
|
+
const readResolvedCompilerOptions = (tsConfigPath, extendsDepth, visitedPaths) => {
|
|
33254
|
+
const realPath = NFS.realpathSync.native(tsConfigPath);
|
|
33255
|
+
if (visitedPaths.has(realPath)) return null;
|
|
33256
|
+
const tsConfig = readTsConfig(realPath);
|
|
33257
|
+
if (!tsConfig) return null;
|
|
33258
|
+
const nextVisitedPaths = new Set(visitedPaths);
|
|
33259
|
+
nextVisitedPaths.add(realPath);
|
|
33260
|
+
if (tsConfig.extends && extendsDepth < 8) {
|
|
33261
|
+
const parentPath = resolveExtendsPath(tsConfig.extends, Path.dirname(realPath));
|
|
33262
|
+
if (parentPath && isFile(parentPath)) return mergeCompilerOptions(readResolvedCompilerOptions(parentPath, extendsDepth + 1, nextVisitedPaths), tsConfig.compilerOptions);
|
|
33263
|
+
}
|
|
33264
|
+
return tsConfig.compilerOptions;
|
|
33265
|
+
};
|
|
33266
|
+
const targetYearIsPreES2023 = (target) => {
|
|
33267
|
+
const year = ES_TARGET_YEAR_BY_NAME[target.toLowerCase()];
|
|
33268
|
+
return year !== void 0 && year < 2023;
|
|
33269
|
+
};
|
|
33270
|
+
const libEntryIncludesES2023Array = (entry) => {
|
|
33271
|
+
const normalizedEntry = entry.toLowerCase();
|
|
33272
|
+
if (normalizedEntry === "esnext" || normalizedEntry === "esnext.array") return true;
|
|
33273
|
+
const esYearMatch = /^es(\d{4})(?:\.(.+))?$/.exec(normalizedEntry);
|
|
33274
|
+
if (!esYearMatch) return false;
|
|
33275
|
+
if (Number(esYearMatch[1]) < 2023) return false;
|
|
33276
|
+
const component = esYearMatch[2];
|
|
33277
|
+
return component === void 0 || component === "array";
|
|
33278
|
+
};
|
|
33279
|
+
const libIncludesES2023 = (lib) => lib.some(libEntryIncludesES2023Array);
|
|
33280
|
+
const compilerOptionsArePreES2023 = (compilerOptions) => {
|
|
33281
|
+
if (compilerOptions.target) return targetYearIsPreES2023(compilerOptions.target);
|
|
33282
|
+
if (compilerOptions.hasExplicitLib) return !libIncludesES2023(compilerOptions.lib ?? []);
|
|
33283
|
+
return false;
|
|
33284
|
+
};
|
|
33285
|
+
const compilerOptionsDeclareTargetOrLib = (compilerOptions) => compilerOptions.hasExplicitLib || compilerOptions.target !== void 0;
|
|
33286
|
+
const detectPreES2023FromConfig = (tsConfigPath, visitedConfigPaths = /* @__PURE__ */ new Set()) => {
|
|
33287
|
+
if (visitedConfigPaths.has(tsConfigPath)) return false;
|
|
33288
|
+
const compilerOptions = readResolvedCompilerOptions(tsConfigPath, 0, /* @__PURE__ */ new Set());
|
|
33289
|
+
if (!compilerOptions) return false;
|
|
33290
|
+
if (!compilerOptionsDeclareTargetOrLib(compilerOptions)) {
|
|
33291
|
+
const tsConfig = readTsConfig(tsConfigPath);
|
|
33292
|
+
if (!tsConfig) return false;
|
|
33293
|
+
const nextVisitedConfigPaths = new Set(visitedConfigPaths);
|
|
33294
|
+
nextVisitedConfigPaths.add(tsConfigPath);
|
|
33295
|
+
const configDirectory = Path.dirname(tsConfigPath);
|
|
33296
|
+
return tsConfig.referencePaths.some((referencePath) => {
|
|
33297
|
+
const resolvedReferencePath = Path.resolve(configDirectory, referencePath);
|
|
33298
|
+
const referencedConfigPath = isFile(resolvedReferencePath) ? resolvedReferencePath : Path.join(resolvedReferencePath, TSCONFIG_FILENAME);
|
|
33299
|
+
return isFile(referencedConfigPath) && detectPreES2023FromConfig(referencedConfigPath, nextVisitedConfigPaths);
|
|
33300
|
+
});
|
|
33301
|
+
}
|
|
33302
|
+
return compilerOptionsArePreES2023(compilerOptions);
|
|
33303
|
+
};
|
|
33304
|
+
const detectPreES2023Target = (directory) => {
|
|
33305
|
+
const tsConfigPath = Path.join(directory, TSCONFIG_FILENAME);
|
|
33306
|
+
if (isFile(tsConfigPath)) return detectPreES2023FromConfig(tsConfigPath);
|
|
33307
|
+
for (const fallbackFilename of FALLBACK_TSCONFIG_FILENAMES) {
|
|
33308
|
+
const fallbackPath = Path.join(directory, fallbackFilename);
|
|
33309
|
+
if (isFile(fallbackPath)) return detectPreES2023FromConfig(fallbackPath);
|
|
33310
|
+
}
|
|
33311
|
+
return false;
|
|
33312
|
+
};
|
|
33313
|
+
const FALLBACK_TSCONFIG_FILENAMES = ["tsconfig.app.json", "tsconfig.build.json"];
|
|
33314
|
+
const FRAMEWORK_PACKAGES = {
|
|
33315
|
+
next: "nextjs",
|
|
33316
|
+
"@tanstack/react-start": "tanstack-start",
|
|
33317
|
+
vite: "vite",
|
|
33318
|
+
"react-scripts": "cra",
|
|
33319
|
+
"@remix-run/react": "remix",
|
|
33320
|
+
gatsby: "gatsby",
|
|
33321
|
+
expo: "expo",
|
|
33322
|
+
"react-native": "react-native"
|
|
33323
|
+
};
|
|
33324
|
+
const detectFramework = (dependencies) => {
|
|
33325
|
+
for (const [packageName, frameworkName] of Object.entries(FRAMEWORK_PACKAGES)) if (dependencies[packageName]) return frameworkName;
|
|
33326
|
+
if (dependencies.preact && !dependencies.react) return "preact";
|
|
33327
|
+
return "unknown";
|
|
33328
|
+
};
|
|
33329
|
+
const MOBILE_FRAMEWORKS = new Set(["expo", "react-native"]);
|
|
33330
|
+
const frameworkMergeRank = (framework) => {
|
|
33331
|
+
if (framework === "unknown") return 3;
|
|
33332
|
+
return MOBILE_FRAMEWORKS.has(framework) ? 2 : 1;
|
|
33333
|
+
};
|
|
33190
33334
|
const REACT_COMPILER_PACKAGES = new Set([
|
|
33191
33335
|
"babel-plugin-react-compiler",
|
|
33192
33336
|
"react-compiler-runtime",
|
|
@@ -33223,6 +33367,7 @@ const EXPO_APP_CONFIG_FILENAMES = [
|
|
|
33223
33367
|
];
|
|
33224
33368
|
const REACT_COMPILER_PACKAGE_REFERENCE_PATTERN = /babel-plugin-react-compiler|react-compiler-runtime|eslint-plugin-react-compiler|["']react-compiler["']/;
|
|
33225
33369
|
const REACT_COMPILER_ENABLED_FLAG_PATTERN = /["']?reactCompiler["']?\s*:\s*(?:true\b|\{)/;
|
|
33370
|
+
const STATIC_EXPORT_OUTPUT_PATTERN = /(?:^|[^.\w])["']?output["']?\s*:\s*["']export["']/m;
|
|
33226
33371
|
const hasCompilerPackage = (packageJson) => {
|
|
33227
33372
|
const allDependencies = {
|
|
33228
33373
|
...packageJson.peerDependencies,
|
|
@@ -33255,34 +33400,10 @@ const detectReactCompiler = (directory, packageJson) => {
|
|
|
33255
33400
|
}
|
|
33256
33401
|
return false;
|
|
33257
33402
|
};
|
|
33258
|
-
const
|
|
33259
|
-
|
|
33260
|
-
|
|
33261
|
-
|
|
33262
|
-
"react-scripts": "cra",
|
|
33263
|
-
"@remix-run/react": "remix",
|
|
33264
|
-
gatsby: "gatsby",
|
|
33265
|
-
expo: "expo",
|
|
33266
|
-
"react-native": "react-native"
|
|
33267
|
-
};
|
|
33268
|
-
const FRAMEWORK_DISPLAY_NAMES = {
|
|
33269
|
-
nextjs: "Next.js",
|
|
33270
|
-
"tanstack-start": "TanStack Start",
|
|
33271
|
-
vite: "Vite",
|
|
33272
|
-
cra: "Create React App",
|
|
33273
|
-
remix: "Remix",
|
|
33274
|
-
gatsby: "Gatsby",
|
|
33275
|
-
expo: "Expo",
|
|
33276
|
-
"react-native": "React Native",
|
|
33277
|
-
preact: "Preact",
|
|
33278
|
-
unknown: "React"
|
|
33279
|
-
};
|
|
33280
|
-
const formatFrameworkName = (framework) => FRAMEWORK_DISPLAY_NAMES[framework];
|
|
33281
|
-
const detectFramework = (dependencies) => {
|
|
33282
|
-
for (const [packageName, frameworkName] of Object.entries(FRAMEWORK_PACKAGES)) if (dependencies[packageName]) return frameworkName;
|
|
33283
|
-
if (dependencies.preact && !dependencies.react) return "preact";
|
|
33284
|
-
return "unknown";
|
|
33285
|
-
};
|
|
33403
|
+
const detectNextjsStaticExport = (directory) => NEXT_CONFIG_FILENAMES.some((filename) => {
|
|
33404
|
+
const filePath = Path.join(directory, filename);
|
|
33405
|
+
return isFile(filePath) && STATIC_EXPORT_OUTPUT_PATTERN.test(NFS.readFileSync(filePath, "utf-8"));
|
|
33406
|
+
});
|
|
33286
33407
|
const UNRESOLVABLE_PROTOCOL_VERSION = /^(?:file|git|github|https?|link|patch|portal|workspace|npm):/i;
|
|
33287
33408
|
const DIST_TAG_VERSION = /^[a-z][a-z0-9._-]*$/i;
|
|
33288
33409
|
const WILDCARD_VERSION = /^[*xX](?:\.[*xX])*$/;
|
|
@@ -33420,10 +33541,76 @@ const isConcreteDependencyVersion = (version) => {
|
|
|
33420
33541
|
const normalizedVersion = normalizeDependencyVersion(version);
|
|
33421
33542
|
return normalizedVersion !== null && /\d/.test(normalizedVersion);
|
|
33422
33543
|
};
|
|
33423
|
-
const
|
|
33424
|
-
if (
|
|
33425
|
-
|
|
33426
|
-
return
|
|
33544
|
+
const isMajorMinorAtLeast = (detected, required) => {
|
|
33545
|
+
if (detected === null) return true;
|
|
33546
|
+
if (detected.major !== required.major) return detected.major > required.major;
|
|
33547
|
+
return detected.minor >= required.minor;
|
|
33548
|
+
};
|
|
33549
|
+
const parseReactMajor = (reactVersion) => {
|
|
33550
|
+
if (typeof reactVersion !== "string") return null;
|
|
33551
|
+
return getLowestDependencyMajor(reactVersion);
|
|
33552
|
+
};
|
|
33553
|
+
const MAJOR_MINOR_PATTERN = /(\d{1,4})\.(\d{1,4})/;
|
|
33554
|
+
const MAJOR_ONLY_PATTERN = /(\d{1,4})/;
|
|
33555
|
+
const UPPER_BOUND_COMPARATOR_PATTERN = /<=?\s{0,8}\d{1,4}(?:\.\d{1,4}){0,2}(?:-[^\s,|]+)?/g;
|
|
33556
|
+
const parseReactMajorMinor = (reactVersion) => {
|
|
33557
|
+
if (typeof reactVersion !== "string") return null;
|
|
33558
|
+
const trimmed = reactVersion.trim();
|
|
33559
|
+
if (trimmed.length === 0) return null;
|
|
33560
|
+
const lowerBoundsOnly = trimmed.replace(UPPER_BOUND_COMPARATOR_PATTERN, " ").trim();
|
|
33561
|
+
if (lowerBoundsOnly.length === 0) return null;
|
|
33562
|
+
const majorMinorMatch = lowerBoundsOnly.match(MAJOR_MINOR_PATTERN);
|
|
33563
|
+
if (majorMinorMatch) {
|
|
33564
|
+
const major = Number.parseInt(majorMinorMatch[1], 10);
|
|
33565
|
+
const minor = Number.parseInt(majorMinorMatch[2], 10);
|
|
33566
|
+
if (!Number.isFinite(major) || major <= 0) return null;
|
|
33567
|
+
if (!Number.isFinite(minor) || minor < 0) return null;
|
|
33568
|
+
return {
|
|
33569
|
+
major,
|
|
33570
|
+
minor
|
|
33571
|
+
};
|
|
33572
|
+
}
|
|
33573
|
+
const majorOnlyMatch = lowerBoundsOnly.match(MAJOR_ONLY_PATTERN);
|
|
33574
|
+
if (!majorOnlyMatch) return null;
|
|
33575
|
+
const major = Number.parseInt(majorOnlyMatch[1], 10);
|
|
33576
|
+
if (!Number.isFinite(major) || major <= 0) return null;
|
|
33577
|
+
return {
|
|
33578
|
+
major,
|
|
33579
|
+
minor: 0
|
|
33580
|
+
};
|
|
33581
|
+
};
|
|
33582
|
+
const parseLowerBoundVersion = (versionSpec) => import_semver.validRange(versionSpec) !== null ? import_semver.minVersion(versionSpec) : import_semver.coerce(versionSpec);
|
|
33583
|
+
const parseTailwindMajorMinor = (tailwindVersion) => {
|
|
33584
|
+
if (typeof tailwindVersion !== "string") return null;
|
|
33585
|
+
const trimmed = tailwindVersion.trim();
|
|
33586
|
+
if (trimmed.length === 0) return null;
|
|
33587
|
+
const lowerBound = parseLowerBoundVersion(trimmed);
|
|
33588
|
+
if (lowerBound === null || lowerBound.major <= 0) return null;
|
|
33589
|
+
return {
|
|
33590
|
+
major: lowerBound.major,
|
|
33591
|
+
minor: lowerBound.minor
|
|
33592
|
+
};
|
|
33593
|
+
};
|
|
33594
|
+
const hasUpperBoundOnlyPeerRange = (range) => {
|
|
33595
|
+
if (typeof range !== "string") return false;
|
|
33596
|
+
const normalizedRange = normalizeDependencyVersion(range);
|
|
33597
|
+
if (normalizedRange === null) return false;
|
|
33598
|
+
return splitDependencyVersionBranches(normalizedRange).some((branch) => {
|
|
33599
|
+
const normalizedBranch = normalizeDependencyVersion(branch);
|
|
33600
|
+
return normalizedBranch !== null && getBranchLowestMajor(normalizedBranch) === null && hasUpperBoundComparator(normalizedBranch);
|
|
33601
|
+
});
|
|
33602
|
+
};
|
|
33603
|
+
const peerRangeMinMajor = (range) => {
|
|
33604
|
+
if (typeof range !== "string") return null;
|
|
33605
|
+
return getLowestDependencyMajor(range);
|
|
33606
|
+
};
|
|
33607
|
+
const resolveEffectiveReactMajor = (reactVersion, packageJson) => {
|
|
33608
|
+
const installedReactMajor = parseReactMajor(reactVersion);
|
|
33609
|
+
const peerReactRange = packageJson.peerDependencies?.react;
|
|
33610
|
+
if (typeof peerReactRange !== "string") return installedReactMajor;
|
|
33611
|
+
const peerFloor = peerRangeMinMajor(peerReactRange);
|
|
33612
|
+
if (peerFloor === null) return hasUpperBoundOnlyPeerRange(peerReactRange) ? null : installedReactMajor;
|
|
33613
|
+
return installedReactMajor !== null ? Math.min(installedReactMajor, peerFloor) : peerFloor;
|
|
33427
33614
|
};
|
|
33428
33615
|
const isCatalogReference = (version) => version.startsWith("catalog:");
|
|
33429
33616
|
const extractCatalogName = (version) => {
|
|
@@ -33565,6 +33752,16 @@ const resolveCatalogVersion = (packageJson, packageName, rootDirectory, explicit
|
|
|
33565
33752
|
}
|
|
33566
33753
|
return null;
|
|
33567
33754
|
};
|
|
33755
|
+
const REACT_SECTIONS = [
|
|
33756
|
+
"dependencies",
|
|
33757
|
+
"peerDependencies",
|
|
33758
|
+
"devDependencies"
|
|
33759
|
+
];
|
|
33760
|
+
const TAILWIND_ZOD_SECTIONS = [
|
|
33761
|
+
"dependencies",
|
|
33762
|
+
"devDependencies",
|
|
33763
|
+
"peerDependencies"
|
|
33764
|
+
];
|
|
33568
33765
|
const EMPTY_DEPENDENCY_INFO = {
|
|
33569
33766
|
reactVersion: null,
|
|
33570
33767
|
tailwindVersion: null,
|
|
@@ -33605,6 +33802,10 @@ const extractDependencyInfo = (packageJson) => {
|
|
|
33605
33802
|
framework: detectFramework(allDependencies)
|
|
33606
33803
|
};
|
|
33607
33804
|
};
|
|
33805
|
+
const getDependencySpec = (packageJson, packageName) => {
|
|
33806
|
+
const spec = packageJson.dependencies?.[packageName] ?? packageJson.devDependencies?.[packageName] ?? packageJson.peerDependencies?.[packageName] ?? packageJson.optionalDependencies?.[packageName];
|
|
33807
|
+
return typeof spec === "string" ? spec : null;
|
|
33808
|
+
};
|
|
33608
33809
|
const getDependencyDeclaration = ({ packageJson, packageName, sections }) => {
|
|
33609
33810
|
for (const section of sections) {
|
|
33610
33811
|
const version = packageJson[section]?.[packageName];
|
|
@@ -33621,203 +33822,67 @@ const getDependencyDeclaration = ({ packageJson, packageName, sections }) => {
|
|
|
33621
33822
|
version: null
|
|
33622
33823
|
};
|
|
33623
33824
|
};
|
|
33624
|
-
const
|
|
33625
|
-
|
|
33626
|
-
|
|
33627
|
-
|
|
33628
|
-
|
|
33629
|
-
|
|
33630
|
-
|
|
33631
|
-
const
|
|
33632
|
-
|
|
33633
|
-
|
|
33634
|
-
|
|
33635
|
-
|
|
33636
|
-
|
|
33637
|
-
if (!isFile(Path.join(rootDirectory, "nx.json"))) return [];
|
|
33638
|
-
const collected = [];
|
|
33639
|
-
for (const candidate of NX_PROJECT_DISCOVERY_DIRS) {
|
|
33640
|
-
const candidatePath = Path.join(rootDirectory, candidate);
|
|
33641
|
-
if (!isDirectory(candidatePath)) continue;
|
|
33642
|
-
for (const entry of readDirectoryEntries(candidatePath)) {
|
|
33643
|
-
if (!entry.isDirectory()) continue;
|
|
33644
|
-
const projectDirectory = Path.join(candidatePath, entry.name);
|
|
33645
|
-
if (isFile(Path.join(projectDirectory, "project.json")) || isFile(Path.join(projectDirectory, "package.json"))) collected.push(`${candidate}/${entry.name}`);
|
|
33646
|
-
}
|
|
33647
|
-
}
|
|
33648
|
-
return collected;
|
|
33649
|
-
};
|
|
33650
|
-
const parsePnpmWorkspacePatterns = (rootDirectory) => {
|
|
33651
|
-
const workspacePath = Path.join(rootDirectory, "pnpm-workspace.yaml");
|
|
33652
|
-
if (!isFile(workspacePath)) return [];
|
|
33653
|
-
const content = NFS.readFileSync(workspacePath, "utf-8");
|
|
33654
|
-
const patterns = [];
|
|
33655
|
-
let isInsidePackagesBlock = false;
|
|
33656
|
-
for (const line of content.split("\n")) {
|
|
33657
|
-
const trimmed = line.trim();
|
|
33658
|
-
if (trimmed === "packages:") {
|
|
33659
|
-
isInsidePackagesBlock = true;
|
|
33660
|
-
continue;
|
|
33661
|
-
}
|
|
33662
|
-
if (isInsidePackagesBlock && trimmed.startsWith("-")) patterns.push(trimmed.replace(/^-\s*/, "").replace(/["']/g, ""));
|
|
33663
|
-
else if (isInsidePackagesBlock && trimmed.length > 0 && !trimmed.startsWith("#")) isInsidePackagesBlock = false;
|
|
33664
|
-
}
|
|
33665
|
-
return patterns;
|
|
33666
|
-
};
|
|
33667
|
-
const getWorkspacePatterns = (rootDirectory, packageJson) => {
|
|
33668
|
-
const pnpmPatterns = parsePnpmWorkspacePatterns(rootDirectory);
|
|
33669
|
-
if (pnpmPatterns.length > 0) return pnpmPatterns;
|
|
33670
|
-
if (Array.isArray(packageJson.workspaces)) return packageJson.workspaces;
|
|
33671
|
-
if (packageJson.workspaces?.packages) return packageJson.workspaces.packages;
|
|
33672
|
-
const nxPatterns = getNxWorkspaceDirectories(rootDirectory);
|
|
33673
|
-
if (nxPatterns.length > 0) return nxPatterns;
|
|
33674
|
-
return [];
|
|
33675
|
-
};
|
|
33676
|
-
const parseReactMajor = (reactVersion) => {
|
|
33677
|
-
if (typeof reactVersion !== "string") return null;
|
|
33678
|
-
return getLowestDependencyMajor(reactVersion);
|
|
33825
|
+
const REACT_DEPENDENCY_NAMES = new Set([
|
|
33826
|
+
"react",
|
|
33827
|
+
"react-native",
|
|
33828
|
+
"next",
|
|
33829
|
+
"preact"
|
|
33830
|
+
]);
|
|
33831
|
+
const hasReactDependency = (packageJson) => {
|
|
33832
|
+
const allDependencies = {
|
|
33833
|
+
...packageJson.peerDependencies,
|
|
33834
|
+
...packageJson.dependencies,
|
|
33835
|
+
...packageJson.devDependencies
|
|
33836
|
+
};
|
|
33837
|
+
return Object.keys(allDependencies).some((packageName) => REACT_DEPENDENCY_NAMES.has(packageName));
|
|
33679
33838
|
};
|
|
33680
|
-
const
|
|
33681
|
-
|
|
33682
|
-
|
|
33683
|
-
|
|
33684
|
-
|
|
33685
|
-
|
|
33686
|
-
}
|
|
33687
|
-
const wildcardIndex = cleanPattern.indexOf("*");
|
|
33688
|
-
const baseDirectory = Path.join(rootDirectory, cleanPattern.slice(0, wildcardIndex));
|
|
33689
|
-
const suffixAfterWildcard = cleanPattern.slice(wildcardIndex + 1);
|
|
33690
|
-
if (!isDirectory(baseDirectory)) return [];
|
|
33691
|
-
const resolved = [];
|
|
33692
|
-
for (const entry of readDirectoryEntries(baseDirectory)) {
|
|
33693
|
-
const entryPath = Path.join(baseDirectory, entry.name, suffixAfterWildcard);
|
|
33694
|
-
if (isDirectory(entryPath) && isFile(Path.join(entryPath, "package.json"))) resolved.push(entryPath);
|
|
33695
|
-
}
|
|
33696
|
-
return resolved;
|
|
33839
|
+
const getPreactVersion = (packageJson) => {
|
|
33840
|
+
return {
|
|
33841
|
+
...packageJson.peerDependencies,
|
|
33842
|
+
...packageJson.dependencies,
|
|
33843
|
+
...packageJson.devDependencies
|
|
33844
|
+
}.preact ?? null;
|
|
33697
33845
|
};
|
|
33698
|
-
const
|
|
33699
|
-
|
|
33700
|
-
|
|
33701
|
-
|
|
33702
|
-
|
|
33703
|
-
|
|
33704
|
-
|
|
33705
|
-
|
|
33846
|
+
const TANSTACK_QUERY_PACKAGES = new Set([
|
|
33847
|
+
"@tanstack/react-query",
|
|
33848
|
+
"@tanstack/query-core",
|
|
33849
|
+
"react-query"
|
|
33850
|
+
]);
|
|
33851
|
+
const hasTanStackQuery = (packageJson) => {
|
|
33852
|
+
const allDependencies = {
|
|
33853
|
+
...packageJson.peerDependencies,
|
|
33854
|
+
...packageJson.dependencies,
|
|
33855
|
+
...packageJson.devDependencies
|
|
33856
|
+
};
|
|
33857
|
+
return Object.keys(allDependencies).some((packageName) => TANSTACK_QUERY_PACKAGES.has(packageName));
|
|
33706
33858
|
};
|
|
33707
|
-
const
|
|
33708
|
-
if (!
|
|
33709
|
-
const
|
|
33710
|
-
const
|
|
33711
|
-
if (
|
|
33712
|
-
|
|
33713
|
-
|
|
33859
|
+
const resolveCatalogBackedDependencyVersion = ({ rootDirectory, rootPackageJson, packageName, version }) => {
|
|
33860
|
+
if (version === null || !isCatalogReference(version)) return version;
|
|
33861
|
+
const catalogName = extractCatalogName(version);
|
|
33862
|
+
const resolvedLocalVersion = resolveCatalogVersion(rootPackageJson, packageName, rootDirectory, catalogName);
|
|
33863
|
+
if (resolvedLocalVersion) return resolvedLocalVersion;
|
|
33864
|
+
const monorepoRoot = findMonorepoRoot(rootDirectory);
|
|
33865
|
+
if (!monorepoRoot) return version;
|
|
33866
|
+
const monorepoPackageJsonPath = Path.join(monorepoRoot, "package.json");
|
|
33867
|
+
if (!isFile(monorepoPackageJsonPath)) return version;
|
|
33868
|
+
return resolveCatalogVersion(readPackageJson(monorepoPackageJsonPath), packageName, monorepoRoot, catalogName) ?? version;
|
|
33714
33869
|
};
|
|
33715
|
-
|
|
33716
|
-
|
|
33717
|
-
|
|
33718
|
-
|
|
33719
|
-
|
|
33720
|
-
|
|
33721
|
-
|
|
33722
|
-
|
|
33723
|
-
|
|
33724
|
-
|
|
33725
|
-
|
|
33726
|
-
|
|
33727
|
-
|
|
33728
|
-
|
|
33729
|
-
|
|
33730
|
-
"peerDependencies",
|
|
33731
|
-
"devDependencies"
|
|
33732
|
-
],
|
|
33733
|
-
workspaceDirectory,
|
|
33734
|
-
workspacePackageJson
|
|
33735
|
-
});
|
|
33736
|
-
const tailwindVersion = resolveWorkspaceDependencyVersion({
|
|
33737
|
-
concreteVersion: info.tailwindVersion,
|
|
33738
|
-
packageName: "tailwindcss",
|
|
33739
|
-
rootDirectory,
|
|
33740
|
-
rootPackageJson: packageJson,
|
|
33741
|
-
sections: [
|
|
33742
|
-
"dependencies",
|
|
33743
|
-
"devDependencies",
|
|
33744
|
-
"peerDependencies"
|
|
33745
|
-
],
|
|
33746
|
-
workspaceDirectory,
|
|
33747
|
-
workspacePackageJson
|
|
33748
|
-
});
|
|
33749
|
-
const zodVersion = resolveWorkspaceDependencyVersion({
|
|
33750
|
-
concreteVersion: info.zodVersion,
|
|
33751
|
-
packageName: "zod",
|
|
33752
|
-
rootDirectory,
|
|
33753
|
-
rootPackageJson: packageJson,
|
|
33754
|
-
sections: [
|
|
33755
|
-
"dependencies",
|
|
33756
|
-
"devDependencies",
|
|
33757
|
-
"peerDependencies"
|
|
33758
|
-
],
|
|
33759
|
-
workspaceDirectory,
|
|
33760
|
-
workspacePackageJson
|
|
33761
|
-
});
|
|
33762
|
-
if (reactVersion && shouldReplaceReactVersion(result.reactVersion, reactVersion)) result.reactVersion = reactVersion;
|
|
33763
|
-
if (tailwindVersion && !result.tailwindVersion) result.tailwindVersion = tailwindVersion;
|
|
33764
|
-
if (zodVersion && !result.zodVersion) result.zodVersion = zodVersion;
|
|
33765
|
-
if (info.framework !== "unknown" && result.framework === "unknown") result.framework = info.framework;
|
|
33766
|
-
const resultReactMajor = parseReactMajor(result.reactVersion);
|
|
33767
|
-
if (result.reactVersion && result.tailwindVersion && result.framework !== "unknown" && resultReactMajor !== null && resultReactMajor <= 17) return result;
|
|
33768
|
-
}
|
|
33870
|
+
/**
|
|
33871
|
+
* The chain of ancestor directories of `startDirectory`, nearest first, up to
|
|
33872
|
+
* and including the filesystem root. With `includeStart`, the chain begins at
|
|
33873
|
+
* `startDirectory` itself; otherwise at its parent. The one place the "walk up
|
|
33874
|
+
* the tree" traversal is expressed, so each caller adds only its own match /
|
|
33875
|
+
* stop policy instead of re-deriving the `path.dirname` loop.
|
|
33876
|
+
*/
|
|
33877
|
+
const ancestorDirectories = (startDirectory, { includeStart }) => {
|
|
33878
|
+
const directories = [];
|
|
33879
|
+
let currentDirectory = includeStart ? startDirectory : Path.dirname(startDirectory);
|
|
33880
|
+
while (true) {
|
|
33881
|
+
directories.push(currentDirectory);
|
|
33882
|
+
const parentDirectory = Path.dirname(currentDirectory);
|
|
33883
|
+
if (parentDirectory === currentDirectory) return directories;
|
|
33884
|
+
currentDirectory = parentDirectory;
|
|
33769
33885
|
}
|
|
33770
|
-
return result;
|
|
33771
|
-
};
|
|
33772
|
-
const findDependencyInfoFromMonorepoRoot = (directory) => {
|
|
33773
|
-
const monorepoRoot = findMonorepoRoot(directory);
|
|
33774
|
-
if (!monorepoRoot) return EMPTY_DEPENDENCY_INFO;
|
|
33775
|
-
const monorepoPackageJsonPath = Path.join(monorepoRoot, "package.json");
|
|
33776
|
-
if (!isFile(monorepoPackageJsonPath)) return EMPTY_DEPENDENCY_INFO;
|
|
33777
|
-
const rootPackageJson = readPackageJson(monorepoPackageJsonPath);
|
|
33778
|
-
const rootInfo = extractDependencyInfo(rootPackageJson);
|
|
33779
|
-
const leafPackageJsonPath = Path.join(directory, "package.json");
|
|
33780
|
-
const leafPackageJson = isFile(leafPackageJsonPath) ? readPackageJson(leafPackageJsonPath) : null;
|
|
33781
|
-
const leafReactDeclaration = leafPackageJson ? getDependencyDeclaration({
|
|
33782
|
-
packageJson: leafPackageJson,
|
|
33783
|
-
packageName: "react",
|
|
33784
|
-
sections: [
|
|
33785
|
-
"dependencies",
|
|
33786
|
-
"peerDependencies",
|
|
33787
|
-
"devDependencies"
|
|
33788
|
-
]
|
|
33789
|
-
}) : null;
|
|
33790
|
-
const leafTailwindDeclaration = leafPackageJson ? getDependencyDeclaration({
|
|
33791
|
-
packageJson: leafPackageJson,
|
|
33792
|
-
packageName: "tailwindcss",
|
|
33793
|
-
sections: [
|
|
33794
|
-
"dependencies",
|
|
33795
|
-
"devDependencies",
|
|
33796
|
-
"peerDependencies"
|
|
33797
|
-
]
|
|
33798
|
-
}) : null;
|
|
33799
|
-
const leafZodDeclaration = leafPackageJson ? getDependencyDeclaration({
|
|
33800
|
-
packageJson: leafPackageJson,
|
|
33801
|
-
packageName: "zod",
|
|
33802
|
-
sections: [
|
|
33803
|
-
"dependencies",
|
|
33804
|
-
"devDependencies",
|
|
33805
|
-
"peerDependencies"
|
|
33806
|
-
]
|
|
33807
|
-
}) : null;
|
|
33808
|
-
const shouldUseReactFallback = !leafReactDeclaration?.hasDeclaration;
|
|
33809
|
-
const shouldUseTailwindFallback = leafTailwindDeclaration?.hasDeclaration ?? true;
|
|
33810
|
-
const shouldUseZodFallback = leafZodDeclaration?.hasDeclaration ?? true;
|
|
33811
|
-
const reactCatalogVersion = shouldUseReactFallback ? resolveCatalogVersion(rootPackageJson, "react", monorepoRoot, leafReactDeclaration?.catalogReference) : null;
|
|
33812
|
-
const tailwindCatalogVersion = shouldUseTailwindFallback ? resolveCatalogVersion(rootPackageJson, "tailwindcss", monorepoRoot, leafTailwindDeclaration?.catalogReference) : null;
|
|
33813
|
-
const zodCatalogVersion = shouldUseZodFallback ? resolveCatalogVersion(rootPackageJson, "zod", monorepoRoot, leafZodDeclaration?.catalogReference) : null;
|
|
33814
|
-
const workspaceInfo = findReactInWorkspaces(monorepoRoot, rootPackageJson);
|
|
33815
|
-
return {
|
|
33816
|
-
reactVersion: shouldUseReactFallback ? reactCatalogVersion ?? rootInfo.reactVersion ?? workspaceInfo.reactVersion : rootInfo.reactVersion ?? workspaceInfo.reactVersion,
|
|
33817
|
-
tailwindVersion: shouldUseTailwindFallback ? tailwindCatalogVersion ?? rootInfo.tailwindVersion ?? workspaceInfo.tailwindVersion : null,
|
|
33818
|
-
zodVersion: shouldUseZodFallback ? zodCatalogVersion ?? rootInfo.zodVersion ?? workspaceInfo.zodVersion : null,
|
|
33819
|
-
framework: rootInfo.framework !== "unknown" ? rootInfo.framework : workspaceInfo.framework
|
|
33820
|
-
};
|
|
33821
33886
|
};
|
|
33822
33887
|
/**
|
|
33823
33888
|
* Walk up from `startDirectory` to the nearest ancestor that owns a
|
|
@@ -33842,69 +33907,6 @@ const findNearestAncestorPackageJson = (startDirectory) => {
|
|
|
33842
33907
|
}
|
|
33843
33908
|
return null;
|
|
33844
33909
|
};
|
|
33845
|
-
/**
|
|
33846
|
-
* Last-resort React detection: locate React the way Node's `node_modules`
|
|
33847
|
-
* resolution would from `directory` — check each `node_modules/react` up the
|
|
33848
|
-
* tree — but bounded to the enclosing repo so a globally installed React can't
|
|
33849
|
-
* masquerade as the project's version. Stops at (and including) the nearest
|
|
33850
|
-
* project boundary — the git root or a monorepo root — and returns the first
|
|
33851
|
-
* installed `version`.
|
|
33852
|
-
*
|
|
33853
|
-
* Makes "React is installed and importable" ⇒ "React is detected" an invariant
|
|
33854
|
-
* for packages whose only React declaration is a version-less spec
|
|
33855
|
-
* (`workspace:*`, `*`, a dist-tag) or where React lives solely in a hoisted
|
|
33856
|
-
* `node_modules` the declaration walks never reach — the profile of a component
|
|
33857
|
-
* package inside a monorepo. Matching the `node_modules/react` entry rather than
|
|
33858
|
-
* its realpath means a project whose `node_modules` is symlinked elsewhere (a
|
|
33859
|
-
* Docker volume, a shared store) still counts, while a global install outside
|
|
33860
|
-
* the repo does not.
|
|
33861
|
-
*
|
|
33862
|
-
* Returns `null` when React isn't installed within the repo or its package.json
|
|
33863
|
-
* carries no version string. A tree with no boundary marker at all bounds the
|
|
33864
|
-
* search to `directory`, so a dependency hoisted above the scanned package there
|
|
33865
|
-
* is conservatively not adopted.
|
|
33866
|
-
*/
|
|
33867
|
-
/**
|
|
33868
|
-
* The nearest enclosing project boundary (git root or monorepo root), or
|
|
33869
|
-
* `directory` itself when the scan target sits outside any repo. Bounds the
|
|
33870
|
-
* node_modules walk so a React hoisted anywhere inside the repo counts, but a
|
|
33871
|
-
* React in an ancestor above the repo — a home-directory or global
|
|
33872
|
-
* `node_modules` — can't leak in.
|
|
33873
|
-
*/
|
|
33874
|
-
const findContainmentRoot = (directory) => {
|
|
33875
|
-
for (const ancestorDirectory of ancestorDirectories(directory, { includeStart: true })) if (isProjectBoundary(ancestorDirectory)) return ancestorDirectory;
|
|
33876
|
-
return directory;
|
|
33877
|
-
};
|
|
33878
|
-
const resolveInstalledReactVersion = (directory) => {
|
|
33879
|
-
const containmentRoot = findContainmentRoot(directory);
|
|
33880
|
-
for (const ancestorDirectory of ancestorDirectories(directory, { includeStart: true })) {
|
|
33881
|
-
const reactPackageJsonPath = Path.join(ancestorDirectory, "node_modules", "react", "package.json");
|
|
33882
|
-
if (isFile(reactPackageJsonPath)) {
|
|
33883
|
-
const installedVersion = readPackageJson(reactPackageJsonPath).version;
|
|
33884
|
-
return typeof installedVersion === "string" ? installedVersion : null;
|
|
33885
|
-
}
|
|
33886
|
-
if (ancestorDirectory === containmentRoot) return null;
|
|
33887
|
-
}
|
|
33888
|
-
return null;
|
|
33889
|
-
};
|
|
33890
|
-
const findInWorkspacePackageJsons = (rootDirectory, rootPackageJson, select) => {
|
|
33891
|
-
const rootValue = select(rootPackageJson);
|
|
33892
|
-
if (rootValue !== null) return rootValue;
|
|
33893
|
-
const patterns = getWorkspacePatterns(rootDirectory, rootPackageJson);
|
|
33894
|
-
if (patterns.length === 0) return null;
|
|
33895
|
-
const visitedDirectories = /* @__PURE__ */ new Set();
|
|
33896
|
-
for (const pattern of patterns) {
|
|
33897
|
-
const directories = [...resolveWorkspaceDirectories(rootDirectory, pattern)].sort();
|
|
33898
|
-
for (const workspaceDirectory of directories) {
|
|
33899
|
-
if (visitedDirectories.has(workspaceDirectory)) continue;
|
|
33900
|
-
visitedDirectories.add(workspaceDirectory);
|
|
33901
|
-
const value = select(readPackageJson(Path.join(workspaceDirectory, "package.json")));
|
|
33902
|
-
if (value !== null) return value;
|
|
33903
|
-
}
|
|
33904
|
-
}
|
|
33905
|
-
return null;
|
|
33906
|
-
};
|
|
33907
|
-
const someWorkspacePackageJson = (rootDirectory, rootPackageJson, predicate) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => predicate(packageJson) ? true : null) !== null;
|
|
33908
33910
|
const NAMES = new Set([
|
|
33909
33911
|
"react-native",
|
|
33910
33912
|
"react-native-tvos",
|
|
@@ -33937,47 +33939,7 @@ const isPackageJsonReactNativeAware = (packageJson) => {
|
|
|
33937
33939
|
if (containsAnyReactNativeDependency(packageJson.optionalDependencies)) return true;
|
|
33938
33940
|
return false;
|
|
33939
33941
|
};
|
|
33940
|
-
const
|
|
33941
|
-
const getDependencySpec = (packageJson, packageName) => {
|
|
33942
|
-
const spec = packageJson.dependencies?.[packageName] ?? packageJson.devDependencies?.[packageName] ?? packageJson.peerDependencies?.[packageName] ?? packageJson.optionalDependencies?.[packageName];
|
|
33943
|
-
return typeof spec === "string" ? spec : null;
|
|
33944
|
-
};
|
|
33945
|
-
const findExpoVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, "expo"));
|
|
33946
|
-
const SHOPIFY_FLASH_LIST_PACKAGE_NAME = "@shopify/flash-list";
|
|
33947
|
-
const findShopifyFlashListVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, SHOPIFY_FLASH_LIST_PACKAGE_NAME));
|
|
33948
|
-
const resolveCatalogBackedDependencyVersion = ({ rootDirectory, rootPackageJson, packageName, version }) => {
|
|
33949
|
-
if (version === null || !isCatalogReference(version)) return version;
|
|
33950
|
-
const catalogName = extractCatalogName(version);
|
|
33951
|
-
const resolvedLocalVersion = resolveCatalogVersion(rootPackageJson, packageName, rootDirectory, catalogName);
|
|
33952
|
-
if (resolvedLocalVersion) return resolvedLocalVersion;
|
|
33953
|
-
const monorepoRoot = findMonorepoRoot(rootDirectory);
|
|
33954
|
-
if (!monorepoRoot) return version;
|
|
33955
|
-
const monorepoPackageJsonPath = path.join(monorepoRoot, "package.json");
|
|
33956
|
-
if (!isFile(monorepoPackageJsonPath)) return version;
|
|
33957
|
-
return resolveCatalogVersion(readPackageJson(monorepoPackageJsonPath), packageName, monorepoRoot, catalogName) ?? version;
|
|
33958
|
-
};
|
|
33959
|
-
const findNextjsVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, "next"));
|
|
33960
|
-
const getPreactVersion = (packageJson) => {
|
|
33961
|
-
return {
|
|
33962
|
-
...packageJson.peerDependencies,
|
|
33963
|
-
...packageJson.dependencies,
|
|
33964
|
-
...packageJson.devDependencies
|
|
33965
|
-
}.preact ?? null;
|
|
33966
|
-
};
|
|
33967
|
-
const TANSTACK_QUERY_PACKAGES = new Set([
|
|
33968
|
-
"@tanstack/react-query",
|
|
33969
|
-
"@tanstack/query-core",
|
|
33970
|
-
"react-query"
|
|
33971
|
-
]);
|
|
33972
|
-
const hasTanStackQuery = (packageJson) => {
|
|
33973
|
-
const allDependencies = {
|
|
33974
|
-
...packageJson.peerDependencies,
|
|
33975
|
-
...packageJson.dependencies,
|
|
33976
|
-
...packageJson.devDependencies
|
|
33977
|
-
};
|
|
33978
|
-
return Object.keys(allDependencies).some((packageName) => TANSTACK_QUERY_PACKAGES.has(packageName));
|
|
33979
|
-
};
|
|
33980
|
-
const REANIMATED_DEPENDENCY_NAME = "react-native-reanimated";
|
|
33942
|
+
const REANIMATED_DEPENDENCY_NAME$1 = "react-native-reanimated";
|
|
33981
33943
|
const isPackageJsonReanimatedAware = (packageJson) => {
|
|
33982
33944
|
const allDependencies = {
|
|
33983
33945
|
...packageJson.peerDependencies,
|
|
@@ -33985,144 +33947,70 @@ const isPackageJsonReanimatedAware = (packageJson) => {
|
|
|
33985
33947
|
...packageJson.devDependencies,
|
|
33986
33948
|
...packageJson.optionalDependencies
|
|
33987
33949
|
};
|
|
33988
|
-
return Object.hasOwn(allDependencies, REANIMATED_DEPENDENCY_NAME);
|
|
33989
|
-
};
|
|
33990
|
-
const parseZodMajor = (zodVersion) => {
|
|
33991
|
-
if (typeof zodVersion !== "string") return null;
|
|
33992
|
-
return getLowestDependencyMajor(zodVersion);
|
|
33993
|
-
};
|
|
33994
|
-
const hasUpperBoundOnlyPeerRange = (range) => {
|
|
33995
|
-
if (typeof range !== "string") return false;
|
|
33996
|
-
const normalizedRange = normalizeDependencyVersion(range);
|
|
33997
|
-
if (normalizedRange === null) return false;
|
|
33998
|
-
return splitDependencyVersionBranches(normalizedRange).some((branch) => {
|
|
33999
|
-
const normalizedBranch = normalizeDependencyVersion(branch);
|
|
34000
|
-
return normalizedBranch !== null && getBranchLowestMajor(normalizedBranch) === null && hasUpperBoundComparator(normalizedBranch);
|
|
34001
|
-
});
|
|
33950
|
+
return Object.hasOwn(allDependencies, REANIMATED_DEPENDENCY_NAME$1);
|
|
34002
33951
|
};
|
|
34003
|
-
const
|
|
34004
|
-
|
|
34005
|
-
|
|
34006
|
-
|
|
34007
|
-
|
|
34008
|
-
const
|
|
34009
|
-
|
|
34010
|
-
|
|
34011
|
-
const peerFloor = peerRangeMinMajor(peerReactRange);
|
|
34012
|
-
if (peerFloor === null) return hasUpperBoundOnlyPeerRange(peerReactRange) ? null : installedReactMajor;
|
|
34013
|
-
return installedReactMajor !== null ? Math.min(installedReactMajor, peerFloor) : peerFloor;
|
|
33952
|
+
const getWorkspacePatterns = (rootDirectory, packageJson) => {
|
|
33953
|
+
const pnpmPatterns = parsePnpmWorkspacePatterns(rootDirectory);
|
|
33954
|
+
if (pnpmPatterns.length > 0) return pnpmPatterns;
|
|
33955
|
+
if (Array.isArray(packageJson.workspaces)) return packageJson.workspaces;
|
|
33956
|
+
if (packageJson.workspaces?.packages) return packageJson.workspaces.packages;
|
|
33957
|
+
const nxPatterns = getNxWorkspaceDirectories(rootDirectory);
|
|
33958
|
+
if (nxPatterns.length > 0) return nxPatterns;
|
|
33959
|
+
return [];
|
|
34014
33960
|
};
|
|
34015
|
-
const
|
|
34016
|
-
const
|
|
34017
|
-
|
|
34018
|
-
const
|
|
34019
|
-
const
|
|
34020
|
-
|
|
34021
|
-
|
|
34022
|
-
|
|
34023
|
-
|
|
34024
|
-
|
|
34025
|
-
|
|
34026
|
-
|
|
34027
|
-
|
|
34028
|
-
|
|
33961
|
+
const parsePnpmWorkspacePatterns = (rootDirectory) => {
|
|
33962
|
+
const workspacePath = Path.join(rootDirectory, "pnpm-workspace.yaml");
|
|
33963
|
+
if (!isFile(workspacePath)) return [];
|
|
33964
|
+
const content = NFS.readFileSync(workspacePath, "utf-8");
|
|
33965
|
+
const patterns = [];
|
|
33966
|
+
let isInsidePackagesBlock = false;
|
|
33967
|
+
for (const line of content.split("\n")) {
|
|
33968
|
+
const trimmed = line.trim();
|
|
33969
|
+
if (trimmed === "packages:") {
|
|
33970
|
+
isInsidePackagesBlock = true;
|
|
33971
|
+
continue;
|
|
33972
|
+
}
|
|
33973
|
+
if (isInsidePackagesBlock && trimmed.startsWith("-")) patterns.push(trimmed.replace(/^-\s*/, "").replace(/["']/g, ""));
|
|
33974
|
+
else if (isInsidePackagesBlock && trimmed.length > 0 && !trimmed.startsWith("#")) isInsidePackagesBlock = false;
|
|
34029
33975
|
}
|
|
34030
|
-
return
|
|
34031
|
-
};
|
|
34032
|
-
const resolveExtendsPath = (extendsValue, fromConfigDirectory) => {
|
|
34033
|
-
if (isRelativeExtendsValue(extendsValue)) return ensureJsonExtension(Path.resolve(fromConfigDirectory, extendsValue));
|
|
34034
|
-
return resolvePackageExtendsPath(extendsValue, fromConfigDirectory);
|
|
34035
|
-
};
|
|
34036
|
-
const normalizeCompilerOptions = (compilerOptions) => {
|
|
34037
|
-
if (!isPlainObject(compilerOptions)) return { hasExplicitLib: false };
|
|
34038
|
-
const target = typeof compilerOptions.target === "string" ? compilerOptions.target : void 0;
|
|
34039
|
-
const hasExplicitLib = Object.hasOwn(compilerOptions, "lib");
|
|
34040
|
-
return {
|
|
34041
|
-
target,
|
|
34042
|
-
lib: Array.isArray(compilerOptions.lib) ? compilerOptions.lib.filter((entry) => typeof entry === "string") : void 0,
|
|
34043
|
-
hasExplicitLib
|
|
34044
|
-
};
|
|
33976
|
+
return patterns;
|
|
34045
33977
|
};
|
|
34046
|
-
const
|
|
34047
|
-
|
|
34048
|
-
|
|
34049
|
-
|
|
34050
|
-
|
|
34051
|
-
|
|
33978
|
+
const NX_PROJECT_DISCOVERY_DIRS = [
|
|
33979
|
+
"apps",
|
|
33980
|
+
"libs",
|
|
33981
|
+
"packages"
|
|
33982
|
+
];
|
|
33983
|
+
const getNxWorkspaceDirectories = (rootDirectory) => {
|
|
33984
|
+
if (!isFile(Path.join(rootDirectory, "nx.json"))) return [];
|
|
33985
|
+
const collected = [];
|
|
33986
|
+
for (const candidate of NX_PROJECT_DISCOVERY_DIRS) {
|
|
33987
|
+
const candidatePath = Path.join(rootDirectory, candidate);
|
|
33988
|
+
if (!isDirectory(candidatePath)) continue;
|
|
33989
|
+
for (const entry of readDirectoryEntries(candidatePath)) {
|
|
33990
|
+
if (!entry.isDirectory()) continue;
|
|
33991
|
+
const projectDirectory = Path.join(candidatePath, entry.name);
|
|
33992
|
+
if (isFile(Path.join(projectDirectory, "project.json")) || isFile(Path.join(projectDirectory, "package.json"))) collected.push(`${candidate}/${entry.name}`);
|
|
33993
|
+
}
|
|
34052
33994
|
}
|
|
34053
|
-
|
|
34054
|
-
if (!isPlainObject(parsed.config)) return null;
|
|
34055
|
-
return {
|
|
34056
|
-
extends: typeof parsed.config.extends === "string" ? parsed.config.extends : void 0,
|
|
34057
|
-
compilerOptions: normalizeCompilerOptions(parsed.config.compilerOptions)
|
|
34058
|
-
};
|
|
34059
|
-
};
|
|
34060
|
-
const mergeCompilerOptions = (inherited, current) => {
|
|
34061
|
-
const target = current.target ?? inherited?.target;
|
|
34062
|
-
const hasExplicitLib = current.hasExplicitLib || Boolean(inherited?.hasExplicitLib);
|
|
34063
|
-
return {
|
|
34064
|
-
target,
|
|
34065
|
-
lib: current.hasExplicitLib ? current.lib : inherited?.lib,
|
|
34066
|
-
hasExplicitLib
|
|
34067
|
-
};
|
|
33995
|
+
return collected;
|
|
34068
33996
|
};
|
|
34069
|
-
const
|
|
34070
|
-
const
|
|
34071
|
-
if (
|
|
34072
|
-
|
|
34073
|
-
|
|
34074
|
-
|
|
34075
|
-
nextVisitedPaths.add(realPath);
|
|
34076
|
-
if (tsConfig.extends && extendsDepth < 8) {
|
|
34077
|
-
const parentPath = resolveExtendsPath(tsConfig.extends, Path.dirname(realPath));
|
|
34078
|
-
if (parentPath && isFile(parentPath)) return mergeCompilerOptions(readResolvedCompilerOptions(parentPath, extendsDepth + 1, nextVisitedPaths), tsConfig.compilerOptions);
|
|
33997
|
+
const resolveWorkspaceDirectories = (rootDirectory, pattern) => {
|
|
33998
|
+
const cleanPattern = pattern.replace(/["']/g, "").replace(/\/\*\*$/, "/*");
|
|
33999
|
+
if (!cleanPattern.includes("*")) {
|
|
34000
|
+
const directoryPath = Path.join(rootDirectory, cleanPattern);
|
|
34001
|
+
if (isDirectory(directoryPath) && isFile(Path.join(directoryPath, "package.json"))) return [directoryPath];
|
|
34002
|
+
return [];
|
|
34079
34003
|
}
|
|
34080
|
-
|
|
34081
|
-
|
|
34082
|
-
const
|
|
34083
|
-
|
|
34084
|
-
|
|
34085
|
-
|
|
34086
|
-
const
|
|
34087
|
-
|
|
34088
|
-
|
|
34089
|
-
|
|
34090
|
-
if (!esYearMatch) return false;
|
|
34091
|
-
if (Number(esYearMatch[1]) < 2023) return false;
|
|
34092
|
-
const component = esYearMatch[2];
|
|
34093
|
-
return component === void 0 || component === "array";
|
|
34094
|
-
};
|
|
34095
|
-
const libIncludesES2023 = (lib) => lib.some(libEntryIncludesES2023Array);
|
|
34096
|
-
const compilerOptionsArePreES2023 = (compilerOptions) => {
|
|
34097
|
-
if (compilerOptions.hasExplicitLib) return !libIncludesES2023(compilerOptions.lib ?? []);
|
|
34098
|
-
if (compilerOptions.target) return targetYearIsPreES2023(compilerOptions.target);
|
|
34099
|
-
return false;
|
|
34100
|
-
};
|
|
34101
|
-
const compilerOptionsDeclareTargetOrLib = (compilerOptions) => compilerOptions.hasExplicitLib || compilerOptions.target !== void 0;
|
|
34102
|
-
const detectPreES2023FromConfig = (tsConfigPath) => {
|
|
34103
|
-
const compilerOptions = readResolvedCompilerOptions(tsConfigPath, 0, /* @__PURE__ */ new Set());
|
|
34104
|
-
if (!compilerOptions) return false;
|
|
34105
|
-
if (!compilerOptionsDeclareTargetOrLib(compilerOptions)) return false;
|
|
34106
|
-
return compilerOptionsArePreES2023(compilerOptions);
|
|
34107
|
-
};
|
|
34108
|
-
const detectPreES2023Target = (directory) => {
|
|
34109
|
-
const tsConfigPath = Path.join(directory, TSCONFIG_FILENAME);
|
|
34110
|
-
if (isFile(tsConfigPath)) return detectPreES2023FromConfig(tsConfigPath);
|
|
34111
|
-
return false;
|
|
34112
|
-
};
|
|
34113
|
-
const REACT_DEPENDENCY_NAMES = new Set([
|
|
34114
|
-
"react",
|
|
34115
|
-
"react-native",
|
|
34116
|
-
"next",
|
|
34117
|
-
"preact"
|
|
34118
|
-
]);
|
|
34119
|
-
const hasReactDependency = (packageJson) => {
|
|
34120
|
-
const allDependencies = {
|
|
34121
|
-
...packageJson.peerDependencies,
|
|
34122
|
-
...packageJson.dependencies,
|
|
34123
|
-
...packageJson.devDependencies
|
|
34124
|
-
};
|
|
34125
|
-
return Object.keys(allDependencies).some((packageName) => REACT_DEPENDENCY_NAMES.has(packageName));
|
|
34004
|
+
const wildcardIndex = cleanPattern.indexOf("*");
|
|
34005
|
+
const baseDirectory = Path.join(rootDirectory, cleanPattern.slice(0, wildcardIndex));
|
|
34006
|
+
const suffixAfterWildcard = cleanPattern.slice(wildcardIndex + 1);
|
|
34007
|
+
if (!isDirectory(baseDirectory)) return [];
|
|
34008
|
+
const resolved = [];
|
|
34009
|
+
for (const entry of readDirectoryEntries(baseDirectory)) {
|
|
34010
|
+
const entryPath = Path.join(baseDirectory, entry.name, suffixAfterWildcard);
|
|
34011
|
+
if (isDirectory(entryPath) && isFile(Path.join(entryPath, "package.json"))) resolved.push(entryPath);
|
|
34012
|
+
}
|
|
34013
|
+
return resolved;
|
|
34126
34014
|
};
|
|
34127
34015
|
const listWorkspacePackages = (rootDirectory) => {
|
|
34128
34016
|
const packageJsonPath = Path.join(rootDirectory, "package.json");
|
|
@@ -34154,6 +34042,208 @@ const listWorkspacePackages = (rootDirectory) => {
|
|
|
34154
34042
|
}
|
|
34155
34043
|
return packages;
|
|
34156
34044
|
};
|
|
34045
|
+
const REANIMATED_DEPENDENCY_NAME = "react-native-reanimated";
|
|
34046
|
+
const SHOPIFY_FLASH_LIST_PACKAGE_NAME = "@shopify/flash-list";
|
|
34047
|
+
const resolveWorkspaceDependencyVersion = ({ concreteVersion, packageName, rootDirectory, rootPackageJson, sections, workspaceDirectory, workspacePackageJson }) => {
|
|
34048
|
+
const dependencyDeclaration = getDependencyDeclaration({
|
|
34049
|
+
packageJson: workspacePackageJson,
|
|
34050
|
+
packageName,
|
|
34051
|
+
sections
|
|
34052
|
+
});
|
|
34053
|
+
if (!dependencyDeclaration.hasDeclaration) return null;
|
|
34054
|
+
return concreteVersion ?? resolveCatalogVersion(workspacePackageJson, packageName, workspaceDirectory, dependencyDeclaration.catalogReference) ?? resolveCatalogVersion(rootPackageJson, packageName, rootDirectory, dependencyDeclaration.catalogReference);
|
|
34055
|
+
};
|
|
34056
|
+
const shouldReplaceReactVersion = (currentVersion, nextVersion) => {
|
|
34057
|
+
if (!currentVersion) return true;
|
|
34058
|
+
const currentMajor = parseReactMajor(currentVersion);
|
|
34059
|
+
const nextMajor = parseReactMajor(nextVersion);
|
|
34060
|
+
if (currentMajor === null) return nextMajor !== null;
|
|
34061
|
+
if (nextMajor === null) return false;
|
|
34062
|
+
return nextMajor < currentMajor;
|
|
34063
|
+
};
|
|
34064
|
+
const evaluateManifestFacts = (facts, packageJson, directory) => {
|
|
34065
|
+
if (facts.expo.version === null) {
|
|
34066
|
+
const spec = getDependencySpec(packageJson, "expo");
|
|
34067
|
+
if (spec !== null) facts.expo = {
|
|
34068
|
+
version: spec,
|
|
34069
|
+
sourceDirectory: directory
|
|
34070
|
+
};
|
|
34071
|
+
}
|
|
34072
|
+
if (facts.next.version === null) {
|
|
34073
|
+
const spec = getDependencySpec(packageJson, "next");
|
|
34074
|
+
if (spec !== null) facts.next = {
|
|
34075
|
+
version: spec,
|
|
34076
|
+
sourceDirectory: directory
|
|
34077
|
+
};
|
|
34078
|
+
}
|
|
34079
|
+
if (facts.shopifyFlashList.version === null) {
|
|
34080
|
+
const spec = getDependencySpec(packageJson, SHOPIFY_FLASH_LIST_PACKAGE_NAME);
|
|
34081
|
+
if (spec !== null) facts.shopifyFlashList = {
|
|
34082
|
+
version: spec,
|
|
34083
|
+
sourceDirectory: directory
|
|
34084
|
+
};
|
|
34085
|
+
}
|
|
34086
|
+
if (facts.reanimatedVersion === null) {
|
|
34087
|
+
const spec = getDependencySpec(packageJson, REANIMATED_DEPENDENCY_NAME);
|
|
34088
|
+
if (spec !== null) facts.reanimatedVersion = spec;
|
|
34089
|
+
}
|
|
34090
|
+
facts.hasReactNativeAwarePackage = facts.hasReactNativeAwarePackage || isPackageJsonReactNativeAware(packageJson);
|
|
34091
|
+
facts.hasReanimatedAwarePackage = facts.hasReanimatedAwarePackage || isPackageJsonReanimatedAware(packageJson);
|
|
34092
|
+
};
|
|
34093
|
+
const collectWorkspaceFacts = (rootDirectory, rootPackageJson, { collectReactGroup }) => {
|
|
34094
|
+
const facts = {
|
|
34095
|
+
reactVersion: null,
|
|
34096
|
+
tailwindVersion: null,
|
|
34097
|
+
zodVersion: null,
|
|
34098
|
+
framework: "unknown",
|
|
34099
|
+
expo: {
|
|
34100
|
+
version: null,
|
|
34101
|
+
sourceDirectory: null
|
|
34102
|
+
},
|
|
34103
|
+
next: {
|
|
34104
|
+
version: null,
|
|
34105
|
+
sourceDirectory: null
|
|
34106
|
+
},
|
|
34107
|
+
shopifyFlashList: {
|
|
34108
|
+
version: null,
|
|
34109
|
+
sourceDirectory: null
|
|
34110
|
+
},
|
|
34111
|
+
hasReactNativeAwarePackage: false,
|
|
34112
|
+
hasReanimatedAwarePackage: false,
|
|
34113
|
+
reanimatedVersion: null
|
|
34114
|
+
};
|
|
34115
|
+
evaluateManifestFacts(facts, rootPackageJson, rootDirectory);
|
|
34116
|
+
let isReactGroupSettled = !collectReactGroup;
|
|
34117
|
+
const visitedDirectories = /* @__PURE__ */ new Set();
|
|
34118
|
+
for (const pattern of getWorkspacePatterns(rootDirectory, rootPackageJson)) {
|
|
34119
|
+
const directories = [...resolveWorkspaceDirectories(rootDirectory, pattern)].sort();
|
|
34120
|
+
for (const workspaceDirectory of directories) {
|
|
34121
|
+
if (visitedDirectories.has(workspaceDirectory)) continue;
|
|
34122
|
+
visitedDirectories.add(workspaceDirectory);
|
|
34123
|
+
const workspacePackageJson = readPackageJson(Path.join(workspaceDirectory, "package.json"));
|
|
34124
|
+
evaluateManifestFacts(facts, workspacePackageJson, workspaceDirectory);
|
|
34125
|
+
const info = extractDependencyInfo(workspacePackageJson);
|
|
34126
|
+
if (info.framework !== "unknown" && frameworkMergeRank(info.framework) < frameworkMergeRank(facts.framework)) facts.framework = info.framework;
|
|
34127
|
+
if (isReactGroupSettled) continue;
|
|
34128
|
+
const reactVersion = resolveWorkspaceDependencyVersion({
|
|
34129
|
+
concreteVersion: info.reactVersion,
|
|
34130
|
+
packageName: "react",
|
|
34131
|
+
rootDirectory,
|
|
34132
|
+
rootPackageJson,
|
|
34133
|
+
sections: REACT_SECTIONS,
|
|
34134
|
+
workspaceDirectory,
|
|
34135
|
+
workspacePackageJson
|
|
34136
|
+
});
|
|
34137
|
+
const tailwindVersion = resolveWorkspaceDependencyVersion({
|
|
34138
|
+
concreteVersion: info.tailwindVersion,
|
|
34139
|
+
packageName: "tailwindcss",
|
|
34140
|
+
rootDirectory,
|
|
34141
|
+
rootPackageJson,
|
|
34142
|
+
sections: TAILWIND_ZOD_SECTIONS,
|
|
34143
|
+
workspaceDirectory,
|
|
34144
|
+
workspacePackageJson
|
|
34145
|
+
});
|
|
34146
|
+
const zodVersion = resolveWorkspaceDependencyVersion({
|
|
34147
|
+
concreteVersion: info.zodVersion,
|
|
34148
|
+
packageName: "zod",
|
|
34149
|
+
rootDirectory,
|
|
34150
|
+
rootPackageJson,
|
|
34151
|
+
sections: TAILWIND_ZOD_SECTIONS,
|
|
34152
|
+
workspaceDirectory,
|
|
34153
|
+
workspacePackageJson
|
|
34154
|
+
});
|
|
34155
|
+
if (reactVersion && shouldReplaceReactVersion(facts.reactVersion, reactVersion)) facts.reactVersion = reactVersion;
|
|
34156
|
+
if (tailwindVersion && !facts.tailwindVersion) facts.tailwindVersion = tailwindVersion;
|
|
34157
|
+
if (zodVersion && !facts.zodVersion) facts.zodVersion = zodVersion;
|
|
34158
|
+
const settledReactMajor = parseReactMajor(facts.reactVersion);
|
|
34159
|
+
isReactGroupSettled = Boolean(facts.reactVersion && facts.tailwindVersion && facts.framework !== "unknown" && settledReactMajor !== null && settledReactMajor <= 17);
|
|
34160
|
+
}
|
|
34161
|
+
}
|
|
34162
|
+
return facts;
|
|
34163
|
+
};
|
|
34164
|
+
const findDependencyInfoFromMonorepoRoot = (directory) => {
|
|
34165
|
+
const monorepoRoot = findMonorepoRoot(directory);
|
|
34166
|
+
if (!monorepoRoot) return EMPTY_DEPENDENCY_INFO;
|
|
34167
|
+
const monorepoPackageJsonPath = Path.join(monorepoRoot, "package.json");
|
|
34168
|
+
if (!isFile(monorepoPackageJsonPath)) return EMPTY_DEPENDENCY_INFO;
|
|
34169
|
+
const rootPackageJson = readPackageJson(monorepoPackageJsonPath);
|
|
34170
|
+
const rootInfo = extractDependencyInfo(rootPackageJson);
|
|
34171
|
+
const leafPackageJsonPath = Path.join(directory, "package.json");
|
|
34172
|
+
const leafPackageJson = isFile(leafPackageJsonPath) ? readPackageJson(leafPackageJsonPath) : null;
|
|
34173
|
+
const leafReactDeclaration = leafPackageJson ? getDependencyDeclaration({
|
|
34174
|
+
packageJson: leafPackageJson,
|
|
34175
|
+
packageName: "react",
|
|
34176
|
+
sections: REACT_SECTIONS
|
|
34177
|
+
}) : null;
|
|
34178
|
+
const leafTailwindDeclaration = leafPackageJson ? getDependencyDeclaration({
|
|
34179
|
+
packageJson: leafPackageJson,
|
|
34180
|
+
packageName: "tailwindcss",
|
|
34181
|
+
sections: TAILWIND_ZOD_SECTIONS
|
|
34182
|
+
}) : null;
|
|
34183
|
+
const leafZodDeclaration = leafPackageJson ? getDependencyDeclaration({
|
|
34184
|
+
packageJson: leafPackageJson,
|
|
34185
|
+
packageName: "zod",
|
|
34186
|
+
sections: TAILWIND_ZOD_SECTIONS
|
|
34187
|
+
}) : null;
|
|
34188
|
+
const shouldUseReactFallback = !leafReactDeclaration?.hasDeclaration;
|
|
34189
|
+
const shouldUseTailwindFallback = leafTailwindDeclaration?.hasDeclaration ?? true;
|
|
34190
|
+
const shouldUseZodFallback = leafZodDeclaration?.hasDeclaration ?? true;
|
|
34191
|
+
const reactCatalogVersion = shouldUseReactFallback ? resolveCatalogVersion(rootPackageJson, "react", monorepoRoot, leafReactDeclaration?.catalogReference) : null;
|
|
34192
|
+
const tailwindCatalogVersion = shouldUseTailwindFallback ? resolveCatalogVersion(rootPackageJson, "tailwindcss", monorepoRoot, leafTailwindDeclaration?.catalogReference) : null;
|
|
34193
|
+
const zodCatalogVersion = shouldUseZodFallback ? resolveCatalogVersion(rootPackageJson, "zod", monorepoRoot, leafZodDeclaration?.catalogReference) : null;
|
|
34194
|
+
const workspaceFacts = collectWorkspaceFacts(monorepoRoot, rootPackageJson, { collectReactGroup: true });
|
|
34195
|
+
return {
|
|
34196
|
+
reactVersion: shouldUseReactFallback ? reactCatalogVersion ?? rootInfo.reactVersion ?? workspaceFacts.reactVersion : rootInfo.reactVersion ?? workspaceFacts.reactVersion,
|
|
34197
|
+
tailwindVersion: shouldUseTailwindFallback ? tailwindCatalogVersion ?? rootInfo.tailwindVersion ?? workspaceFacts.tailwindVersion : null,
|
|
34198
|
+
zodVersion: shouldUseZodFallback ? zodCatalogVersion ?? rootInfo.zodVersion ?? workspaceFacts.zodVersion : null,
|
|
34199
|
+
framework: rootInfo.framework !== "unknown" ? rootInfo.framework : workspaceFacts.framework
|
|
34200
|
+
};
|
|
34201
|
+
};
|
|
34202
|
+
/**
|
|
34203
|
+
* Last-resort React detection: locate React the way Node's `node_modules`
|
|
34204
|
+
* resolution would from `directory` — check each `node_modules/react` up the
|
|
34205
|
+
* tree — but bounded to the enclosing repo so a globally installed React can't
|
|
34206
|
+
* masquerade as the project's version. Stops at (and including) the nearest
|
|
34207
|
+
* project boundary — the git root or a monorepo root — and returns the first
|
|
34208
|
+
* installed `version`.
|
|
34209
|
+
*
|
|
34210
|
+
* Makes "React is installed and importable" ⇒ "React is detected" an invariant
|
|
34211
|
+
* for packages whose only React declaration is a version-less spec
|
|
34212
|
+
* (`workspace:*`, `*`, a dist-tag) or where React lives solely in a hoisted
|
|
34213
|
+
* `node_modules` the declaration walks never reach — the profile of a component
|
|
34214
|
+
* package inside a monorepo. Matching the `node_modules/react` entry rather than
|
|
34215
|
+
* its realpath means a project whose `node_modules` is symlinked elsewhere (a
|
|
34216
|
+
* Docker volume, a shared store) still counts, while a global install outside
|
|
34217
|
+
* the repo does not.
|
|
34218
|
+
*
|
|
34219
|
+
* Returns `null` when React isn't installed within the repo or its package.json
|
|
34220
|
+
* carries no version string. A tree with no boundary marker at all bounds the
|
|
34221
|
+
* search to `directory`, so a dependency hoisted above the scanned package there
|
|
34222
|
+
* is conservatively not adopted.
|
|
34223
|
+
*/
|
|
34224
|
+
/**
|
|
34225
|
+
* The nearest enclosing project boundary (git root or monorepo root), or
|
|
34226
|
+
* `directory` itself when the scan target sits outside any repo. Bounds the
|
|
34227
|
+
* node_modules walk so a React hoisted anywhere inside the repo counts, but a
|
|
34228
|
+
* React in an ancestor above the repo — a home-directory or global
|
|
34229
|
+
* `node_modules` — can't leak in.
|
|
34230
|
+
*/
|
|
34231
|
+
const findContainmentRoot = (directory) => {
|
|
34232
|
+
for (const ancestorDirectory of ancestorDirectories(directory, { includeStart: true })) if (isProjectBoundary(ancestorDirectory)) return ancestorDirectory;
|
|
34233
|
+
return directory;
|
|
34234
|
+
};
|
|
34235
|
+
const resolveInstalledReactVersion = (directory) => {
|
|
34236
|
+
const containmentRoot = findContainmentRoot(directory);
|
|
34237
|
+
for (const ancestorDirectory of ancestorDirectories(directory, { includeStart: true })) {
|
|
34238
|
+
const reactPackageJsonPath = Path.join(ancestorDirectory, "node_modules", "react", "package.json");
|
|
34239
|
+
if (isFile(reactPackageJsonPath)) {
|
|
34240
|
+
const installedVersion = readPackageJson(reactPackageJsonPath).version;
|
|
34241
|
+
return typeof installedVersion === "string" ? installedVersion : null;
|
|
34242
|
+
}
|
|
34243
|
+
if (ancestorDirectory === containmentRoot) return null;
|
|
34244
|
+
}
|
|
34245
|
+
return null;
|
|
34246
|
+
};
|
|
34157
34247
|
const toReactWorkspacePackages = (directories) => {
|
|
34158
34248
|
const packages = [];
|
|
34159
34249
|
for (const directory of directories) {
|
|
@@ -34226,14 +34316,13 @@ const clearProjectCache = () => {
|
|
|
34226
34316
|
};
|
|
34227
34317
|
/**
|
|
34228
34318
|
* Build a `ProjectInfo` for a directory that has no `package.json` of
|
|
34229
|
-
* its own — a
|
|
34230
|
-
*
|
|
34231
|
-
*
|
|
34232
|
-
*
|
|
34233
|
-
*
|
|
34234
|
-
*
|
|
34235
|
-
*
|
|
34236
|
-
* nothing to scan (no enclosing project and no source files of its own).
|
|
34319
|
+
* its own — a monorepo subfolder like `repo/packages`, or any loose tree
|
|
34320
|
+
* of TypeScript/JavaScript files. Dependency + framework detection is
|
|
34321
|
+
* inherited from the enclosing workspace root when there is one, so
|
|
34322
|
+
* scanning a subdirectory of a React monorepo still gets the React
|
|
34323
|
+
* capabilities; a standalone non-React directory simply scans with the
|
|
34324
|
+
* framework-agnostic rules. Throws only when the directory has nothing
|
|
34325
|
+
* to scan (no enclosing project and no source files of its own).
|
|
34237
34326
|
*/
|
|
34238
34327
|
const discoverProjectWithoutPackageJson = (directory) => {
|
|
34239
34328
|
const sourceFileCount = countSourceFiles(directory);
|
|
@@ -34269,7 +34358,9 @@ const discoverProjectWithoutPackageJson = (directory) => {
|
|
|
34269
34358
|
shopifyFlashListVersion: null,
|
|
34270
34359
|
shopifyFlashListMajorVersion: null,
|
|
34271
34360
|
hasReanimated: false,
|
|
34361
|
+
reanimatedVersion: null,
|
|
34272
34362
|
isPreES2023Target: hasOwnTsConfig && detectPreES2023Target(directory),
|
|
34363
|
+
isStaticExport: false,
|
|
34273
34364
|
sourceFileCount
|
|
34274
34365
|
};
|
|
34275
34366
|
};
|
|
@@ -34283,89 +34374,85 @@ const discoverProject = (directory) => {
|
|
|
34283
34374
|
return synthesized;
|
|
34284
34375
|
}
|
|
34285
34376
|
const packageJson = readPackageJson(packageJsonPath);
|
|
34286
|
-
|
|
34287
|
-
|
|
34288
|
-
|
|
34289
|
-
|
|
34290
|
-
|
|
34291
|
-
|
|
34292
|
-
|
|
34293
|
-
|
|
34294
|
-
|
|
34295
|
-
|
|
34296
|
-
|
|
34297
|
-
|
|
34298
|
-
|
|
34299
|
-
|
|
34300
|
-
|
|
34301
|
-
|
|
34302
|
-
|
|
34303
|
-
]
|
|
34304
|
-
});
|
|
34305
|
-
const zodDeclaration = getDependencyDeclaration({
|
|
34377
|
+
const rootInfo = extractDependencyInfo(packageJson);
|
|
34378
|
+
let framework = rootInfo.framework;
|
|
34379
|
+
const tracked = {
|
|
34380
|
+
react: {
|
|
34381
|
+
version: rootInfo.reactVersion,
|
|
34382
|
+
sections: REACT_SECTIONS
|
|
34383
|
+
},
|
|
34384
|
+
tailwindcss: {
|
|
34385
|
+
version: rootInfo.tailwindVersion,
|
|
34386
|
+
sections: TAILWIND_ZOD_SECTIONS
|
|
34387
|
+
},
|
|
34388
|
+
zod: {
|
|
34389
|
+
version: rootInfo.zodVersion,
|
|
34390
|
+
sections: TAILWIND_ZOD_SECTIONS
|
|
34391
|
+
}
|
|
34392
|
+
};
|
|
34393
|
+
const declarations = Object.fromEntries(Object.entries(tracked).map(([packageName, entry]) => [packageName, getDependencyDeclaration({
|
|
34306
34394
|
packageJson,
|
|
34307
|
-
packageName
|
|
34308
|
-
sections:
|
|
34309
|
-
|
|
34310
|
-
|
|
34311
|
-
|
|
34312
|
-
|
|
34313
|
-
|
|
34314
|
-
if (!
|
|
34315
|
-
if (!tailwindVersion && tailwindDeclaration.hasDeclaration) tailwindVersion = resolveCatalogVersion(packageJson, "tailwindcss", directory, tailwindDeclaration.catalogReference);
|
|
34316
|
-
if (!zodVersion && zodDeclaration.hasDeclaration) zodVersion = resolveCatalogVersion(packageJson, "zod", directory, zodDeclaration.catalogReference);
|
|
34317
|
-
if (!reactVersion || !tailwindVersion || !zodVersion) {
|
|
34395
|
+
packageName,
|
|
34396
|
+
sections: entry.sections
|
|
34397
|
+
})]));
|
|
34398
|
+
const fillFromCatalogs = (source, sourceDirectory) => {
|
|
34399
|
+
for (const [packageName, entry] of Object.entries(tracked)) if (!entry.version && declarations[packageName].hasDeclaration) entry.version = resolveCatalogVersion(source, packageName, sourceDirectory, declarations[packageName].catalogReference);
|
|
34400
|
+
};
|
|
34401
|
+
fillFromCatalogs(packageJson, directory);
|
|
34402
|
+
if (!tracked.react.version || !tracked.tailwindcss.version || !tracked.zod.version) {
|
|
34318
34403
|
const monorepoRoot = findMonorepoRoot(directory);
|
|
34319
34404
|
if (monorepoRoot) {
|
|
34320
34405
|
const monorepoPackageJsonPath = Path.join(monorepoRoot, "package.json");
|
|
34321
|
-
if (isFile(monorepoPackageJsonPath))
|
|
34322
|
-
const rootPackageJson = readPackageJson(monorepoPackageJsonPath);
|
|
34323
|
-
if (!reactVersion && reactDeclaration.hasDeclaration) reactVersion = resolveCatalogVersion(rootPackageJson, "react", monorepoRoot, reactDeclaration.catalogReference);
|
|
34324
|
-
if (!tailwindVersion && tailwindDeclaration.hasDeclaration) tailwindVersion = resolveCatalogVersion(rootPackageJson, "tailwindcss", monorepoRoot, tailwindDeclaration.catalogReference);
|
|
34325
|
-
if (!zodVersion && zodDeclaration.hasDeclaration) zodVersion = resolveCatalogVersion(rootPackageJson, "zod", monorepoRoot, zodDeclaration.catalogReference);
|
|
34326
|
-
}
|
|
34406
|
+
if (isFile(monorepoPackageJsonPath)) fillFromCatalogs(readPackageJson(monorepoPackageJsonPath), monorepoRoot);
|
|
34327
34407
|
}
|
|
34328
34408
|
}
|
|
34329
|
-
|
|
34330
|
-
|
|
34331
|
-
|
|
34332
|
-
|
|
34333
|
-
|
|
34334
|
-
|
|
34409
|
+
const shouldCollectReactGroup = !tracked.react.version || framework === "unknown";
|
|
34410
|
+
const workspaceFacts = collectWorkspaceFacts(directory, packageJson, { collectReactGroup: shouldCollectReactGroup });
|
|
34411
|
+
if (shouldCollectReactGroup) {
|
|
34412
|
+
tracked.react.version ||= workspaceFacts.reactVersion;
|
|
34413
|
+
tracked.tailwindcss.version ||= workspaceFacts.tailwindVersion;
|
|
34414
|
+
tracked.zod.version ||= workspaceFacts.zodVersion;
|
|
34415
|
+
if (framework === "unknown" && workspaceFacts.framework !== "unknown") framework = workspaceFacts.framework;
|
|
34335
34416
|
}
|
|
34336
|
-
if ((!
|
|
34417
|
+
if ((!tracked.react.version || framework === "unknown") && !isMonorepoRoot(directory)) {
|
|
34337
34418
|
const monorepoInfo = findDependencyInfoFromMonorepoRoot(directory);
|
|
34338
|
-
|
|
34339
|
-
|
|
34340
|
-
|
|
34419
|
+
tracked.react.version ||= monorepoInfo.reactVersion;
|
|
34420
|
+
tracked.tailwindcss.version ||= monorepoInfo.tailwindVersion;
|
|
34421
|
+
tracked.zod.version ||= monorepoInfo.zodVersion;
|
|
34341
34422
|
if (framework === "unknown") framework = monorepoInfo.framework;
|
|
34342
34423
|
}
|
|
34343
|
-
|
|
34344
|
-
|
|
34345
|
-
|
|
34424
|
+
for (const [packageName, entry] of Object.entries(tracked)) {
|
|
34425
|
+
const declaredVersion = declarations[packageName].version;
|
|
34426
|
+
if (!entry.version && declaredVersion && !isCatalogReference(declaredVersion)) entry.version = declaredVersion;
|
|
34427
|
+
}
|
|
34428
|
+
const { react, tailwindcss, zod } = tracked;
|
|
34429
|
+
let reactVersion = react.version;
|
|
34346
34430
|
if (!reactVersion || parseReactMajor(reactVersion) === null) reactVersion = resolveInstalledReactVersion(directory) ?? reactVersion;
|
|
34431
|
+
const tailwindVersion = tailwindcss.version;
|
|
34432
|
+
const zodVersion = zod.version;
|
|
34347
34433
|
const projectName = packageJson.name ?? Path.basename(directory);
|
|
34348
34434
|
const hasTypeScript = NFS.existsSync(Path.join(directory, "tsconfig.json"));
|
|
34349
34435
|
const sourceFileCount = countSourceFiles(directory);
|
|
34350
|
-
const hasReactNativeWorkspace = framework === "expo" || framework === "react-native" ||
|
|
34436
|
+
const hasReactNativeWorkspace = framework === "expo" || framework === "react-native" || workspaceFacts.hasReactNativeAwarePackage;
|
|
34351
34437
|
const expoVersion = hasReactNativeWorkspace ? resolveCatalogBackedDependencyVersion({
|
|
34352
34438
|
rootDirectory: directory,
|
|
34353
34439
|
rootPackageJson: packageJson,
|
|
34354
34440
|
packageName: "expo",
|
|
34355
|
-
version:
|
|
34441
|
+
version: workspaceFacts.expo.version
|
|
34356
34442
|
}) : null;
|
|
34357
34443
|
const shopifyFlashListVersion = hasReactNativeWorkspace ? resolveCatalogBackedDependencyVersion({
|
|
34358
34444
|
rootDirectory: directory,
|
|
34359
34445
|
rootPackageJson: packageJson,
|
|
34360
34446
|
packageName: SHOPIFY_FLASH_LIST_PACKAGE_NAME,
|
|
34361
|
-
version:
|
|
34447
|
+
version: workspaceFacts.shopifyFlashList.version
|
|
34362
34448
|
}) : null;
|
|
34363
|
-
const hasReanimated = hasReactNativeWorkspace &&
|
|
34449
|
+
const hasReanimated = hasReactNativeWorkspace && workspaceFacts.hasReanimatedAwarePackage;
|
|
34450
|
+
const reanimatedVersion = hasReanimated ? workspaceFacts.reanimatedVersion : null;
|
|
34364
34451
|
const nextjsVersion = framework === "nextjs" ? resolveCatalogBackedDependencyVersion({
|
|
34365
34452
|
rootDirectory: directory,
|
|
34366
34453
|
rootPackageJson: packageJson,
|
|
34367
34454
|
packageName: "next",
|
|
34368
|
-
version:
|
|
34455
|
+
version: workspaceFacts.next.version
|
|
34369
34456
|
}) : null;
|
|
34370
34457
|
const preactVersion = getPreactVersion(packageJson);
|
|
34371
34458
|
const isPreES2023Target = hasTypeScript && detectPreES2023Target(directory);
|
|
@@ -34376,7 +34463,7 @@ const discoverProject = (directory) => {
|
|
|
34376
34463
|
reactMajorVersion: resolveEffectiveReactMajor(reactVersion, packageJson),
|
|
34377
34464
|
tailwindVersion,
|
|
34378
34465
|
zodVersion,
|
|
34379
|
-
zodMajorVersion:
|
|
34466
|
+
zodMajorVersion: zodVersion === null ? null : getLowestDependencyMajor(zodVersion),
|
|
34380
34467
|
framework,
|
|
34381
34468
|
hasTypeScript,
|
|
34382
34469
|
hasReactCompiler: detectReactCompiler(directory, packageJson),
|
|
@@ -34390,7 +34477,9 @@ const discoverProject = (directory) => {
|
|
|
34390
34477
|
shopifyFlashListVersion,
|
|
34391
34478
|
shopifyFlashListMajorVersion: shopifyFlashListVersion === null ? null : getLowestDependencyMajor(shopifyFlashListVersion),
|
|
34392
34479
|
hasReanimated,
|
|
34480
|
+
reanimatedVersion,
|
|
34393
34481
|
isPreES2023Target,
|
|
34482
|
+
isStaticExport: framework === "nextjs" && detectNextjsStaticExport(workspaceFacts.next.sourceDirectory ?? directory),
|
|
34394
34483
|
sourceFileCount
|
|
34395
34484
|
};
|
|
34396
34485
|
cachedProjectInfos.set(directory, projectInfo);
|
|
@@ -34406,57 +34495,6 @@ const discoverProject = (directory) => {
|
|
|
34406
34495
|
* single source file has nothing to analyze.
|
|
34407
34496
|
*/
|
|
34408
34497
|
const isAnalyzableProject = (project) => project.reactVersion !== null || project.preactVersion !== null || project.sourceFileCount > 0;
|
|
34409
|
-
const MAJOR_MINOR_PATTERN = /(\d{1,4})\.(\d{1,4})/;
|
|
34410
|
-
const MAJOR_ONLY_PATTERN = /(\d{1,4})/;
|
|
34411
|
-
const UPPER_BOUND_COMPARATOR_PATTERN = /<=?\s{0,8}\d{1,4}(?:\.\d{1,4}){0,2}(?:-[^\s,|]+)?/g;
|
|
34412
|
-
const parseReactMajorMinor = (reactVersion) => {
|
|
34413
|
-
if (typeof reactVersion !== "string") return null;
|
|
34414
|
-
const trimmed = reactVersion.trim();
|
|
34415
|
-
if (trimmed.length === 0) return null;
|
|
34416
|
-
const lowerBoundsOnly = trimmed.replace(UPPER_BOUND_COMPARATOR_PATTERN, " ").trim();
|
|
34417
|
-
if (lowerBoundsOnly.length === 0) return null;
|
|
34418
|
-
const majorMinorMatch = lowerBoundsOnly.match(MAJOR_MINOR_PATTERN);
|
|
34419
|
-
if (majorMinorMatch) {
|
|
34420
|
-
const major = Number.parseInt(majorMinorMatch[1], 10);
|
|
34421
|
-
const minor = Number.parseInt(majorMinorMatch[2], 10);
|
|
34422
|
-
if (!Number.isFinite(major) || major <= 0) return null;
|
|
34423
|
-
if (!Number.isFinite(minor) || minor < 0) return null;
|
|
34424
|
-
return {
|
|
34425
|
-
major,
|
|
34426
|
-
minor
|
|
34427
|
-
};
|
|
34428
|
-
}
|
|
34429
|
-
const majorOnlyMatch = lowerBoundsOnly.match(MAJOR_ONLY_PATTERN);
|
|
34430
|
-
if (!majorOnlyMatch) return null;
|
|
34431
|
-
const major = Number.parseInt(majorOnlyMatch[1], 10);
|
|
34432
|
-
if (!Number.isFinite(major) || major <= 0) return null;
|
|
34433
|
-
return {
|
|
34434
|
-
major,
|
|
34435
|
-
minor: 0
|
|
34436
|
-
};
|
|
34437
|
-
};
|
|
34438
|
-
const isReactAtLeast = (detected, required) => {
|
|
34439
|
-
if (detected === null) return true;
|
|
34440
|
-
if (detected.major !== required.major) return detected.major > required.major;
|
|
34441
|
-
return detected.minor >= required.minor;
|
|
34442
|
-
};
|
|
34443
|
-
const parseLowerBoundVersion = (versionSpec) => import_semver.validRange(versionSpec) !== null ? import_semver.minVersion(versionSpec) : import_semver.coerce(versionSpec);
|
|
34444
|
-
const parseTailwindMajorMinor = (tailwindVersion) => {
|
|
34445
|
-
if (typeof tailwindVersion !== "string") return null;
|
|
34446
|
-
const trimmed = tailwindVersion.trim();
|
|
34447
|
-
if (trimmed.length === 0) return null;
|
|
34448
|
-
const lowerBound = parseLowerBoundVersion(trimmed);
|
|
34449
|
-
if (lowerBound === null || lowerBound.major <= 0) return null;
|
|
34450
|
-
return {
|
|
34451
|
-
major: lowerBound.major,
|
|
34452
|
-
minor: lowerBound.minor
|
|
34453
|
-
};
|
|
34454
|
-
};
|
|
34455
|
-
const isTailwindAtLeast = (detected, required) => {
|
|
34456
|
-
if (detected === null) return true;
|
|
34457
|
-
if (detected.major !== required.major) return detected.major > required.major;
|
|
34458
|
-
return detected.minor >= required.minor;
|
|
34459
|
-
};
|
|
34460
34498
|
const messageFromUnknown = (error) => error instanceof Error ? error.message : String(error);
|
|
34461
34499
|
var InvalidGlobPatternError = class extends Error {
|
|
34462
34500
|
pattern;
|
|
@@ -34582,6 +34620,9 @@ const buildRuleSeverityControls = (config) => {
|
|
|
34582
34620
|
...config.buckets !== void 0 ? { buckets: config.buckets } : {}
|
|
34583
34621
|
};
|
|
34584
34622
|
};
|
|
34623
|
+
const FOREIGN_INLINE_DISABLE_PATTERN = /(?:\/\/|\/\*)[ \t]*(eslint|oxlint)-disable-(next-line|line)(?![\w-])([^\r\n]*)/;
|
|
34624
|
+
const FOREIGN_BLOCK_DISABLE_PATTERN = /\/\*[ \t]*(eslint|oxlint)-disable(?![\w-])([^*\r\n]*)/;
|
|
34625
|
+
const FOREIGN_BLOCK_ENABLE_PATTERN = /\/\*[ \t]*(?:eslint|oxlint)-enable(?![\w-])([^*\r\n]*)/;
|
|
34585
34626
|
const LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY = {
|
|
34586
34627
|
"effect/no-adjust-state-on-prop-change": "react-doctor/no-adjust-state-on-prop-change",
|
|
34587
34628
|
"effect/no-chain-state-updates": "react-doctor/no-chain-state-updates",
|
|
@@ -34659,6 +34700,7 @@ const LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY = {
|
|
|
34659
34700
|
"react/jsx-no-new-function-as-prop": "react-doctor/jsx-no-new-function-as-prop",
|
|
34660
34701
|
"react/jsx-no-new-object-as-prop": "react-doctor/jsx-no-new-object-as-prop",
|
|
34661
34702
|
"react/jsx-no-script-url": "react-doctor/jsx-no-script-url",
|
|
34703
|
+
"react/jsx-no-target-blank": "react-doctor/jsx-no-target-blank",
|
|
34662
34704
|
"react/jsx-no-undef": "react-doctor/jsx-no-undef",
|
|
34663
34705
|
"react/jsx-no-useless-fragment": "react-doctor/jsx-no-useless-fragment",
|
|
34664
34706
|
"react/jsx-pascal-case": "react-doctor/jsx-pascal-case",
|
|
@@ -34732,9 +34774,6 @@ const tokenizeRuleList = (ruleList) => {
|
|
|
34732
34774
|
if (!ruleSection) return [];
|
|
34733
34775
|
return ruleSection.split(/[,\s]+/).map((token) => token.trim()).filter(Boolean);
|
|
34734
34776
|
};
|
|
34735
|
-
const FOREIGN_INLINE_DISABLE_PATTERN = /(?:\/\/|\/\*)[ \t]*(eslint|oxlint)-disable-(next-line|line)(?![\w-])([^\r\n]*)/;
|
|
34736
|
-
const FOREIGN_BLOCK_DISABLE_PATTERN = /\/\*[ \t]*(eslint|oxlint)-disable(?![\w-])([^*\r\n]*)/;
|
|
34737
|
-
const FOREIGN_BLOCK_ENABLE_PATTERN = /\/\*[ \t]*(?:eslint|oxlint)-enable(?![\w-])([^*\r\n]*)/;
|
|
34738
34777
|
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}\`).`;
|
|
34739
34778
|
const tokenMisnamesRule = (token, ruleId) => token !== ruleId && isSameRuleKey(token, ruleId);
|
|
34740
34779
|
const detectInlineNearMiss = (lines, diagnosticLineIndex, ruleId) => {
|
|
@@ -34788,6 +34827,53 @@ const detectForeignDisableNearMiss = (lines, diagnosticLineIndex, ruleId) => {
|
|
|
34788
34827
|
if (!ruleId.startsWith("react-doctor/")) return null;
|
|
34789
34828
|
return detectInlineNearMiss(lines, diagnosticLineIndex, ruleId) ?? detectBlockNearMiss(lines, diagnosticLineIndex, ruleId);
|
|
34790
34829
|
};
|
|
34830
|
+
const ESLINT_REACT_HOOKS_PLUGIN_PREFIX = "react-hooks/";
|
|
34831
|
+
const buildAcceptedTokens = (ruleId) => {
|
|
34832
|
+
const shortRuleId = ruleId.slice(15);
|
|
34833
|
+
return new Set([ruleId, `${ESLINT_REACT_HOOKS_PLUGIN_PREFIX}${shortRuleId}`]);
|
|
34834
|
+
};
|
|
34835
|
+
const listNamesRule = (ruleList, acceptedTokens) => tokenizeRuleList(ruleList).some((token) => acceptedTokens.has(token));
|
|
34836
|
+
const isInlineDisabled = (lines, diagnosticLineIndex, acceptedTokens) => {
|
|
34837
|
+
const candidates = [{
|
|
34838
|
+
line: lines[diagnosticLineIndex],
|
|
34839
|
+
requiredScope: "line"
|
|
34840
|
+
}, {
|
|
34841
|
+
line: lines[diagnosticLineIndex - 1],
|
|
34842
|
+
requiredScope: "next-line"
|
|
34843
|
+
}];
|
|
34844
|
+
for (const { line, requiredScope } of candidates) {
|
|
34845
|
+
const match = line?.match(FOREIGN_INLINE_DISABLE_PATTERN);
|
|
34846
|
+
if (!match) continue;
|
|
34847
|
+
const [, , scope, ruleList] = match;
|
|
34848
|
+
if (scope !== requiredScope) continue;
|
|
34849
|
+
if (listNamesRule(ruleList, acceptedTokens)) return true;
|
|
34850
|
+
}
|
|
34851
|
+
return false;
|
|
34852
|
+
};
|
|
34853
|
+
const isBlockDisabled = (lines, diagnosticLineIndex, acceptedTokens) => {
|
|
34854
|
+
let isRangeOpen = false;
|
|
34855
|
+
const lastLineIndex = Math.min(diagnosticLineIndex, lines.length - 1);
|
|
34856
|
+
for (let lineIndex = 0; lineIndex <= lastLineIndex; lineIndex++) {
|
|
34857
|
+
const line = lines[lineIndex];
|
|
34858
|
+
if (line === void 0 || !line.includes("-disable") && !line.includes("-enable")) continue;
|
|
34859
|
+
const disableMatch = line.match(FOREIGN_BLOCK_DISABLE_PATTERN);
|
|
34860
|
+
if (disableMatch && listNamesRule(disableMatch[2], acceptedTokens)) {
|
|
34861
|
+
isRangeOpen = true;
|
|
34862
|
+
continue;
|
|
34863
|
+
}
|
|
34864
|
+
const enableMatch = line.match(FOREIGN_BLOCK_ENABLE_PATTERN);
|
|
34865
|
+
if (enableMatch) {
|
|
34866
|
+
const enabledRules = tokenizeRuleList(enableMatch[1]);
|
|
34867
|
+
if (enabledRules.length === 0 || enabledRules.some((rule) => acceptedTokens.has(rule))) isRangeOpen = false;
|
|
34868
|
+
}
|
|
34869
|
+
}
|
|
34870
|
+
return isRangeOpen;
|
|
34871
|
+
};
|
|
34872
|
+
const isCompilerRuleForeignDisabled = (lines, diagnosticLineIndex, ruleId) => {
|
|
34873
|
+
if (!ruleId.startsWith("react-hooks-js/")) return false;
|
|
34874
|
+
const acceptedTokens = buildAcceptedTokens(ruleId);
|
|
34875
|
+
return isInlineDisabled(lines, diagnosticLineIndex, acceptedTokens) || isBlockDisabled(lines, diagnosticLineIndex, acceptedTokens);
|
|
34876
|
+
};
|
|
34791
34877
|
const JSX_OPENER_TAG_PATTERN = /<[A-Za-z][\w.]*/g;
|
|
34792
34878
|
const JSX_TAG_NAME_FOLLOW = /[A-Za-z]/;
|
|
34793
34879
|
const isOpenerMatchInsideLineComment = (line, openerCharIndex) => {
|
|
@@ -34931,6 +35017,11 @@ const evaluateSuppression = (lines, diagnosticLineIndex, ruleId) => {
|
|
|
34931
35017
|
isSuppressed: true,
|
|
34932
35018
|
nearMissHint: null
|
|
34933
35019
|
};
|
|
35020
|
+
if (isCompilerRuleForeignDisabled(lines, diagnosticLineIndex, ruleId)) return {
|
|
35021
|
+
isSuppressed: true,
|
|
35022
|
+
nearMissHint: null,
|
|
35023
|
+
isForeignDirective: true
|
|
35024
|
+
};
|
|
34934
35025
|
const directComments = findStackedDisableCommentsAbove(lines, diagnosticLineIndex);
|
|
34935
35026
|
if (hasChainSuppressor(directComments, ruleId)) return {
|
|
34936
35027
|
isSuppressed: true,
|
|
@@ -35212,12 +35303,18 @@ const collectStringSet = (values) => {
|
|
|
35212
35303
|
return new Set(values.filter((value) => typeof value === "string"));
|
|
35213
35304
|
};
|
|
35214
35305
|
const JSX_NO_UNDEF_IDENTIFIER_PATTERN = /^`([^`]+)`/;
|
|
35306
|
+
const NODE_CLI_PATH_SEGMENTS = new Set([
|
|
35307
|
+
"scripts",
|
|
35308
|
+
"bin",
|
|
35309
|
+
"tools"
|
|
35310
|
+
]);
|
|
35311
|
+
const hasNodeCliPathSegment = (filePath) => filePath.split(/[\\/]/).slice(0, -1).some((segment) => NODE_CLI_PATH_SEGMENTS.has(segment));
|
|
35215
35312
|
/**
|
|
35216
35313
|
* Pre-compiles every stateful filter and returns a single
|
|
35217
35314
|
* `apply(diagnostic)` closure that runs (in order):
|
|
35218
35315
|
*
|
|
35219
35316
|
* 1. auto-suppress (test-noise rules in test files; `migration-hint`
|
|
35220
|
-
* wins over `test-noise`)
|
|
35317
|
+
* wins over `test-noise`; `no-console` on Node CLI scripts)
|
|
35221
35318
|
* 2. severity overrides (top-level `rules` / `categories`, with
|
|
35222
35319
|
* `"off"` dropping)
|
|
35223
35320
|
* 3. warning suppression (only when `showWarnings` is false: drops every
|
|
@@ -35315,6 +35412,12 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
35315
35412
|
if (hasRawTextWrappers && isInsideStringOnlyWrapper(lines, diagnostic.line, diagnostic.column, rawTextWrapperComponentNames)) return true;
|
|
35316
35413
|
return false;
|
|
35317
35414
|
};
|
|
35415
|
+
const isNoConsoleOnNodeCliScript = (diagnostic) => {
|
|
35416
|
+
if (diagnostic.rule !== "no-console") return false;
|
|
35417
|
+
if (hasNodeCliPathSegment(diagnostic.filePath)) return true;
|
|
35418
|
+
const lines = getFileLines(diagnostic.filePath);
|
|
35419
|
+
return lines !== null && Boolean(lines[0]?.startsWith("#!"));
|
|
35420
|
+
};
|
|
35318
35421
|
const isJsxNoUndefSuppressedByConfig = (diagnostic) => {
|
|
35319
35422
|
if (!hasRuntimeGlobals) return false;
|
|
35320
35423
|
if (diagnostic.rule !== "jsx-no-undef") return false;
|
|
@@ -35324,6 +35427,7 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
35324
35427
|
return {
|
|
35325
35428
|
apply: (diagnostic) => {
|
|
35326
35429
|
if (shouldAutoSuppress(diagnostic)) return null;
|
|
35430
|
+
if (isNoConsoleOnNodeCliScript(diagnostic)) return null;
|
|
35327
35431
|
let current = diagnostic;
|
|
35328
35432
|
let explicitSeverityOverride;
|
|
35329
35433
|
let explicitRuleOverride;
|
|
@@ -35353,7 +35457,7 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
35353
35457
|
if (lines) {
|
|
35354
35458
|
const ruleIdentifier = `${current.plugin}/${current.rule}`;
|
|
35355
35459
|
const evaluation = evaluateSuppression(lines, current.line - 1, ruleIdentifier);
|
|
35356
|
-
if (evaluation.isSuppressed) return suppress(current, "inline");
|
|
35460
|
+
if (evaluation.isSuppressed) return suppress(current, evaluation.isForeignDirective ? "foreign-inline" : "inline");
|
|
35357
35461
|
if (evaluation.nearMissHint) current = {
|
|
35358
35462
|
...current,
|
|
35359
35463
|
suppressionHint: evaluation.nearMissHint
|
|
@@ -37092,64 +37196,64 @@ function* collectSecurityScanFiles(rootDirectory) {
|
|
|
37092
37196
|
}
|
|
37093
37197
|
}
|
|
37094
37198
|
}
|
|
37095
|
-
|
|
37096
|
-
|
|
37097
|
-
|
|
37098
|
-
|
|
37099
|
-
|
|
37100
|
-
|
|
37101
|
-
|
|
37199
|
+
const CLIENT_ONLY_FRAMEWORKS = new Set([
|
|
37200
|
+
"vite",
|
|
37201
|
+
"cra",
|
|
37202
|
+
"gatsby",
|
|
37203
|
+
"react-native",
|
|
37204
|
+
"expo"
|
|
37205
|
+
]);
|
|
37206
|
+
const addMajorLadder = (capabilities, name, major, earliest, latest) => {
|
|
37207
|
+
if (major === null) return;
|
|
37208
|
+
const cappedMajor = Math.min(major, latest);
|
|
37209
|
+
for (let candidate = earliest; candidate <= cappedMajor; candidate += 1) capabilities.add(`${name}:${candidate}`);
|
|
37210
|
+
};
|
|
37102
37211
|
const buildCapabilities = (project) => {
|
|
37103
37212
|
const capabilities = /* @__PURE__ */ new Set();
|
|
37104
37213
|
capabilities.add(project.framework);
|
|
37105
|
-
if (
|
|
37214
|
+
if (project.reactVersion !== null || project.preactVersion !== null) capabilities.add("react");
|
|
37106
37215
|
if (project.framework === "expo" || project.framework === "react-native" || project.hasReactNativeWorkspace) capabilities.add("react-native");
|
|
37107
37216
|
if (project.expoVersion !== null) capabilities.add("expo");
|
|
37217
|
+
if ((project.framework === "nextjs" || project.framework === "tanstack-start" || project.framework === "remix") && !project.isStaticExport) capabilities.add("server-actions");
|
|
37218
|
+
if (CLIENT_ONLY_FRAMEWORKS.has(project.framework)) capabilities.add("client-only");
|
|
37219
|
+
if (project.isStaticExport) capabilities.add("nextjs:static-export");
|
|
37108
37220
|
if (project.nextjsMajorVersion !== null && project.nextjsMajorVersion >= 15) capabilities.add("nextjs:15");
|
|
37109
|
-
|
|
37110
|
-
if (
|
|
37111
|
-
|
|
37112
|
-
|
|
37113
|
-
|
|
37114
|
-
|
|
37115
|
-
|
|
37116
|
-
|
|
37117
|
-
|
|
37118
|
-
|
|
37119
|
-
|
|
37120
|
-
if (project.
|
|
37121
|
-
capabilities.add("tailwind");
|
|
37122
|
-
if (isTailwindAtLeast(parseTailwindMajorMinor(project.tailwindVersion), {
|
|
37123
|
-
major: 3,
|
|
37124
|
-
minor: 4
|
|
37125
|
-
})) capabilities.add("tailwind:3.4");
|
|
37126
|
-
}
|
|
37127
|
-
if (project.zodVersion !== null) {
|
|
37128
|
-
capabilities.add("zod");
|
|
37129
|
-
if (project.zodMajorVersion !== null && project.zodMajorVersion >= 4) capabilities.add("zod:4");
|
|
37130
|
-
}
|
|
37221
|
+
addMajorLadder(capabilities, "react", project.reactMajorVersion, 17, 30);
|
|
37222
|
+
if (project.reactMajorVersion !== null && project.reactMajorVersion >= 19 && isMajorMinorAtLeast(parseReactMajorMinor(project.reactVersion), {
|
|
37223
|
+
major: 19,
|
|
37224
|
+
minor: 2
|
|
37225
|
+
})) capabilities.add("react:19.2");
|
|
37226
|
+
if (project.tailwindVersion !== null) capabilities.add("tailwind");
|
|
37227
|
+
if (project.tailwindVersion !== null && isMajorMinorAtLeast(parseTailwindMajorMinor(project.tailwindVersion), {
|
|
37228
|
+
major: 3,
|
|
37229
|
+
minor: 4
|
|
37230
|
+
})) capabilities.add("tailwind:3.4");
|
|
37231
|
+
if (project.zodVersion !== null) capabilities.add("zod");
|
|
37232
|
+
if (project.zodMajorVersion !== null && project.zodMajorVersion >= 4) capabilities.add("zod:4");
|
|
37131
37233
|
if (project.isPreES2023Target) capabilities.add("pre-es2023");
|
|
37132
37234
|
if (project.hasReactCompiler) capabilities.add("react-compiler");
|
|
37133
37235
|
if (project.hasTanStackQuery) capabilities.add("tanstack-query");
|
|
37134
37236
|
if (project.hasTypeScript) capabilities.add("typescript");
|
|
37135
|
-
if (project.preactVersion !== null)
|
|
37136
|
-
|
|
37137
|
-
|
|
37138
|
-
|
|
37139
|
-
|
|
37140
|
-
|
|
37141
|
-
|
|
37142
|
-
|
|
37143
|
-
|
|
37237
|
+
if (project.preactVersion !== null) capabilities.add("preact");
|
|
37238
|
+
addMajorLadder(capabilities, "preact", project.preactMajorVersion, 10, 20);
|
|
37239
|
+
if (project.preactVersion !== null && project.reactVersion === null) capabilities.add("pure-preact");
|
|
37240
|
+
return capabilities;
|
|
37241
|
+
};
|
|
37242
|
+
const capabilitiesByProject = /* @__PURE__ */ new WeakMap();
|
|
37243
|
+
const getCapabilities = (project) => {
|
|
37244
|
+
const cached = capabilitiesByProject.get(project);
|
|
37245
|
+
if (cached !== void 0) return cached;
|
|
37246
|
+
const capabilities = buildCapabilities(project);
|
|
37247
|
+
capabilitiesByProject.set(project, capabilities);
|
|
37144
37248
|
return capabilities;
|
|
37145
37249
|
};
|
|
37146
|
-
const shouldEnableRule = (requires, tags, capabilities, ignoredTags,
|
|
37250
|
+
const shouldEnableRule = (requires, tags, capabilities, ignoredTags, disabledWhen) => {
|
|
37147
37251
|
if (requires) {
|
|
37148
37252
|
for (const capability of requires) if (!capabilities.has(capability)) return false;
|
|
37149
37253
|
}
|
|
37150
37254
|
if (tags?.includes("react-jsx-only") && !capabilities.has("react")) return false;
|
|
37151
|
-
if (
|
|
37152
|
-
for (const capability of
|
|
37255
|
+
if (disabledWhen) {
|
|
37256
|
+
for (const capability of disabledWhen) if (capabilities.has(capability)) return false;
|
|
37153
37257
|
}
|
|
37154
37258
|
if (tags) {
|
|
37155
37259
|
for (const tag of tags) if (ignoredTags.has(tag)) return false;
|
|
@@ -37160,14 +37264,14 @@ const yieldToEventLoop = () => new Promise((resolve) => {
|
|
|
37160
37264
|
setImmediate(resolve);
|
|
37161
37265
|
});
|
|
37162
37266
|
const createSecurityScanSession = (rootDirectory, options) => {
|
|
37163
|
-
const capabilities = options.project ?
|
|
37267
|
+
const capabilities = options.project ? getCapabilities(options.project) : /* @__PURE__ */ new Set();
|
|
37164
37268
|
const ignoredTags = options.ignoredTags ?? /* @__PURE__ */ new Set();
|
|
37165
37269
|
const enabledScanRules = REACT_DOCTOR_RULES.flatMap((entry) => {
|
|
37166
37270
|
const rule = entry.rule;
|
|
37167
37271
|
const scan = rule.scan;
|
|
37168
37272
|
if (typeof scan !== "function") return [];
|
|
37169
37273
|
if (rule.defaultEnabled === false) return [];
|
|
37170
|
-
if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.
|
|
37274
|
+
if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.disabledWhen)) return [];
|
|
37171
37275
|
return [{
|
|
37172
37276
|
entry,
|
|
37173
37277
|
scan,
|
|
@@ -37187,6 +37291,7 @@ const createSecurityScanSession = (rootDirectory, options) => {
|
|
|
37187
37291
|
return status === true;
|
|
37188
37292
|
};
|
|
37189
37293
|
const scanFileByRule = function* (file) {
|
|
37294
|
+
if (file === null) return;
|
|
37190
37295
|
for (const { entry, scan, committedFilesOnly } of enabledScanRules) {
|
|
37191
37296
|
for (const finding of scan(file)) {
|
|
37192
37297
|
if (committedFilesOnly && isFileGitIgnored(file)) continue;
|
|
@@ -39067,7 +39172,7 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
39067
39172
|
const reactCompilerRules = reactHooksJsPlugin ? applyRuleSeverityControls(filterRulesToAvailable(REACT_COMPILER_RULES, "react-hooks-js", reactHooksJsPlugin.availableRuleNames), severityControls) : {};
|
|
39068
39173
|
const jsPlugins = [];
|
|
39069
39174
|
if (reactHooksJsPlugin) jsPlugins.push(reactHooksJsPlugin.entry);
|
|
39070
|
-
const capabilities =
|
|
39175
|
+
const capabilities = getCapabilities(project);
|
|
39071
39176
|
const enabledReactDoctorRules = {};
|
|
39072
39177
|
for (const registryEntry of REACT_DOCTOR_RULES) {
|
|
39073
39178
|
const rule = reactDoctorPlugin.rules[registryEntry.id];
|
|
@@ -39078,7 +39183,7 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
39078
39183
|
if (rule.scan !== void 0) continue;
|
|
39079
39184
|
if (customRulesOnly && registryEntry.originallyExternal) continue;
|
|
39080
39185
|
if (rule.framework !== "global" && !rule.requires) continue;
|
|
39081
|
-
if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.
|
|
39186
|
+
if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.disabledWhen)) continue;
|
|
39082
39187
|
const explicitRuleOverride = resolveRuleSeverityOverride({ ruleKey: registryEntry.key }, severityControls);
|
|
39083
39188
|
if (rule.defaultEnabled === false && explicitRuleOverride === void 0) continue;
|
|
39084
39189
|
const severity = resolveRuleSeverityOverride({
|
|
@@ -39109,6 +39214,7 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
39109
39214
|
settings: { "react-doctor": {
|
|
39110
39215
|
framework: project.framework,
|
|
39111
39216
|
rootDirectory: resolveSettingsRootDirectory(project.rootDirectory),
|
|
39217
|
+
capabilities: [...capabilities].sort(),
|
|
39112
39218
|
...project.shopifyFlashListMajorVersion !== null ? { shopifyFlashListMajorVersion: project.shopifyFlashListMajorVersion } : {},
|
|
39113
39219
|
...serverAuthFunctionNames && serverAuthFunctionNames.length > 0 ? { serverAuthFunctionNames: [...serverAuthFunctionNames] } : {}
|
|
39114
39220
|
} },
|
|
@@ -39399,25 +39505,18 @@ const mapWithConcurrency = async (items, concurrency, task) => {
|
|
|
39399
39505
|
if (errors.length > 0) throw errors[0];
|
|
39400
39506
|
return results;
|
|
39401
39507
|
};
|
|
39402
|
-
const getPublicEnvPrefix = (framework) => {
|
|
39403
|
-
switch (framework) {
|
|
39404
|
-
case "nextjs": return "NEXT_PUBLIC_*";
|
|
39405
|
-
case "vite":
|
|
39406
|
-
case "tanstack-start": return "VITE_*";
|
|
39407
|
-
case "cra": return "REACT_APP_*";
|
|
39408
|
-
case "gatsby": return "GATSBY_*";
|
|
39409
|
-
default: return null;
|
|
39410
|
-
}
|
|
39411
|
-
};
|
|
39412
|
-
const buildNoSecretsRecommendation = (project, fallbackRecommendation) => {
|
|
39413
|
-
const publicEnvPrefix = getPublicEnvPrefix(project.framework);
|
|
39414
|
-
if (!publicEnvPrefix) return fallbackRecommendation;
|
|
39415
|
-
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`;
|
|
39416
|
-
};
|
|
39417
39508
|
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";
|
|
39509
|
+
const ACCESSORS_MIN_VERSION = "3.15.0";
|
|
39510
|
+
const supportsAccessors = (reanimatedVersion) => {
|
|
39511
|
+
if (reanimatedVersion === null) return true;
|
|
39512
|
+
const lowerBound = import_semver.validRange(reanimatedVersion) !== null ? import_semver.minVersion(reanimatedVersion) : import_semver.coerce(reanimatedVersion);
|
|
39513
|
+
if (lowerBound === null) return true;
|
|
39514
|
+
return import_semver.gte(lowerBound, ACCESSORS_MIN_VERSION);
|
|
39515
|
+
};
|
|
39418
39516
|
const appendReanimatedSharedValueHint = (help, rule, project) => {
|
|
39419
39517
|
if (rule !== "immutability") return help;
|
|
39420
39518
|
if (!project.hasReanimated) return help;
|
|
39519
|
+
if (!supportsAccessors(project.reanimatedVersion)) return help;
|
|
39421
39520
|
if (!help) return REANIMATED_SHARED_VALUE_HINT;
|
|
39422
39521
|
return `${help}\n\n${REANIMATED_SHARED_VALUE_HINT}`;
|
|
39423
39522
|
};
|
|
@@ -39579,13 +39678,13 @@ const REACT_USE_BINDING_RESOLUTION = {
|
|
|
39579
39678
|
isReactUseBinding: true,
|
|
39580
39679
|
isReactNamespaceBinding: false
|
|
39581
39680
|
};
|
|
39582
|
-
const getScriptKind = (filename) => {
|
|
39681
|
+
const getScriptKind$2 = (filename) => {
|
|
39583
39682
|
if (filename.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
39584
39683
|
if (filename.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
39585
39684
|
if (filename.endsWith(".ts")) return ts.ScriptKind.TS;
|
|
39586
39685
|
return ts.ScriptKind.JS;
|
|
39587
39686
|
};
|
|
39588
|
-
const getUtf16Offset = (sourceText, utf8Offset) => Buffer.from(sourceText).subarray(0, utf8Offset).toString("utf8").length;
|
|
39687
|
+
const getUtf16Offset$1 = (sourceText, utf8Offset) => Buffer.from(sourceText).subarray(0, utf8Offset).toString("utf8").length;
|
|
39589
39688
|
const unwrapExpression = (expression) => {
|
|
39590
39689
|
let currentExpression = expression;
|
|
39591
39690
|
while (ts.isParenthesizedExpression(currentExpression) || ts.isAsExpression(currentExpression) || ts.isSatisfiesExpression(currentExpression) || ts.isNonNullExpression(currentExpression) || ts.isTypeAssertionExpression(currentExpression)) currentExpression = currentExpression.expression;
|
|
@@ -39834,8 +39933,8 @@ const findUseCallIdentifier = (sourceFile, useOffset) => {
|
|
|
39834
39933
|
return matchedIdentifier;
|
|
39835
39934
|
};
|
|
39836
39935
|
const resolveUseCallBinding = (sourceText, filename, utf8Offset) => {
|
|
39837
|
-
const sourceFile = ts.createSourceFile(filename, sourceText, ts.ScriptTarget.Latest, true, getScriptKind(filename));
|
|
39838
|
-
const useIdentifier = findUseCallIdentifier(sourceFile, getUtf16Offset(sourceText, utf8Offset));
|
|
39936
|
+
const sourceFile = ts.createSourceFile(filename, sourceText, ts.ScriptTarget.Latest, true, getScriptKind$2(filename));
|
|
39937
|
+
const useIdentifier = findUseCallIdentifier(sourceFile, getUtf16Offset$1(sourceText, utf8Offset));
|
|
39839
39938
|
if (!useIdentifier) return null;
|
|
39840
39939
|
return resolveIdentifierBinding(useIdentifier, collectReactImportBindings(sourceFile), sourceFile);
|
|
39841
39940
|
};
|
|
@@ -39856,6 +39955,201 @@ const shouldSuppressLocalUseHookDiagnostic = (diagnostic, rootDirectory) => {
|
|
|
39856
39955
|
const bindingResolution = resolveUseCallBinding(sourceText, absolutePath, primaryLabel.span.offset);
|
|
39857
39956
|
return bindingResolution !== null && !bindingResolution.isReactUseBinding;
|
|
39858
39957
|
};
|
|
39958
|
+
const SUPPRESSED_COMPILER_CODES = new Set(["react-hooks-js(immutability)", "react-hooks-js(refs)"]);
|
|
39959
|
+
const WORKLET_DIRECTIVE = "worklet";
|
|
39960
|
+
const WORKLET_ACCEPTING_CALLEES = new Set([
|
|
39961
|
+
"useAnimatedStyle",
|
|
39962
|
+
"useAnimatedProps",
|
|
39963
|
+
"useDerivedValue",
|
|
39964
|
+
"useAnimatedReaction",
|
|
39965
|
+
"useAnimatedScrollHandler",
|
|
39966
|
+
"useAnimatedGestureHandler",
|
|
39967
|
+
"useFrameCallback",
|
|
39968
|
+
"runOnUI",
|
|
39969
|
+
"executeOnUIRuntimeSync"
|
|
39970
|
+
]);
|
|
39971
|
+
const GESTURE_ROOT_IDENTIFIER = "Gesture";
|
|
39972
|
+
const getScriptKind$1 = (filename) => {
|
|
39973
|
+
if (filename.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
39974
|
+
if (filename.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
39975
|
+
if (filename.endsWith(".ts")) return ts.ScriptKind.TS;
|
|
39976
|
+
return ts.ScriptKind.JS;
|
|
39977
|
+
};
|
|
39978
|
+
const getUtf16Offset = (sourceText, utf8Offset) => Buffer.from(sourceText).subarray(0, utf8Offset).toString("utf8").length;
|
|
39979
|
+
const hasWorkletDirective = (node) => {
|
|
39980
|
+
if (!ts.isFunctionDeclaration(node) && !ts.isFunctionExpression(node) && !ts.isArrowFunction(node) && !ts.isMethodDeclaration(node)) return false;
|
|
39981
|
+
const body = node.body;
|
|
39982
|
+
if (!body || !ts.isBlock(body)) return false;
|
|
39983
|
+
for (const statement of body.statements) {
|
|
39984
|
+
if (!ts.isExpressionStatement(statement)) return false;
|
|
39985
|
+
if (!ts.isStringLiteral(statement.expression)) return false;
|
|
39986
|
+
if (statement.expression.text === WORKLET_DIRECTIVE) return true;
|
|
39987
|
+
}
|
|
39988
|
+
return false;
|
|
39989
|
+
};
|
|
39990
|
+
const rootIdentifierOfCallChain = (expression) => {
|
|
39991
|
+
let currentExpression = expression;
|
|
39992
|
+
while (true) {
|
|
39993
|
+
if (ts.isIdentifier(currentExpression)) return currentExpression.text;
|
|
39994
|
+
if (ts.isPropertyAccessExpression(currentExpression)) {
|
|
39995
|
+
currentExpression = currentExpression.expression;
|
|
39996
|
+
continue;
|
|
39997
|
+
}
|
|
39998
|
+
if (ts.isCallExpression(currentExpression)) {
|
|
39999
|
+
currentExpression = currentExpression.expression;
|
|
40000
|
+
continue;
|
|
40001
|
+
}
|
|
40002
|
+
return null;
|
|
40003
|
+
}
|
|
40004
|
+
};
|
|
40005
|
+
const isWorkletAcceptingCall = (callExpression) => {
|
|
40006
|
+
const callee = callExpression.expression;
|
|
40007
|
+
if (ts.isIdentifier(callee)) return WORKLET_ACCEPTING_CALLEES.has(callee.text);
|
|
40008
|
+
if (ts.isPropertyAccessExpression(callee)) {
|
|
40009
|
+
if (WORKLET_ACCEPTING_CALLEES.has(callee.name.text)) return true;
|
|
40010
|
+
return rootIdentifierOfCallChain(callee) === GESTURE_ROOT_IDENTIFIER;
|
|
40011
|
+
}
|
|
40012
|
+
return false;
|
|
40013
|
+
};
|
|
40014
|
+
const isFunctionWorklet = (functionNode) => {
|
|
40015
|
+
if (hasWorkletDirective(functionNode)) return true;
|
|
40016
|
+
const parent = functionNode.parent;
|
|
40017
|
+
return ts.isCallExpression(parent) && parent.arguments.some((argument) => argument === functionNode) && isWorkletAcceptingCall(parent);
|
|
40018
|
+
};
|
|
40019
|
+
const findNodeAtOffset = (sourceFile, targetOffset) => {
|
|
40020
|
+
let matchedNode = null;
|
|
40021
|
+
const visit = (node) => {
|
|
40022
|
+
if (node.getStart(sourceFile) > targetOffset || node.getEnd() <= targetOffset) return;
|
|
40023
|
+
matchedNode = node;
|
|
40024
|
+
ts.forEachChild(node, visit);
|
|
40025
|
+
};
|
|
40026
|
+
visit(sourceFile);
|
|
40027
|
+
return matchedNode;
|
|
40028
|
+
};
|
|
40029
|
+
const isOffsetInsideWorklet = (sourceFile, targetOffset) => {
|
|
40030
|
+
let currentNode = findNodeAtOffset(sourceFile, targetOffset);
|
|
40031
|
+
while (currentNode) {
|
|
40032
|
+
if (ts.isFunctionLike(currentNode) && isFunctionWorklet(currentNode)) return true;
|
|
40033
|
+
currentNode = currentNode.parent;
|
|
40034
|
+
}
|
|
40035
|
+
return false;
|
|
40036
|
+
};
|
|
40037
|
+
const shouldSuppressCompilerFindingInWorklet = (diagnostic, project, rootDirectory) => {
|
|
40038
|
+
if (!SUPPRESSED_COMPILER_CODES.has(diagnostic.code)) return false;
|
|
40039
|
+
if (!project.hasReanimated) return false;
|
|
40040
|
+
const primaryLabel = diagnostic.labels[0];
|
|
40041
|
+
if (!primaryLabel) return false;
|
|
40042
|
+
const absolutePath = Path.isAbsolute(diagnostic.filename) ? diagnostic.filename : Path.join(rootDirectory, diagnostic.filename);
|
|
40043
|
+
let sourceText;
|
|
40044
|
+
try {
|
|
40045
|
+
sourceText = NFS.readFileSync(absolutePath, "utf-8");
|
|
40046
|
+
} catch {
|
|
40047
|
+
return false;
|
|
40048
|
+
}
|
|
40049
|
+
return isOffsetInsideWorklet(ts.createSourceFile(absolutePath, sourceText, ts.ScriptTarget.Latest, true, getScriptKind$1(absolutePath)), getUtf16Offset(sourceText, primaryLabel.span.offset));
|
|
40050
|
+
};
|
|
40051
|
+
const MANUAL_MEMOIZATION_PLUGIN = "react-doctor";
|
|
40052
|
+
const MANUAL_MEMOIZATION_RULE = "react-compiler-no-manual-memoization";
|
|
40053
|
+
const REACT_COMPILER_PLUGIN = "react-hooks-js";
|
|
40054
|
+
const getScriptKind = (filename) => {
|
|
40055
|
+
if (filename.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
40056
|
+
if (filename.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
40057
|
+
if (filename.endsWith(".ts")) return ts.ScriptKind.TS;
|
|
40058
|
+
return ts.ScriptKind.JS;
|
|
40059
|
+
};
|
|
40060
|
+
const isManualMemoizationDiagnostic = (diagnostic) => diagnostic.plugin === MANUAL_MEMOIZATION_PLUGIN && diagnostic.rule === MANUAL_MEMOIZATION_RULE;
|
|
40061
|
+
const nodeLineRange = (sourceFile, node) => ({
|
|
40062
|
+
startLine: sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1,
|
|
40063
|
+
endLine: sourceFile.getLineAndCharacterOfPosition(node.getEnd()).line + 1
|
|
40064
|
+
});
|
|
40065
|
+
const findOutermostEnclosingFunctionRange = (sourceFile, targetLine) => {
|
|
40066
|
+
let foundRange = null;
|
|
40067
|
+
const visit = (node) => {
|
|
40068
|
+
if (foundRange) return;
|
|
40069
|
+
if (ts.isFunctionLike(node)) {
|
|
40070
|
+
const range = nodeLineRange(sourceFile, node);
|
|
40071
|
+
if (range.startLine <= targetLine && targetLine <= range.endLine) foundRange = range;
|
|
40072
|
+
return;
|
|
40073
|
+
}
|
|
40074
|
+
ts.forEachChild(node, visit);
|
|
40075
|
+
};
|
|
40076
|
+
visit(sourceFile);
|
|
40077
|
+
return foundRange;
|
|
40078
|
+
};
|
|
40079
|
+
const NEWLINE_BYTE = 10;
|
|
40080
|
+
const lineOfUtf8Offset = (sourceBuffer, utf8Offset) => {
|
|
40081
|
+
let lineNumber = 1;
|
|
40082
|
+
const scanEnd = Math.min(utf8Offset, sourceBuffer.length);
|
|
40083
|
+
for (let byteIndex = 0; byteIndex < scanEnd; byteIndex++) if (sourceBuffer[byteIndex] === NEWLINE_BYTE) lineNumber++;
|
|
40084
|
+
return lineNumber;
|
|
40085
|
+
};
|
|
40086
|
+
/**
|
|
40087
|
+
* Drops `react-doctor/react-compiler-no-manual-memoization` diagnostics
|
|
40088
|
+
* whose enclosing function also carries a `react-hooks-js` (React
|
|
40089
|
+
* Compiler) diagnostic. Every react-hooks-js finding marks a function the
|
|
40090
|
+
* compiler could NOT compile (a bail-out / skip / preserve-manual-
|
|
40091
|
+
* memoization failure), so "React Compiler already caches every value
|
|
40092
|
+
* here" is false for that function and the manual memoization is
|
|
40093
|
+
* load-bearing, not dead weight.
|
|
40094
|
+
*
|
|
40095
|
+
* Matching is per enclosing function, not per file: a bailed-out
|
|
40096
|
+
* component must not silence redundant-memo findings in a sibling
|
|
40097
|
+
* component the compiler did optimize.
|
|
40098
|
+
*
|
|
40099
|
+
* Module-scope `memo(() => …)` wrappers have no enclosing function; for
|
|
40100
|
+
* those the memo call's own span is the containment range, so a bail-out
|
|
40101
|
+
* inside the inline-wrapped component still suppresses. Every failure
|
|
40102
|
+
* path (unreadable file, missing span) fails open and keeps the
|
|
40103
|
+
* diagnostic.
|
|
40104
|
+
*/
|
|
40105
|
+
const suppressMemoizationInBailedOutFunctions = (diagnostics, rootDirectory) => {
|
|
40106
|
+
const compilerDiagnosticLinesByFile = /* @__PURE__ */ new Map();
|
|
40107
|
+
let hasManualMemoizationDiagnostic = false;
|
|
40108
|
+
for (const diagnostic of diagnostics) {
|
|
40109
|
+
if (isManualMemoizationDiagnostic(diagnostic)) {
|
|
40110
|
+
hasManualMemoizationDiagnostic = true;
|
|
40111
|
+
continue;
|
|
40112
|
+
}
|
|
40113
|
+
if (diagnostic.plugin !== REACT_COMPILER_PLUGIN || diagnostic.line <= 0) continue;
|
|
40114
|
+
const lines = compilerDiagnosticLinesByFile.get(diagnostic.filePath);
|
|
40115
|
+
if (lines) lines.push(diagnostic.line);
|
|
40116
|
+
else compilerDiagnosticLinesByFile.set(diagnostic.filePath, [diagnostic.line]);
|
|
40117
|
+
}
|
|
40118
|
+
if (!hasManualMemoizationDiagnostic || compilerDiagnosticLinesByFile.size === 0) return diagnostics;
|
|
40119
|
+
const parsedFileCache = /* @__PURE__ */ new Map();
|
|
40120
|
+
const readAndParse = (filePath) => {
|
|
40121
|
+
const cached = parsedFileCache.get(filePath);
|
|
40122
|
+
if (cached !== void 0) return cached;
|
|
40123
|
+
const absolutePath = Path.isAbsolute(filePath) ? filePath : Path.resolve(rootDirectory || ".", filePath);
|
|
40124
|
+
let parsed;
|
|
40125
|
+
try {
|
|
40126
|
+
const buffer = NFS.readFileSync(absolutePath);
|
|
40127
|
+
parsed = {
|
|
40128
|
+
sourceFile: ts.createSourceFile(absolutePath, buffer.toString("utf8"), ts.ScriptTarget.Latest, true, getScriptKind(absolutePath)),
|
|
40129
|
+
buffer
|
|
40130
|
+
};
|
|
40131
|
+
} catch {
|
|
40132
|
+
parsed = null;
|
|
40133
|
+
}
|
|
40134
|
+
parsedFileCache.set(filePath, parsed);
|
|
40135
|
+
return parsed;
|
|
40136
|
+
};
|
|
40137
|
+
const isInsideBailedOutFunction = (diagnostic) => {
|
|
40138
|
+
const compilerLines = compilerDiagnosticLinesByFile.get(diagnostic.filePath);
|
|
40139
|
+
if (!compilerLines || diagnostic.line <= 0) return false;
|
|
40140
|
+
const parsed = readAndParse(diagnostic.filePath);
|
|
40141
|
+
if (!parsed) return false;
|
|
40142
|
+
let containmentRange = findOutermostEnclosingFunctionRange(parsed.sourceFile, diagnostic.line);
|
|
40143
|
+
if (!containmentRange && diagnostic.offset !== void 0 && diagnostic.length !== void 0) containmentRange = {
|
|
40144
|
+
startLine: diagnostic.line,
|
|
40145
|
+
endLine: lineOfUtf8Offset(parsed.buffer, diagnostic.offset + diagnostic.length)
|
|
40146
|
+
};
|
|
40147
|
+
if (!containmentRange) return false;
|
|
40148
|
+
const { startLine, endLine } = containmentRange;
|
|
40149
|
+
return compilerLines.some((compilerLine) => startLine <= compilerLine && compilerLine <= endLine);
|
|
40150
|
+
};
|
|
40151
|
+
return diagnostics.filter((diagnostic) => !isManualMemoizationDiagnostic(diagnostic) || !isInsideBailedOutFunction(diagnostic));
|
|
40152
|
+
};
|
|
39859
40153
|
const FILEPATH_WITH_LOCATION_PATTERN = /\S+\.\w+:\d+:\d+[\s\S]*$/;
|
|
39860
40154
|
const LEADING_SEVERITY_LABEL_PATTERN = /^(?:Error|Warning):\s*/;
|
|
39861
40155
|
const TRAILING_PERIOD_PATTERN = /\.$/;
|
|
@@ -39891,8 +40185,14 @@ const PLUGIN_CATEGORY_MAP = {
|
|
|
39891
40185
|
};
|
|
39892
40186
|
const lookupOwnString = (record, key) => Object.hasOwn(record, key) ? record[key] : void 0;
|
|
39893
40187
|
const getRuleRecommendation = (ruleName, project) => {
|
|
39894
|
-
|
|
39895
|
-
return
|
|
40188
|
+
const rule = reactDoctorPlugin.rules[ruleName];
|
|
40189
|
+
if (!rule) return void 0;
|
|
40190
|
+
if (rule.recommendationFor) {
|
|
40191
|
+
const capabilities = getCapabilities(project);
|
|
40192
|
+
const conditionalRecommendation = rule.recommendationFor((capability) => capabilities.has(capability));
|
|
40193
|
+
if (conditionalRecommendation !== void 0) return conditionalRecommendation;
|
|
40194
|
+
}
|
|
40195
|
+
return rule.recommendation;
|
|
39896
40196
|
};
|
|
39897
40197
|
const getRuleCategory = (ruleName) => reactDoctorPlugin.rules[ruleName]?.category;
|
|
39898
40198
|
const getRuleTitle = (ruleName) => reactDoctorPlugin.rules[ruleName]?.title;
|
|
@@ -39995,7 +40295,7 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
39995
40295
|
minifiedFileCache.set(absolutePath, minified);
|
|
39996
40296
|
return minified;
|
|
39997
40297
|
};
|
|
39998
|
-
return parsed.diagnostics.filter((diagnostic) => diagnostic.code && isLintableSourceFile(diagnostic.filename) && !isMinifiedDiagnosticFile(diagnostic.filename) && !shouldSuppressLocalUseHookDiagnostic(diagnostic, rootDirectory)).map((diagnostic) => {
|
|
40298
|
+
return suppressMemoizationInBailedOutFunctions(parsed.diagnostics.filter((diagnostic) => diagnostic.code && isLintableSourceFile(diagnostic.filename) && !isMinifiedDiagnosticFile(diagnostic.filename) && !shouldSuppressLocalUseHookDiagnostic(diagnostic, rootDirectory) && !shouldSuppressCompilerFindingInWorklet(diagnostic, project, rootDirectory)).map((diagnostic) => {
|
|
39999
40299
|
const { plugin, rule } = parseRuleCode(diagnostic.code);
|
|
40000
40300
|
const primaryLabel = diagnostic.labels[0];
|
|
40001
40301
|
const cleaned = cleanDiagnosticMessage(diagnostic.message, diagnostic.help, plugin, rule, project);
|
|
@@ -40022,7 +40322,7 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
40022
40322
|
...resolveMatchByOccurrence(rule, category) ? { matchByOccurrence: true } : {},
|
|
40023
40323
|
...relatedLocations.length > 0 ? { relatedLocations } : {}
|
|
40024
40324
|
};
|
|
40025
|
-
});
|
|
40325
|
+
}), rootDirectory);
|
|
40026
40326
|
};
|
|
40027
40327
|
const buildOxlintChildEnv = (sourceEnv) => {
|
|
40028
40328
|
const childEnv = {};
|
|
@@ -40046,7 +40346,9 @@ const SANITIZED_ENV = buildOxlintChildEnv(process.env);
|
|
|
40046
40346
|
* - `OxlintBatchExceeded { kind: "output-too-large" }` — stdout+stderr
|
|
40047
40347
|
* crossed `OXLINT_OUTPUT_MAX_BYTES`.
|
|
40048
40348
|
* - `OxlintBatchExceeded { kind: "oom" | "killed" }` — child exited
|
|
40049
|
-
* on a signal (SIGABRT → OOM, others → generic kill)
|
|
40349
|
+
* on a signal (SIGABRT → OOM, others → generic kill), or with one
|
|
40350
|
+
* of the abort exit codes Windows reports instead of a signal
|
|
40351
|
+
* (`ABORT_EXIT_CODES` → OOM).
|
|
40050
40352
|
* - `OxlintSpawnFailed { cause }` — `spawn` itself errored, or the
|
|
40051
40353
|
* child exited successfully but printed only stderr.
|
|
40052
40354
|
*
|
|
@@ -40112,7 +40414,7 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath, spawnTimeoutMs = OXLIN
|
|
|
40112
40414
|
clearAbortListener();
|
|
40113
40415
|
reject(new ReactDoctorError({ reason: new OxlintSpawnFailed({ cause: error }) }));
|
|
40114
40416
|
});
|
|
40115
|
-
child.on("close", (
|
|
40417
|
+
child.on("close", (code, signal) => {
|
|
40116
40418
|
clearTimeout(timeoutHandle);
|
|
40117
40419
|
clearAbortListener();
|
|
40118
40420
|
if (didKillForSize) {
|
|
@@ -40122,10 +40424,11 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath, spawnTimeoutMs = OXLIN
|
|
|
40122
40424
|
}) }));
|
|
40123
40425
|
return;
|
|
40124
40426
|
}
|
|
40125
|
-
|
|
40427
|
+
const isAbortExitCode = code !== null && ABORT_EXIT_CODES.has(code);
|
|
40428
|
+
if (signal || isAbortExitCode) {
|
|
40126
40429
|
const stderrOutput = Buffer.concat(stderrBuffers).toString("utf-8").trim();
|
|
40127
|
-
const isOom = signal === "SIGABRT";
|
|
40128
|
-
const detailParts = [`killed by ${signal}`];
|
|
40430
|
+
const isOom = signal === "SIGABRT" || isAbortExitCode;
|
|
40431
|
+
const detailParts = [signal ? `killed by ${signal}` : `aborted with exit code ${code}`];
|
|
40129
40432
|
if (isOom) detailParts.push("try scanning fewer files with --diff");
|
|
40130
40433
|
if (stderrOutput) detailParts.push(stderrOutput);
|
|
40131
40434
|
reject(new ReactDoctorError({ reason: new OxlintBatchExceeded({
|
|
@@ -40168,12 +40471,19 @@ const isParallelismRelatedSpawnError = (error) => {
|
|
|
40168
40471
|
* (surfaced via `onPartialFailure`) so JSON-mode consumers see WHICH
|
|
40169
40472
|
* files were skipped instead of silently losing them.
|
|
40170
40473
|
*
|
|
40171
|
-
* Parallel runs (concurrency > 1) get
|
|
40172
|
-
* fails with a resource-exhaustion error that's exclusive
|
|
40173
|
-
* many oxlint subprocesses at once (EAGAIN / EMFILE /
|
|
40174
|
-
* see `isParallelismRelatedSpawnError`), the whole
|
|
40175
|
-
* with a single worker. That's the only failure a
|
|
40176
|
-
* clear, so every other error class is left to
|
|
40474
|
+
* Parallel runs (concurrency > 1) get two extra safety nets:
|
|
40475
|
+
* - If the pass fails with a resource-exhaustion error that's exclusive
|
|
40476
|
+
* to running many oxlint subprocesses at once (EAGAIN / EMFILE /
|
|
40477
|
+
* ENFILE / ENOMEM — see `isParallelismRelatedSpawnError`), the whole
|
|
40478
|
+
* pass replays once with a single worker. That's the only failure a
|
|
40479
|
+
* serial replay can clear, so every other error class is left to
|
|
40480
|
+
* propagate.
|
|
40481
|
+
* - Files dropped because oxlint's native binding SIGABRT'd under
|
|
40482
|
+
* memory pressure (`OxlintBatchExceeded { kind: "oom" }`) get one
|
|
40483
|
+
* serial single-file-batch rescue pass: the OOM is usually a function
|
|
40484
|
+
* of N concurrent oxlint allocator arenas, not the file itself, so a
|
|
40485
|
+
* lone process with the machine to itself typically clears it —
|
|
40486
|
+
* turning a partial scan into a complete (if slower) one.
|
|
40177
40487
|
*
|
|
40178
40488
|
* Errors that aren't splittable and aren't parallelism-related (oxlint
|
|
40179
40489
|
* config crash, JS plugin resolution failure, etc.) propagate to the
|
|
@@ -40183,13 +40493,16 @@ const isParallelismRelatedSpawnError = (error) => {
|
|
|
40183
40493
|
const spawnLintBatches = async (input) => {
|
|
40184
40494
|
const { baseArgs, fileBatches, rootDirectory, nodeBinaryPath, project, onPartialFailure, onFileProgress, spawnTimeoutMs, outputMaxBytes, splitTotalBudgetMs = OXLINT_SPLIT_TOTAL_BUDGET_MS, splitMaxDepth = 8, signal, deadlineEpochMs } = input;
|
|
40185
40495
|
const requestedConcurrency = resolveScanConcurrency(input.concurrency ?? 1);
|
|
40186
|
-
|
|
40187
|
-
const
|
|
40496
|
+
let rescueDeadlineEpochMs;
|
|
40497
|
+
const isPastDeadline = () => deadlineEpochMs !== void 0 && remainingDeadlineBudgetMs(deadlineEpochMs) === 0 || rescueDeadlineEpochMs !== void 0 && remainingDeadlineBudgetMs(rescueDeadlineEpochMs) === 0;
|
|
40498
|
+
const runBatchPass = async (concurrency, passBatches, passOnFileProgress) => {
|
|
40499
|
+
const totalFileCount = passBatches.reduce((sum, batch) => sum + batch.length, 0);
|
|
40188
40500
|
const allDiagnostics = [];
|
|
40189
40501
|
const droppedFiles = [];
|
|
40502
|
+
const oomDroppedFiles = [];
|
|
40190
40503
|
const deadlineSkippedFiles = [];
|
|
40191
|
-
const isPastDeadline = () => deadlineEpochMs !== void 0 && remainingDeadlineBudgetMs(deadlineEpochMs) === 0;
|
|
40192
40504
|
let firstDropReason = null;
|
|
40505
|
+
let firstNonOomDropReason = null;
|
|
40193
40506
|
const spawnLintBatch = async (batch, depth, batchState) => {
|
|
40194
40507
|
if (isPastDeadline()) {
|
|
40195
40508
|
deadlineSkippedFiles.push(...batch);
|
|
@@ -40211,12 +40524,14 @@ const spawnLintBatches = async (input) => {
|
|
|
40211
40524
|
const isDepthCapReached = depth >= splitMaxDepth;
|
|
40212
40525
|
if (batch.length <= 1 || isBudgetElapsed || isDepthCapReached) {
|
|
40213
40526
|
droppedFiles.push(...batch);
|
|
40214
|
-
|
|
40215
|
-
|
|
40216
|
-
|
|
40217
|
-
|
|
40218
|
-
|
|
40219
|
-
}
|
|
40527
|
+
const isOomDrop = error.reason._tag === "OxlintBatchExceeded" && error.reason.kind === "oom";
|
|
40528
|
+
if (isOomDrop) oomDroppedFiles.push(...batch);
|
|
40529
|
+
let limitHint = "";
|
|
40530
|
+
if (isDepthCapReached) limitHint = ` (split depth cap of ${splitMaxDepth} levels reached)`;
|
|
40531
|
+
else if (isBudgetElapsed) limitHint = ` (split budget of ${splitTotalBudgetMs / MILLISECONDS_PER_SECOND}s exhausted at depth ${depth})`;
|
|
40532
|
+
const dropReason = batch.length > 1 ? `${error.message}${limitHint}` : error.message;
|
|
40533
|
+
firstDropReason ??= dropReason;
|
|
40534
|
+
if (!isOomDrop) firstNonOomDropReason ??= dropReason;
|
|
40220
40535
|
return [];
|
|
40221
40536
|
}
|
|
40222
40537
|
const splitIndex = Math.ceil(batch.length / 2);
|
|
@@ -40226,16 +40541,16 @@ const spawnLintBatches = async (input) => {
|
|
|
40226
40541
|
let startedFileCount = 0;
|
|
40227
40542
|
let scannedFileCount = 0;
|
|
40228
40543
|
let displayedFileCount = 0;
|
|
40229
|
-
const progressTimer =
|
|
40544
|
+
const progressTimer = passOnFileProgress && totalFileCount > 1 ? setInterval(() => {
|
|
40230
40545
|
const ceiling = Math.min(startedFileCount, totalFileCount - 1);
|
|
40231
40546
|
if (displayedFileCount < ceiling) {
|
|
40232
40547
|
displayedFileCount += 1;
|
|
40233
|
-
|
|
40548
|
+
passOnFileProgress(displayedFileCount, totalFileCount);
|
|
40234
40549
|
}
|
|
40235
40550
|
}, 50) : null;
|
|
40236
40551
|
progressTimer?.unref?.();
|
|
40237
40552
|
try {
|
|
40238
|
-
const batchResults = await mapWithConcurrency(
|
|
40553
|
+
const batchResults = await mapWithConcurrency(passBatches, concurrency, async (batch) => {
|
|
40239
40554
|
if (isPastDeadline()) {
|
|
40240
40555
|
deadlineSkippedFiles.push(...batch);
|
|
40241
40556
|
return [];
|
|
@@ -40247,9 +40562,9 @@ const spawnLintBatches = async (input) => {
|
|
|
40247
40562
|
};
|
|
40248
40563
|
const batchDiagnostics = await spawnLintBatch(batch, 0, batchState);
|
|
40249
40564
|
scannedFileCount += batch.length - batchState.deadlineSkippedFileCount;
|
|
40250
|
-
if (
|
|
40565
|
+
if (passOnFileProgress) {
|
|
40251
40566
|
displayedFileCount = Math.min(Math.max(displayedFileCount, scannedFileCount), totalFileCount);
|
|
40252
|
-
|
|
40567
|
+
passOnFileProgress(displayedFileCount, totalFileCount);
|
|
40253
40568
|
}
|
|
40254
40569
|
return batchDiagnostics;
|
|
40255
40570
|
});
|
|
@@ -40257,22 +40572,53 @@ const spawnLintBatches = async (input) => {
|
|
|
40257
40572
|
} finally {
|
|
40258
40573
|
if (progressTimer !== null) clearInterval(progressTimer);
|
|
40259
40574
|
}
|
|
40260
|
-
|
|
40261
|
-
|
|
40262
|
-
|
|
40575
|
+
return {
|
|
40576
|
+
diagnostics: allDiagnostics,
|
|
40577
|
+
droppedFiles,
|
|
40578
|
+
oomDroppedFiles,
|
|
40579
|
+
deadlineSkippedFiles,
|
|
40580
|
+
firstDropReason,
|
|
40581
|
+
firstNonOomDropReason
|
|
40263
40582
|
};
|
|
40264
|
-
const reasonHint = firstDropReason ? ` — first failure: ${firstDropReason}` : "";
|
|
40265
|
-
reportSkippedFiles(droppedFiles, (fileListPreview) => `${droppedFiles.length} file(s) failed to lint and were skipped (${fileListPreview})${reasonHint}`);
|
|
40266
|
-
reportSkippedFiles(deadlineSkippedFiles, (fileListPreview) => `${deadlineSkippedFiles.length} file(s) skipped — max scan duration reached before they were linted (${fileListPreview})`);
|
|
40267
|
-
return allDiagnostics;
|
|
40268
40583
|
};
|
|
40269
|
-
let
|
|
40584
|
+
let outcome;
|
|
40585
|
+
let effectiveConcurrency = requestedConcurrency;
|
|
40270
40586
|
try {
|
|
40271
|
-
|
|
40587
|
+
outcome = await runBatchPass(requestedConcurrency, fileBatches, onFileProgress);
|
|
40272
40588
|
} catch (error) {
|
|
40273
40589
|
if (requestedConcurrency <= 1 || !isParallelismRelatedSpawnError(error)) throw error;
|
|
40274
|
-
|
|
40590
|
+
effectiveConcurrency = 1;
|
|
40591
|
+
outcome = await runBatchPass(1, fileBatches, onFileProgress);
|
|
40592
|
+
}
|
|
40593
|
+
let { droppedFiles, firstDropReason } = outcome;
|
|
40594
|
+
const diagnostics = outcome.diagnostics;
|
|
40595
|
+
if (outcome.oomDroppedFiles.length > 0 && effectiveConcurrency > 1 && !isPastDeadline()) {
|
|
40596
|
+
rescueDeadlineEpochMs = Date.now() + OXLINT_OOM_RESCUE_BUDGET_MS;
|
|
40597
|
+
const rescuedFiles = /* @__PURE__ */ new Set();
|
|
40598
|
+
let firstRescueFailureReason = null;
|
|
40599
|
+
for (const oomDroppedFile of outcome.oomDroppedFiles) {
|
|
40600
|
+
if (isPastDeadline()) break;
|
|
40601
|
+
try {
|
|
40602
|
+
const rescueOutcome = await runBatchPass(1, [[oomDroppedFile]], void 0);
|
|
40603
|
+
diagnostics.push(...rescueOutcome.diagnostics);
|
|
40604
|
+
if (rescueOutcome.droppedFiles.length > 0 || rescueOutcome.deadlineSkippedFiles.length > 0) firstRescueFailureReason ??= rescueOutcome.firstDropReason;
|
|
40605
|
+
else rescuedFiles.add(oomDroppedFile);
|
|
40606
|
+
} catch (error) {
|
|
40607
|
+
firstRescueFailureReason ??= error instanceof Error ? error.message : String(error);
|
|
40608
|
+
}
|
|
40609
|
+
}
|
|
40610
|
+
rescueDeadlineEpochMs = void 0;
|
|
40611
|
+
droppedFiles = droppedFiles.filter((filePath) => !rescuedFiles.has(filePath));
|
|
40612
|
+
if (droppedFiles.length === 0) firstDropReason = null;
|
|
40613
|
+
else firstDropReason = firstRescueFailureReason ?? outcome.firstNonOomDropReason ?? firstDropReason;
|
|
40275
40614
|
}
|
|
40615
|
+
const reportSkippedFiles = (skippedFiles, buildMessage) => {
|
|
40616
|
+
if (skippedFiles.length === 0 || !onPartialFailure) return;
|
|
40617
|
+
onPartialFailure(buildMessage(`${skippedFiles.slice(0, 3).join(", ")}${skippedFiles.length > 3 ? `, +${skippedFiles.length - 3} more` : ""}`));
|
|
40618
|
+
};
|
|
40619
|
+
const reasonHint = firstDropReason ? ` — first failure: ${firstDropReason}` : "";
|
|
40620
|
+
reportSkippedFiles(droppedFiles, (fileListPreview) => `${droppedFiles.length} file(s) failed to lint and were skipped (${fileListPreview})${reasonHint}`);
|
|
40621
|
+
reportSkippedFiles(outcome.deadlineSkippedFiles, (fileListPreview) => `${outcome.deadlineSkippedFiles.length} file(s) skipped — max scan duration reached before they were linted (${fileListPreview})`);
|
|
40276
40622
|
return dedupeDiagnostics(diagnostics);
|
|
40277
40623
|
};
|
|
40278
40624
|
let didValidate = false;
|
|
@@ -40533,6 +40879,7 @@ const runOxlint = async (options) => {
|
|
|
40533
40879
|
const serverAuthFunctionNames = Array.isArray(userConfig?.serverAuthFunctionNames) ? userConfig.serverAuthFunctionNames.filter((entry) => typeof entry === "string" && entry.length > 0) : void 0;
|
|
40534
40880
|
const severityControls = buildRuleSeverityControls(userConfig);
|
|
40535
40881
|
validateRuleRegistration();
|
|
40882
|
+
resetManifestCaches();
|
|
40536
40883
|
if (includePaths !== void 0 && includePaths.length === 0) return [];
|
|
40537
40884
|
const pluginPath = resolvePluginPath();
|
|
40538
40885
|
const extendsPaths = (adoptExistingLintConfig && !customRulesOnly ? detectUserLintConfigPaths(rootDirectory) : []).filter(canOxlintExtendConfig);
|
|
@@ -41195,9 +41542,10 @@ const CONTEXT_AXES = [
|
|
|
41195
41542
|
}
|
|
41196
41543
|
];
|
|
41197
41544
|
const SCORE_AXES = [...GATED_AXES, ...CONTEXT_AXES];
|
|
41198
|
-
const
|
|
41199
|
-
|
|
41200
|
-
|
|
41545
|
+
const gatedAxesForSection = (section) => section === "devDependencies" ? GATED_AXES.filter((axis) => axis.key !== "vulnerability") : GATED_AXES;
|
|
41546
|
+
const worstGatedAxis = (score, gatedAxes) => {
|
|
41547
|
+
let worst = gatedAxes[0];
|
|
41548
|
+
for (const axis of gatedAxes) if (score[axis.key] < score[worst.key]) worst = axis;
|
|
41201
41549
|
return worst;
|
|
41202
41550
|
};
|
|
41203
41551
|
const clampScore = (value) => {
|
|
@@ -41221,6 +41569,69 @@ const resolveConcreteVersion = (spec) => {
|
|
|
41221
41569
|
if (range === null || range === "*") return null;
|
|
41222
41570
|
return import_semver.minVersion(trimmed)?.version ?? null;
|
|
41223
41571
|
};
|
|
41572
|
+
const readTextFileOrNull = (filePath) => {
|
|
41573
|
+
try {
|
|
41574
|
+
return NFS.readFileSync(filePath, "utf-8");
|
|
41575
|
+
} catch {
|
|
41576
|
+
return null;
|
|
41577
|
+
}
|
|
41578
|
+
};
|
|
41579
|
+
const buildNpmLockLookup = (lockText) => {
|
|
41580
|
+
let parsed;
|
|
41581
|
+
try {
|
|
41582
|
+
parsed = JSON.parse(lockText);
|
|
41583
|
+
} catch {
|
|
41584
|
+
return null;
|
|
41585
|
+
}
|
|
41586
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
41587
|
+
const lock = parsed;
|
|
41588
|
+
return (name) => {
|
|
41589
|
+
const entry = lock.packages?.[`node_modules/${name}`] ?? lock.dependencies?.[name];
|
|
41590
|
+
return typeof entry?.version === "string" ? entry.version : null;
|
|
41591
|
+
};
|
|
41592
|
+
};
|
|
41593
|
+
const buildYarnLockLookup = (lockText) => {
|
|
41594
|
+
const lines = lockText.split(/\r?\n/);
|
|
41595
|
+
return (name, spec) => {
|
|
41596
|
+
const descriptor = `${name}@${spec}`;
|
|
41597
|
+
const berryDescriptor = `${name}@npm:${spec}`;
|
|
41598
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
41599
|
+
const line = lines[lineIndex];
|
|
41600
|
+
if (line.startsWith(" ") || line.startsWith("#") || !line.includes(":")) continue;
|
|
41601
|
+
const descriptors = line.replace(/:\s*$/, "").split(",").map((part) => part.trim().replace(/^"|"$/g, ""));
|
|
41602
|
+
if (!descriptors.includes(descriptor) && !descriptors.includes(berryDescriptor)) continue;
|
|
41603
|
+
for (let bodyIndex = lineIndex + 1; bodyIndex < lines.length && (lines[bodyIndex].startsWith(" ") || lines[bodyIndex] === ""); bodyIndex += 1) {
|
|
41604
|
+
const versionMatch = /^\s+version:?\s+"?([^"\s]+)"?/.exec(lines[bodyIndex]);
|
|
41605
|
+
if (versionMatch) return versionMatch[1];
|
|
41606
|
+
}
|
|
41607
|
+
return null;
|
|
41608
|
+
}
|
|
41609
|
+
return null;
|
|
41610
|
+
};
|
|
41611
|
+
};
|
|
41612
|
+
const buildPnpmLockLookup = (lockText) => {
|
|
41613
|
+
return (name) => {
|
|
41614
|
+
const entryPattern = new RegExp(`^ {4,8}['"]?${name.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&")}['"]?:\\s*$`);
|
|
41615
|
+
const lines = lockText.split(/\r?\n/);
|
|
41616
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
41617
|
+
if (!entryPattern.test(lines[lineIndex])) continue;
|
|
41618
|
+
for (let bodyIndex = lineIndex + 1; bodyIndex < lines.length && /^ {5,}\S/.test(lines[bodyIndex]); bodyIndex += 1) {
|
|
41619
|
+
const versionMatch = /^\s+version:\s+['"]?([^'"\s(]+)/.exec(lines[bodyIndex]);
|
|
41620
|
+
if (versionMatch) return versionMatch[1];
|
|
41621
|
+
}
|
|
41622
|
+
}
|
|
41623
|
+
return null;
|
|
41624
|
+
};
|
|
41625
|
+
};
|
|
41626
|
+
const buildLockedVersionLookup = (rootDirectory) => {
|
|
41627
|
+
const npmLockText = readTextFileOrNull(Path.join(rootDirectory, "package-lock.json"));
|
|
41628
|
+
if (npmLockText !== null) return buildNpmLockLookup(npmLockText);
|
|
41629
|
+
const yarnLockText = readTextFileOrNull(Path.join(rootDirectory, "yarn.lock"));
|
|
41630
|
+
if (yarnLockText !== null) return buildYarnLockLookup(yarnLockText);
|
|
41631
|
+
const pnpmLockText = readTextFileOrNull(Path.join(rootDirectory, "pnpm-lock.yaml"));
|
|
41632
|
+
if (pnpmLockText !== null) return buildPnpmLockLookup(pnpmLockText);
|
|
41633
|
+
return null;
|
|
41634
|
+
};
|
|
41224
41635
|
const locateDependencyKey = (packageJsonText, section, name) => {
|
|
41225
41636
|
const needle = `"${name}"`;
|
|
41226
41637
|
const sectionHeader = new RegExp(`"${section}"\\s*:\\s*\\{`);
|
|
@@ -41247,7 +41658,7 @@ const locateDependencyKey = (packageJsonText, section, name) => {
|
|
|
41247
41658
|
}
|
|
41248
41659
|
return null;
|
|
41249
41660
|
};
|
|
41250
|
-
const collectDependenciesToScore = (packageJson, packageJsonText, includeDevDependencies) => {
|
|
41661
|
+
const collectDependenciesToScore = (packageJson, packageJsonText, includeDevDependencies, lockedVersionLookup) => {
|
|
41251
41662
|
const sectionByName = /* @__PURE__ */ new Map();
|
|
41252
41663
|
for (const name of Object.keys(packageJson.dependencies ?? {})) sectionByName.set(name, "dependencies");
|
|
41253
41664
|
if (includeDevDependencies) {
|
|
@@ -41257,13 +41668,18 @@ const collectDependenciesToScore = (packageJson, packageJsonText, includeDevDepe
|
|
|
41257
41668
|
for (const [name, section] of sectionByName) {
|
|
41258
41669
|
if (SUPPLY_CHAIN_IGNORED_PACKAGES.has(name)) continue;
|
|
41259
41670
|
const spec = (packageJson[section] ?? {})[name] ?? "";
|
|
41260
|
-
const
|
|
41261
|
-
if (
|
|
41671
|
+
const floorVersion = resolveConcreteVersion(spec);
|
|
41672
|
+
if (floorVersion === null) continue;
|
|
41673
|
+
const lockedVersion = lockedVersionLookup?.(name, spec) ?? null;
|
|
41674
|
+
const lockSatisfiesSpec = lockedVersion !== null && import_semver.valid(lockedVersion) !== null && import_semver.satisfies(lockedVersion, spec);
|
|
41675
|
+
const isExactPin = import_semver.valid(spec.trim()) !== null;
|
|
41262
41676
|
const location = locateDependencyKey(packageJsonText, section, name);
|
|
41263
41677
|
dependencies.push({
|
|
41264
41678
|
name,
|
|
41265
|
-
version,
|
|
41679
|
+
version: lockSatisfiesSpec ? lockedVersion : floorVersion,
|
|
41266
41680
|
spec,
|
|
41681
|
+
section,
|
|
41682
|
+
resolution: isExactPin ? "pin" : lockSatisfiesSpec ? "locked" : "floor",
|
|
41267
41683
|
line: location?.line ?? 0,
|
|
41268
41684
|
column: location?.column ?? 0
|
|
41269
41685
|
});
|
|
@@ -41414,7 +41830,11 @@ const formatAlertReason = (topAlerts, totalCount) => {
|
|
|
41414
41830
|
}
|
|
41415
41831
|
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)}.`;
|
|
41416
41832
|
};
|
|
41417
|
-
const formatDependencyIdentity = (dependency) =>
|
|
41833
|
+
const formatDependencyIdentity = (dependency) => {
|
|
41834
|
+
if (dependency.resolution === "pin") return `${dependency.name}@${dependency.version}`;
|
|
41835
|
+
if (dependency.resolution === "locked") return `${dependency.name}@${dependency.version} (lockfile resolution of "${dependency.spec}")`;
|
|
41836
|
+
return `${dependency.name}@${dependency.version} (lowest version "${dependency.spec}" allows)`;
|
|
41837
|
+
};
|
|
41418
41838
|
const buildSupplyChainHelp = (dependency, failingAxis, topAlerts, packagePageUrl, options) => {
|
|
41419
41839
|
const hasCriticalAlert = topAlerts.some((alert) => alert.severity.toLowerCase() === "critical");
|
|
41420
41840
|
const entry = `\`"${dependency.name}": "${dependency.spec}"\``;
|
|
@@ -41457,7 +41877,7 @@ const buildLowScoreDiagnostic = (dependency, artifact, failingAxis, options) =>
|
|
|
41457
41877
|
const checkSupplyChain = (input) => gen(function* () {
|
|
41458
41878
|
const options = resolveOptions(input.userConfig);
|
|
41459
41879
|
const packageJsonPath = Path.join(input.rootDirectory, "package.json");
|
|
41460
|
-
const dependencies = collectDependenciesToScore(readPackageJson(packageJsonPath), readPackageJsonText(packageJsonPath), options.includeDevDependencies);
|
|
41880
|
+
const dependencies = collectDependenciesToScore(readPackageJson(packageJsonPath), readPackageJsonText(packageJsonPath), options.includeDevDependencies, buildLockedVersionLookup(input.rootDirectory));
|
|
41461
41881
|
if (dependencies.length === 0) return [];
|
|
41462
41882
|
const cacheDirectory = isSupplyChainCacheDisabled() ? null : resolveReactDoctorCacheDir(input.rootDirectory);
|
|
41463
41883
|
if (cacheDirectory !== null) pruneExpiredSocketCache(cacheDirectory);
|
|
@@ -41466,7 +41886,7 @@ const checkSupplyChain = (input) => gen(function* () {
|
|
|
41466
41886
|
for (let index = 0; index < dependencies.length; index += 1) {
|
|
41467
41887
|
const artifact = artifacts[index];
|
|
41468
41888
|
if (!artifact) continue;
|
|
41469
|
-
const worstAxis = worstGatedAxis(artifact.score);
|
|
41889
|
+
const worstAxis = worstGatedAxis(artifact.score, gatedAxesForSection(dependencies[index].section));
|
|
41470
41890
|
if (toHundred(artifact.score[worstAxis.key]) >= options.minScore) continue;
|
|
41471
41891
|
diagnostics.push(buildLowScoreDiagnostic(dependencies[index], artifact, worstAxis, options));
|
|
41472
41892
|
}
|
|
@@ -42192,7 +42612,7 @@ const computeConfigFingerprint = (projectDirectory, version) => {
|
|
|
42192
42612
|
/** Display name used in client-facing messages and progress titles. */
|
|
42193
42613
|
const SERVER_DISPLAY_NAME = "React Doctor";
|
|
42194
42614
|
/** Server version reported in `serverInfo`; injected at build, `dev` from source. */
|
|
42195
|
-
const SERVER_VERSION = "0.7.
|
|
42615
|
+
const SERVER_VERSION = "0.7.2";
|
|
42196
42616
|
/** `Diagnostic.source` shown next to every published diagnostic. */
|
|
42197
42617
|
const DIAGNOSTIC_SOURCE = "react-doctor";
|
|
42198
42618
|
/**
|
|
@@ -44243,5 +44663,5 @@ const startLanguageServer = () => {
|
|
|
44243
44663
|
};
|
|
44244
44664
|
//#endregion
|
|
44245
44665
|
export { startLanguageServer };
|
|
44246
|
-
!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]="
|
|
44247
|
-
//# debugId=
|
|
44666
|
+
!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]="fff2fb6a-e10e-51f4-b9c3-f531f8dab288")}catch(e){}}();
|
|
44667
|
+
//# debugId=fff2fb6a-e10e-51f4-b9c3-f531f8dab288
|