Commit Graph
14777 Commits
Author SHA1 Message Date
Zoltan KochanandClaude Opus 5.5 439b0f6f48 docs: name runtime: false in the v11 migration engine check
The setting skips runtimes like --no-runtime since pnpm/pnpm#17261, so
it changes the engines.node check the same way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 11:19:21 +02:00
Zoltan Kochan 6ab3c27774 fix(config): honor the runtime setting (#17261)
pnpm v12 had Config::skip_runtimes but no source set it, so
`runtime: false` was dropped and only `--no-runtime` skipped the
runtimes from devEngines.runtime and engines.runtime. pnpm v11 passes
`skipRuntimes: opts.runtime === false` (installSingleProject.ts,
recursive/context.ts) and reads the setting from pnpm-workspace.yaml,
`pnpm_config_runtime`, and `--config.runtime`, not from the global
config.yaml, where `runtime` is an excluded key.

WorkspaceSettings now has a `runtime` field, read from
pnpm-workspace.yaml and PNPM_CONFIG_RUNTIME (and through it from
`--config.runtime`), cleared from the global config.yaml, and applied
as `skip_runtimes = !runtime`. `pnpm dedupe` and `pnpm prune` no longer
force runtimes on, as in v11, where both go through installDeps.
`pnpm dedupe` accepts `--no-runtime`, since v11 dedupe takes install's
options. v11 prune does not, so v12 prune stays without it.

updateConfig hooks see `runtime` as v11 shows it: absent until a source
sets it, and `false` under `--no-runtime`. The install pipelines record
`--no-runtime` as a CLI-set `runtime: false` before the hooks run, so a
hook cannot turn runtimes back on (v11 restoreCliSettings).

Intended difference from v11: with engineStrict, a skipped runtime
makes the root engine check use the active Node.js, which is v12's
existing `--no-runtime` behavior (pnpm/pnpm#15135). v11 checks against
the manifest runtime version even when it does not install it. dedupe
and prune now pass the configured runtime policy, like add, update, and
remove, and RuntimePolicy::Always is removed.

Tests: config-crate tests for the yaml, env override, and global
config.yaml exclusion; e2e tests in install_runtimes::no_runtime for
the yaml and `--config.runtime` skip, the CLI override, dedupe and
prune under the setting and `dedupe --no-runtime`, the hook view, a
hook unable to undo `--no-runtime`, and a hook setting `runtime: false`;
install_root_engine covers the engine check of dedupe and prune with a
skipped runtime. Each fix fails its test when reverted.

The install docs now spell the setting `runtime: false`, and the dedupe
docs list `--no-runtime`.
2026-10-23 11:10:27 +02:00
Zoltan Kochan da187027e0 fix(add): accept the lowercase save aliases (#17270)
`pnpm add` rejected the lowercase short options `-p`, `-d`, `-o`, and `-e`
with "unexpected argument". pnpm v11 declares them as `add` shorthands for
`--save-prod`, `--save-dev`, `--save-optional`, and `--save-exact`
(pnpm11/installing/commands/src/add.ts), and the v12 docs already list them.
Only `add` has them in v11; `install` and `update` map `-P`/`-D` to
`--production`/`--dev` and have no lowercase forms, so they are unchanged.
`pnpm install <pkg>` is rewritten to `add` in both versions, so it accepts
them too.

The aliases are visible clap short aliases on the existing options, so
`--help` lists them the way v11's help does. No `add` option already used
these letters.

Adding them makes `-o` and `-d` ambiguous in the pre-subcommand arity union
(`pack-app -o <dir>` and `patch -d <dir>` take a value). The union reads an
ambiguous short as a flag, so `pnpm -o dist pack-app` would have found `dist`
as the command. When the union's reading names no subcommand and an option
only subcommands declare precedes it, or names a command that does not
declare that option, flag_relocation now retries the scan with each
subcommand's own arities and relocates for the one that matches. A moved
option's value now stops before the subcommand token, so
`pnpm -o pack-app target` becomes `pnpm pack-app -o target` and no longer
drops the subcommand from argv.

install_as_add::rewrite relocates once more after renaming `install` to
`add`, so an `add`-only option written before the command, as in
`pnpm -D install foo`, moves after `add` instead of being rejected at the
top level. Only command lines that take the rewrite pay for the pass.
The flag_relocation tests that used `-o` as their mixed-cluster example now
use pack-app's unambiguous `-t`.

The new e2e test `add::add_accepts_lowercase_save_aliases` runs
`add -d -e`, `add -o`, and `add -p` and checks the manifest groups and the
exact spec, then runs `install -d <pkg>` and checks devDependencies. It fails
with "unexpected argument '-d'" without the aliases. flag_relocation unit
tests pin that `pnpm -o dist pack-app`, `pnpm -d dir patch foo`, and
`pnpm -d edit patch foo` relocate and parse, that `pnpm -d add foo` keeps
the boolean reading, and that `pnpm -o dist build` stays untouched.

Part of pnpm/tasks#191.
2026-10-23 11:07:13 +02:00
Zoltan Kochanandgithub-actions[bot] 901f2438e0 chore: update dependencies, Node.js, pnpm, and GitHub Actions (#17217)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-10-23 10:35:07 +02:00
Zoltan KochanandClaude Opus 5.5 6d736a02a8 fix(add): resolve a version-less dependency to the tag setting (#17260)
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>
2026-10-23 09:46:18 +02:00
Zoltan Kochan be2f02ffc8 fix(pnpr): emit the access record outside the request span (#17267)
With the default pretty format, every pnpr::access line printed each
field twice and user three times. emit_access_log entered the
per-request span before emitting, the span records the same fields,
and the compact formatter appends the current span's fields after the
event's. The JSON format hid the same duplication under a span object.

The access event now fires outside the request span. It carries every
documented field itself, so nothing is lost in either format. The span
keeps request_id, method, uri, client_ip, user_agent, and user for the
events emitted inside a request: the audit records inherit them, and
the authentication layer records user before the handler runs. The
span's status, latency_ms, and bytes slots were only filled after the
handler returned, when no event inside the request could see them, so
they are removed along with record_span_and_metrics.

The subscriber construction moves from main.rs into
pnpr::logging::log_subscriber so a test can run a request through the
real pretty and JSON configurations and assert on the formatted line.
The audit-log test captures values recorded on the span after it opens
and checks the span's user against the audit actor.

Closes pnpm/tasks#195
2026-10-23 09:45:34 +02:00
Zoltan Kochan 3bca7ffd2b fix(pnpr): record a token's last use off the request path, at most once a minute (#17266)
Every token-authenticated request awaited an UPDATE of last_used_at before
the handler ran. With the libsql backend that is a round trip to the primary
even when an embedded replica answers the lookup locally, so a replica
removed almost none of the latency for authenticated traffic.

LastUsedWriter in pnpr-auth now owns the write. It skips the write while the
stored time, which the authenticating lookup already read, is under a minute
old; claims the interval per token in memory so a burst of concurrent
requests from one token writes once even before the first write lands or the
replica syncs it back; and runs the write in a spawned task the request never
waits for. Both middleware sites, bearer tokens and OCI tokens' parents, go
through it. GET /-/npm/v1/tokens therefore reports a last-used time accurate
to within a minute.

Related to https://github.com/pnpm/tasks/issues/194
2026-10-23 09:45:07 +02:00
Zoltan Kochan c9ba76ff98 docs: add a guide for migrating from pnpm 11 (#17268)
The pnpm 12 docs covered only the move from pnpm 10. Add a
"Migrating from pnpm 11" page with the upgrade steps and the behavior
changes that can need action, each checked against both CLIs:

- unrecognized pnpm-workspace.yaml settings: the pnpm 11 warning becomes
  an error under a satisfied pin; confirmModulesPurge is rejected
- a boolean flag followed by a separate value token is rejected
  (--frozen-lockfile=false and --no-frozen-lockfile work)
- hosted Git dependencies always resolve over HTTPS, no SSH probing;
  existing SSH lockfile entries change only on re-resolution
- the importPackage hook is not run, filterLog is ignored with a warning
- engineStrict fails through optional subtrees, and checks the active
  Node.js under --no-runtime
- package managers and runtimes named as packages are provisioned
  (links the existing table in package-managers.md)
- a globally installed Node.js follows the project's runtime pin
  (globalShims)
- global commands under sudo fail
- registry TLS trusts the OS store, then bundled Mozilla roots
- maxsockets has no default of 50 for direct connections
- shellEmulator uses the built-in shell, with its parameter-expansion limits
- packageImportMethod auto hardlinks before cloning on Linux
- canonical cycle breaking rewrites cyclic peer variants once
- dlx/pnx do not use the global virtual store
- pack-app defaults to Node.js 25.5.0
- --frozen-store has no Node.js version requirement

Add the page to the sidebar and link it from the v10 guide.

Part of pnpm/tasks#191.
2026-10-23 09:37:59 +02:00
Zoltan Kochan 0e8584301b fix(store): remove the metadata cache on store prune (#17253)
`pnpm store prune` pruned the dlx cache, the global virtual store, and the
content-addressable store, but left the registry metadata cache in place.
The `ERR_PNPM_TARBALL_INTEGRITY` advice in the errors docs ("run pnpm store
prune") relies on prune clearing that cache, so a stale integrity
checksum stayed after following it.

pnpm v11 removes the cacheDir entries named `metadata*` or `vN` and
`<storeDir>/tmp`, and logs "Removed all cached metadata files"
(pnpm11/store/controller/src/storeController/prune.ts). The store's tmp
dir was already cleared by the v12 CAS prune.

`StoreDir::prune` now takes the cache directory and a list of protected
directories. Between the global virtual store and CAS passes, in v11's
order, it removes `<cacheDir>/metadata*` and `<cacheDir>/vN/metadata*`
and prints v11's message. That covers every mirror root v12 writes
(`v12/metadata`, `metadata-full`, `metadata-full-filtered`,
`metadata-private`), the `v11/` legacy roots, and older top-level ones.
Unlike v11 it keeps the rest of each `vN` directory (cargo index,
tarball resolutions, runtime shasums), and it keeps any matching
directory that is or holds the store, globalDir, the global packages and
bin dirs, the pnpm home, or the state dir, since a project's
pnpm-workspace.yaml can point cacheDir at a broad directory. Symbolic
links are kept, as v11's directory filter keeps them.

It runs under the exclusive store prune lock, which every install holds
shared for its whole run, so no concurrent install reads the cache
while it goes.

Tested by unit tests for the name filter, symlinks, an enclosing store,
cacheDir == globalDir, and a home-like cacheDir, and by CLI e2e tests
that install a package and prune, and that keep a pnpm home placed
inside cacheDir.

Part of pnpm/tasks#191.
2026-10-23 02:38:38 +02:00
Zoltan Kochan 7fbde8b1b4 fix(hooks): warn when a pnpmfile defines importPackage (#17263)
pnpm v12 does not run the `importPackage` pnpmfile hook. That is
deliberate: commit 78cf081f97 deprecated the hook in v11 and explains why
it is not ported. But v12 ignored a pnpmfile that defines it silently,
while v11 warns about it from `requireHooks` and the v12 docs say pnpm
warns.

The hooks crate now reports every hook it accepts but never runs through
one `ignored_hooks` query, with `filterLog` and `importPackage` as the
`IgnoredHook` variants. It replaces `has_filter_log`, and the worker
answers both names in one request. `run_update_config_hooks` collects
them across all pnpmfiles and prints one warning per hook per command.
The `importPackage` warning names the pnpmfiles that define the hook,
redacted and sanitized as v11 does, and points at the
`packageImportMethod` setting.

Tests: a CLI e2e test installs with a pnpmfile that defines
`importPackage` and asserts the warning appears exactly once. It fails
when the hook name is not matched. A second test asserts a pnpmfile
with only `readPackage` gets neither the importPackage nor the filterLog
warning.

Part of pnpm/tasks#191.
2026-10-23 02:37:49 +02:00
Zoltan Kochan c0539430d7 fix(help): accept --all and -a (#17262)
`pnpm help --all` and `pnpm help -a` failed with "unrecognized
subcommand", because `help` is clap's built-in subcommand, which takes
no options. pnpm v11 accepts the flag (pnpm11/pnpm/src/cmd/help.ts) and
uses it to list the commands its default help hides.

Clap's help already lists every command, so the flag is dropped from the
arguments of `help` before clap parses argv. `pnpm help -a <command>`
prints that command's help, as in v11. Plain `pnpm help` and
`pnpm help <command>` are unchanged.

An e2e test compares the output of `help --all` and `help -a` with
`help`, with and without a command name. Both tests fail without the
argv rewrite. `--all=<value>` and `--no-all`, which v11's nopt Boolean
accepts, are dropped too. A unit test pins that only the arguments
after `help` are touched: `pnpm run help -a` keeps `-a` for the script.
`pnpm help help` does not list `--all`, since clap's built-in help
subcommand declares no options and the flag has no effect in v12.

Part of pnpm/tasks#191.
2026-10-23 02:36:40 +02:00
Zoltan Kochan 359ff10948 fix(run): honor requiredScripts in recursive runs (#17257)
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.
2026-10-23 02:18:06 +02:00
Zoltan Kochan 4e6351952d docs: correct the guides from agent skills to lockfile (#17248)
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.
2026-10-23 02:16:47 +02:00
Zoltan Kochan 52dce0f4dc docs(settings): correct defaults and document missing settings (#17246)
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.
2026-10-23 01:38:49 +02:00
Zoltan Kochan b3eee14a96 docs(cli): correct the command reference from access to init (#17247)
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.
2026-10-23 00:49:08 +02:00
Zoltan Kochan 30f6d58a56 fix(pnpr): bound libsql auth reads, and a write's wait for a connection, by the auth deadline (#17252)
Only reads ran under the auth timeout, so a write that never got an
answer held its pool slot indefinitely, and once every slot was held that
way each lookup waited for one until it timed out. Putting writes under
the deadline instead would report a write the database committed late as
a timeout.

The pool now has a read and a write entry point. A read runs under the
deadline end to end. A write is bounded only while it waits for a
connection, before anything is sent, and then runs to completion, the
half of the sqlx model libsql can follow without a server-side statement
timeout. A database that stops answering writes can still hold every
connection, but the pool recovers once those writes return. Connections
open on the blocking thread pool, with at most as many in flight as the
pool has connections, so a replica's stalled background sync can neither
hold a slot past the deadline nor pile up blocked threads.

Also drops the claim that a pooled connection avoids new TCP or TLS
handshakes. Measured against a local libsql server, one pooled connection
served 50 token lookups but libsql opened 45 TCP connections for them;
the old shared connection behaved the same.
2026-10-23 00:48:42 +02:00
Zoltan Kochan cb3f29bc0a docs(cli): correct the command reference from install to with (#17251)
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.
2026-10-23 00:47:42 +02:00
Zoltan Kochan 7fb2bfab4a docs(pnpr): correct configuration keys, defaults, and commands (#17245)
Part of https://github.com/pnpm/tasks/issues/191.

Wrong claims:
- cli: drop the nonexistent `add-user --password` flag and document the
  terminal/stdin password read; `oci-gc --min-age` and `cache-gc --max-age`
  are the flags (the `-secs` forms are aliases); `--from` defaults to the
  `storage` key, not an internal field; `info` and `http` log the same
  access records; only global and server options have PNPR_* variables.
- configuration: `packumentTtl` defaults to 5m; the minimal config now
  sets `auth.htpasswd.file`, and the auth section says accounts live in
  memory without it or a backend; grouped hosted registries default their
  org to `<ecosystem>~<name>`; the handshake is served for any surface;
  the pipeline surface also satisfies "something must be served"; add
  `package.update_maintainers` to the audit list.
- tls-and-reverse-proxy: remove the invalid `publicUrl:` YAML key and the
  nonexistent `rate_limiting` setting.
- backup-and-restore: `pnpr verify` hashes by default (`--check-hashes`
  does not exist, `--skip-hashes` skips only the hash
  comparison); `migrate-storage
  --verify` compares bytes.
- webhooks: audit events do not cover publishes or token creation; the
  example uses a real action and the `actor` field.
- cli: the `RUST_LOG` example keeps `pnpr::audit=info`.
- install-acceleration: the `--public-url` host is exempt from the
  private-address refusal.
- shared-side-effects-cache: `pnpm pipeline` publishes workspace-task
  artifacts.

Missing facts: `routes.allowedPrivateNetworks`, the upstream `search` key
and canonical `maxAge`, built-in `npm.jsr.io` route, relative path
resolution, `log` defaults and `type`, `${VAR:-default}` substitution and
the full secret-field list, `oci` defaults, `artifacts.quota` bounds,
`migrate-storage` destination rules, `--osv false`, `--packument-ttl`
default, S3 `forcePathStyle`/`allowHttp` defaults, grouped org layout,
OIDC key table, container image section, `backend:` ignoring the htpasswd
and token files.

Duplicates and links: health probe bodies, reload procedure, and the
resolver allowlist now link to their one home; broken `#secret` anchor
fixed; links into the pnpm docs use absolute pnpm.io URLs.

Stale history: drop "pnpr v1" and the per-paragraph "Since alpha.N" /
"Since pnpm 12.x" prefixes, and the contributor-only extensibility text.

Also corrects the `as_filter_directive` doc comment, which claimed `http`
quiets the rest of the crate.
2026-10-23 00:11:00 +02:00
Zoltan Kochanandgithub-actions[bot] 71d90a06a3 chore(release): 11.28.8 (#17256)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
v11.28.8
2026-10-22 23:45:10 +02:00
Zoltan Kochan ae47bbf062 fix(pnpr): sync the embedded replica on a connection of its own (#17232)
libsql's read-your-writes pulls the primary's frames on the connection
that issued the write and resets that connection's pager when the pull
ends, so a read in flight on the same connection then fails with "file
is not a database"; the schema setup at startup hit it deterministically,
since it holds a count query open across the counter transaction. Turn
read-your-writes off and pull through `Database::sync` after each write
instead, which uses a fresh connection. A login that only checks a
password does not pull. Schema setup takes the sync as a hook and runs
it before each read that depends on a preceding write, and the login
that follows a lost insert race syncs before reading the winner's row.

TokenBackend gains revoke_all_for_user, so revoking every token of an
account is one DELETE and one pull instead of one pull per token; the
pull is skipped when nothing was deleted and the replica lists no token
for the account.

The test's mock primary now serves the frames of its own WAL, modelling
a WAL restart as the next generation, so the test covers a write
reaching the replica through background sync, a replica seeing its own
write and bulk revocation, and schema setup against an empty primary.

Related to pnpm/tasks#183.
2026-10-22 23:37:41 +02:00
Zoltan Kochan 2f4535cf69 fix(add): lock the version an explicit tag or range picked (#17240)
`add` resolves an explicit registry selector (a tag, a range, a version)
to a concrete version and saves it as a range. The install that follows
re-resolved the saved range without knowing which version the add had
picked, so it locked the newest match. A tag below the newest version of
its major, or a range with an upper bound, locked a version the user did
not ask for, and a re-add kept the importer's existing pin.

`resolve_explicit_registry_spec` now returns the picked version with the
range. `prepare_manifest` collects one `PickedVersion` per selector whose
saved specifier is not that exact version, and the add seeds each one
into `preferred_versions_override` with the requested-version weight,
the way `pnpm update` and the catalog path do:

- A pick is settled, and seeds nothing, when the fast lockfile update
  cannot move it: the importer keeps both its pin at the picked version
  and its specifier, or the lockfile holds no other version of the
  package. The fast path then still applies. When the specifier changes,
  the fast update retargets the edge to the highest locked version the
  new range admits, so a matching pin is not enough on its own.
- If the importer locks another version, its pin is withheld
  (`ImporterUpdateSeedPolicy::DropOnly`) so the resolver can move it.
- Otherwise only the preference is seeded. A dropped pin marks the edge
  as an update target, which revalidates its metadata. `prefer_frozen`
  is turned off whenever a preference is seeded, so the fast path cannot
  absorb a lower pick into a version the lockfile already holds.
- A filtered add (`--filter`) seeds each selected project the same way.

The seeded preference also lets the resolver take the metadata mirror
the add's own pick just wrote, so an `add --save-types` companion
package is fetched once. `bulk_add_fetches_only_required_metadata_formats`
now expects that.

The workspace specifier helpers move into `add/workspace_specifier.rs`,
and two functions are split to stay within the size lints.

`config_includes::a_config_dependency_packed_with_pnpm_pack_keeps_its_config_entry`
now sets its own `storeDir`. Without one it fell back to the device
store, which kept the byte-identical packed tarball from an earlier
run, so every local run after the first failed.

`jsr:` selectors are not seeded. They resolve through the npm alias of the
package, and this change does not cover them.

Closes pnpm/pnpm#17234
2026-10-22 22:43:26 +02:00
Zoltan Kochan 26047ac598 fix(pnpr): run libsql auth operations on a pool of connections (#17237)
The libsql backend ran every operation on one shared connection. On a
remote database that connection is a Hrana stream, and each response
carries the baton for the stream's next request. An operation dropped
while its request was in flight (a client that disconnected, the auth
timeout) never received the new baton, so the connection kept the spent
one and the server refused every later request with "Received a baton
that has already been used". From then on every login and every
authenticated request failed with a 500 until pnpr restarted. One shared
stream also put concurrent operations into each other's transactions.

Operations now check a connection out of a pool and run alone on it. A
connection returns to the pool only after its operation succeeded outside
a transaction; any other is closed and replaced on a later checkout. This
is what sqlx does for the PostgreSQL and MySQL backends. Pooled
connections keep their HTTP client, so reuse costs no new TCP or TLS
handshake, which a connection per operation would have paid on every
token lookup. backend.libsql.maxConnections sizes the pool, defaulting to
10 like sqlx.
2026-10-22 22:42:11 +02:00
be4d6f5924 fix(git-fetcher): build a git dependency in every slot it is linked into (#17231)
A git-hosted dependency with install scripts but no `prepare` (for
example a native addon with `install: node-gyp rebuild`) was built only
by the install that fetched it. A later install that linked the same
commit from the store into a new location, such as a new global virtual
store slot or a fresh `node_modules`, skipped the build and exited 0.

The git fetchers wrote the store-index row with `requiresBuild` set to
whether the package needed `prepare`. The warm prefetch trusts a stored
`false`, so the build gate never marked or built the new slot. The cold
path leaves the value unset and derives it from the fetched files,
which is why the first install built it.

- The git fetchers now store `requiresBuild` derived from the stored
  files (install scripts, `binding.gyp`, `.hooks/`), as for any other
  package. `requiresPrepare` still records whether `prepare` is needed.
- The prefetch re-derives `requiresBuild` from the files for a row that
  records `requiresPrepare` and bundles no manifest. Only the v12 git
  fetcher writes that shape, so stores that already hold such a row are
  repaired without clearing them, and registry, zip and pnpm v11 rows
  keep their stored value.

`type_git_dependency_reuses_side_effects_on_warm_install` passed only
because of this bug: its postinstall wrote nothing inside the package,
and since pnpm/pnpm#14717 a build with nothing to restore runs on every
install. The postinstall now also writes a file inside the package, so
the test checks that the warm install restores it from the side-effects
cache without rerunning the script, which is what pnpm/pnpm#13426 meant
to cover.

pnpm v11 computes `requiresBuild` from the bundled manifest and files,
so it is not affected.

Closes pnpm/pnpm#17224

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 22:13:13 +02:00
Khải 40fab6f038 fix(fs): pass a replaced signal handler the details it expects (#17220)
`sigaction` keeps the one-argument and three-argument handler forms in a
union, and nothing but `SA_SIGINFO` in the flags says which one it holds.
The install read only the handler address out of the disposition it
replaced and dropped the flags, so the chained call went through
`extern "C" fn(c_int)` whatever the handler had been installed as. A
handler installed with `SA_SIGINFO` was then called with its `siginfo_t`
and context arguments left to whatever the registers happened to hold,
which is undefined rather than merely incomplete.

Nothing in pnpm's own process installs such a handler, which is what the
old `// SAFETY:` comment rested on. pnpm also ships as a Node addon, where
the process is the host's and the disposition being replaced is whatever
the host installed, so the premise is not pnpm's to make.

The flag is now read from `sa_flags` alongside the address and kept with
it, and the chained call goes through the matching signature. pnpm's own
handler takes `SA_SIGINFO` so that it has the delivery's `siginfo_t` and
context to pass on.
2026-10-22 22:10:20 +02:00
Zoltan KochanandClaude Opus 5.5 6f95957063 docs: lint prose with Vale (#17238)
* 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>
2026-10-22 21:48:09 +02:00
Zoltan KochanandClaude Opus 5.5 cb496f5427 docs(pnpr): get zstd through Cloudflare's edge (#17230)
Cloudflare removes zstd from Accept-Encoding before a Worker runs, and
from cf.clientAcceptEncoding too, so pnpr behind the recipe's Worker never
sent zstd. The Worker now asks pnpr for "zstd, gzip", the value pnpm sends
on archive requests, and returns the coded body with encodeBody "manual".
Cloudflare passes a zstd body to a client that accepts it and decodes it
for one that does not. Verified on a trial deployment: a tar for pnpm
arrives zstd-coded, metadata for a gzip client is still gzip-coded, and a
gzip-only client gets the tar uncompressed.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 19:40:28 +02:00
Zoltan Kochan 79b4dd2d78 fix(pnpr): follow a Turso Cloud primary from the embedded replica (#17225)
Enable libsql's `sync` feature so the embedded replica
(`backend.libsql.replicaPath`) can follow a Turso Cloud primary.

The replica was built with libsql's `replication` feature only, whose
protocol Turso Cloud stopped serving: pnpr failed to start with
"Write delegation: Unimplemented ... deprecated version of sync". With
the `sync` feature, `Builder::new_remote_replica` defaults to
`SyncProtocol::Auto`: it probes the primary's `/info` route and uses the
newer sync protocol (bootstrap from `/export`, frames from `/sync`,
writes over Hrana) when the primary answers, and keeps the replication
protocol otherwise. A self-hosted `sqld` answers 404 on `/info`, so it
is unaffected.

The new test runs a mock primary that serves those routes in front of a
local libsql database and checks that the replica bootstraps from the
export, reads locally, and writes to the primary. Without the feature
it fails the way the issue describes.

`connect` also creates the replica's parent directory, as the local
stores do for their files, and with `syncInterval: 0` it runs the
initial sync under the startup deadline, since libsql would otherwise
bootstrap inside `Database::connect` with no bound.

Closes pnpm/tasks#183.
2026-10-22 19:19:52 +02:00
Zoltan Kochan aeeba836c4 fix(cli): accept a registries entry routed through the default scope (#17226)
The "registries entries do not match any configured registry" warning built
its configured set from the scoped and prefixed routes and the built-in
prefixes. An entry whose only route is the bare `@` scope is written to
`config.registry` and stored under no scope, so the warning named it as
ignored while the install resolved through it and kept its `serverType`.

The warning now builds its configured set from Config::resolved_registries,
the view that carries the default registry beside the scoped routes. The
built-in `@jsr` route comes with it, so an entry for npm.jsr.io is a
legitimate target too, as the built-in named registries already were.

Closes https://github.com/pnpm/tasks/issues/184
2026-10-22 19:16:49 +02:00
Khải 7b5aab6aca test(cli): share and widen the recursive-run marker deadline (#17221)
`task_starts_as_soon_as_its_dependencies_finish` failed on macOS after
30.448s, which is its build script's own 30000ms poll deadline rather than
any assertion in the test. The same test passed on ubuntu and windows in
that attempt, and the rerun passed on macOS with the identical commit, so
the threshold was marginal.

The deadline is there so a fixture that never overlaps fails instead of
hanging, but 30 seconds also asserts a schedule: the waiting script needs
the package manager to finish one project and then install and spawn
another, which on a loaded runner costs tens of seconds.
`.config/nextest.toml` already says as much, giving a stalled test
`period = 60s` and `terminate-after = 5`. The scripts were holding
themselves to a tenth of that.

`wait_for_marker_script` now builds the poll, so the deadline has one home
and reads as the deadlock detector it is. It is separate from
`CHILD_DEADLINE`, which is also 30 seconds but waits on a child the test
spawned itself rather than on a whole run of the package manager. The
deadline itself moves to 2 minutes.

Three fixtures shared the inlined poll, two in `task_graph` and one in
`environment`, so all three had the same latent flake and now share the
helper.

This widens the window rather than closing it. The fixture proves
interleaving with a barrier, and from inside the waiting script a project
that has not started yet is indistinguishable from one that never will
without a bound, so a bound remains and a slow enough runner can still
cross it. Asserting on dispatch order instead of blocking on a barrier is
the design that would not need one, and is not attempted here.
2026-10-22 19:15:54 +02:00
Amit Tal 7ed672f914 fix(deploy): verify the source lockfile on legacy deploy (#17211)
The legacy deploy resolves the deployed project from scratch and seeds
the resolver with the source lockfile's pins as preferred versions. The
lockfile-verification gate only runs on the install's wanted lockfile,
which for a legacy deploy is the deploy directory's own (absent) one, so
the source lockfile was never verified. A pin that minimumReleaseAge
rejects was then dropped by the resolver's release-age filter and
replaced by a mature version outside the lockfile, with no diagnostic.

pnpm 11 installs the legacy deploy against the shared lockfile and runs
verifyLockfileResolutions on it, so it fails with
ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION. Restore that behaviour by
verifying the source lockfile with the same resolution verifiers before
the deploy install. The check covers the whole source lockfile, as the
v11 gate and a workspace install do. trustLockfile and --trust-lockfile
skip it.

LegacyDeploySource now holds the loaded source lockfile and its path, so
the lockfile is read once for both the preferred versions and the
verification, and the verification cache is keyed by the same path a
workspace install records.

Fixes https://github.com/pnpm/pnpm/issues/17209
2026-10-22 19:09:59 +02:00
Zoltan Kochan 81e6e9ec1a docs: say where serverType pnpr applies and what a tar main base costs (#17227)
The registries page presented the pnpr layout as the layout of every pnpr
registry, but a pnpr .tgz base advertises the npm layout, which the
undeclared default already reconstructs; serverType: pnpr belongs on a tar
base. The pnpr configuration page now says tarballs: tar may go on the main
router and what that costs other clients. The Cloudflare recipe's Worker
waits through a cold start instead of failing it, and the page notes that
the edge rewrites Accept-Encoding.
2026-10-22 18:53:48 +02:00
Zoltan KochanandClaude Fable 5.1 5154bf82ae fix(exe): embed Node.js 26.12.1 in the standalone builds (#17228)
The standalone builds embedded Node.js 26.0.0, which predates the
October 2026 CA distrust shipped in Node.js 26.12.1, so a standalone
pnpm still accepted certificates issued under the distrusted
intermediates. Node.js 26.12.1 is already the repo's devEngines
runtime and is in the lockfile.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 18:32:26 +02:00
Zoltan Kochan f880d91124 fix(pnpr): recognise a unique violation reported by Turso (#17223)
The libsql auth backend classified a duplicate row only through SQLite
result codes. Turso's remote protocol reports it as a Hrana stream error
whose message carries the code name, so the counter seed on a second
start against the same database failed instead of being tolerated, and
pnpr exited at startup. A taken username reported the same way answered
500 instead of the login or name-taken path.

Parse the Hrana message the way the lock-conflict check already does.
Related to https://github.com/pnpm/tasks/issues/183.
2026-10-22 17:36:24 +02:00
Khải 9c11ea9948 fix(fs/linux): read the umask without writing it (#17219)
`current_umask` set the mask to `0` to learn what it was and put it back,
as POSIX leaves no other way: `umask(2)` returns the value it replaced.
That window is process-wide, so a file another thread created inside it was
created unmasked, with wider permissions than the user asked for. An
install under `umask 077` could leave a world-readable file behind.

The window was known and argued harmless, and the argument holds for what
it reasoned about: an unmasked file carries no executable bit, matches no
desired mode, and so is copied by the import tiers rather than linked. It
says nothing about the file's permissions, which is the defect.

Linux has published the mask in the `Umask:` line of `/proc/self/status`
since 4.7, so it is read from there first and the window never opens. A
kernel without the line, a host without `procfs`, and every other Unix fall
back to the syscall pair, which now has nothing at all between its two
calls.

One read per process either way: the `LazyLock` that kept the import hot
path from paying per file is unchanged.
2026-10-22 16:05:05 +02:00
7e8e991e7e feat(pacquet): load dotenv files named by --env-file (#16570)
Related to pnpm/pnpm#7111.

pnpm has no .env support, so users wrap every invocation with dotenv-cli
or node --env-file. This adds the loading to the Rust CLI itself: a
repeatable global --env-file <path> (clap Vec<PathBuf>) whose files are
parsed with dotenvy and layered into the process environment in order so
later files override earlier files while never overriding variables
already in the initial process environment (matching node --env-file).
Relative paths resolve against the invocation working directory before
--dir or --workspace-root is applied.

The load runs on the main thread before the startup thread exists
(set_var must not run once another thread is alive), ahead of config
resolution, so PNPM_CONFIG_* overrides and user-level .npmrc ${VAR}
placeholders (the registry-token case from pnpm/pnpm#7111) resolve from
the files, and ahead of any pinned-pnpm dispatch so the child inherits
the variables. Shim invocations skip the load (their arguments belong to
the target). The flag is stripped from the child argv (an older pnpm
would reject it). dotenvy $VAR expansion is accepted as standard dotenv
behavior (single-quote to keep a literal $); project-level .npmrc
expansion stays refused as before. Parse errors name the file without
echoing its contents, NUL keys or values fail instead of panicking,
invalid UTF-8 fails as a parse error, and a UTF-8 BOM is tolerated.

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-10-22 16:02:29 +02:00
Alessio AttilioandZoltan Kochan f0982d4f11 fix(resolver): refresh metadata during dedupe (#17215)
pnpm dedupe resolves a fresh lockfile without an existing wanted_lockfile
and with prefer_highest_version: true, so locked versions sit in
preferred_versions with VersionPreference::Highest while update remains
UpdateBehavior::Off. Under that combination, MirrorShortcuts::decide
allowed dominant_lockfile_version and DiskFirst::decide allowed reading
the on-disk metadata mirror without a conditional registry request,
leaving stale packument metadata (such as deprecation messages) in place
even when pnpm dedupe --check on a cold cache reported a diff.

Thread refresh_metadata through ResolutionRefreshOptions into
MetadataPickRequest for dedupe runs, and disable unverified on-disk
mirror shortcuts when refresh_metadata is set and the network policy is
not cache-first. Conditional ETag / Last-Modified headers from the mirror
are still sent so unchanged packuments get a 304 response.

Closes pnpm/pnpm#17208.

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-10-22 01:35:04 +02:00
Zoltan Kochan 7ef0e9d540 fix(exportable-manifest): keep pnpm.config in packed and published manifests (#17213)
A config dependency declares its settings YAML file in `pnpm.config` of
its `package.json`, which consumers read from the installed manifest
under `node_modules/.pnpm-config`. Preserve `pnpm.config` when exporting
a manifest for `pnpm pack` and `pnpm publish` while continuing to strip
the rest of the `pnpm` field, and always include the file referenced by
`pnpm.config` in the packlist.

Closes pnpm/pnpm#17210
2026-10-22 01:34:12 +02:00
Zoltan Kochan e2cc15488e fix(exec): do not dump core for pnpm when re-raising a child signal (#17214)
When a command started by `pnpm run`, `pnpm exec`, or `pnpm dlx` is
killed by a signal on Unix, `pnpm_executor::exit_like` calls
`pnpm_fs::die_from_signal` to reset that signal to `SIG_DFL` and
re-raise it on pnpm so the parent shell observes the same wait status.

When the child died from a core-dumping signal such as `SIGABRT` or
`SIGSEGV` (for example when `tauri build` aborts after a panic in
`@tauri-apps/cli`), re-raising that signal with `SIG_DFL` caused the
kernel to generate a second core dump for `pnpm` itself, recording a
`SIGABRT (SI_TKILL)` crash of `pacquet-main` in `systemd-coredump` or
overwriting the child's `core` file.

Suppress core dumps in `die_from_signal` before re-raising by setting
`RLIMIT_CORE` to 0 and, on Linux and Android where `do_coredump`
ignores `RLIMIT_CORE` when `kernel.core_pattern` pipes to a helper such
as `systemd-coredump`, clearing `PR_SET_DUMPABLE`.

Closes pnpm/pnpm#17204
2026-10-22 00:23:36 +02:00
Zoltan Kochan 3ce74f1e71 fix(tarball): do not retry rejected archive entry paths (#17212)
Attach a typed RejectedEntryPathError to TarballError::ReadTarballEntries when
clean_archive_entry_path rejects a tar entry path, preserve the extraction error
when a streaming tar hash is skipped after an extraction failure, and classify
both RejectedEntryPathError and TarballError::PathTraversal as permanent in
is_transient_error so rejected archives fail on the first attempt instead of
sleeping through the retry budget.

Closes pnpm/pnpm#17205
2026-10-22 00:02:40 +02:00
Zoltan Kochan cbe5accc7a fix(dedupe): ignore deprecation notices in dedupe --check (#17199)
`dedupe --check` gates on the lockfile text, so that changes a run makes
outside the resolution diff (config dependencies, settings) also fail
the check. A `deprecated` notice the registry added after the lockfile
was written changed that text too, and the check failed with "no
dependency resolution would change". The result depended on how fresh
the metadata cache was, so the same commit could pass locally and fail
in CI.

When the text differs, the check now passes if the env document is
unchanged, the old main document is in canonical form, and serializing
it with the new `deprecated` notices reproduces the rewritten text
exactly. Any other rewrite, formatting included, still fails. The guard
still restores the original lockfile.

pnpm v11 gates on the resolution diff only and is not affected.

Closes pnpm/pnpm#17198
2026-10-21 17:02:59 +02:00
Khải 7e81d63bdb refactor: replace bare bools with named enums and structs (#16501)
Several facts travelled through the resolver, the lockfile's YAML emitter,
the default reporter and pnpr as bare `bool`s that could not be read at
the use site: a `true`/`false` literal selecting which of two behaviors a
function performs, a `bool` travelling in a tuple with no name, two flags
where one implies the other, and a `bool` return meaning "which of two
things happened". Each of those now has a name.

In the resolver:

- `PeerWalkMode { Discovery, Final }` replaces `Walker::new`'s
  `discovery: bool` and the traversal field it was stored in, and
  `ChildOutputs::push` takes that mode instead of its own
  `collect_dep_paths: bool`.
- `PeerIdRecording { Record, Skip }` replaces the `record_peer_ids: bool`
  of the final-path pass, parameter and field.
- `ChildrenRecording` travels back to the callers of the children
  realisation next to the children, instead of the `bool` that
  `into_children` flattened it to; its callers ask it
  `stales_other_occurrences`.
- `PackageRegistration { Created(Arc<str>), Existing(Arc<str>) }`
  replaces the `(Arc<str>, bool)` the two package registrations
  returned.
- `DependencySpec { alias, bare_specifier, optional, injected }` replaces
  the `(String, String, bool, bool)` tuple that lived under two aliases,
  `WantedSpec` and `ChildSpec`, and was read positionally in six files. Its
  fields are named after `WantedDependency`'s, the resolved form they feed.
  `hoisted_dependency` builds the shape both hoist rounds want, with the
  rationale for its two defaults in one place.
- `collect_deps` takes a `DependencyGroup` instead of a manifest key and
  an `optional: bool`, and derives both from the group, so a caller
  cannot pair a key with the wrong flag.
- `WorkspacePeerSettings` groups the three consecutive booleans of
  `resolve_peers_workspace` into named fields.
- `NodeKind { Link, Leaf, Branch }` replaces the `is_link`/`is_leaf`
  field pair, of which only three of the four combinations were states.
- `ReusableResolution { Registry, Git }` replaces the `bool` inside the
  `Option<bool>` that `reusable_resolution_is_git` answered.
- `reuse.rs` calls the shared `register_peer_dep_names` instead of
  keeping a verbatim copy of it.

In the lockfile's YAML emitter, four `bool`s threaded through every node
and were read positionally by ten functions: `CollectionStyle`,
`FirstEntry`, `EntrySpacing` and `ScalarLines` replace them, in
`yaml_emit/style.rs`.

In the default reporter, `BlockPlacement { Scrolling, Pinned }` replaces
`Frame::emit`'s `fixed: bool`.

In the filesystem crate, `LockMode { Exclusive, Shared }` replaces the
`exclusive: bool` of `lock_file` and `try_lock_file`, and every
implementation maps it with a match: the standard library's methods,
Android's `flock` operation, and the WebContainer host lease's flag.

In the hoisted linker, `DirectLink { Unchanged, Created,
RootShadowRemoved }` replaces the `(bool, bool)` that linking one direct
dependency returned, of which only three combinations occurred.

In pnpr:

- `ResponseBody { Sent, Omitted }` replaces the `head: bool` of three OCI
  response builders and derives itself from the request method in one
  place.
- `MetadataFreshness { Cached, Refetched }` replaces the `refresh: bool`
  of the OIDC provider-metadata read, and separates the freshness
  question from the retry rate limit that shared the flag. A preparatory
  commit moves that cache into `oidc/provider_metadata.rs`, which also
  brings `oidc.rs` back under the file-length limit.
- `OrgGrant { Read, Publish }` replaces the `publish: bool` that main's
  organization consolidation left on `CallerOrgs::authorize` and on
  `authorized_owner_key`, so the pipeline-run, compiler-cache and
  shared-artifact paths ask for a grant with one named type.
- `GroupMatching { shadowed, allow_unresolved }` replaces the
  `(bool, &HashSet<&str>)` tuple parameter of the directory-dependency
  freshness check, whose call site read `(false, &peers.shadowed)`.
- `reject_removed_blocks` takes the parsed `packages:` and `groups:`
  blocks, whose distinct types make the transposition its two same-typed
  flags allowed a compile error.
- `ExistingDestination { Replace, Keep }` replaces the `replace: bool`
  behind `write_atomic` and `write_atomic_new`.
- `StaleEntry { Delete, Keep }` replaces the `delete_stale: bool` of the
  cargo index cache read.
- The resolution cache's eviction helpers lose a `private_first: bool`
  that their callers always passed as `true`, and the fallback branch
  that only ran when it was `false`;
  `CandidateScope { PrivateOnly, Any }` names the choice that remains.
- `CrateDocument::max_version` reads its two passes through named
  closures instead of a `yanked_allowed` flag.

Every branch on one of these enums is an exhaustive `match` that names
every variant, with no wildcard arm, no `matches!` and no `==` against a
variant, so adding a variant is a compile error at each site that has to
decide.

The criteria behind these choices are written down in
pnpm/CODE_STYLE_GUIDE.md under "Boolean parameters and fields": which
questions a `bool` may answer, how its use sites must read, what a
neighbouring flag does to it, and the exemptions that keep most of the
workspace's `bool`s as they are. This branch adds the section, including
the rule that a flag named for one of several alternatives wants an enum
of those alternatives rather than a better name, and the wildcard arm
that gives an unreachable combination away.

No behavior changes. The lockfile, resolver, package-manager, reporter,
fs, store-dir, deps-restorer, CLI and pnpr suites pass, and the first two
compare emitted lockfiles byte for byte.
2026-10-21 15:49:31 +02:00
43d0924777 fix(deps-restorer): keep an explicit copy import for built packages (#17196)
pnpm 12 imports a package that a lifecycle script or a patch will still
write with `clone-or-copy`, whatever `packageImportMethod` says, so the
build cannot write into the store through a hard link. That override
also replaced an explicit `copy`, which already gives the build private
files. On a filesystem that supports block cloning, such as ReFS or a
Dev Drive on Windows, those packages were cloned even though the user
asked for copies.

Keep `copy` when it is the configured method. Every other method still
becomes `clone-or-copy`. This matches pnpm 11, whose
`createPackageImporter` keeps `copy` for packages that will be built.

Closes pnpm/pnpm#17195

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 15:34:22 +02:00
Arthur Fontaine 01f0a52cdc fix(deps-restorer): leave present patched hoisted packages alone (#17189)
`package_is_reusable` excluded every reference containing
`(patch_hash=`. That matches a patched package, and also any package
whose peer suffix names a patched peer, such as
`react-dom@19.2.0(react@19.2.0(patch_hash=...))`. Each install that
reached the hoisted linker imported them all again. A React Native
workspace with patched `react-native` and `expo` re-imported 115
packages per install.

The exclusion is not needed. The patch hash is part of the dep path
that `hoistedLocations` records, so a changed or removed patch gives
the package another key, and the walker imports it again through that.
pnpm 10 and 11 reuse a placed package on location and version alone, so
this matches them.

Closes pnpm/pnpm#17188
2026-10-21 15:30:26 +02:00
Zoltan KochanandClaude Opus 5.5 78365b06ce chore: require code-owner review for tasks.yaml (#17197)
The pipelines decide what each CI job runs, and `pnpm pipeline`
reports a pipeline whose script does not exist as skipped and exits 0.
The task settings also control ordering and the cache keys of the
remotely cached jobs. An unowned tasks.yaml could therefore turn a CI
check into a no-op without code-owner review.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 15:10:24 +02:00
KhảiandClaude 9aa4a453af refactor(install): split directory_deps into the stages of its descent (#17194)
`directory_deps.rs` held 393 lines of code against the 400 the
`perfectionist::overly_long_file` limit allows, so every addition to the
directory-dependency freshness check had to be paid for elsewhere in the file.

Its items are the three stages of one descent, each reached through a single
entry point, plus the vocabulary all three share:

- `importer_deps` reads the specs an importer declares, decides which of them
  name a directory dependency at all, answers a `link:` spec on its own, and
  resolves the rest to their lockfile entries.
- `package` checks one directory package: the manifest the resolver would read
  for it, whether this scope has checked it already, and the peers its own
  dependencies shadow.
- `children` compares the children the resolver walks for a package with the
  ones its snapshot records, and recurses into `package` for a child that is
  itself a directory.

The module root keeps what the three share: `LocalDepContext`, the staleness
error they raise, the test for a `file:` spec, and the directory set the spec
comparisons resolve against.

The move is otherwise verbatim. `check_single_directory_dep_freshness` and
`check_local_dep_children_freshness` widen to `pub(super)` because their
callers are now siblings rather than neighbours, and the root re-exports
`check_directory_dependencies_freshness` so `manifest.rs` reaches it unchanged.
No behavior changes.


Claude-Session: https://claude.ai/code/session_01QYTBQo8HY7xD2iz6hjo1CW

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-21 14:53:50 +02:00
Zoltan KochanandClaude Opus 5.5 82e235bacf chore: split workspace settings into policy, catalog, and task files (#17192)
* chore: move supply-chain settings to a protected config file

Use the `include` setting from pnpm 12.15 to move the settings that
decide what gets installed and what may run into
.config/pnpm/security.yaml: allowBuilds, auditConfig, the
minimumReleaseAge and trustPolicy settings, overrides,
packageExtensions, patchedDependencies, patchesDir, and registries.
pnpm-workspace.yaml delegates exactly that list, so no other file can
define those settings. Patch paths are relative to the included file.

pnpm 12.14 ignores `include` and would drop every moved setting, so
the packageManager pin moves to 12.15.0.

CODEOWNERS now names both workspace files, the pnpmfile, and the
patches explicitly, and the CI path filters watch the new file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: move routine settings to a file without a code owner

The catalog, the update settings, and the task-runner settings
(pipelines, tasks, concurrencyGroups) move to
.config/pnpm/routine.yaml, which CODEOWNERS leaves unowned. Because
pnpm-workspace.yaml delegates only that list to the file, any other
setting defined there fails with ERR_PNPM_SETTING_NOT_DELEGATED, so
the file cannot be used to change the protected settings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: move the catalogs to their own file

.config/pnpm/catalog.yaml owns `catalogs`, which covers the default
catalog and any named catalog added later. Like routine.yaml, it has
no code owner.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: name the protected file policies.yaml and give tasks their own file

security.yaml becomes policies.yaml. The task-runner settings
(pipelines, tasks, concurrencyGroups) move to tasks.yaml, and
`update` joins the catalogs in catalog.yaml, since its ignoreDeps list
holds back catalog entries. routine.yaml is gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: move catalogMode and catalogPrune to catalog.yaml

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: keep the patch list next to the patches

patchedDependencies and patchesDir move to __patches__/patches.yaml,
so the entries name the patch files without a directory prefix and
patchesDir is `.`. CODEOWNERS already covers __patches__/. The CI path
filters that did not watch __patches__/ now watch patches.yaml, since
they watched the patch list when it lived in pnpm-workspace.yaml.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: move the patches to .config/pnpm/patches

Every pnpm configuration file now lives under .config/pnpm, so each CI
path filter watches `.config/pnpm/**` instead of listing the files.
CODEOWNERS keeps the patches protected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 14:33:59 +02:00
Zoltan Kochanandgithub-actions[bot] 87636f932c chore: update dependencies, Node.js, pnpm, and GitHub Actions (#17191)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-10-21 14:08:06 +02:00
Zoltan Kochanandgithub-actions[bot] 91774e72a0 chore(release): 11.28.7, pacquet 12.15.0, pnpr 0.1.0-alpha.16 (#17187)
* chore(release): 11.28.7, pacquet 12.15.0, pnpr 0.1.0-alpha.16

* docs(release): curate 11.28.7, pacquet 12.15.0, and pnpr 0.1.0-alpha.16 release notes

Add lead paragraphs and group the patch entries by what the reader was
doing, with security fixes first on each page.

- v11: lead with the registry metadata name check, pinned pnpm
  trustPolicy check, maxTarballUnpackedSize, and the removal of registry
  revisions; merge overlapping entries for minimumReleaseAge metadata
  revalidation, frozen-lockfile directory dependency checks,
  syncInjectedDepsAfterScripts, verifyDepsBeforeRun, pnpm sbom, and
  unreadable config settings.
- v12: order minor highlights from config includes and
  lockfile.integrityTarget down to interactive update styling; drop the
  pnpr-only distribution note from the CLI changelog; merge overlapping
  entries for workspace and pnpr install speedups, minimumReleaseAge and
  If-Modified-Since revalidation, frozen-lockfile and injected package
  checks, verifyDepsBeforeRun, pnpm prune, pnpm pack, pnpm licenses
  list, pnpm self-update, pinned pnpm flag forwarding, and WebContainer
  fixes.
- pnpr: lead the minor section with the breaking ecosystem URL prefix,
  hosted storage layout, artifacts.orgs, and strict config schema
  changes; fold dist.pnpr tarball metadata into one entry and drop the
  redundant /metrics note; lead the patch section with reserved-username
  and auth security fixes.
- Use one issue-link label style across the release pages.

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
pnpr@0.1.0-alpha.16 v11.28.7 v12.15.0
2026-10-21 12:15:12 +02:00
7b956715f0 feat(update): highlight the active row of the interactive update prompt (#17168)
`pnpm update --interactive` marked the row under the cursor only with the
`❯` glyph. The dependency list previously turned off the prompt's cyan
highlight (`highlight_active: false`), because repainting the row cyan with
a single outer wrap would hide the colors that show which part of each
target version changed. In a long list the cursor was hard to follow.

`CheckboxPrompt` now always highlights the active row in cyan and re-applies
the cyan foreground after every SGR foreground reset (`\x1b[39m` or `\x1b[0m`)
inside the row, so embedded colors such as the target version diff survive
while the rest of the row is cyan. `CheckboxTheme::highlight_active` is
removed now that every prompt style uses the same cyan active-row highlight.

The prompt for each PromptStyle is now built by
PromptStyle::checkbox_prompt, outside the terminal-only branch of
UpdatePrompt::select, so tests cover the theme each style uses.

---------

Co-authored-by: Martin Olsson <martin@minimum.se>
Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-10-21 11:46:00 +02:00
Zoltan Kochan e2a11f5611 perf(install): precalculate package bins and shebang runtimes (#17186)
Precalculate package binary entrypoints and shebang runtimes during tar
streaming in pnpr (dist.pnpr.bins) and during cold tarball extraction in
pnpm so .bin command shims can be linked without reading package.json,
walking directories.bin, or opening bin scripts on disk.

- Add PackageBin, ScriptRuntime, parse_shebang_from_bytes,
  resolve_package_bins, and verify_package_bins in pnpm-package-manifest
  with strict path normalization and shell-safe runtime validation.
- Extend inspect_tar in pnpm-tarball to compute bins and shebang runtimes
  in the same streaming pass as requiresBuild when requiresBuild is false.
- Emit dist.pnpr.bins in pnpr packument responses and cache it in
  pnpr-storage TarMetadataCache.
- Propagate cold-extracted manifests and precalculated bins through
  CreateVirtualStoreOutput into LinkVirtualStoreBins and direct-dep bin
  linking, and verify warm-cache dist.pnpr.bins against the store index
  manifest and CAS file list before skipping shebang reads.

Closes pnpm/tasks#178
2026-10-21 11:42:03 +02:00