Commit the pnpm-managed Cargo source block. The block is a function of
Cargo.lock, so an install regenerates it byte for byte and the tracked
`.cargo/config.toml` stops reporting as modified after every install. A new
Rust CI step fails if the committed block and the lockfile drift apart.
Cargo now resolves every crate through `.pnpm/crates` and falls back to its
own registry nowhere, so each job that runs cargo needs an install behind it.
Eight did not have one: both cargo-unused jobs, the micro-benchmark, the
integrated benchmark's build and executor jobs, and the three release builds.
The new `install-crates` action gives them one and narrows the JavaScript half
with `--filter pacquet`, which a filter does not do to the Cargo half.
The release builds go through `cross`, which mounts the checkout at
`/project`. pnpm links each crate into `.pnpm/crates` with a relative symlink,
so a store outside the checkout stops resolving under that mount. Those jobs
install into a store inside the checkout instead, on Linux, the one host where
cross containerizes the build at all.
The two workflows that commit no longer discard the block. It is tracked
content now, and a run that changes it should carry the change.
Claude-Session: https://claude.ai/code/session_01Gg6uVUzLw1MniCLC81TQjP
Claude-Session: https://claude.ai/code/session_01HwJS1pAz9HHQpJWEJAiaUu
Co-authored-by: Claude Opus 5 (1M context) <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>
Picks up pnpm/update#4: the pin bump now happens first, so the
dependency update and the refreshed lockfile are produced by the pnpm
version the update PR moves the repository onto.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Bump pnpm/update to the release that adds the
update-pnpm-minimum-release-age input, and set it to 0. pnpm 12
defaults minimumReleaseAge to 24 hours and self-update deliberately
ignores the repo's minimumReleaseAgeExclude, so the job could never
bump the pin to a pnpm release published the same day: the next-12
dist-tag silently resolved to the newest mature version instead.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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.
The action reproduces this workflow's pattern (persistent chore/update-lockfile
branch off latest main, force-pushed each run, one open PR reused via a
gh-pr-list check), so the bespoke Prepare/Check/Commit/Create steps collapse into
a single `uses:` step. Input mapping:
- update-deps: latest + refresh-lockfile: true -> delete node_modules and
pnpm-lock.yaml, then `pnpm update --latest -r` for a full refresh.
- github-actions: true -> also bump the actions pinned in .github/workflows and
action.yml. UPDATE_LOCKFILE_TOKEN already carries the workflow scope (it pushes
the meta-updater's workflow changes today).
- Node.js: derived from devEngines.runtime (no hardcoded `26`), staying on the
pinned major.
- update-pnpm: next-12 -> `pnpm self-update next-12`.
- post-update: pnpm update-manifests.
- changesets left on (default): `pnpm update --changeset` records a changeset
only when a published package's production/peer deps change, and stays silent
on devDependency-only bumps.
- token: secrets.UPDATE_LOCKFILE_TOKEN, pushed via a single-use credential helper
(works with persist-credentials: false; referenced only in the action's push
step, never during install).
The action is SHA-pinned (76f98d72, tagged v0), matching how actions/checkout and
pnpm/setup are pinned here.
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.
* 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>
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>
* chore: also bump Node.js, pnpm, and pacquet in update-lockfile workflow
* chore: address PR review feedback on update-lockfile workflow
- Base the update branch on an explicitly fetched origin/main
- Don't persist the write token during install; push with explicit URL
- Detect open PRs via gh --json instead of grepping table output
- Add a concurrency guard to serialize dispatch + scheduled runs
Resolves all 30 zizmor alerts reported on main after #11607:
- template-injection (19): move `${{ ... }}` interpolations in `run:` blocks
to `env:` so untrusted-ish values (workflow_dispatch inputs, github.ref_name,
github.actor) can't break out of shell quoting.
- artipacked (8): add `persist-credentials: false` to `actions/checkout` in
audit, benchmark, ci, codeql-analysis, docker, release, test workflows.
`update-lockfile.yml` keeps the persisted token (later step pushes to a
branch) with a `zizmor: ignore[artipacked]` comment and justification.
- dependabot-cooldown (1): add a 7-day cooldown so brand-new (potentially
malicious) Actions releases don't get auto-PR'd day-of-release.
- ref-version-mismatch (1): `bluwy/release-for-reddit-action` SHA pointed at
the `v2` tag, not a non-existent `v2.0.0`. Fix the comment.
- superfluous-actions (1): mark `softprops/action-gh-release` with a
`zizmor: ignore` and justification — the release pipeline is sensitive and
the action is battle-tested; we're not swapping it for `gh release` here.
Verified locally with `zizmor --persona regular .github` (online audits on):
No findings to report. Good job! (2 ignored, 32 suppressed)
---
Written by an agent (Claude Code, claude-opus-4-7).
## Summary
Migrates CI workflows from `pnpm/action-setup` + manual `pn runtime set node …` + `pn install` to the new combined `pnpm/setup` action (see https://github.com/pnpm/setup/pull/1).
`pnpm/setup` installs pnpm and the JS runtime in one step. It also runs `pnpm install` automatically when a `package.json` is present, so per-workflow install steps are dropped. When the `runtime` input is set, the action passes `--no-runtime` to `pnpm install` so the matrix-selected Node version isn't shadowed by a different `devEngines.runtime` pin.
## What changed
| Workflow | Migration |
|---|---|
| `test.yml` | `pnpm/setup` with `runtime: node@${{ inputs.node }}`. Verify-Node step asserts the matrix version stayed active. Verify-npm step retained as canary (npm comes from the runner image, not the pnpm-installed runtime). |
| `ci.yml` | `pnpm/setup` (no `runtime` input — `devEngines.runtime` in package.json handles the Node pin). |
| `release.yml` | `pnpm/setup` with `runtime: node@26.0.0`. |
| `benchmark.yml` | `pnpm/setup` with `runtime: node@26.0.0`. |
| `audit.yml` | `pnpm/setup` with `install: false` — audit only needs pnpm itself, not `node_modules`. |
| `update-lockfile.yml` | `pnpm/setup` with `install: false` — the job deletes `pnpm-lock.yaml` and regenerates it via `--lockfile-only`, so the action's auto-install would be wasted. |
| `update-latest.yml` | Untouched — it only uses npm, no pnpm setup needed. |
## Caveats / things to watch
- **npm availability.** `pnpm runtime set node` does not extract npm. The runner image's pre-installed Node toolchain provides `npm` on PATH; if a future runner image change removes that, dlx-style git-hosted dependency tests in `test.yml` will fail. The `Verify npm` step in `test.yml` is the canary.
## Related upstream change
- [pnpm/setup#3](https://github.com/pnpm/setup/pull/3) — added the `install` input so callers like `audit.yml` and `update-lockfile.yml` can opt out of the action's auto-install.
Adds `devEngines.runtime` to pin the Node.js version (24.6.0, `onFail: download`) the project uses for development, so contributors don't have to manage Node versions manually.
CI changes that come with it:
- Bumps pnpm to **11.1.1** and `pnpm/action-setup` to a bootstrap that ships `@zkochan/cmd-shim` 9.0.3. The cmd-shim update is required because the previous shim's `exec cmd /C` got mangled by Git Bash's MSYS path conversion (`/C` → Windows path), which broke any `pn …` invocation from `shell: bash` on Windows.
- Switches the install step to `pn install --no-runtime` so the per-test-matrix Node version chosen by `pn runtime -g set node …` isn't overridden by the project-pinned 24.6.0.
- Adds a `Verify Node version` step that asserts `pn node -v` matches the matrix's Node.
* fix: ensure PNPM_HOME/bin is in PATH during pnpm setup
When upgrading from old pnpm (global bin = PNPM_HOME) to new pnpm
(global bin = PNPM_HOME/bin), `pnpm setup` would fail because the
spawned `pnpm add -g` checks that the global bin dir is in PATH.
Prepend PNPM_HOME/bin to PATH in the spawned process env so the
check passes during the transition.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update pnpm to v11 beta 2
* chore: update pnpm to v11 beta 2
* chore: update pnpm to v11 beta 2
* chore: update pnpm to v11 beta 2
* fix: lint
* refactor: rename _-prefixed scripts to .-prefixed scripts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update root package.json to use .test instead of _test
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* ci: update action-setup
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Third-party cmd shims (e.g., npm's rimraf.cmd) call node.exe from
within IF/ELSE blocks in batch files. When node resolves to node.cmd
instead of node.exe, Windows batch file chaining breaks with
"The system cannot find the path specified."
On Windows, hardlink node.exe directly into the bin directory.
On non-Windows, symlink the node binary directly.