Commit Graph

12209 Commits

Author SHA1 Message Date
dependabot[bot]
d67385eac1 chore(deps): bump the github-actions group across 1 directory with 12 updates
Bumps the github-actions group with 12 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [dorny/paths-filter](https://github.com/dorny/paths-filter) | `4.0.1` | `4.0.2` |
| [github/codeql-action/init](https://github.com/github/codeql-action) | `4.36.2` | `4.36.3` |
| [github/codeql-action/autobuild](https://github.com/github/codeql-action) | `4.36.2` | `4.36.3` |
| [github/codeql-action/analyze](https://github.com/github/codeql-action) | `4.36.2` | `4.36.3` |
| [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) | `4.1.0` | `4.2.0` |
| [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) | `4.1.0` | `4.2.0` |
| [docker/login-action](https://github.com/docker/login-action) | `4.2.0` | `4.4.0` |
| [docker/build-push-action](https://github.com/docker/build-push-action) | `7.2.0` | `7.3.0` |
| [taiki-e/install-action](https://github.com/taiki-e/install-action) | `2.82.5` | `2.82.9` |
| [crate-ci/typos](https://github.com/crate-ci/typos) | `1.47.2` | `1.48.0` |
| [garnet-org/action](https://github.com/garnet-org/action) | `2.0.2` | `2.2.0` |
| [warpdotdev/oz-agent-action](https://github.com/warpdotdev/oz-agent-action) | `1.0.21` | `1.0.23` |



Updates `dorny/paths-filter` from 4.0.1 to 4.0.2
- [Release notes](https://github.com/dorny/paths-filter/releases)
- [Changelog](https://github.com/dorny/paths-filter/blob/master/CHANGELOG.md)
- [Commits](fbd0ab8f3e...7b450fff21)

Updates `github/codeql-action/init` from 4.36.2 to 4.36.3
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](8aad20d150...54f647b7e1)

Updates `github/codeql-action/autobuild` from 4.36.2 to 4.36.3
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](8aad20d150...54f647b7e1)

Updates `github/codeql-action/analyze` from 4.36.2 to 4.36.3
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](8aad20d150...54f647b7e1)

Updates `docker/setup-qemu-action` from 4.1.0 to 4.2.0
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](06116385d9...96fe6ef7f3)

Updates `docker/setup-buildx-action` from 4.1.0 to 4.2.0
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](d7f5e7f509...bb05f3f551)

Updates `docker/login-action` from 4.2.0 to 4.4.0
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](650006c6eb...af1e73f918)

Updates `docker/build-push-action` from 7.2.0 to 7.3.0
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](f9f3042f7e...53b7df96c9)

Updates `taiki-e/install-action` from 2.82.5 to 2.82.9
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](bffeee26d4...4684b84056)

Updates `crate-ci/typos` from 1.47.2 to 1.48.0
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](37bb98842b...bee27e3a4f)

Updates `garnet-org/action` from 2.0.2 to 2.2.0
- [Release notes](https://github.com/garnet-org/action/releases)
- [Commits](2b7fc9d79b...3d47f4a900)

Updates `warpdotdev/oz-agent-action` from 1.0.21 to 1.0.23
- [Release notes](https://github.com/warpdotdev/oz-agent-action/releases)
- [Commits](1922f22c00...8ba4e14206)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-version: 1.48.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: docker/build-push-action
  dependency-version: 7.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: docker/login-action
  dependency-version: 4.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: docker/setup-buildx-action
  dependency-version: 4.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: docker/setup-qemu-action
  dependency-version: 4.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: dorny/paths-filter
  dependency-version: 4.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: garnet-org/action
  dependency-version: 2.1.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: github/codeql-action/analyze
  dependency-version: 4.36.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: github/codeql-action/autobuild
  dependency-version: 4.36.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: github/codeql-action/init
  dependency-version: 4.36.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: taiki-e/install-action
  dependency-version: 2.82.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: warpdotdev/oz-agent-action
  dependency-version: 1.0.23
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-13 09:30:55 +00: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
Zoltan Kochan
f573900611 fix(pacquet): pin saved node runtime version and preserve package.json formatting (#12948)
The update-lockfile workflow runs `pnpm runtime set node 26` and expects
the exact resolved version in devEngines.runtime.version. The TypeScript
CLI rewrites the manifest after resolution with the node resolver's
normalized specifier; pacquet decides the saved specifier before the
install runs, so the requested range was written verbatim ("26" instead
of "26.5.0") even though the lockfile resolved 26.5.0 correctly.

Expose the normalization as NodeResolver::resolve_save_specifier — the
shared parse/mirror/pick-version path without the platform asset fetch —
and call it from Add::run for explicit node@runtime: requests, mirroring
how registry specs are already pinned up front. Deno and bun stay on the
verbatim save path; both stacks normalize those to the requested spec.

Also close the remaining manifest-write parity gaps with the TypeScript
project-manifest reader/writer, which surfaced in the same automated PR:

- Preserve the source file's final-newline state and indentation unit
  across the read/save round-trip; a single-line document round-trips
  back to its compact form, and new manifests get two-space indentation
  plus a final newline. The indentation unit is capped at 10 characters
  on write, the cap JSON.stringify applies to its space argument, which
  the TypeScript writer serializes through.
- Normalize dependency fields on write: sort entries by name and drop a
  field that holds no entries, so a reification-only devDependencies: {}
  never lands on disk.
- Skip the write entirely when the file already encodes the same
  manifest, so a no-op save never churns formatting or mtime. This turns
  PackageManifest::save into a &mut self method (it tracks the on-disk
  baseline). A freshly scaffolded manifest reads its baseline back from
  the file it just wrote, so scaffolds and pre-existing manifests share
  one serialization path.

Related to pnpm/pnpm#12802.
v12.0.0-alpha.9
2026-07-12 22:31:00 +02:00
Khải
c6fd0b7e2f bench(micro-benchmark): isolate the packument and lockfile parse paths (#12950)
* bench(micro-benchmark): isolate per-sink install-path benches

Add four benches that isolate the individual CPU sinks on the
install/resolve path, so a performance change (such as an LTO or
release-profile tweak) can be attributed per sink instead of only to the
combined tarball-download bench:

- inflate/gunzip_tarball: zune-inflate, mirroring the DeflateOptions of
  pacquet_tarball::decompress_gzip.
- integrity/sha512_{compute,check}: ssri -> sha2, the whole-tarball
  integrity verify taken under the default verify-store-integrity=true.
- json/packument_parse_value: serde_json parse of a committed abbreviated
  lodash packument (the metadata format pacquet fetches during
  resolution).
- lockfile/parse_pnpm_lock: pacquet_lockfile parse of the
  integrated-benchmark's 12k-line pnpm-lock.yaml.

The JSON fixture is committed so the suite runs from a clean checkout;
its base64 signature blobs are excluded from typos.

* bench(micro-benchmark): scope benches to pacquet's own parse paths

Retarget the added benches onto code pacquet owns, so the numbers move
on pacquet changes rather than only on dependency or build-profile
changes:

- packument/parse deserializes a registry packument into
  pacquet_registry::Package and hydrates the picked version, covering
  PackageVersions' lazy fragment capture and PackageVersion hydration.
- lockfile/parse_pnpm_lock parses via pacquet_lockfile.

Drop the gzip-inflate and SHA-512 benches: they measured zune-inflate
and ssri/sha2 directly, which pacquet code cannot move, and their
real-world cost is already covered by the download_dependency pipeline
bench. Drop the now-unused zune-inflate dependency.

* bench(micro-benchmark): guard the lockfile bench against a no-op parse

Assert the fixture parses to Some at setup, so a lockfile that loads as
None (absent, empty, or env-only main document) fails loudly instead of
letting the bench report a fast, meaningless throughput.

While here, take the throughput length from fs::metadata rather than
reading the whole file, source the filename from Lockfile::FILE_NAME,
and drop a call-site comment that repeated the callee's doc comment.

* bench(micro-benchmark): drop the unnecessary fixture comments

The typos-exclude reason, the packument provenance/refresh note, and the
lockfile-reuse note restate what the filenames and the bench doc
comments already convey. Remove them.

* bench(micro-benchmark): read the packument fixture through pipe-trait

Match the crate's existing pipe-trait style for the fixture read.
pipe-trait is already a dependency.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-12 22:03:54 +02:00
Zoltan Kochan
826366b7bc feat(releasing): native workspace release management in both stacks — change intents, release plans, per-package release lanes (#12953)
Implements phases 1-2 of the native monorepo versioning plan
(https://github.com/pnpm/pnpm/issues/12952, RFC https://github.com/pnpm/rfcs/pull/18)
in both stacks.

TypeScript: new @pnpm/releasing.versioning package (intent reader/writer for
changesets-compatible .changeset/*.md files with the additive 'none' decline;
the committed per-package consumed-intents ledger .changeset/ledger.yaml;
the release-plan assembler with direct bumps, dependent propagation via
materialized workspace: ranges under real semver semantics, fixed groups,
ignore, maxBump enforced against the real version distance, --filter
narrowing, per-package release lanes with cumulative stable-target
escalation and graduation; the applier with format-preserving
version-only manifest updates, changelog composition in repository storage
mode, ledger append, and intent GC with the prerelease retention exemption).
CLI: pnpm change / change status; bare pnpm version -r (--dry-run,
the new root pnpm lane command manages
versioning.lanes through the format-preserving workspace manifest writer. The versioning key is typed on PnpmSettings/Config and validated by
the workspace manifest reader.

Rust: new pacquet-versioning crate mirroring the engine (same file formats,
error codes, messages, and output), versioning settings on
WorkspaceSettings/Config, and new change/version/lane commands in pacquet-cli.
The npm-style pnpm version <bump> forms remain unported in pacquet (they
did not exist there before) and error with a clear message. The two deploy
install futures are boxed because Config grew past clippy's large-future
threshold.

Both stacks' engine test suites mirror each other one for one; integration
tests drive the real binaries through record -> plan -> release ->
lane -> graduation.
2026-07-12 21:55:49 +02:00
dependabot[bot]
48641d9750 chore(cargo): bump itertools from 0.14.0 to 0.15.0 (#12904)
Bumps [itertools](https://github.com/rust-itertools/itertools) from 0.14.0 to 0.15.0.
- [Changelog](https://github.com/rust-itertools/itertools/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-itertools/itertools/compare/v0.14.0...v0.15.0)

---
updated-dependencies:
- dependency-name: itertools
  dependency-version: 0.15.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-12 13:24:45 +02:00
dependabot[bot]
f560b25fc6 chore(cargo): bump open from 5.3.5 to 5.3.6 (#12903)
---
updated-dependencies:
- dependency-name: open
  dependency-version: 5.3.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-12 13:24:06 +02:00
dependabot[bot]
f03cc16783 chore(cargo): bump sigstore-sign from 0.9.0 to 0.10.0 (#12902)
Bumps [sigstore-sign](https://github.com/prefix-dev/sigstore-rust) from 0.9.0 to 0.10.0.
- [Release notes](https://github.com/prefix-dev/sigstore-rust/releases)
- [Changelog](https://github.com/sigstore/sigstore-rust/blob/main/release-plz.toml)
- [Commits](https://github.com/prefix-dev/sigstore-rust/compare/sigstore-sign-v0.9.0...sigstore-sign-v0.10.0)

---
updated-dependencies:
- dependency-name: sigstore-sign
  dependency-version: 0.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-12 13:23:22 +02:00
Alessio Attilio
411bbe89ff feat(registry-access): implement team command in both TypeScript and Rust stacks (#12789)
The team command communicates with the registry through the standard npm team API endpoints.  The scope:team format is parsed to separate the organization scope from the team name.  For mutation subcommands (create, destroy, add, rm) the registry URL is resolved per scope from the registries map with an optional --registry override, the auth header is resolved from the configured credentials honoring scoped credentials, and the request is sent with retry support and bounded response reads.  When an OTP is in play, the Rust side restricts redirects to the configured registry origins so the npm-otp header cannot leak to another host; the TypeScript fetch layer already strips it on cross-host redirects.  The ls subcommand dispatches to listing teams within an org when given @scope and to listing members of a specific team when given @scope:team.  Output supports three modes, the default human-readable listing, --parseable which emits newline-delimited names, and --json which emits structured arrays.

On the TypeScript side the command is registered in pnpm/src/cmd/index.ts and removed from the notImplemented list.  On the Rust side it is added as a CliCommand variant, routed in dispatch, and dispatched in dispatch_query.  Both sides include comprehensive tests covering all subcommands, error paths for 401 403 404 and 409 responses, empty results, and the three output formats.

pnpr serves the npm team API from each hosted registry's config-declared teams: GET /-/org/{scope}/team and GET /-/team/{scope}/{team}/user list teams and members, gated by the registry-level access with denials masked as not-found, while team mutations answer an explicit 403 since pnpr teams are config-managed.

@pnpm/cli.parse-cli-args no longer stops option parsing at an escape word (create, exec, test) that appears as another command's parameter, which previously made pnpm team create drop a trailing --registry option.

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-07-12 13:17:26 +02:00
blacksmith-sh[bot]
d32809261f .github/workflows: Migrate workflows to Blacksmith runners (#12951)
* Migrate workflows to Blacksmith

* ci: keep pacquet-ci test matrix on GitHub-hosted runners

The migration moved only the Windows leg of the pacquet-ci test matrix
to a Blacksmith runner while leaving the ubuntu and macOS legs on
GitHub-hosted runners. Revert that leg to windows-latest so the whole
matrix stays GitHub-hosted, matching the sibling ci.yml (whose
cross-platform test matrix was likewise left on GitHub-hosted runners)
and avoiding a Bencher testbed-name regression: the Stage Bencher test
durations step derives the testbed via ${TESTBED_OS%-latest}, a suffix
a Blacksmith label lacks, so the Windows testbed would have become
pacquet.blacksmith-4vcpu-windows-2025 instead of pacquet.windows.

Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>

---------

Co-authored-by: blacksmith-sh[bot] <157653362+blacksmith-sh[bot]@users.noreply.github.com>
Co-authored-by: zkochan <z@kochan.io>
Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
2026-07-12 10:42:04 +02:00
Zoltan Kochan
3acb421adf feat(pnpr): pnpr-native access control — strict token grammar and registry-scoped teams (#12790)
pnpr's access-control declaration is now pnpr-native, matching the
already-native routing config.

Token grammar: only the $-sigiled built-ins ($all, $authenticated,
$anonymous) are recognized; verdaccio's @-prefixed and bare alias
spellings are rejected at config load with a did-you-mean error, and
the $ namespace is reserved so a typo'd built-in cannot silently
become a username that admits nobody. Access lists and member lists
no longer whitespace-split: a YAML scalar is one token, multi-token
lists are YAML sequences, and an empty-string value is an error
pointing at [] or omission.

Teams: the global groups: block is replaced by registry-scoped teams.
Each hosted or upstream registry declares its own teams: map and
references it from its access lists as team:<name>; a bare token is a
username only. This closes the escalation where open registration let
anyone claim a username equal to a group name and inherit its grants,
and it makes cross-registry reuse explicit (YAML anchors) instead of a
global namespace. group:<name> gets a pointer at team:<name>, unknown
<type>: prefixes are rejected (htpasswd forbids ':' in usernames), and
an undeclared team reference is a startup error listing the declared
teams.

team: references are resolved to member sets at config load, so
access-list evaluation still needs only the caller's identity:
Identity drops its groups field and route classification, search, and
the resolver are untouched. The removed top-level groups: block is
rejected loudly, like the removed top-level packages: block, because
silently dropping it would change who may reach what on upgrade.
2026-07-12 01:38:00 +02:00
Zoltan Kochan
3e01bc5e0e feat(napi): pass the resolved directory to the readPackage hook (#12936)
* **New Features**
  * `readPackage` hooks can now receive an optional `resolvedDir` value for directory/file-based dependency resolutions.
  * Updated public type definitions and JSDoc to reflect the new `(manifest, resolvedDir?) => manifest` hook signature.
* **Bug Fixes**
  * Ensured the directory context is propagated consistently to `readPackage` for directory resolutions, while leaving it unset for registry-based resolutions.
* **Tests**
  * Added regression coverage verifying `resolvedDir` is provided for directory dependencies and omitted for registry dependencies.
2026-07-12 00:02:01 +02:00
Zoltan Kochan
1f530acb59 chore: update pnpm to v12.0.0-alpha.8 (#12946) v11.12.0 2026-07-11 23:05:25 +02:00
Zoltan Kochan
54efed02f7 fix(pacquet): close CLI gaps that broke the pnpm release workflow (#12944)
* fix(pacquet): close CLI gaps that broke the pnpm release workflow

The v11.12.0 release workflow failed because bundle-deps.ts invokes

  pnpm --config.inject-workspace-packages=true --config.node-linker=hoisted \
       --ignore-scripts --force --filter=pnpm --prod deploy <dir>

and pacquet's clap-based CLI rejected --ignore-scripts ahead of the
subcommand. Running the whole release.yml sequence locally against a
fresh build surfaced five gaps, all fixed here:

- Accept options anywhere on the command line. nopt parses
  position-independently, so pnpm accepts a command's options before
  the command name. The new flag_relocation pass moves pre-subcommand
  option tokens that aren't top-level grammar to directly after the
  subcommand before clap parses argv. Widths for value-consuming
  options come from the union of the subcommand arg tables; arity
  conflicts degrade to "no value" so a subcommand name can never be
  swallowed. External commands and post-`--` tokens are untouched.

- Honor --config.inject-workspace-packages and --config.node-linker
  in ConfigOverrides; both were silently dropped, so the shared-lockfile
  deploy failed with ERR_PNPM_DEPLOY_NONINJECTED_WORKSPACE.

- Give deploy --force pnpm's install semantics. Config::force now
  bypasses the installability check on the frozen and fresh install
  paths and in the hoisted walker (and clears the skipped-snapshot
  seed), so optional deps of every platform are materialized - that is
  how all `@reflink/reflink-*` variants reach the published
  dist/node_modules. The ERR_PNPM_CONFIG_CONFLICT_FROZEN_STORE_WITH_FORCE
  guard is ported alongside, as the frozen_store docs promised.

- Exclude dev dependencies from the hoisted linker under --prod. The
  hoist tree seeded from every importer dep map regardless of the
  included dependency groups, so the --prod deploy shipped 588 packages
  in dist/node_modules instead of 22. run_hoisted_linker now clears
  excluded importer dep groups from the lockfile before the walk,
  mirroring pnpm's include-filtered lockfile.

- Resolve catalog: specifiers in pack/publish from the workspace
  manifest when no updateConfig hook injected Config::catalogs;
  publishing `@pnpm/exe` failed with
  ERR_PNPM_CATALOG_ENTRY_NOT_FOUND_FOR_SPEC.

Verified locally by driving every release.yml step with the built
binary: build-artifacts (including the bundle-deps deploy that broke
CI), the three publish steps under --dry-run, config set/delete,
copy-artifacts, and make-release-description.

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

* refactor(cli): expose cli_command module crate-wide for tests

The dylint excessive-inline-tests lint rejects a cfg(test) re-export in
cli_args.rs; make the module pub(crate) and path-reference CliCommand
from the flag_relocation tests instead.

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
v12.0.0-alpha.8
2026-07-11 22:14:44 +02:00
Zoltan Kochan
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
C. Spencer Beggs
252f15e583 fix(resolver): stop hoisted peers from ignoring the declared peer range (#12932)
When deduplicating a missing peer onto the workspace's preferred
versions, hoistPeers matched candidates against '*' unless the wanted
range was an exact version. In a multi-importer workspace,
allPreferredVersions aggregates versions from every importer, so a peer
declared as ^1.0.0 inside one importer's closure could be auto-installed
as a 2.x resolved for another importer, silently producing a peer graph
that mixes incompatible majors. This survived pnpm/pnpm#12847 (which
fixed the single-importer case in resolvePeers) and is independent of
the pnpm/pnpm#12921 deadlock fix: the wrong version is chosen upstream
of peer resolution, during peer hoisting.

Both stacks now dedupe onto the highest preferred version that
satisfies the wanted range and fall back to resolving the range from
the registry when no candidate satisfies it. Non-semver specifiers
(workspace:, npm: aliases, dist-tags) keep the dedupe-to-highest
behavior.

Fixing this exposed a pacquet-only divergence the old behavior masked:
resolve_peers indexed a node's children into ParentRefs only by their
install alias, so an npm-alias child could never provide a peer under
its real package name below the importer level, and the binding fell
through to the importer-level hoisted entry. Descent-time children are
now indexed by both alias and real name, matching resolvePeers.ts.

Measured on the multi-importer reproduction: 46 poisoned snapshots on
pnpm 11.11.0, 0 with this change, in both the TypeScript CLI and
pacquet.

Additionally, hoistPeers now binds the same peer on a re-resolve with
an existing lockfile as on a fresh install of the same manifest: root
dependencies reused from the lockfile skip full resolution and were
invisible to the workspace-root branch of the hoist, so the picked
version depended on whether a lockfile was present. The root-dep table
falls back to the importer's wanted specifiers, and its entry type is
narrowed to HoistableRootDep — the fields hoistPeers reads, matching
the Rust port's WorkspaceRootDep.

Related to pnpm/pnpm#12847 and pnpm/pnpm#12921.

---------

Signed-off-by: C. Spencer Beggs <spencer@beggs.codes>
Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-07-11 11:17:21 +02:00
Zoltan Kochan
bcb8b5ace1 fix(pacquet): accept sibling-slot native binaries for the unscoped pnpm wrapper (#12930)
The global virtual store lays slots out as
links/<scope>/<name>/<version>/<hash>, with a literal @ placeholder as the
scope segment of unscoped packages (see format_global_virtual_store_path).
global_virtual_store_root_from_slot walked the slot path upward without
that placeholder, so for the unscoped pnpm wrapper it landed on @ where it
expected links, gave up, and native_source_trust_root fell back to the
wrapper's own slot. The wrapper's @pnpm/exe.<target> platform package is a
symlink to a sibling slot under links, so linking the native binary then
failed with "the native pnpm binary ... resolves outside ...". This is
exactly the packageManager-delegation path: any delegation to a different
v12 version aborted, which broke the Update Lockfile workflow after its
updater bumped the pin mid-job.

Derive the slot's links-relative path with format_global_virtual_store_path
- the formatter that laid the slot out - instead of a hand-rolled walk, so
the reader and writer of the layout cannot drift again. Scoped wrappers
resolved to the links root before and still do, and a platform-package
symlink escaping links entirely is still rejected.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 10:20:01 +02:00
Zoltan Kochan
3c6718b91d fix(resolver): break peer dep-path await cycles that span resolution passes (#12927)
The dep path of a package with peer dependencies is computed after the
dep paths of its peers, and peer cycles are broken by collapsing the
suffix to name@version. That cycle detection only sees the children of
a single resolvePeersOfChildren call, and since hoisted peer providers
stopped being walked in the root context (commit fecfe8334b), a peer
cycle between a project's own dependency and a hoisted provider - or a
provider resolved at its true tree position - spans two calls, so the
dep path calculations awaited each other's promise forever and
pnpm install hung, e.g. with electron-builder@26.15.3.

Record which dep path promise each calculation awaits (borrowing the
cache owner's edges for peers-cache hits) and, after the traversal,
resolve every promise on a remaining await cycle to name@version - the
same collapse in-call cycle detection applies. Installs that resolved
before are untouched: the breaker only fires on await graphs that could
never settle.

pacquet's synchronous walker already detects these cycles globally and
needs no code change; mirrored tests in both stacks pin the same dep
paths, and the electron-builder repro now produces byte-identical
lockfiles across the two implementations.

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

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 10:08:11 +02:00
Zoltan Kochan
7bb4e94dda chore: update pnpm (#12929) 2026-07-11 03:28:55 +02:00
Zoltan Kochan
4658133e19 fix(cli): stop pacquet bugs tests from opening the developer's browser (#12926)
* fix(cli): stop pacquet bugs tests from opening the developer's browser

The bugs happy-path integration tests spawned the real binary, whose
open_url launched xdg-open unconditionally, so every test run opened
https://github.com/test/pkg/issues (and friends) in the developer's
browser.

Thread the existing pacquet_network_web_auth::OpenUrl capability
through BugsArgs::run, replacing the hand-rolled per-OS launcher with
the shared Host provider (the open crate, as the web-auth flow already
uses). Port the happy-path coverage to unit tests driving run through a
recording OpenUrl fake — mirroring the mocked open module in the
TypeScript tests — and keep only the error paths, which never reach the
browser, as binary-level integration tests.

* style(cli): appease clippy pedantic in bugs tests

* test(cli): drain the recorded browser URLs between bugs tests

Tests that libtest runs on the same thread (--test-threads=1) would
otherwise observe URLs recorded by earlier tests through the shared
thread-local.
2026-07-11 03:14:17 +02:00
Zoltan Kochan
7da1dfc314 fix(pacquet): serialize same-name injected importer deps as plain file: refs (#12924)
* **Bug Fixes**
  * Improved lockfile generation for injected workspace dependencies: when the dependency alias matches the package name, lockfile entries now use a plain `file:<pkg>` reference instead of an alias-prefixed form.
  * Preserved the previous alias-based lockfile behavior when the alias differs from the package name.

* **Tests**
  * Added unit test coverage for both the matching-alias and differing-alias scenarios.
  * Updated end-to-end assertions to reflect the new expected lockfile output for injected workspace dependencies.
2026-07-11 02:18:31 +02:00
Ellen Agarwal
b2b1cf65b0 fix(cli): accept top-level --if-present and stop workspace installs scaffolding a root manifest (#12919)
Accept --if-present ahead of the script name, the way pnpm's option
parser does. The repo's own test-pkgs-branch script uses that spelling,
so every PR TS CI job died at argument parsing under the pacquet-based
pnpm v12. Declared top-level like the other run-scoped options but not
clap-global (run/stop/restart declare their own --if-present), merged
with the subcommand flags, validated like --resume-from/--no-bail.
Rejected for exec like pnpm, where dispatch would drop it silently.

Never scaffold a missing manifest inside a workspace. Gate State::init's
no-scaffold path on workspace membership (config.workspace_dir) rather
than a pnpm-workspace.yaml beside the missing manifest, so installs in
workspace member directories can't scaffold a package.json either —
pnpm never creates one anywhere in a workspace. Keep an empty object as
the in-memory stand-in instead of the init template: pnpm's add at a
rootless workspace root persists a manifest holding only the added
dependencies, and the template's failing test script would otherwise
ride along on any explicit save.

Together with pnpm/pnpm#12914 this unblocks the ci:test-branch job once
a pnpm v12 alpha carrying both is released and packageManager is bumped.
2026-07-11 02:12:17 +02:00
Zoltan Kochan
0609982b29 refactor(pacquet): ship the CLI under its final pnpm name (#12923)
* **New Features**
  * The CLI build and distributed archives are now `pnpm`, with updated dev commands and release artifacts.
  * Shell completions are consistently branded as `pnpm` and generate `pnpm completion-server` wiring.
* **Bug Fixes**
  * Version output/user-agent reporting now aligns with `pnpm`.
  * Diagnostics and retry-related error codes are updated to `ERR_PNPM_*`.
* **Documentation**
  * Updated E2E and integrated-benchmark guides to reference `pnpm` build outputs.
  * SBOM metadata now identifies the tool as `pnpm`.
* **Chores**
  * Updated CI E2E/benchmark workflows and test suites to run the `pnpm` binary.
2026-07-11 02:07:14 +02:00
Zoltan Kochan
6a85968c12 feat(pacquet): port the stage command and add its -/stage endpoints to pnpr (#12922)
Port `pnpm stage` (publish, list, view, approve, reject, download) to the
Rust CLI, mirroring the TypeScript command's flags, error codes, endpoints,
and output. `stage publish` reuses the publish pipeline (PublishArgs is
split into a reusable PublishFlags, and the pipeline returns summaries
instead of printing); approve/reject drive the shared OTP / web-auth flow;
download summarizes the tarball with the same traversal protections as
the TypeScript implementation. Also fixes the staged-publish route in
pacquet-publish to POST -/stage/package/:pkg (libnpmpublish's stage route)
instead of the regular packument PUT — previously unreachable because the
CLI hardcoded stage: false.

pnpr grows the server half: POST /-/stage/package/:pkg validates and
authorizes like a direct publish and holds the document under a UUID;
list/view/tarball inspect held records; approve replays the document
through the regular validate/stage/commit flow; reject deletes it. Records
persist under a reserved .staged/ namespace on the fs and S3 backends,
with stage-id validation ahead of any path or object key.

Shared plumbing: the capped response-body reader moves from the dist-tag
command into pacquet-network, and pacquet-pack exports its en-locale path
sort for the tarball summary.

Also fixes a broken test on main (semantic conflict between
pnpm/pnpm#12910 and pnpm/pnpm#12914): State::init no longer persists a
scaffolded root package.json when a pnpm-workspace.yaml sits next to the
missing manifest, so a verify-deps-before-run install can't turn the
workspace root into a selectable project with the init template's failing
test script.
2026-07-11 01:15:59 +02:00
Zoltan Kochan
84e3d3bffe chore: trust pnpm publishes and update pnpm to v12.0.0-alpha.6 (#12920) 2026-07-10 22:18:54 +02:00
Zoltan Kochan
2d7e723cff fix(ci): stage the Rust release binary outside the repo root before archiving (#12918)
The Release pnpm (Rust) workflow renamed the built binary to `pnpm` at the
repo root before tarring it. Since the pacquet/ directory was renamed to
pnpm/, that `mv` moved the binary *into* the existing `pnpm/` source
directory instead, so the non-Windows archives contained the whole Rust
source tree. The publish job then extracted a directory where it expected a
binary and failed with EISDIR in generate-packages.mjs
(https://github.com/pnpm/pnpm/actions/runs/29113833296).

Stage the binary in a scratch directory and tar from there, keeping the
archive layout (a plain `pnpm` binary at the root) unchanged.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 21:15:55 +02:00
Zoltan Kochan
78c2245fd4 ci: run typos in the Rust pre-push hook (#12915)
Spelling was the only Rust CI gate with no local counterpart in the
pre-push hook, so typos surfaced for the first time in CI. Add a typos
block mirroring the "Rust CI / Spell Check" job (same dirs), with the
same skip-if-not-installed behavior as the dylint and taplo checks —
`just init` already installs typos-cli.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 20:13:09 +02:00
Zoltan Kochan
eee7c9a0fd fix: don't spawn an install outside a project; port verify-deps-before-run to pacquet (#12910)
pnpm (TypeScript): when pnpm was executed in a directory without a
package.json (most commonly through a mistyped command falling back to
`pnpm run`), checkDepsStatus found no workspace state and reported the
dependencies as outdated. With verify-deps-before-run set to "install"
(the default), that spawned a `pnpm install` which could only fail with
NO_PKG_MANIFEST, and the parent process crashed with a raw execa stack
trace. Report "unknown" (upToDate: undefined) instead when there is no
root manifest, no workspace, and no project list — runDepsStatusCheck
already skips the check in that case.

pacquet: port the verify-deps-before-run gate (config setting with the
'install' default, deps-status check for the run/exec paths, the
install/prompt/error/warn actions, and the script-env recursion guard),
including the same no-project skip. Also stop `pacquet install` from
scaffolding a package.json over an existing package.yaml manifest,
which the gate's default install action exposed.
2026-07-10 20:12:35 +02:00
Zoltan Kochan
2b02764105 feat(pacquet): support the changed-packages filter selector ([<since>]) (#12914)
Port git-diff project selection from @pnpm/workspace.projects-filter to
pacquet, so a --filter "...[origin/main]" selector selects the workspace
projects whose files changed since the given git ref instead of failing
with unsupported_diff_selector. This unblocks the ci:test-branch job,
whose "pn --filter=...[origin/main] -r list --depth=-1 --json" discovery
step fails on every PR with the pacquet-based pnpm exe.

The port mirrors upstream getChangedProjects:

- "git diff --name-only <since> -- <workspace_dir>" runs in the
  selector's {dir} (else the workspace root); the repository root the
  diff paths are relative to is the parent of the nearest .git
  directory, falling back to the nearest .git file (worktrees).
- Each changed file maps to the nearest enclosing workspace project.
  Files matching changedFilesIgnorePattern are ignored entirely;
  projects whose changes all match testPattern are selected without
  their dependents. Both pattern sets are matched with wax, the glob
  crate pacquet already uses where upstream uses micromatch/fast-glob.
- A failing git diff surfaces git's stderr under pnpm's
  ERR_PNPM_FILTER_CHANGED error code.

FilterWorkspaceProjectsOptions / FilterProjectsOptions gain
workspace_dir, test_pattern, and changed_files_ignore_pattern, threaded
from Config by the recursive commands and deploy. The testPattern /
changedFilesIgnorePattern settings are read from pnpm-workspace.yaml
and PNPM_CONFIG_* env vars, overridable by the global --test-pattern /
--changed-files-ignore-pattern flags, and excluded from the global
config.yaml - all matching the TypeScript CLI.

All upstream tests around the feature are ported: the three
projects-filter unit tests (from their known_failures stubs into
filter::tests::changed_packages), the two monorepo e2e tests
(testPattern respected by the test script; changedFilesIgnorePattern
respected incl. the empty CLI override), and the config-reader tests
for both settings. The recursive run/exec integration tests now assert
the selector scopes execution to the changed project. Verified against the
TypeScript CLI on this repository: both stacks select the identical
project set for ...[origin/main], with and without the two pattern
flags.
2026-07-10 20:12:09 +02:00
Zoltan Kochan
8e17c3d366 refactor: rename the pacquet/ directory to pnpm/ (#12913)
Pure directory move plus path fixups: the Rust port ships as pnpm v12,
so the source tree now lives at pnpm/ (alongside pnpm11/, the frozen
TypeScript line). No identifiers change in this pass — crate names
(pacquet-*), the pacquet bin, PACQUET_VERSION, the @pacquet/* npm
package names v11's runPacquet spawns, the .pacquet virtual-store dir,
the benchmark harness's clone dir, and the pacquet-*.yml workflow
filenames (npm trusted publishing is bound to them) all stay for a
follow-up.

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

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

Check the global self-update directory before taking the active-version no-op
path, and add coverage for both cases: already installed globally and missing
globally. Apply the same fix to pacquet's self-update, and deduplicate the
TypeScript global-install check into a shared findGlobalPnpmInstallDir helper.
2026-07-10 17:17:20 +02:00
Khải
4627fa77ca feat(pacquet): login (#12851)
Ports pnpm's login / adduser command (with 2FA) to the Rust pacquet
stack, reaching parity with the TypeScript @pnpm/auth.commands login.

The command probes web-based login (POST -/v1/login) first and falls
back to classic username/password/email login (PUT
-/user/org.couchdb.user:{name}) on HTTP 404/405. Either path satisfies
a two-factor (OTP) challenge through pacquet-network-web-auth — a
browser round-trip for web auth, or a prompted one-time password
otherwise — and the granted token is written to auth.ini, keyed to the
scope when --scope is given.

TypeScript's function-closure DI is reworked into pacquet's trait seam:
the OTP / web-auth effects reuse pacquet-network-web-auth's eight
self-less capability traits (composed on one Sys parameter), credential
prompts sit behind the new PromptInput / PromptPassword capabilities (the
raw dialoguer reads; the spawn_blocking handoff and error classification
stay in the prompt_line wrapper, outside the DI seam), auth.ini I/O
reuses logout's FsReadToString / FsWrite, and globalInfo maps to the
Reporter seam on the pnpm:global channel. The two registry requests go
over the shared ThrottledClient, tested against a mockito server; only
the effects a fixture can't stage portably sit behind the Sys seam.

Hardening beyond a literal port (both no-ops for normal input): the
success line redacts registry credentials / escape sequences (matching
logout and ping), and auth.ini keys and values are JSON-quoted exactly
as the ini package does, so a newline-bearing token cannot inject extra
auth entries, a registry path containing = still keys its token, and
every entry round-trips.

All 17 upstream login.test.ts cases are ported as unit tests
(TEST_PORTING.md updated).
2026-07-10 16:58:50 +02:00
Zoltan Kochan
fb680c35fb ci: stop bumping pacquet config dep and self-update pnpm from next-12 (#12912)
The update-lockfile workflow still bumped the `@pnpm/pacquet` config
dependency, which has since been removed from the repo, so the daily run
would have re-added it. It also ran `pnpm self-update latest`, but the
repo is now on the pnpm v12 prereleases published under the `next-12`
dist-tag, so `latest` would have downgraded the `packageManager` and
`devEngines.packageManager` pins back to v11.

Drop the config-dependency bump and self-update from `next-12` instead,
updating the step names, commit message, and PR text accordingly.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 16:52:42 +02:00
Zoltan Kochan
c2248eb9ba ci: derive latest-<major> dist-tag from the version in the Tag workflow (#12911)
The Tag workflow hardcoded the latest-11 dist-tag (each release branch
carried its own hardcoded major), so dispatching it from the wrong branch
applied the wrong line's tag: tagging 10.34.5 from main pointed latest-11
at a v10 release (https://github.com/pnpm/pnpm/issues/12906).

Derive latest-<major> from the version input instead, and fail fast when
the requested tag names a different major than the version, or when a
plain 'latest' tag would move backwards to an older major.
2026-07-10 16:52:23 +02:00
Zoltan Kochan
7074b6b338 ci(pacquet): draft a GitHub release with install-script assets (#12907)
* ci(pacquet): draft a GitHub release with install-script assets

The Rust pnpm releases only published to npm, so get.pnpm.io's
install.sh / install.ps1 — which download pnpm-<platform>-<arch>[-musl]
archives from GitHub releases — could not install v12 at all.

Add a github-release job that repackages the build artifacts under the
v11 asset naming scheme (binary renamed from pacquet-<target> to pnpm /
pnpm.exe at the archive root), attests them, and drafts a GitHub
release. Draft keeps publication a deliberate maintainer action, same
as the staged npm wrappers; make_latest: false keeps a v12 prerelease
from taking the "Latest" badge from the stable v11 line.

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

* ci(pacquet): ship the release archives under their final pnpm names

Rename the build-job archives from pacquet-<target> to pnpm-<target>
with a plain pnpm / pnpm.exe binary at the archive root — the layout
the v11 releases use and the install scripts expect. The github-release
job now uploads the build artifacts byte-for-byte as attested, instead
of repackaging (and re-attesting) them.

Since every archive now holds an identically-named binary, the publish
job extracts each into a scratch dir and moves the binary out under its
target-qualified name (this also drops the third-party unzip action).
The generated native package dirs keep the pacquet- prefix so they
stay out of the pacquet/npm/pnpm* wrapper glob.

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 16:38:46 +02:00
Benjamin Staneck
52b57ade2f feat(cli): support --store-dir in pacquet (#12889)
Accept global and dotted store directory overrides with pnpm-compatible path and config precedence.

Expose overrides to updateConfig hooks and keep global virtual store derivation in sync. Preserve the raw empty CLI value for hooks while restoring the platform's volume-aware default store for the effective configuration.

Cover workspace-relative paths, quoted home paths, frozen installs, global virtual stores, hook replacement, and empty overrides.

Related to pnpm/pnpm#11633
2026-07-10 16:27:17 +02:00
Gavin Lee
43711ce654 fix(outdated): skip local lockfile refs (#12834)
`pnpm outdated` now skips dependencies whose lockfile ref is local (`link:`, `file:`, or `workspace:`), even when the manifest specifier is a plain semver range.

This covers workspace-linked packages such as:

```yaml
devDependencies:
  private-workspace-pkg:
    specifier: ^1.0.0
    version: link:../private-workspace-pkg
```

In that shape the dependency is already resolved locally, so asking the registry for a latest version can fail for private workspace packages that are not published. Refs pnpm/pnpm#12827.

Follow-up commits after review:

- The local-ref early return runs before any other per-dependency work, and the `isLocalRef` helper documents why local refs have no registry "latest".
- Added a test locking in the other side of the behavior: a dependency whose *current* ref is local but whose *wanted* ref is a registry version is still checked and reported (addresses CodeRabbit's suggestion).
- **pacquet parity:** pacquet's `outdated` already skips local refs — `ImporterDepVersion::ver_peer()` returns `None` for `link:`/`file:`, so `collect_outdated` drops those deps before any registry fetch. This PR adds a Rust test (`current_versions_omit_local_refs`) proving that behavior, so both stacks now cover the same scenario. No pacquet behavior change was needed; the TypeScript fix brings pnpm in line with pacquet.

---------

Co-authored-by: Zoltan Kochan <zoltankochan@gmail.com>
2026-07-10 16:10:43 +02:00
Trevor Burnham
a897ef728d feat(pacquet): support custom fetchers from pnpmfile (#12846)
Support custom fetchers from pnpmfiles in pacquet, with delegate-envelope parity in the TypeScript CLI (related to pnpm/pnpm#11685)

Pacquet already supported custom resolvers via its Node.js worker IPC but
lacked the fetcher counterpart. This extends the same protocol pattern:

- Define a CustomFetcher trait and get_custom_fetchers() on PnpmfileHooks
- Add fetchers/fetcher target dispatch to the worker's NDJSON protocol
  and JS runner, mirroring the existing resolver path; the hook is
  invoked with the TS-parity args fetch(cafs, resolution, opts, fetchers)
  (cafs/fetchers are null over IPC)
- Implement NodeJsCustomFetcher bridging the trait to the worker
- Create CustomFetcherPicker for consulting fetchers in declared order
- Consult custom fetchers before the built-in dispatch on both the
  frozen-lockfile and fresh-lockfile install paths; hook-load failures
  abort the install (PNPMFILE_FAIL)
- Support delegation: { delegate: <resolution> } rewrites the resolution
  for the standard tarball/git path; non-delegate responses and
  custom-typed delegates fail the install
- lockfile: add LockfileResolution::Custom preserving custom-typed
  resolution objects verbatim, so custom resolvers/fetchers can round-trip
  them; unclaimed custom-typed resolutions fail with the TS-parity
  UNSUPPORTED_RESOLUTION_TYPE error
- TypeScript: pickFetcher now accepts the { delegate: <resolution> }
  envelope from custom fetchers (the portable delegation form), and
  hooks.types exports CustomFetcherDelegation

Full CAS fetch in pacquet (where a fetcher produces file content directly
rather than delegating) requires a streaming protocol extension
(separate follow-up).
2026-07-10 16:06:09 +02:00
Zoltan Kochan
5333a2543d fix(pacquet): restore Bit workspace/capsule install parity with the v11 engine (#12899)
* **New Features**
  * Installations now generate and preserve `file:` injected workspace dependency mappings and carry them through `.modules.yaml`.
  * Manifest `link:` dependencies are now materialized as symlinks in project `modulesDir` (supports absolute, relative, and `link:.`, plus safe `modulesDir` validation).
  * Workspace components missing `package.json` now link to all other sibling root members.
* **Bug Fixes**
  * Empty/whitespace “no range” specifiers no longer break dependency resolution.
  * Peer-suffixed metadata is now resolved via peer-stripped lookups when needed.
  * Hoisted workspace importers are retained even when workspace-hoisting is disabled.
  * Trusted importer IDs prevent incorrect unsafe-path rejection during isolated symlinking.
2026-07-10 16:02:33 +02:00
Zoltan Kochan
601ab8499f fix(pacquet): put workspace root node_modules/.bin on script PATH (#12891)
pnpm adds the workspace root's node_modules/.bin to PATH for scripts and
exec in every workspace project (extraBinPaths in the config reader), so
root-level dev tools like tsgo are callable from member packages. pacquet
had the extra_bin_paths plumbing through run/exec/dlx/pack/publish, but
nothing ever populated it, so member scripts failed with 'command not
found' for workspace-root binaries.

Populate extra_bin_paths in Config::current from the discovered workspace
dir, mirroring pnpm's config reader.

The gap survived because the behavior-defining upstream tests were never
ported: pnpm/test/recursive/run.ts 'pnpm recursive run finds bins from
the root of the workspace' and the config reader's 'extraBinPaths' test.
Port them — regression tests for root-bin resolution in plain and
filtered-recursive run, the bin-priority rule (a project's own
node_modules/.bin outranks the workspace root's), and the config-level
population — and record them in plans/TEST_PORTING.md.

Also scope the typos invocation in the just ready recipe to pacquet and
pnpr, matching CI's spell-check job; the bare invocation failed on
generated files under pnpm11 that cspell covers instead.
2026-07-10 14:26:57 +02:00
Zoltan Kochan
d2ed60afba test: make git-resolver and dlx git tests immune to GitHub flakiness (#12885)
The git-resolver unit tests hit live github.com by default: the mocks for
fetchWithDispatcher and graceful-git existed, but beforeEach restored the
real implementations. When GitHub throttles the shared CI runner IPs, the
HEAD probe in isRepoPublic() fails (it has zero retries and treats any
error as "private"), and resolution silently degrades from the hosted
tarball to a git clone, changing the resolved id and failing the
assertions. This broke the main branch build at
https://github.com/pnpm/pnpm/actions/runs/29026897310/job/86153736091

The mocks are now the default: fetch reports every repository as public
and graceful-git serves ls-remote output from a fixture table captured
from the real repositories, with the same commit hashes the assertions
already expected. The private-repo-over-HTTPS test now calls
mockFetchAsPrivate() explicitly instead of relying on a real 404 for the
nonexistent github.com/foo/bar. The one live-network case in
parsePref.test.ts got the same treatment. The suite drops from ~40s to
under half a second and runs offline.

The dlx e2e test stays a genuine end-to-end test against GitHub, but its
allowBuild list now approves both resolution shapes of the same commit
(codeload tarball and git+https clone), so the resolver's
rate-limit-induced fallback no longer trips the
GIT_DEP_PREPARE_NOT_ALLOWED gate, as seen in
https://github.com/pnpm/pnpm/actions/runs/29029971938/job/86170695840

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 00:53:03 +02:00
Zoltan Kochan
25da8c8cde chore: update package manager to pnpm v12 (#12811) 2026-07-10 00:49:52 +02:00
Zoltan Kochan
8e1e4c0aae chore(release): 11.11.0 (#12886)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
v11.11.0
2026-07-09 22:29:10 +02:00
C. Spencer Beggs
fecfe8334b fix(resolver): keep hoisted peer providers out of the root peer context (#12847)
With autoInstallPeers, peers resolved inside a dependency subtree are
attached to the root importer's direct dependencies so other subtrees
can reuse them. Those entries alias tree nodes that already have a
position deep in the graph, and the peer-resolution pass walked them a
second time as root children. The two walks raced on the shared
per-node dep-path state, so a package inside a self-contained closure
could get its peers bound to the root project's incompatible version
of a peer instead of the provider next to it in the tree, producing a
lockfile that mixes both versions and a peer mismatch at run time.

The peer-resolution pass now keeps the attached providers visible as
root-level peer providers but resolves their own peers only at their
true tree position, falling back to the root context only when that
position was pruned by the peers cache. All pruned providers are
resolved in a single fallback pass, because a pass only detects peer
cycles among its own children and mutually peer-depending providers
would otherwise await each other's dep path forever. The fix lands in both stacks:
the TypeScript `@pnpm/installing.deps-resolver` and pacquet's
`resolving-deps-resolver` crate.

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

---------

Signed-off-by: C. Spencer Beggs <spencer@beggs.codes>
Co-authored-by: Zoltan Kochan <zoltankochan@gmail.com>
2026-07-09 22:00:20 +02:00
Yashas Gunderia
a8ad82d4dd fix: register pn alias in generated completions (#12861)
Register the pn short alias in generated shell completion scripts.

pnpm exposes pn as a binary alias, but completion generation only registered
pnpm with the supported shells. This meant zsh completions generated by
pnpm completion zsh registered pnpm only, so pn did not receive the same
completion function.

Post-process the tabtab-generated scripts to register pn alongside pnpm for
bash, fish, pwsh, and zsh, and cover each shell in the completion generator
tests.

Fixes pnpm/pnpm#11955.

---------

Co-authored-by: ychampion <ychampion@users.noreply.github.com>
Co-authored-by: Zoltan Kochan <zoltankochan@gmail.com>
2026-07-09 19:12:10 +02:00
David Barratt
14332f07dd fix: don't silently drop locked optional deps on resolution failure (#12855)
When re-resolution of an optional dependency fails (for example, a
registry mirror whose packument has not synced the pinned version yet),
the resolver silently skipped the dependency. The parent's snapshot was
then rewritten without the edge and the pruner erased the locked
entries, so identical inputs produced different lockfiles depending on
which machine ran the install, and a frozen install on another host had
no entry to link for the affected package.

Rethrow the resolution error instead of skipping when the wanted
lockfile already holds an entry that satisfies the wanted range. An
optional dependency that never resolved keeps the skip-on-failure
behavior.

pacquet previously failed loudly on every optional-dependency
resolution failure. It now skips never-locked optional deps like pnpm,
emitting the same skipped-optional-dependency log (with the parents
chain and the same top-level reporter output), and keeps the loud
failure — with the same hint — when the lockfile holds a satisfying
entry, so the two stacks agree on both cases.

Closes pnpm/pnpm#12853

---------

Co-authored-by: Zoltan Kochan <zoltankochan@gmail.com>
2026-07-09 19:07:23 +02:00
maroKanatani
3067e4f6b4 perf(npm-resolver): stop retaining raw registry bodies in the memoized fetch cache (#12870)
The npm-resolver memoizes its metadata fetch for the whole resolution phase
and clears it only once, via clearResolutionCache(), after resolution ends.
Each memoized FetchMetadataResult carried jsonText, the raw registry response
body kept only to mirror the response to disk without re-serializing meta.
With an unbounded memo cache living for the entire phase, every raw body
stayed resident until the end. On large cold-cache graphs that fetch full
metadata (minimumReleaseAge / trustPolicy) these bodies reached hundreds of
MB and OOM-killed 3GB CI runners. v10 did not keep the raw text on the
memoized result, so this was a v10-to-v11 regression.

Enforce the invariant at the cache boundary: the memoized fetch
(memoizeFetchMetadata, replacing p-memoize, which has no hook between compute
and store) caches a body-less shallow clone of each result, so jsonText
reaches only the caller that initiated the fetch — the one that writes the
disk mirror — and dies with that call. The cache is structurally incapable of
pinning a body regardless of who consumes the fetch. Peak RSS drops by ~30%
(back to the v10 level) with a byte-identical lockfile. Cache-hit callers see
jsonText undefined and fall back to JSON.stringify(meta) in
prepareJsonForDisk, which is functionally equivalent because loadMeta
re-derives etag from the headers line on read. The replacement preserves
p-memoize's semantics: in-flight dedup, eviction of rejections, and cache
clearing. Follows the projection applied to the verifier caches in
pnpm/pnpm#11878.

Closes pnpm/pnpm#12868.
2026-07-09 19:06:06 +02:00
Yashas Gunderia
c70e33e887 fix: allow git build approvals by repository (#12856)
Allow git-hosted build approvals to match the package/repository portion of a git depPath before the resolved commit hash.

This lets trusted git repositories continue running build scripts after branch updates without adding a new hash-qualified `allowBuilds` entry for every resolved commit. Exact depPath rules still work, disallow rules still win first, and package-name-only allow rules remain limited to trusted registry-style depPaths so git artifacts are not approved by name alone.

Fixes pnpm/pnpm#12367.

---------

Co-authored-by: ychampion <ychampion@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 16:48:31 +02:00