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/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
2
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="3dc4a1ac-42dd-51e8-ba38-0260edf45659")}catch(e){}}();
|
|
3
3
|
import { r as __toESM$1, t as __commonJSMin$1 } from "./chunk-N93fKeF6.js";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import * as NFS from "node:fs";
|
|
@@ -9,7 +9,7 @@ import path from "node:path";
|
|
|
9
9
|
import * as NodeChildProcess from "node:child_process";
|
|
10
10
|
import { execFileSync, spawn, spawnSync } from "node:child_process";
|
|
11
11
|
import * as ts from "typescript";
|
|
12
|
-
import reactDoctorPlugin, { ALL_REACT_DOCTOR_RULE_KEYS, CROSS_FILE_DEPENDENCY_COLLECTORS, CROSS_FILE_RULE_IDS, FRAMEWORK_SPECIFIC_RULE_KEYS, MOTION_LIBRARY_PACKAGES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, classifySecurityScanFile, collectCrossFileDependencyProbes, shouldReadSecurityScanContent } from "oxlint-plugin-react-doctor";
|
|
12
|
+
import reactDoctorPlugin, { ALL_REACT_DOCTOR_RULE_KEYS, CROSS_FILE_DEPENDENCY_COLLECTORS, CROSS_FILE_RULE_IDS, FRAMEWORK_SPECIFIC_RULE_KEYS, MOTION_LIBRARY_PACKAGES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, classifySecurityScanFile, collectCrossFileDependencyProbes, resetManifestCaches, shouldReadSecurityScanContent } from "oxlint-plugin-react-doctor";
|
|
13
13
|
import * as OS from "node:os";
|
|
14
14
|
import os from "node:os";
|
|
15
15
|
import { parseJSON5 } from "confbox";
|
|
@@ -32791,6 +32791,25 @@ var AmbiguousProjectError = class extends Error {
|
|
|
32791
32791
|
}
|
|
32792
32792
|
};
|
|
32793
32793
|
const isProjectDiscoveryError = (value) => value instanceof ProjectNotFoundError || value instanceof NoReactDependencyError || value instanceof PackageJsonNotFoundError || value instanceof NotADirectoryError || value instanceof AmbiguousProjectError;
|
|
32794
|
+
const isErrnoException = (error) => error instanceof Error && "code" in error;
|
|
32795
|
+
const IGNORABLE_READDIR_ERROR_CODES = new Set([
|
|
32796
|
+
"EACCES",
|
|
32797
|
+
"EPERM",
|
|
32798
|
+
"ENOENT",
|
|
32799
|
+
"ENOTDIR",
|
|
32800
|
+
"EINVAL",
|
|
32801
|
+
"ELOOP",
|
|
32802
|
+
"ENAMETOOLONG"
|
|
32803
|
+
]);
|
|
32804
|
+
const isIgnorableReaddirError = (error) => isErrnoException(error) && typeof error.code === "string" && IGNORABLE_READDIR_ERROR_CODES.has(error.code);
|
|
32805
|
+
const readDirectoryEntries = (directoryPath) => {
|
|
32806
|
+
try {
|
|
32807
|
+
return NFS.readdirSync(directoryPath, { withFileTypes: true });
|
|
32808
|
+
} catch (error) {
|
|
32809
|
+
if (isIgnorableReaddirError(error)) return [];
|
|
32810
|
+
throw error;
|
|
32811
|
+
}
|
|
32812
|
+
};
|
|
32794
32813
|
const isFile = (filePath) => {
|
|
32795
32814
|
try {
|
|
32796
32815
|
return NFS.statSync(filePath).isFile();
|
|
@@ -32798,10 +32817,24 @@ const isFile = (filePath) => {
|
|
|
32798
32817
|
return false;
|
|
32799
32818
|
}
|
|
32800
32819
|
};
|
|
32820
|
+
const isDirectory = (directoryPath) => {
|
|
32821
|
+
try {
|
|
32822
|
+
return NFS.statSync(directoryPath).isDirectory();
|
|
32823
|
+
} catch {
|
|
32824
|
+
return false;
|
|
32825
|
+
}
|
|
32826
|
+
};
|
|
32827
|
+
const isPlainObject = (value) => {
|
|
32828
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
32829
|
+
const prototype = Object.getPrototypeOf(value);
|
|
32830
|
+
return prototype === null || prototype === Object.prototype;
|
|
32831
|
+
};
|
|
32801
32832
|
const SOURCE_FILE_PATTERN = /\.(tsx?|jsx?|mts|mjs)$/;
|
|
32802
32833
|
const GENERATED_BUNDLE_FILE_PATTERN = /\.(iife|umd|global|min)\.m?js$/i;
|
|
32834
|
+
const GENERATED_SOURCE_DIRECTORY_PATTERN = /(?:^|\/)__generated__\//;
|
|
32803
32835
|
const MINIFIED_SNIFF_BYTES = 65536;
|
|
32804
32836
|
const GIT_LS_FILES_MAX_BUFFER_BYTES = 50 * 1024 * 1024;
|
|
32837
|
+
const SCANNED_DOT_DIRECTORIES = new Set([".dumi", ".storybook"]);
|
|
32805
32838
|
const IGNORED_DIRECTORIES = new Set([
|
|
32806
32839
|
".angular",
|
|
32807
32840
|
".astro",
|
|
@@ -32897,6 +32930,8 @@ const OXLINT_SPAWN_TIMEOUT_MS = 6e4;
|
|
|
32897
32930
|
const NODE_COMPILE_CACHE_DIR_NAME = "node-compile-cache";
|
|
32898
32931
|
const DEAD_CODE_WORKER_TIMEOUT_MS = 12e4;
|
|
32899
32932
|
const OXLINT_SPLIT_TOTAL_BUDGET_MS = 18e4;
|
|
32933
|
+
const ABORT_EXIT_CODES = new Set([134, 3221226505]);
|
|
32934
|
+
const OXLINT_OOM_RESCUE_BUDGET_MS = 6e4;
|
|
32900
32935
|
const DEAD_CODE_PHASE_TIMEOUT_MS = 15e4;
|
|
32901
32936
|
const LINT_PHASE_TIMEOUT_MS = 3e5;
|
|
32902
32937
|
const SCAN_TOTAL_DEADLINE_MS = 9e5;
|
|
@@ -32943,7 +32978,7 @@ const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
|
|
|
32943
32978
|
const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
|
|
32944
32979
|
const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
|
|
32945
32980
|
const SIDECAR_LINT_CACHE_FILENAME = "sidecar-lint-cache.json";
|
|
32946
|
-
const CORE_PACKAGE_VERSION = "0.7.
|
|
32981
|
+
const CORE_PACKAGE_VERSION = "0.7.2";
|
|
32947
32982
|
const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
|
|
32948
32983
|
const DEAD_CODE_SUMMARY_CACHE_FILENAME = "dead-code-summaries.json";
|
|
32949
32984
|
const SUPPLY_CHAIN_PLUGIN = "socket";
|
|
@@ -32953,8 +32988,9 @@ const SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS = 9e4;
|
|
|
32953
32988
|
const SUPPLY_CHAIN_CACHE_TTL_MS = 864e5;
|
|
32954
32989
|
const SUPPLY_CHAIN_CACHE_SUBDIR = "supply-chain";
|
|
32955
32990
|
const SUPPLY_CHAIN_IGNORED_PACKAGES = new Set(["next"]);
|
|
32956
|
-
const
|
|
32957
|
-
const
|
|
32991
|
+
const isIgnoredDirectoryName = (directoryName) => IGNORED_DIRECTORIES.has(directoryName) || directoryName.startsWith(".") && !SCANNED_DOT_DIRECTORIES.has(directoryName);
|
|
32992
|
+
const hasIgnoredPathSegment = (relativePath) => relativePath.split(/[/\\]/).slice(0, -1).some((segment) => isIgnoredDirectoryName(segment));
|
|
32993
|
+
const isLintableSourceFile = (filePath) => SOURCE_FILE_PATTERN.test(filePath) && !GENERATED_BUNDLE_FILE_PATTERN.test(filePath) && !GENERATED_SOURCE_DIRECTORY_PATTERN.test(filePath);
|
|
32958
32994
|
const isMinifiedSource = (absolutePath) => {
|
|
32959
32995
|
let fileDescriptor;
|
|
32960
32996
|
try {
|
|
@@ -32991,25 +33027,6 @@ const isLargeMinifiedFile = (absolutePath, knownSizeBytes) => {
|
|
|
32991
33027
|
cachedIsLargeMinifiedByPath.set(absolutePath, result);
|
|
32992
33028
|
return result;
|
|
32993
33029
|
};
|
|
32994
|
-
const isErrnoException = (error) => error instanceof Error && "code" in error;
|
|
32995
|
-
const IGNORABLE_READDIR_ERROR_CODES = new Set([
|
|
32996
|
-
"EACCES",
|
|
32997
|
-
"EPERM",
|
|
32998
|
-
"ENOENT",
|
|
32999
|
-
"ENOTDIR",
|
|
33000
|
-
"EINVAL",
|
|
33001
|
-
"ELOOP",
|
|
33002
|
-
"ENAMETOOLONG"
|
|
33003
|
-
]);
|
|
33004
|
-
const isIgnorableReaddirError = (error) => isErrnoException(error) && typeof error.code === "string" && IGNORABLE_READDIR_ERROR_CODES.has(error.code);
|
|
33005
|
-
const readDirectoryEntries = (directoryPath) => {
|
|
33006
|
-
try {
|
|
33007
|
-
return NFS.readdirSync(directoryPath, { withFileTypes: true });
|
|
33008
|
-
} catch (error) {
|
|
33009
|
-
if (isIgnorableReaddirError(error)) return [];
|
|
33010
|
-
throw error;
|
|
33011
|
-
}
|
|
33012
|
-
};
|
|
33013
33030
|
function* walkSourceTreeFiles(rootDirectory) {
|
|
33014
33031
|
const stack = [rootDirectory];
|
|
33015
33032
|
while (stack.length > 0) {
|
|
@@ -33018,7 +33035,7 @@ function* walkSourceTreeFiles(rootDirectory) {
|
|
|
33018
33035
|
for (const entry of readDirectoryEntries(currentDirectory)) {
|
|
33019
33036
|
const absolutePath = Path.join(currentDirectory, entry.name);
|
|
33020
33037
|
if (entry.isDirectory()) {
|
|
33021
|
-
if (!
|
|
33038
|
+
if (!isIgnoredDirectoryName(entry.name)) stack.push(absolutePath);
|
|
33022
33039
|
continue;
|
|
33023
33040
|
}
|
|
33024
33041
|
if (entry.isFile()) yield {
|
|
@@ -33064,23 +33081,6 @@ const listSourceFilesViaFilesystem = (rootDirectory) => {
|
|
|
33064
33081
|
const listSourceFilesWithSize = (rootDirectory) => collectSizedSourceFiles(rootDirectory, (listSourceFilesViaGit(rootDirectory) ?? listSourceFilesViaFilesystem(rootDirectory)).sort());
|
|
33065
33082
|
const listSourceFiles = (rootDirectory) => listSourceFilesWithSize(rootDirectory).map((entry) => entry.path);
|
|
33066
33083
|
const countSourceFiles = (rootDirectory) => listSourceFilesWithSize(rootDirectory).length;
|
|
33067
|
-
/**
|
|
33068
|
-
* The chain of ancestor directories of `startDirectory`, nearest first, up to
|
|
33069
|
-
* and including the filesystem root. With `includeStart`, the chain begins at
|
|
33070
|
-
* `startDirectory` itself; otherwise at its parent. The one place the "walk up
|
|
33071
|
-
* the tree" traversal is expressed, so each caller adds only its own match /
|
|
33072
|
-
* stop policy instead of re-deriving the `path.dirname` loop.
|
|
33073
|
-
*/
|
|
33074
|
-
const ancestorDirectories = (startDirectory, { includeStart }) => {
|
|
33075
|
-
const directories = [];
|
|
33076
|
-
let currentDirectory = includeStart ? startDirectory : Path.dirname(startDirectory);
|
|
33077
|
-
while (true) {
|
|
33078
|
-
directories.push(currentDirectory);
|
|
33079
|
-
const parentDirectory = Path.dirname(currentDirectory);
|
|
33080
|
-
if (parentDirectory === currentDirectory) return directories;
|
|
33081
|
-
currentDirectory = parentDirectory;
|
|
33082
|
-
}
|
|
33083
|
-
};
|
|
33084
33084
|
const cachedPackageJsons = /* @__PURE__ */ new Map();
|
|
33085
33085
|
const clearPackageJsonCache = () => {
|
|
33086
33086
|
cachedPackageJsons.clear();
|
|
@@ -33114,7 +33114,11 @@ const isMonorepoRoot = (directory) => {
|
|
|
33114
33114
|
return Array.isArray(packageJson.workspaces) || Boolean(packageJson.workspaces?.packages);
|
|
33115
33115
|
};
|
|
33116
33116
|
const findMonorepoRoot = (startDirectory) => {
|
|
33117
|
-
|
|
33117
|
+
let currentDirectory = Path.dirname(startDirectory);
|
|
33118
|
+
while (currentDirectory !== Path.dirname(currentDirectory)) {
|
|
33119
|
+
if (isMonorepoRoot(currentDirectory)) return currentDirectory;
|
|
33120
|
+
currentDirectory = Path.dirname(currentDirectory);
|
|
33121
|
+
}
|
|
33118
33122
|
return null;
|
|
33119
33123
|
};
|
|
33120
33124
|
/**
|
|
@@ -33128,6 +33132,146 @@ const findMonorepoRoot = (startDirectory) => {
|
|
|
33128
33132
|
* byte-equivalent copy.
|
|
33129
33133
|
*/
|
|
33130
33134
|
const isProjectBoundary = (directory) => NFS.existsSync(Path.join(directory, ".git")) || isMonorepoRoot(directory);
|
|
33135
|
+
const TSCONFIG_FILENAME = "tsconfig.json";
|
|
33136
|
+
const isRelativeExtendsValue = (extendsValue) => extendsValue.startsWith("./") || extendsValue.startsWith("../") || Path.isAbsolute(extendsValue);
|
|
33137
|
+
const ensureJsonExtension = (filePath) => Path.extname(filePath) === "" ? `${filePath}.json` : filePath;
|
|
33138
|
+
const resolvePackageExtendsPath = (extendsValue, fromConfigDirectory) => {
|
|
33139
|
+
const requireFromConfig = createRequire(Path.join(fromConfigDirectory, "tsconfig.json"));
|
|
33140
|
+
const candidates = [
|
|
33141
|
+
extendsValue,
|
|
33142
|
+
ensureJsonExtension(extendsValue),
|
|
33143
|
+
`${extendsValue.replace(/\/$/, "")}/tsconfig.json`
|
|
33144
|
+
];
|
|
33145
|
+
for (const candidate of candidates) try {
|
|
33146
|
+
return requireFromConfig.resolve(candidate);
|
|
33147
|
+
} catch {
|
|
33148
|
+
continue;
|
|
33149
|
+
}
|
|
33150
|
+
return null;
|
|
33151
|
+
};
|
|
33152
|
+
const resolveExtendsPath = (extendsValue, fromConfigDirectory) => {
|
|
33153
|
+
if (isRelativeExtendsValue(extendsValue)) return ensureJsonExtension(Path.resolve(fromConfigDirectory, extendsValue));
|
|
33154
|
+
return resolvePackageExtendsPath(extendsValue, fromConfigDirectory);
|
|
33155
|
+
};
|
|
33156
|
+
const normalizeCompilerOptions = (compilerOptions) => {
|
|
33157
|
+
if (!isPlainObject(compilerOptions)) return { hasExplicitLib: false };
|
|
33158
|
+
const target = typeof compilerOptions.target === "string" ? compilerOptions.target : void 0;
|
|
33159
|
+
const hasExplicitLib = Object.hasOwn(compilerOptions, "lib");
|
|
33160
|
+
return {
|
|
33161
|
+
target,
|
|
33162
|
+
lib: Array.isArray(compilerOptions.lib) ? compilerOptions.lib.filter((entry) => typeof entry === "string") : void 0,
|
|
33163
|
+
hasExplicitLib
|
|
33164
|
+
};
|
|
33165
|
+
};
|
|
33166
|
+
const readTsConfig = (filePath) => {
|
|
33167
|
+
let content;
|
|
33168
|
+
try {
|
|
33169
|
+
content = NFS.readFileSync(filePath, "utf-8");
|
|
33170
|
+
} catch {
|
|
33171
|
+
return null;
|
|
33172
|
+
}
|
|
33173
|
+
const parsed = ts.parseConfigFileTextToJson(filePath, content);
|
|
33174
|
+
if (!isPlainObject(parsed.config)) return null;
|
|
33175
|
+
return {
|
|
33176
|
+
extends: typeof parsed.config.extends === "string" ? parsed.config.extends : void 0,
|
|
33177
|
+
referencePaths: normalizeReferencePaths(parsed.config.references),
|
|
33178
|
+
compilerOptions: normalizeCompilerOptions(parsed.config.compilerOptions)
|
|
33179
|
+
};
|
|
33180
|
+
};
|
|
33181
|
+
const normalizeReferencePaths = (references) => {
|
|
33182
|
+
if (!Array.isArray(references)) return [];
|
|
33183
|
+
return references.map((reference) => isPlainObject(reference) && typeof reference.path === "string" ? reference.path : null).filter((referencePath) => referencePath !== null);
|
|
33184
|
+
};
|
|
33185
|
+
const mergeCompilerOptions = (inherited, current) => {
|
|
33186
|
+
const target = current.target ?? inherited?.target;
|
|
33187
|
+
const hasExplicitLib = current.hasExplicitLib || Boolean(inherited?.hasExplicitLib);
|
|
33188
|
+
return {
|
|
33189
|
+
target,
|
|
33190
|
+
lib: current.hasExplicitLib ? current.lib : inherited?.lib,
|
|
33191
|
+
hasExplicitLib
|
|
33192
|
+
};
|
|
33193
|
+
};
|
|
33194
|
+
const readResolvedCompilerOptions = (tsConfigPath, extendsDepth, visitedPaths) => {
|
|
33195
|
+
const realPath = NFS.realpathSync.native(tsConfigPath);
|
|
33196
|
+
if (visitedPaths.has(realPath)) return null;
|
|
33197
|
+
const tsConfig = readTsConfig(realPath);
|
|
33198
|
+
if (!tsConfig) return null;
|
|
33199
|
+
const nextVisitedPaths = new Set(visitedPaths);
|
|
33200
|
+
nextVisitedPaths.add(realPath);
|
|
33201
|
+
if (tsConfig.extends && extendsDepth < 8) {
|
|
33202
|
+
const parentPath = resolveExtendsPath(tsConfig.extends, Path.dirname(realPath));
|
|
33203
|
+
if (parentPath && isFile(parentPath)) return mergeCompilerOptions(readResolvedCompilerOptions(parentPath, extendsDepth + 1, nextVisitedPaths), tsConfig.compilerOptions);
|
|
33204
|
+
}
|
|
33205
|
+
return tsConfig.compilerOptions;
|
|
33206
|
+
};
|
|
33207
|
+
const targetYearIsPreES2023 = (target) => {
|
|
33208
|
+
const year = ES_TARGET_YEAR_BY_NAME[target.toLowerCase()];
|
|
33209
|
+
return year !== void 0 && year < 2023;
|
|
33210
|
+
};
|
|
33211
|
+
const libEntryIncludesES2023Array = (entry) => {
|
|
33212
|
+
const normalizedEntry = entry.toLowerCase();
|
|
33213
|
+
if (normalizedEntry === "esnext" || normalizedEntry === "esnext.array") return true;
|
|
33214
|
+
const esYearMatch = /^es(\d{4})(?:\.(.+))?$/.exec(normalizedEntry);
|
|
33215
|
+
if (!esYearMatch) return false;
|
|
33216
|
+
if (Number(esYearMatch[1]) < 2023) return false;
|
|
33217
|
+
const component = esYearMatch[2];
|
|
33218
|
+
return component === void 0 || component === "array";
|
|
33219
|
+
};
|
|
33220
|
+
const libIncludesES2023 = (lib) => lib.some(libEntryIncludesES2023Array);
|
|
33221
|
+
const compilerOptionsArePreES2023 = (compilerOptions) => {
|
|
33222
|
+
if (compilerOptions.target) return targetYearIsPreES2023(compilerOptions.target);
|
|
33223
|
+
if (compilerOptions.hasExplicitLib) return !libIncludesES2023(compilerOptions.lib ?? []);
|
|
33224
|
+
return false;
|
|
33225
|
+
};
|
|
33226
|
+
const compilerOptionsDeclareTargetOrLib = (compilerOptions) => compilerOptions.hasExplicitLib || compilerOptions.target !== void 0;
|
|
33227
|
+
const detectPreES2023FromConfig = (tsConfigPath, visitedConfigPaths = /* @__PURE__ */ new Set()) => {
|
|
33228
|
+
if (visitedConfigPaths.has(tsConfigPath)) return false;
|
|
33229
|
+
const compilerOptions = readResolvedCompilerOptions(tsConfigPath, 0, /* @__PURE__ */ new Set());
|
|
33230
|
+
if (!compilerOptions) return false;
|
|
33231
|
+
if (!compilerOptionsDeclareTargetOrLib(compilerOptions)) {
|
|
33232
|
+
const tsConfig = readTsConfig(tsConfigPath);
|
|
33233
|
+
if (!tsConfig) return false;
|
|
33234
|
+
const nextVisitedConfigPaths = new Set(visitedConfigPaths);
|
|
33235
|
+
nextVisitedConfigPaths.add(tsConfigPath);
|
|
33236
|
+
const configDirectory = Path.dirname(tsConfigPath);
|
|
33237
|
+
return tsConfig.referencePaths.some((referencePath) => {
|
|
33238
|
+
const resolvedReferencePath = Path.resolve(configDirectory, referencePath);
|
|
33239
|
+
const referencedConfigPath = isFile(resolvedReferencePath) ? resolvedReferencePath : Path.join(resolvedReferencePath, TSCONFIG_FILENAME);
|
|
33240
|
+
return isFile(referencedConfigPath) && detectPreES2023FromConfig(referencedConfigPath, nextVisitedConfigPaths);
|
|
33241
|
+
});
|
|
33242
|
+
}
|
|
33243
|
+
return compilerOptionsArePreES2023(compilerOptions);
|
|
33244
|
+
};
|
|
33245
|
+
const detectPreES2023Target = (directory) => {
|
|
33246
|
+
const tsConfigPath = Path.join(directory, TSCONFIG_FILENAME);
|
|
33247
|
+
if (isFile(tsConfigPath)) return detectPreES2023FromConfig(tsConfigPath);
|
|
33248
|
+
for (const fallbackFilename of FALLBACK_TSCONFIG_FILENAMES) {
|
|
33249
|
+
const fallbackPath = Path.join(directory, fallbackFilename);
|
|
33250
|
+
if (isFile(fallbackPath)) return detectPreES2023FromConfig(fallbackPath);
|
|
33251
|
+
}
|
|
33252
|
+
return false;
|
|
33253
|
+
};
|
|
33254
|
+
const FALLBACK_TSCONFIG_FILENAMES = ["tsconfig.app.json", "tsconfig.build.json"];
|
|
33255
|
+
const FRAMEWORK_PACKAGES = {
|
|
33256
|
+
next: "nextjs",
|
|
33257
|
+
"@tanstack/react-start": "tanstack-start",
|
|
33258
|
+
vite: "vite",
|
|
33259
|
+
"react-scripts": "cra",
|
|
33260
|
+
"@remix-run/react": "remix",
|
|
33261
|
+
gatsby: "gatsby",
|
|
33262
|
+
expo: "expo",
|
|
33263
|
+
"react-native": "react-native"
|
|
33264
|
+
};
|
|
33265
|
+
const detectFramework = (dependencies) => {
|
|
33266
|
+
for (const [packageName, frameworkName] of Object.entries(FRAMEWORK_PACKAGES)) if (dependencies[packageName]) return frameworkName;
|
|
33267
|
+
if (dependencies.preact && !dependencies.react) return "preact";
|
|
33268
|
+
return "unknown";
|
|
33269
|
+
};
|
|
33270
|
+
const MOBILE_FRAMEWORKS = new Set(["expo", "react-native"]);
|
|
33271
|
+
const frameworkMergeRank = (framework) => {
|
|
33272
|
+
if (framework === "unknown") return 3;
|
|
33273
|
+
return MOBILE_FRAMEWORKS.has(framework) ? 2 : 1;
|
|
33274
|
+
};
|
|
33131
33275
|
const REACT_COMPILER_PACKAGES = new Set([
|
|
33132
33276
|
"babel-plugin-react-compiler",
|
|
33133
33277
|
"react-compiler-runtime",
|
|
@@ -33164,6 +33308,7 @@ const EXPO_APP_CONFIG_FILENAMES = [
|
|
|
33164
33308
|
];
|
|
33165
33309
|
const REACT_COMPILER_PACKAGE_REFERENCE_PATTERN = /babel-plugin-react-compiler|react-compiler-runtime|eslint-plugin-react-compiler|["']react-compiler["']/;
|
|
33166
33310
|
const REACT_COMPILER_ENABLED_FLAG_PATTERN = /["']?reactCompiler["']?\s*:\s*(?:true\b|\{)/;
|
|
33311
|
+
const STATIC_EXPORT_OUTPUT_PATTERN = /(?:^|[^.\w])["']?output["']?\s*:\s*["']export["']/m;
|
|
33167
33312
|
const hasCompilerPackage = (packageJson) => {
|
|
33168
33313
|
const allDependencies = {
|
|
33169
33314
|
...packageJson.peerDependencies,
|
|
@@ -33196,34 +33341,10 @@ const detectReactCompiler = (directory, packageJson) => {
|
|
|
33196
33341
|
}
|
|
33197
33342
|
return false;
|
|
33198
33343
|
};
|
|
33199
|
-
const
|
|
33200
|
-
|
|
33201
|
-
|
|
33202
|
-
|
|
33203
|
-
"react-scripts": "cra",
|
|
33204
|
-
"@remix-run/react": "remix",
|
|
33205
|
-
gatsby: "gatsby",
|
|
33206
|
-
expo: "expo",
|
|
33207
|
-
"react-native": "react-native"
|
|
33208
|
-
};
|
|
33209
|
-
const FRAMEWORK_DISPLAY_NAMES = {
|
|
33210
|
-
nextjs: "Next.js",
|
|
33211
|
-
"tanstack-start": "TanStack Start",
|
|
33212
|
-
vite: "Vite",
|
|
33213
|
-
cra: "Create React App",
|
|
33214
|
-
remix: "Remix",
|
|
33215
|
-
gatsby: "Gatsby",
|
|
33216
|
-
expo: "Expo",
|
|
33217
|
-
"react-native": "React Native",
|
|
33218
|
-
preact: "Preact",
|
|
33219
|
-
unknown: "React"
|
|
33220
|
-
};
|
|
33221
|
-
const formatFrameworkName = (framework) => FRAMEWORK_DISPLAY_NAMES[framework];
|
|
33222
|
-
const detectFramework = (dependencies) => {
|
|
33223
|
-
for (const [packageName, frameworkName] of Object.entries(FRAMEWORK_PACKAGES)) if (dependencies[packageName]) return frameworkName;
|
|
33224
|
-
if (dependencies.preact && !dependencies.react) return "preact";
|
|
33225
|
-
return "unknown";
|
|
33226
|
-
};
|
|
33344
|
+
const detectNextjsStaticExport = (directory) => NEXT_CONFIG_FILENAMES.some((filename) => {
|
|
33345
|
+
const filePath = Path.join(directory, filename);
|
|
33346
|
+
return isFile(filePath) && STATIC_EXPORT_OUTPUT_PATTERN.test(NFS.readFileSync(filePath, "utf-8"));
|
|
33347
|
+
});
|
|
33227
33348
|
const UNRESOLVABLE_PROTOCOL_VERSION = /^(?:file|git|github|https?|link|patch|portal|workspace|npm):/i;
|
|
33228
33349
|
const DIST_TAG_VERSION = /^[a-z][a-z0-9._-]*$/i;
|
|
33229
33350
|
const WILDCARD_VERSION = /^[*xX](?:\.[*xX])*$/;
|
|
@@ -33361,10 +33482,76 @@ const isConcreteDependencyVersion = (version) => {
|
|
|
33361
33482
|
const normalizedVersion = normalizeDependencyVersion(version);
|
|
33362
33483
|
return normalizedVersion !== null && /\d/.test(normalizedVersion);
|
|
33363
33484
|
};
|
|
33364
|
-
const
|
|
33365
|
-
if (
|
|
33366
|
-
|
|
33367
|
-
return
|
|
33485
|
+
const isMajorMinorAtLeast = (detected, required) => {
|
|
33486
|
+
if (detected === null) return true;
|
|
33487
|
+
if (detected.major !== required.major) return detected.major > required.major;
|
|
33488
|
+
return detected.minor >= required.minor;
|
|
33489
|
+
};
|
|
33490
|
+
const parseReactMajor = (reactVersion) => {
|
|
33491
|
+
if (typeof reactVersion !== "string") return null;
|
|
33492
|
+
return getLowestDependencyMajor(reactVersion);
|
|
33493
|
+
};
|
|
33494
|
+
const MAJOR_MINOR_PATTERN = /(\d{1,4})\.(\d{1,4})/;
|
|
33495
|
+
const MAJOR_ONLY_PATTERN = /(\d{1,4})/;
|
|
33496
|
+
const UPPER_BOUND_COMPARATOR_PATTERN = /<=?\s{0,8}\d{1,4}(?:\.\d{1,4}){0,2}(?:-[^\s,|]+)?/g;
|
|
33497
|
+
const parseReactMajorMinor = (reactVersion) => {
|
|
33498
|
+
if (typeof reactVersion !== "string") return null;
|
|
33499
|
+
const trimmed = reactVersion.trim();
|
|
33500
|
+
if (trimmed.length === 0) return null;
|
|
33501
|
+
const lowerBoundsOnly = trimmed.replace(UPPER_BOUND_COMPARATOR_PATTERN, " ").trim();
|
|
33502
|
+
if (lowerBoundsOnly.length === 0) return null;
|
|
33503
|
+
const majorMinorMatch = lowerBoundsOnly.match(MAJOR_MINOR_PATTERN);
|
|
33504
|
+
if (majorMinorMatch) {
|
|
33505
|
+
const major = Number.parseInt(majorMinorMatch[1], 10);
|
|
33506
|
+
const minor = Number.parseInt(majorMinorMatch[2], 10);
|
|
33507
|
+
if (!Number.isFinite(major) || major <= 0) return null;
|
|
33508
|
+
if (!Number.isFinite(minor) || minor < 0) return null;
|
|
33509
|
+
return {
|
|
33510
|
+
major,
|
|
33511
|
+
minor
|
|
33512
|
+
};
|
|
33513
|
+
}
|
|
33514
|
+
const majorOnlyMatch = lowerBoundsOnly.match(MAJOR_ONLY_PATTERN);
|
|
33515
|
+
if (!majorOnlyMatch) return null;
|
|
33516
|
+
const major = Number.parseInt(majorOnlyMatch[1], 10);
|
|
33517
|
+
if (!Number.isFinite(major) || major <= 0) return null;
|
|
33518
|
+
return {
|
|
33519
|
+
major,
|
|
33520
|
+
minor: 0
|
|
33521
|
+
};
|
|
33522
|
+
};
|
|
33523
|
+
const parseLowerBoundVersion = (versionSpec) => import_semver.validRange(versionSpec) !== null ? import_semver.minVersion(versionSpec) : import_semver.coerce(versionSpec);
|
|
33524
|
+
const parseTailwindMajorMinor = (tailwindVersion) => {
|
|
33525
|
+
if (typeof tailwindVersion !== "string") return null;
|
|
33526
|
+
const trimmed = tailwindVersion.trim();
|
|
33527
|
+
if (trimmed.length === 0) return null;
|
|
33528
|
+
const lowerBound = parseLowerBoundVersion(trimmed);
|
|
33529
|
+
if (lowerBound === null || lowerBound.major <= 0) return null;
|
|
33530
|
+
return {
|
|
33531
|
+
major: lowerBound.major,
|
|
33532
|
+
minor: lowerBound.minor
|
|
33533
|
+
};
|
|
33534
|
+
};
|
|
33535
|
+
const hasUpperBoundOnlyPeerRange = (range) => {
|
|
33536
|
+
if (typeof range !== "string") return false;
|
|
33537
|
+
const normalizedRange = normalizeDependencyVersion(range);
|
|
33538
|
+
if (normalizedRange === null) return false;
|
|
33539
|
+
return splitDependencyVersionBranches(normalizedRange).some((branch) => {
|
|
33540
|
+
const normalizedBranch = normalizeDependencyVersion(branch);
|
|
33541
|
+
return normalizedBranch !== null && getBranchLowestMajor(normalizedBranch) === null && hasUpperBoundComparator(normalizedBranch);
|
|
33542
|
+
});
|
|
33543
|
+
};
|
|
33544
|
+
const peerRangeMinMajor = (range) => {
|
|
33545
|
+
if (typeof range !== "string") return null;
|
|
33546
|
+
return getLowestDependencyMajor(range);
|
|
33547
|
+
};
|
|
33548
|
+
const resolveEffectiveReactMajor = (reactVersion, packageJson) => {
|
|
33549
|
+
const installedReactMajor = parseReactMajor(reactVersion);
|
|
33550
|
+
const peerReactRange = packageJson.peerDependencies?.react;
|
|
33551
|
+
if (typeof peerReactRange !== "string") return installedReactMajor;
|
|
33552
|
+
const peerFloor = peerRangeMinMajor(peerReactRange);
|
|
33553
|
+
if (peerFloor === null) return hasUpperBoundOnlyPeerRange(peerReactRange) ? null : installedReactMajor;
|
|
33554
|
+
return installedReactMajor !== null ? Math.min(installedReactMajor, peerFloor) : peerFloor;
|
|
33368
33555
|
};
|
|
33369
33556
|
const isCatalogReference = (version) => version.startsWith("catalog:");
|
|
33370
33557
|
const extractCatalogName = (version) => {
|
|
@@ -33506,6 +33693,16 @@ const resolveCatalogVersion = (packageJson, packageName, rootDirectory, explicit
|
|
|
33506
33693
|
}
|
|
33507
33694
|
return null;
|
|
33508
33695
|
};
|
|
33696
|
+
const REACT_SECTIONS = [
|
|
33697
|
+
"dependencies",
|
|
33698
|
+
"peerDependencies",
|
|
33699
|
+
"devDependencies"
|
|
33700
|
+
];
|
|
33701
|
+
const TAILWIND_ZOD_SECTIONS = [
|
|
33702
|
+
"dependencies",
|
|
33703
|
+
"devDependencies",
|
|
33704
|
+
"peerDependencies"
|
|
33705
|
+
];
|
|
33509
33706
|
const EMPTY_DEPENDENCY_INFO = {
|
|
33510
33707
|
reactVersion: null,
|
|
33511
33708
|
tailwindVersion: null,
|
|
@@ -33546,6 +33743,10 @@ const extractDependencyInfo = (packageJson) => {
|
|
|
33546
33743
|
framework: detectFramework(allDependencies)
|
|
33547
33744
|
};
|
|
33548
33745
|
};
|
|
33746
|
+
const getDependencySpec = (packageJson, packageName) => {
|
|
33747
|
+
const spec = packageJson.dependencies?.[packageName] ?? packageJson.devDependencies?.[packageName] ?? packageJson.peerDependencies?.[packageName] ?? packageJson.optionalDependencies?.[packageName];
|
|
33748
|
+
return typeof spec === "string" ? spec : null;
|
|
33749
|
+
};
|
|
33549
33750
|
const getDependencyDeclaration = ({ packageJson, packageName, sections }) => {
|
|
33550
33751
|
for (const section of sections) {
|
|
33551
33752
|
const version = packageJson[section]?.[packageName];
|
|
@@ -33562,31 +33763,141 @@ const getDependencyDeclaration = ({ packageJson, packageName, sections }) => {
|
|
|
33562
33763
|
version: null
|
|
33563
33764
|
};
|
|
33564
33765
|
};
|
|
33565
|
-
const
|
|
33566
|
-
|
|
33567
|
-
|
|
33568
|
-
|
|
33569
|
-
|
|
33766
|
+
const REACT_DEPENDENCY_NAMES = new Set([
|
|
33767
|
+
"react",
|
|
33768
|
+
"react-native",
|
|
33769
|
+
"next",
|
|
33770
|
+
"preact"
|
|
33771
|
+
]);
|
|
33772
|
+
const hasReactDependency = (packageJson) => {
|
|
33773
|
+
const allDependencies = {
|
|
33774
|
+
...packageJson.peerDependencies,
|
|
33775
|
+
...packageJson.dependencies,
|
|
33776
|
+
...packageJson.devDependencies
|
|
33777
|
+
};
|
|
33778
|
+
return Object.keys(allDependencies).some((packageName) => REACT_DEPENDENCY_NAMES.has(packageName));
|
|
33779
|
+
};
|
|
33780
|
+
const getPreactVersion = (packageJson) => {
|
|
33781
|
+
return {
|
|
33782
|
+
...packageJson.peerDependencies,
|
|
33783
|
+
...packageJson.dependencies,
|
|
33784
|
+
...packageJson.devDependencies
|
|
33785
|
+
}.preact ?? null;
|
|
33786
|
+
};
|
|
33787
|
+
const TANSTACK_QUERY_PACKAGES = new Set([
|
|
33788
|
+
"@tanstack/react-query",
|
|
33789
|
+
"@tanstack/query-core",
|
|
33790
|
+
"react-query"
|
|
33791
|
+
]);
|
|
33792
|
+
const hasTanStackQuery = (packageJson) => {
|
|
33793
|
+
const allDependencies = {
|
|
33794
|
+
...packageJson.peerDependencies,
|
|
33795
|
+
...packageJson.dependencies,
|
|
33796
|
+
...packageJson.devDependencies
|
|
33797
|
+
};
|
|
33798
|
+
return Object.keys(allDependencies).some((packageName) => TANSTACK_QUERY_PACKAGES.has(packageName));
|
|
33799
|
+
};
|
|
33800
|
+
const resolveCatalogBackedDependencyVersion = ({ rootDirectory, rootPackageJson, packageName, version }) => {
|
|
33801
|
+
if (version === null || !isCatalogReference(version)) return version;
|
|
33802
|
+
const catalogName = extractCatalogName(version);
|
|
33803
|
+
const resolvedLocalVersion = resolveCatalogVersion(rootPackageJson, packageName, rootDirectory, catalogName);
|
|
33804
|
+
if (resolvedLocalVersion) return resolvedLocalVersion;
|
|
33805
|
+
const monorepoRoot = findMonorepoRoot(rootDirectory);
|
|
33806
|
+
if (!monorepoRoot) return version;
|
|
33807
|
+
const monorepoPackageJsonPath = Path.join(monorepoRoot, "package.json");
|
|
33808
|
+
if (!isFile(monorepoPackageJsonPath)) return version;
|
|
33809
|
+
return resolveCatalogVersion(readPackageJson(monorepoPackageJsonPath), packageName, monorepoRoot, catalogName) ?? version;
|
|
33810
|
+
};
|
|
33811
|
+
/**
|
|
33812
|
+
* The chain of ancestor directories of `startDirectory`, nearest first, up to
|
|
33813
|
+
* and including the filesystem root. With `includeStart`, the chain begins at
|
|
33814
|
+
* `startDirectory` itself; otherwise at its parent. The one place the "walk up
|
|
33815
|
+
* the tree" traversal is expressed, so each caller adds only its own match /
|
|
33816
|
+
* stop policy instead of re-deriving the `path.dirname` loop.
|
|
33817
|
+
*/
|
|
33818
|
+
const ancestorDirectories = (startDirectory, { includeStart }) => {
|
|
33819
|
+
const directories = [];
|
|
33820
|
+
let currentDirectory = includeStart ? startDirectory : Path.dirname(startDirectory);
|
|
33821
|
+
while (true) {
|
|
33822
|
+
directories.push(currentDirectory);
|
|
33823
|
+
const parentDirectory = Path.dirname(currentDirectory);
|
|
33824
|
+
if (parentDirectory === currentDirectory) return directories;
|
|
33825
|
+
currentDirectory = parentDirectory;
|
|
33570
33826
|
}
|
|
33571
33827
|
};
|
|
33572
|
-
|
|
33573
|
-
|
|
33574
|
-
|
|
33575
|
-
|
|
33576
|
-
|
|
33577
|
-
|
|
33578
|
-
|
|
33579
|
-
|
|
33580
|
-
|
|
33581
|
-
|
|
33582
|
-
|
|
33583
|
-
|
|
33584
|
-
|
|
33585
|
-
|
|
33586
|
-
|
|
33587
|
-
|
|
33828
|
+
/**
|
|
33829
|
+
* Walk up from `startDirectory` to the nearest ancestor that owns a
|
|
33830
|
+
* `package.json`, stopping at (and including) the enclosing project boundary
|
|
33831
|
+
* — the working tree's git root or a monorepo root. Lets a scan of a package
|
|
33832
|
+
* subfolder that has no `package.json` of its own adopt the nearest enclosing
|
|
33833
|
+
* package as its project root, so it inherits that package's dependency +
|
|
33834
|
+
* framework detection instead of synthesizing an empty (React-blind) project.
|
|
33835
|
+
*
|
|
33836
|
+
* Generalizes the older monorepo-root-only lookup: the nearest ancestor is the
|
|
33837
|
+
* most specific owning package (a leaf workspace, not just the repo root), so
|
|
33838
|
+
* a subfolder of one workspace no longer borrows a sibling's capabilities.
|
|
33839
|
+
*
|
|
33840
|
+
* Returns `null` when no ancestor `package.json` exists inside the project
|
|
33841
|
+
* boundary (a loose tree of files, or a subfolder of a package-less repo).
|
|
33842
|
+
*/
|
|
33843
|
+
const findNearestAncestorPackageJson = (startDirectory) => {
|
|
33844
|
+
if (isProjectBoundary(startDirectory)) return null;
|
|
33845
|
+
for (const directory of ancestorDirectories(startDirectory, { includeStart: false })) {
|
|
33846
|
+
if (isFile(Path.join(directory, "package.json"))) return directory;
|
|
33847
|
+
if (isProjectBoundary(directory)) return null;
|
|
33588
33848
|
}
|
|
33589
|
-
return
|
|
33849
|
+
return null;
|
|
33850
|
+
};
|
|
33851
|
+
const NAMES = new Set([
|
|
33852
|
+
"react-native",
|
|
33853
|
+
"react-native-tvos",
|
|
33854
|
+
...new Set([
|
|
33855
|
+
"expo",
|
|
33856
|
+
"expo-router",
|
|
33857
|
+
"@expo/cli",
|
|
33858
|
+
"@expo/metro-config",
|
|
33859
|
+
"@expo/metro-runtime"
|
|
33860
|
+
]),
|
|
33861
|
+
"react-native-windows",
|
|
33862
|
+
"react-native-macos"
|
|
33863
|
+
]);
|
|
33864
|
+
const PREFIXES = ["@react-native/", "@react-native-"];
|
|
33865
|
+
const isReactNativeDependencyName = (dependencyName) => {
|
|
33866
|
+
if (NAMES.has(dependencyName)) return true;
|
|
33867
|
+
for (const prefix of PREFIXES) if (dependencyName.startsWith(prefix)) return true;
|
|
33868
|
+
return false;
|
|
33869
|
+
};
|
|
33870
|
+
const containsAnyReactNativeDependency = (section) => {
|
|
33871
|
+
if (!section) return false;
|
|
33872
|
+
for (const dependencyName of Object.keys(section)) if (isReactNativeDependencyName(dependencyName)) return true;
|
|
33873
|
+
return false;
|
|
33874
|
+
};
|
|
33875
|
+
const isPackageJsonReactNativeAware = (packageJson) => {
|
|
33876
|
+
if (typeof packageJson["react-native"] === "string") return true;
|
|
33877
|
+
if (containsAnyReactNativeDependency(packageJson.dependencies)) return true;
|
|
33878
|
+
if (containsAnyReactNativeDependency(packageJson.devDependencies)) return true;
|
|
33879
|
+
if (containsAnyReactNativeDependency(packageJson.peerDependencies)) return true;
|
|
33880
|
+
if (containsAnyReactNativeDependency(packageJson.optionalDependencies)) return true;
|
|
33881
|
+
return false;
|
|
33882
|
+
};
|
|
33883
|
+
const REANIMATED_DEPENDENCY_NAME$1 = "react-native-reanimated";
|
|
33884
|
+
const isPackageJsonReanimatedAware = (packageJson) => {
|
|
33885
|
+
const allDependencies = {
|
|
33886
|
+
...packageJson.peerDependencies,
|
|
33887
|
+
...packageJson.dependencies,
|
|
33888
|
+
...packageJson.devDependencies,
|
|
33889
|
+
...packageJson.optionalDependencies
|
|
33890
|
+
};
|
|
33891
|
+
return Object.hasOwn(allDependencies, REANIMATED_DEPENDENCY_NAME$1);
|
|
33892
|
+
};
|
|
33893
|
+
const getWorkspacePatterns = (rootDirectory, packageJson) => {
|
|
33894
|
+
const pnpmPatterns = parsePnpmWorkspacePatterns(rootDirectory);
|
|
33895
|
+
if (pnpmPatterns.length > 0) return pnpmPatterns;
|
|
33896
|
+
if (Array.isArray(packageJson.workspaces)) return packageJson.workspaces;
|
|
33897
|
+
if (packageJson.workspaces?.packages) return packageJson.workspaces.packages;
|
|
33898
|
+
const nxPatterns = getNxWorkspaceDirectories(rootDirectory);
|
|
33899
|
+
if (nxPatterns.length > 0) return nxPatterns;
|
|
33900
|
+
return [];
|
|
33590
33901
|
};
|
|
33591
33902
|
const parsePnpmWorkspacePatterns = (rootDirectory) => {
|
|
33592
33903
|
const workspacePath = Path.join(rootDirectory, "pnpm-workspace.yaml");
|
|
@@ -33605,18 +33916,24 @@ const parsePnpmWorkspacePatterns = (rootDirectory) => {
|
|
|
33605
33916
|
}
|
|
33606
33917
|
return patterns;
|
|
33607
33918
|
};
|
|
33608
|
-
const
|
|
33609
|
-
|
|
33610
|
-
|
|
33611
|
-
|
|
33612
|
-
|
|
33613
|
-
|
|
33614
|
-
if (
|
|
33615
|
-
|
|
33616
|
-
|
|
33617
|
-
const
|
|
33618
|
-
|
|
33619
|
-
|
|
33919
|
+
const NX_PROJECT_DISCOVERY_DIRS = [
|
|
33920
|
+
"apps",
|
|
33921
|
+
"libs",
|
|
33922
|
+
"packages"
|
|
33923
|
+
];
|
|
33924
|
+
const getNxWorkspaceDirectories = (rootDirectory) => {
|
|
33925
|
+
if (!isFile(Path.join(rootDirectory, "nx.json"))) return [];
|
|
33926
|
+
const collected = [];
|
|
33927
|
+
for (const candidate of NX_PROJECT_DISCOVERY_DIRS) {
|
|
33928
|
+
const candidatePath = Path.join(rootDirectory, candidate);
|
|
33929
|
+
if (!isDirectory(candidatePath)) continue;
|
|
33930
|
+
for (const entry of readDirectoryEntries(candidatePath)) {
|
|
33931
|
+
if (!entry.isDirectory()) continue;
|
|
33932
|
+
const projectDirectory = Path.join(candidatePath, entry.name);
|
|
33933
|
+
if (isFile(Path.join(projectDirectory, "project.json")) || isFile(Path.join(projectDirectory, "package.json"))) collected.push(`${candidate}/${entry.name}`);
|
|
33934
|
+
}
|
|
33935
|
+
}
|
|
33936
|
+
return collected;
|
|
33620
33937
|
};
|
|
33621
33938
|
const resolveWorkspaceDirectories = (rootDirectory, pattern) => {
|
|
33622
33939
|
const cleanPattern = pattern.replace(/["']/g, "").replace(/\/\*\*$/, "/*");
|
|
@@ -33636,6 +33953,38 @@ const resolveWorkspaceDirectories = (rootDirectory, pattern) => {
|
|
|
33636
33953
|
}
|
|
33637
33954
|
return resolved;
|
|
33638
33955
|
};
|
|
33956
|
+
const listWorkspacePackages = (rootDirectory) => {
|
|
33957
|
+
const packageJsonPath = Path.join(rootDirectory, "package.json");
|
|
33958
|
+
if (!isFile(packageJsonPath)) return [];
|
|
33959
|
+
const packageJson = readPackageJson(packageJsonPath);
|
|
33960
|
+
const patterns = getWorkspacePatterns(rootDirectory, packageJson);
|
|
33961
|
+
if (patterns.length === 0) return [];
|
|
33962
|
+
const packages = [];
|
|
33963
|
+
const seenDirectories = /* @__PURE__ */ new Set();
|
|
33964
|
+
const pushIfNew = (workspacePackage) => {
|
|
33965
|
+
if (seenDirectories.has(workspacePackage.directory)) return;
|
|
33966
|
+
seenDirectories.add(workspacePackage.directory);
|
|
33967
|
+
packages.push(workspacePackage);
|
|
33968
|
+
};
|
|
33969
|
+
if (hasReactDependency(packageJson)) pushIfNew({
|
|
33970
|
+
name: packageJson.name ?? Path.basename(rootDirectory),
|
|
33971
|
+
directory: rootDirectory
|
|
33972
|
+
});
|
|
33973
|
+
for (const pattern of patterns) {
|
|
33974
|
+
const directories = resolveWorkspaceDirectories(rootDirectory, pattern);
|
|
33975
|
+
for (const workspaceDirectory of directories) {
|
|
33976
|
+
const workspacePackageJson = readPackageJson(Path.join(workspaceDirectory, "package.json"));
|
|
33977
|
+
if (!hasReactDependency(workspacePackageJson)) continue;
|
|
33978
|
+
pushIfNew({
|
|
33979
|
+
name: workspacePackageJson.name ?? Path.basename(workspaceDirectory),
|
|
33980
|
+
directory: workspaceDirectory
|
|
33981
|
+
});
|
|
33982
|
+
}
|
|
33983
|
+
}
|
|
33984
|
+
return packages;
|
|
33985
|
+
};
|
|
33986
|
+
const REANIMATED_DEPENDENCY_NAME = "react-native-reanimated";
|
|
33987
|
+
const SHOPIFY_FLASH_LIST_PACKAGE_NAME = "@shopify/flash-list";
|
|
33639
33988
|
const resolveWorkspaceDependencyVersion = ({ concreteVersion, packageName, rootDirectory, rootPackageJson, sections, workspaceDirectory, workspacePackageJson }) => {
|
|
33640
33989
|
const dependencyDeclaration = getDependencyDeclaration({
|
|
33641
33990
|
packageJson: workspacePackageJson,
|
|
@@ -33653,24 +34002,76 @@ const shouldReplaceReactVersion = (currentVersion, nextVersion) => {
|
|
|
33653
34002
|
if (nextMajor === null) return false;
|
|
33654
34003
|
return nextMajor < currentMajor;
|
|
33655
34004
|
};
|
|
33656
|
-
const
|
|
33657
|
-
|
|
33658
|
-
|
|
33659
|
-
|
|
33660
|
-
|
|
34005
|
+
const evaluateManifestFacts = (facts, packageJson, directory) => {
|
|
34006
|
+
if (facts.expo.version === null) {
|
|
34007
|
+
const spec = getDependencySpec(packageJson, "expo");
|
|
34008
|
+
if (spec !== null) facts.expo = {
|
|
34009
|
+
version: spec,
|
|
34010
|
+
sourceDirectory: directory
|
|
34011
|
+
};
|
|
34012
|
+
}
|
|
34013
|
+
if (facts.next.version === null) {
|
|
34014
|
+
const spec = getDependencySpec(packageJson, "next");
|
|
34015
|
+
if (spec !== null) facts.next = {
|
|
34016
|
+
version: spec,
|
|
34017
|
+
sourceDirectory: directory
|
|
34018
|
+
};
|
|
34019
|
+
}
|
|
34020
|
+
if (facts.shopifyFlashList.version === null) {
|
|
34021
|
+
const spec = getDependencySpec(packageJson, SHOPIFY_FLASH_LIST_PACKAGE_NAME);
|
|
34022
|
+
if (spec !== null) facts.shopifyFlashList = {
|
|
34023
|
+
version: spec,
|
|
34024
|
+
sourceDirectory: directory
|
|
34025
|
+
};
|
|
34026
|
+
}
|
|
34027
|
+
if (facts.reanimatedVersion === null) {
|
|
34028
|
+
const spec = getDependencySpec(packageJson, REANIMATED_DEPENDENCY_NAME);
|
|
34029
|
+
if (spec !== null) facts.reanimatedVersion = spec;
|
|
34030
|
+
}
|
|
34031
|
+
facts.hasReactNativeAwarePackage = facts.hasReactNativeAwarePackage || isPackageJsonReactNativeAware(packageJson);
|
|
34032
|
+
facts.hasReanimatedAwarePackage = facts.hasReanimatedAwarePackage || isPackageJsonReanimatedAware(packageJson);
|
|
34033
|
+
};
|
|
34034
|
+
const collectWorkspaceFacts = (rootDirectory, rootPackageJson, { collectReactGroup }) => {
|
|
34035
|
+
const facts = {
|
|
34036
|
+
reactVersion: null,
|
|
34037
|
+
tailwindVersion: null,
|
|
34038
|
+
zodVersion: null,
|
|
34039
|
+
framework: "unknown",
|
|
34040
|
+
expo: {
|
|
34041
|
+
version: null,
|
|
34042
|
+
sourceDirectory: null
|
|
34043
|
+
},
|
|
34044
|
+
next: {
|
|
34045
|
+
version: null,
|
|
34046
|
+
sourceDirectory: null
|
|
34047
|
+
},
|
|
34048
|
+
shopifyFlashList: {
|
|
34049
|
+
version: null,
|
|
34050
|
+
sourceDirectory: null
|
|
34051
|
+
},
|
|
34052
|
+
hasReactNativeAwarePackage: false,
|
|
34053
|
+
hasReanimatedAwarePackage: false,
|
|
34054
|
+
reanimatedVersion: null
|
|
34055
|
+
};
|
|
34056
|
+
evaluateManifestFacts(facts, rootPackageJson, rootDirectory);
|
|
34057
|
+
let isReactGroupSettled = !collectReactGroup;
|
|
34058
|
+
const visitedDirectories = /* @__PURE__ */ new Set();
|
|
34059
|
+
for (const pattern of getWorkspacePatterns(rootDirectory, rootPackageJson)) {
|
|
34060
|
+
const directories = [...resolveWorkspaceDirectories(rootDirectory, pattern)].sort();
|
|
33661
34061
|
for (const workspaceDirectory of directories) {
|
|
34062
|
+
if (visitedDirectories.has(workspaceDirectory)) continue;
|
|
34063
|
+
visitedDirectories.add(workspaceDirectory);
|
|
33662
34064
|
const workspacePackageJson = readPackageJson(Path.join(workspaceDirectory, "package.json"));
|
|
34065
|
+
evaluateManifestFacts(facts, workspacePackageJson, workspaceDirectory);
|
|
33663
34066
|
const info = extractDependencyInfo(workspacePackageJson);
|
|
34067
|
+
if (info.framework !== "unknown" && frameworkMergeRank(info.framework) < frameworkMergeRank(facts.framework)) facts.framework = info.framework;
|
|
34068
|
+
if (isReactGroupSettled) continue;
|
|
33664
34069
|
const reactVersion = resolveWorkspaceDependencyVersion({
|
|
33665
34070
|
concreteVersion: info.reactVersion,
|
|
33666
34071
|
packageName: "react",
|
|
33667
34072
|
rootDirectory,
|
|
33668
|
-
rootPackageJson
|
|
33669
|
-
sections:
|
|
33670
|
-
"dependencies",
|
|
33671
|
-
"peerDependencies",
|
|
33672
|
-
"devDependencies"
|
|
33673
|
-
],
|
|
34073
|
+
rootPackageJson,
|
|
34074
|
+
sections: REACT_SECTIONS,
|
|
33674
34075
|
workspaceDirectory,
|
|
33675
34076
|
workspacePackageJson
|
|
33676
34077
|
});
|
|
@@ -33678,12 +34079,8 @@ const findReactInWorkspaces = (rootDirectory, packageJson) => {
|
|
|
33678
34079
|
concreteVersion: info.tailwindVersion,
|
|
33679
34080
|
packageName: "tailwindcss",
|
|
33680
34081
|
rootDirectory,
|
|
33681
|
-
rootPackageJson
|
|
33682
|
-
sections:
|
|
33683
|
-
"dependencies",
|
|
33684
|
-
"devDependencies",
|
|
33685
|
-
"peerDependencies"
|
|
33686
|
-
],
|
|
34082
|
+
rootPackageJson,
|
|
34083
|
+
sections: TAILWIND_ZOD_SECTIONS,
|
|
33687
34084
|
workspaceDirectory,
|
|
33688
34085
|
workspacePackageJson
|
|
33689
34086
|
});
|
|
@@ -33691,24 +34088,19 @@ const findReactInWorkspaces = (rootDirectory, packageJson) => {
|
|
|
33691
34088
|
concreteVersion: info.zodVersion,
|
|
33692
34089
|
packageName: "zod",
|
|
33693
34090
|
rootDirectory,
|
|
33694
|
-
rootPackageJson
|
|
33695
|
-
sections:
|
|
33696
|
-
"dependencies",
|
|
33697
|
-
"devDependencies",
|
|
33698
|
-
"peerDependencies"
|
|
33699
|
-
],
|
|
34091
|
+
rootPackageJson,
|
|
34092
|
+
sections: TAILWIND_ZOD_SECTIONS,
|
|
33700
34093
|
workspaceDirectory,
|
|
33701
34094
|
workspacePackageJson
|
|
33702
34095
|
});
|
|
33703
|
-
if (reactVersion && shouldReplaceReactVersion(
|
|
33704
|
-
if (tailwindVersion && !
|
|
33705
|
-
if (zodVersion && !
|
|
33706
|
-
|
|
33707
|
-
|
|
33708
|
-
if (result.reactVersion && result.tailwindVersion && result.framework !== "unknown" && resultReactMajor !== null && resultReactMajor <= 17) return result;
|
|
34096
|
+
if (reactVersion && shouldReplaceReactVersion(facts.reactVersion, reactVersion)) facts.reactVersion = reactVersion;
|
|
34097
|
+
if (tailwindVersion && !facts.tailwindVersion) facts.tailwindVersion = tailwindVersion;
|
|
34098
|
+
if (zodVersion && !facts.zodVersion) facts.zodVersion = zodVersion;
|
|
34099
|
+
const settledReactMajor = parseReactMajor(facts.reactVersion);
|
|
34100
|
+
isReactGroupSettled = Boolean(facts.reactVersion && facts.tailwindVersion && facts.framework !== "unknown" && settledReactMajor !== null && settledReactMajor <= 17);
|
|
33709
34101
|
}
|
|
33710
34102
|
}
|
|
33711
|
-
return
|
|
34103
|
+
return facts;
|
|
33712
34104
|
};
|
|
33713
34105
|
const findDependencyInfoFromMonorepoRoot = (directory) => {
|
|
33714
34106
|
const monorepoRoot = findMonorepoRoot(directory);
|
|
@@ -33722,29 +34114,17 @@ const findDependencyInfoFromMonorepoRoot = (directory) => {
|
|
|
33722
34114
|
const leafReactDeclaration = leafPackageJson ? getDependencyDeclaration({
|
|
33723
34115
|
packageJson: leafPackageJson,
|
|
33724
34116
|
packageName: "react",
|
|
33725
|
-
sections:
|
|
33726
|
-
"dependencies",
|
|
33727
|
-
"peerDependencies",
|
|
33728
|
-
"devDependencies"
|
|
33729
|
-
]
|
|
34117
|
+
sections: REACT_SECTIONS
|
|
33730
34118
|
}) : null;
|
|
33731
34119
|
const leafTailwindDeclaration = leafPackageJson ? getDependencyDeclaration({
|
|
33732
34120
|
packageJson: leafPackageJson,
|
|
33733
34121
|
packageName: "tailwindcss",
|
|
33734
|
-
sections:
|
|
33735
|
-
"dependencies",
|
|
33736
|
-
"devDependencies",
|
|
33737
|
-
"peerDependencies"
|
|
33738
|
-
]
|
|
34122
|
+
sections: TAILWIND_ZOD_SECTIONS
|
|
33739
34123
|
}) : null;
|
|
33740
34124
|
const leafZodDeclaration = leafPackageJson ? getDependencyDeclaration({
|
|
33741
34125
|
packageJson: leafPackageJson,
|
|
33742
34126
|
packageName: "zod",
|
|
33743
|
-
sections:
|
|
33744
|
-
"dependencies",
|
|
33745
|
-
"devDependencies",
|
|
33746
|
-
"peerDependencies"
|
|
33747
|
-
]
|
|
34127
|
+
sections: TAILWIND_ZOD_SECTIONS
|
|
33748
34128
|
}) : null;
|
|
33749
34129
|
const shouldUseReactFallback = !leafReactDeclaration?.hasDeclaration;
|
|
33750
34130
|
const shouldUseTailwindFallback = leafTailwindDeclaration?.hasDeclaration ?? true;
|
|
@@ -33752,38 +34132,15 @@ const findDependencyInfoFromMonorepoRoot = (directory) => {
|
|
|
33752
34132
|
const reactCatalogVersion = shouldUseReactFallback ? resolveCatalogVersion(rootPackageJson, "react", monorepoRoot, leafReactDeclaration?.catalogReference) : null;
|
|
33753
34133
|
const tailwindCatalogVersion = shouldUseTailwindFallback ? resolveCatalogVersion(rootPackageJson, "tailwindcss", monorepoRoot, leafTailwindDeclaration?.catalogReference) : null;
|
|
33754
34134
|
const zodCatalogVersion = shouldUseZodFallback ? resolveCatalogVersion(rootPackageJson, "zod", monorepoRoot, leafZodDeclaration?.catalogReference) : null;
|
|
33755
|
-
const
|
|
34135
|
+
const workspaceFacts = collectWorkspaceFacts(monorepoRoot, rootPackageJson, { collectReactGroup: true });
|
|
33756
34136
|
return {
|
|
33757
|
-
reactVersion: shouldUseReactFallback ? reactCatalogVersion ?? rootInfo.reactVersion ??
|
|
33758
|
-
tailwindVersion: shouldUseTailwindFallback ? tailwindCatalogVersion ?? rootInfo.tailwindVersion ??
|
|
33759
|
-
zodVersion: shouldUseZodFallback ? zodCatalogVersion ?? rootInfo.zodVersion ??
|
|
33760
|
-
framework: rootInfo.framework !== "unknown" ? rootInfo.framework :
|
|
34137
|
+
reactVersion: shouldUseReactFallback ? reactCatalogVersion ?? rootInfo.reactVersion ?? workspaceFacts.reactVersion : rootInfo.reactVersion ?? workspaceFacts.reactVersion,
|
|
34138
|
+
tailwindVersion: shouldUseTailwindFallback ? tailwindCatalogVersion ?? rootInfo.tailwindVersion ?? workspaceFacts.tailwindVersion : null,
|
|
34139
|
+
zodVersion: shouldUseZodFallback ? zodCatalogVersion ?? rootInfo.zodVersion ?? workspaceFacts.zodVersion : null,
|
|
34140
|
+
framework: rootInfo.framework !== "unknown" ? rootInfo.framework : workspaceFacts.framework
|
|
33761
34141
|
};
|
|
33762
34142
|
};
|
|
33763
34143
|
/**
|
|
33764
|
-
* Walk up from `startDirectory` to the nearest ancestor that owns a
|
|
33765
|
-
* `package.json`, stopping at (and including) the enclosing project boundary
|
|
33766
|
-
* — the working tree's git root or a monorepo root. Lets a scan of a package
|
|
33767
|
-
* subfolder that has no `package.json` of its own adopt the nearest enclosing
|
|
33768
|
-
* package as its project root, so it inherits that package's dependency +
|
|
33769
|
-
* framework detection instead of synthesizing an empty (React-blind) project.
|
|
33770
|
-
*
|
|
33771
|
-
* Generalizes the older monorepo-root-only lookup: the nearest ancestor is the
|
|
33772
|
-
* most specific owning package (a leaf workspace, not just the repo root), so
|
|
33773
|
-
* a subfolder of one workspace no longer borrows a sibling's capabilities.
|
|
33774
|
-
*
|
|
33775
|
-
* Returns `null` when no ancestor `package.json` exists inside the project
|
|
33776
|
-
* boundary (a loose tree of files, or a subfolder of a package-less repo).
|
|
33777
|
-
*/
|
|
33778
|
-
const findNearestAncestorPackageJson = (startDirectory) => {
|
|
33779
|
-
if (isProjectBoundary(startDirectory)) return null;
|
|
33780
|
-
for (const directory of ancestorDirectories(startDirectory, { includeStart: false })) {
|
|
33781
|
-
if (isFile(Path.join(directory, "package.json"))) return directory;
|
|
33782
|
-
if (isProjectBoundary(directory)) return null;
|
|
33783
|
-
}
|
|
33784
|
-
return null;
|
|
33785
|
-
};
|
|
33786
|
-
/**
|
|
33787
34144
|
* Last-resort React detection: locate React the way Node's `node_modules`
|
|
33788
34145
|
* resolution would from `directory` — check each `node_modules/react` up the
|
|
33789
34146
|
* tree — but bounded to the enclosing repo so a globally installed React can't
|
|
@@ -33828,273 +34185,6 @@ const resolveInstalledReactVersion = (directory) => {
|
|
|
33828
34185
|
}
|
|
33829
34186
|
return null;
|
|
33830
34187
|
};
|
|
33831
|
-
const findInWorkspacePackageJsons = (rootDirectory, rootPackageJson, select) => {
|
|
33832
|
-
const rootValue = select(rootPackageJson);
|
|
33833
|
-
if (rootValue !== null) return rootValue;
|
|
33834
|
-
const patterns = getWorkspacePatterns(rootDirectory, rootPackageJson);
|
|
33835
|
-
if (patterns.length === 0) return null;
|
|
33836
|
-
const visitedDirectories = /* @__PURE__ */ new Set();
|
|
33837
|
-
for (const pattern of patterns) {
|
|
33838
|
-
const directories = [...resolveWorkspaceDirectories(rootDirectory, pattern)].sort();
|
|
33839
|
-
for (const workspaceDirectory of directories) {
|
|
33840
|
-
if (visitedDirectories.has(workspaceDirectory)) continue;
|
|
33841
|
-
visitedDirectories.add(workspaceDirectory);
|
|
33842
|
-
const value = select(readPackageJson(Path.join(workspaceDirectory, "package.json")));
|
|
33843
|
-
if (value !== null) return value;
|
|
33844
|
-
}
|
|
33845
|
-
}
|
|
33846
|
-
return null;
|
|
33847
|
-
};
|
|
33848
|
-
const someWorkspacePackageJson = (rootDirectory, rootPackageJson, predicate) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => predicate(packageJson) ? true : null) !== null;
|
|
33849
|
-
const NAMES = new Set([
|
|
33850
|
-
"react-native",
|
|
33851
|
-
"react-native-tvos",
|
|
33852
|
-
...new Set([
|
|
33853
|
-
"expo",
|
|
33854
|
-
"expo-router",
|
|
33855
|
-
"@expo/cli",
|
|
33856
|
-
"@expo/metro-config",
|
|
33857
|
-
"@expo/metro-runtime"
|
|
33858
|
-
]),
|
|
33859
|
-
"react-native-windows",
|
|
33860
|
-
"react-native-macos"
|
|
33861
|
-
]);
|
|
33862
|
-
const PREFIXES = ["@react-native/", "@react-native-"];
|
|
33863
|
-
const isReactNativeDependencyName = (dependencyName) => {
|
|
33864
|
-
if (NAMES.has(dependencyName)) return true;
|
|
33865
|
-
for (const prefix of PREFIXES) if (dependencyName.startsWith(prefix)) return true;
|
|
33866
|
-
return false;
|
|
33867
|
-
};
|
|
33868
|
-
const containsAnyReactNativeDependency = (section) => {
|
|
33869
|
-
if (!section) return false;
|
|
33870
|
-
for (const dependencyName of Object.keys(section)) if (isReactNativeDependencyName(dependencyName)) return true;
|
|
33871
|
-
return false;
|
|
33872
|
-
};
|
|
33873
|
-
const isPackageJsonReactNativeAware = (packageJson) => {
|
|
33874
|
-
if (typeof packageJson["react-native"] === "string") return true;
|
|
33875
|
-
if (containsAnyReactNativeDependency(packageJson.dependencies)) return true;
|
|
33876
|
-
if (containsAnyReactNativeDependency(packageJson.devDependencies)) return true;
|
|
33877
|
-
if (containsAnyReactNativeDependency(packageJson.peerDependencies)) return true;
|
|
33878
|
-
if (containsAnyReactNativeDependency(packageJson.optionalDependencies)) return true;
|
|
33879
|
-
return false;
|
|
33880
|
-
};
|
|
33881
|
-
const hasReactNativeWorkspaceAnywhere = (rootDirectory, rootPackageJson) => someWorkspacePackageJson(rootDirectory, rootPackageJson, isPackageJsonReactNativeAware);
|
|
33882
|
-
const getDependencySpec = (packageJson, packageName) => {
|
|
33883
|
-
const spec = packageJson.dependencies?.[packageName] ?? packageJson.devDependencies?.[packageName] ?? packageJson.peerDependencies?.[packageName] ?? packageJson.optionalDependencies?.[packageName];
|
|
33884
|
-
return typeof spec === "string" ? spec : null;
|
|
33885
|
-
};
|
|
33886
|
-
const findExpoVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, "expo"));
|
|
33887
|
-
const SHOPIFY_FLASH_LIST_PACKAGE_NAME = "@shopify/flash-list";
|
|
33888
|
-
const findShopifyFlashListVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, SHOPIFY_FLASH_LIST_PACKAGE_NAME));
|
|
33889
|
-
const resolveCatalogBackedDependencyVersion = ({ rootDirectory, rootPackageJson, packageName, version }) => {
|
|
33890
|
-
if (version === null || !isCatalogReference(version)) return version;
|
|
33891
|
-
const catalogName = extractCatalogName(version);
|
|
33892
|
-
const resolvedLocalVersion = resolveCatalogVersion(rootPackageJson, packageName, rootDirectory, catalogName);
|
|
33893
|
-
if (resolvedLocalVersion) return resolvedLocalVersion;
|
|
33894
|
-
const monorepoRoot = findMonorepoRoot(rootDirectory);
|
|
33895
|
-
if (!monorepoRoot) return version;
|
|
33896
|
-
const monorepoPackageJsonPath = path.join(monorepoRoot, "package.json");
|
|
33897
|
-
if (!isFile(monorepoPackageJsonPath)) return version;
|
|
33898
|
-
return resolveCatalogVersion(readPackageJson(monorepoPackageJsonPath), packageName, monorepoRoot, catalogName) ?? version;
|
|
33899
|
-
};
|
|
33900
|
-
const findNextjsVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, "next"));
|
|
33901
|
-
const getPreactVersion = (packageJson) => {
|
|
33902
|
-
return {
|
|
33903
|
-
...packageJson.peerDependencies,
|
|
33904
|
-
...packageJson.dependencies,
|
|
33905
|
-
...packageJson.devDependencies
|
|
33906
|
-
}.preact ?? null;
|
|
33907
|
-
};
|
|
33908
|
-
const TANSTACK_QUERY_PACKAGES = new Set([
|
|
33909
|
-
"@tanstack/react-query",
|
|
33910
|
-
"@tanstack/query-core",
|
|
33911
|
-
"react-query"
|
|
33912
|
-
]);
|
|
33913
|
-
const hasTanStackQuery = (packageJson) => {
|
|
33914
|
-
const allDependencies = {
|
|
33915
|
-
...packageJson.peerDependencies,
|
|
33916
|
-
...packageJson.dependencies,
|
|
33917
|
-
...packageJson.devDependencies
|
|
33918
|
-
};
|
|
33919
|
-
return Object.keys(allDependencies).some((packageName) => TANSTACK_QUERY_PACKAGES.has(packageName));
|
|
33920
|
-
};
|
|
33921
|
-
const REANIMATED_DEPENDENCY_NAME = "react-native-reanimated";
|
|
33922
|
-
const isPackageJsonReanimatedAware = (packageJson) => {
|
|
33923
|
-
const allDependencies = {
|
|
33924
|
-
...packageJson.peerDependencies,
|
|
33925
|
-
...packageJson.dependencies,
|
|
33926
|
-
...packageJson.devDependencies,
|
|
33927
|
-
...packageJson.optionalDependencies
|
|
33928
|
-
};
|
|
33929
|
-
return Object.hasOwn(allDependencies, REANIMATED_DEPENDENCY_NAME);
|
|
33930
|
-
};
|
|
33931
|
-
const parseZodMajor = (zodVersion) => {
|
|
33932
|
-
if (typeof zodVersion !== "string") return null;
|
|
33933
|
-
return getLowestDependencyMajor(zodVersion);
|
|
33934
|
-
};
|
|
33935
|
-
const hasUpperBoundOnlyPeerRange = (range) => {
|
|
33936
|
-
if (typeof range !== "string") return false;
|
|
33937
|
-
const normalizedRange = normalizeDependencyVersion(range);
|
|
33938
|
-
if (normalizedRange === null) return false;
|
|
33939
|
-
return splitDependencyVersionBranches(normalizedRange).some((branch) => {
|
|
33940
|
-
const normalizedBranch = normalizeDependencyVersion(branch);
|
|
33941
|
-
return normalizedBranch !== null && getBranchLowestMajor(normalizedBranch) === null && hasUpperBoundComparator(normalizedBranch);
|
|
33942
|
-
});
|
|
33943
|
-
};
|
|
33944
|
-
const peerRangeMinMajor = (range) => {
|
|
33945
|
-
if (typeof range !== "string") return null;
|
|
33946
|
-
return getLowestDependencyMajor(range);
|
|
33947
|
-
};
|
|
33948
|
-
const resolveEffectiveReactMajor = (reactVersion, packageJson) => {
|
|
33949
|
-
const installedReactMajor = parseReactMajor(reactVersion);
|
|
33950
|
-
const peerReactRange = packageJson.peerDependencies?.react;
|
|
33951
|
-
if (typeof peerReactRange !== "string") return installedReactMajor;
|
|
33952
|
-
const peerFloor = peerRangeMinMajor(peerReactRange);
|
|
33953
|
-
if (peerFloor === null) return hasUpperBoundOnlyPeerRange(peerReactRange) ? null : installedReactMajor;
|
|
33954
|
-
return installedReactMajor !== null ? Math.min(installedReactMajor, peerFloor) : peerFloor;
|
|
33955
|
-
};
|
|
33956
|
-
const TSCONFIG_FILENAME = "tsconfig.json";
|
|
33957
|
-
const isRelativeExtendsValue = (extendsValue) => extendsValue.startsWith("./") || extendsValue.startsWith("../") || Path.isAbsolute(extendsValue);
|
|
33958
|
-
const ensureJsonExtension = (filePath) => Path.extname(filePath) === "" ? `${filePath}.json` : filePath;
|
|
33959
|
-
const resolvePackageExtendsPath = (extendsValue, fromConfigDirectory) => {
|
|
33960
|
-
const requireFromConfig = createRequire(Path.join(fromConfigDirectory, "tsconfig.json"));
|
|
33961
|
-
const candidates = [
|
|
33962
|
-
extendsValue,
|
|
33963
|
-
ensureJsonExtension(extendsValue),
|
|
33964
|
-
`${extendsValue.replace(/\/$/, "")}/tsconfig.json`
|
|
33965
|
-
];
|
|
33966
|
-
for (const candidate of candidates) try {
|
|
33967
|
-
return requireFromConfig.resolve(candidate);
|
|
33968
|
-
} catch {
|
|
33969
|
-
continue;
|
|
33970
|
-
}
|
|
33971
|
-
return null;
|
|
33972
|
-
};
|
|
33973
|
-
const resolveExtendsPath = (extendsValue, fromConfigDirectory) => {
|
|
33974
|
-
if (isRelativeExtendsValue(extendsValue)) return ensureJsonExtension(Path.resolve(fromConfigDirectory, extendsValue));
|
|
33975
|
-
return resolvePackageExtendsPath(extendsValue, fromConfigDirectory);
|
|
33976
|
-
};
|
|
33977
|
-
const normalizeCompilerOptions = (compilerOptions) => {
|
|
33978
|
-
if (!isPlainObject(compilerOptions)) return { hasExplicitLib: false };
|
|
33979
|
-
const target = typeof compilerOptions.target === "string" ? compilerOptions.target : void 0;
|
|
33980
|
-
const hasExplicitLib = Object.hasOwn(compilerOptions, "lib");
|
|
33981
|
-
return {
|
|
33982
|
-
target,
|
|
33983
|
-
lib: Array.isArray(compilerOptions.lib) ? compilerOptions.lib.filter((entry) => typeof entry === "string") : void 0,
|
|
33984
|
-
hasExplicitLib
|
|
33985
|
-
};
|
|
33986
|
-
};
|
|
33987
|
-
const readTsConfig = (filePath) => {
|
|
33988
|
-
let content;
|
|
33989
|
-
try {
|
|
33990
|
-
content = NFS.readFileSync(filePath, "utf-8");
|
|
33991
|
-
} catch {
|
|
33992
|
-
return null;
|
|
33993
|
-
}
|
|
33994
|
-
const parsed = ts.parseConfigFileTextToJson(filePath, content);
|
|
33995
|
-
if (!isPlainObject(parsed.config)) return null;
|
|
33996
|
-
return {
|
|
33997
|
-
extends: typeof parsed.config.extends === "string" ? parsed.config.extends : void 0,
|
|
33998
|
-
compilerOptions: normalizeCompilerOptions(parsed.config.compilerOptions)
|
|
33999
|
-
};
|
|
34000
|
-
};
|
|
34001
|
-
const mergeCompilerOptions = (inherited, current) => {
|
|
34002
|
-
const target = current.target ?? inherited?.target;
|
|
34003
|
-
const hasExplicitLib = current.hasExplicitLib || Boolean(inherited?.hasExplicitLib);
|
|
34004
|
-
return {
|
|
34005
|
-
target,
|
|
34006
|
-
lib: current.hasExplicitLib ? current.lib : inherited?.lib,
|
|
34007
|
-
hasExplicitLib
|
|
34008
|
-
};
|
|
34009
|
-
};
|
|
34010
|
-
const readResolvedCompilerOptions = (tsConfigPath, extendsDepth, visitedPaths) => {
|
|
34011
|
-
const realPath = NFS.realpathSync.native(tsConfigPath);
|
|
34012
|
-
if (visitedPaths.has(realPath)) return null;
|
|
34013
|
-
const tsConfig = readTsConfig(realPath);
|
|
34014
|
-
if (!tsConfig) return null;
|
|
34015
|
-
const nextVisitedPaths = new Set(visitedPaths);
|
|
34016
|
-
nextVisitedPaths.add(realPath);
|
|
34017
|
-
if (tsConfig.extends && extendsDepth < 8) {
|
|
34018
|
-
const parentPath = resolveExtendsPath(tsConfig.extends, Path.dirname(realPath));
|
|
34019
|
-
if (parentPath && isFile(parentPath)) return mergeCompilerOptions(readResolvedCompilerOptions(parentPath, extendsDepth + 1, nextVisitedPaths), tsConfig.compilerOptions);
|
|
34020
|
-
}
|
|
34021
|
-
return tsConfig.compilerOptions;
|
|
34022
|
-
};
|
|
34023
|
-
const targetYearIsPreES2023 = (target) => {
|
|
34024
|
-
const year = ES_TARGET_YEAR_BY_NAME[target.toLowerCase()];
|
|
34025
|
-
return year !== void 0 && year < 2023;
|
|
34026
|
-
};
|
|
34027
|
-
const libEntryIncludesES2023Array = (entry) => {
|
|
34028
|
-
const normalizedEntry = entry.toLowerCase();
|
|
34029
|
-
if (normalizedEntry === "esnext" || normalizedEntry === "esnext.array") return true;
|
|
34030
|
-
const esYearMatch = /^es(\d{4})(?:\.(.+))?$/.exec(normalizedEntry);
|
|
34031
|
-
if (!esYearMatch) return false;
|
|
34032
|
-
if (Number(esYearMatch[1]) < 2023) return false;
|
|
34033
|
-
const component = esYearMatch[2];
|
|
34034
|
-
return component === void 0 || component === "array";
|
|
34035
|
-
};
|
|
34036
|
-
const libIncludesES2023 = (lib) => lib.some(libEntryIncludesES2023Array);
|
|
34037
|
-
const compilerOptionsArePreES2023 = (compilerOptions) => {
|
|
34038
|
-
if (compilerOptions.hasExplicitLib) return !libIncludesES2023(compilerOptions.lib ?? []);
|
|
34039
|
-
if (compilerOptions.target) return targetYearIsPreES2023(compilerOptions.target);
|
|
34040
|
-
return false;
|
|
34041
|
-
};
|
|
34042
|
-
const compilerOptionsDeclareTargetOrLib = (compilerOptions) => compilerOptions.hasExplicitLib || compilerOptions.target !== void 0;
|
|
34043
|
-
const detectPreES2023FromConfig = (tsConfigPath) => {
|
|
34044
|
-
const compilerOptions = readResolvedCompilerOptions(tsConfigPath, 0, /* @__PURE__ */ new Set());
|
|
34045
|
-
if (!compilerOptions) return false;
|
|
34046
|
-
if (!compilerOptionsDeclareTargetOrLib(compilerOptions)) return false;
|
|
34047
|
-
return compilerOptionsArePreES2023(compilerOptions);
|
|
34048
|
-
};
|
|
34049
|
-
const detectPreES2023Target = (directory) => {
|
|
34050
|
-
const tsConfigPath = Path.join(directory, TSCONFIG_FILENAME);
|
|
34051
|
-
if (isFile(tsConfigPath)) return detectPreES2023FromConfig(tsConfigPath);
|
|
34052
|
-
return false;
|
|
34053
|
-
};
|
|
34054
|
-
const REACT_DEPENDENCY_NAMES = new Set([
|
|
34055
|
-
"react",
|
|
34056
|
-
"react-native",
|
|
34057
|
-
"next",
|
|
34058
|
-
"preact"
|
|
34059
|
-
]);
|
|
34060
|
-
const hasReactDependency = (packageJson) => {
|
|
34061
|
-
const allDependencies = {
|
|
34062
|
-
...packageJson.peerDependencies,
|
|
34063
|
-
...packageJson.dependencies,
|
|
34064
|
-
...packageJson.devDependencies
|
|
34065
|
-
};
|
|
34066
|
-
return Object.keys(allDependencies).some((packageName) => REACT_DEPENDENCY_NAMES.has(packageName));
|
|
34067
|
-
};
|
|
34068
|
-
const listWorkspacePackages = (rootDirectory) => {
|
|
34069
|
-
const packageJsonPath = Path.join(rootDirectory, "package.json");
|
|
34070
|
-
if (!isFile(packageJsonPath)) return [];
|
|
34071
|
-
const packageJson = readPackageJson(packageJsonPath);
|
|
34072
|
-
const patterns = getWorkspacePatterns(rootDirectory, packageJson);
|
|
34073
|
-
if (patterns.length === 0) return [];
|
|
34074
|
-
const packages = [];
|
|
34075
|
-
const seenDirectories = /* @__PURE__ */ new Set();
|
|
34076
|
-
const pushIfNew = (workspacePackage) => {
|
|
34077
|
-
if (seenDirectories.has(workspacePackage.directory)) return;
|
|
34078
|
-
seenDirectories.add(workspacePackage.directory);
|
|
34079
|
-
packages.push(workspacePackage);
|
|
34080
|
-
};
|
|
34081
|
-
if (hasReactDependency(packageJson)) pushIfNew({
|
|
34082
|
-
name: packageJson.name ?? Path.basename(rootDirectory),
|
|
34083
|
-
directory: rootDirectory
|
|
34084
|
-
});
|
|
34085
|
-
for (const pattern of patterns) {
|
|
34086
|
-
const directories = resolveWorkspaceDirectories(rootDirectory, pattern);
|
|
34087
|
-
for (const workspaceDirectory of directories) {
|
|
34088
|
-
const workspacePackageJson = readPackageJson(Path.join(workspaceDirectory, "package.json"));
|
|
34089
|
-
if (!hasReactDependency(workspacePackageJson)) continue;
|
|
34090
|
-
pushIfNew({
|
|
34091
|
-
name: workspacePackageJson.name ?? Path.basename(workspaceDirectory),
|
|
34092
|
-
directory: workspaceDirectory
|
|
34093
|
-
});
|
|
34094
|
-
}
|
|
34095
|
-
}
|
|
34096
|
-
return packages;
|
|
34097
|
-
};
|
|
34098
34188
|
const toReactWorkspacePackages = (directories) => {
|
|
34099
34189
|
const packages = [];
|
|
34100
34190
|
for (const directory of directories) {
|
|
@@ -34167,14 +34257,13 @@ const clearProjectCache = () => {
|
|
|
34167
34257
|
};
|
|
34168
34258
|
/**
|
|
34169
34259
|
* Build a `ProjectInfo` for a directory that has no `package.json` of
|
|
34170
|
-
* its own — a
|
|
34171
|
-
*
|
|
34172
|
-
*
|
|
34173
|
-
*
|
|
34174
|
-
*
|
|
34175
|
-
*
|
|
34176
|
-
*
|
|
34177
|
-
* nothing to scan (no enclosing project and no source files of its own).
|
|
34260
|
+
* its own — a monorepo subfolder like `repo/packages`, or any loose tree
|
|
34261
|
+
* of TypeScript/JavaScript files. Dependency + framework detection is
|
|
34262
|
+
* inherited from the enclosing workspace root when there is one, so
|
|
34263
|
+
* scanning a subdirectory of a React monorepo still gets the React
|
|
34264
|
+
* capabilities; a standalone non-React directory simply scans with the
|
|
34265
|
+
* framework-agnostic rules. Throws only when the directory has nothing
|
|
34266
|
+
* to scan (no enclosing project and no source files of its own).
|
|
34178
34267
|
*/
|
|
34179
34268
|
const discoverProjectWithoutPackageJson = (directory) => {
|
|
34180
34269
|
const sourceFileCount = countSourceFiles(directory);
|
|
@@ -34210,7 +34299,9 @@ const discoverProjectWithoutPackageJson = (directory) => {
|
|
|
34210
34299
|
shopifyFlashListVersion: null,
|
|
34211
34300
|
shopifyFlashListMajorVersion: null,
|
|
34212
34301
|
hasReanimated: false,
|
|
34302
|
+
reanimatedVersion: null,
|
|
34213
34303
|
isPreES2023Target: hasOwnTsConfig && detectPreES2023Target(directory),
|
|
34304
|
+
isStaticExport: false,
|
|
34214
34305
|
sourceFileCount
|
|
34215
34306
|
};
|
|
34216
34307
|
};
|
|
@@ -34224,89 +34315,85 @@ const discoverProject = (directory) => {
|
|
|
34224
34315
|
return synthesized;
|
|
34225
34316
|
}
|
|
34226
34317
|
const packageJson = readPackageJson(packageJsonPath);
|
|
34227
|
-
|
|
34228
|
-
|
|
34229
|
-
|
|
34230
|
-
|
|
34231
|
-
|
|
34232
|
-
|
|
34233
|
-
|
|
34234
|
-
|
|
34235
|
-
|
|
34236
|
-
|
|
34237
|
-
|
|
34238
|
-
|
|
34239
|
-
|
|
34240
|
-
|
|
34241
|
-
|
|
34242
|
-
|
|
34243
|
-
|
|
34244
|
-
]
|
|
34245
|
-
});
|
|
34246
|
-
const zodDeclaration = getDependencyDeclaration({
|
|
34318
|
+
const rootInfo = extractDependencyInfo(packageJson);
|
|
34319
|
+
let framework = rootInfo.framework;
|
|
34320
|
+
const tracked = {
|
|
34321
|
+
react: {
|
|
34322
|
+
version: rootInfo.reactVersion,
|
|
34323
|
+
sections: REACT_SECTIONS
|
|
34324
|
+
},
|
|
34325
|
+
tailwindcss: {
|
|
34326
|
+
version: rootInfo.tailwindVersion,
|
|
34327
|
+
sections: TAILWIND_ZOD_SECTIONS
|
|
34328
|
+
},
|
|
34329
|
+
zod: {
|
|
34330
|
+
version: rootInfo.zodVersion,
|
|
34331
|
+
sections: TAILWIND_ZOD_SECTIONS
|
|
34332
|
+
}
|
|
34333
|
+
};
|
|
34334
|
+
const declarations = Object.fromEntries(Object.entries(tracked).map(([packageName, entry]) => [packageName, getDependencyDeclaration({
|
|
34247
34335
|
packageJson,
|
|
34248
|
-
packageName
|
|
34249
|
-
sections:
|
|
34250
|
-
|
|
34251
|
-
|
|
34252
|
-
|
|
34253
|
-
|
|
34254
|
-
|
|
34255
|
-
if (!
|
|
34256
|
-
if (!tailwindVersion && tailwindDeclaration.hasDeclaration) tailwindVersion = resolveCatalogVersion(packageJson, "tailwindcss", directory, tailwindDeclaration.catalogReference);
|
|
34257
|
-
if (!zodVersion && zodDeclaration.hasDeclaration) zodVersion = resolveCatalogVersion(packageJson, "zod", directory, zodDeclaration.catalogReference);
|
|
34258
|
-
if (!reactVersion || !tailwindVersion || !zodVersion) {
|
|
34336
|
+
packageName,
|
|
34337
|
+
sections: entry.sections
|
|
34338
|
+
})]));
|
|
34339
|
+
const fillFromCatalogs = (source, sourceDirectory) => {
|
|
34340
|
+
for (const [packageName, entry] of Object.entries(tracked)) if (!entry.version && declarations[packageName].hasDeclaration) entry.version = resolveCatalogVersion(source, packageName, sourceDirectory, declarations[packageName].catalogReference);
|
|
34341
|
+
};
|
|
34342
|
+
fillFromCatalogs(packageJson, directory);
|
|
34343
|
+
if (!tracked.react.version || !tracked.tailwindcss.version || !tracked.zod.version) {
|
|
34259
34344
|
const monorepoRoot = findMonorepoRoot(directory);
|
|
34260
34345
|
if (monorepoRoot) {
|
|
34261
34346
|
const monorepoPackageJsonPath = Path.join(monorepoRoot, "package.json");
|
|
34262
|
-
if (isFile(monorepoPackageJsonPath))
|
|
34263
|
-
const rootPackageJson = readPackageJson(monorepoPackageJsonPath);
|
|
34264
|
-
if (!reactVersion && reactDeclaration.hasDeclaration) reactVersion = resolveCatalogVersion(rootPackageJson, "react", monorepoRoot, reactDeclaration.catalogReference);
|
|
34265
|
-
if (!tailwindVersion && tailwindDeclaration.hasDeclaration) tailwindVersion = resolveCatalogVersion(rootPackageJson, "tailwindcss", monorepoRoot, tailwindDeclaration.catalogReference);
|
|
34266
|
-
if (!zodVersion && zodDeclaration.hasDeclaration) zodVersion = resolveCatalogVersion(rootPackageJson, "zod", monorepoRoot, zodDeclaration.catalogReference);
|
|
34267
|
-
}
|
|
34347
|
+
if (isFile(monorepoPackageJsonPath)) fillFromCatalogs(readPackageJson(monorepoPackageJsonPath), monorepoRoot);
|
|
34268
34348
|
}
|
|
34269
34349
|
}
|
|
34270
|
-
|
|
34271
|
-
|
|
34272
|
-
|
|
34273
|
-
|
|
34274
|
-
|
|
34275
|
-
|
|
34350
|
+
const shouldCollectReactGroup = !tracked.react.version || framework === "unknown";
|
|
34351
|
+
const workspaceFacts = collectWorkspaceFacts(directory, packageJson, { collectReactGroup: shouldCollectReactGroup });
|
|
34352
|
+
if (shouldCollectReactGroup) {
|
|
34353
|
+
tracked.react.version ||= workspaceFacts.reactVersion;
|
|
34354
|
+
tracked.tailwindcss.version ||= workspaceFacts.tailwindVersion;
|
|
34355
|
+
tracked.zod.version ||= workspaceFacts.zodVersion;
|
|
34356
|
+
if (framework === "unknown" && workspaceFacts.framework !== "unknown") framework = workspaceFacts.framework;
|
|
34276
34357
|
}
|
|
34277
|
-
if ((!
|
|
34358
|
+
if ((!tracked.react.version || framework === "unknown") && !isMonorepoRoot(directory)) {
|
|
34278
34359
|
const monorepoInfo = findDependencyInfoFromMonorepoRoot(directory);
|
|
34279
|
-
|
|
34280
|
-
|
|
34281
|
-
|
|
34360
|
+
tracked.react.version ||= monorepoInfo.reactVersion;
|
|
34361
|
+
tracked.tailwindcss.version ||= monorepoInfo.tailwindVersion;
|
|
34362
|
+
tracked.zod.version ||= monorepoInfo.zodVersion;
|
|
34282
34363
|
if (framework === "unknown") framework = monorepoInfo.framework;
|
|
34283
34364
|
}
|
|
34284
|
-
|
|
34285
|
-
|
|
34286
|
-
|
|
34365
|
+
for (const [packageName, entry] of Object.entries(tracked)) {
|
|
34366
|
+
const declaredVersion = declarations[packageName].version;
|
|
34367
|
+
if (!entry.version && declaredVersion && !isCatalogReference(declaredVersion)) entry.version = declaredVersion;
|
|
34368
|
+
}
|
|
34369
|
+
const { react, tailwindcss, zod } = tracked;
|
|
34370
|
+
let reactVersion = react.version;
|
|
34287
34371
|
if (!reactVersion || parseReactMajor(reactVersion) === null) reactVersion = resolveInstalledReactVersion(directory) ?? reactVersion;
|
|
34372
|
+
const tailwindVersion = tailwindcss.version;
|
|
34373
|
+
const zodVersion = zod.version;
|
|
34288
34374
|
const projectName = packageJson.name ?? Path.basename(directory);
|
|
34289
34375
|
const hasTypeScript = NFS.existsSync(Path.join(directory, "tsconfig.json"));
|
|
34290
34376
|
const sourceFileCount = countSourceFiles(directory);
|
|
34291
|
-
const hasReactNativeWorkspace = framework === "expo" || framework === "react-native" ||
|
|
34377
|
+
const hasReactNativeWorkspace = framework === "expo" || framework === "react-native" || workspaceFacts.hasReactNativeAwarePackage;
|
|
34292
34378
|
const expoVersion = hasReactNativeWorkspace ? resolveCatalogBackedDependencyVersion({
|
|
34293
34379
|
rootDirectory: directory,
|
|
34294
34380
|
rootPackageJson: packageJson,
|
|
34295
34381
|
packageName: "expo",
|
|
34296
|
-
version:
|
|
34382
|
+
version: workspaceFacts.expo.version
|
|
34297
34383
|
}) : null;
|
|
34298
34384
|
const shopifyFlashListVersion = hasReactNativeWorkspace ? resolveCatalogBackedDependencyVersion({
|
|
34299
34385
|
rootDirectory: directory,
|
|
34300
34386
|
rootPackageJson: packageJson,
|
|
34301
34387
|
packageName: SHOPIFY_FLASH_LIST_PACKAGE_NAME,
|
|
34302
|
-
version:
|
|
34388
|
+
version: workspaceFacts.shopifyFlashList.version
|
|
34303
34389
|
}) : null;
|
|
34304
|
-
const hasReanimated = hasReactNativeWorkspace &&
|
|
34390
|
+
const hasReanimated = hasReactNativeWorkspace && workspaceFacts.hasReanimatedAwarePackage;
|
|
34391
|
+
const reanimatedVersion = hasReanimated ? workspaceFacts.reanimatedVersion : null;
|
|
34305
34392
|
const nextjsVersion = framework === "nextjs" ? resolveCatalogBackedDependencyVersion({
|
|
34306
34393
|
rootDirectory: directory,
|
|
34307
34394
|
rootPackageJson: packageJson,
|
|
34308
34395
|
packageName: "next",
|
|
34309
|
-
version:
|
|
34396
|
+
version: workspaceFacts.next.version
|
|
34310
34397
|
}) : null;
|
|
34311
34398
|
const preactVersion = getPreactVersion(packageJson);
|
|
34312
34399
|
const isPreES2023Target = hasTypeScript && detectPreES2023Target(directory);
|
|
@@ -34317,7 +34404,7 @@ const discoverProject = (directory) => {
|
|
|
34317
34404
|
reactMajorVersion: resolveEffectiveReactMajor(reactVersion, packageJson),
|
|
34318
34405
|
tailwindVersion,
|
|
34319
34406
|
zodVersion,
|
|
34320
|
-
zodMajorVersion:
|
|
34407
|
+
zodMajorVersion: zodVersion === null ? null : getLowestDependencyMajor(zodVersion),
|
|
34321
34408
|
framework,
|
|
34322
34409
|
hasTypeScript,
|
|
34323
34410
|
hasReactCompiler: detectReactCompiler(directory, packageJson),
|
|
@@ -34331,7 +34418,9 @@ const discoverProject = (directory) => {
|
|
|
34331
34418
|
shopifyFlashListVersion,
|
|
34332
34419
|
shopifyFlashListMajorVersion: shopifyFlashListVersion === null ? null : getLowestDependencyMajor(shopifyFlashListVersion),
|
|
34333
34420
|
hasReanimated,
|
|
34421
|
+
reanimatedVersion,
|
|
34334
34422
|
isPreES2023Target,
|
|
34423
|
+
isStaticExport: framework === "nextjs" && detectNextjsStaticExport(workspaceFacts.next.sourceDirectory ?? directory),
|
|
34335
34424
|
sourceFileCount
|
|
34336
34425
|
};
|
|
34337
34426
|
cachedProjectInfos.set(directory, projectInfo);
|
|
@@ -34347,57 +34436,6 @@ const discoverProject = (directory) => {
|
|
|
34347
34436
|
* single source file has nothing to analyze.
|
|
34348
34437
|
*/
|
|
34349
34438
|
const isAnalyzableProject = (project) => project.reactVersion !== null || project.preactVersion !== null || project.sourceFileCount > 0;
|
|
34350
|
-
const MAJOR_MINOR_PATTERN = /(\d{1,4})\.(\d{1,4})/;
|
|
34351
|
-
const MAJOR_ONLY_PATTERN = /(\d{1,4})/;
|
|
34352
|
-
const UPPER_BOUND_COMPARATOR_PATTERN = /<=?\s{0,8}\d{1,4}(?:\.\d{1,4}){0,2}(?:-[^\s,|]+)?/g;
|
|
34353
|
-
const parseReactMajorMinor = (reactVersion) => {
|
|
34354
|
-
if (typeof reactVersion !== "string") return null;
|
|
34355
|
-
const trimmed = reactVersion.trim();
|
|
34356
|
-
if (trimmed.length === 0) return null;
|
|
34357
|
-
const lowerBoundsOnly = trimmed.replace(UPPER_BOUND_COMPARATOR_PATTERN, " ").trim();
|
|
34358
|
-
if (lowerBoundsOnly.length === 0) return null;
|
|
34359
|
-
const majorMinorMatch = lowerBoundsOnly.match(MAJOR_MINOR_PATTERN);
|
|
34360
|
-
if (majorMinorMatch) {
|
|
34361
|
-
const major = Number.parseInt(majorMinorMatch[1], 10);
|
|
34362
|
-
const minor = Number.parseInt(majorMinorMatch[2], 10);
|
|
34363
|
-
if (!Number.isFinite(major) || major <= 0) return null;
|
|
34364
|
-
if (!Number.isFinite(minor) || minor < 0) return null;
|
|
34365
|
-
return {
|
|
34366
|
-
major,
|
|
34367
|
-
minor
|
|
34368
|
-
};
|
|
34369
|
-
}
|
|
34370
|
-
const majorOnlyMatch = lowerBoundsOnly.match(MAJOR_ONLY_PATTERN);
|
|
34371
|
-
if (!majorOnlyMatch) return null;
|
|
34372
|
-
const major = Number.parseInt(majorOnlyMatch[1], 10);
|
|
34373
|
-
if (!Number.isFinite(major) || major <= 0) return null;
|
|
34374
|
-
return {
|
|
34375
|
-
major,
|
|
34376
|
-
minor: 0
|
|
34377
|
-
};
|
|
34378
|
-
};
|
|
34379
|
-
const isReactAtLeast = (detected, required) => {
|
|
34380
|
-
if (detected === null) return true;
|
|
34381
|
-
if (detected.major !== required.major) return detected.major > required.major;
|
|
34382
|
-
return detected.minor >= required.minor;
|
|
34383
|
-
};
|
|
34384
|
-
const parseLowerBoundVersion = (versionSpec) => import_semver.validRange(versionSpec) !== null ? import_semver.minVersion(versionSpec) : import_semver.coerce(versionSpec);
|
|
34385
|
-
const parseTailwindMajorMinor = (tailwindVersion) => {
|
|
34386
|
-
if (typeof tailwindVersion !== "string") return null;
|
|
34387
|
-
const trimmed = tailwindVersion.trim();
|
|
34388
|
-
if (trimmed.length === 0) return null;
|
|
34389
|
-
const lowerBound = parseLowerBoundVersion(trimmed);
|
|
34390
|
-
if (lowerBound === null || lowerBound.major <= 0) return null;
|
|
34391
|
-
return {
|
|
34392
|
-
major: lowerBound.major,
|
|
34393
|
-
minor: lowerBound.minor
|
|
34394
|
-
};
|
|
34395
|
-
};
|
|
34396
|
-
const isTailwindAtLeast = (detected, required) => {
|
|
34397
|
-
if (detected === null) return true;
|
|
34398
|
-
if (detected.major !== required.major) return detected.major > required.major;
|
|
34399
|
-
return detected.minor >= required.minor;
|
|
34400
|
-
};
|
|
34401
34439
|
const messageFromUnknown = (error) => error instanceof Error ? error.message : String(error);
|
|
34402
34440
|
var InvalidGlobPatternError = class extends Error {
|
|
34403
34441
|
pattern;
|
|
@@ -34523,6 +34561,9 @@ const buildRuleSeverityControls = (config) => {
|
|
|
34523
34561
|
...config.buckets !== void 0 ? { buckets: config.buckets } : {}
|
|
34524
34562
|
};
|
|
34525
34563
|
};
|
|
34564
|
+
const FOREIGN_INLINE_DISABLE_PATTERN = /(?:\/\/|\/\*)[ \t]*(eslint|oxlint)-disable-(next-line|line)(?![\w-])([^\r\n]*)/;
|
|
34565
|
+
const FOREIGN_BLOCK_DISABLE_PATTERN = /\/\*[ \t]*(eslint|oxlint)-disable(?![\w-])([^*\r\n]*)/;
|
|
34566
|
+
const FOREIGN_BLOCK_ENABLE_PATTERN = /\/\*[ \t]*(?:eslint|oxlint)-enable(?![\w-])([^*\r\n]*)/;
|
|
34526
34567
|
const LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY = {
|
|
34527
34568
|
"effect/no-adjust-state-on-prop-change": "react-doctor/no-adjust-state-on-prop-change",
|
|
34528
34569
|
"effect/no-chain-state-updates": "react-doctor/no-chain-state-updates",
|
|
@@ -34600,6 +34641,7 @@ const LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY = {
|
|
|
34600
34641
|
"react/jsx-no-new-function-as-prop": "react-doctor/jsx-no-new-function-as-prop",
|
|
34601
34642
|
"react/jsx-no-new-object-as-prop": "react-doctor/jsx-no-new-object-as-prop",
|
|
34602
34643
|
"react/jsx-no-script-url": "react-doctor/jsx-no-script-url",
|
|
34644
|
+
"react/jsx-no-target-blank": "react-doctor/jsx-no-target-blank",
|
|
34603
34645
|
"react/jsx-no-undef": "react-doctor/jsx-no-undef",
|
|
34604
34646
|
"react/jsx-no-useless-fragment": "react-doctor/jsx-no-useless-fragment",
|
|
34605
34647
|
"react/jsx-pascal-case": "react-doctor/jsx-pascal-case",
|
|
@@ -34673,9 +34715,6 @@ const tokenizeRuleList = (ruleList) => {
|
|
|
34673
34715
|
if (!ruleSection) return [];
|
|
34674
34716
|
return ruleSection.split(/[,\s]+/).map((token) => token.trim()).filter(Boolean);
|
|
34675
34717
|
};
|
|
34676
|
-
const FOREIGN_INLINE_DISABLE_PATTERN = /(?:\/\/|\/\*)[ \t]*(eslint|oxlint)-disable-(next-line|line)(?![\w-])([^\r\n]*)/;
|
|
34677
|
-
const FOREIGN_BLOCK_DISABLE_PATTERN = /\/\*[ \t]*(eslint|oxlint)-disable(?![\w-])([^*\r\n]*)/;
|
|
34678
|
-
const FOREIGN_BLOCK_ENABLE_PATTERN = /\/\*[ \t]*(?:eslint|oxlint)-enable(?![\w-])([^*\r\n]*)/;
|
|
34679
34718
|
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}\`).`;
|
|
34680
34719
|
const tokenMisnamesRule = (token, ruleId) => token !== ruleId && isSameRuleKey(token, ruleId);
|
|
34681
34720
|
const detectInlineNearMiss = (lines, diagnosticLineIndex, ruleId) => {
|
|
@@ -34729,6 +34768,53 @@ const detectForeignDisableNearMiss = (lines, diagnosticLineIndex, ruleId) => {
|
|
|
34729
34768
|
if (!ruleId.startsWith("react-doctor/")) return null;
|
|
34730
34769
|
return detectInlineNearMiss(lines, diagnosticLineIndex, ruleId) ?? detectBlockNearMiss(lines, diagnosticLineIndex, ruleId);
|
|
34731
34770
|
};
|
|
34771
|
+
const ESLINT_REACT_HOOKS_PLUGIN_PREFIX = "react-hooks/";
|
|
34772
|
+
const buildAcceptedTokens = (ruleId) => {
|
|
34773
|
+
const shortRuleId = ruleId.slice(15);
|
|
34774
|
+
return new Set([ruleId, `${ESLINT_REACT_HOOKS_PLUGIN_PREFIX}${shortRuleId}`]);
|
|
34775
|
+
};
|
|
34776
|
+
const listNamesRule = (ruleList, acceptedTokens) => tokenizeRuleList(ruleList).some((token) => acceptedTokens.has(token));
|
|
34777
|
+
const isInlineDisabled = (lines, diagnosticLineIndex, acceptedTokens) => {
|
|
34778
|
+
const candidates = [{
|
|
34779
|
+
line: lines[diagnosticLineIndex],
|
|
34780
|
+
requiredScope: "line"
|
|
34781
|
+
}, {
|
|
34782
|
+
line: lines[diagnosticLineIndex - 1],
|
|
34783
|
+
requiredScope: "next-line"
|
|
34784
|
+
}];
|
|
34785
|
+
for (const { line, requiredScope } of candidates) {
|
|
34786
|
+
const match = line?.match(FOREIGN_INLINE_DISABLE_PATTERN);
|
|
34787
|
+
if (!match) continue;
|
|
34788
|
+
const [, , scope, ruleList] = match;
|
|
34789
|
+
if (scope !== requiredScope) continue;
|
|
34790
|
+
if (listNamesRule(ruleList, acceptedTokens)) return true;
|
|
34791
|
+
}
|
|
34792
|
+
return false;
|
|
34793
|
+
};
|
|
34794
|
+
const isBlockDisabled = (lines, diagnosticLineIndex, acceptedTokens) => {
|
|
34795
|
+
let isRangeOpen = false;
|
|
34796
|
+
const lastLineIndex = Math.min(diagnosticLineIndex, lines.length - 1);
|
|
34797
|
+
for (let lineIndex = 0; lineIndex <= lastLineIndex; lineIndex++) {
|
|
34798
|
+
const line = lines[lineIndex];
|
|
34799
|
+
if (line === void 0 || !line.includes("-disable") && !line.includes("-enable")) continue;
|
|
34800
|
+
const disableMatch = line.match(FOREIGN_BLOCK_DISABLE_PATTERN);
|
|
34801
|
+
if (disableMatch && listNamesRule(disableMatch[2], acceptedTokens)) {
|
|
34802
|
+
isRangeOpen = true;
|
|
34803
|
+
continue;
|
|
34804
|
+
}
|
|
34805
|
+
const enableMatch = line.match(FOREIGN_BLOCK_ENABLE_PATTERN);
|
|
34806
|
+
if (enableMatch) {
|
|
34807
|
+
const enabledRules = tokenizeRuleList(enableMatch[1]);
|
|
34808
|
+
if (enabledRules.length === 0 || enabledRules.some((rule) => acceptedTokens.has(rule))) isRangeOpen = false;
|
|
34809
|
+
}
|
|
34810
|
+
}
|
|
34811
|
+
return isRangeOpen;
|
|
34812
|
+
};
|
|
34813
|
+
const isCompilerRuleForeignDisabled = (lines, diagnosticLineIndex, ruleId) => {
|
|
34814
|
+
if (!ruleId.startsWith("react-hooks-js/")) return false;
|
|
34815
|
+
const acceptedTokens = buildAcceptedTokens(ruleId);
|
|
34816
|
+
return isInlineDisabled(lines, diagnosticLineIndex, acceptedTokens) || isBlockDisabled(lines, diagnosticLineIndex, acceptedTokens);
|
|
34817
|
+
};
|
|
34732
34818
|
const JSX_OPENER_TAG_PATTERN = /<[A-Za-z][\w.]*/g;
|
|
34733
34819
|
const JSX_TAG_NAME_FOLLOW = /[A-Za-z]/;
|
|
34734
34820
|
const isOpenerMatchInsideLineComment = (line, openerCharIndex) => {
|
|
@@ -34872,6 +34958,11 @@ const evaluateSuppression = (lines, diagnosticLineIndex, ruleId) => {
|
|
|
34872
34958
|
isSuppressed: true,
|
|
34873
34959
|
nearMissHint: null
|
|
34874
34960
|
};
|
|
34961
|
+
if (isCompilerRuleForeignDisabled(lines, diagnosticLineIndex, ruleId)) return {
|
|
34962
|
+
isSuppressed: true,
|
|
34963
|
+
nearMissHint: null,
|
|
34964
|
+
isForeignDirective: true
|
|
34965
|
+
};
|
|
34875
34966
|
const directComments = findStackedDisableCommentsAbove(lines, diagnosticLineIndex);
|
|
34876
34967
|
if (hasChainSuppressor(directComments, ruleId)) return {
|
|
34877
34968
|
isSuppressed: true,
|
|
@@ -35157,12 +35248,18 @@ const collectStringSet = (values) => {
|
|
|
35157
35248
|
return new Set(values.filter((value) => typeof value === "string"));
|
|
35158
35249
|
};
|
|
35159
35250
|
const JSX_NO_UNDEF_IDENTIFIER_PATTERN = /^`([^`]+)`/;
|
|
35251
|
+
const NODE_CLI_PATH_SEGMENTS = new Set([
|
|
35252
|
+
"scripts",
|
|
35253
|
+
"bin",
|
|
35254
|
+
"tools"
|
|
35255
|
+
]);
|
|
35256
|
+
const hasNodeCliPathSegment = (filePath) => filePath.split(/[\\/]/).slice(0, -1).some((segment) => NODE_CLI_PATH_SEGMENTS.has(segment));
|
|
35160
35257
|
/**
|
|
35161
35258
|
* Pre-compiles every stateful filter and returns a single
|
|
35162
35259
|
* `apply(diagnostic)` closure that runs (in order):
|
|
35163
35260
|
*
|
|
35164
35261
|
* 1. auto-suppress (test-noise rules in test files; `migration-hint`
|
|
35165
|
-
* wins over `test-noise`)
|
|
35262
|
+
* wins over `test-noise`; `no-console` on Node CLI scripts)
|
|
35166
35263
|
* 2. severity overrides (top-level `rules` / `categories`, with
|
|
35167
35264
|
* `"off"` dropping)
|
|
35168
35265
|
* 3. warning suppression (only when `showWarnings` is false: drops every
|
|
@@ -35260,6 +35357,12 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
35260
35357
|
if (hasRawTextWrappers && isInsideStringOnlyWrapper(lines, diagnostic.line, diagnostic.column, rawTextWrapperComponentNames)) return true;
|
|
35261
35358
|
return false;
|
|
35262
35359
|
};
|
|
35360
|
+
const isNoConsoleOnNodeCliScript = (diagnostic) => {
|
|
35361
|
+
if (diagnostic.rule !== "no-console") return false;
|
|
35362
|
+
if (hasNodeCliPathSegment(diagnostic.filePath)) return true;
|
|
35363
|
+
const lines = getFileLines(diagnostic.filePath);
|
|
35364
|
+
return lines !== null && Boolean(lines[0]?.startsWith("#!"));
|
|
35365
|
+
};
|
|
35263
35366
|
const isJsxNoUndefSuppressedByConfig = (diagnostic) => {
|
|
35264
35367
|
if (!hasRuntimeGlobals) return false;
|
|
35265
35368
|
if (diagnostic.rule !== "jsx-no-undef") return false;
|
|
@@ -35269,6 +35372,7 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
35269
35372
|
return {
|
|
35270
35373
|
apply: (diagnostic) => {
|
|
35271
35374
|
if (shouldAutoSuppress(diagnostic)) return null;
|
|
35375
|
+
if (isNoConsoleOnNodeCliScript(diagnostic)) return null;
|
|
35272
35376
|
let current = diagnostic;
|
|
35273
35377
|
let explicitSeverityOverride;
|
|
35274
35378
|
let explicitRuleOverride;
|
|
@@ -35298,7 +35402,7 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
35298
35402
|
if (lines) {
|
|
35299
35403
|
const ruleIdentifier = `${current.plugin}/${current.rule}`;
|
|
35300
35404
|
const evaluation = evaluateSuppression(lines, current.line - 1, ruleIdentifier);
|
|
35301
|
-
if (evaluation.isSuppressed) return suppress(current, "inline");
|
|
35405
|
+
if (evaluation.isSuppressed) return suppress(current, evaluation.isForeignDirective ? "foreign-inline" : "inline");
|
|
35302
35406
|
if (evaluation.nearMissHint) current = {
|
|
35303
35407
|
...current,
|
|
35304
35408
|
suppressionHint: evaluation.nearMissHint
|
|
@@ -37107,64 +37211,64 @@ function* collectSecurityScanFiles(rootDirectory) {
|
|
|
37107
37211
|
}
|
|
37108
37212
|
}
|
|
37109
37213
|
}
|
|
37110
|
-
|
|
37111
|
-
|
|
37112
|
-
|
|
37113
|
-
|
|
37114
|
-
|
|
37115
|
-
|
|
37116
|
-
|
|
37214
|
+
const CLIENT_ONLY_FRAMEWORKS = new Set([
|
|
37215
|
+
"vite",
|
|
37216
|
+
"cra",
|
|
37217
|
+
"gatsby",
|
|
37218
|
+
"react-native",
|
|
37219
|
+
"expo"
|
|
37220
|
+
]);
|
|
37221
|
+
const addMajorLadder = (capabilities, name, major, earliest, latest) => {
|
|
37222
|
+
if (major === null) return;
|
|
37223
|
+
const cappedMajor = Math.min(major, latest);
|
|
37224
|
+
for (let candidate = earliest; candidate <= cappedMajor; candidate += 1) capabilities.add(`${name}:${candidate}`);
|
|
37225
|
+
};
|
|
37117
37226
|
const buildCapabilities = (project) => {
|
|
37118
37227
|
const capabilities = /* @__PURE__ */ new Set();
|
|
37119
37228
|
capabilities.add(project.framework);
|
|
37120
|
-
if (
|
|
37229
|
+
if (project.reactVersion !== null || project.preactVersion !== null) capabilities.add("react");
|
|
37121
37230
|
if (project.framework === "expo" || project.framework === "react-native" || project.hasReactNativeWorkspace) capabilities.add("react-native");
|
|
37122
37231
|
if (project.expoVersion !== null) capabilities.add("expo");
|
|
37232
|
+
if ((project.framework === "nextjs" || project.framework === "tanstack-start" || project.framework === "remix") && !project.isStaticExport) capabilities.add("server-actions");
|
|
37233
|
+
if (CLIENT_ONLY_FRAMEWORKS.has(project.framework)) capabilities.add("client-only");
|
|
37234
|
+
if (project.isStaticExport) capabilities.add("nextjs:static-export");
|
|
37123
37235
|
if (project.nextjsMajorVersion !== null && project.nextjsMajorVersion >= 15) capabilities.add("nextjs:15");
|
|
37124
|
-
|
|
37125
|
-
if (
|
|
37126
|
-
|
|
37127
|
-
|
|
37128
|
-
|
|
37129
|
-
|
|
37130
|
-
|
|
37131
|
-
|
|
37132
|
-
|
|
37133
|
-
|
|
37134
|
-
|
|
37135
|
-
if (project.
|
|
37136
|
-
capabilities.add("tailwind");
|
|
37137
|
-
if (isTailwindAtLeast(parseTailwindMajorMinor(project.tailwindVersion), {
|
|
37138
|
-
major: 3,
|
|
37139
|
-
minor: 4
|
|
37140
|
-
})) capabilities.add("tailwind:3.4");
|
|
37141
|
-
}
|
|
37142
|
-
if (project.zodVersion !== null) {
|
|
37143
|
-
capabilities.add("zod");
|
|
37144
|
-
if (project.zodMajorVersion !== null && project.zodMajorVersion >= 4) capabilities.add("zod:4");
|
|
37145
|
-
}
|
|
37236
|
+
addMajorLadder(capabilities, "react", project.reactMajorVersion, 17, 30);
|
|
37237
|
+
if (project.reactMajorVersion !== null && project.reactMajorVersion >= 19 && isMajorMinorAtLeast(parseReactMajorMinor(project.reactVersion), {
|
|
37238
|
+
major: 19,
|
|
37239
|
+
minor: 2
|
|
37240
|
+
})) capabilities.add("react:19.2");
|
|
37241
|
+
if (project.tailwindVersion !== null) capabilities.add("tailwind");
|
|
37242
|
+
if (project.tailwindVersion !== null && isMajorMinorAtLeast(parseTailwindMajorMinor(project.tailwindVersion), {
|
|
37243
|
+
major: 3,
|
|
37244
|
+
minor: 4
|
|
37245
|
+
})) capabilities.add("tailwind:3.4");
|
|
37246
|
+
if (project.zodVersion !== null) capabilities.add("zod");
|
|
37247
|
+
if (project.zodMajorVersion !== null && project.zodMajorVersion >= 4) capabilities.add("zod:4");
|
|
37146
37248
|
if (project.isPreES2023Target) capabilities.add("pre-es2023");
|
|
37147
37249
|
if (project.hasReactCompiler) capabilities.add("react-compiler");
|
|
37148
37250
|
if (project.hasTanStackQuery) capabilities.add("tanstack-query");
|
|
37149
37251
|
if (project.hasTypeScript) capabilities.add("typescript");
|
|
37150
|
-
if (project.preactVersion !== null)
|
|
37151
|
-
|
|
37152
|
-
|
|
37153
|
-
if (preactMajor !== null) {
|
|
37154
|
-
const cappedPreactMajor = Math.min(preactMajor, 20);
|
|
37155
|
-
for (let major = 10; major <= cappedPreactMajor; major++) capabilities.add(`preact:${major}`);
|
|
37156
|
-
}
|
|
37157
|
-
if (project.reactVersion === null) capabilities.add("pure-preact");
|
|
37158
|
-
}
|
|
37252
|
+
if (project.preactVersion !== null) capabilities.add("preact");
|
|
37253
|
+
addMajorLadder(capabilities, "preact", project.preactMajorVersion, 10, 20);
|
|
37254
|
+
if (project.preactVersion !== null && project.reactVersion === null) capabilities.add("pure-preact");
|
|
37159
37255
|
return capabilities;
|
|
37160
37256
|
};
|
|
37161
|
-
const
|
|
37257
|
+
const capabilitiesByProject = /* @__PURE__ */ new WeakMap();
|
|
37258
|
+
const getCapabilities = (project) => {
|
|
37259
|
+
const cached = capabilitiesByProject.get(project);
|
|
37260
|
+
if (cached !== void 0) return cached;
|
|
37261
|
+
const capabilities = buildCapabilities(project);
|
|
37262
|
+
capabilitiesByProject.set(project, capabilities);
|
|
37263
|
+
return capabilities;
|
|
37264
|
+
};
|
|
37265
|
+
const shouldEnableRule = (requires, tags, capabilities, ignoredTags, disabledWhen) => {
|
|
37162
37266
|
if (requires) {
|
|
37163
37267
|
for (const capability of requires) if (!capabilities.has(capability)) return false;
|
|
37164
37268
|
}
|
|
37165
37269
|
if (tags?.includes("react-jsx-only") && !capabilities.has("react")) return false;
|
|
37166
|
-
if (
|
|
37167
|
-
for (const capability of
|
|
37270
|
+
if (disabledWhen) {
|
|
37271
|
+
for (const capability of disabledWhen) if (capabilities.has(capability)) return false;
|
|
37168
37272
|
}
|
|
37169
37273
|
if (tags) {
|
|
37170
37274
|
for (const tag of tags) if (ignoredTags.has(tag)) return false;
|
|
@@ -37175,14 +37279,14 @@ const yieldToEventLoop = () => new Promise((resolve) => {
|
|
|
37175
37279
|
setImmediate(resolve);
|
|
37176
37280
|
});
|
|
37177
37281
|
const createSecurityScanSession = (rootDirectory, options) => {
|
|
37178
|
-
const capabilities = options.project ?
|
|
37282
|
+
const capabilities = options.project ? getCapabilities(options.project) : /* @__PURE__ */ new Set();
|
|
37179
37283
|
const ignoredTags = options.ignoredTags ?? /* @__PURE__ */ new Set();
|
|
37180
37284
|
const enabledScanRules = REACT_DOCTOR_RULES.flatMap((entry) => {
|
|
37181
37285
|
const rule = entry.rule;
|
|
37182
37286
|
const scan = rule.scan;
|
|
37183
37287
|
if (typeof scan !== "function") return [];
|
|
37184
37288
|
if (rule.defaultEnabled === false) return [];
|
|
37185
|
-
if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.
|
|
37289
|
+
if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.disabledWhen)) return [];
|
|
37186
37290
|
return [{
|
|
37187
37291
|
entry,
|
|
37188
37292
|
scan,
|
|
@@ -37202,6 +37306,7 @@ const createSecurityScanSession = (rootDirectory, options) => {
|
|
|
37202
37306
|
return status === true;
|
|
37203
37307
|
};
|
|
37204
37308
|
const scanFileByRule = function* (file) {
|
|
37309
|
+
if (file === null) return;
|
|
37205
37310
|
for (const { entry, scan, committedFilesOnly } of enabledScanRules) {
|
|
37206
37311
|
for (const finding of scan(file)) {
|
|
37207
37312
|
if (committedFilesOnly && isFileGitIgnored(file)) continue;
|
|
@@ -39082,7 +39187,7 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
39082
39187
|
const reactCompilerRules = reactHooksJsPlugin ? applyRuleSeverityControls(filterRulesToAvailable(REACT_COMPILER_RULES, "react-hooks-js", reactHooksJsPlugin.availableRuleNames), severityControls) : {};
|
|
39083
39188
|
const jsPlugins = [];
|
|
39084
39189
|
if (reactHooksJsPlugin) jsPlugins.push(reactHooksJsPlugin.entry);
|
|
39085
|
-
const capabilities =
|
|
39190
|
+
const capabilities = getCapabilities(project);
|
|
39086
39191
|
const enabledReactDoctorRules = {};
|
|
39087
39192
|
for (const registryEntry of REACT_DOCTOR_RULES) {
|
|
39088
39193
|
const rule = reactDoctorPlugin.rules[registryEntry.id];
|
|
@@ -39093,7 +39198,7 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
39093
39198
|
if (rule.scan !== void 0) continue;
|
|
39094
39199
|
if (customRulesOnly && registryEntry.originallyExternal) continue;
|
|
39095
39200
|
if (rule.framework !== "global" && !rule.requires) continue;
|
|
39096
|
-
if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.
|
|
39201
|
+
if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.disabledWhen)) continue;
|
|
39097
39202
|
const explicitRuleOverride = resolveRuleSeverityOverride({ ruleKey: registryEntry.key }, severityControls);
|
|
39098
39203
|
if (rule.defaultEnabled === false && explicitRuleOverride === void 0) continue;
|
|
39099
39204
|
const severity = resolveRuleSeverityOverride({
|
|
@@ -39124,6 +39229,7 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
39124
39229
|
settings: { "react-doctor": {
|
|
39125
39230
|
framework: project.framework,
|
|
39126
39231
|
rootDirectory: resolveSettingsRootDirectory(project.rootDirectory),
|
|
39232
|
+
capabilities: [...capabilities].sort(),
|
|
39127
39233
|
...project.shopifyFlashListMajorVersion !== null ? { shopifyFlashListMajorVersion: project.shopifyFlashListMajorVersion } : {},
|
|
39128
39234
|
...serverAuthFunctionNames && serverAuthFunctionNames.length > 0 ? { serverAuthFunctionNames: [...serverAuthFunctionNames] } : {}
|
|
39129
39235
|
} },
|
|
@@ -39414,25 +39520,18 @@ const mapWithConcurrency = async (items, concurrency, task) => {
|
|
|
39414
39520
|
if (errors.length > 0) throw errors[0];
|
|
39415
39521
|
return results;
|
|
39416
39522
|
};
|
|
39417
|
-
const getPublicEnvPrefix = (framework) => {
|
|
39418
|
-
switch (framework) {
|
|
39419
|
-
case "nextjs": return "NEXT_PUBLIC_*";
|
|
39420
|
-
case "vite":
|
|
39421
|
-
case "tanstack-start": return "VITE_*";
|
|
39422
|
-
case "cra": return "REACT_APP_*";
|
|
39423
|
-
case "gatsby": return "GATSBY_*";
|
|
39424
|
-
default: return null;
|
|
39425
|
-
}
|
|
39426
|
-
};
|
|
39427
|
-
const buildNoSecretsRecommendation = (project, fallbackRecommendation) => {
|
|
39428
|
-
const publicEnvPrefix = getPublicEnvPrefix(project.framework);
|
|
39429
|
-
if (!publicEnvPrefix) return fallbackRecommendation;
|
|
39430
|
-
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`;
|
|
39431
|
-
};
|
|
39432
39523
|
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";
|
|
39524
|
+
const ACCESSORS_MIN_VERSION = "3.15.0";
|
|
39525
|
+
const supportsAccessors = (reanimatedVersion) => {
|
|
39526
|
+
if (reanimatedVersion === null) return true;
|
|
39527
|
+
const lowerBound = import_semver.validRange(reanimatedVersion) !== null ? import_semver.minVersion(reanimatedVersion) : import_semver.coerce(reanimatedVersion);
|
|
39528
|
+
if (lowerBound === null) return true;
|
|
39529
|
+
return import_semver.gte(lowerBound, ACCESSORS_MIN_VERSION);
|
|
39530
|
+
};
|
|
39433
39531
|
const appendReanimatedSharedValueHint = (help, rule, project) => {
|
|
39434
39532
|
if (rule !== "immutability") return help;
|
|
39435
39533
|
if (!project.hasReanimated) return help;
|
|
39534
|
+
if (!supportsAccessors(project.reanimatedVersion)) return help;
|
|
39436
39535
|
if (!help) return REANIMATED_SHARED_VALUE_HINT;
|
|
39437
39536
|
return `${help}\n\n${REANIMATED_SHARED_VALUE_HINT}`;
|
|
39438
39537
|
};
|
|
@@ -39594,13 +39693,13 @@ const REACT_USE_BINDING_RESOLUTION = {
|
|
|
39594
39693
|
isReactUseBinding: true,
|
|
39595
39694
|
isReactNamespaceBinding: false
|
|
39596
39695
|
};
|
|
39597
|
-
const getScriptKind = (filename) => {
|
|
39696
|
+
const getScriptKind$2 = (filename) => {
|
|
39598
39697
|
if (filename.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
39599
39698
|
if (filename.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
39600
39699
|
if (filename.endsWith(".ts")) return ts.ScriptKind.TS;
|
|
39601
39700
|
return ts.ScriptKind.JS;
|
|
39602
39701
|
};
|
|
39603
|
-
const getUtf16Offset = (sourceText, utf8Offset) => Buffer.from(sourceText).subarray(0, utf8Offset).toString("utf8").length;
|
|
39702
|
+
const getUtf16Offset$1 = (sourceText, utf8Offset) => Buffer.from(sourceText).subarray(0, utf8Offset).toString("utf8").length;
|
|
39604
39703
|
const unwrapExpression = (expression) => {
|
|
39605
39704
|
let currentExpression = expression;
|
|
39606
39705
|
while (ts.isParenthesizedExpression(currentExpression) || ts.isAsExpression(currentExpression) || ts.isSatisfiesExpression(currentExpression) || ts.isNonNullExpression(currentExpression) || ts.isTypeAssertionExpression(currentExpression)) currentExpression = currentExpression.expression;
|
|
@@ -39849,8 +39948,8 @@ const findUseCallIdentifier = (sourceFile, useOffset) => {
|
|
|
39849
39948
|
return matchedIdentifier;
|
|
39850
39949
|
};
|
|
39851
39950
|
const resolveUseCallBinding = (sourceText, filename, utf8Offset) => {
|
|
39852
|
-
const sourceFile = ts.createSourceFile(filename, sourceText, ts.ScriptTarget.Latest, true, getScriptKind(filename));
|
|
39853
|
-
const useIdentifier = findUseCallIdentifier(sourceFile, getUtf16Offset(sourceText, utf8Offset));
|
|
39951
|
+
const sourceFile = ts.createSourceFile(filename, sourceText, ts.ScriptTarget.Latest, true, getScriptKind$2(filename));
|
|
39952
|
+
const useIdentifier = findUseCallIdentifier(sourceFile, getUtf16Offset$1(sourceText, utf8Offset));
|
|
39854
39953
|
if (!useIdentifier) return null;
|
|
39855
39954
|
return resolveIdentifierBinding(useIdentifier, collectReactImportBindings(sourceFile), sourceFile);
|
|
39856
39955
|
};
|
|
@@ -39871,6 +39970,201 @@ const shouldSuppressLocalUseHookDiagnostic = (diagnostic, rootDirectory) => {
|
|
|
39871
39970
|
const bindingResolution = resolveUseCallBinding(sourceText, absolutePath, primaryLabel.span.offset);
|
|
39872
39971
|
return bindingResolution !== null && !bindingResolution.isReactUseBinding;
|
|
39873
39972
|
};
|
|
39973
|
+
const SUPPRESSED_COMPILER_CODES = new Set(["react-hooks-js(immutability)", "react-hooks-js(refs)"]);
|
|
39974
|
+
const WORKLET_DIRECTIVE = "worklet";
|
|
39975
|
+
const WORKLET_ACCEPTING_CALLEES = new Set([
|
|
39976
|
+
"useAnimatedStyle",
|
|
39977
|
+
"useAnimatedProps",
|
|
39978
|
+
"useDerivedValue",
|
|
39979
|
+
"useAnimatedReaction",
|
|
39980
|
+
"useAnimatedScrollHandler",
|
|
39981
|
+
"useAnimatedGestureHandler",
|
|
39982
|
+
"useFrameCallback",
|
|
39983
|
+
"runOnUI",
|
|
39984
|
+
"executeOnUIRuntimeSync"
|
|
39985
|
+
]);
|
|
39986
|
+
const GESTURE_ROOT_IDENTIFIER = "Gesture";
|
|
39987
|
+
const getScriptKind$1 = (filename) => {
|
|
39988
|
+
if (filename.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
39989
|
+
if (filename.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
39990
|
+
if (filename.endsWith(".ts")) return ts.ScriptKind.TS;
|
|
39991
|
+
return ts.ScriptKind.JS;
|
|
39992
|
+
};
|
|
39993
|
+
const getUtf16Offset = (sourceText, utf8Offset) => Buffer.from(sourceText).subarray(0, utf8Offset).toString("utf8").length;
|
|
39994
|
+
const hasWorkletDirective = (node) => {
|
|
39995
|
+
if (!ts.isFunctionDeclaration(node) && !ts.isFunctionExpression(node) && !ts.isArrowFunction(node) && !ts.isMethodDeclaration(node)) return false;
|
|
39996
|
+
const body = node.body;
|
|
39997
|
+
if (!body || !ts.isBlock(body)) return false;
|
|
39998
|
+
for (const statement of body.statements) {
|
|
39999
|
+
if (!ts.isExpressionStatement(statement)) return false;
|
|
40000
|
+
if (!ts.isStringLiteral(statement.expression)) return false;
|
|
40001
|
+
if (statement.expression.text === WORKLET_DIRECTIVE) return true;
|
|
40002
|
+
}
|
|
40003
|
+
return false;
|
|
40004
|
+
};
|
|
40005
|
+
const rootIdentifierOfCallChain = (expression) => {
|
|
40006
|
+
let currentExpression = expression;
|
|
40007
|
+
while (true) {
|
|
40008
|
+
if (ts.isIdentifier(currentExpression)) return currentExpression.text;
|
|
40009
|
+
if (ts.isPropertyAccessExpression(currentExpression)) {
|
|
40010
|
+
currentExpression = currentExpression.expression;
|
|
40011
|
+
continue;
|
|
40012
|
+
}
|
|
40013
|
+
if (ts.isCallExpression(currentExpression)) {
|
|
40014
|
+
currentExpression = currentExpression.expression;
|
|
40015
|
+
continue;
|
|
40016
|
+
}
|
|
40017
|
+
return null;
|
|
40018
|
+
}
|
|
40019
|
+
};
|
|
40020
|
+
const isWorkletAcceptingCall = (callExpression) => {
|
|
40021
|
+
const callee = callExpression.expression;
|
|
40022
|
+
if (ts.isIdentifier(callee)) return WORKLET_ACCEPTING_CALLEES.has(callee.text);
|
|
40023
|
+
if (ts.isPropertyAccessExpression(callee)) {
|
|
40024
|
+
if (WORKLET_ACCEPTING_CALLEES.has(callee.name.text)) return true;
|
|
40025
|
+
return rootIdentifierOfCallChain(callee) === GESTURE_ROOT_IDENTIFIER;
|
|
40026
|
+
}
|
|
40027
|
+
return false;
|
|
40028
|
+
};
|
|
40029
|
+
const isFunctionWorklet = (functionNode) => {
|
|
40030
|
+
if (hasWorkletDirective(functionNode)) return true;
|
|
40031
|
+
const parent = functionNode.parent;
|
|
40032
|
+
return ts.isCallExpression(parent) && parent.arguments.some((argument) => argument === functionNode) && isWorkletAcceptingCall(parent);
|
|
40033
|
+
};
|
|
40034
|
+
const findNodeAtOffset = (sourceFile, targetOffset) => {
|
|
40035
|
+
let matchedNode = null;
|
|
40036
|
+
const visit = (node) => {
|
|
40037
|
+
if (node.getStart(sourceFile) > targetOffset || node.getEnd() <= targetOffset) return;
|
|
40038
|
+
matchedNode = node;
|
|
40039
|
+
ts.forEachChild(node, visit);
|
|
40040
|
+
};
|
|
40041
|
+
visit(sourceFile);
|
|
40042
|
+
return matchedNode;
|
|
40043
|
+
};
|
|
40044
|
+
const isOffsetInsideWorklet = (sourceFile, targetOffset) => {
|
|
40045
|
+
let currentNode = findNodeAtOffset(sourceFile, targetOffset);
|
|
40046
|
+
while (currentNode) {
|
|
40047
|
+
if (ts.isFunctionLike(currentNode) && isFunctionWorklet(currentNode)) return true;
|
|
40048
|
+
currentNode = currentNode.parent;
|
|
40049
|
+
}
|
|
40050
|
+
return false;
|
|
40051
|
+
};
|
|
40052
|
+
const shouldSuppressCompilerFindingInWorklet = (diagnostic, project, rootDirectory) => {
|
|
40053
|
+
if (!SUPPRESSED_COMPILER_CODES.has(diagnostic.code)) return false;
|
|
40054
|
+
if (!project.hasReanimated) return false;
|
|
40055
|
+
const primaryLabel = diagnostic.labels[0];
|
|
40056
|
+
if (!primaryLabel) return false;
|
|
40057
|
+
const absolutePath = Path.isAbsolute(diagnostic.filename) ? diagnostic.filename : Path.join(rootDirectory, diagnostic.filename);
|
|
40058
|
+
let sourceText;
|
|
40059
|
+
try {
|
|
40060
|
+
sourceText = NFS.readFileSync(absolutePath, "utf-8");
|
|
40061
|
+
} catch {
|
|
40062
|
+
return false;
|
|
40063
|
+
}
|
|
40064
|
+
return isOffsetInsideWorklet(ts.createSourceFile(absolutePath, sourceText, ts.ScriptTarget.Latest, true, getScriptKind$1(absolutePath)), getUtf16Offset(sourceText, primaryLabel.span.offset));
|
|
40065
|
+
};
|
|
40066
|
+
const MANUAL_MEMOIZATION_PLUGIN = "react-doctor";
|
|
40067
|
+
const MANUAL_MEMOIZATION_RULE = "react-compiler-no-manual-memoization";
|
|
40068
|
+
const REACT_COMPILER_PLUGIN = "react-hooks-js";
|
|
40069
|
+
const getScriptKind = (filename) => {
|
|
40070
|
+
if (filename.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
40071
|
+
if (filename.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
40072
|
+
if (filename.endsWith(".ts")) return ts.ScriptKind.TS;
|
|
40073
|
+
return ts.ScriptKind.JS;
|
|
40074
|
+
};
|
|
40075
|
+
const isManualMemoizationDiagnostic = (diagnostic) => diagnostic.plugin === MANUAL_MEMOIZATION_PLUGIN && diagnostic.rule === MANUAL_MEMOIZATION_RULE;
|
|
40076
|
+
const nodeLineRange = (sourceFile, node) => ({
|
|
40077
|
+
startLine: sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1,
|
|
40078
|
+
endLine: sourceFile.getLineAndCharacterOfPosition(node.getEnd()).line + 1
|
|
40079
|
+
});
|
|
40080
|
+
const findOutermostEnclosingFunctionRange = (sourceFile, targetLine) => {
|
|
40081
|
+
let foundRange = null;
|
|
40082
|
+
const visit = (node) => {
|
|
40083
|
+
if (foundRange) return;
|
|
40084
|
+
if (ts.isFunctionLike(node)) {
|
|
40085
|
+
const range = nodeLineRange(sourceFile, node);
|
|
40086
|
+
if (range.startLine <= targetLine && targetLine <= range.endLine) foundRange = range;
|
|
40087
|
+
return;
|
|
40088
|
+
}
|
|
40089
|
+
ts.forEachChild(node, visit);
|
|
40090
|
+
};
|
|
40091
|
+
visit(sourceFile);
|
|
40092
|
+
return foundRange;
|
|
40093
|
+
};
|
|
40094
|
+
const NEWLINE_BYTE = 10;
|
|
40095
|
+
const lineOfUtf8Offset = (sourceBuffer, utf8Offset) => {
|
|
40096
|
+
let lineNumber = 1;
|
|
40097
|
+
const scanEnd = Math.min(utf8Offset, sourceBuffer.length);
|
|
40098
|
+
for (let byteIndex = 0; byteIndex < scanEnd; byteIndex++) if (sourceBuffer[byteIndex] === NEWLINE_BYTE) lineNumber++;
|
|
40099
|
+
return lineNumber;
|
|
40100
|
+
};
|
|
40101
|
+
/**
|
|
40102
|
+
* Drops `react-doctor/react-compiler-no-manual-memoization` diagnostics
|
|
40103
|
+
* whose enclosing function also carries a `react-hooks-js` (React
|
|
40104
|
+
* Compiler) diagnostic. Every react-hooks-js finding marks a function the
|
|
40105
|
+
* compiler could NOT compile (a bail-out / skip / preserve-manual-
|
|
40106
|
+
* memoization failure), so "React Compiler already caches every value
|
|
40107
|
+
* here" is false for that function and the manual memoization is
|
|
40108
|
+
* load-bearing, not dead weight.
|
|
40109
|
+
*
|
|
40110
|
+
* Matching is per enclosing function, not per file: a bailed-out
|
|
40111
|
+
* component must not silence redundant-memo findings in a sibling
|
|
40112
|
+
* component the compiler did optimize.
|
|
40113
|
+
*
|
|
40114
|
+
* Module-scope `memo(() => …)` wrappers have no enclosing function; for
|
|
40115
|
+
* those the memo call's own span is the containment range, so a bail-out
|
|
40116
|
+
* inside the inline-wrapped component still suppresses. Every failure
|
|
40117
|
+
* path (unreadable file, missing span) fails open and keeps the
|
|
40118
|
+
* diagnostic.
|
|
40119
|
+
*/
|
|
40120
|
+
const suppressMemoizationInBailedOutFunctions = (diagnostics, rootDirectory) => {
|
|
40121
|
+
const compilerDiagnosticLinesByFile = /* @__PURE__ */ new Map();
|
|
40122
|
+
let hasManualMemoizationDiagnostic = false;
|
|
40123
|
+
for (const diagnostic of diagnostics) {
|
|
40124
|
+
if (isManualMemoizationDiagnostic(diagnostic)) {
|
|
40125
|
+
hasManualMemoizationDiagnostic = true;
|
|
40126
|
+
continue;
|
|
40127
|
+
}
|
|
40128
|
+
if (diagnostic.plugin !== REACT_COMPILER_PLUGIN || diagnostic.line <= 0) continue;
|
|
40129
|
+
const lines = compilerDiagnosticLinesByFile.get(diagnostic.filePath);
|
|
40130
|
+
if (lines) lines.push(diagnostic.line);
|
|
40131
|
+
else compilerDiagnosticLinesByFile.set(diagnostic.filePath, [diagnostic.line]);
|
|
40132
|
+
}
|
|
40133
|
+
if (!hasManualMemoizationDiagnostic || compilerDiagnosticLinesByFile.size === 0) return diagnostics;
|
|
40134
|
+
const parsedFileCache = /* @__PURE__ */ new Map();
|
|
40135
|
+
const readAndParse = (filePath) => {
|
|
40136
|
+
const cached = parsedFileCache.get(filePath);
|
|
40137
|
+
if (cached !== void 0) return cached;
|
|
40138
|
+
const absolutePath = Path.isAbsolute(filePath) ? filePath : Path.resolve(rootDirectory || ".", filePath);
|
|
40139
|
+
let parsed;
|
|
40140
|
+
try {
|
|
40141
|
+
const buffer = NFS.readFileSync(absolutePath);
|
|
40142
|
+
parsed = {
|
|
40143
|
+
sourceFile: ts.createSourceFile(absolutePath, buffer.toString("utf8"), ts.ScriptTarget.Latest, true, getScriptKind(absolutePath)),
|
|
40144
|
+
buffer
|
|
40145
|
+
};
|
|
40146
|
+
} catch {
|
|
40147
|
+
parsed = null;
|
|
40148
|
+
}
|
|
40149
|
+
parsedFileCache.set(filePath, parsed);
|
|
40150
|
+
return parsed;
|
|
40151
|
+
};
|
|
40152
|
+
const isInsideBailedOutFunction = (diagnostic) => {
|
|
40153
|
+
const compilerLines = compilerDiagnosticLinesByFile.get(diagnostic.filePath);
|
|
40154
|
+
if (!compilerLines || diagnostic.line <= 0) return false;
|
|
40155
|
+
const parsed = readAndParse(diagnostic.filePath);
|
|
40156
|
+
if (!parsed) return false;
|
|
40157
|
+
let containmentRange = findOutermostEnclosingFunctionRange(parsed.sourceFile, diagnostic.line);
|
|
40158
|
+
if (!containmentRange && diagnostic.offset !== void 0 && diagnostic.length !== void 0) containmentRange = {
|
|
40159
|
+
startLine: diagnostic.line,
|
|
40160
|
+
endLine: lineOfUtf8Offset(parsed.buffer, diagnostic.offset + diagnostic.length)
|
|
40161
|
+
};
|
|
40162
|
+
if (!containmentRange) return false;
|
|
40163
|
+
const { startLine, endLine } = containmentRange;
|
|
40164
|
+
return compilerLines.some((compilerLine) => startLine <= compilerLine && compilerLine <= endLine);
|
|
40165
|
+
};
|
|
40166
|
+
return diagnostics.filter((diagnostic) => !isManualMemoizationDiagnostic(diagnostic) || !isInsideBailedOutFunction(diagnostic));
|
|
40167
|
+
};
|
|
39874
40168
|
const FILEPATH_WITH_LOCATION_PATTERN = /\S+\.\w+:\d+:\d+[\s\S]*$/;
|
|
39875
40169
|
const LEADING_SEVERITY_LABEL_PATTERN = /^(?:Error|Warning):\s*/;
|
|
39876
40170
|
const TRAILING_PERIOD_PATTERN = /\.$/;
|
|
@@ -39906,8 +40200,14 @@ const PLUGIN_CATEGORY_MAP = {
|
|
|
39906
40200
|
};
|
|
39907
40201
|
const lookupOwnString = (record, key) => Object.hasOwn(record, key) ? record[key] : void 0;
|
|
39908
40202
|
const getRuleRecommendation = (ruleName, project) => {
|
|
39909
|
-
|
|
39910
|
-
return
|
|
40203
|
+
const rule = reactDoctorPlugin.rules[ruleName];
|
|
40204
|
+
if (!rule) return void 0;
|
|
40205
|
+
if (rule.recommendationFor) {
|
|
40206
|
+
const capabilities = getCapabilities(project);
|
|
40207
|
+
const conditionalRecommendation = rule.recommendationFor((capability) => capabilities.has(capability));
|
|
40208
|
+
if (conditionalRecommendation !== void 0) return conditionalRecommendation;
|
|
40209
|
+
}
|
|
40210
|
+
return rule.recommendation;
|
|
39911
40211
|
};
|
|
39912
40212
|
const getRuleCategory = (ruleName) => reactDoctorPlugin.rules[ruleName]?.category;
|
|
39913
40213
|
const getRuleTitle = (ruleName) => reactDoctorPlugin.rules[ruleName]?.title;
|
|
@@ -40010,7 +40310,7 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
40010
40310
|
minifiedFileCache.set(absolutePath, minified);
|
|
40011
40311
|
return minified;
|
|
40012
40312
|
};
|
|
40013
|
-
return parsed.diagnostics.filter((diagnostic) => diagnostic.code && isLintableSourceFile(diagnostic.filename) && !isMinifiedDiagnosticFile(diagnostic.filename) && !shouldSuppressLocalUseHookDiagnostic(diagnostic, rootDirectory)).map((diagnostic) => {
|
|
40313
|
+
return suppressMemoizationInBailedOutFunctions(parsed.diagnostics.filter((diagnostic) => diagnostic.code && isLintableSourceFile(diagnostic.filename) && !isMinifiedDiagnosticFile(diagnostic.filename) && !shouldSuppressLocalUseHookDiagnostic(diagnostic, rootDirectory) && !shouldSuppressCompilerFindingInWorklet(diagnostic, project, rootDirectory)).map((diagnostic) => {
|
|
40014
40314
|
const { plugin, rule } = parseRuleCode(diagnostic.code);
|
|
40015
40315
|
const primaryLabel = diagnostic.labels[0];
|
|
40016
40316
|
const cleaned = cleanDiagnosticMessage(diagnostic.message, diagnostic.help, plugin, rule, project);
|
|
@@ -40037,7 +40337,7 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
40037
40337
|
...resolveMatchByOccurrence(rule, category) ? { matchByOccurrence: true } : {},
|
|
40038
40338
|
...relatedLocations.length > 0 ? { relatedLocations } : {}
|
|
40039
40339
|
};
|
|
40040
|
-
});
|
|
40340
|
+
}), rootDirectory);
|
|
40041
40341
|
};
|
|
40042
40342
|
const buildOxlintChildEnv = (sourceEnv) => {
|
|
40043
40343
|
const childEnv = {};
|
|
@@ -40061,7 +40361,9 @@ const SANITIZED_ENV = buildOxlintChildEnv(process.env);
|
|
|
40061
40361
|
* - `OxlintBatchExceeded { kind: "output-too-large" }` — stdout+stderr
|
|
40062
40362
|
* crossed `OXLINT_OUTPUT_MAX_BYTES`.
|
|
40063
40363
|
* - `OxlintBatchExceeded { kind: "oom" | "killed" }` — child exited
|
|
40064
|
-
* on a signal (SIGABRT → OOM, others → generic kill)
|
|
40364
|
+
* on a signal (SIGABRT → OOM, others → generic kill), or with one
|
|
40365
|
+
* of the abort exit codes Windows reports instead of a signal
|
|
40366
|
+
* (`ABORT_EXIT_CODES` → OOM).
|
|
40065
40367
|
* - `OxlintSpawnFailed { cause }` — `spawn` itself errored, or the
|
|
40066
40368
|
* child exited successfully but printed only stderr.
|
|
40067
40369
|
*
|
|
@@ -40127,7 +40429,7 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath, spawnTimeoutMs = OXLIN
|
|
|
40127
40429
|
clearAbortListener();
|
|
40128
40430
|
reject(new ReactDoctorError({ reason: new OxlintSpawnFailed({ cause: error }) }));
|
|
40129
40431
|
});
|
|
40130
|
-
child.on("close", (
|
|
40432
|
+
child.on("close", (code, signal) => {
|
|
40131
40433
|
clearTimeout(timeoutHandle);
|
|
40132
40434
|
clearAbortListener();
|
|
40133
40435
|
if (didKillForSize) {
|
|
@@ -40137,10 +40439,11 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath, spawnTimeoutMs = OXLIN
|
|
|
40137
40439
|
}) }));
|
|
40138
40440
|
return;
|
|
40139
40441
|
}
|
|
40140
|
-
|
|
40442
|
+
const isAbortExitCode = code !== null && ABORT_EXIT_CODES.has(code);
|
|
40443
|
+
if (signal || isAbortExitCode) {
|
|
40141
40444
|
const stderrOutput = Buffer.concat(stderrBuffers).toString("utf-8").trim();
|
|
40142
|
-
const isOom = signal === "SIGABRT";
|
|
40143
|
-
const detailParts = [`killed by ${signal}`];
|
|
40445
|
+
const isOom = signal === "SIGABRT" || isAbortExitCode;
|
|
40446
|
+
const detailParts = [signal ? `killed by ${signal}` : `aborted with exit code ${code}`];
|
|
40144
40447
|
if (isOom) detailParts.push("try scanning fewer files with --diff");
|
|
40145
40448
|
if (stderrOutput) detailParts.push(stderrOutput);
|
|
40146
40449
|
reject(new ReactDoctorError({ reason: new OxlintBatchExceeded({
|
|
@@ -40183,12 +40486,19 @@ const isParallelismRelatedSpawnError = (error) => {
|
|
|
40183
40486
|
* (surfaced via `onPartialFailure`) so JSON-mode consumers see WHICH
|
|
40184
40487
|
* files were skipped instead of silently losing them.
|
|
40185
40488
|
*
|
|
40186
|
-
* Parallel runs (concurrency > 1) get
|
|
40187
|
-
* fails with a resource-exhaustion error that's exclusive
|
|
40188
|
-
* many oxlint subprocesses at once (EAGAIN / EMFILE /
|
|
40189
|
-
* see `isParallelismRelatedSpawnError`), the whole
|
|
40190
|
-
* with a single worker. That's the only failure a
|
|
40191
|
-
* clear, so every other error class is left to
|
|
40489
|
+
* Parallel runs (concurrency > 1) get two extra safety nets:
|
|
40490
|
+
* - If the pass fails with a resource-exhaustion error that's exclusive
|
|
40491
|
+
* to running many oxlint subprocesses at once (EAGAIN / EMFILE /
|
|
40492
|
+
* ENFILE / ENOMEM — see `isParallelismRelatedSpawnError`), the whole
|
|
40493
|
+
* pass replays once with a single worker. That's the only failure a
|
|
40494
|
+
* serial replay can clear, so every other error class is left to
|
|
40495
|
+
* propagate.
|
|
40496
|
+
* - Files dropped because oxlint's native binding SIGABRT'd under
|
|
40497
|
+
* memory pressure (`OxlintBatchExceeded { kind: "oom" }`) get one
|
|
40498
|
+
* serial single-file-batch rescue pass: the OOM is usually a function
|
|
40499
|
+
* of N concurrent oxlint allocator arenas, not the file itself, so a
|
|
40500
|
+
* lone process with the machine to itself typically clears it —
|
|
40501
|
+
* turning a partial scan into a complete (if slower) one.
|
|
40192
40502
|
*
|
|
40193
40503
|
* Errors that aren't splittable and aren't parallelism-related (oxlint
|
|
40194
40504
|
* config crash, JS plugin resolution failure, etc.) propagate to the
|
|
@@ -40198,13 +40508,16 @@ const isParallelismRelatedSpawnError = (error) => {
|
|
|
40198
40508
|
const spawnLintBatches = async (input) => {
|
|
40199
40509
|
const { baseArgs, fileBatches, rootDirectory, nodeBinaryPath, project, onPartialFailure, onFileProgress, spawnTimeoutMs, outputMaxBytes, splitTotalBudgetMs = OXLINT_SPLIT_TOTAL_BUDGET_MS, splitMaxDepth = 8, signal, deadlineEpochMs } = input;
|
|
40200
40510
|
const requestedConcurrency = resolveScanConcurrency(input.concurrency ?? 1);
|
|
40201
|
-
|
|
40202
|
-
const
|
|
40511
|
+
let rescueDeadlineEpochMs;
|
|
40512
|
+
const isPastDeadline = () => deadlineEpochMs !== void 0 && remainingDeadlineBudgetMs(deadlineEpochMs) === 0 || rescueDeadlineEpochMs !== void 0 && remainingDeadlineBudgetMs(rescueDeadlineEpochMs) === 0;
|
|
40513
|
+
const runBatchPass = async (concurrency, passBatches, passOnFileProgress) => {
|
|
40514
|
+
const totalFileCount = passBatches.reduce((sum, batch) => sum + batch.length, 0);
|
|
40203
40515
|
const allDiagnostics = [];
|
|
40204
40516
|
const droppedFiles = [];
|
|
40517
|
+
const oomDroppedFiles = [];
|
|
40205
40518
|
const deadlineSkippedFiles = [];
|
|
40206
|
-
const isPastDeadline = () => deadlineEpochMs !== void 0 && remainingDeadlineBudgetMs(deadlineEpochMs) === 0;
|
|
40207
40519
|
let firstDropReason = null;
|
|
40520
|
+
let firstNonOomDropReason = null;
|
|
40208
40521
|
const spawnLintBatch = async (batch, depth, batchState) => {
|
|
40209
40522
|
if (isPastDeadline()) {
|
|
40210
40523
|
deadlineSkippedFiles.push(...batch);
|
|
@@ -40226,12 +40539,14 @@ const spawnLintBatches = async (input) => {
|
|
|
40226
40539
|
const isDepthCapReached = depth >= splitMaxDepth;
|
|
40227
40540
|
if (batch.length <= 1 || isBudgetElapsed || isDepthCapReached) {
|
|
40228
40541
|
droppedFiles.push(...batch);
|
|
40229
|
-
|
|
40230
|
-
|
|
40231
|
-
|
|
40232
|
-
|
|
40233
|
-
|
|
40234
|
-
}
|
|
40542
|
+
const isOomDrop = error.reason._tag === "OxlintBatchExceeded" && error.reason.kind === "oom";
|
|
40543
|
+
if (isOomDrop) oomDroppedFiles.push(...batch);
|
|
40544
|
+
let limitHint = "";
|
|
40545
|
+
if (isDepthCapReached) limitHint = ` (split depth cap of ${splitMaxDepth} levels reached)`;
|
|
40546
|
+
else if (isBudgetElapsed) limitHint = ` (split budget of ${splitTotalBudgetMs / MILLISECONDS_PER_SECOND}s exhausted at depth ${depth})`;
|
|
40547
|
+
const dropReason = batch.length > 1 ? `${error.message}${limitHint}` : error.message;
|
|
40548
|
+
firstDropReason ??= dropReason;
|
|
40549
|
+
if (!isOomDrop) firstNonOomDropReason ??= dropReason;
|
|
40235
40550
|
return [];
|
|
40236
40551
|
}
|
|
40237
40552
|
const splitIndex = Math.ceil(batch.length / 2);
|
|
@@ -40241,16 +40556,16 @@ const spawnLintBatches = async (input) => {
|
|
|
40241
40556
|
let startedFileCount = 0;
|
|
40242
40557
|
let scannedFileCount = 0;
|
|
40243
40558
|
let displayedFileCount = 0;
|
|
40244
|
-
const progressTimer =
|
|
40559
|
+
const progressTimer = passOnFileProgress && totalFileCount > 1 ? setInterval(() => {
|
|
40245
40560
|
const ceiling = Math.min(startedFileCount, totalFileCount - 1);
|
|
40246
40561
|
if (displayedFileCount < ceiling) {
|
|
40247
40562
|
displayedFileCount += 1;
|
|
40248
|
-
|
|
40563
|
+
passOnFileProgress(displayedFileCount, totalFileCount);
|
|
40249
40564
|
}
|
|
40250
40565
|
}, 50) : null;
|
|
40251
40566
|
progressTimer?.unref?.();
|
|
40252
40567
|
try {
|
|
40253
|
-
const batchResults = await mapWithConcurrency(
|
|
40568
|
+
const batchResults = await mapWithConcurrency(passBatches, concurrency, async (batch) => {
|
|
40254
40569
|
if (isPastDeadline()) {
|
|
40255
40570
|
deadlineSkippedFiles.push(...batch);
|
|
40256
40571
|
return [];
|
|
@@ -40262,9 +40577,9 @@ const spawnLintBatches = async (input) => {
|
|
|
40262
40577
|
};
|
|
40263
40578
|
const batchDiagnostics = await spawnLintBatch(batch, 0, batchState);
|
|
40264
40579
|
scannedFileCount += batch.length - batchState.deadlineSkippedFileCount;
|
|
40265
|
-
if (
|
|
40580
|
+
if (passOnFileProgress) {
|
|
40266
40581
|
displayedFileCount = Math.min(Math.max(displayedFileCount, scannedFileCount), totalFileCount);
|
|
40267
|
-
|
|
40582
|
+
passOnFileProgress(displayedFileCount, totalFileCount);
|
|
40268
40583
|
}
|
|
40269
40584
|
return batchDiagnostics;
|
|
40270
40585
|
});
|
|
@@ -40272,22 +40587,53 @@ const spawnLintBatches = async (input) => {
|
|
|
40272
40587
|
} finally {
|
|
40273
40588
|
if (progressTimer !== null) clearInterval(progressTimer);
|
|
40274
40589
|
}
|
|
40275
|
-
|
|
40276
|
-
|
|
40277
|
-
|
|
40590
|
+
return {
|
|
40591
|
+
diagnostics: allDiagnostics,
|
|
40592
|
+
droppedFiles,
|
|
40593
|
+
oomDroppedFiles,
|
|
40594
|
+
deadlineSkippedFiles,
|
|
40595
|
+
firstDropReason,
|
|
40596
|
+
firstNonOomDropReason
|
|
40278
40597
|
};
|
|
40279
|
-
const reasonHint = firstDropReason ? ` — first failure: ${firstDropReason}` : "";
|
|
40280
|
-
reportSkippedFiles(droppedFiles, (fileListPreview) => `${droppedFiles.length} file(s) failed to lint and were skipped (${fileListPreview})${reasonHint}`);
|
|
40281
|
-
reportSkippedFiles(deadlineSkippedFiles, (fileListPreview) => `${deadlineSkippedFiles.length} file(s) skipped — max scan duration reached before they were linted (${fileListPreview})`);
|
|
40282
|
-
return allDiagnostics;
|
|
40283
40598
|
};
|
|
40284
|
-
let
|
|
40599
|
+
let outcome;
|
|
40600
|
+
let effectiveConcurrency = requestedConcurrency;
|
|
40285
40601
|
try {
|
|
40286
|
-
|
|
40602
|
+
outcome = await runBatchPass(requestedConcurrency, fileBatches, onFileProgress);
|
|
40287
40603
|
} catch (error) {
|
|
40288
40604
|
if (requestedConcurrency <= 1 || !isParallelismRelatedSpawnError(error)) throw error;
|
|
40289
|
-
|
|
40605
|
+
effectiveConcurrency = 1;
|
|
40606
|
+
outcome = await runBatchPass(1, fileBatches, onFileProgress);
|
|
40607
|
+
}
|
|
40608
|
+
let { droppedFiles, firstDropReason } = outcome;
|
|
40609
|
+
const diagnostics = outcome.diagnostics;
|
|
40610
|
+
if (outcome.oomDroppedFiles.length > 0 && effectiveConcurrency > 1 && !isPastDeadline()) {
|
|
40611
|
+
rescueDeadlineEpochMs = Date.now() + OXLINT_OOM_RESCUE_BUDGET_MS;
|
|
40612
|
+
const rescuedFiles = /* @__PURE__ */ new Set();
|
|
40613
|
+
let firstRescueFailureReason = null;
|
|
40614
|
+
for (const oomDroppedFile of outcome.oomDroppedFiles) {
|
|
40615
|
+
if (isPastDeadline()) break;
|
|
40616
|
+
try {
|
|
40617
|
+
const rescueOutcome = await runBatchPass(1, [[oomDroppedFile]], void 0);
|
|
40618
|
+
diagnostics.push(...rescueOutcome.diagnostics);
|
|
40619
|
+
if (rescueOutcome.droppedFiles.length > 0 || rescueOutcome.deadlineSkippedFiles.length > 0) firstRescueFailureReason ??= rescueOutcome.firstDropReason;
|
|
40620
|
+
else rescuedFiles.add(oomDroppedFile);
|
|
40621
|
+
} catch (error) {
|
|
40622
|
+
firstRescueFailureReason ??= error instanceof Error ? error.message : String(error);
|
|
40623
|
+
}
|
|
40624
|
+
}
|
|
40625
|
+
rescueDeadlineEpochMs = void 0;
|
|
40626
|
+
droppedFiles = droppedFiles.filter((filePath) => !rescuedFiles.has(filePath));
|
|
40627
|
+
if (droppedFiles.length === 0) firstDropReason = null;
|
|
40628
|
+
else firstDropReason = firstRescueFailureReason ?? outcome.firstNonOomDropReason ?? firstDropReason;
|
|
40290
40629
|
}
|
|
40630
|
+
const reportSkippedFiles = (skippedFiles, buildMessage) => {
|
|
40631
|
+
if (skippedFiles.length === 0 || !onPartialFailure) return;
|
|
40632
|
+
onPartialFailure(buildMessage(`${skippedFiles.slice(0, 3).join(", ")}${skippedFiles.length > 3 ? `, +${skippedFiles.length - 3} more` : ""}`));
|
|
40633
|
+
};
|
|
40634
|
+
const reasonHint = firstDropReason ? ` — first failure: ${firstDropReason}` : "";
|
|
40635
|
+
reportSkippedFiles(droppedFiles, (fileListPreview) => `${droppedFiles.length} file(s) failed to lint and were skipped (${fileListPreview})${reasonHint}`);
|
|
40636
|
+
reportSkippedFiles(outcome.deadlineSkippedFiles, (fileListPreview) => `${outcome.deadlineSkippedFiles.length} file(s) skipped — max scan duration reached before they were linted (${fileListPreview})`);
|
|
40291
40637
|
return dedupeDiagnostics(diagnostics);
|
|
40292
40638
|
};
|
|
40293
40639
|
let didValidate = false;
|
|
@@ -40548,6 +40894,7 @@ const runOxlint = async (options) => {
|
|
|
40548
40894
|
const serverAuthFunctionNames = Array.isArray(userConfig?.serverAuthFunctionNames) ? userConfig.serverAuthFunctionNames.filter((entry) => typeof entry === "string" && entry.length > 0) : void 0;
|
|
40549
40895
|
const severityControls = buildRuleSeverityControls(userConfig);
|
|
40550
40896
|
validateRuleRegistration();
|
|
40897
|
+
resetManifestCaches();
|
|
40551
40898
|
if (includePaths !== void 0 && includePaths.length === 0) return [];
|
|
40552
40899
|
const pluginPath = resolvePluginPath();
|
|
40553
40900
|
const extendsPaths = (adoptExistingLintConfig && !customRulesOnly ? detectUserLintConfigPaths(rootDirectory) : []).filter(canOxlintExtendConfig);
|
|
@@ -41210,9 +41557,10 @@ const CONTEXT_AXES = [
|
|
|
41210
41557
|
}
|
|
41211
41558
|
];
|
|
41212
41559
|
const SCORE_AXES = [...GATED_AXES, ...CONTEXT_AXES];
|
|
41213
|
-
const
|
|
41214
|
-
|
|
41215
|
-
|
|
41560
|
+
const gatedAxesForSection = (section) => section === "devDependencies" ? GATED_AXES.filter((axis) => axis.key !== "vulnerability") : GATED_AXES;
|
|
41561
|
+
const worstGatedAxis = (score, gatedAxes) => {
|
|
41562
|
+
let worst = gatedAxes[0];
|
|
41563
|
+
for (const axis of gatedAxes) if (score[axis.key] < score[worst.key]) worst = axis;
|
|
41216
41564
|
return worst;
|
|
41217
41565
|
};
|
|
41218
41566
|
const clampScore = (value) => {
|
|
@@ -41236,6 +41584,69 @@ const resolveConcreteVersion = (spec) => {
|
|
|
41236
41584
|
if (range === null || range === "*") return null;
|
|
41237
41585
|
return import_semver.minVersion(trimmed)?.version ?? null;
|
|
41238
41586
|
};
|
|
41587
|
+
const readTextFileOrNull = (filePath) => {
|
|
41588
|
+
try {
|
|
41589
|
+
return NFS.readFileSync(filePath, "utf-8");
|
|
41590
|
+
} catch {
|
|
41591
|
+
return null;
|
|
41592
|
+
}
|
|
41593
|
+
};
|
|
41594
|
+
const buildNpmLockLookup = (lockText) => {
|
|
41595
|
+
let parsed;
|
|
41596
|
+
try {
|
|
41597
|
+
parsed = JSON.parse(lockText);
|
|
41598
|
+
} catch {
|
|
41599
|
+
return null;
|
|
41600
|
+
}
|
|
41601
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
41602
|
+
const lock = parsed;
|
|
41603
|
+
return (name) => {
|
|
41604
|
+
const entry = lock.packages?.[`node_modules/${name}`] ?? lock.dependencies?.[name];
|
|
41605
|
+
return typeof entry?.version === "string" ? entry.version : null;
|
|
41606
|
+
};
|
|
41607
|
+
};
|
|
41608
|
+
const buildYarnLockLookup = (lockText) => {
|
|
41609
|
+
const lines = lockText.split(/\r?\n/);
|
|
41610
|
+
return (name, spec) => {
|
|
41611
|
+
const descriptor = `${name}@${spec}`;
|
|
41612
|
+
const berryDescriptor = `${name}@npm:${spec}`;
|
|
41613
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
41614
|
+
const line = lines[lineIndex];
|
|
41615
|
+
if (line.startsWith(" ") || line.startsWith("#") || !line.includes(":")) continue;
|
|
41616
|
+
const descriptors = line.replace(/:\s*$/, "").split(",").map((part) => part.trim().replace(/^"|"$/g, ""));
|
|
41617
|
+
if (!descriptors.includes(descriptor) && !descriptors.includes(berryDescriptor)) continue;
|
|
41618
|
+
for (let bodyIndex = lineIndex + 1; bodyIndex < lines.length && (lines[bodyIndex].startsWith(" ") || lines[bodyIndex] === ""); bodyIndex += 1) {
|
|
41619
|
+
const versionMatch = /^\s+version:?\s+"?([^"\s]+)"?/.exec(lines[bodyIndex]);
|
|
41620
|
+
if (versionMatch) return versionMatch[1];
|
|
41621
|
+
}
|
|
41622
|
+
return null;
|
|
41623
|
+
}
|
|
41624
|
+
return null;
|
|
41625
|
+
};
|
|
41626
|
+
};
|
|
41627
|
+
const buildPnpmLockLookup = (lockText) => {
|
|
41628
|
+
return (name) => {
|
|
41629
|
+
const entryPattern = new RegExp(`^ {4,8}['"]?${name.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&")}['"]?:\\s*$`);
|
|
41630
|
+
const lines = lockText.split(/\r?\n/);
|
|
41631
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
41632
|
+
if (!entryPattern.test(lines[lineIndex])) continue;
|
|
41633
|
+
for (let bodyIndex = lineIndex + 1; bodyIndex < lines.length && /^ {5,}\S/.test(lines[bodyIndex]); bodyIndex += 1) {
|
|
41634
|
+
const versionMatch = /^\s+version:\s+['"]?([^'"\s(]+)/.exec(lines[bodyIndex]);
|
|
41635
|
+
if (versionMatch) return versionMatch[1];
|
|
41636
|
+
}
|
|
41637
|
+
}
|
|
41638
|
+
return null;
|
|
41639
|
+
};
|
|
41640
|
+
};
|
|
41641
|
+
const buildLockedVersionLookup = (rootDirectory) => {
|
|
41642
|
+
const npmLockText = readTextFileOrNull(Path.join(rootDirectory, "package-lock.json"));
|
|
41643
|
+
if (npmLockText !== null) return buildNpmLockLookup(npmLockText);
|
|
41644
|
+
const yarnLockText = readTextFileOrNull(Path.join(rootDirectory, "yarn.lock"));
|
|
41645
|
+
if (yarnLockText !== null) return buildYarnLockLookup(yarnLockText);
|
|
41646
|
+
const pnpmLockText = readTextFileOrNull(Path.join(rootDirectory, "pnpm-lock.yaml"));
|
|
41647
|
+
if (pnpmLockText !== null) return buildPnpmLockLookup(pnpmLockText);
|
|
41648
|
+
return null;
|
|
41649
|
+
};
|
|
41239
41650
|
const locateDependencyKey = (packageJsonText, section, name) => {
|
|
41240
41651
|
const needle = `"${name}"`;
|
|
41241
41652
|
const sectionHeader = new RegExp(`"${section}"\\s*:\\s*\\{`);
|
|
@@ -41262,7 +41673,7 @@ const locateDependencyKey = (packageJsonText, section, name) => {
|
|
|
41262
41673
|
}
|
|
41263
41674
|
return null;
|
|
41264
41675
|
};
|
|
41265
|
-
const collectDependenciesToScore = (packageJson, packageJsonText, includeDevDependencies) => {
|
|
41676
|
+
const collectDependenciesToScore = (packageJson, packageJsonText, includeDevDependencies, lockedVersionLookup) => {
|
|
41266
41677
|
const sectionByName = /* @__PURE__ */ new Map();
|
|
41267
41678
|
for (const name of Object.keys(packageJson.dependencies ?? {})) sectionByName.set(name, "dependencies");
|
|
41268
41679
|
if (includeDevDependencies) {
|
|
@@ -41272,13 +41683,18 @@ const collectDependenciesToScore = (packageJson, packageJsonText, includeDevDepe
|
|
|
41272
41683
|
for (const [name, section] of sectionByName) {
|
|
41273
41684
|
if (SUPPLY_CHAIN_IGNORED_PACKAGES.has(name)) continue;
|
|
41274
41685
|
const spec = (packageJson[section] ?? {})[name] ?? "";
|
|
41275
|
-
const
|
|
41276
|
-
if (
|
|
41686
|
+
const floorVersion = resolveConcreteVersion(spec);
|
|
41687
|
+
if (floorVersion === null) continue;
|
|
41688
|
+
const lockedVersion = lockedVersionLookup?.(name, spec) ?? null;
|
|
41689
|
+
const lockSatisfiesSpec = lockedVersion !== null && import_semver.valid(lockedVersion) !== null && import_semver.satisfies(lockedVersion, spec);
|
|
41690
|
+
const isExactPin = import_semver.valid(spec.trim()) !== null;
|
|
41277
41691
|
const location = locateDependencyKey(packageJsonText, section, name);
|
|
41278
41692
|
dependencies.push({
|
|
41279
41693
|
name,
|
|
41280
|
-
version,
|
|
41694
|
+
version: lockSatisfiesSpec ? lockedVersion : floorVersion,
|
|
41281
41695
|
spec,
|
|
41696
|
+
section,
|
|
41697
|
+
resolution: isExactPin ? "pin" : lockSatisfiesSpec ? "locked" : "floor",
|
|
41282
41698
|
line: location?.line ?? 0,
|
|
41283
41699
|
column: location?.column ?? 0
|
|
41284
41700
|
});
|
|
@@ -41429,7 +41845,11 @@ const formatAlertReason = (topAlerts, totalCount) => {
|
|
|
41429
41845
|
}
|
|
41430
41846
|
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)}.`;
|
|
41431
41847
|
};
|
|
41432
|
-
const formatDependencyIdentity = (dependency) =>
|
|
41848
|
+
const formatDependencyIdentity = (dependency) => {
|
|
41849
|
+
if (dependency.resolution === "pin") return `${dependency.name}@${dependency.version}`;
|
|
41850
|
+
if (dependency.resolution === "locked") return `${dependency.name}@${dependency.version} (lockfile resolution of "${dependency.spec}")`;
|
|
41851
|
+
return `${dependency.name}@${dependency.version} (lowest version "${dependency.spec}" allows)`;
|
|
41852
|
+
};
|
|
41433
41853
|
const buildSupplyChainHelp = (dependency, failingAxis, topAlerts, packagePageUrl, options) => {
|
|
41434
41854
|
const hasCriticalAlert = topAlerts.some((alert) => alert.severity.toLowerCase() === "critical");
|
|
41435
41855
|
const entry = `\`"${dependency.name}": "${dependency.spec}"\``;
|
|
@@ -41472,7 +41892,7 @@ const buildLowScoreDiagnostic = (dependency, artifact, failingAxis, options) =>
|
|
|
41472
41892
|
const checkSupplyChain = (input) => gen(function* () {
|
|
41473
41893
|
const options = resolveOptions(input.userConfig);
|
|
41474
41894
|
const packageJsonPath = Path.join(input.rootDirectory, "package.json");
|
|
41475
|
-
const dependencies = collectDependenciesToScore(readPackageJson(packageJsonPath), readPackageJsonText(packageJsonPath), options.includeDevDependencies);
|
|
41895
|
+
const dependencies = collectDependenciesToScore(readPackageJson(packageJsonPath), readPackageJsonText(packageJsonPath), options.includeDevDependencies, buildLockedVersionLookup(input.rootDirectory));
|
|
41476
41896
|
if (dependencies.length === 0) return [];
|
|
41477
41897
|
const cacheDirectory = isSupplyChainCacheDisabled() ? null : resolveReactDoctorCacheDir(input.rootDirectory);
|
|
41478
41898
|
if (cacheDirectory !== null) pruneExpiredSocketCache(cacheDirectory);
|
|
@@ -41481,7 +41901,7 @@ const checkSupplyChain = (input) => gen(function* () {
|
|
|
41481
41901
|
for (let index = 0; index < dependencies.length; index += 1) {
|
|
41482
41902
|
const artifact = artifacts[index];
|
|
41483
41903
|
if (!artifact) continue;
|
|
41484
|
-
const worstAxis = worstGatedAxis(artifact.score);
|
|
41904
|
+
const worstAxis = worstGatedAxis(artifact.score, gatedAxesForSection(dependencies[index].section));
|
|
41485
41905
|
if (toHundred(artifact.score[worstAxis.key]) >= options.minScore) continue;
|
|
41486
41906
|
diagnostics.push(buildLowScoreDiagnostic(dependencies[index], artifact, worstAxis, options));
|
|
41487
41907
|
}
|
|
@@ -42158,6 +42578,13 @@ const summarizeDiagnostics = (diagnostics, worstScore = null, worstScoreLabel =
|
|
|
42158
42578
|
scoreLabel: worstScoreLabel
|
|
42159
42579
|
};
|
|
42160
42580
|
};
|
|
42581
|
+
/**
|
|
42582
|
+
* Whether project discovery resolved a React-compatible runtime (React or
|
|
42583
|
+
* Preact, which ships the same hooks + JSX model). This is the same
|
|
42584
|
+
* predicate that grants the `react` capability, so `false` means every
|
|
42585
|
+
* React-runtime rule family was gated off for the project.
|
|
42586
|
+
*/
|
|
42587
|
+
const hasReactRuntime = (project) => project.reactVersion !== null || project.preactVersion !== null;
|
|
42161
42588
|
const toJsonDiff = (diff) => {
|
|
42162
42589
|
if (!diff) return null;
|
|
42163
42590
|
return {
|
|
@@ -42392,17 +42819,20 @@ const warnIfAiTrainingEnvironment = () => {
|
|
|
42392
42819
|
didWarnAiTraining = true;
|
|
42393
42820
|
console.warn("[react-doctor] Use in an AI or ML pipeline requires written permission under the react-doctor license. Contact founders@million.dev to request access.");
|
|
42394
42821
|
};
|
|
42395
|
-
const
|
|
42396
|
-
|
|
42397
|
-
|
|
42398
|
-
|
|
42399
|
-
|
|
42822
|
+
const resolveShouldRunLint = (options, effectiveConfig) => options.lint ?? effectiveConfig?.lint ?? true;
|
|
42823
|
+
const resolveShouldRunDeadCode = (options, effectiveConfig) => options.deadCode ?? effectiveConfig?.deadCode ?? true;
|
|
42824
|
+
const buildDiagnoseLayer = (input) => {
|
|
42825
|
+
const configLayer = input.configOverrideTarget === void 0 ? Config.layerNode : Config.layerOf({
|
|
42826
|
+
config: input.config,
|
|
42827
|
+
resolvedDirectory: input.configOverrideTarget.resolvedDirectory,
|
|
42828
|
+
configSourceDirectory: input.configOverrideTarget.configSourceDirectory
|
|
42400
42829
|
});
|
|
42401
|
-
return mergeAll$1(Project.layerNode, configLayer, DeadCode.layerNode, Files.layerNode, Git.layerNode, Linter.layerOxlint, LintPartialFailures.layerLive, Progress.layerNoop, Reporter.layerNoop, Score.layerHttp, config?.supplyChain?.enabled !== false ? SupplyChain.layerNode : SupplyChain.layerOf([]));
|
|
42830
|
+
return mergeAll$1(Project.layerNode, configLayer, input.shouldRunDeadCode ? DeadCode.layerNode : DeadCode.layerOf([]), Files.layerNode, Git.layerNode, input.shouldRunLint ? Linter.layerOxlint : Linter.layerOf([]), LintPartialFailures.layerLive, Progress.layerNoop, Reporter.layerNoop, Score.layerHttp, input.config?.supplyChain?.enabled !== false ? SupplyChain.layerNode : SupplyChain.layerOf([]));
|
|
42402
42831
|
};
|
|
42403
42832
|
const buildInspectProgram = (scanTarget, options, configOverride) => {
|
|
42404
42833
|
const effectiveConfig = configOverride ?? scanTarget.userConfig;
|
|
42405
42834
|
const includePaths = options.includePaths ?? [];
|
|
42835
|
+
const shouldRunDeadCode = resolveShouldRunDeadCode(options, effectiveConfig);
|
|
42406
42836
|
return runInspect({
|
|
42407
42837
|
directory: scanTarget.resolvedDirectory,
|
|
42408
42838
|
includePaths,
|
|
@@ -42411,7 +42841,7 @@ const buildInspectProgram = (scanTarget, options, configOverride) => {
|
|
|
42411
42841
|
warnings: options.warnings ?? effectiveConfig?.warnings ?? true,
|
|
42412
42842
|
adoptExistingLintConfig: effectiveConfig?.adoptExistingLintConfig ?? true,
|
|
42413
42843
|
ignoredTags: new Set(effectiveConfig?.ignore?.tags ?? []),
|
|
42414
|
-
runDeadCode:
|
|
42844
|
+
runDeadCode: shouldRunDeadCode,
|
|
42415
42845
|
isCi: false,
|
|
42416
42846
|
resolveLocalGithubViewerPermission: true
|
|
42417
42847
|
});
|
|
@@ -42433,7 +42863,14 @@ const diagnoseDirectory = async (directory, options) => {
|
|
|
42433
42863
|
warnIfAiTrainingEnvironment();
|
|
42434
42864
|
const startTime = globalThis.performance.now();
|
|
42435
42865
|
const scanTarget = await resolveScanTarget(directory);
|
|
42436
|
-
|
|
42866
|
+
const program = buildInspectProgram(scanTarget, options);
|
|
42867
|
+
const shouldRunLint = resolveShouldRunLint(options, scanTarget.userConfig);
|
|
42868
|
+
const shouldRunDeadCode = resolveShouldRunDeadCode(options, scanTarget.userConfig);
|
|
42869
|
+
return outputToDiagnoseResult(await runPromise(restoreLegacyThrow(program.pipe(provide(buildDiagnoseLayer({
|
|
42870
|
+
config: scanTarget.userConfig,
|
|
42871
|
+
shouldRunLint,
|
|
42872
|
+
shouldRunDeadCode
|
|
42873
|
+
})), provide(layerOtlp)))), globalThis.performance.now() - startTime);
|
|
42437
42874
|
};
|
|
42438
42875
|
const findWorstScore = (projectResults) => {
|
|
42439
42876
|
let worstResult = null;
|
|
@@ -42454,15 +42891,27 @@ const diagnoseProject = async (projectDefinition, baseOptions, batchConfig) => {
|
|
|
42454
42891
|
const { directory: _, config: projectConfig, ...perProjectOptions } = projectDefinition;
|
|
42455
42892
|
const didOverrideConfig = batchConfig !== void 0 || projectConfig !== void 0;
|
|
42456
42893
|
const effectiveConfig = mergeReactDoctorConfigs(mergeReactDoctorConfigs(scanTarget.userConfig, batchConfig), projectConfig);
|
|
42457
|
-
const
|
|
42894
|
+
const mergedOptions = {
|
|
42458
42895
|
...baseOptions,
|
|
42459
42896
|
...perProjectOptions
|
|
42460
|
-
}
|
|
42897
|
+
};
|
|
42898
|
+
const shouldRunLint = resolveShouldRunLint(mergedOptions, effectiveConfig);
|
|
42899
|
+
const shouldRunDeadCode = resolveShouldRunDeadCode(mergedOptions, effectiveConfig);
|
|
42900
|
+
const program = buildInspectProgram(scanTarget, mergedOptions, effectiveConfig ?? void 0);
|
|
42461
42901
|
const didOverridePlugins = batchConfig?.plugins !== void 0 || projectConfig?.plugins !== void 0;
|
|
42462
|
-
const layer = buildDiagnoseLayer(
|
|
42463
|
-
|
|
42464
|
-
|
|
42465
|
-
|
|
42902
|
+
const layer = buildDiagnoseLayer(didOverrideConfig ? {
|
|
42903
|
+
config: effectiveConfig,
|
|
42904
|
+
shouldRunLint,
|
|
42905
|
+
shouldRunDeadCode,
|
|
42906
|
+
configOverrideTarget: {
|
|
42907
|
+
resolvedDirectory: scanTarget.resolvedDirectory,
|
|
42908
|
+
configSourceDirectory: didOverridePlugins ? null : scanTarget.configSourceDirectory
|
|
42909
|
+
}
|
|
42910
|
+
} : {
|
|
42911
|
+
config: effectiveConfig,
|
|
42912
|
+
shouldRunLint,
|
|
42913
|
+
shouldRunDeadCode
|
|
42914
|
+
});
|
|
42466
42915
|
return {
|
|
42467
42916
|
ok: true,
|
|
42468
42917
|
...outputToDiagnoseResult(await runPromise(restoreLegacyThrow(program.pipe(provide(layer), provide(layerOtlp)))), globalThis.performance.now() - startTime),
|
|
@@ -42524,4 +42973,4 @@ const toJsonReport = (result, options) => buildJsonReport({
|
|
|
42524
42973
|
export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, hasReactRuntime, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
|
|
42525
42974
|
|
|
42526
42975
|
//# sourceMappingURL=index.js.map
|
|
42527
|
-
//# debugId=
|
|
42976
|
+
//# debugId=3dc4a1ac-42dd-51e8-ba38-0260edf45659
|