Skip to content

fix(installing.commands): key selectProjectByDir graph by project.rootDir#12380

Merged
zkochan merged 5 commits into
pnpm:mainfrom
tsushanth:fix-recursive-undefined-manifest
Jun 14, 2026
Merged

fix(installing.commands): key selectProjectByDir graph by project.rootDir#12380
zkochan merged 5 commits into
pnpm:mainfrom
tsushanth:fix-recursive-undefined-manifest

Conversation

@tsushanth

@tsushanth tsushanth commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #12379.

selectProjectByDir constructs a single-entry ProjectsGraph for the non-workspace install path (called from installDeps.ts:266 and import/index.ts:147). It was using searchedDir (opts.dir) as the key, but downstream recursive() in installing/commands/src/recursive.ts builds manifestsByPath from the projects array (keyed by project.rootDir) and then looks up entries via manifestsByPath[rootDir] where rootDir is drawn from Object.keys(selectedProjectsGraph). When opts.dir and project.rootDir differ in platform-normalized form (most often on Windows due to drive-letter casing), the lookup falls through as undefined and pnpm add <pkg> crashes with the exact error in the issue:

[ERROR] Cannot destructure property 'manifest' of 'manifestsByPath[rootDir]' as it is undefined.

Reporter's stack trace pins it to installing/commands/src/recursive.ts:240 (const { manifest } = manifestsByPath[rootDir] inside the importers.map body), reached via recursiverecursiveInstallThenUpdateWorkspaceStateinstallDeps.

Pin the graph key to project.rootDir in both selectProjectByDir callsites so the keys stay in sync with manifestsByPath. The bug only surfaced on the single-project (non-workspace) pnpm add path because workspace flows go through a different graph builder that already keys by project.rootDir.

Test commands

