Skip to content

feat(pnpr): support --lockfile-only on the pnpr install path (resolve-only mode)#12148

Merged
zkochan merged 2 commits into
mainfrom
fix/12146
Jun 2, 2026
Merged

feat(pnpr): support --lockfile-only on the pnpr install path (resolve-only mode)#12148
zkochan merged 2 commits into
mainfrom
fix/12146

Conversation

@zkochan

@zkochan zkochan commented Jun 2, 2026

Copy link
Copy Markdown
Member

What

Lift the restriction tracked by #12146: pacquet install --lockfile-only (and the lockfileOnly setting) is now honored when a pnprServer / agent is configured. Previously pacquet errored rather than silently fetching + linking (added in #12144). The faithful pnpm behavior — resolve + write pnpm-lock.yaml, fetch nothing, link nothing — is now supported via a server-side resolve-only mode plus a defensive client.

How

This implements both options the issue outlined: a server resolve-only mode and a client that skips materialization.

Rust pnpr / pacquet (the issue's explicit scope)

  • pnpr server (install_accelerator.rs, protocol.rs): new lockfileOnly request field. When set, the handler resolves and returns only the L line — skipping fetch_uncached (tarball fetch) and compute_diff (file diff), so no D/I lines are streamed.
  • pacquet-pnpr-client (lib.rs): new lockfile_only option, forwarded to /v1/install. As a backstop, when set the client returns the lockfile immediately and never touches the local store — holding the guarantee even against a server that predates resolve-only.
  • pacquet-cli (install.rs): removed the error; threads the flag through and returns right after writing the lockfile, skipping the materialization Install.

TypeScript @pnpm/agent.* client (client-side only, per maintainer guidance — the agent server is intentionally untouched)

  • @pnpm/agent.client (fetchFromPnpmRegistry.ts): new lockfileOnly option, sent to the server; ignores any D/I lines so the store stays untouched even though the current TS agent server still streams them.
  • @pnpm/installing.deps-installer (installFromPnpmRegistry): forwards lockfileOnly and returns after writing the lockfile, skipping the headless install.

Tests

  • Rust: pacquet-pnpr-client integration test lockfile_only_resolves_without_fetching_files (lockfile resolved, files_written == 0, index_entries_written == 0, client store index empty); pacquet-cli e2e test install_via_pnpr_lockfile_only_writes_lockfile_without_linking (lockfile written, no node_modules, no client store). All 219 tests in pnpr + pacquet-pnpr-client pass.
  • TS: agent/server integration test asserting resolution happens but indexEntries is empty and no CAFS files/ dir is written — the server streamed D lines that the client correctly ignored. All 5 integration tests pass.

Notes

  • The wire field is backward/forward compatible (unknown fields are ignored), so any client/server mix is safe: a resolve-only-aware server skips the work; an unaware one does it but the client ignores the result.
  • Per the parity rule, the TS agent server was left as-is (maintainer decision); it remains correct because the lockfile-only guarantee is enforced client-side.

Written by an agent (Claude Code, claude-opus-4-8).

Summary by CodeRabbit

  • New Features
    • Support for --lockfile-only in agent and pnpr-server modes: resolve and write the lockfile without fetching package files or creating node_modules links.
  • Tests
    • Added integration and end-to-end tests verifying lockfile resolution while skipping file downloads, store index writes, and node_modules creation.
  • Documentation
    • Release notes updated to document lockfile-only behavior and compatibility notes with older agents.

Add a server-side resolve-only mode so `--lockfile-only` (and the
`lockfileOnly` setting) is honored when a pnprServer / agent is
configured: resolve and write the lockfile, fetch nothing, link nothing.

The pnpr server skips the tarball fetch and file diff when the request
sets `lockfileOnly`, returning just the lockfile. The pnpr client and
the TypeScript agent client forward the flag and ignore any file/index
lines an older server still streams, keeping the store untouched. The
pacquet CLI no longer errors and stops after writing the lockfile.

Closes #12146
@coderabbitai

coderabbitai Bot commented Jun 2, 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: d7d8e7ec-563a-4d88-80be-28189f4fcd18

📥 Commits

Reviewing files that changed from the base of the PR and between 61e7b0a and 483f71d.

📒 Files selected for processing (2)
  • agent/client/src/fetchFromPnpmRegistry.ts
  • installing/deps-installer/src/install/index.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • agent/client/src/fetchFromPnpmRegistry.ts
📜 Recent 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). (9)
  • GitHub Check: Lint and Test (ubuntu-latest)
  • GitHub Check: Lint and Test (macos-latest)
  • GitHub Check: Lint and Test (windows-latest)
  • GitHub Check: Dylint
  • GitHub Check: Code Coverage
  • GitHub Check: Run benchmark on ubuntu-latest
  • GitHub Check: Analyze (javascript)
  • GitHub Check: Run benchmark on ubuntu-latest
  • GitHub Check: Compile & Lint
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Follow Standard Style with trailing commas, preferring functions over classes, and declaring functions after they are used (relying on hoisting)
Use a single options object instead of multiple parameters when a function needs more than two or three arguments
Follow Import Order: Standard libraries first, then external dependencies (alphabetically), then relative imports
Write self-documenting code where function names, parameters, and types explain what a function does without requiring prose comments
Do not write comments that restate what the code already says; refactor via renaming, splitting helpers, or restructuring instead
Do not repeat documentation at call sites that already exists in JSDoc on the callee; update JSDoc once for all call sites to benefit
Use JSDoc only for a function's contract (preconditions, postconditions, edge cases, why the function exists), not for re-narrating the body
Do not record past implementation shape, refactor history, or 'the previous code did X' framing in code; use git log and git blame instead
Write comments only when: the reason for code is non-obvious (hidden invariant, workaround for known bug, deliberate exception), or the right name doesn't fit (temporary technical constraint)

