Skip to content

missingNodeModulesDetection false positives in pnpm strict node_modules layout #19

@drPytho

Description

@drPytho

[Disclaimer] This issue was partially created by AI, and audited and verified by me (If this is not appreciated I am happy to remove the issue)
I am also working on a fix for the issue, just want to make sure this is not something out of scope for the project.

missingNodeModulesDetection doesn't work with pnpm strict node_modules

Problem

missingNodeModulesDetection flags every workspace-specific dependency as "missing" in a pnpm monorepo.

pnpm uses a strict node_modules layout by default: workspace dependencies are only symlinked into each workspace's own node_modules/, not the root. rev-dep's missingNodeModulesDetection only checks the CWD resolver's nodeModules map, so any dependency declared in a workspace package.json (but absent from the root node_modules/) gets flagged as missing.

In a monorepo with 100+ workspaces this produces hundreds of false positives, making the check unusable.

orphanFilesDetection has a related issue: workspace src/index.ts files appear orphaned because they're only reachable via cross-package imports that the orphan analysis doesn't trace through.

Reproduction

pnpm monorepo, strict node_modules (default pnpm behavior):

my-monorepo/
  package.json              # root — no @ai-sdk/cerebras here
  node_modules/             # only root-level deps hoisted here
  packages/
    llm-clients/
      package.json          # "@ai-sdk/cerebras": "^4.0.0"
      node_modules/
        @ai-sdk/cerebras/   # symlinked by pnpm ✓
      src/
        index.ts            # import { ... } from '@ai-sdk/cerebras'

Config (single rule or per-workspace — same result):

{
  "configVersion": "1.5",
  "rules": [
    {
      "path": "packages/llm-clients",
      "followMonorepoPackages": true,
      "missingNodeModulesDetection": { "enabled": true }
    }
  ]
}

Result:

❌ Missing Node Modules Issues (1):
  - @ai-sdk/cerebras (imported from: packages/llm-clients/src/index.ts)

Expected: no issues — the package is declared and installed in the workspace.

Workarounds tested (none worked)

  • Per-workspace rule with path: "packages/llm-clients" — same false positive
  • --package-json packages/llm-clients/package.json CLI flag — same
  • Direct version "^4.0.0" instead of pnpm "catalog:" — same

Root cause

In nodeModules.go (~lines 168-174), GetMissingNodeModulesFromTree receives only the CWD resolver's node modules:

resolverForCwd := resolverManager.GetResolverForFile(cwd)
cwdNodeModules := resolverForCwd.nodeModules
// ...
GetMissingNodeModulesFromTree(tree, cwdNodeModules, ...)

The CWD resolver points at the root package.json, so only root-level dependencies are in cwdNodeModules. Workspace dependencies are held by their own resolvers but never consulted.

The monorepo infrastructure already handles this — ResolverManager creates a resolver per workspace and CollectAllNodeModules() (lines 288-306) aggregates them. It's just not called here.

Proposed fix

missingNodeModulesDetection (small change)

Replace the CWD-only lookup with the existing aggregation method:

// Before:
resolverForCwd := resolverManager.GetResolverForFile(cwd)
cwdNodeModules = resolverForCwd.nodeModules

// After:
cwdNodeModules := resolverManager.CollectAllNodeModules()

CollectAllNodeModules() already iterates all resolvers (root + workspace packages) and merges their nodeModules maps. This single change should fix the false positives.

A more precise alternative: for each flagged import, look up the resolver for the importing file (GetResolverForFile(importingFile)) and check that resolver's nodeModules. This would be per-file accurate rather than a global merge, but the global merge is simpler and sufficient since a "missing" module that exists in any workspace resolver is clearly installed.

orphanFilesDetection (medium change)

When followMonorepoPackages is enabled, automatically treat each workspace's entry points (derived from package.json exports/main fields) as valid entry points for the orphan analysis. This would require:

  1. In MonorepoContext.FindWorkspacePackages() — extract entry points from each workspace's package.json
  2. Pass these as additional validEntryPoints to FindOrphanFiles()
  3. Estimated 2-3 functions to modify

Environment

  • rev-dep: 2.9.1
  • pnpm: 10.x (strict node_modules layout)
  • Config version: 1.5

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions