* feat(pacquet): port resolving/git-resolver and wire it into the install chain Adds `pacquet-resolving-git-resolver`, the Rust port of pnpm's `@pnpm/resolving.git-resolver`. Recognises GitHub / GitLab / Bitbucket shortcut forms and full `git+ssh:` / `git+https:` / `ssh:` / plain `https://…/repo.git` URLs, runs `git ls-remote` to pin the commit (partial commit search, annotated-tag dereference, semver-range matching), and emits either a git-hosted tarball resolution or a `Git{repo,commit}` resolution. Production runners shell out to the system `git` binary via `tokio::task::spawn_blocking` and use the install-wide `ThrottledClient` for the HEAD probe. Widens the resolver-base contract so URL-shaped IDs fit: adds a `PkgResolutionId` newtype (rule-3 branded string, infallible `From<String>`/`From<&str>`/`From<&PkgNameVer>`), changes `ResolveResult.id` to that type, and adds `name_ver: Option<PkgNameVer>` so callers that need the structured `name@version` form keep working. npm-resolver fills both fields; git-resolver leaves `name_ver` `None` (the install path that consumes git resolutions hasn't landed yet, so those call sites panic with a TODO message until then). `DefaultResolver` now implements `Resolver` too (returns `Ok(None)` when no resolver in the chain claims), letting `resolve_dependency_tree` accept the chain directly. The install-side wiring in `install_without_lockfile.rs` constructs `DefaultResolver::new(vec![Box::new(npm_resolver), Box::new(git_resolver)])` with `RealGitProbe` / `RealGitRunner`, mirroring upstream's `createResolver` chain order. Test coverage: 51 unit tests in the new crate, including the full SCP-style URL repair matrix ported from `parsePref.test.ts` and the GitLab `/-/archive/` tarball regression for pnpm #11533. Full workspace `cargo nextest run` is green at 1635 tests. --- Written by an agent (Claude Code, claude-opus-4-7). * fix(pacquet): satisfy dylint perfectionist + rustdoc on git-resolver port * Reorder `#[derive(...)]` on `PkgResolutionId` to match the `prefix_then_alphabetical` rule the dylint Perfectionist lint enforces (`From` last after `Serialize`/`Deserialize`). * Add `()` to function intra-doc links that collide with same-named modules (`create_git_hosted_pkg_id`, `parse_bare_specifier`) so rustdoc's `broken-intra-doc-links` lint stops treating them as ambiguous. --- Written by an agent (Claude Code, claude-opus-4-7). * fix(pacquet): satisfy Perfectionist dylint lints on git-resolver port CI's `just ready` doesn't surface Perfectionist (it runs only as a dedicated dylint job on a nightly toolchain). Fixes: * Rename single-letter generics `P`/`R` → `Probe`/`Runner` on `GitResolver`, `PartialSpec::finalize`, `from_hosted_git`, and `resolve_ref`. * Rename single-letter closure / function / let-binding params (`s`/`h`/`c`/`p`/`i`/`g`/...) to descriptive names. * Replace Unicode ellipsis (`…`, U+2026) with ASCII `...` in comments. * Add trailing commas to multi-line `assert_eq!` / `assert!` invocations, and remove the stray trailing comma on a single-line one. Also fix follow-on JSR-resolver test cases that still read `result.id.{name,suffix}`: switch them to `result.name_ver.as_ref()...` to match the post-widening `ResolveResult` shape. --- Written by an agent (Claude Code, claude-opus-4-7). * fix(pacquet): address PR review on git-resolver port * Replace the two `.expect()` calls on `ResolveResult.name_ver` in the install path with `.ok_or_else()` that surfaces a typed error: `InstallPackageFromRegistryError::UnsupportedResolution` and a new `InstallWithoutLockfileError::UnsupportedInstallResolution`. Now that the git resolver is in the chain, a git/tarball/local resolution reaching the without-lockfile install path returns an error end-to-end instead of panicking. Add a regression test pinning the contract. * Make `percent_decode` (in `hosted_git.rs`) and `percent_decode_str` (in `parse_bare_specifier.rs`) UTF-8 aware: collect decoded bytes into a `Vec<u8>` and reassemble via `String::from_utf8`, falling back to the original input on malformed UTF-8 (matches Node's `decodeURIComponent` throwing a `URIError` that upstream's `try/catch` swallows). The byte→`char` cast was corrupting any multi-byte sequence (e.g., `%E2%80%A6` → ellipsis); regression test added. --- Written by an agent (Claude Code, claude-opus-4-7). * chore(pacquet): drop unused UnsupportedInstallResolution after rebase Main's `feat(pacquet): peer-dependency resolution stage` reworked `install_without_lockfile.rs` to derive the virtual-store name from the resolved depPath via `pacquet_deps_path::dep_path_to_filename` instead of reading `result.name_ver`. That removed the `.expect()` / `.ok_or_else()` site this error variant was added for; with no remaining callers, drop the dead variant. --- 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: