Commit Graph
41 Commits
Author SHA1 Message Date
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 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>
2026-10-21 12:15:12 +02:00
Zoltan Kochan 473cccecef fix(prune): remove extraneous direct deps and honor workspace selection (#17154)
Fix three `pnpm prune` issues across pnpm v11 and pnpm v12:

1. Extraneous direct dependencies in `node_modules`:
   - In pnpm v11, `prune.handler` did not disable `optimisticRepeatInstall`, and `installDeps` could short-circuit via `canReportAlreadyUpToDate` before `headlessInstall` ran `prune()`. In addition, `pruneRemovedDependencies` skipped `prune()` when `currentLockfile` was absent (for example, when a project had zero dependencies). Disable `optimisticRepeatInstall` in `prune.handler`, prevent `canReportAlreadyUpToDate` when `pruneDirectDependencies` or `pruneStore` is set, and pass an empty `currentLockfile` to `prune()` when `pruneDirectDependencies` is enabled and no current lockfile exists.
   - In pnpm v12 (`pacquet`), `prune_stale_modules` only unlinked direct dependencies that were present in `current_lockfile` and removed from `wanted_lockfile`, never scanning `node_modules` on disk for extraneous entries when `prune` ran. Add `PruneTreeOptions::direct_dependencies` and `PruneExtraneousDirectDeps` to scan each selected importer's `node_modules` directory during `pnpm prune`, remove entries (and their bin shims and emptied scope directories) that are not wanted direct dependencies, excluded manifest links, or root public hoists, and force `commit_modules_state`'s virtual-store sweep during `prune`.

2. `pnpm --filter <pkg> prune` and `pnpm -r prune` in pnpm v11:
   - Separate `cliOptionsTypes` from `rcOptionsTypes` in `@pnpm/installing.commands` `prune` and add `'recursive': Boolean` so the CLI parser accepts `-r` / `--recursive` and `--filter`.
   - Do not pass `include` to `validateModules` when `pruneDirectDependencies` is set so `prune --prod` does not purge every workspace project's `node_modules` directory.
   - Do not auto-include the workspace root in `shouldInstallWorkspaceRoot` during filtered `prune`.
   - Scope `pruneDirectDependencies` to the selected `ctx.projects` and keep all dependency groups in `findOrphanDepPaths` / `getPkgsDepPathsOwnedOnlyByImporters` during a partial workspace `prune` so unselected workspace importers do not lose their devDependencies from the shared virtual store.

3. Workspace selection and `sharedWorkspaceLockfile: false` in pnpm v12:
   - Add `CliCommand::Prune(_)` to `CliCommand::recursive_by_default`.
   - Route `PrunePipeline` through `select_install_family_plan` (`PerProject`, `Shared`, `Single`) and pass `WorkspaceInstallSelection` (with `install_dirs` restricted to `selected_dirs`) into `Install::run_prune`.

Closes https://github.com/pnpm/tasks/issues/171
2026-10-20 17:07:56 +02:00
fe4a0c2323 chore(release): 11.28.6, pacquet 12.14.0 (#16952)
* chore(release): 11.28.6, pacquet 12.14.0

* docs(release): curate 11.28.6 and pacquet 12.14.0 release notes

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

- v11: lead with the NPM_ID_TOKEN audience check and the invalid file
  name warning as security fixes; say the `pnpm setup` message was
  printed, since v11 reports it as a warning.
- v12: merge the stdout and stderr broken-pipe entries and the two
  optional-peer entries; say the Windows Rust toolchain change undoes
  the MinGW fallback a MinGW-built pnpm used since 12.13.0; correct the
  `browser` packing claim; drop the reference to another release line.
- Use one issue-link label style on both pages.

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

* docs(release): smooth two sentences in the 11.28.6 and 12.14.0 notes

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

* docs(release): say sideEffectsCache.exclude covers the local and remote cache

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

* docs: give sideEffectsCache.exclude an example a cache key cannot cover

An environment variable such as JAVA_HOME could be added to the cache
key, so it does not justify excluding a package. Name the system
libraries a native addon links against instead.

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

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 14:35:42 +02:00
68112bada4 chore(release): 11.28.5, pacquet 12.10.0 (#16637)
* chore(release): 11.28.5, pacquet 12.10.0

* docs(release): curate the 11.28.5 and 12.10.0 release notes

Add a lead paragraph to each page and group the patch entries under
subject headings, with the security fixes first.

- Merge the unpublish path-boundary fix into the unpublish entry, the
  legacy metadata cache prune into the metadata cache entry, the two
  binary size entries, and the two 64 MiB archive limits on the v11 page.
- Drop the manifest-utils saveType entry from the CLI page: the CLI
  only passes saveType values derived from its own flags, so no CLI user
  can reach the fixed path.
- Rewrite mechanism-only entries (PAX header validation, bundled
  dependency symlinks, custom resolutions under name@version keys, proxy
  setting validation) in terms of what the user sees.
- Use one link style for pnpm/pnpm issues.

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

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 02:00:03 +02:00
5dafb09800 chore(release): 11.28.4, pacquet 12.9.1 (#16567)
* chore(release): 11.28.4, pacquet 12.9.1

* docs(release): curate 11.28.4 and pacquet 12.9.1 release notes

Add a lead paragraph to both pages. Move the two credential-leak fixes
in 11.28.4 to the top and group the remaining 27 entries by what the
reader was doing. Merge entries that described one change: the two
INVALID_SETTING validations, the two metadata revalidation fixes, the
two hoisted filtered-install fixes, and, in pacquet 12.9.1, the two
[<since>] selector fixes. Order pacquet 12.9.1 so the `@pnpm/wasm` move,
which WebContainer users must act on, comes first.

Normalize issue links to the [#N] form and link the ETag fix to
pnpm/pnpm#16506.

Every fix was checked against the code released in 11.28.3 and
pacquet 12.9.0; none fixes a defect that never shipped.

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

* docs(release): describe the frozen-lockfile fixes accurately in the 11.28.4 lead

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

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 22:13:02 +02:00
Zoltan KochanandClaude Opus 5.5 b67ff1958a fix(install): accept a lockfile that records only the pinned pnpm (#16479)
A frozen install failed in a project with no dependencies when
pnpm-lock.yaml held only the env document, which every command writes
before the first install. v12 reported a missing lockfile, v11 a broken
one. When the separator after the env document was also gone, both read
the file as having no env document and rejected the pinned pnpm version
as outdated.

- Both stacks now read a file that opens with `---` and has no separator
  as an env-only lockfile when it carries `configDependencies`, so its pin is found and a main-lockfile write
  keeps it.
- v11 no longer calls a lockfile with an env document but no main
  document broken.
- The env-only fallback requires the root importer's `configDependencies`
  key, so a main lockfile that merely opens with `---` is not read as an
  env document.
- v12 lets a frozen install without a lockfile proceed when no project
  has dependencies, as v11 does.

Closes pnpm/pnpm#16477

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 15:38:03 +02:00
d5c11144a6 fix(config): isolate project state with a custom global store (#16327)
With enableGlobalVirtualStore and an explicit virtualStoreDir, the
project's internal directory, which holds its current lockfile and
hidden hoisted dependencies, was the shared store root, so every
project using that root read the others' packages as its own.

Under a global virtual store, the install state directory is now
always <modules_dir>/.pnpm, pnpm 11's internalPnpmDir. The resolved
virtualStoreDir setting is kept in configured_virtual_store_dir, which
apply_global_virtual_store_derivation reads: the derivation runs again
after --store-dir, updateConfig hooks and store placement, when
install_state_dir is already project-local. Without a global virtual
store the derivation restores install_state_dir from the setting, so a
hook that turns the store off still gets the configured directory.

--virtual-store-dir records the setting the same way, so it names the
store root under a global virtual store, as in pnpm 11. The Node API
overlay re-derives after setting globalVirtualStoreDir, keeping the
internal directory project-local there too.

Config names the two directories by meaning: the field that held the
state directory is now install_state_dir, and virtual_store_dir(),
previously effective_virtual_store_dir(), is where packages are
linked. The Node API keeps its virtualStoreDir and
effectiveVirtualStoreDir fields.
The TypeScript CLI's internalPnpmDir is renamed to installStateDir to
match.

The v12 settings reference documents virtualStoreDir under a global
virtual store.

pnpm clean still removes a shared store that virtualStoreDir or
globalVirtualStoreDir places inside the project, choosing the
directory install populates. It never removes the project root, and it
resolves symlinked parents so the removal cannot leave the project.

Closes pnpm/tasks#47.

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 06:46:51 +02:00
Zoltan Kochan 9179ae3339 refactor(ts): meet size and shape limits in 15 packages (#16456)
* refactor(ts): meet size and shape limits in 5 packages

Refactor 5 TypeScript packages to satisfy strict size and shape lint limits
(function length, nesting, cognitive complexity, local declarations):
- `@pnpm/installing.linking.modules-cleaner`
- `@pnpm/installing.linking.real-hoist`
- `@pnpm/lockfile.utils`
- `@pnpm/modules-mounter.daemon`
- `@pnpm/releasing.exportable-manifest`

Remove them from PENDING_SIZE_AND_SHAPE_REFACTOR in eslint.config.mjs.

* refactor(ts): meet size and shape limits in 10 packages

Refactor 10 TypeScript packages to satisfy strict size and shape lint limits
(function length, nesting, cognitive complexity, local declarations):
- `@pnpm/bins.cmd-shim`
- `@pnpm/engine.runtime.commands`
- `@pnpm/exec.lifecycle`
- `@pnpm/fetching.git-fetcher`
- `@pnpm/fetching.tarball-fetcher`
- `@pnpm/fs.indexed-pkg-importer`
- `@pnpm/hooks.pnpmfile`
- `@pnpm/installing.context`
- `@pnpm/resolving.git-resolver`
- `@pnpm/store.commands`

Graduate them from PENDING_SIZE_AND_SHAPE_REFACTOR in eslint.config.mjs.

* fix(ts): address review feedback on shims and lockfile comments

* test(cmd-shim): add regression test for isShimBasedirAnchorCurrent with absolute targets
2026-10-01 04:53:20 +02:00
de9611cf28 chore(release): 11.28.3 (#16444)
* chore(release): 11.28.3

* docs(changelog): curate the 11.28.3 release notes

Merge the eleven entries about names such as `constructor` into one,
group the patch changes by what the reader was doing, and add a lead
paragraph.

Drop the zlib-rs entry: that change only touched Cargo.toml, and the
TypeScript CLI does not use the Rust engine. It belongs to the next
pacquet release.

Rewrite the store index entry for CLI users. `ReadOnlyStoreIndex` and
`ImmutableStoreIndex` are library API documented in the
`@pnpm/store.index` changelog.

Say what the store prune mark-phase fix changes for the user.

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

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 16:56:44 +02:00
9ce8c2e734 chore(release): 11.28.1, pacquet 12.8.0, pnpr 0.1.0-alpha.14 (#16312)
* chore(release): 11.28.1, pacquet 12.8.0, pnpr 0.1.0-alpha.14

* docs(release): park Rust changelogs and curate release notes

The release bump ran with pnpm 12.7.0, which since pnpm/pnpm#14934
writes the changelog of every private project to a committed
CHANGELOG.md. pacquet, `@pnpm/napi` and `@pnpm/pnpr` are private
in-repo but published, and release.yml and the publish-time changelog
composer both read their parked section. Move the three sections back
to .changeset/changelogs so the GitHub release bodies and the npm
tarball changelogs are not empty. The tooling follow-up is recorded
on pnpm/tasks#38.

Curate the pnpm 11.28.1, pnpm 12.8.0 and pnpr 0.1.0-alpha.14 pages:
add lead paragraphs, group patch entries by subject, merge entries
describing one change, drop internals no CLI user sees, and fix
"instead of" tails, implementation names and link placement. This
addresses both Qodo findings on the catalog entry.

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

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 08:57:31 +02:00
c4eeecf36c fix(update): skip compatibility database for project manifests (#13670)
Keep the built-in package compatibility database scoped to dependency manifests. A project manifest should not inherit registry compatibility entries simply because its name and version match a published package, while user-configured packageExtensions should continue to apply.

Apply the same boundary in the TypeScript and Rust implementations. In Rust this covers both the fresh-resolution manifest transforms and the install freshness gates that extend project manifests. Cover it through the read-package hook, update flow, and Rust manifest-transform tests.

Fixes pnpm/pnpm#11700.

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 14:52:59 +02:00
89547446ed fix(lockfile): detect stale patch hashes in pnpm-lock.yaml (#15336)
A patched dependency's hash appears in a lockfile twice: in
`patchedDependencies`, and as a `(patch_hash=...)` segment on every dependency
path referring to that package. The freshness gate compared only the first
against the configured patches, which stands in for the second in every
lockfile pnpm writes but not in one a hand edit or mis-resolved merge conflict
left disagreeing with itself. That lockfile read as up to date: resolution was
skipped, the stale paths were never rewritten, the install reused
already-linked content rather than the current patch, and `--frozen-lockfile`
accepted it.

Add `checkPatchedDepPaths` (`check_patched_dep_paths` in pacquet), which
compares every dependency path naming a patched package, and every path
carrying a segment, in the importer entries, snapshot keys and dependency
edges against the patch `patchedDependencies` gives that package. A path
missing the segment its patch calls for disagrees as much as one carrying the
wrong hash:

- a normal install forces a full resolution;
- `--frozen-lockfile` raises ERR_PNPM_INCONSISTENT_PATCH_HASH, or
  ERR_PNPM_UNCHECKABLE_PATCH_HASH when it cannot tell;
- `verifyDepsBeforeRun` reports it where it compares the lockfile settings.

It returns three values, not a predicate: judging a path needs the package's
version and the patch set, and this lockfile state can be missing either.
Calling that stale would send the user after the wrong defect and calling it up
to date would hide a real one, so `indeterminate` forces the resolver without
naming a cause. A `patchedDependencies` key that does not resolve leaves only
its own package undecided, and any definite disagreement outranks it. Paths
for unpatched packages are skipped before they are built, and repeated paths
reuse their verdict, so large lockfiles pay little for the walk.

Derive the package identity as every other patch consumer does, so this check
and the install cannot disagree about which patch applies: a git, tarball,
`file:` or registry-qualified path holds its resolution where the version goes,
while patches match the version `packages:` records. In pacquet,
`name_version_from_package_key` moves from `pnpm-deps-restorer` to
`pnpm-lockfile` so the frozen installer, the fast-update path and this check
share one definition.

That move uncovered a pre-existing divergence, fixed here: pacquet rendered a
registry-qualified key's whole `registry:version` slot where pnpm's `parse`
reports the bare semver, so such a patch matched in one stack and missed in the
other. The frozen installer's lookup was affected too.

So a more actionable cause wins, the check runs last in
`check_lockfile_settings`. A non-frozen install skips it for a lockfile with
merge conflicts, which is re-resolved anyway; a frozen install checks an
autofixed lockfile too, since it installs it as is. The fast-update path no
longer treats an agreeing `patchedDependencies` map as sufficient on its own.

A suffix is read from the end, as pnpm's `parse` reads it: the trailing run
of balanced, back-to-back segments, with the package's own hash first. A
marker anywhere else, or in a suffix whose parentheses do not balance, makes
the path uncheckable. Unless peers are deduped, a peer segment is the peer's
whole dependency path, so each peer segment carrying a marker is judged as its
own path, and so is an unmarked one naming a registry version of a patched
package behind a registry parent. Peers are walked level by level, so nesting
depth never reaches the call stack.

`groupPatchedDependencies` now builds its record without a prototype, so a
`patchedDependencies` key named after an `Object.prototype` member gets its
own group.

The importer fast path in pnpm declines a locked version the range admits
when its key is patched: the bare version it would write points at no
snapshot. pacquet already declined it. pacquet's patch fast-update path now
checks the paths before absorbing drift in `patchedDependencies`.

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-09-25 18:18:07 +02:00
b341f5ada2 chore(release): 11.28.0, pacquet 12.7.0, pnpr 0.1.0-alpha.13 (#15647)
* chore(release): 11.28.0, pacquet 12.7.0, pnpr 0.1.0-alpha.13

* docs(release): curate the 11.28.0 and 12.7.0 release pages

Group both pages under subject headings with a lead paragraph that
names the headline changes and the security fixes, and move the
security entries to the top of the patch section.

Merge entries that describe one change: the node shim version files,
ranged selectors without a package.json, SemVer build metadata,
completion injection, custom modulesDir command lookup,
list --only-projects, store status, npm_execpath, catalog and npm-alias
workspace edges, the macOS CA fallback, and the prepare/devPreinstall
skips.

Drop entries that describe no change a published version could show:
the test-only --no-optional entry on both pages, the exported
CreateNewStoreControllerOptions type, the publish availability-probe
fix for a flag first shipping in 12.7.0, and the executable-bit check
the Rust CLI never had wrong.

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

* docs(release): describe the bin shim part of the modulesDir entry accurately

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

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 12:01:04 +02:00
Zoltan Kochanandgithub-actions[bot] 201f9fc51f chore(release): 11.27.1 (#15185)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-09-20 23:11:14 +02:00
Zoltan Kochanandgithub-actions[bot] b62f5b004f chore(release): 11.27.0 (#14856)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-09-12 12:01:04 +02:00
Zoltan Kochan f00e7680aa chore(release): 11.26.0 (#14638) 2026-09-07 01:30:44 +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
Benjamin Staneck f73a1a66c6 fix: protect symlinked modules during install (#14298)
Resolve virtual store paths against the canonical modules directory so installs through a
symlink do not rewrite another checkout.

Keep partial workspace lockfile-only operations in the resolution path and stop before
materializing dependencies.

Fixes pnpm/pnpm#14286
2026-08-29 01:22:38 +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
Lazizbek ErgashevandZoltan Kochan ccff620f66 fix(lockfile): drop removed dependencies when merging branch lockfiles (#13967)
Merging git branch lockfiles unions their keys, so a dependency that the main branch removed after a branch lockfile was written comes back in the merged result. Resolution normally prunes it again, but --frozen-lockfile skips resolution and fails the manifest check with ERR_PNPM_OUTDATED_LOCKFILE.

Once the branch lockfiles are merged, prune each importer back to what its manifest declares. The declared names are derived per dependency group the way satisfiesPackageManifest derives them, so a dependency that moved between groups is reconciled rather than left in both. Only a peer that no other field declares is auto-installed, so a dependency listed in both devDependencies and peerDependencies keeps the field that declares it rather than being dropped from it. Pruning importer edges can strand packages, so the merged lockfile then goes through pruneSharedLockfile.

The Rust CLI merges branch lockfiles the same way and had the same bug. It gets the same reconciliation, hooked into the install pipeline because the lazily loaded wanted lockfile has no access to the project manifests at load time.

Fixes pnpm/pnpm#13966.
---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-08-24 15:49:36 +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
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 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 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 2d30dd6db7 feat(lockfile): key named-registry packages by their registry (#13528)
Packages resolved from a named registry are now keyed
`<name>@<registryName>:<version>` (e.g. `foo@work:1.0.0`) instead of
`<name>@<version>`. Without the qualifier the same name+version served by two
different registries collapsed onto one `packages:` entry, so whichever
resolved first won and the other consumer silently got the wrong tarball.
The lockfile also carried no registry marker at all: entry lookup keys on
`refToRelative(ref, name)`, so a `work:foo` spec could be satisfied by an
entry that was really resolved from npmjs, and vice versa.

The alias goes in the version slot because the dep-path grammar already
admits colon-bearing non-semver values there (`foo@file:...`,
`node@runtime:...`), so every suffix mechanism composes unchanged:
`foo@work:1.0.0(react@18.0.0)` parses with the existing balanced-paren scan,
`depPathToFilename` escapes the colon to `+`, and `refToRelative` already
reconstructs `foo@work:1.0.0` from a `work:1.0.0` snapshot ref with no
change. The alias is recorded rather than the registry URL so keys stay
readable and independent of registry configuration; the concrete URL is
still pinned by the resolution when it is not derivable.

Tarball URLs now apply the pre-existing "omit whatever is reconstructible"
rule against the named registry instead of the scope-routed default, so a
canonical named-registry tarball serializes as bare `{integrity}` and is
recomputed from the alias on read. Registries whose URLs cannot be rebuilt
from name, version, and base URL (GitHub Packages hashed `/download/` URLs)
keep the explicit field, exactly as before.

The lockfile format version is unchanged. Registry-qualified keys are
additive: they appear only for packages resolved through `namedRegistries`,
the tarball-omission rule only fires on those entries, and the alias
validation is config-time, so a project that configures no named registry
produces a byte-identical lockfile. A format bump would not have been
adoptable incrementally — readers gate on the major version alone and reject
an unknown one outright rather than degrading, including pnpm engines
embedded in other tools, which cannot be patched after the fact.

Named-registry aliases that would shadow a reserved specifier prefix
(file, link, workspace, runtime, npm, jsr, and so on) are now rejected at
resolver construction with ERR_PNPM_RESERVED_NAMED_REGISTRY_NAME. They were
previously accepted and silently shadowed by that prefix's own resolver in
the chain, which was harmless while the alias only appeared in specifiers;
in the version slot it would make the dep path genuinely ambiguous.
2026-08-02 16:52:28 +02:00
Zoltan Kochan 536b7a2c8a chore(release): 11.19.0 (#13524) 2026-07-31 10:46:26 +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 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 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 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
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
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 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 Kochanandgithub-actions[bot] 7cd1e4f4f6 chore(release): 11.10.0 (#12799)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-04 21:05:03 +02:00
Zoltan Kochanandgithub-actions[bot] 9671d9aeed chore(release): 11.9.0 (#12611)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-23 17:16:24 +02:00
Ajeet D'SouzaandZoltan Kochan bae694f639 fix(lockfile): compute tarball integrity upon download (#12491)
Some registries generate tarballs on demand and cannot list an integrity in
their packument. pnpm then wrote integrity-less lockfile entries on the first
install and failed the next one with ERR_PNPM_MISSING_TARBALL_INTEGRITY, unable
to install from its own lockfile.

Compute the missing integrity from the downloaded bytes and write it into the
resolution before the lockfile is built:

- Add an optional `resolutionNeedsFetch` contract to the fetcher API (backward
  compatible, since custom fetchers come from hooks). The remote-tarball fetcher
  reports it when a resolution lacks integrity; the picked fetcher's signal flows
  through PackageResponse -> ResolvedPackage so nothing re-derives it.
- The package requester downloads such tarballs (including under --lockfile-only /
  skipFetch / not-installable) and fills the computed integrity onto the resolution
  via the already-running `fetching` promise, so dependency resolution isn't
  blocked. The deps-resolver awaits only the flagged entries before updateLockfile,
  because the integrity feeds the global virtual-store paths.
- Move read-side enforcement into the npm resolver's lockfile verifier
  (MISSING_TARBALL_INTEGRITY): reject a registry/http(s) tarball entry whose
  integrity is missing/empty/non-string, fail-closed, before the URL-keyed and
  semver short-circuits. Drop the earlier read-side auto-heal (a missing-field
  bypass). Harden against tampered lockfiles (non-string tarball/integrity).
- Reuse the fetcher picked during resolution on the fetch path instead of running
  pickFetcher (and a custom fetcher's async canFetch) twice per package.

Mirrored in pacquet: PrefetchingResolver computes the integrity for integrity-less
tarball resolutions during resolution (FetchTarballForResolution::run), deduped per
URL with a singleflight cache.

Closes pnpm/pnpm#12145.

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-06-22 12:02:29 +02:00
Zoltan Kochan fc2f33912e refactor: move the TypeScript pnpm CLI into a pnpm11/ directory (#12537)
The TypeScript pnpm CLI freezes at v11; pnpm 12 will be the Rust pacquet
port. To make that split legible, all TypeScript source, test, and build
directories move under a new top-level pnpm11/ directory. The name states
the version boundary rather than implying a behavioral fork, since the two
stacks are meant to behave identically.

Scope is source-only: the shared workspace root stays at the repo root.
pnpm-workspace.yaml, package.json, pnpm-lock.yaml, .pnpmfile.cjs,
.meta-updater, __patches__, .changeset, .husky, and the lint/spell configs
remain in place, so one pnpm workspace and one Cargo workspace still span
all three products. pnpr/client and pacquet/tasks/registry-mock stay as
cross-product workspace members.

Rewiring the move required:
- pnpm-workspace.yaml globs prefixed with pnpm11/
- root package.json script paths, eslint.config.mjs, tsconfig.lint.json,
  .gitignore, and CODEOWNERS updated
- .meta-updater/src/index.ts literals repointed (pnpm11/pnpm/package.json,
  pnpm11/__utils__, pnpm11/__typings__, and the main package directory)
- regenerated every moved package's repository/homepage URL via meta-updater
- pnpm11/pnpm/bundle-deps.ts and __utils__/scripts/src/typecheck-only.ts
  climb one more level to reach the repo root

.meta-updater stays at the repo root because @pnpm/meta-updater resolves
its config at <cwd>/.meta-updater/main.mjs.

TS CI (.github/workflows/ci.yml) now only runs when pnpm11/-relevant paths
change, via a dorny/paths-filter changes job plus a TS CI / Success
aggregate gate; branch protection should require only that gate.
2026-06-20 14:36:25 +02:00