Sharmila 122ab0a1ed fix(deps-resolver): preserve locked optional peer candidates (#12075)
## Summary

Preserve compatible optional-peer versions already recorded in the lockfile
when pnpm re-resolves a workspace.

## Reproduction

Public reproduction:
<https://github.com/sharmila-oai/pnpm-optional-peer-lockfile-repro>

The workspace contains:

```text
packages/uses-vitest -> vitest@3.2.4
packages/older       -> jsdom@26.1.0
```

Vitest declares `jsdom` as an optional peer dependency.

The committed lockfile was generated when another workspace package also
depended on `jsdom@27.4.0`. At that time, pnpm selected the higher compatible
version for Vitest:

```text
vitest@3.2.4(jsdom@27.4.0)
```

That additional direct dependency was then removed from its `package.json`,
without regenerating the lockfile. This simulates a normal manifest edit.

Running:

```sh
pnpm install --lockfile-only --no-frozen-lockfile
```

unnecessarily rewrites Vitest's still-valid optional-peer context:

```diff
-        version: 3.2.4(jsdom@27.4.0)
+        version: 3.2.4(jsdom@26.1.0)
```

Both versions satisfy Vitest's optional peer range. The existing `27.4.0`
resolution remains valid and should not be discarded while pnpm updates the
lockfile.

## Cause

Preferred versions loaded from the wanted lockfile are stored as weighted
selectors:

```ts
{ selectorType: 'version', weight: EXISTING_VERSION_SELECTOR_WEIGHT }
```

`getHoistableOptionalPeers()` only recognized the plain string form:

```ts
specType === 'version'
```

As a result, it ignored the compatible locked `27.4.0` candidate. It only saw
`26.1.0`, which was rediscovered from `packages/older/package.json`, and
rewrote the peer context.

## Fix

Normalize the selector before checking its type, matching the handling already
used by `hoistPeers()` for required peers:

```ts
const specType = typeof selector === 'string'
  ? selector
  : selector.selectorType
```

This restores lockfile-seeded versions to the candidate set. It does not add a
new preference rule or force pnpm to keep every locked version. Optional-peer
auto-installation continues to choose the highest version satisfying every
recorded peer range.

The equivalent fix is included in pacquet, pnpm's Rust port.

## Validation

- Added matching TypeScript and Rust regression tests.
- Verified the public reproduction against `pnpm@11.4.0` and the patched CLI.
- Ran the focused TypeScript resolver checks and pacquet test, clippy, format,
  and `cargo nextest` checks.

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-06-02 07:30:52 +02:00
2026-04-10 18:30:33 +02:00
2026-05-27 15:15:01 +02:00
2026-05-29 17:26:13 +02:00
2026-05-29 17:26:13 +02:00
2026-05-29 17:26:13 +02:00
2026-05-29 17:26:13 +02:00
2026-05-27 15:15:01 +02:00
2026-04-30 23:03:46 +02:00
2026-05-29 17:26:13 +02:00
2026-05-29 17:26:13 +02:00
2026-05-27 15:15:01 +02:00
2026-05-27 15:15:01 +02:00
2026-05-29 17:26:13 +02:00
2026-05-27 15:15:01 +02:00
2026-05-29 17:26:13 +02:00
2026-05-29 17:26:13 +02:00
2026-05-24 02:23:07 +02:00
2026-05-29 17:26:13 +02:00
2026-05-29 17:26:13 +02:00
2026-04-30 23:19:31 +02:00
2026-05-29 17:26:13 +02:00
2026-05-29 17:26:13 +02:00
2026-04-30 23:03:46 +02:00
2026-05-27 15:15:01 +02:00
2026-01-16 16:31:31 +01:00
2024-03-21 01:09:22 +01:00

简体中文 | 日本語 | 한국어 | Italiano | Português Brasileiro

pnpm

Fast, disk space efficient package manager:

  • Fast. Up to 2x faster than the alternatives (see benchmark).
  • Efficient. Files inside node_modules are linked from a single content-addressable storage.
  • Great for monorepos.
  • Strict. A package can access only dependencies that are specified in its package.json.
  • Deterministic. Has a lockfile called pnpm-lock.yaml.
  • Works as a Node.js version manager. See pnpm runtime.
  • Works everywhere. Supports Windows, Linux, and macOS.
  • Battle-tested. Used in production by teams of all sizes since 2016.
  • See the full feature comparison with npm and Yarn.

To quote the Rush team:

Microsoft uses pnpm in Rush repos with hundreds of projects and hundreds of PRs per day, and weve found it to be very fast and reliable.

npm version OpenCollective OpenCollective X Follow Stand With Ukraine

Platinum Sponsors

Bit

Gold Sponsors

Sanity Discord Vite
SerpApi CodeRabbit Stackblitz
Workleap Nx

Silver Sponsors

Replit Cybozu devowl.io
u|screen Leniolabs_ Depot
Cerbos ⏱️ Time.now

Support this project by becoming a sponsor.

Background

pnpm uses a content-addressable filesystem to store all files from all module directories on a disk. When using npm, if you have 100 projects using lodash, you will have 100 copies of lodash on disk. With pnpm, lodash will be stored in a content-addressable storage, so:

  1. If you depend on different versions of lodash, only the files that differ are added to the store. If lodash has 100 files, and a new version has a change only in one of those files, pnpm update will only add 1 new file to the storage.
  2. All the files are saved in a single place on the disk. When packages are installed, their files are linked from that single place consuming no additional disk space. Linking is performed using either hard-links or reflinks (copy-on-write).

As a result, you save gigabytes of space on your disk and you have a lot faster installations! If you'd like more details about the unique node_modules structure that pnpm creates and why it works fine with the Node.js ecosystem, read this small article: Flat node_modules is not the only way.

💖 Like this project? Let people know with a tweet

Getting Started

Benchmark

pnpm is up to 2x faster than npm and Yarn classic. See all benchmarks here.

Benchmarks on an app with lots of dependencies:

License

MIT, except the pnpr/ directory, which is source-available under the PolyForm Shield License 1.0.0.

Description
No description provided
Readme MIT 346 MiB
Languages
Rust 55.9%
TypeScript 43.5%
JavaScript 0.5%