Files:

  • installing/deps-installer/src/install/index.ts
🧠 Learnings (1)
📚 Learning: 2026-05-14T09:04:00.133Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11622
File: resolving/npm-resolver/test/publishedBy.test.ts:350-354
Timestamp: 2026-05-14T09:04:00.133Z
Learning: In the pnpm/pnpm repository, ESLint is the authoritative style linter. Do not raise review findings for missing trailing commas in multiline function calls (e.g., `fs.writeFileSync(...)`) when this repo’s ESLint configuration does not report them and lint passes. Prefer deferring to the ESLint results for this specific trailing-comma rule rather than enforcing it manually in code review.

Applied to files:

  • installing/deps-installer/src/install/index.ts
🔇 Additional comments (1)
installing/deps-installer/src/install/index.ts (1)

2418-2421: LGTM!


📝 Walkthrough

Walkthrough

This PR implements --lockfile-only across pnpr server, pacquet CLI/client, and pnpm agent. The flag is forwarded through the install request, causes the server to skip tarball/diff work, the client to ignore streamed file/index lines, and the CLI/deps-installer to return early after writing the resolved lockfile.

Changes

Lockfile-only install across pnpr, pacquet, and pnpm agent

Layer / File(s) Summary
pnpr protocol and server-side diff skip
pnpr/crates/pnpr/src/install_accelerator/protocol.rs, pnpr/crates/pnpr/src/install_accelerator.rs
InstallRequest adds lockfile_only field; handle_install skips tarball/file diff computation when set, returning empty missing_files and package_index while preserving total_packages.
pacquet pnpr-client API and response handling
pacquet/crates/pnpr-client/src/lib.rs, pacquet/crates/pnpr-client/tests/integration.rs
InstallOptions gains lockfile_only field forwarded in request JSON; client returns early with lockfile/stats while zeroing file and index write counts when enabled. Integration test verifies resolved lockfile with no file downloads or index writes.
pacquet CLI wiring through install_via_pnpr
pacquet/crates/cli/src/cli_args/install.rs, pacquet/crates/cli/tests/pnpr_install.rs
PnprLink docs updated; install_via_pnpr forwards flag to client and early-returns after lockfile write, skipping local linking/materialization. E2E test confirms pnpm-lock.yaml created without node_modules or client store population.
pnpm agent-client stream filtering and forwarding
agent/client/src/fetchFromPnpmRegistry.ts, agent/server/test/integration.ts
FetchFromPnpmRegistryOptions adds lockfileOnly field; agent request includes the flag; NDJSON handler ignores D (digest) and I (index) lines when set to prevent CAFS downloads and store index writes. Integration test verifies lockfile resolution with empty index entries and no CAFS/files.
pnpm deps-installer integration and early return
installing/deps-installer/src/install/index.ts
installFromPnpmRegistry forwards lockfileOnly to fetchFromPnpmRegistry and early-returns after lockfile write when set, returning the lockfile and zeroed stats and skipping headless install/fetch/link phases.
Release notes and changeset documentation
.changeset/pnpr-lockfile-only.md
Changeset documents lockfile-only mode honoring for pnpr/agent: resolves and writes lockfile without store writes or node_modules linking, with client-side filtering for older server compatibility.

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant FetchFromPnpmRegistry
  participant AgentServer
  participant StreamHandler
  Caller->>FetchFromPnpmRegistry: opts.lockfileOnly=true
  FetchFromPnpmRegistry->>AgentServer: {lockfileOnly: true, ...}
  AgentServer->>StreamHandler: resolve packages, stream L/D/I lines
  StreamHandler->>StreamHandler: if lockfileOnly: skip D (digest)
  StreamHandler->>StreamHandler: if lockfileOnly: skip I (index)
  StreamHandler->>FetchFromPnpmRegistry: return L (lockfile) only
  FetchFromPnpmRegistry->>Caller: lockfile, empty indexEntries
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

  • pnpm/pnpm#11714: Related edits to install-time lockfile handling in deps-installer; overlaps in the locking/write flow.
  • pnpm/pnpm#12046: Adds --lockfile-only support in pacquet CLI/install; closely related to the CLI and client changes here.
  • pnpm/pnpm#12144: Prior pnpr client/server contract updates that touch similar protocol and install accelerator areas.

Poem

🐰 I hopped through code to skip the haul,

Resolved the lockfile, no tarballs to fall,
Streams politely ignored the file and index call,
From client to server we answered the call,
A tiny rabbit clap — the lockfile stands tall!

🚥 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 accurately and specifically describes the main feature: adding --lockfile-only support for the pnpr install path in resolve-only mode.
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.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/12146

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


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.

❤️ Share

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

@github-actions

github-actions Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Micro-Benchmark Results

Linux

group                          main                                   pr
-----                          ----                                   --
tarball/download_dependency    1.00      7.5±0.07ms   574.8 KB/sec    1.00      7.6±0.33ms   573.3 KB/sec

@codecov-commenter

codecov-commenter commented Jun 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.42857% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.48%. Comparing base (32c07bf) to head (483f71d).

Files with missing lines Patch % Lines
pnpr/crates/pnpr/src/install_accelerator.rs 86.36% 3 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main   #12148   +/-   ##
=======================================
  Coverage   87.47%   87.48%           
=======================================
  Files         262      262           
  Lines       29903    29917   +14     
=======================================
+ Hits        26158    26172   +14     
  Misses       3745     3745           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@zkochan zkochan marked this pull request as ready for review June 2, 2026 18:19
Copilot AI review requested due to automatic review settings June 2, 2026 18:19
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Review Summary by Qodo

Support --lockfile-only on pnpr install path (resolve-only mode)

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Support --lockfile-only flag on pnpr/agent install path
• Server skips tarball fetch and file diff when lockfileOnly is set
• Client returns lockfile immediately, skips materialization
• Backward compatible: older servers still work, client ignores D/I lines
Diagram
flowchart LR
  A["Client requests install<br/>with lockfileOnly flag"] -->|sends request| B["pnpr server"]
  B -->|lockfileOnly=true| C["Resolve dependencies"]
  C -->|skip fetch & diff| D["Return lockfile only<br/>no D/I lines"]
  D -->|receives lockfile| E["Write pnpm-lock.yaml"]
  E -->|stop here| F["Skip materialization<br/>no node_modules"]
  B -->|lockfileOnly=false| G["Fetch tarballs<br/>compute diff"]
  G -->|stream D/I lines| H["Download files<br/>link node_modules"]

Loading

Grey Divider

File Changes

1. pacquet/crates/cli/src/cli_args/install.rs ✨ Enhancement +17/-18

Remove lockfile-only error, forward flag to server

pacquet/crates/cli/src/cli_args/install.rs


2. pacquet/crates/cli/tests/pnpr_install.rs 🧪 Tests +34/-0

Add e2e test for lockfile-only install behavior

pacquet/crates/cli/tests/pnpr_install.rs


3. pacquet/crates/pnpr-client/src/lib.rs ✨ Enhancement +21/-0

Add lockfile_only option, skip store writes when set

pacquet/crates/pnpr-client/src/lib.rs


View more (7)
4. pacquet/crates/pnpr-client/tests/integration.rs 🧪 Tests +32/-0

Add integration test for lockfile-only resolve

pacquet/crates/pnpr-client/tests/integration.rs


5. pnpr/crates/pnpr/src/install_accelerator.rs ✨ Enhancement +30/-17

Skip fetch and diff when lockfile_only is true

pnpr/crates/pnpr/src/install_accelerator.rs


6. pnpr/crates/pnpr/src/install_accelerator/protocol.rs ✨ Enhancement +8/-0

Add lockfile_only field to InstallRequest struct

pnpr/crates/pnpr/src/install_accelerator/protocol.rs


7. agent/client/src/fetchFromPnpmRegistry.ts ✨ Enhancement +16/-0

Forward lockfileOnly flag, ignore D/I lines when set

agent/client/src/fetchFromPnpmRegistry.ts


8. agent/server/test/integration.ts 🧪 Tests +33/-0

Add integration test for lockfileOnly mode

agent/server/test/integration.ts


9. installing/deps-installer/src/install/index.ts ✨ Enhancement +15/-0

Forward lockfileOnly flag, skip headless install

