Skip to content

feat(npm): experimental Yarn PnP zero-install support - #2957

Open
mh0pe wants to merge 4 commits into
aspect-build:mainfrom
mh0pe:codex/yarn-lock-repo-cache-v2.3.7
Open

feat(npm): experimental Yarn PnP zero-install support#2957
mh0pe wants to merge 4 commits into
aspect-build:mainfrom
mh0pe:codex/yarn-lock-repo-cache-v2.3.7

Conversation

@mh0pe

@mh0pe mh0pe commented Jul 29, 2026

Copy link
Copy Markdown

Summary

This is an experimental Yarn PnP zero-install importer. rules_js never runs
Yarn and never synthesizes a node_modules tree. The caller supplies labels
for the checked-in yarn.lock, .pnp.data.json, .pnp.cjs,
.pnp.integrity.json, .yarnrc.yml, cache archives, and any unplugged runtime
files.

Repository analysis parses Yarn Berry lockfile versions 6, 8, and 10 and
cross-validates the generated PnP graph: roots and top-level fallback state,
dependency edges and aliases, virtual-to-physical locators, link types, cache
and unplugged locations, and lockfile bindings. It generates
pnp_js_binary / pnp_js_test wrappers plus pnp_verify_test.

The integrity manifest binds file type, executable mode, and sha512 for the
resolver, graph, config, lockfile, every cache archive, and every referenced
unplugged file. Runtime verification happens before .pnp.cjs is loaded,
rejects noncanonical or escaping paths and source symlinks, checks exact cache
and unplugged tree membership, and independently checks lockfile checksums when
present. Valid lock entries without a checksum remain protected by the
integrity manifest; an active unplugged package may retain one matching
lock-backed source archive.

Version support

  • Yarn 4.18.0 / lockfile v10: import, verification, and runtime execution,
    covered by e2e/pnp_zero_install with real peer-virtualized React packages
    and an unplugged package.
  • Yarn 3.8.7 / lockfile v6: import and verification, covered by
    e2e/pnp_zero_install_yarn3. Yarn 3 runtime execution under Bazel is not
    claimed; the fixture README documents the generated runtime's multi-path
    recursion limitation.
  • Lockfile v8 remains supported by the parser and unit coverage.

Known limitations: CommonJS PnP only (.pnp.loader.mjs / ESM is not yet
supported), Yarn-generated cache and unplugged path conventions are recognized
rather than invented, and these wrappers do not compose with rules that
require a linked node_modules layout.

Changes are visible to end-users: yes

  • Searched for relevant documentation and updated as needed: yes (fixture
    READMEs and inline API documentation)
  • Breaking change (forces users to change their own code or config): no
  • Suggested release notes appear below: yes

Suggested release note: experimental support for consuming integrity-bound
Yarn PnP zero-install projects in Bazel without executing Yarn or creating a
node_modules tree.

Test plan

Fresh direct validation on the final reconstructed tree:

  • Buildifier 8.2.1 format check and lint: passed for all 11 authored
    Starlark/BUILD files.
  • node --test npm/private/pnp/pnp_verify_test.mjs: 14/14 passed, including
    corrupted archives, graph/config mutation, executable-mode binding, path
    traversal, source symlinks, and exact unplugged-tree controls.
  • Both integrity manifests regenerate byte-for-byte; direct verification
    passes for the Yarn 4 and Yarn 3 fixtures.
  • Yarn 4.18.0: install --immutable --immutable-cache --check-cache passed;
    yarn node main.js printed pnp-zero-install-ok.
  • Yarn 3.8.7: install --immutable --immutable-cache --check-cache passed
    (import/verification support only).
  • git diff --check, Git object connectivity, and clean-worktree checks
    passed. Read-only self-review found and fixed a standalone-verifier ..
    path traversal mismatch; no P0/P1/P2 findings remain.

Earlier focused, forced-uncached Bazel 7.7.1 validation executed 13/13 focused
tests (126 packages, 7,555 configured targets, 51.009s). That run predates the
final formatting, copy-mode, and path-traversal hardening, so it is supporting
evidence rather than a final-head Bazel result.

The full Yarn 4 //... gate is not claimed green. After repairing the
local Node cache artifact, the final authorized warmed run on the HFS-backed
workspace emitted zero package, target, action, or test progress for 30
minutes and was stopped by its exact process handle (exit 143). This is
qualified as a local Bazel/filesystem infrastructure block; the direct gates
above are the current final-head evidence.

@CLAassistant

CLAassistant commented Jul 29, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@mh0pe
mh0pe force-pushed the codex/yarn-lock-repo-cache-v2.3.7 branch 2 times, most recently from e216450 to 9bc6ffc Compare July 29, 2026 12:23
@mh0pe
mh0pe marked this pull request as ready for review July 29, 2026 14:53

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9bc6ffccd1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread npm/private/yarn_graph.bzl Outdated
Comment thread npm/private/npm_translate_lock_state.bzl Outdated
Comment thread docs/pnpm.md Outdated
@jbedard

jbedard commented Jul 29, 2026

Copy link
Copy Markdown
Member

Why? Does modern yarn provide a strict lockfile describing the exact structure of node_modules similar to pnpm? Seeing 3000 line .js files implies no to me, and I would not want to support such a feature.

@mh0pe
mh0pe force-pushed the codex/yarn-lock-repo-cache-v2.3.7 branch from dc7115a to 2748ff5 Compare July 30, 2026 01:24

mh0pe commented Jul 30, 2026

Copy link
Copy Markdown
Author

You’re right on the core distinction: modern Yarn pins the logical resolution graph, not the exact physical node_modules topology. The final representation is derived from the lockfile together with manifests, configuration, the selected linker, and the Yarn version.

This PR compensates by running pinned Yarn to export a stricter graph and verified archives, which explains the adapter size and creates a larger maintenance boundary than the original description made clear. I don’t think this broad exporter is appropriate to merge upstream as-is, so I’ve updated the description to say that explicitly and will keep this implementation in the fork.

If Yarn support is potentially in scope, I can instead open a small design proposal for a PnP-focused importer based on Yarn’s generated PnP representation and checked-in cache, without asking rules_js to maintain this exporter.

@jbedard

jbedard commented Aug 1, 2026

Copy link
Copy Markdown
Member

rules_js should never run yarn, running pnpm to convert yarn to pnpm-lock is an edge case we handle but not part of the standard workflow

If we support yarn the lockfile must be deterministic without any rules_js logic deciding the node_modules layout which I assume is what a lot of this PR is doing

@mh0pe
mh0pe force-pushed the codex/yarn-lock-repo-cache-v2.3.7 branch from 0bc86ee to f8c36bc Compare August 1, 2026 02:21
@mh0pe

mh0pe commented Aug 1, 2026

Copy link
Copy Markdown
Author

@jbedard Took your last comment as the spec and replaced this PR's content entirely —
code instead of the design proposal I'd offered. The exporter is gone.

What's here now is a PnP zero-install importer: rules_js never executes yarn,
and nothing in rules_js decides a layout. The developer checks in what
yarn install generated (.pnp.data.json, yarn.lock, .pnp.cjs, the cache
zips), Starlark cross-validates the data file against the lockfile, and a
generated test re-hashes every cache archive against yarn.lock at build time.
Under PnP there's no node_modules for anyone to decide — the checked-in
registry is the layout, reviewed the same way pnpm-lock.yaml is.

Authored surface is ~1,100 lines; the diff looks bigger because two
Yarn-generated .pnp.cjs e2e fixtures are checked in. Yarn 4 is supported
end-to-end including runtime under Bazel (tested on 7.7.1 and 8.4.2); Yarn 3
is import/verify only, for a reason documented in the fixture. Honest gaps:
CJS-only runtime so far, and it doesn't compose with node_modules-shaped
rules yet.

Apologies for the force-push over the old review context — the previous
architecture wasn't worth preserving in the diff. If this shape is worth
reviewing I'll take it wherever you want it to go; if it's still out of
scope, say so and I'll close. I'm trying to avoid maintaining a fork for the #2 package manager in the JS ecosystem.

@mh0pe mh0pe changed the title feat(npm): add native Yarn lock support feat(npm): experimental Yarn PnP zero-install support Aug 1, 2026
mh0pe and others added 4 commits August 1, 2026 13:24
Consume a checked-in Yarn PnP zero-install project without ever running
Yarn. The pnp module extension reads the user-generated .pnp.data.json and
yarn.lock, cross-validates them (every hard-linked package must resolve to
an offline-cache archive covered by a lockfile checksum), and generates
pnp_js_binary / pnp_js_test wrappers that load the checked-in .pnp.cjs
resolver via --require. A generated pnp_verify_test recomputes the sha512
of every cache archive against yarn.lock and rejects stale or foreign
archives.

All resolution and layout decisions are made by Yarn on the developer's
machine and are fully described by the checked-in files; rules_js only
parses and consumes them, mirroring the pnpm-lock.yaml review model.

Tests:
- npm/private/test: 4 Starlark suites covering the Berry lockfile parser
  (incl. multi-descriptor keys), PnP data validation, virtual (peer)
  locator devirtualization, and error paths - all passing
- e2e/pnp_zero_install: a real Yarn 4.12.0 zero-install fixture; main_test
  resolves transitive deps out of the cache zips under Bazel and
  verify_cache_test validates all archives - both passing

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hsnpEj9uwyqyFo4W9swPQ
Extend the Berry lockfile parser to metadata version 6 (Yarn 3.x): bare
sha512 checksums without a cache-key prefix and unquoted dependency
ranges, validated against artifacts generated by a real Yarn 3.8.7
install in the new e2e/pnp_zero_install_yarn3 fixture.

Yarn 3 support is import/verification only: pnp_verify_test passes
against the Yarn 3 cache, but the Yarn 3 .pnp.cjs runtime loads a second
instance of itself when the project is reachable through more than one
path spelling (as under Bazel's runfiles and output trees) and recurses
until the stack overflows, so runtime execution stays Yarn 4+ and the
limitation is documented in the fixture.

The Yarn 4 e2e (runtime + verification) also passes on Bazel 8.4.2 in
addition to 7.7.1.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hsnpEj9uwyqyFo4W9swPQ
@mh0pe
mh0pe force-pushed the codex/yarn-lock-repo-cache-v2.3.7 branch 2 times, most recently from b048d13 to 40014c2 Compare August 7, 2026 00:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants