Skip to content

feat(pacquet): port lockfileOnly setting#12046

Merged
zkochan merged 1 commit into
mainfrom
lockfile-settings
May 29, 2026
Merged

feat(pacquet): port lockfileOnly setting#12046
zkochan merged 1 commit into
mainfrom
lockfile-settings

Conversation

@zkochan

@zkochan zkochan commented May 28, 2026

Copy link
Copy Markdown
Member

Ports pnpm's lockfileOnly / --lockfile-only to pacquet — one of the items tracked in #12042: resolve dependencies and write pnpm-lock.yaml without fetching any tarball into the store or materializing node_modules.

Design

lockfile-only lives in pnpm's excludedPnpmKeys (alongside frozen-lockfile), so it is a pure per-invocation CLI flag — not a valid pnpm-workspace.yaml / config.yaml key. It is therefore threaded straight from the CLI through Install / Add, mirroring the existing frozen_lockfile plumbing. No config-crate changes.

What changed

  • CLI: --lockfile-only added to both install and add.
  • Install::run:
    • --frozen-lockfile / auto-frozen (preferFrozenLockfile) + --lockfile-only: the freshness gate still runs (a stale lockfile surfaces OutdatedLockfile, matching pnpm), then the wanted lockfile is re-persisted and the run returns without touching node_modules. Mirrors pnpm's index.ts#L979-L986.
    • Otherwise the fresh-resolve path runs, and .modules.yaml, the current lockfile, and the workspace-state file are skipped — there is no node_modules to describe, and writing the workspace-state file would make the next install's up-to-date check believe materialization happened (pnpm skips updateWorkspaceState under lockfileOnly).
    • --lockfile-only with lockfile: false (pnpm's useLockfile: false) fails fast with ERR_PNPM_CONFIG_CONFLICT_LOCKFILE_ONLY_WITH_NO_LOCKFILE, matching pnpm's extendInstallOptions guard.
  • InstallWithFreshLockfile::run: skips the PrefetchingResolver wrapper so no tarball is fetched (matching pnpm's dryRun: opts.lockfileOnly), then writes pnpm-lock.yaml and returns before the prefetch / virtual-store / symlink / hoist / bin steps.

Tests

New pacquet/crates/cli/tests/lockfile_only.rs ports every lockfileOnly-focused test from installing/deps-installer/test/:

All other lockfileOnly references in the upstream test suite (deepRecursive, catalogs, peerDependencies, …) merely use it as setup to generate a lockfile and aren't lockfileOnly behavior tests.

The 61 existing Install { … } literals in install/tests.rs were updated with lockfile_only: false.

cargo check / clippy --deny warnings / fmt clean; the 4 new tests pass, pacquet-package-manager 365/365, pacquet-cli add+install green.

Pacquet-only change (the pnpm CLI already implements lockfileOnly), so no changeset.


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

Summary by CodeRabbit

  • New Features

    • Added --lockfile-only to pacquet add and pacquet install to update the lockfile without downloading deps or creating node_modules, matching pnpm behavior
    • Validation to prevent using --lockfile-only when lockfile writing is disabled
  • Tests

    • Integration tests covering lockfile-only behavior, frozen-lockfile interactions, workspace importer updates, and error cases when lockfiles are disabled

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown

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: 052215e0-d932-40e4-92ba-40a655f5270e

📥 Commits

Reviewing files that changed from the base of the PR and between 2690e5b and 9d42340.

📒 Files selected for processing (7)
  • pacquet/crates/cli/src/cli_args/add.rs
  • pacquet/crates/cli/src/cli_args/install.rs
  • pacquet/crates/cli/tests/lockfile_only.rs
  • pacquet/crates/package-manager/src/add.rs
  • pacquet/crates/package-manager/src/install.rs
  • pacquet/crates/package-manager/src/install/tests.rs
  • pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs
🚧 Files skipped from review as they are similar to previous changes (4)
  • pacquet/crates/package-manager/src/install/tests.rs
  • pacquet/crates/cli/src/cli_args/add.rs
  • pacquet/crates/cli/src/cli_args/install.rs
  • pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs
📜 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). (8)
  • GitHub Check: Code Coverage
  • GitHub Check: Lint and Test (windows-latest)
  • GitHub Check: Dylint
  • GitHub Check: Lint and Test (macos-latest)
  • GitHub Check: Run benchmark on ubuntu-latest
  • GitHub Check: Lint and Test (ubuntu-latest)
  • GitHub Check: Run benchmark on ubuntu-latest
  • GitHub Check: Compile & Lint
🧰 Additional context used
📓 Path-based instructions (2)
pacquet/**/*.rs

📄 CodeRabbit inference engine (pacquet/AGENTS.md)

pacquet/**/*.rs: Warnings are errors (--deny warnings in lint). Do not silence them with #[allow(...)] unless there is a specific, justified reason.
Choose owned vs. borrowed parameters to minimize copies; widen to the most encompassing type (&Path over &PathBuf, &str over &String) when it doesn't force extra copies.
Prefer Arc::clone(&x) / Rc::clone(&x) over x.clone() for reference-counted types, so the cost is visible at the call site.
Follow Rust API Guidelines for naming: https://rust-lang.github.io/api-guidelines/naming.html
No star imports inside module bodies. Write use super::{Foo, bar} instead of use super::*;, and the same for any other glob whose target is a module you control. Two forms stay allowed: external-crate preludes such as use rayon::prelude::*; and root-of-module re-exports such as pub use submodule::*; in a lib.rs.
Doc comments (///, //!) document the contract: preconditions, postconditions, panics, the reason the function exists. They are not a re-narration of the body.
Do not restate at call sites what the callee's doc comment already says. If /// on the function says 'no-op when …', the caller should not repeat that. Update the doc once; let every call site benefit.
Tests are documentation. Do not duplicate them in prose. If a behavioral scenario, edge case, failure mode, or worked example is already captured by a test, do not also narrate it in the doc comment on the implementation. The same applies in reverse: a test's own doc comment should not re-explain what the asserts already say, only the why if it is not obvious.
Use // SAFETY:, // TODO:, and similar prefixes to signal hidden invariants or known follow-ups that a reader cannot recover from the code alone.
When editing existing code, do not break a method chain (including pipe-trait .pipe(...) chains) into intermediate let bindings unless you can justify the rewrite. Valid justifications include a chain that fails to compile after your...

Files:

  • pacquet/crates/package-manager/src/add.rs
  • pacquet/crates/cli/tests/lockfile_only.rs
  • pacquet/crates/package-manager/src/install.rs
pacquet/**/{tests,test}/*.rs

📄 CodeRabbit inference engine (pacquet/AGENTS.md)

pacquet/**/{tests,test}/*.rs: Tests must not be tolerant of a missing build / runtime environment by silently returning early when a tool isn't found. If the test needs a tool, just call into it and let the existing .unwrap() / .expect(...) panic when the tool is absent.
Prefer platform-specific gates like #[cfg_attr(target_os = "windows", ignore = "...")] or #[cfg(unix)] over runtime probe-and-skip helpers for platform-locked tools, because the gate is visible to cargo test and shows up in the test report.
Follow the test-logging guidance in the style guide — log before non-assert_eq! assertions, dbg! complex structures, skip logging for simple scalar assert_eq!.

Files:

  • pacquet/crates/cli/tests/lockfile_only.rs
🧠 Learnings (3)
📚 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/package-manager/src/add.rs
  • pacquet/crates/cli/tests/lockfile_only.rs
  • pacquet/crates/package-manager/src/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/package-manager/src/add.rs
  • pacquet/crates/cli/tests/lockfile_only.rs
  • pacquet/crates/package-manager/src/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/package-manager/src/add.rs
  • pacquet/crates/cli/tests/lockfile_only.rs
  • pacquet/crates/package-manager/src/install.rs
🔇 Additional comments (12)
pacquet/crates/package-manager/src/add.rs (1)

35-38: LGTM!

Also applies to: 72-72, 118-118

pacquet/crates/package-manager/src/install.rs (4)

140-152: LGTM!

Also applies to: 292-303, 330-330, 333-340


712-740: LGTM!


836-844: LGTM!

Also applies to: 914-916


932-945: LGTM!

pacquet/crates/cli/tests/lockfile_only.rs (7)

1-17: LGTM!


19-37: LGTM!


44-111: LGTM!


116-157: LGTM!


164-204: LGTM!


211-250: LGTM!


257-332: LGTM!


📝 Walkthrough

Walkthrough

Adds a --lockfile-only CLI flag to pacquet's add and install that resolves and updates pnpm-lock.yaml without downloading tarballs or materializing node_modules. Threads the flag through Add and Install, adds validation for lockfile: false conflict, gates resolver/prefetching, and includes integration tests.

Changes

--lockfile-only feature implementation

Layer / File(s) Summary
CLI flag definitions and wiring
pacquet/crates/cli/src/cli_args/add.rs, pacquet/crates/cli/src/cli_args/install.rs
AddArgs and InstallArgs gain --lockfile-only Clap flags; CLI run destructures and forwards the flag into package-manager inputs.
Add struct wiring and propagation
pacquet/crates/package-manager/src/add.rs, pacquet/crates/cli/src/cli_args/add.rs
Add struct adds lockfile_only: bool; Add::run passes the flag into the constructed Install.
Install struct and validation
pacquet/crates/package-manager/src/install.rs
Install adds lockfile_only: bool. New InstallError::ConfigConflictLockfileOnlyWithNoLockfile variant and an early fail-fast guard when config.lockfile == false.
Frozen-path lockfile-only short-circuit
pacquet/crates/package-manager/src/install.rs
On frozen/auto-frozen path with lockfile_only, re-saves pnpm-lock.yaml (if enabled), emits ImportingDone and a summary, and returns early to avoid materialization and downstream writes.
InstallWithFreshLockfile gating and early exit
pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs
Adds lockfile_only field; when true, skips PrefetchingResolver (no tarball prefetch), resolves, optionally writes the fresh lockfile, closes store-index writer, emits ImportingDone, and returns without hoisting/materialization.
Update unit test wiring
pacquet/crates/package-manager/src/install/tests.rs
Adds lockfile_only: false to all Install { ... } test struct literals.
CLI integration tests for lockfile-only
pacquet/crates/cli/tests/lockfile_only.rs
Integration tests covering: lockfile-only writes pnpm-lock.yaml without downloads or node_modules; --frozen-lockfile --lockfile-only rejects stale lockfile; lockfile-only succeeds when fresh; config conflict when lockfile: false; workspace importer updates without materialization.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

Poem

🐰 I hop and write the lock so neat,

No tarballs fetched, no folders to meet,
Just resolve, then save — a tidy delight,
Pacquet's lockfile shines through the night. ✨

🚥 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 'feat(pacquet): port lockfileOnly setting' clearly summarizes the main change: porting the lockfileOnly feature from pnpm to pacquet. It is concise, specific, and directly related to the primary objective.
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 lockfile-settings

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 May 28, 2026

Copy link
Copy Markdown
Contributor

Micro-Benchmark Results

Linux

group                          main                                   pr
-----                          ----                                   --
tarball/download_dependency    1.01      7.5±0.34ms   575.2 KB/sec    1.00      7.5±0.59ms   578.4 KB/sec

@zkochan zkochan force-pushed the lockfile-settings branch from 98e0ed7 to 2690e5b Compare May 28, 2026 23:17
@codecov-commenter

codecov-commenter commented May 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.83333% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.94%. Comparing base (a39a83d) to head (9d42340).

Files with missing lines Patch % Lines
...package-manager/src/install_with_fresh_lockfile.rs 95.23% 2 Missing ⚠️
pacquet/crates/package-manager/src/install.rs 96.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #12046      +/-   ##
==========================================
+ Coverage   88.92%   88.94%   +0.02%     
==========================================
  Files         235      235              
  Lines       30780    30839      +59     
==========================================
+ Hits        27371    27430      +59     
  Misses       3409     3409              

☔ 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 May 28, 2026 23:28
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Review Summary by Qodo

Port pnpm's lockfileOnly setting to pacquet

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Add --lockfile-only flag to install and add commands
• Resolve dependencies and write pnpm-lock.yaml without fetching tarballs or materializing
  node_modules
• Validate lockfile freshness when combining --frozen-lockfile with --lockfile-only
• Fail fast when --lockfile-only conflicts with lockfile: false configuration
• Add comprehensive test coverage porting pnpm's lockfileOnly test suite
Diagram
flowchart LR
  CLI["CLI: --lockfile-only flag"]
  CLI -->|install| Install["Install::run"]
  CLI -->|add| Add["Add::run"]
  Install -->|frozen path| FrozenPath["Re-persist lockfile<br/>Return early"]
  Install -->|fresh path| Fresh["InstallWithFreshLockfile"]
  Fresh -->|skip prefetch| Resolve["Resolve graph<br/>No tarball fetch"]
  Resolve -->|write lockfile| Write["Write pnpm-lock.yaml<br/>Return early"]
  Add -->|mutate manifest| AddManifest["Update package.json"]
  AddManifest -->|forward flag| Install
  Validate["Config validation<br/>lockfile-only + lockfile:false<br/>= error"]
  CLI -.->|validate| Validate

Loading

Grey Divider

File Changes

1. pacquet/crates/cli/src/cli_args/install.rs ✨ Enhancement +9/-0

Add --lockfile-only flag to install command

• Add lockfile_only: bool field to InstallArgs struct with --lockfile-only CLI flag
• Thread lockfile_only parameter through to Install::run invocation
• Document flag behavior: resolve dependencies and update lockfile without fetching or materializing

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


2. pacquet/crates/cli/src/cli_args/add.rs ✨ Enhancement +6/-0

Add --lockfile-only flag to add command

• Add lockfile_only: bool field to AddArgs struct with --lockfile-only CLI flag
• Thread lockfile_only parameter through to Install::run invocation
• Document flag behavior matching install command semantics

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


3. pacquet/crates/package-manager/src/install.rs ✨ Enhancement +90/-2

Implement lockfile-only logic in Install::run

• Add lockfile_only: bool field to Install struct with comprehensive documentation
• Add ConfigConflictLockfileOnlyWithNoLockfile error variant for lockfile: false conflict
• Implement config validation: fail fast when --lockfile-only combined with lockfile: false
• Add frozen-path short-circuit: re-persist lockfile and return without materializing when
 lockfile_only && take_frozen_path
• Add fresh-path short-circuit: skip hoisted-linker and skip-runtimes guards under lockfile_only
 since no materialization occurs
• Add fresh-path early return: skip .modules.yaml, current lockfile, and workspace-state file
 writes

pacquet/crates/package-manager/src/install.rs


View more (4)
4. pacquet/crates/package-manager/src/add.rs ✨ Enhancement +6/-0

Thread lockfile-only through Add to Install

• Add lockfile_only: bool field to Add struct with documentation
• Thread lockfile_only parameter through to follow-up Install::run invocation
• Forward flag from manifest mutation to dependency materialization step

pacquet/crates/package-manager/src/add.rs


5. pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs ✨ Enhancement +75/-12

Implement lockfile-only in fresh-lockfile resolver path

• Add lockfile_only: bool field to InstallWithFreshLockfile struct
• Skip PrefetchingResolver wrapper when lockfile_only to prevent tarball fetching
• Add early return after graph resolution: build and write pnpm-lock.yaml, emit ImportingDone
 stage, return before prefetch/virtual-store/symlink/hoist/bin steps
• Cleanly close store-index writer and await writer task even when no rows written

pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs


6. pacquet/crates/package-manager/src/install/tests.rs 🧪 Tests +61/-0

Update all install tests with lockfile_only field

• Add lockfile_only: false field to all 50+ Install struct instantiations in existing tests
• Maintain test compatibility with new required field across frozen-lockfile, fresh-install,
 workspace, and edge-case scenarios

pacquet/crates/package-manager/src/install/tests.rs


7. pacquet/crates/cli/tests/lockfile_only.rs 🧪 Tests +283/-0

Add comprehensive lockfile-only test suite

• Port writes_lockfile_without_downloading_or_linking: verify lockfile written with direct and
 transitive deps, no node_modules, no store blobs, repeat run stable, follow-up install
 materializes
• Port frozen_lockfile_only_rejects_a_stale_lockfile: verify --frozen-lockfile --lockfile-only
 validates on-disk lockfile and fails when manifest drifts
• Port lockfile_false_with_lockfile_only_is_a_config_conflict: verify lockfile: false +
 --lockfile-only fails with ERR_PNPM_CONFIG_CONFLICT_LOCKFILE_ONLY_WITH_NO_LOCKFILE
• Port lockfile_only_updates_importers_when_a_project_is_added: verify workspace lockfile records
 all importers and updates when new project added, no materialization occurs

pacquet/crates/cli/tests/lockfile_only.rs


Grey Divider

Qodo Logo

@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 `@pacquet/crates/cli/tests/lockfile_only.rs`:
- Around line 11-12: The file currently re-exports the local module via a glob
(pub use _utils::*), which violates the no-local-glob rule; remove the glob and
either keep the module private (delete the pub use line) or replace it with
explicit re-exports of the specific symbols you need from module _utils (e.g.,
pub use _utils::{ThingA, helper_fn}) so only named items are re-exported; update
any call sites to import the explicit names if you choose to make the module
private.
- Around line 33-35: The cas_blobs helper is brittle because it checks
path.contains("files/") which fails on Windows; change the filter to be
path-separator-agnostic by inspecting path components instead (e.g. call
get_all_files(store_dir).into_iter().filter(|path|
Path::new(path).components().any(|c| c.as_os_str() == "files")).collect()), and
ensure you convert the matched Path/OsStr back to String consistently (e.g.
to_string_lossy().into_owned()) so cas_blobs still returns Vec<String>;
reference functions: cas_blobs and get_all_files.
🪄 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: dfd4107d-fa2b-4c63-8aaa-7b0e9d4d4bf6

📥 Commits

Reviewing files that changed from the base of the PR and between a9011ad and 2690e5b.

📒 Files selected for processing (7)
  • pacquet/crates/cli/src/cli_args/add.rs
  • pacquet/crates/cli/src/cli_args/install.rs
  • pacquet/crates/cli/tests/lockfile_only.rs
  • pacquet/crates/package-manager/src/add.rs
  • pacquet/crates/package-manager/src/install.rs
  • pacquet/crates/package-manager/src/install/tests.rs
  • pacquet/crates/package-manager/src/install_with_fresh_lockfile.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). (5)
  • GitHub Check: ubuntu-latest / Node.js 22.13.0 / Test
  • GitHub Check: windows-latest / Node.js 22.13.0 / Test
  • GitHub Check: Run benchmark on ubuntu-latest
  • GitHub Check: Lint and Test (ubuntu-latest)
  • GitHub Check: Lint and Test (macos-latest)
🧰 Additional context used
📓 Path-based instructions (2)
pacquet/**/*.rs

📄 CodeRabbit inference engine (pacquet/AGENTS.md)

pacquet/**/*.rs: Warnings are errors (--deny warnings in lint). Do not silence them with #[allow(...)] unless there is a specific, justified reason.
Choose owned vs. borrowed parameters to minimize copies; widen to the most encompassing type (&Path over &PathBuf, &str over &String) when it doesn't force extra copies.
Prefer Arc::clone(&x) / Rc::clone(&x) over x.clone() for reference-counted types, so the cost is visible at the call site.
Follow Rust API Guidelines for naming: https://rust-lang.github.io/api-guidelines/naming.html
No star imports inside module bodies. Write use super::{Foo, bar} instead of use super::*;, and the same for any other glob whose target is a module you control. Two forms stay allowed: external-crate preludes such as use rayon::prelude::*; and root-of-module re-exports such as pub use submodule::*; in a lib.rs.
Doc comments (///, //!) document the contract: preconditions, postconditions, panics, the reason the function exists. They are not a re-narration of the body.
Do not restate at call sites what the callee's doc comment already says. If /// on the function says 'no-op when …', the caller should not repeat that. Update the doc once; let every call site benefit.
Tests are documentation. Do not duplicate them in prose. If a behavioral scenario, edge case, failure mode, or worked example is already captured by a test, do not also narrate it in the doc comment on the implementation. The same applies in reverse: a test's own doc comment should not re-explain what the asserts already say, only the why if it is not obvious.
Use // SAFETY:, // TODO:, and similar prefixes to signal hidden invariants or known follow-ups that a reader cannot recover from the code alone.
When editing existing code, do not break a method chain (including pipe-trait .pipe(...) chains) into intermediate let bindings unless you can justify the rewrite. Valid justifications include a chain that fails to compile after your...

Files:

  • pacquet/crates/package-manager/src/add.rs
  • pacquet/crates/cli/src/cli_args/add.rs
  • pacquet/crates/cli/src/cli_args/install.rs
  • pacquet/crates/package-manager/src/install.rs
  • pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs
  • pacquet/crates/cli/tests/lockfile_only.rs
  • pacquet/crates/package-manager/src/install/tests.rs
pacquet/**/{tests,test}/*.rs

📄 CodeRabbit inference engine (pacquet/AGENTS.md)

pacquet/**/{tests,test}/*.rs: Tests must not be tolerant of a missing build / runtime environment by silently returning early when a tool isn't found. If the test needs a tool, just call into it and let the existing .unwrap() / .expect(...) panic when the tool is absent.
Prefer platform-specific gates like #[cfg_attr(target_os = "windows", ignore = "...")] or #[cfg(unix)] over runtime probe-and-skip helpers for platform-locked tools, because the gate is visible to cargo test and shows up in the test report.
Follow the test-logging guidance in the style guide — log before non-assert_eq! assertions, dbg! complex structures, skip logging for simple scalar assert_eq!.

Files:

  • pacquet/crates/cli/tests/lockfile_only.rs
🧠 Learnings (3)
📚 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/package-manager/src/add.rs
  • pacquet/crates/cli/src/cli_args/add.rs
  • pacquet/crates/cli/src/cli_args/install.rs
  • pacquet/crates/package-manager/src/install.rs
  • pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs
  • pacquet/crates/cli/tests/lockfile_only.rs
  • pacquet/crates/package-manager/src/install/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/package-manager/src/add.rs
  • pacquet/crates/cli/src/cli_args/add.rs
  • pacquet/crates/cli/src/cli_args/install.rs
  • pacquet/crates/package-manager/src/install.rs
  • pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs
  • pacquet/crates/cli/tests/lockfile_only.rs
  • pacquet/crates/package-manager/src/install/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/package-manager/src/add.rs
  • pacquet/crates/cli/src/cli_args/add.rs
  • pacquet/crates/cli/src/cli_args/install.rs
  • pacquet/crates/package-manager/src/install.rs
  • pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs
  • pacquet/crates/cli/tests/lockfile_only.rs
  • pacquet/crates/package-manager/src/install/tests.rs
🔇 Additional comments (7)
pacquet/crates/package-manager/src/install/tests.rs (1)

69-69: LGTM!

Also applies to: 139-139, 184-184, 258-258, 325-325, 409-409, 478-478, 567-567, 713-713, 815-815, 1017-1017, 1091-1091, 1188-1188, 1319-1319, 1418-1418, 1525-1525, 1576-1576, 1669-1669, 1765-1765, 1830-1830, 1896-1896, 1988-1988, 2096-2096, 2158-2158, 2247-2247, 2355-2355, 2470-2470, 2551-2551, 2751-2751, 2876-2876, 2977-2977, 3084-3084, 3176-3176, 3271-3271, 3363-3363, 3452-3452, 3548-3548, 3641-3641, 3739-3739, 3840-3840, 3927-3927, 4012-4012, 4083-4083, 4150-4150, 4216-4216, 4285-4285, 4370-4370, 4433-4433, 4519-4519, 4571-4571, 4643-4643, 4716-4716, 4783-4783, 5036-5036, 5218-5218, 5367-5367, 5517-5517, 5596-5596, 5648-5648, 5756-5756, 5853-5853

pacquet/crates/cli/tests/lockfile_only.rs (1)

1-10: LGTM!

Also applies to: 14-32, 37-284

pacquet/crates/cli/src/cli_args/add.rs (1)

85-89: LGTM!

Also applies to: 132-132

pacquet/crates/cli/src/cli_args/install.rs (1)

83-88: LGTM!

Also applies to: 214-214, 291-291

pacquet/crates/package-manager/src/install.rs (1)

140-152: LGTM!

Also applies to: 309-319, 346-346, 349-356, 736-756, 865-865, 868-868, 940-940, 966-969

pacquet/crates/package-manager/src/add.rs (1)

35-38: LGTM!

Also applies to: 72-72, 118-118

pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs (1)

148-154: LGTM!

Also applies to: 324-324, 501-522, 717-760

Comment thread pacquet/crates/cli/tests/lockfile_only.rs Outdated
Comment thread pacquet/crates/cli/tests/lockfile_only.rs
@github-actions

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.106 ± 0.234 1.951 2.752 1.06 ± 0.12
pacquet@main 1.988 ± 0.057 1.926 2.125 1.00
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 2.10619682816,
      "stddev": 0.23389690801367494,
      "median": 2.03772575316,
      "user": 2.6839114599999996,
      "system": 3.2414313200000002,
      "min": 1.9506793126600002,
      "max": 2.75222574966,
      "times": [
        2.0431120156600002,
        2.14113537766,
        2.03332842966,
        2.03887764866,
        2.03657385766,
        1.9506793126600002,
        1.9719466796600003,
        2.10145188666,
        2.75222574966,
        1.9926373236600001
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 1.9879522551600002,
      "stddev": 0.05653843189216168,
      "median": 1.9743547011600002,
      "user": 2.6942929599999994,
      "system": 3.19173012,
      "min": 1.9264751206600002,
      "max": 2.12484721666,
      "times": [
        1.9264751206600002,
        1.96949812166,
        1.9907137386600002,
        2.12484721666,
        1.9492454006600002,
        1.9865213536600002,
        1.9792112806600002,
        1.9578569806600001,
        2.03778762666,
        1.95736571166
      ]
    }
  ]
}

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

Command Mean [ms] Min [ms] Max [ms] Relative
pacquet@HEAD 646.1 ± 13.3 623.3 673.7 1.00
pacquet@main 658.4 ± 13.9 637.8 689.6 1.02 ± 0.03
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 0.6461327111,
      "stddev": 0.01328872438336953,
      "median": 0.6434235424000001,
      "user": 0.36951024,
      "system": 1.31888886,
      "min": 0.6232941314000001,
      "max": 0.6736648214000001,
      "times": [
        0.6736648214000001,
        0.6232941314000001,
        0.6420423574,
        0.6364569384000001,
        0.6426479474000001,
        0.6441991374000001,
        0.6493818824,
        0.6558997654000001,
        0.6406886314000001,
        0.6530514984000001
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 0.6583893615,
      "stddev": 0.013929173203895603,
      "median": 0.6551761724,
      "user": 0.36459684,
      "system": 1.3290545599999999,
      "min": 0.6378143264000001,
      "max": 0.6896485804000001,
      "times": [
        0.6896485804000001,
        0.6378143264000001,
        0.6555069904,
        0.6499928154000001,
        0.6521966204,
        0.6625356674000001,
        0.6712636634000001,
        0.6573571754,
        0.6548453544000001,
        0.6527324214000001
      ]
    }
  ]
}

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

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 2.332 ± 0.030 2.294 2.386 1.00
pacquet@main 2.334 ± 0.022 2.298 2.360 1.00 ± 0.02
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 2.3317092817800003,
      "stddev": 0.029738614634645853,
      "median": 2.34310809658,
      "user": 3.8818582,
      "system": 3.03535138,
      "min": 2.29354436958,
      "max": 2.38573571958,
      "times": [
        2.34254785158,
        2.34366834158,
        2.29354436958,
        2.29618661058,
        2.31539739558,
        2.34423987858,
        2.29900950258,
        2.38573571958,
        2.35096969258,
        2.34579345558
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 2.3341033367799997,
      "stddev": 0.02248033575467846,
      "median": 2.34144389158,
      "user": 3.8430397,
      "system": 3.0485176800000002,
      "min": 2.29798132658,
      "max": 2.3603300905799998,
      "times": [
        2.3603300905799998,
        2.34342933758,
        2.33762647358,
        2.35338760658,
        2.29992254558,
        2.31248604358,
        2.29798132658,
        2.33945844558,
        2.3448658185799998,
        2.35154567958
      ]
    }
  ]
}

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

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 1.525 ± 0.019 1.485 1.547 1.00
pacquet@main 1.556 ± 0.049 1.510 1.668 1.02 ± 0.03
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 1.52508339606,
      "stddev": 0.018502043665102358,
      "median": 1.5246331340600001,
      "user": 1.7524151000000003,
      "system": 1.82179768,
      "min": 1.4851054240600001,
      "max": 1.5469693880600002,
      "times": [
        1.5469693880600002,
        1.51199292106,
        1.4851054240600001,
        1.5243261240600001,
        1.5341115120600002,
        1.5431134700600002,
        1.5249401440600001,
        1.51874317606,
        1.5434223340600002,
        1.5181094670600002
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 1.5556893176600002,
      "stddev": 0.04943078561416003,
      "median": 1.5357794260600002,
      "user": 1.7304346999999995,
      "system": 1.8646747799999996,
      "min": 1.5096290900600002,
      "max": 1.66758604906,
      "times": [
        1.5476894360600002,
        1.5230548940600002,
        1.66758604906,
        1.59612995206,
        1.59543004006,
        1.5387068570600002,
        1.5276600630600001,
        1.5328519950600001,
        1.51815480006,
        1.5096290900600002
      ]
    }
  ]
}

@github-actions

Copy link
Copy Markdown
Contributor

🐰 Bencher Report

Branchpr/12046
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,331.71 ms
(-0.44%)Baseline: 2,342.00 ms
2,810.40 ms
(82.97%)
isolated-linker.fresh-install.hot-cache.hot-store📈 view plot
🚷 view threshold
1,525.08 ms
(+4.57%)Baseline: 1,458.40 ms
1,750.09 ms
(87.14%)
isolated-linker.fresh-restore.cold-cache.cold-store📈 view plot
🚷 view threshold
2,106.20 ms
(+0.74%)Baseline: 2,090.77 ms
2,508.92 ms
(83.95%)
isolated-linker.fresh-restore.hot-cache.hot-store📈 view plot
🚷 view threshold
646.13 ms
(-5.07%)Baseline: 680.65 ms
816.78 ms
(79.11%)
🐰 View full continuous benchmarking report in Bencher

Ports pnpm's `--lockfile-only` to pacquet: resolve dependencies and
write `pnpm-lock.yaml` without fetching any tarball into the store or
materializing `node_modules`.

`lockfile-only` is in pnpm's `excludedPnpmKeys` (like `frozen-lockfile`),
so it's a pure per-invocation CLI flag with no `pnpm-workspace.yaml` /
`config.yaml` counterpart. It's threaded straight from the CLI through
`Install` / `Add`, mirroring the existing `frozen_lockfile` plumbing —
no config-crate changes.

- `--lockfile-only` flag added to `install` and `add`.
- `Install::run`: frozen / auto-frozen + `--lockfile-only` validates the
  on-disk lockfile (errors on stale, matching pnpm), re-persists the
  wanted lockfile, and returns without materializing. Otherwise the
  fresh-resolve path runs and `.modules.yaml`, the current lockfile, and
  the workspace-state file are skipped (mirrors pnpm skipping
  `updateWorkspaceState` under `lockfileOnly`).
- `InstallWithFreshLockfile::run`: skips the `PrefetchingResolver`
  wrapper so no tarball is fetched (matching pnpm's
  `dryRun: opts.lockfileOnly`), then writes `pnpm-lock.yaml` and returns
  before prefetch / virtual-store / symlink / hoist / bin steps.
- `--lockfile-only` with `lockfile: false` (pnpm's `useLockfile: false`)
  now fails with `ERR_PNPM_CONFIG_CONFLICT_LOCKFILE_ONLY_WITH_NO_LOCKFILE`,
  matching pnpm's `extendInstallOptions` guard.

Tests: new `crates/cli/tests/lockfile_only.rs` ports every lockfileOnly
test from `installing/deps-installer/test/` — the two in
`install/lockfileOnly.ts` (resolve-and-write with no download/link;
`--frozen-lockfile --lockfile-only` rejects a stale lockfile) plus the
two lockfileOnly-focused cases in `lockfile.ts` (the `useLockfile: false`
conflict and the workspace "new project added" importer update).
@zkochan zkochan force-pushed the lockfile-settings branch from 2690e5b to 9d42340 Compare May 28, 2026 23:55
@zkochan zkochan merged commit 75e11a6 into main May 29, 2026
28 checks passed
@zkochan zkochan deleted the lockfile-settings branch May 29, 2026 00:07
zkochan added a commit that referenced this pull request May 29, 2026
…2046)

Ports pnpm's `--lockfile-only` to pacquet: resolve dependencies and
write `pnpm-lock.yaml` without fetching any tarball into the store or
materializing `node_modules`.

`lockfile-only` is in pnpm's `excludedPnpmKeys` (like `frozen-lockfile`),
so it's a pure per-invocation CLI flag with no `pnpm-workspace.yaml` /
`config.yaml` counterpart. It's threaded straight from the CLI through
`Install` / `Add`, mirroring the existing `frozen_lockfile` plumbing —
no config-crate changes.

- `--lockfile-only` flag added to `install` and `add`.
- `Install::run`: frozen / auto-frozen + `--lockfile-only` validates the
  on-disk lockfile (errors on stale, matching pnpm), re-persists the
  wanted lockfile, and returns without materializing. Otherwise the
  fresh-resolve path runs and `.modules.yaml`, the current lockfile, and
  the workspace-state file are skipped (mirrors pnpm skipping
  `updateWorkspaceState` under `lockfileOnly`).
- `InstallWithFreshLockfile::run`: skips the `PrefetchingResolver`
  wrapper so no tarball is fetched (matching pnpm's
  `dryRun: opts.lockfileOnly`), then writes `pnpm-lock.yaml` and returns
  before prefetch / virtual-store / symlink / hoist / bin steps.
- `--lockfile-only` with `lockfile: false` (pnpm's `useLockfile: false`)
  now fails with `ERR_PNPM_CONFIG_CONFLICT_LOCKFILE_ONLY_WITH_NO_LOCKFILE`,
  matching pnpm's `extendInstallOptions` guard.

Tests: new `crates/cli/tests/lockfile_only.rs` ports every lockfileOnly
test from `installing/deps-installer/test/` — the two in
`install/lockfileOnly.ts` (resolve-and-write with no download/link;
`--frozen-lockfile --lockfile-only` rejects a stale lockfile) plus the
two lockfileOnly-focused cases in `lockfile.ts` (the `useLockfile: false`
conflict and the workspace "new project added" importer update).
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