installing/deps-installer/src/install/index.ts


10. .changeset/pnpr-lockfile-only.md 📝 Documentation +7/-0

Document lockfile-only support in changelog

.changeset/pnpr-lockfile-only.md


Grey Divider

Qodo Logo

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR adds faithful --lockfile-only / lockfileOnly support to the pnpr/agent “install via server” fast path, matching pnpm’s behavior: resolve and write pnpm-lock.yaml, but fetch nothing into the store and link nothing into node_modules.

Changes:

  • Adds a lockfileOnly request flag to the Rust pnpr server and uses it to return only the final L NDJSON line (no D/I streaming).
  • Updates Rust and TypeScript clients to forward lockfileOnly and defensively avoid any store materialization (including ignoring D/I lines client-side).
  • Adds integration/e2e tests covering “lockfile resolved and written, store untouched, node_modules absent” across Rust and TS paths, plus a changeset.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
pnpr/crates/pnpr/src/install_accelerator/protocol.rs Extends /v1/install request schema with lockfileOnly (camelCase) for resolve-only mode.
pnpr/crates/pnpr/src/install_accelerator.rs Implements server-side resolve-only flow by skipping tarball fetch + diff computation and returning only the lockfile line.
pacquet/crates/pnpr-client/src/lib.rs Forwards lockfileOnly and adds a client-side backstop to return early without touching the store.
pacquet/crates/pnpr-client/tests/integration.rs Adds an integration test asserting lockfile-only resolves without writing files/index entries.
pacquet/crates/cli/src/cli_args/install.rs Threads --lockfile-only through the pnpr path and stops after writing the lockfile.
pacquet/crates/cli/tests/pnpr_install.rs Adds an e2e test asserting lockfile-only writes lockfile and does not create node_modules or populate the client store.
agent/client/src/fetchFromPnpmRegistry.ts Adds lockfileOnly option and ignores D/I lines to keep the client store untouched against older servers.
agent/server/test/integration.ts Adds an integration test verifying lockfile-only does not write CAFS files or index entries client-side.
installing/deps-installer/src/install/index.ts Forwards lockfileOnly to the agent client and returns early after writing the lockfile (skipping headless install).
.changeset/pnpr-lockfile-only.md Publishes release notes/versions for the agent client and deps-installer changes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread agent/client/src/fetchFromPnpmRegistry.ts

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
pacquet/crates/cli/src/cli_args/install.rs (1)

502-518: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Always persist the resolved lockfile before the lockfile-only early return.

Line 502 still gates save_to_path() on state.config.lockfile, but Lines 516-518 return immediately for --lockfile-only. If lockfile writing is disabled in config, this path resolves and exits without creating pnpm-lock.yaml, which breaks the flag’s resolve-and-write contract.

