lockfile.integrityTarget: tar makes the integrity of every package tarball
entry the hash of its uncompressed tar, recorded as settings.integrityTarget.
The lockfile then survives a registry recompressing its tarballs, switching
to tar resources, or being replaced by a mirror that does either. A lockfile
recorded under another target is stale: a frozen install fails with
ERR_PNPM_LOCKFILE_CONFIG_MISMATCH, and any other install re-resolves with
update-checksums semantics so no hash of the old kind carries forward. The
setting is opt-in and the lockfile version is unchanged.
The tarball crate now reports both hashes of every extraction: the resource
after content decoding, and the tar a .tgz decodes to. A pin names one of
them (IntegrityKind::Resource / Tar), or either at resolution, where a locked
or published integrity may be of either kind (ResourceOrTar, sha512 only).
Under a tar pin every gzip member is decoded and nothing may follow the tar.
The resource pin keeps today's behaviour, extracting the first member and
tolerating trailing data, but then records no tar.
Store rows extracted from a .tgz record tarIntegrity and get an alias under
the tar key, in a table pnpm v11 never reads. Store lookups follow aliases,
so a tar-pinned frozen install is served offline by a store that .tgz
installs populated, and resolution takes a version's tar hash from its row
before downloading anything.
Registry revisions now always pin their tar, revision 0 included, and are
fetched with a tar pin, so the digest route can use any content coding.
Revision metadata must list revision 0 and the selected revision, and a
digest-route URL without a revision is rejected. pnpr keys its digest
references by tar hash on every registry format, serves the route as a
negotiated tar resource, and projects revision 0 explicitly. pnpm v11 cannot
extract tar resources, so it now rejects revisions with
ERR_PNPM_REGISTRY_REVISIONS_UNSUPPORTED.
Implements pnpm/rfcs text/0000-tar-integrity-for-every-source.md and
pnpm/rfcs#42.
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.
pnpm/pnpm#16238 kept build-created symlinks out of the remote
side-effects cache: pnpm v12 did not publish a diff that records one
and rejected an artifact that carries one, and both clients' artifact
validators refused the mode. The guards protected clients that would
restore such an entry as a regular file. No client has shipped the
remote cache, so there is no such client, and builds that create links
were never shared.
Let a remote artifact carry a symlink entry with the shape the local
side-effects diff already uses: the mode carries the S_IFLNK file-type
bits and the blob is the link target. The shared-artifact protocol owns
SYMLINK_MODE, and the store crate re-exports it. The artifact key stays
at v1, because no artifact of the old shape exists outside tests.
Both clients derive a resolved artifact's overlay the way they derive a
persisted store row's, so a link is restored under the same rules or the
artifact is quarantined: pnpm v12 through the store crate's
side_effects_overlay, pnpm v11 through toSideEffectsFilesMap in
@pnpm/store.cafs. The persisted-row blob check accounts for a link's
target blob, which the overlay names by the link rather than by path.
Each reader tells a recorded link apart from a file inside the loop it
already runs over the diff, in both stacks: pnpm v11 feeds a
SideEffectsFilesMapBuilder from its verification, path-mapping and
artifact-hydration loops, and the Rust overlay builder branches on the
mode in its one pass.
A project that publishes from a subdirectory reached the pnpr server without
its publishConfig, so the server rebuilt the project's package.json as
publishing from its own root: the lockfile recorded no publishDirectory,
dependents linked the project root and served sources instead of the built
directory, and a lockfile written that way failed the next frozen install
with ERR_PNPM_OUTDATED_LOCKFILE.
Send publishConfig.directory (with linkDirectory) with every project from both
clients, pacquet and `@pnpm/pnpr.client`, and keep it in the resolution cache
key so a changed publish directory cannot reuse a cached resolution. The field
stays optional on the wire: a project without a publish directory sends the
body servers already accept, and a server that predates the field ignores it.
Forwarding only fixes installs whose server is upgraded too, so both clients
now also check the returned lockfile the way the project transforms are
checked — an importer the server returns without the requested publish
directory fails the resolve instead of being merged, which is what the issue
asks for when a server cannot honour the setting. An importer the response
does not carry at all is left alone: a partial install only ever merges the
importers that came back.
A publish directory that escapes its project — absolute, drive- or
UNC-bearing, or carrying a `..` component — is rejected among the request's
other refusals, before any resolve path runs, so the new field cannot address
a link target outside the workspace the server resolves in and the frozen fast
path cannot return a lockfile it never checked. The rule rejects escapes only:
a local install accepts `./dist`, so the pnpr path must not fail where it
succeeds.
`@pnpm/pnpr.client` takes a minor changeset because the option joins the public
`ResolveViaPnprServerOptions` surface; the other four packages keep a patch
bump.
resolve_project moves from pnpr_resolution.rs to pnpr_request.rs, next to the
two functions that call it: the new field pushed the former past the
400-code-line file limit the perfectionist dylint enforces.
Related to pnpm/pnpm#14460.
Co-authored-by: Hermes Agent <210416642+0xNoramiya@users.noreply.github.com>
pnpm/pnpm#14243 recorded every verified shared build artifact's envelope
digest in the consuming package snapshot, keyed by input key, artifact
owner, and consumer platform fingerprint. Reproducibility is now enforced
by the registry instead: a published artifact occupies a write-once slot
per input key and compatibility set, so the digest a lockfile would have
pinned is the only one that entry can ever serve.
That makes the pins pure lockfile churn. They were per-machine — a
platform fingerprint only the machines sharing it could write — so a
lockfile grew an entry per package, per owner, and per platform in the
team, and no two contributors produced the same diff.
Remove the `artifactPins` snapshot field, the `pnpm update
--build-artifacts` mode that rewrote it, and the pin plumbing through
resolution, materialization, and the pnpr client in both stacks. Stored
artifacts are still reverified against current trust, policy, platform,
and source before reuse; that check now reports a verdict rather than
returning the digest that fed a pin.
The feature was never released, so no changeset records its removal.
Related to pnpm/pnpm#13771.
`sideEffectsCache` now carries the whole story — whether a build is
restored, whether one is saved, and the remote tier that shares it
between machines. `remoteSideEffectsCache` broke the rule that a setting
extending another starts with its name, sorting away from the family it
belongs to; uniting the three rather than renaming one follows what the
registries settings did. The remote tier's `organization` becomes `org`,
which is what pnpr calls that namespace and what its endpoints are built
from.
Both spellings shipped in pacquet 12.0.0 but in no released pnpm 11, so
the TypeScript side is a free rename and only pacquet needs the aliases.
Every older spelling keeps working. Precedence is per field rather than
per section, and does not depend on key order: the two spellings
accumulate separately, `organization` is resolved to `org` per source,
and they are combined once after every source has been read. The Rust
side uses an explicit field rather than a serde alias, which would make a
file carrying both keys a duplicate-field parse error.
Two behaviours move toward pacquet, which already had them right:
`sideEffectsCacheReadonly` blocks writing, and setting it alongside
`sideEffectsCache: false` gives a read-only view rather than switching
the cache off. The declaration can also express writing without reading,
which the Rust `cache`/`readonly` pair had no spelling for, so `Config`
gained the two settings its helpers now prefer.
The shared-artifact protocol encoded package identity and source integrity at the top level of every candidate and signed payload. That shape could only represent dependency side effects, even though task caching is designed to reuse the same transport and trust model.
Move those fields into a discriminated subject shared by the Rust and TypeScript implementations. Dependency artifacts retain their package and source integrity, while workspace-task artifacts identify a workspace-relative project and task. Validate each subject against its artifact kind, input-key domain, and allowed owner type, and include the subject in pnpr's entry identity and candidate matching.
This intentionally changes the experimental wire format, so pnpr servers and clients must be upgraded together.
Related to pnpm/rfcs#22.
Teach the shared-artifact protocol to recognize additive v1 Darwin and Windows tags that encode architecture, Node major, and a minimum operating-system version.
Select exact supported tags before version floors, then choose the greatest compatible floor and keep universal artifacts as the final fallback. Detect macOS product versions with the absolute system `sw_vers` path and Windows NT kernel versions with the platform APIs. Enable restoration and publishing on Darwin and Windows x64 and arm64.
Implement the same protocol, fingerprints, selection, and host-detection behavior in the TypeScript CLI and pacquet. Keep all existing Linux tags and fingerprints stable.
Related to pnpm/pnpm#13771 and pnpm/rfcs#24.
Persist the signed origin metadata for hydrated remote build artifacts in each package's store-index row. This lets later installs reuse a verified artifact without contacting pnpr, while preserving enough evidence to reevaluate the artifact under the consumer's current trust configuration.
Before reuse, verify the envelope signature, signer, owner, package identity, source integrity, input key, compatibility constraints, lockfile pin, builder profile, manifest-to-diff mapping, and the digest and size of every stored CAS blob. Remote entries do not pass through the ordinary local side-effects-cache path when this verification is unavailable or fails.
Track trusted invalid manifests and corrupt remote blobs in a bounded per-channel quarantine stored alongside the package index. Keep transient network, server, and local filesystem failures out of quarantine so they can recover normally. Frozen stores may reuse valid persisted artifacts but do not hydrate, persist, or quarantine.
Apply the same behavior and serialized metadata shape to the TypeScript CLI and pacquet.
Related to pnpm/pnpm#13771.
Persist verified shared build artifact envelope digests in the package snapshot
that consumed them. Scope each pin by dependency input key, artifact owner, and
consumer platform so compatible variants remain independently reproducible.
Apply pins only after the existing trust and compatibility checks. A missing
pinned artifact is a remote-cache miss and falls back to a local build; frozen
installs enforce pins without mutating the wanted lockfile.
Add `pnpm update --build-artifacts` as an explicit update mode that clears
existing pins, forces materialization, and records only artifacts that complete
verification and hydration without changing package versions. Reject command
combinations that cannot safely write or verify the lockfile.
Implement the lockfile format, install plumbing, update mode, and tests in both
TypeScript pnpm and pacquet.
Related to pnpm/pnpm#13771.
Reuse store content when restoring a remote build.
A remote side-effects restore downloaded every file of the artifact, then handed the bytes to the store, which addresses content by the very digest the artifact manifest already carries. A built package's files are mostly its own, and artifacts share files with one another, so most of what was transferred was content the store already had.
Ask the store first. `locateFileInStore` answers with the path it holds for a digest and mode, or nothing, and the restore only fetches what is missing. The store keeps executable and non-executable content apart, so the mode is part of the question.
Nothing is trusted that was not trusted before: the store addresses content by its hash, so a file it holds under an artifact's digest is that artifact's bytes.
The pacquet side already had the store helper this needs; its test pins the part that could silently drift — a digest or executable-bit rule diverging between the write side and the artifact side would not fail anything, it would just make the lookup always miss.
Follow-up to pnpm/pnpm#14171.
Integrate the remote side-effects cache proof of concept with dependency installation in both TypeScript pnpm and pacquet.
Plan eligible build candidates before contacting pnpr, batch lookup requests, verify the configured P-256 trust keys and artifact integrity, hydrate verified files into CAFS, and select the restored side-effects map before lifecycle scripts run. Fall back to the ordinary local build on all cache failures.
Configure the feature through the remoteSideEffectsCache setting, assembled by the config reader from the workspace file, the global config file and the environment. A repository declares which organization and packages are eligible and nothing else: everything describing the act of signing stays with the machine holding the key, so a cloned repository cannot turn that key into a signing oracle.
Capture and publish the actual post-build diff from trusted builders. Bound artifact hydration and blob downloads, retain CAFS paths instead of downloaded buffers, and withhold direct store writes from a store opened read-only. Add transactional owner and global storage accounting to pnpr alongside request, response, timeout, streaming-memory, manifest, variant, descriptor, lock-namespace, concurrency, and crash-residue bounds.
Keep the proof of concept organization-scoped and Linux glibc-only. Defer lockfile pinning, persistent quarantine, publisher ownership, and key lifecycle policy to the RFC.
Related to pnpm/rfcs#20.
Allow revision-only updates to delegate dependency resolution to pnpr.
Carry updatePatches across both pnpr clients and map it server-side to pacquet's RefreshRevisions seed policy. Explicit refreshes bypass frozen reuse and pnpr's whole-resolution cache, while the registry resolver revalidates metadata even when its in-memory or disk cache is warm.
Route both pnpm implementations through their existing pnpr install and materialization paths. The Rust update command accepts --pnpr-server, and partial TypeScript workspace refreshes continue locally so unselected importers are not refreshed.
Follow-up to https://github.com/pnpm/pnpm/pull/14175.
Related to https://github.com/pnpm/rfcs/pull/19.
The pnpr resolution protocol omitted patchedDependencies and packageExtensions. Its fast path therefore returned an untransformed lockfile, while bypassing pnpr for these projects would sacrifice the remote-resolution performance the setting exists to provide.
Compute patch hashes on the client and send only the ordered selector-to-hash map to pnpr. The server uses those hashes to key patched snapshots in the lockfile, while the client retains the local paths and applies the patch files during materialization. Send package extensions and unused-patch policy alongside them so pnpr resolves the same transformed dependency graph as local resolution.
Key pnpr's interned configuration and resolution cache by the forwarded transforms, validate patch digests at the protocol boundary, and scan package-extension dependency URLs through the existing fetch allowlist and inline-credential checks. Implement the protocol in both TypeScript and Rust clients and cover it with live server-to-client tests. Both clients validate the returned transform metadata so older servers that ignore the new request fields fail with a stable protocol error instead of silently returning an untransformed lockfile. pnpr advertises transform support in the HTTP response headers, allowing the Rust client to validate compatibility before consuming any package frames. Current servers retain streaming prefetch performance; older servers fail before they can trigger downloads or unbounded hint buffering.
Fixespnpm/pnpm#14177.
A resolve request carried the client's `minimumReleaseAge` and
`trustPolicy` but not its `resolutionMode`, and `intern_config` never
set `config.resolution_mode`, so every pnpr-delegated resolve ran on the
server's `highest` default. A project configured for `time-based` or
`lowest-direct` got a lockfile pinning the highest satisfying version of
everything, with nothing to show that the setting had been dropped.
The mode now travels with the request and reaches the interned config,
which is what `PickPolicy::from_config` reads on the server side. It
also joins both cache keys — the interned-config key and the resolution
cache key — so two modes can neither share a config nor replay each
other's resolution.
`VerifyLockfileOptions` deliberately does not carry it: verification
judges an existing lockfile against the client's policy and picks no
version.
Source the fallback for an omitted setting from the config's own
defaults rather than a hardcoded copy of them, and restore the
rationale for gating the input-lockfile fallback on a frozen request.
Send the three settings inline like every other optional field of the
resolve request: the server reads an explicit null the same as an
absent key, so the manual inserts bought nothing.
Give the resolver settings their own forwarding test instead of
folding them into the verification-policy one.
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
Pacquet's recursive command pipeline previously dispatched selected projects independently or lost the selection before package-manager execution. This could overwrite earlier manifest mutations, truncate workspace lockfile state, or materialize projects outside the requested filter.
Thread one workspace selection through command dispatch and package-manager execution, batch manifest mutations into a single install, retain the complete wanted lockfile, and derive current-lockfile and node_modules state from the selected dependency closure. Apply the same workspace root and selection semantics to query, state, global, deploy, and pnpr-backed paths.
Keep full-workspace recursive installs on the ordinary unfiltered fast and frozen paths, and add regression coverage for filtering, lockfile preservation, dependency closure materialization, pnpr multi-importer responses, and TypeScript parity edge cases.
Depends on pnpm/pnpm#13016.
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>