# How react-doctor Automatically Detects the Framework and React Version

> Discover how react-doctor automatically detects project frameworks and React versions by analyzing package.json, matching dependencies, and resolving monorepo references.

- Repository: [Million Software, Inc./react-doctor](https://github.com/millionco/react-doctor)
- Tags: how-to-guide
- Published: 2026-05-12

---

**react-doctor analyzes your project's [`package.json`](https://github.com/millionco/react-doctor/blob/main/package.json) to detect the framework and React version by merging all dependency fields, matching against known framework packages, and resolving catalog references in monorepo environments.**

The millionco/react-doctor tool tailors its diagnostics to your specific React environment by automatically identifying whether you're running React, Next.js, Expo, or React Native, along with the exact version installed. This detection happens in [`packages/react-doctor/src/utils/discover-project.ts`](https://github.com/millionco/react-doctor/blob/main/packages/react-doctor/src/utils/discover-project.ts), where the tool parses dependency declarations and handles complex monorepo configurations including PNPM and Yarn catalogs.

## Scanning package.json Dependencies

The detection process begins by reading the nearest [`package.json`](https://github.com/millionco/react-doctor/blob/main/package.json) file and aggregating every possible dependency declaration.

### Merging Dependency Fields

The `collectAllDependencies` helper merges `dependencies`, `devDependencies`, and `peerDependencies` into a single map. This ensures that `react` declarations in any section are visible to the detection logic.

```typescript
const collectAllDependencies = (packageJson: PackageJson) => ({
  ...packageJson.peerDependencies,
  ...packageJson.dependencies,
  ...packageJson.devDependencies,
});

```

This unified dependency map serves as the foundation for both framework identification and version extraction.

## Framework Detection Logic

Once dependencies are collected, react-doctor determines which framework the project uses by scanning for specific package names.

### The Framework Package Mapping

The detection relies on a hardcoded mapping of package names to framework identifiers defined in [`packages/react-doctor/src/utils/discover-project.ts`](https://github.com/millionco/react-doctor/blob/main/packages/react-doctor/src/utils/discover-project.ts):

```typescript
const FRAMEWORK_PACKAGES: Record<string, Framework> = {
  react: "react",
  "react-dom": "react",
  next: "next",
  expo: "expo",
  "react-native": "react-native",
  // …other recognised packages
};

```

### Detecting the Specific Framework

The `detectFramework` function iterates through the merged dependencies and returns the first matching framework identifier.

```typescript
const detectFramework = (dependencies: Record<string, string>): Framework => {
  for (const [pkg, framework] of Object.entries(FRAMEWORK_PACKAGES)) {
    if (dependencies[pkg]) return framework;
  }
  return "unknown";
};

```

If no recognized packages are found, the function returns `"unknown"`, allowing react-doctor to proceed with generic analysis.

## Resolving React Version Strings

After identifying the framework, react-doctor extracts the React version string from the merged dependencies.

### Extracting Raw Version Declarations

The `discoverProject` function first attempts to read the `react` key directly from the dependency map:

```typescript
const rawReactVersion = allDependencies.react ?? null;
const reactVersion =
  rawReactVersion && !isCatalogReference(rawReactVersion)
    ? rawReactVersion
    : null;

```

If the version is a plain semver string like `"^18.2.0"`, it is used immediately. If it is a catalog reference, the tool triggers specialized resolution logic.

### Handling Catalog References in Monorepos

Modern monorepos often use PNPM or Yarn catalogs to centralize version management. When react-doctor encounters a `catalog:` reference (e.g., `"catalog:react"`), it resolves the concrete version using workspace-aware lookup:

```typescript
const resolveCatalogVersion = (
  packageJson: PackageJson,
  packageName: string,
  rootDirectory?: string,
  explicitCatalogReference?: string,
): string | null => {
  // …check packageJson.catalog, packageJson.catalogs,
  // …search pnpm‑workspace.yaml catalogs, etc.
};

```

This resolution traverses the workspace tree, checks root-level catalog definitions, and falls back to `null` if no concrete version can be determined.

## Determining the Final Version

The ultimate React version is computed by layering multiple potential sources. The `discoverProject` function constructs the final `projectInfo` object by prioritizing resolution results:

```typescript
const projectInfo = {
  reactVersion:
    rootInfo.reactVersion ??
    reactCatalogVersion ??
    workspaceInfo.reactVersion,
  // …
};

```

If none of these sources yields a version string, `reactVersion` remains `null`, and react-doctor runs with framework-agnostic defaults.

## Parsing the Major Version

For rule-selection purposes, react-doctor extracts the major version number from the resolved semver string. The `parseReactMajor` utility in [`packages/react-doctor/src/utils/parse-react-major.ts`](https://github.com/millionco/react-doctor/blob/main/packages/react-doctor/src/utils/parse-react-major.ts) uses a regex to isolate the major digit:

```typescript
export const parseReactMajor = (reactVersion: string | null | undefined): number | null => {
  if (typeof reactVersion !== "string") return null;
  const trimmed = reactVersion.trim();
  const match = trimmed.match(/^(\d+)\./);
  return match ? Number(match[1]) : null;
};

```

This integer value drives the conditional application of React 19-specific rules or legacy compatibility checks.

## Practical Implementation Example

You can invoke the detection logic programmatically using the `discoverProject` export:

```typescript
import { discoverProject } from "react-doctor";

async function demo() {
  const info = await discoverProject("/path/to/your/project");
  console.log("Framework :", info.framework);   // → "react", "next", "expo", …
  console.log("React ver :", info.reactVersion); // → "^18.2.0", "^19.0.0", or null
}

```

For direct access to the framework detection heuristic:

```typescript
import { detectFramework } from "react-doctor/src/utils/discover-project";

const deps = { react: "^18.2.0", next: "^13.0.0" };
console.log(detectFramework(deps)); // "next"

```

## Summary

- **react-doctor** detects frameworks by scanning merged [`package.json`](https://github.com/millionco/react-doctor/blob/main/package.json) dependencies against a predefined mapping of package names to framework identifiers in [`packages/react-doctor/src/utils/discover-project.ts`](https://github.com/millionco/react-doctor/blob/main/packages/react-doctor/src/utils/discover-project.ts).
- The tool handles monorepo complexity by resolving `catalog:` references through `resolveCatalogVersion`, which traverses workspace configurations to find concrete semver strings.
- React versions are extracted from `dependencies`, `devDependencies`, or `peerDependencies`, with fallbacks to root and workspace-level declarations.
- The major version is parsed using `parseReactMajor` in [`utils/parse-react-major.ts`](https://github.com/millionco/react-doctor/blob/main/utils/parse-react-major.ts) to enable version-specific linting rules.
- Consumption of this data occurs in [`packages/react-doctor/src/scan.ts`](https://github.com/millionco/react-doctor/blob/main/packages/react-doctor/src/scan.ts), which drives the diagnostic engine based on the detected environment.

## Frequently Asked Questions

### How does react-doctor handle monorepos with catalog references?

react-doctor detects `catalog:` strings (such as `"catalog:react"`) using `isCatalogReference`, then resolves them via `resolveCatalogVersion`. This function searches the local [`package.json`](https://github.com/millionco/react-doctor/blob/main/package.json) catalog fields, traverses PNPM or Yarn workspace configurations, and checks the monorepo root for centralized version definitions before falling back to `null`.

### What frameworks can react-doctor detect automatically?

The tool recognizes **React**, **Next.js**, **Expo**, and **React Native** by default, along with any framework mapped in the `FRAMEWORK_PACKAGES` constant. Detection occurs when the corresponding package name appears in any dependency field of [`package.json`](https://github.com/millionco/react-doctor/blob/main/package.json).

### Where does the version detection logic live in the codebase?

The core implementation resides in [`packages/react-doctor/src/utils/discover-project.ts`](https://github.com/millionco/react-doctor/blob/main/packages/react-doctor/src/utils/discover-project.ts), which exports `discoverProject` and `detectFramework`. The major version parser lives in [`packages/react-doctor/src/utils/parse-react-major.ts`](https://github.com/millionco/react-doctor/blob/main/packages/react-doctor/src/utils/parse-react-major.ts), while the diagnostic consumer is located in [`packages/react-doctor/src/scan.ts`](https://github.com/millionco/react-doctor/blob/main/packages/react-doctor/src/scan.ts).

### How does react-doctor determine which rules to apply based on the React version?

After resolving the version string, react-doctor calls `parseReactMajor` to extract the numeric major version. This value is passed to the rule engine, which selectively enables or disables diagnostics—such as React 19-specific linting rules—based on the returned integer or `null` for unknown versions.