12255 Commits

Author SHA1 Message Date
Zoltan Kochan
30a59d0209 chore: update lockfile, Node.js, and pnpm versions (#13003)
* chore: update lockfile, Node.js, and pnpm versions

* chore: pin packageManagerDependencies specifier to the resolved pnpm version

The update-lockfile workflow recorded the next-12 dist-tag as the
packageManagerDependencies specifier, but the manifest pins pnpm to
12.0.0-alpha.11. pacquet validates the specifier against the manifest, so a
later --frozen-lockfile install failed with
ERR_PNPM_FROZEN_LOCKFILE_WITH_OUTDATED_LOCKFILE. Record the resolved pin so the
specifier matches the wanted version.

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-14 22:04:18 +02:00
Zoltan Kochan
338dda6f30 ci: run Rust CI on root package metadata changes (#13002) 2026-07-14 21:19:01 +02:00
Zoltan Kochan
e36df0a7a3 chore(release): pacquet 12.0.0-alpha.11 (#13001)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-14 20:51:26 +02:00
Zoltan Kochan
a9b75f4d51 ci: disable git hooks in the release-pr and update-lockfile workflows (#12938)
* ci: disable git hooks in the create-release-pr workflow

The pre-push hook ran the full TS compile/lint and the Rust clippy/doc
sweep inside the workflow's "Commit and push" step, because the shallow
clone and URL-based push remote make the hook's change detection always
conclude that Rust sources changed. Setting HUSKY=0 on the job keeps
husky from wiring any hooks on the runner.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* ci: disable git hooks in the update-lockfile workflow too

Same failure mode as create-release-pr: the full install wires the husky
hooks and the push step runs the pre-push TS compile/lint on every daily
run. It only skips the Rust sweep when the remote chore/update-lockfile
branch already exists, because the shallow clone lacks the remote tip and
the hook's git log fails silently; on a fresh branch it does the full
cargo clippy/doc pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 20:43:38 +02:00
Zoltan Kochan
e0c790c5e9 fix(self-update): store the resolved pnpm pin as the packageManagerDependencies specifier (#12992)
`pnpm self-update <dist-tag>` resolved the tag to an exact version and wrote it
into devEngines.packageManager.version, but recorded the raw dist-tag as the
packageManagerDependencies specifier in pnpm-lock.yaml. pacquet validates that
specifier against the manifest under --frozen-lockfile, so the mismatch made a
later frozen install fail with FrozenLockfileOutdated (the cause of the invalid
lockfile in pnpm/pnpm#12989, hot-fixed in pnpm/pnpm#12990).

Both self-update call sites now record the resolved pin: the project-pin path
stores the updated devEngines constraint (the value a later install reads back)
via a new package_manager_pin_specifier helper, and the global-switch path
stores the resolved version. The TypeScript CLI already derived the specifier
from the resolved version and is unaffected.
2026-07-14 20:42:17 +02:00
Zoltan Kochan
d52abd73ba fix(fs): rewrite symlink paths to native separators on Windows (#12991)
A scoped dependency's node_modules/@scope/name link path is built by
joining the whole @scope/name alias as one segment. Rust's Path::join
appends segments verbatim (unlike Node's path.join, which normalizes),
so the / inside the alias survived into an otherwise \-separated Windows
path and reached CreateSymbolicLinkW, which rejects forward-slash paths
— the long store paths reach it in verbatim \\?\ form where / is a
literal filename byte — with ERROR_DIRECTORY (os error 267), aborting
the install.

This is the same failure class as the global-virtual-store slot-path fix
in pnpm/pnpm#12976, which normalized the slot suffix at its construction
sites. Rather than chase every join site, rewrite paths to native
separators at the symlink writer's own choke points: symlink_dir
(covering the hoisted linker's direct calls) and force_symlink_dir's
entry (so its read_link / remove_dir / rename / create_dir_all retry
steps all see a native path too). The rewrite only allocates when a /
is actually present and is a no-op on Unix.

The TypeScript CLI is unaffected: Node's path.join already normalizes
separators on Windows, so no counterpart change is needed.
2026-07-14 19:53:10 +02:00
Zoltan Kochan
6e65538a8a chore: fix lockfile (#12990) 2026-07-14 00:17:46 +02:00
Zoltan Kochan
d3ed5da76c chore: update lockfile, Node.js, and pnpm versions (#12989)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-14 00:10:37 +02:00
Zoltan Kochan
6dcfadd5ae fix(release): sync Rust product versions via the meta-updater (#12988)
The Rust CLI and pnpr embed the versions their release builds report and
verify (`PNPM_VERSION` in pnpm/crates/config/src/defaults.rs, the crate
version in pnpr/crates/pnpr/Cargo.toml, and its Cargo.lock entry). These
were mirrored from the npm wrappers by `syncRustVersions` in bump.ts — a
step separate from the meta-updater, so running `pnpm version -r` without
the full bump flow bumped the wrappers (pacquet 12.0.0-alpha.10, pnpr
0.1.0-alpha.2) while the Rust sources stayed at the previous versions. The
release workflow's "Verify the committed version" step then failed, and
nothing caught the drift before the tag.

Move the sync into the meta-updater as a set of Rust-source file handlers,
so it is written by `pnpm update-manifests` and, crucially, validated by
`meta-updater --test` in pre-push and CI — a missed sync now fails locally
instead of at release time. bump.ts drops the redundant `syncRustVersions`
and runs `pnpm update-manifests` after `pnpm version -r`; the root `bump`
script no longer needs its own trailing `update-manifests`.

Regenerating brings the Rust sources up to the already-bumped wrapper
versions (alpha.10 / alpha.2), unblocking the release.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v11.13.0 v12.0.0-alpha.10
2026-07-13 23:24:21 +02:00
Zoltan Kochan
1c887b2e98 chore(release): don't re-lint the pnpm CLI at publish time (#12987)
* chore(release): don't re-lint the pnpm CLI at publish time

The pnpm package's `prepublishOnly` ran `compile`, which includes
`eslint --fix` over the src and test files. On the macOS release runner
the `import-x/no-extraneous-dependencies` allow-list for test files did
not take effect, so linting reported hundreds of spurious errors and
aborted the publish of the `pnpm` wrapper — the last package the release
job publishes — leaving 11.13.0 unpublished.

Split the artifact build out of `compile` into a new `build` script and
point `prepublishOnly` at it, so publishing no longer re-lints. Lint is
still run by `compile` (used for local development and `pnpm test`) and
enforced by CI on every PR, so a merged, tagged release commit is already
linted.

`build` runs the same tsgo build, bundle, and asset-copy steps `compile`
did; only the lint step is removed from the publish path. The scripts are
generated by the meta-updater, so the change is made there and the
generated `pnpm/package.json` is regenerated to match.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: bump pnpm 12 and pnpr

* fix: update pnpm v12

* chore: bump versions

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 23:05:14 +02:00
Zoltan Kochan
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 Kochan
ddd90eaff8 fix(pacquet): base repeat-install freshness on filesystem mtime at nanosecond precision (#12981)
Pacquet's repeat-install fast path recorded lastValidatedTimestamp from
the wall clock but compared it against filesystem mtimes. On a runner
whose wall clock runs ahead of the filesystem's mtime clock (~2-3 ms on
Blacksmith Linux), a file edited just after an install got an mtime below
the baseline and the edit was missed; and because installs are fast, the
lockfile and a same-instant edit could share a millisecond, which the
ms-truncated comparison also missed.

Record the baseline from filesystem mtimes (max of the lockfile and the
validated manifests) so it shares a clock with the files it is compared
against, and compare the subject at nanosecond precision against the
rounded-down millisecond reference. Mirrors pnpm's checkDepsStatus, which
already keys single-project freshness off the lockfile mtime.
2026-07-13 22:06:22 +02:00
Zoltan Kochan
ed445b51dd feat(versioning): add versioning.epics for banded monorepo versioning (#12979)
Implements epics from the native monorepo versioning RFC in both the
TypeScript release engine (`@pnpm/releasing.versioning`) and the Rust one
(`pacquet-versioning`), kept behaviorally identical.

An epic ties a group of member packages to a lead package, constraining
each member's major version to the band derived from the lead's major
(while the lead is on major M, members live in M*100 .. M*100+99). Members
move independently inside the band; when a release plan takes the lead to a
new stable major, every member re-bases to the band floor in the same plan
(deferred while the lead sits on a prerelease lane). Membership is matched
with pnpm's package selectors: name globs, ./-prefixed directory globs, and
!-prefixed negations.

Conflicting configuration is rejected: a package matched by two epics, a
fixed group straddling an epic boundary, and an unknown or non-releasable
lead.
2026-07-13 22:02:04 +02:00
Zoltan Kochan
45f682fe46 test: opt version -r changelog test into repository storage (#12984)
The "bare version -r applies the release plan and cleans up the intent" test
asserts the committed-CHANGELOG flow: it reads lib/CHANGELOG.md and expects the
consumed intent to be deleted at version time. Since composing changelogs at
publish time (registry storage) became the default, `pnpm version -r` no longer
commits CHANGELOG.md — it parks the section under .changeset/changelogs/ and
defers intent GC until the registry confirms publication. With no verifyPublished
registry in the test, the changelog file is never written (ENOENT) and the
intent is never collected, so the test failed.

Opt the test into `versioning.changelog.storage: repository` so it keeps
exercising the committed-changelog + intent-cleanup path, matching the pattern
the versioning package's lifecycle tests already use. Registry-storage behavior
(parked section, deferred GC) is covered by those lifecycle tests.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 21:04:01 +02:00
Zoltan Kochan
a72c2b1953 test: stabilize lockfile-verify cache content-change test on coarse-mtime runners (#12980)
`tryLockfileVerificationCache` has a stat fast path that trusts the cached
hash when a lockfile's (size, mtimeNs, inode) still match the recorded values,
skipping the content hash. The "miss when content changed even if size happens
to match" test rewrote the lockfile in place (rm + writeFile) with identical
byte length, expecting the stat shortcut to bail so the hash check rejects the
new content.

On CI runners with coarse (>=1s) filesystem mtime granularity, both writes land
in the same mtime tick, and the freed inode gets reused, so all three stat
fields match. The stat shortcut then fires and returns a hit before the hash
check runs, so the test saw `hit: true` instead of `false`.

Write the replacement to a sibling file and rename it in, as the sibling test
already does. Two files that coexist must have distinct inodes, so the renamed
replacement is guaranteed a different inode from the original regardless of
mtime granularity — the stat shortcut always bails and the content hash is what
rejects the change, which is what the test means to exercise.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 21:02:47 +02:00
Zoltan Kochan
0a68c77935 test: fix process-cleanup fixtures aborting on implicit install in CI (#12978)
The errorHandler process-cleanup tests run `pnpm exec`/`pnpm run` directly
inside the shared exec-error-exit and multiple-scripts-error-exit fixtures.
`prepareFixtures` pre-installs every fixture with the default store, so the
committed `node_modules/.modules.yaml` records that store. At test time the
CLI runs with `store_dir=../store`, and the default `verifyDepsBeforeRun:
install` spawns an implicit `pnpm install` whose `checkCompatibility` throws
`UnexpectedStoreError` on the store mismatch and tries to purge the modules
directory. In the non-TTY test subprocess (which also never inherits `CI`),
that purge aborts with `ERR_PNPM_ABORTED_REMOVE_MODULES_DIR_NO_TTY` before the
scripts ever run, so the expected `ERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL` is never
produced.

These fixtures test process-tree cleanup, not dependency verification, and have
no dependencies, so set `verifyDepsBeforeRun: false` to skip the implicit
install entirely.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 18:33:52 +02:00
Zoltan Kochan
29efb4ae49 fix: honor same-second edits on whole-second-mtime filesystems (#12975)
The optimistic repeat-install fast path and verify-deps-before-run decide
whether a package.json, .pnpmfile.cjs, or patch file changed since the
last install by comparing its mtime against the last-validated timestamp
with a strict `>`. On a filesystem that records mtimes at whole-second
resolution (ext4 with 128-byte inodes, HFS+, some CI runner disks) a file
edited in the same second as the install gets an mtime that rounds down
below the timestamp, so the edit looks unchanged, the fast path
short-circuits, and re-resolution is skipped — the second install is a
no-op and keeps the stale result.

Detect a whole-second mtime (no sub-second component) and treat its whole
second as possibly-after the reference, falling through to the
authoritative content check instead of trusting the rounded-down mtime.
Erring toward "modified" only ever runs the content check; it never skips
a needed install. On sub-second filesystems the mtime carries a
fractional part, so the comparison stays exact and the common case is
completely unaffected — the existing deps-status unit tests pass
unchanged.

Landed in both stacks: pacquet's optimistic_repeat_install and pnpm's
checkDepsStatus, with a regression test on each side that forces a
whole-second mtime so the coarse-filesystem path is exercised on any
filesystem.
2026-07-13 18:32:16 +02:00
Zoltan Kochan
4124f481ba test: opt change/version integration tests into repository changelog storage (#12977)
`compose changelogs at publish time (registry storage)` (#12971) made
`changelog.storage: registry` the default, so `pnpm version -r` no longer writes
a committed CHANGELOG.md — it parks each release's section under
`.changeset/changelogs/` for publish-time composition. The `change` command
integration tests (added in #12953) still assert committed CHANGELOG.md files,
so they broke on `main`. #12971 gave the versioning unit tests the same
treatment (a `repository()` settings helper); this does the equivalent for the
CLI integration tests by opting their workspace into `repository` storage.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 18:31:55 +02:00
Zoltan Kochan
c03b2d45a5 fix: create global virtual store symlinks with native path separators on Windows (#12976)
The global-virtual-store slot suffix is formatted as `@/<name>/<version>/<hash>`
with `/` separators because it doubles as a cross-platform canonical id (it
feeds hashing and lockfile comparison). Joining it onto the store dir with a
plain `PathBuf::join` preserved those forward slashes on Windows, and they then
reached `CreateSymbolicLinkW`, which rejects forward-slash paths with
`ERROR_DIRECTORY` (os error 267) — aborting `pnpm install` on Windows whenever
the global virtual store is enabled.

Add a `join_global_virtual_store_path` helper that expands the `/`-separated
suffix into native path components, and route the slot-path join sites through
it (`VirtualStoreLayout::slot_dir` plus the two config-deps installer joins).
No-op on Unix; produces `\` paths on Windows.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 18:30:33 +02:00
Zoltan Kochan
5bd00823c9 fix(publish): send README to the registry as metadata (#12968)
`pnpm publish` again includes the package's README in the metadata sent to
the registry, matching the npm CLI, so registries can render it on the
package page.

Until v11 the actual upload was delegated to `npm publish <tarball>`, and
npm reads the README out of the tarball (pacote's fullReadJson) into the
version metadata regardless of pnpm's embed-readme setting. Once publishing
became fully native and went through libnpmpublish with pnpm's own
publishedManifest, the README only reached the registry when embed-readme
was true — but that setting defaults to false (deliberately, so the tarball's
package.json stays clean). The result was that published packages silently
lost their README metadata.

Decouple the two concerns: the README is now always attached to the manifest
reported for publishing, while embed-readme continues to control only whether
it is written into the package.json inside the tarball. For a pre-built
tarball passed to `pnpm publish <tarball>`, the README is read back out of the
tarball, mirroring npm's fullReadJson. Applied to both the TypeScript CLI and
the Rust pacquet stack (pack + publish; stage delegates to publish).

pnpr additionally hoists the latest version's readme to the packument's
top-level readme/readmeFilename on publish, matching npm and verdaccio, so a
package published to pnpm's own registry exposes a top-level readme.

Closes pnpm/pnpm#12966.
2026-07-13 18:27:50 +02:00
Zoltan Kochan
0dfce95b76 feat(release): compose changelogs at publish time (registry storage) (#12971)
Implement RFC 0006 `changelog.storage: registry` and make it the default: no
CHANGELOG.md is committed. A release's section is parked under
.changeset/changelogs/ at `pnpm version -r` time and composed into the
published tarball at publish, on top of the previous published version's
changelog (highest published version semver-lower than the one being
published — never a dist-tag lookup, so concurrent lines chain correctly).

Consumed intents are garbage-collected by a later `pnpm version -r` only once
the registry confirms the ledgered version is published and its tarball's
CHANGELOG.md carries the composed section; a foreign-published version keeps
the intent instead of losing it. The pure `versioning` crate/package stays
network-free: the publish check is injected (a callback in TS, a
confirmed-key set in Rust) from the command layer, which already has registry
access. The parked-section store captures dependency-update lines and exact
formatting that can't be recomposed from intents at publish time.

`versioning.changelog.storage: repository` keeps the previous committed
CHANGELOG.md behavior. Landed in both stacks.
2026-07-13 17:46:02 +02:00
Zoltan Kochan
81cbd50a89 ci: run TS and Rust test jobs on Blacksmith runners (#12969)
* ci: run TS and Rust test jobs on Blacksmith runners

Move the unit and e2e test jobs that were still on GitHub-hosted runners
onto Blacksmith 8vcpu runners, matching the Blacksmith runners the
lint/detect/ancillary jobs already use.

- TS CI (ci.yml): test-smoke and the test/test-windows matrices now pass
  blacksmith-8vcpu-ubuntu-2404 / blacksmith-8vcpu-windows-2022 into the
  reusable test.yml.
- Rust CI (pacquet-ci.yml): the test matrix ubuntu/windows legs move to the
  same Blacksmith labels. The macOS leg stays on macos-latest because
  Blacksmith offers no macOS runners.

The Bencher upload workflow validates testbed names against strict
ubuntu/windows/macos regexes, and both test workflows derived that slug by
stripping "-latest" from the runner label, which no longer works for
Blacksmith labels. test.yml now maps *ubuntu*/*windows* labels to the bare
slug, and the Rust test job derives the slug (and its artifact name) from
matrix.os_label instead.

* test(cli): make stale-pin-dedupe robust on coarse-mtime filesystems

The three stale_pin_dedupe tests each run two back-to-back installs,
rewriting package.json between them, and assert the second install
re-resolves the bumped version. pnpm and pacquet both gate their
incremental-install fast path on a manifest's mtime being newer than the
previous install (optimistic_repeat_install). The two installs run a few
hundred milliseconds apart, so on a filesystem whose mtime granularity is
coarser than that gap the rewritten manifest lands in the same mtime tick
as the first install, the edit is not seen as newer, re-resolution is
skipped, and the lockfile keeps the stale version.

This surfaced on CI runners whose filesystem has second-granularity
mtimes: the tests pass on ext4-backed runners and local dev but fail
deterministically there. It is not a pacquet-vs-pnpm divergence — the
mtime fast path behaves the same in both stacks, and forcing the second
manifest's mtime equal to the lockfile's reproduces the failure locally
regardless of which baseline (wall clock or lockfile mtime) the gate
uses.

Model the intended scenario explicitly: push the rewritten manifest's
mtime well past the lockfile's so the second install reads it as a
genuine later edit, matching what a user editing the manifest at a later
time would produce. The assertions still exercise the real
stale-pin-refresh behavior.

* ci: use Blacksmith windows-2025 label for the test jobs

Blacksmith's Windows runners run Windows Server 2025; the valid labels
are blacksmith-<N>vcpu-windows-2025. The windows-2022 label matches no
runner, so the Windows test legs would queue forever. Point them at the
windows-2025 label instead.

* ci: run the Build pnpr jobs on Blacksmith

The two build-pnpr legs compile the pnpr binaries the TS test jobs
consume; they gate those tests but were still on GitHub-hosted runners.
Move them onto the same Blacksmith 8vcpu labels as the test jobs
(ubuntu-2404 / windows-2025). build-pnpr.yml keys its disk-cleanup gate,
cache, and artifact name off `runner.os`, which is unaffected by the
label change.
2026-07-13 17:09:06 +02:00
Yashas Gunderia
b519489269 fix: respect workspace root ignores when packing (#12878)
Fixes pnpm/pnpm#12560.

Workspace package packing now passes workspace context into packlist only when the package is actually under that workspace root. That lets workspace-root .npmignore/.gitignore files affect sub-package packing while preserving package-only behavior for normal packlist calls and unrelated workspaceDir values.

The pacquet packlist and pack path use the same workspace-aware rules so the Rust port stays aligned with the TypeScript CLI.

---------

Co-authored-by: ychampion <ychampion@users.noreply.github.com>
Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-07-13 16:32:21 +02:00
Mateusz Burzyński
96cf320eeb fix: pnpm publish --otp and pnpm publish --batch --otp to send the configured OTP to the registry (#12957)
Fixed `pnpm publish --otp` and `pnpm publish --batch --otp` to send the configured OTP to the registry. It was accidentally overridden by the `undefined` coming from the first `operation` call in `withOtpHandling`

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-07-13 16:31:32 +02:00
arman
b46f96165f fix: kill spawned process trees with taskkill on Windows error exits (#12925)
Main thread panicked: begin > end (105 > 28) when slicing
`@pnpm/npm-lifecycle@1100.0.0(patch_hash=e3541c…)(supports-color@10.2.2)`
at pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs:2490:51

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-07-13 16:30:01 +02:00
Bingjia Yang
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
sensei-woo
73ba1102cd fix(pacquet): resolve versionless workspace packages (#12940)
Index named workspace projects without a version under 0.0.0 instead of dropping them from the workspace package map. This restores pnpm 11 behavior for private/versionless workspace packages and allows workspace:* and workspace:0.0.0 dependencies to resolve in injected workspaces.

Related to #2648 and #2678.

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-07-13 16:28:24 +02:00
sensei-woo
35e0afeded fix(pacquet): resolve catalogs in injected workspace packages (#12941)
Resolve catalog specifiers in the dependency children of injected workspace packages. Limit this to workspace-resolved file dependencies so ordinary transitive dependencies remain outside importer catalog scope, matching the pnpm 11 implementation.

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-07-13 16:27:17 +02:00
Yashas Gunderia
505b136830 fix(deps-resolver): preserve linked transitive dependencies (#12928)
Only reuse the already-linked shortcut at the update-depth boundary. Below that boundary, resolution still needs to reconstruct transitive edges from the existing lockfile; returning early there makes the parent snapshot appear childless during a partial update.

Closes pnpm/pnpm#12456.

---------

Co-authored-by: ychampion <ychampion@users.noreply.github.com>
Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-07-13 16:21:52 +02:00
sensei-woo
d4d78df213 fix(cli): support catalogs in deploy (#12931)
Summary:
- omit catalog snapshots from generated deploy lockfiles
- add an end-to-end catalog deploy regression test
- add a pnpm patch changeset

Testing:
- cargo fmt --check
- cargo test -p pacquet-cli --test deploy deploy_from_shared_lockfile_supports_catalog_dependencies
- verified the regression test fails with the production fix removed

Full just ready is currently blocked on upstream main because sysinfo 0.39.3 requires Rust 1.95 while rust-toolchain.toml pins Rust 1.93.1.

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-07-13 16:21:16 +02:00
sensei-woo
bb3188143e fix(network): respect macOS scoped DNS (#12934)
## Problem

pnpm v12 hard-enables Hickory DNS for install requests. On macOS, Hickory reads the global DNS state but does not honor the full scoped/supplemental resolver graph used by VPNs such as Tailscale. In that setup, public registry names can be sent to a supplemental resolver with its search suffix and repeatedly fail.

## Fix

- use macOS native `getaddrinfo` resolution through `tokio::net::lookup_host`
- cap native lookups at four concurrent requests, matching Node/libuv behavior and retaining protection against `mDNSResponder` overload
- keep Hickory on non-macOS platforms
- cover the concurrency cap with a deterministic resolver test

This also avoids a registry-specific trailing-dot workaround, so tarball, custom-registry, and other HTTP hosts use the same correct macOS resolver routing. Hickory per-domain macOS resolver support is tracked in https://github.com/hickory-dns/hickory-dns/issues/2251.

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-07-13 16:18:40 +02:00
Zoltan Kochan
6a590deadf test(deploy): pass the required include option in createDeployFiles test (#12970)
The createDeployFiles test predates the required `include` option that was
added to CreateDeployFilesOptions when the deploy graph-pruning logic
landed. The test's call site was never updated, so `pnpm run compile-only`
fails on main with TS2741 (Property 'include' is missing).

This is a logical merge conflict: the test's call site and the newly
required option arrived in separate PRs that were each green against an
older base. Pass `include` with every dependency field enabled, matching
the default the production deploy command uses when no --prod/--dev/--no-
optional flags are set, so the test keeps exercising the same behavior.
2026-07-13 16:07:30 +02:00
Alessio Attilio
0cdd988e49 feat: implement star, stars, unstar natively in pacquet (#12917)
Implemented the `star`, `stars`, and `unstar` commands natively in Rust for the pacquet package manager.
The CLI bindings were added to `cli_command.rs`, `dispatch.rs`, and `dispatch_query.rs`.
The core logic resides in `star.rs`, `stars.rs`, and `unstar.rs`.
Comprehensive unit tests were added to `pnpm/crates/cli/tests/star.rs`.

Related to: #11633 

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-07-13 16:06:31 +02:00
kyungseopk1m
cbecd4afc0 fix(config): allow tokenHelper to be set in the global auth.ini (#12833)
### What

Allow a `tokenHelper` configured in the global pnpm `auth.ini` to be used, instead of rejecting it as project-level configuration.

Closes #12759.

### Why

The guard that blocks `tokenHelper` from a project `.npmrc` (so a checked-in `.npmrc` can't run an arbitrary command) validated the helper against `userConfig`, which only contains `~/.npmrc`. But `pnpm config set` writes a `tokenHelper` to the global `auth.ini`, which flows into `trustedConfig`, not `userConfig`. As a result a legitimately configured `tokenHelper` was rejected on every command with `ERR_PNPM_TOKEN_HELPER_IN_PROJECT_CONFIG`, and `pnpm config delete` failed as well. The guard's own comment already says the helper may come from "`~/.npmrc` or global auth.ini".

### How

Validate against `trustedConfig` instead of `userConfig`. `trustedConfig` merges the trusted sources (including `~/.npmrc` and the global `auth.ini`) but excludes workspace and project `.npmrc` files, so the global helper is accepted while a project-level one is still rejected.

Tests in `config/reader` cover both paths: a `tokenHelper` in the global `auth.ini` is honored, and a `tokenHelper` in a project `.npmrc` still throws `ERR_PNPM_TOKEN_HELPER_IN_PROJECT_CONFIG`.


### Pacquet parity

Pacquet had no `tokenHelper` support at all, so this PR also ports the whole feature to the Rust stack (matching the cardinal rule that user-visible changes land in both stacks):

- **Config layer** (`pnpm/crates/config`): parse `//host/:tokenHelper` (scoped and default forms too) from trusted `.npmrc` / `auth.ini` sources only; reject a project/workspace helper via the same full-vs-trusted comparison used on the TypeScript side; and reject reserved characters — surfacing the same `ERR_PNPM_TOKEN_HELPER_IN_PROJECT_CONFIG` / `ERR_PNPM_TOKEN_HELPER_UNSUPPORTED_CHARACTER` codes.
- **Network layer** (`pnpm/crates/network`): `AuthHeaders` carries the un-executed command per registry and runs it lazily on lookup, memoized (runs at most once, and never for a command that makes no matching request). Baked headers keep their eager, lock-free hot path; a header is emitted only on success, so a failing helper sends no credential.
- **Known divergence**: because `AuthHeaders::for_url` is infallible and there is no single network seam, a helper failure surfaces as a loud error plus an unauthenticated request rather than pnpm's hard whole-command abort.
- **Known divergence — execution timing**: pnpm runs every configured registry's `tokenHelper` when it builds the auth-header lookup (so a helper for a registry never contacted still runs); pacquet runs each helper lazily on the first request to that registry (per-registry, memoized, and never on a command that makes no matching request). Both bound execution at 60s.

Covered by new Rust unit + integration tests (parse, trust guard, reserved-char, env-drop, lazy execution / memoization, and an end-to-end `/bin/echo` run).

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-07-13 16:05:48 +02:00
Zoltan Kochan
5b8b101754 feat(overrides): add convergence overrides ("pkg@": exact version) (#12876)
Implements the empty-range override selector from pnpm/pnpm#12794 in both
stacks. "form-data@": "4.0.6" rewrites a dependency edge to exactly 4.0.6
iff 4.0.6 satisfies the edge's declared range, so compatible consumers
converge while incompatible ones keep their own resolution — semver-safe
by construction, unlike a bare override, and future-proof against new
compatible dependents, unlike a range-scoped one.

Design decisions:

- The value must be an exact version (ERR_PNPM_INVALID_CONVERGENCE_OVERRIDE
  otherwise): a range value would make "satisfies" ill-defined (issue open
  question 4).
- Empty ranges are rejected inside parent>child selectors, reserving that
  design space until precedence there is settled (open question 5, first
  half); when "pkg" or "pkg@range" and "pkg@" both match an edge, the
  explicit override wins.
- Satisfies checks use loose semver, matching the npm picker; workspace:,
  catalog:, npm:, git/URL, and dist-tag specifiers are skipped ("satisfies"
  is only defined against a semver range — open question 3).
- Staleness detection (lifecycle rule 3) ships with the syntax: after a
  full resolution — the only time the versions overrider has seen every
  manifest, so the collected declared-range set is complete — each
  convergence entry resolves every declared range against the cached
  registry metadata (release-age policy applied), and if a version newer
  than the value satisfies every declared range, install warns with that
  concrete replacement value. Partial resolutions stay silent to avoid
  false positives from unseen ranges; failed range resolutions can only
  suppress the warning, never fabricate one.
- An empty range previously slipped through parsing and behaved like a bare
  override; that undocumented accident is replaced by validation and the
  converge semantics.

pacquet mirrors the TS behavior byte-for-byte: same error code and
messages, same warning text on the pnpm:global warn channel, same
precedence and skip rules, with matching test coverage.

Deferred follow-ups from the issue's lifecycle plan: pnpm update / pnpm
dedupe refreshing and pruning managed entries (rules 1-2) and pnpm
outdated listing stale entries (rule 4).

Related to pnpm/pnpm#12794.
2026-07-13 16:02:13 +02:00
Yashas Gunderia
57cc33127e fix(resolver): recover from unsolicited 304 responses (#12888)
A 304 response is only meaningful when pnpm sent an ETag or
Last-Modified validator. Some caching proxies return 304 to a cold,
unconditional request, leaving pnpm without a metadata body.

Retry that response once with cache reuse disabled. If the retry still
returns 304, report META_NOT_MODIFIED_WITHOUT_CACHE instead of treating
a missing cache as validated.

Fixes pnpm/pnpm#12882.

---------

Co-authored-by: ychampion <ychampion@users.noreply.github.com>
Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-07-13 15:56:52 +02:00
sensei-woo
fcea44f61e fix(deploy): prune unreachable package graph (#12935)
Fixes #12933.

## Problem

The Rust modern deploy path converts every workspace importer and every package/snapshot into the dedicated deploy lockfile. `--prod` removes top-level dev links, but `CreateVirtualStore` still materializes that entire graph into the localized `.pnpm` directory. Unrelated workspaces and dev-only tools therefore make production deploys much larger.

The TypeScript implementation already filters the graph before physical linking, but its emitted deploy lockfile is also broader than the selected dependency groups.

## Fix

- derive the exact dependency groups once for Rust deploy
- walk the converted deploy graph from importer `.` and keep only reachable snapshots/package metadata
- include required transitive edges and gate optional edges on `--no-optional`
- apply the equivalent graph filtering to TypeScript deploy lockfile generation
- preserve importer maps and the generated package manifest so frozen freshness validation still succeeds

## Large-monorepo measurement

With a warm store and `deploy --prod`:

- before: 4,576,976,896 bytes and 3,795 virtual-store directories
- after: 1,292,177,408 bytes and 1,826 virtual-store directories
- fixed Rust deploy: 16.39s and 210,681,856-byte max RSS
- TypeScript pnpm 11.11 baseline: 28.08s and 1,895,956,480-byte max RSS

The fixed output contains no Storybook, Playwright, Expo, or TypeScript 7 snapshots for the selected production service.

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-07-13 15:55:25 +02:00
Trevor Burnham
1adc62c436 feat(pacquet): honor extraBinPaths and extraEnv from the updateConfig hook (#12943)
The TypeScript pnpm CLI passes the whole Config object through the
updateConfig pnpmfile hook and adopts the returned object wholesale, so
a hook can read and extend extraBinPaths / extraEnv. Pacquet only
round-tripped WorkspaceSettings through the hook, silently dropping both
fields.

Bring pacquet to parity:

- Add extra_env to Config (extra_bin_paths already existed).
- In run_update_config_hooks, seed the hook input with the live
  extraBinPaths / extraEnv (so a hook can append to them), then read the
  hook's values back from the delta and assign them to Config. Neither is
  a pnpm-workspace.yaml key, so they are special-cased like storeDir /
  catalogs rather than going through WorkspaceSettings::apply_to.
- Seed config.extra_env into every child-process spawn site: the
  install-family lifecycle scripts, run, recursive run, pack, publish,
  exec, and the dlx bin. Populated only by the hook, which today runs
  only for the install-family commands, so it is empty at the other
  sites until the hook broadens; wiring them uniformly keeps behavior
  consistent.
- Apply extra_env before pnpm's reserved per-call stamps in make_env, so
  a hook's extraEnv can't override INIT_CWD, PNPM_SCRIPT_SRC_DIR,
  npm_config_user_agent, or the verify-deps guard (matching TS
  runLifecycleHook); non-reserved keys still apply. The exec/dlx spawns
  layer pnpm's keys on top the same way.
- Forward config.extra_bin_paths to project lifecycle scripts, matching
  TS install/index.ts.

npmPath is intentionally not implemented: it is a type-only field on the
TypeScript Config that is never read as a value, and publish is native
on both stacks, so pacquet ignoring it already matches TypeScript
behavior.

This is a pacquet-only parity fix; the TypeScript CLI already has this
behavior, so no changeset is warranted.

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-07-13 15:38:34 +02:00
Yashas Gunderia
cc00c45afb fix(deploy): preserve local tarball package names (#12864)
## Summary

- Keep the package name from the source lockfile when `pnpm deploy` rewrites local `file:` tarball dependencies.
- Add coverage for the deploy lockfile rewrite and the warm-store shared-lockfile deploy path.

## Why

Fixes pnpm/pnpm#12792. On the second deploy with a warm store, the generated deploy lockfile used the tarball filename as the package name, so the store content check expected `is-positive-3.1.0.tgz@3.1.0` while the stored package was `is-positive@3.1.0`.

---------

Co-authored-by: ychampion <ychampion@users.noreply.github.com>
Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-07-13 15:13:33 +02:00
Zoltan Kochan
b8f856b165 fix(pacquet): honor --no-optional for transitive optional dependencies (#12965)
## What

A fresh install with `--no-optional` (`includeOptionalDeps: false`) only dropped the **root importer's** own `optionalDependencies`. A **transitive** optional — an `optionalDependencies` entry of a resolved package — stayed in the graph and was materialized into `node_modules`. So `install <pkg> --no-optional` still linked `<pkg>`'s optional deps.

## Why

The `dependency_groups` filter excludes the Optional group when building the root importer's direct-dep set, but `extract_children` in the deps resolver descends into every resolved package's `optionalDependencies` unconditionally. The frozen-lockfile path already compensates for this (it adds every `optional` snapshot to the transient `optional_excluded` skip set when Optional isn't requested); the fresh-resolve path did not.

## Fix

In `install_with_fresh_lockfile.rs`, after resolution, mirror the frozen path: when `Optional` is not in the requested `dependency_groups`, add every `built_lockfile` snapshot with `optional == true` to `skipped.add_optional_excluded(key)`. Those snapshots:

- are excluded from materialization (through the same gate installability skips use),
- stay out of `.modules.yaml.skipped`, so a later install without `--no-optional` brings them back,
- remain in the lockfile.

The fresh path already re-derives `snapshot.optional` (optional-only-reachable) in `dependencies_graph_to_lockfile.rs`, so the flag is available.
2026-07-13 15:12:30 +02:00
Zoltan Kochan
fe4dad55ca chore: update lockfile, Node.js, and pnpm versions (#12802)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-13 14:52:19 +02:00
sensei-woo
a4b544fc6e fix(pacquet): parse peers after patch hashes (#12939) 2026-07-13 14:32:00 +02:00
Zoltan Kochan
bcedb9518b feat(pacquet): report extraneous packages in pnpm list (#12967)
`pnpm list --json` and `pnpm list --parseable` now report packages that
are present in an importer's `node_modules` but absent from the lockfile
(npm's "extraneous" dependencies) under `unsavedDependencies`, matching
the TypeScript CLI.

pacquet built the list tree entirely from the lockfile, so extraneous
packages were never surfaced. The tree view still omits them, matching
the TypeScript CLI, which hardcodes `showExtraneous: false`; the scan of
`node_modules` runs only when the `--json`/`--parseable` renderers will
surface it. Extraneous detection is also skipped when searching by
package name and for `--depth -1` (project-only output) — the same gates
the TypeScript CLI applies before scanning `node_modules`. A missing
`node_modules` yields no extraneous packages; any other read failure is
surfaced, matching `readModulesDir`.
2026-07-13 14:28:05 +02:00
Omri Luzon
6986a42faa fix(deps-resolver): dedupe injected deps despite unrelated peer-suffix mismatch (#12800)
getDedupeMap's isSubset check required byte-identical depPath strings
between an injected workspace occurrence's resolved children and the
target project's own resolved dependencies. A completely unrelated,
ordinary shared dependency can legitimately resolve differently on each
side: when reconciling against an existing lockfile, a package with its
own optional peer (e.g. debug's supports-color) can get pinned to its
previously-locked peer-suffixed variant for the target project's own
copy while the injected occurrence resolves fresh without one. Both are
valid resolutions of the same package, but the strict string equality
treated them as incompatible and left the entry as file:(...) instead of
collapsing to link:, silently causing nodeLinker: hoisted setups to copy
a stale/unbuilt snapshot into node_modules instead of linking to the
live workspace source.

Fix the comparison to accept a target dependency that
isCompatibleAndHasMoreDeps than the injected occurrence needed, reusing
the same compatibility notion dedupePeerDependents's deduplicateDepPaths
already relies on elsewhere in this file, instead of requiring the two
depPath strings to be identical.

Confirmed against a standalone minimal reproduction
(github.com/omril1/pnpm-workspace-link-corruption-repro) and an
independent real-world monorepo replay: the corruption is fully
deterministic given the same lockfile and triggering change (verified
across repeated runs from an identical starting state), not a timing
race.

Related to pnpm/pnpm#10433

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-07-13 14:24:45 +02:00
Alessio Attilio
b9fccc857d feat(cli): implement deprecate and undeprecate commands natively in Rust (#12884)
This implements the native Rust versions of the pnpm deprecate
and undeprecate commands 

Related to: pnpm/pnpm#11633.

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-07-13 14:16:10 +02:00
Minha Kang
85efb4900b fix: limit concurrent reads of unsaved dependencies (#12866)
`pnpm list`/`pnpm why` could crash with `EMFILE: too many open files` on
projects with many unsaved dependencies (packages in node_modules but not in
the lockfile). buildDependenciesTree read them all at once via an unbounded
Promise.all; bound the concurrency with p-limit(4), mirroring the existing
limit in @pnpm/deps.inspection.list's readPkg.

Closes #12426
2026-07-13 13:33:21 +02:00
Zoltan Kochan
0dd21df745 fix: keep bundled dependency fields out of the published pnpm manifest (#12964)
The published pnpm package bundles its runtime dependencies into
dist/node_modules, so its manifest must not declare dependencies or
devDependencies — otherwise pnpm installs them a second time, and the
internal-only devDependencies (e.g. @pnpm/test-ipc-server) aren't
published, so the install 404s. pnpm 11.11.0 published with neither field.

The workspace .pnpmfile.cjs beforePacking hook drops these fields when
packing, but the release for 11.12.0 ran a pnpm 12 alpha (pacquet) that
does not yet honor beforePacking, so the fields leaked into the published
manifest and broke installs.

This fixes the pnpm 12 gap and adds a stopgap for it:

1. pacquet now honors beforePacking pnpmfile hooks during pack/publish,
   so once the release runs a pnpm that includes this, the hook drops the
   fields as intended:
   - PnpmfileHooks::before_packing (trait default no-op) plus a worker JS
     branch and NodeJsHooks impl, called as beforePacking(pkg, dir, ctx)
     with pnpm's `?? manifest` fallback.
   - pack::api is now async; it loads the workspace-root and
     config-dependency pnpmfiles (via a resolve_pnpmfile_paths helper
     shared with the updateConfig hook) and applies beforePacking to the
     published manifest before the file list is computed. The async change
     ripples through pack::run, dispatch_query::pack, publish, and napi.

2. As a temporary workaround until the release runs such a pnpm,
   bundle-deps.ts (the CLI's prepublishOnly) strips dependencies and
   devDependencies from the on-disk manifest after building the bundle.
   pack re-reads the manifest after prepublishOnly, so the fields never
   reach the tarball regardless of the beforePacking gap.

Integration tests drive the real binary through pnpm pack with a
.pnpmfile.cjs beforePacking hook, including the release shape a
workspace-root pnpmfile applied to a --filter-selected sub-package.

Closes pnpm/pnpm#12955.
2026-07-13 12:39:54 +02:00
Zoltan Kochan
d3e1383ff4 feat(release): unify the TypeScript, Rust and pnpr release flows on pnpm's native versioning (#12949)
Merge the three release flows (TypeScript, Rust CLI + @pnpm/napi, pnpr)
into a single flow driven by pnpm's native workspace versioning
(pnpm/pnpm#12953), dropping the @changesets/cli dependency (Closes
pnpm/pnpm#12947).

The native engine keys package identity on the workspace directory, so
the Rust CLI wrapper is named pnpm (the v12 line at pnpm/npm/pnpm) and
shares the published name with the TypeScript CLI at pnpm11/pnpm.
Release configuration moves from .changeset/config.json to the
versioning key of pnpm-workspace.yaml: versioning.lanes puts the Rust
CLI, @pnpm/napi, and @pnpm/pnpr on an alpha lane (X.Y.Z-alpha.N
prereleases published under next) while the TypeScript CLI releases
stable on the main lane; versioning.fixed keeps the Rust CLI and
@pnpm/napi at one shared version; versioning.ignore freezes
@pnpm/logger, which is consumed as a catalog: peer the engine would
otherwise reject as an internal range.

Lanes replace the hand-rolled prerelease continuation, and the
committed .changeset/ledger.yaml replaces the .changeset-released
directory as the cherry-pick-safe record of consumed intents. bump.ts
drops both and is now just pnpm version -r plus syncRustVersions, which
mirrors the bumped wrapper versions into defaults.rs and the pnpr crate
version.

Because two workspace projects are named pnpm, name-based --filter=pnpm
is qualified by directory (pnpm{pnpm11/pnpm}) across the build and
release scripts, the meta-updater excludes the Rust wrappers by
directory, and changesets targeting the TypeScript CLI reference it as
./pnpm11/pnpm. release.yml's plan job gates per-product publish jobs on
which committed versions are unpublished; everything publishes via
trusted publishing. @changesets/cli, .changeset/config.json, and the
standalone pacquet/pnpr release workflows are removed; their npm
trusted-publisher bindings must be re-pointed at release.yml before the
first unified release.

pnpm-lock.yaml is regenerated from scratch: an incremental
--lockfile-only resolve after the @changesets/cli removal hit a pacquet
incremental-resolver bug (pnpm/pnpm#12958) that emptied a peer-context
snapshot the CLI depends on and broke the bundle build. A from-scratch
resolve is correct; the bug is filed separately.
2026-07-13 11:11:41 +02:00
YES!HYUNGSEOK
806ff49162 fix(pnpr): prevent stale hosted packument writes (#12832)
S3-backed pnpr deployments can run multiple stateless replicas against the same hosted
object store. The in-process package lock only serializes one replica, so the old
read/merge/write path could let a stale packument overwrite a newer merge.

Add a hosted packument read-for-update path that captures object-store update versions and
use conditional S3 writes for hosted packuments. Publish, partial unpublish, and dist-tag
writes now use that conditional write path; dist-tag writes retry after conflicts because
their mutation can be replayed on a fresh packument. The dist-tag request path and journal
roll-forward share one Storage::update_hosted_packument_with_retry helper so their
conflict/backoff handling stays in a single place.

Tarball finalize on the S3 backend is now compare-and-swap: it promotes with
PutMode::Create, tolerates a byte-identical object, and refuses to overwrite a different
object left by a concurrent same-version publisher. commit_publishes surfaces that as an
HTTP 409 before writing its packument, and journal roll-forward keeps the winner's
immutable version, so a losing publish can no longer corrupt the winner's tarball.

Publish commit journal recovery now rereads the current hosted packument, re-merges the
journaled manifest, and retries conditional writes.
Repeated conflicts surface as HTTP 409 rather than silently losing another writer's update.
The local fs backend keeps its existing single-process behavior because the production
shared-store race is specific to S3-backed replicas.

Regression tests verify that a stale S3 packument update is rejected and that a concurrent
tarball finalize with different bytes is refused without overwriting the first writer.

---------

Co-authored-by: Zoltan Kochan <zoltankochan@gmail.com>
2026-07-13 10:35:26 +02:00
Minha Kang
b45c85a5fc fix: pnpm cache delete removes a package from all metadata cache directories (#12831)
* fix: pnpm cache delete removes a package from all metadata cache directories

* fix(pacquet): mirror cache delete across all metadata directories

* test(cli): use metadata-dir constants in cache delete test

The cache delete multi-directory test hardcoded the metadata directory
names as string literals, which could drift from the constants the delete
logic actually uses. Reference ABBREVIATED_META_DIR / FULL_META_DIR /
FULL_FILTERED_META_DIR directly so the test and the code stay in lockstep.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Zoltan Kochan <zoltankochan@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 21:31:59 +00:00