react-doctor 0.7.1 → 0.7.2-dev.0eb5293

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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]="7cb857ba-6f0a-57d9-91f3-3c286472121d")}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]="63c65851-4fa5-5cd1-9337-0f2feefe37ad")}catch(e){}}();
3
3
  import { r as __toESM$1, t as __commonJSMin$1 } from "./chunk-N93fKeF6.js";
4
4
  import { createRequire } from "node:module";
5
5
  import * as NFS from "node:fs";
@@ -9,7 +9,7 @@ import path from "node:path";
9
9
  import * as NodeChildProcess from "node:child_process";
10
10
  import { execFileSync, spawn, spawnSync } from "node:child_process";
11
11
  import * as ts from "typescript";
12
- import reactDoctorPlugin, { ALL_REACT_DOCTOR_RULE_KEYS, CROSS_FILE_DEPENDENCY_COLLECTORS, CROSS_FILE_RULE_IDS, FRAMEWORK_SPECIFIC_RULE_KEYS, MOTION_LIBRARY_PACKAGES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, classifySecurityScanFile, collectCrossFileDependencyProbes, shouldReadSecurityScanContent } from "oxlint-plugin-react-doctor";
12
+ import reactDoctorPlugin, { ALL_REACT_DOCTOR_RULE_KEYS, CROSS_FILE_DEPENDENCY_COLLECTORS, CROSS_FILE_RULE_IDS, FRAMEWORK_SPECIFIC_RULE_KEYS, MOTION_LIBRARY_PACKAGES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, classifySecurityScanFile, collectCrossFileDependencyProbes, resetManifestCaches, shouldReadSecurityScanContent } from "oxlint-plugin-react-doctor";
13
13
  import * as OS from "node:os";
14
14
  import os from "node:os";
15
15
  import { parseJSON5 } from "confbox";
@@ -32791,6 +32791,25 @@ var AmbiguousProjectError = class extends Error {
32791
32791
  }
32792
32792
  };
32793
32793
  const isProjectDiscoveryError = (value) => value instanceof ProjectNotFoundError || value instanceof NoReactDependencyError || value instanceof PackageJsonNotFoundError || value instanceof NotADirectoryError || value instanceof AmbiguousProjectError;
