* test(testing-utils): detach git fixtures from the contributor's global config `pipeline_cache::symlinked_inputs_are_hashed_as_link_targets` builds a project whose tracked input is a symlink named `CLAUDE.md`, and asserts that retargeting the link invalidates the task. On a machine whose `core.excludesFile` ignores that name, `git add -A` never stages the link and the `git ls-files --cached --others --exclude-standard` pnpm runs to collect cache inputs never lists it. The link is then not part of the task's cache key, retargeting it changes nothing, and pnpm correctly reports the hit the test refuses. The symlink hashing the test covers is right, so the fixture is what needs fixing. `detach_from_global_config` points `core.excludesFile` at a path that does not exist, in the repo's own local configuration. Local configuration also reaches the `git` pnpm itself spawns inside the repo, so one setting covers the fixture's staging and the listing under test. The path is inside `.git/info` rather than `/dev/null` so it holds on Windows too. The `gpgsign` overrides `GitRepoFixture::init` already made for the same reason move in beside it. `init_isolated_repo` gives the two suites that call `git init` directly the same isolation without the work tree and bare clone pair `GitRepoFixture` exists to provide. `symlinked_input_project` now checks that the link is staged before any caching assertion runs, because the failure this produced named neither git nor the ignore rule that caused it. Closes pnpm/pnpm#14713 * fix(cargo): gate the non-UTF-8 name test's imports with the test `import_package`, `CheckoutPackage`, and `BTreeSet` are referenced only by `a_file_whose_name_is_not_utf8_is_refused`, which is compiled out on macOS because APFS answers `EILSEQ` for the name it needs. At module scope they were dead imports there, so `cargo clippy -D warnings` failed on macOS while passing on Linux. * test(testing-utils): narrow the fixture isolation contract to what it delivers `init_isolated_repo` promised that a contributor's global git configuration could not reach the repo, which overstated three `git config` overrides. Full isolation is not available here: the point of writing the overrides into the repo's local configuration is that they also govern the `git` pnpm spawns inside it, which an environment variable on the fixture's own commands would miss. So the doc now names the settings it handles and says that anything else still reaches the repo, and `detach_from_global_config` becomes `override_global_config` to match. `core.hooksPath` joins the overridden set. A contributor who points it at a hooks directory globally would otherwise have their own hooks, which are arbitrary code, run on a fixture's commits. `init_isolated_repo` passes `-b main` rather than leaving the branch name to `init.defaultBranch`, as `GitRepoFixture::init` already does. `tracked_files` replaces the hand-rolled `git ls-files` in `symlinked_input_project`, which read stdout and ignored the exit status. A failing `git` would have surfaced as an empty listing, blaming the ignore rule the assertion exists to detect for what was a git error. The existing `git` helper already asserts the status and carries stderr into the panic. * Fix typo in documentation comment for git_repo.rs Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * test(testing-utils): isolate the bare repo and every suite that commits Running the git-backed suites with `GIT_CONFIG_GLOBAL` pointed at a hostile configuration (a global excludes file, a `core.hooksPath` whose hooks all fail, `init.defaultBranch = trunk`, `gpgsign = true`) showed three gaps the fixture isolation still left open. `GitRepoFixture` overrode the work tree only. `git push` into the bare repo runs `pre-receive` and `update` from that repo's own `core.hooksPath`, so a contributor's global hooks rejected every mirror. The bare repo now gets the same overrides, and `override_global_config` takes the git directory so the placeholder path lands inside it for both repo shapes. The bare init also passes `-b main`: without it, `HEAD` inherits `init.defaultBranch` and points at a branch the mirror never pushes. `pipeline_cargo_cache` and `exec_recursive` also `git init` their own repos and commit into them, and both failed on the global `pre-commit` hook. They now go through `init_isolated_repo`, which makes their per-command `-c user.name`, `-c commit.gpgsign=false`, and `--no-gpg-sign` overrides redundant. `tracked_files` listed tracked and untracked-but-not-ignored files, so it is now `unignored_files`. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XPJkFm8WwhDuQpvC5CAmBo * test(testing-utils): keep global templates and attributes out of fixtures Two more user-global settings reached the fixture repos. `init.templateDir` seeds every new repo's `info/exclude`, and no configuration setting overrides that file, so a template that ignores a fixture name hid it from `git add -A` and from the listing pnpm hashes exactly as the global excludes file did. Every fixture `git init` now passes `--template=`, which skips the template directory entirely. `core.attributesFile` can assign a `clean` filter to a fixture's files, and git runs that filter, the contributor's own command, on `git add`. The override now points it at the same absent path as the excludes file and hooks directory. Verified under a global config carrying a template whose `info/exclude` lists `CLAUDE.md` and a required `*.md` clean filter that exits 1: the seven git-backed suites and the `cargo_deps::git` unit tests pass, and a repo initialised the old way loses the file and fails the add. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XPJkFm8WwhDuQpvC5CAmBo * test(testing-utils): keep a global fsmonitor out of fixtures A user-global `core.fsmonitor` names a command git consults whenever it refreshes the index, so `git add` and commit in a fixture repo ran the contributor's own program. The fixture override now sets it to `false` locally, beside the hooks and attributes overrides that close the same class of setting. Verified with a global `core.fsmonitor` that prints and exits 1: a repo initialised the old way runs it on `git add`, the fixture does not, and the seven git-backed suites and `cargo_deps::git` unit tests pass under the full hostile configuration. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XPJkFm8WwhDuQpvC5CAmBo --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Zoltan Kochan <z@kochan.io> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
简体中文 | 日本語 | 한국어 | 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.
- Experimental Rust port. Includes pacquet, an experimental port of the CLI written in Rust.
- 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:
License
MIT, except the pnpr/ directory, which is source-available under the PolyForm Shield License 1.0.0.