Suggested fix
-    if state.config.lockfile {
+    if state.config.lockfile || link.lockfile_only {
         let lockfile_dir =
             state.manifest.path().parent().expect("manifest path always has a parent dir");
         outcome
             .lockfile
             .save_to_path(&lockfile_dir.join(Lockfile::FILE_NAME))
             .map_err(|err| miette::miette!("{err}"))
             .wrap_err("writing the pnpr-resolved lockfile")?;
     }

Based on learnings: only match pnpm’s behavior exactly.

🤖 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/cli/src/cli_args/install.rs` around lines 502 - 518, The
current logic only calls outcome.lockfile.save_to_path when
state.config.lockfile is true, but returns early on link.lockfile_only before
writing when the config disables lockfile, violating the --lockfile-only
contract; change the control flow so that when link.lockfile_only is set you
always persist the resolved lockfile (call
outcome.lockfile.save_to_path(&lockfile_dir.join(Lockfile::FILE_NAME)) and
wrap_err as before) regardless of state.config.lockfile, then return Ok(());
keep the original behavior for non‑lockfile‑only runs by still gating
save_to_path on state.config.lockfile.
pacquet/crates/pnpr-client/src/lib.rs (1)

198-224: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Skip opening the local store in resolve-only mode.

Line 198 still reads index.db to build storeIntegrities even when opts.lockfile_only is set. That breaks the lockfile-only “don’t touch the local store” contract and adds unnecessary local I/O on the resolve-only path.

Suggested fix
-        let store_keys = read_store_keys(opts.store_dir);
-        let store_integrities = integrities_from_keys(&store_keys);
-        let present: HashSet<&str> = store_keys.iter().map(String::as_str).collect();
+        let store_keys = (!opts.lockfile_only).then(|| read_store_keys(opts.store_dir));
+        let store_integrities =
+            store_keys.as_deref().map(integrities_from_keys).unwrap_or_default();
@@
         let parsed = parse_install_response(&ndjson)?;
@@
         if opts.lockfile_only {
             return Ok(InstallOutcome {
                 lockfile: parsed.lockfile,
                 stats: parsed.stats,
                 files_written: 0,
                 index_entries_written: 0,
             });
         }
+
+        let present: HashSet<&str> = store_keys
+            .as_ref()
+            .map(|keys| keys.iter().map(String::as_str).collect())
+            .unwrap_or_default();
🤖 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/pnpr-client/src/lib.rs` around lines 198 - 224, The code
always calls read_store_keys and integrities_from_keys to populate
storeIntegrities even when opts.lockfile_only is true; change it so that when
opts.lockfile_only is set you do NOT call read_store_keys/integrities_from_keys
(avoid opening index.db) and instead set store_integrities to an empty/neutral
value (e.g., empty vec/map) used in the JSON request; locate the calls to
read_store_keys and integrities_from_keys and the variable store_integrities
created before building the serde_json::json! request and short-circuit that
logic based on opts.lockfile_only.
installing/deps-installer/src/install/index.ts (1)

2409-2412: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Avoid logging downloads that won't happen.

This message still reports ${agentStats.filesToDownload} files to download, but the opts.lockfileOnly branch below skips fetching entirely. With the unchanged TS agent server, that count can be non-zero here, so the user-facing summary becomes misleading.

Proposed fix
     logger.info({
-      message: `Resolved ${agentStats.totalPackages} packages: ${agentStats.alreadyInStore} cached, ${agentStats.filesToDownload} files to download`,
+      message: opts.lockfileOnly
+        ? `Resolved ${agentStats.totalPackages} packages and wrote the lockfile`
+        : `Resolved ${agentStats.totalPackages} packages: ${agentStats.alreadyInStore} cached, ${agentStats.filesToDownload} files to download`,
       prefix: rootDir,
     })
🤖 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 `@installing/deps-installer/src/install/index.ts` around lines 2409 - 2412, The
log message using logger.info currently reports agentStats.filesToDownload even
when opts.lockfileOnly short-circuits fetching; update the message to reflect
the lockfile-only mode by setting the displayed files-to-download to 0 when
opts.lockfileOnly is true (or compute a local variable like
effectiveFilesToDownload = opts.lockfileOnly ? 0 : agentStats.filesToDownload)
and use that in the logger.info call (same call site that references
agentStats.totalPackages, agentStats.alreadyInStore, and prefix rootDir) so the
user-facing summary isn't misleading.
🤖 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 `@installing/deps-installer/src/install/index.ts`:
- Around line 2417-2425: The early-return for opts.lockfileOnly exits before
observing the fileDownloads promise returned by fetchFromPnpmRegistry(), risking
unhandled rejections; before returning in the lockfile-only branch, explicitly
consume the fileDownloads promise (e.g., await it or await
fileDownloads.catch(() => { /* propagate or log */ })) so any rejection is
observed, then proceed to return updatedManifest/lockfile/etc.; target the
branch guarded by opts.lockfileOnly and the fileDownloads variable created by
fetchFromPnpmRegistry().

---

Outside diff comments:
In `@installing/deps-installer/src/install/index.ts`:
- Around line 2409-2412: The log message using logger.info currently reports
agentStats.filesToDownload even when opts.lockfileOnly short-circuits fetching;
update the message to reflect the lockfile-only mode by setting the displayed
files-to-download to 0 when opts.lockfileOnly is true (or compute a local
variable like effectiveFilesToDownload = opts.lockfileOnly ? 0 :
agentStats.filesToDownload) and use that in the logger.info call (same call site
that references agentStats.totalPackages, agentStats.alreadyInStore, and prefix
rootDir) so the user-facing summary isn't misleading.

In `@pacquet/crates/cli/src/cli_args/install.rs`:
- Around line 502-518: The current logic only calls
outcome.lockfile.save_to_path when state.config.lockfile is true, but returns
early on link.lockfile_only before writing when the config disables lockfile,
violating the --lockfile-only contract; change the control flow so that when
link.lockfile_only is set you always persist the resolved lockfile (call
outcome.lockfile.save_to_path(&lockfile_dir.join(Lockfile::FILE_NAME)) and
wrap_err as before) regardless of state.config.lockfile, then return Ok(());
keep the original behavior for non‑lockfile‑only runs by still gating
save_to_path on state.config.lockfile.

In `@pacquet/crates/pnpr-client/src/lib.rs`:
- Around line 198-224: The code always calls read_store_keys and
integrities_from_keys to populate storeIntegrities even when opts.lockfile_only
is true; change it so that when opts.lockfile_only is set you do NOT call
read_store_keys/integrities_from_keys (avoid opening index.db) and instead set
store_integrities to an empty/neutral value (e.g., empty vec/map) used in the
JSON request; locate the calls to read_store_keys and integrities_from_keys and
the variable store_integrities created before building the serde_json::json!
request and short-circuit that logic based on opts.lockfile_only.
🪄 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: adf444a7-70d0-412d-80ca-40c281444360

📥 Commits

Reviewing files that changed from the base of the PR and between 32c07bf and 61e7b0a.

📒 Files selected for processing (10)
  • .changeset/pnpr-lockfile-only.md
  • agent/client/src/fetchFromPnpmRegistry.ts
  • agent/server/test/integration.ts
  • installing/deps-installer/src/install/index.ts
  • pacquet/crates/cli/src/cli_args/install.rs
  • pacquet/crates/cli/tests/pnpr_install.rs
  • pacquet/crates/pnpr-client/src/lib.rs
  • pacquet/crates/pnpr-client/tests/integration.rs
  • pnpr/crates/pnpr/src/install_accelerator.rs
  • pnpr/crates/pnpr/src/install_accelerator/protocol.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). (4)
  • GitHub Check: Agent
  • GitHub Check: ubuntu-latest / Node.js 24 / Test
  • GitHub Check: Run benchmark on ubuntu-latest
  • GitHub Check: Lint and Test (windows-latest)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Follow Standard Style with trailing commas, preferring functions over classes, and declaring functions after they are used (relying on hoisting)