Could not reproduce on macOS (drive-letter casing isn't a concern there), but verified the logic on the source side. The change is small and targeted: same project lookup, just a different key for the single-entry graph object. Both call sites use the identical fix.

Notes

The bug was introduced in 11.6.0 in feat(pacquet): configurational dependencies support (#12285) when recursive.ts was added.

Summary by CodeRabbit

  • Bug Fixes
    • Fixed a Windows regression where pnpm add <pkg> would fail with a manifest error when run outside a workspace. The issue occurred due to path normalization differences, particularly when drive-letter casing varied between the current directory and the project root.

…tDir

`selectProjectByDir` constructs a single-entry `ProjectsGraph` for the
non-workspace install path. It was using `searchedDir` (`opts.dir`) as
the key, but downstream `recursive()` builds `manifestsByPath` from the
projects array (keyed by `project.rootDir`) and then looks up entries
via `manifestsByPath[rootDir]` where `rootDir` is drawn from
`Object.keys(selectedProjectsGraph)`. When `opts.dir` and
`project.rootDir` differ in platform-normalized form (most often on
Windows due to drive-letter casing), the lookup falls through as
`undefined` and `pnpm add <pkg>` crashes with:

  Cannot destructure property 'manifest' of 'manifestsByPath[rootDir]' as it is undefined

Pin the graph key to `project.rootDir` in both `installing/commands/src/installDeps.ts`
and `installing/commands/src/import/index.ts`, so the keys stay in sync
with `manifestsByPath`. Closes pnpm#12379

Written by an agent (Claude Code, claude-opus-4-7).
@tsushanth tsushanth requested a review from zkochan as a code owner June 13, 2026 13:28
@welcome

welcome Bot commented Jun 13, 2026

Copy link
Copy Markdown

💖 Thanks for opening this pull request! 💖
Please be patient and we will get back to you as soon as we can.

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 13, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0)

Grey Divider


Action required

1. No regression test added ✓ Resolved 📎 Requirement gap ☼ Reliability
Description
The PR fixes the manifestsByPath[rootDir] crash by changing the selectProjectByDir graph key,
but it does not add/update any automated test to prevent the regression from returning. This
violates the requirement to add regression coverage that would fail on the prior crashing behavior
and pass with the fix.
Code

installing/commands/src/installDeps.ts[R519-530]

function selectProjectByDir (projects: Project[], searchedDir: string): ProjectsGraph | undefined {
const project = projects.find(({ rootDir }) => path.relative(rootDir, searchedDir) === '')
if (project == null) return undefined
-  return { [searchedDir]: { dependencies: [], package: project } }
+  // Key the graph by `project.rootDir`, not `searchedDir`. Downstream
+  // `recursive()` builds `manifestsByPath` from `projects` (keyed by
+  // `project.rootDir`) and then iterates `Object.keys(selectedProjectsGraph)`
+  // to look up `manifestsByPath[rootDir]`. On Windows, `opts.dir` and
+  // `project.rootDir` can end up with different drive-letter casing or path
+  // separators after platform normalization, leaving the lookup `undefined`
+  // and surfacing as `Cannot destructure property 'manifest' of
+  // 'manifestsByPath[rootDir]' as it is undefined`.
+  return { [project.rootDir]: { dependencies: [], package: project } }
Evidence
Compliance ID 2 requires new/updated automated regression coverage explicitly asserting the prior
crash does not occur. The PR changes production code in selectProjectByDir (in both call sites)
but does not include any corresponding test change to exercise the mismatched opts.dir vs
project.rootDir scenario and assert pnpm add does not throw.

Add regression coverage for the pnpm add crash related to missing rootDir manifest
installing/commands/src/installDeps.ts[519-531]
installing/commands/src/import/index.ts[337-345]
.changeset/fix-windows-recursive-undefined-manifest.md[1-6]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
PR Compliance requires adding automated regression coverage for the Windows/non-workspace `pnpm add` crash where `manifestsByPath[rootDir]` was `undefined` and destructuring `manifest` crashed.
## Issue Context
The fix changes `selectProjectByDir` to key the single-entry `ProjectsGraph` by `project.rootDir` instead of `searchedDir`/`opts.dir`, preventing mismatched normalized paths (notably drive-letter casing on Windows) from causing `manifestsByPath[rootDir]` lookup to return `undefined`.
## Fix Focus Areas
- installing/commands/test/add.ts[331-413]
- installing/commands/src/installDeps.ts[519-531]
- installing/commands/src/import/index.ts[337-345]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ec6bd00a-e206-47b0-9b1c-26f707b0567e

📥 Commits

Reviewing files that changed from the base of the PR and between 426fae9 and cbb308c.

📒 Files selected for processing (3)
  • installing/commands/src/import/index.ts
  • installing/commands/src/installDeps.ts
  • installing/commands/test/add.ts
📜 Recent review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Compile & Lint
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use Standard Style with trailing commas, prefer functions over classes, declare functions after they are used relying on hoisting, limit function arguments to two or three with options objects for additional parameters
Follow import order: standard libraries, external dependencies (sorted alphabetically), then relative imports
Use JSDoc for function contracts (preconditions, postconditions, edge cases, why it exists) not for re-narrating the body; do not record past implementation shape or refactor history in comments

Files:

  • installing/commands/test/add.ts
  • installing/commands/src/installDeps.ts
  • installing/commands/src/import/index.ts
🧠 Learnings (3)
📚 Learning: 2026-05-14T09:04:00.133Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11622
File: resolving/npm-resolver/test/publishedBy.test.ts:350-354
Timestamp: 2026-05-14T09:04:00.133Z
Learning: In the pnpm/pnpm repository, ESLint is the authoritative style linter. Do not raise review findings for missing trailing commas in multiline function calls (e.g., `fs.writeFileSync(...)`) when this repo’s ESLint configuration does not report them and lint passes. Prefer deferring to the ESLint results for this specific trailing-comma rule rather than enforcing it manually in code review.

Applied to files:

  • installing/commands/test/add.ts
  • installing/commands/src/installDeps.ts
  • installing/commands/src/import/index.ts
📚 Learning: 2026-06-05T13:47:05.929Z
Learnt from: vsumner
Repo: pnpm/pnpm PR: 12190
File: installing/deps-installer/test/install/frozenStore.ts:2-17
Timestamp: 2026-06-05T13:47:05.929Z
Learning: In the pnpm/pnpm repository, the shared Jest preset keeps `injectGlobals` at its default (`true`), so `test` and `expect` are available as Jest globals. Therefore, reviewers should not flag (or treat as TypeScript/compilation errors) missing `import { test, expect } from 'jest/globals'` when a test file uses `test`/`expect` without importing them. Importing from `jest/globals` may still be used for consistency with sibling files, but it is not required for execution in this repo unless a Jest preset is explicitly configured with `injectGlobals: false`.

Applied to files:

  • installing/commands/test/add.ts
📚 Learning: 2026-06-05T13:47:26.046Z
Learnt from: vsumner
Repo: pnpm/pnpm PR: 12190
File: installing/deps-installer/src/install/index.ts:2337-2343
Timestamp: 2026-06-05T13:47:26.046Z
Learning: In the pnpm/pnpm codebase, `PnpmError` automatically prefixes `err.code` with `ERR_PNPM_` when you pass a code that does not already start with `ERR_PNPM_` (it normalizes `this.code` via `code.startsWith('ERR_PNPM_') ? code : `ERR_PNPM_${code}``). Therefore, during code review you should NOT flag `new PnpmError(...)` call sites for passing a bare error code (e.g., `new PnpmError('FROZEN_STORE_INCOMPATIBLE_WITH_PNPR', ...)`); the resulting `err.code` will still be `ERR_PNPM_FROZEN_STORE_INCOMPATIBLE_WITH_PNPR`.

Applied to files:

  • installing/commands/test/add.ts
  • installing/commands/src/installDeps.ts
  • installing/commands/src/import/index.ts
🔇 Additional comments (6)
installing/commands/src/installDeps.ts (2)

1-2: LGTM!


519-523: LGTM!

installing/commands/src/import/index.ts (2)

15-15: LGTM!


337-341: LGTM!

installing/commands/test/add.ts (2)

9-9: LGTM!


311-344: LGTM!


📝 Walkthrough

Walkthrough

Two one-line fixes in selectProjectByDir change the ProjectsGraph key from the caller-supplied searchedDir to the matched project.rootDir in both installDeps.ts and import/index.ts. A regression test is added to add.ts and a changeset entry documents the patch.

Changes

Windows Path Normalization Fix

Layer / File(s) Summary
Key ProjectsGraph by project.rootDir
installing/commands/src/installDeps.ts, installing/commands/src/import/index.ts, .changeset/fix-windows-recursive-undefined-manifest.md
selectProjectByDir now returns the graph keyed by the matched project.rootDir instead of the input searchedDir, so downstream manifestsByPath lookups succeed when paths differ in drive-letter casing on Windows. Changeset documents the patch for @pnpm/installing.commands and pnpm.
Regression test for dir/rootDir mismatch
installing/commands/test/add.ts
Adds a Jest test that calls add.handler with a dir differing from rootProjectManifest's rootDir and asserts the install succeeds with the dependency written to package.json.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Suggested reviewers

  • zkochan

Poem

🐇 A key was the culprit, mismatched by a letter,
The drive said "C:" but the map knew it better.
Now rootDir aligns with the path that we store,
No more undefined manifests to deplore.
Hop hop, Windows friends — pnpm works once more! 🪟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main fix: changing how selectProjectByDir keys the project graph to use project.rootDir instead of opts.dir.
Linked Issues check ✅ Passed The PR directly addresses issue #12379 by fixing the key mismatch between ProjectsGraph and manifestsByPath that caused Windows crashes when opts.dir and project.rootDir differed.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing the selectProjectByDir graph keying issue: two source file modifications, one test addition, and one changeset documentation entry.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install timed out. The project may have too many dependencies for the sandbox.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Fix non-workspace install graph keys to use project.rootDir
🐞 Bug fix 📝 Documentation 🕐 10-20 Minutes

Grey Divider

Walkthroughs

Description
• Key single-project ProjectsGraph entries by project.rootDir for consistent downstream lookups.
• Prevent Windows non-workspace pnpm add crash caused by path normalization differences.
• Add changeset documenting the regression fix and patch release impact.
Diagram
graph TD
A["installDeps.ts"] --> B["selectProjectByDir()"] --> D[("ProjectsGraph (key=rootDir)")] --> E["recursive()"] --> F[("manifestsByPath (key=rootDir)")] --> G["importers.map reads manifest"]
C["import/index.ts"] --> B
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Normalize/canonicalize graph keys (realpath/lowercase drive)
  • ➕ Would also tolerate other path-form mismatches beyond drive-letter casing
  • ➕ Could centralize normalization in one place for future callers
  • ➖ More invasive (needs consistent normalization semantics across platforms)
  • ➖ Risk of subtly changing behavior for symlinks/realpath resolution
2. Make recursive() lookup tolerant (fallback search by path equivalence)
  • ➕ Keeps upstream graph construction unchanged
  • ➕ Can defensively handle malformed/mixed keys from any caller
  • ➖ Adds runtime cost/complexity to a hot path (per-importer lookups)
  • ➖ Can mask upstream inconsistencies instead of fixing the source

Recommendation: The PR’s approach is the best tradeoff: keying the single-entry ProjectsGraph by project.rootDir fixes the mismatch at the source with minimal behavioral surface area. The alternatives (path canonicalization or tolerant lookup) are broader changes with higher platform/edge-case risk than necessary for this regression.

Grey Divider

File Changes

Bug fix (2)
index.ts Key single-project ProjectsGraph by project.rootDir in import flow +5/-1

Key single-project ProjectsGraph by project.rootDir in import flow

• Changes selectProjectByDir to return a ProjectsGraph keyed by project.rootDir instead of the searched directory. Prevents downstream manifestsByPath lookups from failing when path normalization differs (e.g., Windows drive-letter casing).

installing/commands/src/import/index.ts


installDeps.ts Key single-project ProjectsGraph by project.rootDir in non-workspace install flow +9/-1

Key single-project ProjectsGraph by project.rootDir in non-workspace install flow

• Updates selectProjectByDir to key the returned ProjectsGraph by project.rootDir, aligning with recursive()’s manifestsByPath indexing. Avoids undefined manifest destructuring when opts.dir and project.rootDir normalize differently on Windows.

installing/commands/src/installDeps.ts


Documentation (1)
fix-windows-recursive-undefined-manifest.md Add changeset for Windows non-workspace 'pnpm add' crash regression +6/-0

Add changeset for Windows non-workspace 'pnpm add' crash regression

• Introduces a patch-level changeset for @pnpm/installing.commands and pnpm. Documents the Windows-only crash scenario, root cause (graph key mismatch), and links to pnpm/pnpm#12379.

.changeset/fix-windows-recursive-undefined-manifest.md


Grey Divider

Qodo Logo

Comment thread installing/commands/src/installDeps.ts
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 14, 2026

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 426fae9

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 14, 2026

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit cbb308c

@zkochan zkochan merged commit 86e70d2 into pnpm:main Jun 14, 2026
9 of 10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cannot destructure property 'manifest' of 'manifestsByPath[rootDir]' as it is undefined.

2 participants