feat(lockfile): emit pnpmfileChecksum in pacquet lockfiles#12280
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughAdds pnpmfile checksum support: SHA256+base64 helpers, worker protocol to detect pnpmfile hooks, checksum calculation in NodeJsHooks, threading of checksum into lockfile generation and fresh-install, lockfile schema addition, and tests. Changespnpmfile Checksum Support
Sequence DiagramsequenceDiagram
participant CLI
participant Install as Install/FreshLockfile
participant NodeJsHooks
participant NodeWorker
participant CryptoHash
participant LockfileGen as GraphToLockfileOptions
CLI->>Install: start fresh install
Install->>NodeJsHooks: call afterAllResolved hook
NodeJsHooks->>NodeWorker: ensure worker & query has_hooks
NodeWorker->>NodeWorker: send hasHooks request to runner
NodeWorker-->>NodeJsHooks: bool(hasHooks)
alt has hooks
NodeJsHooks->>CryptoHash: create_hash_from_file(pnpmfile)
CryptoHash-->>NodeJsHooks: "sha256-<base64>"
NodeJsHooks-->>Install: Some(checksum)
Install->>LockfileGen: build_fresh_lockfile(..., pnpmfile_checksum)
else no hooks
NodeJsHooks-->>Install: None
Install->>LockfileGen: build_fresh_lockfile(..., pnpmfile_checksum=None)
end
LockfileGen->>LockfileGen: include pnpmfile_checksum in Lockfile
LockfileGen-->>CLI: write pnpm-lock.yaml (with/without pnpmfileChecksum)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning Review ran into problems🔥 ProblemsStopped waiting for pipeline failures after 30000ms. One of your pipelines takes longer than our 30000ms fetch window to run, so review may not consider pipeline-failure results for inline comments if any failures occurred after the fetch window. Increase the timeout if you want to wait longer or run a Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Port pnpm's `calculatePnpmfileChecksum` so a pacquet install records the
`pnpmfileChecksum` field in `pnpm-lock.yaml` when the project's
`.pnpmfile.{cjs,mjs}` exports a `hooks` object — matching pnpm's value
byte-for-byte (sha256-base64 of the pnpmfile's CRLF-normalized contents)
and its position in the lockfile (right after `packageExtensionsChecksum`).
The checksum value is pure file hashing; only pnpm's
`entries.some(entry => entry.hooks != null)` gate consults the evaluated
module, answered here by a new `hasHooks` query on the existing
long-lived pnpmfile worker. A pnpmfile that exports no hooks records no
checksum, matching pnpm.
Addresses item 5 of #12266.
Micro-Benchmark ResultsLinux |
4d305bc to
e556028
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #12280 +/- ##
==========================================
+ Coverage 87.53% 87.54% +0.01%
==========================================
Files 280 280
Lines 33441 33476 +35
==========================================
+ Hits 29271 29307 +36
+ Misses 4170 4169 -1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Code Review by Qodo
1. Checksum failures silently dropped
|
There was a problem hiding this comment.
🧹 Nitpick comments (1)
pacquet/crates/crypto-hash/src/tests.rs (1)
19-28: ⚡ Quick winUse
tempfile::TempDirfor automatic cleanup.The test manually creates and cleans up a temp directory, but cleanup can be skipped on panic and the path may not be unique across concurrent test runs. The hooks tests (e.g.,
pacquet/crates/hooks/tests/node_hooks.rs) usetempfile::TempDir::new()which ensures uniqueness and automatic cleanup.♻️ Refactor to use tempfile::TempDir
Add
tempfileto[dev-dependencies]inpacquet/crates/crypto-hash/Cargo.toml:[dev-dependencies] tempfile = { workspace = true }Then update the test:
+use tempfile::TempDir; + #[test] fn hash_from_file_normalizes_crlf() { - let dir = std::env::temp_dir().join("pacquet-crypto-hash-crlf-test"); - std::fs::create_dir_all(&dir).unwrap(); + let dir = TempDir::new().unwrap(); + let dir = dir.path(); let crlf = dir.join("crlf.txt"); let lf = dir.join("lf.txt"); std::fs::write(&crlf, "a\r\nb\r\n").unwrap(); std::fs::write(&lf, "a\nb\n").unwrap(); assert_eq!(create_hash_from_file(&crlf).unwrap(), create_hash_from_file(&lf).unwrap()); assert_eq!(create_hash_from_file(&lf).unwrap(), create_hash("a\nb\n")); - std::fs::remove_dir_all(&dir).ok(); }🤖 Prompt for 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. In `@pacquet/crates/crypto-hash/src/tests.rs` around lines 19 - 28, Replace the manual temp-dir creation and cleanup in the test that uses create_hash_from_file and create_hash with tempfile::TempDir to ensure unique, automatically cleaned directories: add tempfile to [dev-dependencies] in pacquet/crates/crypto-hash/Cargo.toml (tempfile = { workspace = true }), then in the test use tempfile::TempDir::new() to create the directory, use tempdir.path().join(...) for crlf.txt and lf.txt, write the files there, run the same assertions against create_hash_from_file(&crlf) and create_hash(&lf) and remove the explicit std::fs::remove_dir_all call so cleanup is automatic.
🤖 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.
Nitpick comments:
In `@pacquet/crates/crypto-hash/src/tests.rs`:
- Around line 19-28: Replace the manual temp-dir creation and cleanup in the
test that uses create_hash_from_file and create_hash with tempfile::TempDir to
ensure unique, automatically cleaned directories: add tempfile to
[dev-dependencies] in pacquet/crates/crypto-hash/Cargo.toml (tempfile = {
workspace = true }), then in the test use tempfile::TempDir::new() to create the
directory, use tempdir.path().join(...) for crlf.txt and lf.txt, write the files
there, run the same assertions against create_hash_from_file(&crlf) and
create_hash(&lf) and remove the explicit std::fs::remove_dir_all call so cleanup
is automatic.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: f65f3115-c001-40a2-ad0b-139be028ea6a
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (19)
pacquet/crates/crypto-hash/Cargo.tomlpacquet/crates/crypto-hash/src/lib.rspacquet/crates/crypto-hash/src/tests.rspacquet/crates/hooks/Cargo.tomlpacquet/crates/hooks/src/lib.rspacquet/crates/hooks/src/node_runtime.rspacquet/crates/hooks/src/worker.rspacquet/crates/hooks/tests/node_hooks.rspacquet/crates/lockfile/src/lib.rspacquet/crates/lockfile/src/save_lockfile/tests.rspacquet/crates/package-manager/src/current_lockfile.rspacquet/crates/package-manager/src/current_lockfile/tests.rspacquet/crates/package-manager/src/dependencies_graph_to_lockfile.rspacquet/crates/package-manager/src/dependencies_graph_to_lockfile/tests.rspacquet/crates/package-manager/src/hoisted_dep_graph/tests.rspacquet/crates/package-manager/src/install/tests.rspacquet/crates/package-manager/src/install_with_fresh_lockfile.rspacquet/crates/real-hoist/src/tests.rspacquet/crates/resolving-deps-resolver/src/lockfile_reuse/tests.rs
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Lint and Test (macos-latest)
- GitHub Check: Run benchmark on ubuntu-latest
🧰 Additional context used
📓 Path-based instructions (3)
pacquet/**/Cargo.toml
📄 CodeRabbit inference engine (pacquet/AGENTS.md)
pacquet/**/Cargo.toml: Check whether the workspace already depends on something suitable in[workspace.dependencies]in the rootCargo.tomlbefore adding a new dependency
Keep dependencies at the right level — add a new dependency to the specific crate that needs it, not to the workspace root or shared crate unless multiple crates depend on it
Files:
pacquet/crates/crypto-hash/Cargo.tomlpacquet/crates/hooks/Cargo.toml
pacquet/**/*.rs
📄 CodeRabbit inference engine (pacquet/AGENTS.md)
pacquet/**/*.rs: Log emissions are part of matching pnpm — when porting a function that firespnpm:<channel>events throughglobalLogger,logger.debug(...), orstreamParser.write(...), mirror the call site, payload, and ordering so@pnpm/cli.default-reporterparses pacquet's NDJSON the same way
Declare a newtype wrapper for branded string types instead of collapsing the brand into a plainStringor&strin Rust
If upstream TypeScript always validates before construction of a branded string, validate in the Rust wrapper too viaTryFrom<String>and/orFromStrand do not provide an infallible public constructor
If upstream TypeScript never validates a branded string, just brand for type-safety in Rust by exposing an infallibleFrom<String>constructor
If upstream TypeScript occasionally constructs a branded string without validation, exposefrom_str_uncheckedin Rust as an escape hatch alongside the validating constructor
Match upstream serde behavior for branded strings crossing JSON, YAML, or INI boundaries by using#[serde(try_from = "String")]for deserialization and#[serde(into = "String")]for serialization
Derive simple conversions for branded strings using#[derive(derive_more::From)]and#[derive(derive_more::Into)]instead of handwritingimplblocks; use manualimplonly when conversion needs custom logic
Model TypeScript string literal unions (like'auto' | 'always' | 'never') as Rustenums instead of newtype wrappers, since the set of valid values is closed
Treat TypeScript string template literal types (like`${string}@${string}`) the same as branded string types in Rust, using a newtype wrapper with validation
Follow the code style guide inCODE_STYLE_GUIDE.md— imports, modules, naming, ownership and borrowing, parameter type selection, trait bounds, pattern matching,pipe-trait, error handling, test layout, and cloning ofArcandRc
Choose owned vs. borrowed parameters to minimize copies; widen to t...
Files:
pacquet/crates/resolving-deps-resolver/src/lockfile_reuse/tests.rspacquet/crates/package-manager/src/current_lockfile/tests.rspacquet/crates/package-manager/src/current_lockfile.rspacquet/crates/hooks/tests/node_hooks.rspacquet/crates/crypto-hash/src/lib.rspacquet/crates/hooks/src/node_runtime.rspacquet/crates/crypto-hash/src/tests.rspacquet/crates/package-manager/src/dependencies_graph_to_lockfile.rspacquet/crates/lockfile/src/save_lockfile/tests.rspacquet/crates/lockfile/src/lib.rspacquet/crates/hooks/src/lib.rspacquet/crates/hooks/src/worker.rspacquet/crates/package-manager/src/dependencies_graph_to_lockfile/tests.rspacquet/crates/package-manager/src/install_with_fresh_lockfile.rspacquet/crates/real-hoist/src/tests.rspacquet/crates/package-manager/src/install/tests.rspacquet/crates/package-manager/src/hoisted_dep_graph/tests.rs
pacquet/**/tests/**/*.rs
📄 CodeRabbit inference engine (pacquet/AGENTS.md)
pacquet/**/tests/**/*.rs: Prefer real fixtures over dependency-injection seams — usetempfile::TempDir, the mocked registry, or integration tests spawning the actual binary for happy paths and most error paths; use the DI seam only for filesystem error kinds, deterministic time, shared process-global state, or external-service happy paths
Follow test-logging guidance — log before non-assert_eq!assertions,dbg!complex structures, skip logging for simple scalarassert_eq!
Tests must not be tolerant of missing build/runtime environment by silently returning early when a tool isn't found — let the test panic when a required tool is absent
Prefer#[cfg_attr(target_os = "windows", ignore = "...")](or matching#[cfg(unix)]gates) over runtime probe-and-skip helpers for platform-locked tools
Port relevant pnpm tests to Rust tests whenever they translate when porting behavior from pnpm
Use snapshot tests withinstaand carefully review diffs when intentional changes alter snapshots; accept withcargo insta reviewonly after careful review
Tests that need the mocked registry should startpnprthroughpacquet-testing-utils;cargo test/cargo nextest runshould not require a separatejust registry-mock launchstep
Files:
pacquet/crates/hooks/tests/node_hooks.rs
🧠 Learnings (7)
📚 Learning: 2026-05-20T19:40:55.051Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11774
File: pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs:0-0
Timestamp: 2026-05-20T19:40:55.051Z
Learning: In the pacquet Rust code, ensure the semver implementation uses the `node-semver` crate (not `nodejs-semver`). `node-semver`’s public API does not include a `satisfies_with_prerelease`-style method; prerelease-tolerant matching should be implemented inline by first calling `Range::satisfies`, and when it rejects a prerelease version, retry matching against a stripped `MAJOR.MINOR.PATCH` base of the prerelease version.
Applied to files:
pacquet/crates/resolving-deps-resolver/src/lockfile_reuse/tests.rspacquet/crates/package-manager/src/current_lockfile/tests.rspacquet/crates/package-manager/src/current_lockfile.rspacquet/crates/hooks/tests/node_hooks.rspacquet/crates/crypto-hash/src/lib.rspacquet/crates/hooks/src/node_runtime.rspacquet/crates/crypto-hash/src/tests.rspacquet/crates/package-manager/src/dependencies_graph_to_lockfile.rspacquet/crates/lockfile/src/save_lockfile/tests.rspacquet/crates/lockfile/src/lib.rspacquet/crates/hooks/src/lib.rspacquet/crates/hooks/src/worker.rspacquet/crates/package-manager/src/dependencies_graph_to_lockfile/tests.rspacquet/crates/package-manager/src/install_with_fresh_lockfile.rspacquet/crates/real-hoist/src/tests.rspacquet/crates/package-manager/src/install/tests.rspacquet/crates/package-manager/src/hoisted_dep_graph/tests.rs
📚 Learning: 2026-05-22T00:08:44.646Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11837
File: pacquet/crates/resolving-npm-resolver/src/pick_package.rs:33-51
Timestamp: 2026-05-22T00:08:44.646Z
Learning: In the pnpm/pnpm repo’s pacquet Rust crates, do not flag Unicode ellipsis characters (U+2026, `…`) in Rust doc comments (`///` / `/** */`) as a lint violation. The pacquet crate’s `dylint.toml` only enables `perfectionist::derive_ordering`, and the Dylint `unicode-ellipsis` rule is not enabled for this project—so `…` in doc comments is an intentional, repo-consistent style.
Applied to files:
pacquet/crates/resolving-deps-resolver/src/lockfile_reuse/tests.rspacquet/crates/package-manager/src/current_lockfile/tests.rspacquet/crates/package-manager/src/current_lockfile.rspacquet/crates/hooks/tests/node_hooks.rspacquet/crates/crypto-hash/src/lib.rspacquet/crates/hooks/src/node_runtime.rspacquet/crates/crypto-hash/src/tests.rspacquet/crates/package-manager/src/dependencies_graph_to_lockfile.rspacquet/crates/lockfile/src/save_lockfile/tests.rspacquet/crates/lockfile/src/lib.rspacquet/crates/hooks/src/lib.rspacquet/crates/hooks/src/worker.rspacquet/crates/package-manager/src/dependencies_graph_to_lockfile/tests.rspacquet/crates/package-manager/src/install_with_fresh_lockfile.rspacquet/crates/real-hoist/src/tests.rspacquet/crates/package-manager/src/install/tests.rspacquet/crates/package-manager/src/hoisted_dep_graph/tests.rs
📚 Learning: 2026-05-20T23:07:58.444Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11784
File: pacquet/crates/resolving-deps-resolver/src/hoist_peers.rs:120-133
Timestamp: 2026-05-20T23:07:58.444Z
Learning: When reviewing code in this pacquet Rust port, follow the upstream pnpm compatibility rule: only match pnpm’s behavior exactly. Do not propose review changes that intentionally deviate from pnpm’s documented/observed behavior, even if pnpm appears buggy. If you identify a real bug in pnpm behavior, the review should prioritize fixing it upstream in pnpm first, and avoid implementing a pnpm-behavior workaround here unless the same fix has already landed upstream.
Applied to files:
pacquet/crates/resolving-deps-resolver/src/lockfile_reuse/tests.rspacquet/crates/package-manager/src/current_lockfile/tests.rspacquet/crates/package-manager/src/current_lockfile.rspacquet/crates/hooks/tests/node_hooks.rspacquet/crates/crypto-hash/src/lib.rspacquet/crates/hooks/src/node_runtime.rspacquet/crates/crypto-hash/src/tests.rspacquet/crates/package-manager/src/dependencies_graph_to_lockfile.rspacquet/crates/lockfile/src/save_lockfile/tests.rspacquet/crates/lockfile/src/lib.rspacquet/crates/hooks/src/lib.rspacquet/crates/hooks/src/worker.rspacquet/crates/package-manager/src/dependencies_graph_to_lockfile/tests.rspacquet/crates/package-manager/src/install_with_fresh_lockfile.rspacquet/crates/real-hoist/src/tests.rspacquet/crates/package-manager/src/install/tests.rspacquet/crates/package-manager/src/hoisted_dep_graph/tests.rs
📚 Learning: 2026-05-24T21:10:50.292Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11915
File: pacquet/crates/resolving-deps-resolver/src/resolve_dependency_tree.rs:553-617
Timestamp: 2026-05-24T21:10:50.292Z
Learning: When implementing dependency “revisit”/optional-folding in pacquet, mirror pnpm’s `resolveDependencies.ts` behavior: on revisit, update the `optional` flag only for the directly visited package, and do not automatically change `optional` for transitive descendants. pnpm corrects stale optional flags later via `copyDependencySubGraph` (a BFS in `lockfile/pruner/src/index.ts`/`copyDependencySubGraph`). Until pacquet has an equivalent pruner/copy-subgraph step, be aware that the raw `node.optional` can flow through to the lockfile/virtual store via `dependencies_graph_to_lockfile.rs` → `create_virtual_store.rs` → `installability.rs`, so review changes that affect optional flag propagation for this mismatch.
Applied to files:
pacquet/crates/resolving-deps-resolver/src/lockfile_reuse/tests.rs
📚 Learning: 2026-05-24T21:10:50.292Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11915
File: pacquet/crates/resolving-deps-resolver/src/resolve_dependency_tree.rs:553-617
Timestamp: 2026-05-24T21:10:50.292Z
Learning: In pacquet’s Rust resolver layer, the behavior of `ResolvedPackage.optional` “AND-folding”/revisit updates is intentionally aligned with pnpm: when a package is directly revisited, only that package’s `optional` flag is updated; transitive descendants are not re-walked/rescored in this stage. pnpm later corrects any stale optional flags downstream via its pruner/BFS logic (`copyDependencySubGraph` + `nonOptional` reachability stamping in the lockfile pruner). Since pacquet does not yet have that pruner equivalent, review should NOT flag the downstream/stale optional propagation as a bug in pacquet PRs; treat the resolver-layer optional propagation gap as expected parity until the pruning/stamping step is ported.
Applied to files:
pacquet/crates/resolving-deps-resolver/src/lockfile_reuse/tests.rs
📚 Learning: 2026-06-06T18:58:37.156Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 12243
File: pacquet/crates/package-manager/src/install_package_by_snapshot.rs:319-322
Timestamp: 2026-06-06T18:58:37.156Z
Learning: When reviewing Rust code, do not assume `matches!(expr, Pattern(_))` will move out of `expr` if `Pattern(_)` contains no by-value bindings. `matches!` desugars to a `match` that auto-borrows the scrutinee for discrimination, so even if `expr` is a non-`Copy` value behind a shared reference (e.g., `&T`), the macro should not move-out of the borrowed data purely due to `matches!`. Treat `matches!(&expr, Pattern(_))` as a readability/clarity improvement, not a correctness requirement. Only flag potential move-out-of-borrow risks when the pattern includes by-value bindings that would require moving the matched value.
Applied to files:
pacquet/crates/resolving-deps-resolver/src/lockfile_reuse/tests.rspacquet/crates/package-manager/src/current_lockfile/tests.rspacquet/crates/package-manager/src/current_lockfile.rspacquet/crates/hooks/tests/node_hooks.rspacquet/crates/crypto-hash/src/lib.rspacquet/crates/hooks/src/node_runtime.rspacquet/crates/crypto-hash/src/tests.rspacquet/crates/package-manager/src/dependencies_graph_to_lockfile.rspacquet/crates/lockfile/src/save_lockfile/tests.rspacquet/crates/lockfile/src/lib.rspacquet/crates/hooks/src/lib.rspacquet/crates/hooks/src/worker.rspacquet/crates/package-manager/src/dependencies_graph_to_lockfile/tests.rspacquet/crates/package-manager/src/install_with_fresh_lockfile.rspacquet/crates/real-hoist/src/tests.rspacquet/crates/package-manager/src/install/tests.rspacquet/crates/package-manager/src/hoisted_dep_graph/tests.rs
📚 Learning: 2026-05-20T23:23:40.606Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11788
File: pacquet/crates/lockfile/src/snapshot_dep_ref.rs:52-56
Timestamp: 2026-05-20T23:23:40.606Z
Learning: In the pacquet lockfile crate, the link target is intentionally represented as a raw `Link(String)` (not a `LinkTarget` newtype) for both `SnapshotDepRef::Link` and `ImporterDepVersion::Link`. pnpm upstream preserves `link:` targets verbatim with only a prefix check (e.g., `refToRelative`), so do not suggest introducing a `LinkTarget` newtype in a bug-fix/targeted PR. If type branding is ever desired, it should be done together for both variants in a separate dedicated refactor PR rather than as part of a small fix.
Applied to files:
pacquet/crates/lockfile/src/save_lockfile/tests.rspacquet/crates/lockfile/src/lib.rs
🔇 Additional comments (26)
pacquet/crates/lockfile/src/lib.rs (1)
140-152: LGTM!pacquet/crates/lockfile/src/save_lockfile/tests.rs (1)
205-205: LGTM!Also applies to: 243-243
pacquet/crates/package-manager/src/dependencies_graph_to_lockfile.rs (1)
103-108: LGTM!Also applies to: 153-153, 188-188
pacquet/crates/package-manager/src/current_lockfile.rs (1)
92-94: LGTM!pacquet/crates/package-manager/src/current_lockfile/tests.rs (1)
61-61: LGTM!pacquet/crates/package-manager/src/dependencies_graph_to_lockfile/tests.rs (1)
50-50: LGTM!Also applies to: 222-222, 248-248, 365-365, 1038-1038, 1166-1166, 1323-1323
pacquet/crates/crypto-hash/src/lib.rs (2)
18-28: LGTM!
30-41: LGTM!pacquet/crates/hooks/Cargo.toml (1)
14-14: LGTM!pacquet/crates/hooks/src/lib.rs (1)
83-96: LGTM!pacquet/crates/hooks/src/worker.rs (4)
114-116: LGTM!
118-127: LGTM!
129-157: LGTM!
208-208: LGTM!pacquet/crates/hooks/src/node_runtime.rs (1)
175-185: LGTM!pacquet/crates/hooks/tests/node_hooks.rs (2)
32-43: LGTM!
45-54: LGTM!pacquet/crates/crypto-hash/Cargo.toml (1)
14-14: base64 is already declared in the root workspace dependencies (rootCargo.tomlline 93), sobase64 = { workspace = true }inpacquet/crates/crypto-hash/Cargo.tomlcomplies with the workspace-dependency guideline.pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs (4)
1078-1087: LGTM!
1102-1102: LGTM!
1233-1233: LGTM!
1753-1753: LGTM!Also applies to: 1779-1779
pacquet/crates/package-manager/src/install/tests.rs (1)
6489-6540: LGTM!pacquet/crates/package-manager/src/hoisted_dep_graph/tests.rs (1)
181-181: LGTM!Also applies to: 200-200, 651-651, 691-691, 826-826
pacquet/crates/real-hoist/src/tests.rs (1)
39-39: LGTM!Also applies to: 70-70, 123-123, 178-178, 258-258, 331-331, 372-372, 427-427, 534-534, 619-619, 686-686, 764-764, 818-818, 875-875, 934-934, 987-987, 1043-1043, 1082-1082, 1128-1128
pacquet/crates/resolving-deps-resolver/src/lockfile_reuse/tests.rs (1)
35-35: LGTM!
Integrated-Benchmark Report (Linux)Each scenario reports direct installs and pnpr installs. Bencher consumes pacquet@HEAD and pnpr@HEAD. Scenario: Isolated linker: fresh restore, cold cache + cold store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 9.9832617432,
"stddev": 0.13634004699648122,
"median": 9.9306405737,
"user": 3.08201258,
"system": 3.358720880000001,
"min": 9.8567486807,
"max": 10.2557052427,
"times": [
10.2557052427,
10.1053012767,
10.1432183207,
9.8949357157,
9.9384038327,
9.8936032347,
9.8567486807,
9.922877314700001,
9.9589694847,
9.862854328700001
]
},
{
"command": "pacquet@main",
"mean": 9.908926701999999,
"stddev": 0.10714037630937995,
"median": 9.8738077122,
"user": 3.09710488,
"system": 3.36731198,
"min": 9.8470628167,
"max": 10.2063807127,
"times": [
10.2063807127,
9.8800836057,
9.8675318187,
9.8970813257,
9.9252905897,
9.8640043557,
9.8470628167,
9.8617378967,
9.8502493217,
9.8898445767
]
},
{
"command": "pnpr@HEAD",
"mean": 5.416131193000001,
"stddev": 0.08016063786197437,
"median": 5.4251777732,
"user": 2.5153872799999997,
"system": 3.0333097799999997,
"min": 5.281901098700001,
"max": 5.5099710687000005,
"times": [
5.4625021217,
5.3424196557000005,
5.371567943700001,
5.281901098700001,
5.4512345837,
5.3991209627000005,
5.497923198700001,
5.5034215737,
5.5099710687000005,
5.3412497227
]
},
{
"command": "pnpr@main",
"mean": 5.508535795799999,
"stddev": 0.1699532051797275,
"median": 5.4480172467,
"user": 2.5332630800000002,
"system": 3.0292811799999995,
"min": 5.3552728687,
"max": 5.8308583057000005,
"times": [
5.5168750717,
5.7096266557,
5.3791594217,
5.372603717700001,
5.3681556707,
5.3626434567,
5.3552728687,
5.6160720277000005,
5.8308583057000005,
5.5740907617
]
}
]
}Scenario: Isolated linker: fresh restore, hot cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 0.6454477131,
"stddev": 0.010505140063458774,
"median": 0.6437139991,
"user": 0.37901878,
"system": 1.32881896,
"min": 0.6328604646000001,
"max": 0.6696031546000001,
"times": [
0.6418006016000001,
0.6457633666000001,
0.6447760286,
0.6696031546000001,
0.6328604646000001,
0.6546219166000001,
0.6469618896000001,
0.6426519696,
0.6411612776000001,
0.6342764616000001
]
},
{
"command": "pacquet@main",
"mean": 0.6701470952000002,
"stddev": 0.05746860307523407,
"median": 0.6498552686000001,
"user": 0.36905598000000006,
"system": 1.3379893600000001,
"min": 0.6372513446000001,
"max": 0.8278510976000001,
"times": [
0.6374130656000001,
0.6372513446000001,
0.6432630766,
0.8278510976000001,
0.6554114156,
0.6390301266,
0.6794292166000001,
0.6646813876000001,
0.6442991216000001,
0.6728410996
]
},
{
"command": "pnpr@HEAD",
"mean": 0.7815292248000001,
"stddev": 0.0477518935674792,
"median": 0.7779616576000001,
"user": 0.40771298,
"system": 1.32410856,
"min": 0.7319468936000001,
"max": 0.8722978106000001,
"times": [
0.8234746496,
0.7674032486000001,
0.7407094696000001,
0.7329261536000001,
0.8722978106000001,
0.7319468936000001,
0.7885200666000001,
0.7381090536000001,
0.8219405866000001,
0.7979643156000001
]
},
{
"command": "pnpr@main",
"mean": 0.7506389971,
"stddev": 0.01723659619782505,
"median": 0.7464271966,
"user": 0.38966038000000003,
"system": 1.3396560599999998,
"min": 0.7278054656,
"max": 0.7786678496000001,
"times": [
0.7494149766000001,
0.7779090926000001,
0.7465694216000001,
0.7403459526000001,
0.7633532716000001,
0.7377582826000001,
0.7278054656,
0.7382806866000001,
0.7462849716000001,
0.7786678496000001
]
}
]
}Scenario: Isolated linker: fresh install, cold cache + cold store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 9.24331068378,
"stddev": 0.05346759522423389,
"median": 9.25121832658,
"user": 3.537384839999999,
"system": 3.32912914,
"min": 9.15869387808,
"max": 9.32413648808,
"times": [
9.213948909079999,
9.20399552408,
9.26784571008,
9.24701413008,
9.26634333908,
9.31225150808,
9.18345482808,
9.25542252308,
9.15869387808,
9.32413648808
]
},
{
"command": "pacquet@main",
"mean": 9.256965807780002,
"stddev": 0.05985070233802738,
"median": 9.24717619808,
"user": 3.59277644,
"system": 3.34216614,
"min": 9.18495978408,
"max": 9.365052670079999,
"times": [
9.20405280108,
9.18495978408,
9.20590303308,
9.203104636079999,
9.30952076108,
9.30376821008,
9.23832206708,
9.365052670079999,
9.25603032908,
9.298943786079999
]
},
{
"command": "pnpr@HEAD",
"mean": 5.120680822079999,
"stddev": 0.079623850882933,
"median": 5.09943497808,
"user": 2.30208684,
"system": 2.91443154,
"min": 5.03200230308,
"max": 5.3075110280799995,
"times": [
5.1384227320799996,
5.10966999408,
5.3075110280799995,
5.10026934208,
5.0986006140799995,
5.06875821708,
5.19951699908,
5.08109184508,
5.07096514608,
5.03200230308
]
},
{
"command": "pnpr@main",
"mean": 5.09342903858,
"stddev": 0.02826087065331442,
"median": 5.08554062408,
"user": 2.32269364,
"system": 2.9073443399999994,
"min": 5.0678015920799995,
"max": 5.14591145808,
"times": [
5.0684118930799995,
5.114412125079999,
5.0892614930799995,
5.08204391608,
5.070013331079999,
5.13357564408,
5.07382160108,
5.14591145808,
5.089037332079999,
5.0678015920799995
]
}
]
}Scenario: Isolated linker: fresh install, hot cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 1.3860114511800001,
"stddev": 0.012097854630041057,
"median": 1.38774608648,
"user": 1.54290028,
"system": 1.7695706999999998,
"min": 1.36019993198,
"max": 1.4003177769800002,
"times": [
1.3906060229800001,
1.39378700798,
1.39826212198,
1.36019993198,
1.38580787598,
1.37203434398,
1.4003177769800002,
1.38361715498,
1.38968429698,
1.38579797798
]
},
{
"command": "pacquet@main",
"mean": 1.40728049928,
"stddev": 0.011377408882940865,
"median": 1.4049762509800001,
"user": 1.55856238,
"system": 1.8153241999999998,
"min": 1.39421910098,
"max": 1.42394545598,
"times": [
1.39669331998,
1.4226374829800001,
1.42394545598,
1.4095822639800002,
1.4123586429800001,
1.40037023798,
1.39421910098,
1.41744176398,
1.3960329719800002,
1.3995237519800001
]
},
{
"command": "pnpr@HEAD",
"mean": 0.6736389228800002,
"stddev": 0.025326070170085434,
"median": 0.6627966814800001,
"user": 0.33198258,
"system": 1.2709217999999998,
"min": 0.6538189489800001,
"max": 0.7284719209800001,
"times": [
0.7284719209800001,
0.7126296939800001,
0.6691180549800001,
0.6605440879800001,
0.6594933439800001,
0.6628048119800001,
0.6538189489800001,
0.6627885509800001,
0.6660510039800001,
0.6606688109800001
]
},
{
"command": "pnpr@main",
"mean": 0.6932302469800001,
"stddev": 0.08754233751205424,
"median": 0.6676783679800001,
"user": 0.33891298,
"system": 1.2655534999999998,
"min": 0.6447194639800001,
"max": 0.9409071619800001,
"times": [
0.6797650839800001,
0.6686329609800001,
0.6647212389800001,
0.6705116399800001,
0.6750820599800001,
0.9409071619800001,
0.6667237749800001,
0.6596987159800001,
0.6447194639800001,
0.6615403689800001
]
}
]
}Scenario: Isolated linker: fresh install, cold cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 4.973146718780001,
"stddev": 0.031073116923122753,
"median": 4.95913319688,
"user": 1.70364666,
"system": 1.9008816199999998,
"min": 4.94195778288,
"max": 5.0257645148800005,
"times": [
4.947923916880001,
5.021870609880001,
4.94195778288,
4.94563518488,
5.0257645148800005,
4.95561389488,
4.962652498880001,
4.98730606088,
4.95468223288,
4.988060490880001
]
},
{
"command": "pacquet@main",
"mean": 5.0065043543800005,
"stddev": 0.0220028247436783,
"median": 5.00845822488,
"user": 1.75131716,
"system": 1.9239037199999995,
"min": 4.9658012968800005,
"max": 5.03735091588,
"times": [
5.03735091588,
5.01425144488,
5.0311837738800005,
5.00673987488,
5.02400369188,
5.01017657488,
4.994366539880001,
4.99785844988,
4.983310980880001,
4.9658012968800005
]
},
{
"command": "pnpr@HEAD",
"mean": 0.67529925138,
"stddev": 0.03729556452272575,
"median": 0.6623729598800001,
"user": 0.33613916,
"system": 1.28424262,
"min": 0.65092948388,
"max": 0.7755267088800001,
"times": [
0.7755267088800001,
0.66860721588,
0.6551142458800001,
0.69000793788,
0.6537076718800001,
0.6669963418800001,
0.6577495778800001,
0.65092948388,
0.6780293528800001,
0.6563239768800001
]
},
{
"command": "pnpr@main",
"mean": 0.7021361152800001,
"stddev": 0.06723310026428778,
"median": 0.67800935388,
"user": 0.34224426,
"system": 1.2880938199999998,
"min": 0.6678710478800001,
"max": 0.89031461588,
"times": [
0.6993053858800001,
0.6678710478800001,
0.67079111688,
0.6709917228800001,
0.89031461588,
0.6777916788800001,
0.6926466048800001,
0.6782270288800001,
0.7009702388800001,
0.6724517118800001
]
}
]
} |
|
Code review by qodo was updated up to the latest commit 526ca1a |
`hash_from_file_normalizes_crlf` used a fixed directory under the OS temp dir, so parallel test runs could contend on the same create/write/remove path and flake. Use a per-test `tempfile::TempDir` instead and drop the explicit cleanup.
526ca1a to
dc9b96f
Compare
|
Code review by qodo was updated up to the latest commit dc9b96f |
PR Summary by Qodo
Emit pnpmfileChecksum in pacquet-generated pnpm-lock.yaml (pnpm parity)
✨ Enhancement🧪 Tests⚙️ Configuration changes🕐 40+ MinutesWalkthroughs
User Description
What
Ports pnpm's
calculatePnpmfileChecksumso a pacquet install records thepnpmfileChecksumfield inpnpm-lock.yamlwhen the project's.pnpmfile.{cjs,mjs}exports ahooksobject.Addresses item 5 of #12266 (lockfile-parity gaps vs the pnpm CLI). Previously pacquet ran pnpmfile hooks but never wrote the checksum, so its lockfile diverged from pnpm whenever a project had a pnpmfile.
How
crypto-hash— ports the two missing helpers from@pnpm/crypto.hash:create_hash(sha256-<base64>) andcreate_hash_from_file(UTF-8 read + CRLF→LF normalization, the cross-platform-stability rule pnpm relies on).hooks— addsPnpmfileHooks::calculate_pnpmfile_checksum(). The checksum value is pure file hashing; only pnpm'sentries.some(entry => entry.hooks != null)gate consults the evaluated module, answered by a new lightweighthasHooksquery on the existing long-lived pnpmfile worker (already spawned during resolution). A pnpmfile that exports no hooks records no checksum, matching pnpm.lockfile— adds thepnpmfile_checksumtop-level field toLockfile, serialized right afterpackageExtensionsChecksumper pnpm'sROOT_KEYSorder (yaml_emit::ROOT_KEYSalready listed it).package-manager— threads the computed checksum throughGraphToLockfileOptions→dependencies_graph_to_lockfile→ both fresh-lockfile build paths;current_lockfilecarries it over verbatim.Verification
create_hashof this monorepo's real.pnpmfile.cjsreproduces the committedpnpmfileChecksum: sha256-FyKtfMeHqu9zq1rXAr8P3zdA0NAXUioGdY0esg+SB5s=byte-for-byte.cargo check --workspace --all-targets, targetednextestsuites, clippy-D warnings,cargo fmt --check,cargo doc -D warnings, andtyposall pass.Scope
This covers the write side (the issue's stated gap). The matching read side — frozen-install drift detection via
getOutdatedLockfileSetting— is separate;freshness.rsstill listspnpmfileChecksumas a pending settings-checker variant and is not part of item 5.Written by an agent (Claude Code, claude-opus-4-8).
AI Description
Diagram
High-Level Assessment
The following are alternative approaches to this PR:
1. Always hash when pnpmfile exists (no hooks gate)
2. Heuristic/static detection of hooks export (parse/regex)
Recommendation: Keep the PR’s approach: query the already-running pnpmfile worker for the exact
hooks-export gate (matching pnpm semantics), and compute the checksum purely from normalized file bytes. This preserves pnpm parity while keeping runtime cost low (one cheap query) and avoids brittle static analysis.File Changes
Enhancement (8)
Tests (9)
Other (2)
Summary by CodeRabbit
New Features
Tests