react-doctor 0.7.2-dev.aa519e5 → 0.7.2-dev.cb8f726

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/lsp.js CHANGED
@@ -32827,6 +32827,25 @@ var AmbiguousProjectError = class extends Error {
32827
32827
  }
32828
32828
  };
32829
32829
  const isProjectDiscoveryError = (value) => value instanceof ProjectNotFoundError || value instanceof NoReactDependencyError || value instanceof PackageJsonNotFoundError || value instanceof NotADirectoryError || value instanceof AmbiguousProjectError;
32830
+ const isErrnoException = (error) => error instanceof Error && "code" in error;
32831
+ const IGNORABLE_READDIR_ERROR_CODES = new Set([
32832
+ "EACCES",
32833
+ "EPERM",
32834
+ "ENOENT",
32835
+ "ENOTDIR",
32836
+ "EINVAL",
32837
+ "ELOOP",
32838
+ "ENAMETOOLONG"
32839
+ ]);
32840
+ const isIgnorableReaddirError = (error) => isErrnoException(error) && typeof error.code === "string" && IGNORABLE_READDIR_ERROR_CODES.has(error.code);
32841
+ const readDirectoryEntries = (directoryPath) => {
32842
+ try {
32843
+ return NFS.readdirSync(directoryPath, { withFileTypes: true });
32844
+ } catch (error) {
32845
+ if (isIgnorableReaddirError(error)) return [];
32846
+ throw error;
32847
+ }
32848
+ };
32830
32849
  const isFile = (filePath) => {
32831
32850
  try {
32832
32851
  return NFS.statSync(filePath).isFile();
@@ -32834,6 +32853,18 @@ const isFile = (filePath) => {
32834
32853
  return false;
32835
32854
  }
32836
32855
  };
32856
+ const isDirectory = (directoryPath) => {
32857
+ try {
32858
+ return NFS.statSync(directoryPath).isDirectory();
32859
+ } catch {
32860
+ return false;
32861
+ }
32862
+ };
32863
+ const isPlainObject = (value) => {
32864
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
32865
+ const prototype = Object.getPrototypeOf(value);
32866
+ return prototype === null || prototype === Object.prototype;
32867
+ };
32837
32868
  const SOURCE_FILE_PATTERN = /\.(tsx?|jsx?|mts|mjs)$/;
32838
32869
  const GENERATED_BUNDLE_FILE_PATTERN = /\.(iife|umd|global|min)\.m?js$/i;
32839
32870
  const GENERATED_SOURCE_DIRECTORY_PATTERN = /(?:^|\/)__generated__\//;
@@ -33053,25 +33084,6 @@ const isLargeMinifiedFile = (absolutePath, knownSizeBytes) => {
33053
33084
  cachedIsLargeMinifiedByPath.set(absolutePath, result);
33054
33085
  return result;
33055
33086
  };
33056
- const isErrnoException = (error) => error instanceof Error && "code" in error;
33057
- const IGNORABLE_READDIR_ERROR_CODES = new Set([
33058
- "EACCES",
33059
- "EPERM",
33060
- "ENOENT",
33061
- "ENOTDIR",
33062
- "EINVAL",
33063
- "ELOOP",
33064
- "ENAMETOOLONG"
33065
- ]);
33066
- const isIgnorableReaddirError = (error) => isErrnoException(error) && typeof error.code === "string" && IGNORABLE_READDIR_ERROR_CODES.has(error.code);
33067
- const readDirectoryEntries = (directoryPath) => {
33068
- try {
33069
- return NFS.readdirSync(directoryPath, { withFileTypes: true });
33070
- } catch (error) {
33071
- if (isIgnorableReaddirError(error)) return [];
33072
- throw error;
33073
- }
33074
- };
33075
33087
  function* walkSourceTreeFiles(rootDirectory) {
33076
33088
  const stack = [rootDirectory];
33077
33089
  while (stack.length > 0) {
@@ -33126,23 +33138,6 @@ const listSourceFilesViaFilesystem = (rootDirectory) => {
33126
33138
  const listSourceFilesWithSize = (rootDirectory) => collectSizedSourceFiles(rootDirectory, (listSourceFilesViaGit(rootDirectory) ?? listSourceFilesViaFilesystem(rootDirectory)).sort());
33127
33139
  const listSourceFiles = (rootDirectory) => listSourceFilesWithSize(rootDirectory).map((entry) => entry.path);
33128
33140
  const countSourceFiles = (rootDirectory) => listSourceFilesWithSize(rootDirectory).length;
33129
- /**
33130
- * The chain of ancestor directories of `startDirectory`, nearest first, up to
33131
- * and including the filesystem root. With `includeStart`, the chain begins at
33132
- * `startDirectory` itself; otherwise at its parent. The one place the "walk up
33133
- * the tree" traversal is expressed, so each caller adds only its own match /
33134
- * stop policy instead of re-deriving the `path.dirname` loop.
33135
- */
33136
- const ancestorDirectories = (startDirectory, { includeStart }) => {
33137
- const directories = [];
33138
- let currentDirectory = includeStart ? startDirectory : Path.dirname(startDirectory);
33139
- while (true) {
33140
- directories.push(currentDirectory);
33141
- const parentDirectory = Path.dirname(currentDirectory);
33142
- if (parentDirectory === currentDirectory) return directories;
33143
- currentDirectory = parentDirectory;
33144
- }
33145
- };
33146
33141
  const cachedPackageJsons = /* @__PURE__ */ new Map();
33147
33142
  const clearPackageJsonCache = () => {
33148
33143
  cachedPackageJsons.clear();
@@ -33176,7 +33171,11 @@ const isMonorepoRoot = (directory) => {
33176
33171
  return Array.isArray(packageJson.workspaces) || Boolean(packageJson.workspaces?.packages);
33177
33172
  };
33178
33173
  const findMonorepoRoot = (startDirectory) => {
33179
- for (const directory of ancestorDirectories(startDirectory, { includeStart: false })) if (isMonorepoRoot(directory)) return directory;
33174
+ let currentDirectory = Path.dirname(startDirectory);
33175
+ while (currentDirectory !== Path.dirname(currentDirectory)) {
33176
+ if (isMonorepoRoot(currentDirectory)) return currentDirectory;
33177
+ currentDirectory = Path.dirname(currentDirectory);
33178
+ }
33180
33179
  return null;
33181
33180
  };
33182
33181
  /**
@@ -33190,6 +33189,146 @@ const findMonorepoRoot = (startDirectory) => {
33190
33189
  * byte-equivalent copy.
33191
33190
  */
33192
33191
  const isProjectBoundary = (directory) => NFS.existsSync(Path.join(directory, ".git")) || isMonorepoRoot(directory);
33192
+ const TSCONFIG_FILENAME = "tsconfig.json";
33193
+ const isRelativeExtendsValue = (extendsValue) => extendsValue.startsWith("./") || extendsValue.startsWith("../") || Path.isAbsolute(extendsValue);
33194
+ const ensureJsonExtension = (filePath) => Path.extname(filePath) === "" ? `${filePath}.json` : filePath;
33195
+ const resolvePackageExtendsPath = (extendsValue, fromConfigDirectory) => {
33196
+ const requireFromConfig = createRequire(Path.join(fromConfigDirectory, "tsconfig.json"));
33197
+ const candidates = [
33198
+ extendsValue,
33199
+ ensureJsonExtension(extendsValue),
33200
+ `${extendsValue.replace(/\/$/, "")}/tsconfig.json`
33201
+ ];
33202
+ for (const candidate of candidates) try {
33203
+ return requireFromConfig.resolve(candidate);
33204
+ } catch {
33205
+ continue;
33206
+ }
33207
+ return null;
33208
+ };
33209
+ const resolveExtendsPath = (extendsValue, fromConfigDirectory) => {
33210
+ if (isRelativeExtendsValue(extendsValue)) return ensureJsonExtension(Path.resolve(fromConfigDirectory, extendsValue));
33211
+ return resolvePackageExtendsPath(extendsValue, fromConfigDirectory);
33212
+ };
33213
+ const normalizeCompilerOptions = (compilerOptions) => {
33214
+ if (!isPlainObject(compilerOptions)) return { hasExplicitLib: false };
33215
+ const target = typeof compilerOptions.target === "string" ? compilerOptions.target : void 0;
33216
+ const hasExplicitLib = Object.hasOwn(compilerOptions, "lib");
33217
+ return {
33218
+ target,
33219
+ lib: Array.isArray(compilerOptions.lib) ? compilerOptions.lib.filter((entry) => typeof entry === "string") : void 0,
33220
+ hasExplicitLib
33221
+ };
33222
+ };
33223
+ const readTsConfig = (filePath) => {
33224
+ let content;
33225
+ try {
33226
+ content = NFS.readFileSync(filePath, "utf-8");
33227
+ } catch {
33228
+ return null;
33229
+ }
33230
+ const parsed = ts.parseConfigFileTextToJson(filePath, content);
33231
+ if (!isPlainObject(parsed.config)) return null;
33232
+ return {
33233
+ extends: typeof parsed.config.extends === "string" ? parsed.config.extends : void 0,
33234
+ referencePaths: normalizeReferencePaths(parsed.config.references),
33235
+ compilerOptions: normalizeCompilerOptions(parsed.config.compilerOptions)
33236
+ };
33237
+ };
33238
+ const normalizeReferencePaths = (references) => {
33239
+ if (!Array.isArray(references)) return [];
33240
+ return references.map((reference) => isPlainObject(reference) && typeof reference.path === "string" ? reference.path : null).filter((referencePath) => referencePath !== null);
33241
+ };
33242
+ const mergeCompilerOptions = (inherited, current) => {
33243
+ const target = current.target ?? inherited?.target;
33244
+ const hasExplicitLib = current.hasExplicitLib || Boolean(inherited?.hasExplicitLib);
33245
+ return {
33246
+ target,
33247
+ lib: current.hasExplicitLib ? current.lib : inherited?.lib,
33248
+ hasExplicitLib
33249
+ };
33250
+ };
33251
+ const readResolvedCompilerOptions = (tsConfigPath, extendsDepth, visitedPaths) => {
33252
+ const realPath = NFS.realpathSync.native(tsConfigPath);
33253
+ if (visitedPaths.has(realPath)) return null;
33254
+ const tsConfig = readTsConfig(realPath);
33255
+ if (!tsConfig) return null;
33256
+ const nextVisitedPaths = new Set(visitedPaths);
33257
+ nextVisitedPaths.add(realPath);
33258
+ if (tsConfig.extends && extendsDepth < 8) {
33259
+ const parentPath = resolveExtendsPath(tsConfig.extends, Path.dirname(realPath));
33260
+ if (parentPath && isFile(parentPath)) return mergeCompilerOptions(readResolvedCompilerOptions(parentPath, extendsDepth + 1, nextVisitedPaths), tsConfig.compilerOptions);
33261
+ }
33262
+ return tsConfig.compilerOptions;
33263
+ };
33264
+ const targetYearIsPreES2023 = (target) => {
33265
+ const year = ES_TARGET_YEAR_BY_NAME[target.toLowerCase()];
33266
+ return year !== void 0 && year < 2023;
33267
+ };
33268
+ const libEntryIncludesES2023Array = (entry) => {
33269
+ const normalizedEntry = entry.toLowerCase();
33270
+ if (normalizedEntry === "esnext" || normalizedEntry === "esnext.array") return true;
33271
+ const esYearMatch = /^es(\d{4})(?:\.(.+))?$/.exec(normalizedEntry);
33272
+ if (!esYearMatch) return false;
33273
+ if (Number(esYearMatch[1]) < 2023) return false;
33274
+ const component = esYearMatch[2];
33275
+ return component === void 0 || component === "array";
33276
+ };
33277
+ const libIncludesES2023 = (lib) => lib.some(libEntryIncludesES2023Array);
33278
+ const compilerOptionsArePreES2023 = (compilerOptions) => {
33279
+ if (compilerOptions.target) return targetYearIsPreES2023(compilerOptions.target);
33280
+ if (compilerOptions.hasExplicitLib) return !libIncludesES2023(compilerOptions.lib ?? []);
33281
+ return false;
33282
+ };
33283
+ const compilerOptionsDeclareTargetOrLib = (compilerOptions) => compilerOptions.hasExplicitLib || compilerOptions.target !== void 0;
33284
+ const detectPreES2023FromConfig = (tsConfigPath, visitedConfigPaths = /* @__PURE__ */ new Set()) => {
33285
+ if (visitedConfigPaths.has(tsConfigPath)) return false;
33286
+ const compilerOptions = readResolvedCompilerOptions(tsConfigPath, 0, /* @__PURE__ */ new Set());
33287
+ if (!compilerOptions) return false;
33288
+ if (!compilerOptionsDeclareTargetOrLib(compilerOptions)) {
33289
+ const tsConfig = readTsConfig(tsConfigPath);
33290
+ if (!tsConfig) return false;
33291
+ const nextVisitedConfigPaths = new Set(visitedConfigPaths);
33292
+ nextVisitedConfigPaths.add(tsConfigPath);
33293
+ const configDirectory = Path.dirname(tsConfigPath);
33294
+ return tsConfig.referencePaths.some((referencePath) => {
33295
+ const resolvedReferencePath = Path.resolve(configDirectory, referencePath);
33296
+ const referencedConfigPath = isFile(resolvedReferencePath) ? resolvedReferencePath : Path.join(resolvedReferencePath, TSCONFIG_FILENAME);
33297
+ return isFile(referencedConfigPath) && detectPreES2023FromConfig(referencedConfigPath, nextVisitedConfigPaths);
33298
+ });
33299
+ }
33300
+ return compilerOptionsArePreES2023(compilerOptions);
33301
+ };
33302
+ const detectPreES2023Target = (directory) => {
33303
+ const tsConfigPath = Path.join(directory, TSCONFIG_FILENAME);
33304
+ if (isFile(tsConfigPath)) return detectPreES2023FromConfig(tsConfigPath);
33305
+ for (const fallbackFilename of FALLBACK_TSCONFIG_FILENAMES) {
33306
+ const fallbackPath = Path.join(directory, fallbackFilename);
33307
+ if (isFile(fallbackPath)) return detectPreES2023FromConfig(fallbackPath);
33308
+ }
33309
+ return false;
33310
+ };
33311
+ const FALLBACK_TSCONFIG_FILENAMES = ["tsconfig.app.json", "tsconfig.build.json"];
33312
+ const FRAMEWORK_PACKAGES = {
33313
+ next: "nextjs",
33314
+ "@tanstack/react-start": "tanstack-start",
33315
+ vite: "vite",
33316
+ "react-scripts": "cra",
33317
+ "@remix-run/react": "remix",
33318
+ gatsby: "gatsby",
33319
+ expo: "expo",
33320
+ "react-native": "react-native"
33321
+ };
33322
+ const detectFramework = (dependencies) => {
33323
+ for (const [packageName, frameworkName] of Object.entries(FRAMEWORK_PACKAGES)) if (dependencies[packageName]) return frameworkName;
33324
+ if (dependencies.preact && !dependencies.react) return "preact";
33325
+ return "unknown";
33326
+ };
33327
+ const MOBILE_FRAMEWORKS = new Set(["expo", "react-native"]);
33328
+ const frameworkMergeRank = (framework) => {
33329
+ if (framework === "unknown") return 3;
33330
+ return MOBILE_FRAMEWORKS.has(framework) ? 2 : 1;
33331
+ };
33193
33332
  const REACT_COMPILER_PACKAGES = new Set([
33194
33333
  "babel-plugin-react-compiler",
33195
33334
  "react-compiler-runtime",
@@ -33226,6 +33365,7 @@ const EXPO_APP_CONFIG_FILENAMES = [
33226
33365
  ];
33227
33366
  const REACT_COMPILER_PACKAGE_REFERENCE_PATTERN = /babel-plugin-react-compiler|react-compiler-runtime|eslint-plugin-react-compiler|["']react-compiler["']/;
33228
33367
  const REACT_COMPILER_ENABLED_FLAG_PATTERN = /["']?reactCompiler["']?\s*:\s*(?:true\b|\{)/;
33368
+ const STATIC_EXPORT_OUTPUT_PATTERN = /(?:^|[^.\w])["']?output["']?\s*:\s*["']export["']/m;
33229
33369
  const hasCompilerPackage = (packageJson) => {
33230
33370
  const allDependencies = {
33231
33371
  ...packageJson.peerDependencies,
@@ -33258,34 +33398,10 @@ const detectReactCompiler = (directory, packageJson) => {
33258
33398
  }
33259
33399
  return false;
33260
33400
  };
33261
- const FRAMEWORK_PACKAGES = {
33262
- next: "nextjs",
33263
- "@tanstack/react-start": "tanstack-start",
33264
- vite: "vite",
33265
- "react-scripts": "cra",
33266
- "@remix-run/react": "remix",
33267
- gatsby: "gatsby",
33268
- expo: "expo",
33269
- "react-native": "react-native"
33270
- };
33271
- const FRAMEWORK_DISPLAY_NAMES = {
33272
- nextjs: "Next.js",
33273
- "tanstack-start": "TanStack Start",
33274
- vite: "Vite",
33275
- cra: "Create React App",
33276
- remix: "Remix",
33277
- gatsby: "Gatsby",
33278
- expo: "Expo",
33279
- "react-native": "React Native",
33280
- preact: "Preact",
33281
- unknown: "React"
33282
- };
33283
- const formatFrameworkName = (framework) => FRAMEWORK_DISPLAY_NAMES[framework];
33284
- const detectFramework = (dependencies) => {
33285
- for (const [packageName, frameworkName] of Object.entries(FRAMEWORK_PACKAGES)) if (dependencies[packageName]) return frameworkName;
33286
- if (dependencies.preact && !dependencies.react) return "preact";
33287
- return "unknown";
33288
- };
33401
+ const detectNextjsStaticExport = (directory) => NEXT_CONFIG_FILENAMES.some((filename) => {
33402
+ const filePath = Path.join(directory, filename);
33403
+ return isFile(filePath) && STATIC_EXPORT_OUTPUT_PATTERN.test(NFS.readFileSync(filePath, "utf-8"));
33404
+ });
33289
33405
  const UNRESOLVABLE_PROTOCOL_VERSION = /^(?:file|git|github|https?|link|patch|portal|workspace|npm):/i;
33290
33406
  const DIST_TAG_VERSION = /^[a-z][a-z0-9._-]*$/i;
33291
33407
  const WILDCARD_VERSION = /^[*xX](?:\.[*xX])*$/;
@@ -33423,39 +33539,105 @@ const isConcreteDependencyVersion = (version) => {
33423
33539
  const normalizedVersion = normalizeDependencyVersion(version);
33424
33540
  return normalizedVersion !== null && /\d/.test(normalizedVersion);
33425
33541
  };
33426
- const isPlainObject = (value) => {
33427
- if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
33428
- const prototype = Object.getPrototypeOf(value);
33429
- return prototype === null || prototype === Object.prototype;
33430
- };
33431
- const isCatalogReference = (version) => version.startsWith("catalog:");
33432
- const extractCatalogName = (version) => {
33433
- if (!isCatalogReference(version)) return null;
33434
- const name = version.slice(8).trim();
33435
- return name.length > 0 ? name : null;
33542
+ const isMajorMinorAtLeast = (detected, required) => {
33543
+ if (detected === null) return true;
33544
+ if (detected.major !== required.major) return detected.major > required.major;
33545
+ return detected.minor >= required.minor;
33436
33546
  };
33437
- const resolveVersionFromCatalog = (catalog, packageName) => {
33438
- const version = catalog[packageName];
33439
- if (typeof version === "string" && !isCatalogReference(version)) return version;
33440
- return null;
33547
+ const parseReactMajor = (reactVersion) => {
33548
+ if (typeof reactVersion !== "string") return null;
33549
+ return getLowestDependencyMajor(reactVersion);
33441
33550
  };
33442
- const parsePnpmWorkspaceCatalogs = (rootDirectory) => {
33443
- const workspacePath = Path.join(rootDirectory, "pnpm-workspace.yaml");
33444
- if (!isFile(workspacePath)) return {
33445
- defaultCatalog: {},
33446
- namedCatalogs: {}
33447
- };
33448
- const content = NFS.readFileSync(workspacePath, "utf-8");
33449
- const defaultCatalog = {};
33450
- const namedCatalogs = {};
33451
- let currentSection = "none";
33452
- let currentCatalogName = "";
33453
- for (const line of content.split("\n")) {
33454
- const trimmed = line.trim();
33455
- if (trimmed.length === 0 || trimmed.startsWith("#")) continue;
33456
- const indentLevel = line.search(/\S/);
33457
- if (indentLevel === 0 && trimmed === "catalog:") {
33458
- currentSection = "catalog";
33551
+ const MAJOR_MINOR_PATTERN = /(\d{1,4})\.(\d{1,4})/;
33552
+ const MAJOR_ONLY_PATTERN = /(\d{1,4})/;
33553
+ const UPPER_BOUND_COMPARATOR_PATTERN = /<=?\s{0,8}\d{1,4}(?:\.\d{1,4}){0,2}(?:-[^\s,|]+)?/g;
33554
+ const parseReactMajorMinor = (reactVersion) => {
33555
+ if (typeof reactVersion !== "string") return null;
33556
+ const trimmed = reactVersion.trim();
33557
+ if (trimmed.length === 0) return null;
33558
+ const lowerBoundsOnly = trimmed.replace(UPPER_BOUND_COMPARATOR_PATTERN, " ").trim();
33559
+ if (lowerBoundsOnly.length === 0) return null;
33560
+ const majorMinorMatch = lowerBoundsOnly.match(MAJOR_MINOR_PATTERN);
33561
+ if (majorMinorMatch) {
33562
+ const major = Number.parseInt(majorMinorMatch[1], 10);
33563
+ const minor = Number.parseInt(majorMinorMatch[2], 10);
33564
+ if (!Number.isFinite(major) || major <= 0) return null;
33565
+ if (!Number.isFinite(minor) || minor < 0) return null;
33566
+ return {
33567
+ major,
33568
+ minor
33569
+ };
33570
+ }
33571
+ const majorOnlyMatch = lowerBoundsOnly.match(MAJOR_ONLY_PATTERN);
33572
+ if (!majorOnlyMatch) return null;
33573
+ const major = Number.parseInt(majorOnlyMatch[1], 10);
33574
+ if (!Number.isFinite(major) || major <= 0) return null;
33575
+ return {
33576
+ major,
33577
+ minor: 0
33578
+ };
33579
+ };
33580
+ const parseLowerBoundVersion = (versionSpec) => import_semver.validRange(versionSpec) !== null ? import_semver.minVersion(versionSpec) : import_semver.coerce(versionSpec);
33581
+ const parseTailwindMajorMinor = (tailwindVersion) => {
33582
+ if (typeof tailwindVersion !== "string") return null;
33583
+ const trimmed = tailwindVersion.trim();
33584
+ if (trimmed.length === 0) return null;
33585
+ const lowerBound = parseLowerBoundVersion(trimmed);
33586
+ if (lowerBound === null || lowerBound.major <= 0) return null;
33587
+ return {
33588
+ major: lowerBound.major,
33589
+ minor: lowerBound.minor
33590
+ };
33591
+ };
33592
+ const hasUpperBoundOnlyPeerRange = (range) => {
33593
+ if (typeof range !== "string") return false;
33594
+ const normalizedRange = normalizeDependencyVersion(range);
33595
+ if (normalizedRange === null) return false;
33596
+ return splitDependencyVersionBranches(normalizedRange).some((branch) => {
33597
+ const normalizedBranch = normalizeDependencyVersion(branch);
33598
+ return normalizedBranch !== null && getBranchLowestMajor(normalizedBranch) === null && hasUpperBoundComparator(normalizedBranch);
33599
+ });
33600
+ };
33601
+ const peerRangeMinMajor = (range) => {
33602
+ if (typeof range !== "string") return null;
33603
+ return getLowestDependencyMajor(range);
33604
+ };
33605
+ const resolveEffectiveReactMajor = (reactVersion, packageJson) => {
33606
+ const installedReactMajor = parseReactMajor(reactVersion);
33607
+ const peerReactRange = packageJson.peerDependencies?.react;
33608
+ if (typeof peerReactRange !== "string") return installedReactMajor;
33609
+ const peerFloor = peerRangeMinMajor(peerReactRange);
33610
+ if (peerFloor === null) return hasUpperBoundOnlyPeerRange(peerReactRange) ? null : installedReactMajor;
33611
+ return installedReactMajor !== null ? Math.min(installedReactMajor, peerFloor) : peerFloor;
33612
+ };
33613
+ const isCatalogReference = (version) => version.startsWith("catalog:");
33614
+ const extractCatalogName = (version) => {
33615
+ if (!isCatalogReference(version)) return null;
33616
+ const name = version.slice(8).trim();
33617
+ return name.length > 0 ? name : null;
33618
+ };
33619
+ const resolveVersionFromCatalog = (catalog, packageName) => {
33620
+ const version = catalog[packageName];
33621
+ if (typeof version === "string" && !isCatalogReference(version)) return version;
33622
+ return null;
33623
+ };
33624
+ const parsePnpmWorkspaceCatalogs = (rootDirectory) => {
33625
+ const workspacePath = Path.join(rootDirectory, "pnpm-workspace.yaml");
33626
+ if (!isFile(workspacePath)) return {
33627
+ defaultCatalog: {},
33628
+ namedCatalogs: {}
33629
+ };
33630
+ const content = NFS.readFileSync(workspacePath, "utf-8");
33631
+ const defaultCatalog = {};
33632
+ const namedCatalogs = {};
33633
+ let currentSection = "none";
33634
+ let currentCatalogName = "";
33635
+ for (const line of content.split("\n")) {
33636
+ const trimmed = line.trim();
33637
+ if (trimmed.length === 0 || trimmed.startsWith("#")) continue;
33638
+ const indentLevel = line.search(/\S/);
33639
+ if (indentLevel === 0 && trimmed === "catalog:") {
33640
+ currentSection = "catalog";
33459
33641
  continue;
33460
33642
  }
33461
33643
  if (indentLevel === 0 && trimmed === "catalogs:") {
@@ -33568,6 +33750,16 @@ const resolveCatalogVersion = (packageJson, packageName, rootDirectory, explicit
33568
33750
  }
33569
33751
  return null;
33570
33752
  };
33753
+ const REACT_SECTIONS = [
33754
+ "dependencies",
33755
+ "peerDependencies",
33756
+ "devDependencies"
33757
+ ];
33758
+ const TAILWIND_ZOD_SECTIONS = [
33759
+ "dependencies",
33760
+ "devDependencies",
33761
+ "peerDependencies"
33762
+ ];
33571
33763
  const EMPTY_DEPENDENCY_INFO = {
33572
33764
  reactVersion: null,
33573
33765
  tailwindVersion: null,
@@ -33608,6 +33800,10 @@ const extractDependencyInfo = (packageJson) => {
33608
33800
  framework: detectFramework(allDependencies)
33609
33801
  };
33610
33802
  };
33803
+ const getDependencySpec = (packageJson, packageName) => {
33804
+ const spec = packageJson.dependencies?.[packageName] ?? packageJson.devDependencies?.[packageName] ?? packageJson.peerDependencies?.[packageName] ?? packageJson.optionalDependencies?.[packageName];
33805
+ return typeof spec === "string" ? spec : null;
33806
+ };
33611
33807
  const getDependencyDeclaration = ({ packageJson, packageName, sections }) => {
33612
33808
  for (const section of sections) {
33613
33809
  const version = packageJson[section]?.[packageName];
@@ -33624,31 +33820,141 @@ const getDependencyDeclaration = ({ packageJson, packageName, sections }) => {
33624
33820
  version: null
33625
33821
  };
33626
33822
  };
33627
- const isDirectory = (directoryPath) => {
33628
- try {
33629
- return NFS.statSync(directoryPath).isDirectory();
33630
- } catch {
33631
- return false;
33823
+ const REACT_DEPENDENCY_NAMES = new Set([
33824
+ "react",
33825
+ "react-native",
33826
+ "next",
33827
+ "preact"
33828
+ ]);
33829
+ const hasReactDependency = (packageJson) => {
33830
+ const allDependencies = {
33831
+ ...packageJson.peerDependencies,
33832
+ ...packageJson.dependencies,
33833
+ ...packageJson.devDependencies
33834
+ };
33835
+ return Object.keys(allDependencies).some((packageName) => REACT_DEPENDENCY_NAMES.has(packageName));
33836
+ };
33837
+ const getPreactVersion = (packageJson) => {
33838
+ return {
33839
+ ...packageJson.peerDependencies,
33840
+ ...packageJson.dependencies,
33841
+ ...packageJson.devDependencies
33842
+ }.preact ?? null;
33843
+ };
33844
+ const TANSTACK_QUERY_PACKAGES = new Set([
33845
+ "@tanstack/react-query",
33846
+ "@tanstack/query-core",
33847
+ "react-query"
33848
+ ]);
33849
+ const hasTanStackQuery = (packageJson) => {
33850
+ const allDependencies = {
33851
+ ...packageJson.peerDependencies,
33852
+ ...packageJson.dependencies,
33853
+ ...packageJson.devDependencies
33854
+ };
33855
+ return Object.keys(allDependencies).some((packageName) => TANSTACK_QUERY_PACKAGES.has(packageName));
33856
+ };
33857
+ const resolveCatalogBackedDependencyVersion = ({ rootDirectory, rootPackageJson, packageName, version }) => {
33858
+ if (version === null || !isCatalogReference(version)) return version;
33859
+ const catalogName = extractCatalogName(version);
33860
+ const resolvedLocalVersion = resolveCatalogVersion(rootPackageJson, packageName, rootDirectory, catalogName);
33861
+ if (resolvedLocalVersion) return resolvedLocalVersion;
33862
+ const monorepoRoot = findMonorepoRoot(rootDirectory);
33863
+ if (!monorepoRoot) return version;
33864
+ const monorepoPackageJsonPath = Path.join(monorepoRoot, "package.json");
33865
+ if (!isFile(monorepoPackageJsonPath)) return version;
33866
+ return resolveCatalogVersion(readPackageJson(monorepoPackageJsonPath), packageName, monorepoRoot, catalogName) ?? version;
33867
+ };
33868
+ /**
33869
+ * The chain of ancestor directories of `startDirectory`, nearest first, up to
33870
+ * and including the filesystem root. With `includeStart`, the chain begins at
33871
+ * `startDirectory` itself; otherwise at its parent. The one place the "walk up
33872
+ * the tree" traversal is expressed, so each caller adds only its own match /
33873
+ * stop policy instead of re-deriving the `path.dirname` loop.
33874
+ */
33875
+ const ancestorDirectories = (startDirectory, { includeStart }) => {
33876
+ const directories = [];
33877
+ let currentDirectory = includeStart ? startDirectory : Path.dirname(startDirectory);
33878
+ while (true) {
33879
+ directories.push(currentDirectory);
33880
+ const parentDirectory = Path.dirname(currentDirectory);
33881
+ if (parentDirectory === currentDirectory) return directories;
33882
+ currentDirectory = parentDirectory;
33632
33883
  }
33633
33884
  };
33634
- const NX_PROJECT_DISCOVERY_DIRS = [
33635
- "apps",
33636
- "libs",
33637
- "packages"
33638
- ];
33639
- const getNxWorkspaceDirectories = (rootDirectory) => {
33640
- if (!isFile(Path.join(rootDirectory, "nx.json"))) return [];
33641
- const collected = [];
33642
- for (const candidate of NX_PROJECT_DISCOVERY_DIRS) {
33643
- const candidatePath = Path.join(rootDirectory, candidate);
33644
- if (!isDirectory(candidatePath)) continue;
33645
- for (const entry of readDirectoryEntries(candidatePath)) {
33646
- if (!entry.isDirectory()) continue;
33647
- const projectDirectory = Path.join(candidatePath, entry.name);
33648
- if (isFile(Path.join(projectDirectory, "project.json")) || isFile(Path.join(projectDirectory, "package.json"))) collected.push(`${candidate}/${entry.name}`);
33649
- }
33885
+ /**
33886
+ * Walk up from `startDirectory` to the nearest ancestor that owns a
33887
+ * `package.json`, stopping at (and including) the enclosing project boundary
33888
+ * — the working tree's git root or a monorepo root. Lets a scan of a package
33889
+ * subfolder that has no `package.json` of its own adopt the nearest enclosing
33890
+ * package as its project root, so it inherits that package's dependency +
33891
+ * framework detection instead of synthesizing an empty (React-blind) project.
33892
+ *
33893
+ * Generalizes the older monorepo-root-only lookup: the nearest ancestor is the
33894
+ * most specific owning package (a leaf workspace, not just the repo root), so
33895
+ * a subfolder of one workspace no longer borrows a sibling's capabilities.
33896
+ *
33897
+ * Returns `null` when no ancestor `package.json` exists inside the project
33898
+ * boundary (a loose tree of files, or a subfolder of a package-less repo).
33899
+ */
33900
+ const findNearestAncestorPackageJson = (startDirectory) => {
33901
+ if (isProjectBoundary(startDirectory)) return null;
33902
+ for (const directory of ancestorDirectories(startDirectory, { includeStart: false })) {
33903
+ if (isFile(Path.join(directory, "package.json"))) return directory;
33904
+ if (isProjectBoundary(directory)) return null;
33650
33905
  }
33651
- return collected;
33906
+ return null;
33907
+ };
33908
+ const NAMES = new Set([
33909
+ "react-native",
33910
+ "react-native-tvos",
33911
+ ...new Set([
33912
+ "expo",
33913
+ "expo-router",
33914
+ "@expo/cli",
33915
+ "@expo/metro-config",
33916
+ "@expo/metro-runtime"
33917
+ ]),
33918
+ "react-native-windows",
33919
+ "react-native-macos"
33920
+ ]);
33921
+ const PREFIXES = ["@react-native/", "@react-native-"];
33922
+ const isReactNativeDependencyName = (dependencyName) => {
33923
+ if (NAMES.has(dependencyName)) return true;
33924
+ for (const prefix of PREFIXES) if (dependencyName.startsWith(prefix)) return true;
33925
+ return false;
33926
+ };
33927
+ const containsAnyReactNativeDependency = (section) => {
33928
+ if (!section) return false;
33929
+ for (const dependencyName of Object.keys(section)) if (isReactNativeDependencyName(dependencyName)) return true;
33930
+ return false;
33931
+ };
33932
+ const isPackageJsonReactNativeAware = (packageJson) => {
33933
+ if (typeof packageJson["react-native"] === "string") return true;
33934
+ if (containsAnyReactNativeDependency(packageJson.dependencies)) return true;
33935
+ if (containsAnyReactNativeDependency(packageJson.devDependencies)) return true;
33936
+ if (containsAnyReactNativeDependency(packageJson.peerDependencies)) return true;
33937
+ if (containsAnyReactNativeDependency(packageJson.optionalDependencies)) return true;
33938
+ return false;
33939
+ };
33940
+ const REANIMATED_DEPENDENCY_NAME$1 = "react-native-reanimated";
33941
+ const isPackageJsonReanimatedAware = (packageJson) => {
33942
+ const allDependencies = {
33943
+ ...packageJson.peerDependencies,
33944
+ ...packageJson.dependencies,
33945
+ ...packageJson.devDependencies,
33946
+ ...packageJson.optionalDependencies
33947
+ };
33948
+ return Object.hasOwn(allDependencies, REANIMATED_DEPENDENCY_NAME$1);
33949
+ };
33950
+ const getWorkspacePatterns = (rootDirectory, packageJson) => {
33951
+ const pnpmPatterns = parsePnpmWorkspacePatterns(rootDirectory);
33952
+ if (pnpmPatterns.length > 0) return pnpmPatterns;
33953
+ if (Array.isArray(packageJson.workspaces)) return packageJson.workspaces;
33954
+ if (packageJson.workspaces?.packages) return packageJson.workspaces.packages;
33955
+ const nxPatterns = getNxWorkspaceDirectories(rootDirectory);
33956
+ if (nxPatterns.length > 0) return nxPatterns;
33957
+ return [];
33652
33958
  };
33653
33959
  const parsePnpmWorkspacePatterns = (rootDirectory) => {
33654
33960
  const workspacePath = Path.join(rootDirectory, "pnpm-workspace.yaml");
@@ -33667,18 +33973,24 @@ const parsePnpmWorkspacePatterns = (rootDirectory) => {
33667
33973
  }
33668
33974
  return patterns;
33669
33975
  };
33670
- const getWorkspacePatterns = (rootDirectory, packageJson) => {
33671
- const pnpmPatterns = parsePnpmWorkspacePatterns(rootDirectory);
33672
- if (pnpmPatterns.length > 0) return pnpmPatterns;
33673
- if (Array.isArray(packageJson.workspaces)) return packageJson.workspaces;
33674
- if (packageJson.workspaces?.packages) return packageJson.workspaces.packages;
33675
- const nxPatterns = getNxWorkspaceDirectories(rootDirectory);
33676
- if (nxPatterns.length > 0) return nxPatterns;
33677
- return [];
33678
- };
33679
- const parseReactMajor = (reactVersion) => {
33680
- if (typeof reactVersion !== "string") return null;
33681
- return getLowestDependencyMajor(reactVersion);
33976
+ const NX_PROJECT_DISCOVERY_DIRS = [
33977
+ "apps",
33978
+ "libs",
33979
+ "packages"
33980
+ ];
33981
+ const getNxWorkspaceDirectories = (rootDirectory) => {
33982
+ if (!isFile(Path.join(rootDirectory, "nx.json"))) return [];
33983
+ const collected = [];
33984
+ for (const candidate of NX_PROJECT_DISCOVERY_DIRS) {
33985
+ const candidatePath = Path.join(rootDirectory, candidate);
33986
+ if (!isDirectory(candidatePath)) continue;
33987
+ for (const entry of readDirectoryEntries(candidatePath)) {
33988
+ if (!entry.isDirectory()) continue;
33989
+ const projectDirectory = Path.join(candidatePath, entry.name);
33990
+ if (isFile(Path.join(projectDirectory, "project.json")) || isFile(Path.join(projectDirectory, "package.json"))) collected.push(`${candidate}/${entry.name}`);
33991
+ }
33992
+ }
33993
+ return collected;
33682
33994
  };
33683
33995
  const resolveWorkspaceDirectories = (rootDirectory, pattern) => {
33684
33996
  const cleanPattern = pattern.replace(/["']/g, "").replace(/\/\*\*$/, "/*");
@@ -33690,464 +34002,13 @@ const resolveWorkspaceDirectories = (rootDirectory, pattern) => {
33690
34002
  const wildcardIndex = cleanPattern.indexOf("*");
33691
34003
  const baseDirectory = Path.join(rootDirectory, cleanPattern.slice(0, wildcardIndex));
33692
34004
  const suffixAfterWildcard = cleanPattern.slice(wildcardIndex + 1);
33693
- if (!isDirectory(baseDirectory)) return [];
33694
- const resolved = [];
33695
- for (const entry of readDirectoryEntries(baseDirectory)) {
33696
- const entryPath = Path.join(baseDirectory, entry.name, suffixAfterWildcard);
33697
- if (isDirectory(entryPath) && isFile(Path.join(entryPath, "package.json"))) resolved.push(entryPath);
33698
- }
33699
- return resolved;
33700
- };
33701
- const resolveWorkspaceDependencyVersion = ({ concreteVersion, packageName, rootDirectory, rootPackageJson, sections, workspaceDirectory, workspacePackageJson }) => {
33702
- const dependencyDeclaration = getDependencyDeclaration({
33703
- packageJson: workspacePackageJson,
33704
- packageName,
33705
- sections
33706
- });
33707
- if (!dependencyDeclaration.hasDeclaration) return null;
33708
- return concreteVersion ?? resolveCatalogVersion(workspacePackageJson, packageName, workspaceDirectory, dependencyDeclaration.catalogReference) ?? resolveCatalogVersion(rootPackageJson, packageName, rootDirectory, dependencyDeclaration.catalogReference);
33709
- };
33710
- const shouldReplaceReactVersion = (currentVersion, nextVersion) => {
33711
- if (!currentVersion) return true;
33712
- const currentMajor = parseReactMajor(currentVersion);
33713
- const nextMajor = parseReactMajor(nextVersion);
33714
- if (currentMajor === null) return nextMajor !== null;
33715
- if (nextMajor === null) return false;
33716
- return nextMajor < currentMajor;
33717
- };
33718
- const findReactInWorkspaces = (rootDirectory, packageJson) => {
33719
- const patterns = getWorkspacePatterns(rootDirectory, packageJson);
33720
- const result = { ...EMPTY_DEPENDENCY_INFO };
33721
- for (const pattern of patterns) {
33722
- const directories = resolveWorkspaceDirectories(rootDirectory, pattern);
33723
- for (const workspaceDirectory of directories) {
33724
- const workspacePackageJson = readPackageJson(Path.join(workspaceDirectory, "package.json"));
33725
- const info = extractDependencyInfo(workspacePackageJson);
33726
- const reactVersion = resolveWorkspaceDependencyVersion({
33727
- concreteVersion: info.reactVersion,
33728
- packageName: "react",
33729
- rootDirectory,
33730
- rootPackageJson: packageJson,
33731
- sections: [
33732
- "dependencies",
33733
- "peerDependencies",
33734
- "devDependencies"
33735
- ],
33736
- workspaceDirectory,
33737
- workspacePackageJson
33738
- });
33739
- const tailwindVersion = resolveWorkspaceDependencyVersion({
33740
- concreteVersion: info.tailwindVersion,
33741
- packageName: "tailwindcss",
33742
- rootDirectory,
33743
- rootPackageJson: packageJson,
33744
- sections: [
33745
- "dependencies",
33746
- "devDependencies",
33747
- "peerDependencies"
33748
- ],
33749
- workspaceDirectory,
33750
- workspacePackageJson
33751
- });
33752
- const zodVersion = resolveWorkspaceDependencyVersion({
33753
- concreteVersion: info.zodVersion,
33754
- packageName: "zod",
33755
- rootDirectory,
33756
- rootPackageJson: packageJson,
33757
- sections: [
33758
- "dependencies",
33759
- "devDependencies",
33760
- "peerDependencies"
33761
- ],
33762
- workspaceDirectory,
33763
- workspacePackageJson
33764
- });
33765
- if (reactVersion && shouldReplaceReactVersion(result.reactVersion, reactVersion)) result.reactVersion = reactVersion;
33766
- if (tailwindVersion && !result.tailwindVersion) result.tailwindVersion = tailwindVersion;
33767
- if (zodVersion && !result.zodVersion) result.zodVersion = zodVersion;
33768
- if (info.framework !== "unknown" && result.framework === "unknown") result.framework = info.framework;
33769
- const resultReactMajor = parseReactMajor(result.reactVersion);
33770
- if (result.reactVersion && result.tailwindVersion && result.framework !== "unknown" && resultReactMajor !== null && resultReactMajor <= 17) return result;
33771
- }
33772
- }
33773
- return result;
33774
- };
33775
- const findDependencyInfoFromMonorepoRoot = (directory) => {
33776
- const monorepoRoot = findMonorepoRoot(directory);
33777
- if (!monorepoRoot) return EMPTY_DEPENDENCY_INFO;
33778
- const monorepoPackageJsonPath = Path.join(monorepoRoot, "package.json");
33779
- if (!isFile(monorepoPackageJsonPath)) return EMPTY_DEPENDENCY_INFO;
33780
- const rootPackageJson = readPackageJson(monorepoPackageJsonPath);
33781
- const rootInfo = extractDependencyInfo(rootPackageJson);
33782
- const leafPackageJsonPath = Path.join(directory, "package.json");
33783
- const leafPackageJson = isFile(leafPackageJsonPath) ? readPackageJson(leafPackageJsonPath) : null;
33784
- const leafReactDeclaration = leafPackageJson ? getDependencyDeclaration({
33785
- packageJson: leafPackageJson,
33786
- packageName: "react",
33787
- sections: [
33788
- "dependencies",
33789
- "peerDependencies",
33790
- "devDependencies"
33791
- ]
33792
- }) : null;
33793
- const leafTailwindDeclaration = leafPackageJson ? getDependencyDeclaration({
33794
- packageJson: leafPackageJson,
33795
- packageName: "tailwindcss",
33796
- sections: [
33797
- "dependencies",
33798
- "devDependencies",
33799
- "peerDependencies"
33800
- ]
33801
- }) : null;
33802
- const leafZodDeclaration = leafPackageJson ? getDependencyDeclaration({
33803
- packageJson: leafPackageJson,
33804
- packageName: "zod",
33805
- sections: [
33806
- "dependencies",
33807
- "devDependencies",
33808
- "peerDependencies"
33809
- ]
33810
- }) : null;
33811
- const shouldUseReactFallback = !leafReactDeclaration?.hasDeclaration;
33812
- const shouldUseTailwindFallback = leafTailwindDeclaration?.hasDeclaration ?? true;
33813
- const shouldUseZodFallback = leafZodDeclaration?.hasDeclaration ?? true;
33814
- const reactCatalogVersion = shouldUseReactFallback ? resolveCatalogVersion(rootPackageJson, "react", monorepoRoot, leafReactDeclaration?.catalogReference) : null;
33815
- const tailwindCatalogVersion = shouldUseTailwindFallback ? resolveCatalogVersion(rootPackageJson, "tailwindcss", monorepoRoot, leafTailwindDeclaration?.catalogReference) : null;
33816
- const zodCatalogVersion = shouldUseZodFallback ? resolveCatalogVersion(rootPackageJson, "zod", monorepoRoot, leafZodDeclaration?.catalogReference) : null;
33817
- const workspaceInfo = findReactInWorkspaces(monorepoRoot, rootPackageJson);
33818
- return {
33819
- reactVersion: shouldUseReactFallback ? reactCatalogVersion ?? rootInfo.reactVersion ?? workspaceInfo.reactVersion : rootInfo.reactVersion ?? workspaceInfo.reactVersion,
33820
- tailwindVersion: shouldUseTailwindFallback ? tailwindCatalogVersion ?? rootInfo.tailwindVersion ?? workspaceInfo.tailwindVersion : null,
33821
- zodVersion: shouldUseZodFallback ? zodCatalogVersion ?? rootInfo.zodVersion ?? workspaceInfo.zodVersion : null,
33822
- framework: rootInfo.framework !== "unknown" ? rootInfo.framework : workspaceInfo.framework
33823
- };
33824
- };
33825
- /**
33826
- * Walk up from `startDirectory` to the nearest ancestor that owns a
33827
- * `package.json`, stopping at (and including) the enclosing project boundary
33828
- * — the working tree's git root or a monorepo root. Lets a scan of a package
33829
- * subfolder that has no `package.json` of its own adopt the nearest enclosing
33830
- * package as its project root, so it inherits that package's dependency +
33831
- * framework detection instead of synthesizing an empty (React-blind) project.
33832
- *
33833
- * Generalizes the older monorepo-root-only lookup: the nearest ancestor is the
33834
- * most specific owning package (a leaf workspace, not just the repo root), so
33835
- * a subfolder of one workspace no longer borrows a sibling's capabilities.
33836
- *
33837
- * Returns `null` when no ancestor `package.json` exists inside the project
33838
- * boundary (a loose tree of files, or a subfolder of a package-less repo).
33839
- */
33840
- const findNearestAncestorPackageJson = (startDirectory) => {
33841
- if (isProjectBoundary(startDirectory)) return null;
33842
- for (const directory of ancestorDirectories(startDirectory, { includeStart: false })) {
33843
- if (isFile(Path.join(directory, "package.json"))) return directory;
33844
- if (isProjectBoundary(directory)) return null;
33845
- }
33846
- return null;
33847
- };
33848
- /**
33849
- * Last-resort React detection: locate React the way Node's `node_modules`
33850
- * resolution would from `directory` — check each `node_modules/react` up the
33851
- * tree — but bounded to the enclosing repo so a globally installed React can't
33852
- * masquerade as the project's version. Stops at (and including) the nearest
33853
- * project boundary — the git root or a monorepo root — and returns the first
33854
- * installed `version`.
33855
- *
33856
- * Makes "React is installed and importable" ⇒ "React is detected" an invariant
33857
- * for packages whose only React declaration is a version-less spec
33858
- * (`workspace:*`, `*`, a dist-tag) or where React lives solely in a hoisted
33859
- * `node_modules` the declaration walks never reach — the profile of a component
33860
- * package inside a monorepo. Matching the `node_modules/react` entry rather than
33861
- * its realpath means a project whose `node_modules` is symlinked elsewhere (a
33862
- * Docker volume, a shared store) still counts, while a global install outside
33863
- * the repo does not.
33864
- *
33865
- * Returns `null` when React isn't installed within the repo or its package.json
33866
- * carries no version string. A tree with no boundary marker at all bounds the
33867
- * search to `directory`, so a dependency hoisted above the scanned package there
33868
- * is conservatively not adopted.
33869
- */
33870
- /**
33871
- * The nearest enclosing project boundary (git root or monorepo root), or
33872
- * `directory` itself when the scan target sits outside any repo. Bounds the
33873
- * node_modules walk so a React hoisted anywhere inside the repo counts, but a
33874
- * React in an ancestor above the repo — a home-directory or global
33875
- * `node_modules` — can't leak in.
33876
- */
33877
- const findContainmentRoot = (directory) => {
33878
- for (const ancestorDirectory of ancestorDirectories(directory, { includeStart: true })) if (isProjectBoundary(ancestorDirectory)) return ancestorDirectory;
33879
- return directory;
33880
- };
33881
- const resolveInstalledReactVersion = (directory) => {
33882
- const containmentRoot = findContainmentRoot(directory);
33883
- for (const ancestorDirectory of ancestorDirectories(directory, { includeStart: true })) {
33884
- const reactPackageJsonPath = Path.join(ancestorDirectory, "node_modules", "react", "package.json");
33885
- if (isFile(reactPackageJsonPath)) {
33886
- const installedVersion = readPackageJson(reactPackageJsonPath).version;
33887
- return typeof installedVersion === "string" ? installedVersion : null;
33888
- }
33889
- if (ancestorDirectory === containmentRoot) return null;
33890
- }
33891
- return null;
33892
- };
33893
- const findInWorkspacePackageJsons = (rootDirectory, rootPackageJson, select) => {
33894
- const rootValue = select(rootPackageJson);
33895
- if (rootValue !== null) return rootValue;
33896
- const patterns = getWorkspacePatterns(rootDirectory, rootPackageJson);
33897
- if (patterns.length === 0) return null;
33898
- const visitedDirectories = /* @__PURE__ */ new Set();
33899
- for (const pattern of patterns) {
33900
- const directories = [...resolveWorkspaceDirectories(rootDirectory, pattern)].sort();
33901
- for (const workspaceDirectory of directories) {
33902
- if (visitedDirectories.has(workspaceDirectory)) continue;
33903
- visitedDirectories.add(workspaceDirectory);
33904
- const value = select(readPackageJson(Path.join(workspaceDirectory, "package.json")));
33905
- if (value !== null) return value;
33906
- }
33907
- }
33908
- return null;
33909
- };
33910
- const someWorkspacePackageJson = (rootDirectory, rootPackageJson, predicate) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => predicate(packageJson) ? true : null) !== null;
33911
- const NAMES = new Set([
33912
- "react-native",
33913
- "react-native-tvos",
33914
- ...new Set([
33915
- "expo",
33916
- "expo-router",
33917
- "@expo/cli",
33918
- "@expo/metro-config",
33919
- "@expo/metro-runtime"
33920
- ]),
33921
- "react-native-windows",
33922
- "react-native-macos"
33923
- ]);
33924
- const PREFIXES = ["@react-native/", "@react-native-"];
33925
- const isReactNativeDependencyName = (dependencyName) => {
33926
- if (NAMES.has(dependencyName)) return true;
33927
- for (const prefix of PREFIXES) if (dependencyName.startsWith(prefix)) return true;
33928
- return false;
33929
- };
33930
- const containsAnyReactNativeDependency = (section) => {
33931
- if (!section) return false;
33932
- for (const dependencyName of Object.keys(section)) if (isReactNativeDependencyName(dependencyName)) return true;
33933
- return false;
33934
- };
33935
- const isPackageJsonReactNativeAware = (packageJson) => {
33936
- if (typeof packageJson["react-native"] === "string") return true;
33937
- if (containsAnyReactNativeDependency(packageJson.dependencies)) return true;
33938
- if (containsAnyReactNativeDependency(packageJson.devDependencies)) return true;
33939
- if (containsAnyReactNativeDependency(packageJson.peerDependencies)) return true;
33940
- if (containsAnyReactNativeDependency(packageJson.optionalDependencies)) return true;
33941
- return false;
33942
- };
33943
- const hasReactNativeWorkspaceAnywhere = (rootDirectory, rootPackageJson) => someWorkspacePackageJson(rootDirectory, rootPackageJson, isPackageJsonReactNativeAware);
33944
- const getDependencySpec = (packageJson, packageName) => {
33945
- const spec = packageJson.dependencies?.[packageName] ?? packageJson.devDependencies?.[packageName] ?? packageJson.peerDependencies?.[packageName] ?? packageJson.optionalDependencies?.[packageName];
33946
- return typeof spec === "string" ? spec : null;
33947
- };
33948
- const findExpoVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, "expo"));
33949
- const SHOPIFY_FLASH_LIST_PACKAGE_NAME = "@shopify/flash-list";
33950
- const findShopifyFlashListVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, SHOPIFY_FLASH_LIST_PACKAGE_NAME));
33951
- const resolveCatalogBackedDependencyVersion = ({ rootDirectory, rootPackageJson, packageName, version }) => {
33952
- if (version === null || !isCatalogReference(version)) return version;
33953
- const catalogName = extractCatalogName(version);
33954
- const resolvedLocalVersion = resolveCatalogVersion(rootPackageJson, packageName, rootDirectory, catalogName);
33955
- if (resolvedLocalVersion) return resolvedLocalVersion;
33956
- const monorepoRoot = findMonorepoRoot(rootDirectory);
33957
- if (!monorepoRoot) return version;
33958
- const monorepoPackageJsonPath = path.join(monorepoRoot, "package.json");
33959
- if (!isFile(monorepoPackageJsonPath)) return version;
33960
- return resolveCatalogVersion(readPackageJson(monorepoPackageJsonPath), packageName, monorepoRoot, catalogName) ?? version;
33961
- };
33962
- const findNextjsVersion = (rootDirectory, rootPackageJson) => findInWorkspacePackageJsons(rootDirectory, rootPackageJson, (packageJson) => getDependencySpec(packageJson, "next"));
33963
- const getPreactVersion = (packageJson) => {
33964
- return {
33965
- ...packageJson.peerDependencies,
33966
- ...packageJson.dependencies,
33967
- ...packageJson.devDependencies
33968
- }.preact ?? null;
33969
- };
33970
- const TANSTACK_QUERY_PACKAGES = new Set([
33971
- "@tanstack/react-query",
33972
- "@tanstack/query-core",
33973
- "react-query"
33974
- ]);
33975
- const hasTanStackQuery = (packageJson) => {
33976
- const allDependencies = {
33977
- ...packageJson.peerDependencies,
33978
- ...packageJson.dependencies,
33979
- ...packageJson.devDependencies
33980
- };
33981
- return Object.keys(allDependencies).some((packageName) => TANSTACK_QUERY_PACKAGES.has(packageName));
33982
- };
33983
- const REANIMATED_DEPENDENCY_NAME = "react-native-reanimated";
33984
- const isPackageJsonReanimatedAware = (packageJson) => {
33985
- const allDependencies = {
33986
- ...packageJson.peerDependencies,
33987
- ...packageJson.dependencies,
33988
- ...packageJson.devDependencies,
33989
- ...packageJson.optionalDependencies
33990
- };
33991
- return Object.hasOwn(allDependencies, REANIMATED_DEPENDENCY_NAME);
33992
- };
33993
- const parseZodMajor = (zodVersion) => {
33994
- if (typeof zodVersion !== "string") return null;
33995
- return getLowestDependencyMajor(zodVersion);
33996
- };
33997
- const hasUpperBoundOnlyPeerRange = (range) => {
33998
- if (typeof range !== "string") return false;
33999
- const normalizedRange = normalizeDependencyVersion(range);
34000
- if (normalizedRange === null) return false;
34001
- return splitDependencyVersionBranches(normalizedRange).some((branch) => {
34002
- const normalizedBranch = normalizeDependencyVersion(branch);
34003
- return normalizedBranch !== null && getBranchLowestMajor(normalizedBranch) === null && hasUpperBoundComparator(normalizedBranch);
34004
- });
34005
- };
34006
- const peerRangeMinMajor = (range) => {
34007
- if (typeof range !== "string") return null;
34008
- return getLowestDependencyMajor(range);
34009
- };
34010
- const resolveEffectiveReactMajor = (reactVersion, packageJson) => {
34011
- const installedReactMajor = parseReactMajor(reactVersion);
34012
- const peerReactRange = packageJson.peerDependencies?.react;
34013
- if (typeof peerReactRange !== "string") return installedReactMajor;
34014
- const peerFloor = peerRangeMinMajor(peerReactRange);
34015
- if (peerFloor === null) return hasUpperBoundOnlyPeerRange(peerReactRange) ? null : installedReactMajor;
34016
- return installedReactMajor !== null ? Math.min(installedReactMajor, peerFloor) : peerFloor;
34017
- };
34018
- const TSCONFIG_FILENAME = "tsconfig.json";
34019
- const isRelativeExtendsValue = (extendsValue) => extendsValue.startsWith("./") || extendsValue.startsWith("../") || Path.isAbsolute(extendsValue);
34020
- const ensureJsonExtension = (filePath) => Path.extname(filePath) === "" ? `${filePath}.json` : filePath;
34021
- const resolvePackageExtendsPath = (extendsValue, fromConfigDirectory) => {
34022
- const requireFromConfig = createRequire(Path.join(fromConfigDirectory, "tsconfig.json"));
34023
- const candidates = [
34024
- extendsValue,
34025
- ensureJsonExtension(extendsValue),
34026
- `${extendsValue.replace(/\/$/, "")}/tsconfig.json`
34027
- ];
34028
- for (const candidate of candidates) try {
34029
- return requireFromConfig.resolve(candidate);
34030
- } catch {
34031
- continue;
34032
- }
34033
- return null;
34034
- };
34035
- const resolveExtendsPath = (extendsValue, fromConfigDirectory) => {
34036
- if (isRelativeExtendsValue(extendsValue)) return ensureJsonExtension(Path.resolve(fromConfigDirectory, extendsValue));
34037
- return resolvePackageExtendsPath(extendsValue, fromConfigDirectory);
34038
- };
34039
- const normalizeCompilerOptions = (compilerOptions) => {
34040
- if (!isPlainObject(compilerOptions)) return { hasExplicitLib: false };
34041
- const target = typeof compilerOptions.target === "string" ? compilerOptions.target : void 0;
34042
- const hasExplicitLib = Object.hasOwn(compilerOptions, "lib");
34043
- return {
34044
- target,
34045
- lib: Array.isArray(compilerOptions.lib) ? compilerOptions.lib.filter((entry) => typeof entry === "string") : void 0,
34046
- hasExplicitLib
34047
- };
34048
- };
34049
- const normalizeReferencePaths = (references) => {
34050
- if (!Array.isArray(references)) return [];
34051
- return references.map((reference) => isPlainObject(reference) && typeof reference.path === "string" ? reference.path : null).filter((referencePath) => referencePath !== null);
34052
- };
34053
- const readTsConfig = (filePath) => {
34054
- let content;
34055
- try {
34056
- content = NFS.readFileSync(filePath, "utf-8");
34057
- } catch {
34058
- return null;
34059
- }
34060
- const parsed = ts.parseConfigFileTextToJson(filePath, content);
34061
- if (!isPlainObject(parsed.config)) return null;
34062
- return {
34063
- extends: typeof parsed.config.extends === "string" ? parsed.config.extends : void 0,
34064
- referencePaths: normalizeReferencePaths(parsed.config.references),
34065
- compilerOptions: normalizeCompilerOptions(parsed.config.compilerOptions)
34066
- };
34067
- };
34068
- const mergeCompilerOptions = (inherited, current) => {
34069
- const target = current.target ?? inherited?.target;
34070
- const hasExplicitLib = current.hasExplicitLib || Boolean(inherited?.hasExplicitLib);
34071
- return {
34072
- target,
34073
- lib: current.hasExplicitLib ? current.lib : inherited?.lib,
34074
- hasExplicitLib
34075
- };
34076
- };
34077
- const readResolvedCompilerOptions = (tsConfigPath, extendsDepth, visitedPaths) => {
34078
- const realPath = NFS.realpathSync.native(tsConfigPath);
34079
- if (visitedPaths.has(realPath)) return null;
34080
- const tsConfig = readTsConfig(realPath);
34081
- if (!tsConfig) return null;
34082
- const nextVisitedPaths = new Set(visitedPaths);
34083
- nextVisitedPaths.add(realPath);
34084
- if (tsConfig.extends && extendsDepth < 8) {
34085
- const parentPath = resolveExtendsPath(tsConfig.extends, Path.dirname(realPath));
34086
- if (parentPath && isFile(parentPath)) return mergeCompilerOptions(readResolvedCompilerOptions(parentPath, extendsDepth + 1, nextVisitedPaths), tsConfig.compilerOptions);
34087
- }
34088
- return tsConfig.compilerOptions;
34089
- };
34090
- const targetYearIsPreES2023 = (target) => {
34091
- const year = ES_TARGET_YEAR_BY_NAME[target.toLowerCase()];
34092
- return year !== void 0 && year < 2023;
34093
- };
34094
- const libEntryIncludesES2023Array = (entry) => {
34095
- const normalizedEntry = entry.toLowerCase();
34096
- if (normalizedEntry === "esnext" || normalizedEntry === "esnext.array") return true;
34097
- const esYearMatch = /^es(\d{4})(?:\.(.+))?$/.exec(normalizedEntry);
34098
- if (!esYearMatch) return false;
34099
- if (Number(esYearMatch[1]) < 2023) return false;
34100
- const component = esYearMatch[2];
34101
- return component === void 0 || component === "array";
34102
- };
34103
- const libIncludesES2023 = (lib) => lib.some(libEntryIncludesES2023Array);
34104
- const compilerOptionsArePreES2023 = (compilerOptions) => {
34105
- if (compilerOptions.target && targetYearIsPreES2023(compilerOptions.target)) return true;
34106
- if (compilerOptions.hasExplicitLib) return !libIncludesES2023(compilerOptions.lib ?? []);
34107
- return false;
34108
- };
34109
- const compilerOptionsDeclareTargetOrLib = (compilerOptions) => compilerOptions.hasExplicitLib || compilerOptions.target !== void 0;
34110
- const detectPreES2023FromConfig = (tsConfigPath, visitedConfigPaths = /* @__PURE__ */ new Set()) => {
34111
- if (visitedConfigPaths.has(tsConfigPath)) return false;
34112
- const compilerOptions = readResolvedCompilerOptions(tsConfigPath, 0, /* @__PURE__ */ new Set());
34113
- if (!compilerOptions) return false;
34114
- if (!compilerOptionsDeclareTargetOrLib(compilerOptions)) {
34115
- const tsConfig = readTsConfig(tsConfigPath);
34116
- if (!tsConfig) return false;
34117
- const nextVisitedConfigPaths = new Set(visitedConfigPaths);
34118
- nextVisitedConfigPaths.add(tsConfigPath);
34119
- const configDirectory = Path.dirname(tsConfigPath);
34120
- return tsConfig.referencePaths.some((referencePath) => {
34121
- const resolvedReferencePath = Path.resolve(configDirectory, referencePath);
34122
- const referencedConfigPath = isFile(resolvedReferencePath) ? resolvedReferencePath : Path.join(resolvedReferencePath, TSCONFIG_FILENAME);
34123
- return isFile(referencedConfigPath) && detectPreES2023FromConfig(referencedConfigPath, nextVisitedConfigPaths);
34124
- });
34125
- }
34126
- return compilerOptionsArePreES2023(compilerOptions);
34127
- };
34128
- const FALLBACK_TSCONFIG_FILENAMES = ["tsconfig.app.json", "tsconfig.build.json"];
34129
- const detectPreES2023Target = (directory) => {
34130
- const tsConfigPath = Path.join(directory, TSCONFIG_FILENAME);
34131
- if (isFile(tsConfigPath)) return detectPreES2023FromConfig(tsConfigPath);
34132
- for (const fallbackFilename of FALLBACK_TSCONFIG_FILENAMES) {
34133
- const fallbackPath = Path.join(directory, fallbackFilename);
34134
- if (isFile(fallbackPath)) return detectPreES2023FromConfig(fallbackPath);
34005
+ if (!isDirectory(baseDirectory)) return [];
34006
+ const resolved = [];
34007
+ for (const entry of readDirectoryEntries(baseDirectory)) {
34008
+ const entryPath = Path.join(baseDirectory, entry.name, suffixAfterWildcard);
34009
+ if (isDirectory(entryPath) && isFile(Path.join(entryPath, "package.json"))) resolved.push(entryPath);
34135
34010
  }
34136
- return false;
34137
- };
34138
- const REACT_DEPENDENCY_NAMES = new Set([
34139
- "react",
34140
- "react-native",
34141
- "next",
34142
- "preact"
34143
- ]);
34144
- const hasReactDependency = (packageJson) => {
34145
- const allDependencies = {
34146
- ...packageJson.peerDependencies,
34147
- ...packageJson.dependencies,
34148
- ...packageJson.devDependencies
34149
- };
34150
- return Object.keys(allDependencies).some((packageName) => REACT_DEPENDENCY_NAMES.has(packageName));
34011
+ return resolved;
34151
34012
  };
34152
34013
  const listWorkspacePackages = (rootDirectory) => {
34153
34014
  const packageJsonPath = Path.join(rootDirectory, "package.json");
@@ -34179,6 +34040,208 @@ const listWorkspacePackages = (rootDirectory) => {
34179
34040
  }
34180
34041
  return packages;
34181
34042
  };
34043
+ const REANIMATED_DEPENDENCY_NAME = "react-native-reanimated";
34044
+ const SHOPIFY_FLASH_LIST_PACKAGE_NAME = "@shopify/flash-list";
34045
+ const resolveWorkspaceDependencyVersion = ({ concreteVersion, packageName, rootDirectory, rootPackageJson, sections, workspaceDirectory, workspacePackageJson }) => {
34046
+ const dependencyDeclaration = getDependencyDeclaration({
34047
+ packageJson: workspacePackageJson,
34048
+ packageName,
34049
+ sections
34050
+ });
34051
+ if (!dependencyDeclaration.hasDeclaration) return null;
34052
+ return concreteVersion ?? resolveCatalogVersion(workspacePackageJson, packageName, workspaceDirectory, dependencyDeclaration.catalogReference) ?? resolveCatalogVersion(rootPackageJson, packageName, rootDirectory, dependencyDeclaration.catalogReference);
34053
+ };
34054
+ const shouldReplaceReactVersion = (currentVersion, nextVersion) => {
34055
+ if (!currentVersion) return true;
34056
+ const currentMajor = parseReactMajor(currentVersion);
34057
+ const nextMajor = parseReactMajor(nextVersion);
34058
+ if (currentMajor === null) return nextMajor !== null;
34059
+ if (nextMajor === null) return false;
34060
+ return nextMajor < currentMajor;
34061
+ };
34062
+ const evaluateManifestFacts = (facts, packageJson, directory) => {
34063
+ if (facts.expo.version === null) {
34064
+ const spec = getDependencySpec(packageJson, "expo");
34065
+ if (spec !== null) facts.expo = {
34066
+ version: spec,
34067
+ sourceDirectory: directory
34068
+ };
34069
+ }
34070
+ if (facts.next.version === null) {
34071
+ const spec = getDependencySpec(packageJson, "next");
34072
+ if (spec !== null) facts.next = {
34073
+ version: spec,
34074
+ sourceDirectory: directory
34075
+ };
34076
+ }
34077
+ if (facts.shopifyFlashList.version === null) {
34078
+ const spec = getDependencySpec(packageJson, SHOPIFY_FLASH_LIST_PACKAGE_NAME);
34079
+ if (spec !== null) facts.shopifyFlashList = {
34080
+ version: spec,
34081
+ sourceDirectory: directory
34082
+ };
34083
+ }
34084
+ if (facts.reanimatedVersion === null) {
34085
+ const spec = getDependencySpec(packageJson, REANIMATED_DEPENDENCY_NAME);
34086
+ if (spec !== null) facts.reanimatedVersion = spec;
34087
+ }
34088
+ facts.hasReactNativeAwarePackage = facts.hasReactNativeAwarePackage || isPackageJsonReactNativeAware(packageJson);
34089
+ facts.hasReanimatedAwarePackage = facts.hasReanimatedAwarePackage || isPackageJsonReanimatedAware(packageJson);
34090
+ };
34091
+ const collectWorkspaceFacts = (rootDirectory, rootPackageJson, { collectReactGroup }) => {
34092
+ const facts = {
34093
+ reactVersion: null,
34094
+ tailwindVersion: null,
34095
+ zodVersion: null,
34096
+ framework: "unknown",
34097
+ expo: {
34098
+ version: null,
34099
+ sourceDirectory: null
34100
+ },
34101
+ next: {
34102
+ version: null,
34103
+ sourceDirectory: null
34104
+ },
34105
+ shopifyFlashList: {
34106
+ version: null,
34107
+ sourceDirectory: null
34108
+ },
34109
+ hasReactNativeAwarePackage: false,
34110
+ hasReanimatedAwarePackage: false,
34111
+ reanimatedVersion: null
34112
+ };
34113
+ evaluateManifestFacts(facts, rootPackageJson, rootDirectory);
34114
+ let isReactGroupSettled = !collectReactGroup;
34115
+ const visitedDirectories = /* @__PURE__ */ new Set();
34116
+ for (const pattern of getWorkspacePatterns(rootDirectory, rootPackageJson)) {
34117
+ const directories = [...resolveWorkspaceDirectories(rootDirectory, pattern)].sort();
34118
+ for (const workspaceDirectory of directories) {
34119
+ if (visitedDirectories.has(workspaceDirectory)) continue;
34120
+ visitedDirectories.add(workspaceDirectory);
34121
+ const workspacePackageJson = readPackageJson(Path.join(workspaceDirectory, "package.json"));
34122
+ evaluateManifestFacts(facts, workspacePackageJson, workspaceDirectory);
34123
+ const info = extractDependencyInfo(workspacePackageJson);
34124
+ if (info.framework !== "unknown" && frameworkMergeRank(info.framework) < frameworkMergeRank(facts.framework)) facts.framework = info.framework;
34125
+ if (isReactGroupSettled) continue;
34126
+ const reactVersion = resolveWorkspaceDependencyVersion({
34127
+ concreteVersion: info.reactVersion,
34128
+ packageName: "react",
34129
+ rootDirectory,
34130
+ rootPackageJson,
34131
+ sections: REACT_SECTIONS,
34132
+ workspaceDirectory,
34133
+ workspacePackageJson
34134
+ });
34135
+ const tailwindVersion = resolveWorkspaceDependencyVersion({
34136
+ concreteVersion: info.tailwindVersion,
34137
+ packageName: "tailwindcss",
34138
+ rootDirectory,
34139
+ rootPackageJson,
34140
+ sections: TAILWIND_ZOD_SECTIONS,
34141
+ workspaceDirectory,
34142
+ workspacePackageJson
34143
+ });
34144
+ const zodVersion = resolveWorkspaceDependencyVersion({
34145
+ concreteVersion: info.zodVersion,
34146
+ packageName: "zod",
34147
+ rootDirectory,
34148
+ rootPackageJson,
34149
+ sections: TAILWIND_ZOD_SECTIONS,
34150
+ workspaceDirectory,
34151
+ workspacePackageJson
34152
+ });
34153
+ if (reactVersion && shouldReplaceReactVersion(facts.reactVersion, reactVersion)) facts.reactVersion = reactVersion;
34154
+ if (tailwindVersion && !facts.tailwindVersion) facts.tailwindVersion = tailwindVersion;
34155
+ if (zodVersion && !facts.zodVersion) facts.zodVersion = zodVersion;
34156
+ const settledReactMajor = parseReactMajor(facts.reactVersion);
34157
+ isReactGroupSettled = Boolean(facts.reactVersion && facts.tailwindVersion && facts.framework !== "unknown" && settledReactMajor !== null && settledReactMajor <= 17);
34158
+ }
34159
+ }
34160
+ return facts;
34161
+ };
34162
+ const findDependencyInfoFromMonorepoRoot = (directory) => {
34163
+ const monorepoRoot = findMonorepoRoot(directory);
34164
+ if (!monorepoRoot) return EMPTY_DEPENDENCY_INFO;
34165
+ const monorepoPackageJsonPath = Path.join(monorepoRoot, "package.json");
34166
+ if (!isFile(monorepoPackageJsonPath)) return EMPTY_DEPENDENCY_INFO;
34167
+ const rootPackageJson = readPackageJson(monorepoPackageJsonPath);
34168
+ const rootInfo = extractDependencyInfo(rootPackageJson);
34169
+ const leafPackageJsonPath = Path.join(directory, "package.json");
34170
+ const leafPackageJson = isFile(leafPackageJsonPath) ? readPackageJson(leafPackageJsonPath) : null;
34171
+ const leafReactDeclaration = leafPackageJson ? getDependencyDeclaration({
34172
+ packageJson: leafPackageJson,
34173
+ packageName: "react",
34174
+ sections: REACT_SECTIONS
34175
+ }) : null;
34176
+ const leafTailwindDeclaration = leafPackageJson ? getDependencyDeclaration({
34177
+ packageJson: leafPackageJson,
34178
+ packageName: "tailwindcss",
34179
+ sections: TAILWIND_ZOD_SECTIONS
34180
+ }) : null;
34181
+ const leafZodDeclaration = leafPackageJson ? getDependencyDeclaration({
34182
+ packageJson: leafPackageJson,
34183
+ packageName: "zod",
34184
+ sections: TAILWIND_ZOD_SECTIONS
34185
+ }) : null;
34186
+ const shouldUseReactFallback = !leafReactDeclaration?.hasDeclaration;
34187
+ const shouldUseTailwindFallback = leafTailwindDeclaration?.hasDeclaration ?? true;
34188
+ const shouldUseZodFallback = leafZodDeclaration?.hasDeclaration ?? true;
34189
+ const reactCatalogVersion = shouldUseReactFallback ? resolveCatalogVersion(rootPackageJson, "react", monorepoRoot, leafReactDeclaration?.catalogReference) : null;
34190
+ const tailwindCatalogVersion = shouldUseTailwindFallback ? resolveCatalogVersion(rootPackageJson, "tailwindcss", monorepoRoot, leafTailwindDeclaration?.catalogReference) : null;
34191
+ const zodCatalogVersion = shouldUseZodFallback ? resolveCatalogVersion(rootPackageJson, "zod", monorepoRoot, leafZodDeclaration?.catalogReference) : null;
34192
+ const workspaceFacts = collectWorkspaceFacts(monorepoRoot, rootPackageJson, { collectReactGroup: true });
34193
+ return {
34194
+ reactVersion: shouldUseReactFallback ? reactCatalogVersion ?? rootInfo.reactVersion ?? workspaceFacts.reactVersion : rootInfo.reactVersion ?? workspaceFacts.reactVersion,
34195
+ tailwindVersion: shouldUseTailwindFallback ? tailwindCatalogVersion ?? rootInfo.tailwindVersion ?? workspaceFacts.tailwindVersion : null,
34196
+ zodVersion: shouldUseZodFallback ? zodCatalogVersion ?? rootInfo.zodVersion ?? workspaceFacts.zodVersion : null,
34197
+ framework: rootInfo.framework !== "unknown" ? rootInfo.framework : workspaceFacts.framework
34198
+ };
34199
+ };
34200
+ /**
34201
+ * Last-resort React detection: locate React the way Node's `node_modules`
34202
+ * resolution would from `directory` — check each `node_modules/react` up the
34203
+ * tree — but bounded to the enclosing repo so a globally installed React can't
34204
+ * masquerade as the project's version. Stops at (and including) the nearest
34205
+ * project boundary — the git root or a monorepo root — and returns the first
34206
+ * installed `version`.
34207
+ *
34208
+ * Makes "React is installed and importable" ⇒ "React is detected" an invariant
34209
+ * for packages whose only React declaration is a version-less spec
34210
+ * (`workspace:*`, `*`, a dist-tag) or where React lives solely in a hoisted
34211
+ * `node_modules` the declaration walks never reach — the profile of a component
34212
+ * package inside a monorepo. Matching the `node_modules/react` entry rather than
34213
+ * its realpath means a project whose `node_modules` is symlinked elsewhere (a
34214
+ * Docker volume, a shared store) still counts, while a global install outside
34215
+ * the repo does not.
34216
+ *
34217
+ * Returns `null` when React isn't installed within the repo or its package.json
34218
+ * carries no version string. A tree with no boundary marker at all bounds the
34219
+ * search to `directory`, so a dependency hoisted above the scanned package there
34220
+ * is conservatively not adopted.
34221
+ */
34222
+ /**
34223
+ * The nearest enclosing project boundary (git root or monorepo root), or
34224
+ * `directory` itself when the scan target sits outside any repo. Bounds the
34225
+ * node_modules walk so a React hoisted anywhere inside the repo counts, but a
34226
+ * React in an ancestor above the repo — a home-directory or global
34227
+ * `node_modules` — can't leak in.
34228
+ */
34229
+ const findContainmentRoot = (directory) => {
34230
+ for (const ancestorDirectory of ancestorDirectories(directory, { includeStart: true })) if (isProjectBoundary(ancestorDirectory)) return ancestorDirectory;
34231
+ return directory;
34232
+ };
34233
+ const resolveInstalledReactVersion = (directory) => {
34234
+ const containmentRoot = findContainmentRoot(directory);
34235
+ for (const ancestorDirectory of ancestorDirectories(directory, { includeStart: true })) {
34236
+ const reactPackageJsonPath = Path.join(ancestorDirectory, "node_modules", "react", "package.json");
34237
+ if (isFile(reactPackageJsonPath)) {
34238
+ const installedVersion = readPackageJson(reactPackageJsonPath).version;
34239
+ return typeof installedVersion === "string" ? installedVersion : null;
34240
+ }
34241
+ if (ancestorDirectory === containmentRoot) return null;
34242
+ }
34243
+ return null;
34244
+ };
34182
34245
  const toReactWorkspacePackages = (directories) => {
34183
34246
  const packages = [];
34184
34247
  for (const directory of directories) {
@@ -34251,14 +34314,13 @@ const clearProjectCache = () => {
34251
34314
  };
34252
34315
  /**
34253
34316
  * Build a `ProjectInfo` for a directory that has no `package.json` of
34254
- * its own — a package subfolder like `repo/packages` or `app/src/features`,
34255
- * or any loose tree of TypeScript/JavaScript files. Dependency + framework
34256
- * detection is inherited from the nearest enclosing package (a leaf
34257
- * workspace, a plain app root, or a monorepo root — whichever is closest,
34258
- * bounded by the git root), so scanning a subdirectory of a React project
34259
- * still gets the React capabilities; a standalone non-React directory simply
34260
- * scans with the framework-agnostic rules. Throws only when the directory has
34261
- * nothing to scan (no enclosing project and no source files of its own).
34317
+ * its own — a monorepo subfolder like `repo/packages`, or any loose tree
34318
+ * of TypeScript/JavaScript files. Dependency + framework detection is
34319
+ * inherited from the enclosing workspace root when there is one, so
34320
+ * scanning a subdirectory of a React monorepo still gets the React
34321
+ * capabilities; a standalone non-React directory simply scans with the
34322
+ * framework-agnostic rules. Throws only when the directory has nothing
34323
+ * to scan (no enclosing project and no source files of its own).
34262
34324
  */
34263
34325
  const discoverProjectWithoutPackageJson = (directory) => {
34264
34326
  const sourceFileCount = countSourceFiles(directory);
@@ -34296,6 +34358,7 @@ const discoverProjectWithoutPackageJson = (directory) => {
34296
34358
  hasReanimated: false,
34297
34359
  reanimatedVersion: null,
34298
34360
  isPreES2023Target: hasOwnTsConfig && detectPreES2023Target(directory),
34361
+ isStaticExport: false,
34299
34362
  sourceFileCount
34300
34363
  };
34301
34364
  };
@@ -34309,90 +34372,85 @@ const discoverProject = (directory) => {
34309
34372
  return synthesized;
34310
34373
  }
34311
34374
  const packageJson = readPackageJson(packageJsonPath);
34312
- let { reactVersion, tailwindVersion, zodVersion, framework } = extractDependencyInfo(packageJson);
34313
- const reactDeclaration = getDependencyDeclaration({
34314
- packageJson,
34315
- packageName: "react",
34316
- sections: [
34317
- "dependencies",
34318
- "peerDependencies",
34319
- "devDependencies"
34320
- ]
34321
- });
34322
- const tailwindDeclaration = getDependencyDeclaration({
34323
- packageJson,
34324
- packageName: "tailwindcss",
34325
- sections: [
34326
- "dependencies",
34327
- "devDependencies",
34328
- "peerDependencies"
34329
- ]
34330
- });
34331
- const zodDeclaration = getDependencyDeclaration({
34375
+ const rootInfo = extractDependencyInfo(packageJson);
34376
+ let framework = rootInfo.framework;
34377
+ const tracked = {
34378
+ react: {
34379
+ version: rootInfo.reactVersion,
34380
+ sections: REACT_SECTIONS
34381
+ },
34382
+ tailwindcss: {
34383
+ version: rootInfo.tailwindVersion,
34384
+ sections: TAILWIND_ZOD_SECTIONS
34385
+ },
34386
+ zod: {
34387
+ version: rootInfo.zodVersion,
34388
+ sections: TAILWIND_ZOD_SECTIONS
34389
+ }
34390
+ };
34391
+ const declarations = Object.fromEntries(Object.entries(tracked).map(([packageName, entry]) => [packageName, getDependencyDeclaration({
34332
34392
  packageJson,
34333
- packageName: "zod",
34334
- sections: [
34335
- "dependencies",
34336
- "devDependencies",
34337
- "peerDependencies"
34338
- ]
34339
- });
34340
- if (!reactVersion && reactDeclaration.hasDeclaration) reactVersion = resolveCatalogVersion(packageJson, "react", directory, reactDeclaration.catalogReference);
34341
- if (!tailwindVersion && tailwindDeclaration.hasDeclaration) tailwindVersion = resolveCatalogVersion(packageJson, "tailwindcss", directory, tailwindDeclaration.catalogReference);
34342
- if (!zodVersion && zodDeclaration.hasDeclaration) zodVersion = resolveCatalogVersion(packageJson, "zod", directory, zodDeclaration.catalogReference);
34343
- if (!reactVersion || !tailwindVersion || !zodVersion) {
34393
+ packageName,
34394
+ sections: entry.sections
34395
+ })]));
34396
+ const fillFromCatalogs = (source, sourceDirectory) => {
34397
+ for (const [packageName, entry] of Object.entries(tracked)) if (!entry.version && declarations[packageName].hasDeclaration) entry.version = resolveCatalogVersion(source, packageName, sourceDirectory, declarations[packageName].catalogReference);
34398
+ };
34399
+ fillFromCatalogs(packageJson, directory);
34400
+ if (!tracked.react.version || !tracked.tailwindcss.version || !tracked.zod.version) {
34344
34401
  const monorepoRoot = findMonorepoRoot(directory);
34345
34402
  if (monorepoRoot) {
34346
34403
  const monorepoPackageJsonPath = Path.join(monorepoRoot, "package.json");
34347
- if (isFile(monorepoPackageJsonPath)) {
34348
- const rootPackageJson = readPackageJson(monorepoPackageJsonPath);
34349
- if (!reactVersion && reactDeclaration.hasDeclaration) reactVersion = resolveCatalogVersion(rootPackageJson, "react", monorepoRoot, reactDeclaration.catalogReference);
34350
- if (!tailwindVersion && tailwindDeclaration.hasDeclaration) tailwindVersion = resolveCatalogVersion(rootPackageJson, "tailwindcss", monorepoRoot, tailwindDeclaration.catalogReference);
34351
- if (!zodVersion && zodDeclaration.hasDeclaration) zodVersion = resolveCatalogVersion(rootPackageJson, "zod", monorepoRoot, zodDeclaration.catalogReference);
34352
- }
34404
+ if (isFile(monorepoPackageJsonPath)) fillFromCatalogs(readPackageJson(monorepoPackageJsonPath), monorepoRoot);
34353
34405
  }
34354
34406
  }
34355
- if (!reactVersion || framework === "unknown") {
34356
- const workspaceInfo = findReactInWorkspaces(directory, packageJson);
34357
- if (!reactVersion && workspaceInfo.reactVersion) reactVersion = workspaceInfo.reactVersion;
34358
- if (!tailwindVersion && workspaceInfo.tailwindVersion) tailwindVersion = workspaceInfo.tailwindVersion;
34359
- if (!zodVersion && workspaceInfo.zodVersion) zodVersion = workspaceInfo.zodVersion;
34360
- if (framework === "unknown" && workspaceInfo.framework !== "unknown") framework = workspaceInfo.framework;
34407
+ const shouldCollectReactGroup = !tracked.react.version || framework === "unknown";
34408
+ const workspaceFacts = collectWorkspaceFacts(directory, packageJson, { collectReactGroup: shouldCollectReactGroup });
34409
+ if (shouldCollectReactGroup) {
34410
+ tracked.react.version ||= workspaceFacts.reactVersion;
34411
+ tracked.tailwindcss.version ||= workspaceFacts.tailwindVersion;
34412
+ tracked.zod.version ||= workspaceFacts.zodVersion;
34413
+ if (framework === "unknown" && workspaceFacts.framework !== "unknown") framework = workspaceFacts.framework;
34361
34414
  }
34362
- if ((!reactVersion || framework === "unknown") && !isMonorepoRoot(directory)) {
34415
+ if ((!tracked.react.version || framework === "unknown") && !isMonorepoRoot(directory)) {
34363
34416
  const monorepoInfo = findDependencyInfoFromMonorepoRoot(directory);
34364
- if (!reactVersion) reactVersion = monorepoInfo.reactVersion;
34365
- if (!tailwindVersion) tailwindVersion = monorepoInfo.tailwindVersion;
34366
- if (!zodVersion) zodVersion = monorepoInfo.zodVersion;
34417
+ tracked.react.version ||= monorepoInfo.reactVersion;
34418
+ tracked.tailwindcss.version ||= monorepoInfo.tailwindVersion;
34419
+ tracked.zod.version ||= monorepoInfo.zodVersion;
34367
34420
  if (framework === "unknown") framework = monorepoInfo.framework;
34368
34421
  }
34369
- if (!reactVersion && reactDeclaration.version && !isCatalogReference(reactDeclaration.version)) reactVersion = reactDeclaration.version;
34370
- if (!tailwindVersion && tailwindDeclaration.version && !isCatalogReference(tailwindDeclaration.version)) tailwindVersion = tailwindDeclaration.version;
34371
- if (!zodVersion && zodDeclaration.version && !isCatalogReference(zodDeclaration.version)) zodVersion = zodDeclaration.version;
34422
+ for (const [packageName, entry] of Object.entries(tracked)) {
34423
+ const declaredVersion = declarations[packageName].version;
34424
+ if (!entry.version && declaredVersion && !isCatalogReference(declaredVersion)) entry.version = declaredVersion;
34425
+ }
34426
+ const { react, tailwindcss, zod } = tracked;
34427
+ let reactVersion = react.version;
34372
34428
  if (!reactVersion || parseReactMajor(reactVersion) === null) reactVersion = resolveInstalledReactVersion(directory) ?? reactVersion;
34429
+ const tailwindVersion = tailwindcss.version;
34430
+ const zodVersion = zod.version;
34373
34431
  const projectName = packageJson.name ?? Path.basename(directory);
34374
34432
  const hasTypeScript = NFS.existsSync(Path.join(directory, "tsconfig.json"));
34375
34433
  const sourceFileCount = countSourceFiles(directory);
34376
- const hasReactNativeWorkspace = framework === "expo" || framework === "react-native" || hasReactNativeWorkspaceAnywhere(directory, packageJson);
34434
+ const hasReactNativeWorkspace = framework === "expo" || framework === "react-native" || workspaceFacts.hasReactNativeAwarePackage;
34377
34435
  const expoVersion = hasReactNativeWorkspace ? resolveCatalogBackedDependencyVersion({
34378
34436
  rootDirectory: directory,
34379
34437
  rootPackageJson: packageJson,
34380
34438
  packageName: "expo",
34381
- version: findExpoVersion(directory, packageJson)
34439
+ version: workspaceFacts.expo.version
34382
34440
  }) : null;
34383
34441
  const shopifyFlashListVersion = hasReactNativeWorkspace ? resolveCatalogBackedDependencyVersion({
34384
34442
  rootDirectory: directory,
34385
34443
  rootPackageJson: packageJson,
34386
34444
  packageName: SHOPIFY_FLASH_LIST_PACKAGE_NAME,
34387
- version: findShopifyFlashListVersion(directory, packageJson)
34445
+ version: workspaceFacts.shopifyFlashList.version
34388
34446
  }) : null;
34389
- const hasReanimated = hasReactNativeWorkspace && someWorkspacePackageJson(directory, packageJson, isPackageJsonReanimatedAware);
34390
- const reanimatedVersion = hasReanimated ? findInWorkspacePackageJsons(directory, packageJson, (workspacePackageJson) => getDependencySpec(workspacePackageJson, REANIMATED_DEPENDENCY_NAME)) : null;
34447
+ const hasReanimated = hasReactNativeWorkspace && workspaceFacts.hasReanimatedAwarePackage;
34448
+ const reanimatedVersion = hasReanimated ? workspaceFacts.reanimatedVersion : null;
34391
34449
  const nextjsVersion = framework === "nextjs" ? resolveCatalogBackedDependencyVersion({
34392
34450
  rootDirectory: directory,
34393
34451
  rootPackageJson: packageJson,
34394
34452
  packageName: "next",
34395
- version: findNextjsVersion(directory, packageJson)
34453
+ version: workspaceFacts.next.version
34396
34454
  }) : null;
34397
34455
  const preactVersion = getPreactVersion(packageJson);
34398
34456
  const isPreES2023Target = hasTypeScript && detectPreES2023Target(directory);
@@ -34403,7 +34461,7 @@ const discoverProject = (directory) => {
34403
34461
  reactMajorVersion: resolveEffectiveReactMajor(reactVersion, packageJson),
34404
34462
  tailwindVersion,
34405
34463
  zodVersion,
34406
- zodMajorVersion: parseZodMajor(zodVersion),
34464
+ zodMajorVersion: zodVersion === null ? null : getLowestDependencyMajor(zodVersion),
34407
34465
  framework,
34408
34466
  hasTypeScript,
34409
34467
  hasReactCompiler: detectReactCompiler(directory, packageJson),
@@ -34419,6 +34477,7 @@ const discoverProject = (directory) => {
34419
34477
  hasReanimated,
34420
34478
  reanimatedVersion,
34421
34479
  isPreES2023Target,
34480
+ isStaticExport: framework === "nextjs" && detectNextjsStaticExport(workspaceFacts.next.sourceDirectory ?? directory),
34422
34481
  sourceFileCount
34423
34482
  };
34424
34483
  cachedProjectInfos.set(directory, projectInfo);
@@ -34434,57 +34493,6 @@ const discoverProject = (directory) => {
34434
34493
  * single source file has nothing to analyze.
34435
34494
  */
34436
34495
  const isAnalyzableProject = (project) => project.reactVersion !== null || project.preactVersion !== null || project.sourceFileCount > 0;
34437
- const MAJOR_MINOR_PATTERN = /(\d{1,4})\.(\d{1,4})/;
34438
- const MAJOR_ONLY_PATTERN = /(\d{1,4})/;
34439
- const UPPER_BOUND_COMPARATOR_PATTERN = /<=?\s{0,8}\d{1,4}(?:\.\d{1,4}){0,2}(?:-[^\s,|]+)?/g;
34440
- const parseReactMajorMinor = (reactVersion) => {
34441
- if (typeof reactVersion !== "string") return null;
34442
- const trimmed = reactVersion.trim();
34443
- if (trimmed.length === 0) return null;
34444
- const lowerBoundsOnly = trimmed.replace(UPPER_BOUND_COMPARATOR_PATTERN, " ").trim();
34445
- if (lowerBoundsOnly.length === 0) return null;
34446
- const majorMinorMatch = lowerBoundsOnly.match(MAJOR_MINOR_PATTERN);
34447
- if (majorMinorMatch) {
34448
- const major = Number.parseInt(majorMinorMatch[1], 10);
34449
- const minor = Number.parseInt(majorMinorMatch[2], 10);
34450
- if (!Number.isFinite(major) || major <= 0) return null;
34451
- if (!Number.isFinite(minor) || minor < 0) return null;
34452
- return {
34453
- major,
34454
- minor
34455
- };
34456
- }
34457
- const majorOnlyMatch = lowerBoundsOnly.match(MAJOR_ONLY_PATTERN);
34458
- if (!majorOnlyMatch) return null;
34459
- const major = Number.parseInt(majorOnlyMatch[1], 10);
34460
- if (!Number.isFinite(major) || major <= 0) return null;
34461
- return {
34462
- major,
34463
- minor: 0
34464
- };
34465
- };
34466
- const isReactAtLeast = (detected, required) => {
34467
- if (detected === null) return true;
34468
- if (detected.major !== required.major) return detected.major > required.major;
34469
- return detected.minor >= required.minor;
34470
- };
34471
- const parseLowerBoundVersion = (versionSpec) => import_semver.validRange(versionSpec) !== null ? import_semver.minVersion(versionSpec) : import_semver.coerce(versionSpec);
34472
- const parseTailwindMajorMinor = (tailwindVersion) => {
34473
- if (typeof tailwindVersion !== "string") return null;
34474
- const trimmed = tailwindVersion.trim();
34475
- if (trimmed.length === 0) return null;
34476
- const lowerBound = parseLowerBoundVersion(trimmed);
34477
- if (lowerBound === null || lowerBound.major <= 0) return null;
34478
- return {
34479
- major: lowerBound.major,
34480
- minor: lowerBound.minor
34481
- };
34482
- };
34483
- const isTailwindAtLeast = (detected, required) => {
34484
- if (detected === null) return true;
34485
- if (detected.major !== required.major) return detected.major > required.major;
34486
- return detected.minor >= required.minor;
34487
- };
34488
34496
  const messageFromUnknown = (error) => error instanceof Error ? error.message : String(error);
34489
34497
  var InvalidGlobPatternError = class extends Error {
34490
34498
  pattern;
@@ -37186,64 +37194,64 @@ function* collectSecurityScanFiles(rootDirectory) {
37186
37194
  }
37187
37195
  }
37188
37196
  }
37189
- /**
37190
- * Whether project discovery resolved a React-compatible runtime (React or
37191
- * Preact, which ships the same hooks + JSX model). This is the same
37192
- * predicate that grants the `react` capability, so `false` means every
37193
- * React-runtime rule family was gated off for the project.
37194
- */
37195
- const hasReactRuntime = (project) => project.reactVersion !== null || project.preactVersion !== null;
37197
+ const CLIENT_ONLY_FRAMEWORKS = new Set([
37198
+ "vite",
37199
+ "cra",
37200
+ "gatsby",
37201
+ "react-native",
37202
+ "expo"
37203
+ ]);
37204
+ const addMajorLadder = (capabilities, name, major, earliest, latest) => {
37205
+ if (major === null) return;
37206
+ const cappedMajor = Math.min(major, latest);
37207
+ for (let candidate = earliest; candidate <= cappedMajor; candidate += 1) capabilities.add(`${name}:${candidate}`);
37208
+ };
37196
37209
  const buildCapabilities = (project) => {
37197
37210
  const capabilities = /* @__PURE__ */ new Set();
37198
37211
  capabilities.add(project.framework);
37199
- if (hasReactRuntime(project)) capabilities.add("react");
37212
+ if (project.reactVersion !== null || project.preactVersion !== null) capabilities.add("react");
37200
37213
  if (project.framework === "expo" || project.framework === "react-native" || project.hasReactNativeWorkspace) capabilities.add("react-native");
37201
37214
  if (project.expoVersion !== null) capabilities.add("expo");
37215
+ if ((project.framework === "nextjs" || project.framework === "tanstack-start" || project.framework === "remix") && !project.isStaticExport) capabilities.add("server-actions");
37216
+ if (CLIENT_ONLY_FRAMEWORKS.has(project.framework)) capabilities.add("client-only");
37217
+ if (project.isStaticExport) capabilities.add("nextjs:static-export");
37202
37218
  if (project.nextjsMajorVersion !== null && project.nextjsMajorVersion >= 15) capabilities.add("nextjs:15");
37203
- const reactMajor = project.reactMajorVersion;
37204
- if (reactMajor !== null) {
37205
- const cappedReactMajor = Math.min(reactMajor, 30);
37206
- for (let major = 17; major <= cappedReactMajor; major++) capabilities.add(`react:${major}`);
37207
- if (reactMajor >= 19) {
37208
- if (isReactAtLeast(parseReactMajorMinor(project.reactVersion), {
37209
- major: 19,
37210
- minor: 2
37211
- })) capabilities.add("react:19.2");
37212
- }
37213
- }
37214
- if (project.tailwindVersion !== null) {
37215
- capabilities.add("tailwind");
37216
- if (isTailwindAtLeast(parseTailwindMajorMinor(project.tailwindVersion), {
37217
- major: 3,
37218
- minor: 4
37219
- })) capabilities.add("tailwind:3.4");
37220
- }
37221
- if (project.zodVersion !== null) {
37222
- capabilities.add("zod");
37223
- if (project.zodMajorVersion !== null && project.zodMajorVersion >= 4) capabilities.add("zod:4");
37224
- }
37219
+ addMajorLadder(capabilities, "react", project.reactMajorVersion, 17, 30);
37220
+ if (project.reactMajorVersion !== null && project.reactMajorVersion >= 19 && isMajorMinorAtLeast(parseReactMajorMinor(project.reactVersion), {
37221
+ major: 19,
37222
+ minor: 2
37223
+ })) capabilities.add("react:19.2");
37224
+ if (project.tailwindVersion !== null) capabilities.add("tailwind");
37225
+ if (project.tailwindVersion !== null && isMajorMinorAtLeast(parseTailwindMajorMinor(project.tailwindVersion), {
37226
+ major: 3,
37227
+ minor: 4
37228
+ })) capabilities.add("tailwind:3.4");
37229
+ if (project.zodVersion !== null) capabilities.add("zod");
37230
+ if (project.zodMajorVersion !== null && project.zodMajorVersion >= 4) capabilities.add("zod:4");
37225
37231
  if (project.isPreES2023Target) capabilities.add("pre-es2023");
37226
37232
  if (project.hasReactCompiler) capabilities.add("react-compiler");
37227
37233
  if (project.hasTanStackQuery) capabilities.add("tanstack-query");
37228
37234
  if (project.hasTypeScript) capabilities.add("typescript");
37229
- if (project.preactVersion !== null) {
37230
- capabilities.add("preact");
37231
- const preactMajor = project.preactMajorVersion;
37232
- if (preactMajor !== null) {
37233
- const cappedPreactMajor = Math.min(preactMajor, 20);
37234
- for (let major = 10; major <= cappedPreactMajor; major++) capabilities.add(`preact:${major}`);
37235
- }
37236
- if (project.reactVersion === null) capabilities.add("pure-preact");
37237
- }
37235
+ if (project.preactVersion !== null) capabilities.add("preact");
37236
+ addMajorLadder(capabilities, "preact", project.preactMajorVersion, 10, 20);
37237
+ if (project.preactVersion !== null && project.reactVersion === null) capabilities.add("pure-preact");
37238
+ return capabilities;
37239
+ };
37240
+ const capabilitiesByProject = /* @__PURE__ */ new WeakMap();
37241
+ const getCapabilities = (project) => {
37242
+ const cached = capabilitiesByProject.get(project);
37243
+ if (cached !== void 0) return cached;
37244
+ const capabilities = buildCapabilities(project);
37245
+ capabilitiesByProject.set(project, capabilities);
37238
37246
  return capabilities;
37239
37247
  };
37240
- const shouldEnableRule = (requires, tags, capabilities, ignoredTags, disabledBy) => {
37248
+ const shouldEnableRule = (requires, tags, capabilities, ignoredTags, disabledWhen) => {
37241
37249
  if (requires) {
37242
37250
  for (const capability of requires) if (!capabilities.has(capability)) return false;
37243
37251
  }
37244
37252
  if (tags?.includes("react-jsx-only") && !capabilities.has("react")) return false;
37245
- if (disabledBy) {
37246
- for (const capability of disabledBy) if (capabilities.has(capability)) return false;
37253
+ if (disabledWhen) {
37254
+ for (const capability of disabledWhen) if (capabilities.has(capability)) return false;
37247
37255
  }
37248
37256
  if (tags) {
37249
37257
  for (const tag of tags) if (ignoredTags.has(tag)) return false;
@@ -37254,14 +37262,14 @@ const yieldToEventLoop = () => new Promise((resolve) => {
37254
37262
  setImmediate(resolve);
37255
37263
  });
37256
37264
  const createSecurityScanSession = (rootDirectory, options) => {
37257
- const capabilities = options.project ? buildCapabilities(options.project) : /* @__PURE__ */ new Set();
37265
+ const capabilities = options.project ? getCapabilities(options.project) : /* @__PURE__ */ new Set();
37258
37266
  const ignoredTags = options.ignoredTags ?? /* @__PURE__ */ new Set();
37259
37267
  const enabledScanRules = REACT_DOCTOR_RULES.flatMap((entry) => {
37260
37268
  const rule = entry.rule;
37261
37269
  const scan = rule.scan;
37262
37270
  if (typeof scan !== "function") return [];
37263
37271
  if (rule.defaultEnabled === false) return [];
37264
- if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.disabledBy)) return [];
37272
+ if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.disabledWhen)) return [];
37265
37273
  return [{
37266
37274
  entry,
37267
37275
  scan,
@@ -37281,6 +37289,7 @@ const createSecurityScanSession = (rootDirectory, options) => {
37281
37289
  return status === true;
37282
37290
  };
37283
37291
  const scanFileByRule = function* (file) {
37292
+ if (file === null) return;
37284
37293
  for (const { entry, scan, committedFilesOnly } of enabledScanRules) {
37285
37294
  for (const finding of scan(file)) {
37286
37295
  if (committedFilesOnly && isFileGitIgnored(file)) continue;
@@ -39161,7 +39170,7 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
39161
39170
  const reactCompilerRules = reactHooksJsPlugin ? applyRuleSeverityControls(filterRulesToAvailable(REACT_COMPILER_RULES, "react-hooks-js", reactHooksJsPlugin.availableRuleNames), severityControls) : {};
39162
39171
  const jsPlugins = [];
39163
39172
  if (reactHooksJsPlugin) jsPlugins.push(reactHooksJsPlugin.entry);
39164
- const capabilities = buildCapabilities(project);
39173
+ const capabilities = getCapabilities(project);
39165
39174
  const enabledReactDoctorRules = {};
39166
39175
  for (const registryEntry of REACT_DOCTOR_RULES) {
39167
39176
  const rule = reactDoctorPlugin.rules[registryEntry.id];
@@ -39172,7 +39181,7 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
39172
39181
  if (rule.scan !== void 0) continue;
39173
39182
  if (customRulesOnly && registryEntry.originallyExternal) continue;
39174
39183
  if (rule.framework !== "global" && !rule.requires) continue;
39175
- if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.disabledBy)) continue;
39184
+ if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.disabledWhen)) continue;
39176
39185
  const explicitRuleOverride = resolveRuleSeverityOverride({ ruleKey: registryEntry.key }, severityControls);
39177
39186
  if (rule.defaultEnabled === false && explicitRuleOverride === void 0) continue;
39178
39187
  const severity = resolveRuleSeverityOverride({
@@ -39203,6 +39212,7 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
39203
39212
  settings: { "react-doctor": {
39204
39213
  framework: project.framework,
39205
39214
  rootDirectory: resolveSettingsRootDirectory(project.rootDirectory),
39215
+ capabilities: [...capabilities].sort(),
39206
39216
  ...project.shopifyFlashListMajorVersion !== null ? { shopifyFlashListMajorVersion: project.shopifyFlashListMajorVersion } : {},
39207
39217
  ...serverAuthFunctionNames && serverAuthFunctionNames.length > 0 ? { serverAuthFunctionNames: [...serverAuthFunctionNames] } : {}
39208
39218
  } },
@@ -39493,21 +39503,6 @@ const mapWithConcurrency = async (items, concurrency, task) => {
39493
39503
  if (errors.length > 0) throw errors[0];
39494
39504
  return results;
39495
39505
  };
39496
- const getPublicEnvPrefix = (framework) => {
39497
- switch (framework) {
39498
- case "nextjs": return "NEXT_PUBLIC_*";
39499
- case "vite":
39500
- case "tanstack-start": return "VITE_*";
39501
- case "cra": return "REACT_APP_*";
39502
- case "gatsby": return "GATSBY_*";
39503
- default: return null;
39504
- }
39505
- };
39506
- const buildNoSecretsRecommendation = (project, fallbackRecommendation) => {
39507
- const publicEnvPrefix = getPublicEnvPrefix(project.framework);
39508
- if (!publicEnvPrefix) return fallbackRecommendation;
39509
- 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`;
39510
- };
39511
39506
  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";
39512
39507
  const ACCESSORS_MIN_VERSION = "3.15.0";
39513
39508
  const supportsAccessors = (reanimatedVersion) => {
@@ -40188,8 +40183,14 @@ const PLUGIN_CATEGORY_MAP = {
40188
40183
  };
40189
40184
  const lookupOwnString = (record, key) => Object.hasOwn(record, key) ? record[key] : void 0;
40190
40185
  const getRuleRecommendation = (ruleName, project) => {
40191
- if (ruleName === "no-secrets-in-client-code") return buildNoSecretsRecommendation(project, reactDoctorPlugin.rules["no-secrets-in-client-code"]?.recommendation ?? "Move secrets to server-only code");
40192
- return reactDoctorPlugin.rules[ruleName]?.recommendation;
40186
+ const rule = reactDoctorPlugin.rules[ruleName];
40187
+ if (!rule) return void 0;
40188
+ if (rule.recommendationFor) {
40189
+ const capabilities = getCapabilities(project);
40190
+ const conditionalRecommendation = rule.recommendationFor((capability) => capabilities.has(capability));
40191
+ if (conditionalRecommendation !== void 0) return conditionalRecommendation;
40192
+ }
40193
+ return rule.recommendation;
40193
40194
  };
40194
40195
  const getRuleCategory = (ruleName) => reactDoctorPlugin.rules[ruleName]?.category;
40195
40196
  const getRuleTitle = (ruleName) => reactDoctorPlugin.rules[ruleName]?.title;
@@ -44614,5 +44615,5 @@ const startLanguageServer = () => {
44614
44615
  };
44615
44616
  //#endregion
44616
44617
  export { startLanguageServer };
44617
- !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]="30e226f8-fb2c-5784-b48c-95e8d218d035")}catch(e){}}();
44618
- //# debugId=30e226f8-fb2c-5784-b48c-95e8d218d035
44618
+ !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]="94aee89d-9f35-5b59-a911-566ac2ef5a0a")}catch(e){}}();
44619
+ //# debugId=94aee89d-9f35-5b59-a911-566ac2ef5a0a