Use a single options object instead of multiple parameters when a function needs more than two or three arguments
Follow Import Order: Standard libraries first, then external dependencies (alphabetically), then relative imports
Write self-documenting code where function names, parameters, and types explain what a function does without requiring prose comments
Do not write comments that restate what the code already says; refactor via renaming, splitting helpers, or restructuring instead
Do not repeat documentation at call sites that already exists in JSDoc on the callee; update JSDoc once for all call sites to benefit
Use JSDoc only for a function's contract (preconditions, postconditions, edge cases, why the function exists), not for re-narrating the body
Do not record past implementation shape, refactor history, or 'the previous code did X' framing in code; use git log and git blame instead
Write comments only when: the reason for code is non-obvious (hidden invariant, workaround for known bug, deliberate exception), or the right name doesn't fit (temporary technical constraint)

Files:

  • agent/server/test/integration.ts
  • agent/client/src/fetchFromPnpmRegistry.ts
  • installing/deps-installer/src/install/index.ts
pacquet/**/*.rs

📄 CodeRabbit inference engine (pacquet/AGENTS.md)

pacquet/**/*.rs: Log emissions are part of matching pnpm — when porting a function that fires pnpm:<channel> events through globalLogger, logger.debug(...), or streamParser.write(...), mirror the call site, payload, and ordering so @pnpm/cli.default-reporter parses pacquet's NDJSON the same way
Declare a newtype wrapper for branded string types instead of collapsing the brand into a plain String or &str in Rust
If upstream TypeScript always validates before construction of a branded string, validate in the Rust wrapper too via TryFrom<String> and/or FromStr and 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 infallible From<String> constructor
If upstream TypeScript occasionally constructs a branded string without validation, expose from_str_unchecked in 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 handwriting impl blocks; use manual impl only when conversion needs custom logic
Model TypeScript string literal unions (like 'auto' | 'always' | 'never') as Rust enums 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 in CODE_STYLE_GUIDE.md — imports, modules, naming, ownership and borrowing, parameter type selection, trait bounds, pattern matching, pipe-trait, error handling, test layout, and cloning of Arc and Rc
Choose owned vs. borrowed parameters to minimize copies; widen to t...

Files:

  • pacquet/crates/cli/tests/pnpr_install.rs
  • pacquet/crates/pnpr-client/tests/integration.rs
  • pacquet/crates/pnpr-client/src/lib.rs
  • pacquet/crates/cli/src/cli_args/install.rs
pacquet/**/tests/**/*.rs

📄 CodeRabbit inference engine (pacquet/AGENTS.md)

pacquet/**/tests/**/*.rs: Prefer real fixtures over dependency-injection seams — use tempfile::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 scalar assert_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 with insta and carefully review diffs when intentional changes alter snapshots; accept with cargo insta review only after careful review
Tests that need the mocked registry should start pnpr through pacquet-testing-utils; cargo test / cargo nextest run should not require a separate just registry-mock launch step

Files:

  • pacquet/crates/cli/tests/pnpr_install.rs
  • pacquet/crates/pnpr-client/tests/integration.rs
pnpr/**/pnpr/**/*.rs

📄 CodeRabbit inference engine (pnpr/AGENTS.md)

pnpr/**/pnpr/**/*.rs: Follow the pacquet code-style guide (../pacquet/CODE_STYLE_GUIDE.md) for Rust-level conventions including imports, naming, ownership, and error handling
Follow the pacquet contributing guide (../pacquet/CONTRIBUTING.md) for test layout and Rust conventions

