Skip to content

feat(lockfile): emit packageExtensionsChecksum and pnpmfileChecksum for pnpm parity - #883

Merged
jdx merged 4 commits into
aubepkg:mainfrom
rubnogueira:feat/pnpm-lockfile-checksums
Jun 13, 2026
Merged

jdx merged 4 commits into
aubepkg:mainfrom
rubnogueira:feat/pnpm-lockfile-checksums

Conversation

@rubnogueira

@rubnogueira rubnogueira commented Jun 13, 2026 •

Copy link
Copy Markdown
Contributor

Summary

pnpm writes two top-level integrity keys into pnpm-lock.yaml that aube was omitting, producing a gratuitous diff on every aube install against a pnpm-generated lockfile:

  • packageExtensionsChecksum — guards the effective packageExtensions config
  • pnpmfileChecksum — guards the active .pnpmfile.cjs / .pnpmfile.mjs

This PR computes and emits both, byte-for-byte identical to pnpm, so a workspace that uses packageExtensions and/or a pnpmfile round-trips cleanly between pnpm and aube. The keys are pnpm-format only; aube-lock.yaml output is unchanged.

What changed

  • New crates/aube-lockfile/src/pnpm/checksum.rs
  • package_extensions_checksum: a faithful Rust port of the object-hash npm package (the same library pnpm uses) over the effective packageExtensions map, with pnpm's options (respectType: false, algorithm: sha256, encoding: base64, unordered arrays/objects/sets). Output is prefixed sha256- exactly as pnpm does.
    • pnpmfile_checksum: sha256- + base64(sha256(file content)) with \r\n→\n normalization. Multiple pnpmfiles hash to sha256- over the joined, sorted per-file digests (pnpm's multi-file rule).
  • LockfileGraph (crates/aube-lockfile/src/lib.rs): two new optional fields, propagated through filter_deps and subset_to_importer.
  • Read/write (raw.rs, read.rs, write.rs): parse and serialize the keys. In the writable struct they sit immediately after overrides: and before importers:, each as its own blank-line-separated scalar — matching pnpm's field order and formatting.
  • Install plumbing: a stamp_pnpm_config_checksums helper (commands/install/settings.rs) computes both checksums from the resolved config + on-disk pnpmfile and stamps the graph right before it's written. Wired into both write paths — the streaming install (mod.rs) and --lockfile-only (resolve.rs) — so they stay byte-identical. Gated on the target lockfile being pnpm.
  • WARN_AUBE_PNPMFILE_CHECKSUM_FAILED added for the (best-effort) case where a pnpmfile can't be read for hashing.

Parity verification

  • Unit tests pin both algorithms to ground-truth values.
  • End-to-end against the real binary on a workspace using the exact packageExtensions of a production monorepo produced packageExtensionsChecksum: sha256-abc, byte-identical to pnpm's output, plus a matching pnpmfileChecksum.
  • Round-trip tests confirm read → write preserves the keys, ordering, and blank-line separation; absent keys are omitted (no diff on workspaces without packageExtensions/pnpmfile).

Notes

  • Pre-existing and orthogonal to this PR: aube emits top-level catalogs: after overrides:, whereas current pnpm emits it before. The new checksums are placed correctly relative to overrides: in either ordering. Full byte-identical parity for workspaces that use catalogs would also need that ordering aligned — out of scope here.

Test plan

  • cargo test -p aube-lockfile (checksum unit + round-trip tests)
  • cargo test -p aube-resolver
  • cargo test -p aube
  • cargo clippy --tests clean, cargo fmt --check clean
  • Manual: aube install on a pnpm workspace with packageExtensions + a
    pnpmfile, then diff pnpm-lock.yaml against pnpm install — checksums match

Summary by CodeRabbit

  • New Features

    • Lockfiles now record pnpm configuration checksums (packageExtensions and pnpmfile) and the install flow stamps them when available.
    • Added a pnpmfile detection gate to determine whether hooks are exported.
  • Bug Fixes

    • Checksum fields are preserved across graph transformations and during lockfile-only writes to avoid accidental loss.
  • Documentation

    • New warning and docs entry for pnpmfile checksum computation failures.
  • Tests

    • Unit and integration tests for checksum computation, serialization, round-trip behavior, and pnpmfile scenarios.

@coderabbitai

coderabbitai Bot commented Jun 13, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9123fa22-38f4-44be-b21b-5ad94d7993fc

📥 Commits

Reviewing files that changed from the base of the PR and between b44ec8b and 61cf65e.

📒 Files selected for processing (5)
  • crates/aube/src/commands/install/mod.rs
  • crates/aube/src/commands/install/resolve.rs
  • crates/aube/src/commands/install/settings.rs
  • crates/aube/src/pnpmfile.rs
  • test/pnpmfile_checksum.bats
🚧 Files skipped from review as they are similar to previous changes (3)
  • crates/aube/src/commands/install/resolve.rs
  • crates/aube/src/commands/install/settings.rs
  • crates/aube/src/commands/install/mod.rs

📝 Walkthrough

Walkthrough

Implements pnpm-compatible packageExtensions and pnpmfile checksum computation, exposes optional checksum fields on lockfile models, preserves them across graph transforms and I/O, and stamps computed checksums during install/write flows.

Changes

pnpm Lockfile Checksum Integration

Layer / File(s) Summary
Warning code and base64 dependency
crates/aube-codes/src/warnings.rs, crates/aube-lockfile/Cargo.toml, docs/error-codes.data.json
New warning constant WARN_AUBE_PNPMFILE_CHECKSUM_FAILED, docs entry, and base64 workspace dependency.
pnpm-compatible checksum algorithms
crates/aube-lockfile/src/pnpm/checksum.rs, crates/aube-lockfile/src/pnpm/mod.rs
Core module implementing package_extensions_checksum via object-hash emulation and pnpmfile_checksum via per-file hashing and sorted composition; includes JS-number formatting and unit tests pinning vectors.
Lockfile model and serialization
crates/aube-lockfile/src/lib.rs, crates/aube-lockfile/src/pnpm/raw.rs, crates/aube-lockfile/src/pnpm/write.rs, crates/aube-lockfile/src/pnpm/read.rs
Added package_extensions_checksum and pnpmfile_checksum to LockfileGraph, raw, and writable pnpm lockfile models; read/write preserve and optionally emit these top-level fields in the expected order.
Lockfile structural operations
crates/aube-lockfile/src/lib.rs
Updated filter_deps and subset_to_importer to carry checksum fields through graph pruning and subset extraction.
Graph transformation preservation
crates/aube-resolver/src/peer_context.rs
Ensured peer-context dedupe/propagation passes preserve checksum fields when reconstructing LockfileGraph.
Resolver initialization and fixtures
crates/aube-resolver/src/resolve.rs, crates/aube-resolver/src/tests.rs
Fresh resolves initialize checksum fields to None; resolver fixtures updated to include the new fields.
Configuration consolidation for checksums
crates/aube/src/commands/install/settings.rs
Introduced effective_package_extensions to merge packageExtensions consistently; stamp_pnpm_config_checksums computes packageExtensionsChecksum from it and attempts pnpmfile hashing with a warning fallback.
Install-time checksum stamping
crates/aube/src/commands/install/mod.rs, crates/aube/src/commands/install/resolve.rs
Install and lockfile-only flows detect local pnpmfile (respecting ignore_pnpmfile) and call stamp_pnpm_config_checksums to stamp checksums before writing.
pnpmfile exports gate and integration tests
crates/aube/src/pnpmfile.rs, test/pnpmfile_checksum.bats
Adds exports_hooks node-based gate and unit tests, plus Bats integration tests verifying pnpmfileChecksum emission and clearing with --ignore-pnpmfile.
Lockfile format validation
crates/aube-lockfile/src/pnpm/tests.rs
Tests validate checksum emission order, exact YAML scalar formatting, round-trip preservation, and omission when absent.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • jdx/aube#879: Minor overlap on install command wiring; otherwise separate changes focused on pnpmfile checksum handling.

Suggested labels

keep-open

Poem

A rabbit hops through pnpm trees,
I hash the hooks and sort the keys,
CRLF to LF, each file's song,
Checksums stamped where they belong,
Lockfiles hum—safe and lean. 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main change: emitting two pnpm-specific checksum fields (packageExtensionsChecksum and pnpmfileChecksum) to achieve pnpm format parity.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-apps Bot commented Jun 13, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds pnpm's two top-level lockfile integrity fields — packageExtensionsChecksum and pnpmfileChecksum — so aube-written pnpm-lock.yaml files no longer produce a gratuitous diff against pnpm's own output on workspaces that use packageExtensions or a pnpmfile.

  • checksum.rs: A careful Rust port of object-hash (with pnpm's options) for the extensions checksum, plus a sha256-<base64> file-content hasher for the pnpmfile checksum; both algorithms are pinned to ground-truth vectors from the real npm package.
  • Graph plumbing: LockfileGraph gains two new Option<String> fields that are threaded through all struct-literal construction sites in peer_context.rs, filter_deps, and subset_to_importer, and read/written verbatim by the pnpm raw/read/write layers.
  • Install stamping: stamp_pnpm_config_checksums (called on both the streaming and --lockfile-only paths) computes both checksums from the resolved config and an exports_hooks Node gate that matches pnpm's "only stamp when hooks are actually exported" behaviour.

Confidence Score: 5/5

Safe to merge; the change is additive and the checksum algorithms are pinned to ground-truth test vectors.

The new checksum paths are well-isolated, gated on pnpm lockfile format only, and covered by round-trip + vector tests. The single finding is a sync file read inside an async function — a minor convention gap that won't affect correctness or cause observable starvation given the small, once-per-install file involved.

crates/aube/src/commands/install/settings.rs — the pnpmfile_checksum sync read inside stamp_pnpm_config_checksums.

Important Files Changed

Filename Overview
crates/aube-lockfile/src/pnpm/checksum.rs New file: implements object-hash port for packageExtensionsChecksum and pnpmfile_checksum, with ground-truth unit test vectors pinning the algorithm to real npm output
crates/aube/src/commands/install/settings.rs Adds stamp_pnpm_config_checksums and effective_package_extensions helpers; pnpmfile_checksum (sync fs read) is called from inside the async stamp function without spawn_blocking
crates/aube/src/pnpmfile.rs Adds exports_hooks async fn that spawns Node via LOAD_PNPMFILE_JS to check if a pnpmfile actually exports a hooks object before stamping the checksum
crates/aube-lockfile/src/lib.rs Adds package_extensions_checksum and pnpmfile_checksum optional fields to LockfileGraph, correctly propagated through filter_deps and subset_to_importer
crates/aube-lockfile/src/pnpm/raw.rs Adds two optional serde fields to RawPnpmLockfile; rename_all = "camelCase" on the struct ensures correct YAML key mapping (packageExtensionsChecksum ↔ pnpmfileChecksum)
crates/aube-lockfile/src/pnpm/write.rs Adds the two checksum fields to WritablePnpmLockfile in the correct struct position (after overrides, before patchedDependencies) with skip_serializing_if = "Option::is_none"
crates/aube-resolver/src/peer_context.rs Threads the two new fields through all LockfileGraph struct-literal construction sites in peer context propagation functions
crates/aube-codes/src/warnings.rs Adds WARN_AUBE_PNPMFILE_CHECKSUM_FAILED as both a pub const and an entry in the ALL slice — fully meeting the add_a_code convention from CLAUDE.md
crates/aube-lockfile/src/pnpm/tests.rs Adds config_checksums_round_trip_in_pnpm_order and absent_config_checksums_are_omitted_from_yaml, covering both the happy path and the no-extensions omission case

Reviews (3): Last reviewed commit: "fix(lockfile): gate pnpmfileChecksum on ..." | Re-trigger Greptile

Comment thread crates/aube-codes/src/warnings.rs Resolved

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/aube-codes/src/warnings.rs`:
- Line 13: The constant WARN_AUBE_PNPMFILE_CHECKSUM_FAILED is declared but not
registered in the ALL array of CodeMeta entries; add a corresponding CodeMeta
entry for WARN_AUBE_PNPMFILE_CHECKSUM_FAILED to the ALL array (the same
structure used by other entries) so the metadata registry includes it for docs
and self-tests—use the constant name as the code field and fill
description/category/severity to match the style of nearby entries (ensure the
new entry follows the pattern used by other CodeMeta items and is placed with
the other warnings in the ALL array).

In `@crates/aube/src/commands/install/settings.rs`:
- Around line 495-505: The code currently preserves a stale
graph.pnpmfile_checksum when local_pnpmfile is None or when
aube_lockfile::pnpm::pnpmfile_checksum returns Err; update the control flow in
the section handling local_pnpmfile so that if let Some(path) = local_pnpmfile {
... } else { graph.pnpmfile_checksum = None; return; } and inside the match on
aube_lockfile::pnpm::pnpmfile_checksum, set graph.pnpmfile_checksum = None in
the Err(e) arm (while still emitting the tracing::warn) so missing or unreadable
pnpmfile clears the checksum instead of leaving an old value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ce504bd4-2434-4102-9b92-8d929b44f10c

📥 Commits

Reviewing files that changed from the base of the PR and between bfcd123 and d9c4697.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (15)
  • crates/aube-codes/src/warnings.rs
  • crates/aube-lockfile/Cargo.toml
  • crates/aube-lockfile/src/lib.rs
  • crates/aube-lockfile/src/pnpm/checksum.rs
  • crates/aube-lockfile/src/pnpm/mod.rs
  • crates/aube-lockfile/src/pnpm/raw.rs
  • crates/aube-lockfile/src/pnpm/read.rs
  • crates/aube-lockfile/src/pnpm/tests.rs
  • crates/aube-lockfile/src/pnpm/write.rs
  • crates/aube-resolver/src/peer_context.rs
  • crates/aube-resolver/src/resolve.rs
  • crates/aube-resolver/src/tests.rs
  • crates/aube/src/commands/install/mod.rs
  • crates/aube/src/commands/install/resolve.rs
  • crates/aube/src/commands/install/settings.rs

Comment thread crates/aube-codes/src/warnings.rs Resolved
@rubnogueira
rubnogueira force-pushed the feat/pnpm-lockfile-checksums branch from d9c4697 to 0f2599c Compare June 13, 2026 19:57

jdx commented Jun 13, 2026

Copy link
Copy Markdown
Collaborator

I found one parity issue that seems worth fixing before merge:

stamp_pnpm_config_checksums currently stamps pnpmfileChecksum for any local pnpmfile returned by crate::pnpmfile::detect(...), but that only proves the file exists. pnpm only emits pnpmfileChecksum when the loaded pnpmfile has a hooks object.

Repro I checked with pnpm@11.6.0:

printf '{"name":"x","version":"1.0.0","dependencies":{"is-number":"7.0.0"}}\n' > package.json
: > .pnpmfile.cjs
npx pnpm@11.6.0 install --lockfile-only --ignore-scripts
rg pnpmfileChecksum pnpm-lock.yaml # no match

If aube writes a checksum for that empty file, pnpm treats it as config drift on frozen installs:

ERR_PNPM_LOCKFILE_CONFIG_MISMATCH: The current "pnpmfileChecksum" configuration doesn't match the value found in the lockfile

So I think this should only stamp pnpmfileChecksum when the local pnpmfile actually participates the way pnpm expects, likely by detecting/exporting a hooks object rather than hashing on file existence alone.

This comment was generated by Codex.

pnpm records pnpmfileChecksum only when a loaded pnpmfile actually
exports a hooks object (requireHooks gates calculatePnpmfileChecksum on
`entries.some((e) => e.hooks != null)`). aube stamped the checksum for
any detected pnpmfile, so an empty or hook-less .pnpmfile.cjs grew a
spurious checksum that aborts pnpm's frozen install with
ERR_PNPM_LOCKFILE_CONFIG_MISMATCH.

Load the pnpmfile the way pnpm does and only stamp the checksum when it
exports hooks; a hook-less, unreadable, or absent pnpmfile clears it.

Co-authored-by: Cursor <cursoragent@cursor.com>
@jdx
jdx enabled auto-merge (squash) June 13, 2026 22:00
@jdx
jdx merged commit cacdd02 into aubepkg:main Jun 13, 2026
17 checks passed
@greptile-apps

greptile-apps Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Want your agent to iterate on Greptile's feedback? Try greploops.

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.

2 participants