react-doctor 0.7.2 → 0.7.3-dev.82187a3

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