* feat(pacquet): port auto-install-peers algorithm
Replaces the placeholder peer-folding behavior with a faithful port of
pnpm's `hoistPeers` algorithm. Missing required peers are hoisted to
the importer's direct deps (shared across consumers, not nested), with
a multi-pass loop that re-resolves until no required peer remains and
the optional-peer pass picks already-available versions from the
preferred-versions map.
- New `pacquet-lockfile-preferred-versions` crate seeds the version-
picker tie-break table from manifest + lockfile snapshots.
- New `hoist_peers` module ports `hoistPeers` and
`getHoistableOptionalPeers` with the full upstream test suite.
- New `resolve_importer` orchestrator drives the multi-pass hoist
loop and threads `parent_pkg_aliases` / `all_preferred_versions`
across iterations.
- `resolve_dependency_tree` exposes `TreeCtx` / `extend_tree` /
`snapshot` so the orchestrator can extend the tree incrementally
without re-walking already-resolved subtrees.
- `auto-install-peers-from-highest-match` config setting added,
mirroring upstream's flag for range-merge behavior.
Ported from pnpm's installing/deps-resolver `resolveRootDependencies`
and hoistPeers.ts at commit 097983fbca.
---
Written by an agent (Claude Code, claude-opus-4-7).
* chore: drop changeset
Pacquet changes don't get changesets.
---
Written by an agent (Claude Code, claude-opus-4-7).
* test(pacquet): port single-importer auto-install-peers cases
Port nine portable scenarios from
installing/deps-installer/test/install/autoInstallPeers.ts as
orchestrator-level unit tests in resolve_importer/tests.rs:
- skip optional peers without a preferred-version hint
- dedupe via range intersection when consumers agree
- drop the peer when ranges conflict (default)
- install via `||` join when autoInstallPeersFromHighestMatch is on
- reuse a peer already brought by a sibling (preferred-versions)
- skip hoisting when the root already has the dep as a direct
- collapse the same missing peer across a transitive chain
- prefer the version pinned in the importer's own peerDependencies
- reuse a regular-dep version over re-resolving via the peer arm
The last case exposed a gap: the orchestrator wasn't including the
importer's own `peerDependencies` as initial wanted deps when
auto-install-peers is on, mirroring upstream's
`getAllDependenciesFromManifest({ autoInstallPeers: true })`. Fixed
in resolve_importer alongside the test ports.
Workspace, frozen-lockfile mutation, hook-using, override-using,
and webpack-circular-deps tests from the upstream file remain
un-ported pending the matching features in pacquet.
---
Written by an agent (Claude Code, claude-opus-4-7).
* fix(pacquet): satisfy Dylint Perfectionist + rustdoc checks
CI's Dylint job (Perfectionist lints) and the Doc job both fail on
the prior commits' style. Cleanups:
- Rename single-letter closure params (`|c|`, `|d|`, `|e|`, `|r|`)
to descriptive names in resolve_importer, hoist_peers,
version_selector_type, and the install + orchestrator test files.
Triggered `perfectionist::single-letter-closure-param`.
- Add trailing commas to multi-line `assert!` invocations in
resolve_importer/tests.rs. Triggered
`perfectionist::macro-trailing-comma`.
- Disambiguate `[`crate::resolve_importer`]` and friends with the
`[`fn@crate::resolve_importer`]` form — `resolve_importer`,
`resolve_peers`, `hoist_peers`, and `get_hoistable_optional_peers`
are each both a module and a re-exported function, which rustdoc
flags as ambiguous.
- Replace the broken `[`resolve_dependency_tree`]` and
`[`resolve_peers`]` intra-doc links in install_without_lockfile.rs
with prose now that those symbols are no longer in scope there
(the orchestrator wraps them).
- Drop the link to the private `add_weight_to_version_selector`
helper from `get_preferred_versions_from_lockfile_and_manifests`'
public docs and the link to the private `resolve_node` from
`extend_tree`'s public docs.
- Remove the redundant explicit `(crate::extend_tree)` link target
in lib.rs's module doc.
No behavior change; tests pass.
---
Written by an agent (Claude Code, claude-opus-4-7).
* fix(pacquet): include prereleases in hoist-peers max_satisfying
Upstream's `hoistPeers` and `getHoistableOptionalPeers` both pass
`{ includePrerelease: true }` to `semver.maxSatisfying`, so a
prerelease candidate from the preferred-versions table (e.g.
`18.0.0-rc.1`) satisfies a regular range (e.g. `^18.0.0`). Rust's
`node_semver::Range::satisfies` follows strict semver semantics and
rejects prereleases when the range has none of its own, which
silently dropped valid picks in the hoist loop.
Mirror the strip-and-retry pattern already used by
`satisfies_with_prereleases` in `resolve_peers.rs`: a new
`satisfies_including_prerelease` helper in `hoist_peers.rs` retries
with the prerelease tag stripped, and `max_satisfying` /
`get_hoistable_optional_peers` now both go through it.
Add two regression tests covering an `^18.0.0` range against an
`18.0.0-rc.1` preferred-versions entry for each function.
Reported by CodeRabbit on PR #11784.
---
Written by an agent (Claude Code, claude-opus-4-7).
* fix(pacquet): disambiguate intra-doc link to resolve_peers
The doc comment I added for `satisfies_including_prerelease` linked
to `crate::resolve_peers`, which rustdoc flags as ambiguous (both a
module and a re-exported function). Replace the link with prose
since the helper this paragraph compares to is module-internal
anyway — no link can resolve to it from outside the module.
---
Written by an agent (Claude Code, claude-opus-4-7).
简体中文 | 日本語 | 한국어 | Italiano | Português Brasileiro
Fast, disk space efficient package manager:
- Fast. Up to 2x faster than the alternatives (see benchmark).
- Efficient. Files inside
node_modulesare 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 we’ve found it to be very fast and reliable.
Platinum Sponsors
|
|
Gold Sponsors
|
|
|
|
|
|
|
|
|
|
|
Silver Sponsors
|
|
|
|
|
|
|
|
|
⏱️ 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:
- 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 updatewill only add 1 new file to the storage. - 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: