* docs: lint prose with Vale
Add Vale with an in-repo style, .vale/styles/pnpm, that turns the
writing rules in AGENTS.md and the Google developer documentation style
guide into lint rules. The stock Vale packages (Google, Microsoft,
RedHat, write-good) were too noisy to block on, for example 1,300
passive-voice hits in the v12 docs alone, so the rules are written for
this repo, following the GitLab model. Each one links the guideline it
enforces.
Levels:
- error, fails CI: dashes, plain-word substitutions, condescending
words, "please", "note that", hedges, puffery, optional plurals,
exclamation marks, ellipses, curly quotes, generic link text, product
spellings, sentence-case headings, repeated words, double spaces,
implementation history in comments; in changesets, bold or italic
emphasis, internal names and release-line names.
- warning: future tense, sentences over 30 words, "instead of" and
"so that" tails in changesets.
- suggestion: passive voice.
.vale.ini scopes the rules. Docs get every rule. Contributor Markdown
and code comments keep dashes and headings as warnings until their
roughly 8,100 em dashes are rewritten. Released changesets stay in
.changeset/ and are not rewritten, so lint-prose.mjs lints only the
changesets added since PROSE_BASE (the PR base in CI).
The prose workflow installs a pinned, checksum-verified Vale release and
runs `pnpm lint:prose`. The rest of this change rewrites the existing
docs, READMEs, contributor Markdown, and code comments to pass the
error-level rules. Only prose and comment text changed. Renamed
headings keep their anchors.
Spelling stays with cspell, which checks identifiers that Vale does not
see.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs: keep prose lint from flattening meaning
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The pipelines decide what each CI job runs, and `pnpm pipeline`
reports a pipeline whose script does not exist as skipped and exits 0.
The task settings also control ordering and the cache keys of the
remotely cached jobs. An unowned tasks.yaml could therefore turn a CI
check into a no-op without code-owner review.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* chore: move supply-chain settings to a protected config file
Use the `include` setting from pnpm 12.15 to move the settings that
decide what gets installed and what may run into
.config/pnpm/security.yaml: allowBuilds, auditConfig, the
minimumReleaseAge and trustPolicy settings, overrides,
packageExtensions, patchedDependencies, patchesDir, and registries.
pnpm-workspace.yaml delegates exactly that list, so no other file can
define those settings. Patch paths are relative to the included file.
pnpm 12.14 ignores `include` and would drop every moved setting, so
the packageManager pin moves to 12.15.0.
CODEOWNERS now names both workspace files, the pnpmfile, and the
patches explicitly, and the CI path filters watch the new file.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore: move routine settings to a file without a code owner
The catalog, the update settings, and the task-runner settings
(pipelines, tasks, concurrencyGroups) move to
.config/pnpm/routine.yaml, which CODEOWNERS leaves unowned. Because
pnpm-workspace.yaml delegates only that list to the file, any other
setting defined there fails with ERR_PNPM_SETTING_NOT_DELEGATED, so
the file cannot be used to change the protected settings.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore: move the catalogs to their own file
.config/pnpm/catalog.yaml owns `catalogs`, which covers the default
catalog and any named catalog added later. Like routine.yaml, it has
no code owner.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore: name the protected file policies.yaml and give tasks their own file
security.yaml becomes policies.yaml. The task-runner settings
(pipelines, tasks, concurrencyGroups) move to tasks.yaml, and
`update` joins the catalogs in catalog.yaml, since its ignoreDeps list
holds back catalog entries. routine.yaml is gone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore: move catalogMode and catalogPrune to catalog.yaml
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore: keep the patch list next to the patches
patchedDependencies and patchesDir move to __patches__/patches.yaml,
so the entries name the patch files without a directory prefix and
patchesDir is `.`. CODEOWNERS already covers __patches__/. The CI path
filters that did not watch __patches__/ now watch patches.yaml, since
they watched the patch list when it lived in pnpm-workspace.yaml.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore: move the patches to .config/pnpm/patches
Every pnpm configuration file now lives under .config/pnpm, so each CI
path filter watches `.config/pnpm/**` instead of listing the files.
CODEOWNERS keeps the patches protected.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Complete the test harness requirements of pnpm/tasks#141 (including the
deferred cross-version switch matrix from pnpm/tasks#144 and
injected-freshness convergence/differential checks from pnpm/tasks#148):
1. Equivalence and convergence harnesses:
- Extract shared `TreeView`, `workspace_lockfiles`, and normalized
`workspace_modules_manifests` helpers into
`pnpm/crates/cli/tests/suite/_utils/mod.rs`.
- Include `.modules.yaml` in `repeat_install_oracle` snapshots and
assert convergence after every scenario (second install snapshot
byte-identity, `--frozen-lockfile` acceptance, `dedupe --check`,
and quiet repeat `exec` under `verifyDepsBeforeRun=install`).
- Add `check_repeat` (second install byte-identity) and
`check_run_gate` (`verify-deps-before-run=error` exec gate) to
`peer_convergence::harness::check_row`.
- Add repeat-install lockfile byte-identity,
`verify-deps-before-run=error` run-gate checks, `dedupe --check`,
and `PNPM_V11_BUNDLE` differential lockfile comparison to
`injected_freshness_matrix::check`.
2. v11/v12 differential suite (`pnpm_compatibility.rs`):
- Honor `PNPM_V11_BUNDLE` in `CommandTempCwd::init()` so integration
tests no longer require a system-installed pnpm v11 on `PATH`.
- Un-ignore `store_usable_by_pnpm_offline` and
`same_global_virtual_store_layout_with_approved_postinstall`.
- Compare wanted lockfiles, current lockfiles, `.modules.yaml`,
`node_modules` resolution tree, cross-CLI frozen handoffs, and
registry request logs across shared/dedicated workspace lockfiles,
isolated/hoisted `nodeLinker`, `injectWorkspacePackages`,
`autoInstallPeers`, `dedupePeerDependents`, and filtered installs.
3. Upward cross-version switch harness and nightly matrix:
- Add `switches_up_to_a_newer_pnpm_across_setting_sources_and_unknown_options`
and `v11_switches_up_to_head_binary_from_local_registry` in
`version.rs` using a local `pnpr` registry populated with
synthetic `@pnpm/exe` platform packages.
- Add `.github/workflows/version-switch-matrix.yml` to run the
outer-pnpm x pin x setting-source x platform matrix nightly and on
`workflow_dispatch`.
- Wire `pnpm_compatibility`, `injected_freshness_matrix`, and upward
version-switch tests into the `convergence` job of
`.github/workflows/pacquet-ci.yml`.
Closespnpm/tasks#141
Unify repeat-install freshness and `verifyDepsBeforeRun` validation across
pnpm v11 and v12 (Closespnpm/tasks#161, pnpm/tasks#157, pnpm/tasks#148):
1. Centralize v11 workspace state updates in `@pnpm/workspace.state`
(`preserveCarriedSettings`) and `@pnpm/installing.commands`
(`workspaceStateUpdate.ts`), preserving carried `dev`/`optional`/`production`
settings when `ignoredWorkspaceStateSettings` ignores them and preserving
v12-recorded `autoDedupe`. A filtered install that prunes direct
dependencies no longer records the workspace root as installed.
2. Hold `{.}...` workspace dependencies for non-recursive `pnpm run`,
`pnpm exec`, and `pnpm restart` inside a workspace project in v11
(`selectProjectsToVerify.ts`).
3. Align v11 recorded validation (`checkRecordedWorkspace`) and legacy
unrecorded validation (`checkModifiedSinceLastValidation`) in
`@pnpm/deps.status` with v12's `check_freshness`, including empty-project
handling, `dedupeDirectDeps`, `dedupePeerDependents`, `autoInstallPeers`,
`excludeLinksFromLockfile`, catalog/override config comparison, and
`opts.lockfileDir` resolution.
4. Forward effective CLI flags (`--lockfile-dir`, `--modules-dir`,
`--virtual-store-dir`, `--store-dir`, `--dir`, `--workspace-root`, `--prod`,
`--dev`, `--no-optional`, `--filter`, `--filter-prod`, `--test-pattern`,
`--changed-files-ignore-pattern`, and `--config.*`) when
`verifyDepsBeforeRun: install` spawns `pnpm install` in both v11 and v12.
5. Align `GatePolicy::dedupe_peers` and empty-project predicates in v12
`pnpm-package-manager`, and run `repeat_install_oracle.rs` across v12, v11,
and mixed v11/v12 writers in CI, with a longer nextest period for the
peer convergence harness that shares the job.
6. Parse `--config.<key>=<value>` with the type the command declares for
`<key>` in v11, so the list and number options forwarded to the spawned
install arrive as lists and numbers; forward `--npmrc-auth-file` in v11
and `--test-pattern` / `--changed-files-ignore-pattern` in v12, so both
forward the same set; and skip the current-lockfile read for a project
whose manifest changed but whose lockfile did not, as v12 does.
7. Resolve relative v11 store overrides against the workspace or project
root before forwarding them to the spawned install. Remove the isolated
linker's duplicate package merge so filtered installs retain unselected
dependencies and drop obsolete selected dependencies from the current
lockfile. Cover both cases with CLI regressions. The restore API also
handles a selected project absent from the previous current lockfile.
8. Record workspace validation from the projects the installers actually
processed. Ignored projects receive no new proof, and empty or entirely
ignored selections leave state untouched. Cover both lockfile modes and
an ignored workspace root.
* chore(bench): add Cargo repeat-install scenario to integrated-benchmark
Add the cargo.repeat-install.hot-cache.hot-store scenario to
pnpm-integrated-benchmark and the CI integrated-benchmark workflow
to guard Cargo crate slot reuse (pnpm/pnpm#17105).
* fix(bench): isolate cargo repeat-install benchmark from parent workspace
* fix(bench): connect generated crates in Cargo dependency graph and verify prewarm slots
With `PNPM_V11_BUNDLE` naming the pnpm v11 bundle, the peer convergence
harness also runs every fixture through v11, apart from the rows that
set the v12-only `autoDedupe`, and each row's fresh v11 lockfile has to
equal the v12 one. v11 gets every check the harness makes, so a
convergence bug no longer has to be confirmed on v11 by hand.
A Rust CI job builds the v11 bundle and runs the harness this way on
changes to the resolution, lockfile, install and deploy code of either
stack. The success gate requires it.
pnpm/pnpm#17094 is tracked as a divergence only v11 shows.
Related to pnpm/tasks#143
pnpr-error depended on axum, bcrypt, object_store, reqwest, rusqlite,
serde_json, tokio and tracing on every non-wasm target. pnpm-config depends
on pnpr-registry for Ecosystem and PackagePattern, and pnpr-registry and
pnpr-package-name both depend on pnpr-error, so every consumer of pnpm-config
compiled and linked the registry server's HTTP, storage and auth crates.
- pnpr-error: those dependencies are optional and enabled by a new `server`
feature. The response module (IntoResponse, public_message, log_kind) and
the Upstream, Bcrypt, Sqlite, JoinError, ObjectStore and Json variants are
gated on it, replacing the cfg(not(target_family = "wasm")) gate.
- The pnpr crates that use those variants or the response rendering (auth,
osv, pipeline-runs, pnpr, search, shared-artifacts, storage, upstream)
enable `server`. pnpr-config only builds InvalidConfig and does not.
- pnpr-registry drops its unused pnpr-error dependency.
- pnpr-package-name tests compare Display output instead of
public_message(), so they pass without the server feature.
- Three workflow_run workflows carry their zizmor ignore directive on the
trigger line. zizmor did not apply the standalone comment, which failed the
zizmor job on fork PRs.
Closespnpm/tasks#134.
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Adds `<version>-alpine`, `<major>-alpine`, and `alpine` tags built from
docker/Dockerfile.alpine with the musl release tarball. Alpine users can
use the image as a base or copy /opt/pnpm into `node:*-alpine` without
fetching the installer from get.pnpm.io, which was returning 403 to
bursts of concurrent CI jobs.
The download, checksum, and version check move into
docker/install-pnpm.sh, shared by both Dockerfiles. The Alpine image
also installs libstdc++ and libgcc, which the musl Node.js builds
installed by `pnpm runtime` need. The Alpine build runs only for
releases that ship musl tarballs for both x64 and arm64, so v11 and
v10 releases skip it.
Every release used to publish the `latest` and `<major>` tags, so a v11
maintenance release moved `latest` back to v11. The builds now push only
the exact version tags. A final step lists the stable releases and, with
`docker buildx imagetools create`, points `<major>` at the highest
version in that major and `latest` at the highest version overall. The
Alpine tags follow the same rule. Deciding after the builds sees every
release of a burst, since lines are published seconds apart. A
concurrency group was rejected because GitHub cancels a pending run when
a third one queues. The selection lives in
.github/scripts/docker-image-tags.sh with a test run by CI, and fails on
an empty release list.
The v12 CI docs now run the Bitbucket Pipelines and GitLab CI examples
in `ghcr.io/pnpm/pnpm:12` instead of downloading the installer, and
recommend the image for CI that runs jobs in containers. CircleCI keeps
the installer because its checkout step needs git and ssh.
The CI examples that still use the standalone script fall back to the
latest pnpm/get.pnpm.io installer release when get.pnpm.io fails. They
run the script only after a download succeeds.
Related to pnpm/pnpm#16973.
zizmor 1.31.0 stopped applying ignore comments to the dangerous-triggers
audit, so the three workflow_run workflows failed the zizmor check on
every fork pull request. zizmor-action v0.7.1 defaults to zizmor 1.31.1,
which fixes the regression.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Since the update check no longer holds the command (pnpm/pnpm#16902),
two suite tests that depended on the wait fail.
The notice test installed an empty workspace, which is done in
milliseconds, so the install finished before the registry answered the
check. It now installs a dependency, as the add tests in the same module
do, so the check's request, which is in flight before the install's
first, is answered while the install still has work.
The Rust test runner sets PNPM_CONFIG_CI=false, so a pnpm spawned
without the shared command harness ran the daily check against the
runner's un-isolated state directory. While the command awaited the
check, the first such pnpm recorded it and every later one skipped it.
Now an unanswered check is dropped unrecorded, so every bare pnpm asked
its mock registry for pnpm's latest, and a test whose public mock
forbids any request failed. The runner and the coverage workflow now set
PNPM_CONFIG_UPDATE_NOTIFIER=false as well; the notifier suite turns the
check back on itself.
None of the integrated benchmark's scenarios resolved against a lockfile
the walk could mostly reuse, installed store-only (`enableModulesDir:
false`), or read the per-file store index. The install regressions fixed
in pnpm/pnpm#16886, pnpm/pnpm#16888 and pnpm/pnpm#16890 all sat in that shape; the
benchmark would have missed each of them, and would miss their reversal.
The new `store-only.unlocked-direct-deps.cold-cache.hot-store` scenario
seeds the static fixture's lockfile with the direct dependencies whose
locked version carries a peer suffix left out of the importer, while
`packages` and `snapshots` keep the whole graph. That is `pnpm install`
after dependencies were added to `package.json` by hand: the missing
direct dependencies resolve from the registry, land on versions the
graph holds, and the walk reuses their subtrees. The choice of the
peered ones is deliberate: a dependency without peers is taken from the
lockfile's package list without a registry request, and a dependency
whose specifier merely changed is reused as long as the recorded version
satisfies the new range, so neither exercises the fresh pick that the
subtree reuse then follows. The metadata cache is wiped per iteration,
so every package the walk does resolve costs a round trip over the
emulated registry link; with a hot cache on local disk the whole
speculative warm-up costs about 25 ms and sits inside the noise. The
lockfile is trusted (`trustLockfile: true`): its supply-chain
verification reads every entry's packument and left nothing for the
resolution to fetch, hiding the difference entirely. With
`enableModulesDir: false` nothing is linked, so the rest of the timed
work is the check that the store holds every package, read through
`storeIndexFormat: files`.
`--no-frozen-lockfile` is passed explicitly because the runners set
`CI`, where a plain `install` is frozen and would reject the lockfile.
Only `cache-dir` is removed and the lockfile restored between
iterations; `node_modules` is never created and the warm store stays.
The scenario runs direct targets only, like the workspace fixtures: a
pnpr server would take over the resolution being measured. The
comparison script and its matrix-coverage test, the Bencher upload and
the summary list it alongside the others. Every `cleanup` arm now goes
through a `Cleanup` constructor, which keeps the method within the
function-length limit with the new arm.
Measured locally (hyperfine, 5 runs, main vs the commit before pnpm/pnpm#16886):
main 1.008 s +- 0.025 s (0.991 .. 1.052 s), 513 packuments
before pnpm/pnpm#16886 1.148 s +- 0.026 s (1.131 .. 1.194 s), 840 packuments
* docs: remove users list page
* ci(docs): build against pnpm.io with the users page redirect
check-urls failed because /users is no longer built. pnpm.io now
redirects it to the home page.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The Clippy and Doc tasks declare `outputs: []`, so `pnpm pipeline` caches
their results, keyed on every file in the repository plus `RUSTFLAGS` (and
`RUSTDOCFLAGS` for Doc). Their jobs no longer pass `--no-cache`.
A new `remote-cache` composite action points the pipeline at Vercel Remote
Cache. It runs only in the merge queue and on pushes to main, which also
sign and publish their results, so the Vercel token never reaches pull
request code. The push to main tests the tree the merge queue already
tested, so its Clippy and Doc rows restore the results instead of
recompiling. The action does nothing until the token, team, and public key
are configured.
The credentials live in the remote-cache environment, which only main and
the merge queue branches can use, so a workflow on a pushed branch cannot
read the signing key.
Dylint keeps `--no-cache`: its run on main saves the build cache pull
requests restore, and a cache hit would leave that cache stale.
Avoid replacing an injected directory when its regular files are still
hardlinked to the current source and its entries match. Keep the existing
refresh path for mismatches, links, forced imports and build isolation.
Preserve import reporting and add a frozen-repeat scenario to the existing
integrated benchmark and CI comparison.
Closespnpm/tasks#121.
pnpm runs scripts with the workspace toolchain first on PATH, the way it
puts a pinned Node.js first. cargo-dylint needs rustup itself: it runs the
`rustup` binary and builds its lint library with the nightly the library
pins, which only rustup's cargo selects. That is a requirement of the one
tool, so the dylint script asks for rustup's proxies instead of pnpm
changing how it runs Rust for every script.
pnpm/scripts/cargo-dylint.mjs runs `cargo dylint` from the first PATH
directory that holds both rustup and cargo, with that directory first on
PATH. The ci:rust-dylint script, the rust-dylint pipeline and its cargo
concurrency group come back, and CI runs the pipeline again, which also
installs the esm-loader bundle the crates embed, so its separate install
step goes. The pre-push hook calls the same helper in place of its own
PATH edit.
CONTRIBUTING.md no longer says pnpm install needs cargo on PATH: pnpm runs
cargo metadata from the toolchain it installs for rust-toolchain.toml.
* 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>
pnpr now serves its web UI at /-/ui/ from the same origin, so the UI
needs no CORS setup and no server URL. The UI ships as the separate
`@pnpm/pnpr-ui` package, an optional peer dependency of `@pnpm/pnpr`.
pnpr finds it next to its own package (npm and pnpm both link a resolved
peer beside the package that declares it), or reads it from `ui.dir`,
and `ui.enabled: false` turns it off.
- Serving: files from the UI directory, `index.html` for every other
path outside `assets/`, a 404 for a missing hashed asset. The page
gets a relative `<base>` so deep routes and path prefixes work without
an absolute Vite base. Responses carry a CSP that keeps scripts and
requests on this origin.
- The UI source stays on bit.cloud. `pnpr/ui` is a workspace of its own
that pins `@pnpm/pnpr.apps.pnpr-web` from bit.cloud's registry and
builds it into `pnpr/npm/pnpr-ui/dist`.
- Release: a `build-pnpr-ui` job builds the bundle. The verify and
publish jobs download it. The smoke test installs the UI beside the
wrapper and checks that pnpr serves it. `@pnpm/pnpr-ui` releases in a
fixed group with `@pnpm/pnpr`.
* chore: update dependencies, Node.js, pnpm, and GitHub Actions
* chore: generate the embedded CAS loader bundle on install
The CAS loader that `deps-restorer` embeds with include_str! was a
minified esbuild bundle committed to the repo. Every enhanced-resolve or
esbuild bump rewrote most of it, and a stale copy failed CI with a
byte-by-byte assertion diff.
`@pnpm/esm-loader` now generates it from a `prepare` script, so every
`pnpm install` that includes the package writes it before cargo builds.
Cargo already depends on that install for `.pnpm/crates`. The package
gets its own esbuild devDependency instead of borrowing pnpm11/pnpm's.
The filtered installs that feed cargo (install-crates and the wasm
release job) now also select `@pnpm/esm-loader`.
Also ignore GHSA-hp3w-g68c-fv3c. sprintf-js has no patched release, and
it is reached only through argparse 1 formatting its own help text.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore: write the CAS loader bundle atomically and generate it for wasm CI
An interrupted `prepare` could leave a truncated bundle that a later
up-to-date install never rewrites. Write to a temporary file and rename.
The pnpm-wasm workflow builds the CLI after a filtered install, so it now
selects `@pnpm/esm-loader` too.
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>
Add an opt-in CAS installer and synchronous Node.js module loader.
Generate package-instance manifests and embed the runtime in the Rust CLI.
Load compatible package files directly from verified store blobs while retaining
ordinary complete GVS installations for explicitly materialized dependency trees.
Reuse the existing fetch, graph hashing, linking, build approval, and command shim
pipelines. Preserve loader references during store pruning and preload the loader
for normal script and command execution. Keep application filesystem APIs intact.
Document the install mode; keep compatibility experiment results in the implementation task.
Related to https://github.com/pnpm/tasks/issues/64. Future linker-specific settings and nested hoisting configuration are tracked separately in https://github.com/pnpm/tasks/issues/72. Only the loaded-linker object form is included here.
rustc emits unwind tables on Linux even under panic = "abort". The CLI
never unwinds, so pass -C force-unwind-tables=no to its Linux release
builds. The NAPI addon builds with panic = "unwind" and keeps them.
The flag goes in CARGO_TARGET_<TRIPLE>_RUSTFLAGS rather than RUSTFLAGS:
Cargo appends it to the ppc64le rustflags in .cargo/config.toml, while
RUSTFLAGS would replace them and drop the LLD workaround. cross forwards
CARGO_* variables into its container.
Measured on fat-LTO release builds of pnpm-cli:
- aarch64 gnu: 48.48 MB to 47.42 MB, .eh_frame 2.60 MB to 1.53 MB,
gzip -185 KB.
- aarch64 musl: -0.98 MB.
- x86_64 gnu and musl: unchanged. Fat LTO merges in std's bitcode, and
LLVM's x86 backend then emits the same frame info for every function.
On aarch64 every FDE remains, but without uwtable LLVM writes only the
prologue's unwind info, so each entry is smaller. Panic messages, exit
codes and RUST_BACKTRACE output are unchanged.
* chore: update dependencies, Node.js, pnpm, and GitHub Actions
* fix(deps): hold `@yarnpkg/core` at 4.9.1
`@yarnpkg/core` 4.9.2 was published with `got` declared as a Yarn
`patch:` specifier. pnpm resolves that to a `link:` into a nonexistent
workspace path, so `got` is missing and the TypeScript compile fails
with TS2307 in `@yarnpkg/core/lib/httpUtils.d.ts`.
Pin the catalog entry back to 4.9.1, override the transitive copy that
`@yarnpkg/nm` pulls in through `^4.9.1`, and add the package to
`update.ignoreDeps` so the daily update-lockfile run stops bumping it.
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>
* ci(release): unpack each verified tarball once
The release verify jobs compared every payload file against the packed
tarball with one `tar -xOf` per file, which decompresses the whole
tarball each time. Since the WebContainer runtime added dist/wasm (a
47 MB pnpm.wasm plus about 600 files) to both the pnpm and `@pnpm/exe`
wrappers, that loop runs for more than ten minutes. Unpack each tarball
once and compare files with cmp.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* ci(release): reject payload paths outside the package
With the tarball unpacked once, a files entry with enough .. segments
resolves to the same outside file on both sides of cmp and passes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The 12.9.0 release bundled the 47 MB WebAssembly build and its runtime
dependencies into the `pnpm` and `@pnpm/exe` wrappers, so every native
install downloaded about 55 MB to run a binary that never uses it.
The runtime now publishes as its own package, `@pnpm/wasm`, which provides
the `pnpm`, `pn`, `pnpx`, and `pnx` bins. `pnpm/wasm/stage.mjs` writes it to
`pnpm/npm/wasm`, and the release stages it with the native packages. The
wrappers no longer carry `dist/wasm`. In a WebContainer their launchers
exit with an error that names `@pnpm/wasm`, and `install.js` leaves the
Node launchers in place instead of linking a native binary there.
The browser test now covers the `@pnpm/wasm` tarball installed locally and
globally, including the bin aliases and `npx` dispatch. The wrapper and
Corepack modes are removed, since the wrapper no longer runs in
WebContainers.
A release event runs the workflow file from the tagged commit, and the
sync took the release keys and verification scripts from that commit
too. Anyone able to push a tag and publish its release controlled the
checks that guard the job holding DOCS_SYNC_TOKEN.
The release event now only dispatches Sync documentation from the
default branch with the release tag, using GITHUB_TOKEN with
actions: write. The sync itself runs only on workflow_dispatch, as a
manual retry does, with the workflow, scripts and keys from main. The
docs-sync environment should be restricted to main to close the other
route to the token.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sync documentation ran on the completion of the Release workflow. The
release only stages pnpm on npm, so the publication check always failed
and every sync had to be dispatched by hand after approval.
The maintainer publishes the draft GitHub release after approving the
staged packages, so that event now starts the sync. The tag commit must
match the commit the release event names, and the npm publication check
stays.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sync documentation now carries blog/releases/<version>.md into the
pnpm.io publish commit. pnpm/pnpm.io writes the page from the changelog
section the release PR curated, read at the signed release commit, so
the release page and the GitHub release body share one source and patch
releases get pages too.
Release pages are added without clearing the directory, which keeps the
hand-written pages for earlier releases.
Compile the shared Rust CLI for wasm32-wasip1-threads and supply filesystem,
network, process, terminal and cancellation capabilities through a Node host.
Select host implementations at compile time so native builds retain their
native dependency/features and execution paths.
Bundle the WASM runtime in the regular npm wrappers and select it from the
WebContainer runtime marker. Keep Node launchers usable without installation
scripts. Native installation replaces all four launchers with native bytes.
Regenerate shims after upgrades or dependency builds replace Node targets with
native binaries, including before dependent lifecycle scripts run. Preserve
collision winners and publish replacement shims with executable permissions.
Reject non-descendant permission templates and intermediate symlink escapes, validate before creating directories, and avoid path-based cleanup races.
Restore direct native aliases in both version-management implementations.
Retain process cleanup after failed stdin shutdown. Prevent cross-origin redirects from replaying login credentials in both CLI lines, and strip npm-otp from cross-origin WASM requests. Avoid repeated selected-snapshot and removed-bin scans.
Preserve resolution, lockfile, integrity and build-approval behavior. Use an
isolated WASM store with rollback journals and process leases. Patch WASI path
normalization for multiple parent segments and retain that patch in bundles.
Add pinned tooling, release packaging, real-browser workflow and selection
tests, documentation and a release note. Document host limits, including the
Corepack cold-download failure in WebContainer's streaming hash implementation.
Controlled native CLI cases showed no separated slowdown, and the latest
hoisted rebuild and repeat-install fixture improved. Retain the documented
microbenchmark uncertainty and host limitations.
Related to pnpm/tasks#63.
GitHub CLI can print an HTTP error response to stdout before exiting nonzero.
Appending pending inside command substitution retained that response in the
conclusion variable, which the polling loop misread as a terminal build result.
Check the command's exit status and set conclusion to pending on failure.
Preserve terminal build failure handling, polling frequency, and step timeout.
Run the workflow step with stubbed commands in CI to cover API-error recovery
and genuine build failures.
The unsaved constructor dependency test expected the temporary directory's
original spelling, but the dependency tree returns its canonical path.
Resolve the expected path with the native asynchronous API to handle Windows
short names and symlinked temp roots. The production dependency-tree behavior is unchanged.
Add TypeScript ports of the perfectionist rules that the Rust workspace
enforces through dylint.toml to @pnpm/eslint-config, with the same limits,
and fix every finding that needs no refactor.
The size and shape rules (function length, cognitive complexity, nesting,
local bindings, file length) are enforced for packages that already meet
them. The packages that do not are listed in PENDING_SIZE_AND_SHAPE_REFACTOR
in eslint.config.mjs and are refactored in follow-up changes.
The custom rules mirror perfectionist's semantics rather than reusing
sonarjs or max-lines-per-function, so that one limit means the same thing
in both codebases: a closure is a level of the body that contains it, a
nested function declaration is measured on its own, and a test-framework
callback plays the role of a #[test] function.
Bugs found and fixed along the way:
- a registry error body cut at exactly 64 KiB lost its truncation marker
- removing a dependency could leave dangling .bin shims
- constructor/__proto__ names crashed the projects graph, were skipped by
the injected-deps syncer, and were dropped from writeWantedLockfile's
returned lockfile
The CI chunk runner learns to run the node:test suite of the ESLint
config package.
The root package.json pins packageManager to pnpm 12.8.1, so the freshly
built 12.8.2 binary handed off to the pinned x64 pnpm under qemu-ppc64le
and failed with an exec format error, blocking the v12.8.2 release.
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>
Gate documentation publication on creation of a generated commit.
An unchanged import exits the commit step successfully, but must also skip
credential lookup and pushing. Emit changed=true after a successful commit
and use it as the condition for the push step.
The cross 0.2.5 ppc64le image uses binutils 2.26.1. It mislinks
calls to optional libc functions absent from its sysroot: the
published binary tests the dynamically resolved gettid address,
then branches to an unmapped address instead of calling it.
Let GCC retain its sysroot and library selection, but route its
linker invocation to the LLD bundled with the pinned Rust toolchain.
This fixes the weak-symbol calls without raising the glibc baseline
or adding another linker download. Scope the setting to ppc64le.
Smoke-test the actual release binary against a newer libc, where
gettid is present. Testing only against the build sysroot misses
the bug because Rust takes its syscall fallback there.
Closespnpm/pnpm#16380.