* chore: update dependencies, Node.js, pnpm, and GitHub Actions
* ci: run the wasm nightly through pnx and dylint through rustup
pnpm 12.11 runs scripts with the toolchain from rust-toolchain.toml first
on PATH, so `cargo +nightly-2026-08-27` reached a plain cargo that does not
know the `+toolchain` syntax. The wasm and runtime-probe builds now run
`pnx --package=rust@nightly-2026-08-27 cargo`, which installs the nightly
with the wasm target, so the rustup install and the job-wide
RUSTUP_TOOLCHAIN go.
cargo-dylint builds its lint library through the rustup proxies, which
pick the nightly the library pins and set RUSTUP_TOOLCHAIN for
dylint-link. Under pnpm it got the workspace's stable cargo instead. CI
now runs `cargo dylint` directly, as `just dylint` does, and the unused
pipeline goes. The pre-push hook, which runs as a pnpm script, puts the
directory holding `rustup` first on PATH for its dylint check.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* ci: bundle the esm loader before the dylint check
`pnpm-deps-restorer` embeds `cas-loader.mjs.inc`, which the prepare
script of `@pnpm/esm-loader` generates. `pnpm pipeline` installed it
implicitly; the direct `cargo dylint` run needs the install as a step.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* test: run pnpm in a StackBlitz WebContainer on the ecosystem e2e cron
WebContainers run Node.js in the browser, and their fs errors and
node:sqlite differ from Node.js in ways no local test reproduces. Two
such differences broke pnpm install there (pnpm/pnpm#15649).
The new harness boots a WebContainer in headless Chromium through
`@webcontainer/api`, mounts the files the published pnpm package ships,
and runs install without a lockfile, a repeat install, a reinstall from
the store, add, remove, and list, failing on any non-zero exit. It
lives next to the Rust ecosystem-e2e harness, and a webcontainer job in
the same daily workflow runs it against the bundle the build job makes.
It needs StackBlitz's servers, so it stays off the per-PR checks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(webcontainer-e2e): open the page on the address the server listens on
The server binds 127.0.0.1, while the page was opened at localhost,
which reached it only through Chromium falling back from ::1.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(webcontainer-e2e): reinstall offline and check the removal
A reinstall that could reach the registry passed even when the store
index was unreadable, and a remove that left is-odd in place passed too.
The reinstall now runs with --offline, and a new step checks that is-odd
left package.json and no longer resolves. The server helper now rejects
when it cannot listen and documents that.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* chore: install the Rust dependencies with pnpm
Turn on `cargo.enabled` so `pnpm install` installs the crates `Cargo.lock`
pins alongside the JavaScript dependencies. pnpm links the registry crates
into `.pnpm/crates/crates-io` and the git-sourced ones into
`.pnpm/crates/git`, then writes a source replacement block into
`.cargo/config.toml` that points Cargo at both.
The `node-semver` fork stays patched in. pnpm installs git-sourced crates
since pnpm/pnpm#14694, which shipped in the 12.4.1 the repository pins, so
enabling this no longer costs the fork's `<=` range and `Ord for Bound`
fixes.
Document the workflow, including the two things a contributor hits first:
`pnpm install` shells out to `cargo`, so it fails when `cargo` is off
`PATH`, and the generated block leaves the tracked `.cargo/config.toml`
modified after every install.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SDxJNVEeyUBb4pcepEpGcj
* ci: initialize Rust before pnpm installs dependencies
Every `pnpm install` now shells out to `cargo metadata`, and there is no
way to opt one out: `cargo.enabled` is read from `pnpm-workspace.yaml`
only, no CLI flag or `PNPM_CONFIG_*` variable overrides it, and `--filter`
does not narrow the Cargo half. So each job whose install runs gets the
pinned toolchain first. `pnpm/setup` installs unless told not to,
`pnpm/update` runs its own install, and `pnpm pipeline` installs before it
runs anything.
The step has to precede the install rather than follow it. The rustup
action ends with `git restore .`, which would otherwise wipe the source
replacement block back out of `.cargo/config.toml`.
Keep that block out of the two workflows that commit. It points at the
gitignored `.pnpm/crates`, so a commit carrying it would break every
Rust-only job and every checkout that has not installed. Both workflows
also decide whether to commit at all by reading `git status --porcelain`,
which the block would make non-empty on every run. Each discards it right
before that check: the release PR in a step of its own, the lockfile
update through the `post-update` command pnpm/update runs between its
install and its commit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SDxJNVEeyUBb4pcepEpGcj
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
zizmor's self-repository audit flags every `uses: ./.github/...` reference
now that GitHub has a dedicated `$/` form. The `$/` form resolves against the
workflow's own commit rather than the runtime checkout, so it cannot load an
action cloned into the workspace at runtime, and GitHub treats it as pinned.
Rewrites all 22 references (18 local actions, 4 reusable workflow calls).
With the `$/` form the runner downloads the whole repository as an action
archive at job setup, and that download fails on any broken symlink in the
tree. The four broken symlinks were all test fixtures: the directory-fetcher
and cafs tests now copy their fixture into a temp dir and create the broken
symlink there, and the has-not-outdated-deps fixture drops two dangling
node_modules links that `pnpm outdated` never followed.
pnpm's GitHub Actions dependency discovery only followed `./` references
into local actions and reusable workflows; both stacks now follow `$/` too.
The Ecosystem E2E workflow failed on every run since it was added, for
two stacked reasons.
The matrix jobs relied on the runner image's system Node (v20), while
the built pnpm bundle needs at least v22.13 for node:sqlite, so every
scaffold died with ERR_UNKNOWN_BUILTIN_MODULE before any cell ran. The
jobs now install the devEngines.runtime pin via pnpm/setup, the same
way the workflow's build job already did.
With scaffolding unblocked, the angular and nuxt global-virtual-store
cells failed in both binaries: "@angular/build" requires tslib and
"@nuxt/vite-builder" (since v4) imports unplugin without declaring
them, and the global virtual store only exposes declared dependencies.
Since neither entry is in the "@yarnpkg/extensions" compatibility DB
yet, carry them as pnpm-specific compatibility packageExtensions in
both stacks, merged after the upstream DB and gated by the same
ignoreCompatibilityDb setting. Drop an entry once the upstream DB or
the package itself declares the dependency.
The first pnpm 12 beta release used pnpm 12.0.0-alpha.21 to publish a wrapper whose workspace name is `pacquet`. That version predates `publishConfig.name`, so npm trusted publishing was attempted for `pacquet` instead of `pnpm` and the root package failed after all native packages had already been published.
Use pnpm 11.18.0, the released TypeScript CLI that supports `publishConfig.name`, for release tooling. Update every `pnpm/setup` consumer to the revision that can install v11 from GitHub release archives. Make the Rust publishing loop query each effective published name and skip versions already on npm, while preserving hard failures for registry errors other than 404. This allows a moved beta tag to resume the partial release and reach the dependent GitHub release job.
* chore: update dependencies, Node.js, pnpm, and GitHub Actions
* chore: hold typescript on 6.x for typescript-eslint compatibility
The update-lockfile job bumped the `typescript` catalog entry to 7.0.2,
which typescript-eslint refuses to load against — it hard-errors at load
with "typescript-eslint does not support TS 7.0" — failing the
Compile & Lint job.
The 7.x compiler is already used for the build via
`@typescript/native-preview` (tsgo); the `typescript` npm package only
serves the JS-API tooling (eslint, jest), which needs 6.x until
typescript-eslint adds TS >=7.1 support
(https://github.com/typescript-eslint/typescript-eslint/issues/10940).
Revert the catalog entry to 6.0.3 and add `typescript` to
`update.ignoreDeps` so the update-lockfile workflow stops re-bumping it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: drop the stray typescript@7.0.2 from the lockfile
openpgp declares an optional `typescript` peer (>=4.7). After the catalog
was reverted to 6.0.3, pnpm's incremental resolution kept openpgp latched
onto the leftover typescript@7.0.2 node, leaving it (and its native
platform packages) in the lockfile and tripping peer-mismatch review bots.
Re-point openpgp's optional peer to 6.0.3 so typescript@7.0.2 is gone
entirely; the openpgp subtree is now identical to the base branch.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* ci: update pnpm/setup to the v12 standalone-executable revision
Since pnpm/setup#11 the action downloads the standalone pnpm v12
executable directly from the registry — no npm bootstrap, no system
Node.js dependency. Every workflow moves to the new revision except
update-latest.yml: its RELEASE_TOOL_PNPM_VERSION deliberately pins a
proven pnpm 11, which only the pre-v12 action revision can install, so
it keeps the old pin until that version moves to v12.
* ci: operate the Tag workflow with pnpm v12
RELEASE_TOOL_PNPM_VERSION moves to 12.0.0-alpha.19 and the pnpm/setup
pins to the v12 revision. The commands the workflow runs are rewritten
to forms both CLIs accept, since the Rust CLI does not (yet) expose
--registry, --allow-build on add, or a post-subcommand --dir:
- --registry becomes --config.registry (read by both stacks)
- the add build-script approval moves from `--allow-build=@pnpm/exe` to
an allowBuilds entry in the scratch dir pnpm-workspace.yaml
- --dir moves ahead of the subcommand
Each rewritten command was verified against the 12.0.0-alpha.19 binary,
including the verify-upgrade sequence end to end (add of
`@pnpm/exe@11.16.0` with the build approval, bin shim run) and the
config set/get/delete round trip for the auth token.
* revert: operate the Tag workflow with pnpm v11 again
Moving the operator to v12 required rewriting commands around three
CLI surfaces pacquet is missing (--registry, --allow-build on add,
post-subcommand --dir). The Rust CLI must gain those for parity instead
of the workflow papering over them — tracked in
https://github.com/pnpm/pnpm/issues/13242. The workflow keeps the
pre-v12 pnpm/setup pin and pnpm 11.13.1 until that lands.
Move non-npm-trusted-publishing GitHub Actions jobs to Blacksmith runner labels.
Use larger Blacksmith runners for heavier build, Docker, benchmark, Windows, and macOS jobs, while leaving the npm OIDC trusted-publishing publish jobs on GitHub-hosted runners. npm trusted publishing currently does not support self-hosted runner providers, so the TypeScript pnpm publish job, Rust pnpm publish job, and pnpr publish job keep their GitHub-hosted labels.
Keep benchmark artifact names stable so follow-up workflow_run jobs on the default branch keep matching the producing benchmark jobs.
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.
* **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.
The TypeScript pnpm CLI freezes at v11; pnpm 12 will be the Rust pacquet
port. To make that split legible, all TypeScript source, test, and build
directories move under a new top-level pnpm11/ directory. The name states
the version boundary rather than implying a behavioral fork, since the two
stacks are meant to behave identically.
Scope is source-only: the shared workspace root stays at the repo root.
pnpm-workspace.yaml, package.json, pnpm-lock.yaml, .pnpmfile.cjs,
.meta-updater, __patches__, .changeset, .husky, and the lint/spell configs
remain in place, so one pnpm workspace and one Cargo workspace still span
all three products. pnpr/client and pacquet/tasks/registry-mock stay as
cross-product workspace members.
Rewiring the move required:
- pnpm-workspace.yaml globs prefixed with pnpm11/
- root package.json script paths, eslint.config.mjs, tsconfig.lint.json,
.gitignore, and CODEOWNERS updated
- .meta-updater/src/index.ts literals repointed (pnpm11/pnpm/package.json,
pnpm11/__utils__, pnpm11/__typings__, and the main package directory)
- regenerated every moved package's repository/homepage URL via meta-updater
- pnpm11/pnpm/bundle-deps.ts and __utils__/scripts/src/typecheck-only.ts
climb one more level to reach the repo root
.meta-updater stays at the repo root because @pnpm/meta-updater resolves
its config at <cwd>/.meta-updater/main.mjs.
TS CI (.github/workflows/ci.yml) now only runs when pnpm11/-relevant paths
change, via a dorny/paths-filter changes job plus a TS CI / Success
aggregate gate; branch protection should require only that gate.
* fix(ecosystem-e2e): run the build script via absolute /bin/sh
The build stage sets the child PATH to prepend the project's
node_modules/.bin, and Rust resolves a bare program name against that child
PATH. Spawning `sh` therefore let a dependency-installed `.bin/sh` run in
place of the system shell — running unintended code and masking build
pass/fail. Use the absolute `/bin/sh` so the orchestrating shell is always
the system one; the script it runs still finds framework bins through the
prepended PATH.
* ci(ecosystem-e2e): build pnpm and pacquet once, share via artifacts
The stack matrix rebuilt the pacquet release binary and the pnpm bundle in
every one of its seven jobs. Add a single build job that compiles pacquet,
the harness, and the pnpm bundle once and uploads them as an artifact; the
per-stack jobs download it and run, so the multi-minute Rust and bundle
builds happen once per run instead of seven times.
* feat(ecosystem-e2e): add harness installing real JS stacks across pnpm/pacquet and layouts
Adds `pacquet/tasks/ecosystem-e2e`, a Yarn-PnP-style ecosystem test that
installs, builds, and serves real framework scaffolds (Next.js, Vite,
Angular, Astro, SvelteKit, Nuxt, React Router 7) across the cross product
of {pnpm, pacquet} x {isolated, global virtual store}.
The binary axis catches pnpm/pacquet parity gaps; the layout axis catches
breakage introduced by the global virtual store. Each cell scaffolds the
project once, installs with the binary under test, runs the build, then
boots the production server and probes it over HTTP — so a green cell means
the produced node_modules works at runtime, not just at bundle time.
Runs on a daily cron (one job per stack), not per-PR: the installs are slow
and track upstream framework releases, so a red cell is investigated rather
than treated as a merge blocker.
* style(ecosystem-e2e): use ASCII ellipsis in comments for dylint
* fix(ecosystem-e2e): address PR review findings
- workflow: wrap `pnpm/bin/pnpm.cjs` for the `--pnpm` shim; the built bundle
is `dist/pnpm.mjs`, so the previous `dist/pnpm.cjs` path never existed and
the shim would have failed to launch the repo-built pnpm.
- keep: `--keep` now reuses an already-scaffolded template instead of
re-running the generator into a non-empty directory (which fails).
- serve: retry HTTP 4xx/5xx until the deadline rather than failing on the
first one — dev servers can briefly answer error statuses while warming up.
- serve: the probe reads only the status line, not the whole response body.
- stacks: pin the create-astro, sv, nuxi, and create-react-router generators
to a major version instead of tracking the latest tag, matching the stated
reproducibility intent for the scheduled run.
* fix(ecosystem-e2e): scrub subprocess env, bound CI job, fix README fence
- security: scaffold/install/build/serve run third-party lifecycle code, so
they now launch via `sandboxed_command`, which clears the inherited
environment down to a small allowlist (PATH, HOME, proxy/cert/locale vars,
plus PORT/HOST for servers). An unattended dependency build script can no
longer read ambient CI secrets such as the workflow token.
- workflow: add a 60-minute job timeout so a hung build or serve subprocess
can't pin a runner indefinitely.
- docs: give the README's grid code block a language identifier (MD040).
* fix(ecosystem-e2e): spawn serve argv directly and reset cell log per run
- serve: run the tokenized serve command directly off the project's
node_modules/.bin instead of joining tokens into a `sh -c` string, so
argument boundaries survive and a token that needs quoting can't break the
launch. The spawned PID is the server itself, so teardown still works.
- log: truncate cell.log at the start of each run so reruns (notably under
--keep) don't interleave with stale output.