How react-doctor Automatically Detects the Framework and React Version
react-doctor analyzes your project's 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, 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 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.
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:
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.
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:
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:
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:
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 uses a regex to isolate the major digit:
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:
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:
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.jsondependencies against a predefined mapping of package names to framework identifiers inpackages/react-doctor/src/utils/discover-project.ts. - The tool handles monorepo complexity by resolving
catalog:references throughresolveCatalogVersion, which traverses workspace configurations to find concrete semver strings. - React versions are extracted from
dependencies,devDependencies, orpeerDependencies, with fallbacks to root and workspace-level declarations. - The major version is parsed using
parseReactMajorinutils/parse-react-major.tsto enable version-specific linting rules. - Consumption of this data occurs in
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 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.
Where does the version detection logic live in the codebase?
The core implementation resides in 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, while the diagnostic consumer is located in 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →