react-doctor 0.7.1-dev.d353dad → 0.7.2-dev.08b768b
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1171 -742
- package/dist/index.d.ts +21 -2
- package/dist/index.js +1147 -698
- package/dist/lsp.js +1158 -738
- package/package.json +6 -6
package/dist/cli.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]="9aed4e70-dd94-51e8-9810-f8e8945d80a3")}catch(e){}}();
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import * as NodeChildProcess from "node:child_process";
|
|
5
5
|
import { execFile, execFileSync, spawn, spawnSync } from "node:child_process";
|
|
@@ -9,7 +9,7 @@ import * as NFS from "node:fs";
|
|
|
9
9
|
import fs, { mkdtempSync, rmSync } from "node:fs";
|
|
10
10
|
import process$1 from "node:process";
|
|
11
11
|
import * as ts from "typescript";
|
|
12
|
-
import reactDoctorPlugin, { ALL_REACT_DOCTOR_RULE_KEYS, CROSS_FILE_DEPENDENCY_COLLECTORS, CROSS_FILE_RULE_IDS, FRAMEWORK_SPECIFIC_RULE_KEYS, MOTION_LIBRARY_PACKAGES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, classifySecurityScanFile, collectCrossFileDependencyProbes, shouldReadSecurityScanContent } from "oxlint-plugin-react-doctor";
|
|
12
|
+
import reactDoctorPlugin, { ALL_REACT_DOCTOR_RULE_KEYS, CROSS_FILE_DEPENDENCY_COLLECTORS, CROSS_FILE_RULE_IDS, FRAMEWORK_SPECIFIC_RULE_KEYS, MOTION_LIBRARY_PACKAGES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, classifySecurityScanFile, collectCrossFileDependencyProbes, resetManifestCaches, shouldReadSecurityScanContent } from "oxlint-plugin-react-doctor";
|
|
13
13
|
import * as OS from "node:os";
|
|
14
14
|
import os, { tmpdir } from "node:os";
|
|
15
15
|
import { parseJSON5 } from "confbox";
|
|
@@ -35959,6 +35959,25 @@ var AmbiguousProjectError = class extends Error {
|
|
|
35959
35959
|
}
|
|
35960
35960
|
};
|
|
35961
35961
|
const isProjectDiscoveryError = (value) => value instanceof ProjectNotFoundError || value instanceof NoReactDependencyError || value instanceof PackageJsonNotFoundError || value instanceof NotADirectoryError || value instanceof AmbiguousProjectError;
|
|
35962
|
+
const isErrnoException = (error) => error instanceof Error && "code" in error;
|
|
35963
|
+
const IGNORABLE_READDIR_ERROR_CODES = new Set([
|
|
35964
|
+
"EACCES",
|
|
35965
|
+
"EPERM",
|
|
35966
|
+
"ENOENT",
|
|
35967
|
+
"ENOTDIR",
|
|
35968
|
+
"EINVAL",
|
|
35969
|
+
"ELOOP",
|
|
35970
|
+
"ENAMETOOLONG"
|
|
35971
|
+
]);
|
|
35972
|
+
const isIgnorableReaddirError = (error) => isErrnoException(error) && typeof error.code === "string" && IGNORABLE_READDIR_ERROR_CODES.has(error.code);
|
|
35973
|
+
const readDirectoryEntries = (directoryPath) => {
|
|
35974
|
+
try {
|
|
35975
|
+
return NFS.readdirSync(directoryPath, { withFileTypes: true });
|
|
35976
|
+
} catch (error) {
|
|
35977
|
+
if (isIgnorableReaddirError(error)) return [];
|
|
35978
|
+
throw error;
|
|
35979
|
+
}
|
|
35980
|
+
};
|
|
35962
35981
|
const isFile = (filePath) => {
|
|
35963
35982
|
try {
|
|
35964
35983
|
return NFS.statSync(filePath).isFile();
|
|
@@ -35966,10 +35985,24 @@ const isFile = (filePath) => {
|
|
|
35966
35985
|
return false;
|
|
35967
35986
|
}
|
|
35968
35987
|
};
|
|
35988
|
+
const isDirectory = (directoryPath) => {
|
|
35989
|
+
try {
|
|
35990
|
+
return NFS.statSync(directoryPath).isDirectory();
|
|
35991
|
+
} catch {
|
|
35992
|
+
return false;
|
|
35993
|
+
}
|
|
35994
|
+
};
|
|
35995
|
+
const isPlainObject = (value) => {
|
|
35996
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
35997
|
+
const prototype = Object.getPrototypeOf(value);
|
|
35998
|
+
return prototype === null || prototype === Object.prototype;
|
|
35999
|
+
};
|
|
35969
36000
|
const SOURCE_FILE_PATTERN = /\.(tsx?|jsx?|mts|mjs)$/;
|
|
35970
36001
|
const GENERATED_BUNDLE_FILE_PATTERN = /\.(iife|umd|global|min)\.m?js$/i;
|
|
36002
|
+
const GENERATED_SOURCE_DIRECTORY_PATTERN = /(?:^|\/)__generated__\//;
|
|
35971
36003
|
const MINIFIED_SNIFF_BYTES = 65536;
|
|
35972
36004
|
const GIT_LS_FILES_MAX_BUFFER_BYTES = 50 * 1024 * 1024;
|
|
36005
|
+
const SCANNED_DOT_DIRECTORIES = new Set([".dumi", ".storybook"]);
|
|
35973
36006
|
const IGNORED_DIRECTORIES = new Set([
|
|
35974
36007
|
".angular",
|
|
35975
36008
|
".astro",
|
|
@@ -36098,6 +36131,8 @@ const OXLINT_SPAWN_TIMEOUT_MS = 6e4;
|
|
|
36098
36131
|
const NODE_COMPILE_CACHE_DIR_NAME = "node-compile-cache";
|
|
36099
36132
|
const DEAD_CODE_WORKER_TIMEOUT_MS = 12e4;
|
|
36100
36133
|
const OXLINT_SPLIT_TOTAL_BUDGET_MS = 18e4;
|
|
36134
|
+
const ABORT_EXIT_CODES = new Set([134, 3221226505]);
|
|
36135
|
+
const OXLINT_OOM_RESCUE_BUDGET_MS = 6e4;
|
|
36101
36136
|
const DEAD_CODE_PHASE_TIMEOUT_MS = 15e4;
|
|
36102
36137
|
const LINT_PHASE_TIMEOUT_MS = 3e5;
|
|
36103
36138
|
const SCAN_TOTAL_DEADLINE_MS = 9e5;
|
|
@@ -36144,7 +36179,7 @@ const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
|
|
|
36144
36179
|
const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
|
|
36145
36180
|
const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
|
|
36146
36181
|
const SIDECAR_LINT_CACHE_FILENAME = "sidecar-lint-cache.json";
|
|
36147
|
-
const CORE_PACKAGE_VERSION = "0.7.
|
|
36182
|
+
const CORE_PACKAGE_VERSION = "0.7.2";
|
|
36148
36183
|
const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
|
|
36149
36184
|
const DEAD_CODE_SUMMARY_CACHE_FILENAME = "dead-code-summaries.json";
|
|
36150
36185
|
const SUPPLY_CHAIN_PLUGIN = "socket";
|
|
@@ -36154,8 +36189,9 @@ const SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS = 9e4;
|
|
|
36154
36189
|
const SUPPLY_CHAIN_CACHE_TTL_MS = 864e5;
|
|
36155
36190
|
const SUPPLY_CHAIN_CACHE_SUBDIR = "supply-chain";
|
|
36156
36191
|
const SUPPLY_CHAIN_IGNORED_PACKAGES = new Set(["next"]);
|
|
36157
|
-
const
|
|
36158
|
-
const
|
|
36192
|
+
const isIgnoredDirectoryName = (directoryName) => IGNORED_DIRECTORIES.has(directoryName) || directoryName.startsWith(".") && !SCANNED_DOT_DIRECTORIES.has(directoryName);
|
|
36193
|
+
const hasIgnoredPathSegment = (relativePath) => relativePath.split(/[/\\]/).slice(0, -1).some((segment) => isIgnoredDirectoryName(segment));
|
|
36194
|
+
const isLintableSourceFile = (filePath) => SOURCE_FILE_PATTERN.test(filePath) && !GENERATED_BUNDLE_FILE_PATTERN.test(filePath) && !GENERATED_SOURCE_DIRECTORY_PATTERN.test(filePath);
|
|
36159
36195
|
const isMinifiedSource = (absolutePath) => {
|
|
36160
36196
|
let fileDescriptor;
|
|
36161
36197
|
try {
|
|
@@ -36189,25 +36225,6 @@ const isLargeMinifiedFile = (absolutePath, knownSizeBytes) => {
|
|
|
36189
36225
|
cachedIsLargeMinifiedByPath.set(absolutePath, result);
|
|
36190
36226
|
return result;
|
|
36191
36227
|
};
|
|
36192
|
-
const isErrnoException = (error) => error instanceof Error && "code" in error;
|
|
36193
|
-
const IGNORABLE_READDIR_ERROR_CODES = new Set([
|
|
36194
|
-
"EACCES",
|
|
36195
|
-
"EPERM",
|
|
36196
|
-
"ENOENT",
|
|
36197
|
-
"ENOTDIR",
|
|
36198
|
-
"EINVAL",
|
|
36199
|
-
"ELOOP",
|
|
36200
|
-
"ENAMETOOLONG"
|
|
36201
|
-
]);
|
|
36202
|
-
const isIgnorableReaddirError = (error) => isErrnoException(error) && typeof error.code === "string" && IGNORABLE_READDIR_ERROR_CODES.has(error.code);
|
|
36203
|
-
const readDirectoryEntries = (directoryPath) => {
|
|
36204
|
-
try {
|
|
36205
|
-
return NFS.readdirSync(directoryPath, { withFileTypes: true });
|
|
36206
|
-
} catch (error) {
|
|
36207
|
-
if (isIgnorableReaddirError(error)) return [];
|
|
36208
|
-
throw error;
|
|
36209
|
-
}
|
|
36210
|
-
};
|
|
36211
36228
|
function* walkSourceTreeFiles(rootDirectory) {
|
|
36212
36229
|
const stack = [rootDirectory];
|
|
36213
36230
|
while (stack.length > 0) {
|
|
@@ -36216,7 +36233,7 @@ function* walkSourceTreeFiles(rootDirectory) {
|
|
|
36216
36233
|
for (const entry of readDirectoryEntries(currentDirectory)) {
|
|
36217
36234
|
const absolutePath = Path.join(currentDirectory, entry.name);
|
|
36218
36235
|
if (entry.isDirectory()) {
|
|
36219
|
-
if (!
|
|
36236
|
+
if (!isIgnoredDirectoryName(entry.name)) stack.push(absolutePath);
|
|
36220
36237
|
continue;
|
|
36221
36238
|
}
|
|
36222
36239
|
if (entry.isFile()) yield {
|
|
@@ -36262,23 +36279,6 @@ const listSourceFilesViaFilesystem = (rootDirectory) => {
|
|
|
36262
36279
|
const listSourceFilesWithSize = (rootDirectory) => collectSizedSourceFiles(rootDirectory, (listSourceFilesViaGit(rootDirectory) ?? listSourceFilesViaFilesystem(rootDirectory)).sort());
|
|
36263
36280
|
const listSourceFiles = (rootDirectory) => listSourceFilesWithSize(rootDirectory).map((entry) => entry.path);
|
|
36264
36281
|
const countSourceFiles = (rootDirectory) => listSourceFilesWithSize(rootDirectory).length;
|
|
36265
|
-
/**
|
|
36266
|
-
* The chain of ancestor directories of `startDirectory`, nearest first, up to
|
|
36267
|
-
* and including the filesystem root. With `includeStart`, the chain begins at
|
|
36268
|
-
* `startDirectory` itself; otherwise at its parent. The one place the "walk up
|
|
36269
|
-
* the tree" traversal is expressed, so each caller adds only its own match /
|
|
36270
|
-
* stop policy instead of re-deriving the `path.dirname` loop.
|
|
36271
|
-
*/
|
|
36272
|
-
const ancestorDirectories = (startDirectory, { includeStart }) => {
|
|
36273
|
-
const directories = [];
|
|
36274
|
-
let currentDirectory = includeStart ? startDirectory : Path.dirname(startDirectory);
|
|
36275
|
-
while (true) {
|
|
36276
|
-
directories.push(currentDirectory);
|
|
36277
|
-
const parentDirectory = Path.dirname(currentDirectory);
|
|
36278
|
-
if (parentDirectory === currentDirectory) return directories;
|
|
36279
|
-
currentDirectory = parentDirectory;
|
|
36280
|
-
}
|
|
36281
|
-
};
|
|
36282
36282
|
const cachedPackageJsons = /* @__PURE__ */ new Map();
|
|
36283
36283
|
const readPackageJsonUncached = (packageJsonPath) => {
|
|
36284
36284
|
try {
|
|
@@ -36309,7 +36309,11 @@ const isMonorepoRoot = (directory) => {
|
|
|
36309
36309
|
return Array.isArray(packageJson.workspaces) || Boolean(packageJson.workspaces?.packages);
|
|
36310
36310
|
};
|
|
36311
36311
|
const findMonorepoRoot = (startDirectory) => {
|
|
36312
|
-
|
|
36312
|
+
let currentDirectory = Path.dirname(startDirectory);
|
|
36313
|
+
while (currentDirectory !== Path.dirname(currentDirectory)) {
|
|
36314
|
+
if (isMonorepoRoot(currentDirectory)) return currentDirectory;
|
|
36315
|
+
currentDirectory = Path.dirname(currentDirectory);
|
|
36316
|
+
}
|
|
36313
36317
|
return null;
|
|
36314
36318
|
};
|
|
36315
36319
|
/**
|
|
@@ -36323,6 +36327,146 @@ const findMonorepoRoot = (startDirectory) => {
|
|
|
36323
36327
|
* byte-equivalent copy.
|
|
36324
36328
|
*/
|
|
36325
36329
|
const isProjectBoundary = (directory) => NFS.existsSync(Path.join(directory, ".git")) || isMonorepoRoot(directory);
|
|
36330
|
+
const TSCONFIG_FILENAME = "tsconfig.json";
|
|
36331
|
+
const isRelativeExtendsValue = (extendsValue) => extendsValue.startsWith("./") || extendsValue.startsWith("../") || Path.isAbsolute(extendsValue);
|
|
36332
|
+
const ensureJsonExtension = (filePath) => Path.extname(filePath) === "" ? `${filePath}.json` : filePath;
|
|
36333
|
+
const resolvePackageExtendsPath = (extendsValue, fromConfigDirectory) => {
|
|
36334
|
+
const requireFromConfig = createRequire(Path.join(fromConfigDirectory, "tsconfig.json"));
|
|
36335
|
+
const candidates = [
|
|
36336
|
+
extendsValue,
|
|
36337
|
+
ensureJsonExtension(extendsValue),
|
|
36338
|
+
`${extendsValue.replace(/\/$/, "")}/tsconfig.json`
|
|
36339
|
+
];
|
|
36340
|
+
for (const candidate of candidates) try {
|
|
36341
|
+
return requireFromConfig.resolve(candidate);
|
|
36342
|
+
} catch {
|
|
36343
|
+
continue;
|
|
36344
|
+
}
|
|
36345
|
+
return null;
|
|
36346
|
+
};
|
|
36347
|
+
const resolveExtendsPath = (extendsValue, fromConfigDirectory) => {
|
|
36348
|
+
if (isRelativeExtendsValue(extendsValue)) return ensureJsonExtension(Path.resolve(fromConfigDirectory, extendsValue));
|
|
36349
|
+
return resolvePackageExtendsPath(extendsValue, fromConfigDirectory);
|
|
36350
|
+
};
|
|
36351
|
+
const normalizeCompilerOptions = (compilerOptions) => {
|
|
36352
|
+
if (!isPlainObject(compilerOptions)) return { hasExplicitLib: false };
|
|
36353
|
+
const target = typeof compilerOptions.target === "string" ? compilerOptions.target : void 0;
|
|
36354
|
+
const hasExplicitLib = Object.hasOwn(compilerOptions, "lib");
|
|
36355
|
+
return {
|
|
36356
|
+
target,
|
|
36357
|
+
lib: Array.isArray(compilerOptions.lib) ? compilerOptions.lib.filter((entry) => typeof entry === "string") : void 0,
|
|
36358
|
+
hasExplicitLib
|
|
36359
|
+
};
|
|
36360
|
+
};
|
|
36361
|
+
const readTsConfig = (filePath) => {
|
|
36362
|
+
let content;
|
|
36363
|
+
try {
|
|
36364
|
+
content = NFS.readFileSync(filePath, "utf-8");
|
|
36365
|
+
} catch {
|
|
36366
|
+
return null;
|
|
36367
|
+
}
|
|
36368
|
+
const parsed = ts.parseConfigFileTextToJson(filePath, content);
|
|
36369
|
+
if (!isPlainObject(parsed.config)) return null;
|
|
36370
|
+
return {
|
|
36371
|
+
extends: typeof parsed.config.extends === "string" ? parsed.config.extends : void 0,
|
|
36372
|
+
referencePaths: normalizeReferencePaths(parsed.config.references),
|
|
36373
|
+
compilerOptions: normalizeCompilerOptions(parsed.config.compilerOptions)
|
|
36374
|
+
};
|
|
36375
|
+
};
|
|
36376
|
+
const normalizeReferencePaths = (references) => {
|
|
36377
|
+
if (!Array.isArray(references)) return [];
|
|
36378
|
+
return references.map((reference) => isPlainObject(reference) && typeof reference.path === "string" ? reference.path : null).filter((referencePath) => referencePath !== null);
|
|
36379
|
+
};
|
|
36380
|
+
const mergeCompilerOptions = (inherited, current) => {
|
|
36381
|
+
const target = current.target ?? inherited?.target;
|
|
36382
|
+
const hasExplicitLib = current.hasExplicitLib || Boolean(inherited?.hasExplicitLib);
|
|
36383
|
+
return {
|
|
36384
|
+
target,
|
|
36385
|
+
lib: current.hasExplicitLib ? current.lib : inherited?.lib,
|
|
36386
|
+
hasExplicitLib
|
|
36387
|
+
};
|
|
36388
|
+
};
|
|
36389
|
+
const readResolvedCompilerOptions = (tsConfigPath, extendsDepth, visitedPaths) => {
|
|
36390
|
+
const realPath = NFS.realpathSync.native(tsConfigPath);
|
|
36391
|
+
if (visitedPaths.has(realPath)) return null;
|
|
36392
|
+
const tsConfig = readTsConfig(realPath);
|
|
36393
|
+
if (!tsConfig) return null;
|
|
36394
|
+
const nextVisitedPaths = new Set(visitedPaths);
|
|
36395
|
+
nextVisitedPaths.add(realPath);
|
|
36396
|
+
if (tsConfig.extends && extendsDepth < 8) {
|
|
36397
|
+
const parentPath = resolveExtendsPath(tsConfig.extends, Path.dirname(realPath));
|
|
36398
|
+
if (parentPath && isFile(parentPath)) return mergeCompilerOptions(readResolvedCompilerOptions(parentPath, extendsDepth + 1, nextVisitedPaths), tsConfig.compilerOptions);
|
|
36399
|
+
}
|
|
36400
|
+
return tsConfig.compilerOptions;
|
|
36401
|
+
};
|
|
36402
|
+
const targetYearIsPreES2023 = (target) => {
|
|
36403
|
+
const year = ES_TARGET_YEAR_BY_NAME[target.toLowerCase()];
|
|
36404
|
+
return year !== void 0 && year < 2023;
|
|
36405
|
+
};
|
|
36406
|
+
const libEntryIncludesES2023Array = (entry) => {
|
|
36407
|
+
const normalizedEntry = entry.toLowerCase();
|
|
36408
|
+
if (normalizedEntry === "esnext" || normalizedEntry === "esnext.array") return true;
|
|
36409
|
+
const esYearMatch = /^es(\d{4})(?:\.(.+))?$/.exec(normalizedEntry);
|
|
36410
|
+
if (!esYearMatch) return false;
|
|
36411
|
+
if (Number(esYearMatch[1]) < 2023) return false;
|
|
36412
|
+
const component = esYearMatch[2];
|
|
36413
|
+
return component === void 0 || component === "array";
|
|
36414
|
+
};
|
|
36415
|
+
const libIncludesES2023 = (lib) => lib.some(libEntryIncludesES2023Array);
|
|
36416
|
+
const compilerOptionsArePreES2023 = (compilerOptions) => {
|
|
36417
|
+
if (compilerOptions.target) return targetYearIsPreES2023(compilerOptions.target);
|
|
36418
|
+
if (compilerOptions.hasExplicitLib) return !libIncludesES2023(compilerOptions.lib ?? []);
|
|
36419
|
+
return false;
|
|
36420
|
+
};
|
|
36421
|
+
const compilerOptionsDeclareTargetOrLib = (compilerOptions) => compilerOptions.hasExplicitLib || compilerOptions.target !== void 0;
|
|
36422
|
+
const detectPreES2023FromConfig = (tsConfigPath, visitedConfigPaths = /* @__PURE__ */ new Set()) => {
|
|
36423
|
+
if (visitedConfigPaths.has(tsConfigPath)) return false;
|
|
36424
|
+
const compilerOptions = readResolvedCompilerOptions(tsConfigPath, 0, /* @__PURE__ */ new Set());
|
|
36425
|
+
if (!compilerOptions) return false;
|
|
36426
|
+
if (!compilerOptionsDeclareTargetOrLib(compilerOptions)) {
|
|
36427
|
+
const tsConfig = readTsConfig(tsConfigPath);
|
|
36428
|
+
if (!tsConfig) return false;
|
|
36429
|
+
const nextVisitedConfigPaths = new Set(visitedConfigPaths);
|
|
36430
|
+
nextVisitedConfigPaths.add(tsConfigPath);
|
|
36431
|
+
const configDirectory = Path.dirname(tsConfigPath);
|
|
36432
|
+
return tsConfig.referencePaths.some((referencePath) => {
|
|
36433
|
+
const resolvedReferencePath = Path.resolve(configDirectory, referencePath);
|
|
36434
|
+
const referencedConfigPath = isFile(resolvedReferencePath) ? resolvedReferencePath : Path.join(resolvedReferencePath, TSCONFIG_FILENAME);
|
|
36435
|
+
return isFile(referencedConfigPath) && detectPreES2023FromConfig(referencedConfigPath, nextVisitedConfigPaths);
|
|
36436
|
+
});
|
|
36437
|
+
}
|
|
36438
|
+
return compilerOptionsArePreES2023(compilerOptions);
|
|
36439
|
+
};
|
|
36440
|
+
const detectPreES2023Target = (directory) => {
|
|
36441
|
+
const tsConfigPath = Path.join(directory, TSCONFIG_FILENAME);
|
|
36442
|
+
if (isFile(tsConfigPath)) return detectPreES2023FromConfig(tsConfigPath);
|
|
36443
|
+
for (const fallbackFilename of FALLBACK_TSCONFIG_FILENAMES) {
|
|
36444
|
+
const fallbackPath = Path.join(directory, fallbackFilename);
|
|
36445
|
+
if (isFile(fallbackPath)) return detectPreES2023FromConfig(fallbackPath);
|
|
36446
|
+
}
|
|
36447
|
+
return false;
|
|
36448
|
+
};
|
|
36449
|
+
const FALLBACK_TSCONFIG_FILENAMES = ["tsconfig.app.json", "tsconfig.build.json"];
|
|
36450
|
+
const FRAMEWORK_PACKAGES = {
|
|
36451
|
+
next: "nextjs",
|
|
36452
|
+
"@tanstack/react-start": "tanstack-start",
|
|
36453
|
+
vite: "vite",
|
|
36454
|
+
"react-scripts": "cra",
|
|
36455
|
+
"@remix-run/react": "remix",
|
|
36456
|
+
gatsby: "gatsby",
|
|
36457
|
+
expo: "expo",
|
|
36458
|
+
"react-native": "react-native"
|
|
36459
|
+
};
|
|
36460
|
+
const detectFramework = (dependencies) => {
|
|
36461
|
+
for (const [packageName, frameworkName] of Object.entries(FRAMEWORK_PACKAGES)) if (dependencies[packageName]) return frameworkName;
|
|
36462
|
+
if (dependencies.preact && !dependencies.react) return "preact";
|
|
36463
|
+
return "unknown";
|
|
36464
|
+
};
|
|
36465
|
+
const MOBILE_FRAMEWORKS = new Set(["expo", "react-native"]);
|
|
36466
|
+
const frameworkMergeRank = (framework) => {
|
|
36467
|
+
if (framework === "unknown") return 3;
|
|
36468
|
+
return MOBILE_FRAMEWORKS.has(framework) ? 2 : 1;
|
|
36469
|
+
};
|
|
36326
36470
|
const REACT_COMPILER_PACKAGES = new Set([
|
|
36327
36471
|
"babel-plugin-react-compiler",
|
|
36328
36472
|
"react-compiler-runtime",
|
|
@@ -36359,6 +36503,7 @@ const EXPO_APP_CONFIG_FILENAMES = [
|
|
|
36359
36503
|
];
|
|
36360
36504
|
const REACT_COMPILER_PACKAGE_REFERENCE_PATTERN = /babel-plugin-react-compiler|react-compiler-runtime|eslint-plugin-react-compiler|["']react-compiler["']/;
|
|
36361
36505
|
const REACT_COMPILER_ENABLED_FLAG_PATTERN = /["']?reactCompiler["']?\s*:\s*(?:true\b|\{)/;
|
|
36506
|
+
const STATIC_EXPORT_OUTPUT_PATTERN = /(?:^|[^.\w])["']?output["']?\s*:\s*["']export["']/m;
|
|
36362
36507
|
const hasCompilerPackage = (packageJson) => {
|
|
36363
36508
|
const allDependencies = {
|
|
36364
36509
|
...packageJson.peerDependencies,
|
|
@@ -36391,34 +36536,10 @@ const detectReactCompiler = (directory, packageJson) => {
|
|
|
36391
36536
|
}
|
|
36392
36537
|
return false;
|
|
36393
36538
|
};
|
|
36394
|
-
const
|
|
36395
|
-
|
|
36396
|
-
|
|
36397
|
-
|
|
36398
|
-
"react-scripts": "cra",
|
|
36399
|
-
"@remix-run/react": "remix",
|
|
36400
|
-
gatsby: "gatsby",
|
|
36401
|
-
expo: "expo",
|
|
36402
|
-
"react-native": "react-native"
|
|
36403
|
-
};
|
|
36404
|
-
const FRAMEWORK_DISPLAY_NAMES = {
|
|
36405
|
-
nextjs: "Next.js",
|
|
36406
|
-
"tanstack-start": "TanStack Start",
|
|
36407
|
-
vite: "Vite",
|
|
36408
|
-
cra: "Create React App",
|
|
36409
|
-
remix: "Remix",
|
|
36410
|
-
gatsby: "Gatsby",
|
|
36411
|
-
expo: "Expo",
|
|
36412
|
-
"react-native": "React Native",
|
|
36413
|
-
preact: "Preact",
|
|
36414
|
-
unknown: "React"
|
|
36415
|
-
};
|
|
36416
|
-
const formatFrameworkName = (framework) => FRAMEWORK_DISPLAY_NAMES[framework];
|
|
36417
|
-
const detectFramework = (dependencies) => {
|
|
36418
|
-
for (const [packageName, frameworkName] of Object.entries(FRAMEWORK_PACKAGES)) if (dependencies[packageName]) return frameworkName;
|
|
36419
|
-
if (dependencies.preact && !dependencies.react) return "preact";
|
|
36420
|
-
return "unknown";
|
|
36421
|
-
};
|
|
36539
|
+
const detectNextjsStaticExport = (directory) => NEXT_CONFIG_FILENAMES.some((filename) => {
|
|
36540
|
+
const filePath = Path.join(directory, filename);
|
|
36541
|
+
return isFile(filePath) && STATIC_EXPORT_OUTPUT_PATTERN.test(NFS.readFileSync(filePath, "utf-8"));
|
|
36542
|
+
});
|
|
36422
36543
|
const UNRESOLVABLE_PROTOCOL_VERSION = /^(?:file|git|github|https?|link|patch|portal|workspace|npm):/i;
|
|
36423
36544
|
const DIST_TAG_VERSION = /^[a-z][a-z0-9._-]*$/i;
|
|
36424
36545
|
const WILDCARD_VERSION = /^[*xX](?:\.[*xX])*$/;
|
|
@@ -36556,10 +36677,76 @@ const isConcreteDependencyVersion = (version) => {
|
|
|
36556
36677
|
const normalizedVersion = normalizeDependencyVersion(version);
|
|
36557
36678
|
return normalizedVersion !== null && /\d/.test(normalizedVersion);
|
|
36558
36679
|
};
|
|
36559
|
-
const
|
|
36560
|
-
if (
|
|
36561
|
-
|
|
36562
|
-
return
|
|
36680
|
+
const isMajorMinorAtLeast = (detected, required) => {
|
|
36681
|
+
if (detected === null) return true;
|
|
36682
|
+
if (detected.major !== required.major) return detected.major > required.major;
|
|
36683
|
+
return detected.minor >= required.minor;
|
|
36684
|
+
};
|
|
36685
|
+
const parseReactMajor = (reactVersion) => {
|
|
36686
|
+
if (typeof reactVersion !== "string") return null;
|
|
36687
|
+
return getLowestDependencyMajor(reactVersion);
|
|
36688
|
+
};
|
|
36689
|
+
const MAJOR_MINOR_PATTERN = /(\d{1,4})\.(\d{1,4})/;
|
|
36690
|
+
const MAJOR_ONLY_PATTERN = /(\d{1,4})/;
|
|
36691
|
+
const UPPER_BOUND_COMPARATOR_PATTERN = /<=?\s{0,8}\d{1,4}(?:\.\d{1,4}){0,2}(?:-[^\s,|]+)?/g;
|
|
36692
|
+
const parseReactMajorMinor = (reactVersion) => {
|
|
36693
|
+
if (typeof reactVersion !== "string") return null;
|
|
36694
|
+
const trimmed = reactVersion.trim();
|
|
36695
|
+
if (trimmed.length === 0) return null;
|
|
36696
|
+
const lowerBoundsOnly = trimmed.replace(UPPER_BOUND_COMPARATOR_PATTERN, " ").trim();
|
|
36697
|
+
if (lowerBoundsOnly.length === 0) return null;
|
|
36698
|
+
const majorMinorMatch = lowerBoundsOnly.match(MAJOR_MINOR_PATTERN);
|
|
36699
|
+
if (majorMinorMatch) {
|
|
36700
|
+
const major = Number.parseInt(majorMinorMatch[1], 10);
|
|
36701
|
+
const minor = Number.parseInt(majorMinorMatch[2], 10);
|
|
36702
|
+
if (!Number.isFinite(major) || major <= 0) return null;
|
|
36703
|
+
if (!Number.isFinite(minor) || minor < 0) return null;
|
|
36704
|
+
return {
|
|
36705
|
+
major,
|
|
36706
|
+
minor
|
|
36707
|
+
};
|
|
36708
|
+
}
|
|
36709
|
+
const majorOnlyMatch = lowerBoundsOnly.match(MAJOR_ONLY_PATTERN);
|
|
36710
|
+
if (!majorOnlyMatch) return null;
|
|
36711
|
+
const major = Number.parseInt(majorOnlyMatch[1], 10);
|
|
36712
|
+
if (!Number.isFinite(major) || major <= 0) return null;
|
|
36713
|
+
return {
|
|
36714
|
+
major,
|
|
36715
|
+
minor: 0
|
|
36716
|
+
};
|
|
36717
|
+
};
|
|
36718
|
+
const parseLowerBoundVersion = (versionSpec) => import_semver.validRange(versionSpec) !== null ? import_semver.minVersion(versionSpec) : import_semver.coerce(versionSpec);
|
|
36719
|
+
const parseTailwindMajorMinor = (tailwindVersion) => {
|
|
36720
|
+
if (typeof tailwindVersion !== "string") return null;
|
|
36721
|
+
const trimmed = tailwindVersion.trim();
|
|
36722
|
+
if (trimmed.length === 0) return null;
|
|
36723
|
+
const lowerBound = parseLowerBoundVersion(trimmed);
|
|
36724
|
+
if (lowerBound === null || lowerBound.major <= 0) return null;
|
|
36725
|
+
return {
|
|
36726
|
+
major: lowerBound.major,
|
|
36727
|
+
minor: lowerBound.minor
|
|
36728
|
+
};
|
|
36729
|
+
};
|
|
36730
|
+
const hasUpperBoundOnlyPeerRange = (range) => {
|
|
36731
|
+
if (typeof range !== "string") return false;
|
|
36732
|
+
const normalizedRange = normalizeDependencyVersion(range);
|
|
36733
|
+
if (normalizedRange === null) return false;
|
|
36734
|
+
return splitDependencyVersionBranches(normalizedRange).some((branch) => {
|
|
36735
|
+
const normalizedBranch = normalizeDependencyVersion(branch);
|
|
36736
|
+
return normalizedBranch !== null && getBranchLowestMajor(normalizedBranch) === null && hasUpperBoundComparator(normalizedBranch);
|
|
36737
|
+
});
|
|
36738
|
+
};
|
|
36739
|
+
const peerRangeMinMajor = (range) => {
|
|
36740
|
+
if (typeof range !== "string") return null;
|
|
36741
|
+
return getLowestDependencyMajor(range);
|
|
36742
|
+
};
|
|
36743
|
+
const resolveEffectiveReactMajor = (reactVersion, packageJson) => {
|
|
36744
|
+
const installedReactMajor = parseReactMajor(reactVersion);
|
|
36745
|
+
const peerReactRange = packageJson.peerDependencies?.react;
|
|
36746
|
+
if (typeof peerReactRange !== "string") return installedReactMajor;
|
|
36747
|
+
const peerFloor = peerRangeMinMajor(peerReactRange);
|
|
36748
|
+
if (peerFloor === null) return hasUpperBoundOnlyPeerRange(peerReactRange) ? null : installedReactMajor;
|
|
36749
|
+
return installedReactMajor !== null ? Math.min(installedReactMajor, peerFloor) : peerFloor;
|
|
36563
36750
|
};
|
|
36564
36751
|
const isCatalogReference = (version) => version.startsWith("catalog:");
|
|
36565
36752
|
const extractCatalogName = (version) => {
|
|
@@ -36701,6 +36888,16 @@ const resolveCatalogVersion = (packageJson, packageName, rootDirectory, explicit
|
|
|
36701
36888
|
}
|
|
36702
36889
|
return null;
|
|
36703
36890
|
};
|
|
36891
|
+
const REACT_SECTIONS = [
|
|
36892
|
+
"dependencies",
|
|
36893
|
+
"peerDependencies",
|
|
36894
|
+
"devDependencies"
|
|
36895
|
+
];
|
|
36896
|
+
const TAILWIND_ZOD_SECTIONS = [
|
|
36897
|
+
"dependencies",
|
|
36898
|
+
"devDependencies",
|
|
36899
|
+
"peerDependencies"
|
|
36900
|
+
];
|
|
36704
36901
|
const EMPTY_DEPENDENCY_INFO = {
|
|
36705
36902
|
reactVersion: null,
|
|
36706
36903
|
tailwindVersion: null,
|
|
@@ -36741,6 +36938,10 @@ const extractDependencyInfo = (packageJson) => {
|
|
|
36741
36938
|
framework: detectFramework(allDependencies)
|
|
36742
36939
|
};
|
|
36743
36940
|
};
|
|
36941
|
+
const getDependencySpec = (packageJson, packageName) => {
|
|
36942
|
+
const spec = packageJson.dependencies?.[packageName] ?? packageJson.devDependencies?.[packageName] ?? packageJson.peerDependencies?.[packageName] ?? packageJson.optionalDependencies?.[packageName];
|
|
36943
|
+
return typeof spec === "string" ? spec : null;
|
|
36944
|
+
};
|
|
36744
36945
|
const getDependencyDeclaration = ({ packageJson, packageName, sections }) => {
|
|
36745
36946
|
for (const section of sections) {
|
|
36746
36947
|
const version = packageJson[section]?.[packageName];
|
|
@@ -36757,203 +36958,67 @@ const getDependencyDeclaration = ({ packageJson, packageName, sections }) => {
|
|
|
36757
36958
|
version: null
|
|
36758
36959
|
};
|
|
36759
36960
|
};
|
|
36760
|
-
const
|
|
36761
|
-
|
|
36762
|
-
|
|
36763
|
-
|
|
36764
|
-
|
|
36765
|
-
|
|
36766
|
-
|
|
36767
|
-
const
|
|
36768
|
-
|
|
36769
|
-
|
|
36770
|
-
|
|
36771
|
-
|
|
36772
|
-
|
|
36773
|
-
if (!isFile(Path.join(rootDirectory, "nx.json"))) return [];
|
|
36774
|
-
const collected = [];
|
|
36775
|
-
for (const candidate of NX_PROJECT_DISCOVERY_DIRS) {
|
|
36776
|
-
const candidatePath = Path.join(rootDirectory, candidate);
|
|
36777
|
-
if (!isDirectory(candidatePath)) continue;
|
|
36778
|
-
for (const entry of readDirectoryEntries(candidatePath)) {
|
|
36779
|
-
if (!entry.isDirectory()) continue;
|
|
36780
|
-
const projectDirectory = Path.join(candidatePath, entry.name);
|
|
36781
|
-
if (isFile(Path.join(projectDirectory, "project.json")) || isFile(Path.join(projectDirectory, "package.json"))) collected.push(`${candidate}/${entry.name}`);
|
|
36782
|
-
}
|
|
36783
|
-
}
|
|
36784
|
-
return collected;
|
|
36785
|
-
};
|
|
36786
|
-
const parsePnpmWorkspacePatterns = (rootDirectory) => {
|
|
36787
|
-
const workspacePath = Path.join(rootDirectory, "pnpm-workspace.yaml");
|
|
36788
|
-
if (!isFile(workspacePath)) return [];
|
|
36789
|
-
const content = NFS.readFileSync(workspacePath, "utf-8");
|
|
36790
|
-
const patterns = [];
|
|
36791
|
-
let isInsidePackagesBlock = false;
|
|
36792
|
-
for (const line of content.split("\n")) {
|
|
36793
|
-
const trimmed = line.trim();
|
|
36794
|
-
if (trimmed === "packages:") {
|
|
36795
|
-
isInsidePackagesBlock = true;
|
|
36796
|
-
continue;
|
|
36797
|
-
}
|
|
36798
|
-
if (isInsidePackagesBlock && trimmed.startsWith("-")) patterns.push(trimmed.replace(/^-\s*/, "").replace(/["']/g, ""));
|
|
36799
|
-
else if (isInsidePackagesBlock && trimmed.length > 0 && !trimmed.startsWith("#")) isInsidePackagesBlock = false;
|
|
36800
|
-
}
|
|
36801
|
-
return patterns;
|
|
36802
|
-
};
|
|
36803
|
-
const getWorkspacePatterns = (rootDirectory, packageJson) => {
|
|
36804
|
-
const pnpmPatterns = parsePnpmWorkspacePatterns(rootDirectory);
|
|
36805
|
-
if (pnpmPatterns.length > 0) return pnpmPatterns;
|
|
36806
|
-
if (Array.isArray(packageJson.workspaces)) return packageJson.workspaces;
|
|
36807
|
-
if (packageJson.workspaces?.packages) return packageJson.workspaces.packages;
|
|
36808
|
-
const nxPatterns = getNxWorkspaceDirectories(rootDirectory);
|
|
36809
|
-
if (nxPatterns.length > 0) return nxPatterns;
|
|
36810
|
-
return [];
|
|
36811
|
-
};
|
|
36812
|
-
const parseReactMajor = (reactVersion) => {
|
|
36813
|
-
if (typeof reactVersion !== "string") return null;
|
|
36814
|
-
return getLowestDependencyMajor(reactVersion);
|
|
36961
|
+
const REACT_DEPENDENCY_NAMES = new Set([
|
|
36962
|
+
"react",
|
|
36963
|
+
"react-native",
|
|
36964
|
+
"next",
|
|
36965
|
+
"preact"
|
|
36966
|
+
]);
|
|
36967
|
+
const hasReactDependency = (packageJson) => {
|
|
36968
|
+
const allDependencies = {
|
|
36969
|
+
...packageJson.peerDependencies,
|
|
36970
|
+
...packageJson.dependencies,
|
|
36971
|
+
...packageJson.devDependencies
|
|
36972
|
+
};
|
|
36973
|
+
return Object.keys(allDependencies).some((packageName) => REACT_DEPENDENCY_NAMES.has(packageName));
|
|
36815
36974
|
};
|
|
36816
|
-
const
|
|
36817
|
-
|
|
36818
|
-
|
|
36819
|
-
|
|
36820
|
-
|
|
36821
|
-
|
|
36822
|
-
}
|
|
36823
|
-
const wildcardIndex = cleanPattern.indexOf("*");
|
|
36824
|
-
const baseDirectory = Path.join(rootDirectory, cleanPattern.slice(0, wildcardIndex));
|
|
36825
|
-
const suffixAfterWildcard = cleanPattern.slice(wildcardIndex + 1);
|
|
36826
|
-
if (!isDirectory(baseDirectory)) return [];
|
|
36827
|
-
const resolved = [];
|
|
36828
|
-
for (const entry of readDirectoryEntries(baseDirectory)) {
|
|
36829
|
-
const entryPath = Path.join(baseDirectory, entry.name, suffixAfterWildcard);
|
|
36830
|
-
if (isDirectory(entryPath) && isFile(Path.join(entryPath, "package.json"))) resolved.push(entryPath);
|
|
36831
|
-
}
|
|
36832
|
-
return resolved;
|
|
36975
|
+
const getPreactVersion = (packageJson) => {
|
|
36976
|
+
return {
|
|
36977
|
+
...packageJson.peerDependencies,
|
|
36978
|
+
...packageJson.dependencies,
|
|
36979
|
+
...packageJson.devDependencies
|
|
36980
|
+
}.preact ?? null;
|
|
36833
36981
|
};
|
|
36834
|
-
const
|
|
36835
|
-
|
|
36836
|
-
|
|
36837
|
-
|
|
36838
|
-
|
|
36839
|
-
|
|
36840
|
-
|
|
36841
|
-
|
|
36982
|
+
const TANSTACK_QUERY_PACKAGES = new Set([
|
|
36983
|
+
"@tanstack/react-query",
|
|
36984
|
+
"@tanstack/query-core",
|
|
36985
|
+
"react-query"
|
|
36986
|
+
]);
|
|
36987
|
+
const hasTanStackQuery = (packageJson) => {
|
|
36988
|
+
const allDependencies = {
|
|
36989
|
+
...packageJson.peerDependencies,
|
|
36990
|
+
...packageJson.dependencies,
|
|
36991
|
+
...packageJson.devDependencies
|
|
36992
|
+
};
|
|
36993
|
+
return Object.keys(allDependencies).some((packageName) => TANSTACK_QUERY_PACKAGES.has(packageName));
|
|
36842
36994
|
};
|
|
36843
|
-
const
|
|
36844
|
-
if (!
|
|
36845
|
-
const
|
|
36846
|
-
const
|
|
36847
|
-
if (
|
|
36848
|
-
|
|
36849
|
-
|
|
36995
|
+
const resolveCatalogBackedDependencyVersion = ({ rootDirectory, rootPackageJson, packageName, version }) => {
|
|
36996
|
+
if (version === null || !isCatalogReference(version)) return version;
|
|
36997
|
+
const catalogName = extractCatalogName(version);
|
|
36998
|
+
const resolvedLocalVersion = resolveCatalogVersion(rootPackageJson, packageName, rootDirectory, catalogName);
|
|
36999
|
+
if (resolvedLocalVersion) return resolvedLocalVersion;
|
|
37000
|
+
const monorepoRoot = findMonorepoRoot(rootDirectory);
|
|
37001
|
+
if (!monorepoRoot) return version;
|
|
37002
|
+
const monorepoPackageJsonPath = Path.join(monorepoRoot, "package.json");
|
|
37003
|
+
if (!isFile(monorepoPackageJsonPath)) return version;
|
|
37004
|
+
return resolveCatalogVersion(readPackageJson$1(monorepoPackageJsonPath), packageName, monorepoRoot, catalogName) ?? version;
|
|
36850
37005
|
};
|
|
36851
|
-
|
|
36852
|
-
|
|
36853
|
-
|
|
36854
|
-
|
|
36855
|
-
|
|
36856
|
-
|
|
36857
|
-
|
|
36858
|
-
|
|
36859
|
-
|
|
36860
|
-
|
|
36861
|
-
|
|
36862
|
-
|
|
36863
|
-
|
|
36864
|
-
|
|
36865
|
-
|
|
36866
|
-
"peerDependencies",
|
|
36867
|
-
"devDependencies"
|
|
36868
|
-
],
|
|
36869
|
-
workspaceDirectory,
|
|
36870
|
-
workspacePackageJson
|
|
36871
|
-
});
|
|
36872
|
-
const tailwindVersion = resolveWorkspaceDependencyVersion({
|
|
36873
|
-
concreteVersion: info.tailwindVersion,
|
|
36874
|
-
packageName: "tailwindcss",
|
|
36875
|
-
rootDirectory,
|
|
36876
|
-
rootPackageJson: packageJson,
|
|
36877
|
-
sections: [
|
|
36878
|
-
"dependencies",
|
|
36879
|
-
"devDependencies",
|
|
36880
|
-
"peerDependencies"
|
|
36881
|
-
],
|
|
36882
|
-
workspaceDirectory,
|
|
36883
|
-
workspacePackageJson
|
|
36884
|
-
});
|
|
36885
|
-
const zodVersion = resolveWorkspaceDependencyVersion({
|
|
36886
|
-
concreteVersion: info.zodVersion,
|
|
36887
|
-
packageName: "zod",
|
|
36888
|
-
rootDirectory,
|
|
36889
|
-
rootPackageJson: packageJson,
|
|
36890
|
-
sections: [
|
|
36891
|
-
"dependencies",
|
|
36892
|
-
"devDependencies",
|
|
36893
|
-
"peerDependencies"
|
|
36894
|
-
],
|
|
36895
|
-
workspaceDirectory,
|
|
36896
|
-
workspacePackageJson
|
|
36897
|
-
});
|
|
36898
|
-
if (reactVersion && shouldReplaceReactVersion(result.reactVersion, reactVersion)) result.reactVersion = reactVersion;
|
|
36899
|
-
if (tailwindVersion && !result.tailwindVersion) result.tailwindVersion = tailwindVersion;
|
|
36900
|
-
if (zodVersion && !result.zodVersion) result.zodVersion = zodVersion;
|
|
36901
|
-
if (info.framework !== "unknown" && result.framework === "unknown") result.framework = info.framework;
|
|
36902
|
-
const resultReactMajor = parseReactMajor(result.reactVersion);
|
|
36903
|
-
if (result.reactVersion && result.tailwindVersion && result.framework !== "unknown" && resultReactMajor !== null && resultReactMajor <= 17) return result;
|
|
36904
|
-
}
|
|
37006
|
+
/**
|
|
37007
|
+
* The chain of ancestor directories of `startDirectory`, nearest first, up to
|
|
37008
|
+
* and including the filesystem root. With `includeStart`, the chain begins at
|
|
37009
|
+
* `startDirectory` itself; otherwise at its parent. The one place the "walk up
|
|
37010
|
+
* the tree" traversal is expressed, so each caller adds only its own match /
|
|
37011
|
+
* stop policy instead of re-deriving the `path.dirname` loop.
|
|
37012
|
+
*/
|
|
37013
|
+
const ancestorDirectories = (startDirectory, { includeStart }) => {
|
|
37014
|
+
const directories = [];
|
|
37015
|
+
let currentDirectory = includeStart ? startDirectory : Path.dirname(startDirectory);
|
|
37016
|
+
while (true) {
|
|
37017
|
+
directories.push(currentDirectory);
|
|
37018
|
+
const parentDirectory = Path.dirname(currentDirectory);
|
|
37019
|
+
if (parentDirectory === currentDirectory) return directories;
|
|
37020
|
+
currentDirectory = parentDirectory;
|
|
36905
37021
|
}
|
|
36906
|
-
return result;
|
|
36907
|
-
};
|
|
36908
|
-
const findDependencyInfoFromMonorepoRoot = (directory) => {
|
|
36909
|
-
const monorepoRoot = findMonorepoRoot(directory);
|
|
36910
|
-
if (!monorepoRoot) return EMPTY_DEPENDENCY_INFO;
|
|
36911
|
-
const monorepoPackageJsonPath = Path.join(monorepoRoot, "package.json");
|
|
36912
|
-
if (!isFile(monorepoPackageJsonPath)) return EMPTY_DEPENDENCY_INFO;
|
|
36913
|
-
const rootPackageJson = readPackageJson$1(monorepoPackageJsonPath);
|
|
36914
|
-
const rootInfo = extractDependencyInfo(rootPackageJson);
|
|
36915
|
-
const leafPackageJsonPath = Path.join(directory, "package.json");
|
|
36916
|
-
const leafPackageJson = isFile(leafPackageJsonPath) ? readPackageJson$1(leafPackageJsonPath) : null;
|
|
36917
|
-
const leafReactDeclaration = leafPackageJson ? getDependencyDeclaration({
|
|
36918
|
-
packageJson: leafPackageJson,
|
|
36919
|
-
packageName: "react",
|
|
36920
|
-
sections: [
|
|
36921
|
-
"dependencies",
|
|
36922
|
-
"peerDependencies",
|
|
36923
|
-
"devDependencies"
|
|
36924
|
-
]
|
|
36925
|
-
}) : null;
|
|
36926
|
-
const leafTailwindDeclaration = leafPackageJson ? getDependencyDeclaration({
|
|
36927
|
-
packageJson: leafPackageJson,
|
|
36928
|
-
packageName: "tailwindcss",
|
|
36929
|
-
sections: [
|
|
36930
|
-
"dependencies",
|
|
36931
|
-
"devDependencies",
|
|
36932
|
-
"peerDependencies"
|
|
36933
|
-
]
|
|
36934
|
-
}) : null;
|
|
36935
|
-
const leafZodDeclaration = leafPackageJson ? getDependencyDeclaration({
|
|
36936
|
-
packageJson: leafPackageJson,
|
|
36937
|
-
packageName: "zod",
|
|
36938
|
-
sections: [
|
|
36939
|
-
"dependencies",
|
|
36940
|
-
"devDependencies",
|
|
36941
|
-
"peerDependencies"
|
|
36942
|
-
]
|
|
36943
|
-
}) : null;
|
|
36944
|
-
const shouldUseReactFallback = !leafReactDeclaration?.hasDeclaration;
|
|
36945
|
-
const shouldUseTailwindFallback = leafTailwindDeclaration?.hasDeclaration ?? true;
|
|
36946
|
-
const shouldUseZodFallback = leafZodDeclaration?.hasDeclaration ?? true;
|
|
36947
|
-
const reactCatalogVersion = shouldUseReactFallback ? resolveCatalogVersion(rootPackageJson, "react", monorepoRoot, leafReactDeclaration?.catalogReference) : null;
|
|
36948
|
-
const tailwindCatalogVersion = shouldUseTailwindFallback ? resolveCatalogVersion(rootPackageJson, "tailwindcss", monorepoRoot, leafTailwindDeclaration?.catalogReference) : null;
|
|
36949
|
-
const zodCatalogVersion = shouldUseZodFallback ? resolveCatalogVersion(rootPackageJson, "zod", monorepoRoot, leafZodDeclaration?.catalogReference) : null;
|
|
36950
|
-
const workspaceInfo = findReactInWorkspaces(monorepoRoot, rootPackageJson);
|
|
36951
|
-
return {
|
|
36952
|
-
reactVersion: shouldUseReactFallback ? reactCatalogVersion ?? rootInfo.reactVersion ?? workspaceInfo.reactVersion : rootInfo.reactVersion ?? workspaceInfo.reactVersion,
|
|
36953
|
-
tailwindVersion: shouldUseTailwindFallback ? tailwindCatalogVersion ?? rootInfo.tailwindVersion ?? workspaceInfo.tailwindVersion : null,
|
|
36954
|
-
zodVersion: shouldUseZodFallback ? zodCatalogVersion ?? rootInfo.zodVersion ?? workspaceInfo.zodVersion : null,
|
|
36955
|
-
framework: rootInfo.framework !== "unknown" ? rootInfo.framework : workspaceInfo.framework
|
|
36956
|
-
};
|
|
36957
37022
|
};
|
|
36958
37023
|
/**
|
|
36959
37024
|
* Walk up from `startDirectory` to the nearest ancestor that owns a
|
|
@@ -36978,69 +37043,6 @@ const findNearestAncestorPackageJson = (startDirectory) => {
|
|
|
36978
37043
|
}
|
|
36979
37044
|
return null;
|
|
36980
37045
|
};
|
|
36981
|
-
/**
|
|
36982
|
-
* Last-resort React detection: locate React the way Node's `node_modules`
|
|
36983
|
-
* resolution would from `directory` — check each `node_modules/react` up the
|
|
36984
|
-
* tree — but bounded to the enclosing repo so a globally installed React can't
|
|
36985
|
-
* masquerade as the project's version. Stops at (and including) the nearest
|
|
36986
|
-
* project boundary — the git root or a monorepo root — and returns the first
|
|
36987
|
-
* installed `version`.
|
|
36988
|
-
*
|
|
36989
|
-
* Makes "React is installed and importable" ⇒ "React is detected" an invariant
|
|
36990
|
-
* for packages whose only React declaration is a version-less spec
|
|
36991
|
-
* (`workspace:*`, `*`, a dist-tag) or where React lives solely in a hoisted
|
|
36992
|
-
* `node_modules` the declaration walks never reach — the profile of a component
|
|
36993
|
-
* package inside a monorepo. Matching the `node_modules/react` entry rather than
|
|
36994
|
-
* its realpath means a project whose `node_modules` is symlinked elsewhere (a
|
|
36995
|
-
* Docker volume, a shared store) still counts, while a global install outside
|
|
36996
|
-
* the repo does not.
|
|
36997
|
-
*
|
|
36998
|
-
* Returns `null` when React isn't installed within the repo or its package.json
|
|
36999
|
-
* carries no version string. A tree with no boundary marker at all bounds the
|
|
37000
|
-
* search to `directory`, so a dependency hoisted above the scanned package there
|
|
37001
|
-
* is conservatively not adopted.
|
|
37002
|
-
*/
|
|
37003
|
-
/**
|
|
37004
|
-
* The nearest enclosing project boundary (git root or monorepo root), or
|
|
37005
|
-
* `directory` itself when the scan target sits outside any repo. Bounds the
|
|
37006
|
-
* node_modules walk so a React hoisted anywhere inside the repo counts, but a
|
|
37007
|
-
* React in an ancestor above the repo — a home-directory or global
|
|
37008
|
-
* `node_modules` — can't leak in.
|
|
37009
|
-
*/
|
|
37010
|
-
const findContainmentRoot = (directory) => {
|
|
37011
|
-
for (const ancestorDirectory of ancestorDirectories(directory, { includeStart: true })) if (isProjectBoundary(ancestorDirectory)) return ancestorDirectory;
|
|
37012
|
-
return directory;
|
|
37013
|
-
};
|
|
37014
|
-
const resolveInstalledReactVersion = (directory) => {
|
|
37015
|
-
const containmentRoot = findContainmentRoot(directory);
|
|
37016
|
-
for (const ancestorDirectory of ancestorDirectories(directory, { includeStart: true })) {
|
|
37017
|
-
const reactPackageJsonPath = Path.join(ancestorDirectory, "node_modules", "react", "package.json");
|
|
37018
|
-
if (isFile(reactPackageJsonPath)) {
|
|
37019
|
-
const installedVersion = readPackageJson$1(reactPackageJsonPath).version;
|
|
37020
|
-
return typeof installedVersion === "string" ? installedVersion : null;
|
|
37021
|
-
}
|
|
37022
|
-
if (ancestorDirectory === containmentRoot) return null;
|
|
37023
|
-
}
|
|
37024
|
-
return null;
|
|
37025
|
-
};
|
|
37026
|
-
const findInWorkspacePackageJsons = (rootDirectory, rootPackageJson, select) => {
|
|
37027
|
-
const rootValue = select(rootPackageJson);
|
|
37028
|
-
if (rootValue !== null) return rootValue;
|
|
37029
|
-
const patterns = getWorkspacePatterns(rootDirectory, rootPackageJson);
|
|
37030
|
-
if (patterns.length === 0) return null;
|
|
37031
|
-
const visitedDirectories = /* @__PURE__ */ new Set();
|
|
37032
|
-
for (const pattern of patterns) {
|
|
37033
|
-
const directories = [...resolveWorkspaceDirectories(rootDirectory, pattern)].sort();
|
|
37034
|
-
for (const workspaceDirectory of directories) {
|
|
37035
|
-
if (visitedDirectories.has(workspaceDirectory)) continue;
|
|
37036
|
-
visitedDirectories.add(workspaceDirectory);
|
|
37037
|
-
const value = select(readPackageJson$1(Path.join(workspaceDirectory, "package.json")));
|
|
37038
|
-
if (value !== null) return value;
|
|
37039
|
-
}
|
|
37040
|
-
}
|
|
37041
|
-
return null;
|
|
37042
|
-
};
|
|
37043
|
-
const someWorkspacePackageJson = (rootDirectory, rootPackageJson, predicate) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => predicate(packageJson) ? true : null) !== null;
|
|
37044
37046
|
const NAMES = new Set([
|
|
37045
37047
|
"react-native",
|
|
37046
37048
|
"react-native-tvos",
|
|
@@ -37073,47 +37075,7 @@ const isPackageJsonReactNativeAware = (packageJson) => {
|
|
|
37073
37075
|
if (containsAnyReactNativeDependency(packageJson.optionalDependencies)) return true;
|
|
37074
37076
|
return false;
|
|
37075
37077
|
};
|
|
37076
|
-
const
|
|
37077
|
-
const getDependencySpec = (packageJson, packageName) => {
|
|
37078
|
-
const spec = packageJson.dependencies?.[packageName] ?? packageJson.devDependencies?.[packageName] ?? packageJson.peerDependencies?.[packageName] ?? packageJson.optionalDependencies?.[packageName];
|
|
37079
|
-
return typeof spec === "string" ? spec : null;
|
|
37080
|
-
};
|
|
37081
|
-
const findExpoVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, "expo"));
|
|
37082
|
-
const SHOPIFY_FLASH_LIST_PACKAGE_NAME = "@shopify/flash-list";
|
|
37083
|
-
const findShopifyFlashListVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, SHOPIFY_FLASH_LIST_PACKAGE_NAME));
|
|
37084
|
-
const resolveCatalogBackedDependencyVersion = ({ rootDirectory, rootPackageJson, packageName, version }) => {
|
|
37085
|
-
if (version === null || !isCatalogReference(version)) return version;
|
|
37086
|
-
const catalogName = extractCatalogName(version);
|
|
37087
|
-
const resolvedLocalVersion = resolveCatalogVersion(rootPackageJson, packageName, rootDirectory, catalogName);
|
|
37088
|
-
if (resolvedLocalVersion) return resolvedLocalVersion;
|
|
37089
|
-
const monorepoRoot = findMonorepoRoot(rootDirectory);
|
|
37090
|
-
if (!monorepoRoot) return version;
|
|
37091
|
-
const monorepoPackageJsonPath = path.join(monorepoRoot, "package.json");
|
|
37092
|
-
if (!isFile(monorepoPackageJsonPath)) return version;
|
|
37093
|
-
return resolveCatalogVersion(readPackageJson$1(monorepoPackageJsonPath), packageName, monorepoRoot, catalogName) ?? version;
|
|
37094
|
-
};
|
|
37095
|
-
const findNextjsVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, "next"));
|
|
37096
|
-
const getPreactVersion = (packageJson) => {
|
|
37097
|
-
return {
|
|
37098
|
-
...packageJson.peerDependencies,
|
|
37099
|
-
...packageJson.dependencies,
|
|
37100
|
-
...packageJson.devDependencies
|
|
37101
|
-
}.preact ?? null;
|
|
37102
|
-
};
|
|
37103
|
-
const TANSTACK_QUERY_PACKAGES = new Set([
|
|
37104
|
-
"@tanstack/react-query",
|
|
37105
|
-
"@tanstack/query-core",
|
|
37106
|
-
"react-query"
|
|
37107
|
-
]);
|
|
37108
|
-
const hasTanStackQuery = (packageJson) => {
|
|
37109
|
-
const allDependencies = {
|
|
37110
|
-
...packageJson.peerDependencies,
|
|
37111
|
-
...packageJson.dependencies,
|
|
37112
|
-
...packageJson.devDependencies
|
|
37113
|
-
};
|
|
37114
|
-
return Object.keys(allDependencies).some((packageName) => TANSTACK_QUERY_PACKAGES.has(packageName));
|
|
37115
|
-
};
|
|
37116
|
-
const REANIMATED_DEPENDENCY_NAME = "react-native-reanimated";
|
|
37078
|
+
const REANIMATED_DEPENDENCY_NAME$1 = "react-native-reanimated";
|
|
37117
37079
|
const isPackageJsonReanimatedAware = (packageJson) => {
|
|
37118
37080
|
const allDependencies = {
|
|
37119
37081
|
...packageJson.peerDependencies,
|
|
@@ -37121,144 +37083,70 @@ const isPackageJsonReanimatedAware = (packageJson) => {
|
|
|
37121
37083
|
...packageJson.devDependencies,
|
|
37122
37084
|
...packageJson.optionalDependencies
|
|
37123
37085
|
};
|
|
37124
|
-
return Object.hasOwn(allDependencies, REANIMATED_DEPENDENCY_NAME);
|
|
37125
|
-
};
|
|
37126
|
-
const parseZodMajor = (zodVersion) => {
|
|
37127
|
-
if (typeof zodVersion !== "string") return null;
|
|
37128
|
-
return getLowestDependencyMajor(zodVersion);
|
|
37129
|
-
};
|
|
37130
|
-
const hasUpperBoundOnlyPeerRange = (range) => {
|
|
37131
|
-
if (typeof range !== "string") return false;
|
|
37132
|
-
const normalizedRange = normalizeDependencyVersion(range);
|
|
37133
|
-
if (normalizedRange === null) return false;
|
|
37134
|
-
return splitDependencyVersionBranches(normalizedRange).some((branch) => {
|
|
37135
|
-
const normalizedBranch = normalizeDependencyVersion(branch);
|
|
37136
|
-
return normalizedBranch !== null && getBranchLowestMajor(normalizedBranch) === null && hasUpperBoundComparator(normalizedBranch);
|
|
37137
|
-
});
|
|
37086
|
+
return Object.hasOwn(allDependencies, REANIMATED_DEPENDENCY_NAME$1);
|
|
37138
37087
|
};
|
|
37139
|
-
const
|
|
37140
|
-
|
|
37141
|
-
|
|
37142
|
-
|
|
37143
|
-
|
|
37144
|
-
const
|
|
37145
|
-
|
|
37146
|
-
|
|
37147
|
-
const peerFloor = peerRangeMinMajor(peerReactRange);
|
|
37148
|
-
if (peerFloor === null) return hasUpperBoundOnlyPeerRange(peerReactRange) ? null : installedReactMajor;
|
|
37149
|
-
return installedReactMajor !== null ? Math.min(installedReactMajor, peerFloor) : peerFloor;
|
|
37088
|
+
const getWorkspacePatterns = (rootDirectory, packageJson) => {
|
|
37089
|
+
const pnpmPatterns = parsePnpmWorkspacePatterns(rootDirectory);
|
|
37090
|
+
if (pnpmPatterns.length > 0) return pnpmPatterns;
|
|
37091
|
+
if (Array.isArray(packageJson.workspaces)) return packageJson.workspaces;
|
|
37092
|
+
if (packageJson.workspaces?.packages) return packageJson.workspaces.packages;
|
|
37093
|
+
const nxPatterns = getNxWorkspaceDirectories(rootDirectory);
|
|
37094
|
+
if (nxPatterns.length > 0) return nxPatterns;
|
|
37095
|
+
return [];
|
|
37150
37096
|
};
|
|
37151
|
-
const
|
|
37152
|
-
const
|
|
37153
|
-
|
|
37154
|
-
const
|
|
37155
|
-
const
|
|
37156
|
-
|
|
37157
|
-
|
|
37158
|
-
|
|
37159
|
-
|
|
37160
|
-
|
|
37161
|
-
|
|
37162
|
-
|
|
37163
|
-
|
|
37164
|
-
|
|
37097
|
+
const parsePnpmWorkspacePatterns = (rootDirectory) => {
|
|
37098
|
+
const workspacePath = Path.join(rootDirectory, "pnpm-workspace.yaml");
|
|
37099
|
+
if (!isFile(workspacePath)) return [];
|
|
37100
|
+
const content = NFS.readFileSync(workspacePath, "utf-8");
|
|
37101
|
+
const patterns = [];
|
|
37102
|
+
let isInsidePackagesBlock = false;
|
|
37103
|
+
for (const line of content.split("\n")) {
|
|
37104
|
+
const trimmed = line.trim();
|
|
37105
|
+
if (trimmed === "packages:") {
|
|
37106
|
+
isInsidePackagesBlock = true;
|
|
37107
|
+
continue;
|
|
37108
|
+
}
|
|
37109
|
+
if (isInsidePackagesBlock && trimmed.startsWith("-")) patterns.push(trimmed.replace(/^-\s*/, "").replace(/["']/g, ""));
|
|
37110
|
+
else if (isInsidePackagesBlock && trimmed.length > 0 && !trimmed.startsWith("#")) isInsidePackagesBlock = false;
|
|
37165
37111
|
}
|
|
37166
|
-
return
|
|
37167
|
-
};
|
|
37168
|
-
const resolveExtendsPath = (extendsValue, fromConfigDirectory) => {
|
|
37169
|
-
if (isRelativeExtendsValue(extendsValue)) return ensureJsonExtension(Path.resolve(fromConfigDirectory, extendsValue));
|
|
37170
|
-
return resolvePackageExtendsPath(extendsValue, fromConfigDirectory);
|
|
37171
|
-
};
|
|
37172
|
-
const normalizeCompilerOptions = (compilerOptions) => {
|
|
37173
|
-
if (!isPlainObject(compilerOptions)) return { hasExplicitLib: false };
|
|
37174
|
-
const target = typeof compilerOptions.target === "string" ? compilerOptions.target : void 0;
|
|
37175
|
-
const hasExplicitLib = Object.hasOwn(compilerOptions, "lib");
|
|
37176
|
-
return {
|
|
37177
|
-
target,
|
|
37178
|
-
lib: Array.isArray(compilerOptions.lib) ? compilerOptions.lib.filter((entry) => typeof entry === "string") : void 0,
|
|
37179
|
-
hasExplicitLib
|
|
37180
|
-
};
|
|
37112
|
+
return patterns;
|
|
37181
37113
|
};
|
|
37182
|
-
const
|
|
37183
|
-
|
|
37184
|
-
|
|
37185
|
-
|
|
37186
|
-
|
|
37187
|
-
|
|
37114
|
+
const NX_PROJECT_DISCOVERY_DIRS = [
|
|
37115
|
+
"apps",
|
|
37116
|
+
"libs",
|
|
37117
|
+
"packages"
|
|
37118
|
+
];
|
|
37119
|
+
const getNxWorkspaceDirectories = (rootDirectory) => {
|
|
37120
|
+
if (!isFile(Path.join(rootDirectory, "nx.json"))) return [];
|
|
37121
|
+
const collected = [];
|
|
37122
|
+
for (const candidate of NX_PROJECT_DISCOVERY_DIRS) {
|
|
37123
|
+
const candidatePath = Path.join(rootDirectory, candidate);
|
|
37124
|
+
if (!isDirectory(candidatePath)) continue;
|
|
37125
|
+
for (const entry of readDirectoryEntries(candidatePath)) {
|
|
37126
|
+
if (!entry.isDirectory()) continue;
|
|
37127
|
+
const projectDirectory = Path.join(candidatePath, entry.name);
|
|
37128
|
+
if (isFile(Path.join(projectDirectory, "project.json")) || isFile(Path.join(projectDirectory, "package.json"))) collected.push(`${candidate}/${entry.name}`);
|
|
37129
|
+
}
|
|
37188
37130
|
}
|
|
37189
|
-
|
|
37190
|
-
if (!isPlainObject(parsed.config)) return null;
|
|
37191
|
-
return {
|
|
37192
|
-
extends: typeof parsed.config.extends === "string" ? parsed.config.extends : void 0,
|
|
37193
|
-
compilerOptions: normalizeCompilerOptions(parsed.config.compilerOptions)
|
|
37194
|
-
};
|
|
37195
|
-
};
|
|
37196
|
-
const mergeCompilerOptions = (inherited, current) => {
|
|
37197
|
-
const target = current.target ?? inherited?.target;
|
|
37198
|
-
const hasExplicitLib = current.hasExplicitLib || Boolean(inherited?.hasExplicitLib);
|
|
37199
|
-
return {
|
|
37200
|
-
target,
|
|
37201
|
-
lib: current.hasExplicitLib ? current.lib : inherited?.lib,
|
|
37202
|
-
hasExplicitLib
|
|
37203
|
-
};
|
|
37131
|
+
return collected;
|
|
37204
37132
|
};
|
|
37205
|
-
const
|
|
37206
|
-
const
|
|
37207
|
-
if (
|
|
37208
|
-
|
|
37209
|
-
|
|
37210
|
-
|
|
37211
|
-
nextVisitedPaths.add(realPath);
|
|
37212
|
-
if (tsConfig.extends && extendsDepth < 8) {
|
|
37213
|
-
const parentPath = resolveExtendsPath(tsConfig.extends, Path.dirname(realPath));
|
|
37214
|
-
if (parentPath && isFile(parentPath)) return mergeCompilerOptions(readResolvedCompilerOptions(parentPath, extendsDepth + 1, nextVisitedPaths), tsConfig.compilerOptions);
|
|
37133
|
+
const resolveWorkspaceDirectories = (rootDirectory, pattern) => {
|
|
37134
|
+
const cleanPattern = pattern.replace(/["']/g, "").replace(/\/\*\*$/, "/*");
|
|
37135
|
+
if (!cleanPattern.includes("*")) {
|
|
37136
|
+
const directoryPath = Path.join(rootDirectory, cleanPattern);
|
|
37137
|
+
if (isDirectory(directoryPath) && isFile(Path.join(directoryPath, "package.json"))) return [directoryPath];
|
|
37138
|
+
return [];
|
|
37215
37139
|
}
|
|
37216
|
-
|
|
37217
|
-
|
|
37218
|
-
const
|
|
37219
|
-
|
|
37220
|
-
|
|
37221
|
-
|
|
37222
|
-
const
|
|
37223
|
-
|
|
37224
|
-
|
|
37225
|
-
|
|
37226
|
-
if (!esYearMatch) return false;
|
|
37227
|
-
if (Number(esYearMatch[1]) < 2023) return false;
|
|
37228
|
-
const component = esYearMatch[2];
|
|
37229
|
-
return component === void 0 || component === "array";
|
|
37230
|
-
};
|
|
37231
|
-
const libIncludesES2023 = (lib) => lib.some(libEntryIncludesES2023Array);
|
|
37232
|
-
const compilerOptionsArePreES2023 = (compilerOptions) => {
|
|
37233
|
-
if (compilerOptions.hasExplicitLib) return !libIncludesES2023(compilerOptions.lib ?? []);
|
|
37234
|
-
if (compilerOptions.target) return targetYearIsPreES2023(compilerOptions.target);
|
|
37235
|
-
return false;
|
|
37236
|
-
};
|
|
37237
|
-
const compilerOptionsDeclareTargetOrLib = (compilerOptions) => compilerOptions.hasExplicitLib || compilerOptions.target !== void 0;
|
|
37238
|
-
const detectPreES2023FromConfig = (tsConfigPath) => {
|
|
37239
|
-
const compilerOptions = readResolvedCompilerOptions(tsConfigPath, 0, /* @__PURE__ */ new Set());
|
|
37240
|
-
if (!compilerOptions) return false;
|
|
37241
|
-
if (!compilerOptionsDeclareTargetOrLib(compilerOptions)) return false;
|
|
37242
|
-
return compilerOptionsArePreES2023(compilerOptions);
|
|
37243
|
-
};
|
|
37244
|
-
const detectPreES2023Target = (directory) => {
|
|
37245
|
-
const tsConfigPath = Path.join(directory, TSCONFIG_FILENAME);
|
|
37246
|
-
if (isFile(tsConfigPath)) return detectPreES2023FromConfig(tsConfigPath);
|
|
37247
|
-
return false;
|
|
37248
|
-
};
|
|
37249
|
-
const REACT_DEPENDENCY_NAMES = new Set([
|
|
37250
|
-
"react",
|
|
37251
|
-
"react-native",
|
|
37252
|
-
"next",
|
|
37253
|
-
"preact"
|
|
37254
|
-
]);
|
|
37255
|
-
const hasReactDependency = (packageJson) => {
|
|
37256
|
-
const allDependencies = {
|
|
37257
|
-
...packageJson.peerDependencies,
|
|
37258
|
-
...packageJson.dependencies,
|
|
37259
|
-
...packageJson.devDependencies
|
|
37260
|
-
};
|
|
37261
|
-
return Object.keys(allDependencies).some((packageName) => REACT_DEPENDENCY_NAMES.has(packageName));
|
|
37140
|
+
const wildcardIndex = cleanPattern.indexOf("*");
|
|
37141
|
+
const baseDirectory = Path.join(rootDirectory, cleanPattern.slice(0, wildcardIndex));
|
|
37142
|
+
const suffixAfterWildcard = cleanPattern.slice(wildcardIndex + 1);
|
|
37143
|
+
if (!isDirectory(baseDirectory)) return [];
|
|
37144
|
+
const resolved = [];
|
|
37145
|
+
for (const entry of readDirectoryEntries(baseDirectory)) {
|
|
37146
|
+
const entryPath = Path.join(baseDirectory, entry.name, suffixAfterWildcard);
|
|
37147
|
+
if (isDirectory(entryPath) && isFile(Path.join(entryPath, "package.json"))) resolved.push(entryPath);
|
|
37148
|
+
}
|
|
37149
|
+
return resolved;
|
|
37262
37150
|
};
|
|
37263
37151
|
const listWorkspacePackages = (rootDirectory) => {
|
|
37264
37152
|
const packageJsonPath = Path.join(rootDirectory, "package.json");
|
|
@@ -37290,6 +37178,208 @@ const listWorkspacePackages = (rootDirectory) => {
|
|
|
37290
37178
|
}
|
|
37291
37179
|
return packages;
|
|
37292
37180
|
};
|
|
37181
|
+
const REANIMATED_DEPENDENCY_NAME = "react-native-reanimated";
|
|
37182
|
+
const SHOPIFY_FLASH_LIST_PACKAGE_NAME = "@shopify/flash-list";
|
|
37183
|
+
const resolveWorkspaceDependencyVersion = ({ concreteVersion, packageName, rootDirectory, rootPackageJson, sections, workspaceDirectory, workspacePackageJson }) => {
|
|
37184
|
+
const dependencyDeclaration = getDependencyDeclaration({
|
|
37185
|
+
packageJson: workspacePackageJson,
|
|
37186
|
+
packageName,
|
|
37187
|
+
sections
|
|
37188
|
+
});
|
|
37189
|
+
if (!dependencyDeclaration.hasDeclaration) return null;
|
|
37190
|
+
return concreteVersion ?? resolveCatalogVersion(workspacePackageJson, packageName, workspaceDirectory, dependencyDeclaration.catalogReference) ?? resolveCatalogVersion(rootPackageJson, packageName, rootDirectory, dependencyDeclaration.catalogReference);
|
|
37191
|
+
};
|
|
37192
|
+
const shouldReplaceReactVersion = (currentVersion, nextVersion) => {
|
|
37193
|
+
if (!currentVersion) return true;
|
|
37194
|
+
const currentMajor = parseReactMajor(currentVersion);
|
|
37195
|
+
const nextMajor = parseReactMajor(nextVersion);
|
|
37196
|
+
if (currentMajor === null) return nextMajor !== null;
|
|
37197
|
+
if (nextMajor === null) return false;
|
|
37198
|
+
return nextMajor < currentMajor;
|
|
37199
|
+
};
|
|
37200
|
+
const evaluateManifestFacts = (facts, packageJson, directory) => {
|
|
37201
|
+
if (facts.expo.version === null) {
|
|
37202
|
+
const spec = getDependencySpec(packageJson, "expo");
|
|
37203
|
+
if (spec !== null) facts.expo = {
|
|
37204
|
+
version: spec,
|
|
37205
|
+
sourceDirectory: directory
|
|
37206
|
+
};
|
|
37207
|
+
}
|
|
37208
|
+
if (facts.next.version === null) {
|
|
37209
|
+
const spec = getDependencySpec(packageJson, "next");
|
|
37210
|
+
if (spec !== null) facts.next = {
|
|
37211
|
+
version: spec,
|
|
37212
|
+
sourceDirectory: directory
|
|
37213
|
+
};
|
|
37214
|
+
}
|
|
37215
|
+
if (facts.shopifyFlashList.version === null) {
|
|
37216
|
+
const spec = getDependencySpec(packageJson, SHOPIFY_FLASH_LIST_PACKAGE_NAME);
|
|
37217
|
+
if (spec !== null) facts.shopifyFlashList = {
|
|
37218
|
+
version: spec,
|
|
37219
|
+
sourceDirectory: directory
|
|
37220
|
+
};
|
|
37221
|
+
}
|
|
37222
|
+
if (facts.reanimatedVersion === null) {
|
|
37223
|
+
const spec = getDependencySpec(packageJson, REANIMATED_DEPENDENCY_NAME);
|
|
37224
|
+
if (spec !== null) facts.reanimatedVersion = spec;
|
|
37225
|
+
}
|
|
37226
|
+
facts.hasReactNativeAwarePackage = facts.hasReactNativeAwarePackage || isPackageJsonReactNativeAware(packageJson);
|
|
37227
|
+
facts.hasReanimatedAwarePackage = facts.hasReanimatedAwarePackage || isPackageJsonReanimatedAware(packageJson);
|
|
37228
|
+
};
|
|
37229
|
+
const collectWorkspaceFacts = (rootDirectory, rootPackageJson, { collectReactGroup }) => {
|
|
37230
|
+
const facts = {
|
|
37231
|
+
reactVersion: null,
|
|
37232
|
+
tailwindVersion: null,
|
|
37233
|
+
zodVersion: null,
|
|
37234
|
+
framework: "unknown",
|
|
37235
|
+
expo: {
|
|
37236
|
+
version: null,
|
|
37237
|
+
sourceDirectory: null
|
|
37238
|
+
},
|
|
37239
|
+
next: {
|
|
37240
|
+
version: null,
|
|
37241
|
+
sourceDirectory: null
|
|
37242
|
+
},
|
|
37243
|
+
shopifyFlashList: {
|
|
37244
|
+
version: null,
|
|
37245
|
+
sourceDirectory: null
|
|
37246
|
+
},
|
|
37247
|
+
hasReactNativeAwarePackage: false,
|
|
37248
|
+
hasReanimatedAwarePackage: false,
|
|
37249
|
+
reanimatedVersion: null
|
|
37250
|
+
};
|
|
37251
|
+
evaluateManifestFacts(facts, rootPackageJson, rootDirectory);
|
|
37252
|
+
let isReactGroupSettled = !collectReactGroup;
|
|
37253
|
+
const visitedDirectories = /* @__PURE__ */ new Set();
|
|
37254
|
+
for (const pattern of getWorkspacePatterns(rootDirectory, rootPackageJson)) {
|
|
37255
|
+
const directories = [...resolveWorkspaceDirectories(rootDirectory, pattern)].sort();
|
|
37256
|
+
for (const workspaceDirectory of directories) {
|
|
37257
|
+
if (visitedDirectories.has(workspaceDirectory)) continue;
|
|
37258
|
+
visitedDirectories.add(workspaceDirectory);
|
|
37259
|
+
const workspacePackageJson = readPackageJson$1(Path.join(workspaceDirectory, "package.json"));
|
|
37260
|
+
evaluateManifestFacts(facts, workspacePackageJson, workspaceDirectory);
|
|
37261
|
+
const info = extractDependencyInfo(workspacePackageJson);
|
|
37262
|
+
if (info.framework !== "unknown" && frameworkMergeRank(info.framework) < frameworkMergeRank(facts.framework)) facts.framework = info.framework;
|
|
37263
|
+
if (isReactGroupSettled) continue;
|
|
37264
|
+
const reactVersion = resolveWorkspaceDependencyVersion({
|
|
37265
|
+
concreteVersion: info.reactVersion,
|
|
37266
|
+
packageName: "react",
|
|
37267
|
+
rootDirectory,
|
|
37268
|
+
rootPackageJson,
|
|
37269
|
+
sections: REACT_SECTIONS,
|
|
37270
|
+
workspaceDirectory,
|
|
37271
|
+
workspacePackageJson
|
|
37272
|
+
});
|
|
37273
|
+
const tailwindVersion = resolveWorkspaceDependencyVersion({
|
|
37274
|
+
concreteVersion: info.tailwindVersion,
|
|
37275
|
+
packageName: "tailwindcss",
|
|
37276
|
+
rootDirectory,
|
|
37277
|
+
rootPackageJson,
|
|
37278
|
+
sections: TAILWIND_ZOD_SECTIONS,
|
|
37279
|
+
workspaceDirectory,
|
|
37280
|
+
workspacePackageJson
|
|
37281
|
+
});
|
|
37282
|
+
const zodVersion = resolveWorkspaceDependencyVersion({
|
|
37283
|
+
concreteVersion: info.zodVersion,
|
|
37284
|
+
packageName: "zod",
|
|
37285
|
+
rootDirectory,
|
|
37286
|
+
rootPackageJson,
|
|
37287
|
+
sections: TAILWIND_ZOD_SECTIONS,
|
|
37288
|
+
workspaceDirectory,
|
|
37289
|
+
workspacePackageJson
|
|
37290
|
+
});
|
|
37291
|
+
if (reactVersion && shouldReplaceReactVersion(facts.reactVersion, reactVersion)) facts.reactVersion = reactVersion;
|
|
37292
|
+
if (tailwindVersion && !facts.tailwindVersion) facts.tailwindVersion = tailwindVersion;
|
|
37293
|
+
if (zodVersion && !facts.zodVersion) facts.zodVersion = zodVersion;
|
|
37294
|
+
const settledReactMajor = parseReactMajor(facts.reactVersion);
|
|
37295
|
+
isReactGroupSettled = Boolean(facts.reactVersion && facts.tailwindVersion && facts.framework !== "unknown" && settledReactMajor !== null && settledReactMajor <= 17);
|
|
37296
|
+
}
|
|
37297
|
+
}
|
|
37298
|
+
return facts;
|
|
37299
|
+
};
|
|
37300
|
+
const findDependencyInfoFromMonorepoRoot = (directory) => {
|
|
37301
|
+
const monorepoRoot = findMonorepoRoot(directory);
|
|
37302
|
+
if (!monorepoRoot) return EMPTY_DEPENDENCY_INFO;
|
|
37303
|
+
const monorepoPackageJsonPath = Path.join(monorepoRoot, "package.json");
|
|
37304
|
+
if (!isFile(monorepoPackageJsonPath)) return EMPTY_DEPENDENCY_INFO;
|
|
37305
|
+
const rootPackageJson = readPackageJson$1(monorepoPackageJsonPath);
|
|
37306
|
+
const rootInfo = extractDependencyInfo(rootPackageJson);
|
|
37307
|
+
const leafPackageJsonPath = Path.join(directory, "package.json");
|
|
37308
|
+
const leafPackageJson = isFile(leafPackageJsonPath) ? readPackageJson$1(leafPackageJsonPath) : null;
|
|
37309
|
+
const leafReactDeclaration = leafPackageJson ? getDependencyDeclaration({
|
|
37310
|
+
packageJson: leafPackageJson,
|
|
37311
|
+
packageName: "react",
|
|
37312
|
+
sections: REACT_SECTIONS
|
|
37313
|
+
}) : null;
|
|
37314
|
+
const leafTailwindDeclaration = leafPackageJson ? getDependencyDeclaration({
|
|
37315
|
+
packageJson: leafPackageJson,
|
|
37316
|
+
packageName: "tailwindcss",
|
|
37317
|
+
sections: TAILWIND_ZOD_SECTIONS
|
|
37318
|
+
}) : null;
|
|
37319
|
+
const leafZodDeclaration = leafPackageJson ? getDependencyDeclaration({
|
|
37320
|
+
packageJson: leafPackageJson,
|
|
37321
|
+
packageName: "zod",
|
|
37322
|
+
sections: TAILWIND_ZOD_SECTIONS
|
|
37323
|
+
}) : null;
|
|
37324
|
+
const shouldUseReactFallback = !leafReactDeclaration?.hasDeclaration;
|
|
37325
|
+
const shouldUseTailwindFallback = leafTailwindDeclaration?.hasDeclaration ?? true;
|
|
37326
|
+
const shouldUseZodFallback = leafZodDeclaration?.hasDeclaration ?? true;
|
|
37327
|
+
const reactCatalogVersion = shouldUseReactFallback ? resolveCatalogVersion(rootPackageJson, "react", monorepoRoot, leafReactDeclaration?.catalogReference) : null;
|
|
37328
|
+
const tailwindCatalogVersion = shouldUseTailwindFallback ? resolveCatalogVersion(rootPackageJson, "tailwindcss", monorepoRoot, leafTailwindDeclaration?.catalogReference) : null;
|
|
37329
|
+
const zodCatalogVersion = shouldUseZodFallback ? resolveCatalogVersion(rootPackageJson, "zod", monorepoRoot, leafZodDeclaration?.catalogReference) : null;
|
|
37330
|
+
const workspaceFacts = collectWorkspaceFacts(monorepoRoot, rootPackageJson, { collectReactGroup: true });
|
|
37331
|
+
return {
|
|
37332
|
+
reactVersion: shouldUseReactFallback ? reactCatalogVersion ?? rootInfo.reactVersion ?? workspaceFacts.reactVersion : rootInfo.reactVersion ?? workspaceFacts.reactVersion,
|
|
37333
|
+
tailwindVersion: shouldUseTailwindFallback ? tailwindCatalogVersion ?? rootInfo.tailwindVersion ?? workspaceFacts.tailwindVersion : null,
|
|
37334
|
+
zodVersion: shouldUseZodFallback ? zodCatalogVersion ?? rootInfo.zodVersion ?? workspaceFacts.zodVersion : null,
|
|
37335
|
+
framework: rootInfo.framework !== "unknown" ? rootInfo.framework : workspaceFacts.framework
|
|
37336
|
+
};
|
|
37337
|
+
};
|
|
37338
|
+
/**
|
|
37339
|
+
* Last-resort React detection: locate React the way Node's `node_modules`
|
|
37340
|
+
* resolution would from `directory` — check each `node_modules/react` up the
|
|
37341
|
+
* tree — but bounded to the enclosing repo so a globally installed React can't
|
|
37342
|
+
* masquerade as the project's version. Stops at (and including) the nearest
|
|
37343
|
+
* project boundary — the git root or a monorepo root — and returns the first
|
|
37344
|
+
* installed `version`.
|
|
37345
|
+
*
|
|
37346
|
+
* Makes "React is installed and importable" ⇒ "React is detected" an invariant
|
|
37347
|
+
* for packages whose only React declaration is a version-less spec
|
|
37348
|
+
* (`workspace:*`, `*`, a dist-tag) or where React lives solely in a hoisted
|
|
37349
|
+
* `node_modules` the declaration walks never reach — the profile of a component
|
|
37350
|
+
* package inside a monorepo. Matching the `node_modules/react` entry rather than
|
|
37351
|
+
* its realpath means a project whose `node_modules` is symlinked elsewhere (a
|
|
37352
|
+
* Docker volume, a shared store) still counts, while a global install outside
|
|
37353
|
+
* the repo does not.
|
|
37354
|
+
*
|
|
37355
|
+
* Returns `null` when React isn't installed within the repo or its package.json
|
|
37356
|
+
* carries no version string. A tree with no boundary marker at all bounds the
|
|
37357
|
+
* search to `directory`, so a dependency hoisted above the scanned package there
|
|
37358
|
+
* is conservatively not adopted.
|
|
37359
|
+
*/
|
|
37360
|
+
/**
|
|
37361
|
+
* The nearest enclosing project boundary (git root or monorepo root), or
|
|
37362
|
+
* `directory` itself when the scan target sits outside any repo. Bounds the
|
|
37363
|
+
* node_modules walk so a React hoisted anywhere inside the repo counts, but a
|
|
37364
|
+
* React in an ancestor above the repo — a home-directory or global
|
|
37365
|
+
* `node_modules` — can't leak in.
|
|
37366
|
+
*/
|
|
37367
|
+
const findContainmentRoot = (directory) => {
|
|
37368
|
+
for (const ancestorDirectory of ancestorDirectories(directory, { includeStart: true })) if (isProjectBoundary(ancestorDirectory)) return ancestorDirectory;
|
|
37369
|
+
return directory;
|
|
37370
|
+
};
|
|
37371
|
+
const resolveInstalledReactVersion = (directory) => {
|
|
37372
|
+
const containmentRoot = findContainmentRoot(directory);
|
|
37373
|
+
for (const ancestorDirectory of ancestorDirectories(directory, { includeStart: true })) {
|
|
37374
|
+
const reactPackageJsonPath = Path.join(ancestorDirectory, "node_modules", "react", "package.json");
|
|
37375
|
+
if (isFile(reactPackageJsonPath)) {
|
|
37376
|
+
const installedVersion = readPackageJson$1(reactPackageJsonPath).version;
|
|
37377
|
+
return typeof installedVersion === "string" ? installedVersion : null;
|
|
37378
|
+
}
|
|
37379
|
+
if (ancestorDirectory === containmentRoot) return null;
|
|
37380
|
+
}
|
|
37381
|
+
return null;
|
|
37382
|
+
};
|
|
37293
37383
|
const toReactWorkspacePackages = (directories) => {
|
|
37294
37384
|
const packages = [];
|
|
37295
37385
|
for (const directory of directories) {
|
|
@@ -37359,14 +37449,13 @@ const discoverReactSubprojects = (rootDirectory) => {
|
|
|
37359
37449
|
const cachedProjectInfos = /* @__PURE__ */ new Map();
|
|
37360
37450
|
/**
|
|
37361
37451
|
* Build a `ProjectInfo` for a directory that has no `package.json` of
|
|
37362
|
-
* its own — a
|
|
37363
|
-
*
|
|
37364
|
-
*
|
|
37365
|
-
*
|
|
37366
|
-
*
|
|
37367
|
-
*
|
|
37368
|
-
*
|
|
37369
|
-
* nothing to scan (no enclosing project and no source files of its own).
|
|
37452
|
+
* its own — a monorepo subfolder like `repo/packages`, or any loose tree
|
|
37453
|
+
* of TypeScript/JavaScript files. Dependency + framework detection is
|
|
37454
|
+
* inherited from the enclosing workspace root when there is one, so
|
|
37455
|
+
* scanning a subdirectory of a React monorepo still gets the React
|
|
37456
|
+
* capabilities; a standalone non-React directory simply scans with the
|
|
37457
|
+
* framework-agnostic rules. Throws only when the directory has nothing
|
|
37458
|
+
* to scan (no enclosing project and no source files of its own).
|
|
37370
37459
|
*/
|
|
37371
37460
|
const discoverProjectWithoutPackageJson = (directory) => {
|
|
37372
37461
|
const sourceFileCount = countSourceFiles(directory);
|
|
@@ -37402,7 +37491,9 @@ const discoverProjectWithoutPackageJson = (directory) => {
|
|
|
37402
37491
|
shopifyFlashListVersion: null,
|
|
37403
37492
|
shopifyFlashListMajorVersion: null,
|
|
37404
37493
|
hasReanimated: false,
|
|
37494
|
+
reanimatedVersion: null,
|
|
37405
37495
|
isPreES2023Target: hasOwnTsConfig && detectPreES2023Target(directory),
|
|
37496
|
+
isStaticExport: false,
|
|
37406
37497
|
sourceFileCount
|
|
37407
37498
|
};
|
|
37408
37499
|
};
|
|
@@ -37416,89 +37507,85 @@ const discoverProject = (directory) => {
|
|
|
37416
37507
|
return synthesized;
|
|
37417
37508
|
}
|
|
37418
37509
|
const packageJson = readPackageJson$1(packageJsonPath);
|
|
37419
|
-
|
|
37420
|
-
|
|
37421
|
-
|
|
37422
|
-
|
|
37423
|
-
|
|
37424
|
-
|
|
37425
|
-
|
|
37426
|
-
|
|
37427
|
-
|
|
37428
|
-
|
|
37429
|
-
|
|
37430
|
-
|
|
37431
|
-
|
|
37432
|
-
|
|
37433
|
-
|
|
37434
|
-
|
|
37435
|
-
|
|
37436
|
-
]
|
|
37437
|
-
});
|
|
37438
|
-
const zodDeclaration = getDependencyDeclaration({
|
|
37510
|
+
const rootInfo = extractDependencyInfo(packageJson);
|
|
37511
|
+
let framework = rootInfo.framework;
|
|
37512
|
+
const tracked = {
|
|
37513
|
+
react: {
|
|
37514
|
+
version: rootInfo.reactVersion,
|
|
37515
|
+
sections: REACT_SECTIONS
|
|
37516
|
+
},
|
|
37517
|
+
tailwindcss: {
|
|
37518
|
+
version: rootInfo.tailwindVersion,
|
|
37519
|
+
sections: TAILWIND_ZOD_SECTIONS
|
|
37520
|
+
},
|
|
37521
|
+
zod: {
|
|
37522
|
+
version: rootInfo.zodVersion,
|
|
37523
|
+
sections: TAILWIND_ZOD_SECTIONS
|
|
37524
|
+
}
|
|
37525
|
+
};
|
|
37526
|
+
const declarations = Object.fromEntries(Object.entries(tracked).map(([packageName, entry]) => [packageName, getDependencyDeclaration({
|
|
37439
37527
|
packageJson,
|
|
37440
|
-
packageName
|
|
37441
|
-
sections:
|
|
37442
|
-
|
|
37443
|
-
|
|
37444
|
-
|
|
37445
|
-
|
|
37446
|
-
|
|
37447
|
-
if (!
|
|
37448
|
-
if (!tailwindVersion && tailwindDeclaration.hasDeclaration) tailwindVersion = resolveCatalogVersion(packageJson, "tailwindcss", directory, tailwindDeclaration.catalogReference);
|
|
37449
|
-
if (!zodVersion && zodDeclaration.hasDeclaration) zodVersion = resolveCatalogVersion(packageJson, "zod", directory, zodDeclaration.catalogReference);
|
|
37450
|
-
if (!reactVersion || !tailwindVersion || !zodVersion) {
|
|
37528
|
+
packageName,
|
|
37529
|
+
sections: entry.sections
|
|
37530
|
+
})]));
|
|
37531
|
+
const fillFromCatalogs = (source, sourceDirectory) => {
|
|
37532
|
+
for (const [packageName, entry] of Object.entries(tracked)) if (!entry.version && declarations[packageName].hasDeclaration) entry.version = resolveCatalogVersion(source, packageName, sourceDirectory, declarations[packageName].catalogReference);
|
|
37533
|
+
};
|
|
37534
|
+
fillFromCatalogs(packageJson, directory);
|
|
37535
|
+
if (!tracked.react.version || !tracked.tailwindcss.version || !tracked.zod.version) {
|
|
37451
37536
|
const monorepoRoot = findMonorepoRoot(directory);
|
|
37452
37537
|
if (monorepoRoot) {
|
|
37453
37538
|
const monorepoPackageJsonPath = Path.join(monorepoRoot, "package.json");
|
|
37454
|
-
if (isFile(monorepoPackageJsonPath))
|
|
37455
|
-
const rootPackageJson = readPackageJson$1(monorepoPackageJsonPath);
|
|
37456
|
-
if (!reactVersion && reactDeclaration.hasDeclaration) reactVersion = resolveCatalogVersion(rootPackageJson, "react", monorepoRoot, reactDeclaration.catalogReference);
|
|
37457
|
-
if (!tailwindVersion && tailwindDeclaration.hasDeclaration) tailwindVersion = resolveCatalogVersion(rootPackageJson, "tailwindcss", monorepoRoot, tailwindDeclaration.catalogReference);
|
|
37458
|
-
if (!zodVersion && zodDeclaration.hasDeclaration) zodVersion = resolveCatalogVersion(rootPackageJson, "zod", monorepoRoot, zodDeclaration.catalogReference);
|
|
37459
|
-
}
|
|
37539
|
+
if (isFile(monorepoPackageJsonPath)) fillFromCatalogs(readPackageJson$1(monorepoPackageJsonPath), monorepoRoot);
|
|
37460
37540
|
}
|
|
37461
37541
|
}
|
|
37462
|
-
|
|
37463
|
-
|
|
37464
|
-
|
|
37465
|
-
|
|
37466
|
-
|
|
37467
|
-
|
|
37542
|
+
const shouldCollectReactGroup = !tracked.react.version || framework === "unknown";
|
|
37543
|
+
const workspaceFacts = collectWorkspaceFacts(directory, packageJson, { collectReactGroup: shouldCollectReactGroup });
|
|
37544
|
+
if (shouldCollectReactGroup) {
|
|
37545
|
+
tracked.react.version ||= workspaceFacts.reactVersion;
|
|
37546
|
+
tracked.tailwindcss.version ||= workspaceFacts.tailwindVersion;
|
|
37547
|
+
tracked.zod.version ||= workspaceFacts.zodVersion;
|
|
37548
|
+
if (framework === "unknown" && workspaceFacts.framework !== "unknown") framework = workspaceFacts.framework;
|
|
37468
37549
|
}
|
|
37469
|
-
if ((!
|
|
37550
|
+
if ((!tracked.react.version || framework === "unknown") && !isMonorepoRoot(directory)) {
|
|
37470
37551
|
const monorepoInfo = findDependencyInfoFromMonorepoRoot(directory);
|
|
37471
|
-
|
|
37472
|
-
|
|
37473
|
-
|
|
37552
|
+
tracked.react.version ||= monorepoInfo.reactVersion;
|
|
37553
|
+
tracked.tailwindcss.version ||= monorepoInfo.tailwindVersion;
|
|
37554
|
+
tracked.zod.version ||= monorepoInfo.zodVersion;
|
|
37474
37555
|
if (framework === "unknown") framework = monorepoInfo.framework;
|
|
37475
37556
|
}
|
|
37476
|
-
|
|
37477
|
-
|
|
37478
|
-
|
|
37557
|
+
for (const [packageName, entry] of Object.entries(tracked)) {
|
|
37558
|
+
const declaredVersion = declarations[packageName].version;
|
|
37559
|
+
if (!entry.version && declaredVersion && !isCatalogReference(declaredVersion)) entry.version = declaredVersion;
|
|
37560
|
+
}
|
|
37561
|
+
const { react, tailwindcss, zod } = tracked;
|
|
37562
|
+
let reactVersion = react.version;
|
|
37479
37563
|
if (!reactVersion || parseReactMajor(reactVersion) === null) reactVersion = resolveInstalledReactVersion(directory) ?? reactVersion;
|
|
37564
|
+
const tailwindVersion = tailwindcss.version;
|
|
37565
|
+
const zodVersion = zod.version;
|
|
37480
37566
|
const projectName = packageJson.name ?? Path.basename(directory);
|
|
37481
37567
|
const hasTypeScript = NFS.existsSync(Path.join(directory, "tsconfig.json"));
|
|
37482
37568
|
const sourceFileCount = countSourceFiles(directory);
|
|
37483
|
-
const hasReactNativeWorkspace = framework === "expo" || framework === "react-native" ||
|
|
37569
|
+
const hasReactNativeWorkspace = framework === "expo" || framework === "react-native" || workspaceFacts.hasReactNativeAwarePackage;
|
|
37484
37570
|
const expoVersion = hasReactNativeWorkspace ? resolveCatalogBackedDependencyVersion({
|
|
37485
37571
|
rootDirectory: directory,
|
|
37486
37572
|
rootPackageJson: packageJson,
|
|
37487
37573
|
packageName: "expo",
|
|
37488
|
-
version:
|
|
37574
|
+
version: workspaceFacts.expo.version
|
|
37489
37575
|
}) : null;
|
|
37490
37576
|
const shopifyFlashListVersion = hasReactNativeWorkspace ? resolveCatalogBackedDependencyVersion({
|
|
37491
37577
|
rootDirectory: directory,
|
|
37492
37578
|
rootPackageJson: packageJson,
|
|
37493
37579
|
packageName: SHOPIFY_FLASH_LIST_PACKAGE_NAME,
|
|
37494
|
-
version:
|
|
37580
|
+
version: workspaceFacts.shopifyFlashList.version
|
|
37495
37581
|
}) : null;
|
|
37496
|
-
const hasReanimated = hasReactNativeWorkspace &&
|
|
37582
|
+
const hasReanimated = hasReactNativeWorkspace && workspaceFacts.hasReanimatedAwarePackage;
|
|
37583
|
+
const reanimatedVersion = hasReanimated ? workspaceFacts.reanimatedVersion : null;
|
|
37497
37584
|
const nextjsVersion = framework === "nextjs" ? resolveCatalogBackedDependencyVersion({
|
|
37498
37585
|
rootDirectory: directory,
|
|
37499
37586
|
rootPackageJson: packageJson,
|
|
37500
37587
|
packageName: "next",
|
|
37501
|
-
version:
|
|
37588
|
+
version: workspaceFacts.next.version
|
|
37502
37589
|
}) : null;
|
|
37503
37590
|
const preactVersion = getPreactVersion(packageJson);
|
|
37504
37591
|
const isPreES2023Target = hasTypeScript && detectPreES2023Target(directory);
|
|
@@ -37509,7 +37596,7 @@ const discoverProject = (directory) => {
|
|
|
37509
37596
|
reactMajorVersion: resolveEffectiveReactMajor(reactVersion, packageJson),
|
|
37510
37597
|
tailwindVersion,
|
|
37511
37598
|
zodVersion,
|
|
37512
|
-
zodMajorVersion:
|
|
37599
|
+
zodMajorVersion: zodVersion === null ? null : getLowestDependencyMajor(zodVersion),
|
|
37513
37600
|
framework,
|
|
37514
37601
|
hasTypeScript,
|
|
37515
37602
|
hasReactCompiler: detectReactCompiler(directory, packageJson),
|
|
@@ -37523,7 +37610,9 @@ const discoverProject = (directory) => {
|
|
|
37523
37610
|
shopifyFlashListVersion,
|
|
37524
37611
|
shopifyFlashListMajorVersion: shopifyFlashListVersion === null ? null : getLowestDependencyMajor(shopifyFlashListVersion),
|
|
37525
37612
|
hasReanimated,
|
|
37613
|
+
reanimatedVersion,
|
|
37526
37614
|
isPreES2023Target,
|
|
37615
|
+
isStaticExport: framework === "nextjs" && detectNextjsStaticExport(workspaceFacts.next.sourceDirectory ?? directory),
|
|
37527
37616
|
sourceFileCount
|
|
37528
37617
|
};
|
|
37529
37618
|
cachedProjectInfos.set(directory, projectInfo);
|
|
@@ -37539,57 +37628,6 @@ const discoverProject = (directory) => {
|
|
|
37539
37628
|
* single source file has nothing to analyze.
|
|
37540
37629
|
*/
|
|
37541
37630
|
const isAnalyzableProject = (project) => project.reactVersion !== null || project.preactVersion !== null || project.sourceFileCount > 0;
|
|
37542
|
-
const MAJOR_MINOR_PATTERN = /(\d{1,4})\.(\d{1,4})/;
|
|
37543
|
-
const MAJOR_ONLY_PATTERN = /(\d{1,4})/;
|
|
37544
|
-
const UPPER_BOUND_COMPARATOR_PATTERN = /<=?\s{0,8}\d{1,4}(?:\.\d{1,4}){0,2}(?:-[^\s,|]+)?/g;
|
|
37545
|
-
const parseReactMajorMinor = (reactVersion) => {
|
|
37546
|
-
if (typeof reactVersion !== "string") return null;
|
|
37547
|
-
const trimmed = reactVersion.trim();
|
|
37548
|
-
if (trimmed.length === 0) return null;
|
|
37549
|
-
const lowerBoundsOnly = trimmed.replace(UPPER_BOUND_COMPARATOR_PATTERN, " ").trim();
|
|
37550
|
-
if (lowerBoundsOnly.length === 0) return null;
|
|
37551
|
-
const majorMinorMatch = lowerBoundsOnly.match(MAJOR_MINOR_PATTERN);
|
|
37552
|
-
if (majorMinorMatch) {
|
|
37553
|
-
const major = Number.parseInt(majorMinorMatch[1], 10);
|
|
37554
|
-
const minor = Number.parseInt(majorMinorMatch[2], 10);
|
|
37555
|
-
if (!Number.isFinite(major) || major <= 0) return null;
|
|
37556
|
-
if (!Number.isFinite(minor) || minor < 0) return null;
|
|
37557
|
-
return {
|
|
37558
|
-
major,
|
|
37559
|
-
minor
|
|
37560
|
-
};
|
|
37561
|
-
}
|
|
37562
|
-
const majorOnlyMatch = lowerBoundsOnly.match(MAJOR_ONLY_PATTERN);
|
|
37563
|
-
if (!majorOnlyMatch) return null;
|
|
37564
|
-
const major = Number.parseInt(majorOnlyMatch[1], 10);
|
|
37565
|
-
if (!Number.isFinite(major) || major <= 0) return null;
|
|
37566
|
-
return {
|
|
37567
|
-
major,
|
|
37568
|
-
minor: 0
|
|
37569
|
-
};
|
|
37570
|
-
};
|
|
37571
|
-
const isReactAtLeast = (detected, required) => {
|
|
37572
|
-
if (detected === null) return true;
|
|
37573
|
-
if (detected.major !== required.major) return detected.major > required.major;
|
|
37574
|
-
return detected.minor >= required.minor;
|
|
37575
|
-
};
|
|
37576
|
-
const parseLowerBoundVersion = (versionSpec) => import_semver.validRange(versionSpec) !== null ? import_semver.minVersion(versionSpec) : import_semver.coerce(versionSpec);
|
|
37577
|
-
const parseTailwindMajorMinor = (tailwindVersion) => {
|
|
37578
|
-
if (typeof tailwindVersion !== "string") return null;
|
|
37579
|
-
const trimmed = tailwindVersion.trim();
|
|
37580
|
-
if (trimmed.length === 0) return null;
|
|
37581
|
-
const lowerBound = parseLowerBoundVersion(trimmed);
|
|
37582
|
-
if (lowerBound === null || lowerBound.major <= 0) return null;
|
|
37583
|
-
return {
|
|
37584
|
-
major: lowerBound.major,
|
|
37585
|
-
minor: lowerBound.minor
|
|
37586
|
-
};
|
|
37587
|
-
};
|
|
37588
|
-
const isTailwindAtLeast = (detected, required) => {
|
|
37589
|
-
if (detected === null) return true;
|
|
37590
|
-
if (detected.major !== required.major) return detected.major > required.major;
|
|
37591
|
-
return detected.minor >= required.minor;
|
|
37592
|
-
};
|
|
37593
37631
|
const messageFromUnknown = (error) => error instanceof Error ? error.message : String(error);
|
|
37594
37632
|
var InvalidGlobPatternError = class extends Error {
|
|
37595
37633
|
pattern;
|
|
@@ -37715,6 +37753,9 @@ const buildRuleSeverityControls = (config) => {
|
|
|
37715
37753
|
...config.buckets !== void 0 ? { buckets: config.buckets } : {}
|
|
37716
37754
|
};
|
|
37717
37755
|
};
|
|
37756
|
+
const FOREIGN_INLINE_DISABLE_PATTERN = /(?:\/\/|\/\*)[ \t]*(eslint|oxlint)-disable-(next-line|line)(?![\w-])([^\r\n]*)/;
|
|
37757
|
+
const FOREIGN_BLOCK_DISABLE_PATTERN = /\/\*[ \t]*(eslint|oxlint)-disable(?![\w-])([^*\r\n]*)/;
|
|
37758
|
+
const FOREIGN_BLOCK_ENABLE_PATTERN = /\/\*[ \t]*(?:eslint|oxlint)-enable(?![\w-])([^*\r\n]*)/;
|
|
37718
37759
|
const LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY = {
|
|
37719
37760
|
"effect/no-adjust-state-on-prop-change": "react-doctor/no-adjust-state-on-prop-change",
|
|
37720
37761
|
"effect/no-chain-state-updates": "react-doctor/no-chain-state-updates",
|
|
@@ -37792,6 +37833,7 @@ const LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY = {
|
|
|
37792
37833
|
"react/jsx-no-new-function-as-prop": "react-doctor/jsx-no-new-function-as-prop",
|
|
37793
37834
|
"react/jsx-no-new-object-as-prop": "react-doctor/jsx-no-new-object-as-prop",
|
|
37794
37835
|
"react/jsx-no-script-url": "react-doctor/jsx-no-script-url",
|
|
37836
|
+
"react/jsx-no-target-blank": "react-doctor/jsx-no-target-blank",
|
|
37795
37837
|
"react/jsx-no-undef": "react-doctor/jsx-no-undef",
|
|
37796
37838
|
"react/jsx-no-useless-fragment": "react-doctor/jsx-no-useless-fragment",
|
|
37797
37839
|
"react/jsx-pascal-case": "react-doctor/jsx-pascal-case",
|
|
@@ -37876,9 +37918,6 @@ const tokenizeRuleList = (ruleList) => {
|
|
|
37876
37918
|
if (!ruleSection) return [];
|
|
37877
37919
|
return ruleSection.split(/[,\s]+/).map((token) => token.trim()).filter(Boolean);
|
|
37878
37920
|
};
|
|
37879
|
-
const FOREIGN_INLINE_DISABLE_PATTERN = /(?:\/\/|\/\*)[ \t]*(eslint|oxlint)-disable-(next-line|line)(?![\w-])([^\r\n]*)/;
|
|
37880
|
-
const FOREIGN_BLOCK_DISABLE_PATTERN = /\/\*[ \t]*(eslint|oxlint)-disable(?![\w-])([^*\r\n]*)/;
|
|
37881
|
-
const FOREIGN_BLOCK_ENABLE_PATTERN = /\/\*[ \t]*(?:eslint|oxlint)-enable(?![\w-])([^*\r\n]*)/;
|
|
37882
37921
|
const buildHint = (tool, token, ruleId) => `oxlint matches plugin rules only by their full name, so \`${token}\` in your ${tool}-disable comment does not silence \`${ruleId}\` — add \`${ruleId}\` to the comment (e.g. \`${token}, ${ruleId}\`).`;
|
|
37883
37922
|
const tokenMisnamesRule = (token, ruleId) => token !== ruleId && isSameRuleKey(token, ruleId);
|
|
37884
37923
|
const detectInlineNearMiss = (lines, diagnosticLineIndex, ruleId) => {
|
|
@@ -37932,6 +37971,53 @@ const detectForeignDisableNearMiss = (lines, diagnosticLineIndex, ruleId) => {
|
|
|
37932
37971
|
if (!ruleId.startsWith("react-doctor/")) return null;
|
|
37933
37972
|
return detectInlineNearMiss(lines, diagnosticLineIndex, ruleId) ?? detectBlockNearMiss(lines, diagnosticLineIndex, ruleId);
|
|
37934
37973
|
};
|
|
37974
|
+
const ESLINT_REACT_HOOKS_PLUGIN_PREFIX = "react-hooks/";
|
|
37975
|
+
const buildAcceptedTokens = (ruleId) => {
|
|
37976
|
+
const shortRuleId = ruleId.slice(15);
|
|
37977
|
+
return new Set([ruleId, `${ESLINT_REACT_HOOKS_PLUGIN_PREFIX}${shortRuleId}`]);
|
|
37978
|
+
};
|
|
37979
|
+
const listNamesRule = (ruleList, acceptedTokens) => tokenizeRuleList(ruleList).some((token) => acceptedTokens.has(token));
|
|
37980
|
+
const isInlineDisabled = (lines, diagnosticLineIndex, acceptedTokens) => {
|
|
37981
|
+
const candidates = [{
|
|
37982
|
+
line: lines[diagnosticLineIndex],
|
|
37983
|
+
requiredScope: "line"
|
|
37984
|
+
}, {
|
|
37985
|
+
line: lines[diagnosticLineIndex - 1],
|
|
37986
|
+
requiredScope: "next-line"
|
|
37987
|
+
}];
|
|
37988
|
+
for (const { line, requiredScope } of candidates) {
|
|
37989
|
+
const match = line?.match(FOREIGN_INLINE_DISABLE_PATTERN);
|
|
37990
|
+
if (!match) continue;
|
|
37991
|
+
const [, , scope, ruleList] = match;
|
|
37992
|
+
if (scope !== requiredScope) continue;
|
|
37993
|
+
if (listNamesRule(ruleList, acceptedTokens)) return true;
|
|
37994
|
+
}
|
|
37995
|
+
return false;
|
|
37996
|
+
};
|
|
37997
|
+
const isBlockDisabled = (lines, diagnosticLineIndex, acceptedTokens) => {
|
|
37998
|
+
let isRangeOpen = false;
|
|
37999
|
+
const lastLineIndex = Math.min(diagnosticLineIndex, lines.length - 1);
|
|
38000
|
+
for (let lineIndex = 0; lineIndex <= lastLineIndex; lineIndex++) {
|
|
38001
|
+
const line = lines[lineIndex];
|
|
38002
|
+
if (line === void 0 || !line.includes("-disable") && !line.includes("-enable")) continue;
|
|
38003
|
+
const disableMatch = line.match(FOREIGN_BLOCK_DISABLE_PATTERN);
|
|
38004
|
+
if (disableMatch && listNamesRule(disableMatch[2], acceptedTokens)) {
|
|
38005
|
+
isRangeOpen = true;
|
|
38006
|
+
continue;
|
|
38007
|
+
}
|
|
38008
|
+
const enableMatch = line.match(FOREIGN_BLOCK_ENABLE_PATTERN);
|
|
38009
|
+
if (enableMatch) {
|
|
38010
|
+
const enabledRules = tokenizeRuleList(enableMatch[1]);
|
|
38011
|
+
if (enabledRules.length === 0 || enabledRules.some((rule) => acceptedTokens.has(rule))) isRangeOpen = false;
|
|
38012
|
+
}
|
|
38013
|
+
}
|
|
38014
|
+
return isRangeOpen;
|
|
38015
|
+
};
|
|
38016
|
+
const isCompilerRuleForeignDisabled = (lines, diagnosticLineIndex, ruleId) => {
|
|
38017
|
+
if (!ruleId.startsWith("react-hooks-js/")) return false;
|
|
38018
|
+
const acceptedTokens = buildAcceptedTokens(ruleId);
|
|
38019
|
+
return isInlineDisabled(lines, diagnosticLineIndex, acceptedTokens) || isBlockDisabled(lines, diagnosticLineIndex, acceptedTokens);
|
|
38020
|
+
};
|
|
37935
38021
|
const JSX_OPENER_TAG_PATTERN = /<[A-Za-z][\w.]*/g;
|
|
37936
38022
|
const JSX_TAG_NAME_FOLLOW = /[A-Za-z]/;
|
|
37937
38023
|
const isOpenerMatchInsideLineComment = (line, openerCharIndex) => {
|
|
@@ -38075,6 +38161,11 @@ const evaluateSuppression = (lines, diagnosticLineIndex, ruleId) => {
|
|
|
38075
38161
|
isSuppressed: true,
|
|
38076
38162
|
nearMissHint: null
|
|
38077
38163
|
};
|
|
38164
|
+
if (isCompilerRuleForeignDisabled(lines, diagnosticLineIndex, ruleId)) return {
|
|
38165
|
+
isSuppressed: true,
|
|
38166
|
+
nearMissHint: null,
|
|
38167
|
+
isForeignDirective: true
|
|
38168
|
+
};
|
|
38078
38169
|
const directComments = findStackedDisableCommentsAbove(lines, diagnosticLineIndex);
|
|
38079
38170
|
if (hasChainSuppressor(directComments, ruleId)) return {
|
|
38080
38171
|
isSuppressed: true,
|
|
@@ -38356,12 +38447,18 @@ const collectStringSet = (values) => {
|
|
|
38356
38447
|
return new Set(values.filter((value) => typeof value === "string"));
|
|
38357
38448
|
};
|
|
38358
38449
|
const JSX_NO_UNDEF_IDENTIFIER_PATTERN = /^`([^`]+)`/;
|
|
38450
|
+
const NODE_CLI_PATH_SEGMENTS = new Set([
|
|
38451
|
+
"scripts",
|
|
38452
|
+
"bin",
|
|
38453
|
+
"tools"
|
|
38454
|
+
]);
|
|
38455
|
+
const hasNodeCliPathSegment = (filePath) => filePath.split(/[\\/]/).slice(0, -1).some((segment) => NODE_CLI_PATH_SEGMENTS.has(segment));
|
|
38359
38456
|
/**
|
|
38360
38457
|
* Pre-compiles every stateful filter and returns a single
|
|
38361
38458
|
* `apply(diagnostic)` closure that runs (in order):
|
|
38362
38459
|
*
|
|
38363
38460
|
* 1. auto-suppress (test-noise rules in test files; `migration-hint`
|
|
38364
|
-
* wins over `test-noise`)
|
|
38461
|
+
* wins over `test-noise`; `no-console` on Node CLI scripts)
|
|
38365
38462
|
* 2. severity overrides (top-level `rules` / `categories`, with
|
|
38366
38463
|
* `"off"` dropping)
|
|
38367
38464
|
* 3. warning suppression (only when `showWarnings` is false: drops every
|
|
@@ -38459,6 +38556,12 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
38459
38556
|
if (hasRawTextWrappers && isInsideStringOnlyWrapper(lines, diagnostic.line, diagnostic.column, rawTextWrapperComponentNames)) return true;
|
|
38460
38557
|
return false;
|
|
38461
38558
|
};
|
|
38559
|
+
const isNoConsoleOnNodeCliScript = (diagnostic) => {
|
|
38560
|
+
if (diagnostic.rule !== "no-console") return false;
|
|
38561
|
+
if (hasNodeCliPathSegment(diagnostic.filePath)) return true;
|
|
38562
|
+
const lines = getFileLines(diagnostic.filePath);
|
|
38563
|
+
return lines !== null && Boolean(lines[0]?.startsWith("#!"));
|
|
38564
|
+
};
|
|
38462
38565
|
const isJsxNoUndefSuppressedByConfig = (diagnostic) => {
|
|
38463
38566
|
if (!hasRuntimeGlobals) return false;
|
|
38464
38567
|
if (diagnostic.rule !== "jsx-no-undef") return false;
|
|
@@ -38468,6 +38571,7 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
38468
38571
|
return {
|
|
38469
38572
|
apply: (diagnostic) => {
|
|
38470
38573
|
if (shouldAutoSuppress(diagnostic)) return null;
|
|
38574
|
+
if (isNoConsoleOnNodeCliScript(diagnostic)) return null;
|
|
38471
38575
|
let current = diagnostic;
|
|
38472
38576
|
let explicitSeverityOverride;
|
|
38473
38577
|
let explicitRuleOverride;
|
|
@@ -38497,7 +38601,7 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
38497
38601
|
if (lines) {
|
|
38498
38602
|
const ruleIdentifier = `${current.plugin}/${current.rule}`;
|
|
38499
38603
|
const evaluation = evaluateSuppression(lines, current.line - 1, ruleIdentifier);
|
|
38500
|
-
if (evaluation.isSuppressed) return suppress(current, "inline");
|
|
38604
|
+
if (evaluation.isSuppressed) return suppress(current, evaluation.isForeignDirective ? "foreign-inline" : "inline");
|
|
38501
38605
|
if (evaluation.nearMissHint) current = {
|
|
38502
38606
|
...current,
|
|
38503
38607
|
suppressionHint: evaluation.nearMissHint
|
|
@@ -40334,64 +40438,64 @@ function* collectSecurityScanFiles(rootDirectory) {
|
|
|
40334
40438
|
}
|
|
40335
40439
|
}
|
|
40336
40440
|
}
|
|
40337
|
-
|
|
40338
|
-
|
|
40339
|
-
|
|
40340
|
-
|
|
40341
|
-
|
|
40342
|
-
|
|
40343
|
-
|
|
40441
|
+
const CLIENT_ONLY_FRAMEWORKS = new Set([
|
|
40442
|
+
"vite",
|
|
40443
|
+
"cra",
|
|
40444
|
+
"gatsby",
|
|
40445
|
+
"react-native",
|
|
40446
|
+
"expo"
|
|
40447
|
+
]);
|
|
40448
|
+
const addMajorLadder = (capabilities, name, major, earliest, latest) => {
|
|
40449
|
+
if (major === null) return;
|
|
40450
|
+
const cappedMajor = Math.min(major, latest);
|
|
40451
|
+
for (let candidate = earliest; candidate <= cappedMajor; candidate += 1) capabilities.add(`${name}:${candidate}`);
|
|
40452
|
+
};
|
|
40344
40453
|
const buildCapabilities = (project) => {
|
|
40345
40454
|
const capabilities = /* @__PURE__ */ new Set();
|
|
40346
40455
|
capabilities.add(project.framework);
|
|
40347
|
-
if (
|
|
40456
|
+
if (project.reactVersion !== null || project.preactVersion !== null) capabilities.add("react");
|
|
40348
40457
|
if (project.framework === "expo" || project.framework === "react-native" || project.hasReactNativeWorkspace) capabilities.add("react-native");
|
|
40349
40458
|
if (project.expoVersion !== null) capabilities.add("expo");
|
|
40459
|
+
if ((project.framework === "nextjs" || project.framework === "tanstack-start" || project.framework === "remix") && !project.isStaticExport) capabilities.add("server-actions");
|
|
40460
|
+
if (CLIENT_ONLY_FRAMEWORKS.has(project.framework)) capabilities.add("client-only");
|
|
40461
|
+
if (project.isStaticExport) capabilities.add("nextjs:static-export");
|
|
40350
40462
|
if (project.nextjsMajorVersion !== null && project.nextjsMajorVersion >= 15) capabilities.add("nextjs:15");
|
|
40351
|
-
|
|
40352
|
-
if (
|
|
40353
|
-
|
|
40354
|
-
|
|
40355
|
-
|
|
40356
|
-
|
|
40357
|
-
|
|
40358
|
-
|
|
40359
|
-
|
|
40360
|
-
|
|
40361
|
-
|
|
40362
|
-
if (project.
|
|
40363
|
-
capabilities.add("tailwind");
|
|
40364
|
-
if (isTailwindAtLeast(parseTailwindMajorMinor(project.tailwindVersion), {
|
|
40365
|
-
major: 3,
|
|
40366
|
-
minor: 4
|
|
40367
|
-
})) capabilities.add("tailwind:3.4");
|
|
40368
|
-
}
|
|
40369
|
-
if (project.zodVersion !== null) {
|
|
40370
|
-
capabilities.add("zod");
|
|
40371
|
-
if (project.zodMajorVersion !== null && project.zodMajorVersion >= 4) capabilities.add("zod:4");
|
|
40372
|
-
}
|
|
40463
|
+
addMajorLadder(capabilities, "react", project.reactMajorVersion, 17, 30);
|
|
40464
|
+
if (project.reactMajorVersion !== null && project.reactMajorVersion >= 19 && isMajorMinorAtLeast(parseReactMajorMinor(project.reactVersion), {
|
|
40465
|
+
major: 19,
|
|
40466
|
+
minor: 2
|
|
40467
|
+
})) capabilities.add("react:19.2");
|
|
40468
|
+
if (project.tailwindVersion !== null) capabilities.add("tailwind");
|
|
40469
|
+
if (project.tailwindVersion !== null && isMajorMinorAtLeast(parseTailwindMajorMinor(project.tailwindVersion), {
|
|
40470
|
+
major: 3,
|
|
40471
|
+
minor: 4
|
|
40472
|
+
})) capabilities.add("tailwind:3.4");
|
|
40473
|
+
if (project.zodVersion !== null) capabilities.add("zod");
|
|
40474
|
+
if (project.zodMajorVersion !== null && project.zodMajorVersion >= 4) capabilities.add("zod:4");
|
|
40373
40475
|
if (project.isPreES2023Target) capabilities.add("pre-es2023");
|
|
40374
40476
|
if (project.hasReactCompiler) capabilities.add("react-compiler");
|
|
40375
40477
|
if (project.hasTanStackQuery) capabilities.add("tanstack-query");
|
|
40376
40478
|
if (project.hasTypeScript) capabilities.add("typescript");
|
|
40377
|
-
if (project.preactVersion !== null)
|
|
40378
|
-
|
|
40379
|
-
|
|
40380
|
-
|
|
40381
|
-
|
|
40382
|
-
|
|
40383
|
-
|
|
40384
|
-
|
|
40385
|
-
|
|
40479
|
+
if (project.preactVersion !== null) capabilities.add("preact");
|
|
40480
|
+
addMajorLadder(capabilities, "preact", project.preactMajorVersion, 10, 20);
|
|
40481
|
+
if (project.preactVersion !== null && project.reactVersion === null) capabilities.add("pure-preact");
|
|
40482
|
+
return capabilities;
|
|
40483
|
+
};
|
|
40484
|
+
const capabilitiesByProject = /* @__PURE__ */ new WeakMap();
|
|
40485
|
+
const getCapabilities = (project) => {
|
|
40486
|
+
const cached = capabilitiesByProject.get(project);
|
|
40487
|
+
if (cached !== void 0) return cached;
|
|
40488
|
+
const capabilities = buildCapabilities(project);
|
|
40489
|
+
capabilitiesByProject.set(project, capabilities);
|
|
40386
40490
|
return capabilities;
|
|
40387
40491
|
};
|
|
40388
|
-
const shouldEnableRule = (requires, tags, capabilities, ignoredTags,
|
|
40492
|
+
const shouldEnableRule = (requires, tags, capabilities, ignoredTags, disabledWhen) => {
|
|
40389
40493
|
if (requires) {
|
|
40390
40494
|
for (const capability of requires) if (!capabilities.has(capability)) return false;
|
|
40391
40495
|
}
|
|
40392
40496
|
if (tags?.includes("react-jsx-only") && !capabilities.has("react")) return false;
|
|
40393
|
-
if (
|
|
40394
|
-
for (const capability of
|
|
40497
|
+
if (disabledWhen) {
|
|
40498
|
+
for (const capability of disabledWhen) if (capabilities.has(capability)) return false;
|
|
40395
40499
|
}
|
|
40396
40500
|
if (tags) {
|
|
40397
40501
|
for (const tag of tags) if (ignoredTags.has(tag)) return false;
|
|
@@ -40402,14 +40506,14 @@ const yieldToEventLoop = () => new Promise((resolve) => {
|
|
|
40402
40506
|
setImmediate(resolve);
|
|
40403
40507
|
});
|
|
40404
40508
|
const createSecurityScanSession = (rootDirectory, options) => {
|
|
40405
|
-
const capabilities = options.project ?
|
|
40509
|
+
const capabilities = options.project ? getCapabilities(options.project) : /* @__PURE__ */ new Set();
|
|
40406
40510
|
const ignoredTags = options.ignoredTags ?? /* @__PURE__ */ new Set();
|
|
40407
40511
|
const enabledScanRules = REACT_DOCTOR_RULES.flatMap((entry) => {
|
|
40408
40512
|
const rule = entry.rule;
|
|
40409
40513
|
const scan = rule.scan;
|
|
40410
40514
|
if (typeof scan !== "function") return [];
|
|
40411
40515
|
if (rule.defaultEnabled === false) return [];
|
|
40412
|
-
if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.
|
|
40516
|
+
if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.disabledWhen)) return [];
|
|
40413
40517
|
return [{
|
|
40414
40518
|
entry,
|
|
40415
40519
|
scan,
|
|
@@ -40429,6 +40533,7 @@ const createSecurityScanSession = (rootDirectory, options) => {
|
|
|
40429
40533
|
return status === true;
|
|
40430
40534
|
};
|
|
40431
40535
|
const scanFileByRule = function* (file) {
|
|
40536
|
+
if (file === null) return;
|
|
40432
40537
|
for (const { entry, scan, committedFilesOnly } of enabledScanRules) {
|
|
40433
40538
|
for (const finding of scan(file)) {
|
|
40434
40539
|
if (committedFilesOnly && isFileGitIgnored(file)) continue;
|
|
@@ -42325,7 +42430,7 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
42325
42430
|
const reactCompilerRules = reactHooksJsPlugin ? applyRuleSeverityControls(filterRulesToAvailable(REACT_COMPILER_RULES, "react-hooks-js", reactHooksJsPlugin.availableRuleNames), severityControls) : {};
|
|
42326
42431
|
const jsPlugins = [];
|
|
42327
42432
|
if (reactHooksJsPlugin) jsPlugins.push(reactHooksJsPlugin.entry);
|
|
42328
|
-
const capabilities =
|
|
42433
|
+
const capabilities = getCapabilities(project);
|
|
42329
42434
|
const enabledReactDoctorRules = {};
|
|
42330
42435
|
for (const registryEntry of REACT_DOCTOR_RULES) {
|
|
42331
42436
|
const rule = reactDoctorPlugin.rules[registryEntry.id];
|
|
@@ -42336,7 +42441,7 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
42336
42441
|
if (rule.scan !== void 0) continue;
|
|
42337
42442
|
if (customRulesOnly && registryEntry.originallyExternal) continue;
|
|
42338
42443
|
if (rule.framework !== "global" && !rule.requires) continue;
|
|
42339
|
-
if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.
|
|
42444
|
+
if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.disabledWhen)) continue;
|
|
42340
42445
|
const explicitRuleOverride = resolveRuleSeverityOverride({ ruleKey: registryEntry.key }, severityControls);
|
|
42341
42446
|
if (rule.defaultEnabled === false && explicitRuleOverride === void 0) continue;
|
|
42342
42447
|
const severity = resolveRuleSeverityOverride({
|
|
@@ -42367,6 +42472,7 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
42367
42472
|
settings: { "react-doctor": {
|
|
42368
42473
|
framework: project.framework,
|
|
42369
42474
|
rootDirectory: resolveSettingsRootDirectory(project.rootDirectory),
|
|
42475
|
+
capabilities: [...capabilities].sort(),
|
|
42370
42476
|
...project.shopifyFlashListMajorVersion !== null ? { shopifyFlashListMajorVersion: project.shopifyFlashListMajorVersion } : {},
|
|
42371
42477
|
...serverAuthFunctionNames && serverAuthFunctionNames.length > 0 ? { serverAuthFunctionNames: [...serverAuthFunctionNames] } : {}
|
|
42372
42478
|
} },
|
|
@@ -42657,25 +42763,18 @@ const mapWithConcurrency = async (items, concurrency, task) => {
|
|
|
42657
42763
|
if (errors.length > 0) throw errors[0];
|
|
42658
42764
|
return results;
|
|
42659
42765
|
};
|
|
42660
|
-
const getPublicEnvPrefix = (framework) => {
|
|
42661
|
-
switch (framework) {
|
|
42662
|
-
case "nextjs": return "NEXT_PUBLIC_*";
|
|
42663
|
-
case "vite":
|
|
42664
|
-
case "tanstack-start": return "VITE_*";
|
|
42665
|
-
case "cra": return "REACT_APP_*";
|
|
42666
|
-
case "gatsby": return "GATSBY_*";
|
|
42667
|
-
default: return null;
|
|
42668
|
-
}
|
|
42669
|
-
};
|
|
42670
|
-
const buildNoSecretsRecommendation = (project, fallbackRecommendation) => {
|
|
42671
|
-
const publicEnvPrefix = getPublicEnvPrefix(project.framework);
|
|
42672
|
-
if (!publicEnvPrefix) return fallbackRecommendation;
|
|
42673
|
-
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`;
|
|
42674
|
-
};
|
|
42675
42766
|
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";
|
|
42767
|
+
const ACCESSORS_MIN_VERSION = "3.15.0";
|
|
42768
|
+
const supportsAccessors = (reanimatedVersion) => {
|
|
42769
|
+
if (reanimatedVersion === null) return true;
|
|
42770
|
+
const lowerBound = import_semver.validRange(reanimatedVersion) !== null ? import_semver.minVersion(reanimatedVersion) : import_semver.coerce(reanimatedVersion);
|
|
42771
|
+
if (lowerBound === null) return true;
|
|
42772
|
+
return import_semver.gte(lowerBound, ACCESSORS_MIN_VERSION);
|
|
42773
|
+
};
|
|
42676
42774
|
const appendReanimatedSharedValueHint = (help, rule, project) => {
|
|
42677
42775
|
if (rule !== "immutability") return help;
|
|
42678
42776
|
if (!project.hasReanimated) return help;
|
|
42777
|
+
if (!supportsAccessors(project.reanimatedVersion)) return help;
|
|
42679
42778
|
if (!help) return REANIMATED_SHARED_VALUE_HINT;
|
|
42680
42779
|
return `${help}\n\n${REANIMATED_SHARED_VALUE_HINT}`;
|
|
42681
42780
|
};
|
|
@@ -42837,13 +42936,13 @@ const REACT_USE_BINDING_RESOLUTION = {
|
|
|
42837
42936
|
isReactUseBinding: true,
|
|
42838
42937
|
isReactNamespaceBinding: false
|
|
42839
42938
|
};
|
|
42840
|
-
const getScriptKind = (filename) => {
|
|
42939
|
+
const getScriptKind$2 = (filename) => {
|
|
42841
42940
|
if (filename.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
42842
42941
|
if (filename.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
42843
42942
|
if (filename.endsWith(".ts")) return ts.ScriptKind.TS;
|
|
42844
42943
|
return ts.ScriptKind.JS;
|
|
42845
42944
|
};
|
|
42846
|
-
const getUtf16Offset = (sourceText, utf8Offset) => Buffer.from(sourceText).subarray(0, utf8Offset).toString("utf8").length;
|
|
42945
|
+
const getUtf16Offset$1 = (sourceText, utf8Offset) => Buffer.from(sourceText).subarray(0, utf8Offset).toString("utf8").length;
|
|
42847
42946
|
const unwrapExpression = (expression) => {
|
|
42848
42947
|
let currentExpression = expression;
|
|
42849
42948
|
while (ts.isParenthesizedExpression(currentExpression) || ts.isAsExpression(currentExpression) || ts.isSatisfiesExpression(currentExpression) || ts.isNonNullExpression(currentExpression) || ts.isTypeAssertionExpression(currentExpression)) currentExpression = currentExpression.expression;
|
|
@@ -43092,8 +43191,8 @@ const findUseCallIdentifier = (sourceFile, useOffset) => {
|
|
|
43092
43191
|
return matchedIdentifier;
|
|
43093
43192
|
};
|
|
43094
43193
|
const resolveUseCallBinding = (sourceText, filename, utf8Offset) => {
|
|
43095
|
-
const sourceFile = ts.createSourceFile(filename, sourceText, ts.ScriptTarget.Latest, true, getScriptKind(filename));
|
|
43096
|
-
const useIdentifier = findUseCallIdentifier(sourceFile, getUtf16Offset(sourceText, utf8Offset));
|
|
43194
|
+
const sourceFile = ts.createSourceFile(filename, sourceText, ts.ScriptTarget.Latest, true, getScriptKind$2(filename));
|
|
43195
|
+
const useIdentifier = findUseCallIdentifier(sourceFile, getUtf16Offset$1(sourceText, utf8Offset));
|
|
43097
43196
|
if (!useIdentifier) return null;
|
|
43098
43197
|
return resolveIdentifierBinding(useIdentifier, collectReactImportBindings(sourceFile), sourceFile);
|
|
43099
43198
|
};
|
|
@@ -43114,6 +43213,201 @@ const shouldSuppressLocalUseHookDiagnostic = (diagnostic, rootDirectory) => {
|
|
|
43114
43213
|
const bindingResolution = resolveUseCallBinding(sourceText, absolutePath, primaryLabel.span.offset);
|
|
43115
43214
|
return bindingResolution !== null && !bindingResolution.isReactUseBinding;
|
|
43116
43215
|
};
|
|
43216
|
+
const SUPPRESSED_COMPILER_CODES = new Set(["react-hooks-js(immutability)", "react-hooks-js(refs)"]);
|
|
43217
|
+
const WORKLET_DIRECTIVE = "worklet";
|
|
43218
|
+
const WORKLET_ACCEPTING_CALLEES = new Set([
|
|
43219
|
+
"useAnimatedStyle",
|
|
43220
|
+
"useAnimatedProps",
|
|
43221
|
+
"useDerivedValue",
|
|
43222
|
+
"useAnimatedReaction",
|
|
43223
|
+
"useAnimatedScrollHandler",
|
|
43224
|
+
"useAnimatedGestureHandler",
|
|
43225
|
+
"useFrameCallback",
|
|
43226
|
+
"runOnUI",
|
|
43227
|
+
"executeOnUIRuntimeSync"
|
|
43228
|
+
]);
|
|
43229
|
+
const GESTURE_ROOT_IDENTIFIER = "Gesture";
|
|
43230
|
+
const getScriptKind$1 = (filename) => {
|
|
43231
|
+
if (filename.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
43232
|
+
if (filename.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
43233
|
+
if (filename.endsWith(".ts")) return ts.ScriptKind.TS;
|
|
43234
|
+
return ts.ScriptKind.JS;
|
|
43235
|
+
};
|
|
43236
|
+
const getUtf16Offset = (sourceText, utf8Offset) => Buffer.from(sourceText).subarray(0, utf8Offset).toString("utf8").length;
|
|
43237
|
+
const hasWorkletDirective = (node) => {
|
|
43238
|
+
if (!ts.isFunctionDeclaration(node) && !ts.isFunctionExpression(node) && !ts.isArrowFunction(node) && !ts.isMethodDeclaration(node)) return false;
|
|
43239
|
+
const body = node.body;
|
|
43240
|
+
if (!body || !ts.isBlock(body)) return false;
|
|
43241
|
+
for (const statement of body.statements) {
|
|
43242
|
+
if (!ts.isExpressionStatement(statement)) return false;
|
|
43243
|
+
if (!ts.isStringLiteral(statement.expression)) return false;
|
|
43244
|
+
if (statement.expression.text === WORKLET_DIRECTIVE) return true;
|
|
43245
|
+
}
|
|
43246
|
+
return false;
|
|
43247
|
+
};
|
|
43248
|
+
const rootIdentifierOfCallChain = (expression) => {
|
|
43249
|
+
let currentExpression = expression;
|
|
43250
|
+
while (true) {
|
|
43251
|
+
if (ts.isIdentifier(currentExpression)) return currentExpression.text;
|
|
43252
|
+
if (ts.isPropertyAccessExpression(currentExpression)) {
|
|
43253
|
+
currentExpression = currentExpression.expression;
|
|
43254
|
+
continue;
|
|
43255
|
+
}
|
|
43256
|
+
if (ts.isCallExpression(currentExpression)) {
|
|
43257
|
+
currentExpression = currentExpression.expression;
|
|
43258
|
+
continue;
|
|
43259
|
+
}
|
|
43260
|
+
return null;
|
|
43261
|
+
}
|
|
43262
|
+
};
|
|
43263
|
+
const isWorkletAcceptingCall = (callExpression) => {
|
|
43264
|
+
const callee = callExpression.expression;
|
|
43265
|
+
if (ts.isIdentifier(callee)) return WORKLET_ACCEPTING_CALLEES.has(callee.text);
|
|
43266
|
+
if (ts.isPropertyAccessExpression(callee)) {
|
|
43267
|
+
if (WORKLET_ACCEPTING_CALLEES.has(callee.name.text)) return true;
|
|
43268
|
+
return rootIdentifierOfCallChain(callee) === GESTURE_ROOT_IDENTIFIER;
|
|
43269
|
+
}
|
|
43270
|
+
return false;
|
|
43271
|
+
};
|
|
43272
|
+
const isFunctionWorklet = (functionNode) => {
|
|
43273
|
+
if (hasWorkletDirective(functionNode)) return true;
|
|
43274
|
+
const parent = functionNode.parent;
|
|
43275
|
+
return ts.isCallExpression(parent) && parent.arguments.some((argument) => argument === functionNode) && isWorkletAcceptingCall(parent);
|
|
43276
|
+
};
|
|
43277
|
+
const findNodeAtOffset = (sourceFile, targetOffset) => {
|
|
43278
|
+
let matchedNode = null;
|
|
43279
|
+
const visit = (node) => {
|
|
43280
|
+
if (node.getStart(sourceFile) > targetOffset || node.getEnd() <= targetOffset) return;
|
|
43281
|
+
matchedNode = node;
|
|
43282
|
+
ts.forEachChild(node, visit);
|
|
43283
|
+
};
|
|
43284
|
+
visit(sourceFile);
|
|
43285
|
+
return matchedNode;
|
|
43286
|
+
};
|
|
43287
|
+
const isOffsetInsideWorklet = (sourceFile, targetOffset) => {
|
|
43288
|
+
let currentNode = findNodeAtOffset(sourceFile, targetOffset);
|
|
43289
|
+
while (currentNode) {
|
|
43290
|
+
if (ts.isFunctionLike(currentNode) && isFunctionWorklet(currentNode)) return true;
|
|
43291
|
+
currentNode = currentNode.parent;
|
|
43292
|
+
}
|
|
43293
|
+
return false;
|
|
43294
|
+
};
|
|
43295
|
+
const shouldSuppressCompilerFindingInWorklet = (diagnostic, project, rootDirectory) => {
|
|
43296
|
+
if (!SUPPRESSED_COMPILER_CODES.has(diagnostic.code)) return false;
|
|
43297
|
+
if (!project.hasReanimated) return false;
|
|
43298
|
+
const primaryLabel = diagnostic.labels[0];
|
|
43299
|
+
if (!primaryLabel) return false;
|
|
43300
|
+
const absolutePath = Path.isAbsolute(diagnostic.filename) ? diagnostic.filename : Path.join(rootDirectory, diagnostic.filename);
|
|
43301
|
+
let sourceText;
|
|
43302
|
+
try {
|
|
43303
|
+
sourceText = NFS.readFileSync(absolutePath, "utf-8");
|
|
43304
|
+
} catch {
|
|
43305
|
+
return false;
|
|
43306
|
+
}
|
|
43307
|
+
return isOffsetInsideWorklet(ts.createSourceFile(absolutePath, sourceText, ts.ScriptTarget.Latest, true, getScriptKind$1(absolutePath)), getUtf16Offset(sourceText, primaryLabel.span.offset));
|
|
43308
|
+
};
|
|
43309
|
+
const MANUAL_MEMOIZATION_PLUGIN = "react-doctor";
|
|
43310
|
+
const MANUAL_MEMOIZATION_RULE = "react-compiler-no-manual-memoization";
|
|
43311
|
+
const REACT_COMPILER_PLUGIN = "react-hooks-js";
|
|
43312
|
+
const getScriptKind = (filename) => {
|
|
43313
|
+
if (filename.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
43314
|
+
if (filename.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
43315
|
+
if (filename.endsWith(".ts")) return ts.ScriptKind.TS;
|
|
43316
|
+
return ts.ScriptKind.JS;
|
|
43317
|
+
};
|
|
43318
|
+
const isManualMemoizationDiagnostic = (diagnostic) => diagnostic.plugin === MANUAL_MEMOIZATION_PLUGIN && diagnostic.rule === MANUAL_MEMOIZATION_RULE;
|
|
43319
|
+
const nodeLineRange = (sourceFile, node) => ({
|
|
43320
|
+
startLine: sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1,
|
|
43321
|
+
endLine: sourceFile.getLineAndCharacterOfPosition(node.getEnd()).line + 1
|
|
43322
|
+
});
|
|
43323
|
+
const findOutermostEnclosingFunctionRange = (sourceFile, targetLine) => {
|
|
43324
|
+
let foundRange = null;
|
|
43325
|
+
const visit = (node) => {
|
|
43326
|
+
if (foundRange) return;
|
|
43327
|
+
if (ts.isFunctionLike(node)) {
|
|
43328
|
+
const range = nodeLineRange(sourceFile, node);
|
|
43329
|
+
if (range.startLine <= targetLine && targetLine <= range.endLine) foundRange = range;
|
|
43330
|
+
return;
|
|
43331
|
+
}
|
|
43332
|
+
ts.forEachChild(node, visit);
|
|
43333
|
+
};
|
|
43334
|
+
visit(sourceFile);
|
|
43335
|
+
return foundRange;
|
|
43336
|
+
};
|
|
43337
|
+
const NEWLINE_BYTE = 10;
|
|
43338
|
+
const lineOfUtf8Offset = (sourceBuffer, utf8Offset) => {
|
|
43339
|
+
let lineNumber = 1;
|
|
43340
|
+
const scanEnd = Math.min(utf8Offset, sourceBuffer.length);
|
|
43341
|
+
for (let byteIndex = 0; byteIndex < scanEnd; byteIndex++) if (sourceBuffer[byteIndex] === NEWLINE_BYTE) lineNumber++;
|
|
43342
|
+
return lineNumber;
|
|
43343
|
+
};
|
|
43344
|
+
/**
|
|
43345
|
+
* Drops `react-doctor/react-compiler-no-manual-memoization` diagnostics
|
|
43346
|
+
* whose enclosing function also carries a `react-hooks-js` (React
|
|
43347
|
+
* Compiler) diagnostic. Every react-hooks-js finding marks a function the
|
|
43348
|
+
* compiler could NOT compile (a bail-out / skip / preserve-manual-
|
|
43349
|
+
* memoization failure), so "React Compiler already caches every value
|
|
43350
|
+
* here" is false for that function and the manual memoization is
|
|
43351
|
+
* load-bearing, not dead weight.
|
|
43352
|
+
*
|
|
43353
|
+
* Matching is per enclosing function, not per file: a bailed-out
|
|
43354
|
+
* component must not silence redundant-memo findings in a sibling
|
|
43355
|
+
* component the compiler did optimize.
|
|
43356
|
+
*
|
|
43357
|
+
* Module-scope `memo(() => …)` wrappers have no enclosing function; for
|
|
43358
|
+
* those the memo call's own span is the containment range, so a bail-out
|
|
43359
|
+
* inside the inline-wrapped component still suppresses. Every failure
|
|
43360
|
+
* path (unreadable file, missing span) fails open and keeps the
|
|
43361
|
+
* diagnostic.
|
|
43362
|
+
*/
|
|
43363
|
+
const suppressMemoizationInBailedOutFunctions = (diagnostics, rootDirectory) => {
|
|
43364
|
+
const compilerDiagnosticLinesByFile = /* @__PURE__ */ new Map();
|
|
43365
|
+
let hasManualMemoizationDiagnostic = false;
|
|
43366
|
+
for (const diagnostic of diagnostics) {
|
|
43367
|
+
if (isManualMemoizationDiagnostic(diagnostic)) {
|
|
43368
|
+
hasManualMemoizationDiagnostic = true;
|
|
43369
|
+
continue;
|
|
43370
|
+
}
|
|
43371
|
+
if (diagnostic.plugin !== REACT_COMPILER_PLUGIN || diagnostic.line <= 0) continue;
|
|
43372
|
+
const lines = compilerDiagnosticLinesByFile.get(diagnostic.filePath);
|
|
43373
|
+
if (lines) lines.push(diagnostic.line);
|
|
43374
|
+
else compilerDiagnosticLinesByFile.set(diagnostic.filePath, [diagnostic.line]);
|
|
43375
|
+
}
|
|
43376
|
+
if (!hasManualMemoizationDiagnostic || compilerDiagnosticLinesByFile.size === 0) return diagnostics;
|
|
43377
|
+
const parsedFileCache = /* @__PURE__ */ new Map();
|
|
43378
|
+
const readAndParse = (filePath) => {
|
|
43379
|
+
const cached = parsedFileCache.get(filePath);
|
|
43380
|
+
if (cached !== void 0) return cached;
|
|
43381
|
+
const absolutePath = Path.isAbsolute(filePath) ? filePath : Path.resolve(rootDirectory || ".", filePath);
|
|
43382
|
+
let parsed;
|
|
43383
|
+
try {
|
|
43384
|
+
const buffer = NFS.readFileSync(absolutePath);
|
|
43385
|
+
parsed = {
|
|
43386
|
+
sourceFile: ts.createSourceFile(absolutePath, buffer.toString("utf8"), ts.ScriptTarget.Latest, true, getScriptKind(absolutePath)),
|
|
43387
|
+
buffer
|
|
43388
|
+
};
|
|
43389
|
+
} catch {
|
|
43390
|
+
parsed = null;
|
|
43391
|
+
}
|
|
43392
|
+
parsedFileCache.set(filePath, parsed);
|
|
43393
|
+
return parsed;
|
|
43394
|
+
};
|
|
43395
|
+
const isInsideBailedOutFunction = (diagnostic) => {
|
|
43396
|
+
const compilerLines = compilerDiagnosticLinesByFile.get(diagnostic.filePath);
|
|
43397
|
+
if (!compilerLines || diagnostic.line <= 0) return false;
|
|
43398
|
+
const parsed = readAndParse(diagnostic.filePath);
|
|
43399
|
+
if (!parsed) return false;
|
|
43400
|
+
let containmentRange = findOutermostEnclosingFunctionRange(parsed.sourceFile, diagnostic.line);
|
|
43401
|
+
if (!containmentRange && diagnostic.offset !== void 0 && diagnostic.length !== void 0) containmentRange = {
|
|
43402
|
+
startLine: diagnostic.line,
|
|
43403
|
+
endLine: lineOfUtf8Offset(parsed.buffer, diagnostic.offset + diagnostic.length)
|
|
43404
|
+
};
|
|
43405
|
+
if (!containmentRange) return false;
|
|
43406
|
+
const { startLine, endLine } = containmentRange;
|
|
43407
|
+
return compilerLines.some((compilerLine) => startLine <= compilerLine && compilerLine <= endLine);
|
|
43408
|
+
};
|
|
43409
|
+
return diagnostics.filter((diagnostic) => !isManualMemoizationDiagnostic(diagnostic) || !isInsideBailedOutFunction(diagnostic));
|
|
43410
|
+
};
|
|
43117
43411
|
const FILEPATH_WITH_LOCATION_PATTERN = /\S+\.\w+:\d+:\d+[\s\S]*$/;
|
|
43118
43412
|
const LEADING_SEVERITY_LABEL_PATTERN = /^(?:Error|Warning):\s*/;
|
|
43119
43413
|
const TRAILING_PERIOD_PATTERN = /\.$/;
|
|
@@ -43149,8 +43443,14 @@ const PLUGIN_CATEGORY_MAP = {
|
|
|
43149
43443
|
};
|
|
43150
43444
|
const lookupOwnString = (record, key) => Object.hasOwn(record, key) ? record[key] : void 0;
|
|
43151
43445
|
const getRuleRecommendation = (ruleName, project) => {
|
|
43152
|
-
|
|
43153
|
-
return
|
|
43446
|
+
const rule = reactDoctorPlugin.rules[ruleName];
|
|
43447
|
+
if (!rule) return void 0;
|
|
43448
|
+
if (rule.recommendationFor) {
|
|
43449
|
+
const capabilities = getCapabilities(project);
|
|
43450
|
+
const conditionalRecommendation = rule.recommendationFor((capability) => capabilities.has(capability));
|
|
43451
|
+
if (conditionalRecommendation !== void 0) return conditionalRecommendation;
|
|
43452
|
+
}
|
|
43453
|
+
return rule.recommendation;
|
|
43154
43454
|
};
|
|
43155
43455
|
const getRuleCategory = (ruleName) => reactDoctorPlugin.rules[ruleName]?.category;
|
|
43156
43456
|
const getRuleTitle = (ruleName) => reactDoctorPlugin.rules[ruleName]?.title;
|
|
@@ -43253,7 +43553,7 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
43253
43553
|
minifiedFileCache.set(absolutePath, minified);
|
|
43254
43554
|
return minified;
|
|
43255
43555
|
};
|
|
43256
|
-
return parsed.diagnostics.filter((diagnostic) => diagnostic.code && isLintableSourceFile(diagnostic.filename) && !isMinifiedDiagnosticFile(diagnostic.filename) && !shouldSuppressLocalUseHookDiagnostic(diagnostic, rootDirectory)).map((diagnostic) => {
|
|
43556
|
+
return suppressMemoizationInBailedOutFunctions(parsed.diagnostics.filter((diagnostic) => diagnostic.code && isLintableSourceFile(diagnostic.filename) && !isMinifiedDiagnosticFile(diagnostic.filename) && !shouldSuppressLocalUseHookDiagnostic(diagnostic, rootDirectory) && !shouldSuppressCompilerFindingInWorklet(diagnostic, project, rootDirectory)).map((diagnostic) => {
|
|
43257
43557
|
const { plugin, rule } = parseRuleCode(diagnostic.code);
|
|
43258
43558
|
const primaryLabel = diagnostic.labels[0];
|
|
43259
43559
|
const cleaned = cleanDiagnosticMessage(diagnostic.message, diagnostic.help, plugin, rule, project);
|
|
@@ -43280,7 +43580,7 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
43280
43580
|
...resolveMatchByOccurrence(rule, category) ? { matchByOccurrence: true } : {},
|
|
43281
43581
|
...relatedLocations.length > 0 ? { relatedLocations } : {}
|
|
43282
43582
|
};
|
|
43283
|
-
});
|
|
43583
|
+
}), rootDirectory);
|
|
43284
43584
|
};
|
|
43285
43585
|
const buildOxlintChildEnv = (sourceEnv) => {
|
|
43286
43586
|
const childEnv = {};
|
|
@@ -43304,7 +43604,9 @@ const SANITIZED_ENV = buildOxlintChildEnv(process.env);
|
|
|
43304
43604
|
* - `OxlintBatchExceeded { kind: "output-too-large" }` — stdout+stderr
|
|
43305
43605
|
* crossed `OXLINT_OUTPUT_MAX_BYTES`.
|
|
43306
43606
|
* - `OxlintBatchExceeded { kind: "oom" | "killed" }` — child exited
|
|
43307
|
-
* on a signal (SIGABRT → OOM, others → generic kill)
|
|
43607
|
+
* on a signal (SIGABRT → OOM, others → generic kill), or with one
|
|
43608
|
+
* of the abort exit codes Windows reports instead of a signal
|
|
43609
|
+
* (`ABORT_EXIT_CODES` → OOM).
|
|
43308
43610
|
* - `OxlintSpawnFailed { cause }` — `spawn` itself errored, or the
|
|
43309
43611
|
* child exited successfully but printed only stderr.
|
|
43310
43612
|
*
|
|
@@ -43370,7 +43672,7 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath, spawnTimeoutMs = OXLIN
|
|
|
43370
43672
|
clearAbortListener();
|
|
43371
43673
|
reject(new ReactDoctorError({ reason: new OxlintSpawnFailed({ cause: error }) }));
|
|
43372
43674
|
});
|
|
43373
|
-
child.on("close", (
|
|
43675
|
+
child.on("close", (code, signal) => {
|
|
43374
43676
|
clearTimeout(timeoutHandle);
|
|
43375
43677
|
clearAbortListener();
|
|
43376
43678
|
if (didKillForSize) {
|
|
@@ -43380,10 +43682,11 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath, spawnTimeoutMs = OXLIN
|
|
|
43380
43682
|
}) }));
|
|
43381
43683
|
return;
|
|
43382
43684
|
}
|
|
43383
|
-
|
|
43685
|
+
const isAbortExitCode = code !== null && ABORT_EXIT_CODES.has(code);
|
|
43686
|
+
if (signal || isAbortExitCode) {
|
|
43384
43687
|
const stderrOutput = Buffer.concat(stderrBuffers).toString("utf-8").trim();
|
|
43385
|
-
const isOom = signal === "SIGABRT";
|
|
43386
|
-
const detailParts = [`killed by ${signal}`];
|
|
43688
|
+
const isOom = signal === "SIGABRT" || isAbortExitCode;
|
|
43689
|
+
const detailParts = [signal ? `killed by ${signal}` : `aborted with exit code ${code}`];
|
|
43387
43690
|
if (isOom) detailParts.push("try scanning fewer files with --diff");
|
|
43388
43691
|
if (stderrOutput) detailParts.push(stderrOutput);
|
|
43389
43692
|
reject(new ReactDoctorError({ reason: new OxlintBatchExceeded({
|
|
@@ -43426,12 +43729,19 @@ const isParallelismRelatedSpawnError = (error) => {
|
|
|
43426
43729
|
* (surfaced via `onPartialFailure`) so JSON-mode consumers see WHICH
|
|
43427
43730
|
* files were skipped instead of silently losing them.
|
|
43428
43731
|
*
|
|
43429
|
-
* Parallel runs (concurrency > 1) get
|
|
43430
|
-
* fails with a resource-exhaustion error that's exclusive
|
|
43431
|
-
* many oxlint subprocesses at once (EAGAIN / EMFILE /
|
|
43432
|
-
* see `isParallelismRelatedSpawnError`), the whole
|
|
43433
|
-
* with a single worker. That's the only failure a
|
|
43434
|
-
* clear, so every other error class is left to
|
|
43732
|
+
* Parallel runs (concurrency > 1) get two extra safety nets:
|
|
43733
|
+
* - If the pass fails with a resource-exhaustion error that's exclusive
|
|
43734
|
+
* to running many oxlint subprocesses at once (EAGAIN / EMFILE /
|
|
43735
|
+
* ENFILE / ENOMEM — see `isParallelismRelatedSpawnError`), the whole
|
|
43736
|
+
* pass replays once with a single worker. That's the only failure a
|
|
43737
|
+
* serial replay can clear, so every other error class is left to
|
|
43738
|
+
* propagate.
|
|
43739
|
+
* - Files dropped because oxlint's native binding SIGABRT'd under
|
|
43740
|
+
* memory pressure (`OxlintBatchExceeded { kind: "oom" }`) get one
|
|
43741
|
+
* serial single-file-batch rescue pass: the OOM is usually a function
|
|
43742
|
+
* of N concurrent oxlint allocator arenas, not the file itself, so a
|
|
43743
|
+
* lone process with the machine to itself typically clears it —
|
|
43744
|
+
* turning a partial scan into a complete (if slower) one.
|
|
43435
43745
|
*
|
|
43436
43746
|
* Errors that aren't splittable and aren't parallelism-related (oxlint
|
|
43437
43747
|
* config crash, JS plugin resolution failure, etc.) propagate to the
|
|
@@ -43441,13 +43751,16 @@ const isParallelismRelatedSpawnError = (error) => {
|
|
|
43441
43751
|
const spawnLintBatches = async (input) => {
|
|
43442
43752
|
const { baseArgs, fileBatches, rootDirectory, nodeBinaryPath, project, onPartialFailure, onFileProgress, spawnTimeoutMs, outputMaxBytes, splitTotalBudgetMs = OXLINT_SPLIT_TOTAL_BUDGET_MS, splitMaxDepth = 8, signal, deadlineEpochMs } = input;
|
|
43443
43753
|
const requestedConcurrency = resolveScanConcurrency(input.concurrency ?? 1);
|
|
43444
|
-
|
|
43445
|
-
const
|
|
43754
|
+
let rescueDeadlineEpochMs;
|
|
43755
|
+
const isPastDeadline = () => deadlineEpochMs !== void 0 && remainingDeadlineBudgetMs(deadlineEpochMs) === 0 || rescueDeadlineEpochMs !== void 0 && remainingDeadlineBudgetMs(rescueDeadlineEpochMs) === 0;
|
|
43756
|
+
const runBatchPass = async (concurrency, passBatches, passOnFileProgress) => {
|
|
43757
|
+
const totalFileCount = passBatches.reduce((sum, batch) => sum + batch.length, 0);
|
|
43446
43758
|
const allDiagnostics = [];
|
|
43447
43759
|
const droppedFiles = [];
|
|
43760
|
+
const oomDroppedFiles = [];
|
|
43448
43761
|
const deadlineSkippedFiles = [];
|
|
43449
|
-
const isPastDeadline = () => deadlineEpochMs !== void 0 && remainingDeadlineBudgetMs(deadlineEpochMs) === 0;
|
|
43450
43762
|
let firstDropReason = null;
|
|
43763
|
+
let firstNonOomDropReason = null;
|
|
43451
43764
|
const spawnLintBatch = async (batch, depth, batchState) => {
|
|
43452
43765
|
if (isPastDeadline()) {
|
|
43453
43766
|
deadlineSkippedFiles.push(...batch);
|
|
@@ -43469,12 +43782,14 @@ const spawnLintBatches = async (input) => {
|
|
|
43469
43782
|
const isDepthCapReached = depth >= splitMaxDepth;
|
|
43470
43783
|
if (batch.length <= 1 || isBudgetElapsed || isDepthCapReached) {
|
|
43471
43784
|
droppedFiles.push(...batch);
|
|
43472
|
-
|
|
43473
|
-
|
|
43474
|
-
|
|
43475
|
-
|
|
43476
|
-
|
|
43477
|
-
}
|
|
43785
|
+
const isOomDrop = error.reason._tag === "OxlintBatchExceeded" && error.reason.kind === "oom";
|
|
43786
|
+
if (isOomDrop) oomDroppedFiles.push(...batch);
|
|
43787
|
+
let limitHint = "";
|
|
43788
|
+
if (isDepthCapReached) limitHint = ` (split depth cap of ${splitMaxDepth} levels reached)`;
|
|
43789
|
+
else if (isBudgetElapsed) limitHint = ` (split budget of ${splitTotalBudgetMs / MILLISECONDS_PER_SECOND}s exhausted at depth ${depth})`;
|
|
43790
|
+
const dropReason = batch.length > 1 ? `${error.message}${limitHint}` : error.message;
|
|
43791
|
+
firstDropReason ??= dropReason;
|
|
43792
|
+
if (!isOomDrop) firstNonOomDropReason ??= dropReason;
|
|
43478
43793
|
return [];
|
|
43479
43794
|
}
|
|
43480
43795
|
const splitIndex = Math.ceil(batch.length / 2);
|
|
@@ -43484,16 +43799,16 @@ const spawnLintBatches = async (input) => {
|
|
|
43484
43799
|
let startedFileCount = 0;
|
|
43485
43800
|
let scannedFileCount = 0;
|
|
43486
43801
|
let displayedFileCount = 0;
|
|
43487
|
-
const progressTimer =
|
|
43802
|
+
const progressTimer = passOnFileProgress && totalFileCount > 1 ? setInterval(() => {
|
|
43488
43803
|
const ceiling = Math.min(startedFileCount, totalFileCount - 1);
|
|
43489
43804
|
if (displayedFileCount < ceiling) {
|
|
43490
43805
|
displayedFileCount += 1;
|
|
43491
|
-
|
|
43806
|
+
passOnFileProgress(displayedFileCount, totalFileCount);
|
|
43492
43807
|
}
|
|
43493
43808
|
}, 50) : null;
|
|
43494
43809
|
progressTimer?.unref?.();
|
|
43495
43810
|
try {
|
|
43496
|
-
const batchResults = await mapWithConcurrency(
|
|
43811
|
+
const batchResults = await mapWithConcurrency(passBatches, concurrency, async (batch) => {
|
|
43497
43812
|
if (isPastDeadline()) {
|
|
43498
43813
|
deadlineSkippedFiles.push(...batch);
|
|
43499
43814
|
return [];
|
|
@@ -43505,9 +43820,9 @@ const spawnLintBatches = async (input) => {
|
|
|
43505
43820
|
};
|
|
43506
43821
|
const batchDiagnostics = await spawnLintBatch(batch, 0, batchState);
|
|
43507
43822
|
scannedFileCount += batch.length - batchState.deadlineSkippedFileCount;
|
|
43508
|
-
if (
|
|
43823
|
+
if (passOnFileProgress) {
|
|
43509
43824
|
displayedFileCount = Math.min(Math.max(displayedFileCount, scannedFileCount), totalFileCount);
|
|
43510
|
-
|
|
43825
|
+
passOnFileProgress(displayedFileCount, totalFileCount);
|
|
43511
43826
|
}
|
|
43512
43827
|
return batchDiagnostics;
|
|
43513
43828
|
});
|
|
@@ -43515,22 +43830,53 @@ const spawnLintBatches = async (input) => {
|
|
|
43515
43830
|
} finally {
|
|
43516
43831
|
if (progressTimer !== null) clearInterval(progressTimer);
|
|
43517
43832
|
}
|
|
43518
|
-
|
|
43519
|
-
|
|
43520
|
-
|
|
43833
|
+
return {
|
|
43834
|
+
diagnostics: allDiagnostics,
|
|
43835
|
+
droppedFiles,
|
|
43836
|
+
oomDroppedFiles,
|
|
43837
|
+
deadlineSkippedFiles,
|
|
43838
|
+
firstDropReason,
|
|
43839
|
+
firstNonOomDropReason
|
|
43521
43840
|
};
|
|
43522
|
-
const reasonHint = firstDropReason ? ` — first failure: ${firstDropReason}` : "";
|
|
43523
|
-
reportSkippedFiles(droppedFiles, (fileListPreview) => `${droppedFiles.length} file(s) failed to lint and were skipped (${fileListPreview})${reasonHint}`);
|
|
43524
|
-
reportSkippedFiles(deadlineSkippedFiles, (fileListPreview) => `${deadlineSkippedFiles.length} file(s) skipped — max scan duration reached before they were linted (${fileListPreview})`);
|
|
43525
|
-
return allDiagnostics;
|
|
43526
43841
|
};
|
|
43527
|
-
let
|
|
43842
|
+
let outcome;
|
|
43843
|
+
let effectiveConcurrency = requestedConcurrency;
|
|
43528
43844
|
try {
|
|
43529
|
-
|
|
43845
|
+
outcome = await runBatchPass(requestedConcurrency, fileBatches, onFileProgress);
|
|
43530
43846
|
} catch (error) {
|
|
43531
43847
|
if (requestedConcurrency <= 1 || !isParallelismRelatedSpawnError(error)) throw error;
|
|
43532
|
-
|
|
43848
|
+
effectiveConcurrency = 1;
|
|
43849
|
+
outcome = await runBatchPass(1, fileBatches, onFileProgress);
|
|
43850
|
+
}
|
|
43851
|
+
let { droppedFiles, firstDropReason } = outcome;
|
|
43852
|
+
const diagnostics = outcome.diagnostics;
|
|
43853
|
+
if (outcome.oomDroppedFiles.length > 0 && effectiveConcurrency > 1 && !isPastDeadline()) {
|
|
43854
|
+
rescueDeadlineEpochMs = Date.now() + OXLINT_OOM_RESCUE_BUDGET_MS;
|
|
43855
|
+
const rescuedFiles = /* @__PURE__ */ new Set();
|
|
43856
|
+
let firstRescueFailureReason = null;
|
|
43857
|
+
for (const oomDroppedFile of outcome.oomDroppedFiles) {
|
|
43858
|
+
if (isPastDeadline()) break;
|
|
43859
|
+
try {
|
|
43860
|
+
const rescueOutcome = await runBatchPass(1, [[oomDroppedFile]], void 0);
|
|
43861
|
+
diagnostics.push(...rescueOutcome.diagnostics);
|
|
43862
|
+
if (rescueOutcome.droppedFiles.length > 0 || rescueOutcome.deadlineSkippedFiles.length > 0) firstRescueFailureReason ??= rescueOutcome.firstDropReason;
|
|
43863
|
+
else rescuedFiles.add(oomDroppedFile);
|
|
43864
|
+
} catch (error) {
|
|
43865
|
+
firstRescueFailureReason ??= error instanceof Error ? error.message : String(error);
|
|
43866
|
+
}
|
|
43867
|
+
}
|
|
43868
|
+
rescueDeadlineEpochMs = void 0;
|
|
43869
|
+
droppedFiles = droppedFiles.filter((filePath) => !rescuedFiles.has(filePath));
|
|
43870
|
+
if (droppedFiles.length === 0) firstDropReason = null;
|
|
43871
|
+
else firstDropReason = firstRescueFailureReason ?? outcome.firstNonOomDropReason ?? firstDropReason;
|
|
43533
43872
|
}
|
|
43873
|
+
const reportSkippedFiles = (skippedFiles, buildMessage) => {
|
|
43874
|
+
if (skippedFiles.length === 0 || !onPartialFailure) return;
|
|
43875
|
+
onPartialFailure(buildMessage(`${skippedFiles.slice(0, 3).join(", ")}${skippedFiles.length > 3 ? `, +${skippedFiles.length - 3} more` : ""}`));
|
|
43876
|
+
};
|
|
43877
|
+
const reasonHint = firstDropReason ? ` — first failure: ${firstDropReason}` : "";
|
|
43878
|
+
reportSkippedFiles(droppedFiles, (fileListPreview) => `${droppedFiles.length} file(s) failed to lint and were skipped (${fileListPreview})${reasonHint}`);
|
|
43879
|
+
reportSkippedFiles(outcome.deadlineSkippedFiles, (fileListPreview) => `${outcome.deadlineSkippedFiles.length} file(s) skipped — max scan duration reached before they were linted (${fileListPreview})`);
|
|
43534
43880
|
return dedupeDiagnostics(diagnostics);
|
|
43535
43881
|
};
|
|
43536
43882
|
let didValidate = false;
|
|
@@ -43791,6 +44137,7 @@ const runOxlint = async (options) => {
|
|
|
43791
44137
|
const serverAuthFunctionNames = Array.isArray(userConfig?.serverAuthFunctionNames) ? userConfig.serverAuthFunctionNames.filter((entry) => typeof entry === "string" && entry.length > 0) : void 0;
|
|
43792
44138
|
const severityControls = buildRuleSeverityControls(userConfig);
|
|
43793
44139
|
validateRuleRegistration();
|
|
44140
|
+
resetManifestCaches();
|
|
43794
44141
|
if (includePaths !== void 0 && includePaths.length === 0) return [];
|
|
43795
44142
|
const pluginPath = resolvePluginPath();
|
|
43796
44143
|
const extendsPaths = (adoptExistingLintConfig && !customRulesOnly ? detectUserLintConfigPaths(rootDirectory) : []).filter(canOxlintExtendConfig);
|
|
@@ -44453,9 +44800,10 @@ const CONTEXT_AXES = [
|
|
|
44453
44800
|
}
|
|
44454
44801
|
];
|
|
44455
44802
|
const SCORE_AXES = [...GATED_AXES, ...CONTEXT_AXES];
|
|
44456
|
-
const
|
|
44457
|
-
|
|
44458
|
-
|
|
44803
|
+
const gatedAxesForSection = (section) => section === "devDependencies" ? GATED_AXES.filter((axis) => axis.key !== "vulnerability") : GATED_AXES;
|
|
44804
|
+
const worstGatedAxis = (score, gatedAxes) => {
|
|
44805
|
+
let worst = gatedAxes[0];
|
|
44806
|
+
for (const axis of gatedAxes) if (score[axis.key] < score[worst.key]) worst = axis;
|
|
44459
44807
|
return worst;
|
|
44460
44808
|
};
|
|
44461
44809
|
const clampScore = (value) => {
|
|
@@ -44479,6 +44827,69 @@ const resolveConcreteVersion = (spec) => {
|
|
|
44479
44827
|
if (range === null || range === "*") return null;
|
|
44480
44828
|
return import_semver.minVersion(trimmed)?.version ?? null;
|
|
44481
44829
|
};
|
|
44830
|
+
const readTextFileOrNull = (filePath) => {
|
|
44831
|
+
try {
|
|
44832
|
+
return NFS.readFileSync(filePath, "utf-8");
|
|
44833
|
+
} catch {
|
|
44834
|
+
return null;
|
|
44835
|
+
}
|
|
44836
|
+
};
|
|
44837
|
+
const buildNpmLockLookup = (lockText) => {
|
|
44838
|
+
let parsed;
|
|
44839
|
+
try {
|
|
44840
|
+
parsed = JSON.parse(lockText);
|
|
44841
|
+
} catch {
|
|
44842
|
+
return null;
|
|
44843
|
+
}
|
|
44844
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
44845
|
+
const lock = parsed;
|
|
44846
|
+
return (name) => {
|
|
44847
|
+
const entry = lock.packages?.[`node_modules/${name}`] ?? lock.dependencies?.[name];
|
|
44848
|
+
return typeof entry?.version === "string" ? entry.version : null;
|
|
44849
|
+
};
|
|
44850
|
+
};
|
|
44851
|
+
const buildYarnLockLookup = (lockText) => {
|
|
44852
|
+
const lines = lockText.split(/\r?\n/);
|
|
44853
|
+
return (name, spec) => {
|
|
44854
|
+
const descriptor = `${name}@${spec}`;
|
|
44855
|
+
const berryDescriptor = `${name}@npm:${spec}`;
|
|
44856
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
44857
|
+
const line = lines[lineIndex];
|
|
44858
|
+
if (line.startsWith(" ") || line.startsWith("#") || !line.includes(":")) continue;
|
|
44859
|
+
const descriptors = line.replace(/:\s*$/, "").split(",").map((part) => part.trim().replace(/^"|"$/g, ""));
|
|
44860
|
+
if (!descriptors.includes(descriptor) && !descriptors.includes(berryDescriptor)) continue;
|
|
44861
|
+
for (let bodyIndex = lineIndex + 1; bodyIndex < lines.length && (lines[bodyIndex].startsWith(" ") || lines[bodyIndex] === ""); bodyIndex += 1) {
|
|
44862
|
+
const versionMatch = /^\s+version:?\s+"?([^"\s]+)"?/.exec(lines[bodyIndex]);
|
|
44863
|
+
if (versionMatch) return versionMatch[1];
|
|
44864
|
+
}
|
|
44865
|
+
return null;
|
|
44866
|
+
}
|
|
44867
|
+
return null;
|
|
44868
|
+
};
|
|
44869
|
+
};
|
|
44870
|
+
const buildPnpmLockLookup = (lockText) => {
|
|
44871
|
+
return (name) => {
|
|
44872
|
+
const entryPattern = new RegExp(`^ {4,8}['"]?${name.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&")}['"]?:\\s*$`);
|
|
44873
|
+
const lines = lockText.split(/\r?\n/);
|
|
44874
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
44875
|
+
if (!entryPattern.test(lines[lineIndex])) continue;
|
|
44876
|
+
for (let bodyIndex = lineIndex + 1; bodyIndex < lines.length && /^ {5,}\S/.test(lines[bodyIndex]); bodyIndex += 1) {
|
|
44877
|
+
const versionMatch = /^\s+version:\s+['"]?([^'"\s(]+)/.exec(lines[bodyIndex]);
|
|
44878
|
+
if (versionMatch) return versionMatch[1];
|
|
44879
|
+
}
|
|
44880
|
+
}
|
|
44881
|
+
return null;
|
|
44882
|
+
};
|
|
44883
|
+
};
|
|
44884
|
+
const buildLockedVersionLookup = (rootDirectory) => {
|
|
44885
|
+
const npmLockText = readTextFileOrNull(Path.join(rootDirectory, "package-lock.json"));
|
|
44886
|
+
if (npmLockText !== null) return buildNpmLockLookup(npmLockText);
|
|
44887
|
+
const yarnLockText = readTextFileOrNull(Path.join(rootDirectory, "yarn.lock"));
|
|
44888
|
+
if (yarnLockText !== null) return buildYarnLockLookup(yarnLockText);
|
|
44889
|
+
const pnpmLockText = readTextFileOrNull(Path.join(rootDirectory, "pnpm-lock.yaml"));
|
|
44890
|
+
if (pnpmLockText !== null) return buildPnpmLockLookup(pnpmLockText);
|
|
44891
|
+
return null;
|
|
44892
|
+
};
|
|
44482
44893
|
const locateDependencyKey = (packageJsonText, section, name) => {
|
|
44483
44894
|
const needle = `"${name}"`;
|
|
44484
44895
|
const sectionHeader = new RegExp(`"${section}"\\s*:\\s*\\{`);
|
|
@@ -44505,7 +44916,7 @@ const locateDependencyKey = (packageJsonText, section, name) => {
|
|
|
44505
44916
|
}
|
|
44506
44917
|
return null;
|
|
44507
44918
|
};
|
|
44508
|
-
const collectDependenciesToScore = (packageJson, packageJsonText, includeDevDependencies) => {
|
|
44919
|
+
const collectDependenciesToScore = (packageJson, packageJsonText, includeDevDependencies, lockedVersionLookup) => {
|
|
44509
44920
|
const sectionByName = /* @__PURE__ */ new Map();
|
|
44510
44921
|
for (const name of Object.keys(packageJson.dependencies ?? {})) sectionByName.set(name, "dependencies");
|
|
44511
44922
|
if (includeDevDependencies) {
|
|
@@ -44515,13 +44926,18 @@ const collectDependenciesToScore = (packageJson, packageJsonText, includeDevDepe
|
|
|
44515
44926
|
for (const [name, section] of sectionByName) {
|
|
44516
44927
|
if (SUPPLY_CHAIN_IGNORED_PACKAGES.has(name)) continue;
|
|
44517
44928
|
const spec = (packageJson[section] ?? {})[name] ?? "";
|
|
44518
|
-
const
|
|
44519
|
-
if (
|
|
44929
|
+
const floorVersion = resolveConcreteVersion(spec);
|
|
44930
|
+
if (floorVersion === null) continue;
|
|
44931
|
+
const lockedVersion = lockedVersionLookup?.(name, spec) ?? null;
|
|
44932
|
+
const lockSatisfiesSpec = lockedVersion !== null && import_semver.valid(lockedVersion) !== null && import_semver.satisfies(lockedVersion, spec);
|
|
44933
|
+
const isExactPin = import_semver.valid(spec.trim()) !== null;
|
|
44520
44934
|
const location = locateDependencyKey(packageJsonText, section, name);
|
|
44521
44935
|
dependencies.push({
|
|
44522
44936
|
name,
|
|
44523
|
-
version,
|
|
44937
|
+
version: lockSatisfiesSpec ? lockedVersion : floorVersion,
|
|
44524
44938
|
spec,
|
|
44939
|
+
section,
|
|
44940
|
+
resolution: isExactPin ? "pin" : lockSatisfiesSpec ? "locked" : "floor",
|
|
44525
44941
|
line: location?.line ?? 0,
|
|
44526
44942
|
column: location?.column ?? 0
|
|
44527
44943
|
});
|
|
@@ -44672,7 +45088,11 @@ const formatAlertReason = (topAlerts, totalCount) => {
|
|
|
44672
45088
|
}
|
|
44673
45089
|
return `Socket flagged ${totalCount} alerts (${topAlerts.map((alert) => friendlyAlertType(alert.type)).join(", ")}${totalCount > topAlerts.length ? ` (+${totalCount - topAlerts.length} more)` : ""}); most severe: ${displaySeverity(topAlerts[0].severity)}.`;
|
|
44674
45090
|
};
|
|
44675
|
-
const formatDependencyIdentity = (dependency) =>
|
|
45091
|
+
const formatDependencyIdentity = (dependency) => {
|
|
45092
|
+
if (dependency.resolution === "pin") return `${dependency.name}@${dependency.version}`;
|
|
45093
|
+
if (dependency.resolution === "locked") return `${dependency.name}@${dependency.version} (lockfile resolution of "${dependency.spec}")`;
|
|
45094
|
+
return `${dependency.name}@${dependency.version} (lowest version "${dependency.spec}" allows)`;
|
|
45095
|
+
};
|
|
44676
45096
|
const buildSupplyChainHelp = (dependency, failingAxis, topAlerts, packagePageUrl, options) => {
|
|
44677
45097
|
const hasCriticalAlert = topAlerts.some((alert) => alert.severity.toLowerCase() === "critical");
|
|
44678
45098
|
const entry = `\`"${dependency.name}": "${dependency.spec}"\``;
|
|
@@ -44715,7 +45135,7 @@ const buildLowScoreDiagnostic = (dependency, artifact, failingAxis, options) =>
|
|
|
44715
45135
|
const checkSupplyChain = (input) => gen(function* () {
|
|
44716
45136
|
const options = resolveOptions(input.userConfig);
|
|
44717
45137
|
const packageJsonPath = Path.join(input.rootDirectory, "package.json");
|
|
44718
|
-
const dependencies = collectDependenciesToScore(readPackageJson$1(packageJsonPath), readPackageJsonText(packageJsonPath), options.includeDevDependencies);
|
|
45138
|
+
const dependencies = collectDependenciesToScore(readPackageJson$1(packageJsonPath), readPackageJsonText(packageJsonPath), options.includeDevDependencies, buildLockedVersionLookup(input.rootDirectory));
|
|
44719
45139
|
if (dependencies.length === 0) return [];
|
|
44720
45140
|
const cacheDirectory = isSupplyChainCacheDisabled() ? null : resolveReactDoctorCacheDir(input.rootDirectory);
|
|
44721
45141
|
if (cacheDirectory !== null) pruneExpiredSocketCache(cacheDirectory);
|
|
@@ -44724,7 +45144,7 @@ const checkSupplyChain = (input) => gen(function* () {
|
|
|
44724
45144
|
for (let index = 0; index < dependencies.length; index += 1) {
|
|
44725
45145
|
const artifact = artifacts[index];
|
|
44726
45146
|
if (!artifact) continue;
|
|
44727
|
-
const worstAxis = worstGatedAxis(artifact.score);
|
|
45147
|
+
const worstAxis = worstGatedAxis(artifact.score, gatedAxesForSection(dependencies[index].section));
|
|
44728
45148
|
if (toHundred(artifact.score[worstAxis.key]) >= options.minScore) continue;
|
|
44729
45149
|
diagnostics.push(buildLowScoreDiagnostic(dependencies[index], artifact, worstAxis, options));
|
|
44730
45150
|
}
|
|
@@ -45425,6 +45845,13 @@ const summarizeDiagnostics = (diagnostics, worstScore = null, worstScoreLabel =
|
|
|
45425
45845
|
scoreLabel: worstScoreLabel
|
|
45426
45846
|
};
|
|
45427
45847
|
};
|
|
45848
|
+
/**
|
|
45849
|
+
* Whether project discovery resolved a React-compatible runtime (React or
|
|
45850
|
+
* Preact, which ships the same hooks + JSX model). This is the same
|
|
45851
|
+
* predicate that grants the `react` capability, so `false` means every
|
|
45852
|
+
* React-runtime rule family was gated off for the project.
|
|
45853
|
+
*/
|
|
45854
|
+
const hasReactRuntime = (project) => project.reactVersion !== null || project.preactVersion !== null;
|
|
45428
45855
|
const toJsonDiff = (diff) => {
|
|
45429
45856
|
if (!diff) return null;
|
|
45430
45857
|
return {
|
|
@@ -46007,7 +46434,7 @@ const makeNoopConsole = () => ({
|
|
|
46007
46434
|
});
|
|
46008
46435
|
//#endregion
|
|
46009
46436
|
//#region src/cli/utils/version.ts
|
|
46010
|
-
const VERSION = "0.7.
|
|
46437
|
+
const VERSION = "0.7.2-dev.08b768b";
|
|
46011
46438
|
//#endregion
|
|
46012
46439
|
//#region src/cli/utils/json-mode.ts
|
|
46013
46440
|
let context = null;
|
|
@@ -46376,13 +46803,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
|
|
|
46376
46803
|
* uploads source-map artifacts under, so stack frames symbolicate. Honors the
|
|
46377
46804
|
* standard `SENTRY_RELEASE` override.
|
|
46378
46805
|
*/
|
|
46379
|
-
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.7.
|
|
46806
|
+
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.7.2-dev.08b768b`;
|
|
46380
46807
|
/**
|
|
46381
46808
|
* Deployment environment shown in Sentry's environment filter. Defaults to
|
|
46382
46809
|
* `production` for tagged releases and `development` for dev/unbuilt versions,
|
|
46383
46810
|
* overridable via the standard `SENTRY_ENVIRONMENT` env var.
|
|
46384
46811
|
*/
|
|
46385
|
-
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.7.
|
|
46812
|
+
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.7.2-dev.08b768b") ? "development" : "production");
|
|
46386
46813
|
/**
|
|
46387
46814
|
* Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
|
|
46388
46815
|
* (set to `0` to disable tracing) and falls back to
|
|
@@ -58899,13 +59326,15 @@ const buildOutcomeAttributes = (input) => {
|
|
|
58899
59326
|
const countBySource = {
|
|
58900
59327
|
config: 0,
|
|
58901
59328
|
override: 0,
|
|
58902
|
-
inline: 0
|
|
59329
|
+
inline: 0,
|
|
59330
|
+
"foreign-inline": 0
|
|
58903
59331
|
};
|
|
58904
59332
|
for (const suppression of input.suppressedRuleCounts) countBySource[suppression.source] += suppression.count;
|
|
58905
|
-
suppressionRollup.suppressed = countBySource.config + countBySource.override + countBySource.inline;
|
|
59333
|
+
suppressionRollup.suppressed = countBySource.config + countBySource.override + countBySource.inline + countBySource["foreign-inline"];
|
|
58906
59334
|
suppressionRollup.suppressedConfig = countBySource.config;
|
|
58907
59335
|
suppressionRollup.suppressedOverride = countBySource.override;
|
|
58908
59336
|
suppressionRollup.suppressedInline = countBySource.inline;
|
|
59337
|
+
suppressionRollup.suppressedForeignInline = countBySource["foreign-inline"];
|
|
58909
59338
|
}
|
|
58910
59339
|
const attributes = {
|
|
58911
59340
|
...withNamespace("outcome", {
|
|
@@ -64555,4 +64984,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
|
|
|
64555
64984
|
export {};
|
|
64556
64985
|
|
|
64557
64986
|
//# sourceMappingURL=cli.js.map
|
|
64558
|
-
//# debugId=
|
|
64987
|
+
//# debugId=9aed4e70-dd94-51e8-9810-f8e8945d80a3
|