Project detection
Source: src/engine/project-detector.ts
Detects whether the target is a monorepo or a single project, and extracts
project metadata: NestJS version, ORM, and HTTP framework. Detection runs early
so the pipeline knows whether to loop over sub-projects or scan once. The
metadata is shown in the report and read by ORM-specific rules like
no-orm-in-controllers and no-raw-entity-in-response.
Monorepo detection
Input is the root directory to check:
targetPath: string // root directory to checkThe result is a map of sub-projects, or null:
interface MonorepoInfo {
projects: Map<string, string> // project name → relative root path
}
// Returns null if not a monorepoFive detection strategies are tried in priority order. The first match wins.
Strategy 1: nest-cli.json
Reads nest-cli.json and checks for:
monorepo: true- A
projectsmap with at least one entry
A matching file looks like this:
{
"monorepo": true,
"projects": {
"api": { "root": "apps/api" },
"admin": { "root": "apps/admin" }
}
}Strategy 2: pnpm-workspace.yaml
Reads pnpm-workspace.yaml and:
- Parses the
packages:patterns (block-style and inline arrays are both supported) - Globs for
package.jsonfiles matching those patterns - Filters to packages that have
@nestjs/coreor@nestjs/commonindependencies,devDependencies, orpeerDependencies
The patterns it reads:
packages:
- "apps/*"
- "packages/*"Strategy 3: package.json workspaces (npm / Yarn)
If no pnpm-workspace.yaml exists, reads the workspaces field from the root package.json. Both the array form and the object form (Yarn classic) are supported:
{
"workspaces": ["apps/*", "packages/*"]
}The object form nests the globs under packages:
{
"workspaces": {
"packages": ["apps/*", "packages/*"]
}
}Workspace globs resolve the same way as in strategy 2, filtering to packages with @nestjs/core or @nestjs/common. This strategy is skipped when pnpm-workspace.yaml exists, because pnpm repos may have both and would otherwise be detected twice.
Strategy 4: nx.json (Nx)
Detects the presence of nx.json, then scans for project.json files throughout the repo (excluding node_modules and the root-level project.json). For each project directory with a project.json, reads the sibling package.json. The project is included when that file depends on @nestjs/core or @nestjs/common.
A failed package.json check does not end it. Nx projects declare their dependencies at the workspace root, so many have no package.json at all. The strategy then globs the project's **/*.module.ts files and includes the project when one of them contains the text @nestjs/common. It is a substring match on the file, not an import check, and it runs for a project whose package.json exists without a Nest dependency too.
Strategy 5: lerna.json (standalone Lerna)
Reads lerna.json and uses its packages globs, defaulting to ["packages/*"] when they are not specified. Skipped when useWorkspaces is true. Lerna then delegates to npm or Yarn workspaces, so strategy 3 handles detection instead.
A minimal lerna.json:
{
"packages": ["packages/*"]
}Fallback warning
Some repos carry monorepo markers but no NestJS package. The markers are lerna.json, turbo.json, nx.json, pnpm-workspace.yaml, and a workspaces field in package.json. When a marker is present and no strategy finds a NestJS package, nestjs-doctor warns and falls back to single-project mode.
If a strategy does match, the pipeline runs stages 1-9 independently for each sub-project, then aggregates the results into a combined score.
Single project detection
Input is the project root:
targetPath: string // project root directoryThe result is the project metadata:
interface ProjectInfo {
name: string // from package.json name, or "unknown"
nestVersion: string | null // installed version, else the declared one
orm: string | null // detected ORM
framework: "express" | "fastify" | null
moduleCount: number // 0 initially, updated by scanner
fileCount: number // 0 initially, updated by scanner
}How it works
Reads the project's package.json and inspects dependencies plus
devDependencies. An ORM that shows as null is missing from both.
ORM detection takes the first match:
@prisma/client→"prisma"typeorm→"typeorm"@mikro-orm/core→"mikro-orm"sequelize→"sequelize"mongoose→"mongoose"drizzle-orm→"drizzle"
Framework detection:
@nestjs/platform-fastify→"fastify"@nestjs/platform-expressor@nestjs/core→"express"
The NestJS version is read from node_modules/@nestjs/core, walking up from the
scanned path so a hoisted workspace install is found. The walk stops at the
first directory holding a .git.
With nothing installed it falls back to the first version the declared spec
names, so ^10.0.0 reads as 10.0.0. A spec naming no version, such as
workspace:*, leaves nestVersion as null.