Commit Graph

245 Commits

Author SHA1 Message Date
Zoltan Kochan
d8a79a9c30 feat(registry): add auth/dist-tag/publish endpoints + wire TS tests onto pnpm-registry (#11914)
Lands the pieces of the npm registry protocol that pnpm-registry was missing, and switches the TypeScript test harness off verdaccio onto pnpm-registry. `@pnpm/registry-mock` (the npm package) is untouched.

### Server-side additions (`registry/crates/pnpm-registry`)

- `PUT /-/user/org.couchdb.user:<name>` — adduser / login, returns a Bearer token. In-memory user + token stores.
- `PUT /:pkg` — publish (scoped + unscoped). Base64-decodes `_attachments`, merges into the existing packument, writes manifest + tarball atomically. 100 MiB body limit.
- `GET /-/package/:pkg/dist-tags` + `PUT/DELETE /-/package/:pkg/dist-tags/:tag` — rewrites the on-disk packument so tag changes survive a restart.
- `Authorization: Bearer` and `Authorization: Basic` both identify the caller.
- Per-package access policy (wax glob patterns). Defaults mirror `@pnpm/registry-mock`'s `config.yaml`: `@private/*` and `@pnpm.e2e/needs-auth` require auth; everything else is anonymous read, authenticated write. Enforced on every packument / version-manifest / tarball GET and every write endpoint.

### TypeScript-test migration

- `__utils__/jest-config/with-registry/globalSetup.js` keeps `prepare()` from `@pnpm/registry-mock` (still needed for the tempy storage path written into the runtime-config yaml — `getIntegrity` reads it from there) but spawns `pnpm-registry` instead of verdaccio. `addUser`, `addDistTag`, `getIntegrity`, `REGISTRY_MOCK_*` from registry-mock work as-is — they're plain npm-wire-protocol HTTP calls.
- Binary lookup follows pacquet's pattern: `PNPM_REGISTRY_BIN` env override, then `target/release/pnpm-registry`, then `target/debug/pnpm-registry`.
- CI test job (`.github/workflows/test.yml`) installs the Rust toolchain via the existing `./.github/actions/rustup` composite action and builds `pnpm-registry --release` before tests run. Per-platform — Linux and Windows in the matrix each build their own.
2026-05-25 09:40:09 +02:00
Zoltan Kochan
b9de85dcb6 ci(pacquet): drop pnpm comparison and self-compare on main from integrated-benchmark (#11913)
The pnpm-CLI baseline was useful while pacquet was slower than pnpm; now
that pacquet is the perf target itself, comparing against pnpm on every
run is noise. Drop --with-pnpm from every scenario step.

When the workflow runs on main, HEAD and main point at the same commit,
so a HEAD-vs-main comparison is wasted work. Resolve the target list at
job level: pacquet@HEAD on main, pacquet@HEAD pacquet@main everywhere
else (PRs, workflow_dispatch from non-main). The Bencher upload already
filters to pacquet@HEAD, so the single-target result still lands on the
main baseline as before.
2026-05-24 22:12:29 +02:00
Zoltan Kochan
add6c794f1 feat(registry): implement pnpm-registry server and adopt it in pacquet's test mock (#11898)
Creates a working pnpm-compatible npm registry server (verdaccio analogue, in Rust) — and replaces `@pnpm/registry-mock`'s Node + Verdaccio launcher in pacquet's test setup with the new binary, against `@pnpm/registry-mock`'s shipped storage.

### What `pnpm-registry` does
- **HTTP server** (axum + tower-http) with the three endpoints pnpm/npm clients need:
  - `GET /<pkg>` — packument (`/{name}` and `/{scope}/{name}`)
  - `GET /<pkg>/<version-or-tag>` — single-version manifest, resolves `dist-tags` and rewrites `dist.tarball` to point at this server
  - `GET /<pkg>/-/<tarball>` — tarball, streamed
- **Two modes:**
  - **Proxy** — fetches missing packuments/tarballs from a configurable upstream (defaults to `https://registry.npmjs.org`), caches to disk
  - **Static** (`--static`) — serves the storage directory verbatim, 404s on cache miss
- **Verdaccio-shaped on-disk storage** (`<root>/<pkg>/package.json` + flat tarballs) — drop-in compatible with the storage `@pnpm/registry-mock` publishes
- **Tarball streaming** — cache hits stream off disk; cache misses tee upstream chunks into a temp file via an mpsc channel and forward them to the client at the same time, atomically renaming on success and abandoning on upstream error or client disconnect
- **Tuned HTTP client** — wraps `pacquet_network::ThrottledClient::new_for_installs()`, inheriting pnpm's tuned defaults (`User-Agent: pnpm`, HTTP/1.1, hickory DNS, connection-pool tuning, concurrency semaphore)
- **Gateway-style status mapping** — `is_timeout()` → 504, `is_connect()` → 503, everything else (incl. upstream 5xx) → 502. No proxy-side retry (the pnpm client already has `fetch-retries`; stacking retries would only multiply latency on real failures).

### What changed in pacquet
- `pacquet/tasks/registry-mock` now spawns `pnpm-registry` against `node_modules/@pnpm/registry-mock/registry/storage-cache` (proxy mode with `npmjs.org` upstream and a 1-year packument TTL — matching `@pnpm/registry-mock`'s `'**': proxy: npmjs` verdaccio config). No more Node, no more Verdaccio, no more `launch.mjs`, no more process-tree walk to kill child verdaccios.
- `@pnpm/registry-mock` stays as a devDep — only for the storage data it ships, not the launcher.

### Tests
- **36 pnpm-registry tests** (12 unit + 7 against `@pnpm/registry-mock` storage in static mode + 17 mockito-based proxy/cache/streaming): packument rewrite, version-manifest resolution, tarball streaming (large body, cache finalize, mid-stream upstream error, client disconnect mid-stream, concurrent fetches → one cache file), gateway status mapping (504/503/502), stale-cache fallback on upstream failure, TTL refresh, invalid-package-name 400, scoped vs unscoped routing.
- **Full pacquet test suite** (2043 tests) runs green against `pnpm-registry`-backed mock.

### CI
- `pacquet-ci.yml` and `pacquet-codecov.yml` path filters now include `registry/**` (so registry-only PRs trigger the workspace CI); typos checker covers `registry` too. The workflow name stays "Pacquet CI" but a header comment explains the intentional cross-stack scope.
- `just registry-mock launch` pre-builds with `cargo nextest run --no-run` (workspace-wide) so its fingerprint matches what `just test` will later need — without this, Windows MSVC fails with `os error 5` trying to re-link the running `pnpm-registry.exe`.

### Crates.io name reservations (from the original scaffold commit)
- [`pnpm-registry`](https://crates.io/crates/pnpm-registry) — published from this repo
- [`pnpm-registry-cli`](https://crates.io/crates/pnpm-registry-cli) / [`pnpm-registry-server`](https://crates.io/crates/pnpm-registry-server) — placeholder stubs, name reservation only
2026-05-24 21:18:09 +02:00
Zoltan Kochan
389dae8382 ci: fix zizmor ref-version-mismatch on action-gh-release (#11888)
The dependabot bump to v3.0.0 updated the pinned commit hash but left
the trailing version comment as v2.5.0.
2026-05-23 22:48:09 +02:00
dependabot[bot]
a5b1ac783f chore(deps): bump the github-actions group with 4 updates (#11854)
Bumps the github-actions group with 4 updates: [github/codeql-action](https://github.com/github/codeql-action), [taiki-e/install-action](https://github.com/taiki-e/install-action), [garnet-org/action](https://github.com/garnet-org/action) and [zizmorcore/zizmor-action](https://github.com/zizmorcore/zizmor-action).


Updates `github/codeql-action` from 4.35.4 to 4.35.5
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](68bde559de...9e0d7b8d25)

Updates `taiki-e/install-action` from 2.78.0 to 2.78.1
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](e1c4cd4211...184183c240)

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

Updates `zizmorcore/zizmor-action` from 0.5.3 to 0.5.5
- [Release notes](https://github.com/zizmorcore/zizmor-action/releases)
- [Commits](b1d7e1fb5d...a16621b09c)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.35.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: taiki-e/install-action
  dependency-version: 2.78.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: garnet-org/action
  dependency-version: 2.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: zizmorcore/zizmor-action
  dependency-version: 0.5.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-23 19:43:23 +02:00
Zoltan Kochan
bf581bb0a5 ci(bencher): enforce PR thresholds and grant checks: write (#11883)
- Add `--start-point-clone-thresholds` to the non-main upload arms
  so PR/feature branches inherit thresholds configured on main; pair
  it with `--err` so a sample over the upper boundary fails the job.
- Add `checks: write` to the three workflows that call `bencher run`.
  On main pushes (no `--ci-number`, not a PR event) Bencher falls back
  to creating a GitHub Check on the commit; without the permission it
  exits 1 with "Failed to create GitHub Check".
2026-05-23 18:49:39 +02:00
Zoltan Kochan
4088de0433 ci(bencher): record benchmark results to Bencher (#11875)
* ci(bencher): record benchmark results to Bencher

Tracks both stacks in one Bencher project (`pnpm`), under separate
testbeds (`pnpm`, `pacquet`).

- `benchmarks/bench.sh` emits a hyperfine-shaped `bencher-results.json`
  combining the six pnpm scenarios.
- `benchmark.yml` adds `push: branches: [main]` so each merge updates
  the `pnpm` baseline, then uploads via the Bencher CLI.
- `pacquet-integrated-benchmark.yml` adds the same main-push baseline
  for `pacquet`, combines the four scenario JSONs into a Bencher
  report, and stages it into the existing artifact.
- `pacquet-integrated-benchmark-comment.yml` uploads PR results from
  the trusted `workflow_run` context so fork PRs are covered too.

Requires `BENCHER_API_TOKEN` in repo secrets; workflows no-op with a
`::notice::` if it's missing.

* ci(bencher): allow workflow_dispatch to upload pacquet results

The inline Bencher upload was gated to `event_name == 'push'`, which
meant manual dispatch from a feature branch ran the bench but skipped
the upload. Both push and workflow_dispatch execute in the base-repo
privilege context, so it's safe to upload from both — the fork-safety
gate only needs to keep `pull_request` runs out.

On dispatch from a non-main branch we record into the ref name with
`--start-point main --start-point-reset`, matching the pnpm bench's
branch policy.

* ci(bencher): treat workflow_dispatch on main as the main baseline

Two small fixes from PR review:

- `benchmark.yml`: manual dispatch from `main` was falling into the
  non-main branch arm, recording `--branch main --start-point main
  --start-point-reset` — equivalent to forking main from itself.
  Treat it like a `push` event so a manual run on main updates the
  baseline directly. Matches the pacquet workflow's branch policy.
- `bench.sh`: emit a stderr warning when `jq` is missing instead of
  silently skipping `bencher-results.json`. Keeps behaviour optional
  for local users but makes the skip discoverable.

* bench: rename scenarios with explicit state axes

Replaces the hyperfine-leaking names (`clean-install`, `frozen-lockfile`,
`peek`, `gvs-warm`, …) with a consistent grid that spells out every
state the benchmark depends on:

- "Fresh" — node_modules wiped at start (future variants will start
  with a populated node_modules).
- "Install" vs "Restore" vs "Add new dep" — the work being measured.
- "hot/cold cache + hot/cold store" — both pnpm directories,
  spelled out separately because they're distinct on disk.
- "isolated linker" — nodeLinker mode (future variants will cover
  `hoisted` and `pnp`).

The slugs map directly from the clap-derived kebab-case names, so
`--scenario=fresh-restore-cold-cache-cold-store-isolated` is the new
CLI surface. Updates land across the Rust orchestrator
(`BenchmarkScenario`), `benchmarks/bench.sh`, the pacquet workflow,
`benchmarks/README.md`, and `pacquet/CONTRIBUTING.md` so the names
agree end-to-end.

Adds a justified `#[allow(clippy::enum_variant_names)]` on the enum
because every variant currently shares the `Fresh` prefix; the lint
will stop firing once `Filled*`/`Resynced*` counterparts land.

Bencher's stored history for the old benchmark names will become
orphaned and can be archived in the UI.

* bench: linker-first slug shape with dot-separated axes

Reshapes the scenario identifiers so the linker mode is the leading
group: `<linker>.<action>.<cache state>.<store state>`. Dots separate
the four axes the bench varies, and `isolated-linker.*` /
`gvs-linker.*` sort together in any dashboard that groups by prefix.
Future buckets (`hoisted-linker.*`, `pnp-linker.*`) will slot in
without disturbing the existing names.

GVS is its own top-level bucket rather than a sub-variant of
isolated — its perf profile differs enough to chart separately.

Renames:

- `clean-install` → `isolated-linker.fresh-install.cold-cache.cold-store`
- `full-resolution` → `isolated-linker.fresh-install.hot-cache.hot-store`
- `frozen-lockfile` → `isolated-linker.fresh-restore.cold-cache.cold-store`
- `frozen-lockfile-hot-cache` → `isolated-linker.fresh-restore.hot-cache.hot-store`
- `peek` → `isolated-linker.fresh-add-dep.hot-cache.hot-store`
- `gvs-warm` → `gvs-linker.fresh-restore.hot-cache.hot-store`

Each Rust variant now carries `#[value(name = "…")]` so clap accepts
the dotted CLI form (`--scenario=isolated-linker.fresh-install.cold-cache.cold-store`).

Display labels follow the slug structure: `Isolated linker: fresh
install, cold cache + cold store` and `GVS linker: fresh restore,
hot cache + hot store`.

The `#[allow(clippy::enum_variant_names)]` is renewed; 5 of 6 variants
share the `Isolated` prefix today. Once `Hoisted*` / `Pnp*` buckets
land the lint will stop firing on its own.

* style: apply rustfmt after scenario rename

The longer match-arm pattern produced by the linker-first rename
exceeded the rustfmt width budget. Auto-format breaks the
`&["install", "--frozen-lockfile"]` body onto its own line so the
arm stays within the limit.
2026-05-23 15:19:49 +02:00
Khải
815e507d67 ci(integrated-benchmark): scenarios without lockfiles (#11838)
* ci(benchmark): run all six integrated-benchmark scenarios

Wires `clean-install`, `full-resolution`, `peek`, and `gvs-warm` into
`pacquet-integrated-benchmark.yml` so per-PR runs cover the same scenario
set the manual `benchmark.yml` workflow already exercises via
`benchmarks/bench.sh`. Requested for #11837, where the perf delta affects
the resolution-bound scenarios (`firstInstall`, `withWarmCache`,
`withWarmModules`, `updatedDependencies`) that the prior two-scenario set
did not measure.

Each scenario gets its own step with a 10 min hyperfine timeout (same
rationale as the existing steps) and writes per-scenario report copies
that the summary step concatenates into `SUMMARY.md`.

* ci(benchmark): drop peek and gvs-warm scenarios

Keep only the two new no-lockfile scenarios (`clean-install`,
`full-resolution`) on top of the existing `frozen-lockfile` and
`frozen-lockfile-hot-cache`. #11837's perf change is in the
fresh-lockfile install path, which only runs when resolution runs — i.e.,
exactly the no-lockfile scenarios. `peek` mutates an existing lockfile
and `gvs-warm` is a frozen-lockfile variant; neither exercises the
affected path, and including them only costs per-PR CI wall time.

* fix(bench): pin packages: ['.'] in synthesized pnpm-workspace.yaml

The integrated-benchmark clones each pacquet revision's source tree into
`<bench_dir>/pacquet/`, which on the pnpm/pnpm monorepo includes upstream
test fixtures like
`workspace/project-manifest-reader/__fixtures__/invalid-package-json/package.json`
— intentionally malformed JSON used to exercise pnpm's manifest reader.

Without a `packages:` field, both pnpm's `findPackages.ts:28` and
pacquet's `crates/workspace/src/projects.rs:128` default to `[".", "**"]`,
so the fresh-resolve install path's `find_workspace_projects` walk
descends into the cloned source tree and trips on the bad fixture:

  Error: pacquet_package_manifest::serialization_error
    × installing dependencies
    ╰─▶ expected `,` or `}` at line 3 column 3

The walk only runs on the fresh-lockfile branch (`install.rs:628-630`),
which is why frozen-lockfile and frozen-lockfile-hot-cache stay green
while clean-install and full-resolution fail every time.

Pin `packages: ['.']` in the synthesized manifest so enumeration stays
at the workspace root. The benchmark's installs are single-project,
so this doesn't narrow anything the install actually needed to see.
Fixtures supplied via `--fixture-dir` that already declare `packages:`
keep their own value.

* ci(benchmark): bump no-lockfile scenarios to 20 min

Clean-install and full-resolution go through pacquet's fresh-resolve
install path, which is currently ~3-5x slower than pnpm on the
`alotta-files` fixture (pnpm/pnpm#11832). Hyperfine's default 1 warmup
+ 10 timed runs across three benchmark targets (pacquet@HEAD,
pacquet@main, system pnpm) projects to ~13 min wallclock for these
two scenarios, putting the previous 10 min cap right on the edge.
Doubling to 20 min keeps the per-step timeout meaningful as a stuck-
install detector without losing CI time when the bench is healthy.

The frozen-lockfile steps stay at 10 min — they don't traverse the
slower fresh-resolve path.

* fix(bench): drop --no-frozen-lockfile from full-resolution scenario

Pacquet doesn't expose `--no-frozen-lockfile` (only `--frozen-lockfile`,
`--prefer-frozen-lockfile`, and `--no-prefer-frozen-lockfile`). Passing
it makes clap reject the install:

  error: unexpected argument '--no-frozen-lockfile' found
    tip: a similar argument exists: '--frozen-lockfile'

The flag was redundant for this scenario anyway: full-resolution starts
every iteration with no lockfile on disk (init() skips the lockfile when
`lockfile_enabled()` is false; cleanup removes it; `lockfile=false` in
the synthesized npmrc/workspace prevents writing one). With no lockfile
present the frozen path is unreachable regardless of the flag, so both
tools take fresh resolution by definition. Fold full-resolution into
clean-install's bare `install` arm.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-22 08:03:51 +02:00
Khải
fd564a43b9 ci(benchmark): consolidate (#11741)
Unify the two install-benchmark stacks (`benchmarks/bench.sh` and `pacquet/tasks/integrated-benchmark/`) into one shared Rust orchestrator. Scenario, fixture, workspace-manifest, install-script, and report generation live in one place so changes propagate to both stacks.

Scenario sets per workflow are unchanged: `benchmark.yml` keeps measuring the same 6 scenarios; `pacquet-integrated-benchmark.yml` keeps measuring the same 2. Both verdaccio and live-npm registry modes are preserved; neither is removed in favor of the other. All six scenarios accept both `pacquet@<rev>` and `pnpm@<rev>` targets.
2026-05-21 21:12:29 +02:00
Zoltan Kochan
fecaee0b35 chore: update pnpm and add pacquet to config deps (#11765) 2026-05-21 18:45:01 +02:00
Zoltan Kochan
f279d77d60 fix(pacquet/cli): patch --version literal during release build (#11812)
The `pacquet --version` string is a hardcoded clap attribute in
`cli_args.rs`. It didn't get bumped for the 0.2.2 release, so the
published binary still reports 0.2.1. Bump the literal to 0.2.2 and
add a release-workflow step that rewrites the attribute from
`inputs.version` before `cross build`, so future releases stay
correct automatically. A trailing `grep -F` fails the job loudly if
the regex stops matching after a future refactor of the attribute.
2026-05-21 14:02:45 +02:00
Zoltan Kochan
b206a15395 feat(installing): delegate fetch / import / link to pacquet when configured (#11734)
When `configDependencies` declares pacquet (under either the unscoped `pacquet` or the scoped `@pnpm/pacquet` alias), pnpm delegates the fetch / import / link / build phases of an install to the pacquet Rust binary. Pnpm keeps owning dependency resolution — pacquet's resolver isn't ready yet — and hands pacquet a freshly-written lockfile to materialize.

Covered install shapes:

- frozen install (`tryFrozenInstall` → pacquet, no resolve needed)
- default isolated `nodeLinker` (`installInContext`: lockfileOnly resolve via JS, then pacquet)
- hoisted `nodeLinker` (same resolve-then-materialize shape)
- workspace partial install (subset of workspace projects mutated)
- agent-server install (`@pnpm/agent.client` resolves, pacquet materializes)

```yaml
# pnpm-workspace.yaml
configDependencies:
  "@pnpm/pacquet": "^0.2.0"     # or unscoped `pacquet`
```

## How it works

- `installing/commands/src/runPacquet.ts` resolves the platform binary via `createRequire(realpath(.pnpm-config/<name>/package.json))` — same algorithm the upstream wrapper uses, but skipping the JS shim's extra Node startup.
- Pacquet's NDJSON stderr is forwarded through `@pnpm/logger`'s global `streamParser` so `@pnpm/cli.default-reporter` renders its events the same way it renders pnpm's own. Non-JSON stderr lines pass through verbatim.
- A few pnpm-side log emits (`importing_done` placeholder, `pnpm:summary`) are suppressed when pacquet will take over so the reporter doesn't close streams or lock in empty diffs before pacquet's real events arrive. Pacquet's duplicate `pnpm:progress status:resolved` events are filtered on the resolve-then-materialize paths so the reporter doesn't double-count.
- `installing/deps-installer/src/install/index.ts` gates the delegation on a `runPacquet?: () => Promise<void>` callback in `StrictInstallOptions`. The CLI layer in `installing/commands/src/installDeps.ts` constructs the callback, threaded through both the single-project and workspace-recursive paths.
- The `pacquet` and `@pnpm/pacquet` npm packages ship the same JS shim from `pacquet/npm/pacquet/scripts/generate-packages.mjs`; per-platform binaries stay under the existing `@pacquet/<plat>-<arch>` scope and aren't duplicated.
2026-05-19 20:56:15 +02:00
Khải
61e724cef3 ci(pacquet): pass --workspace --all-targets to clippy and check (#11735)
Co-authored-by: Claude <noreply@anthropic.com>
2026-05-19 08:24:07 +02:00
Zoltan Kochan
471b59025d chore: update pnpm to v11.1.3 (#11719)
* chore: update pnpm to v11.1.3

* fix: lock pacquet version
2026-05-18 17:23:32 +02:00
dependabot[bot]
85ceff2383 chore(deps): bump the github-actions group across 1 directory with 7 updates (#11642)
Bumps the github-actions group with 7 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [actions/upload-artifact](https://github.com/actions/upload-artifact) | `7.0.0` | `7.0.1` |
| [github/codeql-action](https://github.com/github/codeql-action) | `4.32.5` | `4.35.4` |
| [actions/download-artifact](https://github.com/actions/download-artifact) | `8.0.0` | `8.0.1` |
| [softprops/action-gh-release](https://github.com/softprops/action-gh-release) | `2.5.0` | `3.0.0` |
| [actions/setup-node](https://github.com/actions/setup-node) | `6.2.0` | `6.4.0` |
| [vedantmgoyal9/winget-releaser](https://github.com/vedantmgoyal9/winget-releaser) | `19e706d4c9121098010096f9c495a70a7518b30f` | `7bd472be23763def6e16bd06cc8b1cdfab0e2fd5` |
| [cbrgm/mastodon-github-action](https://github.com/cbrgm/mastodon-github-action) | `2.1.26` | `2.2.0` |



Updates `actions/upload-artifact` from 7.0.0 to 7.0.1
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](bbbca2ddaa...043fb46d1a)

Updates `github/codeql-action` from 4.32.5 to 4.35.4
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](c793b717bc...68bde559de)

Updates `actions/download-artifact` from 8.0.0 to 8.0.1
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](70fc10c6e5...3e5f45b2cf)

Updates `softprops/action-gh-release` from 2.5.0 to 3.0.0
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](a06a81a03e...b430933298)

Updates `actions/setup-node` from 6.2.0 to 6.4.0
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](6044e13b5d...48b55a011b)

Updates `vedantmgoyal9/winget-releaser` from 19e706d4c9121098010096f9c495a70a7518b30f to 7bd472be23763def6e16bd06cc8b1cdfab0e2fd5
- [Release notes](https://github.com/vedantmgoyal9/winget-releaser/releases)
- [Commits](19e706d4c9...7bd472be23)

Updates `cbrgm/mastodon-github-action` from 2.1.26 to 2.2.0
- [Release notes](https://github.com/cbrgm/mastodon-github-action/releases)
- [Commits](fc8b40e2ec...776364a15d)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: 8.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: actions/setup-node
  dependency-version: 6.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: actions/upload-artifact
  dependency-version: 7.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: cbrgm/mastodon-github-action
  dependency-version: 2.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: github/codeql-action
  dependency-version: 4.35.4
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: softprops/action-gh-release
  dependency-version: 3.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: vedantmgoyal9/winget-releaser
  dependency-version: 7bd472be23763def6e16bd06cc8b1cdfab0e2fd5
  dependency-type: direct:production
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-15 21:39:30 +02:00
Zoltan Kochan
7ff112bac6 ci: run install with pacquet (#11657) 2026-05-15 08:10:46 +02:00
Zoltan Kochan
e8fc34389a ci: pin Run tests step to bash so $TEST_SCRIPT expands on Windows (#11659)
Without an explicit shell, the step ran under PowerShell on
windows-latest, where `$TEST_SCRIPT` is not a variable (PowerShell
exposes env vars as `$env:TEST_SCRIPT`). `pn run ""` then exited 0
and just listed available scripts — the Windows test legs have been
silently no-op'ing since the env-var move in #11608.

The sibling `Verify Node version` and `Determine test scope` steps
already pin `shell: bash`; this brings `Run tests` in line.

---
Written by an agent (Claude Code, claude-opus-4-7).
2026-05-15 01:28:11 +02:00
Zoltan Kochan
a4f3c6d3b7 ci: enable manual releases for pacquet and pnpm (#11652)
Two related workflow changes:

### `pacquet-release-to-npm.yml`: switch to `workflow_dispatch`

The trigger was "push to main touching `pacquet/npm/pacquet/package.json`" — the version came from a committed bump and the workflow auto-fired on every such commit. Switch to `workflow_dispatch` only, with a `version` input (validated as semver). The workflow patches `pacquet/npm/pacquet/package.json` before `generate-packages.mjs` runs, so the version is single-sourced from the manual trigger rather than needing a separate commit to bump the manifest first.

The committed manifest now omits the `version` field entirely — it only exists at release time inside the runner.

Dropped along the way:

- The `check` job (EndBug/version-check against unpkg) — no longer needed when the operator types the version.
- The `Create GitHub Release` step — no draft release, no `v*.*.*` git tag. The pacquet `v0.x.x` tag scheme collided with pnpm's `v11.x.x`; npm is the authoritative artifact store and provenance attestations stay attached via `--provenance` on `pnpm publish`.
- `contents: write` on the publish job (no longer needs to create a tag).

### `release.yml`: add `workflow_dispatch` as a lib-only republish path

Add a `workflow_dispatch:` trigger alongside the existing tag-push trigger. Tag-push behaves exactly as before. Manual dispatch becomes a fast **lib-only republish** path — useful after a version bump to one or more lib packages that doesn't warrant a full CLI release.

On `workflow_dispatch` from any ref, the following are skipped (guarded with `if: startsWith(github.ref, 'refs/tags/')`):

- `Publish @pnpm/exe` step — also contains the multi-minute `build-artifacts` call.
- `Publish pnpm CLI` step.
- `Copy Artifacts`, `Attest build provenance` (the `dist/*` attestation), `Generate release description`, `Release` (`softprops/action-gh-release`) — these are the GitHub-Release-side ceremony. Without an explicit `tag_name`, `softprops/action-gh-release@v2.5.0` defaults to `github.ref_name`, which on a manual dispatch from main would create a junk release tagged literally `main`.

What still runs on `workflow_dispatch`:

- `actions/checkout`, garnet scan, `pnpm/setup`
- `Publish internal workspace packages (static token)` — i.e. `pn publish --filter=!pnpm --filter=!@pnpm/exe --access=public --provenance`

Compilation is handled by each lib package's own `prepublishOnly: tsgo --build` hook (which `pnpm publish` runs automatically), same as the existing tag-push flow.

The npm registry rejects any version already on it, so re-running on an already-released tree is a no-op — that's the safety net for accidental clicks.

## How to use

**pacquet release**: Actions → Release Pacquet → Run workflow → fill in `version` (e.g. `0.2.3` or `0.2.3-rc.1`) → Run. No tag, no GitHub release.

**pnpm full release**: still triggered by a `v*.*.*` tag push. Publishes @pnpm/exe + libs + CLI, attests, copies artifacts, creates a draft GitHub release.

**pnpm lib-only republish**: Actions → Release → Run workflow → choose `main` → Run. Publishes just the internal workspace packages from whatever versions are currently in each `package.json`. Skips CLI, @pnpm/exe, build-artifacts, GitHub release.
2026-05-14 22:50:42 +02:00
Zoltan Kochan
1575076d08 chore(pacquet): fold registry-mock into root workspace, fix npm metadata (#11643)
* chore(pacquet): fold registry-mock into root workspace, fix npm metadata

Two unrelated cleanups bundled because they touch the same publishing/
workspace plumbing:

1. **`pacquet/npm/pacquet/package.json` metadata** — the imported file
   still pointed at the standalone repo: `repository.url` was
   `pnpm/pacquet`, `repository.directory` was `npm/pacquet`, `homepage`
   and `bugs` likewise. Repoint at `pnpm/pnpm`. Update
   `generate-packages.mjs` so the per-platform packages it emits at
   release time also point at `pnpm/pnpm`.

2. **Fold `pacquet/tasks/registry-mock` into the root pnpm workspace**.
   Pacquet's standalone-repo nested-workspace setup pinned
   `nodeLinker: hoisted` "for verdaccio CJS resolution," but pnpm's
   own jest globalSetup (`__utils__/jest-config/with-registry/`) calls
   the same `@pnpm/registry-mock.start()` API under the default
   isolated linker without issue, and verified locally that
   `node launch.mjs prepare` works after consolidation. The hoisted
   constraint was scoped to standalone-pacquet's install pattern; in
   the monorepo it's unnecessary.

Changes for (2):
  - Add `pacquet/tasks/registry-mock` to `pnpm-workspace.yaml`.
  - Rename the package `@pnpm-private/pacquet-registry-mock-launcher`
    (private, matches the `@pnpm-private/*` convention used by other
    internal workspace members) and switch `@pnpm/registry-mock` to
    `catalog:` (the root catalog already pins it at 6.0.0).
  - Delete `pacquet/tasks/registry-mock/pnpm-lock.yaml` and
    `pnpm-workspace.yaml` — root install handles both now.
  - Delete `pacquet/package.json` and `pacquet/pnpm-lock.yaml` — the
    file only had a `cargo build` script + `devEngines: pnpm`, both
    already covered by root, and nothing referenced it.
  - `justfile install` is now just `pnpm install` (was
    `cd pacquet/tasks/registry-mock && pnpm install --frozen-lockfile`).
  - `pacquet-integrated-benchmark.yml` path filter and cache key
    swap the deleted nested lockfile for the root `pnpm-lock.yaml` /
    `pnpm-workspace.yaml`.

Verified: `pnpm install` resolves the workspace member, the lockfile
gains a `pacquet/tasks/registry-mock` importer entry, and
`pacquet/tasks/registry-mock/node_modules/@pnpm/registry-mock` is
linked correctly under the isolated layout.

* fix(pacquet): match meta-updater conventions in registry-mock launcher

The previous version of `pacquet/tasks/registry-mock/package.json`
omitted `version`, which crashed `pn lint:meta` (meta-updater hits
`manifest.version!.split('.')[0]` for every workspace package).

Backfill all the fields meta-updater would emit for an internal
`@pnpm-private/*` private package, matching `__typecheck__/package.json`
and friends:

  - `version: 1100.0.0` (the pnpm 11.x convention for non-experimental
    internal packages)
  - self-devDep entry (`workspace:*`) that meta-updater would otherwise
    inject
  - `keywords: [pnpm, pnpm11]`
  - `repository` pointing at this directory inside pnpm/pnpm

This is the same shape every other `@pnpm-private/*` private workspace
member uses; it lets `pn lint:meta --test` pass without modifying the
file.

* fix: update lockfile

* chore(pacquet): add build:pacquet script at root

Restore the `cargo build --release --bin pacquet` shortcut that lived in
the deleted `pacquet/package.json`. Naming it `build:pacquet` (rather
than `build`) matches the existing namespacing convention in this
file (`lint:ts`, `lint:meta`) and leaves room for a general `build`
script later. Invoke with `pnpm build:pacquet` or `pn build:pacquet`
from the repo root.
2026-05-14 21:05:03 +02:00
Zoltan Kochan
d2b64b6689 ci(pacquet): fix all zizmor code-scanning findings (#11641)
* ci(pacquet): fix all zizmor code-scanning findings

Resolves the 90 alerts opened by zizmor against the imported pacquet-*
workflows and shared composite actions:

- unpinned-uses: pin every third-party action to a SHA + version comment
  (matching SHAs already used elsewhere in the repo where applicable;
  taiki-e/install-action collapsed onto v2.78.0 with explicit `tool:` input).
- artipacked: add `persist-credentials: false` to every actions/checkout.
- template-injection: pass `inputs.*` and `steps.*.outputs.*` through `env:`
  in binstall/rustup composite actions and pacquet-release-to-npm.yml.
- excessive-permissions: add top-level `permissions: contents: read` to
  pacquet-release-to-npm.yml; move issues/pull-requests writes from the
  workflow level to the benchmark-compare job in pacquet-micro-benchmark.yml.
- dangerous-triggers: keep workflow_run in pacquet-integrated-benchmark-
  comment.yml but suppress with a documented zizmor: ignore — the trigger
  is the recommended pattern for posting comments back to fork PRs.
- superfluous-actions: keep softprops/action-gh-release with a zizmor:
  ignore (matches release.yml).

Verified by running `zizmor .github` locally with no remaining findings.

* ci(pacquet): point SHA pins at the patch-version tag

Swatinem/rust-cache and montudor/action-zip were pinned to the SHA the
major-version alias (`v2`, `v1`) resolves to, but the version comments
claimed `v2.9.1` / `v1.0.0`. zizmor's online `ref-version-mismatch`
audit flagged the inconsistency. Repoint at the SHAs the patch-version
tags actually annotate so the pin and the comment agree.
2026-05-14 19:33:30 +02:00
Zoltan Kochan
763ddf1c99 chore(pacquet): wire pacquet workflows into monorepo (#11635)
* chore(pacquet): wire pacquet workflows into monorepo

Move Cargo workspace, Rust toolchain configs, justfile, composite actions,
and 7 workflow files out of `pacquet/` and up to the repo root so:

  - cargo / just / taplo run from repo root, the way the rest of the
    monorepo's tooling does
  - GitHub Actions actually discovers the workflows (it only reads
    `.github/workflows/` at the repo root)

Workflows are prefixed with `pacquet-` and renamed to "Pacquet ..." so
they don't collide with the existing pnpm CI. Path filters are scoped
to `pacquet/**` so they don't trigger on every commit. The cargo entry
from pacquet's standalone `dependabot.yml` is folded into the root one;
pacquet's `CODEOWNERS` and `pull_request_template.md` are dropped because
the root copies supersede them.

Path rewrites:
  - `Cargo.toml` workspace members → `pacquet/crates/*`, `pacquet/tasks/*`
  - all path-deps in `[workspace.dependencies]` → `pacquet/...`
  - `justfile` recipes (`install`, `install-hooks`) point at `pacquet/...`
  - `.taplo.toml` include globs → `pacquet/crates/*/*.toml`, `pacquet/tasks/*/*.toml`
  - `pacquet/npm/pacquet/scripts/generate-packages.mjs` REPO_ROOT walks one
    more level up
  - workflow `paths:` filters, `hashFiles(...)`, and shell paths all updated

Verified: `cargo metadata` resolves the workspace, `cargo fmt --check`
clean, `taplo format --check` picks up all 26 Cargo.tomls, `actionlint`
reports no new issues (the `type:`-on-input warnings on the rustup action
predate this move).

* chore(pacquet): drop pnpm version pin from pacquet CI workflows

The monorepo's root `package.json` declares `pnpm@11.1.1` under
`packageManager`, which conflicts with the workflows' explicit
`version: 11.0.0-rc.5` and trips `pnpm/action-setup` ERR_PNPM_BAD_PM_VERSION.

The pin was a pacquet-era workaround for the v9 lockfile while pnpm 11
was still pre-release. Stable 11.x writes v9 too, so let action-setup
read the version from `packageManager` like every other workflow in
this repo does.

* chore(pacquet): use pnpm/setup matching the rest of the monorepo

Replaces `pnpm/action-setup@v6` with the same `pnpm/setup@b1cac3...`
SHA the rest of pnpm/pnpm uses (release.yml, test.yml, ci.yml,
benchmark.yml, audit.yml). Reads pnpm version from `packageManager`
in root package.json, and skips the implicit `pnpm install` since
pacquet does its own scoped install via `just install` (which only
touches `pacquet/tasks/registry-mock/`).

The release workflow now also installs Node via the same action
(`runtime: node@22`) instead of via `pnpm runtime -g set node 22`,
since pnpm/setup handles runtimes in one step.

* chore(pacquet): tighten permissions and Dependabot cooldown

Address zizmor warnings on the pacquet CI changes:

  - `dependabot.yml`: the cargo entry I added in the previous commit
    inherited from pacquet's standalone repo and is missing the
    `cooldown: default-days: 7` the github-actions entry uses. Add it
    so cargo and github-actions debounce updates consistently.

  - `pacquet-ci.yml`, `pacquet-codecov.yml`, `pacquet-cargo-unused.yml`
    lacked a top-level `permissions:` block, so GITHUB_TOKEN inherited
    the repo default. Declare `contents: read` — every job in these
    workflows only reads the repo and runs local checks.

The other four pacquet workflows already declare permissions
explicitly (integrated-benchmark/comment, micro-benchmark, release).

* chore(pacquet): add "reimagining" to cspell dictionary

cspell at the repo root scans all `**/README.md` and was rejecting
`pacquet/README.md` and `pacquet/npm/pacquet/README.md`, which describe
pacquet as "not a reimagining of pnpm." Add the word to the existing
allow-list rather than rewording two READMEs imported from a separate
repo.

* fix(pacquet): prefix workspace-relative paths with pacquet/

Two Rust source files looked up paths off the cargo workspace root
(\`cargo locate-project --workspace\`), which now resolves to the
monorepo root rather than the pacquet directory. Add the \`pacquet/\`
prefix:

  - \`tasks/registry-mock/src/dirs.rs\` — \`registry_mock()\` was
    pointing the node launcher at \`<repo>/tasks/registry-mock/launch.mjs\`
    instead of \`<repo>/pacquet/tasks/registry-mock/launch.mjs\`, which
    failed every Pacquet CI test job ("Cannot find module ...launch.mjs").
  - \`tasks/micro-benchmark/src/main.rs\` — same idea for the
    fixtures folder.
2026-05-14 18:17:45 +02:00
Zoltan Kochan
9844cdf3a9 ci: integrate garnet-org/action for supply-chain monitoring (#11626)
Adds the Garnet network-monitoring action to the smoke test job, the
release workflow, and the npm tag workflow. The full CI test matrix is
left untouched to keep per-job overhead off the broad cross-platform
runs; the smoke test still exercises a representative install/test flow.
2026-05-14 08:25:30 +02:00
Zoltan Kochan
6b2a955a15 ci: address zizmor findings across workflows (#11608)
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).
2026-05-12 22:03:41 +02:00
Zoltan Kochan
6335860e0c ci: add zizmor workflow for GitHub Actions security analysis (#11607)
- Adds `.github/workflows/zizmor.yml` running [zizmor](https://github.com/zizmorcore/zizmor-action) against the repo's GitHub Actions workflows.
- Triggers: push to `main` and `release/**`, and PRs targeting those branches.
- Empty workflow-level `permissions: {}` with job-level grants (`security-events: write`, `contents: read`, `actions: read`) so findings upload to the Code scanning alerts feed.
- Pins `actions/checkout` and `zizmorcore/zizmor-action` by commit SHA, matching the pinning convention used elsewhere in `.github/workflows/`.
2026-05-12 21:48:11 +02:00
Zoltan Kochan
dcc171a948 chore(ci): migrate workflows to pnpm/setup (#11589)
## 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.
2026-05-12 19:31:54 +02:00
Zoltan Kochan
7e3145f9fc chore: add devEngines.runtime (#11553)
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.
2026-05-12 14:43:05 +02:00
Zoltan Kochan
80037699fb ci: update dist-tag in update-latest.yml 2026-05-07 12:42:52 +02:00
Zoltan Kochan
d98ac7e4bb ci(release): split publish into three steps to force trusted publishing (#11496)
The previous "Publish Packages" step ran `pn release` after writing
NPM_TOKEN into pnpm's config. With a static `_authToken` configured,
`pnpm publish` bails out of OIDC entirely (see #11495 for the longer-
term fix), so every package — including `pnpm` and `@pnpm/exe` — was
silently being published with the legacy token instead of using npm's
trusted publishing. The result: published metadata showed
`_npmUser: pnpmuser` and no provenance attestation.

Until #11495 ships, work around the precedence bug by structuring the
job so the packages we *want* trusted publishing for never see a
static token at all:

1. `@pnpm/exe` — published in a step with no NPM_TOKEN. pnpm has no
   token to short-circuit on, performs OIDC, gets a `trustedPublisher`
   entry on npm.
2. Internal workspace packages — these don't have trusted publishing
   configured on npm, so they still need the static token. The token
   is written, the publish runs, then `pn config delete` removes the
   token before the next step.
3. `pnpm` — published in a step with no NPM_TOKEN, same rationale as
   step 1.

CI-only change; no changeset needed.
2026-05-06 17:19:24 +02:00
Zoltan Kochan
f79f0540ac chore: update Node.js to 26.0.0 (#11472)
* chore: update Node.js to 26.0.0

* fix(jest-config): use amaro for type stripping on Node.js 26

Node.js v26 removed the `transform` mode and `sourceMap` option from
`module.stripTypeScriptTypes`. Switch the Jest transform to call
`amaro.transformSync` directly (the same wasm transformer Node.js wraps)
so we keep inline source maps for tests.
2026-05-05 22:18:12 +02:00
Zoltan Kochan
7e91e4b35f ci: update pnpm/action-setup 2026-05-04 22:24:17 +02:00
Zoltan Kochan
4852e6f85d docs(release): correct rationale comment on macos-latest runner (#11446)
The previous comment attributed the darwin SEA crashes to ldid producing
bad page hashes, but the upstream minimal `node --build-sea` + `codesign`
repro (nodejs/node#62893) shows codesign-signed binaries crash too. The
bug is in LIEF's Mach-O surgery during --build-sea, not in signing.

Rewrite the comment to state the actual reasons the job runs on macOS
(native codesign avoids building ldid; macos-latest is Apple Silicon so
verify-binary.mjs can smoke-test the darwin-arm64 SEA) and explicitly
note that this does NOT fix the darwin-x64 crash.

Comment-only change. No behaviour change.
2026-05-04 14:56:29 +02:00
Zoltan Kochan
caf5b7d422 ci(release): attest build provenance for release artifacts (#11441)
Generate Sigstore-backed SLSA build provenance for the platform tarballs
and zips produced by `pn copy-artifacts` via actions/attest-build-provenance,
so users can verify with `gh attestation verify` that the binaries attached
to a GitHub release came from this repository's release workflow rather
than from a manual upload.

This complements the release attestation that GitHub auto-generates for
Releases (predicate `https://in-toto.io/attestation/release/v0.2`), which
only proves what files were attached to a tag, not how they were built.
The new attestation uses `https://slsa.dev/provenance/v1` and binds each
artifact's digest to the workflow_ref, commit SHA, and runner identity.

The `pn release` step already publishes npm tarballs with provenance, so
this closes the same gap on the GitHub Release side.
2026-05-04 13:09:06 +02:00
Zoltan Kochan
d374e330ad ci(release): build artifacts on macos-latest to fix darwin-x64 signing (#11415)
* ci(release): build artifacts on macos-latest to fix darwin-x64 signing

Cross-signing darwin Mach-O binaries on Linux with the saurik fork of
ldid produces an ad-hoc signature whose page hashes don't match the
post-postject layout for Node.js 25's chained fixups, leaving fixups
unapplied at load and crashing the binary in __cxx_global_var_init
(EXC_BAD_ACCESS at 0x3 — the unprocessed chain-entry tag).

Running the release on macos-latest lets pack-app's adHocSignMacBinary
use native codesign, which understands chained fixups. Drops the entire
ldid build step.

* ci(release): document why release runs on macos-latest
2026-05-01 21:54:51 +02:00
Zoltan Kochan
1beb41652b ci: run all tests on release branches 2026-04-22 02:17:03 +02:00
Zoltan Kochan
bcf49c9cd9 ci: don't continue release on error 2026-04-21 01:33:57 +02:00
Zoltan Kochan
9ae1ca7253 feat: publish base docker image to GHCR (#11302)
* feat: publish base docker image to GHCR

Adds a Dockerfile (debian:stable-slim + pnpm standalone binary) and a
release-triggered workflow that builds multi-arch images and pushes to
ghcr.io/pnpm/pnpm. Users who need Node.js can install it inside the
container via `pnpm runtime set node <version>`.

Refs #11300

* docs: add docker/README.md

* chore(cspell): add buildx to dictionary

* docs: mention devEngines.runtime as alternative to pnpm runtime set

* fix(docker): pin base image, verify tarball sha256, harden download

- Pin `debian:stable-slim` to a digest for reproducibility.
- Compute pnpm tarball SHA256 in the workflow and verify it inside the
  build, detecting tampered artifacts regardless of what `pnpm --version`
  reports.
- Download the tarball to disk with `--retry` instead of `curl | tar`
  for resilience under multi-arch QEMU builds.
- README: use `--load` so the local test image is available to `docker run`.

* chore(cspell): sort dictionary additions

* fix(docker): address Copilot review feedback

- Include $PNPM_HOME/bin on PATH so pnpm-installed globals (node, etc.)
  are discoverable, and make $PNPM_HOME writable for non-root users.
- Document that `pnpm runtime set node` needs `-g` to install globally.
- Pass workflow inputs via env: instead of inlining GitHub expressions
  into shell, and validate the version string before use.

* fix(docker): install libatomic1 for pnpm standalone binary

The pnpm linux standalone binary dynamically links against
libatomic.so.1, which is not present in debian:stable-slim by
default. Without it, `pnpm --version` fails during the build with:

  pnpm: error while loading shared libraries: libatomic.so.1:
  cannot open shared object file: No such file or directory

Caught by local build testing.
2026-04-19 18:59:24 +02:00
Zoltan Kochan
a4305c91b4 ci: remove not needed comment from release.yml 2026-03-30 18:35:59 +02:00
Zoltan Kochan
6bb68e54e7 chore: update pnpm to beta 4 2026-03-30 00:34:47 +02:00
Zoltan Kochan
55a06023fc ci: update release.yml 2026-03-29 13:15:13 +02:00
Zoltan Kochan
d6b8e281b6 chore: use pn instead of pnpm (#11124) 2026-03-28 11:55:51 +01:00
Zoltan Kochan
be681cc3fc ci: fix windows 2026-03-27 20:44:08 +01:00
Zoltan Kochan
b09ae0a9bc ci: update action-setup and use devEngines 2026-03-27 11:15:58 +01:00
Zoltan Kochan
3967d89c17 ci: fix windows 2026-03-25 14:00:58 +01:00
Zoltan Kochan
439cb684a3 fix: allow benchmark workflow to run against PRs from forks 2026-03-24 21:34:46 +01:00
Zoltan Kochan
cd2dc7d481 refactor: prefix internal scripts with . to hide them (#11051)
* 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>
2026-03-21 14:30:56 +01:00
Zoltan Kochan
784cdcd419 ci: only run Windows tests with lowest Node.js on branches (#10962)
On non-main branches, run Windows tests only with Node.js 22.13.0
(the lowest supported version). The full Windows matrix still runs
on main.
2026-03-13 22:42:39 +01:00
Zoltan Kochan
226376eff5 ci: update pnpm/action-setup to v4.4 2026-03-13 21:25:26 +01:00
Zoltan Kochan
9931621152 ci: run Linux/Node 24 tests first, then the rest of the matrix (#10960)
* ci: run Linux/Node 24 tests first, then the rest of the matrix

Run tests on ubuntu-latest / Node.js 24 as a smoke test first.
The remaining 5 matrix combinations only start if it passes,
saving CI resources on failing PRs.

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

* refactor(ci): extract test steps into reusable workflow

Reduces duplication by moving all test steps into test.yml as a
reusable workflow. ci.yml now calls it twice: once for the smoke
test (Linux/Node 24) and once for the remaining matrix.

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

* refactor(ci): remove redundant if conditions from dependent jobs

The if condition only needs to be on compile-and-lint. Downstream
jobs are automatically skipped when their needs are skipped.

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

* refactor(ci): clean up check names for reusable workflow

Drop redundant "Test" prefix from caller job names since the
reusable workflow job key "test" is automatically appended by
GitHub, e.g. "CI / ubuntu-latest / Node.js 24 / test".

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

* style(ci): capitalize Test in reusable workflow job name

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 21:18:24 +01:00
Zoltan Kochan
77165a6fca ci: change pnpm installation to standalone mode 2026-03-07 14:58:43 +01:00
Zoltan Kochan
b20d3fc486 ci: increase timeout on node.js install 2026-03-04 22:59:40 +01:00