Files:

  • pnpr/crates/pnpr/src/install_accelerator/protocol.rs
  • pnpr/crates/pnpr/src/install_accelerator.rs
🧠 Learnings (5)
📚 Learning: 2026-05-14T09:04:00.133Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11622
File: resolving/npm-resolver/test/publishedBy.test.ts:350-354
Timestamp: 2026-05-14T09:04:00.133Z
Learning: In the pnpm/pnpm repository, ESLint is the authoritative style linter. Do not raise review findings for missing trailing commas in multiline function calls (e.g., `fs.writeFileSync(...)`) when this repo’s ESLint configuration does not report them and lint passes. Prefer deferring to the ESLint results for this specific trailing-comma rule rather than enforcing it manually in code review.

Applied to files:

  • agent/server/test/integration.ts
  • agent/client/src/fetchFromPnpmRegistry.ts
  • installing/deps-installer/src/install/index.ts
📚 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/cli/tests/pnpr_install.rs
  • pacquet/crates/pnpr-client/tests/integration.rs
  • pacquet/crates/pnpr-client/src/lib.rs
  • pacquet/crates/cli/src/cli_args/install.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/cli/tests/pnpr_install.rs
  • pacquet/crates/pnpr-client/tests/integration.rs
  • pacquet/crates/pnpr-client/src/lib.rs
  • pacquet/crates/cli/src/cli_args/install.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/cli/tests/pnpr_install.rs
  • pacquet/crates/pnpr-client/tests/integration.rs
  • pacquet/crates/pnpr-client/src/lib.rs
  • pacquet/crates/cli/src/cli_args/install.rs
📚 Learning: 2026-05-26T21:01:06.666Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11966
File: .changeset/require-tarball-integrity.md:6-6
Timestamp: 2026-05-26T21:01:06.666Z
Learning: In pnpm lockfile-related release notes/docs (especially changeset markdown), preserve URL hostnames exactly as they appear in pnpm-lock.yaml tarball resolution entries—keep hosts like `codeload.github.com`, `bitbucket.org`, and `gitlab.com` in lowercase. Do not “correct” them to title-case/preserve brand capitalization (e.g., LanguageTool rules like `GITHUB` capitalization) because these are literal URL fragments, not platform brand names.

Applied to files:

  • .changeset/pnpr-lockfile-only.md
🔇 Additional comments (4)
.changeset/pnpr-lockfile-only.md (1)

1-7: LGTM!

pnpr/crates/pnpr/src/install_accelerator/protocol.rs (1)

69-76: LGTM!

pnpr/crates/pnpr/src/install_accelerator.rs (1)

213-246: LGTM!

pacquet/crates/pnpr-client/tests/integration.rs (1)

69-69: LGTM!

Also applies to: 116-145

Comment thread installing/deps-installer/src/install/index.ts
@github-actions

github-actions Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Integrated-Benchmark Report (Linux)

Scenario: Isolated linker: fresh restore, cold cache + cold store

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 2.067 ± 0.065 1.983 2.181 1.03 ± 0.05
pacquet@main 2.007 ± 0.072 1.943 2.149 1.00
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 2.06747756236,
      "stddev": 0.06490745653052755,
      "median": 2.0580135992599997,
      "user": 2.66473662,
      "system": 3.2600389199999995,
      "min": 1.98319618926,
      "max": 2.18066553226,
      "times": [
        1.98319618926,
        2.01410593426,
        2.14817842026,
        2.0579081002599997,
        2.0581190982599997,
        2.07925413626,
        2.1178257392599997,
        2.03831078126,
        1.99721169226,
        2.18066553226
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 2.00716057136,
      "stddev": 0.0718304372009241,
      "median": 1.98174401426,
      "user": 2.6547695199999994,
      "system": 3.22360922,
      "min": 1.94326843326,
      "max": 2.14933264126,
      "times": [
        2.03828279126,
        1.94385794226,
        1.94326843326,
        1.9740576652600001,
        2.00794579126,
        1.96656887326,
        1.98943036326,
        2.11040965626,
        2.14933264126,
        1.94845155626
      ]
    }
  ]
}

Scenario: Isolated linker: fresh restore, hot cache + hot store

