Commit Graph
56 Commits
Author SHA1 Message Date
Zoltan Kochan f00e7680aa chore(release): 11.26.0 (#14638) 2026-09-07 01:30:44 +02:00
Zoltan Kochan 0034971746 fix(engine.pm.commands): verify only the pnpm engine that is installed (#14583)
The engine identity check verified every package the env lockfile pins and the host's platform binary of the native wrapper, whichever build was going to run. Below v12 the lockfile pins the JavaScript `pnpm` and the SEA `@pnpm/exe` side by side, so on a host where `@pnpm/exe` ships no binary the switch failed before installing the JavaScript `pnpm`, which is the only build that runs there and never links a platform binary.

Pass the package selected for installation into the identity check, in both the TypeScript v11 CLI and pacquet. Verify that package at the exact version the install is rooted at, plus the host platform package the install would link over its bin. Report a dedicated no-native-binary error when the selected native engine cannot run on the host.

Fixes pnpm/pnpm#13622.
2026-09-06 18:46:13 +02:00
Zoltan Kochan 58779a38fa fix: preserve pnpm 11 compatibility with native pnpm 12 (#14506)
pnpm 11 installs package-manager versions with lifecycle scripts disabled.
Starting with pnpm 12.3.0, the published placeholder has a Node.js shebang.
The bin linker records Node.js in its launcher before replacing the target
with the native executable, then retains that launcher because the target
path is unchanged.

Restore the shebangless placeholder in the pnpm 12 npm wrapper so existing
pnpm 11 releases can install future pnpm 12 versions through their version
store. Keep the npm global Windows postinstall relink, which regenerates npm's
shims against pnpm.exe after lifecycle scripts install the native binary.

Script-blocked wrapper installs must enable lifecycle scripts. Corepack keeps
its separate JavaScript entry point.

Closes pnpm/pnpm#14502.
2026-09-03 19:52:13 +02:00
Zoltan Kochan 8a8130fd1f fix(setup): preserve bundled node-gyp (#14401)
Standalone release archives contain node-gyp beside the executable, but setup
reinstalls that directory as a global package. The temporary manifest did not
declare the standalone distribution's files, and pacquet hardcoded directory
fetches to all-files mode, whose dependency exclusion removed the nested
node-gyp payload.

Declare the executable and dist directory as the standalone package's files.
Make pacquet pass its existing deployAllFiles-derived package-file setting to
the directory fetcher, matching the TypeScript install path. This preserves the
bundled node-gyp payload through the normal local-directory install without an
intermediate archive or tarball-specific behavior.

Cover nested node_modules selected by a files field in both fetchers, and verify
the setup-shaped pacquet global install retains node-gyp while ignoring the
standalone package's lifecycle scripts.

Related to https://github.com/orgs/pnpm/discussions/14219#discussioncomment-18227726.
2026-09-01 12:51:43 +02:00
Zoltan Kochan a3de3b2aa5 fix(pacquet): restore pnpm executable without file extension (#14393)
Restore the pnpm wrapper target without a file extension so pnpm 12.1 and earlier can replace it with the downloaded native executable on POSIX systems.

The cross-platform pnpm.exe target left its placeholder script in place during upgrades from older pnpm releases, breaking automatic package-manager switching. The PowerShell shim issue remains tracked by npm/cmd-shim#51.
2026-09-01 02:28:15 +02:00
Zoltan Kochan 3af5322039 fix: authenticate node.js mirror downloads (#14375)
Reuse the existing URL-scoped registry credential lookup for every Node.js mirror request instead of introducing a separate authentication setting.

This covers release indexes, SHASUMS files and signatures, and runtime archives in both implementations while retaining longest-path-prefix scoping and cross-origin redirect protections. Authenticated checksum metadata bypasses the URL-keyed disk cache so it cannot cross credential contexts.

Closes pnpm/pnpm#14334.
2026-08-31 21:31:28 +02:00
Zoltan Kochan 470c15502b fix(pacquet): generate executable PowerShell shim (#14369)
fix(pacquet): generate executable PowerShell shim

Publish the native CLI wrapper with pnpm.exe as its cross-platform bin target so npm-generated PowerShell shims invoke an executable file.

Keep package-manager delegation, self-update validation, and dispatcher refresh compatible with both the new target and existing extensionless wrappers, and cover the npm global-install flow with an integration test.
2026-08-31 20:14:58 +02:00
Zoltan Kochan 6d90c71efd chore(release): 11.25.0, pacquet 12.1.0, pnpr 0.1.0-alpha.9 (#14306) 2026-08-29 15:50:36 +02:00
Zoltan Kochan 8aa1e35ace feat: unite the side-effects cache settings into one declaration (#14285)
`sideEffectsCache` now carries the whole story — whether a build is
restored, whether one is saved, and the remote tier that shares it
between machines. `remoteSideEffectsCache` broke the rule that a setting
extending another starts with its name, sorting away from the family it
belongs to; uniting the three rather than renaming one follows what the
registries settings did. The remote tier's `organization` becomes `org`,
which is what pnpr calls that namespace and what its endpoints are built
from.

Both spellings shipped in pacquet 12.0.0 but in no released pnpm 11, so
the TypeScript side is a free rename and only pacquet needs the aliases.

Every older spelling keeps working. Precedence is per field rather than
per section, and does not depend on key order: the two spellings
accumulate separately, `organization` is resolved to `org` per source,
and they are combined once after every source has been read. The Rust
side uses an explicit field rather than a serde alias, which would make a
file carrying both keys a duplicate-field parse error.

Two behaviours move toward pacquet, which already had them right:
`sideEffectsCacheReadonly` blocks writing, and setting it alongside
`sideEffectsCache: false` gives a read-only view rather than switching
the cache off. The declaration can also express writing without reading,
which the Rust `cache`/`readonly` pair had no spelling for, so `Config`
gained the two settings its helpers now prefer.
2026-08-28 21:51:36 +02:00
Zoltan Kochan c22eac25ab feat(init): pin the latest pnpm version, not the running one (#14167)
`pnpm init` wrote its `devEngines.packageManager` / `packageManager` pin
from the version of pnpm that happened to run the command. That makes
staleness self-perpetuating: a developer whose global pnpm is a year old
scaffolds a project pinned to that year-old pnpm, and the pin then holds
every collaborator there. It is also the friction behind the request to
have pnpm auto-update itself (pnpm/pnpm#7490) — a brand new project is
the one place where jumping to the current release can break nothing.

Both stacks now resolve pnpm's `latest` tag when they are about to write
a pin, and fall back to the running version whenever that cannot be
answered: no `cacheDir`, `offline` / `preferOffline`, an unreachable or
slow registry, a `latest` the `minimumReleaseAge` / `trustPolicy`
settings reject, or a `latest` older than the running version (the tag
lags whenever a new major ships untagged). The lookup makes one attempt
with no retry schedule and is capped at ten seconds, so scaffolding a
manifest can never fail or appear to hang on it, and it is skipped
entirely when a `package.json` is already there.

The lookup runs through the trusted package-manager registries, the
channel `self-update` provisions the engine from, rather than the
project registries: the value becomes a durable pin that drives an
engine download, so a repo-controlled `.npmrc` must not choose it.

On the TypeScript side the version resolution `self-update` already had
— bootstrap config, release-age cutoff excluding the running version,
trust policy — moves to `resolvePnpmVersion`, which `init` now shares;
`self-update`'s `resolution.manifest.version` reads become the
`targetVersion` it already declared. On the pacquet side `init` gains a
real command future, since it now awaits the registry.

Related to pnpm/pnpm#7490.
2026-08-25 18:25:07 +02:00
Zoltan Kochan 4c07abab66 fix: stop pointing users at Corepack for pnpm updates (#14115)
## Summary

The v11 CLI and the Rust engine, matching #14113 (merged) plus its follow-up #14116.

Two places told users to update pnpm with Corepack:

- The **update notification** printed `corepack use pnpm@<version>` when pnpm ran under Corepack. It now prints pnpm's [standalone install script](https://pnpm.io/installation) — `curl -fsSL https://get.pnpm.io/install.sh | sh -`, or the `Invoke-WebRequest` form on Windows.
- **`pnpm self-update`** refused under Corepack with *"You should update pnpm with corepack"*. It now says it cannot update itself under Corepack, and names the standalone install script as the hint.

The notification also stopped naming `pnpm add -g pnpm`, which `add` refuses outright (`ERR_PNPM_GLOBAL_PNPM_INSTALL` in the TypeScript CLI, `GlobalError::GlobalPnpmInstall` in the Rust one). `pnpm self-update` is named only where it can replace the executable in use — an install another package manager owns is resolved from that manager's bin directory, so a self-update would land beside it and report success while the old pnpm keeps announcing the update:

| `PNPM_HOME` set | under Corepack | suggestion |
| --- | --- | --- |
| yes | no | `pnpm self-update` |
| yes | yes | standalone install script |
| no | either | standalone install script |

The command has one definition per stack — `standaloneInstallCommand` in `@pnpm/cli.meta`, `standalone_install_command` in `pnpm-config` (beside `PNPM_VERSION`) — shared by the notification and the self-update refusal so the two cannot drift.
2026-08-24 16:45:42 +02:00
Zoltan Kochanandgithub-actions[bot] e3cb54258c chore(release): 11.24.0, pacquet 12.0.0-rc.10 (#14134)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-24 15:53:03 +02:00
Zoltan Kochan 9798c9f611 revert: "chore(release): stop publishing @pnpm/exe from v12 (#14121)" (#14133)
This reverts commit afb4071811.
2026-08-24 15:35:45 +02:00
Zoltan Kochan afb4071811 chore(release): stop publishing @pnpm/exe from v12 (#14121)
Up to v11, `@pnpm/exe` was the build of pnpm that bundled a Node.js
runtime, so it ran where the JavaScript `pnpm` package could not. From
v12 the `pnpm` package is itself the native executable and needs no
Node.js, which left `@pnpm/exe` an equal-content copy of it, published
and dist-tagged for nothing.

generate-packages.mjs no longer emits the `pnpm-exe` wrapper dir, and
release.yml no longer packs or stages it. The per-platform
`@pnpm/exe.<target>` native packages are unchanged: they carry the
binaries the `pnpm` wrapper resolves.

update-latest.yml would have failed on the first v12 promotion, since it
unconditionally installs and dist-tags `@pnpm/exe` at the promoted
version. Both sites now pick the wrapper by major. The upgrade check
gates on the *starting* version so upgrading a v11 `@pnpm/exe` onto a
v12 release is still covered, and it gained `--allow-build=pnpm` because
from v12 the preinstall that swaps the placeholder bin for the native
one lives on the `pnpm` package -- without it that check would have
validated a placeholder.

No runtime code changes: both stacks' package-to-install selection
already converges on `pnpm` for major >= 12, the update notifier already
prints `pnpm`, and `@pnpm/exe` stays in the bin-resolver and global-alias
lists so v11-and-earlier installs keep working. Only the comments that
justified those behaviors with "published under both names" needed
correcting.
2026-08-24 02:39:51 +02:00
Zoltan Kochanandgithub-actions[bot] 726d6b4a04 chore(release): 11.23.0 (#14111)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-23 15:47:36 +02:00
Pooria FaramarzianandZoltan Kochan bd2d0b7bba fix(self-update): bump caret/tilde devEngines.packageManager ranges (#13949)
## Summary
- `pnpm self-update` now rewrites a simple `^`/`~` `devEngines.packageManager.version` in place when the resolved version still satisfies the range, matching `pnpm update` and `pnpm runtime set`.
- Complex ranges such as `>=8.0.0` are still left unchanged (the lockfile keeps the exact pin).
- Applied in both the TypeScript CLI and pacquet.

Fixes https://github.com/pnpm/pnpm/issues/13935

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-08-21 15:01:01 +02:00
Lazizbek Ergashev f28e6ccffc fix(self-update): do not downgrade when a dist-tag points at the running version (#13889)
The minimumReleaseAge cutoff filters an immature version out of the
packument and repopulates every dist-tag pointing at it to the newest
mature release below it. For a dependency that is the right answer,
but self-update reads the tag as "which pnpm should I be running", so
a tag pointing at the freshly published version the user already runs
resolved to the previous release and downgraded them: pnpm
self-update next-12 on 12.0.0-rc.4 switched to 12.0.0-rc.3.

The no-downgrade guard added in pnpm/pnpm#11435 does not cover this,
and cannot be widened to every tag without losing the explicit
"pnpm self-update latest" downgrade it deliberately allows.

Self-update now appends the running version to
minimumReleaseAgeExclude before it resolves. That version is already
installed, so the cutoff protects nothing by hiding it, and keeping
it visible pins any tag that points at it or past it to it. Mirrored
in pacquet's resolve_pnpm_version.

Closes pnpm/pnpm#13883.
2026-08-20 21:19:54 +02:00
Zoltan Kochan c9b8632ea6 feat(config): declare each registry once in the registries setting (#13942)
Registries do not all lay out tarball URLs the way the npm registry does.
JFrog Artifactory repeats the scope in a scoped package's tarball filename
(`@acme/widget/-/@acme/widget-1.0.0.tgz`) where npm strips it. pnpm cannot
rebuild such a URL, so it writes it out for every scoped package instead of
omitting it, and the lockfile carries a host-specific URL per dependency.

pnpm had no place to record such a fact, because it had no place to
describe a registry at all — only three ways to name one. `registries`
mapped a scope to a URL, `namedRegistries` mapped a bare-specifier prefix
to a URL, and neither could carry anything else.

`registries` now declares a registry once, keyed by its URL, with every
fact about it in the entry: a `serverType`, the `scopes` routed to it,
and the `prefix` it answers to. `serverType` has three states:

  undeclared  strict; only the exact canonical URL is reconstructible
  npm         also serves the percent-encoded scoped path
  artifactory repeats the scope in the tarball filename

registry.npmjs.org resolves to `npm` as a built-in, so its behavior is
unchanged and the old hostname check becomes that one default rather than a
special case in the predicate. `npm` cannot be the default: asserting
npmjs-compatibility is a claim only the operator can make.

The URL is the key because every fact in an entry is a fact about that
server. Keying the layout by scope would bind it to whoever the scope
currently points at, so two developers whose scope resolves differently
would write lockfiles that disagree about which URLs may be omitted.
`scopes` and `prefix` are routes to the registry and are inverted at
config-read time into the two lookups the rest of pnpm already queries,
leaving the resolver, installer, and lockfile layers untouched and the
precedence chain (builtin < .npmrc < yaml < `_auth` < CLI) unchanged.

The layout is declared, never inferred. Sniffing the registry URL cannot work:
a virtual repository serves both layouts at once, depending on whether each
package was synced from upstream or published locally, so no registry-level
signal — route, response header, or probe — decides it. Declaring it also
keeps a wrong guess a fixable misconfiguration instead of a silent breakage.

`serverType` feeds a single URL builder that both sides of the lockfile use:
the writer omits a tarball URL only when the builder reproduces it, and the
reader rebuilds it with the same call. They therefore agree by construction,
so pnpm never has to assume a registry serves some second URL as well.

The setting lives in pnpm-workspace.yaml rather than .npmrc because the
lockfile depends on it: one developer omitting URLs that another reconstructs
differently would break a frozen install. A `serverType` in the global
config.yaml is ignored for the same reason, while the routes declared
alongside it are kept. Credentials are rejected there — the file is
committed — and still belong in .npmrc. The registry URL is the map key, so
the request-destination env gate applies to keys as well as values.
Credentials and unknown fields are refused after parsing, since a parse
error renders the offending source line verbatim.

A map whose values are all strings is the older `<scope>: <url>` shape and
is still read as one. Mixing the two shapes in one map is refused, and so is
a URL-keyed entry written as a string. `namedRegistries` is deprecated in
favor of `prefix` and is read only for prefixes `registries` does not
declare; a prefix stays singular because it is the registry's identity in a
lockfile dep path.

An entry that routes nothing to itself and matches no configured registry is
reported as a warning rather than silently ignored; it is a warning and not
an error because a shared config dependency can legitimately describe
registries a given project does not use.

Config dependencies and pnpr-server-mode resolution pass no server type; in
both, the writer and the reader share that default, so they stay consistent.

Closes pnpm/get-npm-tarball-url#16. Supersedes pnpm/pnpm#13920.
2026-08-17 01:28:21 +02:00
Zoltan Kochanandgithub-actions[bot] 93fcba4224 chore(release): 11.22.0, pacquet 12.0.0-rc.6 (#13926)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-15 18:50:30 +02:00
Zoltan Kochan 509ff4461d perf(resolver): cache Node.js release metadata and skip the index for exact runtime pins (#13901)
The first node shim run in a project pinning a runtime through
devEngines.runtime took ~650ms even when the exact version was already
in the store. ~530ms of that was network, re-downloading immutable and
already-verified release metadata: a cold index.json fetch during
pre-save specifier normalization, a second index.json fetch in the
resolver, the SHASUMS256.txt + signature fetch, and a cold connection to
unofficial-builds.nodejs.org for the musl SHASUMS.

Two changes, mirrored in both stacks:

- Exact stable-release specifiers (runtime:22.23.2) skip the release
  index: the specifier is its own resolution and existence is proven by
  the asset-list fetch. When that fetch fails, the index is consulted
  after the fact so a nonexistent version still raises
  ERR_PNPM_NODEJS_VERSION_NOT_FOUND. The pacquet-only pre-save
  normalization takes the same shortcut.

- Per-version SHASUMS256.txt bodies are cached under
  <cacheDir>/v11/runtime-shasums/<host>/<url path>. The URLs are
  version-pinned and immutable; signed bodies are cached only after
  their OpenPGP signature verified, and a cached body is trusted like
  the registry metadata mirror (no re-verification on read). Both
  stacks share the layout.

With a warm store, the first shim run drops from ~650ms to ~100ms — the
remainder is materializing the runtime tree into the global virtual
store — and no longer needs the network at all.

Closes pnpm/pnpm#13899.
2026-08-14 11:58:24 +02:00
Zoltan Kochan f100948180 perf: let pnpm add skip resolution for an already-locked version (#13807)
pnpm remove already skips resolution when the lockfile proves the
outcome; its twin did not. `pnpm add <pkg>@<version>` re-resolved the
whole graph even when the requested version was one the lockfile
already held — promoting a transitive dependency to a direct one, or
adding to a second workspace package what a first one already depends
on.

The rule is the one pnpm/pnpm#13779 established: resolution reuses the
already-locked version it would dedupe onto rather than fetching a
higher one, because preferredVersions biases it toward what the graph
already holds. The importers handler now records a brand-new importer
edge at that version, under the group the manifest declares, alongside
the whole-new-project entry pnpm/pnpm#13803 added. It reuses that
change's guards: a range several locked versions satisfy under
`resolutionMode: time-based` or `lowest-direct` keeps the resolver,
since those pick the low end and only for a run that leaves the
manifest alone, and so does a plain range on a workspace project's
name, which may resolve to a link instead. A lockfile carrying `time`
keeps it too: `time` records a publish date per direct dependency and
is only pruned on save, never extended, so a package promoted into
that position would be missing the one a resolution records.

`installSome` cannot edit the manifests in place the way
`uninstallSome` does — the resolution path reads them to tell a new
dependency from a re-added one, and reads them again to pick the range
style it saves — so the edit is staged on copies, handed to the fast
update and its freshness gates, and the context takes it only once the
rewrite commits. The range a selector saves is computed with the
resolver's own rule, extracted as `calcVersionRange` in
`@pnpm/pkg-manifest.utils` alongside the `inferRangeSpecStyle` it needs.

pacquet needed only the gate: `add` already pins the manifest before
the install runs, so `may_fast_update_lockfile` admits `InstallSome`
and `add` stops forcing `preferFrozenLockfile` off. `update` is also
`InstallSome` and keeps its explicit opt-out, so it is unaffected. Its
manifest pin still consults the registry packument; only the
install-side resolution is skipped.

The narrow cut is a plain registry specifier. A dist tag, an alias, a
`workspace:`/`catalog:`/git/tarball specifier, `--save-peer`, a
`catalogMode` other than `manual`, an overridden package, and a version
nothing locked satisfies all keep the resolver.

Closes pnpm/pnpm#13798.
2026-08-11 14:32:55 +02:00
Zoltan Kochanandgithub-actions[bot] 8adb97ed05 chore(release): 11.21.0, pacquet 12.0.0-rc.2 (#13742)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-09 16:02:53 +02:00
Zoltan Kochan 4a5a10d2ca fix(setup): declare the module type in the manifest written for a standalone executable (#13732)
`pnpm setup` writes a package.json next to a standalone executable whose
directory has none — the tarball that https://get.pnpm.io/install.sh
downloads — so the global install has a package to install. That
manifest omitted `"type": "module"`, so Node.js reparsed the ESM files
shipped beside the executable (`dist/worker.js`) as CommonJS first and
printed a MODULE_TYPELESS_PACKAGE_JSON warning on every spawn.

The published `@pnpm/exe` manifest already declares the module type, so
this only affected installs made through the standalone script.

Fixed in both stacks. The manifest construction moves into
`standaloneManifest` / `standalone_manifest`, so it can be asserted
without running the global install the surrounding function performs.
2026-08-09 15:43:31 +02:00
Zoltan Kochan f41d01e44a fix(self-update): support registries without canonical tarball URLs or signature metadata (#13728)
The package-manager bootstrap hardening broke version switching on
private mirrors and feed proxies:

1. Registries that advertise tarballs on a different host than the one
   serving the packument always retain a tarball URL in the resolution,
   which the integrity-only env-lockfile validation then rejects
   (Closes pnpm/pnpm#13619).
2. Registries that serve no dist.signatures fail the engine identity
   check outright (Closes pnpm/pnpm#13147).

Fixes, in both stacks:

- Package-manager dependency resolutions are recorded integrity-only:
  the advertised tarball URL is dropped at resolution time, because the
  bootstrap never fetches a lockfile-recorded URL - the download URL is
  always derived from the trusted bootstrap registries. Dropping the URL
  does not perturb the global-virtual-store slot hash, which is derived
  from the integrity alone whenever one is present.
- Persisted entries that fail the bootstrap validation (e.g. written by
  an earlier pnpm) are discarded and re-resolved through the trusted
  registries instead of failing every command.
- Engine identity verification falls back to fetching the signature from
  the canonical npm registry when the configured registry cannot provide
  a verifiable one; it is verified against the same embedded npm keys
  over the installed integrity, so the source of the signature bytes
  does not change what they prove.
- When no signature is obtainable at all (both sources unreachable, or a
  shasum-only registry yields a sha1 pin no npm signature can cover),
  the switch proceeds with a warning - only when the engine resolves
  through a non-canonical registry, which can only come from the user's
  own trusted (non-project) configuration. The canonical-registry
  comparison normalizes through URL parsing, so URL-equivalent spellings
  cannot sidestep the fail-closed policy. Tamper evidence still fails
  closed.

Verified end to end against packagefeedproxy.microsoft.io, the registry
from the report in discussion 3787.
2026-08-08 20:42:37 +02:00
Zoltan Kochanandgithub-actions[bot] ebc48abdc5 chore(release): 11.20.0, pacquet 12.0.0-beta.4 (#13608)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-03 15:48:54 +02:00
Zoltan Kochan 51b43ed396 fix(env-installer): stop pinning the exe package from pnpm v12 (#13599)
From v12 the unscoped `pnpm` package is itself the native executable, so
`@pnpm/exe` is no longer published alongside it. The env lockfile now pins
only `pnpm` for those versions, in both stacks, instead of resolving a
package that does not exist.

The native-binary leg of the engine identity check followed `@pnpm/exe`
by name, so it silently verified nothing once that package is absent. It
now follows whichever package carries the host's platform binary as an
optional dependency: `@pnpm/exe` for the majors that publish it, the
unscoped `pnpm` from v12.

On the TypeScript side the wanted version can arrive as a range or a
dist-tag (`pnpm with latest`), so `pnpm` is resolved first and the second
resolution only happens for the versions that do ship `@pnpm/exe`.
2026-08-03 10:23:06 +02:00
Zoltan Kochan d3556f6ba9 refactor(text): move lexCompare and nerfDart into the monorepo (#13535)
Both utilities lived in the pnpm/components Bit workspace, where their
component names collide with same-named components elsewhere in the Bit
registry. Move the code here and publish it under new names:

  `@pnpm/util.lex-comparator` -> `@pnpm/text.ordinal-comparator`
  `@pnpm/config.nerf-dart`    -> `@pnpm/config.registry-auth-key`

The new names describe what the utilities do: the comparator is ordinal
rather than locale-aware, and the mapped URL is the key that registry
settings are stored under in `.npmrc`. The exported functions keep their
names, so consumers only change their import specifiers.

Implementations and tests are carried over unchanged. The rationale from
the components' docs pages moves into doc comments: why `localeCompare`
cannot be used for values compared across machines, and where `nerfDart`
originates.

No pacquet counterpart is needed. The Rust stack has its own
implementations of both and no user-visible behavior changes.
2026-07-31 23:00:10 +02:00
Zoltan Kochan 536b7a2c8a chore(release): 11.19.0 (#13524) 2026-07-31 10:46:26 +02:00
Minha KangandZoltan Kochan be871d7340 fix(resolver): keep the explicit = operator when updating an exact pin (#13184)
pnpm update wrote the new version of an `=`-pinned dependency back as the
bare version, dropping the explicit operator (`=3.5.1` became `3.5.2`).
The two spellings are the same semver range, but the `=` form marks the
pin as deliberate, so the update should keep it.

The save-style enum (formerly PinnedVersion) gains an `exact` variant
and is renamed to RangeSpecStyle: it selects the operator a specifier is
saved with, not a pin granularity. inferRangeSpecStyle (formerly
whichVersionIsPinned) classifies a bare `=` before a full version as
`exact` (partial `=1.0` / `=1` keep pinning like the plain version they
prefix), and the specifier formatters emit `=` for it. A granularity
projection (rangeSpecGranularity / RangeSpecStyle::granularity) collapses
`exact` to `patch` for consumers that only care about range width, such
as the save-workspace-protocol: rolling mapping, where an `=` pin maps
to `workspace:*` like other exact pins. `@pnpm/types` keeps PinnedVersion
as a deprecated alias and stays declaration-only; the shared helpers
live in `@pnpm/pkg-manifest.utils`. save-prefix now accepts `=`, saving
new dependencies as `=x.y.z`; --save-exact still wins and saves the
bare version. Implemented in both the TypeScript CLI and the Rust port.

Closes pnpm/pnpm#13168

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-07-30 13:37:36 +02:00
Zoltan Kochanandgithub-actions[bot] 925c33d780 chore(release): 11.18.0, pacquet 12.0.0-beta.0 (#13481)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-29 09:29:53 +02:00
Zoltan Kochan 51c849f7d7 fix(deps-graph-hasher): give local directory deps their own global-virtual-store slot (#13383)
Installing a `file:` directory dependency with the global virtual store
enabled crashed the headless install:

    TypeError: Cannot read properties of undefined (reading 'split')
        at assertNoPathTraversal
        at formatGlobalVirtualStorePath
        at calcGraphNodeHash

pnpm omits `version` from a directory snapshot, so `nameVerFromPkgSnapshot`
hands the hasher `undefined` and the traversal guard added in
pnpm/pnpm#12872 dereferences it. Directory snapshots now use a fixed
`directory` segment in place of the version. That also settles a
disagreement between the two install paths: the resolver reads the version
off the manifest and the headless install off the lockfile, where there is
none, so the same package landed on two different slots.

The crash hid a worse defect. A directory resolution is the one resolution
with no integrity — it is a path relative to the lockfile — so `file:dep`
hashed identically in every project that depended on a directory of that
name, and all of them linked to whichever project installed first. Since
the source directory is mutable, pnpm re-imports it on every install, so
the projects went on overwriting each other's copy. The lockfile directory
now joins the hash payload for directory resolutions, giving each project
its own slot; every other resolution hashes exactly as before.

`iterateHashedGraphNodes` takes an options object now — it was up to five
positional parameters before this change added a sixth.

Closes pnpm/pnpm#13335
2026-07-26 09:25:03 +02:00
Abdullah Alaqeel d47ab916ad fix(self-update): stop the project config from steering the pnpm download (#12813)
Fixes pnpm/pnpm#12803.

`pnpm self-update` resolved the pnpm download through the project's own
registry/auth config, loaded the repo's default `.pnpmfile.(c|m)js`, and took
its `minimumReleaseAge` policy from the active workspace — so the outcome of a
global tooling operation depended on the directory it ran in, and on config a
checkout controls.

Route the fetch through the trusted package-manager bootstrap config — the
channel `switchCliVersion` already uses, which excludes the project `.npmrc`
and workspace manifest — and stop auto-loading the repo pnpmfile, whose
`updateConfig` hook and custom resolvers/fetchers reach the same requests.
`getPackageManagerBootstrapConfig` moves into `@pnpm/config.reader` so the
command package can reuse it.

Stop reading the project's `minimumReleaseAge` and `trustPolicy` settings, and
its `ci` flag, for self-update. Each is dangerous in both directions for a
command that replaces the global binary: a cooldown lowered waives the
protection the user configured, raised it pins the machine to the installed
pnpm, including past a release that fixes a vulnerability in it; a trust policy
turned off accepts a release whose evidence the user meant to reject, turned on
blocks the update the same way; and `ci` decides whether an immature pick may be
confirmed at the keyboard at all. Unlike a blocked dependency upgrade, those
decisions follow the user into every other project. The policies come from the
built-in defaults, the global config, the environment, and CLI flags instead.
Other commands keep reading them from `pnpm-workspace.yaml`, and there is no new
default.

When an immature version is refused, an interactive run offers to update
anyway, matching how a strict install prompts; non-interactive runs still fail
closed.

Mirrored in pacquet: `Config::current_for_self_update` skips the same settings,
and `self-update` gained the matching prompt and error.
2026-07-25 01:34:27 +02:00
Scarab Systems 4737386533 fix(setup): persist GitHub Actions env files (#12841)
Write PNPM_HOME to the GitHub Actions environment file.

Add the setup bin directory to the workflow path file.

Later workflow steps can use global pnpm commands.

Keep TypeScript setup and pacquet setup behavior aligned.

Refs pnpm/pnpm#9191.
2026-07-24 02:42:40 +02:00
Zoltan Kochanandgithub-actions[bot] 454e7d62b3 chore(release): 11.17.0, pacquet 12.0.0-alpha.19, pnpr 0.1.0-alpha.5 (#13237)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-23 17:07:05 +02:00
Zoltan Kochanandgithub-actions[bot] d1edab423e chore(release): 11.16.0, pacquet 12.0.0-alpha.18 (#13216)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-22 21:58:38 +02:00
Zoltan Kochanandgithub-actions[bot] 331c26aa4b chore(release): 11.15.1, pacquet 12.0.0-alpha.16 (#13162)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-20 00:19:20 +02:00
Abdullah Alaqeel a3478cfbc8 fix(setup): remove leftover v10-layout shims so self-update stops re-warning (#13140)
pnpm setup migrated PATH to $PNPM_HOME/bin but left the v10 shims
(pnpm/pn/pnpx/pnx and .cmd/.ps1 siblings) at the top of PNPM_HOME.
self-update's v10-layout detector keyed off file existence alone, so
the warning fired on every self-update — even on dangling shims whose
install target had been garbage-collected.

Cleanup: setup unlinks the v10 shim names after PATH migration succeeds
(TS CLI + pacquet). Hardening: hasLegacyHomeDirShim skips a shim whose
cmd-shim-target marker points at a missing path (TS only; pacquet has
no v10-detection branch yet — note in changeset).

Closes pnpm/pnpm#12496.
2026-07-19 14:45:58 +02:00
Zoltan Kochanandgithub-actions[bot] 32a30c4d70 chore(release): 11.15.0, pacquet 12.0.0-alpha.15, pnpr 0.1.0-alpha.4 (#13126)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-18 14:19:57 +02:00
Zoltan Kochan f4948525df chore: remove repository changelogs (#13119)
Remove the legacy repository changelog files now that release changelog storage defaults to the registry. The publish path composes and injects CHANGELOG.md into release tarballs, so keeping historical copies in source control duplicates generated release data.

Update adm-zip to the patched 0.6 release and override vulnerable transitive versions after the dependency audit began rejecting versions below 0.6.0.
2026-07-18 13:10:25 +02:00
Zoltan Kochanandgithub-actions[bot] f8b08ea63f chore(release): 11.14.0, pacquet 12.0.0-alpha.14 (#13113)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-18 00:01:20 +02:00
Zoltan Kochan f15d67c418 fix(self-update): discard an installed pnpm that cannot run (#13076)
A self-update that installs a working pnpm and one that installs a broken
one were indistinguishable. Installing `@pnpm/exe@11.13.0` printed
"Successfully updated pnpm to v11.13.0", exited 0, and left the user with
no working pnpm at all: its platform package shipped without a native, so
setup.js kept the tarball's placeholder bin, and nothing between the
download and the relink of PNPM_HOME ever executed the result.

Run the installed CLI before the caller makes it active. The install
already happens in a throwaway directory and the relink is downstream, so
failing here keeps the version that was working and removes the directory
rather than needing to restore anything. This is what deno's check_exe and
rustup's self-update do — they run the downloaded binary and refuse to
replace the current one if it does not start.

Only that it runs is asserted, not what it prints: matching --version
output exactly would fail on anything else a release writes to stdout, and
the test fixtures show why — one tarball stands in for several mocked
versions.

pacquet gets the same check for native engines, which is the whole set that
can install yet fail to execute: the legacy JS engine has no binary of its
own to be missing, and running it would mean locating a Node.js first.

A release that installs but cannot run still reaches users; this only stops
it from taking their working pnpm with it. Rejecting it before publication
is pnpm/pnpm#13072.
2026-07-16 14:38:22 +02:00
Zoltan Kochanandgithub-actions[bot] ca66b76fb2 chore(release): 11.13.1 (#13058)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-16 00:36:19 +02:00
Yashas Gunderia 3c80449626 fix(self-update): link platform binaries from GVS dependency slots (#13037)
Self-update installs wrappers with lifecycle scripts disabled and then links the
native platform binary manually. With the global virtual store enabled, the
wrapper and its platform dependency can resolve to different hashed slots, so
searching only beside the wrapper's real path misses the binary and leaves the
published placeholder in place.

Prefer the wrapper's exact real-adjacent dependency, then fall back to its
install-root link. This supports sibling GVS slots without a parent-directory
module-resolution search and preserves flat and legacy virtual-store layouts.

Closes pnpm/pnpm#12962 and pnpm/pnpm#13036.
2026-07-15 21:27:49 +02:00
Zoltan Kochanandgithub-actions[bot] 682f57e773 chore(release): 11.13.0, pacquet 12.0.0-alpha.9, pnpr 0.1.0-alpha.1 (#12986)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-13 22:19:04 +02:00
Bingjia YangandZoltan Kochan dc89bdbf1c fix: fetch full registry metadata for global installs under trustPolicy (#12954)
With trustPolicy: no-downgrade (or resolutionMode: time-based) in the
global config, pnpm add -g, pnpm update -g, pnpm setup, and
pnpm self-update failed on every resolution with ERR_PNPM_MISSING_TIME.

The global-install code paths computed the fetchFullMetadata option from
supportedArchitectures.libc alone and produced false when libc was not
configured. createNewStoreController consumes that option with the
nullish coalescing operator, so the explicit false suppressed the
fallback that requests full metadata for time-based resolution and trust
checks, and the trust check then failed on abbreviated metadata, which
has no time field. The local install commands passed undefined for the
same case, which is why the same setting worked in pnpm-workspace.yaml.

The decision now lives in shouldFetchFullMetadata in
@pnpm/store.connection-manager, next to the store-controller creation
that consumes it, and the command-level computations are gone. While
unifying the sites, the no-downgrade branch was aligned with the
self-updater and with pacquet's
Config::requires_full_metadata_for_resolution: it now requests full
metadata regardless of registrySupportsTimeField, because the trust
checks read trust evidence (_npmUser) that abbreviated metadata never
carries.

Closes pnpm/pnpm#12883

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-07-13 16:29:30 +02:00
Zoltan Kochanandgithub-actions[bot] 98722fab10 chore(release): 11.12.0 (#12937)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-11 11:34:29 +02:00
Zoltan Kochan 8e17c3d366 refactor: rename the pacquet/ directory to pnpm/ (#12913)
Pure directory move plus path fixups: the Rust port ships as pnpm v12,
so the source tree now lives at pnpm/ (alongside pnpm11/, the frozen
TypeScript line). No identifiers change in this pass — crate names
(pacquet-*), the pacquet bin, PACQUET_VERSION, the @pacquet/* npm
package names v11's runPacquet spawns, the .pacquet virtual-store dir,
the benchmark harness's clone dir, and the pacquet-*.yml workflow
filenames (npm trusted publishing is bound to them) all stay for a
follow-up.

Also removes the root /pnpm/ .gitignore entry (build detritus in the
pre-pnpm11 package location): pnpm/ is real source now and must not be
ignored. Developers with a stale generated pnpm/ dir should delete it
before checking out this change.
2026-07-10 18:06:56 +02:00
Yashas Gunderia a38adda211 fix: install active pnpm version when self-updating (#12879)
Fixes pnpm/pnpm#12877.

The self-update command previously skipped work whenever the resolved target
version matched the currently running pnpm version. That breaks recovery from a
removed global install when a local project pnpm is used to reinstall the same
version globally.

Check the global self-update directory before taking the active-version no-op
path, and add coverage for both cases: already installed globally and missing
globally. Apply the same fix to pacquet's self-update, and deduplicate the
TypeScript global-install check into a shared findGlobalPnpmInstallDir helper.
2026-07-10 17:17:20 +02:00
Zoltan Kochanandgithub-actions[bot] 8e1e4c0aae chore(release): 11.11.0 (#12886)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-09 22:29:10 +02:00
Zoltan Kochan dcfc611d3b fix: honor trustPolicy in pnpm self-update, and recover from unusable cached engine slots (#12838)
Two pnpm self-update fixes:

1. Honor trustPolicy=no-downgrade when resolving the pnpm engine. Both
   stacks now resolve the engine version like a regular install — deriving
   the metadata mode from config (full metadata under time-based /
   no-downgrade) and passing the trust policy into the resolve — instead of
   a self-update-specific abbreviated-metadata path. Fixes pacquet's "trust
   check failed: missing time" crash and the TypeScript CLI silently not
   enforcing the policy. pacquet centralizes the metadata decision in
   Config::requires_full_metadata_for_resolution, shared with PickPolicy.

2. (pacquet) Reinstall pnpm when a cached engine slot's wrapper resolves
   outside its slot (older global-virtual-store layout). The cache-hit
   native-binary relink is now best-effort: on the containment guard's
   refusal, fall through to a fresh signature-verified install rather than
   aborting self-update. The guard itself is unchanged.
2026-07-07 19:28:06 +02:00