Files
pnpm/pnpm11/installing/linking
Zoltan Kochan 2f703cb8d7 fix(hoisted): keep injected peer variants as separate copies (#14126)
Under `nodeLinker: hoisted`, peer-resolution variants of an **injected directory dependency** (a `file:` snapshot) collapsed onto the first-seen variant, like registry peer variants do since #14039 (and the TypeScript CLI's long-standing `depPathByPkgId` mapping). With #14034, edges declared against a losing variant then resolve to the surviving copy.

For registry packages that dedup is intended: every variant unpacks the same tarball, and peers resolve through the ancestry either way. An injected directory dep is different — each `file:` variant is materialized as its **own copy of the local package with its own peer-resolved dependency set**; that is the point of injecting. Collapsing rewires every dependent of the losing variant onto the survivor's children.

### Symptom

teambit/bit's root-components e2e (`nodeLinker: hoisted`): two Bit root components pin `is-odd@1` and `is-odd@2` across injected copies of the same component (`comp2@file:comp2(is-odd@1.0.0)` / `(is-odd@2.0.0)`). The second root's copy materialized as a symlink into the first root's tree and resolved the wrong peer:

```
AssertionError: expected '1.0.0' to match /^2\./
```

### The fix

Exempt `file:` snapshots from the collapse in the shared identity function — `pkg_id` in pacquet, `getHoisterPkgId` in the TypeScript CLI. Every index over the hoist result (`pkg_locations_by_pkg_id`, `package_ids_by_pkg_id`, `pkgLocationsByPkgId`) derives its keys from that function, so the variants stay apart with no further changes.

The registry collapse stays untouched: it is what stopped the per-path walk from exploding on peer-variant-heavy lockfiles (the OOM #14039 fixed), and `file:` snapshots cannot explode that way — there is one per injected workspace package.
2026-08-24 14:10:32 +02:00
..
2026-08-23 15:47:36 +02:00