Command Mean [ms] Min [ms] Max [ms] Relative
pacquet@HEAD 661.8 ± 13.9 642.7 686.6 1.00
pacquet@main 668.6 ± 40.2 640.0 777.8 1.01 ± 0.06
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 0.661841911,
      "stddev": 0.01387617410992863,
      "median": 0.6609357601,
      "user": 0.3726969400000001,
      "system": 1.3336962199999998,
      "min": 0.6426706506000001,
      "max": 0.6866095536,
      "times": [
        0.6866095536,
        0.6426706506000001,
        0.6762948226,
        0.6468306386,
        0.6646435866,
        0.6574204456,
        0.6731658686,
        0.6516231276,
        0.6644510746,
        0.6547093416
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 0.6686322732000001,
      "stddev": 0.04022184846490968,
      "median": 0.6627268941,
      "user": 0.34213844,
      "system": 1.3283613199999997,
      "min": 0.6399780446,
      "max": 0.7778274106,
      "times": [
        0.7778274106,
        0.6720127216,
        0.6617160636,
        0.6671729856,
        0.6402048866,
        0.6534939726,
        0.6399780446,
        0.6637377246,
        0.6423206806,
        0.6678582416
      ]
    }
  ]
}

Scenario: Isolated linker: fresh install, cold cache + cold store

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 2.344 ± 0.025 2.297 2.378 1.02 ± 0.02
pacquet@main 2.294 ± 0.026 2.256 2.342 1.00
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 2.34448027048,
      "stddev": 0.025178531862936532,
      "median": 2.34418526548,
      "user": 3.8256716799999992,
      "system": 3.0224516199999996,
      "min": 2.29695881098,
      "max": 2.37791400498,
      "times": [
        2.35306427798,
        2.37791400498,
        2.29695881098,
        2.35790787498,
        2.33138348298,
        2.35967726398,
        2.3767403059800003,
        2.32776698898,
        2.33530625298,
        2.32808344098
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 2.2939859944800007,
      "stddev": 0.02556010807404355,
      "median": 2.30148381548,
      "user": 3.76840148,
      "system": 3.0120445200000003,
      "min": 2.2558643539800003,
      "max": 2.3418468159800003,
      "times": [
        2.28307019398,
        2.30789961298,
        2.3008499819800003,
        2.3418468159800003,
        2.3059484569800004,
        2.27952958498,
        2.30416345298,
        2.3021176489800004,
        2.2558643539800003,
        2.25856984198
      ]
    }
  ]
}

Scenario: Isolated linker: fresh install, hot cache + hot store

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 1.537 ± 0.013 1.519 1.555 1.00
pacquet@main 1.573 ± 0.027 1.543 1.633 1.02 ± 0.02
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 1.53692370192,
      "stddev": 0.013353699459997067,
      "median": 1.53596450532,
      "user": 1.74770008,
      "system": 1.84746406,
      "min": 1.51930593682,
      "max": 1.55525909982,
      "times": [
        1.53407630782,
        1.52022605582,
        1.5236021088199998,
        1.54815966982,
        1.55525909982,
        1.51930593682,
        1.5435682088199998,
        1.5327836468199998,
        1.55440328182,
        1.53785270282
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 1.5729258402200001,
      "stddev": 0.02698350147567614,
      "median": 1.56934932832,
      "user": 1.7033368799999997,
      "system": 1.90980676,
      "min": 1.54295348982,
      "max": 1.6333049128200001,
      "times": [
        1.54295348982,
        1.56715763382,
        1.6333049128200001,
        1.55539710982,
        1.57900796482,
        1.57689264782,
        1.56120182382,
        1.57154102282,
        1.59799501682,
        1.54380677982
      ]
    }
  ]
}

@github-actions

github-actions Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

🐰 Bencher Report

Branchpr/12148
Testbedpacquet
Click to view all benchmark results
BenchmarkLatencyBenchmark Result
milliseconds (ms)
(Result Δ%)
Upper Boundary
milliseconds (ms)
(Limit %)
isolated-linker.fresh-install.cold-cache.cold-store📈 view plot
🚷 view threshold
2,344.48 ms
(-0.33%)Baseline: 2,352.36 ms
2,822.83 ms
(83.05%)
isolated-linker.fresh-install.hot-cache.hot-store📈 view plot
🚷 view threshold
1,536.92 ms
(+0.65%)Baseline: 1,526.98 ms
1,832.38 ms
(83.88%)
isolated-linker.fresh-restore.cold-cache.cold-store📈 view plot
🚷 view threshold
2,067.48 ms
(-0.06%)Baseline: 2,068.66 ms
2,482.39 ms
(83.29%)
isolated-linker.fresh-restore.hot-cache.hot-store📈 view plot
🚷 view threshold
661.84 ms
(+1.06%)Baseline: 654.88 ms
785.85 ms
(84.22%)
🐰 View full continuous benchmarking report in Bencher

…ct minimumReleaseAge unit doc

Address PR review: avoid a possible unhandled rejection if the NDJSON
stream errors after the L frame in lockfile-only mode, and fix the
`minimumReleaseAge` JSDoc (minutes, not seconds) to match the resolver
and the Rust client.
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