Skip to content
This repository was archived by the owner on May 14, 2026. It is now read-only.

feat: build concurrency, unsafe-perm, scriptsPrependNodePath (#397 items 12, 14, 15)#429

Merged
zkochan merged 7 commits into
mainfrom
claude/397-remaining-items
May 12, 2026
Merged

feat: build concurrency, unsafe-perm, scriptsPrependNodePath (#397 items 12, 14, 15)#429
zkochan merged 7 commits into
mainfrom
claude/397-remaining-items

Conversation

@zkochan

@zkochan zkochan commented May 12, 2026

Copy link
Copy Markdown
Member

Summary

Bundles the three remaining moderate/minor parity items from #397 into one PR. Each adds a pnpm-workspace.yaml setting, threads it through Config, and applies it to BuildModules so a pnpm yaml that exercises these knobs produces the same install behavior in pacquet.

  • Item 12 — childConcurrency (upstream getWorkspaceConcurrency; runGroups(...)). Adds Config.child_concurrency: u32 with the upstream-matching default min(4, availableParallelism()) and the negative-offset semantics (n < 0max(1, parallelism - |n|)). BuildModules::run now dispatches each chunk's members across a bounded rayon thread pool via par_iter().try_for_each — chunks themselves remain sequential to preserve topological order. ignored_builds and deps_state_cache are wrapped in Mutex so the recursive memo and the dedup set survive concurrent chunk-member access.
  • Item 14 — unsafePerm. Adds Config.unsafe_perm: bool (default true) and threads it through to RunPostinstallHooks. When false, the executor sets TMPDIR=node_modules/.tmp for the spawn. The uid/gid drop side is a no-op upstream too because pnpm's @pnpm/npm-lifecycle never populates opts.user / opts.group. The POSIX auto-detect (getuid() === 0 && setgid → unsafePerm = false) needs libc, which isn't in [workspace.dependencies] yet — for now, root-run CI must set unsafePerm: false in yaml explicitly. Windows is force-overridden to true in WorkspaceSettings::apply_to, matching upstream's process.platform === 'win32' gate.
  • Item 15 — scriptsPrependNodePath (Config.scriptsPrependNodePath). Adds a tri-state ScriptsPrependNodePath enum in pacquet-config (custom serde Deserialize for the boolean | "warn-only" yaml shape) and converts to pacquet_executor::ScriptsPrependNodePath at the BuildModules call site so the executor crate stays free of serde wiring. Default is Never to match upstream's StrictBuildOptions.scriptsPrependNodePath: false.
  • Item 16 — getSubgraphToBuild filter trimming. Already implemented in pacquet's get_subgraph_to_build via the child_should_be_built || needs_build || has_patch gate. Confirmed during this slice; no code change needed.

Why one PR: items 12 / 14 / 15 are three independent yaml-driven config knobs, all small. Bundling keeps the cross-crate signature churn from happening three times (BuildModules's field list, install_frozen_lockfile's call site, and the test fixtures all touch each one), and the upstream sources for the three settings live next to each other so the porting context is shared.

Test plan

  • cargo nextest run — all 605 workspace tests pass.
  • just ready — typos, fmt, check, lint clean.
  • cargo doc -D warnings --document-private-items — clean.
  • taplo format --check — clean.
  • parses_scripts_prepend_node_path_{true,false,warn_only}_from_yaml, rejects_invalid_scripts_prepend_node_path.
  • parses_unsafe_perm_from_yaml_and_applies, unsafe_perm_force_true_on_windows (Windows-only).
  • parses_{positive,negative}_child_concurrency_from_yaml_and_*.
  • Existing BuildModules tests cover the concurrency-1 path (so the rayon refactor doesn't regress the single-threaded case).

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

Summary by CodeRabbit

  • New Features

    • Workspace options: scriptsPrependNodePath (tri‑state), unsafePerm, and childConcurrency; childConcurrency now defaults sensibly based on CPU.
    • Lifecycle scripts run with configurable parallelism; non‑positive childConcurrency supports “use all cores minus N”.
  • Behavior

    • Windows safely defaults unsafePerm when unspecified.
    • Deterministic build ordering and improved side‑effects cache/skip reporting; thread‑pool failures now surfaced.
  • Tests

    • Added YAML parsing, concurrency edge cases, platform behavior, and build-sequence tests.
  • Documentation

    • Clarified scripts-prepend behavior in executor docs.

Review Change Stack

…ems 12, 14, 15)

Bundle the remaining moderate/minor parity items from #397 into one PR.
Each adds a `pnpm-workspace.yaml` setting, threads it through `Config`,
and applies it to the build-modules path so a pnpm yaml that exercises
these knobs produces the same install behavior in pacquet.

Item 12 — `childConcurrency`. Adds `Config.child_concurrency: u32` with
the upstream-matching default `min(4, availableParallelism())` and the
negative-offset semantics from
[`getWorkspaceConcurrency`](https://github.com/pnpm/pnpm/blob/b4f8f47ac2/config/reader/src/concurrency.ts#L25-L34)
(`n < 0` → `max(1, parallelism - |n|)`). `BuildModules::run` now
dispatches each chunk's members across a bounded rayon thread pool
(`par_iter().try_for_each`) — chunks remain sequential to preserve
topological order. `ignored_builds` and `deps_state_cache` are wrapped
in `Mutex` so the recursive memo and the dedup set survive concurrent
chunk-member access. Mirrors upstream's
[`runGroups(getWorkspaceConcurrency(opts.childConcurrency), groups)`](https://github.com/pnpm/pnpm/blob/b4f8f47ac2/building/during-install/src/index.ts#L124).

Item 14 — `unsafePerm`. Adds `Config.unsafe_perm: bool` (default `true`)
and threads it through `BuildModules` → `RunPostinstallHooks`. When
`false`, the executor sets `TMPDIR=node_modules/.tmp` for the spawn
(matching upstream's `safe-buffer` path); the uid/gid drop side is a
no-op upstream too because pnpm's `@pnpm/npm-lifecycle` never
populates `opts.user`/`opts.group`. The upstream POSIX auto-detect
(`getuid() === 0 && setgid → unsafePerm = false`) needs a `libc`
dependency, which isn't in `[workspace.dependencies]` — for now,
root-run CI must set `unsafePerm: false` in yaml explicitly. Windows
is force-overridden to `true` in `WorkspaceSettings::apply_to`,
matching upstream's
[`process.platform === 'win32'` gate](https://github.com/pnpm/npm-lifecycle/blob/d2d8e790/index.js#L204-L220).

Item 15 — `scriptsPrependNodePath`. Adds a tri-state
`ScriptsPrependNodePath` enum in `pacquet-config` (custom serde
`Deserialize` for the `boolean | "warn-only"` yaml shape from
[`Config.scriptsPrependNodePath`](https://github.com/pnpm/pnpm/blob/b4f8f47ac2/config/reader/src/Config.ts#L108)),
converted to `pacquet_executor::ScriptsPrependNodePath` at the
`BuildModules` call site so the executor crate stays free of serde
wiring. Default is `Never` to match upstream's
[`StrictBuildOptions.scriptsPrependNodePath: false`](https://github.com/pnpm/pnpm/blob/b4f8f47ac2/building/after-install/src/extendBuildOptions.ts#L78).

Item 16 — `getSubgraphToBuild` filter trimming — was already
implemented in pacquet's `get_subgraph_to_build` via the
`child_should_be_built || needs_build || has_patch` gate. Confirmed
during this slice; no code change needed for that item.

Tests:
- `parses_scripts_prepend_node_path_{true,false,warn_only}_from_yaml`,
  `rejects_invalid_scripts_prepend_node_path`,
- `parses_unsafe_perm_from_yaml_and_applies`,
  `unsafe_perm_force_true_on_windows` (Windows-only),
- `parses_{positive,negative}_child_concurrency_from_yaml_and_*`.

All 605 workspace tests pass.
@coderabbitai

coderabbitai Bot commented May 12, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds child-concurrency sizing helpers and a tri-state scripts enum to config; parses workspace YAML keys (scriptsPrependNodePath, unsafePerm, childConcurrency) into Config with a Windows override; refactors BuildModules to run chunk members in parallel using a per-install Rayon pool sized by child_concurrency; updates wiring and tests.

Changes

Parallel Build Execution with Configurable Lifecycle Options

Layer / File(s) Summary
Concurrency defaults and config schema
crates/config/src/defaults.rs, crates/config/src/lib.rs
Adds available_parallelism(), default_child_concurrency(), and resolve_child_concurrency(); introduces ScriptsPrependNodePath enum and extends Config with scripts_prepend_node_path, unsafe_perm, and child_concurrency.
YAML parsing and workspace config propagation
crates/config/src/workspace_yaml.rs, crates/config/src/workspace_yaml/tests.rs
WorkspaceSettings accepts scriptsPrependNodePath, unsafePerm, and childConcurrency; apply_to() copies these into Config, forces unsafe_perm=true on Windows when unset, resolves childConcurrency via resolve_child_concurrency(), and includes tests for parsing and platform behavior.
BuildModules parallel execution refactor
crates/package-manager/src/build_modules.rs
BuildModules now has scripts_prepend_node_path, unsafe_perm, and child_concurrency; run() builds a per-install Rayon pool sized by child_concurrency, executes chunk members in parallel, shares memoized deps_state_cache and ignored_builds via Mutex, and moves per-snapshot logic into build_one_snapshot() which handles patching, postinstall, optional failure swallowing, and side-effects-cache uploads.
Config-to-BuildModules wiring and test fixtures
crates/executor/src/extend_path.rs, crates/package-manager/src/install_frozen_lockfile.rs, crates/package-manager/src/build_modules/tests.rs, crates/package-manager/src/install_package_from_registry/tests.rs, crates/config/src/defaults/tests.rs, crates/package-manager/src/build_sequence.rs, crates/package-manager/src/build_sequence/tests.rs
Executor docs describe ScriptsPrependNodePath handling; install_frozen_lockfile maps and forwards the config enum plus unsafe_perm and child_concurrency into BuildModules; many tests and helpers updated to initialize and pass the new fields; defaults tests add i32::MIN regression for resolver; build-sequence sorting made deterministic with a new test.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • pnpm/pacquet#418: Introduces/consumes ScriptsPrependNodePath and related executor plumbing.
  • pnpm/pacquet#420: Overlapping config/refactor changes touching defaults and exports.
  • pnpm/pacquet#427: Related BuildModules/patch application and sequencing changes.

Suggested reviewers

  • anthonyshew

Poem

🐰 I nibbled YAML, counted cores with care,

set defaults, subtracted offsets fair,
rayon pools hummed, chunks leapt in sync,
Windows kept its safety on the brink,
tests hopped in — the builds now share.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the primary changes: three major feature additions (childConcurrency, unsafePerm, scriptsPrependNodePath) with explicit references to upstream items being implemented.
Description check ✅ Passed The description comprehensively covers all three feature items with implementation details, upstream references, test plan verification, and rationale for bundling. All required template sections are addressed.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/397-remaining-items

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

@zkochan zkochan marked this pull request as ready for review May 12, 2026 21:58
Copilot AI review requested due to automatic review settings May 12, 2026 21:58
@codecov

codecov Bot commented May 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.42857% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.94%. Comparing base (eefff34) to head (e5dcb8a).

Files with missing lines Patch % Lines
crates/config/src/lib.rs 84.21% 3 Missing ⚠️
crates/package-manager/src/build_modules.rs 98.79% 2 Missing ⚠️
...tes/package-manager/src/install_frozen_lockfile.rs 71.42% 2 Missing ⚠️
crates/config/src/workspace_yaml.rs 91.66% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #429      +/-   ##
==========================================
+ Coverage   86.82%   86.94%   +0.12%     
==========================================
  Files          93       93              
  Lines        6535     6635     +100     
==========================================
+ Hits         5674     5769      +95     
- Misses        861      866       +5     

☔ 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.

@github-actions

github-actions Bot commented May 12, 2026

Copy link
Copy Markdown

Micro-Benchmark Results

Linux

group                          main                                   pr
-----                          ----                                   --
tarball/download_dependency    1.02     16.3±0.64ms   265.6 KB/sec    1.00     15.9±0.09ms   272.1 KB/sec

@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

🧹 Nitpick comments (2)
crates/package-manager/src/build_modules.rs (2)

405-429: 💤 Low value

The #[allow(clippy::too_many_arguments)] is justified but could be documented.

The 19 parameters are the natural result of extracting the loop body for parallel dispatch. Refactoring into a parameter struct would add complexity for this internal helper without meaningful benefit. Consider adding a brief inline comment explaining the rationale:

+// Extracted loop body for parallel dispatch; parameters mirror the outer scope bindings.
 #[allow(clippy::too_many_arguments)]
 fn build_one_snapshot<R: Reporter>(
🤖 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 `@crates/package-manager/src/build_modules.rs` around lines 405 - 429, The
#[allow(clippy::too_many_arguments)] on build_one_snapshot is justified; add a
concise inline comment above the attribute or function explaining that the 19
parameters are intentional because this helper was extracted from a loop for
parallel dispatch and that wrapping them into a struct would add indirection and
complexity for an internal helper (mentioning build_one_snapshot, the parallel
dispatch context, and why refactoring is undesirable) so future maintainers
understand the rationale.

355-358: 💤 Low value

Thread pool creation panic is acceptable but could be more resilient.

The .expect() on ThreadPoolBuilder::build() will panic if the OS refuses to spawn threads (resource exhaustion, ulimit, etc.). While rare, this is a runtime failure outside of developer control.

Consider propagating the error or at least logging before panicking. However, given that upstream pnpm's runGroups also doesn't gracefully handle worker pool failures, this matches upstream behavior and the panic message is descriptive enough.

🤖 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 `@crates/package-manager/src/build_modules.rs` around lines 355 - 358, The
current code calls ThreadPoolBuilder::new().num_threads(child_concurrency.max(1)
as usize).build().expect(...), which will panic if the OS refuses to create
threads; update this to handle the Result from ThreadPoolBuilder::build()
instead of unwrapping: either propagate the error (return a Result from the
surrounding function) or call build().map_err(|e| {
process_logger.error(format!("failed to build rayon thread pool: {}", e)); e })?
and then propagate or abort; at minimum replace expect(...) with proper error
handling around build() referencing ThreadPoolBuilder::build, the pool variable,
and child_concurrency so the failure can be logged or returned upstream rather
than unconditionally panicking.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/config/src/defaults.rs`:
- Around line 139-140: The current childConcurrency calculation uses let offset
= (-n) as u32 which overflows for n == i32::MIN; replace that conversion with
n.unsigned_abs() to safely get a u32 absolute value without panic (i.e., use let
offset = n.unsigned_abs()), keeping the subsequent
available_parallelism().saturating_sub(offset).max(1) logic intact; refer to the
variables/functions n, offset, and available_parallelism() to locate the change.

---

Nitpick comments:
In `@crates/package-manager/src/build_modules.rs`:
- Around line 405-429: The #[allow(clippy::too_many_arguments)] on
build_one_snapshot is justified; add a concise inline comment above the
attribute or function explaining that the 19 parameters are intentional because
this helper was extracted from a loop for parallel dispatch and that wrapping
them into a struct would add indirection and complexity for an internal helper
(mentioning build_one_snapshot, the parallel dispatch context, and why
refactoring is undesirable) so future maintainers understand the rationale.
- Around line 355-358: The current code calls
ThreadPoolBuilder::new().num_threads(child_concurrency.max(1) as
usize).build().expect(...), which will panic if the OS refuses to create
threads; update this to handle the Result from ThreadPoolBuilder::build()
instead of unwrapping: either propagate the error (return a Result from the
surrounding function) or call build().map_err(|e| {
process_logger.error(format!("failed to build rayon thread pool: {}", e)); e })?
and then propagate or abort; at minimum replace expect(...) with proper error
handling around build() referencing ThreadPoolBuilder::build, the pool variable,
and child_concurrency so the failure can be logged or returned upstream rather
than unconditionally panicking.
🪄 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: fbf122ab-a07a-4780-b5f2-394011446eca

📥 Commits

Reviewing files that changed from the base of the PR and between eefff34 and 32cc16e.

📒 Files selected for processing (9)
  • crates/config/src/defaults.rs
  • crates/config/src/lib.rs
  • crates/config/src/workspace_yaml.rs
  • crates/config/src/workspace_yaml/tests.rs
  • crates/executor/src/extend_path.rs
  • crates/package-manager/src/build_modules.rs
  • crates/package-manager/src/build_modules/tests.rs
  • crates/package-manager/src/install_frozen_lockfile.rs
  • crates/package-manager/src/install_package_from_registry/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). (3)
  • GitHub Check: Agent
  • GitHub Check: Run benchmark on ubuntu-latest
  • GitHub Check: Run benchmark on ubuntu-latest
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Preserve existing method chains and pipe-trait chains; do not break them into intermediate let bindings unless there is a concrete justification such as a compilation failure, borrow checker rejection, meaningful performance improvement, or other technical necessity. Refactoring for style alone is not sufficient justification.
Choose owned vs. borrowed parameters to minimize copies; prefer borrowed types (&Path over &PathBuf, &str over &String) when it does not force extra copies.
Prefer Arc::clone(&x) and Rc::clone(&x) over x.clone() for reference-counted types to make the cost visible at the call site.
Do not use star imports inside module bodies. Write use super::{Foo, bar} instead of use super::*; for any glob whose target is a module you control. External-crate preludes (e.g., use rayon::prelude::*;) and root-of-module re-exports (e.g., pub use submodule::*; in lib.rs) are exceptions.
Follow Rust API Guidelines for naming, as documented in https://rust-lang.github.io/api-guidelines/naming.html.
Declare a newtype wrapper for any branded string type being ported from TypeScript pnpm. Do not collapse the brand into a plain String or &str; give the type its own struct so misuse is a type error.
When porting branded string types where upstream TypeScript always validates before construction, validate in the Rust port too. Construct the wrapper only via TryFrom<String> and/or FromStr; do not provide an infallible public constructor that takes an arbitrary string.
For branded string types where upstream TypeScript never validates (used purely for type-safety to prevent confusion between string slots), expose an infallible From<String> and From<&str> constructor in the Rust wrapper.
When upstream TypeScript occasionally constructs a branded type without validation (via bare as assertion), add a from_str_unchecked (or similarly named) constructor on the Rust side. Keep the validating constructor as well; `from_str_u...

Files:

  • crates/executor/src/extend_path.rs
  • crates/package-manager/src/install_frozen_lockfile.rs
  • crates/package-manager/src/install_package_from_registry/tests.rs
  • crates/config/src/workspace_yaml/tests.rs
  • crates/config/src/defaults.rs
  • crates/config/src/workspace_yaml.rs
  • crates/config/src/lib.rs
  • crates/package-manager/src/build_modules.rs
  • crates/package-manager/src/build_modules/tests.rs
🧠 Learnings (2)
📚 Learning: 2026-05-07T23:19:08.272Z
Learnt from: KSXGitHub
Repo: pnpm/pacquet PR: 401
File: tasks/integrated-benchmark/src/work_env.rs:343-344
Timestamp: 2026-05-07T23:19:08.272Z
Learning: When reviewing Rust code in pnpm/pacquet for deprecated API usage, do not automatically treat `serde_saphyr::to_string` as deprecated. In `serde-saphyr` v0.0.25, `serde_saphyr::to_string` has no `#[deprecated]` attribute (the `#[deprecated]` later in `serde-saphyr-0.0.25/src/lib.rs` applies to a different function). Only flag `serde_saphyr::to_string` as deprecated if the resolved dependency version’s source shows `#[deprecated]` on that specific function.

Applied to files:

  • crates/executor/src/extend_path.rs
  • crates/package-manager/src/install_frozen_lockfile.rs
  • crates/package-manager/src/install_package_from_registry/tests.rs
  • crates/config/src/workspace_yaml/tests.rs
  • crates/config/src/defaults.rs
  • crates/config/src/workspace_yaml.rs
  • crates/config/src/lib.rs
  • crates/package-manager/src/build_modules.rs
  • crates/package-manager/src/build_modules/tests.rs
📚 Learning: 2026-05-01T10:01:33.766Z
Learnt from: zkochan
Repo: pnpm/pacquet PR: 349
File: crates/reporter/src/tests.rs:121-121
Timestamp: 2026-05-01T10:01:33.766Z
Learning: In Rust test code, follow the repo’s CODE_STYLE_GUIDE test-logging rule: add logging (e.g., `eprintln!`/`eprintln!(...)`) so that useful diagnostic values are printed when a test fails, unless the assertion is `assert_eq!` (where the differing values are already included). Concretely, if you use assertions like `assert!`, `assert_ne!`, etc., ensure the test logs the relevant actual/expected values (or context) before/around the assertion so failures can be diagnosed without rerunning.

Applied to files:

  • crates/package-manager/src/install_package_from_registry/tests.rs
  • crates/config/src/workspace_yaml/tests.rs
  • crates/package-manager/src/build_modules/tests.rs
🔇 Additional comments (14)
crates/config/src/lib.rs (1)

43-92: Tri-state config wiring looks solid.

The ScriptsPrependNodePath deserialization and the new Config fields/defaults are coherent and align cleanly with downstream conversion points.

Also applies to: 338-380

crates/config/src/workspace_yaml.rs (1)

110-124: Config application path is correctly wired.

The new workspace YAML fields are applied predictably, including the Windows unsafe_perm force-override and child_concurrency resolution flow.

Also applies to: 244-263

crates/package-manager/src/install_package_from_registry/tests.rs (1)

49-51: Good test-fixture update for new config surface.

Adding explicit defaults for the new fields keeps test behavior deterministic.

crates/config/src/workspace_yaml/tests.rs (1)

279-380: Nice coverage expansion for the new YAML settings.

These tests meaningfully pin parsing and apply_to behavior, including platform-specific branches.

crates/executor/src/extend_path.rs (1)

10-14: Doc update is clear and useful.

The added note explains config/executor boundary responsibilities well without coupling serde into executor.

crates/package-manager/src/install_frozen_lockfile.rs (1)

229-260: Field plumbing into BuildModules looks correct.

The explicit enum conversion plus the new runtime fields are wired through cleanly.

crates/package-manager/src/build_modules/tests.rs (1)

304-307: Test fixture updates are consistent across scenarios.

Good job wiring the new BuildModules fields uniformly so existing behavior-focused tests remain valid.

Also applies to: 355-358, 433-436, 559-562, 620-623, 674-677, 905-908, 1011-1014, 1126-1129, 1351-1354, 1454-1457, 1528-1531

crates/package-manager/src/build_modules.rs (7)

16-16: LGTM!

The rayon::prelude::* import is appropriate here as external-crate preludes are an allowed exception to the no-star-imports rule. As per coding guidelines: "External-crate preludes (e.g., use rayon::prelude::*;) ... are exceptions."


215-233: LGTM!

The new fields are well-documented with upstream references. Using u32 for child_concurrency is appropriate given the .max(1) floor applied at thread pool creation, ensuring forward progress on resource-constrained hosts.


360-392: LGTM!

The parallel dispatch pattern is correct:

  • Chunks iterate sequentially preserving topological order
  • pool.install() runs the closure in the bounded thread pool
  • par_iter().try_for_each() parallelizes chunk members with proper short-circuit on error
  • Error propagation via ? correctly surfaces failures to the caller

395-396: LGTM!

The mutex unwrap pattern with descriptive expect messages is appropriate. Mutex poisoning would only occur if a thread panics while holding the lock, which is a fatal condition that should propagate. The try_for_each pattern properly handles errors without panics in the normal path.


494-517: LGTM!

The mutex lock scope for deps_state_cache is appropriately narrow—held only during calc_dep_state. The recursive memoization pattern correctly shares computed hashes across concurrent chunk members via the Mutex-protected cache.


527-540: LGTM!

The side-effects cache hit check using let-chains is clean and correct. Early return on cache hit properly skips the build, matching upstream's !node.isBuilt filter behavior.


590-616: LGTM!

The optional dependency error handling correctly mirrors upstream's pattern at building/during-install/src/index.ts:226-238: failures are logged via pnpm:skipped-optional-dependency channel and swallowed so the install continues. The SkippedOptionalPackage fields match upstream's payload structure.

Comment thread crates/config/src/defaults.rs Outdated

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

Ports three remaining pnpm v11 parity knobs into pacquet’s workspace config and lifecycle/build pipeline: childConcurrency (bounded build parallelism), unsafePerm (TMPDIR behavior + Windows override), and scriptsPrependNodePath (tri-state PATH behavior).

Changes:

  • Add workspace-yaml-backed config fields for scriptsPrependNodePath, unsafePerm, and childConcurrency (including upstream-like defaults / negative-offset semantics).
  • Thread these settings through install_frozen_lockfile into BuildModules, and use a bounded rayon pool to parallelize chunk members.
  • Add/extend tests for YAML parsing and update existing build/install tests to include the new BuildModules fields.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
crates/package-manager/src/install_package_from_registry/tests.rs Updates test config construction to include the new Config fields.
crates/package-manager/src/install_frozen_lockfile.rs Converts config enum to executor enum and passes new settings into BuildModules.
crates/package-manager/src/build_modules/tests.rs Updates BuildModules test instantiations with new fields.
crates/package-manager/src/build_modules.rs Implements per-chunk parallel build execution and threads unsafe_perm/scripts_prepend_node_path into lifecycle hook execution.
crates/executor/src/extend_path.rs Clarifies the separation between config (serde) and executor (canonical enum) for ScriptsPrependNodePath.
crates/config/src/workspace_yaml/tests.rs Adds tests for parsing/applying the three new workspace settings.
crates/config/src/workspace_yaml.rs Adds the three settings to WorkspaceSettings and applies them into Config (with Windows override for unsafe_perm).
crates/config/src/lib.rs Introduces serde-enabled ScriptsPrependNodePath mirror enum and adds new fields to Config.
crates/config/src/defaults.rs Adds default/resolve logic for childConcurrency based on available parallelism.

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

Comment thread crates/config/src/defaults.rs Outdated
Comment thread crates/config/src/lib.rs Outdated
Comment thread crates/package-manager/src/build_modules.rs Outdated
Comment thread crates/package-manager/src/build_modules.rs
Four review-flagged hardenings from PR #429:

1. `resolve_child_concurrency(Some(i32::MIN))` no longer panics. The
   negative-offset path now uses `n.unsigned_abs()` instead of
   `(-n) as u32` — the latter overflows in debug builds because the
   inner negation itself overflows on `i32::MIN`. Added
   `resolve_child_concurrency_handles_i32_min` to lock in the
   non-panicking floor.

2. `pacquet_config::ScriptsPrependNodePath` doc said "Converted at
   the BuildModules call site via `From`" but no `From` impl exists
   (the conversion is a manual `match` in
   `install_frozen_lockfile.rs`). Adding a `From` impl would require
   inverting the config↔executor crate boundary (neither depends on
   the other today); corrected the doc to reflect what the code
   actually does.

3. `rayon::ThreadPoolBuilder::build()` is fallible (the OS may
   refuse to spawn the worker threads under `RLIMIT_NPROC`). The
   former `.expect("…")` would panic inside the binary on a
   resource-constrained host; replaced with a new
   `BuildModulesError::ThreadPoolBuild` variant
   (`ERR_PACQUET_BUILD_THREAD_POOL`) carrying the underlying
   `rayon::ThreadPoolBuildError` and a remediation hint
   ("lower childConcurrency, or raise RLIMIT_NPROC").

4. `Mutex::lock().expect(...)` and `into_inner().expect(...)` calls
   around `ignored_builds` and `deps_state_cache` now use the
   canonical `unwrap_or_else(|e| e.into_inner())` poison-recovery
   pattern. Recovery is safe here because the protected types are
   `BTreeSet<String>` and `DepsStateCache` (a `HashMap`-backed
   memo), both of which only see one atomic insertion per worker
   task — a panic mid-call cannot leave the structure in a torn
   state. Comment at each call site links the safety argument
   back to the central one in `BuildModules::run`.

@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

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

Inline comments:
In `@crates/config/src/defaults/tests.rs`:
- Around line 57-59: The test currently only asserts result >= 1 for
resolve_child_concurrency(Some(i32::MIN)) which allows incorrect values >1 to
slip through; change the assertion to require the exact saturated value by
asserting result == 1 (use assert_eq!(result, 1) so test output shows both
sides), i.e. update the assertion in the test that calls
resolve_child_concurrency(Some(i32::MIN)) to assert_eq!(result, 1) to make the
regression check precise.
🪄 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: be8a8111-c68e-4ca7-bf5d-09928aa3163d

📥 Commits

Reviewing files that changed from the base of the PR and between 32cc16e and 026ba28.

📒 Files selected for processing (4)
  • crates/config/src/defaults.rs
  • crates/config/src/defaults/tests.rs
  • crates/config/src/lib.rs
  • crates/package-manager/src/build_modules.rs
🚧 Files skipped from review as they are similar to previous changes (3)
  • crates/config/src/defaults.rs
  • crates/config/src/lib.rs
  • crates/package-manager/src/build_modules.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: Run benchmark on ubuntu-latest
  • GitHub Check: Lint and Test (windows-latest)
  • GitHub Check: Run benchmark on ubuntu-latest
  • GitHub Check: Lint and Test (macos-latest)
  • GitHub Check: Code Coverage
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Preserve existing method chains and pipe-trait chains; do not break them into intermediate let bindings unless there is a concrete justification such as a compilation failure, borrow checker rejection, meaningful performance improvement, or other technical necessity. Refactoring for style alone is not sufficient justification.
Choose owned vs. borrowed parameters to minimize copies; prefer borrowed types (&Path over &PathBuf, &str over &String) when it does not force extra copies.
Prefer Arc::clone(&x) and Rc::clone(&x) over x.clone() for reference-counted types to make the cost visible at the call site.
Do not use star imports inside module bodies. Write use super::{Foo, bar} instead of use super::*; for any glob whose target is a module you control. External-crate preludes (e.g., use rayon::prelude::*;) and root-of-module re-exports (e.g., pub use submodule::*; in lib.rs) are exceptions.
Follow Rust API Guidelines for naming, as documented in https://rust-lang.github.io/api-guidelines/naming.html.
Declare a newtype wrapper for any branded string type being ported from TypeScript pnpm. Do not collapse the brand into a plain String or &str; give the type its own struct so misuse is a type error.
When porting branded string types where upstream TypeScript always validates before construction, validate in the Rust port too. Construct the wrapper only via TryFrom<String> and/or FromStr; do not provide an infallible public constructor that takes an arbitrary string.
For branded string types where upstream TypeScript never validates (used purely for type-safety to prevent confusion between string slots), expose an infallible From<String> and From<&str> constructor in the Rust wrapper.
When upstream TypeScript occasionally constructs a branded type without validation (via bare as assertion), add a from_str_unchecked (or similarly named) constructor on the Rust side. Keep the validating constructor as well; `from_str_u...

Files:

  • crates/config/src/defaults/tests.rs
🧠 Learnings (2)
📚 Learning: 2026-05-01T10:01:33.766Z
Learnt from: zkochan
Repo: pnpm/pacquet PR: 349
File: crates/reporter/src/tests.rs:121-121
Timestamp: 2026-05-01T10:01:33.766Z
Learning: In Rust test code, follow the repo’s CODE_STYLE_GUIDE test-logging rule: add logging (e.g., `eprintln!`/`eprintln!(...)`) so that useful diagnostic values are printed when a test fails, unless the assertion is `assert_eq!` (where the differing values are already included). Concretely, if you use assertions like `assert!`, `assert_ne!`, etc., ensure the test logs the relevant actual/expected values (or context) before/around the assertion so failures can be diagnosed without rerunning.

Applied to files:

  • crates/config/src/defaults/tests.rs
📚 Learning: 2026-05-07T23:19:08.272Z
Learnt from: KSXGitHub
Repo: pnpm/pacquet PR: 401
File: tasks/integrated-benchmark/src/work_env.rs:343-344
Timestamp: 2026-05-07T23:19:08.272Z
Learning: When reviewing Rust code in pnpm/pacquet for deprecated API usage, do not automatically treat `serde_saphyr::to_string` as deprecated. In `serde-saphyr` v0.0.25, `serde_saphyr::to_string` has no `#[deprecated]` attribute (the `#[deprecated]` later in `serde-saphyr-0.0.25/src/lib.rs` applies to a different function). Only flag `serde_saphyr::to_string` as deprecated if the resolved dependency version’s source shows `#[deprecated]` on that specific function.

Applied to files:

  • crates/config/src/defaults/tests.rs
🔇 Additional comments (1)
crates/config/src/defaults/tests.rs (1)

1-1: Import update is clean and correctly scoped.

resolve_child_concurrency is used by the new regression test and the explicit super::{...} import matches repo style.

Comment thread crates/config/src/defaults/tests.rs
Switch from `assert!(result >= 1, ...)` to `assert_eq!(result, 1)` so
the test rejects an incorrect value like `2`. The exact result is
provably `1`: `i32::MIN.unsigned_abs()` is `2_147_483_648`, which
exceeds any plausible host parallelism, so `saturating_sub` produces
`0` and `.max(1)` lifts to `1`. Review-suggested in PR #429.
Copilot AI review requested due to automatic review settings May 12, 2026 22:17

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

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

Comment thread crates/package-manager/src/build_modules.rs Outdated
The new struct variant from the previous commit declared a `source`
field carrying `rayon::ThreadPoolBuildError` but didn't tag it with
`#[error(source)]`, so `Error::source()` returned `None` and the
underlying rayon error was invisible to miette's diagnostic chain.
Matches the convention used across the workspace (e.g.
`StoreIndexError::*`). Review-suggested in PR #429.
@github-actions

github-actions Bot commented May 12, 2026

Copy link
Copy Markdown

Integrated-Benchmark Report (Linux)

Scenario: Frozen Lockfile

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 2.541 ± 0.067 2.459 2.657 1.00
pacquet@main 2.548 ± 0.095 2.405 2.685 1.00 ± 0.05
pnpm 6.214 ± 0.106 6.075 6.403 2.45 ± 0.08
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 2.5413818746400003,
      "stddev": 0.06720416742527227,
      "median": 2.5307324769400004,
      "user": 2.54471596,
      "system": 3.5248927799999996,
      "min": 2.4591740749400004,
      "max": 2.65689023994,
      "times": [
        2.65689023994,
        2.5042501779400004,
        2.5330955149400003,
        2.57808248294,
        2.5815059179400004,
        2.62432674294,
        2.47792933894,
        2.4591740749400004,
        2.4701948169400003,
        2.5283694389400004
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 2.5479696613400002,
      "stddev": 0.09498389378171664,
      "median": 2.51437107444,
      "user": 2.50356836,
      "system": 3.52445928,
      "min": 2.40524425194,
      "max": 2.68538857994,
      "times": [
        2.4939638649400004,
        2.68538857994,
        2.51829792894,
        2.62123507694,
        2.4654233599400004,
        2.62039649994,
        2.40524425194,
        2.48663814894,
        2.67266468194,
        2.51044421994
      ]
    },
    {
      "command": "pnpm",
      "mean": 6.214172447939999,
      "stddev": 0.10622611442454018,
      "median": 6.17695053894,
      "user": 9.02524356,
      "system": 4.50272718,
      "min": 6.07542602094,
      "max": 6.40312501894,
      "times": [
        6.14427921794,
        6.07542602094,
        6.10483766594,
        6.18896217494,
        6.16493890294,
        6.16255544894,
        6.26137360194,
        6.40312501894,
        6.30801483694,
        6.32821158994
      ]
    }
  ]
}

Scenario: Frozen Lockfile (Hot Cache)

Command Mean [ms] Min [ms] Max [ms] Relative
pacquet@HEAD 676.9 ± 58.9 638.4 838.5 1.00
pacquet@main 679.1 ± 37.8 640.6 742.8 1.00 ± 0.10
pnpm 2651.3 ± 116.0 2518.1 2870.7 3.92 ± 0.38
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 0.6768961966800001,
      "stddev": 0.05888396546159356,
      "median": 0.6589319948800001,
      "user": 0.34676855999999995,
      "system": 1.44870996,
      "min": 0.6383870583800001,
      "max": 0.8384867593800001,
      "times": [
        0.8384867593800001,
        0.68922598438,
        0.65401368938,
        0.6636074943800001,
        0.64122870838,
        0.67795795238,
        0.6618005743800001,
        0.6481903303800001,
        0.65606341538,
        0.6383870583800001
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 0.67905866838,
      "stddev": 0.03781291280071029,
      "median": 0.66406757738,
      "user": 0.33899636,
      "system": 1.48404726,
      "min": 0.64059221738,
      "max": 0.74276311238,
      "times": [
        0.7185436353800001,
        0.66213214738,
        0.6504320883800001,
        0.74276311238,
        0.6658734283800001,
        0.64776810238,
        0.6623097923800001,
        0.7343467973800001,
        0.66582536238,
        0.64059221738
      ]
    },
    {
      "command": "pnpm",
      "mean": 2.65134618048,
      "stddev": 0.11599941512242547,
      "median": 2.63420353688,
      "user": 3.19323906,
      "system": 2.21810536,
      "min": 2.51805690038,
      "max": 2.8707296733799996,
      "times": [
        2.76253661338,
        2.51805690038,
        2.66963038738,
        2.8707296733799996,
        2.6189944663799998,
        2.55094827838,
        2.5364098503799997,
        2.64941260738,
        2.76215266938,
        2.57459035838
      ]
    }
  ]
}

Brings pacquet's test coverage for #397 items 12 and 16 in line with
the upstream test suites. Two changes:

1. Concurrency tests. Split the `parallelism` input out of
   `default_child_concurrency` and `resolve_child_concurrency` into
   the public `..._with_parallelism` helpers so tests can pin the
   parallelism value (upstream's test suite mocks
   `os.availableParallelism`; pacquet injects it directly). Ports
   six tests from upstream's
   [`config/reader/src/concurrency.test.ts`](https://github.com/pnpm/pnpm/blob/b4f8f47ac2/config/reader/src/concurrency.test.ts):
   - `cpu num < 4` / `= 4` / `> 4` boundaries for the `min(4, p)` cap
   - `default workspace concurrency` (undefined input)
   - `match host cores amount` (n=0 → host cores)
   - `host cores minus X` (n=-1 → cores-1, n=-9999 → 1)
   The pacquet-only `resolve_child_concurrency_handles_i32_min` test
   (added in the previous commit) stays — it's coverage upstream
   doesn't have because TypeScript doesn't have `i32::MIN`.

2. `buildSequence` trim test. Direct port of upstream's
   [`buildSequence() test 2`](https://github.com/pnpm/pnpm/blob/b4f8f47ac2/building/during-install/test/buildSequence.test.ts#L28-L51):
   two importers `a` and `b` share a builder leaf `c`; only `a`
   needs its own build. The result must be `[[c], [a]]` — `b` is
   trimmed even though it's a root.

   The port surfaced a pacquet-side determinism gap.
   `get_subgraph_to_build` is order-sensitive (a root walked first
   marks shared children as already-walked, so a second root
   sharing those children gets trimmed), but
   `collect_root_dep_paths` was sourcing roots from
   `HashMap<_, ProjectSnapshot>` and
   `HashMap<PkgName, ResolvedDependencySpec>` — so iteration order
   varied run to run and the build sequence was flaky. Sorted the
   roots by `PackageKey` string repr so the trim outcome is
   reproducible. The long-term fix is to preserve lockfile
   declaration order via `IndexMap`; the sort is sufficient
   stabilization until that lands.
Copilot AI review requested due to automatic review settings May 12, 2026 22:49

@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

🧹 Nitpick comments (4)
crates/config/src/defaults.rs (2)

109-120: ⚡ Quick win

Use blob/main for upstream pnpm references instead of pinned SHAs.

These inline links currently point to a specific commit hash, which makes long-term porting/cross-checking drift-prone in this repo. Please switch them to https://github.com/pnpm/pnpm/blob/main/....

As per coding guidelines: "If reading on GitHub, open files from https://github.com/pnpm/pnpm/blob/main/... (which always resolves to the tip of main) rather than from a permalinked SHA."

Also applies to: 126-127, 134-134, 148-148

🤖 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 `@crates/config/src/defaults.rs` around lines 109 - 120, Update the inline
GitHub permalinks in the doc comments to point to the moving tip of main instead
of a SHA: replace occurrences of "https://github.com/pnpm/pnpm/blob/<sha>/..."
with "https://github.com/pnpm/pnpm/blob/main/..." in the comments surrounding
default_child_concurrency, default_child_concurrency_with_parallelism and the
referenced getDefaultWorkspaceConcurrency links (also update the other similar
comment links nearby). Ensure all doc comment links in this module use blob/main
so they track upstream main.

117-123: ⚡ Quick win

Narrow visibility for test-only helpers.

default_child_concurrency_with_parallelism and resolve_child_concurrency_with_parallelism are described as internal test helpers but are declared pub. Consider reducing to pub(crate) (or private) to avoid unnecessary public API exposure.

Suggested minimal change
-pub fn default_child_concurrency_with_parallelism(parallelism: u32) -> u32 {
+pub(crate) fn default_child_concurrency_with_parallelism(parallelism: u32) -> u32 {
     parallelism.min(4)
 }

-pub fn resolve_child_concurrency_with_parallelism(option: Option<i32>, parallelism: u32) -> u32 {
+pub(crate) fn resolve_child_concurrency_with_parallelism(option: Option<i32>, parallelism: u32) -> u32 {
     match option {
         None => default_child_concurrency_with_parallelism(parallelism),
         Some(n) if n > 0 => n as u32,
         Some(n) => parallelism.saturating_sub(n.unsigned_abs()).max(1),
     }
 }

Also applies to: 146-151

🤖 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 `@crates/config/src/defaults.rs` around lines 117 - 123, The two test-only
helper functions default_child_concurrency_with_parallelism and
resolve_child_concurrency_with_parallelism are declared public but should have
narrower visibility; change their declarations from pub to pub(crate) (or remove
pub entirely if only used within the crate) so they are not exposed in the
public API, updating the function signatures for
default_child_concurrency_with_parallelism and
resolve_child_concurrency_with_parallelism accordingly.
crates/config/src/defaults/tests.rs (1)

53-53: ⚡ Quick win

Normalize upstream test-reference links to blob/main.

The new upstream references in test docs are pinned to a commit SHA. Please switch them to blob/main for consistency with the repo’s pnpm-porting rule.

As per coding guidelines: "If reading on GitHub, open files from https://github.com/pnpm/pnpm/blob/main/... (which always resolves to the tip of main) rather than from a permalinked SHA."

Also applies to: 61-61, 69-69, 77-77, 89-89, 99-99, 118-118

🤖 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 `@crates/config/src/defaults/tests.rs` at line 53, The test doc links are
pinned to a commit SHA; update each GitHub URL that points to
"github.com/pnpm/pnpm/blob/<commit-sha>/..." and replace the "<commit-sha>"
segment with "main" so they become "github.com/pnpm/pnpm/blob/main/...". Locate
the referenced link near the comment text '`getDefaultWorkspaceConcurrency: cpu
num < 4`' and apply the same change to the other occurrences in this file (the
equivalent link anchors referenced in the comments around the other test
descriptions) so all upstream pnpm links use blob/main.
crates/package-manager/src/build_sequence/tests.rs (1)

176-177: ⚡ Quick win

Use pnpm/blob/main for upstream test reference links

Please switch the doc link to https://github.com/pnpm/pnpm/blob/main/... so the reference always points to current upstream behavior.

As per coding guidelines: “If reading on GitHub, open files from https://github.com/pnpm/pnpm/blob/main/... rather than from a permalinked SHA.”

🤖 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 `@crates/package-manager/src/build_sequence/tests.rs` around lines 176 - 177,
Update the upstream reference URL in the doc comment inside
crates/package-manager/src/build_sequence/tests.rs so it uses the branch-based
path (/blob/main/...) instead of the permalinked SHA; locate the comment block
containing the link to https://github.com/pnpm/pnpm/blob/b4f8f47ac2/... and
replace that SHA segment with main so the doc link always points to current
upstream behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/package-manager/src/build_sequence.rs`:
- Around line 144-155: The current fix sorts the initial roots via
roots.sort_by_key(|k| k.to_string()) but traversal remains non-deterministic
because child lists are constructed from HashMap iteration; update the
construction/traversal that feeds get_subgraph_to_build to deterministically
order children (e.g., when building adjacency lists or when collecting
dependency keys from the HashMap ensure you collect keys into a Vec and sort
them by PackageKey/string before iterating), so modify the code paths that
materialize child lists (the place that builds the per-node children passed into
get_subgraph_to_build) to sort by the same PackageKey string representation to
guarantee reproducible trimming across runs.

---

Nitpick comments:
In `@crates/config/src/defaults.rs`:
- Around line 109-120: Update the inline GitHub permalinks in the doc comments
to point to the moving tip of main instead of a SHA: replace occurrences of
"https://github.com/pnpm/pnpm/blob/<sha>/..." with
"https://github.com/pnpm/pnpm/blob/main/..." in the comments surrounding
default_child_concurrency, default_child_concurrency_with_parallelism and the
referenced getDefaultWorkspaceConcurrency links (also update the other similar
comment links nearby). Ensure all doc comment links in this module use blob/main
so they track upstream main.
- Around line 117-123: The two test-only helper functions
default_child_concurrency_with_parallelism and
resolve_child_concurrency_with_parallelism are declared public but should have
narrower visibility; change their declarations from pub to pub(crate) (or remove
pub entirely if only used within the crate) so they are not exposed in the
public API, updating the function signatures for
default_child_concurrency_with_parallelism and
resolve_child_concurrency_with_parallelism accordingly.

In `@crates/config/src/defaults/tests.rs`:
- Line 53: The test doc links are pinned to a commit SHA; update each GitHub URL
that points to "github.com/pnpm/pnpm/blob/<commit-sha>/..." and replace the
"<commit-sha>" segment with "main" so they become
"github.com/pnpm/pnpm/blob/main/...". Locate the referenced link near the
comment text '`getDefaultWorkspaceConcurrency: cpu num < 4`' and apply the same
change to the other occurrences in this file (the equivalent link anchors
referenced in the comments around the other test descriptions) so all upstream
pnpm links use blob/main.

In `@crates/package-manager/src/build_sequence/tests.rs`:
- Around line 176-177: Update the upstream reference URL in the doc comment
inside crates/package-manager/src/build_sequence/tests.rs so it uses the
branch-based path (/blob/main/...) instead of the permalinked SHA; locate the
comment block containing the link to
https://github.com/pnpm/pnpm/blob/b4f8f47ac2/... and replace that SHA segment
with main so the doc link always points to current upstream behavior.
🪄 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: 22643d68-7105-4353-b831-7fa7ab991d9f

📥 Commits

Reviewing files that changed from the base of the PR and between ce8bce0 and f34d9f1.

📒 Files selected for processing (4)
  • crates/config/src/defaults.rs
  • crates/config/src/defaults/tests.rs
  • crates/package-manager/src/build_sequence.rs
  • crates/package-manager/src/build_sequence/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). (7)
  • GitHub Check: Agent
  • GitHub Check: Code Coverage
  • GitHub Check: Run benchmark on ubuntu-latest
  • GitHub Check: Lint and Test (windows-latest)
  • GitHub Check: Lint and Test (macos-latest)
  • GitHub Check: Lint and Test (ubuntu-latest)
  • GitHub Check: Run benchmark on ubuntu-latest
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Preserve existing method chains and pipe-trait chains; do not break them into intermediate let bindings unless there is a concrete justification such as a compilation failure, borrow checker rejection, meaningful performance improvement, or other technical necessity. Refactoring for style alone is not sufficient justification.
Choose owned vs. borrowed parameters to minimize copies; prefer borrowed types (&Path over &PathBuf, &str over &String) when it does not force extra copies.
Prefer Arc::clone(&x) and Rc::clone(&x) over x.clone() for reference-counted types to make the cost visible at the call site.
Do not use star imports inside module bodies. Write use super::{Foo, bar} instead of use super::*; for any glob whose target is a module you control. External-crate preludes (e.g., use rayon::prelude::*;) and root-of-module re-exports (e.g., pub use submodule::*; in lib.rs) are exceptions.
Follow Rust API Guidelines for naming, as documented in https://rust-lang.github.io/api-guidelines/naming.html.
Declare a newtype wrapper for any branded string type being ported from TypeScript pnpm. Do not collapse the brand into a plain String or &str; give the type its own struct so misuse is a type error.
When porting branded string types where upstream TypeScript always validates before construction, validate in the Rust port too. Construct the wrapper only via TryFrom<String> and/or FromStr; do not provide an infallible public constructor that takes an arbitrary string.
For branded string types where upstream TypeScript never validates (used purely for type-safety to prevent confusion between string slots), expose an infallible From<String> and From<&str> constructor in the Rust wrapper.
When upstream TypeScript occasionally constructs a branded type without validation (via bare as assertion), add a from_str_unchecked (or similarly named) constructor on the Rust side. Keep the validating constructor as well; `from_str_u...

Files:

  • crates/package-manager/src/build_sequence.rs
  • crates/package-manager/src/build_sequence/tests.rs
  • crates/config/src/defaults/tests.rs
  • crates/config/src/defaults.rs
🧠 Learnings (2)
📚 Learning: 2026-05-07T23:19:08.272Z
Learnt from: KSXGitHub
Repo: pnpm/pacquet PR: 401
File: tasks/integrated-benchmark/src/work_env.rs:343-344
Timestamp: 2026-05-07T23:19:08.272Z
Learning: When reviewing Rust code in pnpm/pacquet for deprecated API usage, do not automatically treat `serde_saphyr::to_string` as deprecated. In `serde-saphyr` v0.0.25, `serde_saphyr::to_string` has no `#[deprecated]` attribute (the `#[deprecated]` later in `serde-saphyr-0.0.25/src/lib.rs` applies to a different function). Only flag `serde_saphyr::to_string` as deprecated if the resolved dependency version’s source shows `#[deprecated]` on that specific function.

Applied to files:

  • crates/package-manager/src/build_sequence.rs
  • crates/package-manager/src/build_sequence/tests.rs
  • crates/config/src/defaults/tests.rs
  • crates/config/src/defaults.rs
📚 Learning: 2026-05-01T10:01:33.766Z
Learnt from: zkochan
Repo: pnpm/pacquet PR: 349
File: crates/reporter/src/tests.rs:121-121
Timestamp: 2026-05-01T10:01:33.766Z
Learning: In Rust test code, follow the repo’s CODE_STYLE_GUIDE test-logging rule: add logging (e.g., `eprintln!`/`eprintln!(...)`) so that useful diagnostic values are printed when a test fails, unless the assertion is `assert_eq!` (where the differing values are already included). Concretely, if you use assertions like `assert!`, `assert_ne!`, etc., ensure the test logs the relevant actual/expected values (or context) before/around the assertion so failures can be diagnosed without rerunning.

Applied to files:

  • crates/package-manager/src/build_sequence/tests.rs
  • crates/config/src/defaults/tests.rs
🔇 Additional comments (1)
crates/config/src/defaults/tests.rs (1)

52-140: Great test coverage and determinism for concurrency semantics.

These additions pin the important edge cases (None, zero, negative offsets, positive passthrough, and i32::MIN) with deterministic inputs and exact assertions.

Comment thread crates/package-manager/src/build_sequence.rs

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

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

Comment thread crates/package-manager/src/build_modules.rs
The previous commit sorted the *roots* in `collect_root_dep_paths`,
but `build_children_map` still constructed per-snapshot child lists
by iterating `HashMap` deps — so transitive shared descendants
(root → A → [B, C] → shared D) could still flip the trim result
across runs. Sort children for the same reason roots are sorted,
so every entry-list `get_subgraph_to_build` walks is deterministic
end-to-end. Review-suggested by CodeRabbit in PR #429.
Every existing `BuildModules` test runs with `child_concurrency: 1`,
which is the sequential codepath. The rayon `pool.install` /
`chunk.par_iter().try_for_each(...)` dispatch added for #397 item
12 isn't exercised — a regression that dropped the `Mutex` around
`ignored_builds` / `deps_state_cache`, or removed the `pool.install`
wrapping, wouldn't be caught by the sequential tests.

Adds `build_modules_collects_ignored_builds_under_concurrency` —
the same fixture as `build_modules_collects_ignored_builds` but
`child_concurrency: 2`. Two policy-denied build candidates land in
chunk 0 with no dependency edges between them, so the rayon
dispatch actually runs them concurrently. The `BTreeSet`-backed
`ignored_builds` normalizes the insertion order, so the expected
sorted output is the same as the sequential variant. Review-suggested
by Copilot in PR #429.
Copilot AI review requested due to automatic review settings May 12, 2026 22:57

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

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

Comment on lines +374 to +377
.run::<SilentReporter>()
.expect("run BuildModules under concurrency");
dbg!(&ignored);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Keeping the dbg! per pacquet's documented test-logging convention in CODE_STYLE_GUIDE.md:

If the values being compared with assert_eq! have complex structures (such as a struct or an array), they should be logged with dbg!.

ignored is Vec<String>, which qualifies as an array under that rule, and the sibling tests (build_modules_collects_ignored_builds, build_modules_excludes_explicit_deny_from_ignored, do_not_fail_on_optional_dep_with_failing_postinstall) all use the same dbg!(&ignored); pattern. The harness captures debug output on successful runs, so it won't show in CI logs unless the test fails. Removing it here would diverge from the established convention.


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

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants