react-doctor 0.7.2 → 0.7.3-dev.82187a3
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 +741 -685
- package/dist/index.d.ts +14 -2
- package/dist/index.js +826 -748
- package/dist/lsp.js +862 -813
- 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]="4940d639-1712-5f20-ae4a-a6bf10741ae9")}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";
|
|
@@ -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,6 +32817,18 @@ 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;
|
|
32803
32834
|
const GENERATED_SOURCE_DIRECTORY_PATTERN = /(?:^|\/)__generated__\//;
|
|
@@ -32899,6 +32930,8 @@ const OXLINT_SPAWN_TIMEOUT_MS = 6e4;
|
|
|
32899
32930
|
const NODE_COMPILE_CACHE_DIR_NAME = "node-compile-cache";
|
|
32900
32931
|
const DEAD_CODE_WORKER_TIMEOUT_MS = 12e4;
|
|
32901
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;
|
|
32902
32935
|
const DEAD_CODE_PHASE_TIMEOUT_MS = 15e4;
|
|
32903
32936
|
const LINT_PHASE_TIMEOUT_MS = 3e5;
|
|
32904
32937
|
const SCAN_TOTAL_DEADLINE_MS = 9e5;
|
|
@@ -32945,7 +32978,7 @@ const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
|
|
|
32945
32978
|
const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
|
|
32946
32979
|
const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
|
|
32947
32980
|
const SIDECAR_LINT_CACHE_FILENAME = "sidecar-lint-cache.json";
|
|
32948
|
-
const CORE_PACKAGE_VERSION = "0.7.
|
|
32981
|
+
const CORE_PACKAGE_VERSION = "0.7.3";
|
|
32949
32982
|
const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
|
|
32950
32983
|
const DEAD_CODE_SUMMARY_CACHE_FILENAME = "dead-code-summaries.json";
|
|
32951
32984
|
const SUPPLY_CHAIN_PLUGIN = "socket";
|
|
@@ -32994,25 +33027,6 @@ const isLargeMinifiedFile = (absolutePath, knownSizeBytes) => {
|
|
|
32994
33027
|
cachedIsLargeMinifiedByPath.set(absolutePath, result);
|
|
32995
33028
|
return result;
|
|
32996
33029
|
};
|
|
32997
|
-
const isErrnoException = (error) => error instanceof Error && "code" in error;
|
|
32998
|
-
const IGNORABLE_READDIR_ERROR_CODES = new Set([
|
|
32999
|
-
"EACCES",
|
|
33000
|
-
"EPERM",
|
|
33001
|
-
"ENOENT",
|
|
33002
|
-
"ENOTDIR",
|
|
33003
|
-
"EINVAL",
|
|
33004
|
-
"ELOOP",
|
|
33005
|
-
"ENAMETOOLONG"
|
|
33006
|
-
]);
|
|
33007
|
-
const isIgnorableReaddirError = (error) => isErrnoException(error) && typeof error.code === "string" && IGNORABLE_READDIR_ERROR_CODES.has(error.code);
|
|
33008
|
-
const readDirectoryEntries = (directoryPath) => {
|
|
33009
|
-
try {
|
|
33010
|
-
return NFS.readdirSync(directoryPath, { withFileTypes: true });
|
|
33011
|
-
} catch (error) {
|
|
33012
|
-
if (isIgnorableReaddirError(error)) return [];
|
|
33013
|
-
throw error;
|
|
33014
|
-
}
|
|
33015
|
-
};
|
|
33016
33030
|
function* walkSourceTreeFiles(rootDirectory) {
|
|
33017
33031
|
const stack = [rootDirectory];
|
|
33018
33032
|
while (stack.length > 0) {
|
|
@@ -33067,23 +33081,6 @@ const listSourceFilesViaFilesystem = (rootDirectory) => {
|
|
|
33067
33081
|
const listSourceFilesWithSize = (rootDirectory) => collectSizedSourceFiles(rootDirectory, (listSourceFilesViaGit(rootDirectory) ?? listSourceFilesViaFilesystem(rootDirectory)).sort());
|
|
33068
33082
|
const listSourceFiles = (rootDirectory) => listSourceFilesWithSize(rootDirectory).map((entry) => entry.path);
|
|
33069
33083
|
const countSourceFiles = (rootDirectory) => listSourceFilesWithSize(rootDirectory).length;
|
|
33070
|
-
/**
|
|
33071
|
-
* The chain of ancestor directories of `startDirectory`, nearest first, up to
|
|
33072
|
-
* and including the filesystem root. With `includeStart`, the chain begins at
|
|
33073
|
-
* `startDirectory` itself; otherwise at its parent. The one place the "walk up
|
|
33074
|
-
* the tree" traversal is expressed, so each caller adds only its own match /
|
|
33075
|
-
* stop policy instead of re-deriving the `path.dirname` loop.
|
|
33076
|
-
*/
|
|
33077
|
-
const ancestorDirectories = (startDirectory, { includeStart }) => {
|
|
33078
|
-
const directories = [];
|
|
33079
|
-
let currentDirectory = includeStart ? startDirectory : Path.dirname(startDirectory);
|
|
33080
|
-
while (true) {
|
|
33081
|
-
directories.push(currentDirectory);
|
|
33082
|
-
const parentDirectory = Path.dirname(currentDirectory);
|
|
33083
|
-
if (parentDirectory === currentDirectory) return directories;
|
|
33084
|
-
currentDirectory = parentDirectory;
|
|
33085
|
-
}
|
|
33086
|
-
};
|
|
33087
33084
|
const cachedPackageJsons = /* @__PURE__ */ new Map();
|
|
33088
33085
|
const clearPackageJsonCache = () => {
|
|
33089
33086
|
cachedPackageJsons.clear();
|
|
@@ -33117,7 +33114,11 @@ const isMonorepoRoot = (directory) => {
|
|
|
33117
33114
|
return Array.isArray(packageJson.workspaces) || Boolean(packageJson.workspaces?.packages);
|
|
33118
33115
|
};
|
|
33119
33116
|
const findMonorepoRoot = (startDirectory) => {
|
|
33120
|
-
|
|
33117
|
+
let currentDirectory = Path.dirname(startDirectory);
|
|
33118
|
+
while (currentDirectory !== Path.dirname(currentDirectory)) {
|
|
33119
|
+
if (isMonorepoRoot(currentDirectory)) return currentDirectory;
|
|
33120
|
+
currentDirectory = Path.dirname(currentDirectory);
|
|
33121
|
+
}
|
|
33121
33122
|
return null;
|
|
33122
33123
|
};
|
|
33123
33124
|
/**
|
|
@@ -33131,6 +33132,146 @@ const findMonorepoRoot = (startDirectory) => {
|
|
|
33131
33132
|
* byte-equivalent copy.
|
|
33132
33133
|
*/
|
|
33133
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
|
+
};
|
|
33134
33275
|
const REACT_COMPILER_PACKAGES = new Set([
|
|
33135
33276
|
"babel-plugin-react-compiler",
|
|
33136
33277
|
"react-compiler-runtime",
|
|
@@ -33167,6 +33308,7 @@ const EXPO_APP_CONFIG_FILENAMES = [
|
|
|
33167
33308
|
];
|
|
33168
33309
|
const REACT_COMPILER_PACKAGE_REFERENCE_PATTERN = /babel-plugin-react-compiler|react-compiler-runtime|eslint-plugin-react-compiler|["']react-compiler["']/;
|
|
33169
33310
|
const REACT_COMPILER_ENABLED_FLAG_PATTERN = /["']?reactCompiler["']?\s*:\s*(?:true\b|\{)/;
|
|
33311
|
+
const STATIC_EXPORT_OUTPUT_PATTERN = /(?:^|[^.\w])["']?output["']?\s*:\s*["']export["']/m;
|
|
33170
33312
|
const hasCompilerPackage = (packageJson) => {
|
|
33171
33313
|
const allDependencies = {
|
|
33172
33314
|
...packageJson.peerDependencies,
|
|
@@ -33199,34 +33341,10 @@ const detectReactCompiler = (directory, packageJson) => {
|
|
|
33199
33341
|
}
|
|
33200
33342
|
return false;
|
|
33201
33343
|
};
|
|
33202
|
-
const
|
|
33203
|
-
|
|
33204
|
-
|
|
33205
|
-
|
|
33206
|
-
"react-scripts": "cra",
|
|
33207
|
-
"@remix-run/react": "remix",
|
|
33208
|
-
gatsby: "gatsby",
|
|
33209
|
-
expo: "expo",
|
|
33210
|
-
"react-native": "react-native"
|
|
33211
|
-
};
|
|
33212
|
-
const FRAMEWORK_DISPLAY_NAMES = {
|
|
33213
|
-
nextjs: "Next.js",
|
|
33214
|
-
"tanstack-start": "TanStack Start",
|
|
33215
|
-
vite: "Vite",
|
|
33216
|
-
cra: "Create React App",
|
|
33217
|
-
remix: "Remix",
|
|
33218
|
-
gatsby: "Gatsby",
|
|
33219
|
-
expo: "Expo",
|
|
33220
|
-
"react-native": "React Native",
|
|
33221
|
-
preact: "Preact",
|
|
33222
|
-
unknown: "React"
|
|
33223
|
-
};
|
|
33224
|
-
const formatFrameworkName = (framework) => FRAMEWORK_DISPLAY_NAMES[framework];
|
|
33225
|
-
const detectFramework = (dependencies) => {
|
|
33226
|
-
for (const [packageName, frameworkName] of Object.entries(FRAMEWORK_PACKAGES)) if (dependencies[packageName]) return frameworkName;
|
|
33227
|
-
if (dependencies.preact && !dependencies.react) return "preact";
|
|
33228
|
-
return "unknown";
|
|
33229
|
-
};
|
|
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
|
+
});
|
|
33230
33348
|
const UNRESOLVABLE_PROTOCOL_VERSION = /^(?:file|git|github|https?|link|patch|portal|workspace|npm):/i;
|
|
33231
33349
|
const DIST_TAG_VERSION = /^[a-z][a-z0-9._-]*$/i;
|
|
33232
33350
|
const WILDCARD_VERSION = /^[*xX](?:\.[*xX])*$/;
|
|
@@ -33364,10 +33482,76 @@ const isConcreteDependencyVersion = (version) => {
|
|
|
33364
33482
|
const normalizedVersion = normalizeDependencyVersion(version);
|
|
33365
33483
|
return normalizedVersion !== null && /\d/.test(normalizedVersion);
|
|
33366
33484
|
};
|
|
33367
|
-
const
|
|
33368
|
-
if (
|
|
33369
|
-
|
|
33370
|
-
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;
|
|
33371
33555
|
};
|
|
33372
33556
|
const isCatalogReference = (version) => version.startsWith("catalog:");
|
|
33373
33557
|
const extractCatalogName = (version) => {
|
|
@@ -33509,6 +33693,16 @@ const resolveCatalogVersion = (packageJson, packageName, rootDirectory, explicit
|
|
|
33509
33693
|
}
|
|
33510
33694
|
return null;
|
|
33511
33695
|
};
|
|
33696
|
+
const REACT_SECTIONS = [
|
|
33697
|
+
"dependencies",
|
|
33698
|
+
"peerDependencies",
|
|
33699
|
+
"devDependencies"
|
|
33700
|
+
];
|
|
33701
|
+
const TAILWIND_ZOD_SECTIONS = [
|
|
33702
|
+
"dependencies",
|
|
33703
|
+
"devDependencies",
|
|
33704
|
+
"peerDependencies"
|
|
33705
|
+
];
|
|
33512
33706
|
const EMPTY_DEPENDENCY_INFO = {
|
|
33513
33707
|
reactVersion: null,
|
|
33514
33708
|
tailwindVersion: null,
|
|
@@ -33549,6 +33743,10 @@ const extractDependencyInfo = (packageJson) => {
|
|
|
33549
33743
|
framework: detectFramework(allDependencies)
|
|
33550
33744
|
};
|
|
33551
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
|
+
};
|
|
33552
33750
|
const getDependencyDeclaration = ({ packageJson, packageName, sections }) => {
|
|
33553
33751
|
for (const section of sections) {
|
|
33554
33752
|
const version = packageJson[section]?.[packageName];
|
|
@@ -33565,203 +33763,67 @@ const getDependencyDeclaration = ({ packageJson, packageName, sections }) => {
|
|
|
33565
33763
|
version: null
|
|
33566
33764
|
};
|
|
33567
33765
|
};
|
|
33568
|
-
const
|
|
33569
|
-
|
|
33570
|
-
|
|
33571
|
-
|
|
33572
|
-
|
|
33573
|
-
|
|
33574
|
-
|
|
33575
|
-
const
|
|
33576
|
-
|
|
33577
|
-
|
|
33578
|
-
|
|
33579
|
-
|
|
33580
|
-
|
|
33581
|
-
if (!isFile(Path.join(rootDirectory, "nx.json"))) return [];
|
|
33582
|
-
const collected = [];
|
|
33583
|
-
for (const candidate of NX_PROJECT_DISCOVERY_DIRS) {
|
|
33584
|
-
const candidatePath = Path.join(rootDirectory, candidate);
|
|
33585
|
-
if (!isDirectory(candidatePath)) continue;
|
|
33586
|
-
for (const entry of readDirectoryEntries(candidatePath)) {
|
|
33587
|
-
if (!entry.isDirectory()) continue;
|
|
33588
|
-
const projectDirectory = Path.join(candidatePath, entry.name);
|
|
33589
|
-
if (isFile(Path.join(projectDirectory, "project.json")) || isFile(Path.join(projectDirectory, "package.json"))) collected.push(`${candidate}/${entry.name}`);
|
|
33590
|
-
}
|
|
33591
|
-
}
|
|
33592
|
-
return collected;
|
|
33593
|
-
};
|
|
33594
|
-
const parsePnpmWorkspacePatterns = (rootDirectory) => {
|
|
33595
|
-
const workspacePath = Path.join(rootDirectory, "pnpm-workspace.yaml");
|
|
33596
|
-
if (!isFile(workspacePath)) return [];
|
|
33597
|
-
const content = NFS.readFileSync(workspacePath, "utf-8");
|
|
33598
|
-
const patterns = [];
|
|
33599
|
-
let isInsidePackagesBlock = false;
|
|
33600
|
-
for (const line of content.split("\n")) {
|
|
33601
|
-
const trimmed = line.trim();
|
|
33602
|
-
if (trimmed === "packages:") {
|
|
33603
|
-
isInsidePackagesBlock = true;
|
|
33604
|
-
continue;
|
|
33605
|
-
}
|
|
33606
|
-
if (isInsidePackagesBlock && trimmed.startsWith("-")) patterns.push(trimmed.replace(/^-\s*/, "").replace(/["']/g, ""));
|
|
33607
|
-
else if (isInsidePackagesBlock && trimmed.length > 0 && !trimmed.startsWith("#")) isInsidePackagesBlock = false;
|
|
33608
|
-
}
|
|
33609
|
-
return patterns;
|
|
33610
|
-
};
|
|
33611
|
-
const getWorkspacePatterns = (rootDirectory, packageJson) => {
|
|
33612
|
-
const pnpmPatterns = parsePnpmWorkspacePatterns(rootDirectory);
|
|
33613
|
-
if (pnpmPatterns.length > 0) return pnpmPatterns;
|
|
33614
|
-
if (Array.isArray(packageJson.workspaces)) return packageJson.workspaces;
|
|
33615
|
-
if (packageJson.workspaces?.packages) return packageJson.workspaces.packages;
|
|
33616
|
-
const nxPatterns = getNxWorkspaceDirectories(rootDirectory);
|
|
33617
|
-
if (nxPatterns.length > 0) return nxPatterns;
|
|
33618
|
-
return [];
|
|
33619
|
-
};
|
|
33620
|
-
const parseReactMajor = (reactVersion) => {
|
|
33621
|
-
if (typeof reactVersion !== "string") return null;
|
|
33622
|
-
return getLowestDependencyMajor(reactVersion);
|
|
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));
|
|
33623
33779
|
};
|
|
33624
|
-
const
|
|
33625
|
-
|
|
33626
|
-
|
|
33627
|
-
|
|
33628
|
-
|
|
33629
|
-
|
|
33630
|
-
}
|
|
33631
|
-
const wildcardIndex = cleanPattern.indexOf("*");
|
|
33632
|
-
const baseDirectory = Path.join(rootDirectory, cleanPattern.slice(0, wildcardIndex));
|
|
33633
|
-
const suffixAfterWildcard = cleanPattern.slice(wildcardIndex + 1);
|
|
33634
|
-
if (!isDirectory(baseDirectory)) return [];
|
|
33635
|
-
const resolved = [];
|
|
33636
|
-
for (const entry of readDirectoryEntries(baseDirectory)) {
|
|
33637
|
-
const entryPath = Path.join(baseDirectory, entry.name, suffixAfterWildcard);
|
|
33638
|
-
if (isDirectory(entryPath) && isFile(Path.join(entryPath, "package.json"))) resolved.push(entryPath);
|
|
33639
|
-
}
|
|
33640
|
-
return resolved;
|
|
33780
|
+
const getPreactVersion = (packageJson) => {
|
|
33781
|
+
return {
|
|
33782
|
+
...packageJson.peerDependencies,
|
|
33783
|
+
...packageJson.dependencies,
|
|
33784
|
+
...packageJson.devDependencies
|
|
33785
|
+
}.preact ?? null;
|
|
33641
33786
|
};
|
|
33642
|
-
const
|
|
33643
|
-
|
|
33644
|
-
|
|
33645
|
-
|
|
33646
|
-
|
|
33647
|
-
|
|
33648
|
-
|
|
33649
|
-
|
|
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));
|
|
33650
33799
|
};
|
|
33651
|
-
const
|
|
33652
|
-
if (!
|
|
33653
|
-
const
|
|
33654
|
-
const
|
|
33655
|
-
if (
|
|
33656
|
-
|
|
33657
|
-
|
|
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;
|
|
33658
33810
|
};
|
|
33659
|
-
|
|
33660
|
-
|
|
33661
|
-
|
|
33662
|
-
|
|
33663
|
-
|
|
33664
|
-
|
|
33665
|
-
|
|
33666
|
-
|
|
33667
|
-
|
|
33668
|
-
|
|
33669
|
-
|
|
33670
|
-
|
|
33671
|
-
|
|
33672
|
-
|
|
33673
|
-
|
|
33674
|
-
"peerDependencies",
|
|
33675
|
-
"devDependencies"
|
|
33676
|
-
],
|
|
33677
|
-
workspaceDirectory,
|
|
33678
|
-
workspacePackageJson
|
|
33679
|
-
});
|
|
33680
|
-
const tailwindVersion = resolveWorkspaceDependencyVersion({
|
|
33681
|
-
concreteVersion: info.tailwindVersion,
|
|
33682
|
-
packageName: "tailwindcss",
|
|
33683
|
-
rootDirectory,
|
|
33684
|
-
rootPackageJson: packageJson,
|
|
33685
|
-
sections: [
|
|
33686
|
-
"dependencies",
|
|
33687
|
-
"devDependencies",
|
|
33688
|
-
"peerDependencies"
|
|
33689
|
-
],
|
|
33690
|
-
workspaceDirectory,
|
|
33691
|
-
workspacePackageJson
|
|
33692
|
-
});
|
|
33693
|
-
const zodVersion = resolveWorkspaceDependencyVersion({
|
|
33694
|
-
concreteVersion: info.zodVersion,
|
|
33695
|
-
packageName: "zod",
|
|
33696
|
-
rootDirectory,
|
|
33697
|
-
rootPackageJson: packageJson,
|
|
33698
|
-
sections: [
|
|
33699
|
-
"dependencies",
|
|
33700
|
-
"devDependencies",
|
|
33701
|
-
"peerDependencies"
|
|
33702
|
-
],
|
|
33703
|
-
workspaceDirectory,
|
|
33704
|
-
workspacePackageJson
|
|
33705
|
-
});
|
|
33706
|
-
if (reactVersion && shouldReplaceReactVersion(result.reactVersion, reactVersion)) result.reactVersion = reactVersion;
|
|
33707
|
-
if (tailwindVersion && !result.tailwindVersion) result.tailwindVersion = tailwindVersion;
|
|
33708
|
-
if (zodVersion && !result.zodVersion) result.zodVersion = zodVersion;
|
|
33709
|
-
if (info.framework !== "unknown" && result.framework === "unknown") result.framework = info.framework;
|
|
33710
|
-
const resultReactMajor = parseReactMajor(result.reactVersion);
|
|
33711
|
-
if (result.reactVersion && result.tailwindVersion && result.framework !== "unknown" && resultReactMajor !== null && resultReactMajor <= 17) return result;
|
|
33712
|
-
}
|
|
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;
|
|
33713
33826
|
}
|
|
33714
|
-
return result;
|
|
33715
|
-
};
|
|
33716
|
-
const findDependencyInfoFromMonorepoRoot = (directory) => {
|
|
33717
|
-
const monorepoRoot = findMonorepoRoot(directory);
|
|
33718
|
-
if (!monorepoRoot) return EMPTY_DEPENDENCY_INFO;
|
|
33719
|
-
const monorepoPackageJsonPath = Path.join(monorepoRoot, "package.json");
|
|
33720
|
-
if (!isFile(monorepoPackageJsonPath)) return EMPTY_DEPENDENCY_INFO;
|
|
33721
|
-
const rootPackageJson = readPackageJson(monorepoPackageJsonPath);
|
|
33722
|
-
const rootInfo = extractDependencyInfo(rootPackageJson);
|
|
33723
|
-
const leafPackageJsonPath = Path.join(directory, "package.json");
|
|
33724
|
-
const leafPackageJson = isFile(leafPackageJsonPath) ? readPackageJson(leafPackageJsonPath) : null;
|
|
33725
|
-
const leafReactDeclaration = leafPackageJson ? getDependencyDeclaration({
|
|
33726
|
-
packageJson: leafPackageJson,
|
|
33727
|
-
packageName: "react",
|
|
33728
|
-
sections: [
|
|
33729
|
-
"dependencies",
|
|
33730
|
-
"peerDependencies",
|
|
33731
|
-
"devDependencies"
|
|
33732
|
-
]
|
|
33733
|
-
}) : null;
|
|
33734
|
-
const leafTailwindDeclaration = leafPackageJson ? getDependencyDeclaration({
|
|
33735
|
-
packageJson: leafPackageJson,
|
|
33736
|
-
packageName: "tailwindcss",
|
|
33737
|
-
sections: [
|
|
33738
|
-
"dependencies",
|
|
33739
|
-
"devDependencies",
|
|
33740
|
-
"peerDependencies"
|
|
33741
|
-
]
|
|
33742
|
-
}) : null;
|
|
33743
|
-
const leafZodDeclaration = leafPackageJson ? getDependencyDeclaration({
|
|
33744
|
-
packageJson: leafPackageJson,
|
|
33745
|
-
packageName: "zod",
|
|
33746
|
-
sections: [
|
|
33747
|
-
"dependencies",
|
|
33748
|
-
"devDependencies",
|
|
33749
|
-
"peerDependencies"
|
|
33750
|
-
]
|
|
33751
|
-
}) : null;
|
|
33752
|
-
const shouldUseReactFallback = !leafReactDeclaration?.hasDeclaration;
|
|
33753
|
-
const shouldUseTailwindFallback = leafTailwindDeclaration?.hasDeclaration ?? true;
|
|
33754
|
-
const shouldUseZodFallback = leafZodDeclaration?.hasDeclaration ?? true;
|
|
33755
|
-
const reactCatalogVersion = shouldUseReactFallback ? resolveCatalogVersion(rootPackageJson, "react", monorepoRoot, leafReactDeclaration?.catalogReference) : null;
|
|
33756
|
-
const tailwindCatalogVersion = shouldUseTailwindFallback ? resolveCatalogVersion(rootPackageJson, "tailwindcss", monorepoRoot, leafTailwindDeclaration?.catalogReference) : null;
|
|
33757
|
-
const zodCatalogVersion = shouldUseZodFallback ? resolveCatalogVersion(rootPackageJson, "zod", monorepoRoot, leafZodDeclaration?.catalogReference) : null;
|
|
33758
|
-
const workspaceInfo = findReactInWorkspaces(monorepoRoot, rootPackageJson);
|
|
33759
|
-
return {
|
|
33760
|
-
reactVersion: shouldUseReactFallback ? reactCatalogVersion ?? rootInfo.reactVersion ?? workspaceInfo.reactVersion : rootInfo.reactVersion ?? workspaceInfo.reactVersion,
|
|
33761
|
-
tailwindVersion: shouldUseTailwindFallback ? tailwindCatalogVersion ?? rootInfo.tailwindVersion ?? workspaceInfo.tailwindVersion : null,
|
|
33762
|
-
zodVersion: shouldUseZodFallback ? zodCatalogVersion ?? rootInfo.zodVersion ?? workspaceInfo.zodVersion : null,
|
|
33763
|
-
framework: rootInfo.framework !== "unknown" ? rootInfo.framework : workspaceInfo.framework
|
|
33764
|
-
};
|
|
33765
33827
|
};
|
|
33766
33828
|
/**
|
|
33767
33829
|
* Walk up from `startDirectory` to the nearest ancestor that owns a
|
|
@@ -33786,69 +33848,6 @@ const findNearestAncestorPackageJson = (startDirectory) => {
|
|
|
33786
33848
|
}
|
|
33787
33849
|
return null;
|
|
33788
33850
|
};
|
|
33789
|
-
/**
|
|
33790
|
-
* Last-resort React detection: locate React the way Node's `node_modules`
|
|
33791
|
-
* resolution would from `directory` — check each `node_modules/react` up the
|
|
33792
|
-
* tree — but bounded to the enclosing repo so a globally installed React can't
|
|
33793
|
-
* masquerade as the project's version. Stops at (and including) the nearest
|
|
33794
|
-
* project boundary — the git root or a monorepo root — and returns the first
|
|
33795
|
-
* installed `version`.
|
|
33796
|
-
*
|
|
33797
|
-
* Makes "React is installed and importable" ⇒ "React is detected" an invariant
|
|
33798
|
-
* for packages whose only React declaration is a version-less spec
|
|
33799
|
-
* (`workspace:*`, `*`, a dist-tag) or where React lives solely in a hoisted
|
|
33800
|
-
* `node_modules` the declaration walks never reach — the profile of a component
|
|
33801
|
-
* package inside a monorepo. Matching the `node_modules/react` entry rather than
|
|
33802
|
-
* its realpath means a project whose `node_modules` is symlinked elsewhere (a
|
|
33803
|
-
* Docker volume, a shared store) still counts, while a global install outside
|
|
33804
|
-
* the repo does not.
|
|
33805
|
-
*
|
|
33806
|
-
* Returns `null` when React isn't installed within the repo or its package.json
|
|
33807
|
-
* carries no version string. A tree with no boundary marker at all bounds the
|
|
33808
|
-
* search to `directory`, so a dependency hoisted above the scanned package there
|
|
33809
|
-
* is conservatively not adopted.
|
|
33810
|
-
*/
|
|
33811
|
-
/**
|
|
33812
|
-
* The nearest enclosing project boundary (git root or monorepo root), or
|
|
33813
|
-
* `directory` itself when the scan target sits outside any repo. Bounds the
|
|
33814
|
-
* node_modules walk so a React hoisted anywhere inside the repo counts, but a
|
|
33815
|
-
* React in an ancestor above the repo — a home-directory or global
|
|
33816
|
-
* `node_modules` — can't leak in.
|
|
33817
|
-
*/
|
|
33818
|
-
const findContainmentRoot = (directory) => {
|
|
33819
|
-
for (const ancestorDirectory of ancestorDirectories(directory, { includeStart: true })) if (isProjectBoundary(ancestorDirectory)) return ancestorDirectory;
|
|
33820
|
-
return directory;
|
|
33821
|
-
};
|
|
33822
|
-
const resolveInstalledReactVersion = (directory) => {
|
|
33823
|
-
const containmentRoot = findContainmentRoot(directory);
|
|
33824
|
-
for (const ancestorDirectory of ancestorDirectories(directory, { includeStart: true })) {
|
|
33825
|
-
const reactPackageJsonPath = Path.join(ancestorDirectory, "node_modules", "react", "package.json");
|
|
33826
|
-
if (isFile(reactPackageJsonPath)) {
|
|
33827
|
-
const installedVersion = readPackageJson(reactPackageJsonPath).version;
|
|
33828
|
-
return typeof installedVersion === "string" ? installedVersion : null;
|
|
33829
|
-
}
|
|
33830
|
-
if (ancestorDirectory === containmentRoot) return null;
|
|
33831
|
-
}
|
|
33832
|
-
return null;
|
|
33833
|
-
};
|
|
33834
|
-
const findInWorkspacePackageJsons = (rootDirectory, rootPackageJson, select) => {
|
|
33835
|
-
const rootValue = select(rootPackageJson);
|
|
33836
|
-
if (rootValue !== null) return rootValue;
|
|
33837
|
-
const patterns = getWorkspacePatterns(rootDirectory, rootPackageJson);
|
|
33838
|
-
if (patterns.length === 0) return null;
|
|
33839
|
-
const visitedDirectories = /* @__PURE__ */ new Set();
|
|
33840
|
-
for (const pattern of patterns) {
|
|
33841
|
-
const directories = [...resolveWorkspaceDirectories(rootDirectory, pattern)].sort();
|
|
33842
|
-
for (const workspaceDirectory of directories) {
|
|
33843
|
-
if (visitedDirectories.has(workspaceDirectory)) continue;
|
|
33844
|
-
visitedDirectories.add(workspaceDirectory);
|
|
33845
|
-
const value = select(readPackageJson(Path.join(workspaceDirectory, "package.json")));
|
|
33846
|
-
if (value !== null) return value;
|
|
33847
|
-
}
|
|
33848
|
-
}
|
|
33849
|
-
return null;
|
|
33850
|
-
};
|
|
33851
|
-
const someWorkspacePackageJson = (rootDirectory, rootPackageJson, predicate) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => predicate(packageJson) ? true : null) !== null;
|
|
33852
33851
|
const NAMES = new Set([
|
|
33853
33852
|
"react-native",
|
|
33854
33853
|
"react-native-tvos",
|
|
@@ -33881,47 +33880,7 @@ const isPackageJsonReactNativeAware = (packageJson) => {
|
|
|
33881
33880
|
if (containsAnyReactNativeDependency(packageJson.optionalDependencies)) return true;
|
|
33882
33881
|
return false;
|
|
33883
33882
|
};
|
|
33884
|
-
const
|
|
33885
|
-
const getDependencySpec = (packageJson, packageName) => {
|
|
33886
|
-
const spec = packageJson.dependencies?.[packageName] ?? packageJson.devDependencies?.[packageName] ?? packageJson.peerDependencies?.[packageName] ?? packageJson.optionalDependencies?.[packageName];
|
|
33887
|
-
return typeof spec === "string" ? spec : null;
|
|
33888
|
-
};
|
|
33889
|
-
const findExpoVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, "expo"));
|
|
33890
|
-
const SHOPIFY_FLASH_LIST_PACKAGE_NAME = "@shopify/flash-list";
|
|
33891
|
-
const findShopifyFlashListVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, SHOPIFY_FLASH_LIST_PACKAGE_NAME));
|
|
33892
|
-
const resolveCatalogBackedDependencyVersion = ({ rootDirectory, rootPackageJson, packageName, version }) => {
|
|
33893
|
-
if (version === null || !isCatalogReference(version)) return version;
|
|
33894
|
-
const catalogName = extractCatalogName(version);
|
|
33895
|
-
const resolvedLocalVersion = resolveCatalogVersion(rootPackageJson, packageName, rootDirectory, catalogName);
|
|
33896
|
-
if (resolvedLocalVersion) return resolvedLocalVersion;
|
|
33897
|
-
const monorepoRoot = findMonorepoRoot(rootDirectory);
|
|
33898
|
-
if (!monorepoRoot) return version;
|
|
33899
|
-
const monorepoPackageJsonPath = path.join(monorepoRoot, "package.json");
|
|
33900
|
-
if (!isFile(monorepoPackageJsonPath)) return version;
|
|
33901
|
-
return resolveCatalogVersion(readPackageJson(monorepoPackageJsonPath), packageName, monorepoRoot, catalogName) ?? version;
|
|
33902
|
-
};
|
|
33903
|
-
const findNextjsVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, "next"));
|
|
33904
|
-
const getPreactVersion = (packageJson) => {
|
|
33905
|
-
return {
|
|
33906
|
-
...packageJson.peerDependencies,
|
|
33907
|
-
...packageJson.dependencies,
|
|
33908
|
-
...packageJson.devDependencies
|
|
33909
|
-
}.preact ?? null;
|
|
33910
|
-
};
|
|
33911
|
-
const TANSTACK_QUERY_PACKAGES = new Set([
|
|
33912
|
-
"@tanstack/react-query",
|
|
33913
|
-
"@tanstack/query-core",
|
|
33914
|
-
"react-query"
|
|
33915
|
-
]);
|
|
33916
|
-
const hasTanStackQuery = (packageJson) => {
|
|
33917
|
-
const allDependencies = {
|
|
33918
|
-
...packageJson.peerDependencies,
|
|
33919
|
-
...packageJson.dependencies,
|
|
33920
|
-
...packageJson.devDependencies
|
|
33921
|
-
};
|
|
33922
|
-
return Object.keys(allDependencies).some((packageName) => TANSTACK_QUERY_PACKAGES.has(packageName));
|
|
33923
|
-
};
|
|
33924
|
-
const REANIMATED_DEPENDENCY_NAME = "react-native-reanimated";
|
|
33883
|
+
const REANIMATED_DEPENDENCY_NAME$1 = "react-native-reanimated";
|
|
33925
33884
|
const isPackageJsonReanimatedAware = (packageJson) => {
|
|
33926
33885
|
const allDependencies = {
|
|
33927
33886
|
...packageJson.peerDependencies,
|
|
@@ -33929,166 +33888,70 @@ const isPackageJsonReanimatedAware = (packageJson) => {
|
|
|
33929
33888
|
...packageJson.devDependencies,
|
|
33930
33889
|
...packageJson.optionalDependencies
|
|
33931
33890
|
};
|
|
33932
|
-
return Object.hasOwn(allDependencies, REANIMATED_DEPENDENCY_NAME);
|
|
33933
|
-
};
|
|
33934
|
-
const parseZodMajor = (zodVersion) => {
|
|
33935
|
-
if (typeof zodVersion !== "string") return null;
|
|
33936
|
-
return getLowestDependencyMajor(zodVersion);
|
|
33937
|
-
};
|
|
33938
|
-
const hasUpperBoundOnlyPeerRange = (range) => {
|
|
33939
|
-
if (typeof range !== "string") return false;
|
|
33940
|
-
const normalizedRange = normalizeDependencyVersion(range);
|
|
33941
|
-
if (normalizedRange === null) return false;
|
|
33942
|
-
return splitDependencyVersionBranches(normalizedRange).some((branch) => {
|
|
33943
|
-
const normalizedBranch = normalizeDependencyVersion(branch);
|
|
33944
|
-
return normalizedBranch !== null && getBranchLowestMajor(normalizedBranch) === null && hasUpperBoundComparator(normalizedBranch);
|
|
33945
|
-
});
|
|
33946
|
-
};
|
|
33947
|
-
const peerRangeMinMajor = (range) => {
|
|
33948
|
-
if (typeof range !== "string") return null;
|
|
33949
|
-
return getLowestDependencyMajor(range);
|
|
33950
|
-
};
|
|
33951
|
-
const resolveEffectiveReactMajor = (reactVersion, packageJson) => {
|
|
33952
|
-
const installedReactMajor = parseReactMajor(reactVersion);
|
|
33953
|
-
const peerReactRange = packageJson.peerDependencies?.react;
|
|
33954
|
-
if (typeof peerReactRange !== "string") return installedReactMajor;
|
|
33955
|
-
const peerFloor = peerRangeMinMajor(peerReactRange);
|
|
33956
|
-
if (peerFloor === null) return hasUpperBoundOnlyPeerRange(peerReactRange) ? null : installedReactMajor;
|
|
33957
|
-
return installedReactMajor !== null ? Math.min(installedReactMajor, peerFloor) : peerFloor;
|
|
33958
|
-
};
|
|
33959
|
-
const TSCONFIG_FILENAME = "tsconfig.json";
|
|
33960
|
-
const isRelativeExtendsValue = (extendsValue) => extendsValue.startsWith("./") || extendsValue.startsWith("../") || Path.isAbsolute(extendsValue);
|
|
33961
|
-
const ensureJsonExtension = (filePath) => Path.extname(filePath) === "" ? `${filePath}.json` : filePath;
|
|
33962
|
-
const resolvePackageExtendsPath = (extendsValue, fromConfigDirectory) => {
|
|
33963
|
-
const requireFromConfig = createRequire(Path.join(fromConfigDirectory, "tsconfig.json"));
|
|
33964
|
-
const candidates = [
|
|
33965
|
-
extendsValue,
|
|
33966
|
-
ensureJsonExtension(extendsValue),
|
|
33967
|
-
`${extendsValue.replace(/\/$/, "")}/tsconfig.json`
|
|
33968
|
-
];
|
|
33969
|
-
for (const candidate of candidates) try {
|
|
33970
|
-
return requireFromConfig.resolve(candidate);
|
|
33971
|
-
} catch {
|
|
33972
|
-
continue;
|
|
33973
|
-
}
|
|
33974
|
-
return null;
|
|
33975
|
-
};
|
|
33976
|
-
const resolveExtendsPath = (extendsValue, fromConfigDirectory) => {
|
|
33977
|
-
if (isRelativeExtendsValue(extendsValue)) return ensureJsonExtension(Path.resolve(fromConfigDirectory, extendsValue));
|
|
33978
|
-
return resolvePackageExtendsPath(extendsValue, fromConfigDirectory);
|
|
33979
|
-
};
|
|
33980
|
-
const normalizeCompilerOptions = (compilerOptions) => {
|
|
33981
|
-
if (!isPlainObject(compilerOptions)) return { hasExplicitLib: false };
|
|
33982
|
-
const target = typeof compilerOptions.target === "string" ? compilerOptions.target : void 0;
|
|
33983
|
-
const hasExplicitLib = Object.hasOwn(compilerOptions, "lib");
|
|
33984
|
-
return {
|
|
33985
|
-
target,
|
|
33986
|
-
lib: Array.isArray(compilerOptions.lib) ? compilerOptions.lib.filter((entry) => typeof entry === "string") : void 0,
|
|
33987
|
-
hasExplicitLib
|
|
33988
|
-
};
|
|
33891
|
+
return Object.hasOwn(allDependencies, REANIMATED_DEPENDENCY_NAME$1);
|
|
33989
33892
|
};
|
|
33990
|
-
const
|
|
33991
|
-
|
|
33992
|
-
|
|
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 [];
|
|
33993
33901
|
};
|
|
33994
|
-
const
|
|
33995
|
-
|
|
33996
|
-
|
|
33997
|
-
|
|
33998
|
-
|
|
33999
|
-
|
|
33902
|
+
const parsePnpmWorkspacePatterns = (rootDirectory) => {
|
|
33903
|
+
const workspacePath = Path.join(rootDirectory, "pnpm-workspace.yaml");
|
|
33904
|
+
if (!isFile(workspacePath)) return [];
|
|
33905
|
+
const content = NFS.readFileSync(workspacePath, "utf-8");
|
|
33906
|
+
const patterns = [];
|
|
33907
|
+
let isInsidePackagesBlock = false;
|
|
33908
|
+
for (const line of content.split("\n")) {
|
|
33909
|
+
const trimmed = line.trim();
|
|
33910
|
+
if (trimmed === "packages:") {
|
|
33911
|
+
isInsidePackagesBlock = true;
|
|
33912
|
+
continue;
|
|
33913
|
+
}
|
|
33914
|
+
if (isInsidePackagesBlock && trimmed.startsWith("-")) patterns.push(trimmed.replace(/^-\s*/, "").replace(/["']/g, ""));
|
|
33915
|
+
else if (isInsidePackagesBlock && trimmed.length > 0 && !trimmed.startsWith("#")) isInsidePackagesBlock = false;
|
|
34000
33916
|
}
|
|
34001
|
-
|
|
34002
|
-
if (!isPlainObject(parsed.config)) return null;
|
|
34003
|
-
return {
|
|
34004
|
-
extends: typeof parsed.config.extends === "string" ? parsed.config.extends : void 0,
|
|
34005
|
-
referencePaths: normalizeReferencePaths(parsed.config.references),
|
|
34006
|
-
compilerOptions: normalizeCompilerOptions(parsed.config.compilerOptions)
|
|
34007
|
-
};
|
|
34008
|
-
};
|
|
34009
|
-
const mergeCompilerOptions = (inherited, current) => {
|
|
34010
|
-
const target = current.target ?? inherited?.target;
|
|
34011
|
-
const hasExplicitLib = current.hasExplicitLib || Boolean(inherited?.hasExplicitLib);
|
|
34012
|
-
return {
|
|
34013
|
-
target,
|
|
34014
|
-
lib: current.hasExplicitLib ? current.lib : inherited?.lib,
|
|
34015
|
-
hasExplicitLib
|
|
34016
|
-
};
|
|
33917
|
+
return patterns;
|
|
34017
33918
|
};
|
|
34018
|
-
const
|
|
34019
|
-
|
|
34020
|
-
|
|
34021
|
-
|
|
34022
|
-
|
|
34023
|
-
|
|
34024
|
-
|
|
34025
|
-
|
|
34026
|
-
|
|
34027
|
-
|
|
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
|
+
}
|
|
34028
33935
|
}
|
|
34029
|
-
return
|
|
34030
|
-
};
|
|
34031
|
-
const targetYearIsPreES2023 = (target) => {
|
|
34032
|
-
const year = ES_TARGET_YEAR_BY_NAME[target.toLowerCase()];
|
|
34033
|
-
return year !== void 0 && year < 2023;
|
|
34034
|
-
};
|
|
34035
|
-
const libEntryIncludesES2023Array = (entry) => {
|
|
34036
|
-
const normalizedEntry = entry.toLowerCase();
|
|
34037
|
-
if (normalizedEntry === "esnext" || normalizedEntry === "esnext.array") return true;
|
|
34038
|
-
const esYearMatch = /^es(\d{4})(?:\.(.+))?$/.exec(normalizedEntry);
|
|
34039
|
-
if (!esYearMatch) return false;
|
|
34040
|
-
if (Number(esYearMatch[1]) < 2023) return false;
|
|
34041
|
-
const component = esYearMatch[2];
|
|
34042
|
-
return component === void 0 || component === "array";
|
|
34043
|
-
};
|
|
34044
|
-
const libIncludesES2023 = (lib) => lib.some(libEntryIncludesES2023Array);
|
|
34045
|
-
const compilerOptionsArePreES2023 = (compilerOptions) => {
|
|
34046
|
-
if (compilerOptions.target && targetYearIsPreES2023(compilerOptions.target)) return true;
|
|
34047
|
-
if (compilerOptions.hasExplicitLib) return !libIncludesES2023(compilerOptions.lib ?? []);
|
|
34048
|
-
return false;
|
|
33936
|
+
return collected;
|
|
34049
33937
|
};
|
|
34050
|
-
const
|
|
34051
|
-
const
|
|
34052
|
-
if (
|
|
34053
|
-
|
|
34054
|
-
|
|
34055
|
-
|
|
34056
|
-
const tsConfig = readTsConfig(tsConfigPath);
|
|
34057
|
-
if (!tsConfig) return false;
|
|
34058
|
-
const nextVisitedConfigPaths = new Set(visitedConfigPaths);
|
|
34059
|
-
nextVisitedConfigPaths.add(tsConfigPath);
|
|
34060
|
-
const configDirectory = Path.dirname(tsConfigPath);
|
|
34061
|
-
return tsConfig.referencePaths.some((referencePath) => {
|
|
34062
|
-
const resolvedReferencePath = Path.resolve(configDirectory, referencePath);
|
|
34063
|
-
const referencedConfigPath = isFile(resolvedReferencePath) ? resolvedReferencePath : Path.join(resolvedReferencePath, TSCONFIG_FILENAME);
|
|
34064
|
-
return isFile(referencedConfigPath) && detectPreES2023FromConfig(referencedConfigPath, nextVisitedConfigPaths);
|
|
34065
|
-
});
|
|
33938
|
+
const resolveWorkspaceDirectories = (rootDirectory, pattern) => {
|
|
33939
|
+
const cleanPattern = pattern.replace(/["']/g, "").replace(/\/\*\*$/, "/*");
|
|
33940
|
+
if (!cleanPattern.includes("*")) {
|
|
33941
|
+
const directoryPath = Path.join(rootDirectory, cleanPattern);
|
|
33942
|
+
if (isDirectory(directoryPath) && isFile(Path.join(directoryPath, "package.json"))) return [directoryPath];
|
|
33943
|
+
return [];
|
|
34066
33944
|
}
|
|
34067
|
-
|
|
34068
|
-
|
|
34069
|
-
const
|
|
34070
|
-
|
|
34071
|
-
const
|
|
34072
|
-
|
|
34073
|
-
|
|
34074
|
-
|
|
34075
|
-
if (isFile(fallbackPath)) return detectPreES2023FromConfig(fallbackPath);
|
|
33945
|
+
const wildcardIndex = cleanPattern.indexOf("*");
|
|
33946
|
+
const baseDirectory = Path.join(rootDirectory, cleanPattern.slice(0, wildcardIndex));
|
|
33947
|
+
const suffixAfterWildcard = cleanPattern.slice(wildcardIndex + 1);
|
|
33948
|
+
if (!isDirectory(baseDirectory)) return [];
|
|
33949
|
+
const resolved = [];
|
|
33950
|
+
for (const entry of readDirectoryEntries(baseDirectory)) {
|
|
33951
|
+
const entryPath = Path.join(baseDirectory, entry.name, suffixAfterWildcard);
|
|
33952
|
+
if (isDirectory(entryPath) && isFile(Path.join(entryPath, "package.json"))) resolved.push(entryPath);
|
|
34076
33953
|
}
|
|
34077
|
-
return
|
|
34078
|
-
};
|
|
34079
|
-
const REACT_DEPENDENCY_NAMES = new Set([
|
|
34080
|
-
"react",
|
|
34081
|
-
"react-native",
|
|
34082
|
-
"next",
|
|
34083
|
-
"preact"
|
|
34084
|
-
]);
|
|
34085
|
-
const hasReactDependency = (packageJson) => {
|
|
34086
|
-
const allDependencies = {
|
|
34087
|
-
...packageJson.peerDependencies,
|
|
34088
|
-
...packageJson.dependencies,
|
|
34089
|
-
...packageJson.devDependencies
|
|
34090
|
-
};
|
|
34091
|
-
return Object.keys(allDependencies).some((packageName) => REACT_DEPENDENCY_NAMES.has(packageName));
|
|
33954
|
+
return resolved;
|
|
34092
33955
|
};
|
|
34093
33956
|
const listWorkspacePackages = (rootDirectory) => {
|
|
34094
33957
|
const packageJsonPath = Path.join(rootDirectory, "package.json");
|
|
@@ -34120,6 +33983,208 @@ const listWorkspacePackages = (rootDirectory) => {
|
|
|
34120
33983
|
}
|
|
34121
33984
|
return packages;
|
|
34122
33985
|
};
|
|
33986
|
+
const REANIMATED_DEPENDENCY_NAME = "react-native-reanimated";
|
|
33987
|
+
const SHOPIFY_FLASH_LIST_PACKAGE_NAME = "@shopify/flash-list";
|
|
33988
|
+
const resolveWorkspaceDependencyVersion = ({ concreteVersion, packageName, rootDirectory, rootPackageJson, sections, workspaceDirectory, workspacePackageJson }) => {
|
|
33989
|
+
const dependencyDeclaration = getDependencyDeclaration({
|
|
33990
|
+
packageJson: workspacePackageJson,
|
|
33991
|
+
packageName,
|
|
33992
|
+
sections
|
|
33993
|
+
});
|
|
33994
|
+
if (!dependencyDeclaration.hasDeclaration) return null;
|
|
33995
|
+
return concreteVersion ?? resolveCatalogVersion(workspacePackageJson, packageName, workspaceDirectory, dependencyDeclaration.catalogReference) ?? resolveCatalogVersion(rootPackageJson, packageName, rootDirectory, dependencyDeclaration.catalogReference);
|
|
33996
|
+
};
|
|
33997
|
+
const shouldReplaceReactVersion = (currentVersion, nextVersion) => {
|
|
33998
|
+
if (!currentVersion) return true;
|
|
33999
|
+
const currentMajor = parseReactMajor(currentVersion);
|
|
34000
|
+
const nextMajor = parseReactMajor(nextVersion);
|
|
34001
|
+
if (currentMajor === null) return nextMajor !== null;
|
|
34002
|
+
if (nextMajor === null) return false;
|
|
34003
|
+
return nextMajor < currentMajor;
|
|
34004
|
+
};
|
|
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();
|
|
34061
|
+
for (const workspaceDirectory of directories) {
|
|
34062
|
+
if (visitedDirectories.has(workspaceDirectory)) continue;
|
|
34063
|
+
visitedDirectories.add(workspaceDirectory);
|
|
34064
|
+
const workspacePackageJson = readPackageJson(Path.join(workspaceDirectory, "package.json"));
|
|
34065
|
+
evaluateManifestFacts(facts, workspacePackageJson, workspaceDirectory);
|
|
34066
|
+
const info = extractDependencyInfo(workspacePackageJson);
|
|
34067
|
+
if (info.framework !== "unknown" && frameworkMergeRank(info.framework) < frameworkMergeRank(facts.framework)) facts.framework = info.framework;
|
|
34068
|
+
if (isReactGroupSettled) continue;
|
|
34069
|
+
const reactVersion = resolveWorkspaceDependencyVersion({
|
|
34070
|
+
concreteVersion: info.reactVersion,
|
|
34071
|
+
packageName: "react",
|
|
34072
|
+
rootDirectory,
|
|
34073
|
+
rootPackageJson,
|
|
34074
|
+
sections: REACT_SECTIONS,
|
|
34075
|
+
workspaceDirectory,
|
|
34076
|
+
workspacePackageJson
|
|
34077
|
+
});
|
|
34078
|
+
const tailwindVersion = resolveWorkspaceDependencyVersion({
|
|
34079
|
+
concreteVersion: info.tailwindVersion,
|
|
34080
|
+
packageName: "tailwindcss",
|
|
34081
|
+
rootDirectory,
|
|
34082
|
+
rootPackageJson,
|
|
34083
|
+
sections: TAILWIND_ZOD_SECTIONS,
|
|
34084
|
+
workspaceDirectory,
|
|
34085
|
+
workspacePackageJson
|
|
34086
|
+
});
|
|
34087
|
+
const zodVersion = resolveWorkspaceDependencyVersion({
|
|
34088
|
+
concreteVersion: info.zodVersion,
|
|
34089
|
+
packageName: "zod",
|
|
34090
|
+
rootDirectory,
|
|
34091
|
+
rootPackageJson,
|
|
34092
|
+
sections: TAILWIND_ZOD_SECTIONS,
|
|
34093
|
+
workspaceDirectory,
|
|
34094
|
+
workspacePackageJson
|
|
34095
|
+
});
|
|
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);
|
|
34101
|
+
}
|
|
34102
|
+
}
|
|
34103
|
+
return facts;
|
|
34104
|
+
};
|
|
34105
|
+
const findDependencyInfoFromMonorepoRoot = (directory) => {
|
|
34106
|
+
const monorepoRoot = findMonorepoRoot(directory);
|
|
34107
|
+
if (!monorepoRoot) return EMPTY_DEPENDENCY_INFO;
|
|
34108
|
+
const monorepoPackageJsonPath = Path.join(monorepoRoot, "package.json");
|
|
34109
|
+
if (!isFile(monorepoPackageJsonPath)) return EMPTY_DEPENDENCY_INFO;
|
|
34110
|
+
const rootPackageJson = readPackageJson(monorepoPackageJsonPath);
|
|
34111
|
+
const rootInfo = extractDependencyInfo(rootPackageJson);
|
|
34112
|
+
const leafPackageJsonPath = Path.join(directory, "package.json");
|
|
34113
|
+
const leafPackageJson = isFile(leafPackageJsonPath) ? readPackageJson(leafPackageJsonPath) : null;
|
|
34114
|
+
const leafReactDeclaration = leafPackageJson ? getDependencyDeclaration({
|
|
34115
|
+
packageJson: leafPackageJson,
|
|
34116
|
+
packageName: "react",
|
|
34117
|
+
sections: REACT_SECTIONS
|
|
34118
|
+
}) : null;
|
|
34119
|
+
const leafTailwindDeclaration = leafPackageJson ? getDependencyDeclaration({
|
|
34120
|
+
packageJson: leafPackageJson,
|
|
34121
|
+
packageName: "tailwindcss",
|
|
34122
|
+
sections: TAILWIND_ZOD_SECTIONS
|
|
34123
|
+
}) : null;
|
|
34124
|
+
const leafZodDeclaration = leafPackageJson ? getDependencyDeclaration({
|
|
34125
|
+
packageJson: leafPackageJson,
|
|
34126
|
+
packageName: "zod",
|
|
34127
|
+
sections: TAILWIND_ZOD_SECTIONS
|
|
34128
|
+
}) : null;
|
|
34129
|
+
const shouldUseReactFallback = !leafReactDeclaration?.hasDeclaration;
|
|
34130
|
+
const shouldUseTailwindFallback = leafTailwindDeclaration?.hasDeclaration ?? true;
|
|
34131
|
+
const shouldUseZodFallback = leafZodDeclaration?.hasDeclaration ?? true;
|
|
34132
|
+
const reactCatalogVersion = shouldUseReactFallback ? resolveCatalogVersion(rootPackageJson, "react", monorepoRoot, leafReactDeclaration?.catalogReference) : null;
|
|
34133
|
+
const tailwindCatalogVersion = shouldUseTailwindFallback ? resolveCatalogVersion(rootPackageJson, "tailwindcss", monorepoRoot, leafTailwindDeclaration?.catalogReference) : null;
|
|
34134
|
+
const zodCatalogVersion = shouldUseZodFallback ? resolveCatalogVersion(rootPackageJson, "zod", monorepoRoot, leafZodDeclaration?.catalogReference) : null;
|
|
34135
|
+
const workspaceFacts = collectWorkspaceFacts(monorepoRoot, rootPackageJson, { collectReactGroup: true });
|
|
34136
|
+
return {
|
|
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
|
|
34141
|
+
};
|
|
34142
|
+
};
|
|
34143
|
+
/**
|
|
34144
|
+
* Last-resort React detection: locate React the way Node's `node_modules`
|
|
34145
|
+
* resolution would from `directory` — check each `node_modules/react` up the
|
|
34146
|
+
* tree — but bounded to the enclosing repo so a globally installed React can't
|
|
34147
|
+
* masquerade as the project's version. Stops at (and including) the nearest
|
|
34148
|
+
* project boundary — the git root or a monorepo root — and returns the first
|
|
34149
|
+
* installed `version`.
|
|
34150
|
+
*
|
|
34151
|
+
* Makes "React is installed and importable" ⇒ "React is detected" an invariant
|
|
34152
|
+
* for packages whose only React declaration is a version-less spec
|
|
34153
|
+
* (`workspace:*`, `*`, a dist-tag) or where React lives solely in a hoisted
|
|
34154
|
+
* `node_modules` the declaration walks never reach — the profile of a component
|
|
34155
|
+
* package inside a monorepo. Matching the `node_modules/react` entry rather than
|
|
34156
|
+
* its realpath means a project whose `node_modules` is symlinked elsewhere (a
|
|
34157
|
+
* Docker volume, a shared store) still counts, while a global install outside
|
|
34158
|
+
* the repo does not.
|
|
34159
|
+
*
|
|
34160
|
+
* Returns `null` when React isn't installed within the repo or its package.json
|
|
34161
|
+
* carries no version string. A tree with no boundary marker at all bounds the
|
|
34162
|
+
* search to `directory`, so a dependency hoisted above the scanned package there
|
|
34163
|
+
* is conservatively not adopted.
|
|
34164
|
+
*/
|
|
34165
|
+
/**
|
|
34166
|
+
* The nearest enclosing project boundary (git root or monorepo root), or
|
|
34167
|
+
* `directory` itself when the scan target sits outside any repo. Bounds the
|
|
34168
|
+
* node_modules walk so a React hoisted anywhere inside the repo counts, but a
|
|
34169
|
+
* React in an ancestor above the repo — a home-directory or global
|
|
34170
|
+
* `node_modules` — can't leak in.
|
|
34171
|
+
*/
|
|
34172
|
+
const findContainmentRoot = (directory) => {
|
|
34173
|
+
for (const ancestorDirectory of ancestorDirectories(directory, { includeStart: true })) if (isProjectBoundary(ancestorDirectory)) return ancestorDirectory;
|
|
34174
|
+
return directory;
|
|
34175
|
+
};
|
|
34176
|
+
const resolveInstalledReactVersion = (directory) => {
|
|
34177
|
+
const containmentRoot = findContainmentRoot(directory);
|
|
34178
|
+
for (const ancestorDirectory of ancestorDirectories(directory, { includeStart: true })) {
|
|
34179
|
+
const reactPackageJsonPath = Path.join(ancestorDirectory, "node_modules", "react", "package.json");
|
|
34180
|
+
if (isFile(reactPackageJsonPath)) {
|
|
34181
|
+
const installedVersion = readPackageJson(reactPackageJsonPath).version;
|
|
34182
|
+
return typeof installedVersion === "string" ? installedVersion : null;
|
|
34183
|
+
}
|
|
34184
|
+
if (ancestorDirectory === containmentRoot) return null;
|
|
34185
|
+
}
|
|
34186
|
+
return null;
|
|
34187
|
+
};
|
|
34123
34188
|
const toReactWorkspacePackages = (directories) => {
|
|
34124
34189
|
const packages = [];
|
|
34125
34190
|
for (const directory of directories) {
|
|
@@ -34192,14 +34257,13 @@ const clearProjectCache = () => {
|
|
|
34192
34257
|
};
|
|
34193
34258
|
/**
|
|
34194
34259
|
* Build a `ProjectInfo` for a directory that has no `package.json` of
|
|
34195
|
-
* its own — a
|
|
34196
|
-
*
|
|
34197
|
-
*
|
|
34198
|
-
*
|
|
34199
|
-
*
|
|
34200
|
-
*
|
|
34201
|
-
*
|
|
34202
|
-
* 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).
|
|
34203
34267
|
*/
|
|
34204
34268
|
const discoverProjectWithoutPackageJson = (directory) => {
|
|
34205
34269
|
const sourceFileCount = countSourceFiles(directory);
|
|
@@ -34237,6 +34301,7 @@ const discoverProjectWithoutPackageJson = (directory) => {
|
|
|
34237
34301
|
hasReanimated: false,
|
|
34238
34302
|
reanimatedVersion: null,
|
|
34239
34303
|
isPreES2023Target: hasOwnTsConfig && detectPreES2023Target(directory),
|
|
34304
|
+
isStaticExport: false,
|
|
34240
34305
|
sourceFileCount
|
|
34241
34306
|
};
|
|
34242
34307
|
};
|
|
@@ -34250,90 +34315,85 @@ const discoverProject = (directory) => {
|
|
|
34250
34315
|
return synthesized;
|
|
34251
34316
|
}
|
|
34252
34317
|
const packageJson = readPackageJson(packageJsonPath);
|
|
34253
|
-
|
|
34254
|
-
|
|
34255
|
-
|
|
34256
|
-
|
|
34257
|
-
|
|
34258
|
-
|
|
34259
|
-
|
|
34260
|
-
|
|
34261
|
-
|
|
34262
|
-
|
|
34263
|
-
|
|
34264
|
-
|
|
34265
|
-
|
|
34266
|
-
|
|
34267
|
-
|
|
34268
|
-
|
|
34269
|
-
|
|
34270
|
-
]
|
|
34271
|
-
});
|
|
34272
|
-
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({
|
|
34273
34335
|
packageJson,
|
|
34274
|
-
packageName
|
|
34275
|
-
sections:
|
|
34276
|
-
|
|
34277
|
-
|
|
34278
|
-
|
|
34279
|
-
|
|
34280
|
-
|
|
34281
|
-
if (!
|
|
34282
|
-
if (!tailwindVersion && tailwindDeclaration.hasDeclaration) tailwindVersion = resolveCatalogVersion(packageJson, "tailwindcss", directory, tailwindDeclaration.catalogReference);
|
|
34283
|
-
if (!zodVersion && zodDeclaration.hasDeclaration) zodVersion = resolveCatalogVersion(packageJson, "zod", directory, zodDeclaration.catalogReference);
|
|
34284
|
-
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) {
|
|
34285
34344
|
const monorepoRoot = findMonorepoRoot(directory);
|
|
34286
34345
|
if (monorepoRoot) {
|
|
34287
34346
|
const monorepoPackageJsonPath = Path.join(monorepoRoot, "package.json");
|
|
34288
|
-
if (isFile(monorepoPackageJsonPath))
|
|
34289
|
-
const rootPackageJson = readPackageJson(monorepoPackageJsonPath);
|
|
34290
|
-
if (!reactVersion && reactDeclaration.hasDeclaration) reactVersion = resolveCatalogVersion(rootPackageJson, "react", monorepoRoot, reactDeclaration.catalogReference);
|
|
34291
|
-
if (!tailwindVersion && tailwindDeclaration.hasDeclaration) tailwindVersion = resolveCatalogVersion(rootPackageJson, "tailwindcss", monorepoRoot, tailwindDeclaration.catalogReference);
|
|
34292
|
-
if (!zodVersion && zodDeclaration.hasDeclaration) zodVersion = resolveCatalogVersion(rootPackageJson, "zod", monorepoRoot, zodDeclaration.catalogReference);
|
|
34293
|
-
}
|
|
34347
|
+
if (isFile(monorepoPackageJsonPath)) fillFromCatalogs(readPackageJson(monorepoPackageJsonPath), monorepoRoot);
|
|
34294
34348
|
}
|
|
34295
34349
|
}
|
|
34296
|
-
|
|
34297
|
-
|
|
34298
|
-
|
|
34299
|
-
|
|
34300
|
-
|
|
34301
|
-
|
|
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;
|
|
34302
34357
|
}
|
|
34303
|
-
if ((!
|
|
34358
|
+
if ((!tracked.react.version || framework === "unknown") && !isMonorepoRoot(directory)) {
|
|
34304
34359
|
const monorepoInfo = findDependencyInfoFromMonorepoRoot(directory);
|
|
34305
|
-
|
|
34306
|
-
|
|
34307
|
-
|
|
34360
|
+
tracked.react.version ||= monorepoInfo.reactVersion;
|
|
34361
|
+
tracked.tailwindcss.version ||= monorepoInfo.tailwindVersion;
|
|
34362
|
+
tracked.zod.version ||= monorepoInfo.zodVersion;
|
|
34308
34363
|
if (framework === "unknown") framework = monorepoInfo.framework;
|
|
34309
34364
|
}
|
|
34310
|
-
|
|
34311
|
-
|
|
34312
|
-
|
|
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;
|
|
34313
34371
|
if (!reactVersion || parseReactMajor(reactVersion) === null) reactVersion = resolveInstalledReactVersion(directory) ?? reactVersion;
|
|
34372
|
+
const tailwindVersion = tailwindcss.version;
|
|
34373
|
+
const zodVersion = zod.version;
|
|
34314
34374
|
const projectName = packageJson.name ?? Path.basename(directory);
|
|
34315
34375
|
const hasTypeScript = NFS.existsSync(Path.join(directory, "tsconfig.json"));
|
|
34316
34376
|
const sourceFileCount = countSourceFiles(directory);
|
|
34317
|
-
const hasReactNativeWorkspace = framework === "expo" || framework === "react-native" ||
|
|
34377
|
+
const hasReactNativeWorkspace = framework === "expo" || framework === "react-native" || workspaceFacts.hasReactNativeAwarePackage;
|
|
34318
34378
|
const expoVersion = hasReactNativeWorkspace ? resolveCatalogBackedDependencyVersion({
|
|
34319
34379
|
rootDirectory: directory,
|
|
34320
34380
|
rootPackageJson: packageJson,
|
|
34321
34381
|
packageName: "expo",
|
|
34322
|
-
version:
|
|
34382
|
+
version: workspaceFacts.expo.version
|
|
34323
34383
|
}) : null;
|
|
34324
34384
|
const shopifyFlashListVersion = hasReactNativeWorkspace ? resolveCatalogBackedDependencyVersion({
|
|
34325
34385
|
rootDirectory: directory,
|
|
34326
34386
|
rootPackageJson: packageJson,
|
|
34327
34387
|
packageName: SHOPIFY_FLASH_LIST_PACKAGE_NAME,
|
|
34328
|
-
version:
|
|
34388
|
+
version: workspaceFacts.shopifyFlashList.version
|
|
34329
34389
|
}) : null;
|
|
34330
|
-
const hasReanimated = hasReactNativeWorkspace &&
|
|
34331
|
-
const reanimatedVersion = hasReanimated ?
|
|
34390
|
+
const hasReanimated = hasReactNativeWorkspace && workspaceFacts.hasReanimatedAwarePackage;
|
|
34391
|
+
const reanimatedVersion = hasReanimated ? workspaceFacts.reanimatedVersion : null;
|
|
34332
34392
|
const nextjsVersion = framework === "nextjs" ? resolveCatalogBackedDependencyVersion({
|
|
34333
34393
|
rootDirectory: directory,
|
|
34334
34394
|
rootPackageJson: packageJson,
|
|
34335
34395
|
packageName: "next",
|
|
34336
|
-
version:
|
|
34396
|
+
version: workspaceFacts.next.version
|
|
34337
34397
|
}) : null;
|
|
34338
34398
|
const preactVersion = getPreactVersion(packageJson);
|
|
34339
34399
|
const isPreES2023Target = hasTypeScript && detectPreES2023Target(directory);
|
|
@@ -34344,7 +34404,7 @@ const discoverProject = (directory) => {
|
|
|
34344
34404
|
reactMajorVersion: resolveEffectiveReactMajor(reactVersion, packageJson),
|
|
34345
34405
|
tailwindVersion,
|
|
34346
34406
|
zodVersion,
|
|
34347
|
-
zodMajorVersion:
|
|
34407
|
+
zodMajorVersion: zodVersion === null ? null : getLowestDependencyMajor(zodVersion),
|
|
34348
34408
|
framework,
|
|
34349
34409
|
hasTypeScript,
|
|
34350
34410
|
hasReactCompiler: detectReactCompiler(directory, packageJson),
|
|
@@ -34360,6 +34420,7 @@ const discoverProject = (directory) => {
|
|
|
34360
34420
|
hasReanimated,
|
|
34361
34421
|
reanimatedVersion,
|
|
34362
34422
|
isPreES2023Target,
|
|
34423
|
+
isStaticExport: framework === "nextjs" && detectNextjsStaticExport(workspaceFacts.next.sourceDirectory ?? directory),
|
|
34363
34424
|
sourceFileCount
|
|
34364
34425
|
};
|
|
34365
34426
|
cachedProjectInfos.set(directory, projectInfo);
|
|
@@ -34375,57 +34436,6 @@ const discoverProject = (directory) => {
|
|
|
34375
34436
|
* single source file has nothing to analyze.
|
|
34376
34437
|
*/
|
|
34377
34438
|
const isAnalyzableProject = (project) => project.reactVersion !== null || project.preactVersion !== null || project.sourceFileCount > 0;
|
|
34378
|
-
const MAJOR_MINOR_PATTERN = /(\d{1,4})\.(\d{1,4})/;
|
|
34379
|
-
const MAJOR_ONLY_PATTERN = /(\d{1,4})/;
|
|
34380
|
-
const UPPER_BOUND_COMPARATOR_PATTERN = /<=?\s{0,8}\d{1,4}(?:\.\d{1,4}){0,2}(?:-[^\s,|]+)?/g;
|
|
34381
|
-
const parseReactMajorMinor = (reactVersion) => {
|
|
34382
|
-
if (typeof reactVersion !== "string") return null;
|
|
34383
|
-
const trimmed = reactVersion.trim();
|
|
34384
|
-
if (trimmed.length === 0) return null;
|
|
34385
|
-
const lowerBoundsOnly = trimmed.replace(UPPER_BOUND_COMPARATOR_PATTERN, " ").trim();
|
|
34386
|
-
if (lowerBoundsOnly.length === 0) return null;
|
|
34387
|
-
const majorMinorMatch = lowerBoundsOnly.match(MAJOR_MINOR_PATTERN);
|
|
34388
|
-
if (majorMinorMatch) {
|
|
34389
|
-
const major = Number.parseInt(majorMinorMatch[1], 10);
|
|
34390
|
-
const minor = Number.parseInt(majorMinorMatch[2], 10);
|
|
34391
|
-
if (!Number.isFinite(major) || major <= 0) return null;
|
|
34392
|
-
if (!Number.isFinite(minor) || minor < 0) return null;
|
|
34393
|
-
return {
|
|
34394
|
-
major,
|
|
34395
|
-
minor
|
|
34396
|
-
};
|
|
34397
|
-
}
|
|
34398
|
-
const majorOnlyMatch = lowerBoundsOnly.match(MAJOR_ONLY_PATTERN);
|
|
34399
|
-
if (!majorOnlyMatch) return null;
|
|
34400
|
-
const major = Number.parseInt(majorOnlyMatch[1], 10);
|
|
34401
|
-
if (!Number.isFinite(major) || major <= 0) return null;
|
|
34402
|
-
return {
|
|
34403
|
-
major,
|
|
34404
|
-
minor: 0
|
|
34405
|
-
};
|
|
34406
|
-
};
|
|
34407
|
-
const isReactAtLeast = (detected, required) => {
|
|
34408
|
-
if (detected === null) return true;
|
|
34409
|
-
if (detected.major !== required.major) return detected.major > required.major;
|
|
34410
|
-
return detected.minor >= required.minor;
|
|
34411
|
-
};
|
|
34412
|
-
const parseLowerBoundVersion = (versionSpec) => import_semver.validRange(versionSpec) !== null ? import_semver.minVersion(versionSpec) : import_semver.coerce(versionSpec);
|
|
34413
|
-
const parseTailwindMajorMinor = (tailwindVersion) => {
|
|
34414
|
-
if (typeof tailwindVersion !== "string") return null;
|
|
34415
|
-
const trimmed = tailwindVersion.trim();
|
|
34416
|
-
if (trimmed.length === 0) return null;
|
|
34417
|
-
const lowerBound = parseLowerBoundVersion(trimmed);
|
|
34418
|
-
if (lowerBound === null || lowerBound.major <= 0) return null;
|
|
34419
|
-
return {
|
|
34420
|
-
major: lowerBound.major,
|
|
34421
|
-
minor: lowerBound.minor
|
|
34422
|
-
};
|
|
34423
|
-
};
|
|
34424
|
-
const isTailwindAtLeast = (detected, required) => {
|
|
34425
|
-
if (detected === null) return true;
|
|
34426
|
-
if (detected.major !== required.major) return detected.major > required.major;
|
|
34427
|
-
return detected.minor >= required.minor;
|
|
34428
|
-
};
|
|
34429
34439
|
const messageFromUnknown = (error) => error instanceof Error ? error.message : String(error);
|
|
34430
34440
|
var InvalidGlobPatternError = class extends Error {
|
|
34431
34441
|
pattern;
|
|
@@ -37201,64 +37211,64 @@ function* collectSecurityScanFiles(rootDirectory) {
|
|
|
37201
37211
|
}
|
|
37202
37212
|
}
|
|
37203
37213
|
}
|
|
37204
|
-
|
|
37205
|
-
|
|
37206
|
-
|
|
37207
|
-
|
|
37208
|
-
|
|
37209
|
-
|
|
37210
|
-
|
|
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
|
+
};
|
|
37211
37226
|
const buildCapabilities = (project) => {
|
|
37212
37227
|
const capabilities = /* @__PURE__ */ new Set();
|
|
37213
37228
|
capabilities.add(project.framework);
|
|
37214
|
-
if (
|
|
37229
|
+
if (project.reactVersion !== null || project.preactVersion !== null) capabilities.add("react");
|
|
37215
37230
|
if (project.framework === "expo" || project.framework === "react-native" || project.hasReactNativeWorkspace) capabilities.add("react-native");
|
|
37216
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");
|
|
37217
37235
|
if (project.nextjsMajorVersion !== null && project.nextjsMajorVersion >= 15) capabilities.add("nextjs:15");
|
|
37218
|
-
|
|
37219
|
-
if (
|
|
37220
|
-
|
|
37221
|
-
|
|
37222
|
-
|
|
37223
|
-
|
|
37224
|
-
|
|
37225
|
-
|
|
37226
|
-
|
|
37227
|
-
|
|
37228
|
-
|
|
37229
|
-
if (project.
|
|
37230
|
-
capabilities.add("tailwind");
|
|
37231
|
-
if (isTailwindAtLeast(parseTailwindMajorMinor(project.tailwindVersion), {
|
|
37232
|
-
major: 3,
|
|
37233
|
-
minor: 4
|
|
37234
|
-
})) capabilities.add("tailwind:3.4");
|
|
37235
|
-
}
|
|
37236
|
-
if (project.zodVersion !== null) {
|
|
37237
|
-
capabilities.add("zod");
|
|
37238
|
-
if (project.zodMajorVersion !== null && project.zodMajorVersion >= 4) capabilities.add("zod:4");
|
|
37239
|
-
}
|
|
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");
|
|
37240
37248
|
if (project.isPreES2023Target) capabilities.add("pre-es2023");
|
|
37241
37249
|
if (project.hasReactCompiler) capabilities.add("react-compiler");
|
|
37242
37250
|
if (project.hasTanStackQuery) capabilities.add("tanstack-query");
|
|
37243
37251
|
if (project.hasTypeScript) capabilities.add("typescript");
|
|
37244
|
-
if (project.preactVersion !== null)
|
|
37245
|
-
|
|
37246
|
-
|
|
37247
|
-
|
|
37248
|
-
|
|
37249
|
-
|
|
37250
|
-
|
|
37251
|
-
|
|
37252
|
-
|
|
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");
|
|
37255
|
+
return capabilities;
|
|
37256
|
+
};
|
|
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);
|
|
37253
37263
|
return capabilities;
|
|
37254
37264
|
};
|
|
37255
|
-
const shouldEnableRule = (requires, tags, capabilities, ignoredTags,
|
|
37265
|
+
const shouldEnableRule = (requires, tags, capabilities, ignoredTags, disabledWhen) => {
|
|
37256
37266
|
if (requires) {
|
|
37257
37267
|
for (const capability of requires) if (!capabilities.has(capability)) return false;
|
|
37258
37268
|
}
|
|
37259
37269
|
if (tags?.includes("react-jsx-only") && !capabilities.has("react")) return false;
|
|
37260
|
-
if (
|
|
37261
|
-
for (const capability of
|
|
37270
|
+
if (disabledWhen) {
|
|
37271
|
+
for (const capability of disabledWhen) if (capabilities.has(capability)) return false;
|
|
37262
37272
|
}
|
|
37263
37273
|
if (tags) {
|
|
37264
37274
|
for (const tag of tags) if (ignoredTags.has(tag)) return false;
|
|
@@ -37269,14 +37279,14 @@ const yieldToEventLoop = () => new Promise((resolve) => {
|
|
|
37269
37279
|
setImmediate(resolve);
|
|
37270
37280
|
});
|
|
37271
37281
|
const createSecurityScanSession = (rootDirectory, options) => {
|
|
37272
|
-
const capabilities = options.project ?
|
|
37282
|
+
const capabilities = options.project ? getCapabilities(options.project) : /* @__PURE__ */ new Set();
|
|
37273
37283
|
const ignoredTags = options.ignoredTags ?? /* @__PURE__ */ new Set();
|
|
37274
37284
|
const enabledScanRules = REACT_DOCTOR_RULES.flatMap((entry) => {
|
|
37275
37285
|
const rule = entry.rule;
|
|
37276
37286
|
const scan = rule.scan;
|
|
37277
37287
|
if (typeof scan !== "function") return [];
|
|
37278
37288
|
if (rule.defaultEnabled === false) return [];
|
|
37279
|
-
if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.
|
|
37289
|
+
if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.disabledWhen)) return [];
|
|
37280
37290
|
return [{
|
|
37281
37291
|
entry,
|
|
37282
37292
|
scan,
|
|
@@ -37296,6 +37306,7 @@ const createSecurityScanSession = (rootDirectory, options) => {
|
|
|
37296
37306
|
return status === true;
|
|
37297
37307
|
};
|
|
37298
37308
|
const scanFileByRule = function* (file) {
|
|
37309
|
+
if (file === null) return;
|
|
37299
37310
|
for (const { entry, scan, committedFilesOnly } of enabledScanRules) {
|
|
37300
37311
|
for (const finding of scan(file)) {
|
|
37301
37312
|
if (committedFilesOnly && isFileGitIgnored(file)) continue;
|
|
@@ -39176,7 +39187,7 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
39176
39187
|
const reactCompilerRules = reactHooksJsPlugin ? applyRuleSeverityControls(filterRulesToAvailable(REACT_COMPILER_RULES, "react-hooks-js", reactHooksJsPlugin.availableRuleNames), severityControls) : {};
|
|
39177
39188
|
const jsPlugins = [];
|
|
39178
39189
|
if (reactHooksJsPlugin) jsPlugins.push(reactHooksJsPlugin.entry);
|
|
39179
|
-
const capabilities =
|
|
39190
|
+
const capabilities = getCapabilities(project);
|
|
39180
39191
|
const enabledReactDoctorRules = {};
|
|
39181
39192
|
for (const registryEntry of REACT_DOCTOR_RULES) {
|
|
39182
39193
|
const rule = reactDoctorPlugin.rules[registryEntry.id];
|
|
@@ -39187,7 +39198,7 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
39187
39198
|
if (rule.scan !== void 0) continue;
|
|
39188
39199
|
if (customRulesOnly && registryEntry.originallyExternal) continue;
|
|
39189
39200
|
if (rule.framework !== "global" && !rule.requires) continue;
|
|
39190
|
-
if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.
|
|
39201
|
+
if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.disabledWhen)) continue;
|
|
39191
39202
|
const explicitRuleOverride = resolveRuleSeverityOverride({ ruleKey: registryEntry.key }, severityControls);
|
|
39192
39203
|
if (rule.defaultEnabled === false && explicitRuleOverride === void 0) continue;
|
|
39193
39204
|
const severity = resolveRuleSeverityOverride({
|
|
@@ -39218,6 +39229,7 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
39218
39229
|
settings: { "react-doctor": {
|
|
39219
39230
|
framework: project.framework,
|
|
39220
39231
|
rootDirectory: resolveSettingsRootDirectory(project.rootDirectory),
|
|
39232
|
+
capabilities: [...capabilities].sort(),
|
|
39221
39233
|
...project.shopifyFlashListMajorVersion !== null ? { shopifyFlashListMajorVersion: project.shopifyFlashListMajorVersion } : {},
|
|
39222
39234
|
...serverAuthFunctionNames && serverAuthFunctionNames.length > 0 ? { serverAuthFunctionNames: [...serverAuthFunctionNames] } : {}
|
|
39223
39235
|
} },
|
|
@@ -39508,21 +39520,6 @@ const mapWithConcurrency = async (items, concurrency, task) => {
|
|
|
39508
39520
|
if (errors.length > 0) throw errors[0];
|
|
39509
39521
|
return results;
|
|
39510
39522
|
};
|
|
39511
|
-
const getPublicEnvPrefix = (framework) => {
|
|
39512
|
-
switch (framework) {
|
|
39513
|
-
case "nextjs": return "NEXT_PUBLIC_*";
|
|
39514
|
-
case "vite":
|
|
39515
|
-
case "tanstack-start": return "VITE_*";
|
|
39516
|
-
case "cra": return "REACT_APP_*";
|
|
39517
|
-
case "gatsby": return "GATSBY_*";
|
|
39518
|
-
default: return null;
|
|
39519
|
-
}
|
|
39520
|
-
};
|
|
39521
|
-
const buildNoSecretsRecommendation = (project, fallbackRecommendation) => {
|
|
39522
|
-
const publicEnvPrefix = getPublicEnvPrefix(project.framework);
|
|
39523
|
-
if (!publicEnvPrefix) return fallbackRecommendation;
|
|
39524
|
-
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`;
|
|
39525
|
-
};
|
|
39526
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";
|
|
39527
39524
|
const ACCESSORS_MIN_VERSION = "3.15.0";
|
|
39528
39525
|
const supportsAccessors = (reanimatedVersion) => {
|
|
@@ -40203,8 +40200,14 @@ const PLUGIN_CATEGORY_MAP = {
|
|
|
40203
40200
|
};
|
|
40204
40201
|
const lookupOwnString = (record, key) => Object.hasOwn(record, key) ? record[key] : void 0;
|
|
40205
40202
|
const getRuleRecommendation = (ruleName, project) => {
|
|
40206
|
-
|
|
40207
|
-
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;
|
|
40208
40211
|
};
|
|
40209
40212
|
const getRuleCategory = (ruleName) => reactDoctorPlugin.rules[ruleName]?.category;
|
|
40210
40213
|
const getRuleTitle = (ruleName) => reactDoctorPlugin.rules[ruleName]?.title;
|
|
@@ -40358,7 +40361,9 @@ const SANITIZED_ENV = buildOxlintChildEnv(process.env);
|
|
|
40358
40361
|
* - `OxlintBatchExceeded { kind: "output-too-large" }` — stdout+stderr
|
|
40359
40362
|
* crossed `OXLINT_OUTPUT_MAX_BYTES`.
|
|
40360
40363
|
* - `OxlintBatchExceeded { kind: "oom" | "killed" }` — child exited
|
|
40361
|
-
* 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).
|
|
40362
40367
|
* - `OxlintSpawnFailed { cause }` — `spawn` itself errored, or the
|
|
40363
40368
|
* child exited successfully but printed only stderr.
|
|
40364
40369
|
*
|
|
@@ -40424,7 +40429,7 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath, spawnTimeoutMs = OXLIN
|
|
|
40424
40429
|
clearAbortListener();
|
|
40425
40430
|
reject(new ReactDoctorError({ reason: new OxlintSpawnFailed({ cause: error }) }));
|
|
40426
40431
|
});
|
|
40427
|
-
child.on("close", (
|
|
40432
|
+
child.on("close", (code, signal) => {
|
|
40428
40433
|
clearTimeout(timeoutHandle);
|
|
40429
40434
|
clearAbortListener();
|
|
40430
40435
|
if (didKillForSize) {
|
|
@@ -40434,10 +40439,11 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath, spawnTimeoutMs = OXLIN
|
|
|
40434
40439
|
}) }));
|
|
40435
40440
|
return;
|
|
40436
40441
|
}
|
|
40437
|
-
|
|
40442
|
+
const isAbortExitCode = code !== null && ABORT_EXIT_CODES.has(code);
|
|
40443
|
+
if (signal || isAbortExitCode) {
|
|
40438
40444
|
const stderrOutput = Buffer.concat(stderrBuffers).toString("utf-8").trim();
|
|
40439
|
-
const isOom = signal === "SIGABRT";
|
|
40440
|
-
const detailParts = [`killed by ${signal}`];
|
|
40445
|
+
const isOom = signal === "SIGABRT" || isAbortExitCode;
|
|
40446
|
+
const detailParts = [signal ? `killed by ${signal}` : `aborted with exit code ${code}`];
|
|
40441
40447
|
if (isOom) detailParts.push("try scanning fewer files with --diff");
|
|
40442
40448
|
if (stderrOutput) detailParts.push(stderrOutput);
|
|
40443
40449
|
reject(new ReactDoctorError({ reason: new OxlintBatchExceeded({
|
|
@@ -40480,12 +40486,19 @@ const isParallelismRelatedSpawnError = (error) => {
|
|
|
40480
40486
|
* (surfaced via `onPartialFailure`) so JSON-mode consumers see WHICH
|
|
40481
40487
|
* files were skipped instead of silently losing them.
|
|
40482
40488
|
*
|
|
40483
|
-
* Parallel runs (concurrency > 1) get
|
|
40484
|
-
* fails with a resource-exhaustion error that's exclusive
|
|
40485
|
-
* many oxlint subprocesses at once (EAGAIN / EMFILE /
|
|
40486
|
-
* see `isParallelismRelatedSpawnError`), the whole
|
|
40487
|
-
* with a single worker. That's the only failure a
|
|
40488
|
-
* 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.
|
|
40489
40502
|
*
|
|
40490
40503
|
* Errors that aren't splittable and aren't parallelism-related (oxlint
|
|
40491
40504
|
* config crash, JS plugin resolution failure, etc.) propagate to the
|
|
@@ -40495,13 +40508,16 @@ const isParallelismRelatedSpawnError = (error) => {
|
|
|
40495
40508
|
const spawnLintBatches = async (input) => {
|
|
40496
40509
|
const { baseArgs, fileBatches, rootDirectory, nodeBinaryPath, project, onPartialFailure, onFileProgress, spawnTimeoutMs, outputMaxBytes, splitTotalBudgetMs = OXLINT_SPLIT_TOTAL_BUDGET_MS, splitMaxDepth = 8, signal, deadlineEpochMs } = input;
|
|
40497
40510
|
const requestedConcurrency = resolveScanConcurrency(input.concurrency ?? 1);
|
|
40498
|
-
|
|
40499
|
-
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);
|
|
40500
40515
|
const allDiagnostics = [];
|
|
40501
40516
|
const droppedFiles = [];
|
|
40517
|
+
const oomDroppedFiles = [];
|
|
40502
40518
|
const deadlineSkippedFiles = [];
|
|
40503
|
-
const isPastDeadline = () => deadlineEpochMs !== void 0 && remainingDeadlineBudgetMs(deadlineEpochMs) === 0;
|
|
40504
40519
|
let firstDropReason = null;
|
|
40520
|
+
let firstNonOomDropReason = null;
|
|
40505
40521
|
const spawnLintBatch = async (batch, depth, batchState) => {
|
|
40506
40522
|
if (isPastDeadline()) {
|
|
40507
40523
|
deadlineSkippedFiles.push(...batch);
|
|
@@ -40523,12 +40539,14 @@ const spawnLintBatches = async (input) => {
|
|
|
40523
40539
|
const isDepthCapReached = depth >= splitMaxDepth;
|
|
40524
40540
|
if (batch.length <= 1 || isBudgetElapsed || isDepthCapReached) {
|
|
40525
40541
|
droppedFiles.push(...batch);
|
|
40526
|
-
|
|
40527
|
-
|
|
40528
|
-
|
|
40529
|
-
|
|
40530
|
-
|
|
40531
|
-
}
|
|
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;
|
|
40532
40550
|
return [];
|
|
40533
40551
|
}
|
|
40534
40552
|
const splitIndex = Math.ceil(batch.length / 2);
|
|
@@ -40538,16 +40556,16 @@ const spawnLintBatches = async (input) => {
|
|
|
40538
40556
|
let startedFileCount = 0;
|
|
40539
40557
|
let scannedFileCount = 0;
|
|
40540
40558
|
let displayedFileCount = 0;
|
|
40541
|
-
const progressTimer =
|
|
40559
|
+
const progressTimer = passOnFileProgress && totalFileCount > 1 ? setInterval(() => {
|
|
40542
40560
|
const ceiling = Math.min(startedFileCount, totalFileCount - 1);
|
|
40543
40561
|
if (displayedFileCount < ceiling) {
|
|
40544
40562
|
displayedFileCount += 1;
|
|
40545
|
-
|
|
40563
|
+
passOnFileProgress(displayedFileCount, totalFileCount);
|
|
40546
40564
|
}
|
|
40547
40565
|
}, 50) : null;
|
|
40548
40566
|
progressTimer?.unref?.();
|
|
40549
40567
|
try {
|
|
40550
|
-
const batchResults = await mapWithConcurrency(
|
|
40568
|
+
const batchResults = await mapWithConcurrency(passBatches, concurrency, async (batch) => {
|
|
40551
40569
|
if (isPastDeadline()) {
|
|
40552
40570
|
deadlineSkippedFiles.push(...batch);
|
|
40553
40571
|
return [];
|
|
@@ -40559,9 +40577,9 @@ const spawnLintBatches = async (input) => {
|
|
|
40559
40577
|
};
|
|
40560
40578
|
const batchDiagnostics = await spawnLintBatch(batch, 0, batchState);
|
|
40561
40579
|
scannedFileCount += batch.length - batchState.deadlineSkippedFileCount;
|
|
40562
|
-
if (
|
|
40580
|
+
if (passOnFileProgress) {
|
|
40563
40581
|
displayedFileCount = Math.min(Math.max(displayedFileCount, scannedFileCount), totalFileCount);
|
|
40564
|
-
|
|
40582
|
+
passOnFileProgress(displayedFileCount, totalFileCount);
|
|
40565
40583
|
}
|
|
40566
40584
|
return batchDiagnostics;
|
|
40567
40585
|
});
|
|
@@ -40569,22 +40587,53 @@ const spawnLintBatches = async (input) => {
|
|
|
40569
40587
|
} finally {
|
|
40570
40588
|
if (progressTimer !== null) clearInterval(progressTimer);
|
|
40571
40589
|
}
|
|
40572
|
-
|
|
40573
|
-
|
|
40574
|
-
|
|
40590
|
+
return {
|
|
40591
|
+
diagnostics: allDiagnostics,
|
|
40592
|
+
droppedFiles,
|
|
40593
|
+
oomDroppedFiles,
|
|
40594
|
+
deadlineSkippedFiles,
|
|
40595
|
+
firstDropReason,
|
|
40596
|
+
firstNonOomDropReason
|
|
40575
40597
|
};
|
|
40576
|
-
const reasonHint = firstDropReason ? ` — first failure: ${firstDropReason}` : "";
|
|
40577
|
-
reportSkippedFiles(droppedFiles, (fileListPreview) => `${droppedFiles.length} file(s) failed to lint and were skipped (${fileListPreview})${reasonHint}`);
|
|
40578
|
-
reportSkippedFiles(deadlineSkippedFiles, (fileListPreview) => `${deadlineSkippedFiles.length} file(s) skipped — max scan duration reached before they were linted (${fileListPreview})`);
|
|
40579
|
-
return allDiagnostics;
|
|
40580
40598
|
};
|
|
40581
|
-
let
|
|
40599
|
+
let outcome;
|
|
40600
|
+
let effectiveConcurrency = requestedConcurrency;
|
|
40582
40601
|
try {
|
|
40583
|
-
|
|
40602
|
+
outcome = await runBatchPass(requestedConcurrency, fileBatches, onFileProgress);
|
|
40584
40603
|
} catch (error) {
|
|
40585
40604
|
if (requestedConcurrency <= 1 || !isParallelismRelatedSpawnError(error)) throw error;
|
|
40586
|
-
|
|
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;
|
|
40587
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})`);
|
|
40588
40637
|
return dedupeDiagnostics(diagnostics);
|
|
40589
40638
|
};
|
|
40590
40639
|
let didValidate = false;
|
|
@@ -42529,6 +42578,13 @@ const summarizeDiagnostics = (diagnostics, worstScore = null, worstScoreLabel =
|
|
|
42529
42578
|
scoreLabel: worstScoreLabel
|
|
42530
42579
|
};
|
|
42531
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;
|
|
42532
42588
|
const toJsonDiff = (diff) => {
|
|
42533
42589
|
if (!diff) return null;
|
|
42534
42590
|
return {
|
|
@@ -42763,17 +42819,20 @@ const warnIfAiTrainingEnvironment = () => {
|
|
|
42763
42819
|
didWarnAiTraining = true;
|
|
42764
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.");
|
|
42765
42821
|
};
|
|
42766
|
-
const
|
|
42767
|
-
|
|
42768
|
-
|
|
42769
|
-
|
|
42770
|
-
|
|
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
|
|
42771
42829
|
});
|
|
42772
|
-
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([]));
|
|
42773
42831
|
};
|
|
42774
42832
|
const buildInspectProgram = (scanTarget, options, configOverride) => {
|
|
42775
42833
|
const effectiveConfig = configOverride ?? scanTarget.userConfig;
|
|
42776
42834
|
const includePaths = options.includePaths ?? [];
|
|
42835
|
+
const shouldRunDeadCode = resolveShouldRunDeadCode(options, effectiveConfig);
|
|
42777
42836
|
return runInspect({
|
|
42778
42837
|
directory: scanTarget.resolvedDirectory,
|
|
42779
42838
|
includePaths,
|
|
@@ -42782,7 +42841,7 @@ const buildInspectProgram = (scanTarget, options, configOverride) => {
|
|
|
42782
42841
|
warnings: options.warnings ?? effectiveConfig?.warnings ?? true,
|
|
42783
42842
|
adoptExistingLintConfig: effectiveConfig?.adoptExistingLintConfig ?? true,
|
|
42784
42843
|
ignoredTags: new Set(effectiveConfig?.ignore?.tags ?? []),
|
|
42785
|
-
runDeadCode:
|
|
42844
|
+
runDeadCode: shouldRunDeadCode,
|
|
42786
42845
|
isCi: false,
|
|
42787
42846
|
resolveLocalGithubViewerPermission: true
|
|
42788
42847
|
});
|
|
@@ -42804,7 +42863,14 @@ const diagnoseDirectory = async (directory, options) => {
|
|
|
42804
42863
|
warnIfAiTrainingEnvironment();
|
|
42805
42864
|
const startTime = globalThis.performance.now();
|
|
42806
42865
|
const scanTarget = await resolveScanTarget(directory);
|
|
42807
|
-
|
|
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);
|
|
42808
42874
|
};
|
|
42809
42875
|
const findWorstScore = (projectResults) => {
|
|
42810
42876
|
let worstResult = null;
|
|
@@ -42825,15 +42891,27 @@ const diagnoseProject = async (projectDefinition, baseOptions, batchConfig) => {
|
|
|
42825
42891
|
const { directory: _, config: projectConfig, ...perProjectOptions } = projectDefinition;
|
|
42826
42892
|
const didOverrideConfig = batchConfig !== void 0 || projectConfig !== void 0;
|
|
42827
42893
|
const effectiveConfig = mergeReactDoctorConfigs(mergeReactDoctorConfigs(scanTarget.userConfig, batchConfig), projectConfig);
|
|
42828
|
-
const
|
|
42894
|
+
const mergedOptions = {
|
|
42829
42895
|
...baseOptions,
|
|
42830
42896
|
...perProjectOptions
|
|
42831
|
-
}
|
|
42897
|
+
};
|
|
42898
|
+
const shouldRunLint = resolveShouldRunLint(mergedOptions, effectiveConfig);
|
|
42899
|
+
const shouldRunDeadCode = resolveShouldRunDeadCode(mergedOptions, effectiveConfig);
|
|
42900
|
+
const program = buildInspectProgram(scanTarget, mergedOptions, effectiveConfig ?? void 0);
|
|
42832
42901
|
const didOverridePlugins = batchConfig?.plugins !== void 0 || projectConfig?.plugins !== void 0;
|
|
42833
|
-
const layer = buildDiagnoseLayer(
|
|
42834
|
-
|
|
42835
|
-
|
|
42836
|
-
|
|
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
|
+
});
|
|
42837
42915
|
return {
|
|
42838
42916
|
ok: true,
|
|
42839
42917
|
...outputToDiagnoseResult(await runPromise(restoreLegacyThrow(program.pipe(provide(layer), provide(layerOtlp)))), globalThis.performance.now() - startTime),
|
|
@@ -42895,4 +42973,4 @@ const toJsonReport = (result, options) => buildJsonReport({
|
|
|
42895
42973
|
export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, hasReactRuntime, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
|
|
42896
42974
|
|
|
42897
42975
|
//# sourceMappingURL=index.js.map
|
|
42898
|
-
//# debugId=
|
|
42976
|
+
//# debugId=4940d639-1712-5f20-ae4a-a6bf10741ae9
|