32794
+ const isErrnoException = (error) => error instanceof Error && "code" in error;
32795
+ const IGNORABLE_READDIR_ERROR_CODES = new Set([
32796
+ "EACCES",
32797
+ "EPERM",
32798
+ "ENOENT",
32799
+ "ENOTDIR",
32800
+ "EINVAL",
32801
+ "ELOOP",
32802
+ "ENAMETOOLONG"
32803
+ ]);
32804
+ const isIgnorableReaddirError = (error) => isErrnoException(error) && typeof error.code === "string" && IGNORABLE_READDIR_ERROR_CODES.has(error.code);
32805
+ const readDirectoryEntries = (directoryPath) => {
32806
+ try {
32807
+ return NFS.readdirSync(directoryPath, { withFileTypes: true });
32808
+ } catch (error) {
32809
+ if (isIgnorableReaddirError(error)) return [];
32810
+ throw error;
32811
+ }
32812
+ };
32794
32813
  const isFile = (filePath) => {
32795
32814
  try {
32796
32815
  return NFS.statSync(filePath).isFile();
@@ -32798,10 +32817,24 @@ const isFile = (filePath) => {
32798
32817
  return false;
32799
32818
  }
32800
32819
  };
32820
+ const isDirectory = (directoryPath) => {
32821
+ try {
32822
+ return NFS.statSync(directoryPath).isDirectory();
32823
+ } catch {
32824
+ return false;
32825
+ }
32826
+ };
32827
+ const isPlainObject = (value) => {
32828
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
32829
+ const prototype = Object.getPrototypeOf(value);
32830
+ return prototype === null || prototype === Object.prototype;
32831
+ };
32801
32832
  const SOURCE_FILE_PATTERN = /\.(tsx?|jsx?|mts|mjs)$/;
32802
32833
  const GENERATED_BUNDLE_FILE_PATTERN = /\.(iife|umd|global|min)\.m?js$/i;
32834
+ const GENERATED_SOURCE_DIRECTORY_PATTERN = /(?:^|\/)__generated__\//;
32803
32835
  const MINIFIED_SNIFF_BYTES = 65536;
32804
32836
  const GIT_LS_FILES_MAX_BUFFER_BYTES = 50 * 1024 * 1024;
32837
+ const SCANNED_DOT_DIRECTORIES = new Set([".dumi", ".storybook"]);
32805
32838
  const IGNORED_DIRECTORIES = new Set([
32806
32839
  ".angular",
32807
32840
  ".astro",
@@ -32943,7 +32976,7 @@ const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
32943
32976
  const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
32944
32977
  const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
32945
32978
  const SIDECAR_LINT_CACHE_FILENAME = "sidecar-lint-cache.json";
32946
- const CORE_PACKAGE_VERSION = "0.7.1";
32979
+ const CORE_PACKAGE_VERSION = "0.7.2";
32947
32980
  const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
32948
32981
  const DEAD_CODE_SUMMARY_CACHE_FILENAME = "dead-code-summaries.json";
32949
32982
  const SUPPLY_CHAIN_PLUGIN = "socket";
@@ -32953,8 +32986,9 @@ const SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS = 9e4;
32953
32986
  const SUPPLY_CHAIN_CACHE_TTL_MS = 864e5;
32954
32987
  const SUPPLY_CHAIN_CACHE_SUBDIR = "supply-chain";
32955
32988
  const SUPPLY_CHAIN_IGNORED_PACKAGES = new Set(["next"]);
32956
- const hasIgnoredPathSegment = (relativePath) => relativePath.split(/[/\\]/).slice(0, -1).some((segment) => IGNORED_DIRECTORIES.has(segment));
32957
- const isLintableSourceFile = (filePath) => SOURCE_FILE_PATTERN.test(filePath) && !GENERATED_BUNDLE_FILE_PATTERN.test(filePath);
32989
+ const isIgnoredDirectoryName = (directoryName) => IGNORED_DIRECTORIES.has(directoryName) || directoryName.startsWith(".") && !SCANNED_DOT_DIRECTORIES.has(directoryName);
32990
+ const hasIgnoredPathSegment = (relativePath) => relativePath.split(/[/\\]/).slice(0, -1).some((segment) => isIgnoredDirectoryName(segment));
32991
+ const isLintableSourceFile = (filePath) => SOURCE_FILE_PATTERN.test(filePath) && !GENERATED_BUNDLE_FILE_PATTERN.test(filePath) && !GENERATED_SOURCE_DIRECTORY_PATTERN.test(filePath);
32958
32992
  const isMinifiedSource = (absolutePath) => {
32959
32993
  let fileDescriptor;
32960
32994
  try {
@@ -32991,25 +33025,6 @@ const isLargeMinifiedFile = (absolutePath, knownSizeBytes) => {
32991
33025
  cachedIsLargeMinifiedByPath.set(absolutePath, result);
32992
33026
  return result;
32993
33027
  };
32994
- const isErrnoException = (error) => error instanceof Error && "code" in error;
32995
- const IGNORABLE_READDIR_ERROR_CODES = new Set([
32996
- "EACCES",
32997
- "EPERM",
32998
- "ENOENT",
32999
- "ENOTDIR",
33000
- "EINVAL",
33001
- "ELOOP",
33002
- "ENAMETOOLONG"
33003
- ]);
33004
- const isIgnorableReaddirError = (error) => isErrnoException(error) && typeof error.code === "string" && IGNORABLE_READDIR_ERROR_CODES.has(error.code);
33005
- const readDirectoryEntries = (directoryPath) => {
33006
- try {
33007
- return NFS.readdirSync(directoryPath, { withFileTypes: true });
33008
- } catch (error) {
33009
- if (isIgnorableReaddirError(error)) return [];
33010
- throw error;
33011
- }
33012
- };
33013
33028
  function* walkSourceTreeFiles(rootDirectory) {
33014
33029
  const stack = [rootDirectory];
33015
33030
  while (stack.length > 0) {
@@ -33018,7 +33033,7 @@ function* walkSourceTreeFiles(rootDirectory) {
33018
33033
  for (const entry of readDirectoryEntries(currentDirectory)) {
33019
33034
  const absolutePath = Path.join(currentDirectory, entry.name);
33020
33035
  if (entry.isDirectory()) {
33021
- if (!IGNORED_DIRECTORIES.has(entry.name)) stack.push(absolutePath);
33036
+ if (!isIgnoredDirectoryName(entry.name)) stack.push(absolutePath);
33022
33037
  continue;
33023
33038
  }
33024
33039
  if (entry.isFile()) yield {
@@ -33115,6 +33130,146 @@ const findMonorepoRoot = (startDirectory) => {
33115
33130
  * byte-equivalent copy.
33116
33131
  */
33117
33132
  const isProjectBoundary = (directory) => NFS.existsSync(Path.join(directory, ".git")) || isMonorepoRoot(directory);
33133
+ const TSCONFIG_FILENAME = "tsconfig.json";
33134
+ const isRelativeExtendsValue = (extendsValue) => extendsValue.startsWith("./") || extendsValue.startsWith("../") || Path.isAbsolute(extendsValue);
33135
+ const ensureJsonExtension = (filePath) => Path.extname(filePath) === "" ? `${filePath}.json` : filePath;
33136
+ const resolvePackageExtendsPath = (extendsValue, fromConfigDirectory) => {
33137
+ const requireFromConfig = createRequire(Path.join(fromConfigDirectory, "tsconfig.json"));
33138
+ const candidates = [
33139
+ extendsValue,
33140
+ ensureJsonExtension(extendsValue),
33141
+ `${extendsValue.replace(/\/$/, "")}/tsconfig.json`
33142
+ ];
33143
+ for (const candidate of candidates) try {
33144
+ return requireFromConfig.resolve(candidate);
33145
+ } catch {
33146
+ continue;
33147
+ }
33148
+ return null;
33149
+ };
33150
+ const resolveExtendsPath = (extendsValue, fromConfigDirectory) => {
33151
+ if (isRelativeExtendsValue(extendsValue)) return ensureJsonExtension(Path.resolve(fromConfigDirectory, extendsValue));
33152
+ return resolvePackageExtendsPath(extendsValue, fromConfigDirectory);
33153
+ };
33154
+ const normalizeCompilerOptions = (compilerOptions) => {
33155
+ if (!isPlainObject(compilerOptions)) return { hasExplicitLib: false };
33156
+ const target = typeof compilerOptions.target === "string" ? compilerOptions.target : void 0;
33157
+ const hasExplicitLib = Object.hasOwn(compilerOptions, "lib");
33158
+ return {
33159
+ target,
33160
+ lib: Array.isArray(compilerOptions.lib) ? compilerOptions.lib.filter((entry) => typeof entry === "string") : void 0,
33161
+ hasExplicitLib
33162
+ };
33163
+ };
33164
+ const readTsConfig = (filePath) => {
33165
+ let content;
33166
+ try {
33167
+ content = NFS.readFileSync(filePath, "utf-8");
33168
+ } catch {
33169
+ return null;
33170
+ }
33171
+ const parsed = ts.parseConfigFileTextToJson(filePath, content);
33172
+ if (!isPlainObject(parsed.config)) return null;
33173
+ return {
33174
+ extends: typeof parsed.config.extends === "string" ? parsed.config.extends : void 0,
33175
+ referencePaths: normalizeReferencePaths(parsed.config.references),
33176
+ compilerOptions: normalizeCompilerOptions(parsed.config.compilerOptions)
33177
+ };
33178
+ };
33179
+ const normalizeReferencePaths = (references) => {
33180
+ if (!Array.isArray(references)) return [];
33181
+ return references.map((reference) => isPlainObject(reference) && typeof reference.path === "string" ? reference.path : null).filter((referencePath) => referencePath !== null);
33182
+ };
33183
+ const mergeCompilerOptions = (inherited, current) => {
33184
+ const target = current.target ?? inherited?.target;
33185
+ const hasExplicitLib = current.hasExplicitLib || Boolean(inherited?.hasExplicitLib);
33186
+ return {
33187
+ target,
33188
+ lib: current.hasExplicitLib ? current.lib : inherited?.lib,
33189
+ hasExplicitLib
33190
+ };
33191
+ };
33192
+ const readResolvedCompilerOptions = (tsConfigPath, extendsDepth, visitedPaths) => {
33193
+ const realPath = NFS.realpathSync.native(tsConfigPath);
33194
+ if (visitedPaths.has(realPath)) return null;
33195
+ const tsConfig = readTsConfig(realPath);
33196
+ if (!tsConfig) return null;
33197
+ const nextVisitedPaths = new Set(visitedPaths);
33198
+ nextVisitedPaths.add(realPath);
33199
+ if (tsConfig.extends && extendsDepth < 8) {
33200
+ const parentPath = resolveExtendsPath(tsConfig.extends, Path.dirname(realPath));
33201
+ if (parentPath && isFile(parentPath)) return mergeCompilerOptions(readResolvedCompilerOptions(parentPath, extendsDepth + 1, nextVisitedPaths), tsConfig.compilerOptions);
33202
+ }
33203
+ return tsConfig.compilerOptions;
33204
+ };
33205
+ const targetYearIsPreES2023 = (target) => {
33206
+ const year = ES_TARGET_YEAR_BY_NAME[target.toLowerCase()];
33207
+ return year !== void 0 && year < 2023;
33208
+ };
33209
+ const libEntryIncludesES2023Array = (entry) => {
33210
+ const normalizedEntry = entry.toLowerCase();
33211
+ if (normalizedEntry === "esnext" || normalizedEntry === "esnext.array") return true;
33212
+ const esYearMatch = /^es(\d{4})(?:\.(.+))?$/.exec(normalizedEntry);
33213
+ if (!esYearMatch) return false;
33214
+ if (Number(esYearMatch[1]) < 2023) return false;
33215
+ const component = esYearMatch[2];
33216
+ return component === void 0 || component === "array";
33217
+ };
33218
+ const libIncludesES2023 = (lib) => lib.some(libEntryIncludesES2023Array);
33219
+ const compilerOptionsArePreES2023 = (compilerOptions) => {
33220
+ if (compilerOptions.target) return targetYearIsPreES2023(compilerOptions.target);
33221
+ if (compilerOptions.hasExplicitLib) return !libIncludesES2023(compilerOptions.lib ?? []);
33222
+ return false;
33223
+ };
33224
+ const compilerOptionsDeclareTargetOrLib = (compilerOptions) => compilerOptions.hasExplicitLib || compilerOptions.target !== void 0;
33225
+ const detectPreES2023FromConfig = (tsConfigPath, visitedConfigPaths = /* @__PURE__ */ new Set()) => {
33226
+ if (visitedConfigPaths.has(tsConfigPath)) return false;
33227
+ const compilerOptions = readResolvedCompilerOptions(tsConfigPath, 0, /* @__PURE__ */ new Set());
33228
+ if (!compilerOptions) return false;
33229
+ if (!compilerOptionsDeclareTargetOrLib(compilerOptions)) {
33230
+ const tsConfig = readTsConfig(tsConfigPath);
33231
+ if (!tsConfig) return false;
33232
+ const nextVisitedConfigPaths = new Set(visitedConfigPaths);
33233
+ nextVisitedConfigPaths.add(tsConfigPath);
33234
+ const configDirectory = Path.dirname(tsConfigPath);
33235
+ return tsConfig.referencePaths.some((referencePath) => {
33236
+ const resolvedReferencePath = Path.resolve(configDirectory, referencePath);
33237
+ const referencedConfigPath = isFile(resolvedReferencePath) ? resolvedReferencePath : Path.join(resolvedReferencePath, TSCONFIG_FILENAME);
33238
+ return isFile(referencedConfigPath) && detectPreES2023FromConfig(referencedConfigPath, nextVisitedConfigPaths);
33239
+ });
33240
+ }
33241
+ return compilerOptionsArePreES2023(compilerOptions);
33242
+ };
33243
+ const detectPreES2023Target = (directory) => {
33244
+ const tsConfigPath = Path.join(directory, TSCONFIG_FILENAME);
33245
+ if (isFile(tsConfigPath)) return detectPreES2023FromConfig(tsConfigPath);
33246
+ for (const fallbackFilename of FALLBACK_TSCONFIG_FILENAMES) {
33247
+ const fallbackPath = Path.join(directory, fallbackFilename);
33248
+ if (isFile(fallbackPath)) return detectPreES2023FromConfig(fallbackPath);
33249
+ }
33250
+ return false;
33251
+ };
33252
+ const FALLBACK_TSCONFIG_FILENAMES = ["tsconfig.app.json", "tsconfig.build.json"];
33253
+ const FRAMEWORK_PACKAGES = {
33254
+ next: "nextjs",
33255
+ "@tanstack/react-start": "tanstack-start",
33256
+ vite: "vite",
33257
+ "react-scripts": "cra",
33258
+ "@remix-run/react": "remix",
33259
+ gatsby: "gatsby",
33260
+ expo: "expo",
33261
+ "react-native": "react-native"
33262
+ };
33263
+ const detectFramework = (dependencies) => {
33264
+ for (const [packageName, frameworkName] of Object.entries(FRAMEWORK_PACKAGES)) if (dependencies[packageName]) return frameworkName;
33265
+ if (dependencies.preact && !dependencies.react) return "preact";
33266
+ return "unknown";
33267
+ };
33268
+ const MOBILE_FRAMEWORKS = new Set(["expo", "react-native"]);
33269
+ const frameworkMergeRank = (framework) => {
33270
+ if (framework === "unknown") return 3;
33271
+ return MOBILE_FRAMEWORKS.has(framework) ? 2 : 1;
33272
+ };
33118
33273
  const REACT_COMPILER_PACKAGES = new Set([
33119
33274
  "babel-plugin-react-compiler",
33120
33275
  "react-compiler-runtime",
@@ -33151,6 +33306,7 @@ const EXPO_APP_CONFIG_FILENAMES = [
33151
33306
  ];
33152
33307
  const REACT_COMPILER_PACKAGE_REFERENCE_PATTERN = /babel-plugin-react-compiler|react-compiler-runtime|eslint-plugin-react-compiler|["']react-compiler["']/;
33153
33308
  const REACT_COMPILER_ENABLED_FLAG_PATTERN = /["']?reactCompiler["']?\s*:\s*(?:true\b|\{)/;
33309
+ const STATIC_EXPORT_OUTPUT_PATTERN = /(?:^|[^.\w])["']?output["']?\s*:\s*["']export["']/m;
33154
33310
  const hasCompilerPackage = (packageJson) => {
33155
33311
  const allDependencies = {
33156
33312
  ...packageJson.peerDependencies,
@@ -33183,34 +33339,10 @@ const detectReactCompiler = (directory, packageJson) => {
33183
33339
  }
33184
33340
  return false;
33185
33341
  };
33186
- const FRAMEWORK_PACKAGES = {
33187
- next: "nextjs",
33188
- "@tanstack/react-start": "tanstack-start",
33189
- vite: "vite",
33190
- "react-scripts": "cra",
33191
- "@remix-run/react": "remix",
33192
- gatsby: "gatsby",
33193
- expo: "expo",
33194
- "react-native": "react-native"
33195
- };
33196
- const FRAMEWORK_DISPLAY_NAMES = {
33197
- nextjs: "Next.js",
33198
- "tanstack-start": "TanStack Start",
33199
- vite: "Vite",
33200
- cra: "Create React App",
33201
- remix: "Remix",
33202
- gatsby: "Gatsby",
33203
- expo: "Expo",
33204
- "react-native": "React Native",
33205
- preact: "Preact",
33206
- unknown: "React"
33207
- };
33208
- const formatFrameworkName = (framework) => FRAMEWORK_DISPLAY_NAMES[framework];
33209
- const detectFramework = (dependencies) => {
33210
- for (const [packageName, frameworkName] of Object.entries(FRAMEWORK_PACKAGES)) if (dependencies[packageName]) return frameworkName;
33211
- if (dependencies.preact && !dependencies.react) return "preact";
33212
- return "unknown";
33213
- };
33342
+ const detectNextjsStaticExport = (directory) => NEXT_CONFIG_FILENAMES.some((filename) => {
33343
+ const filePath = Path.join(directory, filename);
33344
+ return isFile(filePath) && STATIC_EXPORT_OUTPUT_PATTERN.test(NFS.readFileSync(filePath, "utf-8"));
33345
+ });
33214
33346
  const UNRESOLVABLE_PROTOCOL_VERSION = /^(?:file|git|github|https?|link|patch|portal|workspace|npm):/i;
33215
33347
  const DIST_TAG_VERSION = /^[a-z][a-z0-9._-]*$/i;
33216
33348
  const WILDCARD_VERSION = /^[*xX](?:\.[*xX])*$/;
@@ -33348,10 +33480,76 @@ const isConcreteDependencyVersion = (version) => {
33348
33480
  const normalizedVersion = normalizeDependencyVersion(version);
33349
33481
  return normalizedVersion !== null && /\d/.test(normalizedVersion);
33350
33482
  };
33351
- const isPlainObject = (value) => {
33352
- if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
33353
- const prototype = Object.getPrototypeOf(value);
33354
- return prototype === null || prototype === Object.prototype;
33483
+ const isMajorMinorAtLeast = (detected, required) => {
33484
+ if (detected === null) return true;
33485
+ if (detected.major !== required.major) return detected.major > required.major;
33486
+ return detected.minor >= required.minor;
33487
+ };
33488
+ const parseReactMajor = (reactVersion) => {
33489
+ if (typeof reactVersion !== "string") return null;
33490
+ return getLowestDependencyMajor(reactVersion);
33491
+ };
33492
+ const MAJOR_MINOR_PATTERN = /(\d{1,4})\.(\d{1,4})/;
33493
+ const MAJOR_ONLY_PATTERN = /(\d{1,4})/;
33494
+ const UPPER_BOUND_COMPARATOR_PATTERN = /<=?\s{0,8}\d{1,4}(?:\.\d{1,4}){0,2}(?:-[^\s,|]+)?/g;
33495
+ const parseReactMajorMinor = (reactVersion) => {
33496
+ if (typeof reactVersion !== "string") return null;
33497
+ const trimmed = reactVersion.trim();
33498
+ if (trimmed.length === 0) return null;
33499
+ const lowerBoundsOnly = trimmed.replace(UPPER_BOUND_COMPARATOR_PATTERN, " ").trim();
33500
+ if (lowerBoundsOnly.length === 0) return null;
33501
+ const majorMinorMatch = lowerBoundsOnly.match(MAJOR_MINOR_PATTERN);
33502
+ if (majorMinorMatch) {
33503
+ const major = Number.parseInt(majorMinorMatch[1], 10);
33504
+ const minor = Number.parseInt(majorMinorMatch[2], 10);
33505
+ if (!Number.isFinite(major) || major <= 0) return null;
33506
+ if (!Number.isFinite(minor) || minor < 0) return null;
33507
+ return {
33508
+ major,
33509
+ minor
33510
+ };
33511
+ }
33512
+ const majorOnlyMatch = lowerBoundsOnly.match(MAJOR_ONLY_PATTERN);
33513
+ if (!majorOnlyMatch) return null;
33514
+ const major = Number.parseInt(majorOnlyMatch[1], 10);
33515
+ if (!Number.isFinite(major) || major <= 0) return null;
33516
+ return {
33517
+ major,
33518
+ minor: 0
33519
+ };
33520
+ };
33521
+ const parseLowerBoundVersion = (versionSpec) => import_semver.validRange(versionSpec) !== null ? import_semver.minVersion(versionSpec) : import_semver.coerce(versionSpec);
33522
+ const parseTailwindMajorMinor = (tailwindVersion) => {
33523
+ if (typeof tailwindVersion !== "string") return null;
33524
+ const trimmed = tailwindVersion.trim();
33525
+ if (trimmed.length === 0) return null;
33526
+ const lowerBound = parseLowerBoundVersion(trimmed);
33527
+ if (lowerBound === null || lowerBound.major <= 0) return null;
33528
+ return {
33529
+ major: lowerBound.major,
33530
+ minor: lowerBound.minor
33531
+ };
33532
+ };
33533
+ const hasUpperBoundOnlyPeerRange = (range) => {
33534
+ if (typeof range !== "string") return false;
33535
+ const normalizedRange = normalizeDependencyVersion(range);
33536
+ if (normalizedRange === null) return false;
33537
+ return splitDependencyVersionBranches(normalizedRange).some((branch) => {
33538
+ const normalizedBranch = normalizeDependencyVersion(branch);
33539
+ return normalizedBranch !== null && getBranchLowestMajor(normalizedBranch) === null && hasUpperBoundComparator(normalizedBranch);
33540
+ });
33541
+ };
33542
+ const peerRangeMinMajor = (range) => {
33543
+ if (typeof range !== "string") return null;
33544
+ return getLowestDependencyMajor(range);
33545
+ };
33546
+ const resolveEffectiveReactMajor = (reactVersion, packageJson) => {
33547
+ const installedReactMajor = parseReactMajor(reactVersion);
33548
+ const peerReactRange = packageJson.peerDependencies?.react;
33549
+ if (typeof peerReactRange !== "string") return installedReactMajor;
33550
+ const peerFloor = peerRangeMinMajor(peerReactRange);
33551
+ if (peerFloor === null) return hasUpperBoundOnlyPeerRange(peerReactRange) ? null : installedReactMajor;
33552
+ return installedReactMajor !== null ? Math.min(installedReactMajor, peerFloor) : peerFloor;
33355
33553
  };
33356
33554
  const isCatalogReference = (version) => version.startsWith("catalog:");
33357
33555
  const extractCatalogName = (version) => {
@@ -33493,6 +33691,16 @@ const resolveCatalogVersion = (packageJson, packageName, rootDirectory, explicit
33493
33691
  }
33494
33692
  return null;
33495
33693
  };
33694
+ const REACT_SECTIONS = [
33695
+ "dependencies",
33696
+ "peerDependencies",
33697
+ "devDependencies"
33698
+ ];
33699
+ const TAILWIND_ZOD_SECTIONS = [
33700
+ "dependencies",
33701
+ "devDependencies",
33702
+ "peerDependencies"
33703
+ ];
33496
33704
  const EMPTY_DEPENDENCY_INFO = {
33497
33705
  reactVersion: null,
33498
33706
  tailwindVersion: null,
@@ -33533,6 +33741,10 @@ const extractDependencyInfo = (packageJson) => {
33533
33741
  framework: detectFramework(allDependencies)
33534
33742
  };
33535
33743
  };
33744
+ const getDependencySpec = (packageJson, packageName) => {
33745
+ const spec = packageJson.dependencies?.[packageName] ?? packageJson.devDependencies?.[packageName] ?? packageJson.peerDependencies?.[packageName] ?? packageJson.optionalDependencies?.[packageName];
33746
+ return typeof spec === "string" ? spec : null;
33747
+ };
33536
33748
  const getDependencyDeclaration = ({ packageJson, packageName, sections }) => {
33537
33749
  for (const section of sections) {
33538
33750
  const version = packageJson[section]?.[packageName];
@@ -33549,222 +33761,91 @@ const getDependencyDeclaration = ({ packageJson, packageName, sections }) => {
33549
33761
  version: null
33550
33762
  };
33551
33763
  };
33552
- const isDirectory = (directoryPath) => {
33553
- try {
33554
- return NFS.statSync(directoryPath).isDirectory();
33555
- } catch {
33556
- return false;
33557
- }
33558
- };
33559
- const NX_PROJECT_DISCOVERY_DIRS = [
33560
- "apps",
33561
- "libs",
33562
- "packages"
33563
- ];
33564
- const getNxWorkspaceDirectories = (rootDirectory) => {
33565
- if (!isFile(Path.join(rootDirectory, "nx.json"))) return [];
33566
- const collected = [];
33567
- for (const candidate of NX_PROJECT_DISCOVERY_DIRS) {
33568
- const candidatePath = Path.join(rootDirectory, candidate);
33569
- if (!isDirectory(candidatePath)) continue;
33570
- for (const entry of readDirectoryEntries(candidatePath)) {
33571
- if (!entry.isDirectory()) continue;
33572
- const projectDirectory = Path.join(candidatePath, entry.name);
33573
- if (isFile(Path.join(projectDirectory, "project.json")) || isFile(Path.join(projectDirectory, "package.json"))) collected.push(`${candidate}/${entry.name}`);
33574
- }
33575
- }
33576
- return collected;
33577
- };
33578
- const parsePnpmWorkspacePatterns = (rootDirectory) => {
33579
- const workspacePath = Path.join(rootDirectory, "pnpm-workspace.yaml");
33580
- if (!isFile(workspacePath)) return [];
33581
- const content = NFS.readFileSync(workspacePath, "utf-8");
33582
- const patterns = [];
33583
- let isInsidePackagesBlock = false;
33584
- for (const line of content.split("\n")) {
33585
- const trimmed = line.trim();
33586
- if (trimmed === "packages:") {
33587
- isInsidePackagesBlock = true;
33588
- continue;
33589
- }
33590
- if (isInsidePackagesBlock && trimmed.startsWith("-")) patterns.push(trimmed.replace(/^-\s*/, "").replace(/["']/g, ""));
33591
- else if (isInsidePackagesBlock && trimmed.length > 0 && !trimmed.startsWith("#")) isInsidePackagesBlock = false;
33592
- }
33593
- return patterns;
33594
- };
33595
- const getWorkspacePatterns = (rootDirectory, packageJson) => {
33596
- const pnpmPatterns = parsePnpmWorkspacePatterns(rootDirectory);
33597
- if (pnpmPatterns.length > 0) return pnpmPatterns;
33598
- if (Array.isArray(packageJson.workspaces)) return packageJson.workspaces;
33599
- if (packageJson.workspaces?.packages) return packageJson.workspaces.packages;
33600
- const nxPatterns = getNxWorkspaceDirectories(rootDirectory);
33601
- if (nxPatterns.length > 0) return nxPatterns;
33602
- return [];
33603
- };
33604
- const parseReactMajor = (reactVersion) => {
33605
- if (typeof reactVersion !== "string") return null;
33606
- return getLowestDependencyMajor(reactVersion);
33764
+ const REACT_DEPENDENCY_NAMES = new Set([
33765
+ "react",
33766
+ "react-native",
33767
+ "next",
33768
+ "preact"
33769
+ ]);
33770
+ const hasReactDependency = (packageJson) => {
33771
+ const allDependencies = {
33772
+ ...packageJson.peerDependencies,
33773
+ ...packageJson.dependencies,
33774
+ ...packageJson.devDependencies
33775
+ };
33776
+ return Object.keys(allDependencies).some((packageName) => REACT_DEPENDENCY_NAMES.has(packageName));
33607
33777
  };
33608
- const resolveWorkspaceDirectories = (rootDirectory, pattern) => {
33609
- const cleanPattern = pattern.replace(/["']/g, "").replace(/\/\*\*$/, "/*");
33610
- if (!cleanPattern.includes("*")) {
33611
- const directoryPath = Path.join(rootDirectory, cleanPattern);
33612
- if (isDirectory(directoryPath) && isFile(Path.join(directoryPath, "package.json"))) return [directoryPath];
33613
- return [];
33614
- }
33615
- const wildcardIndex = cleanPattern.indexOf("*");
33616
- const baseDirectory = Path.join(rootDirectory, cleanPattern.slice(0, wildcardIndex));
33617
- const suffixAfterWildcard = cleanPattern.slice(wildcardIndex + 1);
33618
- if (!isDirectory(baseDirectory)) return [];
33619
- const resolved = [];
33620
- for (const entry of readDirectoryEntries(baseDirectory)) {
33621
- const entryPath = Path.join(baseDirectory, entry.name, suffixAfterWildcard);
33622
- if (isDirectory(entryPath) && isFile(Path.join(entryPath, "package.json"))) resolved.push(entryPath);
33623
- }
33624
- return resolved;
33778
+ const getPreactVersion = (packageJson) => {
33779
+ return {
33780
+ ...packageJson.peerDependencies,
33781
+ ...packageJson.dependencies,
33782
+ ...packageJson.devDependencies
33783
+ }.preact ?? null;
33625
33784
  };
33626
- const resolveWorkspaceDependencyVersion = ({ concreteVersion, packageName, rootDirectory, rootPackageJson, sections, workspaceDirectory, workspacePackageJson }) => {
33627
- const dependencyDeclaration = getDependencyDeclaration({
33628
- packageJson: workspacePackageJson,
33629
- packageName,
33630
- sections
33631
- });
33632
- if (!dependencyDeclaration.hasDeclaration) return null;
33633
- return concreteVersion ?? resolveCatalogVersion(workspacePackageJson, packageName, workspaceDirectory, dependencyDeclaration.catalogReference) ?? resolveCatalogVersion(rootPackageJson, packageName, rootDirectory, dependencyDeclaration.catalogReference);
33785
+ const TANSTACK_QUERY_PACKAGES = new Set([
33786
+ "@tanstack/react-query",
33787
+ "@tanstack/query-core",
33788
+ "react-query"
33789
+ ]);
33790
+ const hasTanStackQuery = (packageJson) => {
33791
+ const allDependencies = {
33792
+ ...packageJson.peerDependencies,
33793
+ ...packageJson.dependencies,
33794
+ ...packageJson.devDependencies
33795
+ };
33796
+ return Object.keys(allDependencies).some((packageName) => TANSTACK_QUERY_PACKAGES.has(packageName));
33634
33797
  };
33635
- const shouldReplaceReactVersion = (currentVersion, nextVersion) => {
33636
- if (!currentVersion) return true;
33637
- const currentMajor = parseReactMajor(currentVersion);
33638
- const nextMajor = parseReactMajor(nextVersion);
33639
- if (currentMajor === null) return nextMajor !== null;
33640
- if (nextMajor === null) return false;
33641
- return nextMajor < currentMajor;
33798
+ const resolveCatalogBackedDependencyVersion = ({ rootDirectory, rootPackageJson, packageName, version }) => {
33799
+ if (version === null || !isCatalogReference(version)) return version;
33800
+ const catalogName = extractCatalogName(version);
33801
+ const resolvedLocalVersion = resolveCatalogVersion(rootPackageJson, packageName, rootDirectory, catalogName);
33802
+ if (resolvedLocalVersion) return resolvedLocalVersion;
33803
+ const monorepoRoot = findMonorepoRoot(rootDirectory);
33804
+ if (!monorepoRoot) return version;
33805
+ const monorepoPackageJsonPath = Path.join(monorepoRoot, "package.json");
33806
+ if (!isFile(monorepoPackageJsonPath)) return version;
33807
+ return resolveCatalogVersion(readPackageJson(monorepoPackageJsonPath), packageName, monorepoRoot, catalogName) ?? version;
33642
33808
  };
33643
- const findReactInWorkspaces = (rootDirectory, packageJson) => {
33644
- const patterns = getWorkspacePatterns(rootDirectory, packageJson);
33645
- const result = { ...EMPTY_DEPENDENCY_INFO };
33646
- for (const pattern of patterns) {
33647
- const directories = resolveWorkspaceDirectories(rootDirectory, pattern);
33648
- for (const workspaceDirectory of directories) {
33649
- const workspacePackageJson = readPackageJson(Path.join(workspaceDirectory, "package.json"));
33650
- const info = extractDependencyInfo(workspacePackageJson);
33651
- const reactVersion = resolveWorkspaceDependencyVersion({
33652
- concreteVersion: info.reactVersion,
33653
- packageName: "react",
33654
- rootDirectory,
33655
- rootPackageJson: packageJson,
33656
- sections: [
33657
- "dependencies",
33658
- "peerDependencies",
33659
- "devDependencies"
33660
- ],
33661
- workspaceDirectory,
33662
- workspacePackageJson
33663
- });
33664
- const tailwindVersion = resolveWorkspaceDependencyVersion({
33665
- concreteVersion: info.tailwindVersion,
33666
- packageName: "tailwindcss",
33667
- rootDirectory,
33668
- rootPackageJson: packageJson,
33669
- sections: [
33670
- "dependencies",
33671
- "devDependencies",
33672
- "peerDependencies"
33673
- ],
33674
- workspaceDirectory,
33675
- workspacePackageJson
33676
- });
33677
- const zodVersion = resolveWorkspaceDependencyVersion({
33678
- concreteVersion: info.zodVersion,
33679
- packageName: "zod",
33680
- rootDirectory,
33681
- rootPackageJson: packageJson,
33682
- sections: [
33683
- "dependencies",
33684
- "devDependencies",
33685
- "peerDependencies"
33686
- ],
33687
- workspaceDirectory,
33688
- workspacePackageJson
33689
- });
33690
- if (reactVersion && shouldReplaceReactVersion(result.reactVersion, reactVersion)) result.reactVersion = reactVersion;
33691
- if (tailwindVersion && !result.tailwindVersion) result.tailwindVersion = tailwindVersion;
33692
- if (zodVersion && !result.zodVersion) result.zodVersion = zodVersion;
33693
- if (info.framework !== "unknown" && result.framework === "unknown") result.framework = info.framework;
33694
- const resultReactMajor = parseReactMajor(result.reactVersion);
33695
- if (result.reactVersion && result.tailwindVersion && result.framework !== "unknown" && resultReactMajor !== null && resultReactMajor <= 17) return result;
33696
- }
33809
+ /**
33810
+ * The chain of ancestor directories of `startDirectory`, nearest first, up to
33811
+ * and including the filesystem root. With `includeStart`, the chain begins at
33812
+ * `startDirectory` itself; otherwise at its parent. The one place the "walk up
33813
+ * the tree" traversal is expressed, so each caller adds only its own match /
33814
+ * stop policy instead of re-deriving the `path.dirname` loop.
33815
+ */
33816
+ const ancestorDirectories = (startDirectory, { includeStart }) => {
33817
+ const directories = [];
33818
+ let currentDirectory = includeStart ? startDirectory : Path.dirname(startDirectory);
33819
+ while (true) {
33820
+ directories.push(currentDirectory);
33821
+ const parentDirectory = Path.dirname(currentDirectory);
33822
+ if (parentDirectory === currentDirectory) return directories;
33823
+ currentDirectory = parentDirectory;
33697
33824
  }
33698
- return result;
33699
33825
  };
33700
- const findDependencyInfoFromMonorepoRoot = (directory) => {
33701
- const monorepoRoot = findMonorepoRoot(directory);
33702
- if (!monorepoRoot) return EMPTY_DEPENDENCY_INFO;
33703
- const monorepoPackageJsonPath = Path.join(monorepoRoot, "package.json");
33704
- if (!isFile(monorepoPackageJsonPath)) return EMPTY_DEPENDENCY_INFO;
33705
- const rootPackageJson = readPackageJson(monorepoPackageJsonPath);
33706
- const rootInfo = extractDependencyInfo(rootPackageJson);
33707
- const leafPackageJsonPath = Path.join(directory, "package.json");
33708
- const leafPackageJson = isFile(leafPackageJsonPath) ? readPackageJson(leafPackageJsonPath) : null;
33709
- const leafReactDeclaration = leafPackageJson ? getDependencyDeclaration({
33710
- packageJson: leafPackageJson,
33711
- packageName: "react",
33712
- sections: [
33713
- "dependencies",
33714
- "peerDependencies",
33715
- "devDependencies"
33716
- ]
33717
- }) : null;
33718
- const leafTailwindDeclaration = leafPackageJson ? getDependencyDeclaration({
33719
- packageJson: leafPackageJson,
33720
- packageName: "tailwindcss",
33721
- sections: [
33722
- "dependencies",
33723
- "devDependencies",
33724
- "peerDependencies"
33725
- ]
33726
- }) : null;
33727
- const leafZodDeclaration = leafPackageJson ? getDependencyDeclaration({
33728
- packageJson: leafPackageJson,
33729
- packageName: "zod",
33730
- sections: [
33731
- "dependencies",
33732
- "devDependencies",
33733
- "peerDependencies"
33734
- ]
33735
- }) : null;
33736
- const shouldUseReactFallback = !leafReactDeclaration?.hasDeclaration;
33737
- const shouldUseTailwindFallback = leafTailwindDeclaration?.hasDeclaration ?? true;
33738
- const shouldUseZodFallback = leafZodDeclaration?.hasDeclaration ?? true;
33739
- const reactCatalogVersion = shouldUseReactFallback ? resolveCatalogVersion(rootPackageJson, "react", monorepoRoot, leafReactDeclaration?.catalogReference) : null;
33740
- const tailwindCatalogVersion = shouldUseTailwindFallback ? resolveCatalogVersion(rootPackageJson, "tailwindcss", monorepoRoot, leafTailwindDeclaration?.catalogReference) : null;
33741
- const zodCatalogVersion = shouldUseZodFallback ? resolveCatalogVersion(rootPackageJson, "zod", monorepoRoot, leafZodDeclaration?.catalogReference) : null;
33742
- const workspaceInfo = findReactInWorkspaces(monorepoRoot, rootPackageJson);
33743
- return {
33744
- reactVersion: shouldUseReactFallback ? reactCatalogVersion ?? rootInfo.reactVersion ?? workspaceInfo.reactVersion : rootInfo.reactVersion ?? workspaceInfo.reactVersion,
33745
- tailwindVersion: shouldUseTailwindFallback ? tailwindCatalogVersion ?? rootInfo.tailwindVersion ?? workspaceInfo.tailwindVersion : null,
33746
- zodVersion: shouldUseZodFallback ? zodCatalogVersion ?? rootInfo.zodVersion ?? workspaceInfo.zodVersion : null,
33747
- framework: rootInfo.framework !== "unknown" ? rootInfo.framework : workspaceInfo.framework
33748
- };
33749
- };
33750
- const findInWorkspacePackageJsons = (rootDirectory, rootPackageJson, select) => {
33751
- const rootValue = select(rootPackageJson);
33752
- if (rootValue !== null) return rootValue;
33753
- const patterns = getWorkspacePatterns(rootDirectory, rootPackageJson);
33754
- if (patterns.length === 0) return null;
33755
- const visitedDirectories = /* @__PURE__ */ new Set();
33756
- for (const pattern of patterns) {
33757
- const directories = [...resolveWorkspaceDirectories(rootDirectory, pattern)].sort();
33758
- for (const workspaceDirectory of directories) {
33759
- if (visitedDirectories.has(workspaceDirectory)) continue;
33760
- visitedDirectories.add(workspaceDirectory);
33761
- const value = select(readPackageJson(Path.join(workspaceDirectory, "package.json")));
33762
- if (value !== null) return value;
33763
- }
33826
+ /**
33827
+ * Walk up from `startDirectory` to the nearest ancestor that owns a
33828
+ * `package.json`, stopping at (and including) the enclosing project boundary
33829
+ * — the working tree's git root or a monorepo root. Lets a scan of a package
33830
+ * subfolder that has no `package.json` of its own adopt the nearest enclosing
33831
+ * package as its project root, so it inherits that package's dependency +
33832
+ * framework detection instead of synthesizing an empty (React-blind) project.
33833
+ *
33834
+ * Generalizes the older monorepo-root-only lookup: the nearest ancestor is the
33835
+ * most specific owning package (a leaf workspace, not just the repo root), so
33836
+ * a subfolder of one workspace no longer borrows a sibling's capabilities.
33837
+ *
33838
+ * Returns `null` when no ancestor `package.json` exists inside the project
33839
+ * boundary (a loose tree of files, or a subfolder of a package-less repo).
33840
+ */
33841
+ const findNearestAncestorPackageJson = (startDirectory) => {
33842
+ if (isProjectBoundary(startDirectory)) return null;
33843
+ for (const directory of ancestorDirectories(startDirectory, { includeStart: false })) {
33844
+ if (isFile(Path.join(directory, "package.json"))) return directory;
33845
+ if (isProjectBoundary(directory)) return null;
33764
33846
  }
33765
33847
  return null;
33766
33848
  };
33767
- const someWorkspacePackageJson = (rootDirectory, rootPackageJson, predicate) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => predicate(packageJson) ? true : null) !== null;
33768
33849
  const NAMES = new Set([
33769
33850
  "react-native",
33770
33851
  "react-native-tvos",
@@ -33797,47 +33878,7 @@ const isPackageJsonReactNativeAware = (packageJson) => {
33797
33878
  if (containsAnyReactNativeDependency(packageJson.optionalDependencies)) return true;
33798
33879
  return false;
33799
33880
  };
33800
- const hasReactNativeWorkspaceAnywhere = (rootDirectory, rootPackageJson) => someWorkspacePackageJson(rootDirectory, rootPackageJson, isPackageJsonReactNativeAware);
33801
- const getDependencySpec = (packageJson, packageName) => {
33802
- const spec = packageJson.dependencies?.[packageName] ?? packageJson.devDependencies?.[packageName] ?? packageJson.peerDependencies?.[packageName] ?? packageJson.optionalDependencies?.[packageName];
33803
- return typeof spec === "string" ? spec : null;
33804
- };
33805
- const findExpoVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, "expo"));
33806
- const SHOPIFY_FLASH_LIST_PACKAGE_NAME = "@shopify/flash-list";
33807
- const findShopifyFlashListVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, SHOPIFY_FLASH_LIST_PACKAGE_NAME));
33808
- const resolveCatalogBackedDependencyVersion = ({ rootDirectory, rootPackageJson, packageName, version }) => {
33809
- if (version === null || !isCatalogReference(version)) return version;
33810
- const catalogName = extractCatalogName(version);
33811
- const resolvedLocalVersion = resolveCatalogVersion(rootPackageJson, packageName, rootDirectory, catalogName);
33812
- if (resolvedLocalVersion) return resolvedLocalVersion;
33813
- const monorepoRoot = findMonorepoRoot(rootDirectory);
33814
- if (!monorepoRoot) return version;
33815
- const monorepoPackageJsonPath = path.join(monorepoRoot, "package.json");
33816
- if (!isFile(monorepoPackageJsonPath)) return version;
33817
- return resolveCatalogVersion(readPackageJson(monorepoPackageJsonPath), packageName, monorepoRoot, catalogName) ?? version;
33818
- };
33819
- const findNextjsVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, "next"));
33820
- const getPreactVersion = (packageJson) => {
33821
- return {
33822
- ...packageJson.peerDependencies,
33823
- ...packageJson.dependencies,
33824
- ...packageJson.devDependencies
33825
- }.preact ?? null;
33826
- };
33827
- const TANSTACK_QUERY_PACKAGES = new Set([
33828
- "@tanstack/react-query",
33829
- "@tanstack/query-core",
33830
- "react-query"
33831
- ]);
33832
- const hasTanStackQuery = (packageJson) => {
33833
- const allDependencies = {
33834
- ...packageJson.peerDependencies,
33835
- ...packageJson.dependencies,
33836
- ...packageJson.devDependencies
33837
- };
33838
- return Object.keys(allDependencies).some((packageName) => TANSTACK_QUERY_PACKAGES.has(packageName));
33839
- };
33840
- const REANIMATED_DEPENDENCY_NAME = "react-native-reanimated";
33881
+ const REANIMATED_DEPENDENCY_NAME$1 = "react-native-reanimated";
33841
33882
  const isPackageJsonReanimatedAware = (packageJson) => {
33842
33883
  const allDependencies = {
33843
33884
  ...packageJson.peerDependencies,
@@ -33845,144 +33886,70 @@ const isPackageJsonReanimatedAware = (packageJson) => {
33845
33886
  ...packageJson.devDependencies,
33846
33887
  ...packageJson.optionalDependencies
33847
33888
  };
33848
- return Object.hasOwn(allDependencies, REANIMATED_DEPENDENCY_NAME);
33849
- };
33850
- const parseZodMajor = (zodVersion) => {
33851
- if (typeof zodVersion !== "string") return null;
33852
- return getLowestDependencyMajor(zodVersion);
33889
+ return Object.hasOwn(allDependencies, REANIMATED_DEPENDENCY_NAME$1);
33853
33890
  };
33854
- const hasUpperBoundOnlyPeerRange = (range) => {
33855
- if (typeof range !== "string") return false;
33856
- const normalizedRange = normalizeDependencyVersion(range);
33857
- if (normalizedRange === null) return false;
33858
- return splitDependencyVersionBranches(normalizedRange).some((branch) => {
33859
- const normalizedBranch = normalizeDependencyVersion(branch);
33860
- return normalizedBranch !== null && getBranchLowestMajor(normalizedBranch) === null && hasUpperBoundComparator(normalizedBranch);
33861
- });
33862
- };
33863
- const peerRangeMinMajor = (range) => {
33864
- if (typeof range !== "string") return null;
33865
- return getLowestDependencyMajor(range);
33866
- };
33867
- const resolveEffectiveReactMajor = (reactVersion, packageJson) => {
33868
- const installedReactMajor = parseReactMajor(reactVersion);
33869
- const peerReactRange = packageJson.peerDependencies?.react;
33870
- if (typeof peerReactRange !== "string") return installedReactMajor;
33871
- const peerFloor = peerRangeMinMajor(peerReactRange);
33872
- if (peerFloor === null) return hasUpperBoundOnlyPeerRange(peerReactRange) ? null : installedReactMajor;
33873
- return installedReactMajor !== null ? Math.min(installedReactMajor, peerFloor) : peerFloor;
33891
+ const getWorkspacePatterns = (rootDirectory, packageJson) => {
33892
+ const pnpmPatterns = parsePnpmWorkspacePatterns(rootDirectory);
33893
+ if (pnpmPatterns.length > 0) return pnpmPatterns;
33894
+ if (Array.isArray(packageJson.workspaces)) return packageJson.workspaces;
33895
+ if (packageJson.workspaces?.packages) return packageJson.workspaces.packages;
33896
+ const nxPatterns = getNxWorkspaceDirectories(rootDirectory);
33897
+ if (nxPatterns.length > 0) return nxPatterns;
33898
+ return [];
33874
33899
  };
33875
- const TSCONFIG_FILENAME = "tsconfig.json";
33876
- const isRelativeExtendsValue = (extendsValue) => extendsValue.startsWith("./") || extendsValue.startsWith("../") || Path.isAbsolute(extendsValue);
33877
- const ensureJsonExtension = (filePath) => Path.extname(filePath) === "" ? `${filePath}.json` : filePath;
33878
- const resolvePackageExtendsPath = (extendsValue, fromConfigDirectory) => {
33879
- const requireFromConfig = createRequire(Path.join(fromConfigDirectory, "tsconfig.json"));
33880
- const candidates = [
33881
- extendsValue,
33882
- ensureJsonExtension(extendsValue),
33883
- `${extendsValue.replace(/\/$/, "")}/tsconfig.json`
33884
- ];
33885
- for (const candidate of candidates) try {
33886
- return requireFromConfig.resolve(candidate);
33887
- } catch {
33888
- continue;
33900
+ const parsePnpmWorkspacePatterns = (rootDirectory) => {
33901
+ const workspacePath = Path.join(rootDirectory, "pnpm-workspace.yaml");
33902
+ if (!isFile(workspacePath)) return [];
33903
+ const content = NFS.readFileSync(workspacePath, "utf-8");
33904
+ const patterns = [];
33905
+ let isInsidePackagesBlock = false;
33906
+ for (const line of content.split("\n")) {
33907
+ const trimmed = line.trim();
33908
+ if (trimmed === "packages:") {
33909
+ isInsidePackagesBlock = true;
33910
+ continue;
33911
+ }
33912
+ if (isInsidePackagesBlock && trimmed.startsWith("-")) patterns.push(trimmed.replace(/^-\s*/, "").replace(/["']/g, ""));
33913
+ else if (isInsidePackagesBlock && trimmed.length > 0 && !trimmed.startsWith("#")) isInsidePackagesBlock = false;
33889
33914
  }
33890
- return null;
33891
- };
33892
- const resolveExtendsPath = (extendsValue, fromConfigDirectory) => {
33893
- if (isRelativeExtendsValue(extendsValue)) return ensureJsonExtension(Path.resolve(fromConfigDirectory, extendsValue));
33894
- return resolvePackageExtendsPath(extendsValue, fromConfigDirectory);
33895
- };
33896
- const normalizeCompilerOptions = (compilerOptions) => {
33897
- if (!isPlainObject(compilerOptions)) return { hasExplicitLib: false };
33898
- const target = typeof compilerOptions.target === "string" ? compilerOptions.target : void 0;
33899
- const hasExplicitLib = Object.hasOwn(compilerOptions, "lib");
33900
- return {
33901
- target,
33902
- lib: Array.isArray(compilerOptions.lib) ? compilerOptions.lib.filter((entry) => typeof entry === "string") : void 0,
33903
- hasExplicitLib
33904
- };
33915
+ return patterns;
33905
33916
  };
33906
- const readTsConfig = (filePath) => {
33907
- let content;
33908
- try {
33909
- content = NFS.readFileSync(filePath, "utf-8");
33910
- } catch {
33911
- return null;
33917
+ const NX_PROJECT_DISCOVERY_DIRS = [
33918
+ "apps",
33919
+ "libs",
33920
+ "packages"
33921
+ ];
33922
+ const getNxWorkspaceDirectories = (rootDirectory) => {
33923
+ if (!isFile(Path.join(rootDirectory, "nx.json"))) return [];
33924
+ const collected = [];
33925
+ for (const candidate of NX_PROJECT_DISCOVERY_DIRS) {
33926
+ const candidatePath = Path.join(rootDirectory, candidate);
33927
+ if (!isDirectory(candidatePath)) continue;
33928
+ for (const entry of readDirectoryEntries(candidatePath)) {
33929
+ if (!entry.isDirectory()) continue;
33930
+ const projectDirectory = Path.join(candidatePath, entry.name);
33931
+ if (isFile(Path.join(projectDirectory, "project.json")) || isFile(Path.join(projectDirectory, "package.json"))) collected.push(`${candidate}/${entry.name}`);
33932
+ }
33912
33933
  }
33913
- const parsed = ts.parseConfigFileTextToJson(filePath, content);
33914
- if (!isPlainObject(parsed.config)) return null;
33915
- return {
33916
- extends: typeof parsed.config.extends === "string" ? parsed.config.extends : void 0,
33917
- compilerOptions: normalizeCompilerOptions(parsed.config.compilerOptions)
33918
- };
33919
- };
33920
- const mergeCompilerOptions = (inherited, current) => {
33921
- const target = current.target ?? inherited?.target;
33922
- const hasExplicitLib = current.hasExplicitLib || Boolean(inherited?.hasExplicitLib);
33923
- return {
33924
- target,
33925
- lib: current.hasExplicitLib ? current.lib : inherited?.lib,
33926
- hasExplicitLib
33927
- };
33934
+ return collected;
33928
33935
  };
33929
- const readResolvedCompilerOptions = (tsConfigPath, extendsDepth, visitedPaths) => {
33930
- const realPath = NFS.realpathSync.native(tsConfigPath);
33931
- if (visitedPaths.has(realPath)) return null;
33932
- const tsConfig = readTsConfig(realPath);
33933
- if (!tsConfig) return null;
33934
- const nextVisitedPaths = new Set(visitedPaths);
33935
- nextVisitedPaths.add(realPath);
33936
- if (tsConfig.extends && extendsDepth < 8) {
33937
- const parentPath = resolveExtendsPath(tsConfig.extends, Path.dirname(realPath));
33938
- if (parentPath && isFile(parentPath)) return mergeCompilerOptions(readResolvedCompilerOptions(parentPath, extendsDepth + 1, nextVisitedPaths), tsConfig.compilerOptions);
33936
+ const resolveWorkspaceDirectories = (rootDirectory, pattern) => {
33937
+ const cleanPattern = pattern.replace(/["']/g, "").replace(/\/\*\*$/, "/*");
33938
+ if (!cleanPattern.includes("*")) {
33939
+ const directoryPath = Path.join(rootDirectory, cleanPattern);
33940
+ if (isDirectory(directoryPath) && isFile(Path.join(directoryPath, "package.json"))) return [directoryPath];
33941
+ return [];
33939
33942
  }
33940
- return tsConfig.compilerOptions;
33941
- };
33942
- const targetYearIsPreES2023 = (target) => {
33943
- const year = ES_TARGET_YEAR_BY_NAME[target.toLowerCase()];
33944
- return year !== void 0 && year < 2023;
33945
- };
33946
- const libEntryIncludesES2023Array = (entry) => {
33947
- const normalizedEntry = entry.toLowerCase();
33948
- if (normalizedEntry === "esnext" || normalizedEntry === "esnext.array") return true;
33949
- const esYearMatch = /^es(\d{4})(?:\.(.+))?$/.exec(normalizedEntry);
33950
- if (!esYearMatch) return false;
33951
- if (Number(esYearMatch[1]) < 2023) return false;
33952
- const component = esYearMatch[2];
33953
- return component === void 0 || component === "array";
33954
- };
33955
- const libIncludesES2023 = (lib) => lib.some(libEntryIncludesES2023Array);
33956
- const compilerOptionsArePreES2023 = (compilerOptions) => {
33957
- if (compilerOptions.hasExplicitLib) return !libIncludesES2023(compilerOptions.lib ?? []);
33958
- if (compilerOptions.target) return targetYearIsPreES2023(compilerOptions.target);
33959
- return false;
33960
- };
33961
- const compilerOptionsDeclareTargetOrLib = (compilerOptions) => compilerOptions.hasExplicitLib || compilerOptions.target !== void 0;
33962
- const detectPreES2023FromConfig = (tsConfigPath) => {
33963
- const compilerOptions = readResolvedCompilerOptions(tsConfigPath, 0, /* @__PURE__ */ new Set());
33964
- if (!compilerOptions) return false;
33965
- if (!compilerOptionsDeclareTargetOrLib(compilerOptions)) return false;
33966
- return compilerOptionsArePreES2023(compilerOptions);
33967
- };
33968
- const detectPreES2023Target = (directory) => {
33969
- const tsConfigPath = Path.join(directory, TSCONFIG_FILENAME);
33970
- if (isFile(tsConfigPath)) return detectPreES2023FromConfig(tsConfigPath);
33971
- return false;
33972
- };
33973
- const REACT_DEPENDENCY_NAMES = new Set([
33974
- "react",
33975
- "react-native",
33976
- "next",
33977
- "preact"
33978
- ]);
33979
- const hasReactDependency = (packageJson) => {
33980
- const allDependencies = {
33981
- ...packageJson.peerDependencies,
33982
- ...packageJson.dependencies,
33983
- ...packageJson.devDependencies
33984
- };
33985
- return Object.keys(allDependencies).some((packageName) => REACT_DEPENDENCY_NAMES.has(packageName));
33943
+ const wildcardIndex = cleanPattern.indexOf("*");
33944
+ const baseDirectory = Path.join(rootDirectory, cleanPattern.slice(0, wildcardIndex));
33945
+ const suffixAfterWildcard = cleanPattern.slice(wildcardIndex + 1);
33946
+ if (!isDirectory(baseDirectory)) return [];
33947
+ const resolved = [];
33948
+ for (const entry of readDirectoryEntries(baseDirectory)) {
33949
+ const entryPath = Path.join(baseDirectory, entry.name, suffixAfterWildcard);
33950
+ if (isDirectory(entryPath) && isFile(Path.join(entryPath, "package.json"))) resolved.push(entryPath);
33951
+ }
33952
+ return resolved;
33986
33953
  };
33987
33954
  const listWorkspacePackages = (rootDirectory) => {
33988
33955
  const packageJsonPath = Path.join(rootDirectory, "package.json");
@@ -34014,6 +33981,208 @@ const listWorkspacePackages = (rootDirectory) => {
34014
33981
  }
34015
33982
  return packages;
34016
33983
  };
33984
+ const REANIMATED_DEPENDENCY_NAME = "react-native-reanimated";
33985
+ const SHOPIFY_FLASH_LIST_PACKAGE_NAME = "@shopify/flash-list";
33986
+ const resolveWorkspaceDependencyVersion = ({ concreteVersion, packageName, rootDirectory, rootPackageJson, sections, workspaceDirectory, workspacePackageJson }) => {
33987
+ const dependencyDeclaration = getDependencyDeclaration({
33988
+ packageJson: workspacePackageJson,
33989
+ packageName,
33990
+ sections
33991
+ });
33992
+ if (!dependencyDeclaration.hasDeclaration) return null;
33993
+ return concreteVersion ?? resolveCatalogVersion(workspacePackageJson, packageName, workspaceDirectory, dependencyDeclaration.catalogReference) ?? resolveCatalogVersion(rootPackageJson, packageName, rootDirectory, dependencyDeclaration.catalogReference);
33994
+ };
33995
+ const shouldReplaceReactVersion = (currentVersion, nextVersion) => {
33996
+ if (!currentVersion) return true;
33997
+ const currentMajor = parseReactMajor(currentVersion);
33998
+ const nextMajor = parseReactMajor(nextVersion);
33999
+ if (currentMajor === null) return nextMajor !== null;
34000
+ if (nextMajor === null) return false;
34001
+ return nextMajor < currentMajor;
34002
+ };
34003
+ const evaluateManifestFacts = (facts, packageJson, directory) => {
34004
+ if (facts.expo.version === null) {
34005
+ const spec = getDependencySpec(packageJson, "expo");
34006
+ if (spec !== null) facts.expo = {
34007
+ version: spec,
34008
+ sourceDirectory: directory
34009
+ };
34010
+ }
34011
+ if (facts.next.version === null) {
34012
+ const spec = getDependencySpec(packageJson, "next");
34013
+ if (spec !== null) facts.next = {
34014
+ version: spec,
34015
+ sourceDirectory: directory
34016
+ };
34017
+ }
34018
+ if (facts.shopifyFlashList.version === null) {
34019
+ const spec = getDependencySpec(packageJson, SHOPIFY_FLASH_LIST_PACKAGE_NAME);
34020
+ if (spec !== null) facts.shopifyFlashList = {
34021
+ version: spec,
34022
+ sourceDirectory: directory
34023
+ };
34024
+ }
34025
+ if (facts.reanimatedVersion === null) {
34026
+ const spec = getDependencySpec(packageJson, REANIMATED_DEPENDENCY_NAME);
34027
+ if (spec !== null) facts.reanimatedVersion = spec;
34028
+ }
34029
+ facts.hasReactNativeAwarePackage = facts.hasReactNativeAwarePackage || isPackageJsonReactNativeAware(packageJson);
34030
+ facts.hasReanimatedAwarePackage = facts.hasReanimatedAwarePackage || isPackageJsonReanimatedAware(packageJson);
34031
+ };
34032
+ const collectWorkspaceFacts = (rootDirectory, rootPackageJson, { collectReactGroup }) => {
34033
+ const facts = {
34034
+ reactVersion: null,
34035
+ tailwindVersion: null,
34036
+ zodVersion: null,
34037
+ framework: "unknown",
34038
+ expo: {
34039
+ version: null,
34040
+ sourceDirectory: null
34041
+ },
34042
+ next: {
34043
+ version: null,
34044
+ sourceDirectory: null
34045
+ },
34046
+ shopifyFlashList: {
34047
+ version: null,
34048
+ sourceDirectory: null
34049
+ },
34050
+ hasReactNativeAwarePackage: false,
34051
+ hasReanimatedAwarePackage: false,
34052
+ reanimatedVersion: null
34053
+ };
34054
+ evaluateManifestFacts(facts, rootPackageJson, rootDirectory);
34055
+ let isReactGroupSettled = !collectReactGroup;
34056
+ const visitedDirectories = /* @__PURE__ */ new Set();
34057
+ for (const pattern of getWorkspacePatterns(rootDirectory, rootPackageJson)) {
34058
+ const directories = [...resolveWorkspaceDirectories(rootDirectory, pattern)].sort();
34059
+ for (const workspaceDirectory of directories) {
34060
+ if (visitedDirectories.has(workspaceDirectory)) continue;
34061
+ visitedDirectories.add(workspaceDirectory);
34062
+ const workspacePackageJson = readPackageJson(Path.join(workspaceDirectory, "package.json"));
34063
+ evaluateManifestFacts(facts, workspacePackageJson, workspaceDirectory);
34064
+ const info = extractDependencyInfo(workspacePackageJson);
34065
+ if (info.framework !== "unknown" && frameworkMergeRank(info.framework) < frameworkMergeRank(facts.framework)) facts.framework = info.framework;
34066
+ if (isReactGroupSettled) continue;
34067
+ const reactVersion = resolveWorkspaceDependencyVersion({
34068
+ concreteVersion: info.reactVersion,
34069
+ packageName: "react",
34070
+ rootDirectory,
34071
+ rootPackageJson,
34072
+ sections: REACT_SECTIONS,
34073
+ workspaceDirectory,
34074
+ workspacePackageJson
34075
+ });
34076
+ const tailwindVersion = resolveWorkspaceDependencyVersion({
34077
+ concreteVersion: info.tailwindVersion,
34078
+ packageName: "tailwindcss",
34079
+ rootDirectory,
34080
+ rootPackageJson,
34081
+ sections: TAILWIND_ZOD_SECTIONS,
34082
+ workspaceDirectory,
34083
+ workspacePackageJson
34084
+ });
34085
+ const zodVersion = resolveWorkspaceDependencyVersion({
34086
+ concreteVersion: info.zodVersion,
34087
+ packageName: "zod",
34088
+ rootDirectory,
34089
+ rootPackageJson,
34090
+ sections: TAILWIND_ZOD_SECTIONS,
34091
+ workspaceDirectory,
34092
+ workspacePackageJson
34093
+ });
34094
+ if (reactVersion && shouldReplaceReactVersion(facts.reactVersion, reactVersion)) facts.reactVersion = reactVersion;
34095
+ if (tailwindVersion && !facts.tailwindVersion) facts.tailwindVersion = tailwindVersion;
34096
+ if (zodVersion && !facts.zodVersion) facts.zodVersion = zodVersion;
34097
+ const settledReactMajor = parseReactMajor(facts.reactVersion);
34098
+ isReactGroupSettled = Boolean(facts.reactVersion && facts.tailwindVersion && facts.framework !== "unknown" && settledReactMajor !== null && settledReactMajor <= 17);
34099
+ }
34100
+ }
34101
+ return facts;
34102
+ };
34103
+ const findDependencyInfoFromMonorepoRoot = (directory) => {
34104
+ const monorepoRoot = findMonorepoRoot(directory);
34105
+ if (!monorepoRoot) return EMPTY_DEPENDENCY_INFO;
34106
+ const monorepoPackageJsonPath = Path.join(monorepoRoot, "package.json");
34107
+ if (!isFile(monorepoPackageJsonPath)) return EMPTY_DEPENDENCY_INFO;
34108
+ const rootPackageJson = readPackageJson(monorepoPackageJsonPath);
34109
+ const rootInfo = extractDependencyInfo(rootPackageJson);
34110
+ const leafPackageJsonPath = Path.join(directory, "package.json");
34111
+ const leafPackageJson = isFile(leafPackageJsonPath) ? readPackageJson(leafPackageJsonPath) : null;
34112
+ const leafReactDeclaration = leafPackageJson ? getDependencyDeclaration({
34113
+ packageJson: leafPackageJson,
34114
+ packageName: "react",
34115
+ sections: REACT_SECTIONS
34116
+ }) : null;
34117
+ const leafTailwindDeclaration = leafPackageJson ? getDependencyDeclaration({
34118
+ packageJson: leafPackageJson,
34119
+ packageName: "tailwindcss",
34120
+ sections: TAILWIND_ZOD_SECTIONS
34121
+ }) : null;
34122
+ const leafZodDeclaration = leafPackageJson ? getDependencyDeclaration({
34123
+ packageJson: leafPackageJson,
34124
+ packageName: "zod",
34125
+ sections: TAILWIND_ZOD_SECTIONS
34126
+ }) : null;
34127
+ const shouldUseReactFallback = !leafReactDeclaration?.hasDeclaration;
34128
+ const shouldUseTailwindFallback = leafTailwindDeclaration?.hasDeclaration ?? true;
34129
+ const shouldUseZodFallback = leafZodDeclaration?.hasDeclaration ?? true;
34130
+ const reactCatalogVersion = shouldUseReactFallback ? resolveCatalogVersion(rootPackageJson, "react", monorepoRoot, leafReactDeclaration?.catalogReference) : null;
34131
+ const tailwindCatalogVersion = shouldUseTailwindFallback ? resolveCatalogVersion(rootPackageJson, "tailwindcss", monorepoRoot, leafTailwindDeclaration?.catalogReference) : null;
34132
+ const zodCatalogVersion = shouldUseZodFallback ? resolveCatalogVersion(rootPackageJson, "zod", monorepoRoot, leafZodDeclaration?.catalogReference) : null;
34133
+ const workspaceFacts = collectWorkspaceFacts(monorepoRoot, rootPackageJson, { collectReactGroup: true });
34134
+ return {
34135
+ reactVersion: shouldUseReactFallback ? reactCatalogVersion ?? rootInfo.reactVersion ?? workspaceFacts.reactVersion : rootInfo.reactVersion ?? workspaceFacts.reactVersion,
34136
+ tailwindVersion: shouldUseTailwindFallback ? tailwindCatalogVersion ?? rootInfo.tailwindVersion ?? workspaceFacts.tailwindVersion : null,
34137
+ zodVersion: shouldUseZodFallback ? zodCatalogVersion ?? rootInfo.zodVersion ?? workspaceFacts.zodVersion : null,
34138
+ framework: rootInfo.framework !== "unknown" ? rootInfo.framework : workspaceFacts.framework
34139
+ };
34140
+ };
34141
+ /**
34142
+ * Last-resort React detection: locate React the way Node's `node_modules`
34143
+ * resolution would from `directory` — check each `node_modules/react` up the
34144
+ * tree — but bounded to the enclosing repo so a globally installed React can't
34145
+ * masquerade as the project's version. Stops at (and including) the nearest
34146
+ * project boundary — the git root or a monorepo root — and returns the first
34147
+ * installed `version`.
34148
+ *
34149
+ * Makes "React is installed and importable" ⇒ "React is detected" an invariant
34150
+ * for packages whose only React declaration is a version-less spec
34151
+ * (`workspace:*`, `*`, a dist-tag) or where React lives solely in a hoisted
34152
+ * `node_modules` the declaration walks never reach — the profile of a component
34153
+ * package inside a monorepo. Matching the `node_modules/react` entry rather than
34154
+ * its realpath means a project whose `node_modules` is symlinked elsewhere (a
34155
+ * Docker volume, a shared store) still counts, while a global install outside
34156
+ * the repo does not.
34157
+ *
34158
+ * Returns `null` when React isn't installed within the repo or its package.json
34159
+ * carries no version string. A tree with no boundary marker at all bounds the
34160
+ * search to `directory`, so a dependency hoisted above the scanned package there
34161
+ * is conservatively not adopted.
34162
+ */
34163
+ /**
34164
+ * The nearest enclosing project boundary (git root or monorepo root), or
34165
+ * `directory` itself when the scan target sits outside any repo. Bounds the
34166
+ * node_modules walk so a React hoisted anywhere inside the repo counts, but a
34167
+ * React in an ancestor above the repo — a home-directory or global
34168
+ * `node_modules` — can't leak in.
34169
+ */
34170
+ const findContainmentRoot = (directory) => {
34171
+ for (const ancestorDirectory of ancestorDirectories(directory, { includeStart: true })) if (isProjectBoundary(ancestorDirectory)) return ancestorDirectory;
34172
+ return directory;
34173
+ };
34174
+ const resolveInstalledReactVersion = (directory) => {
34175
+ const containmentRoot = findContainmentRoot(directory);
34176
+ for (const ancestorDirectory of ancestorDirectories(directory, { includeStart: true })) {
34177
+ const reactPackageJsonPath = Path.join(ancestorDirectory, "node_modules", "react", "package.json");
34178
+ if (isFile(reactPackageJsonPath)) {
34179
+ const installedVersion = readPackageJson(reactPackageJsonPath).version;
34180
+ return typeof installedVersion === "string" ? installedVersion : null;
34181
+ }
34182
+ if (ancestorDirectory === containmentRoot) return null;
34183
+ }
34184
+ return null;
34185
+ };
34017
34186
  const toReactWorkspacePackages = (directories) => {
34018
34187
  const packages = [];
34019
34188
  for (const directory of directories) {
@@ -34097,8 +34266,8 @@ const clearProjectCache = () => {
34097
34266
  const discoverProjectWithoutPackageJson = (directory) => {
34098
34267
  const sourceFileCount = countSourceFiles(directory);
34099
34268
  const hasOwnTsConfig = NFS.existsSync(Path.join(directory, "tsconfig.json"));
34100
- const monorepoRoot = findMonorepoRoot(directory);
34101
- const enclosingProject = monorepoRoot !== null && isFile(Path.join(monorepoRoot, "package.json")) ? discoverProject(monorepoRoot) : null;
34269
+ const enclosingProjectRoot = findNearestAncestorPackageJson(directory);
34270
+ const enclosingProject = enclosingProjectRoot !== null ? discoverProject(enclosingProjectRoot) : null;
34102
34271
  if (enclosingProject !== null) return {
34103
34272
  ...enclosingProject,
34104
34273
  rootDirectory: directory,
@@ -34128,7 +34297,9 @@ const discoverProjectWithoutPackageJson = (directory) => {
34128
34297
  shopifyFlashListVersion: null,
34129
34298
  shopifyFlashListMajorVersion: null,
34130
34299
  hasReanimated: false,
34300
+ reanimatedVersion: null,
34131
34301
  isPreES2023Target: hasOwnTsConfig && detectPreES2023Target(directory),
34302
+ isStaticExport: false,
34132
34303
  sourceFileCount
34133
34304
  };
34134
34305
  };
@@ -34142,88 +34313,85 @@ const discoverProject = (directory) => {
34142
34313
  return synthesized;
34143
34314
  }
34144
34315
  const packageJson = readPackageJson(packageJsonPath);
34145
- let { reactVersion, tailwindVersion, zodVersion, framework } = extractDependencyInfo(packageJson);
34146
- const reactDeclaration = getDependencyDeclaration({
34147
- packageJson,
34148
- packageName: "react",
34149
- sections: [
34150
- "dependencies",
34151
- "peerDependencies",
34152
- "devDependencies"
34153
- ]
34154
- });
34155
- const tailwindDeclaration = getDependencyDeclaration({
34156
- packageJson,
34157
- packageName: "tailwindcss",
34158
- sections: [
34159
- "dependencies",
34160
- "devDependencies",
34161
- "peerDependencies"
34162
- ]
34163
- });
34164
- const zodDeclaration = getDependencyDeclaration({
34316
+ const rootInfo = extractDependencyInfo(packageJson);
34317
+ let framework = rootInfo.framework;
34318
+ const tracked = {
34319
+ react: {
34320
+ version: rootInfo.reactVersion,
34321
+ sections: REACT_SECTIONS
34322
+ },
34323
+ tailwindcss: {
34324
+ version: rootInfo.tailwindVersion,
34325
+ sections: TAILWIND_ZOD_SECTIONS
34326
+ },
34327
+ zod: {
34328
+ version: rootInfo.zodVersion,
34329
+ sections: TAILWIND_ZOD_SECTIONS
34330
+ }
34331
+ };
34332
+ const declarations = Object.fromEntries(Object.entries(tracked).map(([packageName, entry]) => [packageName, getDependencyDeclaration({
34165
34333
  packageJson,
34166
- packageName: "zod",
34167
- sections: [
34168
- "dependencies",
34169
- "devDependencies",
34170
- "peerDependencies"
34171
- ]
34172
- });
34173
- if (!reactVersion && reactDeclaration.hasDeclaration) reactVersion = resolveCatalogVersion(packageJson, "react", directory, reactDeclaration.catalogReference);
34174
- if (!tailwindVersion && tailwindDeclaration.hasDeclaration) tailwindVersion = resolveCatalogVersion(packageJson, "tailwindcss", directory, tailwindDeclaration.catalogReference);
34175
- if (!zodVersion && zodDeclaration.hasDeclaration) zodVersion = resolveCatalogVersion(packageJson, "zod", directory, zodDeclaration.catalogReference);
34176
- if (!reactVersion || !tailwindVersion || !zodVersion) {
34334
+ packageName,
34335
+ sections: entry.sections
34336
+ })]));
34337
+ const fillFromCatalogs = (source, sourceDirectory) => {
34338
+ for (const [packageName, entry] of Object.entries(tracked)) if (!entry.version && declarations[packageName].hasDeclaration) entry.version = resolveCatalogVersion(source, packageName, sourceDirectory, declarations[packageName].catalogReference);
34339
+ };
34340
+ fillFromCatalogs(packageJson, directory);
34341
+ if (!tracked.react.version || !tracked.tailwindcss.version || !tracked.zod.version) {
34177
34342
  const monorepoRoot = findMonorepoRoot(directory);
34178
34343
  if (monorepoRoot) {
34179
34344
  const monorepoPackageJsonPath = Path.join(monorepoRoot, "package.json");
34180
- if (isFile(monorepoPackageJsonPath)) {
34181
- const rootPackageJson = readPackageJson(monorepoPackageJsonPath);
34182
- if (!reactVersion && reactDeclaration.hasDeclaration) reactVersion = resolveCatalogVersion(rootPackageJson, "react", monorepoRoot, reactDeclaration.catalogReference);
34183
- if (!tailwindVersion && tailwindDeclaration.hasDeclaration) tailwindVersion = resolveCatalogVersion(rootPackageJson, "tailwindcss", monorepoRoot, tailwindDeclaration.catalogReference);
34184
- if (!zodVersion && zodDeclaration.hasDeclaration) zodVersion = resolveCatalogVersion(rootPackageJson, "zod", monorepoRoot, zodDeclaration.catalogReference);
34185
- }
34345
+ if (isFile(monorepoPackageJsonPath)) fillFromCatalogs(readPackageJson(monorepoPackageJsonPath), monorepoRoot);
34186
34346
  }
34187
34347
  }
34188
- if (!reactVersion || framework === "unknown") {
34189
- const workspaceInfo = findReactInWorkspaces(directory, packageJson);
34190
- if (!reactVersion && workspaceInfo.reactVersion) reactVersion = workspaceInfo.reactVersion;
34191
- if (!tailwindVersion && workspaceInfo.tailwindVersion) tailwindVersion = workspaceInfo.tailwindVersion;
34192
- if (!zodVersion && workspaceInfo.zodVersion) zodVersion = workspaceInfo.zodVersion;
34193
- if (framework === "unknown" && workspaceInfo.framework !== "unknown") framework = workspaceInfo.framework;
34348
+ const shouldCollectReactGroup = !tracked.react.version || framework === "unknown";
34349
+ const workspaceFacts = collectWorkspaceFacts(directory, packageJson, { collectReactGroup: shouldCollectReactGroup });
34350
+ if (shouldCollectReactGroup) {
34351
+ tracked.react.version ||= workspaceFacts.reactVersion;
34352
+ tracked.tailwindcss.version ||= workspaceFacts.tailwindVersion;
34353
+ tracked.zod.version ||= workspaceFacts.zodVersion;
34354
+ if (framework === "unknown" && workspaceFacts.framework !== "unknown") framework = workspaceFacts.framework;
34194
34355
  }
34195
- if ((!reactVersion || framework === "unknown") && !isMonorepoRoot(directory)) {
34356
+ if ((!tracked.react.version || framework === "unknown") && !isMonorepoRoot(directory)) {
34196
34357
  const monorepoInfo = findDependencyInfoFromMonorepoRoot(directory);
34197
- if (!reactVersion) reactVersion = monorepoInfo.reactVersion;
34198
- if (!tailwindVersion) tailwindVersion = monorepoInfo.tailwindVersion;
34199
- if (!zodVersion) zodVersion = monorepoInfo.zodVersion;
34358
+ tracked.react.version ||= monorepoInfo.reactVersion;
34359
+ tracked.tailwindcss.version ||= monorepoInfo.tailwindVersion;
34360
+ tracked.zod.version ||= monorepoInfo.zodVersion;
34200
34361
  if (framework === "unknown") framework = monorepoInfo.framework;
34201
34362
  }
34202
- if (!reactVersion && reactDeclaration.version && !isCatalogReference(reactDeclaration.version)) reactVersion = reactDeclaration.version;
34203
- if (!tailwindVersion && tailwindDeclaration.version && !isCatalogReference(tailwindDeclaration.version)) tailwindVersion = tailwindDeclaration.version;
34204
- if (!zodVersion && zodDeclaration.version && !isCatalogReference(zodDeclaration.version)) zodVersion = zodDeclaration.version;
34363
+ for (const [packageName, entry] of Object.entries(tracked)) {
34364
+ const declaredVersion = declarations[packageName].version;
34365
+ if (!entry.version && declaredVersion && !isCatalogReference(declaredVersion)) entry.version = declaredVersion;
34366
+ }
34367
+ const { react, tailwindcss, zod } = tracked;
34368
+ let reactVersion = react.version;
34369
+ if (!reactVersion || parseReactMajor(reactVersion) === null) reactVersion = resolveInstalledReactVersion(directory) ?? reactVersion;
34370
+ const tailwindVersion = tailwindcss.version;
34371
+ const zodVersion = zod.version;
34205
34372
  const projectName = packageJson.name ?? Path.basename(directory);
34206
34373
  const hasTypeScript = NFS.existsSync(Path.join(directory, "tsconfig.json"));
34207
34374
  const sourceFileCount = countSourceFiles(directory);
34208
- const hasReactNativeWorkspace = framework === "expo" || framework === "react-native" || hasReactNativeWorkspaceAnywhere(directory, packageJson);
34375
+ const hasReactNativeWorkspace = framework === "expo" || framework === "react-native" || workspaceFacts.hasReactNativeAwarePackage;
34209
34376
  const expoVersion = hasReactNativeWorkspace ? resolveCatalogBackedDependencyVersion({
34210
34377
  rootDirectory: directory,
34211
34378
  rootPackageJson: packageJson,
34212
34379
  packageName: "expo",
34213
- version: findExpoVersion(directory, packageJson)
34380
+ version: workspaceFacts.expo.version
34214
34381
  }) : null;
34215
34382
  const shopifyFlashListVersion = hasReactNativeWorkspace ? resolveCatalogBackedDependencyVersion({
34216
34383
  rootDirectory: directory,
34217
34384
  rootPackageJson: packageJson,
34218
34385
  packageName: SHOPIFY_FLASH_LIST_PACKAGE_NAME,
34219
- version: findShopifyFlashListVersion(directory, packageJson)
34386
+ version: workspaceFacts.shopifyFlashList.version
34220
34387
  }) : null;
34221
- const hasReanimated = hasReactNativeWorkspace && someWorkspacePackageJson(directory, packageJson, isPackageJsonReanimatedAware);
34388
+ const hasReanimated = hasReactNativeWorkspace && workspaceFacts.hasReanimatedAwarePackage;
34389
+ const reanimatedVersion = hasReanimated ? workspaceFacts.reanimatedVersion : null;
34222
34390
  const nextjsVersion = framework === "nextjs" ? resolveCatalogBackedDependencyVersion({
34223
34391
  rootDirectory: directory,
34224
34392
  rootPackageJson: packageJson,
34225
34393
  packageName: "next",
34226
- version: findNextjsVersion(directory, packageJson)
34394
+ version: workspaceFacts.next.version
34227
34395
  }) : null;
34228
34396
  const preactVersion = getPreactVersion(packageJson);
34229
34397
  const isPreES2023Target = hasTypeScript && detectPreES2023Target(directory);
@@ -34234,7 +34402,7 @@ const discoverProject = (directory) => {
34234
34402
  reactMajorVersion: resolveEffectiveReactMajor(reactVersion, packageJson),
34235
34403
  tailwindVersion,
34236
34404
  zodVersion,
34237
- zodMajorVersion: parseZodMajor(zodVersion),
34405
+ zodMajorVersion: zodVersion === null ? null : getLowestDependencyMajor(zodVersion),
34238
34406
  framework,
34239
34407
  hasTypeScript,
34240
34408
  hasReactCompiler: detectReactCompiler(directory, packageJson),
@@ -34248,7 +34416,9 @@ const discoverProject = (directory) => {
34248
34416
  shopifyFlashListVersion,
34249
34417
  shopifyFlashListMajorVersion: shopifyFlashListVersion === null ? null : getLowestDependencyMajor(shopifyFlashListVersion),
34250
34418
  hasReanimated,
34419
+ reanimatedVersion,
34251
34420
  isPreES2023Target,
34421
+ isStaticExport: framework === "nextjs" && detectNextjsStaticExport(workspaceFacts.next.sourceDirectory ?? directory),
34252
34422
  sourceFileCount
34253
34423
  };
34254
34424
  cachedProjectInfos.set(directory, projectInfo);
@@ -34264,57 +34434,6 @@ const discoverProject = (directory) => {
34264
34434
  * single source file has nothing to analyze.
34265
34435
  */
34266
34436
  const isAnalyzableProject = (project) => project.reactVersion !== null || project.preactVersion !== null || project.sourceFileCount > 0;
34267
- const MAJOR_MINOR_PATTERN = /(\d{1,4})\.(\d{1,4})/;
34268
- const MAJOR_ONLY_PATTERN = /(\d{1,4})/;
34269
- const UPPER_BOUND_COMPARATOR_PATTERN = /<=?\s{0,8}\d{1,4}(?:\.\d{1,4}){0,2}(?:-[^\s,|]+)?/g;
34270
- const parseReactMajorMinor = (reactVersion) => {
34271
- if (typeof reactVersion !== "string") return null;
34272
- const trimmed = reactVersion.trim();
34273
- if (trimmed.length === 0) return null;
34274
- const lowerBoundsOnly = trimmed.replace(UPPER_BOUND_COMPARATOR_PATTERN, " ").trim();
34275
- if (lowerBoundsOnly.length === 0) return null;
34276
- const majorMinorMatch = lowerBoundsOnly.match(MAJOR_MINOR_PATTERN);
34277
- if (majorMinorMatch) {
34278
- const major = Number.parseInt(majorMinorMatch[1], 10);
34279
- const minor = Number.parseInt(majorMinorMatch[2], 10);
34280
- if (!Number.isFinite(major) || major <= 0) return null;
34281
- if (!Number.isFinite(minor) || minor < 0) return null;
34282
- return {
34283
- major,
34284
- minor
34285
- };
34286
- }
34287
- const majorOnlyMatch = lowerBoundsOnly.match(MAJOR_ONLY_PATTERN);
34288
- if (!majorOnlyMatch) return null;
34289
- const major = Number.parseInt(majorOnlyMatch[1], 10);
34290
- if (!Number.isFinite(major) || major <= 0) return null;
34291
- return {
34292
- major,
34293
- minor: 0
34294
- };
34295
- };
34296
- const isReactAtLeast = (detected, required) => {
34297
- if (detected === null) return true;
34298
- if (detected.major !== required.major) return detected.major > required.major;
34299
- return detected.minor >= required.minor;
34300
- };
34301
- const parseLowerBoundVersion = (versionSpec) => import_semver.validRange(versionSpec) !== null ? import_semver.minVersion(versionSpec) : import_semver.coerce(versionSpec);
34302
- const parseTailwindMajorMinor = (tailwindVersion) => {
34303
- if (typeof tailwindVersion !== "string") return null;
34304
- const trimmed = tailwindVersion.trim();
34305
- if (trimmed.length === 0) return null;
34306
- const lowerBound = parseLowerBoundVersion(trimmed);
34307
- if (lowerBound === null || lowerBound.major <= 0) return null;
34308
- return {
34309
- major: lowerBound.major,
34310
- minor: lowerBound.minor
34311
- };
34312
- };
34313
- const isTailwindAtLeast = (detected, required) => {
34314
- if (detected === null) return true;
34315
- if (detected.major !== required.major) return detected.major > required.major;
34316
- return detected.minor >= required.minor;
34317
- };
34318
34437
  const messageFromUnknown = (error) => error instanceof Error ? error.message : String(error);
34319
34438
  var InvalidGlobPatternError = class extends Error {
34320
34439
  pattern;
@@ -34440,6 +34559,9 @@ const buildRuleSeverityControls = (config) => {
34440
34559
  ...config.buckets !== void 0 ? { buckets: config.buckets } : {}
34441
34560
  };
34442
34561
  };
34562
+ const FOREIGN_INLINE_DISABLE_PATTERN = /(?:\/\/|\/\*)[ \t]*(eslint|oxlint)-disable-(next-line|line)(?![\w-])([^\r\n]*)/;
34563
+ const FOREIGN_BLOCK_DISABLE_PATTERN = /\/\*[ \t]*(eslint|oxlint)-disable(?![\w-])([^*\r\n]*)/;
34564
+ const FOREIGN_BLOCK_ENABLE_PATTERN = /\/\*[ \t]*(?:eslint|oxlint)-enable(?![\w-])([^*\r\n]*)/;
34443
34565
  const LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY = {
34444
34566
  "effect/no-adjust-state-on-prop-change": "react-doctor/no-adjust-state-on-prop-change",
34445
34567
  "effect/no-chain-state-updates": "react-doctor/no-chain-state-updates",
@@ -34517,6 +34639,7 @@ const LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY = {
34517
34639
  "react/jsx-no-new-function-as-prop": "react-doctor/jsx-no-new-function-as-prop",
34518
34640
  "react/jsx-no-new-object-as-prop": "react-doctor/jsx-no-new-object-as-prop",
34519
34641
  "react/jsx-no-script-url": "react-doctor/jsx-no-script-url",
34642
+ "react/jsx-no-target-blank": "react-doctor/jsx-no-target-blank",
34520
34643
  "react/jsx-no-undef": "react-doctor/jsx-no-undef",
34521
34644
  "react/jsx-no-useless-fragment": "react-doctor/jsx-no-useless-fragment",
34522
34645
  "react/jsx-pascal-case": "react-doctor/jsx-pascal-case",
@@ -34590,9 +34713,6 @@ const tokenizeRuleList = (ruleList) => {
34590
34713
  if (!ruleSection) return [];
34591
34714
  return ruleSection.split(/[,\s]+/).map((token) => token.trim()).filter(Boolean);
34592
34715
  };
34593
- const FOREIGN_INLINE_DISABLE_PATTERN = /(?:\/\/|\/\*)[ \t]*(eslint|oxlint)-disable-(next-line|line)(?![\w-])([^\r\n]*)/;
34594
- const FOREIGN_BLOCK_DISABLE_PATTERN = /\/\*[ \t]*(eslint|oxlint)-disable(?![\w-])([^*\r\n]*)/;
34595
- const FOREIGN_BLOCK_ENABLE_PATTERN = /\/\*[ \t]*(?:eslint|oxlint)-enable(?![\w-])([^*\r\n]*)/;
34596
34716
  const buildHint = (tool, token, ruleId) => `oxlint matches plugin rules only by their full name, so \`${token}\` in your ${tool}-disable comment does not silence \`${ruleId}\` — add \`${ruleId}\` to the comment (e.g. \`${token}, ${ruleId}\`).`;
34597
34717
  const tokenMisnamesRule = (token, ruleId) => token !== ruleId && isSameRuleKey(token, ruleId);
34598
34718
  const detectInlineNearMiss = (lines, diagnosticLineIndex, ruleId) => {
@@ -34646,6 +34766,53 @@ const detectForeignDisableNearMiss = (lines, diagnosticLineIndex, ruleId) => {
34646
34766
  if (!ruleId.startsWith("react-doctor/")) return null;
34647
34767
  return detectInlineNearMiss(lines, diagnosticLineIndex, ruleId) ?? detectBlockNearMiss(lines, diagnosticLineIndex, ruleId);
34648
34768
  };
34769
+ const ESLINT_REACT_HOOKS_PLUGIN_PREFIX = "react-hooks/";
34770
+ const buildAcceptedTokens = (ruleId) => {
34771
+ const shortRuleId = ruleId.slice(15);
34772
+ return new Set([ruleId, `${ESLINT_REACT_HOOKS_PLUGIN_PREFIX}${shortRuleId}`]);
34773
+ };
34774
+ const listNamesRule = (ruleList, acceptedTokens) => tokenizeRuleList(ruleList).some((token) => acceptedTokens.has(token));
34775
+ const isInlineDisabled = (lines, diagnosticLineIndex, acceptedTokens) => {
34776
+ const candidates = [{
34777
+ line: lines[diagnosticLineIndex],
34778
+ requiredScope: "line"
34779
+ }, {
34780
+ line: lines[diagnosticLineIndex - 1],
34781
+ requiredScope: "next-line"
34782
+ }];
34783
+ for (const { line, requiredScope } of candidates) {
34784
+ const match = line?.match(FOREIGN_INLINE_DISABLE_PATTERN);
34785
+ if (!match) continue;
34786
+ const [, , scope, ruleList] = match;
34787
+ if (scope !== requiredScope) continue;
34788
+ if (listNamesRule(ruleList, acceptedTokens)) return true;
34789
+ }
34790
+ return false;
34791
+ };
34792
+ const isBlockDisabled = (lines, diagnosticLineIndex, acceptedTokens) => {
34793
+ let isRangeOpen = false;
34794
+ const lastLineIndex = Math.min(diagnosticLineIndex, lines.length - 1);
34795
+ for (let lineIndex = 0; lineIndex <= lastLineIndex; lineIndex++) {
34796
+ const line = lines[lineIndex];
34797
+ if (line === void 0 || !line.includes("-disable") && !line.includes("-enable")) continue;
34798
+ const disableMatch = line.match(FOREIGN_BLOCK_DISABLE_PATTERN);
34799
+ if (disableMatch && listNamesRule(disableMatch[2], acceptedTokens)) {
34800
+ isRangeOpen = true;
34801
+ continue;
34802
+ }
34803
+ const enableMatch = line.match(FOREIGN_BLOCK_ENABLE_PATTERN);
34804
+ if (enableMatch) {
34805
+ const enabledRules = tokenizeRuleList(enableMatch[1]);
34806
+ if (enabledRules.length === 0 || enabledRules.some((rule) => acceptedTokens.has(rule))) isRangeOpen = false;
34807
+ }
34808
+ }
34809
+ return isRangeOpen;
34810
+ };
34811
+ const isCompilerRuleForeignDisabled = (lines, diagnosticLineIndex, ruleId) => {
34812
+ if (!ruleId.startsWith("react-hooks-js/")) return false;
34813
+ const acceptedTokens = buildAcceptedTokens(ruleId);
34814
+ return isInlineDisabled(lines, diagnosticLineIndex, acceptedTokens) || isBlockDisabled(lines, diagnosticLineIndex, acceptedTokens);
34815
+ };
34649
34816
  const JSX_OPENER_TAG_PATTERN = /<[A-Za-z][\w.]*/g;
34650
34817
  const JSX_TAG_NAME_FOLLOW = /[A-Za-z]/;
34651
34818
  const isOpenerMatchInsideLineComment = (line, openerCharIndex) => {
@@ -34789,6 +34956,11 @@ const evaluateSuppression = (lines, diagnosticLineIndex, ruleId) => {
34789
34956
  isSuppressed: true,
34790
34957
  nearMissHint: null
34791
34958
  };
34959
+ if (isCompilerRuleForeignDisabled(lines, diagnosticLineIndex, ruleId)) return {
34960
+ isSuppressed: true,
34961
+ nearMissHint: null,
34962
+ isForeignDirective: true
34963
+ };
34792
34964
  const directComments = findStackedDisableCommentsAbove(lines, diagnosticLineIndex);
34793
34965
  if (hasChainSuppressor(directComments, ruleId)) return {
34794
34966
  isSuppressed: true,
@@ -35074,12 +35246,18 @@ const collectStringSet = (values) => {
35074
35246
  return new Set(values.filter((value) => typeof value === "string"));
35075
35247
  };
35076
35248
  const JSX_NO_UNDEF_IDENTIFIER_PATTERN = /^`([^`]+)`/;
35249
+ const NODE_CLI_PATH_SEGMENTS = new Set([
35250
+ "scripts",
35251
+ "bin",
35252
+ "tools"
35253
+ ]);
35254
+ const hasNodeCliPathSegment = (filePath) => filePath.split(/[\\/]/).slice(0, -1).some((segment) => NODE_CLI_PATH_SEGMENTS.has(segment));
35077
35255
  /**
35078
35256
  * Pre-compiles every stateful filter and returns a single
35079
35257
  * `apply(diagnostic)` closure that runs (in order):
35080
35258
  *
35081
35259
  * 1. auto-suppress (test-noise rules in test files; `migration-hint`
35082
- * wins over `test-noise`)
35260
+ * wins over `test-noise`; `no-console` on Node CLI scripts)
35083
35261
  * 2. severity overrides (top-level `rules` / `categories`, with
35084
35262
  * `"off"` dropping)
35085
35263
  * 3. warning suppression (only when `showWarnings` is false: drops every
@@ -35177,6 +35355,12 @@ const buildDiagnosticPipeline = (input) => {
35177
35355
  if (hasRawTextWrappers && isInsideStringOnlyWrapper(lines, diagnostic.line, diagnostic.column, rawTextWrapperComponentNames)) return true;
35178
35356
  return false;
35179
35357
  };
35358
+ const isNoConsoleOnNodeCliScript = (diagnostic) => {
35359
+ if (diagnostic.rule !== "no-console") return false;
35360
+ if (hasNodeCliPathSegment(diagnostic.filePath)) return true;
35361
+ const lines = getFileLines(diagnostic.filePath);
35362
+ return lines !== null && Boolean(lines[0]?.startsWith("#!"));
35363
+ };
35180
35364
  const isJsxNoUndefSuppressedByConfig = (diagnostic) => {
35181
35365
  if (!hasRuntimeGlobals) return false;
35182
35366
  if (diagnostic.rule !== "jsx-no-undef") return false;
@@ -35186,6 +35370,7 @@ const buildDiagnosticPipeline = (input) => {
35186
35370
  return {
35187
35371
  apply: (diagnostic) => {
35188
35372
  if (shouldAutoSuppress(diagnostic)) return null;
35373
+ if (isNoConsoleOnNodeCliScript(diagnostic)) return null;
35189
35374
  let current = diagnostic;
35190
35375
  let explicitSeverityOverride;
35191
35376
  let explicitRuleOverride;
@@ -35215,7 +35400,7 @@ const buildDiagnosticPipeline = (input) => {
35215
35400
  if (lines) {
35216
35401
  const ruleIdentifier = `${current.plugin}/${current.rule}`;
35217
35402
  const evaluation = evaluateSuppression(lines, current.line - 1, ruleIdentifier);
35218
- if (evaluation.isSuppressed) return suppress(current, "inline");
35403
+ if (evaluation.isSuppressed) return suppress(current, evaluation.isForeignDirective ? "foreign-inline" : "inline");
35219
35404
  if (evaluation.nearMissHint) current = {
35220
35405
  ...current,
35221
35406
  suppressionHint: evaluation.nearMissHint
@@ -37024,64 +37209,64 @@ function* collectSecurityScanFiles(rootDirectory) {
37024
37209
  }
37025
37210
  }
37026
37211
  }
37027
- /**
37028
- * Whether project discovery resolved a React-compatible runtime (React or
37029
- * Preact, which ships the same hooks + JSX model). This is the same
37030
- * predicate that grants the `react` capability, so `false` means every
37031
- * React-runtime rule family was gated off for the project.
37032
- */
37033
- const hasReactRuntime = (project) => project.reactVersion !== null || project.preactVersion !== null;
37212
+ const CLIENT_ONLY_FRAMEWORKS = new Set([
37213
+ "vite",
37214
+ "cra",
37215
+ "gatsby",
37216
+ "react-native",
37217
+ "expo"
37218
+ ]);
37219
+ const addMajorLadder = (capabilities, name, major, earliest, latest) => {
37220
+ if (major === null) return;
37221
+ const cappedMajor = Math.min(major, latest);
37222
+ for (let candidate = earliest; candidate <= cappedMajor; candidate += 1) capabilities.add(`${name}:${candidate}`);
37223
+ };
37034
37224
  const buildCapabilities = (project) => {
37035
37225
  const capabilities = /* @__PURE__ */ new Set();
37036
37226
  capabilities.add(project.framework);
37037
- if (hasReactRuntime(project)) capabilities.add("react");
37227
+ if (project.reactVersion !== null || project.preactVersion !== null) capabilities.add("react");
37038
37228
  if (project.framework === "expo" || project.framework === "react-native" || project.hasReactNativeWorkspace) capabilities.add("react-native");
37039
37229
  if (project.expoVersion !== null) capabilities.add("expo");
37230
+ if ((project.framework === "nextjs" || project.framework === "tanstack-start" || project.framework === "remix") && !project.isStaticExport) capabilities.add("server-actions");
37231
+ if (CLIENT_ONLY_FRAMEWORKS.has(project.framework)) capabilities.add("client-only");
37232
+ if (project.isStaticExport) capabilities.add("nextjs:static-export");
37040
37233
  if (project.nextjsMajorVersion !== null && project.nextjsMajorVersion >= 15) capabilities.add("nextjs:15");
37041
- const reactMajor = project.reactMajorVersion;
37042
- if (reactMajor !== null) {
37043
- const cappedReactMajor = Math.min(reactMajor, 30);
37044
- for (let major = 17; major <= cappedReactMajor; major++) capabilities.add(`react:${major}`);
37045
- if (reactMajor >= 19) {
37046
- if (isReactAtLeast(parseReactMajorMinor(project.reactVersion), {
37047
- major: 19,
37048
- minor: 2
37049
- })) capabilities.add("react:19.2");
37050
- }
37051
- }
37052
- if (project.tailwindVersion !== null) {
37053
- capabilities.add("tailwind");
37054
- if (isTailwindAtLeast(parseTailwindMajorMinor(project.tailwindVersion), {
37055
- major: 3,
37056
- minor: 4
37057
- })) capabilities.add("tailwind:3.4");
37058
- }
37059
- if (project.zodVersion !== null) {
37060
- capabilities.add("zod");
37061
- if (project.zodMajorVersion !== null && project.zodMajorVersion >= 4) capabilities.add("zod:4");
37062
- }
37234
+ addMajorLadder(capabilities, "react", project.reactMajorVersion, 17, 30);
37235
+ if (project.reactMajorVersion !== null && project.reactMajorVersion >= 19 && isMajorMinorAtLeast(parseReactMajorMinor(project.reactVersion), {
37236
+ major: 19,
37237
+ minor: 2
37238
+ })) capabilities.add("react:19.2");
37239
+ if (project.tailwindVersion !== null) capabilities.add("tailwind");
37240
+ if (project.tailwindVersion !== null && isMajorMinorAtLeast(parseTailwindMajorMinor(project.tailwindVersion), {
37241
+ major: 3,
37242
+ minor: 4
37243
+ })) capabilities.add("tailwind:3.4");
37244
+ if (project.zodVersion !== null) capabilities.add("zod");
37245
+ if (project.zodMajorVersion !== null && project.zodMajorVersion >= 4) capabilities.add("zod:4");
37063
37246
  if (project.isPreES2023Target) capabilities.add("pre-es2023");
37064
37247
  if (project.hasReactCompiler) capabilities.add("react-compiler");
37065
37248
  if (project.hasTanStackQuery) capabilities.add("tanstack-query");
37066
37249
  if (project.hasTypeScript) capabilities.add("typescript");
37067
- if (project.preactVersion !== null) {
37068
- capabilities.add("preact");
37069
- const preactMajor = project.preactMajorVersion;
37070
- if (preactMajor !== null) {
37071
- const cappedPreactMajor = Math.min(preactMajor, 20);
37072
- for (let major = 10; major <= cappedPreactMajor; major++) capabilities.add(`preact:${major}`);
37073
- }
37074
- if (project.reactVersion === null) capabilities.add("pure-preact");
37075
- }
37250
+ if (project.preactVersion !== null) capabilities.add("preact");
37251
+ addMajorLadder(capabilities, "preact", project.preactMajorVersion, 10, 20);
37252
+ if (project.preactVersion !== null && project.reactVersion === null) capabilities.add("pure-preact");
37253
+ return capabilities;
37254
+ };
37255
+ const capabilitiesByProject = /* @__PURE__ */ new WeakMap();
37256
+ const getCapabilities = (project) => {
37257
+ const cached = capabilitiesByProject.get(project);
37258
+ if (cached !== void 0) return cached;
37259
+ const capabilities = buildCapabilities(project);
37260
+ capabilitiesByProject.set(project, capabilities);
37076
37261
  return capabilities;
37077
37262
  };
37078
- const shouldEnableRule = (requires, tags, capabilities, ignoredTags, disabledBy) => {
37263
+ const shouldEnableRule = (requires, tags, capabilities, ignoredTags, disabledWhen) => {
37079
37264
  if (requires) {
37080
37265
  for (const capability of requires) if (!capabilities.has(capability)) return false;
37081
37266
  }
37082
37267
  if (tags?.includes("react-jsx-only") && !capabilities.has("react")) return false;
37083
- if (disabledBy) {
37084
- for (const capability of disabledBy) if (capabilities.has(capability)) return false;
37268
+ if (disabledWhen) {
37269
+ for (const capability of disabledWhen) if (capabilities.has(capability)) return false;
37085
37270
  }
37086
37271
  if (tags) {
37087
37272
  for (const tag of tags) if (ignoredTags.has(tag)) return false;
@@ -37092,14 +37277,14 @@ const yieldToEventLoop = () => new Promise((resolve) => {
37092
37277
  setImmediate(resolve);
37093
37278
  });
37094
37279
  const createSecurityScanSession = (rootDirectory, options) => {
37095
- const capabilities = options.project ? buildCapabilities(options.project) : /* @__PURE__ */ new Set();
37280
+ const capabilities = options.project ? getCapabilities(options.project) : /* @__PURE__ */ new Set();
37096
37281
  const ignoredTags = options.ignoredTags ?? /* @__PURE__ */ new Set();
37097
37282
  const enabledScanRules = REACT_DOCTOR_RULES.flatMap((entry) => {
37098
37283
  const rule = entry.rule;
37099
37284
  const scan = rule.scan;
37100
37285
  if (typeof scan !== "function") return [];
37101
37286
  if (rule.defaultEnabled === false) return [];
37102
- if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.disabledBy)) return [];
37287
+ if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.disabledWhen)) return [];
37103
37288
  return [{
37104
37289
  entry,
37105
37290
  scan,
@@ -37119,6 +37304,7 @@ const createSecurityScanSession = (rootDirectory, options) => {
37119
37304
  return status === true;
37120
37305
  };
37121
37306
  const scanFileByRule = function* (file) {
37307
+ if (file === null) return;
37122
37308
  for (const { entry, scan, committedFilesOnly } of enabledScanRules) {
37123
37309
  for (const finding of scan(file)) {
37124
37310
  if (committedFilesOnly && isFileGitIgnored(file)) continue;
@@ -38564,7 +38750,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
38564
38750
  })),
38565
38751
  showStagedContent: (directory, relativePath, options) => runCommand({
38566
38752
  command: "git",
38567
- args: ["show", `:${relativePath}`],
38753
+ args: ["show", `:./${relativePath}`],
38568
38754
  directory,
38569
38755
  maxStdoutBytes: options?.maxBufferBytes
38570
38756
  }).pipe(map$3((result) => result.status === 0 ? result.stdout : null)),
@@ -38999,7 +39185,7 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
38999
39185
  const reactCompilerRules = reactHooksJsPlugin ? applyRuleSeverityControls(filterRulesToAvailable(REACT_COMPILER_RULES, "react-hooks-js", reactHooksJsPlugin.availableRuleNames), severityControls) : {};
39000
39186
  const jsPlugins = [];
39001
39187
  if (reactHooksJsPlugin) jsPlugins.push(reactHooksJsPlugin.entry);
39002
- const capabilities = buildCapabilities(project);
39188
+ const capabilities = getCapabilities(project);
39003
39189
  const enabledReactDoctorRules = {};
39004
39190
  for (const registryEntry of REACT_DOCTOR_RULES) {
39005
39191
  const rule = reactDoctorPlugin.rules[registryEntry.id];
@@ -39010,7 +39196,7 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
39010
39196
  if (rule.scan !== void 0) continue;
39011
39197
  if (customRulesOnly && registryEntry.originallyExternal) continue;
39012
39198
  if (rule.framework !== "global" && !rule.requires) continue;
39013
- if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.disabledBy)) continue;
39199
+ if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.disabledWhen)) continue;
39014
39200
  const explicitRuleOverride = resolveRuleSeverityOverride({ ruleKey: registryEntry.key }, severityControls);
39015
39201
  if (rule.defaultEnabled === false && explicitRuleOverride === void 0) continue;
39016
39202
  const severity = resolveRuleSeverityOverride({
@@ -39041,6 +39227,7 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
39041
39227
  settings: { "react-doctor": {
39042
39228
  framework: project.framework,
39043
39229
  rootDirectory: resolveSettingsRootDirectory(project.rootDirectory),
39230
+ capabilities: [...capabilities].sort(),
39044
39231
  ...project.shopifyFlashListMajorVersion !== null ? { shopifyFlashListMajorVersion: project.shopifyFlashListMajorVersion } : {},
39045
39232
  ...serverAuthFunctionNames && serverAuthFunctionNames.length > 0 ? { serverAuthFunctionNames: [...serverAuthFunctionNames] } : {}
39046
39233
  } },
@@ -39331,25 +39518,18 @@ const mapWithConcurrency = async (items, concurrency, task) => {
39331
39518
  if (errors.length > 0) throw errors[0];
39332
39519
  return results;
39333
39520
  };
39334
- const getPublicEnvPrefix = (framework) => {
39335
- switch (framework) {
39336
- case "nextjs": return "NEXT_PUBLIC_*";
39337
- case "vite":
39338
- case "tanstack-start": return "VITE_*";
39339
- case "cra": return "REACT_APP_*";
39340
- case "gatsby": return "GATSBY_*";
39341
- default: return null;
39342
- }
39343
- };
39344
- const buildNoSecretsRecommendation = (project, fallbackRecommendation) => {
39345
- const publicEnvPrefix = getPublicEnvPrefix(project.framework);
39346
- if (!publicEnvPrefix) return fallbackRecommendation;
39347
- 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`;
39348
- };
39349
39521
  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";
39522
+ const ACCESSORS_MIN_VERSION = "3.15.0";
39523
+ const supportsAccessors = (reanimatedVersion) => {
39524
+ if (reanimatedVersion === null) return true;
39525
+ const lowerBound = import_semver.validRange(reanimatedVersion) !== null ? import_semver.minVersion(reanimatedVersion) : import_semver.coerce(reanimatedVersion);
39526
+ if (lowerBound === null) return true;
39527
+ return import_semver.gte(lowerBound, ACCESSORS_MIN_VERSION);
39528
+ };
39350
39529
  const appendReanimatedSharedValueHint = (help, rule, project) => {
39351
39530
  if (rule !== "immutability") return help;
39352
39531
  if (!project.hasReanimated) return help;
39532
+ if (!supportsAccessors(project.reanimatedVersion)) return help;
39353
39533
  if (!help) return REANIMATED_SHARED_VALUE_HINT;
39354
39534
  return `${help}\n\n${REANIMATED_SHARED_VALUE_HINT}`;
39355
39535
  };
@@ -39511,13 +39691,13 @@ const REACT_USE_BINDING_RESOLUTION = {
39511
39691
  isReactUseBinding: true,
39512
39692
  isReactNamespaceBinding: false
39513
39693
  };
39514
- const getScriptKind = (filename) => {
39694
+ const getScriptKind$2 = (filename) => {
39515
39695
  if (filename.endsWith(".tsx")) return ts.ScriptKind.TSX;
39516
39696
  if (filename.endsWith(".jsx")) return ts.ScriptKind.JSX;
39517
39697
  if (filename.endsWith(".ts")) return ts.ScriptKind.TS;
39518
39698
  return ts.ScriptKind.JS;
39519
39699
  };
39520
- const getUtf16Offset = (sourceText, utf8Offset) => Buffer.from(sourceText).subarray(0, utf8Offset).toString("utf8").length;
39700
+ const getUtf16Offset$1 = (sourceText, utf8Offset) => Buffer.from(sourceText).subarray(0, utf8Offset).toString("utf8").length;
39521
39701
  const unwrapExpression = (expression) => {
39522
39702
  let currentExpression = expression;
39523
39703
  while (ts.isParenthesizedExpression(currentExpression) || ts.isAsExpression(currentExpression) || ts.isSatisfiesExpression(currentExpression) || ts.isNonNullExpression(currentExpression) || ts.isTypeAssertionExpression(currentExpression)) currentExpression = currentExpression.expression;
@@ -39766,8 +39946,8 @@ const findUseCallIdentifier = (sourceFile, useOffset) => {
39766
39946
  return matchedIdentifier;
39767
39947
  };
39768
39948
  const resolveUseCallBinding = (sourceText, filename, utf8Offset) => {
39769
- const sourceFile = ts.createSourceFile(filename, sourceText, ts.ScriptTarget.Latest, true, getScriptKind(filename));
39770
- const useIdentifier = findUseCallIdentifier(sourceFile, getUtf16Offset(sourceText, utf8Offset));
39949
+ const sourceFile = ts.createSourceFile(filename, sourceText, ts.ScriptTarget.Latest, true, getScriptKind$2(filename));
39950
+ const useIdentifier = findUseCallIdentifier(sourceFile, getUtf16Offset$1(sourceText, utf8Offset));
39771
39951
  if (!useIdentifier) return null;
39772
39952
  return resolveIdentifierBinding(useIdentifier, collectReactImportBindings(sourceFile), sourceFile);
39773
39953
  };
@@ -39788,6 +39968,201 @@ const shouldSuppressLocalUseHookDiagnostic = (diagnostic, rootDirectory) => {
39788
39968
  const bindingResolution = resolveUseCallBinding(sourceText, absolutePath, primaryLabel.span.offset);
39789
39969
  return bindingResolution !== null && !bindingResolution.isReactUseBinding;
39790
39970
  };
39971
+ const SUPPRESSED_COMPILER_CODES = new Set(["react-hooks-js(immutability)", "react-hooks-js(refs)"]);
39972
+ const WORKLET_DIRECTIVE = "worklet";
39973
+ const WORKLET_ACCEPTING_CALLEES = new Set([
39974
+ "useAnimatedStyle",
39975
+ "useAnimatedProps",
39976
+ "useDerivedValue",
39977
+ "useAnimatedReaction",
39978
+ "useAnimatedScrollHandler",
39979
+ "useAnimatedGestureHandler",
39980
+ "useFrameCallback",
39981
+ "runOnUI",
39982
+ "executeOnUIRuntimeSync"
39983
+ ]);
39984
+ const GESTURE_ROOT_IDENTIFIER = "Gesture";
39985
+ const getScriptKind$1 = (filename) => {
39986
+ if (filename.endsWith(".tsx")) return ts.ScriptKind.TSX;
39987
+ if (filename.endsWith(".jsx")) return ts.ScriptKind.JSX;
39988
+ if (filename.endsWith(".ts")) return ts.ScriptKind.TS;
39989
+ return ts.ScriptKind.JS;
39990
+ };
39991
+ const getUtf16Offset = (sourceText, utf8Offset) => Buffer.from(sourceText).subarray(0, utf8Offset).toString("utf8").length;
39992
+ const hasWorkletDirective = (node) => {
39993
+ if (!ts.isFunctionDeclaration(node) && !ts.isFunctionExpression(node) && !ts.isArrowFunction(node) && !ts.isMethodDeclaration(node)) return false;
39994
+ const body = node.body;
39995
+ if (!body || !ts.isBlock(body)) return false;
39996
+ for (const statement of body.statements) {
39997
+ if (!ts.isExpressionStatement(statement)) return false;
39998
+ if (!ts.isStringLiteral(statement.expression)) return false;
39999
+ if (statement.expression.text === WORKLET_DIRECTIVE) return true;
40000
+ }
40001
+ return false;
40002
+ };
40003
+ const rootIdentifierOfCallChain = (expression) => {
40004
+ let currentExpression = expression;
40005
+ while (true) {
40006
+ if (ts.isIdentifier(currentExpression)) return currentExpression.text;
40007
+ if (ts.isPropertyAccessExpression(currentExpression)) {
40008
+ currentExpression = currentExpression.expression;
40009
+ continue;
40010
+ }
40011
+ if (ts.isCallExpression(currentExpression)) {
40012
+ currentExpression = currentExpression.expression;
40013
+ continue;
40014
+ }
40015
+ return null;
40016
+ }
40017
+ };
40018
+ const isWorkletAcceptingCall = (callExpression) => {
40019
+ const callee = callExpression.expression;
40020
+ if (ts.isIdentifier(callee)) return WORKLET_ACCEPTING_CALLEES.has(callee.text);
40021
+ if (ts.isPropertyAccessExpression(callee)) {
40022
+ if (WORKLET_ACCEPTING_CALLEES.has(callee.name.text)) return true;
40023
+ return rootIdentifierOfCallChain(callee) === GESTURE_ROOT_IDENTIFIER;
40024
+ }
40025
+ return false;
40026
+ };
40027
+ const isFunctionWorklet = (functionNode) => {
40028
+ if (hasWorkletDirective(functionNode)) return true;
40029
+ const parent = functionNode.parent;
40030
+ return ts.isCallExpression(parent) && parent.arguments.some((argument) => argument === functionNode) && isWorkletAcceptingCall(parent);
40031
+ };
40032
+ const findNodeAtOffset = (sourceFile, targetOffset) => {
40033
+ let matchedNode = null;
40034
+ const visit = (node) => {
40035
+ if (node.getStart(sourceFile) > targetOffset || node.getEnd() <= targetOffset) return;
40036
+ matchedNode = node;
40037
+ ts.forEachChild(node, visit);
40038
+ };
40039
+ visit(sourceFile);
40040
+ return matchedNode;
40041
+ };
40042
+ const isOffsetInsideWorklet = (sourceFile, targetOffset) => {
40043
+ let currentNode = findNodeAtOffset(sourceFile, targetOffset);
40044
+ while (currentNode) {
40045
+ if (ts.isFunctionLike(currentNode) && isFunctionWorklet(currentNode)) return true;
40046
+ currentNode = currentNode.parent;
40047
+ }
40048
+ return false;
40049
+ };
40050
+ const shouldSuppressCompilerFindingInWorklet = (diagnostic, project, rootDirectory) => {
40051
+ if (!SUPPRESSED_COMPILER_CODES.has(diagnostic.code)) return false;
40052
+ if (!project.hasReanimated) return false;
40053
+ const primaryLabel = diagnostic.labels[0];
40054
+ if (!primaryLabel) return false;
40055
+ const absolutePath = Path.isAbsolute(diagnostic.filename) ? diagnostic.filename : Path.join(rootDirectory, diagnostic.filename);
40056
+ let sourceText;
40057
+ try {
40058
+ sourceText = NFS.readFileSync(absolutePath, "utf-8");
40059
+ } catch {
40060
+ return false;
40061
+ }
40062
+ return isOffsetInsideWorklet(ts.createSourceFile(absolutePath, sourceText, ts.ScriptTarget.Latest, true, getScriptKind$1(absolutePath)), getUtf16Offset(sourceText, primaryLabel.span.offset));
40063
+ };
40064
+ const MANUAL_MEMOIZATION_PLUGIN = "react-doctor";
40065
+ const MANUAL_MEMOIZATION_RULE = "react-compiler-no-manual-memoization";
40066
+ const REACT_COMPILER_PLUGIN = "react-hooks-js";
40067
+ const getScriptKind = (filename) => {
40068
+ if (filename.endsWith(".tsx")) return ts.ScriptKind.TSX;
40069
+ if (filename.endsWith(".jsx")) return ts.ScriptKind.JSX;
40070
+ if (filename.endsWith(".ts")) return ts.ScriptKind.TS;
40071
+ return ts.ScriptKind.JS;
40072
+ };
40073
+ const isManualMemoizationDiagnostic = (diagnostic) => diagnostic.plugin === MANUAL_MEMOIZATION_PLUGIN && diagnostic.rule === MANUAL_MEMOIZATION_RULE;
40074
+ const nodeLineRange = (sourceFile, node) => ({
40075
+ startLine: sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1,
40076
+ endLine: sourceFile.getLineAndCharacterOfPosition(node.getEnd()).line + 1
40077
+ });
40078
+ const findOutermostEnclosingFunctionRange = (sourceFile, targetLine) => {
40079
+ let foundRange = null;
40080
+ const visit = (node) => {
40081
+ if (foundRange) return;
40082
+ if (ts.isFunctionLike(node)) {
40083
+ const range = nodeLineRange(sourceFile, node);
40084
+ if (range.startLine <= targetLine && targetLine <= range.endLine) foundRange = range;
40085
+ return;
40086
+ }
40087
+ ts.forEachChild(node, visit);
40088
+ };
40089
+ visit(sourceFile);
40090
+ return foundRange;
40091
+ };
40092
+ const NEWLINE_BYTE = 10;
40093
+ const lineOfUtf8Offset = (sourceBuffer, utf8Offset) => {
40094
+ let lineNumber = 1;
40095
+ const scanEnd = Math.min(utf8Offset, sourceBuffer.length);
40096
+ for (let byteIndex = 0; byteIndex < scanEnd; byteIndex++) if (sourceBuffer[byteIndex] === NEWLINE_BYTE) lineNumber++;
40097
+ return lineNumber;
40098
+ };
40099
+ /**
40100
+ * Drops `react-doctor/react-compiler-no-manual-memoization` diagnostics
40101
+ * whose enclosing function also carries a `react-hooks-js` (React
40102
+ * Compiler) diagnostic. Every react-hooks-js finding marks a function the
40103
+ * compiler could NOT compile (a bail-out / skip / preserve-manual-
40104
+ * memoization failure), so "React Compiler already caches every value
40105
+ * here" is false for that function and the manual memoization is
40106
+ * load-bearing, not dead weight.
40107
+ *
40108
+ * Matching is per enclosing function, not per file: a bailed-out
40109
+ * component must not silence redundant-memo findings in a sibling
40110
+ * component the compiler did optimize.
40111
+ *
40112
+ * Module-scope `memo(() => …)` wrappers have no enclosing function; for
40113
+ * those the memo call's own span is the containment range, so a bail-out
40114
+ * inside the inline-wrapped component still suppresses. Every failure
40115
+ * path (unreadable file, missing span) fails open and keeps the
40116
+ * diagnostic.
40117
+ */
40118
+ const suppressMemoizationInBailedOutFunctions = (diagnostics, rootDirectory) => {
40119
+ const compilerDiagnosticLinesByFile = /* @__PURE__ */ new Map();
40120
+ let hasManualMemoizationDiagnostic = false;
40121
+ for (const diagnostic of diagnostics) {
40122
+ if (isManualMemoizationDiagnostic(diagnostic)) {
40123
+ hasManualMemoizationDiagnostic = true;
40124
+ continue;
40125
+ }
40126
+ if (diagnostic.plugin !== REACT_COMPILER_PLUGIN || diagnostic.line <= 0) continue;
40127
+ const lines = compilerDiagnosticLinesByFile.get(diagnostic.filePath);
40128
+ if (lines) lines.push(diagnostic.line);
40129
+ else compilerDiagnosticLinesByFile.set(diagnostic.filePath, [diagnostic.line]);
40130
+ }
40131
+ if (!hasManualMemoizationDiagnostic || compilerDiagnosticLinesByFile.size === 0) return diagnostics;
40132
+ const parsedFileCache = /* @__PURE__ */ new Map();
40133
+ const readAndParse = (filePath) => {
40134
+ const cached = parsedFileCache.get(filePath);
40135
+ if (cached !== void 0) return cached;
40136
+ const absolutePath = Path.isAbsolute(filePath) ? filePath : Path.resolve(rootDirectory || ".", filePath);
40137
+ let parsed;
40138
+ try {
40139
+ const buffer = NFS.readFileSync(absolutePath);
40140
+ parsed = {
40141
+ sourceFile: ts.createSourceFile(absolutePath, buffer.toString("utf8"), ts.ScriptTarget.Latest, true, getScriptKind(absolutePath)),
40142
+ buffer
40143
+ };
40144
+ } catch {
40145
+ parsed = null;
40146
+ }
40147
+ parsedFileCache.set(filePath, parsed);
40148
+ return parsed;
40149
+ };
40150
+ const isInsideBailedOutFunction = (diagnostic) => {
40151
+ const compilerLines = compilerDiagnosticLinesByFile.get(diagnostic.filePath);
40152
+ if (!compilerLines || diagnostic.line <= 0) return false;
40153
+ const parsed = readAndParse(diagnostic.filePath);
40154
+ if (!parsed) return false;
40155
+ let containmentRange = findOutermostEnclosingFunctionRange(parsed.sourceFile, diagnostic.line);
40156
+ if (!containmentRange && diagnostic.offset !== void 0 && diagnostic.length !== void 0) containmentRange = {
40157
+ startLine: diagnostic.line,
40158
+ endLine: lineOfUtf8Offset(parsed.buffer, diagnostic.offset + diagnostic.length)
40159
+ };
40160
+ if (!containmentRange) return false;
40161
+ const { startLine, endLine } = containmentRange;
40162
+ return compilerLines.some((compilerLine) => startLine <= compilerLine && compilerLine <= endLine);
40163
+ };
40164
+ return diagnostics.filter((diagnostic) => !isManualMemoizationDiagnostic(diagnostic) || !isInsideBailedOutFunction(diagnostic));
40165
+ };
39791
40166
  const FILEPATH_WITH_LOCATION_PATTERN = /\S+\.\w+:\d+:\d+[\s\S]*$/;
39792
40167
  const LEADING_SEVERITY_LABEL_PATTERN = /^(?:Error|Warning):\s*/;
39793
40168
  const TRAILING_PERIOD_PATTERN = /\.$/;
@@ -39823,8 +40198,14 @@ const PLUGIN_CATEGORY_MAP = {
39823
40198
  };
39824
40199
  const lookupOwnString = (record, key) => Object.hasOwn(record, key) ? record[key] : void 0;
39825
40200
  const getRuleRecommendation = (ruleName, project) => {
39826
- if (ruleName === "no-secrets-in-client-code") return buildNoSecretsRecommendation(project, reactDoctorPlugin.rules["no-secrets-in-client-code"]?.recommendation ?? "Move secrets to server-only code");
39827
- return reactDoctorPlugin.rules[ruleName]?.recommendation;
40201
+ const rule = reactDoctorPlugin.rules[ruleName];
40202
+ if (!rule) return void 0;
40203
+ if (rule.recommendationFor) {
40204
+ const capabilities = getCapabilities(project);
40205
+ const conditionalRecommendation = rule.recommendationFor((capability) => capabilities.has(capability));
40206
+ if (conditionalRecommendation !== void 0) return conditionalRecommendation;
40207
+ }
40208
+ return rule.recommendation;
39828
40209
  };
39829
40210
  const getRuleCategory = (ruleName) => reactDoctorPlugin.rules[ruleName]?.category;
39830
40211
  const getRuleTitle = (ruleName) => reactDoctorPlugin.rules[ruleName]?.title;
@@ -39927,7 +40308,7 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
39927
40308
  minifiedFileCache.set(absolutePath, minified);
39928
40309
  return minified;
39929
40310
  };
39930
- return parsed.diagnostics.filter((diagnostic) => diagnostic.code && isLintableSourceFile(diagnostic.filename) && !isMinifiedDiagnosticFile(diagnostic.filename) && !shouldSuppressLocalUseHookDiagnostic(diagnostic, rootDirectory)).map((diagnostic) => {
40311
+ return suppressMemoizationInBailedOutFunctions(parsed.diagnostics.filter((diagnostic) => diagnostic.code && isLintableSourceFile(diagnostic.filename) && !isMinifiedDiagnosticFile(diagnostic.filename) && !shouldSuppressLocalUseHookDiagnostic(diagnostic, rootDirectory) && !shouldSuppressCompilerFindingInWorklet(diagnostic, project, rootDirectory)).map((diagnostic) => {
39931
40312
  const { plugin, rule } = parseRuleCode(diagnostic.code);
39932
40313
  const primaryLabel = diagnostic.labels[0];
39933
40314
  const cleaned = cleanDiagnosticMessage(diagnostic.message, diagnostic.help, plugin, rule, project);
@@ -39954,7 +40335,7 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
39954
40335
  ...resolveMatchByOccurrence(rule, category) ? { matchByOccurrence: true } : {},
39955
40336
  ...relatedLocations.length > 0 ? { relatedLocations } : {}
39956
40337
  };
39957
- });
40338
+ }), rootDirectory);
39958
40339
  };
39959
40340
  const buildOxlintChildEnv = (sourceEnv) => {
39960
40341
  const childEnv = {};
@@ -40465,6 +40846,7 @@ const runOxlint = async (options) => {
40465
40846
  const serverAuthFunctionNames = Array.isArray(userConfig?.serverAuthFunctionNames) ? userConfig.serverAuthFunctionNames.filter((entry) => typeof entry === "string" && entry.length > 0) : void 0;
40466
40847
  const severityControls = buildRuleSeverityControls(userConfig);
40467
40848
  validateRuleRegistration();
40849
+ resetManifestCaches();
40468
40850
  if (includePaths !== void 0 && includePaths.length === 0) return [];
40469
40851
  const pluginPath = resolvePluginPath();
40470
40852
  const extendsPaths = (adoptExistingLintConfig && !customRulesOnly ? detectUserLintConfigPaths(rootDirectory) : []).filter(canOxlintExtendConfig);
@@ -41127,9 +41509,10 @@ const CONTEXT_AXES = [
41127
41509
  }
41128
41510
  ];
41129
41511
  const SCORE_AXES = [...GATED_AXES, ...CONTEXT_AXES];
41130
- const worstGatedAxis = (score) => {
41131
- let worst = GATED_AXES[0];
41132
- for (const axis of GATED_AXES) if (score[axis.key] < score[worst.key]) worst = axis;
41512
+ const gatedAxesForSection = (section) => section === "devDependencies" ? GATED_AXES.filter((axis) => axis.key !== "vulnerability") : GATED_AXES;
41513
+ const worstGatedAxis = (score, gatedAxes) => {
41514
+ let worst = gatedAxes[0];
41515
+ for (const axis of gatedAxes) if (score[axis.key] < score[worst.key]) worst = axis;
41133
41516
  return worst;
41134
41517
  };
41135
41518
  const clampScore = (value) => {
@@ -41153,6 +41536,69 @@ const resolveConcreteVersion = (spec) => {
41153
41536
  if (range === null || range === "*") return null;
41154
41537
  return import_semver.minVersion(trimmed)?.version ?? null;
41155
41538
  };
41539
+ const readTextFileOrNull = (filePath) => {
41540
+ try {
41541
+ return NFS.readFileSync(filePath, "utf-8");
41542
+ } catch {
41543
+ return null;
41544
+ }
41545
+ };
41546
+ const buildNpmLockLookup = (lockText) => {
41547
+ let parsed;
41548
+ try {
41549
+ parsed = JSON.parse(lockText);
41550
+ } catch {
41551
+ return null;
41552
+ }
41553
+ if (typeof parsed !== "object" || parsed === null) return null;
41554
+ const lock = parsed;
41555
+ return (name) => {
41556
+ const entry = lock.packages?.[`node_modules/${name}`] ?? lock.dependencies?.[name];
41557
+ return typeof entry?.version === "string" ? entry.version : null;
41558
+ };
41559
+ };
41560
+ const buildYarnLockLookup = (lockText) => {
41561
+ const lines = lockText.split(/\r?\n/);
41562
+ return (name, spec) => {
41563
+ const descriptor = `${name}@${spec}`;
41564
+ const berryDescriptor = `${name}@npm:${spec}`;
41565
+ for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
41566
+ const line = lines[lineIndex];
41567
+ if (line.startsWith(" ") || line.startsWith("#") || !line.includes(":")) continue;
41568
+ const descriptors = line.replace(/:\s*$/, "").split(",").map((part) => part.trim().replace(/^"|"$/g, ""));
41569
+ if (!descriptors.includes(descriptor) && !descriptors.includes(berryDescriptor)) continue;
41570
+ for (let bodyIndex = lineIndex + 1; bodyIndex < lines.length && (lines[bodyIndex].startsWith(" ") || lines[bodyIndex] === ""); bodyIndex += 1) {
41571
+ const versionMatch = /^\s+version:?\s+"?([^"\s]+)"?/.exec(lines[bodyIndex]);
41572
+ if (versionMatch) return versionMatch[1];
41573
+ }
41574
+ return null;
41575
+ }
41576
+ return null;
41577
+ };
41578
+ };
41579
+ const buildPnpmLockLookup = (lockText) => {
41580
+ return (name) => {
41581
+ const entryPattern = new RegExp(`^ {4,8}['"]?${name.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&")}['"]?:\\s*$`);
41582
+ const lines = lockText.split(/\r?\n/);
41583
+ for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
41584
+ if (!entryPattern.test(lines[lineIndex])) continue;
41585
+ for (let bodyIndex = lineIndex + 1; bodyIndex < lines.length && /^ {5,}\S/.test(lines[bodyIndex]); bodyIndex += 1) {
41586
+ const versionMatch = /^\s+version:\s+['"]?([^'"\s(]+)/.exec(lines[bodyIndex]);
41587
+ if (versionMatch) return versionMatch[1];
41588
+ }
41589
+ }
41590
+ return null;
41591
+ };
41592
+ };
41593
+ const buildLockedVersionLookup = (rootDirectory) => {
41594
+ const npmLockText = readTextFileOrNull(Path.join(rootDirectory, "package-lock.json"));
41595
+ if (npmLockText !== null) return buildNpmLockLookup(npmLockText);
41596
+ const yarnLockText = readTextFileOrNull(Path.join(rootDirectory, "yarn.lock"));
41597
+ if (yarnLockText !== null) return buildYarnLockLookup(yarnLockText);
41598
+ const pnpmLockText = readTextFileOrNull(Path.join(rootDirectory, "pnpm-lock.yaml"));
41599
+ if (pnpmLockText !== null) return buildPnpmLockLookup(pnpmLockText);
41600
+ return null;
41601
+ };
41156
41602
  const locateDependencyKey = (packageJsonText, section, name) => {
41157
41603
  const needle = `"${name}"`;
41158
41604
  const sectionHeader = new RegExp(`"${section}"\\s*:\\s*\\{`);
@@ -41179,7 +41625,7 @@ const locateDependencyKey = (packageJsonText, section, name) => {
41179
41625
  }
41180
41626
  return null;
41181
41627
  };
41182
- const collectDependenciesToScore = (packageJson, packageJsonText, includeDevDependencies) => {
41628
+ const collectDependenciesToScore = (packageJson, packageJsonText, includeDevDependencies, lockedVersionLookup) => {
41183
41629
  const sectionByName = /* @__PURE__ */ new Map();
41184
41630
  for (const name of Object.keys(packageJson.dependencies ?? {})) sectionByName.set(name, "dependencies");
41185
41631
  if (includeDevDependencies) {
@@ -41189,13 +41635,18 @@ const collectDependenciesToScore = (packageJson, packageJsonText, includeDevDepe
41189
41635
  for (const [name, section] of sectionByName) {
41190
41636
  if (SUPPLY_CHAIN_IGNORED_PACKAGES.has(name)) continue;
41191
41637
  const spec = (packageJson[section] ?? {})[name] ?? "";
41192
- const version = resolveConcreteVersion(spec);
41193
- if (version === null) continue;
41638
+ const floorVersion = resolveConcreteVersion(spec);
41639
+ if (floorVersion === null) continue;
41640
+ const lockedVersion = lockedVersionLookup?.(name, spec) ?? null;
41641
+ const lockSatisfiesSpec = lockedVersion !== null && import_semver.valid(lockedVersion) !== null && import_semver.satisfies(lockedVersion, spec);
41642
+ const isExactPin = import_semver.valid(spec.trim()) !== null;
41194
41643
  const location = locateDependencyKey(packageJsonText, section, name);
41195
41644
  dependencies.push({
41196
41645
  name,
41197
- version,
41646
+ version: lockSatisfiesSpec ? lockedVersion : floorVersion,
41198
41647
  spec,
41648
+ section,
41649
+ resolution: isExactPin ? "pin" : lockSatisfiesSpec ? "locked" : "floor",
41199
41650
  line: location?.line ?? 0,
41200
41651
  column: location?.column ?? 0
41201
41652
  });
@@ -41346,7 +41797,11 @@ const formatAlertReason = (topAlerts, totalCount) => {
41346
41797
  }
41347
41798
  return `Socket flagged ${totalCount} alerts (${topAlerts.map((alert) => friendlyAlertType(alert.type)).join(", ")}${totalCount > topAlerts.length ? ` (+${totalCount - topAlerts.length} more)` : ""}); most severe: ${displaySeverity(topAlerts[0].severity)}.`;
41348
41799
  };
41349
- const formatDependencyIdentity = (dependency) => import_semver.valid(dependency.spec) !== null ? `${dependency.name}@${dependency.version}` : `${dependency.name}@${dependency.version} (lowest version "${dependency.spec}" allows)`;
41800
+ const formatDependencyIdentity = (dependency) => {
41801
+ if (dependency.resolution === "pin") return `${dependency.name}@${dependency.version}`;
41802
+ if (dependency.resolution === "locked") return `${dependency.name}@${dependency.version} (lockfile resolution of "${dependency.spec}")`;
41803
+ return `${dependency.name}@${dependency.version} (lowest version "${dependency.spec}" allows)`;
41804
+ };
41350
41805
  const buildSupplyChainHelp = (dependency, failingAxis, topAlerts, packagePageUrl, options) => {
41351
41806
  const hasCriticalAlert = topAlerts.some((alert) => alert.severity.toLowerCase() === "critical");
41352
41807
  const entry = `\`"${dependency.name}": "${dependency.spec}"\``;
@@ -41389,7 +41844,7 @@ const buildLowScoreDiagnostic = (dependency, artifact, failingAxis, options) =>
41389
41844
  const checkSupplyChain = (input) => gen(function* () {
41390
41845
  const options = resolveOptions(input.userConfig);
41391
41846
  const packageJsonPath = Path.join(input.rootDirectory, "package.json");
41392
- const dependencies = collectDependenciesToScore(readPackageJson(packageJsonPath), readPackageJsonText(packageJsonPath), options.includeDevDependencies);
41847
+ const dependencies = collectDependenciesToScore(readPackageJson(packageJsonPath), readPackageJsonText(packageJsonPath), options.includeDevDependencies, buildLockedVersionLookup(input.rootDirectory));
41393
41848
  if (dependencies.length === 0) return [];
41394
41849
  const cacheDirectory = isSupplyChainCacheDisabled() ? null : resolveReactDoctorCacheDir(input.rootDirectory);
41395
41850
  if (cacheDirectory !== null) pruneExpiredSocketCache(cacheDirectory);
@@ -41398,7 +41853,7 @@ const checkSupplyChain = (input) => gen(function* () {
41398
41853
  for (let index = 0; index < dependencies.length; index += 1) {
41399
41854
  const artifact = artifacts[index];
41400
41855
  if (!artifact) continue;
41401
- const worstAxis = worstGatedAxis(artifact.score);
41856
+ const worstAxis = worstGatedAxis(artifact.score, gatedAxesForSection(dependencies[index].section));
41402
41857
  if (toHundred(artifact.score[worstAxis.key]) >= options.minScore) continue;
41403
41858
  diagnostics.push(buildLowScoreDiagnostic(dependencies[index], artifact, worstAxis, options));
41404
41859
  }
@@ -42075,6 +42530,13 @@ const summarizeDiagnostics = (diagnostics, worstScore = null, worstScoreLabel =
42075
42530
  scoreLabel: worstScoreLabel
42076
42531
  };
42077
42532
  };
42533
+ /**
42534
+ * Whether project discovery resolved a React-compatible runtime (React or
42535
+ * Preact, which ships the same hooks + JSX model). This is the same
42536
+ * predicate that grants the `react` capability, so `false` means every
42537
+ * React-runtime rule family was gated off for the project.
42538
+ */
42539
+ const hasReactRuntime = (project) => project.reactVersion !== null || project.preactVersion !== null;
42078
42540
  const toJsonDiff = (diff) => {
42079
42541
  if (!diff) return null;
42080
42542
  return {
@@ -42309,17 +42771,20 @@ const warnIfAiTrainingEnvironment = () => {
42309
42771
  didWarnAiTraining = true;
42310
42772
  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.");
42311
42773
  };
42312
- const buildDiagnoseLayer = (config, configOverrideTarget) => {
42313
- const configLayer = configOverrideTarget === void 0 ? Config.layerNode : Config.layerOf({
42314
- config,
42315
- resolvedDirectory: configOverrideTarget.resolvedDirectory,
42316
- configSourceDirectory: configOverrideTarget.configSourceDirectory
42774
+ const resolveShouldRunLint = (options, effectiveConfig) => options.lint ?? effectiveConfig?.lint ?? true;
42775
+ const resolveShouldRunDeadCode = (options, effectiveConfig) => options.deadCode ?? effectiveConfig?.deadCode ?? true;
42776
+ const buildDiagnoseLayer = (input) => {
42777
+ const configLayer = input.configOverrideTarget === void 0 ? Config.layerNode : Config.layerOf({
42778
+ config: input.config,
42779
+ resolvedDirectory: input.configOverrideTarget.resolvedDirectory,
42780
+ configSourceDirectory: input.configOverrideTarget.configSourceDirectory
42317
42781
  });
42318
- 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([]));
42782
+ 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([]));
42319
42783
  };
42320
42784
  const buildInspectProgram = (scanTarget, options, configOverride) => {
42321
42785
  const effectiveConfig = configOverride ?? scanTarget.userConfig;
42322
42786
  const includePaths = options.includePaths ?? [];
42787
+ const shouldRunDeadCode = resolveShouldRunDeadCode(options, effectiveConfig);
42323
42788
  return runInspect({
42324
42789
  directory: scanTarget.resolvedDirectory,
42325
42790
  includePaths,
@@ -42328,7 +42793,7 @@ const buildInspectProgram = (scanTarget, options, configOverride) => {
42328
42793
  warnings: options.warnings ?? effectiveConfig?.warnings ?? true,
42329
42794
  adoptExistingLintConfig: effectiveConfig?.adoptExistingLintConfig ?? true,
42330
42795
  ignoredTags: new Set(effectiveConfig?.ignore?.tags ?? []),
42331
- runDeadCode: options.deadCode ?? effectiveConfig?.deadCode ?? true,
42796
+ runDeadCode: shouldRunDeadCode,
42332
42797
  isCi: false,
42333
42798
  resolveLocalGithubViewerPermission: true
42334
42799
  });
@@ -42350,7 +42815,14 @@ const diagnoseDirectory = async (directory, options) => {
42350
42815
  warnIfAiTrainingEnvironment();
42351
42816
  const startTime = globalThis.performance.now();
42352
42817
  const scanTarget = await resolveScanTarget(directory);
42353
- return outputToDiagnoseResult(await runPromise(restoreLegacyThrow(buildInspectProgram(scanTarget, options).pipe(provide(buildDiagnoseLayer(scanTarget.userConfig)), provide(layerOtlp)))), globalThis.performance.now() - startTime);
42818
+ const program = buildInspectProgram(scanTarget, options);
42819
+ const shouldRunLint = resolveShouldRunLint(options, scanTarget.userConfig);
42820
+ const shouldRunDeadCode = resolveShouldRunDeadCode(options, scanTarget.userConfig);
42821
+ return outputToDiagnoseResult(await runPromise(restoreLegacyThrow(program.pipe(provide(buildDiagnoseLayer({
42822
+ config: scanTarget.userConfig,
42823
+ shouldRunLint,
42824
+ shouldRunDeadCode
42825
+ })), provide(layerOtlp)))), globalThis.performance.now() - startTime);
42354
42826
  };
42355
42827
  const findWorstScore = (projectResults) => {
42356
42828
  let worstResult = null;
@@ -42371,15 +42843,27 @@ const diagnoseProject = async (projectDefinition, baseOptions, batchConfig) => {
42371
42843
  const { directory: _, config: projectConfig, ...perProjectOptions } = projectDefinition;
42372
42844
  const didOverrideConfig = batchConfig !== void 0 || projectConfig !== void 0;
42373
42845
  const effectiveConfig = mergeReactDoctorConfigs(mergeReactDoctorConfigs(scanTarget.userConfig, batchConfig), projectConfig);
42374
- const program = buildInspectProgram(scanTarget, {
42846
+ const mergedOptions = {
42375
42847
  ...baseOptions,
42376
42848
  ...perProjectOptions
42377
- }, effectiveConfig ?? void 0);
42849
+ };
42850
+ const shouldRunLint = resolveShouldRunLint(mergedOptions, effectiveConfig);
42851
+ const shouldRunDeadCode = resolveShouldRunDeadCode(mergedOptions, effectiveConfig);
42852
+ const program = buildInspectProgram(scanTarget, mergedOptions, effectiveConfig ?? void 0);
42378
42853
  const didOverridePlugins = batchConfig?.plugins !== void 0 || projectConfig?.plugins !== void 0;
42379
- const layer = buildDiagnoseLayer(effectiveConfig, didOverrideConfig ? {
42380
- resolvedDirectory: scanTarget.resolvedDirectory,
42381
- configSourceDirectory: didOverridePlugins ? null : scanTarget.configSourceDirectory
42382
- } : void 0);
42854
+ const layer = buildDiagnoseLayer(didOverrideConfig ? {
42855
+ config: effectiveConfig,
42856
+ shouldRunLint,
42857
+ shouldRunDeadCode,
42858
+ configOverrideTarget: {
42859
+ resolvedDirectory: scanTarget.resolvedDirectory,
42860
+ configSourceDirectory: didOverridePlugins ? null : scanTarget.configSourceDirectory
42861
+ }
42862
+ } : {
42863
+ config: effectiveConfig,
42864
+ shouldRunLint,
42865
+ shouldRunDeadCode
42866
+ });
42383
42867
  return {
42384
42868
  ok: true,
42385
42869
  ...outputToDiagnoseResult(await runPromise(restoreLegacyThrow(program.pipe(provide(layer), provide(layerOtlp)))), globalThis.performance.now() - startTime),
@@ -42441,4 +42925,4 @@ const toJsonReport = (result, options) => buildJsonReport({
42441
42925
  export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, hasReactRuntime, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
42442
42926
 
42443
42927
  //# sourceMappingURL=index.js.map
42444
- //# debugId=7cb857ba-6f0a-57d9-91f3-3c286472121d
42928
+ //# debugId=63c65851-4fa5-5cd1-9337-0f2feefe37ad