pnpm v12 resolved `pnpm add <pkg>` without a version, and a version-less
`npm:` or `jsr:` dependency, to the `latest` dist-tag even when the `tag`
setting named another one. pnpm v11 reads `tag` from pnpm-workspace.yaml,
the global config, `PNPM_CONFIG_TAG`, and `--config.tag`, and threads it
as `defaultTag` into `parseWantedDependencies` (installSome.ts) and the
resolution context (createResolutionContext.ts).
`Config` now carries `tag`, read from the same layers, with
`Config::default_tag` falling back to `latest`. A brand-new `add` with no
version resolves a non-default tag the way an explicit `add <pkg>@<tag>`
does, so the picked version is also locked. A `jsr:` add and the install
resolver take the same default. `dlx`, `add --config`, `update --latest`,
and `outdated` keep `latest`, as they do in v11.
Tested with e2e tests for the yaml setting, `--config.tag`, and a
version-less `npm:` alias, each seen to fail with `default_tag` forced to
`latest`, plus config unit tests for apply, override, reset, and the
resolved projection.
Part of pnpm/tasks#191
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pnpm v12 read no `requiredScripts` setting, so `pnpm -r run <script>`
ran normally when a selected project lacked a script the workspace
declares required. pnpm v11 fails such a run before dispatching anything
(`throwIfRequiredScriptIsMissing` in
pnpm11/exec/commands/src/runRecursive.ts), with
ERR_PNPM_RECURSIVE_RUN_NO_SCRIPT and the message
`Missing script "<script>" in packages: <names>`.
The config crate now reads `requiredScripts` from pnpm-workspace.yaml
into `Config::required_scripts`. The global config.yaml drops it, as v11
does, since it is not a config-file key there; v11 reads no environment
variable for it either. The recursive run checks it where v11 does,
after `--resume-from` and the dry-run exit, before the hidden-script
filter and the no-script check, and after the `pnpm <script>` shorthand
decides whether to fall back to `exec`. Projects are named by their
manifest name, falling back to their directory.
Tested by a run_recursive e2e module that fails without the check,
including a nameless project listed by its directory and a --filter run
that checks only the selected projects, and a config test for the
workspace and global sources. The requiredScripts docs of both versions
now say the check covers the projects selected for the run.
Part of pnpm/tasks#191.
Correct the v12 guides from agent-skills.md through lockfile.md against
pnpm/crates, and fix the same wrong text on the v11 twins where pnpm11
confirms it. Part of pnpm/tasks#191.
Wrong claims (both versions):
- configuring: the global auth file is auth.ini, not rc; list the
npm_config_* exceptions (workspace dir, userconfig, URL-scoped auth).
- config-dependencies: the hook import used an invalid ESM specifier.
- docker: Example 3 ran fetch --prod and built without an offline install
or pnpm-workspace.yaml.
- errors: drop MISMATCHED_RELEASE_CHANNEL, describe INVALID_NODE_VERSION
through nodeVersion, PEER_DEP_ISSUES needs strictPeerDependencies.
- faq: no lockfile deletion after a readPackage hook, default store order,
junctions depend on symlink privilege.
- filtering: `!./lib` matches one project; use `!./lib/**`.
- git branch lockfiles: every non [A-Za-z0-9_.-] character becomes `!`,
the name is lowercased, merging deletes branch lockfiles, "or" patterns.
- git-worktrees: remove the nonexistent worktree:new and shell/wt.sh.
- global-packages: orphaned dirs are cleaned by add -g (and update -g in
v12), not store prune; document that add -g replaces every group that
contains the package.
- how-peers-are-resolved: current peer directory names.
- completion: built-in completion already covers --filter and scripts.
- feature-comparison: broken JSR anchor.
Wrong claims (v12 only):
- cargo: cargo.indexUrl fails the parse, overrides apply to adding only,
merge the duplicated Cargo index section into Registries.
- global-virtual-store: pnx keeps its own virtual store (dlx part only;
the global-install claims are left for a separate decision).
- installation: the npm package installs on Node.js 18, only get-pnpm
needs 22.13; trim unverified compatibility rows.
Missing facts (v12): agent-skills group filtering and naming, catalogs
(catalogs.default, pnx, --save-catalog, error codes), cargo recursive
add, build-std, resolution keys, completion zsh/pwsh setup, config
dependency location and error codes, include error codes, CI opt-outs and
pnpm ci, finders on list, filtering settings and --no-fail-if-no-match,
global install refusals, trust for npm-registry package managers, Rust
shims, overrides link from aliases.
Stale history (v12): drop v11/v10 version markers and "since v11"
narration in continuous-integration, filtering, finders, git-worktrees,
global-packages, global-virtual-store, how-peers-are-resolved, lockfile,
completion, configuring.
Review follow-up: the Node.js compatibility table keeps the pnpm 10
column; Docker Example 3 builds with all dependencies in a build stage
and ships only production dependencies, with the pnpm-workspace.yaml
caveat (both versions); catalogs keep the bare-path form; v11 global
packages name `pnpm update -g` as cleaning orphans too; the cargo
build-std note says when rust-src must be listed.
Second review: build-std needs a non-empty list; the Docker fetch layer
also depends on the project settings; branch lockfile names keep only
ASCII characters; the conflict merge needs both sides to parse; Rust
shims and package managers are default globalShims participants;
pnpm self-update also cleans orphaned global groups; v12 pnpm login
writes tokens to config.yaml; legacyDirFiltering keeps the subtree
wording (the v12 mismatch is pnpm/tasks#196); the v12 Docker
example copies the files that `include` names before pnpm fetch.
Wrong claims (v12 and the v11 twin where the v11 source agrees):
- proxy precedence: a configured httpsProxy/httpProxy/noProxy wins and the
environment is only a fallback; HTTPS never falls back to HTTP_PROXY
- proxy opt-out: false or null in httpsProxy, httpProxy, or noProxy reads
as unset; only proxy: false turns proxying off
- networkConcurrency auto value is clamped to 64-96, not 16-64
- maxsockets default: unset in v12, 50 in v11
- stateDir on macOS is ~/.local/state/pnpm
- unsafePerm only moves TMPDIR to node_modules/.tmp
- ignoreWorkspaceRootCheck affects pnpm add only; the duplicate CLI
section is removed in favor of the workspaces page
- useRunningStoreServer has no effect and is removed
- .npmrc also supplies TLS, proxy, and local-address settings
- the list of keys a project pnpm-workspace.yaml cannot set now names
scope (both) and macosBackup, ci, tools, globalShims (v12)
- v12: the GVS CI auto-disable sentence, the frozenStore Node.js
requirement, the JavaScript shell emulator, and the dlx global virtual
store claim are corrected
Missing settings (v12): saveExact, embedReadme, scriptsPrependNodePath,
userAgent, lockfileDir, excludeLinksFromLockfile, globalVirtualStoreDir,
externalDependencies, autoInstallPeersFromHighestMatch; loglevel silent;
PNPM_HOME defaults for globalDir/globalBinDir; childConcurrency
negative values; what ci changes and where updateNotifier is skipped.
Index (v12): lists every section on the group pages, including
progress, saveTypes, autoDedupe, maxTarballUnpackedSize, and macosBackup.
Stale history (v12): removed pre-v12 "Added in" markers and rewrote
"Since v10/v11" and "Before v11" sentences as current behavior.
Part of pnpm/tasks#191.
Part of pnpm/tasks#191.
Wrong claims, fixed on the v12 page and on the v11 twin where it had
the same text:
- audit: `--interactive` implies `--fix=override`; `info` is a valid
audit level.
- change check validates the pending intents.
- deploy copies the publish packlist unless `deployAllFiles` is set.
- deprecate: an empty message fails, use `pnpm undeprecate`.
- deprecate, dist-tag: only `--otp` supplies the one-time password.
- dist-tag add defaults to `latest`, not the `tag` setting.
- docs takes exactly one package.
- env: there is no `add` subcommand, and `list` queries the Node.js
mirror only.
- v12 only: clean no longer says it uses Node.js, add no longer
presents `-w` as an alias, the deploy package-manager-pin history
is no longer contradictory, doctor sample shows store v11.
Missing facts:
- add: `--save-prefix`, `--save-build`, `--no-save-peer`,
`--no-save-types`, `--lockfile-only`, `--force`; where Yarn vs npm
and Bun pins go; catalog and configDependencies links.
- access status shorthands; approve-builds error codes, the
placeholder value and the immediate rebuild; audit exit code,
`--optional`, `--production`, and how to make `--ignore` write
`audit.ignore`; `--ignore` exits with code 0 and
`--ignore-unfixable` fails on an advisory without a GHSA ID.
- bugs and docs `--registry`; ci accepts install options;
clean script override and modulesDir; config `--location` type and
`get` output; create name mapping, `--shell-mode`, `--cpu`, `--os`,
`--libc`; dedupe flags; deploy error codes and Dockerfile WORKDIR;
dist-tag aliases and web OTP; env `--global` requirement; exec
recursive options and PNPM_PACKAGE_NAME; fetch `--ignore-pnpmfile`,
lockfile requirement, optional pnpmfile in the Dockerfiles.
- usage, examples, and output for bin, cache, cache list, cache
delete (with the no-argument warning), cache view, cat-file,
cat-index, find-hash, ignored-builds, import, and init
(`initPackageManager`, `initType`, ERR_PNPM_PACKAGE_JSON_EXISTS).
Stale history on v12 pages: removed "Added in" markers for v10 and
v11 and rewrote "Since v11..." passages as current behavior in
access, add, approve-builds, audit, bugs, cache-list-registries,
cache-path, cache-prune, change, ci, config, create, deploy,
deprecate, dist-tag, docs, doctor, help, ignored-builds, and init.
Review follow-up: cat-file now says it rejects the hex digest that
cat-index prints; the settings index links initPackageManager and
initType; `change check` says it plans the release without reporting
it (both versions); `pnpm bin` follows a custom modulesDir.
Part of pnpm/tasks#191.
Wrong claims, fixed in the v12 docs and, where the same text is wrong
for v11, in the v11 docs:
- logout: drop the nonexistent --scope option.
- patch: the edit dir is node_modules/.pnpm_patches/<name>@<version>;
drop the "*" special case.
- patch-commit: fix the ---patches-dir heading.
- prefix: describe the real marker walk instead of "closest package.json".
- setup: setup installs pnpm as a global package and writes pn/pnpx/pnx.
- start, test: "run start" and "run test" are not aliases.
- unlink: drop the system-wide and link --global wording.
- unpublish: a package name is required; --otp has no env var; --registry
wording.
- update: --depth is dependency depth, not directory depth.
- version: --tag-version-prefix defaults to the tagVersionPrefix setting.
- view: the first argument is always the package.
- why: drop --global and --only-projects; --exclude-peers has no effect;
--prod includes optionalDependencies.
- install: recursiveInstall spelling; replace the stale ci-info snippet.
- v12 only: pack-app runtime default and builder download, pnx
provisioning list and cmd.exe typo, tasks sample priority.
Missing flags, settings, and error codes on the v12 pages: install (about
twenty flags), lane, licenses, link, list, outdated, owner, pack-app path
rules, pack, patch, patch-remove, peers, permissions, ping, pipeline, pm,
pnx, prune, publish, rebuild, recursive, remove, repo, root, run,
runtime, sbom, search, self-update, set-script, setup, shim, stage, star,
start, store, tasks, team, test, token, unlink, unpublish, update,
version, view, why, with.
Stale history on the v12 pages: removed "Added in" markers older than
v12 and rewrote "Since v11.x" and "In v11" sentences as current behavior
(login, logout, licenses, link, outdated, patch, pnx, publish, runtime,
sbom, self-update, setup, update, and others).
Review follow-up: patch's default edit directory is named after the
argument as typed (both versions); self-update installs the unscoped
package only for pnpm 12 and newer; only --no-bail is restricted;
npm_package_* covers name, version, config, engines and bin; prefix -g
prints the parent of root -g; --optional no longer claims to override
--no-optional.
* docs: lint prose with Vale
Add Vale with an in-repo style, .vale/styles/pnpm, that turns the
writing rules in AGENTS.md and the Google developer documentation style
guide into lint rules. The stock Vale packages (Google, Microsoft,
RedHat, write-good) were too noisy to block on, for example 1,300
passive-voice hits in the v12 docs alone, so the rules are written for
this repo, following the GitLab model. Each one links the guideline it
enforces.
Levels:
- error, fails CI: dashes, plain-word substitutions, condescending
words, "please", "note that", hedges, puffery, optional plurals,
exclamation marks, ellipses, curly quotes, generic link text, product
spellings, sentence-case headings, repeated words, double spaces,
implementation history in comments; in changesets, bold or italic
emphasis, internal names and release-line names.
- warning: future tense, sentences over 30 words, "instead of" and
"so that" tails in changesets.
- suggestion: passive voice.
.vale.ini scopes the rules. Docs get every rule. Contributor Markdown
and code comments keep dashes and headings as warnings until their
roughly 8,100 em dashes are rewritten. Released changesets stay in
.changeset/ and are not rewritten, so lint-prose.mjs lints only the
changesets added since PROSE_BASE (the PR base in CI).
The prose workflow installs a pinned, checksum-verified Vale release and
runs `pnpm lint:prose`. The rest of this change rewrites the existing
docs, READMEs, contributor Markdown, and code comments to pass the
error-level rules. Only prose and comment text changed. Renamed
headings keep their anchors.
Spelling stays with cspell, which checks identifiers that Vale does not
see.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs: keep prose lint from flattening meaning
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Handle registry read and environment-write failures in setup's existing
post-install outcome handler. Keep installation separate from environment
persistence, as the missing-shell-config fallback already does.
Print session-local PowerShell assignments and profile instructions when
registry persistence is unavailable. Preserve the original diagnostic and
leave environment conflicts and other failures fatal. Do not attempt to
replace an unreadable persisted PATH or elevate to another Windows user.
Apply the behavior in both CLI versions, with regression tests for read and
write failures and literal quoting of ASCII and typographic apostrophes.
Closespnpm/pnpm#17167.
Keep license reporting scoped to importers recorded in the selected lockfile. An unrecorded workspace package contributes no dependencies; it must not crash or inherit the root importer's dependency list.
Require the lockfile in the Rust CLI, matching the existing TypeScript error. Render empty license reports according to the selected output format in both CLIs.
Closespnpm/tasks#174.
Load dedicated lockfiles for selected and reachable workspace projects when
sharedWorkspaceLockfile is false. Re-key their importers to the workspace root
before SBOM collection, and merge package entries without losing required
package status. Reject conflicting package data to avoid emitting an inaccurate
SBOM. Fail when a selected or reachable project lacks a lockfile importer.
Read directory package metadata relative to the project that owns its lockfile.
Fixespnpm/tasks#170.
Fix three `pnpm prune` issues across pnpm v11 and pnpm v12:
1. Extraneous direct dependencies in `node_modules`:
- In pnpm v11, `prune.handler` did not disable `optimisticRepeatInstall`, and `installDeps` could short-circuit via `canReportAlreadyUpToDate` before `headlessInstall` ran `prune()`. In addition, `pruneRemovedDependencies` skipped `prune()` when `currentLockfile` was absent (for example, when a project had zero dependencies). Disable `optimisticRepeatInstall` in `prune.handler`, prevent `canReportAlreadyUpToDate` when `pruneDirectDependencies` or `pruneStore` is set, and pass an empty `currentLockfile` to `prune()` when `pruneDirectDependencies` is enabled and no current lockfile exists.
- In pnpm v12 (`pacquet`), `prune_stale_modules` only unlinked direct dependencies that were present in `current_lockfile` and removed from `wanted_lockfile`, never scanning `node_modules` on disk for extraneous entries when `prune` ran. Add `PruneTreeOptions::direct_dependencies` and `PruneExtraneousDirectDeps` to scan each selected importer's `node_modules` directory during `pnpm prune`, remove entries (and their bin shims and emptied scope directories) that are not wanted direct dependencies, excluded manifest links, or root public hoists, and force `commit_modules_state`'s virtual-store sweep during `prune`.
2. `pnpm --filter <pkg> prune` and `pnpm -r prune` in pnpm v11:
- Separate `cliOptionsTypes` from `rcOptionsTypes` in `@pnpm/installing.commands` `prune` and add `'recursive': Boolean` so the CLI parser accepts `-r` / `--recursive` and `--filter`.
- Do not pass `include` to `validateModules` when `pruneDirectDependencies` is set so `prune --prod` does not purge every workspace project's `node_modules` directory.
- Do not auto-include the workspace root in `shouldInstallWorkspaceRoot` during filtered `prune`.
- Scope `pruneDirectDependencies` to the selected `ctx.projects` and keep all dependency groups in `findOrphanDepPaths` / `getPkgsDepPathsOwnedOnlyByImporters` during a partial workspace `prune` so unselected workspace importers do not lose their devDependencies from the shared virtual store.
3. Workspace selection and `sharedWorkspaceLockfile: false` in pnpm v12:
- Add `CliCommand::Prune(_)` to `CliCommand::recursive_by_default`.
- Route `PrunePipeline` through `select_install_family_plan` (`PerProject`, `Shared`, `Single`) and pass `WorkspaceInstallSelection` (with `install_dirs` restricted to `selected_dirs`) into `Install::run_prune`.
Closes https://github.com/pnpm/tasks/issues/171
Separate config dependency bootstrap from install-time reconciliation. Only
reconciliation removes undeclared roots; diagnostic commands must retain them
so they can inspect the existing lockfile.
Share state loading, declared-root retention, and env graph pruning between
config dependency preparation and exclusion pruning. Load state before opening
the store and pass it into resolution. The Rust repeat-install fast path checks
this state so removing the last declaration cannot bypass cleanup.
Reuse existing verification, writing, and materialization for removals. Frozen
installs reject stale roots before mutation. Preserve package-manager roots,
including distinct versions of a package shared with config dependencies.
Keep Rust graph operations in the lockfile crate to avoid a dependency cycle.
Follow-up to pnpm/pnpm#17144.
Include the env lockfile's snapshots in the resolved-version set used to
prune workspace exclusion lists. Config dependencies and their transitive
dependencies are recorded in that leading document and were missing from
the main-lockfile-only scan.
Read the env document at the configured root only when exclusion pruning
is enabled. Filter out removed config dependency roots and reuse the env
graph pruner before merging versions with the relevant project lockfiles.
Move the Rust graph helper into the lockfile crate for reuse without a
dependency cycle. Keep manifest writes after the fallible env read.
The earlier per-project-lockfile fix established that pruning needs the
union of all resolved packages. Extend that same boundary to both lockfile
documents, with regression coverage across the lockfile layouts and a
frozen reinstall after pruning.
Closespnpm/pnpm#17142.
A gzip bomb inflates a kilobyte-sized tarball into hundreds of gigabytes
during extraction. pnpm v12 bounds what an archive may unpack to with
maxTarballUnpackedSize since pnpm/pnpm#17054; pnpm v11 had no such
bound, so an install could still exhaust the disk.
pnpm v11 now adds up the sizes that the regular-file entries of a
package archive declare in their tar headers and fails the extraction
with ERR_PNPM_TARBALL_TOO_LARGE once the total passes
maxTarballUnpackedSize (default 4 GiB). Header sizes stand in for
written bytes: the tar parser never yields more payload than a header
declares, so their sum bounds what can reach the store, and checking it
before the payload is read keeps the crossing entry out of the store.
Entries the ignore filter drops count as well. The error is
deterministic for a given archive, so the fetch retry loop does not
retry it. The zip archives that runtimes are installed from are held to
the same limit. The setting name, default, error code, message and hint
match pnpm v12.
pnpm v12's zip extractor bounded each entry by its central-directory
size but not their sum. Every stored zip entry now counts against the
payload ceiling of the TarLimits that tarballs use.
The "Added in" line in the v11 docs assumes the next release is 11.28.7.
Completes https://github.com/pnpm/tasks/issues/81.
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
lockfile.integrityTarget: tar makes the integrity of every package tarball
entry the hash of its uncompressed tar, recorded as settings.integrityTarget.
The lockfile then survives a registry recompressing its tarballs, switching
to tar resources, or being replaced by a mirror that does either. A lockfile
recorded under another target is stale: a frozen install fails with
ERR_PNPM_LOCKFILE_CONFIG_MISMATCH, and any other install re-resolves with
update-checksums semantics so no hash of the old kind carries forward. The
setting is opt-in and the lockfile version is unchanged.
The tarball crate now reports both hashes of every extraction: the resource
after content decoding, and the tar a .tgz decodes to. A pin names one of
them (IntegrityKind::Resource / Tar), or either at resolution, where a locked
or published integrity may be of either kind (ResourceOrTar, sha512 only).
Under a tar pin every gzip member is decoded and nothing may follow the tar.
The resource pin keeps today's behaviour, extracting the first member and
tolerating trailing data, but then records no tar.
Store rows extracted from a .tgz record tarIntegrity and get an alias under
the tar key, in a table pnpm v11 never reads. Store lookups follow aliases,
so a tar-pinned frozen install is served offline by a store that .tgz
installs populated, and resolution takes a version's tar hash from its row
before downloading anything.
Registry revisions now always pin their tar, revision 0 included, and are
fetched with a tar pin, so the digest route can use any content coding.
Revision metadata must list revision 0 and the selected revision, and a
digest-route URL without a revision is rejected. pnpr keys its digest
references by tar hash on every registry format, serves the route as a
negotiated tar resource, and projects revision 0 explicitly. pnpm v11 cannot
extract tar resources, so it now rejects revisions with
ERR_PNPM_REGISTRY_REVISIONS_UNSUPPORTED.
Implements pnpm/rfcs text/0000-tar-integrity-for-every-source.md and
pnpm/rfcs#42.
`pnpm setup` treated ERR_PNPM_UNKNOWN_SHELL and ERR_PNPM_NO_SHELL_CONFIG
as fatal. In containers SHELL is usually unset, so setup exited non-zero
and the get.pnpm.io installer failed, although the pnpm binary and bins
were already in place.
Both errors now mean only "no shell config file can be updated". Setup
continues with the GitHub Actions env files and legacy shim cleanup,
then prints the reason and the PNPM_HOME/PATH lines to add manually, and
exits 0. Errors that indicate a real conflict or an unsupported shell
still fail the command.
Implemented in the Rust v12 CLI and the TypeScript v11 CLI with the same
message.
Related to pnpm/pnpm#4495, pnpm/pnpm#6217, pnpm/pnpm#16973.
Adds `<version>-alpine`, `<major>-alpine`, and `alpine` tags built from
docker/Dockerfile.alpine with the musl release tarball. Alpine users can
use the image as a base or copy /opt/pnpm into `node:*-alpine` without
fetching the installer from get.pnpm.io, which was returning 403 to
bursts of concurrent CI jobs.
The download, checksum, and version check move into
docker/install-pnpm.sh, shared by both Dockerfiles. The Alpine image
also installs libstdc++ and libgcc, which the musl Node.js builds
installed by `pnpm runtime` need. The Alpine build runs only for
releases that ship musl tarballs for both x64 and arm64, so v11 and
v10 releases skip it.
Every release used to publish the `latest` and `<major>` tags, so a v11
maintenance release moved `latest` back to v11. The builds now push only
the exact version tags. A final step lists the stable releases and, with
`docker buildx imagetools create`, points `<major>` at the highest
version in that major and `latest` at the highest version overall. The
Alpine tags follow the same rule. Deciding after the builds sees every
release of a burst, since lines are published seconds apart. A
concurrency group was rejected because GitHub cancels a pending run when
a third one queues. The selection lives in
.github/scripts/docker-image-tags.sh with a test run by CI, and fails on
an empty release list.
The v12 CI docs now run the Bitbucket Pipelines and GitLab CI examples
in `ghcr.io/pnpm/pnpm:12` instead of downloading the installer, and
recommend the image for CI that runs jobs in containers. CircleCI keeps
the installer because its checkout step needs git and ssh.
The CI examples that still use the standalone script fall back to the
latest pnpm/get.pnpm.io installer release when get.pnpm.io fails. They
run the script only after a download succeeds.
Related to pnpm/pnpm#16973.
With sharedWorkspaceLockfile: false, each project's install looked for
pnpmfiles only in its own directory, so the workspace root's
.pnpmfile.cjs (and the config-dependency plugin pnpmfiles installed at
the root) never ran: no readPackage, no afterAllResolved, no
pnpmfileChecksum. pnpm v11 merges the hooks it loaded at the root into
each project's.
load_install_pnpmfiles loads, for a project with its own lockfile, the
project's pnpmfiles followed by the root's (global pnpmfile once). As
in v11, the project's hooks run first, and pnpmfileChecksum covers the
project's pnpmfiles when the project side defines hooks, else the
root's, so both write the same lockfile.
With the root's readPackage running, a consumer's lockfile records an
injected sibling as the hook rewrote it. The lockfile freshness check
read that sibling's manifest from disk, unhooked, because a
per-project install holds only its own project. check_lockfile_freshness
now applies packageExtensions and readPackage to the manifests of the
dependencies the lockfile records as injected before comparing, for a
project with its own lockfile only. Lockfile::injected_dependencies
returns the recorded directory and spec for that.
* docs: rewrite the authentication settings page in plain language
A reader reported that pnpm.io/npmrc was hard to follow. The page opened
with a long security-migration section before it described any setting,
packed several rules into each sentence, and mixed pnpm 11 and pnpm 12
history into the v12 page.
- Add a quick start for `pnpm login` and CI.
- Move the environment-variable restriction and the migration options
after the settings, as a list of alternatives.
- Split the `_auth` rules and its routing priority into lists.
- Drop version history from the prose; "Added in" markers remain.
- Fix the `<URL>:keyfile` code span, which rendered an HTML entity
literally.
- Group each certificate setting with its per-registry variant.
Each page keeps its own version's behavior: where `pnpm login` saves
tokens, the `_auth` routing priority, and `${NAME?}` support on v11.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs: drop _auth validation details from the auth settings page
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs: drop version history from the auth settings page
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs: group auth settings by the file they go in
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs: limit the _auth repository note to _auth
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
pnpm v12 resolves the pinned pnpm under the project's `trustPolicy`,
`trustPolicyExclude`, and `trustPolicyIgnoreAfter`, as it resolves any
other dependency. pnpm v11 resolved it with no trust policy at all, both
when switching to the pinned version and when recording a pin the
running pnpm already satisfies. A version whose trust evidence was
downgraded therefore failed under one CLI and ran under the other
(https://github.com/pnpm/pnpm/issues/16319). The trust failure is the
intended behavior, so v11 now matches v12.
`resolveManifestDependencies` and `resolvePackageManagerIntegrities` take
the three settings (`PackageTrustPolicy`) and pass them to
`resolveDependencies`. `switchCliVersion` and `syncEnvLockfile` pass them
from the config. The store controller for the switch is already created
from the full config, so `trustPolicy: no-downgrade` already fetches the
full metadata the check reads.
The trustPolicy docs of both versions now say the check covers the
pinned pnpm.
Part of https://github.com/pnpm/tasks/issues/144
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
`pnpm self-update` located the pnpm pin on its own: it read only
`package.json` in `--dir`. Run inside a workspace package, it missed the
pin in the workspace root's manifest, left the pin alone, and switched the
global pnpm instead. Under Corepack or Homebrew it refused outright. A pin
in `package.yaml` or `package.json5` was missed the same way.
`PinRoots`, the pre-command pass's pin locator, moves to `package_manager`
and becomes the one place both read from. The manifest at the workspace
root (or `--dir` outside a workspace) holds the pin, in whichever basename
it uses, and the env lockfile directory follows `lockfileDir`.
self-update now writes the manifest it read back in its own format and
records `packageManagerDependencies` where the pre-command pass reads them.
The Corepack/Homebrew refusal for a project without a pin now runs after
config loads, so it reads the pin through the same locator. The wasm
refusal still runs before.
pnpm v11 already reads the pin from the workspace root, so this is a
v12-only fix.
Part of https://github.com/pnpm/tasks/issues/144
* docs(pnpr): document reverse-proxy and TLS setup, webhooks, lifecycle, and pnprServer
Document production deployment and operational architecture for pnpr and
pnpm (Section C13 and Section F of https://github.com/pnpm/tasks/issues/131):
- Add Reverse proxy and TLS deployment guide (pnpr/docs/tls-and-reverse-proxy.md)
with architecture rationale and configuration recipes for Nginx, Caddy,
Traefik, and AWS ALB.
- Add Webhooks and event notifications guide (pnpr/docs/webhooks.md)
documenting the decoupled event architecture, structured audit logs
(pnpr::audit), and object storage bucket notifications.
- Document signals, readiness probe state transitions, process lifecycle, and
standard exit codes in pnpr/docs/cli.md.
- Document S3 commit journal lifecycle, multi-replica considerations, and
disaster recovery caveats in pnpr/docs/storage.md.
- Document the pnprServer workspace setting reference in
pnpm/docs/settings/dependency-resolution.md and pnpm11/docs/settings/dependency-resolution.md.
- Update sidebars and quick start navigation.
Related to https://github.com/pnpm/tasks/issues/131
* docs(pnpr): clarify pnprServer offloads resolution and streams package metadata
Clarify that pnprServer offloads dependency resolution rather than file
fetching: the server streams back resolved package metadata frames as it walks
the graph so the client can prefetch tarballs from registries in parallel.
* docs(pnpr): address review feedback on Vector remap and pnprServer tarball fetch boundary
- Add remap transform in Vector example in pnpr/docs/webhooks.md to parse
raw JSON from Docker container logs before filtering audit events.
- Clarify direct public tarball fetching and server-side private upstream
credential boundaries in pnpm/docs/settings/dependency-resolution.md and
pnpm11/docs/settings/dependency-resolution.md.
* docs(pnpr): correct probe endpoints, shutdown lifecycle, and log shippers
* docs(pnpr): correct S3 commit journal staging paths and write-ahead sequence
* docs(pnpr): clarify proxy hop coverage and orphan pruning scope
* docs(pnpr): distinguish X-Real-IP from hop traversal and document ALB target port restriction
`pnpm self-update` refused to run when Homebrew installed pnpm or
Corepack ran it, even in a project that pins pnpm. The refusal guards the
global switch, which would install a second pnpm shadowed by the one on
PATH. Writing the pin touches nothing Homebrew or Corepack manages.
Both CLIs now skip only the global switch under an external manager and
report it after the pin update. Without a pnpm pin the command still fails
with ERR_PNPM_CANT_SELF_UPDATE_IN_HOMEBREW / _IN_COREPACK.
In pnpm v12 the check stays in the dispatcher before config is loaded. It
now reads package.json from the same directory as the handler to decide
whether a pin exists.
Closespnpm/pnpm#17001
`pnpm pkg fix` left `repository` as written. It now follows the
fixRepositoryField step of `npm pkg fix`: a non-empty string becomes
{ "type": "git", "url": <string> }, and a url that hosted-git-info
recognizes becomes https() for a shortcut and the form of its own
protocol otherwise. Unrecognized urls, other keys, and a shortcut with
no owner stay as written. The legacy `repositories` array step is not
ported.
The TypeScript CLI uses the hosted-git-info fork the git resolver
already depends on. The Rust CLI adds HostedGit::normalized_repository_url,
because HostedGit's Display renders the git and http forms differently
from hosted-git-info's toString(). Rust does not recognize gist urls, so
a `gist:` repository keeps its url there, while the TypeScript CLI
normalizes it like npm.
Closespnpm/pnpm#16980
The npm resolver copied the packument's top-level `name` into the picked
manifest without checking it against the requested package. That copy
exists for the GitHub Packages quirk (a package published as
`@owner/foo` whose manifest says `foo`), but it also let a registry
answer a request for `@scope/a` as `b`. The resolved identity then
became `b@x`, so `allowBuilds` rules for `@scope/a` no longer matched
and rules for `b` did.
Both stacks now fail with ERR_PNPM_REGISTRY_NAME_MISMATCH when the
packument's `name` differs from the requested name, and always pin the
per-version manifest name to the requested name. A packument without a
top-level `name` still resolves, under the requested name.
* docs: explain registry routing in updateConfig hooks
* docs: clarify built-in JSR registry routing
* docs: clarify omitted default registry fallback
* docs: state each registry routing fact once in the v12 hook docs
The v12 page repeated the scope-key meaning and command-line precedence
already given under "What the hook receives", and described the
v11.23.0 rename as if v12 had shipped the old name. Frame it as a note
for hooks written for older pnpm instead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Without injectWorkspacePackages, pnpm deploy failed with
ERR_PNPM_DEPLOY_AMBIGUOUS_PEER when a linked workspace package's peer had
more than one resolution in the deployed graph, even when the deployed
project depended on the peer itself. In the issue, the project depends
on the workspace is-number directly, while is-odd brings a registry
is-number@6.
A linked workspace package has no package snapshot in the shared
lockfile, so deploy binds its peers itself. It used the graph-wide set
of resolutions for the peer's name. An injected install instead
resolves a dependency's peer from its parents.
Deploy now does the same. For each unbound peer, it walks up the pruned
deployed graph and takes, on every path, the nearest ancestor that
depends on the peer, the deployed project included. One distinct
provider is bound. Several still refuse, because deploy keeps one copy
of each linked package where injection would create one per parent.
With none, the existing graph-wide singleton rule applies. A peer the
deployed project depends on directly now binds to that version, which
turns a former refusal into the binding an injected install produces.
The pnpm 11 deploy also reads an injected workspace's recorded dev
dependencies as own properties, so a peer named after an
Object.prototype member no longer crashes it.
Closespnpm/pnpm#16807
---------
Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: Zoltan Kochan <z@kochan.io>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Version switching, in both CLIs: when the configuration fails to load,
pnpm retries without what it cannot read and switches to the pinned
version, which reads the configuration itself. Before, a setting the
running pnpm rejected in the global config.yaml, an .npmrc file, _auth,
or (v11) pnpm-workspace.yaml stopped every command. Only the failing
source is left out, so a repository that breaks its own files cannot
drop the machine's pmOnFail opt-out, registry, credentials, or proxy.
_auth, in both CLIs: a key under a registry URL that does not start
with @ is skipped with a warning, since the global config is shared by
every pnpm on the machine. A key holding an authToken object but
missing its @ is still an error: dropping a mistyped scope would drop
its route.
remoteCache: the Turborepo cache token comes from the auth settings for
the cache URL. remoteCache.token and PNPM_REMOTE_CACHE_TOKEN, both
unreleased, are removed. The docs say how to store a Vercel access
token, and why pnpm does not read the token `turbo login` stores.
Give the untagged code blocks a language: text for command output and
directory trees, ini for .npmrc lines. Retag .npmrc snippets that were
marked sh as ini, the faq error output marked console as text, and
normalize Dockerfile to dockerfile.
Also fix snippets that did not parse as their language: trailing commas
in the catalogs.md package.json examples and the peer-dependencies.md
peerDependencyRules YAML, and the missing closing brace in the run.md
pnpm-exec-summary.json example.
Code blocks that hold only shell commands now use the `sh` language,
and blocks showing a file's contents carry a `title` naming the file,
across the pnpm v12, pnpm v11, and pnpr docs. Bold file-name labels
above code blocks are folded into the titles.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* docs: explain how optional peers are resolved from the workspace
The autoInstallPeers docs said only non-optional peers are installed.
Both CLIs also resolve a missing optional peer to a version that already
exists anywhere in the workspace, including another project's
devDependencies, and they do it when either autoInstallPeers or
dedupePeerDependents is on. Document that and how to turn it off.
Related to https://github.com/orgs/pnpm/discussions/16666
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs: say when a missing optional peer is installed
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Verifying locked config dependencies against the registry enforces
minimumReleaseAge, but resolving them did not. A config dependency range
therefore resolved to a version that a later clean frozen install
rejected.
Keep the age gate on verification, since a config dependency's pnpmfile
runs on every install, and apply the same cutoff and
minimumReleaseAgeExclude when resolving config dependencies and their
optional subdependencies, in both the Rust and TypeScript CLIs. A
version+integrity pin resolves without the cutoff, as verification
already skips pins. Config
dependencies are resolved before any updateConfig hook runs, so only an
exclude in pnpm-workspace.yaml can cover one.
Closespnpm/pnpm#16660
---------
Signed-off-by: C. Spencer Beggs <spencer@beggs.codes>
Co-authored-by: Zoltan Kochan <z@kochan.io>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Clean up spill directories left after an interrupted download or archive extraction during store prune. Reuse the existing store-directory scan and removal helpers without adding install-time work.
Only remove matching directories when their own mtime and the mtimes of their immediate files are more than 24 hours old. Checking files preserves an old directory whose download is still writing. Stream file entries and check one directory and file at a time to bound pending filesystem work. Stop scanning a directory after its first recent file. Skip symlinks and tolerate ENOENT when another process finishes cleanup first.
Age is a conservative heuristic, not a process-liveness check: an install suspended for over 24 hours without filesystem activity can lose its spill directory.
Add regression coverage for stale, recent, empty, and unrelated entries, symlinks, missing stores, recent writes in old directories, and expected versus unexpected filesystem errors. Close SQLite fixture handles before removing test directories for Windows compatibility.
Closespnpm/tasks#109.
Verify configuration dependency registry resolutions before installing
plugins, with independently checked pinned tarballs. Require config dependencies to resolve from a registry and skip
registry verification when the installed config dependencies already match
the lockfile.
Constrain archive and deployment paths and preserve content-addressed files
when streaming package entries. Bound metadata allocation before the archive
parser buffers it, using an opt-in patch to the upstream tar crate. Bound
publishing manifest and README buffers without imposing a total extraction
disk quota.
Bind audit signatures to lockfile integrity, hash ambiguous URL dependency
directory names, and sanitize license terminal output. Preserve dependency
error context and structured fatal logs. Update affected documentation,
release notes, and regression coverage.
`pnpm config get --global` and `pnpm config list --global` printed the
settings from the project's pnpm-workspace.yaml when run inside a
project, and `--global` and `--location=global` disagreed on what the
global config is, although the docs call them the same.
pnpm v12: `ConfigFlags::is_global` resolves both flags in one place,
with `--location` winning. A `get`/`list` scoped that way loads config
the way other global commands do, anchored at the pnpm home, so neither
the project's pnpm-workspace.yaml nor its .npmrc applies. The
pre-command pin checks and the `-w` conflict check use the same helper.
A global config read also implies --ignore-workspace, so
PNPM_CONFIG_WORKSPACE_DIR does not bring a workspace back in.
pnpm v11: config commands turn `--global` into `--location=global`
before loading config, so `--global` no longer loads the config of a
global install. That mode required the global bin directory in PATH and
read the global packages' pnpm-workspace.yaml. The CLI parser lets
`--location` win over `--global` when looking for the workspace.
Both now skip the project .npmrc for these reads, as npm does in global
mode. A new `ignoreProjectNpmrc` getConfig option does it in v11; other
v11 global commands are unchanged.
Closespnpm/pnpm#16598
With `optimisticRepeatInstall: false`, a repeat `pnpm install` reached the
"Lockfile is up to date" early return, which skipped every project's own
lifecycle scripts. pnpm v11 runs them there through its headless install, as
pnpm v10 did, so tools that rely on `prepare` (simple-git-hooks, codegen) had
no way to refresh on install. That early return now runs the scripts of the
projects the install selects, through the same selection the materializing
path uses, before the closing summary log. In a workspace with a lockfile
per project, it first waits for the workspace projects a project depends on,
as the materializing path does.
The repeat-install check now returns a verdict instead of a bool. A
`--frozen-lockfile` install still never short-circuits, so a missing or stale
lockfile still fails it, but when the check finds nothing changed it skips the
projects' scripts, matching v11, whose fast path also covers frozen installs.
Docs for `optimisticRepeatInstall` now say that a skipped install skips
lifecycle scripts, in both doc sets.
Closes https://github.com/pnpm/pnpm/issues/16545
`pnpm self-update` installs the target pnpm into the global packages
directory and links it into PNPM_HOME. It never touches the Homebrew
keg, so a Homebrew user ended up with a second pnpm that the Homebrew
binary on PATH kept shadowing. The update reported success while
`pnpm -v` stayed the same, and later runs declined because the target
was "already installed globally".
Both CLIs now detect a running pnpm that lives inside a Homebrew keg
(`<cellar>/pnpm/<version>/` or `<cellar>/pnpm@<major>/<version>/`) from
its canonical path and fail with ERR_PNPM_CANT_SELF_UPDATE_IN_HOMEBREW,
pointing at `brew upgrade <formula>`. The check sits next to the
existing Corepack refusal, before project config is loaded.
Closes https://github.com/pnpm/pnpm/issues/16547
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The changed-packages selector passed --no-relative to git diff so that a
diff.relative=true setting in the user's git config could not make the
printed paths relative to the workspace directory. git only knows that
flag since 2.28. Older versions reject it with a usage error, so
--filter "...[origin/main]" failed with ERR_PNPM_FILTER_CHANGED.
Read the git version once per process and pass --no-relative only to git
2.28 and newer. Older versions have no diff.relative setting and already
print paths relative to the repository root.
git diff and git merge-base also get --end-of-options, which needs git
2.24. An older git now fails with ERR_PNPM_FILTER_CHANGED naming the
required version, instead of with git's usage text.
Closespnpm/pnpm#16561
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* docs(agents): add the documentation and comment principles
Import the principles that transfer from KSXGitHub/perfectionist's
contributor guide, plus four of our own: concise and non-repeating text,
structured text over prose, removal over addition when reviewing or
fixing nitpicks, and a definition of the user-facing text that must not
name implementation details.
The root guide's "Comments" section gains "Register", "Prose that cannot
go stale", "User-facing text", and "Reviewing documentation and
comments". The pacquet style guide adds the Rust specifics (clap and
napi doc comments are user-facing; a default is documented on the
field, not the variant). DOCUMENTATION.md gains the renderer and link
table for each markdown surface.
The guides themselves are brought in line: counts over growable lists
become plain references, a quoted heading becomes an anchor link, and
the size limits are stated once, in the style guide, instead of three
times.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW
* docs: keep user-facing text free of implementation names
The `@pnpm/napi` README and type declarations named the Rust engine, the
in-repo package name, and TypeScript internals (`filterPkgsDiff`,
`nameFormatter`) that a user of the addon cannot reach. The pnpmfile
page in both docs trees explained a limitation by the IPC mechanism
behind it. Four changesets named the release line or implementation,
which the package targets already express.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW
* docs(crates): cut restating and stale comments in pack, modules-yaml and nine more crates
Apply the comment principles to pack, versioning, pnpr-client,
package-is-installable, fs-packlist, workspace-task-scheduler,
engine-runtime-node-resolver, exportable-manifest, modules-yaml,
pnpr-registry, and pnpr-auth. Removed: call-site comments that repeat
the callee's doc, module rosters of exports that the `pub use` lines
already list, "not yet implemented" notes for behaviour that exists
(`beforePacking`, `hoistedLocations`), body narration, and history
framing. Comment-only; no code changes.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW
* docs(crates): cut restating and stale comments in the small resolver, catalog and pnpr crates
Apply the comment principles to the local, tarball, default, deno and
jsr resolvers, the catalog crates, detect-libc, config-parse-overrides,
env-replace, git-utils, matcher, diagnostics, and the pnpr policy,
cargo, error, osv and fixture crates. Removed: parentheticals that
repeat a `#[diagnostic(code)]` attribute, counts over lists of
variants, callers and entry points, rosters of callers, doc paragraphs
that repeat a sibling's or the module's text, comments narrating the
match arms or condition below them, and history framing. The
`pnpr-prepare --help` text no longer names the test harness.
Comment-only; no code changes.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW
* docs(crates): cut restating and stale comments in publish, workspace, registry and graph-hasher
Apply the comment principles to publish, auth-commands, registry,
workspace, lockfile-verification, default-reporter, the git resolver,
deps-inspection, hooks, graph-hasher, package-manifest, and the pnpr
shared-artifacts and route crates. Removed: a module roster of every
export, counts over lists, plan notes for work that landed ("Phase 5",
"Phase 6"), call-site comments repeating the callee's doc, docs
narrating match arms, a default claim on a `#[default]` variant, a
misattached doc, and history framing. Comment-only; no code changes.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW
* docs(crates): cut restating comments in napi, reporter and pnpr crates
Apply the comment principles to napi, reporter, resolving-resolver-base,
real-hoist, git-fetcher, workspace-manifest-writer, and the pnpr config
and storage crates. Removed: "Mirrors X in index.d.ts" sentences on
`#[napi]` items and other implementation names in addon-facing docs,
stale "not emitted yet" and "when it lands" notes for behaviour that
exists, step-by-step comments narrating the body, emit-site and caller
rosters, docs duplicated from a sibling or the module, a doc attached to
the wrong item, and history framing. Comment-only; no code changes.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW
* docs: cut restating and stale comments across the remaining crates and pnpm11
Apply the comment principles to the rest of the Rust workspace and to the
TypeScript CLI: the cli crate and its clap definitions, package-manager,
config, patching, the deps resolver, lockfile, pnpr, the npm resolver,
tarball, network, executor, cmd-shim, the benchmark and fixture tooling,
and the pnpm11 packages.
Removed: comments narrating the body, docs repeating a callee's, a
sibling's or the module's text, counts over growable lists, rosters of
callers and exports, "not yet implemented" notes for behaviour that
exists, history framing, and `@param` lines restating a signature.
One exception to comment-only: in `pnpr/crates/pnpr/src/resolver/resolve.rs`
the `Workspace` struct sat inside the doc block written for `resolve`, so
the struct moved above it and each doc now attaches to its own item.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW
* docs(crates): cut body narration and duplicated docs in resolver, fs and package-manager
Removed: a numbered "Sequence:" list narrating `ensure_file`'s body and
the paragraph restating its backoff constants, the stress-test module
doc's roster of its own tests, refactor-history paragraphs in the peer
walk's finalize pass, call-site and caller-side copies of a callee's doc
(the `optional` AND-fold stays documented on `register_seeded_package`
and on `ResolvedPackage`), a verbatim quotation of a TypeScript test's
expected output in the pnpr search module doc, and field and digest-input
rosters in the package-manager.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW
* docs(deps-restorer): cut plan references and re-attach misplaced docs
Removed the "slice 4" plan references and the comments narrating the
bodies they sat above. The `Auto` downgrade-chain doc moved from
`copy_and_log` to `auto_link`, the function it describes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW
* docs(deps-restorer): cut the remaining body narration in the hoist path
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW
* docs: cut the last body narration and a history clause in the cmd-shim test
`build_graph.rs` kept a `patches` parameter doc that re-derived what
`BuildOptions::patches` documents. The PowerShell-arguments test on main
said the arguments survived "before the shim ended its batch context
too", which describes the change rather than the behaviour.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW
* docs(cmd-shim): point the PowerShell-shim link at its current module
Main moved `wants_powershell_shim` into `windows_shim_policy`, so the
shim writer's link to it no longer resolved and `cargo doc` failed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW
* docs(cli): link the fixture the ignored-build setup installs
The shortened doc named `workspace_with_unapproved_build` in backticks,
which `perfectionist::bare_identifier_reference` rejects for an item that
resolves in scope. It is an intra-doc link now.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW
* docs(crates): restore two contracts and fix four dangling references
A review of this branch's sweep flagged seven places. Six needed a
change:
- `from_ini` documents again that a relative `cafile=` resolves against
`npmrc_dir`, a precondition its callers have to honour.
- `hash_object` documents again that the private serializer's bytestream
has to match pnpm 11's, because the digest is persisted and read by
both.
- The hooked-manifests note no longer says "above" of a gate that lives
in another module.
- The store-dir race test states its synchronization contract instead of
labelling it "Post-Option-C".
- `GraphNodeHashOptions.nodeVersion` keeps `undefined` as a literal.
- The pnpr streaming module drops the sentence fragment that introduced
its list.
The seventh, a `beforePacking` limitation in exportable-manifest, stays
removed: the note said the pnpmfile bridge cannot supply that hook, and
`pnpm_pack::lifecycle::apply_before_packing` has been running it since
before this branch.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW
* docs(cli): say in `pnpm why --help` that `--exclude-peers` is ignored
Demoting the caveat to an ordinary comment took it out of the help,
which then advertised a flag that does nothing: `WhyArgs::run` never
reads `exclude_peers`, and pnpm 11's `why` handler never forwards its
`excludePeers` either. The doc comment states that behavior in the words
a user sees, and the parity reason stays in a comment beside it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW
* docs(cli): drop the doc comment that described the wrong module
The comment described the `pnpm_deps_inspection as deps_tree` re-export
above it, but a doc comment attaches to the item that follows, so rustdoc
showed it as the description of `mod dispatch`. The `use` item already
names the alias, so the comment goes rather than moving.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW
---------
Co-authored-by: Claude <noreply@anthropic.com>
Frozen installs omit unchanged packages from the dependency graph.
Workspace hoisting treated a missing graph node as an unclaimed alias,
so a workspace project could replace an external direct dependency.
A later dedupe pruned that workspace link while retaining the external
package's hoist metadata, leaving no link at the recorded location.
Pass the non-link direct dependency aliases of every importer in the
wanted lockfile to the hoister, so filtered installs keep the aliases
of unselected projects too. Preserve the existing workspace conflict
rules without rebuilding the full dependency graph.
Add CLI regressions for private and public hoisting, with and without
--filter. Verify that `--no-runtime` does not reserve excluded runtime
aliases, and clarify the
frozen-install guarantee in the settings documentation.
Closespnpm/pnpm#16485.
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
`pnpm -r pkg get` keyed its JSON report by package name, so when two
selected workspace projects had the same name the later one silently
replaced the earlier one. Workspaces allow duplicate names, so failing
is not an option.
Keep name keys when a name is unique. Key every project that shares a
name by its workspace-relative directory, the same fallback already
used for nameless projects. Re-keying all of them rather than only the
later one keeps the result independent of selection order. A project
whose name equals a directory key that another project falls back to
also moves to its directory, and this repeats until no key collides.
Directories are distinct, so every key is. Workspaces with unique names
keep the same output.
Implemented in both the TypeScript and Rust CLIs, with the docs updated
for both.
A fetch timeout while another request to the same origin is still in
flight now lowers that origin's in-flight cap to one connection. The
configured network concurrency stays in place until that happens, and
other origins keep it. Remaining retries and later requests to that
origin wait for the single slot.
The cap is per origin, so one slow registry or tarball host cannot drag
down downloads from healthy ones. On a slow link, nearly every download
comes from one registry, so that origin times out against its own peers
and is lowered.
In the Rust client each origin gets a gate next to the maxSockets limit,
acquired before the global semaphore so a request waiting on a lowered
origin holds no global slot. Behind a proxy the origin is the proxy's.
In the TypeScript client each connection origin (again the proxy's when
one applies) gets a gate around its registry fetches, held until the body settles, so a stalled download still counts
as in flight. Already-granted requests finish. Further requests to that
origin wait until one remains.
pnpm/pnpm#12791
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Zoltan Kochan <z@kochan.io>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
When verifyDepsBeforeRun is `install` (the default) and node_modules are
out of date, `pnpm run` and `pnpm exec` start `pnpm install` first. In a
sandbox with a read-only store or no network that install cannot succeed,
so no script could run at all.
A failed install now prints a warning that node_modules may be out of sync
and the command continues, the same outcome as the `warn` action. The
install has already printed its own error. The warning is fixed text, so it
echoes no filter selectors, and it names `verifyDepsBeforeRun: false` as the
way to skip the install.
An install ended by a signal, or by Ctrl+C on Windows (exit code
0xC000013A), still stops the command, so an interrupt is never turned into
a script run. The `prompt` action shares the install path, so a confirmed
prompt behaves the same way.
Implemented in both the TypeScript CLI and pacquet, with the settings docs
for both versions updated.
Related to https://github.com/orgs/pnpm/discussions/16212
A devEngines.packageManager range recorded the running pnpm in
packageManagerDependencies whenever it satisfied the range. The running
pnpm is installed without the project's settings, so a contributor with a
fresh global pnpm wrote a version into the lockfile that every other
contributor's switch then refused under the project's minimumReleaseAge.
When the running pnpm is recorded for a range pin, pnpm now checks it
against the project's cutoff, without the exemption the running version
gets elsewhere. An immature running version gives way to the newest mature
version in the range that also passes the configured trustPolicy, since
every other contributor installs it. Only the lookup of the running pnpm,
which is already executing, leaves the trust policy out. If nothing in the
range passes, the running version is kept, since no switch could do better.
A failed lookup (offline, registry error) warns and records nothing, so
an unchecked version never reaches the lockfile; the next command retries. A frozen lockfile skips
the lookup because it records nothing new. The recording command keeps
running on the current pnpm in both stacks.
pnpm v12 also replaced a recorded version that still satisfied the range
whenever the running pnpm differed, because the sync compared the lockfile
against the running version. It now keeps the locked version, as v11
already did, and only rewrites its specifier when the range changed.
The engine resolve-policy helpers move into config_deps/engine_policy.rs to
keep config_deps.rs under the file-length limit.
Closespnpm/pnpm#16431
When no version published before the time-based cutoff matches, pnpm
now installs the lowest matching version older than the
minimumReleaseAge cutoff, and falls back to an immature version only
if none exists. The resolutionMode docs still described the old
fallback straight to the lowest matching version.
Refs https://github.com/pnpm/pnpm/pull/16304
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(store): coordinate concurrent read-only index access
* test(cli): avoid race in concurrency assertion
wait_for_holders already proves that both tasks hold their slots before
release. The later overlap marker assertion races with release_holders
removing the markers before the scripts inspect them.
Clarify that ordinary concurrent installs and frozen-lockfile allow shared
store writes; only frozenStore requires an immutable store.
* fix(store): preserve immutable reader compatibility
Keep the published ReadOnlyStoreIndex contract for finalized read-only stores.
Expose ConcurrentReadOnlyStoreIndex for readers alongside active writers.
This fixes the shared-store use case without requiring immutable callers
to migrate in a patch release.
* fix(store): configure readers through prepared statements
* test(proxy): use local certificate fixtures
* fix(store)!: coordinate read-only index access
Use the normal WAL-aware connection for ReadOnlyStoreIndex and reserve
ImmutableStoreIndex for frozen stores. Update every frozen-store caller.
The maintainer requested a major release of `@pnpm/store.index` so existing
shared-store consumers receive the safe default when upgrading.
The pnpm CLI and internal consumers retain patch releases.
BREAKING CHANGE: Callers using ReadOnlyStoreIndex for a finalized store on
a read-only filesystem must switch to ImmutableStoreIndex.
* test(store): follow updated variable naming rules
Expose installed executable paths from the global package scanner and
use file identity to recognize hard links during legacy global migration.
Keep the existing symlink and shim cleanup, and include Windows exe paths.
Do not treat equal file contents as proof of ownership.
Apply the fix in both CLI implementations and test hard-link cleanup,
large native executables, and preservation of unrelated files.
Closespnpm/pnpm#16420.
Deploy no longer writes the workspace root's `packageManager` or
`devEngines.packageManager` into the deployed manifest. This reverts the
inheritance added in pnpm/pnpm#15434 for pnpm/pnpm#9079, in both the
Rust and TypeScript CLIs.
A deploy directory is run by whatever package manager or runtime its
consumer uses, often npm in a plain Node.js image. With the default
onFail, npm refuses to run the scripts of a manifest whose
devEngines.packageManager names pnpm, so every script in the deploy
output failed with EBADDEVENGINES. A packageManager pin can likewise
make corepack or pnpm's version switching fetch another pnpm inside the
image. The deploy output is meant for running the app, so it carries no
development-time pin from the workspace. A deployed project's own pin is
kept, since deploy leaves the project's manifest fields as they are.
The inheritance helper, its call sites, and the now-unused
`@pnpm/workspace.project-manifest-reader` dependency of
`@pnpm/releasing.commands` are removed. A regression test in each CLI
checks that neither field reaches the deployed manifest, for native and
legacy deploy.
Closespnpm/pnpm#16403
---------
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Zoltan Kochan <z@kochan.io>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Copilot-Session: 7ee9c5ce-96b9-4d2b-a163-a6e5a4a4eb38
Include bare README and npm-compatible Markdown README filenames in
publish metadata for both pnpm CLIs, from directories and tarballs.
Select README.md before other Markdown names and bare README, with the
lower file name breaking ties so the pick does not depend on readdir or
archive order. Preserve root-only and regular-file checks. Tarballs are
scanned once, reading only README entries that outrank the current pick.
The Rust CLI decodes README bytes lossily, matching the TypeScript CLI.
Related to pnpm/pnpm#12704
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A non-blank scriptShell is used for pnpm run and lifecycle scripts even when shellEmulator is enabled. The emulator runs only when scriptShell is unset or blank.
refs pnpm/tasks#43
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Gabriel Mari S. Flores <Shuuren@users.noreply.github.com>