react-doctor 0.7.1 → 0.7.2-dev.0eb5293
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1136 -654
- package/dist/index.d.ts +21 -2
- package/dist/index.js +1129 -645
- package/dist/lsp.js +1087 -632
- package/package.json +6 -6
package/dist/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]="91ba16f6-8b1a-5cd9-a5d2-fc0b6fce2508")}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",
|
|
@@ -36144,7 +36177,7 @@ const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
|
|
|
36144
36177
|
const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
|
|
36145
36178
|
const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
|
|
36146
36179
|
const SIDECAR_LINT_CACHE_FILENAME = "sidecar-lint-cache.json";
|
|
36147
|
-
const CORE_PACKAGE_VERSION = "0.7.
|
|
36180
|
+
const CORE_PACKAGE_VERSION = "0.7.2";
|
|
36148
36181
|
const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
|
|
36149
36182
|
const DEAD_CODE_SUMMARY_CACHE_FILENAME = "dead-code-summaries.json";
|
|
36150
36183
|
const SUPPLY_CHAIN_PLUGIN = "socket";
|
|
@@ -36154,8 +36187,9 @@ const SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS = 9e4;
|
|
|
36154
36187
|
const SUPPLY_CHAIN_CACHE_TTL_MS = 864e5;
|
|
36155
36188
|
const SUPPLY_CHAIN_CACHE_SUBDIR = "supply-chain";
|
|
36156
36189
|
const SUPPLY_CHAIN_IGNORED_PACKAGES = new Set(["next"]);
|
|
36157
|
-
const
|
|
36158
|
-
const
|
|
36190
|
+
const isIgnoredDirectoryName = (directoryName) => IGNORED_DIRECTORIES.has(directoryName) || directoryName.startsWith(".") && !SCANNED_DOT_DIRECTORIES.has(directoryName);
|
|
36191
|
+
const hasIgnoredPathSegment = (relativePath) => relativePath.split(/[/\\]/).slice(0, -1).some((segment) => isIgnoredDirectoryName(segment));
|
|
36192
|
+
const isLintableSourceFile = (filePath) => SOURCE_FILE_PATTERN.test(filePath) && !GENERATED_BUNDLE_FILE_PATTERN.test(filePath) && !GENERATED_SOURCE_DIRECTORY_PATTERN.test(filePath);
|
|
36159
36193
|
const isMinifiedSource = (absolutePath) => {
|
|
36160
36194
|
let fileDescriptor;
|
|
36161
36195
|
try {
|
|
@@ -36189,25 +36223,6 @@ const isLargeMinifiedFile = (absolutePath, knownSizeBytes) => {
|
|
|
36189
36223
|
cachedIsLargeMinifiedByPath.set(absolutePath, result);
|
|
36190
36224
|
return result;
|
|
36191
36225
|
};
|
|
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
36226
|
function* walkSourceTreeFiles(rootDirectory) {
|
|
36212
36227
|
const stack = [rootDirectory];
|
|
36213
36228
|
while (stack.length > 0) {
|
|
@@ -36216,7 +36231,7 @@ function* walkSourceTreeFiles(rootDirectory) {
|
|
|
36216
36231
|
for (const entry of readDirectoryEntries(currentDirectory)) {
|
|
36217
36232
|
const absolutePath = Path.join(currentDirectory, entry.name);
|
|
36218
36233
|
if (entry.isDirectory()) {
|
|
36219
|
-
if (!
|
|
36234
|
+
if (!isIgnoredDirectoryName(entry.name)) stack.push(absolutePath);
|
|
36220
36235
|
continue;
|
|
36221
36236
|
}
|
|
36222
36237
|
if (entry.isFile()) yield {
|
|
@@ -36310,6 +36325,146 @@ const findMonorepoRoot = (startDirectory) => {
|
|
|
36310
36325
|
* byte-equivalent copy.
|
|
36311
36326
|
*/
|
|
36312
36327
|
const isProjectBoundary = (directory) => NFS.existsSync(Path.join(directory, ".git")) || isMonorepoRoot(directory);
|
|
36328
|
+
const TSCONFIG_FILENAME = "tsconfig.json";
|
|
36329
|
+
const isRelativeExtendsValue = (extendsValue) => extendsValue.startsWith("./") || extendsValue.startsWith("../") || Path.isAbsolute(extendsValue);
|
|
36330
|
+
const ensureJsonExtension = (filePath) => Path.extname(filePath) === "" ? `${filePath}.json` : filePath;
|
|
36331
|
+
const resolvePackageExtendsPath = (extendsValue, fromConfigDirectory) => {
|
|
36332
|
+
const requireFromConfig = createRequire(Path.join(fromConfigDirectory, "tsconfig.json"));
|
|
36333
|
+
const candidates = [
|
|
36334
|
+
extendsValue,
|
|
36335
|
+
ensureJsonExtension(extendsValue),
|
|
36336
|
+
`${extendsValue.replace(/\/$/, "")}/tsconfig.json`
|
|
36337
|
+
];
|
|
36338
|
+
for (const candidate of candidates) try {
|
|
36339
|
+
return requireFromConfig.resolve(candidate);
|
|
36340
|
+
} catch {
|
|
36341
|
+
continue;
|
|
36342
|
+
}
|
|
36343
|
+
return null;
|
|
36344
|
+
};
|
|
36345
|
+
const resolveExtendsPath = (extendsValue, fromConfigDirectory) => {
|
|
36346
|
+
if (isRelativeExtendsValue(extendsValue)) return ensureJsonExtension(Path.resolve(fromConfigDirectory, extendsValue));
|
|
36347
|
+
return resolvePackageExtendsPath(extendsValue, fromConfigDirectory);
|
|
36348
|
+
};
|
|
36349
|
+
const normalizeCompilerOptions = (compilerOptions) => {
|
|
36350
|
+
if (!isPlainObject(compilerOptions)) return { hasExplicitLib: false };
|
|
36351
|
+
const target = typeof compilerOptions.target === "string" ? compilerOptions.target : void 0;
|
|
36352
|
+
const hasExplicitLib = Object.hasOwn(compilerOptions, "lib");
|
|
36353
|
+
return {
|
|
36354
|
+
target,
|
|
36355
|
+
lib: Array.isArray(compilerOptions.lib) ? compilerOptions.lib.filter((entry) => typeof entry === "string") : void 0,
|
|
36356
|
+
hasExplicitLib
|
|
36357
|
+
};
|
|
36358
|
+
};
|
|
36359
|
+
const readTsConfig = (filePath) => {
|
|
36360
|
+
let content;
|
|
36361
|
+
try {
|
|
36362
|
+
content = NFS.readFileSync(filePath, "utf-8");
|
|
36363
|
+
} catch {
|
|
36364
|
+
return null;
|
|
36365
|
+
}
|
|
36366
|
+
const parsed = ts.parseConfigFileTextToJson(filePath, content);
|
|
36367
|
+
if (!isPlainObject(parsed.config)) return null;
|
|
36368
|
+
return {
|
|
36369
|
+
extends: typeof parsed.config.extends === "string" ? parsed.config.extends : void 0,
|
|
36370
|
+
referencePaths: normalizeReferencePaths(parsed.config.references),
|
|
36371
|
+
compilerOptions: normalizeCompilerOptions(parsed.config.compilerOptions)
|
|
36372
|
+
};
|
|
36373
|
+
};
|
|
36374
|
+
const normalizeReferencePaths = (references) => {
|
|
36375
|
+
if (!Array.isArray(references)) return [];
|
|
36376
|
+
return references.map((reference) => isPlainObject(reference) && typeof reference.path === "string" ? reference.path : null).filter((referencePath) => referencePath !== null);
|
|
36377
|
+
};
|
|
36378
|
+
const mergeCompilerOptions = (inherited, current) => {
|
|
36379
|
+
const target = current.target ?? inherited?.target;
|
|
36380
|
+
const hasExplicitLib = current.hasExplicitLib || Boolean(inherited?.hasExplicitLib);
|
|
36381
|
+
return {
|
|
36382
|
+
target,
|
|
36383
|
+
lib: current.hasExplicitLib ? current.lib : inherited?.lib,
|
|
36384
|
+
hasExplicitLib
|
|
36385
|
+
};
|
|
36386
|
+
};
|
|
36387
|
+
const readResolvedCompilerOptions = (tsConfigPath, extendsDepth, visitedPaths) => {
|
|
36388
|
+
const realPath = NFS.realpathSync.native(tsConfigPath);
|
|
36389
|
+
if (visitedPaths.has(realPath)) return null;
|
|
36390
|
+
const tsConfig = readTsConfig(realPath);
|
|
36391
|
+
if (!tsConfig) return null;
|
|
36392
|
+
const nextVisitedPaths = new Set(visitedPaths);
|
|
36393
|
+
nextVisitedPaths.add(realPath);
|
|
36394
|
+
if (tsConfig.extends && extendsDepth < 8) {
|
|
36395
|
+
const parentPath = resolveExtendsPath(tsConfig.extends, Path.dirname(realPath));
|
|
36396
|
+
if (parentPath && isFile(parentPath)) return mergeCompilerOptions(readResolvedCompilerOptions(parentPath, extendsDepth + 1, nextVisitedPaths), tsConfig.compilerOptions);
|
|
36397
|
+
}
|
|
36398
|
+
return tsConfig.compilerOptions;
|
|
36399
|
+
};
|
|
36400
|
+
const targetYearIsPreES2023 = (target) => {
|
|
36401
|
+
const year = ES_TARGET_YEAR_BY_NAME[target.toLowerCase()];
|
|
36402
|
+
return year !== void 0 && year < 2023;
|
|
36403
|
+
};
|
|
36404
|
+
const libEntryIncludesES2023Array = (entry) => {
|
|
36405
|
+
const normalizedEntry = entry.toLowerCase();
|
|
36406
|
+
if (normalizedEntry === "esnext" || normalizedEntry === "esnext.array") return true;
|
|
36407
|
+
const esYearMatch = /^es(\d{4})(?:\.(.+))?$/.exec(normalizedEntry);
|
|
36408
|
+
if (!esYearMatch) return false;
|
|
36409
|
+
if (Number(esYearMatch[1]) < 2023) return false;
|
|
36410
|
+
const component = esYearMatch[2];
|
|
36411
|
+
return component === void 0 || component === "array";
|
|
36412
|
+
};
|
|
36413
|
+
const libIncludesES2023 = (lib) => lib.some(libEntryIncludesES2023Array);
|
|
36414
|
+
const compilerOptionsArePreES2023 = (compilerOptions) => {
|
|
36415
|
+
if (compilerOptions.target) return targetYearIsPreES2023(compilerOptions.target);
|
|
36416
|
+
if (compilerOptions.hasExplicitLib) return !libIncludesES2023(compilerOptions.lib ?? []);
|
|
36417
|
+
return false;
|
|
36418
|
+
};
|
|
36419
|
+
const compilerOptionsDeclareTargetOrLib = (compilerOptions) => compilerOptions.hasExplicitLib || compilerOptions.target !== void 0;
|
|
36420
|
+
const detectPreES2023FromConfig = (tsConfigPath, visitedConfigPaths = /* @__PURE__ */ new Set()) => {
|
|
36421
|
+
if (visitedConfigPaths.has(tsConfigPath)) return false;
|
|
36422
|
+
const compilerOptions = readResolvedCompilerOptions(tsConfigPath, 0, /* @__PURE__ */ new Set());
|
|
36423
|
+
if (!compilerOptions) return false;
|
|
36424
|
+
if (!compilerOptionsDeclareTargetOrLib(compilerOptions)) {
|
|
36425
|
+
const tsConfig = readTsConfig(tsConfigPath);
|
|
36426
|
+
if (!tsConfig) return false;
|
|
36427
|
+
const nextVisitedConfigPaths = new Set(visitedConfigPaths);
|
|
36428
|
+
nextVisitedConfigPaths.add(tsConfigPath);
|
|
36429
|
+
const configDirectory = Path.dirname(tsConfigPath);
|
|
36430
|
+
return tsConfig.referencePaths.some((referencePath) => {
|
|
36431
|
+
const resolvedReferencePath = Path.resolve(configDirectory, referencePath);
|
|
36432
|
+
const referencedConfigPath = isFile(resolvedReferencePath) ? resolvedReferencePath : Path.join(resolvedReferencePath, TSCONFIG_FILENAME);
|
|
36433
|
+
return isFile(referencedConfigPath) && detectPreES2023FromConfig(referencedConfigPath, nextVisitedConfigPaths);
|
|
36434
|
+
});
|
|
36435
|
+
}
|
|
36436
|
+
return compilerOptionsArePreES2023(compilerOptions);
|
|
36437
|
+
};
|
|
36438
|
+
const detectPreES2023Target = (directory) => {
|
|
36439
|
+
const tsConfigPath = Path.join(directory, TSCONFIG_FILENAME);
|
|
36440
|
+
if (isFile(tsConfigPath)) return detectPreES2023FromConfig(tsConfigPath);
|
|
36441
|
+
for (const fallbackFilename of FALLBACK_TSCONFIG_FILENAMES) {
|
|
36442
|
+
const fallbackPath = Path.join(directory, fallbackFilename);
|
|
36443
|
+
if (isFile(fallbackPath)) return detectPreES2023FromConfig(fallbackPath);
|
|
36444
|
+
}
|
|
36445
|
+
return false;
|
|
36446
|
+
};
|
|
36447
|
+
const FALLBACK_TSCONFIG_FILENAMES = ["tsconfig.app.json", "tsconfig.build.json"];
|
|
36448
|
+
const FRAMEWORK_PACKAGES = {
|
|
36449
|
+
next: "nextjs",
|
|
36450
|
+
"@tanstack/react-start": "tanstack-start",
|
|
36451
|
+
vite: "vite",
|
|
36452
|
+
"react-scripts": "cra",
|
|
36453
|
+
"@remix-run/react": "remix",
|
|
36454
|
+
gatsby: "gatsby",
|
|
36455
|
+
expo: "expo",
|
|
36456
|
+
"react-native": "react-native"
|
|
36457
|
+
};
|
|
36458
|
+
const detectFramework = (dependencies) => {
|
|
36459
|
+
for (const [packageName, frameworkName] of Object.entries(FRAMEWORK_PACKAGES)) if (dependencies[packageName]) return frameworkName;
|
|
36460
|
+
if (dependencies.preact && !dependencies.react) return "preact";
|
|
36461
|
+
return "unknown";
|
|
36462
|
+
};
|
|
36463
|
+
const MOBILE_FRAMEWORKS = new Set(["expo", "react-native"]);
|
|
36464
|
+
const frameworkMergeRank = (framework) => {
|
|
36465
|
+
if (framework === "unknown") return 3;
|
|
36466
|
+
return MOBILE_FRAMEWORKS.has(framework) ? 2 : 1;
|
|
36467
|
+
};
|
|
36313
36468
|
const REACT_COMPILER_PACKAGES = new Set([
|
|
36314
36469
|
"babel-plugin-react-compiler",
|
|
36315
36470
|
"react-compiler-runtime",
|
|
@@ -36346,6 +36501,7 @@ const EXPO_APP_CONFIG_FILENAMES = [
|
|
|
36346
36501
|
];
|
|
36347
36502
|
const REACT_COMPILER_PACKAGE_REFERENCE_PATTERN = /babel-plugin-react-compiler|react-compiler-runtime|eslint-plugin-react-compiler|["']react-compiler["']/;
|
|
36348
36503
|
const REACT_COMPILER_ENABLED_FLAG_PATTERN = /["']?reactCompiler["']?\s*:\s*(?:true\b|\{)/;
|
|
36504
|
+
const STATIC_EXPORT_OUTPUT_PATTERN = /(?:^|[^.\w])["']?output["']?\s*:\s*["']export["']/m;
|
|
36349
36505
|
const hasCompilerPackage = (packageJson) => {
|
|
36350
36506
|
const allDependencies = {
|
|
36351
36507
|
...packageJson.peerDependencies,
|
|
@@ -36378,34 +36534,10 @@ const detectReactCompiler = (directory, packageJson) => {
|
|
|
36378
36534
|
}
|
|
36379
36535
|
return false;
|
|
36380
36536
|
};
|
|
36381
|
-
const
|
|
36382
|
-
|
|
36383
|
-
|
|
36384
|
-
|
|
36385
|
-
"react-scripts": "cra",
|
|
36386
|
-
"@remix-run/react": "remix",
|
|
36387
|
-
gatsby: "gatsby",
|
|
36388
|
-
expo: "expo",
|
|
36389
|
-
"react-native": "react-native"
|
|
36390
|
-
};
|
|
36391
|
-
const FRAMEWORK_DISPLAY_NAMES = {
|
|
36392
|
-
nextjs: "Next.js",
|
|
36393
|
-
"tanstack-start": "TanStack Start",
|
|
36394
|
-
vite: "Vite",
|
|
36395
|
-
cra: "Create React App",
|
|
36396
|
-
remix: "Remix",
|
|
36397
|
-
gatsby: "Gatsby",
|
|
36398
|
-
expo: "Expo",
|
|
36399
|
-
"react-native": "React Native",
|
|
36400
|
-
preact: "Preact",
|
|
36401
|
-
unknown: "React"
|
|
36402
|
-
};
|
|
36403
|
-
const formatFrameworkName = (framework) => FRAMEWORK_DISPLAY_NAMES[framework];
|
|
36404
|
-
const detectFramework = (dependencies) => {
|
|
36405
|
-
for (const [packageName, frameworkName] of Object.entries(FRAMEWORK_PACKAGES)) if (dependencies[packageName]) return frameworkName;
|
|
36406
|
-
if (dependencies.preact && !dependencies.react) return "preact";
|
|
36407
|
-
return "unknown";
|
|
36408
|
-
};
|
|
36537
|
+
const detectNextjsStaticExport = (directory) => NEXT_CONFIG_FILENAMES.some((filename) => {
|
|
36538
|
+
const filePath = Path.join(directory, filename);
|
|
36539
|
+
return isFile(filePath) && STATIC_EXPORT_OUTPUT_PATTERN.test(NFS.readFileSync(filePath, "utf-8"));
|
|
36540
|
+
});
|
|
36409
36541
|
const UNRESOLVABLE_PROTOCOL_VERSION = /^(?:file|git|github|https?|link|patch|portal|workspace|npm):/i;
|
|
36410
36542
|
const DIST_TAG_VERSION = /^[a-z][a-z0-9._-]*$/i;
|
|
36411
36543
|
const WILDCARD_VERSION = /^[*xX](?:\.[*xX])*$/;
|
|
@@ -36543,10 +36675,76 @@ const isConcreteDependencyVersion = (version) => {
|
|
|
36543
36675
|
const normalizedVersion = normalizeDependencyVersion(version);
|
|
36544
36676
|
return normalizedVersion !== null && /\d/.test(normalizedVersion);
|
|
36545
36677
|
};
|
|
36546
|
-
const
|
|
36547
|
-
if (
|
|
36548
|
-
|
|
36549
|
-
return
|
|
36678
|
+
const isMajorMinorAtLeast = (detected, required) => {
|
|
36679
|
+
if (detected === null) return true;
|
|
36680
|
+
if (detected.major !== required.major) return detected.major > required.major;
|
|
36681
|
+
return detected.minor >= required.minor;
|
|
36682
|
+
};
|
|
36683
|
+
const parseReactMajor = (reactVersion) => {
|
|
36684
|
+
if (typeof reactVersion !== "string") return null;
|
|
36685
|
+
return getLowestDependencyMajor(reactVersion);
|
|
36686
|
+
};
|
|
36687
|
+
const MAJOR_MINOR_PATTERN = /(\d{1,4})\.(\d{1,4})/;
|
|
36688
|
+
const MAJOR_ONLY_PATTERN = /(\d{1,4})/;
|
|
36689
|
+
const UPPER_BOUND_COMPARATOR_PATTERN = /<=?\s{0,8}\d{1,4}(?:\.\d{1,4}){0,2}(?:-[^\s,|]+)?/g;
|
|
36690
|
+
const parseReactMajorMinor = (reactVersion) => {
|
|
36691
|
+
if (typeof reactVersion !== "string") return null;
|
|
36692
|
+
const trimmed = reactVersion.trim();
|
|
36693
|
+
if (trimmed.length === 0) return null;
|
|
36694
|
+
const lowerBoundsOnly = trimmed.replace(UPPER_BOUND_COMPARATOR_PATTERN, " ").trim();
|
|
36695
|
+
if (lowerBoundsOnly.length === 0) return null;
|
|
36696
|
+
const majorMinorMatch = lowerBoundsOnly.match(MAJOR_MINOR_PATTERN);
|
|
36697
|
+
if (majorMinorMatch) {
|
|
36698
|
+
const major = Number.parseInt(majorMinorMatch[1], 10);
|
|
36699
|
+
const minor = Number.parseInt(majorMinorMatch[2], 10);
|
|
36700
|
+
if (!Number.isFinite(major) || major <= 0) return null;
|
|
36701
|
+
if (!Number.isFinite(minor) || minor < 0) return null;
|
|
36702
|
+
return {
|
|
36703
|
+
major,
|
|
36704
|
+
minor
|
|
36705
|
+
};
|
|
36706
|
+
}
|
|
36707
|
+
const majorOnlyMatch = lowerBoundsOnly.match(MAJOR_ONLY_PATTERN);
|
|
36708
|
+
if (!majorOnlyMatch) return null;
|
|
36709
|
+
const major = Number.parseInt(majorOnlyMatch[1], 10);
|
|
36710
|
+
if (!Number.isFinite(major) || major <= 0) return null;
|
|
36711
|
+
return {
|
|
36712
|
+
major,
|
|
36713
|
+
minor: 0
|
|
36714
|
+
};
|
|
36715
|
+
};
|
|
36716
|
+
const parseLowerBoundVersion = (versionSpec) => import_semver.validRange(versionSpec) !== null ? import_semver.minVersion(versionSpec) : import_semver.coerce(versionSpec);
|
|
36717
|
+
const parseTailwindMajorMinor = (tailwindVersion) => {
|
|
36718
|
+
if (typeof tailwindVersion !== "string") return null;
|
|
36719
|
+
const trimmed = tailwindVersion.trim();
|
|
36720
|
+
if (trimmed.length === 0) return null;
|
|
36721
|
+
const lowerBound = parseLowerBoundVersion(trimmed);
|
|
36722
|
+
if (lowerBound === null || lowerBound.major <= 0) return null;
|
|
36723
|
+
return {
|
|
36724
|
+
major: lowerBound.major,
|
|
36725
|
+
minor: lowerBound.minor
|
|
36726
|
+
};
|
|
36727
|
+
};
|
|
36728
|
+
const hasUpperBoundOnlyPeerRange = (range) => {
|
|
36729
|
+
if (typeof range !== "string") return false;
|
|
36730
|
+
const normalizedRange = normalizeDependencyVersion(range);
|
|
36731
|
+
if (normalizedRange === null) return false;
|
|
36732
|
+
return splitDependencyVersionBranches(normalizedRange).some((branch) => {
|
|
36733
|
+
const normalizedBranch = normalizeDependencyVersion(branch);
|
|
36734
|
+
return normalizedBranch !== null && getBranchLowestMajor(normalizedBranch) === null && hasUpperBoundComparator(normalizedBranch);
|
|
36735
|
+
});
|
|
36736
|
+
};
|
|
36737
|
+
const peerRangeMinMajor = (range) => {
|
|
36738
|
+
if (typeof range !== "string") return null;
|
|
36739
|
+
return getLowestDependencyMajor(range);
|
|
36740
|
+
};
|
|
36741
|
+
const resolveEffectiveReactMajor = (reactVersion, packageJson) => {
|
|
36742
|
+
const installedReactMajor = parseReactMajor(reactVersion);
|
|
36743
|
+
const peerReactRange = packageJson.peerDependencies?.react;
|
|
36744
|
+
if (typeof peerReactRange !== "string") return installedReactMajor;
|
|
36745
|
+
const peerFloor = peerRangeMinMajor(peerReactRange);
|
|
36746
|
+
if (peerFloor === null) return hasUpperBoundOnlyPeerRange(peerReactRange) ? null : installedReactMajor;
|
|
36747
|
+
return installedReactMajor !== null ? Math.min(installedReactMajor, peerFloor) : peerFloor;
|
|
36550
36748
|
};
|
|
36551
36749
|
const isCatalogReference = (version) => version.startsWith("catalog:");
|
|
36552
36750
|
const extractCatalogName = (version) => {
|
|
@@ -36688,6 +36886,16 @@ const resolveCatalogVersion = (packageJson, packageName, rootDirectory, explicit
|
|
|
36688
36886
|
}
|
|
36689
36887
|
return null;
|
|
36690
36888
|
};
|
|
36889
|
+
const REACT_SECTIONS = [
|
|
36890
|
+
"dependencies",
|
|
36891
|
+
"peerDependencies",
|
|
36892
|
+
"devDependencies"
|
|
36893
|
+
];
|
|
36894
|
+
const TAILWIND_ZOD_SECTIONS = [
|
|
36895
|
+
"dependencies",
|
|
36896
|
+
"devDependencies",
|
|
36897
|
+
"peerDependencies"
|
|
36898
|
+
];
|
|
36691
36899
|
const EMPTY_DEPENDENCY_INFO = {
|
|
36692
36900
|
reactVersion: null,
|
|
36693
36901
|
tailwindVersion: null,
|
|
@@ -36728,6 +36936,10 @@ const extractDependencyInfo = (packageJson) => {
|
|
|
36728
36936
|
framework: detectFramework(allDependencies)
|
|
36729
36937
|
};
|
|
36730
36938
|
};
|
|
36939
|
+
const getDependencySpec = (packageJson, packageName) => {
|
|
36940
|
+
const spec = packageJson.dependencies?.[packageName] ?? packageJson.devDependencies?.[packageName] ?? packageJson.peerDependencies?.[packageName] ?? packageJson.optionalDependencies?.[packageName];
|
|
36941
|
+
return typeof spec === "string" ? spec : null;
|
|
36942
|
+
};
|
|
36731
36943
|
const getDependencyDeclaration = ({ packageJson, packageName, sections }) => {
|
|
36732
36944
|
for (const section of sections) {
|
|
36733
36945
|
const version = packageJson[section]?.[packageName];
|
|
@@ -36744,222 +36956,91 @@ const getDependencyDeclaration = ({ packageJson, packageName, sections }) => {
|
|
|
36744
36956
|
version: null
|
|
36745
36957
|
};
|
|
36746
36958
|
};
|
|
36747
|
-
const
|
|
36748
|
-
|
|
36749
|
-
|
|
36750
|
-
|
|
36751
|
-
|
|
36752
|
-
|
|
36753
|
-
|
|
36754
|
-
const
|
|
36755
|
-
|
|
36756
|
-
|
|
36757
|
-
|
|
36758
|
-
|
|
36759
|
-
|
|
36760
|
-
if (!isFile(Path.join(rootDirectory, "nx.json"))) return [];
|
|
36761
|
-
const collected = [];
|
|
36762
|
-
for (const candidate of NX_PROJECT_DISCOVERY_DIRS) {
|
|
36763
|
-
const candidatePath = Path.join(rootDirectory, candidate);
|
|
36764
|
-
if (!isDirectory(candidatePath)) continue;
|
|
36765
|
-
for (const entry of readDirectoryEntries(candidatePath)) {
|
|
36766
|
-
if (!entry.isDirectory()) continue;
|
|
36767
|
-
const projectDirectory = Path.join(candidatePath, entry.name);
|
|
36768
|
-
if (isFile(Path.join(projectDirectory, "project.json")) || isFile(Path.join(projectDirectory, "package.json"))) collected.push(`${candidate}/${entry.name}`);
|
|
36769
|
-
}
|
|
36770
|
-
}
|
|
36771
|
-
return collected;
|
|
36772
|
-
};
|
|
36773
|
-
const parsePnpmWorkspacePatterns = (rootDirectory) => {
|
|
36774
|
-
const workspacePath = Path.join(rootDirectory, "pnpm-workspace.yaml");
|
|
36775
|
-
if (!isFile(workspacePath)) return [];
|
|
36776
|
-
const content = NFS.readFileSync(workspacePath, "utf-8");
|
|
36777
|
-
const patterns = [];
|
|
36778
|
-
let isInsidePackagesBlock = false;
|
|
36779
|
-
for (const line of content.split("\n")) {
|
|
36780
|
-
const trimmed = line.trim();
|
|
36781
|
-
if (trimmed === "packages:") {
|
|
36782
|
-
isInsidePackagesBlock = true;
|
|
36783
|
-
continue;
|
|
36784
|
-
}
|
|
36785
|
-
if (isInsidePackagesBlock && trimmed.startsWith("-")) patterns.push(trimmed.replace(/^-\s*/, "").replace(/["']/g, ""));
|
|
36786
|
-
else if (isInsidePackagesBlock && trimmed.length > 0 && !trimmed.startsWith("#")) isInsidePackagesBlock = false;
|
|
36787
|
-
}
|
|
36788
|
-
return patterns;
|
|
36789
|
-
};
|
|
36790
|
-
const getWorkspacePatterns = (rootDirectory, packageJson) => {
|
|
36791
|
-
const pnpmPatterns = parsePnpmWorkspacePatterns(rootDirectory);
|
|
36792
|
-
if (pnpmPatterns.length > 0) return pnpmPatterns;
|
|
36793
|
-
if (Array.isArray(packageJson.workspaces)) return packageJson.workspaces;
|
|
36794
|
-
if (packageJson.workspaces?.packages) return packageJson.workspaces.packages;
|
|
36795
|
-
const nxPatterns = getNxWorkspaceDirectories(rootDirectory);
|
|
36796
|
-
if (nxPatterns.length > 0) return nxPatterns;
|
|
36797
|
-
return [];
|
|
36798
|
-
};
|
|
36799
|
-
const parseReactMajor = (reactVersion) => {
|
|
36800
|
-
if (typeof reactVersion !== "string") return null;
|
|
36801
|
-
return getLowestDependencyMajor(reactVersion);
|
|
36959
|
+
const REACT_DEPENDENCY_NAMES = new Set([
|
|
36960
|
+
"react",
|
|
36961
|
+
"react-native",
|
|
36962
|
+
"next",
|
|
36963
|
+
"preact"
|
|
36964
|
+
]);
|
|
36965
|
+
const hasReactDependency = (packageJson) => {
|
|
36966
|
+
const allDependencies = {
|
|
36967
|
+
...packageJson.peerDependencies,
|
|
36968
|
+
...packageJson.dependencies,
|
|
36969
|
+
...packageJson.devDependencies
|
|
36970
|
+
};
|
|
36971
|
+
return Object.keys(allDependencies).some((packageName) => REACT_DEPENDENCY_NAMES.has(packageName));
|
|
36802
36972
|
};
|
|
36803
|
-
const
|
|
36804
|
-
|
|
36805
|
-
|
|
36806
|
-
|
|
36807
|
-
|
|
36808
|
-
|
|
36809
|
-
}
|
|
36810
|
-
const wildcardIndex = cleanPattern.indexOf("*");
|
|
36811
|
-
const baseDirectory = Path.join(rootDirectory, cleanPattern.slice(0, wildcardIndex));
|
|
36812
|
-
const suffixAfterWildcard = cleanPattern.slice(wildcardIndex + 1);
|
|
36813
|
-
if (!isDirectory(baseDirectory)) return [];
|
|
36814
|
-
const resolved = [];
|
|
36815
|
-
for (const entry of readDirectoryEntries(baseDirectory)) {
|
|
36816
|
-
const entryPath = Path.join(baseDirectory, entry.name, suffixAfterWildcard);
|
|
36817
|
-
if (isDirectory(entryPath) && isFile(Path.join(entryPath, "package.json"))) resolved.push(entryPath);
|
|
36818
|
-
}
|
|
36819
|
-
return resolved;
|
|
36973
|
+
const getPreactVersion = (packageJson) => {
|
|
36974
|
+
return {
|
|
36975
|
+
...packageJson.peerDependencies,
|
|
36976
|
+
...packageJson.dependencies,
|
|
36977
|
+
...packageJson.devDependencies
|
|
36978
|
+
}.preact ?? null;
|
|
36820
36979
|
};
|
|
36821
|
-
const
|
|
36822
|
-
|
|
36823
|
-
|
|
36824
|
-
|
|
36825
|
-
|
|
36826
|
-
|
|
36827
|
-
|
|
36828
|
-
|
|
36980
|
+
const TANSTACK_QUERY_PACKAGES = new Set([
|
|
36981
|
+
"@tanstack/react-query",
|
|
36982
|
+
"@tanstack/query-core",
|
|
36983
|
+
"react-query"
|
|
36984
|
+
]);
|
|
36985
|
+
const hasTanStackQuery = (packageJson) => {
|
|
36986
|
+
const allDependencies = {
|
|
36987
|
+
...packageJson.peerDependencies,
|
|
36988
|
+
...packageJson.dependencies,
|
|
36989
|
+
...packageJson.devDependencies
|
|
36990
|
+
};
|
|
36991
|
+
return Object.keys(allDependencies).some((packageName) => TANSTACK_QUERY_PACKAGES.has(packageName));
|
|
36829
36992
|
};
|
|
36830
|
-
const
|
|
36831
|
-
if (!
|
|
36832
|
-
const
|
|
36833
|
-
const
|
|
36834
|
-
if (
|
|
36835
|
-
|
|
36836
|
-
|
|
36993
|
+
const resolveCatalogBackedDependencyVersion = ({ rootDirectory, rootPackageJson, packageName, version }) => {
|
|
36994
|
+
if (version === null || !isCatalogReference(version)) return version;
|
|
36995
|
+
const catalogName = extractCatalogName(version);
|
|
36996
|
+
const resolvedLocalVersion = resolveCatalogVersion(rootPackageJson, packageName, rootDirectory, catalogName);
|
|
36997
|
+
if (resolvedLocalVersion) return resolvedLocalVersion;
|
|
36998
|
+
const monorepoRoot = findMonorepoRoot(rootDirectory);
|
|
36999
|
+
if (!monorepoRoot) return version;
|
|
37000
|
+
const monorepoPackageJsonPath = Path.join(monorepoRoot, "package.json");
|
|
37001
|
+
if (!isFile(monorepoPackageJsonPath)) return version;
|
|
37002
|
+
return resolveCatalogVersion(readPackageJson$1(monorepoPackageJsonPath), packageName, monorepoRoot, catalogName) ?? version;
|
|
36837
37003
|
};
|
|
36838
|
-
|
|
36839
|
-
|
|
36840
|
-
|
|
36841
|
-
|
|
36842
|
-
|
|
36843
|
-
|
|
36844
|
-
|
|
36845
|
-
|
|
36846
|
-
|
|
36847
|
-
|
|
36848
|
-
|
|
36849
|
-
|
|
36850
|
-
|
|
36851
|
-
|
|
36852
|
-
|
|
36853
|
-
"peerDependencies",
|
|
36854
|
-
"devDependencies"
|
|
36855
|
-
],
|
|
36856
|
-
workspaceDirectory,
|
|
36857
|
-
workspacePackageJson
|
|
36858
|
-
});
|
|
36859
|
-
const tailwindVersion = resolveWorkspaceDependencyVersion({
|
|
36860
|
-
concreteVersion: info.tailwindVersion,
|
|
36861
|
-
packageName: "tailwindcss",
|
|
36862
|
-
rootDirectory,
|
|
36863
|
-
rootPackageJson: packageJson,
|
|
36864
|
-
sections: [
|
|
36865
|
-
"dependencies",
|
|
36866
|
-
"devDependencies",
|
|
36867
|
-
"peerDependencies"
|
|
36868
|
-
],
|
|
36869
|
-
workspaceDirectory,
|
|
36870
|
-
workspacePackageJson
|
|
36871
|
-
});
|
|
36872
|
-
const zodVersion = resolveWorkspaceDependencyVersion({
|
|
36873
|
-
concreteVersion: info.zodVersion,
|
|
36874
|
-
packageName: "zod",
|
|
36875
|
-
rootDirectory,
|
|
36876
|
-
rootPackageJson: packageJson,
|
|
36877
|
-
sections: [
|
|
36878
|
-
"dependencies",
|
|
36879
|
-
"devDependencies",
|
|
36880
|
-
"peerDependencies"
|
|
36881
|
-
],
|
|
36882
|
-
workspaceDirectory,
|
|
36883
|
-
workspacePackageJson
|
|
36884
|
-
});
|
|
36885
|
-
if (reactVersion && shouldReplaceReactVersion(result.reactVersion, reactVersion)) result.reactVersion = reactVersion;
|
|
36886
|
-
if (tailwindVersion && !result.tailwindVersion) result.tailwindVersion = tailwindVersion;
|
|
36887
|
-
if (zodVersion && !result.zodVersion) result.zodVersion = zodVersion;
|
|
36888
|
-
if (info.framework !== "unknown" && result.framework === "unknown") result.framework = info.framework;
|
|
36889
|
-
const resultReactMajor = parseReactMajor(result.reactVersion);
|
|
36890
|
-
if (result.reactVersion && result.tailwindVersion && result.framework !== "unknown" && resultReactMajor !== null && resultReactMajor <= 17) return result;
|
|
36891
|
-
}
|
|
37004
|
+
/**
|
|
37005
|
+
* The chain of ancestor directories of `startDirectory`, nearest first, up to
|
|
37006
|
+
* and including the filesystem root. With `includeStart`, the chain begins at
|
|
37007
|
+
* `startDirectory` itself; otherwise at its parent. The one place the "walk up
|
|
37008
|
+
* the tree" traversal is expressed, so each caller adds only its own match /
|
|
37009
|
+
* stop policy instead of re-deriving the `path.dirname` loop.
|
|
37010
|
+
*/
|
|
37011
|
+
const ancestorDirectories = (startDirectory, { includeStart }) => {
|
|
37012
|
+
const directories = [];
|
|
37013
|
+
let currentDirectory = includeStart ? startDirectory : Path.dirname(startDirectory);
|
|
37014
|
+
while (true) {
|
|
37015
|
+
directories.push(currentDirectory);
|
|
37016
|
+
const parentDirectory = Path.dirname(currentDirectory);
|
|
37017
|
+
if (parentDirectory === currentDirectory) return directories;
|
|
37018
|
+
currentDirectory = parentDirectory;
|
|
36892
37019
|
}
|
|
36893
|
-
return result;
|
|
36894
37020
|
};
|
|
36895
|
-
|
|
36896
|
-
|
|
36897
|
-
|
|
36898
|
-
|
|
36899
|
-
|
|
36900
|
-
|
|
36901
|
-
|
|
36902
|
-
|
|
36903
|
-
|
|
36904
|
-
|
|
36905
|
-
|
|
36906
|
-
|
|
36907
|
-
|
|
36908
|
-
|
|
36909
|
-
|
|
36910
|
-
|
|
36911
|
-
|
|
36912
|
-
})
|
|
36913
|
-
|
|
36914
|
-
|
|
36915
|
-
packageName: "tailwindcss",
|
|
36916
|
-
sections: [
|
|
36917
|
-
"dependencies",
|
|
36918
|
-
"devDependencies",
|
|
36919
|
-
"peerDependencies"
|
|
36920
|
-
]
|
|
36921
|
-
}) : null;
|
|
36922
|
-
const leafZodDeclaration = leafPackageJson ? getDependencyDeclaration({
|
|
36923
|
-
packageJson: leafPackageJson,
|
|
36924
|
-
packageName: "zod",
|
|
36925
|
-
sections: [
|
|
36926
|
-
"dependencies",
|
|
36927
|
-
"devDependencies",
|
|
36928
|
-
"peerDependencies"
|
|
36929
|
-
]
|
|
36930
|
-
}) : null;
|
|
36931
|
-
const shouldUseReactFallback = !leafReactDeclaration?.hasDeclaration;
|
|
36932
|
-
const shouldUseTailwindFallback = leafTailwindDeclaration?.hasDeclaration ?? true;
|
|
36933
|
-
const shouldUseZodFallback = leafZodDeclaration?.hasDeclaration ?? true;
|
|
36934
|
-
const reactCatalogVersion = shouldUseReactFallback ? resolveCatalogVersion(rootPackageJson, "react", monorepoRoot, leafReactDeclaration?.catalogReference) : null;
|
|
36935
|
-
const tailwindCatalogVersion = shouldUseTailwindFallback ? resolveCatalogVersion(rootPackageJson, "tailwindcss", monorepoRoot, leafTailwindDeclaration?.catalogReference) : null;
|
|
36936
|
-
const zodCatalogVersion = shouldUseZodFallback ? resolveCatalogVersion(rootPackageJson, "zod", monorepoRoot, leafZodDeclaration?.catalogReference) : null;
|
|
36937
|
-
const workspaceInfo = findReactInWorkspaces(monorepoRoot, rootPackageJson);
|
|
36938
|
-
return {
|
|
36939
|
-
reactVersion: shouldUseReactFallback ? reactCatalogVersion ?? rootInfo.reactVersion ?? workspaceInfo.reactVersion : rootInfo.reactVersion ?? workspaceInfo.reactVersion,
|
|
36940
|
-
tailwindVersion: shouldUseTailwindFallback ? tailwindCatalogVersion ?? rootInfo.tailwindVersion ?? workspaceInfo.tailwindVersion : null,
|
|
36941
|
-
zodVersion: shouldUseZodFallback ? zodCatalogVersion ?? rootInfo.zodVersion ?? workspaceInfo.zodVersion : null,
|
|
36942
|
-
framework: rootInfo.framework !== "unknown" ? rootInfo.framework : workspaceInfo.framework
|
|
36943
|
-
};
|
|
36944
|
-
};
|
|
36945
|
-
const findInWorkspacePackageJsons = (rootDirectory, rootPackageJson, select) => {
|
|
36946
|
-
const rootValue = select(rootPackageJson);
|
|
36947
|
-
if (rootValue !== null) return rootValue;
|
|
36948
|
-
const patterns = getWorkspacePatterns(rootDirectory, rootPackageJson);
|
|
36949
|
-
if (patterns.length === 0) return null;
|
|
36950
|
-
const visitedDirectories = /* @__PURE__ */ new Set();
|
|
36951
|
-
for (const pattern of patterns) {
|
|
36952
|
-
const directories = [...resolveWorkspaceDirectories(rootDirectory, pattern)].sort();
|
|
36953
|
-
for (const workspaceDirectory of directories) {
|
|
36954
|
-
if (visitedDirectories.has(workspaceDirectory)) continue;
|
|
36955
|
-
visitedDirectories.add(workspaceDirectory);
|
|
36956
|
-
const value = select(readPackageJson$1(Path.join(workspaceDirectory, "package.json")));
|
|
36957
|
-
if (value !== null) return value;
|
|
36958
|
-
}
|
|
37021
|
+
/**
|
|
37022
|
+
* Walk up from `startDirectory` to the nearest ancestor that owns a
|
|
37023
|
+
* `package.json`, stopping at (and including) the enclosing project boundary
|
|
37024
|
+
* — the working tree's git root or a monorepo root. Lets a scan of a package
|
|
37025
|
+
* subfolder that has no `package.json` of its own adopt the nearest enclosing
|
|
37026
|
+
* package as its project root, so it inherits that package's dependency +
|
|
37027
|
+
* framework detection instead of synthesizing an empty (React-blind) project.
|
|
37028
|
+
*
|
|
37029
|
+
* Generalizes the older monorepo-root-only lookup: the nearest ancestor is the
|
|
37030
|
+
* most specific owning package (a leaf workspace, not just the repo root), so
|
|
37031
|
+
* a subfolder of one workspace no longer borrows a sibling's capabilities.
|
|
37032
|
+
*
|
|
37033
|
+
* Returns `null` when no ancestor `package.json` exists inside the project
|
|
37034
|
+
* boundary (a loose tree of files, or a subfolder of a package-less repo).
|
|
37035
|
+
*/
|
|
37036
|
+
const findNearestAncestorPackageJson = (startDirectory) => {
|
|
37037
|
+
if (isProjectBoundary(startDirectory)) return null;
|
|
37038
|
+
for (const directory of ancestorDirectories(startDirectory, { includeStart: false })) {
|
|
37039
|
+
if (isFile(Path.join(directory, "package.json"))) return directory;
|
|
37040
|
+
if (isProjectBoundary(directory)) return null;
|
|
36959
37041
|
}
|
|
36960
37042
|
return null;
|
|
36961
37043
|
};
|
|
36962
|
-
const someWorkspacePackageJson = (rootDirectory, rootPackageJson, predicate) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => predicate(packageJson) ? true : null) !== null;
|
|
36963
37044
|
const NAMES = new Set([
|
|
36964
37045
|
"react-native",
|
|
36965
37046
|
"react-native-tvos",
|
|
@@ -36992,47 +37073,7 @@ const isPackageJsonReactNativeAware = (packageJson) => {
|
|
|
36992
37073
|
if (containsAnyReactNativeDependency(packageJson.optionalDependencies)) return true;
|
|
36993
37074
|
return false;
|
|
36994
37075
|
};
|
|
36995
|
-
const
|
|
36996
|
-
const getDependencySpec = (packageJson, packageName) => {
|
|
36997
|
-
const spec = packageJson.dependencies?.[packageName] ?? packageJson.devDependencies?.[packageName] ?? packageJson.peerDependencies?.[packageName] ?? packageJson.optionalDependencies?.[packageName];
|
|
36998
|
-
return typeof spec === "string" ? spec : null;
|
|
36999
|
-
};
|
|
37000
|
-
const findExpoVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, "expo"));
|
|
37001
|
-
const SHOPIFY_FLASH_LIST_PACKAGE_NAME = "@shopify/flash-list";
|
|
37002
|
-
const findShopifyFlashListVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, SHOPIFY_FLASH_LIST_PACKAGE_NAME));
|
|
37003
|
-
const resolveCatalogBackedDependencyVersion = ({ rootDirectory, rootPackageJson, packageName, version }) => {
|
|
37004
|
-
if (version === null || !isCatalogReference(version)) return version;
|
|
37005
|
-
const catalogName = extractCatalogName(version);
|
|
37006
|
-
const resolvedLocalVersion = resolveCatalogVersion(rootPackageJson, packageName, rootDirectory, catalogName);
|
|
37007
|
-
if (resolvedLocalVersion) return resolvedLocalVersion;
|
|
37008
|
-
const monorepoRoot = findMonorepoRoot(rootDirectory);
|
|
37009
|
-
if (!monorepoRoot) return version;
|
|
37010
|
-
const monorepoPackageJsonPath = path.join(monorepoRoot, "package.json");
|
|
37011
|
-
if (!isFile(monorepoPackageJsonPath)) return version;
|
|
37012
|
-
return resolveCatalogVersion(readPackageJson$1(monorepoPackageJsonPath), packageName, monorepoRoot, catalogName) ?? version;
|
|
37013
|
-
};
|
|
37014
|
-
const findNextjsVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, "next"));
|
|
37015
|
-
const getPreactVersion = (packageJson) => {
|
|
37016
|
-
return {
|
|
37017
|
-
...packageJson.peerDependencies,
|
|
37018
|
-
...packageJson.dependencies,
|
|
37019
|
-
...packageJson.devDependencies
|
|
37020
|
-
}.preact ?? null;
|
|
37021
|
-
};
|
|
37022
|
-
const TANSTACK_QUERY_PACKAGES = new Set([
|
|
37023
|
-
"@tanstack/react-query",
|
|
37024
|
-
"@tanstack/query-core",
|
|
37025
|
-
"react-query"
|
|
37026
|
-
]);
|
|
37027
|
-
const hasTanStackQuery = (packageJson) => {
|
|
37028
|
-
const allDependencies = {
|
|
37029
|
-
...packageJson.peerDependencies,
|
|
37030
|
-
...packageJson.dependencies,
|
|
37031
|
-
...packageJson.devDependencies
|
|
37032
|
-
};
|
|
37033
|
-
return Object.keys(allDependencies).some((packageName) => TANSTACK_QUERY_PACKAGES.has(packageName));
|
|
37034
|
-
};
|
|
37035
|
-
const REANIMATED_DEPENDENCY_NAME = "react-native-reanimated";
|
|
37076
|
+
const REANIMATED_DEPENDENCY_NAME$1 = "react-native-reanimated";
|
|
37036
37077
|
const isPackageJsonReanimatedAware = (packageJson) => {
|
|
37037
37078
|
const allDependencies = {
|
|
37038
37079
|
...packageJson.peerDependencies,
|
|
@@ -37040,144 +37081,70 @@ const isPackageJsonReanimatedAware = (packageJson) => {
|
|
|
37040
37081
|
...packageJson.devDependencies,
|
|
37041
37082
|
...packageJson.optionalDependencies
|
|
37042
37083
|
};
|
|
37043
|
-
return Object.hasOwn(allDependencies, REANIMATED_DEPENDENCY_NAME);
|
|
37084
|
+
return Object.hasOwn(allDependencies, REANIMATED_DEPENDENCY_NAME$1);
|
|
37044
37085
|
};
|
|
37045
|
-
const
|
|
37046
|
-
|
|
37047
|
-
|
|
37048
|
-
|
|
37049
|
-
|
|
37050
|
-
|
|
37051
|
-
|
|
37052
|
-
|
|
37053
|
-
return splitDependencyVersionBranches(normalizedRange).some((branch) => {
|
|
37054
|
-
const normalizedBranch = normalizeDependencyVersion(branch);
|
|
37055
|
-
return normalizedBranch !== null && getBranchLowestMajor(normalizedBranch) === null && hasUpperBoundComparator(normalizedBranch);
|
|
37056
|
-
});
|
|
37057
|
-
};
|
|
37058
|
-
const peerRangeMinMajor = (range) => {
|
|
37059
|
-
if (typeof range !== "string") return null;
|
|
37060
|
-
return getLowestDependencyMajor(range);
|
|
37061
|
-
};
|
|
37062
|
-
const resolveEffectiveReactMajor = (reactVersion, packageJson) => {
|
|
37063
|
-
const installedReactMajor = parseReactMajor(reactVersion);
|
|
37064
|
-
const peerReactRange = packageJson.peerDependencies?.react;
|
|
37065
|
-
if (typeof peerReactRange !== "string") return installedReactMajor;
|
|
37066
|
-
const peerFloor = peerRangeMinMajor(peerReactRange);
|
|
37067
|
-
if (peerFloor === null) return hasUpperBoundOnlyPeerRange(peerReactRange) ? null : installedReactMajor;
|
|
37068
|
-
return installedReactMajor !== null ? Math.min(installedReactMajor, peerFloor) : peerFloor;
|
|
37086
|
+
const getWorkspacePatterns = (rootDirectory, packageJson) => {
|
|
37087
|
+
const pnpmPatterns = parsePnpmWorkspacePatterns(rootDirectory);
|
|
37088
|
+
if (pnpmPatterns.length > 0) return pnpmPatterns;
|
|
37089
|
+
if (Array.isArray(packageJson.workspaces)) return packageJson.workspaces;
|
|
37090
|
+
if (packageJson.workspaces?.packages) return packageJson.workspaces.packages;
|
|
37091
|
+
const nxPatterns = getNxWorkspaceDirectories(rootDirectory);
|
|
37092
|
+
if (nxPatterns.length > 0) return nxPatterns;
|
|
37093
|
+
return [];
|
|
37069
37094
|
};
|
|
37070
|
-
const
|
|
37071
|
-
const
|
|
37072
|
-
|
|
37073
|
-
const
|
|
37074
|
-
const
|
|
37075
|
-
|
|
37076
|
-
|
|
37077
|
-
|
|
37078
|
-
|
|
37079
|
-
|
|
37080
|
-
|
|
37081
|
-
|
|
37082
|
-
|
|
37083
|
-
|
|
37095
|
+
const parsePnpmWorkspacePatterns = (rootDirectory) => {
|
|
37096
|
+
const workspacePath = Path.join(rootDirectory, "pnpm-workspace.yaml");
|
|
37097
|
+
if (!isFile(workspacePath)) return [];
|
|
37098
|
+
const content = NFS.readFileSync(workspacePath, "utf-8");
|
|
37099
|
+
const patterns = [];
|
|
37100
|
+
let isInsidePackagesBlock = false;
|
|
37101
|
+
for (const line of content.split("\n")) {
|
|
37102
|
+
const trimmed = line.trim();
|
|
37103
|
+
if (trimmed === "packages:") {
|
|
37104
|
+
isInsidePackagesBlock = true;
|
|
37105
|
+
continue;
|
|
37106
|
+
}
|
|
37107
|
+
if (isInsidePackagesBlock && trimmed.startsWith("-")) patterns.push(trimmed.replace(/^-\s*/, "").replace(/["']/g, ""));
|
|
37108
|
+
else if (isInsidePackagesBlock && trimmed.length > 0 && !trimmed.startsWith("#")) isInsidePackagesBlock = false;
|
|
37084
37109
|
}
|
|
37085
|
-
return
|
|
37086
|
-
};
|
|
37087
|
-
const resolveExtendsPath = (extendsValue, fromConfigDirectory) => {
|
|
37088
|
-
if (isRelativeExtendsValue(extendsValue)) return ensureJsonExtension(Path.resolve(fromConfigDirectory, extendsValue));
|
|
37089
|
-
return resolvePackageExtendsPath(extendsValue, fromConfigDirectory);
|
|
37090
|
-
};
|
|
37091
|
-
const normalizeCompilerOptions = (compilerOptions) => {
|
|
37092
|
-
if (!isPlainObject(compilerOptions)) return { hasExplicitLib: false };
|
|
37093
|
-
const target = typeof compilerOptions.target === "string" ? compilerOptions.target : void 0;
|
|
37094
|
-
const hasExplicitLib = Object.hasOwn(compilerOptions, "lib");
|
|
37095
|
-
return {
|
|
37096
|
-
target,
|
|
37097
|
-
lib: Array.isArray(compilerOptions.lib) ? compilerOptions.lib.filter((entry) => typeof entry === "string") : void 0,
|
|
37098
|
-
hasExplicitLib
|
|
37099
|
-
};
|
|
37110
|
+
return patterns;
|
|
37100
37111
|
};
|
|
37101
|
-
const
|
|
37102
|
-
|
|
37103
|
-
|
|
37104
|
-
|
|
37105
|
-
|
|
37106
|
-
|
|
37112
|
+
const NX_PROJECT_DISCOVERY_DIRS = [
|
|
37113
|
+
"apps",
|
|
37114
|
+
"libs",
|
|
37115
|
+
"packages"
|
|
37116
|
+
];
|
|
37117
|
+
const getNxWorkspaceDirectories = (rootDirectory) => {
|
|
37118
|
+
if (!isFile(Path.join(rootDirectory, "nx.json"))) return [];
|
|
37119
|
+
const collected = [];
|
|
37120
|
+
for (const candidate of NX_PROJECT_DISCOVERY_DIRS) {
|
|
37121
|
+
const candidatePath = Path.join(rootDirectory, candidate);
|
|
37122
|
+
if (!isDirectory(candidatePath)) continue;
|
|
37123
|
+
for (const entry of readDirectoryEntries(candidatePath)) {
|
|
37124
|
+
if (!entry.isDirectory()) continue;
|
|
37125
|
+
const projectDirectory = Path.join(candidatePath, entry.name);
|
|
37126
|
+
if (isFile(Path.join(projectDirectory, "project.json")) || isFile(Path.join(projectDirectory, "package.json"))) collected.push(`${candidate}/${entry.name}`);
|
|
37127
|
+
}
|
|
37107
37128
|
}
|
|
37108
|
-
|
|
37109
|
-
if (!isPlainObject(parsed.config)) return null;
|
|
37110
|
-
return {
|
|
37111
|
-
extends: typeof parsed.config.extends === "string" ? parsed.config.extends : void 0,
|
|
37112
|
-
compilerOptions: normalizeCompilerOptions(parsed.config.compilerOptions)
|
|
37113
|
-
};
|
|
37114
|
-
};
|
|
37115
|
-
const mergeCompilerOptions = (inherited, current) => {
|
|
37116
|
-
const target = current.target ?? inherited?.target;
|
|
37117
|
-
const hasExplicitLib = current.hasExplicitLib || Boolean(inherited?.hasExplicitLib);
|
|
37118
|
-
return {
|
|
37119
|
-
target,
|
|
37120
|
-
lib: current.hasExplicitLib ? current.lib : inherited?.lib,
|
|
37121
|
-
hasExplicitLib
|
|
37122
|
-
};
|
|
37129
|
+
return collected;
|
|
37123
37130
|
};
|
|
37124
|
-
const
|
|
37125
|
-
const
|
|
37126
|
-
if (
|
|
37127
|
-
|
|
37128
|
-
|
|
37129
|
-
|
|
37130
|
-
nextVisitedPaths.add(realPath);
|
|
37131
|
-
if (tsConfig.extends && extendsDepth < 8) {
|
|
37132
|
-
const parentPath = resolveExtendsPath(tsConfig.extends, Path.dirname(realPath));
|
|
37133
|
-
if (parentPath && isFile(parentPath)) return mergeCompilerOptions(readResolvedCompilerOptions(parentPath, extendsDepth + 1, nextVisitedPaths), tsConfig.compilerOptions);
|
|
37131
|
+
const resolveWorkspaceDirectories = (rootDirectory, pattern) => {
|
|
37132
|
+
const cleanPattern = pattern.replace(/["']/g, "").replace(/\/\*\*$/, "/*");
|
|
37133
|
+
if (!cleanPattern.includes("*")) {
|
|
37134
|
+
const directoryPath = Path.join(rootDirectory, cleanPattern);
|
|
37135
|
+
if (isDirectory(directoryPath) && isFile(Path.join(directoryPath, "package.json"))) return [directoryPath];
|
|
37136
|
+
return [];
|
|
37134
37137
|
}
|
|
37135
|
-
|
|
37136
|
-
|
|
37137
|
-
const
|
|
37138
|
-
|
|
37139
|
-
|
|
37140
|
-
|
|
37141
|
-
const
|
|
37142
|
-
|
|
37143
|
-
|
|
37144
|
-
|
|
37145
|
-
if (!esYearMatch) return false;
|
|
37146
|
-
if (Number(esYearMatch[1]) < 2023) return false;
|
|
37147
|
-
const component = esYearMatch[2];
|
|
37148
|
-
return component === void 0 || component === "array";
|
|
37149
|
-
};
|
|
37150
|
-
const libIncludesES2023 = (lib) => lib.some(libEntryIncludesES2023Array);
|
|
37151
|
-
const compilerOptionsArePreES2023 = (compilerOptions) => {
|
|
37152
|
-
if (compilerOptions.hasExplicitLib) return !libIncludesES2023(compilerOptions.lib ?? []);
|
|
37153
|
-
if (compilerOptions.target) return targetYearIsPreES2023(compilerOptions.target);
|
|
37154
|
-
return false;
|
|
37155
|
-
};
|
|
37156
|
-
const compilerOptionsDeclareTargetOrLib = (compilerOptions) => compilerOptions.hasExplicitLib || compilerOptions.target !== void 0;
|
|
37157
|
-
const detectPreES2023FromConfig = (tsConfigPath) => {
|
|
37158
|
-
const compilerOptions = readResolvedCompilerOptions(tsConfigPath, 0, /* @__PURE__ */ new Set());
|
|
37159
|
-
if (!compilerOptions) return false;
|
|
37160
|
-
if (!compilerOptionsDeclareTargetOrLib(compilerOptions)) return false;
|
|
37161
|
-
return compilerOptionsArePreES2023(compilerOptions);
|
|
37162
|
-
};
|
|
37163
|
-
const detectPreES2023Target = (directory) => {
|
|
37164
|
-
const tsConfigPath = Path.join(directory, TSCONFIG_FILENAME);
|
|
37165
|
-
if (isFile(tsConfigPath)) return detectPreES2023FromConfig(tsConfigPath);
|
|
37166
|
-
return false;
|
|
37167
|
-
};
|
|
37168
|
-
const REACT_DEPENDENCY_NAMES = new Set([
|
|
37169
|
-
"react",
|
|
37170
|
-
"react-native",
|
|
37171
|
-
"next",
|
|
37172
|
-
"preact"
|
|
37173
|
-
]);
|
|
37174
|
-
const hasReactDependency = (packageJson) => {
|
|
37175
|
-
const allDependencies = {
|
|
37176
|
-
...packageJson.peerDependencies,
|
|
37177
|
-
...packageJson.dependencies,
|
|
37178
|
-
...packageJson.devDependencies
|
|
37179
|
-
};
|
|
37180
|
-
return Object.keys(allDependencies).some((packageName) => REACT_DEPENDENCY_NAMES.has(packageName));
|
|
37138
|
+
const wildcardIndex = cleanPattern.indexOf("*");
|
|
37139
|
+
const baseDirectory = Path.join(rootDirectory, cleanPattern.slice(0, wildcardIndex));
|
|
37140
|
+
const suffixAfterWildcard = cleanPattern.slice(wildcardIndex + 1);
|
|
37141
|
+
if (!isDirectory(baseDirectory)) return [];
|
|
37142
|
+
const resolved = [];
|
|
37143
|
+
for (const entry of readDirectoryEntries(baseDirectory)) {
|
|
37144
|
+
const entryPath = Path.join(baseDirectory, entry.name, suffixAfterWildcard);
|
|
37145
|
+
if (isDirectory(entryPath) && isFile(Path.join(entryPath, "package.json"))) resolved.push(entryPath);
|
|
37146
|
+
}
|
|
37147
|
+
return resolved;
|
|
37181
37148
|
};
|
|
37182
37149
|
const listWorkspacePackages = (rootDirectory) => {
|
|
37183
37150
|
const packageJsonPath = Path.join(rootDirectory, "package.json");
|
|
@@ -37209,6 +37176,208 @@ const listWorkspacePackages = (rootDirectory) => {
|
|
|
37209
37176
|
}
|
|
37210
37177
|
return packages;
|
|
37211
37178
|
};
|
|
37179
|
+
const REANIMATED_DEPENDENCY_NAME = "react-native-reanimated";
|
|
37180
|
+
const SHOPIFY_FLASH_LIST_PACKAGE_NAME = "@shopify/flash-list";
|
|
37181
|
+
const resolveWorkspaceDependencyVersion = ({ concreteVersion, packageName, rootDirectory, rootPackageJson, sections, workspaceDirectory, workspacePackageJson }) => {
|
|
37182
|
+
const dependencyDeclaration = getDependencyDeclaration({
|
|
37183
|
+
packageJson: workspacePackageJson,
|
|
37184
|
+
packageName,
|
|
37185
|
+
sections
|
|
37186
|
+
});
|
|
37187
|
+
if (!dependencyDeclaration.hasDeclaration) return null;
|
|
37188
|
+
return concreteVersion ?? resolveCatalogVersion(workspacePackageJson, packageName, workspaceDirectory, dependencyDeclaration.catalogReference) ?? resolveCatalogVersion(rootPackageJson, packageName, rootDirectory, dependencyDeclaration.catalogReference);
|
|
37189
|
+
};
|
|
37190
|
+
const shouldReplaceReactVersion = (currentVersion, nextVersion) => {
|
|
37191
|
+
if (!currentVersion) return true;
|
|
37192
|
+
const currentMajor = parseReactMajor(currentVersion);
|
|
37193
|
+
const nextMajor = parseReactMajor(nextVersion);
|
|
37194
|
+
if (currentMajor === null) return nextMajor !== null;
|
|
37195
|
+
if (nextMajor === null) return false;
|
|
37196
|
+
return nextMajor < currentMajor;
|
|
37197
|
+
};
|
|
37198
|
+
const evaluateManifestFacts = (facts, packageJson, directory) => {
|
|
37199
|
+
if (facts.expo.version === null) {
|
|
37200
|
+
const spec = getDependencySpec(packageJson, "expo");
|
|
37201
|
+
if (spec !== null) facts.expo = {
|
|
37202
|
+
version: spec,
|
|
37203
|
+
sourceDirectory: directory
|
|
37204
|
+
};
|
|
37205
|
+
}
|
|
37206
|
+
if (facts.next.version === null) {
|
|
37207
|
+
const spec = getDependencySpec(packageJson, "next");
|
|
37208
|
+
if (spec !== null) facts.next = {
|
|
37209
|
+
version: spec,
|
|
37210
|
+
sourceDirectory: directory
|
|
37211
|
+
};
|
|
37212
|
+
}
|
|
37213
|
+
if (facts.shopifyFlashList.version === null) {
|
|
37214
|
+
const spec = getDependencySpec(packageJson, SHOPIFY_FLASH_LIST_PACKAGE_NAME);
|
|
37215
|
+
if (spec !== null) facts.shopifyFlashList = {
|
|
37216
|
+
version: spec,
|
|
37217
|
+
sourceDirectory: directory
|
|
37218
|
+
};
|
|
37219
|
+
}
|
|
37220
|
+
if (facts.reanimatedVersion === null) {
|
|
37221
|
+
const spec = getDependencySpec(packageJson, REANIMATED_DEPENDENCY_NAME);
|
|
37222
|
+
if (spec !== null) facts.reanimatedVersion = spec;
|
|
37223
|
+
}
|
|
37224
|
+
facts.hasReactNativeAwarePackage = facts.hasReactNativeAwarePackage || isPackageJsonReactNativeAware(packageJson);
|
|
37225
|
+
facts.hasReanimatedAwarePackage = facts.hasReanimatedAwarePackage || isPackageJsonReanimatedAware(packageJson);
|
|
37226
|
+
};
|
|
37227
|
+
const collectWorkspaceFacts = (rootDirectory, rootPackageJson, { collectReactGroup }) => {
|
|
37228
|
+
const facts = {
|
|
37229
|
+
reactVersion: null,
|
|
37230
|
+
tailwindVersion: null,
|
|
37231
|
+
zodVersion: null,
|
|
37232
|
+
framework: "unknown",
|
|
37233
|
+
expo: {
|
|
37234
|
+
version: null,
|
|
37235
|
+
sourceDirectory: null
|
|
37236
|
+
},
|
|
37237
|
+
next: {
|
|
37238
|
+
version: null,
|
|
37239
|
+
sourceDirectory: null
|
|
37240
|
+
},
|
|
37241
|
+
shopifyFlashList: {
|
|
37242
|
+
version: null,
|
|
37243
|
+
sourceDirectory: null
|
|
37244
|
+
},
|
|
37245
|
+
hasReactNativeAwarePackage: false,
|
|
37246
|
+
hasReanimatedAwarePackage: false,
|
|
37247
|
+
reanimatedVersion: null
|
|
37248
|
+
};
|
|
37249
|
+
evaluateManifestFacts(facts, rootPackageJson, rootDirectory);
|
|
37250
|
+
let isReactGroupSettled = !collectReactGroup;
|
|
37251
|
+
const visitedDirectories = /* @__PURE__ */ new Set();
|
|
37252
|
+
for (const pattern of getWorkspacePatterns(rootDirectory, rootPackageJson)) {
|
|
37253
|
+
const directories = [...resolveWorkspaceDirectories(rootDirectory, pattern)].sort();
|
|
37254
|
+
for (const workspaceDirectory of directories) {
|
|
37255
|
+
if (visitedDirectories.has(workspaceDirectory)) continue;
|
|
37256
|
+
visitedDirectories.add(workspaceDirectory);
|
|
37257
|
+
const workspacePackageJson = readPackageJson$1(Path.join(workspaceDirectory, "package.json"));
|
|
37258
|
+
evaluateManifestFacts(facts, workspacePackageJson, workspaceDirectory);
|
|
37259
|
+
const info = extractDependencyInfo(workspacePackageJson);
|
|
37260
|
+
if (info.framework !== "unknown" && frameworkMergeRank(info.framework) < frameworkMergeRank(facts.framework)) facts.framework = info.framework;
|
|
37261
|
+
if (isReactGroupSettled) continue;
|
|
37262
|
+
const reactVersion = resolveWorkspaceDependencyVersion({
|
|
37263
|
+
concreteVersion: info.reactVersion,
|
|
37264
|
+
packageName: "react",
|
|
37265
|
+
rootDirectory,
|
|
37266
|
+
rootPackageJson,
|
|
37267
|
+
sections: REACT_SECTIONS,
|
|
37268
|
+
workspaceDirectory,
|
|
37269
|
+
workspacePackageJson
|
|
37270
|
+
});
|
|
37271
|
+
const tailwindVersion = resolveWorkspaceDependencyVersion({
|
|
37272
|
+
concreteVersion: info.tailwindVersion,
|
|
37273
|
+
packageName: "tailwindcss",
|
|
37274
|
+
rootDirectory,
|
|
37275
|
+
rootPackageJson,
|
|
37276
|
+
sections: TAILWIND_ZOD_SECTIONS,
|
|
37277
|
+
workspaceDirectory,
|
|
37278
|
+
workspacePackageJson
|
|
37279
|
+
});
|
|
37280
|
+
const zodVersion = resolveWorkspaceDependencyVersion({
|
|
37281
|
+
concreteVersion: info.zodVersion,
|
|
37282
|
+
packageName: "zod",
|
|
37283
|
+
rootDirectory,
|
|
37284
|
+
rootPackageJson,
|
|
37285
|
+
sections: TAILWIND_ZOD_SECTIONS,
|
|
37286
|
+
workspaceDirectory,
|
|
37287
|
+
workspacePackageJson
|
|
37288
|
+
});
|
|
37289
|
+
if (reactVersion && shouldReplaceReactVersion(facts.reactVersion, reactVersion)) facts.reactVersion = reactVersion;
|
|
37290
|
+
if (tailwindVersion && !facts.tailwindVersion) facts.tailwindVersion = tailwindVersion;
|
|
37291
|
+
if (zodVersion && !facts.zodVersion) facts.zodVersion = zodVersion;
|
|
37292
|
+
const settledReactMajor = parseReactMajor(facts.reactVersion);
|
|
37293
|
+
isReactGroupSettled = Boolean(facts.reactVersion && facts.tailwindVersion && facts.framework !== "unknown" && settledReactMajor !== null && settledReactMajor <= 17);
|
|
37294
|
+
}
|
|
37295
|
+
}
|
|
37296
|
+
return facts;
|
|
37297
|
+
};
|
|
37298
|
+
const findDependencyInfoFromMonorepoRoot = (directory) => {
|
|
37299
|
+
const monorepoRoot = findMonorepoRoot(directory);
|
|
37300
|
+
if (!monorepoRoot) return EMPTY_DEPENDENCY_INFO;
|
|
37301
|
+
const monorepoPackageJsonPath = Path.join(monorepoRoot, "package.json");
|
|
37302
|
+
if (!isFile(monorepoPackageJsonPath)) return EMPTY_DEPENDENCY_INFO;
|
|
37303
|
+
const rootPackageJson = readPackageJson$1(monorepoPackageJsonPath);
|
|
37304
|
+
const rootInfo = extractDependencyInfo(rootPackageJson);
|
|
37305
|
+
const leafPackageJsonPath = Path.join(directory, "package.json");
|
|
37306
|
+
const leafPackageJson = isFile(leafPackageJsonPath) ? readPackageJson$1(leafPackageJsonPath) : null;
|
|
37307
|
+
const leafReactDeclaration = leafPackageJson ? getDependencyDeclaration({
|
|
37308
|
+
packageJson: leafPackageJson,
|
|
37309
|
+
packageName: "react",
|
|
37310
|
+
sections: REACT_SECTIONS
|
|
37311
|
+
}) : null;
|
|
37312
|
+
const leafTailwindDeclaration = leafPackageJson ? getDependencyDeclaration({
|
|
37313
|
+
packageJson: leafPackageJson,
|
|
37314
|
+
packageName: "tailwindcss",
|
|
37315
|
+
sections: TAILWIND_ZOD_SECTIONS
|
|
37316
|
+
}) : null;
|
|
37317
|
+
const leafZodDeclaration = leafPackageJson ? getDependencyDeclaration({
|
|
37318
|
+
packageJson: leafPackageJson,
|
|
37319
|
+
packageName: "zod",
|
|
37320
|
+
sections: TAILWIND_ZOD_SECTIONS
|
|
37321
|
+
}) : null;
|
|
37322
|
+
const shouldUseReactFallback = !leafReactDeclaration?.hasDeclaration;
|
|
37323
|
+
const shouldUseTailwindFallback = leafTailwindDeclaration?.hasDeclaration ?? true;
|
|
37324
|
+
const shouldUseZodFallback = leafZodDeclaration?.hasDeclaration ?? true;
|
|
37325
|
+
const reactCatalogVersion = shouldUseReactFallback ? resolveCatalogVersion(rootPackageJson, "react", monorepoRoot, leafReactDeclaration?.catalogReference) : null;
|
|
37326
|
+
const tailwindCatalogVersion = shouldUseTailwindFallback ? resolveCatalogVersion(rootPackageJson, "tailwindcss", monorepoRoot, leafTailwindDeclaration?.catalogReference) : null;
|
|
37327
|
+
const zodCatalogVersion = shouldUseZodFallback ? resolveCatalogVersion(rootPackageJson, "zod", monorepoRoot, leafZodDeclaration?.catalogReference) : null;
|
|
37328
|
+
const workspaceFacts = collectWorkspaceFacts(monorepoRoot, rootPackageJson, { collectReactGroup: true });
|
|
37329
|
+
return {
|
|
37330
|
+
reactVersion: shouldUseReactFallback ? reactCatalogVersion ?? rootInfo.reactVersion ?? workspaceFacts.reactVersion : rootInfo.reactVersion ?? workspaceFacts.reactVersion,
|
|
37331
|
+
tailwindVersion: shouldUseTailwindFallback ? tailwindCatalogVersion ?? rootInfo.tailwindVersion ?? workspaceFacts.tailwindVersion : null,
|
|
37332
|
+
zodVersion: shouldUseZodFallback ? zodCatalogVersion ?? rootInfo.zodVersion ?? workspaceFacts.zodVersion : null,
|
|
37333
|
+
framework: rootInfo.framework !== "unknown" ? rootInfo.framework : workspaceFacts.framework
|
|
37334
|
+
};
|
|
37335
|
+
};
|
|
37336
|
+
/**
|
|
37337
|
+
* Last-resort React detection: locate React the way Node's `node_modules`
|
|
37338
|
+
* resolution would from `directory` — check each `node_modules/react` up the
|
|
37339
|
+
* tree — but bounded to the enclosing repo so a globally installed React can't
|
|
37340
|
+
* masquerade as the project's version. Stops at (and including) the nearest
|
|
37341
|
+
* project boundary — the git root or a monorepo root — and returns the first
|
|
37342
|
+
* installed `version`.
|
|
37343
|
+
*
|
|
37344
|
+
* Makes "React is installed and importable" ⇒ "React is detected" an invariant
|
|
37345
|
+
* for packages whose only React declaration is a version-less spec
|
|
37346
|
+
* (`workspace:*`, `*`, a dist-tag) or where React lives solely in a hoisted
|
|
37347
|
+
* `node_modules` the declaration walks never reach — the profile of a component
|
|
37348
|
+
* package inside a monorepo. Matching the `node_modules/react` entry rather than
|
|
37349
|
+
* its realpath means a project whose `node_modules` is symlinked elsewhere (a
|
|
37350
|
+
* Docker volume, a shared store) still counts, while a global install outside
|
|
37351
|
+
* the repo does not.
|
|
37352
|
+
*
|
|
37353
|
+
* Returns `null` when React isn't installed within the repo or its package.json
|
|
37354
|
+
* carries no version string. A tree with no boundary marker at all bounds the
|
|
37355
|
+
* search to `directory`, so a dependency hoisted above the scanned package there
|
|
37356
|
+
* is conservatively not adopted.
|
|
37357
|
+
*/
|
|
37358
|
+
/**
|
|
37359
|
+
* The nearest enclosing project boundary (git root or monorepo root), or
|
|
37360
|
+
* `directory` itself when the scan target sits outside any repo. Bounds the
|
|
37361
|
+
* node_modules walk so a React hoisted anywhere inside the repo counts, but a
|
|
37362
|
+
* React in an ancestor above the repo — a home-directory or global
|
|
37363
|
+
* `node_modules` — can't leak in.
|
|
37364
|
+
*/
|
|
37365
|
+
const findContainmentRoot = (directory) => {
|
|
37366
|
+
for (const ancestorDirectory of ancestorDirectories(directory, { includeStart: true })) if (isProjectBoundary(ancestorDirectory)) return ancestorDirectory;
|
|
37367
|
+
return directory;
|
|
37368
|
+
};
|
|
37369
|
+
const resolveInstalledReactVersion = (directory) => {
|
|
37370
|
+
const containmentRoot = findContainmentRoot(directory);
|
|
37371
|
+
for (const ancestorDirectory of ancestorDirectories(directory, { includeStart: true })) {
|
|
37372
|
+
const reactPackageJsonPath = Path.join(ancestorDirectory, "node_modules", "react", "package.json");
|
|
37373
|
+
if (isFile(reactPackageJsonPath)) {
|
|
37374
|
+
const installedVersion = readPackageJson$1(reactPackageJsonPath).version;
|
|
37375
|
+
return typeof installedVersion === "string" ? installedVersion : null;
|
|
37376
|
+
}
|
|
37377
|
+
if (ancestorDirectory === containmentRoot) return null;
|
|
37378
|
+
}
|
|
37379
|
+
return null;
|
|
37380
|
+
};
|
|
37212
37381
|
const toReactWorkspacePackages = (directories) => {
|
|
37213
37382
|
const packages = [];
|
|
37214
37383
|
for (const directory of directories) {
|
|
@@ -37289,8 +37458,8 @@ const cachedProjectInfos = /* @__PURE__ */ new Map();
|
|
|
37289
37458
|
const discoverProjectWithoutPackageJson = (directory) => {
|
|
37290
37459
|
const sourceFileCount = countSourceFiles(directory);
|
|
37291
37460
|
const hasOwnTsConfig = NFS.existsSync(Path.join(directory, "tsconfig.json"));
|
|
37292
|
-
const
|
|
37293
|
-
const enclosingProject =
|
|
37461
|
+
const enclosingProjectRoot = findNearestAncestorPackageJson(directory);
|
|
37462
|
+
const enclosingProject = enclosingProjectRoot !== null ? discoverProject(enclosingProjectRoot) : null;
|
|
37294
37463
|
if (enclosingProject !== null) return {
|
|
37295
37464
|
...enclosingProject,
|
|
37296
37465
|
rootDirectory: directory,
|
|
@@ -37320,7 +37489,9 @@ const discoverProjectWithoutPackageJson = (directory) => {
|
|
|
37320
37489
|
shopifyFlashListVersion: null,
|
|
37321
37490
|
shopifyFlashListMajorVersion: null,
|
|
37322
37491
|
hasReanimated: false,
|
|
37492
|
+
reanimatedVersion: null,
|
|
37323
37493
|
isPreES2023Target: hasOwnTsConfig && detectPreES2023Target(directory),
|
|
37494
|
+
isStaticExport: false,
|
|
37324
37495
|
sourceFileCount
|
|
37325
37496
|
};
|
|
37326
37497
|
};
|
|
@@ -37334,88 +37505,85 @@ const discoverProject = (directory) => {
|
|
|
37334
37505
|
return synthesized;
|
|
37335
37506
|
}
|
|
37336
37507
|
const packageJson = readPackageJson$1(packageJsonPath);
|
|
37337
|
-
|
|
37338
|
-
|
|
37339
|
-
|
|
37340
|
-
|
|
37341
|
-
|
|
37342
|
-
|
|
37343
|
-
|
|
37344
|
-
|
|
37345
|
-
|
|
37346
|
-
|
|
37347
|
-
|
|
37348
|
-
|
|
37349
|
-
|
|
37350
|
-
|
|
37351
|
-
|
|
37352
|
-
|
|
37353
|
-
|
|
37354
|
-
]
|
|
37355
|
-
});
|
|
37356
|
-
const zodDeclaration = getDependencyDeclaration({
|
|
37508
|
+
const rootInfo = extractDependencyInfo(packageJson);
|
|
37509
|
+
let framework = rootInfo.framework;
|
|
37510
|
+
const tracked = {
|
|
37511
|
+
react: {
|
|
37512
|
+
version: rootInfo.reactVersion,
|
|
37513
|
+
sections: REACT_SECTIONS
|
|
37514
|
+
},
|
|
37515
|
+
tailwindcss: {
|
|
37516
|
+
version: rootInfo.tailwindVersion,
|
|
37517
|
+
sections: TAILWIND_ZOD_SECTIONS
|
|
37518
|
+
},
|
|
37519
|
+
zod: {
|
|
37520
|
+
version: rootInfo.zodVersion,
|
|
37521
|
+
sections: TAILWIND_ZOD_SECTIONS
|
|
37522
|
+
}
|
|
37523
|
+
};
|
|
37524
|
+
const declarations = Object.fromEntries(Object.entries(tracked).map(([packageName, entry]) => [packageName, getDependencyDeclaration({
|
|
37357
37525
|
packageJson,
|
|
37358
|
-
packageName
|
|
37359
|
-
sections:
|
|
37360
|
-
|
|
37361
|
-
|
|
37362
|
-
|
|
37363
|
-
|
|
37364
|
-
|
|
37365
|
-
if (!
|
|
37366
|
-
if (!tailwindVersion && tailwindDeclaration.hasDeclaration) tailwindVersion = resolveCatalogVersion(packageJson, "tailwindcss", directory, tailwindDeclaration.catalogReference);
|
|
37367
|
-
if (!zodVersion && zodDeclaration.hasDeclaration) zodVersion = resolveCatalogVersion(packageJson, "zod", directory, zodDeclaration.catalogReference);
|
|
37368
|
-
if (!reactVersion || !tailwindVersion || !zodVersion) {
|
|
37526
|
+
packageName,
|
|
37527
|
+
sections: entry.sections
|
|
37528
|
+
})]));
|
|
37529
|
+
const fillFromCatalogs = (source, sourceDirectory) => {
|
|
37530
|
+
for (const [packageName, entry] of Object.entries(tracked)) if (!entry.version && declarations[packageName].hasDeclaration) entry.version = resolveCatalogVersion(source, packageName, sourceDirectory, declarations[packageName].catalogReference);
|
|
37531
|
+
};
|
|
37532
|
+
fillFromCatalogs(packageJson, directory);
|
|
37533
|
+
if (!tracked.react.version || !tracked.tailwindcss.version || !tracked.zod.version) {
|
|
37369
37534
|
const monorepoRoot = findMonorepoRoot(directory);
|
|
37370
37535
|
if (monorepoRoot) {
|
|
37371
37536
|
const monorepoPackageJsonPath = Path.join(monorepoRoot, "package.json");
|
|
37372
|
-
if (isFile(monorepoPackageJsonPath))
|
|
37373
|
-
const rootPackageJson = readPackageJson$1(monorepoPackageJsonPath);
|
|
37374
|
-
if (!reactVersion && reactDeclaration.hasDeclaration) reactVersion = resolveCatalogVersion(rootPackageJson, "react", monorepoRoot, reactDeclaration.catalogReference);
|
|
37375
|
-
if (!tailwindVersion && tailwindDeclaration.hasDeclaration) tailwindVersion = resolveCatalogVersion(rootPackageJson, "tailwindcss", monorepoRoot, tailwindDeclaration.catalogReference);
|
|
37376
|
-
if (!zodVersion && zodDeclaration.hasDeclaration) zodVersion = resolveCatalogVersion(rootPackageJson, "zod", monorepoRoot, zodDeclaration.catalogReference);
|
|
37377
|
-
}
|
|
37537
|
+
if (isFile(monorepoPackageJsonPath)) fillFromCatalogs(readPackageJson$1(monorepoPackageJsonPath), monorepoRoot);
|
|
37378
37538
|
}
|
|
37379
37539
|
}
|
|
37380
|
-
|
|
37381
|
-
|
|
37382
|
-
|
|
37383
|
-
|
|
37384
|
-
|
|
37385
|
-
|
|
37540
|
+
const shouldCollectReactGroup = !tracked.react.version || framework === "unknown";
|
|
37541
|
+
const workspaceFacts = collectWorkspaceFacts(directory, packageJson, { collectReactGroup: shouldCollectReactGroup });
|
|
37542
|
+
if (shouldCollectReactGroup) {
|
|
37543
|
+
tracked.react.version ||= workspaceFacts.reactVersion;
|
|
37544
|
+
tracked.tailwindcss.version ||= workspaceFacts.tailwindVersion;
|
|
37545
|
+
tracked.zod.version ||= workspaceFacts.zodVersion;
|
|
37546
|
+
if (framework === "unknown" && workspaceFacts.framework !== "unknown") framework = workspaceFacts.framework;
|
|
37386
37547
|
}
|
|
37387
|
-
if ((!
|
|
37548
|
+
if ((!tracked.react.version || framework === "unknown") && !isMonorepoRoot(directory)) {
|
|
37388
37549
|
const monorepoInfo = findDependencyInfoFromMonorepoRoot(directory);
|
|
37389
|
-
|
|
37390
|
-
|
|
37391
|
-
|
|
37550
|
+
tracked.react.version ||= monorepoInfo.reactVersion;
|
|
37551
|
+
tracked.tailwindcss.version ||= monorepoInfo.tailwindVersion;
|
|
37552
|
+
tracked.zod.version ||= monorepoInfo.zodVersion;
|
|
37392
37553
|
if (framework === "unknown") framework = monorepoInfo.framework;
|
|
37393
37554
|
}
|
|
37394
|
-
|
|
37395
|
-
|
|
37396
|
-
|
|
37555
|
+
for (const [packageName, entry] of Object.entries(tracked)) {
|
|
37556
|
+
const declaredVersion = declarations[packageName].version;
|
|
37557
|
+
if (!entry.version && declaredVersion && !isCatalogReference(declaredVersion)) entry.version = declaredVersion;
|
|
37558
|
+
}
|
|
37559
|
+
const { react, tailwindcss, zod } = tracked;
|
|
37560
|
+
let reactVersion = react.version;
|
|
37561
|
+
if (!reactVersion || parseReactMajor(reactVersion) === null) reactVersion = resolveInstalledReactVersion(directory) ?? reactVersion;
|
|
37562
|
+
const tailwindVersion = tailwindcss.version;
|
|
37563
|
+
const zodVersion = zod.version;
|
|
37397
37564
|
const projectName = packageJson.name ?? Path.basename(directory);
|
|
37398
37565
|
const hasTypeScript = NFS.existsSync(Path.join(directory, "tsconfig.json"));
|
|
37399
37566
|
const sourceFileCount = countSourceFiles(directory);
|
|
37400
|
-
const hasReactNativeWorkspace = framework === "expo" || framework === "react-native" ||
|
|
37567
|
+
const hasReactNativeWorkspace = framework === "expo" || framework === "react-native" || workspaceFacts.hasReactNativeAwarePackage;
|
|
37401
37568
|
const expoVersion = hasReactNativeWorkspace ? resolveCatalogBackedDependencyVersion({
|
|
37402
37569
|
rootDirectory: directory,
|
|
37403
37570
|
rootPackageJson: packageJson,
|
|
37404
37571
|
packageName: "expo",
|
|
37405
|
-
version:
|
|
37572
|
+
version: workspaceFacts.expo.version
|
|
37406
37573
|
}) : null;
|
|
37407
37574
|
const shopifyFlashListVersion = hasReactNativeWorkspace ? resolveCatalogBackedDependencyVersion({
|
|
37408
37575
|
rootDirectory: directory,
|
|
37409
37576
|
rootPackageJson: packageJson,
|
|
37410
37577
|
packageName: SHOPIFY_FLASH_LIST_PACKAGE_NAME,
|
|
37411
|
-
version:
|
|
37578
|
+
version: workspaceFacts.shopifyFlashList.version
|
|
37412
37579
|
}) : null;
|
|
37413
|
-
const hasReanimated = hasReactNativeWorkspace &&
|
|
37580
|
+
const hasReanimated = hasReactNativeWorkspace && workspaceFacts.hasReanimatedAwarePackage;
|
|
37581
|
+
const reanimatedVersion = hasReanimated ? workspaceFacts.reanimatedVersion : null;
|
|
37414
37582
|
const nextjsVersion = framework === "nextjs" ? resolveCatalogBackedDependencyVersion({
|
|
37415
37583
|
rootDirectory: directory,
|
|
37416
37584
|
rootPackageJson: packageJson,
|
|
37417
37585
|
packageName: "next",
|
|
37418
|
-
version:
|
|
37586
|
+
version: workspaceFacts.next.version
|
|
37419
37587
|
}) : null;
|
|
37420
37588
|
const preactVersion = getPreactVersion(packageJson);
|
|
37421
37589
|
const isPreES2023Target = hasTypeScript && detectPreES2023Target(directory);
|
|
@@ -37426,7 +37594,7 @@ const discoverProject = (directory) => {
|
|
|
37426
37594
|
reactMajorVersion: resolveEffectiveReactMajor(reactVersion, packageJson),
|
|
37427
37595
|
tailwindVersion,
|
|
37428
37596
|
zodVersion,
|
|
37429
|
-
zodMajorVersion:
|
|
37597
|
+
zodMajorVersion: zodVersion === null ? null : getLowestDependencyMajor(zodVersion),
|
|
37430
37598
|
framework,
|
|
37431
37599
|
hasTypeScript,
|
|
37432
37600
|
hasReactCompiler: detectReactCompiler(directory, packageJson),
|
|
@@ -37440,7 +37608,9 @@ const discoverProject = (directory) => {
|
|
|
37440
37608
|
shopifyFlashListVersion,
|
|
37441
37609
|
shopifyFlashListMajorVersion: shopifyFlashListVersion === null ? null : getLowestDependencyMajor(shopifyFlashListVersion),
|
|
37442
37610
|
hasReanimated,
|
|
37611
|
+
reanimatedVersion,
|
|
37443
37612
|
isPreES2023Target,
|
|
37613
|
+
isStaticExport: framework === "nextjs" && detectNextjsStaticExport(workspaceFacts.next.sourceDirectory ?? directory),
|
|
37444
37614
|
sourceFileCount
|
|
37445
37615
|
};
|
|
37446
37616
|
cachedProjectInfos.set(directory, projectInfo);
|
|
@@ -37456,57 +37626,6 @@ const discoverProject = (directory) => {
|
|
|
37456
37626
|
* single source file has nothing to analyze.
|
|
37457
37627
|
*/
|
|
37458
37628
|
const isAnalyzableProject = (project) => project.reactVersion !== null || project.preactVersion !== null || project.sourceFileCount > 0;
|
|
37459
|
-
const MAJOR_MINOR_PATTERN = /(\d{1,4})\.(\d{1,4})/;
|
|
37460
|
-
const MAJOR_ONLY_PATTERN = /(\d{1,4})/;
|
|
37461
|
-
const UPPER_BOUND_COMPARATOR_PATTERN = /<=?\s{0,8}\d{1,4}(?:\.\d{1,4}){0,2}(?:-[^\s,|]+)?/g;
|
|
37462
|
-
const parseReactMajorMinor = (reactVersion) => {
|
|
37463
|
-
if (typeof reactVersion !== "string") return null;
|
|
37464
|
-
const trimmed = reactVersion.trim();
|
|
37465
|
-
if (trimmed.length === 0) return null;
|
|
37466
|
-
const lowerBoundsOnly = trimmed.replace(UPPER_BOUND_COMPARATOR_PATTERN, " ").trim();
|
|
37467
|
-
if (lowerBoundsOnly.length === 0) return null;
|
|
37468
|
-
const majorMinorMatch = lowerBoundsOnly.match(MAJOR_MINOR_PATTERN);
|
|
37469
|
-
if (majorMinorMatch) {
|
|
37470
|
-
const major = Number.parseInt(majorMinorMatch[1], 10);
|
|
37471
|
-
const minor = Number.parseInt(majorMinorMatch[2], 10);
|
|
37472
|
-
if (!Number.isFinite(major) || major <= 0) return null;
|
|
37473
|
-
if (!Number.isFinite(minor) || minor < 0) return null;
|
|
37474
|
-
return {
|
|
37475
|
-
major,
|
|
37476
|
-
minor
|
|
37477
|
-
};
|
|
37478
|
-
}
|
|
37479
|
-
const majorOnlyMatch = lowerBoundsOnly.match(MAJOR_ONLY_PATTERN);
|
|
37480
|
-
if (!majorOnlyMatch) return null;
|
|
37481
|
-
const major = Number.parseInt(majorOnlyMatch[1], 10);
|
|
37482
|
-
if (!Number.isFinite(major) || major <= 0) return null;
|
|
37483
|
-
return {
|
|
37484
|
-
major,
|
|
37485
|
-
minor: 0
|
|
37486
|
-
};
|
|
37487
|
-
};
|
|
37488
|
-
const isReactAtLeast = (detected, required) => {
|
|
37489
|
-
if (detected === null) return true;
|
|
37490
|
-
if (detected.major !== required.major) return detected.major > required.major;
|
|
37491
|
-
return detected.minor >= required.minor;
|
|
37492
|
-
};
|
|
37493
|
-
const parseLowerBoundVersion = (versionSpec) => import_semver.validRange(versionSpec) !== null ? import_semver.minVersion(versionSpec) : import_semver.coerce(versionSpec);
|
|
37494
|
-
const parseTailwindMajorMinor = (tailwindVersion) => {
|
|
37495
|
-
if (typeof tailwindVersion !== "string") return null;
|
|
37496
|
-
const trimmed = tailwindVersion.trim();
|
|
37497
|
-
if (trimmed.length === 0) return null;
|
|
37498
|
-
const lowerBound = parseLowerBoundVersion(trimmed);
|
|
37499
|
-
if (lowerBound === null || lowerBound.major <= 0) return null;
|
|
37500
|
-
return {
|
|
37501
|
-
major: lowerBound.major,
|
|
37502
|
-
minor: lowerBound.minor
|
|
37503
|
-
};
|
|
37504
|
-
};
|
|
37505
|
-
const isTailwindAtLeast = (detected, required) => {
|
|
37506
|
-
if (detected === null) return true;
|
|
37507
|
-
if (detected.major !== required.major) return detected.major > required.major;
|
|
37508
|
-
return detected.minor >= required.minor;
|
|
37509
|
-
};
|
|
37510
37629
|
const messageFromUnknown = (error) => error instanceof Error ? error.message : String(error);
|
|
37511
37630
|
var InvalidGlobPatternError = class extends Error {
|
|
37512
37631
|
pattern;
|
|
@@ -37632,6 +37751,9 @@ const buildRuleSeverityControls = (config) => {
|
|
|
37632
37751
|
...config.buckets !== void 0 ? { buckets: config.buckets } : {}
|
|
37633
37752
|
};
|
|
37634
37753
|
};
|
|
37754
|
+
const FOREIGN_INLINE_DISABLE_PATTERN = /(?:\/\/|\/\*)[ \t]*(eslint|oxlint)-disable-(next-line|line)(?![\w-])([^\r\n]*)/;
|
|
37755
|
+
const FOREIGN_BLOCK_DISABLE_PATTERN = /\/\*[ \t]*(eslint|oxlint)-disable(?![\w-])([^*\r\n]*)/;
|
|
37756
|
+
const FOREIGN_BLOCK_ENABLE_PATTERN = /\/\*[ \t]*(?:eslint|oxlint)-enable(?![\w-])([^*\r\n]*)/;
|
|
37635
37757
|
const LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY = {
|
|
37636
37758
|
"effect/no-adjust-state-on-prop-change": "react-doctor/no-adjust-state-on-prop-change",
|
|
37637
37759
|
"effect/no-chain-state-updates": "react-doctor/no-chain-state-updates",
|
|
@@ -37709,6 +37831,7 @@ const LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY = {
|
|
|
37709
37831
|
"react/jsx-no-new-function-as-prop": "react-doctor/jsx-no-new-function-as-prop",
|
|
37710
37832
|
"react/jsx-no-new-object-as-prop": "react-doctor/jsx-no-new-object-as-prop",
|
|
37711
37833
|
"react/jsx-no-script-url": "react-doctor/jsx-no-script-url",
|
|
37834
|
+
"react/jsx-no-target-blank": "react-doctor/jsx-no-target-blank",
|
|
37712
37835
|
"react/jsx-no-undef": "react-doctor/jsx-no-undef",
|
|
37713
37836
|
"react/jsx-no-useless-fragment": "react-doctor/jsx-no-useless-fragment",
|
|
37714
37837
|
"react/jsx-pascal-case": "react-doctor/jsx-pascal-case",
|
|
@@ -37793,9 +37916,6 @@ const tokenizeRuleList = (ruleList) => {
|
|
|
37793
37916
|
if (!ruleSection) return [];
|
|
37794
37917
|
return ruleSection.split(/[,\s]+/).map((token) => token.trim()).filter(Boolean);
|
|
37795
37918
|
};
|
|
37796
|
-
const FOREIGN_INLINE_DISABLE_PATTERN = /(?:\/\/|\/\*)[ \t]*(eslint|oxlint)-disable-(next-line|line)(?![\w-])([^\r\n]*)/;
|
|
37797
|
-
const FOREIGN_BLOCK_DISABLE_PATTERN = /\/\*[ \t]*(eslint|oxlint)-disable(?![\w-])([^*\r\n]*)/;
|
|
37798
|
-
const FOREIGN_BLOCK_ENABLE_PATTERN = /\/\*[ \t]*(?:eslint|oxlint)-enable(?![\w-])([^*\r\n]*)/;
|
|
37799
37919
|
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}\`).`;
|
|
37800
37920
|
const tokenMisnamesRule = (token, ruleId) => token !== ruleId && isSameRuleKey(token, ruleId);
|
|
37801
37921
|
const detectInlineNearMiss = (lines, diagnosticLineIndex, ruleId) => {
|
|
@@ -37849,6 +37969,53 @@ const detectForeignDisableNearMiss = (lines, diagnosticLineIndex, ruleId) => {
|
|
|
37849
37969
|
if (!ruleId.startsWith("react-doctor/")) return null;
|
|
37850
37970
|
return detectInlineNearMiss(lines, diagnosticLineIndex, ruleId) ?? detectBlockNearMiss(lines, diagnosticLineIndex, ruleId);
|
|
37851
37971
|
};
|
|
37972
|
+
const ESLINT_REACT_HOOKS_PLUGIN_PREFIX = "react-hooks/";
|
|
37973
|
+
const buildAcceptedTokens = (ruleId) => {
|
|
37974
|
+
const shortRuleId = ruleId.slice(15);
|
|
37975
|
+
return new Set([ruleId, `${ESLINT_REACT_HOOKS_PLUGIN_PREFIX}${shortRuleId}`]);
|
|
37976
|
+
};
|
|
37977
|
+
const listNamesRule = (ruleList, acceptedTokens) => tokenizeRuleList(ruleList).some((token) => acceptedTokens.has(token));
|
|
37978
|
+
const isInlineDisabled = (lines, diagnosticLineIndex, acceptedTokens) => {
|
|
37979
|
+
const candidates = [{
|
|
37980
|
+
line: lines[diagnosticLineIndex],
|
|
37981
|
+
requiredScope: "line"
|
|
37982
|
+
}, {
|
|
37983
|
+
line: lines[diagnosticLineIndex - 1],
|
|
37984
|
+
requiredScope: "next-line"
|
|
37985
|
+
}];
|
|
37986
|
+
for (const { line, requiredScope } of candidates) {
|
|
37987
|
+
const match = line?.match(FOREIGN_INLINE_DISABLE_PATTERN);
|
|
37988
|
+
if (!match) continue;
|
|
37989
|
+
const [, , scope, ruleList] = match;
|
|
37990
|
+
if (scope !== requiredScope) continue;
|
|
37991
|
+
if (listNamesRule(ruleList, acceptedTokens)) return true;
|
|
37992
|
+
}
|
|
37993
|
+
return false;
|
|
37994
|
+
};
|
|
37995
|
+
const isBlockDisabled = (lines, diagnosticLineIndex, acceptedTokens) => {
|
|
37996
|
+
let isRangeOpen = false;
|
|
37997
|
+
const lastLineIndex = Math.min(diagnosticLineIndex, lines.length - 1);
|
|
37998
|
+
for (let lineIndex = 0; lineIndex <= lastLineIndex; lineIndex++) {
|
|
37999
|
+
const line = lines[lineIndex];
|
|
38000
|
+
if (line === void 0 || !line.includes("-disable") && !line.includes("-enable")) continue;
|
|
38001
|
+
const disableMatch = line.match(FOREIGN_BLOCK_DISABLE_PATTERN);
|
|
38002
|
+
if (disableMatch && listNamesRule(disableMatch[2], acceptedTokens)) {
|
|
38003
|
+
isRangeOpen = true;
|
|
38004
|
+
continue;
|
|
38005
|
+
}
|
|
38006
|
+
const enableMatch = line.match(FOREIGN_BLOCK_ENABLE_PATTERN);
|
|
38007
|
+
if (enableMatch) {
|
|
38008
|
+
const enabledRules = tokenizeRuleList(enableMatch[1]);
|
|
38009
|
+
if (enabledRules.length === 0 || enabledRules.some((rule) => acceptedTokens.has(rule))) isRangeOpen = false;
|
|
38010
|
+
}
|
|
38011
|
+
}
|
|
38012
|
+
return isRangeOpen;
|
|
38013
|
+
};
|
|
38014
|
+
const isCompilerRuleForeignDisabled = (lines, diagnosticLineIndex, ruleId) => {
|
|
38015
|
+
if (!ruleId.startsWith("react-hooks-js/")) return false;
|
|
38016
|
+
const acceptedTokens = buildAcceptedTokens(ruleId);
|
|
38017
|
+
return isInlineDisabled(lines, diagnosticLineIndex, acceptedTokens) || isBlockDisabled(lines, diagnosticLineIndex, acceptedTokens);
|
|
38018
|
+
};
|
|
37852
38019
|
const JSX_OPENER_TAG_PATTERN = /<[A-Za-z][\w.]*/g;
|
|
37853
38020
|
const JSX_TAG_NAME_FOLLOW = /[A-Za-z]/;
|
|
37854
38021
|
const isOpenerMatchInsideLineComment = (line, openerCharIndex) => {
|
|
@@ -37992,6 +38159,11 @@ const evaluateSuppression = (lines, diagnosticLineIndex, ruleId) => {
|
|
|
37992
38159
|
isSuppressed: true,
|
|
37993
38160
|
nearMissHint: null
|
|
37994
38161
|
};
|
|
38162
|
+
if (isCompilerRuleForeignDisabled(lines, diagnosticLineIndex, ruleId)) return {
|
|
38163
|
+
isSuppressed: true,
|
|
38164
|
+
nearMissHint: null,
|
|
38165
|
+
isForeignDirective: true
|
|
38166
|
+
};
|
|
37995
38167
|
const directComments = findStackedDisableCommentsAbove(lines, diagnosticLineIndex);
|
|
37996
38168
|
if (hasChainSuppressor(directComments, ruleId)) return {
|
|
37997
38169
|
isSuppressed: true,
|
|
@@ -38273,12 +38445,18 @@ const collectStringSet = (values) => {
|
|
|
38273
38445
|
return new Set(values.filter((value) => typeof value === "string"));
|
|
38274
38446
|
};
|
|
38275
38447
|
const JSX_NO_UNDEF_IDENTIFIER_PATTERN = /^`([^`]+)`/;
|
|
38448
|
+
const NODE_CLI_PATH_SEGMENTS = new Set([
|
|
38449
|
+
"scripts",
|
|
38450
|
+
"bin",
|
|
38451
|
+
"tools"
|
|
38452
|
+
]);
|
|
38453
|
+
const hasNodeCliPathSegment = (filePath) => filePath.split(/[\\/]/).slice(0, -1).some((segment) => NODE_CLI_PATH_SEGMENTS.has(segment));
|
|
38276
38454
|
/**
|
|
38277
38455
|
* Pre-compiles every stateful filter and returns a single
|
|
38278
38456
|
* `apply(diagnostic)` closure that runs (in order):
|
|
38279
38457
|
*
|
|
38280
38458
|
* 1. auto-suppress (test-noise rules in test files; `migration-hint`
|
|
38281
|
-
* wins over `test-noise`)
|
|
38459
|
+
* wins over `test-noise`; `no-console` on Node CLI scripts)
|
|
38282
38460
|
* 2. severity overrides (top-level `rules` / `categories`, with
|
|
38283
38461
|
* `"off"` dropping)
|
|
38284
38462
|
* 3. warning suppression (only when `showWarnings` is false: drops every
|
|
@@ -38376,6 +38554,12 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
38376
38554
|
if (hasRawTextWrappers && isInsideStringOnlyWrapper(lines, diagnostic.line, diagnostic.column, rawTextWrapperComponentNames)) return true;
|
|
38377
38555
|
return false;
|
|
38378
38556
|
};
|
|
38557
|
+
const isNoConsoleOnNodeCliScript = (diagnostic) => {
|
|
38558
|
+
if (diagnostic.rule !== "no-console") return false;
|
|
38559
|
+
if (hasNodeCliPathSegment(diagnostic.filePath)) return true;
|
|
38560
|
+
const lines = getFileLines(diagnostic.filePath);
|
|
38561
|
+
return lines !== null && Boolean(lines[0]?.startsWith("#!"));
|
|
38562
|
+
};
|
|
38379
38563
|
const isJsxNoUndefSuppressedByConfig = (diagnostic) => {
|
|
38380
38564
|
if (!hasRuntimeGlobals) return false;
|
|
38381
38565
|
if (diagnostic.rule !== "jsx-no-undef") return false;
|
|
@@ -38385,6 +38569,7 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
38385
38569
|
return {
|
|
38386
38570
|
apply: (diagnostic) => {
|
|
38387
38571
|
if (shouldAutoSuppress(diagnostic)) return null;
|
|
38572
|
+
if (isNoConsoleOnNodeCliScript(diagnostic)) return null;
|
|
38388
38573
|
let current = diagnostic;
|
|
38389
38574
|
let explicitSeverityOverride;
|
|
38390
38575
|
let explicitRuleOverride;
|
|
@@ -38414,7 +38599,7 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
38414
38599
|
if (lines) {
|
|
38415
38600
|
const ruleIdentifier = `${current.plugin}/${current.rule}`;
|
|
38416
38601
|
const evaluation = evaluateSuppression(lines, current.line - 1, ruleIdentifier);
|
|
38417
|
-
if (evaluation.isSuppressed) return suppress(current, "inline");
|
|
38602
|
+
if (evaluation.isSuppressed) return suppress(current, evaluation.isForeignDirective ? "foreign-inline" : "inline");
|
|
38418
38603
|
if (evaluation.nearMissHint) current = {
|
|
38419
38604
|
...current,
|
|
38420
38605
|
suppressionHint: evaluation.nearMissHint
|
|
@@ -40251,64 +40436,64 @@ function* collectSecurityScanFiles(rootDirectory) {
|
|
|
40251
40436
|
}
|
|
40252
40437
|
}
|
|
40253
40438
|
}
|
|
40254
|
-
|
|
40255
|
-
|
|
40256
|
-
|
|
40257
|
-
|
|
40258
|
-
|
|
40259
|
-
|
|
40260
|
-
|
|
40439
|
+
const CLIENT_ONLY_FRAMEWORKS = new Set([
|
|
40440
|
+
"vite",
|
|
40441
|
+
"cra",
|
|
40442
|
+
"gatsby",
|
|
40443
|
+
"react-native",
|
|
40444
|
+
"expo"
|
|
40445
|
+
]);
|
|
40446
|
+
const addMajorLadder = (capabilities, name, major, earliest, latest) => {
|
|
40447
|
+
if (major === null) return;
|
|
40448
|
+
const cappedMajor = Math.min(major, latest);
|
|
40449
|
+
for (let candidate = earliest; candidate <= cappedMajor; candidate += 1) capabilities.add(`${name}:${candidate}`);
|
|
40450
|
+
};
|
|
40261
40451
|
const buildCapabilities = (project) => {
|
|
40262
40452
|
const capabilities = /* @__PURE__ */ new Set();
|
|
40263
40453
|
capabilities.add(project.framework);
|
|
40264
|
-
if (
|
|
40454
|
+
if (project.reactVersion !== null || project.preactVersion !== null) capabilities.add("react");
|
|
40265
40455
|
if (project.framework === "expo" || project.framework === "react-native" || project.hasReactNativeWorkspace) capabilities.add("react-native");
|
|
40266
40456
|
if (project.expoVersion !== null) capabilities.add("expo");
|
|
40457
|
+
if ((project.framework === "nextjs" || project.framework === "tanstack-start" || project.framework === "remix") && !project.isStaticExport) capabilities.add("server-actions");
|
|
40458
|
+
if (CLIENT_ONLY_FRAMEWORKS.has(project.framework)) capabilities.add("client-only");
|
|
40459
|
+
if (project.isStaticExport) capabilities.add("nextjs:static-export");
|
|
40267
40460
|
if (project.nextjsMajorVersion !== null && project.nextjsMajorVersion >= 15) capabilities.add("nextjs:15");
|
|
40268
|
-
|
|
40269
|
-
if (
|
|
40270
|
-
|
|
40271
|
-
|
|
40272
|
-
|
|
40273
|
-
|
|
40274
|
-
|
|
40275
|
-
|
|
40276
|
-
|
|
40277
|
-
|
|
40278
|
-
|
|
40279
|
-
if (project.
|
|
40280
|
-
capabilities.add("tailwind");
|
|
40281
|
-
if (isTailwindAtLeast(parseTailwindMajorMinor(project.tailwindVersion), {
|
|
40282
|
-
major: 3,
|
|
40283
|
-
minor: 4
|
|
40284
|
-
})) capabilities.add("tailwind:3.4");
|
|
40285
|
-
}
|
|
40286
|
-
if (project.zodVersion !== null) {
|
|
40287
|
-
capabilities.add("zod");
|
|
40288
|
-
if (project.zodMajorVersion !== null && project.zodMajorVersion >= 4) capabilities.add("zod:4");
|
|
40289
|
-
}
|
|
40461
|
+
addMajorLadder(capabilities, "react", project.reactMajorVersion, 17, 30);
|
|
40462
|
+
if (project.reactMajorVersion !== null && project.reactMajorVersion >= 19 && isMajorMinorAtLeast(parseReactMajorMinor(project.reactVersion), {
|
|
40463
|
+
major: 19,
|
|
40464
|
+
minor: 2
|
|
40465
|
+
})) capabilities.add("react:19.2");
|
|
40466
|
+
if (project.tailwindVersion !== null) capabilities.add("tailwind");
|
|
40467
|
+
if (project.tailwindVersion !== null && isMajorMinorAtLeast(parseTailwindMajorMinor(project.tailwindVersion), {
|
|
40468
|
+
major: 3,
|
|
40469
|
+
minor: 4
|
|
40470
|
+
})) capabilities.add("tailwind:3.4");
|
|
40471
|
+
if (project.zodVersion !== null) capabilities.add("zod");
|
|
40472
|
+
if (project.zodMajorVersion !== null && project.zodMajorVersion >= 4) capabilities.add("zod:4");
|
|
40290
40473
|
if (project.isPreES2023Target) capabilities.add("pre-es2023");
|
|
40291
40474
|
if (project.hasReactCompiler) capabilities.add("react-compiler");
|
|
40292
40475
|
if (project.hasTanStackQuery) capabilities.add("tanstack-query");
|
|
40293
40476
|
if (project.hasTypeScript) capabilities.add("typescript");
|
|
40294
|
-
if (project.preactVersion !== null)
|
|
40295
|
-
|
|
40296
|
-
|
|
40297
|
-
if (preactMajor !== null) {
|
|
40298
|
-
const cappedPreactMajor = Math.min(preactMajor, 20);
|
|
40299
|
-
for (let major = 10; major <= cappedPreactMajor; major++) capabilities.add(`preact:${major}`);
|
|
40300
|
-
}
|
|
40301
|
-
if (project.reactVersion === null) capabilities.add("pure-preact");
|
|
40302
|
-
}
|
|
40477
|
+
if (project.preactVersion !== null) capabilities.add("preact");
|
|
40478
|
+
addMajorLadder(capabilities, "preact", project.preactMajorVersion, 10, 20);
|
|
40479
|
+
if (project.preactVersion !== null && project.reactVersion === null) capabilities.add("pure-preact");
|
|
40303
40480
|
return capabilities;
|
|
40304
40481
|
};
|
|
40305
|
-
const
|
|
40482
|
+
const capabilitiesByProject = /* @__PURE__ */ new WeakMap();
|
|
40483
|
+
const getCapabilities = (project) => {
|
|
40484
|
+
const cached = capabilitiesByProject.get(project);
|
|
40485
|
+
if (cached !== void 0) return cached;
|
|
40486
|
+
const capabilities = buildCapabilities(project);
|
|
40487
|
+
capabilitiesByProject.set(project, capabilities);
|
|
40488
|
+
return capabilities;
|
|
40489
|
+
};
|
|
40490
|
+
const shouldEnableRule = (requires, tags, capabilities, ignoredTags, disabledWhen) => {
|
|
40306
40491
|
if (requires) {
|
|
40307
40492
|
for (const capability of requires) if (!capabilities.has(capability)) return false;
|
|
40308
40493
|
}
|
|
40309
40494
|
if (tags?.includes("react-jsx-only") && !capabilities.has("react")) return false;
|
|
40310
|
-
if (
|
|
40311
|
-
for (const capability of
|
|
40495
|
+
if (disabledWhen) {
|
|
40496
|
+
for (const capability of disabledWhen) if (capabilities.has(capability)) return false;
|
|
40312
40497
|
}
|
|
40313
40498
|
if (tags) {
|
|
40314
40499
|
for (const tag of tags) if (ignoredTags.has(tag)) return false;
|
|
@@ -40319,14 +40504,14 @@ const yieldToEventLoop = () => new Promise((resolve) => {
|
|
|
40319
40504
|
setImmediate(resolve);
|
|
40320
40505
|
});
|
|
40321
40506
|
const createSecurityScanSession = (rootDirectory, options) => {
|
|
40322
|
-
const capabilities = options.project ?
|
|
40507
|
+
const capabilities = options.project ? getCapabilities(options.project) : /* @__PURE__ */ new Set();
|
|
40323
40508
|
const ignoredTags = options.ignoredTags ?? /* @__PURE__ */ new Set();
|
|
40324
40509
|
const enabledScanRules = REACT_DOCTOR_RULES.flatMap((entry) => {
|
|
40325
40510
|
const rule = entry.rule;
|
|
40326
40511
|
const scan = rule.scan;
|
|
40327
40512
|
if (typeof scan !== "function") return [];
|
|
40328
40513
|
if (rule.defaultEnabled === false) return [];
|
|
40329
|
-
if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.
|
|
40514
|
+
if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.disabledWhen)) return [];
|
|
40330
40515
|
return [{
|
|
40331
40516
|
entry,
|
|
40332
40517
|
scan,
|
|
@@ -40346,6 +40531,7 @@ const createSecurityScanSession = (rootDirectory, options) => {
|
|
|
40346
40531
|
return status === true;
|
|
40347
40532
|
};
|
|
40348
40533
|
const scanFileByRule = function* (file) {
|
|
40534
|
+
if (file === null) return;
|
|
40349
40535
|
for (const { entry, scan, committedFilesOnly } of enabledScanRules) {
|
|
40350
40536
|
for (const finding of scan(file)) {
|
|
40351
40537
|
if (committedFilesOnly && isFileGitIgnored(file)) continue;
|
|
@@ -41807,7 +41993,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
41807
41993
|
})),
|
|
41808
41994
|
showStagedContent: (directory, relativePath, options) => runCommand({
|
|
41809
41995
|
command: "git",
|
|
41810
|
-
args: ["show",
|
|
41996
|
+
args: ["show", `:./${relativePath}`],
|
|
41811
41997
|
directory,
|
|
41812
41998
|
maxStdoutBytes: options?.maxBufferBytes
|
|
41813
41999
|
}).pipe(map$3((result) => result.status === 0 ? result.stdout : null)),
|
|
@@ -42242,7 +42428,7 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
42242
42428
|
const reactCompilerRules = reactHooksJsPlugin ? applyRuleSeverityControls(filterRulesToAvailable(REACT_COMPILER_RULES, "react-hooks-js", reactHooksJsPlugin.availableRuleNames), severityControls) : {};
|
|
42243
42429
|
const jsPlugins = [];
|
|
42244
42430
|
if (reactHooksJsPlugin) jsPlugins.push(reactHooksJsPlugin.entry);
|
|
42245
|
-
const capabilities =
|
|
42431
|
+
const capabilities = getCapabilities(project);
|
|
42246
42432
|
const enabledReactDoctorRules = {};
|
|
42247
42433
|
for (const registryEntry of REACT_DOCTOR_RULES) {
|
|
42248
42434
|
const rule = reactDoctorPlugin.rules[registryEntry.id];
|
|
@@ -42253,7 +42439,7 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
42253
42439
|
if (rule.scan !== void 0) continue;
|
|
42254
42440
|
if (customRulesOnly && registryEntry.originallyExternal) continue;
|
|
42255
42441
|
if (rule.framework !== "global" && !rule.requires) continue;
|
|
42256
|
-
if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.
|
|
42442
|
+
if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.disabledWhen)) continue;
|
|
42257
42443
|
const explicitRuleOverride = resolveRuleSeverityOverride({ ruleKey: registryEntry.key }, severityControls);
|
|
42258
42444
|
if (rule.defaultEnabled === false && explicitRuleOverride === void 0) continue;
|
|
42259
42445
|
const severity = resolveRuleSeverityOverride({
|
|
@@ -42284,6 +42470,7 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
42284
42470
|
settings: { "react-doctor": {
|
|
42285
42471
|
framework: project.framework,
|
|
42286
42472
|
rootDirectory: resolveSettingsRootDirectory(project.rootDirectory),
|
|
42473
|
+
capabilities: [...capabilities].sort(),
|
|
42287
42474
|
...project.shopifyFlashListMajorVersion !== null ? { shopifyFlashListMajorVersion: project.shopifyFlashListMajorVersion } : {},
|
|
42288
42475
|
...serverAuthFunctionNames && serverAuthFunctionNames.length > 0 ? { serverAuthFunctionNames: [...serverAuthFunctionNames] } : {}
|
|
42289
42476
|
} },
|
|
@@ -42574,25 +42761,18 @@ const mapWithConcurrency = async (items, concurrency, task) => {
|
|
|
42574
42761
|
if (errors.length > 0) throw errors[0];
|
|
42575
42762
|
return results;
|
|
42576
42763
|
};
|
|
42577
|
-
const getPublicEnvPrefix = (framework) => {
|
|
42578
|
-
switch (framework) {
|
|
42579
|
-
case "nextjs": return "NEXT_PUBLIC_*";
|
|
42580
|
-
case "vite":
|
|
42581
|
-
case "tanstack-start": return "VITE_*";
|
|
42582
|
-
case "cra": return "REACT_APP_*";
|
|
42583
|
-
case "gatsby": return "GATSBY_*";
|
|
42584
|
-
default: return null;
|
|
42585
|
-
}
|
|
42586
|
-
};
|
|
42587
|
-
const buildNoSecretsRecommendation = (project, fallbackRecommendation) => {
|
|
42588
|
-
const publicEnvPrefix = getPublicEnvPrefix(project.framework);
|
|
42589
|
-
if (!publicEnvPrefix) return fallbackRecommendation;
|
|
42590
|
-
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`;
|
|
42591
|
-
};
|
|
42592
42764
|
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";
|
|
42765
|
+
const ACCESSORS_MIN_VERSION = "3.15.0";
|
|
42766
|
+
const supportsAccessors = (reanimatedVersion) => {
|
|
42767
|
+
if (reanimatedVersion === null) return true;
|
|
42768
|
+
const lowerBound = import_semver.validRange(reanimatedVersion) !== null ? import_semver.minVersion(reanimatedVersion) : import_semver.coerce(reanimatedVersion);
|
|
42769
|
+
if (lowerBound === null) return true;
|
|
42770
|
+
return import_semver.gte(lowerBound, ACCESSORS_MIN_VERSION);
|
|
42771
|
+
};
|
|
42593
42772
|
const appendReanimatedSharedValueHint = (help, rule, project) => {
|
|
42594
42773
|
if (rule !== "immutability") return help;
|
|
42595
42774
|
if (!project.hasReanimated) return help;
|
|
42775
|
+
if (!supportsAccessors(project.reanimatedVersion)) return help;
|
|
42596
42776
|
if (!help) return REANIMATED_SHARED_VALUE_HINT;
|
|
42597
42777
|
return `${help}\n\n${REANIMATED_SHARED_VALUE_HINT}`;
|
|
42598
42778
|
};
|
|
@@ -42754,13 +42934,13 @@ const REACT_USE_BINDING_RESOLUTION = {
|
|
|
42754
42934
|
isReactUseBinding: true,
|
|
42755
42935
|
isReactNamespaceBinding: false
|
|
42756
42936
|
};
|
|
42757
|
-
const getScriptKind = (filename) => {
|
|
42937
|
+
const getScriptKind$2 = (filename) => {
|
|
42758
42938
|
if (filename.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
42759
42939
|
if (filename.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
42760
42940
|
if (filename.endsWith(".ts")) return ts.ScriptKind.TS;
|
|
42761
42941
|
return ts.ScriptKind.JS;
|
|
42762
42942
|
};
|
|
42763
|
-
const getUtf16Offset = (sourceText, utf8Offset) => Buffer.from(sourceText).subarray(0, utf8Offset).toString("utf8").length;
|
|
42943
|
+
const getUtf16Offset$1 = (sourceText, utf8Offset) => Buffer.from(sourceText).subarray(0, utf8Offset).toString("utf8").length;
|
|
42764
42944
|
const unwrapExpression = (expression) => {
|
|
42765
42945
|
let currentExpression = expression;
|
|
42766
42946
|
while (ts.isParenthesizedExpression(currentExpression) || ts.isAsExpression(currentExpression) || ts.isSatisfiesExpression(currentExpression) || ts.isNonNullExpression(currentExpression) || ts.isTypeAssertionExpression(currentExpression)) currentExpression = currentExpression.expression;
|
|
@@ -43009,8 +43189,8 @@ const findUseCallIdentifier = (sourceFile, useOffset) => {
|
|
|
43009
43189
|
return matchedIdentifier;
|
|
43010
43190
|
};
|
|
43011
43191
|
const resolveUseCallBinding = (sourceText, filename, utf8Offset) => {
|
|
43012
|
-
const sourceFile = ts.createSourceFile(filename, sourceText, ts.ScriptTarget.Latest, true, getScriptKind(filename));
|
|
43013
|
-
const useIdentifier = findUseCallIdentifier(sourceFile, getUtf16Offset(sourceText, utf8Offset));
|
|
43192
|
+
const sourceFile = ts.createSourceFile(filename, sourceText, ts.ScriptTarget.Latest, true, getScriptKind$2(filename));
|
|
43193
|
+
const useIdentifier = findUseCallIdentifier(sourceFile, getUtf16Offset$1(sourceText, utf8Offset));
|
|
43014
43194
|
if (!useIdentifier) return null;
|
|
43015
43195
|
return resolveIdentifierBinding(useIdentifier, collectReactImportBindings(sourceFile), sourceFile);
|
|
43016
43196
|
};
|
|
@@ -43031,6 +43211,201 @@ const shouldSuppressLocalUseHookDiagnostic = (diagnostic, rootDirectory) => {
|
|
|
43031
43211
|
const bindingResolution = resolveUseCallBinding(sourceText, absolutePath, primaryLabel.span.offset);
|
|
43032
43212
|
return bindingResolution !== null && !bindingResolution.isReactUseBinding;
|
|
43033
43213
|
};
|
|
43214
|
+
const SUPPRESSED_COMPILER_CODES = new Set(["react-hooks-js(immutability)", "react-hooks-js(refs)"]);
|
|
43215
|
+
const WORKLET_DIRECTIVE = "worklet";
|
|
43216
|
+
const WORKLET_ACCEPTING_CALLEES = new Set([
|
|
43217
|
+
"useAnimatedStyle",
|
|
43218
|
+
"useAnimatedProps",
|
|
43219
|
+
"useDerivedValue",
|
|
43220
|
+
"useAnimatedReaction",
|
|
43221
|
+
"useAnimatedScrollHandler",
|
|
43222
|
+
"useAnimatedGestureHandler",
|
|
43223
|
+
"useFrameCallback",
|
|
43224
|
+
"runOnUI",
|
|
43225
|
+
"executeOnUIRuntimeSync"
|
|
43226
|
+
]);
|
|
43227
|
+
const GESTURE_ROOT_IDENTIFIER = "Gesture";
|
|
43228
|
+
const getScriptKind$1 = (filename) => {
|
|
43229
|
+
if (filename.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
43230
|
+
if (filename.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
43231
|
+
if (filename.endsWith(".ts")) return ts.ScriptKind.TS;
|
|
43232
|
+
return ts.ScriptKind.JS;
|
|
43233
|
+
};
|
|
43234
|
+
const getUtf16Offset = (sourceText, utf8Offset) => Buffer.from(sourceText).subarray(0, utf8Offset).toString("utf8").length;
|
|
43235
|
+
const hasWorkletDirective = (node) => {
|
|
43236
|
+
if (!ts.isFunctionDeclaration(node) && !ts.isFunctionExpression(node) && !ts.isArrowFunction(node) && !ts.isMethodDeclaration(node)) return false;
|
|
43237
|
+
const body = node.body;
|
|
43238
|
+
if (!body || !ts.isBlock(body)) return false;
|
|
43239
|
+
for (const statement of body.statements) {
|
|
43240
|
+
if (!ts.isExpressionStatement(statement)) return false;
|
|
43241
|
+
if (!ts.isStringLiteral(statement.expression)) return false;
|
|
43242
|
+
if (statement.expression.text === WORKLET_DIRECTIVE) return true;
|
|
43243
|
+
}
|
|
43244
|
+
return false;
|
|
43245
|
+
};
|
|
43246
|
+
const rootIdentifierOfCallChain = (expression) => {
|
|
43247
|
+
let currentExpression = expression;
|
|
43248
|
+
while (true) {
|
|
43249
|
+
if (ts.isIdentifier(currentExpression)) return currentExpression.text;
|
|
43250
|
+
if (ts.isPropertyAccessExpression(currentExpression)) {
|
|
43251
|
+
currentExpression = currentExpression.expression;
|
|
43252
|
+
continue;
|
|
43253
|
+
}
|
|
43254
|
+
if (ts.isCallExpression(currentExpression)) {
|
|
43255
|
+
currentExpression = currentExpression.expression;
|
|
43256
|
+
continue;
|
|
43257
|
+
}
|
|
43258
|
+
return null;
|
|
43259
|
+
}
|
|
43260
|
+
};
|
|
43261
|
+
const isWorkletAcceptingCall = (callExpression) => {
|
|
43262
|
+
const callee = callExpression.expression;
|
|
43263
|
+
if (ts.isIdentifier(callee)) return WORKLET_ACCEPTING_CALLEES.has(callee.text);
|
|
43264
|
+
if (ts.isPropertyAccessExpression(callee)) {
|
|
43265
|
+
if (WORKLET_ACCEPTING_CALLEES.has(callee.name.text)) return true;
|
|
43266
|
+
return rootIdentifierOfCallChain(callee) === GESTURE_ROOT_IDENTIFIER;
|
|
43267
|
+
}
|
|
43268
|
+
return false;
|
|
43269
|
+
};
|
|
43270
|
+
const isFunctionWorklet = (functionNode) => {
|
|
43271
|
+
if (hasWorkletDirective(functionNode)) return true;
|
|
43272
|
+
const parent = functionNode.parent;
|
|
43273
|
+
return ts.isCallExpression(parent) && parent.arguments.some((argument) => argument === functionNode) && isWorkletAcceptingCall(parent);
|
|
43274
|
+
};
|
|
43275
|
+
const findNodeAtOffset = (sourceFile, targetOffset) => {
|
|
43276
|
+
let matchedNode = null;
|
|
43277
|
+
const visit = (node) => {
|
|
43278
|
+
if (node.getStart(sourceFile) > targetOffset || node.getEnd() <= targetOffset) return;
|
|
43279
|
+
matchedNode = node;
|
|
43280
|
+
ts.forEachChild(node, visit);
|
|
43281
|
+
};
|
|
43282
|
+
visit(sourceFile);
|
|
43283
|
+
return matchedNode;
|
|
43284
|
+
};
|
|
43285
|
+
const isOffsetInsideWorklet = (sourceFile, targetOffset) => {
|
|
43286
|
+
let currentNode = findNodeAtOffset(sourceFile, targetOffset);
|
|
43287
|
+
while (currentNode) {
|
|
43288
|
+
if (ts.isFunctionLike(currentNode) && isFunctionWorklet(currentNode)) return true;
|
|
43289
|
+
currentNode = currentNode.parent;
|
|
43290
|
+
}
|
|
43291
|
+
return false;
|
|
43292
|
+
};
|
|
43293
|
+
const shouldSuppressCompilerFindingInWorklet = (diagnostic, project, rootDirectory) => {
|
|
43294
|
+
if (!SUPPRESSED_COMPILER_CODES.has(diagnostic.code)) return false;
|
|
43295
|
+
if (!project.hasReanimated) return false;
|
|
43296
|
+
const primaryLabel = diagnostic.labels[0];
|
|
43297
|
+
if (!primaryLabel) return false;
|
|
43298
|
+
const absolutePath = Path.isAbsolute(diagnostic.filename) ? diagnostic.filename : Path.join(rootDirectory, diagnostic.filename);
|
|
43299
|
+
let sourceText;
|
|
43300
|
+
try {
|
|
43301
|
+
sourceText = NFS.readFileSync(absolutePath, "utf-8");
|
|
43302
|
+
} catch {
|
|
43303
|
+
return false;
|
|
43304
|
+
}
|
|
43305
|
+
return isOffsetInsideWorklet(ts.createSourceFile(absolutePath, sourceText, ts.ScriptTarget.Latest, true, getScriptKind$1(absolutePath)), getUtf16Offset(sourceText, primaryLabel.span.offset));
|
|
43306
|
+
};
|
|
43307
|
+
const MANUAL_MEMOIZATION_PLUGIN = "react-doctor";
|
|
43308
|
+
const MANUAL_MEMOIZATION_RULE = "react-compiler-no-manual-memoization";
|
|
43309
|
+
const REACT_COMPILER_PLUGIN = "react-hooks-js";
|
|
43310
|
+
const getScriptKind = (filename) => {
|
|
43311
|
+
if (filename.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
43312
|
+
if (filename.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
43313
|
+
if (filename.endsWith(".ts")) return ts.ScriptKind.TS;
|
|
43314
|
+
return ts.ScriptKind.JS;
|
|
43315
|
+
};
|
|
43316
|
+
const isManualMemoizationDiagnostic = (diagnostic) => diagnostic.plugin === MANUAL_MEMOIZATION_PLUGIN && diagnostic.rule === MANUAL_MEMOIZATION_RULE;
|
|
43317
|
+
const nodeLineRange = (sourceFile, node) => ({
|
|
43318
|
+
startLine: sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1,
|
|
43319
|
+
endLine: sourceFile.getLineAndCharacterOfPosition(node.getEnd()).line + 1
|
|
43320
|
+
});
|
|
43321
|
+
const findOutermostEnclosingFunctionRange = (sourceFile, targetLine) => {
|
|
43322
|
+
let foundRange = null;
|
|
43323
|
+
const visit = (node) => {
|
|
43324
|
+
if (foundRange) return;
|
|
43325
|
+
if (ts.isFunctionLike(node)) {
|
|
43326
|
+
const range = nodeLineRange(sourceFile, node);
|
|
43327
|
+
if (range.startLine <= targetLine && targetLine <= range.endLine) foundRange = range;
|
|
43328
|
+
return;
|
|
43329
|
+
}
|
|
43330
|
+
ts.forEachChild(node, visit);
|
|
43331
|
+
};
|
|
43332
|
+
visit(sourceFile);
|
|
43333
|
+
return foundRange;
|
|
43334
|
+
};
|
|
43335
|
+
const NEWLINE_BYTE = 10;
|
|
43336
|
+
const lineOfUtf8Offset = (sourceBuffer, utf8Offset) => {
|
|
43337
|
+
let lineNumber = 1;
|
|
43338
|
+
const scanEnd = Math.min(utf8Offset, sourceBuffer.length);
|
|
43339
|
+
for (let byteIndex = 0; byteIndex < scanEnd; byteIndex++) if (sourceBuffer[byteIndex] === NEWLINE_BYTE) lineNumber++;
|
|
43340
|
+
return lineNumber;
|
|
43341
|
+
};
|
|
43342
|
+
/**
|
|
43343
|
+
* Drops `react-doctor/react-compiler-no-manual-memoization` diagnostics
|
|
43344
|
+
* whose enclosing function also carries a `react-hooks-js` (React
|
|
43345
|
+
* Compiler) diagnostic. Every react-hooks-js finding marks a function the
|
|
43346
|
+
* compiler could NOT compile (a bail-out / skip / preserve-manual-
|
|
43347
|
+
* memoization failure), so "React Compiler already caches every value
|
|
43348
|
+
* here" is false for that function and the manual memoization is
|
|
43349
|
+
* load-bearing, not dead weight.
|
|
43350
|
+
*
|
|
43351
|
+
* Matching is per enclosing function, not per file: a bailed-out
|
|
43352
|
+
* component must not silence redundant-memo findings in a sibling
|
|
43353
|
+
* component the compiler did optimize.
|
|
43354
|
+
*
|
|
43355
|
+
* Module-scope `memo(() => …)` wrappers have no enclosing function; for
|
|
43356
|
+
* those the memo call's own span is the containment range, so a bail-out
|
|
43357
|
+
* inside the inline-wrapped component still suppresses. Every failure
|
|
43358
|
+
* path (unreadable file, missing span) fails open and keeps the
|
|
43359
|
+
* diagnostic.
|
|
43360
|
+
*/
|
|
43361
|
+
const suppressMemoizationInBailedOutFunctions = (diagnostics, rootDirectory) => {
|
|
43362
|
+
const compilerDiagnosticLinesByFile = /* @__PURE__ */ new Map();
|
|
43363
|
+
let hasManualMemoizationDiagnostic = false;
|
|
43364
|
+
for (const diagnostic of diagnostics) {
|
|
43365
|
+
if (isManualMemoizationDiagnostic(diagnostic)) {
|
|
43366
|
+
hasManualMemoizationDiagnostic = true;
|
|
43367
|
+
continue;
|
|
43368
|
+
}
|
|
43369
|
+
if (diagnostic.plugin !== REACT_COMPILER_PLUGIN || diagnostic.line <= 0) continue;
|
|
43370
|
+
const lines = compilerDiagnosticLinesByFile.get(diagnostic.filePath);
|
|
43371
|
+
if (lines) lines.push(diagnostic.line);
|
|
43372
|
+
else compilerDiagnosticLinesByFile.set(diagnostic.filePath, [diagnostic.line]);
|
|
43373
|
+
}
|
|
43374
|
+
if (!hasManualMemoizationDiagnostic || compilerDiagnosticLinesByFile.size === 0) return diagnostics;
|
|
43375
|
+
const parsedFileCache = /* @__PURE__ */ new Map();
|
|
43376
|
+
const readAndParse = (filePath) => {
|
|
43377
|
+
const cached = parsedFileCache.get(filePath);
|
|
43378
|
+
if (cached !== void 0) return cached;
|
|
43379
|
+
const absolutePath = Path.isAbsolute(filePath) ? filePath : Path.resolve(rootDirectory || ".", filePath);
|
|
43380
|
+
let parsed;
|
|
43381
|
+
try {
|
|
43382
|
+
const buffer = NFS.readFileSync(absolutePath);
|
|
43383
|
+
parsed = {
|
|
43384
|
+
sourceFile: ts.createSourceFile(absolutePath, buffer.toString("utf8"), ts.ScriptTarget.Latest, true, getScriptKind(absolutePath)),
|
|
43385
|
+
buffer
|
|
43386
|
+
};
|
|
43387
|
+
} catch {
|
|
43388
|
+
parsed = null;
|
|
43389
|
+
}
|
|
43390
|
+
parsedFileCache.set(filePath, parsed);
|
|
43391
|
+
return parsed;
|
|
43392
|
+
};
|
|
43393
|
+
const isInsideBailedOutFunction = (diagnostic) => {
|
|
43394
|
+
const compilerLines = compilerDiagnosticLinesByFile.get(diagnostic.filePath);
|
|
43395
|
+
if (!compilerLines || diagnostic.line <= 0) return false;
|
|
43396
|
+
const parsed = readAndParse(diagnostic.filePath);
|
|
43397
|
+
if (!parsed) return false;
|
|
43398
|
+
let containmentRange = findOutermostEnclosingFunctionRange(parsed.sourceFile, diagnostic.line);
|
|
43399
|
+
if (!containmentRange && diagnostic.offset !== void 0 && diagnostic.length !== void 0) containmentRange = {
|
|
43400
|
+
startLine: diagnostic.line,
|
|
43401
|
+
endLine: lineOfUtf8Offset(parsed.buffer, diagnostic.offset + diagnostic.length)
|
|
43402
|
+
};
|
|
43403
|
+
if (!containmentRange) return false;
|
|
43404
|
+
const { startLine, endLine } = containmentRange;
|
|
43405
|
+
return compilerLines.some((compilerLine) => startLine <= compilerLine && compilerLine <= endLine);
|
|
43406
|
+
};
|
|
43407
|
+
return diagnostics.filter((diagnostic) => !isManualMemoizationDiagnostic(diagnostic) || !isInsideBailedOutFunction(diagnostic));
|
|
43408
|
+
};
|
|
43034
43409
|
const FILEPATH_WITH_LOCATION_PATTERN = /\S+\.\w+:\d+:\d+[\s\S]*$/;
|
|
43035
43410
|
const LEADING_SEVERITY_LABEL_PATTERN = /^(?:Error|Warning):\s*/;
|
|
43036
43411
|
const TRAILING_PERIOD_PATTERN = /\.$/;
|
|
@@ -43066,8 +43441,14 @@ const PLUGIN_CATEGORY_MAP = {
|
|
|
43066
43441
|
};
|
|
43067
43442
|
const lookupOwnString = (record, key) => Object.hasOwn(record, key) ? record[key] : void 0;
|
|
43068
43443
|
const getRuleRecommendation = (ruleName, project) => {
|
|
43069
|
-
|
|
43070
|
-
return
|
|
43444
|
+
const rule = reactDoctorPlugin.rules[ruleName];
|
|
43445
|
+
if (!rule) return void 0;
|
|
43446
|
+
if (rule.recommendationFor) {
|
|
43447
|
+
const capabilities = getCapabilities(project);
|
|
43448
|
+
const conditionalRecommendation = rule.recommendationFor((capability) => capabilities.has(capability));
|
|
43449
|
+
if (conditionalRecommendation !== void 0) return conditionalRecommendation;
|
|
43450
|
+
}
|
|
43451
|
+
return rule.recommendation;
|
|
43071
43452
|
};
|
|
43072
43453
|
const getRuleCategory = (ruleName) => reactDoctorPlugin.rules[ruleName]?.category;
|
|
43073
43454
|
const getRuleTitle = (ruleName) => reactDoctorPlugin.rules[ruleName]?.title;
|
|
@@ -43170,7 +43551,7 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
43170
43551
|
minifiedFileCache.set(absolutePath, minified);
|
|
43171
43552
|
return minified;
|
|
43172
43553
|
};
|
|
43173
|
-
return parsed.diagnostics.filter((diagnostic) => diagnostic.code && isLintableSourceFile(diagnostic.filename) && !isMinifiedDiagnosticFile(diagnostic.filename) && !shouldSuppressLocalUseHookDiagnostic(diagnostic, rootDirectory)).map((diagnostic) => {
|
|
43554
|
+
return suppressMemoizationInBailedOutFunctions(parsed.diagnostics.filter((diagnostic) => diagnostic.code && isLintableSourceFile(diagnostic.filename) && !isMinifiedDiagnosticFile(diagnostic.filename) && !shouldSuppressLocalUseHookDiagnostic(diagnostic, rootDirectory) && !shouldSuppressCompilerFindingInWorklet(diagnostic, project, rootDirectory)).map((diagnostic) => {
|
|
43174
43555
|
const { plugin, rule } = parseRuleCode(diagnostic.code);
|
|
43175
43556
|
const primaryLabel = diagnostic.labels[0];
|
|
43176
43557
|
const cleaned = cleanDiagnosticMessage(diagnostic.message, diagnostic.help, plugin, rule, project);
|
|
@@ -43197,7 +43578,7 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
43197
43578
|
...resolveMatchByOccurrence(rule, category) ? { matchByOccurrence: true } : {},
|
|
43198
43579
|
...relatedLocations.length > 0 ? { relatedLocations } : {}
|
|
43199
43580
|
};
|
|
43200
|
-
});
|
|
43581
|
+
}), rootDirectory);
|
|
43201
43582
|
};
|
|
43202
43583
|
const buildOxlintChildEnv = (sourceEnv) => {
|
|
43203
43584
|
const childEnv = {};
|
|
@@ -43708,6 +44089,7 @@ const runOxlint = async (options) => {
|
|
|
43708
44089
|
const serverAuthFunctionNames = Array.isArray(userConfig?.serverAuthFunctionNames) ? userConfig.serverAuthFunctionNames.filter((entry) => typeof entry === "string" && entry.length > 0) : void 0;
|
|
43709
44090
|
const severityControls = buildRuleSeverityControls(userConfig);
|
|
43710
44091
|
validateRuleRegistration();
|
|
44092
|
+
resetManifestCaches();
|
|
43711
44093
|
if (includePaths !== void 0 && includePaths.length === 0) return [];
|
|
43712
44094
|
const pluginPath = resolvePluginPath();
|
|
43713
44095
|
const extendsPaths = (adoptExistingLintConfig && !customRulesOnly ? detectUserLintConfigPaths(rootDirectory) : []).filter(canOxlintExtendConfig);
|
|
@@ -44370,9 +44752,10 @@ const CONTEXT_AXES = [
|
|
|
44370
44752
|
}
|
|
44371
44753
|
];
|
|
44372
44754
|
const SCORE_AXES = [...GATED_AXES, ...CONTEXT_AXES];
|
|
44373
|
-
const
|
|
44374
|
-
|
|
44375
|
-
|
|
44755
|
+
const gatedAxesForSection = (section) => section === "devDependencies" ? GATED_AXES.filter((axis) => axis.key !== "vulnerability") : GATED_AXES;
|
|
44756
|
+
const worstGatedAxis = (score, gatedAxes) => {
|
|
44757
|
+
let worst = gatedAxes[0];
|
|
44758
|
+
for (const axis of gatedAxes) if (score[axis.key] < score[worst.key]) worst = axis;
|
|
44376
44759
|
return worst;
|
|
44377
44760
|
};
|
|
44378
44761
|
const clampScore = (value) => {
|
|
@@ -44396,6 +44779,69 @@ const resolveConcreteVersion = (spec) => {
|
|
|
44396
44779
|
if (range === null || range === "*") return null;
|
|
44397
44780
|
return import_semver.minVersion(trimmed)?.version ?? null;
|
|
44398
44781
|
};
|
|
44782
|
+
const readTextFileOrNull = (filePath) => {
|
|
44783
|
+
try {
|
|
44784
|
+
return NFS.readFileSync(filePath, "utf-8");
|
|
44785
|
+
} catch {
|
|
44786
|
+
return null;
|
|
44787
|
+
}
|
|
44788
|
+
};
|
|
44789
|
+
const buildNpmLockLookup = (lockText) => {
|
|
44790
|
+
let parsed;
|
|
44791
|
+
try {
|
|
44792
|
+
parsed = JSON.parse(lockText);
|
|
44793
|
+
} catch {
|
|
44794
|
+
return null;
|
|
44795
|
+
}
|
|
44796
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
44797
|
+
const lock = parsed;
|
|
44798
|
+
return (name) => {
|
|
44799
|
+
const entry = lock.packages?.[`node_modules/${name}`] ?? lock.dependencies?.[name];
|
|
44800
|
+
return typeof entry?.version === "string" ? entry.version : null;
|
|
44801
|
+
};
|
|
44802
|
+
};
|
|
44803
|
+
const buildYarnLockLookup = (lockText) => {
|
|
44804
|
+
const lines = lockText.split(/\r?\n/);
|
|
44805
|
+
return (name, spec) => {
|
|
44806
|
+
const descriptor = `${name}@${spec}`;
|
|
44807
|
+
const berryDescriptor = `${name}@npm:${spec}`;
|
|
44808
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
44809
|
+
const line = lines[lineIndex];
|
|
44810
|
+
if (line.startsWith(" ") || line.startsWith("#") || !line.includes(":")) continue;
|
|
44811
|
+
const descriptors = line.replace(/:\s*$/, "").split(",").map((part) => part.trim().replace(/^"|"$/g, ""));
|
|
44812
|
+
if (!descriptors.includes(descriptor) && !descriptors.includes(berryDescriptor)) continue;
|
|
44813
|
+
for (let bodyIndex = lineIndex + 1; bodyIndex < lines.length && (lines[bodyIndex].startsWith(" ") || lines[bodyIndex] === ""); bodyIndex += 1) {
|
|
44814
|
+
const versionMatch = /^\s+version:?\s+"?([^"\s]+)"?/.exec(lines[bodyIndex]);
|
|
44815
|
+
if (versionMatch) return versionMatch[1];
|
|
44816
|
+
}
|
|
44817
|
+
return null;
|
|
44818
|
+
}
|
|
44819
|
+
return null;
|
|
44820
|
+
};
|
|
44821
|
+
};
|
|
44822
|
+
const buildPnpmLockLookup = (lockText) => {
|
|
44823
|
+
return (name) => {
|
|
44824
|
+
const entryPattern = new RegExp(`^ {4,8}['"]?${name.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&")}['"]?:\\s*$`);
|
|
44825
|
+
const lines = lockText.split(/\r?\n/);
|
|
44826
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
44827
|
+
if (!entryPattern.test(lines[lineIndex])) continue;
|
|
44828
|
+
for (let bodyIndex = lineIndex + 1; bodyIndex < lines.length && /^ {5,}\S/.test(lines[bodyIndex]); bodyIndex += 1) {
|
|
44829
|
+
const versionMatch = /^\s+version:\s+['"]?([^'"\s(]+)/.exec(lines[bodyIndex]);
|
|
44830
|
+
if (versionMatch) return versionMatch[1];
|
|
44831
|
+
}
|
|
44832
|
+
}
|
|
44833
|
+
return null;
|
|
44834
|
+
};
|
|
44835
|
+
};
|
|
44836
|
+
const buildLockedVersionLookup = (rootDirectory) => {
|
|
44837
|
+
const npmLockText = readTextFileOrNull(Path.join(rootDirectory, "package-lock.json"));
|
|
44838
|
+
if (npmLockText !== null) return buildNpmLockLookup(npmLockText);
|
|
44839
|
+
const yarnLockText = readTextFileOrNull(Path.join(rootDirectory, "yarn.lock"));
|
|
44840
|
+
if (yarnLockText !== null) return buildYarnLockLookup(yarnLockText);
|
|
44841
|
+
const pnpmLockText = readTextFileOrNull(Path.join(rootDirectory, "pnpm-lock.yaml"));
|
|
44842
|
+
if (pnpmLockText !== null) return buildPnpmLockLookup(pnpmLockText);
|
|
44843
|
+
return null;
|
|
44844
|
+
};
|
|
44399
44845
|
const locateDependencyKey = (packageJsonText, section, name) => {
|
|
44400
44846
|
const needle = `"${name}"`;
|
|
44401
44847
|
const sectionHeader = new RegExp(`"${section}"\\s*:\\s*\\{`);
|
|
@@ -44422,7 +44868,7 @@ const locateDependencyKey = (packageJsonText, section, name) => {
|
|
|
44422
44868
|
}
|
|
44423
44869
|
return null;
|
|
44424
44870
|
};
|
|
44425
|
-
const collectDependenciesToScore = (packageJson, packageJsonText, includeDevDependencies) => {
|
|
44871
|
+
const collectDependenciesToScore = (packageJson, packageJsonText, includeDevDependencies, lockedVersionLookup) => {
|
|
44426
44872
|
const sectionByName = /* @__PURE__ */ new Map();
|
|
44427
44873
|
for (const name of Object.keys(packageJson.dependencies ?? {})) sectionByName.set(name, "dependencies");
|
|
44428
44874
|
if (includeDevDependencies) {
|
|
@@ -44432,13 +44878,18 @@ const collectDependenciesToScore = (packageJson, packageJsonText, includeDevDepe
|
|
|
44432
44878
|
for (const [name, section] of sectionByName) {
|
|
44433
44879
|
if (SUPPLY_CHAIN_IGNORED_PACKAGES.has(name)) continue;
|
|
44434
44880
|
const spec = (packageJson[section] ?? {})[name] ?? "";
|
|
44435
|
-
const
|
|
44436
|
-
if (
|
|
44881
|
+
const floorVersion = resolveConcreteVersion(spec);
|
|
44882
|
+
if (floorVersion === null) continue;
|
|
44883
|
+
const lockedVersion = lockedVersionLookup?.(name, spec) ?? null;
|
|
44884
|
+
const lockSatisfiesSpec = lockedVersion !== null && import_semver.valid(lockedVersion) !== null && import_semver.satisfies(lockedVersion, spec);
|
|
44885
|
+
const isExactPin = import_semver.valid(spec.trim()) !== null;
|
|
44437
44886
|
const location = locateDependencyKey(packageJsonText, section, name);
|
|
44438
44887
|
dependencies.push({
|
|
44439
44888
|
name,
|
|
44440
|
-
version,
|
|
44889
|
+
version: lockSatisfiesSpec ? lockedVersion : floorVersion,
|
|
44441
44890
|
spec,
|
|
44891
|
+
section,
|
|
44892
|
+
resolution: isExactPin ? "pin" : lockSatisfiesSpec ? "locked" : "floor",
|
|
44442
44893
|
line: location?.line ?? 0,
|
|
44443
44894
|
column: location?.column ?? 0
|
|
44444
44895
|
});
|
|
@@ -44589,7 +45040,11 @@ const formatAlertReason = (topAlerts, totalCount) => {
|
|
|
44589
45040
|
}
|
|
44590
45041
|
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)}.`;
|
|
44591
45042
|
};
|
|
44592
|
-
const formatDependencyIdentity = (dependency) =>
|
|
45043
|
+
const formatDependencyIdentity = (dependency) => {
|
|
45044
|
+
if (dependency.resolution === "pin") return `${dependency.name}@${dependency.version}`;
|
|
45045
|
+
if (dependency.resolution === "locked") return `${dependency.name}@${dependency.version} (lockfile resolution of "${dependency.spec}")`;
|
|
45046
|
+
return `${dependency.name}@${dependency.version} (lowest version "${dependency.spec}" allows)`;
|
|
45047
|
+
};
|
|
44593
45048
|
const buildSupplyChainHelp = (dependency, failingAxis, topAlerts, packagePageUrl, options) => {
|
|
44594
45049
|
const hasCriticalAlert = topAlerts.some((alert) => alert.severity.toLowerCase() === "critical");
|
|
44595
45050
|
const entry = `\`"${dependency.name}": "${dependency.spec}"\``;
|
|
@@ -44632,7 +45087,7 @@ const buildLowScoreDiagnostic = (dependency, artifact, failingAxis, options) =>
|
|
|
44632
45087
|
const checkSupplyChain = (input) => gen(function* () {
|
|
44633
45088
|
const options = resolveOptions(input.userConfig);
|
|
44634
45089
|
const packageJsonPath = Path.join(input.rootDirectory, "package.json");
|
|
44635
|
-
const dependencies = collectDependenciesToScore(readPackageJson$1(packageJsonPath), readPackageJsonText(packageJsonPath), options.includeDevDependencies);
|
|
45090
|
+
const dependencies = collectDependenciesToScore(readPackageJson$1(packageJsonPath), readPackageJsonText(packageJsonPath), options.includeDevDependencies, buildLockedVersionLookup(input.rootDirectory));
|
|
44636
45091
|
if (dependencies.length === 0) return [];
|
|
44637
45092
|
const cacheDirectory = isSupplyChainCacheDisabled() ? null : resolveReactDoctorCacheDir(input.rootDirectory);
|
|
44638
45093
|
if (cacheDirectory !== null) pruneExpiredSocketCache(cacheDirectory);
|
|
@@ -44641,7 +45096,7 @@ const checkSupplyChain = (input) => gen(function* () {
|
|
|
44641
45096
|
for (let index = 0; index < dependencies.length; index += 1) {
|
|
44642
45097
|
const artifact = artifacts[index];
|
|
44643
45098
|
if (!artifact) continue;
|
|
44644
|
-
const worstAxis = worstGatedAxis(artifact.score);
|
|
45099
|
+
const worstAxis = worstGatedAxis(artifact.score, gatedAxesForSection(dependencies[index].section));
|
|
44645
45100
|
if (toHundred(artifact.score[worstAxis.key]) >= options.minScore) continue;
|
|
44646
45101
|
diagnostics.push(buildLowScoreDiagnostic(dependencies[index], artifact, worstAxis, options));
|
|
44647
45102
|
}
|
|
@@ -45342,6 +45797,13 @@ const summarizeDiagnostics = (diagnostics, worstScore = null, worstScoreLabel =
|
|
|
45342
45797
|
scoreLabel: worstScoreLabel
|
|
45343
45798
|
};
|
|
45344
45799
|
};
|
|
45800
|
+
/**
|
|
45801
|
+
* Whether project discovery resolved a React-compatible runtime (React or
|
|
45802
|
+
* Preact, which ships the same hooks + JSX model). This is the same
|
|
45803
|
+
* predicate that grants the `react` capability, so `false` means every
|
|
45804
|
+
* React-runtime rule family was gated off for the project.
|
|
45805
|
+
*/
|
|
45806
|
+
const hasReactRuntime = (project) => project.reactVersion !== null || project.preactVersion !== null;
|
|
45345
45807
|
const toJsonDiff = (diff) => {
|
|
45346
45808
|
if (!diff) return null;
|
|
45347
45809
|
return {
|
|
@@ -45924,7 +46386,7 @@ const makeNoopConsole = () => ({
|
|
|
45924
46386
|
});
|
|
45925
46387
|
//#endregion
|
|
45926
46388
|
//#region src/cli/utils/version.ts
|
|
45927
|
-
const VERSION = "0.7.
|
|
46389
|
+
const VERSION = "0.7.2-dev.0eb5293";
|
|
45928
46390
|
//#endregion
|
|
45929
46391
|
//#region src/cli/utils/json-mode.ts
|
|
45930
46392
|
let context = null;
|
|
@@ -46293,13 +46755,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
|
|
|
46293
46755
|
* uploads source-map artifacts under, so stack frames symbolicate. Honors the
|
|
46294
46756
|
* standard `SENTRY_RELEASE` override.
|
|
46295
46757
|
*/
|
|
46296
|
-
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.7.
|
|
46758
|
+
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.7.2-dev.0eb5293`;
|
|
46297
46759
|
/**
|
|
46298
46760
|
* Deployment environment shown in Sentry's environment filter. Defaults to
|
|
46299
46761
|
* `production` for tagged releases and `development` for dev/unbuilt versions,
|
|
46300
46762
|
* overridable via the standard `SENTRY_ENVIRONMENT` env var.
|
|
46301
46763
|
*/
|
|
46302
|
-
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.7.
|
|
46764
|
+
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.7.2-dev.0eb5293") ? "development" : "production");
|
|
46303
46765
|
/**
|
|
46304
46766
|
* Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
|
|
46305
46767
|
* (set to `0` to disable tracing) and falls back to
|
|
@@ -58143,7 +58605,7 @@ const buildRuntimeLayers = (input) => {
|
|
|
58143
58605
|
const linterLayer = input.shouldSkipLint ? Linter.layerOf([]) : Linter.layerOxlint;
|
|
58144
58606
|
const deadCodeLayer = input.shouldRunDeadCode ? DeadCode.layerNode : DeadCode.layerOf([]);
|
|
58145
58607
|
const scoreLayer = input.shouldComputeScore ? Score.layerHttp : Score.layerOf(null);
|
|
58146
|
-
const supplyChainLayer = input.
|
|
58608
|
+
const supplyChainLayer = input.shouldRunSupplyChain ? SupplyChain.layerNode : SupplyChain.layerOf([]);
|
|
58147
58609
|
const projectLayer = input.projectInfoOverride === void 0 ? Project.layerNode : Project.layerOf(input.projectInfoOverride);
|
|
58148
58610
|
const progressLayer = input.shouldShowProgressSpinners ? Progress.layerOra(buildSpinnerProgressHandle) : Progress.layerNoop;
|
|
58149
58611
|
const baseLayers = mergeAll$1(projectLayer, input.hasConfigOverride ? Config.layerOf({
|
|
@@ -58499,21 +58961,22 @@ const resolveDotenvFingerprint = (projectDirectory) => {
|
|
|
58499
58961
|
return [];
|
|
58500
58962
|
}
|
|
58501
58963
|
};
|
|
58502
|
-
const
|
|
58964
|
+
const isGitIdentityTrustworthy = (projectDirectory) => {
|
|
58503
58965
|
const output = runGit(projectDirectory, ["ls-files", "-v"]);
|
|
58504
|
-
if (output === null) return
|
|
58505
|
-
|
|
58966
|
+
if (output === null) return false;
|
|
58967
|
+
const entryLines = output.split("\n").filter((line) => line.length > 0);
|
|
58968
|
+
return entryLines.length > 0 && entryLines.every((line) => line[0] === "H");
|
|
58506
58969
|
};
|
|
58507
58970
|
const resolveCacheFilePath = (projectDirectory) => path.join(resolveReactDoctorCacheDir(projectDirectory), SCAN_RESULT_CACHE_FILENAME);
|
|
58508
58971
|
const readPersistedCache = (cacheFilePath) => {
|
|
58509
58972
|
try {
|
|
58510
58973
|
const parsed = JSON.parse(fs.readFileSync(cacheFilePath, "utf8"));
|
|
58511
|
-
if (!isRecord(parsed) || parsed.version !==
|
|
58512
|
-
version:
|
|
58974
|
+
if (!isRecord(parsed) || parsed.version !== 4) return {
|
|
58975
|
+
version: 4,
|
|
58513
58976
|
entries: []
|
|
58514
58977
|
};
|
|
58515
58978
|
if (!Array.isArray(parsed.entries)) return {
|
|
58516
|
-
version:
|
|
58979
|
+
version: 4,
|
|
58517
58980
|
entries: []
|
|
58518
58981
|
};
|
|
58519
58982
|
const entries = [];
|
|
@@ -58523,12 +58986,12 @@ const readPersistedCache = (cacheFilePath) => {
|
|
|
58523
58986
|
entries.push(entry);
|
|
58524
58987
|
}
|
|
58525
58988
|
return {
|
|
58526
|
-
version:
|
|
58989
|
+
version: 4,
|
|
58527
58990
|
entries
|
|
58528
58991
|
};
|
|
58529
58992
|
} catch {
|
|
58530
58993
|
return {
|
|
58531
|
-
version:
|
|
58994
|
+
version: 4,
|
|
58532
58995
|
entries: []
|
|
58533
58996
|
};
|
|
58534
58997
|
}
|
|
@@ -58560,6 +59023,7 @@ const resolveProjectIdentity = (projectDirectory) => {
|
|
|
58560
59023
|
return path.resolve(projectDirectory);
|
|
58561
59024
|
}
|
|
58562
59025
|
};
|
|
59026
|
+
const resolveManifestContentHash = (projectDirectory) => hashFileContents(getPackageJsonPath(projectDirectory));
|
|
58563
59027
|
const fileFingerprint = (filePath) => {
|
|
58564
59028
|
try {
|
|
58565
59029
|
const stat = fs.statSync(filePath);
|
|
@@ -58582,14 +59046,14 @@ const resolveToolchainFingerprint = (nodeBinaryPath) => {
|
|
|
58582
59046
|
};
|
|
58583
59047
|
const buildScanResultCacheKey = (input) => {
|
|
58584
59048
|
if (isCacheGloballyDisabled()) return null;
|
|
58585
|
-
if (
|
|
59049
|
+
if (!isGitIdentityTrustworthy(input.projectDirectory)) return null;
|
|
58586
59050
|
const worktreeFingerprint = buildWorktreeFingerprint(input.projectDirectory);
|
|
58587
59051
|
if (worktreeFingerprint === null) return null;
|
|
58588
59052
|
const headSha = readHeadSha(input.projectDirectory);
|
|
58589
59053
|
if (headSha === null) return null;
|
|
58590
59054
|
if (stringifyStableJson(input.userConfig) === null) return null;
|
|
58591
59055
|
const cacheKeyJson = stringifyStableJson({
|
|
58592
|
-
schemaVersion:
|
|
59056
|
+
schemaVersion: 4,
|
|
58593
59057
|
projectIdentity: resolveProjectIdentity(input.projectDirectory),
|
|
58594
59058
|
headSha,
|
|
58595
59059
|
worktreeFingerprint,
|
|
@@ -58604,6 +59068,7 @@ const buildScanResultCacheKey = (input) => {
|
|
|
58604
59068
|
engineOptions: {
|
|
58605
59069
|
lint: input.options.lint,
|
|
58606
59070
|
deadCode: input.options.deadCode,
|
|
59071
|
+
supplyChain: input.options.supplyChain,
|
|
58607
59072
|
includePaths: [...input.options.includePaths].sort(),
|
|
58608
59073
|
customRulesOnly: input.options.customRulesOnly,
|
|
58609
59074
|
respectInlineDisables: input.options.respectInlineDisables,
|
|
@@ -58629,17 +59094,24 @@ const createScanResultCache = (projectDirectory) => {
|
|
|
58629
59094
|
for (const entry of persistedCache.entries) entries.set(entry.key, entry);
|
|
58630
59095
|
const persist = () => {
|
|
58631
59096
|
writePersistedCache(cacheFilePath, {
|
|
58632
|
-
version:
|
|
59097
|
+
version: 4,
|
|
58633
59098
|
entries: [...entries.values()].sort((firstEntry, secondEntry) => secondEntry.createdAtMs - firstEntry.createdAtMs).slice(0, 20)
|
|
58634
59099
|
});
|
|
58635
59100
|
};
|
|
58636
59101
|
return {
|
|
58637
|
-
lookup: (key) =>
|
|
59102
|
+
lookup: (key) => {
|
|
59103
|
+
const payload = entries.get(key)?.payload;
|
|
59104
|
+
if (payload === void 0) return null;
|
|
59105
|
+
return resolveProjectIdentity(payload.directory) === resolveProjectIdentity(projectDirectory) && (payload.manifestContentHash ?? null) === resolveManifestContentHash(projectDirectory) ? payload : null;
|
|
59106
|
+
},
|
|
58638
59107
|
store: (key, payload) => {
|
|
58639
59108
|
entries.set(key, {
|
|
58640
59109
|
key,
|
|
58641
59110
|
createdAtMs: Date.now(),
|
|
58642
|
-
payload
|
|
59111
|
+
payload: {
|
|
59112
|
+
...payload,
|
|
59113
|
+
manifestContentHash: resolveManifestContentHash(projectDirectory)
|
|
59114
|
+
}
|
|
58643
59115
|
});
|
|
58644
59116
|
persist();
|
|
58645
59117
|
}
|
|
@@ -58806,13 +59278,15 @@ const buildOutcomeAttributes = (input) => {
|
|
|
58806
59278
|
const countBySource = {
|
|
58807
59279
|
config: 0,
|
|
58808
59280
|
override: 0,
|
|
58809
|
-
inline: 0
|
|
59281
|
+
inline: 0,
|
|
59282
|
+
"foreign-inline": 0
|
|
58810
59283
|
};
|
|
58811
59284
|
for (const suppression of input.suppressedRuleCounts) countBySource[suppression.source] += suppression.count;
|
|
58812
|
-
suppressionRollup.suppressed = countBySource.config + countBySource.override + countBySource.inline;
|
|
59285
|
+
suppressionRollup.suppressed = countBySource.config + countBySource.override + countBySource.inline + countBySource["foreign-inline"];
|
|
58813
59286
|
suppressionRollup.suppressedConfig = countBySource.config;
|
|
58814
59287
|
suppressionRollup.suppressedOverride = countBySource.override;
|
|
58815
59288
|
suppressionRollup.suppressedInline = countBySource.inline;
|
|
59289
|
+
suppressionRollup.suppressedForeignInline = countBySource["foreign-inline"];
|
|
58816
59290
|
}
|
|
58817
59291
|
const attributes = {
|
|
58818
59292
|
...withNamespace("outcome", {
|
|
@@ -58904,6 +59378,7 @@ const buildScanAttributes = (input) => {
|
|
|
58904
59378
|
maxDurationMs: input.maxDurationMs,
|
|
58905
59379
|
lint: input.lint,
|
|
58906
59380
|
deadCode: input.deadCode,
|
|
59381
|
+
supplyChain: input.supplyChain,
|
|
58907
59382
|
scoreOnly: input.scoreOnly,
|
|
58908
59383
|
noScore: input.noScore,
|
|
58909
59384
|
respectInlineDisables: input.respectInlineDisables,
|
|
@@ -60178,6 +60653,7 @@ const buildIgnoredTags = (userConfig) => {
|
|
|
60178
60653
|
const mergeInspectOptions = (inputOptions, userConfig) => ({
|
|
60179
60654
|
lint: inputOptions.lint ?? userConfig?.lint ?? true,
|
|
60180
60655
|
deadCode: inputOptions.deadCode ?? userConfig?.deadCode ?? true,
|
|
60656
|
+
supplyChain: inputOptions.supplyChain ?? userConfig?.supplyChain?.enabled ?? true,
|
|
60181
60657
|
verbose: inputOptions.verbose ?? userConfig?.verbose ?? false,
|
|
60182
60658
|
outputDirectory: inputOptions.outputDirectory || null,
|
|
60183
60659
|
scoreOnly: inputOptions.scoreOnly ?? false,
|
|
@@ -60217,6 +60693,7 @@ const buildRunEventConfig = (options, userConfig, hasCustomConfig, resolvedWorke
|
|
|
60217
60693
|
maxDurationMs: options.maxDurationMs,
|
|
60218
60694
|
lint: options.lint,
|
|
60219
60695
|
deadCode: options.deadCode,
|
|
60696
|
+
supplyChain: options.supplyChain,
|
|
60220
60697
|
scoreOnly: options.scoreOnly,
|
|
60221
60698
|
noScore: options.noScore,
|
|
60222
60699
|
respectInlineDisables: options.respectInlineDisables,
|
|
@@ -60300,6 +60777,7 @@ const runBaselineComparison = async (params) => {
|
|
|
60300
60777
|
projectInfoOverride: params.headProjectInfo,
|
|
60301
60778
|
shouldSkipLint: !params.options.lint || !params.resolvedNodeBinaryPath,
|
|
60302
60779
|
shouldRunDeadCode: false,
|
|
60780
|
+
shouldRunSupplyChain: params.options.supplyChain,
|
|
60303
60781
|
shouldComputeScore: false,
|
|
60304
60782
|
shouldShowProgressSpinners: false,
|
|
60305
60783
|
oxlintConcurrency: params.options.concurrency
|
|
@@ -60388,6 +60866,7 @@ const runInspectWithRuntime = async (directory, options, userConfig, hasConfigOv
|
|
|
60388
60866
|
configSourceDirectory,
|
|
60389
60867
|
shouldSkipLint: !options.lint || lintBindingMissing,
|
|
60390
60868
|
shouldRunDeadCode: options.deadCode,
|
|
60869
|
+
shouldRunSupplyChain: options.supplyChain,
|
|
60391
60870
|
shouldComputeScore: !options.noScore,
|
|
60392
60871
|
shouldShowProgressSpinners,
|
|
60393
60872
|
oxlintConcurrency: options.concurrency
|
|
@@ -62673,8 +63152,8 @@ const resolveParallelFlag = (parallel) => parallel === false ? 1 : void 0;
|
|
|
62673
63152
|
* accepts. Flag-derived fields only (`scoreOnly`, `noScore`, `silent`,
|
|
62674
63153
|
* `isCi`): every `userConfig` fallback — including `noScore` — lives in
|
|
62675
63154
|
* `inspect()`'s merge layer, which sees each project's module-merged
|
|
62676
|
-
* config. The plain boolean knobs (`lint`, `deadCode`, `
|
|
62677
|
-
* through unchanged for the same reason. This resolver reads
|
|
63155
|
+
* config. The plain boolean knobs (`lint`, `deadCode`, `supplyChain`,
|
|
63156
|
+
* `verbose`) pass through unchanged for the same reason. This resolver reads
|
|
62678
63157
|
* `userConfig` only to decide the `--blocking warning` gate.
|
|
62679
63158
|
*/
|
|
62680
63159
|
const resolveCliInspectOptions = (flags, userConfig) => {
|
|
@@ -62682,6 +63161,7 @@ const resolveCliInspectOptions = (flags, userConfig) => {
|
|
|
62682
63161
|
return {
|
|
62683
63162
|
lint: flags.lint,
|
|
62684
63163
|
deadCode: flags.deadCode,
|
|
63164
|
+
supplyChain: flags.supplyChain,
|
|
62685
63165
|
verbose: flags.verbose,
|
|
62686
63166
|
outputDirectory: flags.outputDir,
|
|
62687
63167
|
respectInlineDisables: flags.respectInlineDisables === false ? false : void 0,
|
|
@@ -63232,7 +63712,7 @@ const inspectAction = async (directory, flags) => {
|
|
|
63232
63712
|
const scanDirectory = projectScanTarget.resolvedDirectory;
|
|
63233
63713
|
const projectConfig = projectDirectory === resolvedDirectory ? userConfig : mergeReactDoctorConfigs(userConfig, projectScanTarget.userConfig ?? void 0);
|
|
63234
63714
|
const projectConfigSourceDirectory = projectScanTarget.userConfig?.plugins === void 0 ? scanTarget.configSourceDirectory : projectScanTarget.configSourceDirectory;
|
|
63235
|
-
const supplyChainEnabled = projectConfig?.supplyChain?.enabled !== false;
|
|
63715
|
+
const supplyChainEnabled = flags.supplyChain ?? projectConfig?.supplyChain?.enabled !== false;
|
|
63236
63716
|
let includePaths;
|
|
63237
63717
|
let supplyChainManifestChanged = false;
|
|
63238
63718
|
if (isDiffMode) {
|
|
@@ -64152,10 +64632,12 @@ const ROOT_FLAG_SPEC = {
|
|
|
64152
64632
|
"--no-parallel",
|
|
64153
64633
|
"--no-respect-inline-disables",
|
|
64154
64634
|
"--no-score",
|
|
64635
|
+
"--no-supply-chain",
|
|
64155
64636
|
"--no-telemetry",
|
|
64156
64637
|
"--no-warnings",
|
|
64157
64638
|
"--score",
|
|
64158
64639
|
"--staged",
|
|
64640
|
+
"--supply-chain",
|
|
64159
64641
|
"--verbose",
|
|
64160
64642
|
"--version",
|
|
64161
64643
|
"--warnings",
|
|
@@ -64404,7 +64886,7 @@ ${highlighter.dim("Learn more:")}
|
|
|
64404
64886
|
${highlighter.info(CI_URL)}
|
|
64405
64887
|
`;
|
|
64406
64888
|
const collectCategoryOption = (value, previousValues) => [...previousValues ?? [], value];
|
|
64407
|
-
const program = new Command().name("react-doctor").description("Diagnose React codebase health").version(VERSION, "-v, --version", "display the version number").argument("[directory]", "project directory to scan", ".").option("--lint", "enable linting").option("--no-lint", "skip linting").option("--dead-code", "enable dead-code analysis (default)").option("--no-dead-code", "skip dead-code analysis (unused files / exports / dependencies, circular imports)").option("--verbose", "show every rule and per-file details (default shows top 3 rules)").option("--debug", "force a Sentry trace and print its id at the end (paste it into a bug report)").option("--output-dir <dir>", "directory for the full diagnostics dump (default: a temp folder)").option("--score", "output only the score").option("--json", "output a single structured JSON report (suppresses other output)").option("--json-compact", "with --json, emit compact JSON (no indentation)").option("--json-out <path>", "with --json, write the report to a file instead of stdout").option("-y, --yes", "skip prompts, scan all workspace projects").option("--no-parallel", "lint serially with one worker (default: parallel across CPU cores; set the worker count with REACT_DOCTOR_PARALLEL)").option("--project <name>", "select projects: workspace names or directory paths (comma-separated for multiple); overrides the `projects` config field").option("--scope <value>", "how much to scan/report: full (default), files, changed (only new issues vs base), or lines (only changed lines)").option("--base <ref>", "base git ref for files/changed/lines scope (auto-detected when omitted)").addOption(new Option("--diff [base]", "[deprecated] alias for --scope changed (pass `false` to force a full scan)").hideHelp()).addOption(new Option("--changed-files-from <file>", "scan source files listed in a newline-delimited changed-files file").hideHelp()).option("--no-score", "skip the score API, the share URL, and crash reporting").addOption(new Option("--category <category>", "only show diagnostics in a category (repeatable; e.g. Security)").argParser(collectCategoryOption)).option("--no-telemetry", "alias for --no-score (skip the score API, share URL, and crash reporting)").option("--staged", "scan only staged (git index) files for pre-commit hooks").option("--max-duration <seconds>", "scan time budget for the whole run, shared across workspace projects: past it, remaining lint batches and dead-code are skipped and partial results are reported (skipped files are listed in the JSON report)").option("--blocking <level>", "severity that fails CI: error (default), warning, or none (advisory)").addOption(new Option("--fail-on <level>", "[deprecated] alias for --blocking <level>").hideHelp()).option("--no-respect-inline-disables", "audit mode: neutralize inline lint suppressions before scanning").option("--warnings", "show warning-severity diagnostics (default)").option("--no-warnings", "hide warning-severity diagnostics (errors only)").option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").addHelpText("after", renderRootHelpEpilog);
|
|
64889
|
+
const program = new Command().name("react-doctor").description("Diagnose React codebase health").version(VERSION, "-v, --version", "display the version number").argument("[directory]", "project directory to scan", ".").option("--lint", "enable linting").option("--no-lint", "skip linting").option("--dead-code", "enable dead-code analysis (default)").option("--no-dead-code", "skip dead-code analysis (unused files / exports / dependencies, circular imports)").option("--supply-chain", "enable the dependency supply-chain scan (default)").option("--no-supply-chain", "skip the dependency supply-chain scan (Socket.dev dependency health checks)").option("--verbose", "show every rule and per-file details (default shows top 3 rules)").option("--debug", "force a Sentry trace and print its id at the end (paste it into a bug report)").option("--output-dir <dir>", "directory for the full diagnostics dump (default: a temp folder)").option("--score", "output only the score").option("--json", "output a single structured JSON report (suppresses other output)").option("--json-compact", "with --json, emit compact JSON (no indentation)").option("--json-out <path>", "with --json, write the report to a file instead of stdout").option("-y, --yes", "skip prompts, scan all workspace projects").option("--no-parallel", "lint serially with one worker (default: parallel across CPU cores; set the worker count with REACT_DOCTOR_PARALLEL)").option("--project <name>", "select projects: workspace names or directory paths (comma-separated for multiple); overrides the `projects` config field").option("--scope <value>", "how much to scan/report: full (default), files, changed (only new issues vs base), or lines (only changed lines)").option("--base <ref>", "base git ref for files/changed/lines scope (auto-detected when omitted)").addOption(new Option("--diff [base]", "[deprecated] alias for --scope changed (pass `false` to force a full scan)").hideHelp()).addOption(new Option("--changed-files-from <file>", "scan source files listed in a newline-delimited changed-files file").hideHelp()).option("--no-score", "skip the score API, the share URL, and crash reporting").addOption(new Option("--category <category>", "only show diagnostics in a category (repeatable; e.g. Security)").argParser(collectCategoryOption)).option("--no-telemetry", "alias for --no-score (skip the score API, share URL, and crash reporting)").option("--staged", "scan only staged (git index) files for pre-commit hooks").option("--max-duration <seconds>", "scan time budget for the whole run, shared across workspace projects: past it, remaining lint batches and dead-code are skipped and partial results are reported (skipped files are listed in the JSON report)").option("--blocking <level>", "severity that fails CI: error (default), warning, or none (advisory)").addOption(new Option("--fail-on <level>", "[deprecated] alias for --blocking <level>").hideHelp()).option("--no-respect-inline-disables", "audit mode: neutralize inline lint suppressions before scanning").option("--warnings", "show warning-severity diagnostics (default)").option("--no-warnings", "hide warning-severity diagnostics (errors only)").option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").addHelpText("after", renderRootHelpEpilog);
|
|
64408
64890
|
program.action(inspectAction);
|
|
64409
64891
|
program.command("why <location>").description("Explain why a rule fired (or why a suppression didn't apply) at a file:line").option("--project <name>", "select projects: workspace names or directory paths (comma-separated for multiple)").option("-c, --cwd <cwd>", "working directory", process.cwd()).option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").action((location, options) => whyAction(location, options));
|
|
64410
64892
|
program.command("install").alias("setup").description("Install the react-doctor skill into your coding agents and optional git hook").option("-y, --yes", "skip prompts, install for all detected agents").option("--dry-run", "show what would be installed without writing files").option("--agent-hooks", "install native non-blocking agent hooks for Claude Code and Cursor").option("-c, --cwd <cwd>", "working directory", process.cwd()).option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").addHelpText("after", renderInstallHelpEpilog).action(installAction);
|
|
@@ -64454,4 +64936,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
|
|
|
64454
64936
|
export {};
|
|
64455
64937
|
|
|
64456
64938
|
//# sourceMappingURL=cli.js.map
|
|
64457
|
-
//# debugId=
|
|
64939
|
+
//# debugId=91ba16f6-8b1a-5cd9-a5d2-fc0b6fce2508
|