feat(package-is-installable): platform + engine check, skip optional incompatibles (#434 slice 1)#439
Conversation
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughNew ChangesPackage installability system
Sequence Diagram(s)sequenceDiagram
participant Installer
participant DetectHost
participant ComputeSkipped
participant CreateVS
participant SymlinkDeps
participant BuildModules
Installer->>DetectHost: InstallabilityHost::detect()
Installer->>ComputeSkipped: compute_skipped_snapshots(snapshots, packages, host)
ComputeSkipped-->>CreateVS: skipped set
ComputeSkipped-->>SymlinkDeps: skipped set
ComputeSkipped-->>BuildModules: skipped set
CreateVS->>SymlinkDeps: exclude skipped
SymlinkDeps->>BuildModules: exclude skipped
BuildModules-->>Installer: proceed
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #439 +/- ##
========================================
Coverage 87.20% 87.21%
========================================
Files 101 105 +4
Lines 8075 8493 +418
========================================
+ Hits 7042 7407 +365
- Misses 1033 1086 +53 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Micro-Benchmark ResultsLinux |
There was a problem hiding this comment.
Pull request overview
This PR introduces a foundational “installability gate” to match pnpm’s optional-dependency behavior on incompatible hosts by evaluating per-snapshot platform (os/cpu/libc) and engine (engines.*) constraints, emitting pnpm:skipped-optional-dependency, and threading a computed skip-set through the install pipeline so skipped snapshots are not materialized, linked, or built.
Changes:
- Added new
pacquet-package-is-installablecrate porting pnpm’scheckPlatform,checkEngine, andpackageIsInstallabletri-state verdict (with ported upstream unit tests). - Added
package-manager::installabilityto compute a per-installSkippedSnapshotsset (deduped reporter emits per metadata row) and integrated it intoCreateVirtualStore,SymlinkDirectDependencies, andBuildModules. - Extended
pacquet-graph-hasherto expose host triple helpers and providedetect_node_versionto avoid re-spawningnode --version.
Reviewed changes
Copilot reviewed 22 out of 23 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| crates/package-manager/src/symlink_direct_dependencies/tests.rs | Updates test harness to pass an empty SkippedSnapshots set. |
| crates/package-manager/src/symlink_direct_dependencies.rs | Skips linking direct deps whose resolved snapshot key is in SkippedSnapshots. |
| crates/package-manager/src/lib.rs | Registers and re-exports the new installability module. |
| crates/package-manager/src/installability.rs | New module computing SkippedSnapshots and emitting skipped-optional reporter events. |
| crates/package-manager/src/installability/tests.rs | Unit tests for skip-set computation and reporter dedupe behavior. |
| crates/package-manager/src/install_frozen_lockfile.rs | Detects host once, computes skip-set, threads it into install phases, and derives engine_name from detected node version. |
| crates/package-manager/src/create_virtual_store.rs | Excludes skipped snapshots from virtual-store creation and adjusts “added” stats accordingly. |
| crates/package-manager/src/build_modules.rs | Adds SkippedSnapshots filter to avoid build work for skipped snapshots. |
| crates/package-manager/src/build_modules/tests.rs | Updates build tests to pass an empty SkippedSnapshots set. |
| crates/package-manager/Cargo.toml | Adds dependency on pacquet-package-is-installable. |
| crates/package-is-installable/src/lib.rs | New crate entry point exporting checkers and composer. |
| crates/package-is-installable/src/check_platform.rs | Port of pnpm checkPlatform logic (os/cpu/libc + supportedArchitectures). |
| crates/package-is-installable/src/check_engine.rs | Port of pnpm checkEngine logic with prerelease-handling approximation + tests. |
| crates/package-is-installable/src/package_is_installable.rs | Port of pnpm packageIsInstallable tri-state verdict + error mapping. |
| crates/package-is-installable/src/tests/*, src/tests.rs | Ported upstream unit tests + additional end-to-end verdict tests. |
| crates/package-is-installable/Cargo.toml | New crate manifest. |
| crates/graph-hasher/src/lib.rs | Re-exports new engine/host helpers. |
| crates/graph-hasher/src/engine_name.rs | Adds detect_node_version and exports host_platform/arch/libc. |
| Cargo.toml | Adds new crate to [workspace.dependencies]. |
| Cargo.lock | Adds workspace lock entries for the new crate. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
crates/package-manager/src/installability.rs (1)
194-216: ⚡ Quick winReuse the existing package-key parser for skipped-event name/version extraction.
Using a second local parser here risks payload drift versus other install paths when key formatting evolves (scopes/peers/edge forms). Prefer the shared parser already used in-package.
Proposed refactor
-fn emit_skipped<R: Reporter>(pkg_id: &str, reason: SkipReason, details: String, prefix: &str) { - let (name, version) = split_name_version(pkg_id); +fn emit_skipped<R: Reporter>(pkg_id: &str, reason: SkipReason, details: String, prefix: &str) { + let (name, version) = crate::build_modules::parse_name_version_from_key(pkg_id); @@ -/// Split a `name@version` (with possible leading `@` for scoped -/// packages) into `(name, version)`. Mirrors the `lastIndexOf('@')` -/// rule pacquet's manifest parser already uses. -fn split_name_version(pkg_id: &str) -> (String, String) { - match pkg_id.rfind('@') { - Some(idx) if idx > 0 => (pkg_id[..idx].to_string(), pkg_id[idx + 1..].to_string()), - _ => (pkg_id.to_string(), String::new()), - } -}As per coding guidelines: "Before implementing any non-trivial helper function, search the workspace for existing functions or utilities that do the same or similar thing."
🤖 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/installability.rs` around lines 194 - 216, The split_name_version helper duplicates package-key parsing and risks drift; replace its usage in emit_skipped by calling the shared package-key parser already used elsewhere in this crate (e.g. the PackageKey/parse_package_key utility) to extract name and version, falling back to (pkg_id.to_string(), String::new()) on parse errors, then remove the local split_name_version function; update emit_skipped to build SkippedOptionalPackage from the parser's name and version fields so formatting is consistent with other install paths.
🤖 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/graph-hasher/src/engine_name.rs`:
- Around line 141-143: host_libc currently returns the constant "unknown", which
disables libc filtering; replace it with runtime detection in host_libc() so it
returns a concrete identifier like "musl" or "glibc". Implement detection by
running a system probe (e.g., invoke "ldd --version" or read /lib*/libc*
metadata) and parse output for "musl" to return "musl", otherwise return
"glibc"; ensure failures fall back to "unknown" and propagate no-panics. Update
the host_libc function to perform this probe and return the detected string.
In `@crates/package-is-installable/src/check_engine.rs`:
- Around line 171-179: The current satisfies_with_prerelease fallback in
check_engine.rs incorrectly rejects prerelease versions for strict upper bounds
like `<X.Y.Z`; fix by either (A) adding the nodejs-semver fork to Cargo.toml and
calling its satisfies_with_prerelease(true) from the existing
satisfies_with_prerelease call to match pnpm semantics, or (B) implement a
targeted special-case in the satisfies_with_prerelease function: if the range is
a strict upper-bound of the form `<X.Y.Z` and the candidate version is a
prerelease whose major.minor.patch equals X.Y.Z, return true (treat as
included), otherwise keep the current strip-prerelease behavior—use these
identifiers (satisfies_with_prerelease, check_engine.rs) to locate and update
the logic.
In `@crates/package-is-installable/src/tests/check_engine.rs`:
- Around line 100-102: Replace the #[ignore] approach for the test
pnpm_is_a_prerelease_version_strict_ge_full_version_does_not_satisfy by moving
it into the repo’s known-failures harness: remove the #[ignore] attribute, place
the test under the known_failures module (or annotate it accordingly), and
invoke pacquet_testing_utils::allow_known_failure! for that test so it remains
runnable and flagged as a known failure; ensure the test function name
pnpm_is_a_prerelease_version_strict_ge_full_version_does_not_satisfy is
preserved and that the allow_known_failure! macro wraps or is called at the
start of the test to mark the boundary.
---
Nitpick comments:
In `@crates/package-manager/src/installability.rs`:
- Around line 194-216: The split_name_version helper duplicates package-key
parsing and risks drift; replace its usage in emit_skipped by calling the shared
package-key parser already used elsewhere in this crate (e.g. the
PackageKey/parse_package_key utility) to extract name and version, falling back
to (pkg_id.to_string(), String::new()) on parse errors, then remove the local
split_name_version function; update emit_skipped to build SkippedOptionalPackage
from the parser's name and version fields so formatting is consistent with other
install paths.
🪄 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: 254f001b-8e08-4e26-abdc-06caa8ef81fd
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (22)
Cargo.tomlcrates/graph-hasher/src/engine_name.rscrates/graph-hasher/src/lib.rscrates/package-is-installable/Cargo.tomlcrates/package-is-installable/src/check_engine.rscrates/package-is-installable/src/check_platform.rscrates/package-is-installable/src/lib.rscrates/package-is-installable/src/package_is_installable.rscrates/package-is-installable/src/tests.rscrates/package-is-installable/src/tests/check_engine.rscrates/package-is-installable/src/tests/check_platform.rscrates/package-is-installable/src/tests/package_is_installable.rscrates/package-manager/Cargo.tomlcrates/package-manager/src/build_modules.rscrates/package-manager/src/build_modules/tests.rscrates/package-manager/src/create_virtual_store.rscrates/package-manager/src/install_frozen_lockfile.rscrates/package-manager/src/installability.rscrates/package-manager/src/installability/tests.rscrates/package-manager/src/lib.rscrates/package-manager/src/symlink_direct_dependencies.rscrates/package-manager/src/symlink_direct_dependencies/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). (1)
- GitHub Check: Run benchmark on ubuntu-latest
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs
📄 CodeRabbit inference engine (AGENTS.md)
**/*.rs: Preserve existing method chains andpipe-traitchains; do not break them into intermediateletbindings 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 (&Pathover&PathBuf,&strover&String) when it does not force extra copies.
PreferArc::clone(&x)andRc::clone(&x)overx.clone()for reference-counted types to make the cost visible at the call site.
Do not use star imports inside module bodies. Writeuse super::{Foo, bar}instead ofuse 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::*;inlib.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 plainStringor&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 viaTryFrom<String>and/orFromStr; 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 infallibleFrom<String>andFrom<&str>constructor in the Rust wrapper.
When upstream TypeScript occasionally constructs a branded type without validation (via bareasassertion), add afrom_str_unchecked(or similarly named) constructor on the Rust side. Keep the validating constructor as well; `from_str_u...
Files:
crates/package-is-installable/src/tests/package_is_installable.rscrates/package-manager/src/lib.rscrates/graph-hasher/src/lib.rscrates/package-manager/src/symlink_direct_dependencies.rscrates/package-is-installable/src/tests.rscrates/package-manager/src/build_modules.rscrates/package-is-installable/src/check_platform.rscrates/package-manager/src/installability.rscrates/package-is-installable/src/lib.rscrates/package-manager/src/installability/tests.rscrates/package-manager/src/symlink_direct_dependencies/tests.rscrates/package-manager/src/create_virtual_store.rscrates/package-is-installable/src/tests/check_engine.rscrates/package-is-installable/src/tests/check_platform.rscrates/graph-hasher/src/engine_name.rscrates/package-manager/src/install_frozen_lockfile.rscrates/package-is-installable/src/check_engine.rscrates/package-is-installable/src/package_is_installable.rscrates/package-manager/src/build_modules/tests.rs
**/tests/**/*.rs
📄 CodeRabbit inference engine (AGENTS.md)
**/tests/**/*.rs: When porting behavior from pnpm, port the relevant pnpm tests to Rust tests whenever they translate. Matching test coverage is the easiest way to prove behavioral parity.
Consult the test-porting plan inplans/TEST_PORTING.mdbefore adding ported tests. Follow the conventions expected for ports: useknown_failuresmodules, usepacquet_testing_utils::allow_known_failure!at the not-yet-implemented boundary, and temporarily break the subject under test to verify the ported test actually catches the regression. Update TEST_PORTING.md checkboxes as items land.
Follow the test-logging guidance in CODE_STYLE_GUIDE.md: log before non-assert_eq!assertions, usedbg!for complex structures, skip logging for simple scalarassert_eq!assertions.
Files:
crates/package-is-installable/src/tests/package_is_installable.rscrates/package-is-installable/src/tests/check_engine.rscrates/package-is-installable/src/tests/check_platform.rs
🧠 Learnings (3)
📚 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-is-installable/src/tests/package_is_installable.rscrates/package-manager/src/lib.rscrates/graph-hasher/src/lib.rscrates/package-manager/src/symlink_direct_dependencies.rscrates/package-is-installable/src/tests.rscrates/package-manager/src/build_modules.rscrates/package-is-installable/src/check_platform.rscrates/package-manager/src/installability.rscrates/package-is-installable/src/lib.rscrates/package-manager/src/installability/tests.rscrates/package-manager/src/symlink_direct_dependencies/tests.rscrates/package-manager/src/create_virtual_store.rscrates/package-is-installable/src/tests/check_engine.rscrates/package-is-installable/src/tests/check_platform.rscrates/graph-hasher/src/engine_name.rscrates/package-manager/src/install_frozen_lockfile.rscrates/package-is-installable/src/check_engine.rscrates/package-is-installable/src/package_is_installable.rscrates/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-is-installable/src/tests.rscrates/package-manager/src/installability/tests.rscrates/package-manager/src/symlink_direct_dependencies/tests.rscrates/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 this repo’s Rust tests (run via `cargo nextest`), stdout/stderr from passing tests is captured and is only shown when a test fails. As a result, `eprintln!` (and similar stderr logging) in these tests should generally not be flagged as CI “noise” on the happy path; CI output should only appear for failing tests.
Applied to files:
crates/package-is-installable/src/tests.rs
🔇 Additional comments (20)
crates/package-is-installable/Cargo.toml (1)
1-21: LGTM!Cargo.toml (1)
16-34: LGTM!crates/package-manager/Cargo.toml (1)
14-28: LGTM!crates/graph-hasher/src/lib.rs (1)
21-23: LGTM!crates/package-is-installable/src/check_platform.rs (1)
117-207: LGTM!crates/package-is-installable/src/tests/check_platform.rs (1)
34-157: LGTM!crates/package-is-installable/src/package_is_installable.rs (1)
1-203: LGTM!crates/package-is-installable/src/tests.rs (1)
1-10: LGTM!crates/package-manager/src/symlink_direct_dependencies/tests.rs (1)
2-2: LGTM!Also applies to: 89-89, 203-203, 243-243
crates/package-manager/src/lib.rs (1)
16-16: LGTM!Also applies to: 40-40
crates/package-manager/src/build_modules/tests.rs (1)
2-2: LGTM!Also applies to: 308-308, 375-375, 430-430, 509-509, 636-636, 698-698, 753-753, 985-985, 1092-1092, 1208-1208, 1434-1434, 1538-1538, 1613-1613
crates/package-manager/src/build_modules.rs (1)
1-1: LGTM!Also applies to: 251-257, 286-286, 304-309
crates/package-manager/src/installability/tests.rs (1)
1-249: LGTM!crates/package-manager/src/symlink_direct_dependencies.rs (1)
1-1: LGTM!Also applies to: 31-37, 59-60, 110-119
crates/package-manager/src/install_frozen_lockfile.rs (1)
3-5: LGTM!Also applies to: 80-92, 125-159, 170-170, 176-182, 183-191, 306-306, 345-353
crates/package-is-installable/src/tests/check_engine.rs (1)
16-98: LGTM!Also applies to: 119-130
crates/package-manager/src/create_virtual_store.rs (1)
2-3: LGTM!Also applies to: 83-90, 128-128, 160-170, 286-299
crates/package-is-installable/src/tests/package_is_installable.rs (1)
29-119: LGTM!crates/package-is-installable/src/lib.rs (1)
19-35: LGTM!crates/package-manager/src/installability.rs (1)
36-180: LGTM!
Integrated-Benchmark Report (Linux)Scenario: Frozen Lockfile
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 2.5207950711199993,
"stddev": 0.09047546383440337,
"median": 2.52023782302,
"user": 2.4923601599999996,
"system": 3.53799068,
"min": 2.38760493202,
"max": 2.64718381302,
"times": [
2.64718381302,
2.62394144802,
2.51558165502,
2.3930701270199997,
2.52489399102,
2.56215924302,
2.48693040202,
2.60326239002,
2.46332271002,
2.38760493202
]
},
{
"command": "pacquet@main",
"mean": 2.53327262082,
"stddev": 0.0727320430875739,
"median": 2.53691443902,
"user": 2.50874616,
"system": 3.4906317799999997,
"min": 2.44190387302,
"max": 2.64470230402,
"times": [
2.44882229202,
2.60664173802,
2.47778349102,
2.57411072702,
2.54617471002,
2.44190387302,
2.5276541680199998,
2.64470230402,
2.4639693070199997,
2.60096359802
]
},
{
"command": "pnpm",
"mean": 6.02236690582,
"stddev": 0.08440919018326186,
"median": 6.00859045802,
"user": 8.81789876,
"system": 4.392308979999999,
"min": 5.91730197202,
"max": 6.19799319202,
"times": [
5.98665217402,
6.02166550402,
6.0952855670199995,
6.07589849702,
5.99551541202,
5.91730197202,
5.9455654960199995,
5.94624330802,
6.19799319202,
6.04154793602
]
}
]
}Scenario: Frozen Lockfile (Hot Cache)
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 0.7039470408800002,
"stddev": 0.07187437315238897,
"median": 0.6781522334800001,
"user": 0.35408602000000006,
"system": 1.4752617199999998,
"min": 0.66838989548,
"max": 0.9057613094800001,
"times": [
0.9057613094800001,
0.67931045548,
0.6758797604800001,
0.67326300348,
0.67699401148,
0.6752031524800001,
0.66838989548,
0.6884465644800001,
0.71041395048,
0.68580830548
]
},
{
"command": "pacquet@main",
"mean": 0.68861161948,
"stddev": 0.04423649222548709,
"median": 0.66889923498,
"user": 0.35356692,
"system": 1.47052312,
"min": 0.6505246544800001,
"max": 0.7601003914800001,
"times": [
0.7601003914800001,
0.6563395334800001,
0.66559958748,
0.7361165014800001,
0.6606435094800001,
0.65475103448,
0.67219888248,
0.7575841734800001,
0.67225792648,
0.6505246544800001
]
},
{
"command": "pnpm",
"mean": 2.66795867258,
"stddev": 0.09691184761818279,
"median": 2.66351564248,
"user": 3.10966582,
"system": 2.1947448199999995,
"min": 2.51338479848,
"max": 2.8208296654800002,
"times": [
2.7646161404800003,
2.51338479848,
2.65965596248,
2.8208296654800002,
2.5420245714800003,
2.7063053364800003,
2.64479664048,
2.61072302448,
2.7498752634800003,
2.6673753224800003
]
}
]
} |
Address Doc + Format CI failures and CodeRabbit review feedback on PR #439: - Doc: clear ambiguous fn-vs-module intra-doc links in `lib.rs`, replace links to private items (`satisfies_with_prerelease`, `dedupe_current`) with plain references, and remove the cross-crate link from `graph-hasher` to `pacquet_package_is_installable`. - Format: re-flow `installability.rs` after the previous edit broke the `node_version` line shape `cargo fmt` expects. - `InstallabilityError`: add an `InvalidNodeVersion(InvalidNodeVersionError)` variant so callers keep the upstream `ERR_PNPM_INVALID_NODE_VERSION` error code instead of seeing it collapsed into a synthetic engine mismatch. - `InstallabilityHost`: add a `node_detected: bool` flag that `install_frozen_lockfile` consults when deriving `engine_name`. Restores the prior "None when no `node` on PATH" semantics so a detection failure can't poison the side-effects cache with rows keyed on the `99999.0.0` synthetic fallback. - `InstallabilityOptions`: switch the string fields to `&'a str` and derive `Copy` so `compute_skipped_snapshots` can build the host-derived part once and toggle only `optional` per snapshot. Removes four `String` allocations per snapshot. - `satisfies_with_prerelease` doc comment now lists both known divergences from upstream's `includePrerelease: true` (the `>=X.Y.Z` over-acceptance the ported test pins, plus the `<X.Y.Z` under-acceptance), not just one. - Move the prerelease known-failure case from `#[ignore]` to `tests/known_failures.rs` so `just known-failures` picks it up alongside the existing lifecycle-script entries. - Dedupe the doubled "Resolve the host context from the running process." doc summary on `InstallabilityHost::detect` after the earlier rustfmt run.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
crates/package-is-installable/tests/known_failures.rs (1)
23-35: ⚡ Quick winFix bound terminology in known-failure helper/message (
>=is lower-bound).This known-failure case is a strict lower-bound divergence, but helper/text currently call it upper-bound. Renaming avoids sending follow-up work in the wrong direction.
Suggested patch
- fn semver_strict_upper_bound_prerelease_handled() -> KnownResult<()> { + fn semver_strict_lower_bound_prerelease_handled() -> KnownResult<()> { Err(KnownFailure::new( "pacquet's strip-prerelease fallback approximates npm-semver's \ - `includePrerelease: true`, but over-accepts on a strict upper-bounded \ + `includePrerelease: true`, but over-accepts on a strict lower-bounded \ range. Upstream `>=9.0.0` rejects `9.0.0-alpha.1` (alpha.1 < 9.0.0 in \ semver order, no implicit `-0` floor when fully specified); pacquet's \ strip turns the version into `9.0.0` which then satisfies `>=9.0.0`. \ Fix path: either add the `nodejs-semver` fork (which exposes \ `satisfies_with_prerelease(include_prerelease: bool)`) or open-code the \ - strict-upper-bound carve-out. Engine ranges of this shape are vanishingly \ + strict-lower-bound carve-out. Engine ranges of this shape are vanishingly \ rare in real package.json files.", )) } @@ - allow_known_failure!(semver_strict_upper_bound_prerelease_handled()); + allow_known_failure!(semver_strict_lower_bound_prerelease_handled());Also applies to: 50-50
🤖 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-is-installable/tests/known_failures.rs` around lines 23 - 35, The helper name and message mislabel a strict lower-bound issue as an "upper-bound"; rename the function semver_strict_upper_bound_prerelease_handled to semver_strict_lower_bound_prerelease_handled and update the KnownFailure::new message text (and any other occurrences) to replace "upper-bound" with "lower-bound" so the helper and description correctly reflect that the divergence is for a strict lower-bound range (e.g., change the comment "Boundary helper for the strict-upper-bound prerelease semantics" and the embedded message text accordingly).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@crates/package-is-installable/tests/known_failures.rs`:
- Around line 23-35: The helper name and message mislabel a strict lower-bound
issue as an "upper-bound"; rename the function
semver_strict_upper_bound_prerelease_handled to
semver_strict_lower_bound_prerelease_handled and update the KnownFailure::new
message text (and any other occurrences) to replace "upper-bound" with
"lower-bound" so the helper and description correctly reflect that the
divergence is for a strict lower-bound range (e.g., change the comment "Boundary
helper for the strict-upper-bound prerelease semantics" and the embedded message
text accordingly).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 88998c67-22b8-4c7e-8a92-0d9b5af9aff9
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (12)
crates/graph-hasher/src/engine_name.rscrates/package-is-installable/Cargo.tomlcrates/package-is-installable/src/check_engine.rscrates/package-is-installable/src/check_platform.rscrates/package-is-installable/src/lib.rscrates/package-is-installable/src/package_is_installable.rscrates/package-is-installable/src/tests/check_engine.rscrates/package-is-installable/src/tests/package_is_installable.rscrates/package-is-installable/tests/known_failures.rscrates/package-manager/src/install_frozen_lockfile.rscrates/package-manager/src/installability.rscrates/package-manager/src/installability/tests.rs
🚧 Files skipped from review as they are similar to previous changes (9)
- crates/package-is-installable/src/tests/package_is_installable.rs
- crates/package-is-installable/Cargo.toml
- crates/graph-hasher/src/engine_name.rs
- crates/package-manager/src/install_frozen_lockfile.rs
- crates/package-is-installable/src/check_platform.rs
- crates/package-manager/src/installability.rs
- crates/package-manager/src/installability/tests.rs
- crates/package-is-installable/src/package_is_installable.rs
- crates/package-is-installable/src/lib.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). (1)
- GitHub Check: Run benchmark on ubuntu-latest
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs
📄 CodeRabbit inference engine (AGENTS.md)
**/*.rs: Preserve existing method chains andpipe-traitchains; do not break them into intermediateletbindings 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 (&Pathover&PathBuf,&strover&String) when it does not force extra copies.
PreferArc::clone(&x)andRc::clone(&x)overx.clone()for reference-counted types to make the cost visible at the call site.
Do not use star imports inside module bodies. Writeuse super::{Foo, bar}instead ofuse 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::*;inlib.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 plainStringor&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 viaTryFrom<String>and/orFromStr; 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 infallibleFrom<String>andFrom<&str>constructor in the Rust wrapper.
When upstream TypeScript occasionally constructs a branded type without validation (via bareasassertion), add afrom_str_unchecked(or similarly named) constructor on the Rust side. Keep the validating constructor as well; `from_str_u...
Files:
crates/package-is-installable/tests/known_failures.rscrates/package-is-installable/src/tests/check_engine.rscrates/package-is-installable/src/check_engine.rs
**/tests/**/*.rs
📄 CodeRabbit inference engine (AGENTS.md)
**/tests/**/*.rs: When porting behavior from pnpm, port the relevant pnpm tests to Rust tests whenever they translate. Matching test coverage is the easiest way to prove behavioral parity.
Consult the test-porting plan inplans/TEST_PORTING.mdbefore adding ported tests. Follow the conventions expected for ports: useknown_failuresmodules, usepacquet_testing_utils::allow_known_failure!at the not-yet-implemented boundary, and temporarily break the subject under test to verify the ported test actually catches the regression. Update TEST_PORTING.md checkboxes as items land.
Follow the test-logging guidance in CODE_STYLE_GUIDE.md: log before non-assert_eq!assertions, usedbg!for complex structures, skip logging for simple scalarassert_eq!assertions.
Files:
crates/package-is-installable/tests/known_failures.rscrates/package-is-installable/src/tests/check_engine.rs
🧠 Learnings (1)
📚 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-is-installable/tests/known_failures.rscrates/package-is-installable/src/tests/check_engine.rscrates/package-is-installable/src/check_engine.rs
🔇 Additional comments (2)
crates/package-is-installable/src/check_engine.rs (1)
1-200: LGTM!crates/package-is-installable/src/tests/check_engine.rs (1)
1-120: LGTM!
…ed (#434 slice 1) The cold-install CI benchmark on PR #439 showed a ~3% regression (2.075 ± 0.072 s vs 2.009 ± 0.055 s on a 1352-package fixture). The dominant per-snapshot cost in the new `compute_skipped_snapshots` pass is the `snapshot_key.without_peer()` + `metadata_key.to_string()` + `package_is_installable` chain. Skipping that entirely whenever the lockfile contains no metadata row with an `engines` / `cpu` / `os` / `libc` constraint avoids the per-snapshot work for the common case (most real lockfiles have only a handful of platform-tagged native packages, and many have none). `any_installability_constraint` is a linear scan over `packages` that short-circuits on the first declared constraint, so the fast-path probe is O(1) amortized on lockfiles that do have constraints (it finds one quickly) and O(N) on those that don't — but with a much smaller per-row cost than the loop it replaces. Also drops the redundant `or_else(|| Some(vec!["any".to_string()]))` allocations in `check_package`. Upstream's `index.ts:82-86` defaults absent platform axes to `["any"]` before passing them to `checkPlatform`, but pacquet's `check_platform` already skips an axis when its wanted field is `None`, so the default was allocating one Vec + String per axis per snapshot for nothing. Adds a unit test that exercises the fast path against a synthetic constraint-free lockfile.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 23 out of 24 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
crates/package-manager/src/build_modules.rs:314
BuildModulesfiltersrequires_build_mapbyskipped, but the build graph/chunk computation still traverses the fullsnapshotsmap (including skipped roots/edges). That means dependencies that are only reachable through a skipped optional+incompatible snapshot can still end up in the build sequence and have their scripts run, even though the parent snapshot never landed on disk. Consider threadingskippedintobuild_sequence(or pre-filtering the graph/roots/edges it walks) so skipped depPaths are removed from the traversal, matching the post-skip graph pnpm builds.
let requires_build_map: HashMap<PackageKey, bool> = snapshots
.keys()
// Skip snapshots that never landed on disk. `pkg_requires_build`
// would just return `false` for a missing dir, but the
// walk would still spend a syscall per skipped key — the
// filter short-circuits that on installs with large
// optional fan-out.
.filter(|key| !skipped.contains(key))
.map(|key| {
let pkg_dir = virtual_store_dir_for_key(virtual_store_dir, key);
(key.clone(), pkg_requires_build(&pkg_dir))
})
.collect();
Address Doc + Format CI failures and CodeRabbit review feedback on PR #439: - Doc: clear ambiguous fn-vs-module intra-doc links in `lib.rs`, replace links to private items (`satisfies_with_prerelease`, `dedupe_current`) with plain references, and remove the cross-crate link from `graph-hasher` to `pacquet_package_is_installable`. - Format: re-flow `installability.rs` after the previous edit broke the `node_version` line shape `cargo fmt` expects. - `InstallabilityError`: add an `InvalidNodeVersion(InvalidNodeVersionError)` variant so callers keep the upstream `ERR_PNPM_INVALID_NODE_VERSION` error code instead of seeing it collapsed into a synthetic engine mismatch. - `InstallabilityHost`: add a `node_detected: bool` flag that `install_frozen_lockfile` consults when deriving `engine_name`. Restores the prior "None when no `node` on PATH" semantics so a detection failure can't poison the side-effects cache with rows keyed on the `99999.0.0` synthetic fallback. - `InstallabilityOptions`: switch the string fields to `&'a str` and derive `Copy` so `compute_skipped_snapshots` can build the host-derived part once and toggle only `optional` per snapshot. Removes four `String` allocations per snapshot. - `satisfies_with_prerelease` doc comment now lists both known divergences from upstream's `includePrerelease: true` (the `>=X.Y.Z` over-acceptance the ported test pins, plus the `<X.Y.Z` under-acceptance), not just one. - Move the prerelease known-failure case from `#[ignore]` to `tests/known_failures.rs` so `just known-failures` picks it up alongside the existing lifecycle-script entries. - Dedupe the doubled "Resolve the host context from the running process." doc summary on `InstallabilityHost::detect` after the earlier rustfmt run.
86acbf2 to
ae93da0
Compare
…ed (#434 slice 1) The cold-install CI benchmark on PR #439 showed a ~3% regression (2.075 ± 0.072 s vs 2.009 ± 0.055 s on a 1352-package fixture). The dominant per-snapshot cost in the new `compute_skipped_snapshots` pass is the `snapshot_key.without_peer()` + `metadata_key.to_string()` + `package_is_installable` chain. Skipping that entirely whenever the lockfile contains no metadata row with an `engines` / `cpu` / `os` / `libc` constraint avoids the per-snapshot work for the common case (most real lockfiles have only a handful of platform-tagged native packages, and many have none). `any_installability_constraint` is a linear scan over `packages` that short-circuits on the first declared constraint, so the fast-path probe is O(1) amortized on lockfiles that do have constraints (it finds one quickly) and O(N) on those that don't — but with a much smaller per-row cost than the loop it replaces. Also drops the redundant `or_else(|| Some(vec!["any".to_string()]))` allocations in `check_package`. Upstream's `index.ts:82-86` defaults absent platform axes to `["any"]` before passing them to `checkPlatform`, but pacquet's `check_platform` already skips an axis when its wanted field is `None`, so the default was allocating one Vec + String per axis per snapshot for nothing. Adds a unit test that exercises the fast path against a synthetic constraint-free lockfile.
There was a problem hiding this comment.
🧹 Nitpick comments (2)
crates/package-manager/src/installability.rs (2)
278-300: ⚡ Quick winAvoid re-parsing
PackageKeythroughDisplay.This path turns a typed key into a string and then hand-parses it back into
nameandversion, so the reporter payload now depends on the current serialization format ofPackageKey. Prefer reusing an existing package-id parser/accessor from the lockfile layer, or move this split logic there and call it directly.As per coding guidelines: "Before implementing any non-trivial helper function, search the workspace for existing functions or utilities that do the same or similar thing." "If logic you need already exists in another crate but is not exported, refactor it into a shared crate or move it to a utility crate rather than copy-pasting."
🤖 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/installability.rs` around lines 278 - 300, The code is re-parsing a PackageKey by calling Display and hand-splitting in split_name_version used by emit_skipped; instead, stop relying on string round-tripping — either use an existing package-id parser/accessor from the lockfile crate (export it and call it here) or move the split logic into a shared utility crate and call that from emit_skipped; alternatively change emit_skipped's signature to accept (name, version) or a typed PackageKey accessor so you don't call pkg_id.to_string() and rfind('@') inside split_name_version (update references to split_name_version and emit_skipped accordingly).
10-14: ⚡ Quick winPoint these upstream breadcrumbs at
pnpm/main.These comments are the maintenance trail for future ports; pinning them to
94240bc046bakes in stale upstream behavior. Useblob/main/...links instead.As per coding guidelines: "When porting features, bug fixes, or behavior changes from pnpm, always confirm you are looking at the latest, most up-to-date version of pnpm's main branch before reading. 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/installability.rs` around lines 10 - 14, Update the upstream breadcrumb links in the module doc comment at the top of installability.rs to point to pnpm's main branch instead of the pinned commit SHA; replace the two URLs currently referencing commit 94240bc046 with the equivalent blob/main paths (e.g., change .../blob/94240bc046/... to .../blob/main/...) so the comments in this file (the module-level doc comment in crates/package-manager/src/installability.rs) always reference the latest pnpm/main sources.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@crates/package-manager/src/installability.rs`:
- Around line 278-300: The code is re-parsing a PackageKey by calling Display
and hand-splitting in split_name_version used by emit_skipped; instead, stop
relying on string round-tripping — either use an existing package-id
parser/accessor from the lockfile crate (export it and call it here) or move the
split logic into a shared utility crate and call that from emit_skipped;
alternatively change emit_skipped's signature to accept (name, version) or a
typed PackageKey accessor so you don't call pkg_id.to_string() and rfind('@')
inside split_name_version (update references to split_name_version and
emit_skipped accordingly).
- Around line 10-14: Update the upstream breadcrumb links in the module doc
comment at the top of installability.rs to point to pnpm's main branch instead
of the pinned commit SHA; replace the two URLs currently referencing commit
94240bc046 with the equivalent blob/main paths (e.g., change
.../blob/94240bc046/... to .../blob/main/...) so the comments in this file (the
module-level doc comment in crates/package-manager/src/installability.rs) always
reference the latest pnpm/main sources.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 029aaf55-ea2e-43cf-b799-882453cef514
📒 Files selected for processing (2)
crates/package-is-installable/src/check_engine.rscrates/package-manager/src/installability.rs
🚧 Files skipped from review as they are similar to previous changes (1)
- crates/package-is-installable/src/check_engine.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). (1)
- 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 andpipe-traitchains; do not break them into intermediateletbindings 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 (&Pathover&PathBuf,&strover&String) when it does not force extra copies.
PreferArc::clone(&x)andRc::clone(&x)overx.clone()for reference-counted types to make the cost visible at the call site.
Do not use star imports inside module bodies. Writeuse super::{Foo, bar}instead ofuse 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::*;inlib.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 plainStringor&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 viaTryFrom<String>and/orFromStr; 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 infallibleFrom<String>andFrom<&str>constructor in the Rust wrapper.
When upstream TypeScript occasionally constructs a branded type without validation (via bareasassertion), add afrom_str_unchecked(or similarly named) constructor on the Rust side. Keep the validating constructor as well; `from_str_u...
Files:
crates/package-manager/src/installability.rs
🧠 Learnings (1)
📚 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/installability.rs
Address Doc + Format CI failures and CodeRabbit review feedback on PR #439: - Doc: clear ambiguous fn-vs-module intra-doc links in `lib.rs`, replace links to private items (`satisfies_with_prerelease`, `dedupe_current`) with plain references, and remove the cross-crate link from `graph-hasher` to `pacquet_package_is_installable`. - Format: re-flow `installability.rs` after the previous edit broke the `node_version` line shape `cargo fmt` expects. - `InstallabilityError`: add an `InvalidNodeVersion(InvalidNodeVersionError)` variant so callers keep the upstream `ERR_PNPM_INVALID_NODE_VERSION` error code instead of seeing it collapsed into a synthetic engine mismatch. - `InstallabilityHost`: add a `node_detected: bool` flag that `install_frozen_lockfile` consults when deriving `engine_name`. Restores the prior "None when no `node` on PATH" semantics so a detection failure can't poison the side-effects cache with rows keyed on the `99999.0.0` synthetic fallback. - `InstallabilityOptions`: switch the string fields to `&'a str` and derive `Copy` so `compute_skipped_snapshots` can build the host-derived part once and toggle only `optional` per snapshot. Removes four `String` allocations per snapshot. - `satisfies_with_prerelease` doc comment now lists both known divergences from upstream's `includePrerelease: true` (the `>=X.Y.Z` over-acceptance the ported test pins, plus the `<X.Y.Z` under-acceptance), not just one. - Move the prerelease known-failure case from `#[ignore]` to `tests/known_failures.rs` so `just known-failures` picks it up alongside the existing lifecycle-script entries. - Dedupe the doubled "Resolve the host context from the running process." doc summary on `InstallabilityHost::detect` after the earlier rustfmt run.
…ed (#434 slice 1) The cold-install CI benchmark on PR #439 showed a ~3% regression (2.075 ± 0.072 s vs 2.009 ± 0.055 s on a 1352-package fixture). The dominant per-snapshot cost in the new `compute_skipped_snapshots` pass is the `snapshot_key.without_peer()` + `metadata_key.to_string()` + `package_is_installable` chain. Skipping that entirely whenever the lockfile contains no metadata row with an `engines` / `cpu` / `os` / `libc` constraint avoids the per-snapshot work for the common case (most real lockfiles have only a handful of platform-tagged native packages, and many have none). `any_installability_constraint` is a linear scan over `packages` that short-circuits on the first declared constraint, so the fast-path probe is O(1) amortized on lockfiles that do have constraints (it finds one quickly) and O(N) on those that don't — but with a much smaller per-row cost than the loop it replaces. Also drops the redundant `or_else(|| Some(vec!["any".to_string()]))` allocations in `check_package`. Upstream's `index.ts:82-86` defaults absent platform axes to `["any"]` before passing them to `checkPlatform`, but pacquet's `check_platform` already skips an axis when its wanted field is `None`, so the default was allocating one Vec + String per axis per snapshot for nothing. Adds a unit test that exercises the fast path against a synthetic constraint-free lockfile.
a735e36 to
d723f22
Compare
…l tests After rebasing on top of #439 (`pacquet-package-is-installable` + the `SkippedSnapshots` set), the new test sites this PR added — `per_importer_prefix_in_pnpm_root_events`, `unsafe_importer_keys_error_before_filesystem_writes`, `cross_importer_link_dep_symlinks_to_sibling_rootdir`, and `custom_modules_dir_propagates_to_each_importer` — needed the new `skipped` field on their `SymlinkDirectDependencies` builders. Pass `&SkippedSnapshots::default()` everywhere; the per-importer install behaviour these tests cover is orthogonal to the installability filter, so an empty skipped set keeps the assertion subject the same. --- Written by an agent (Claude Code, claude-opus-4-7).
…l tests After rebasing on top of #439 (`pacquet-package-is-installable` + the `SkippedSnapshots` set), the new test sites this PR added — `per_importer_prefix_in_pnpm_root_events`, `unsafe_importer_keys_error_before_filesystem_writes`, `cross_importer_link_dep_symlinks_to_sibling_rootdir`, and `custom_modules_dir_propagates_to_each_importer` — needed the new `skipped` field on their `SymlinkDirectDependencies` builders. Pass `&SkippedSnapshots::default()` everywhere; the per-importer install behaviour these tests cover is orthogonal to the installability filter, so an empty skipped set keeps the assertion subject the same. --- Written by an agent (Claude Code, claude-opus-4-7).
…l tests After rebasing on top of #439 (`pacquet-package-is-installable` + the `SkippedSnapshots` set), the new test sites this PR added — `per_importer_prefix_in_pnpm_root_events`, `unsafe_importer_keys_error_before_filesystem_writes`, `cross_importer_link_dep_symlinks_to_sibling_rootdir`, and `custom_modules_dir_propagates_to_each_importer` — needed the new `skipped` field on their `SymlinkDirectDependencies` builders. Pass `&SkippedSnapshots::default()` everywhere; the per-importer install behaviour these tests cover is orthogonal to the installability filter, so an empty skipped set keeps the assertion subject the same. --- Written by an agent (Claude Code, claude-opus-4-7).
… fixture `crates/package-manager/src/installability/tests.rs` initialises a `TarballResolution` without the `path` field that #451 added (git- hosted tarball sub-directory selection). The omission landed when #439 (installability check, #434 slice 1) merged just before #451 — neither PR touched the other's file, so the bug shipped on `origin/main`. CI was failing with: ``` error[E0063]: missing field `path` in initializer of `TarballResolution` --> crates/package-manager/src/installability/tests.rs:56:49 ``` Fix is one line: `path: None`. The installability check ignores the resolution shape entirely (see the comment a few lines above the init site), so `None` is correct.
Origin/main grew a `path: Option<String>` field on `TarballResolution` (PR #451, `feat(git-fetcher): install git-hosted tarballs via preparePackage + packlist`). That PR landed after #439 (`feat(package-is-installable): platform + engine check`) and missed updating the `TarballResolution` literal in `crates/package-manager/src/installability/tests.rs`, so any PR opened against current main inherits a build break in the `package-is-installable` test build (#445 hit the same failure). My own `peer_dependency_in_lockfile_surfaces_unsupported` test in `crates/real-hoist/src/tests.rs` had the same gap. Add `path: None` to both literals so the test builds compile against the post-#451 `TarballResolution` shape. Includes the rebase of feat/438-slice-3b onto current origin/main.
… fixture (#455) Landrace between #439 (installability tests fixture) and #451 (added `path: Option<String>` to `TarballResolution`). Both merged green against their own bases but main's tip won't compile: error[E0063]: missing field `path` in initializer of `TarballResolution` --> crates/package-manager/src/installability/tests.rs:56:49 Add the missing field with `path: None` so the struct literal matches the current `TarballResolution` shape.
…bc detection (#434 slice 2) Slice 1 (#439) wired `pacquet-package-is-installable` into the install pipeline but the `SupportedArchitectures` input was always `None`, so every install behaved as if the user had opted into "host triple only". This slice closes that gap by wiring the three upstream input sources pinned at pnpm/pnpm@94240bc046: - `Config.supported_architectures` deserialized from `pnpm-workspace.yaml`, mirroring `getOptionsFromRootManifest.ts`. - `--cpu` / `--os` / `--libc` flags on `install` / `add`, multi-valued, comma-separated. Per-axis CLI override replaces the yaml axis wholesale, matching `overrideSupportedArchitecturesWithCLI.ts`. - Real Linux libc detection — replaces the slice 1 `"unknown"` stub with a `/lib*/ld-musl-*` probe cached via `OnceLock`. Mirrors `detect-libc.familySync()` semantics: `"musl"` / `"glibc"` on Linux, `"unknown"` everywhere else. No new dep — open-coded `read_dir` is cheaper than spawning `ldd --version` and works in slim containers without `ldd` on PATH. Threaded through `Install` / `InstallFrozenLockfile` / `Add` as an explicit field instead of mutating `State.config`, because `State.config` is `&'static Config` — the CLI override merge happens at the CLI layer and the fully-resolved value lands on the install pipeline. Tests: - `workspace_yaml/tests.rs` — yaml round-trip across `os` / `cpu` / `libc`, omission, and partial-axis cases. - `installability/tests.rs` — `supportedArchitectures` widens the accept set so a darwin-only package stays on a linux host when the user lists `darwin`; conversely, listing `linux` keeps the darwin-only package skipped (no implicit host include). Out of scope (umbrella slice 3+): `.modules.yaml.skipped` persistence, current-lockfile diffing for `removeOptionalDependenciesThatAreNotUsed`, and `--no-optional` plumbing. Closes #453.
Origin/main grew a `path: Option<String>` field on `TarballResolution` (PR #451, `feat(git-fetcher): install git-hosted tarballs via preparePackage + packlist`). That PR landed after #439 (`feat(package-is-installable): platform + engine check`) and missed updating the `TarballResolution` literal in `crates/package-manager/src/installability/tests.rs`, so any PR opened against current main inherits a build break in the `package-is-installable` test build (#445 hit the same failure). My own `peer_dependency_in_lockfile_surfaces_unsupported` test in `crates/real-hoist/src/tests.rs` had the same gap. Add `path: None` to both literals so the test builds compile against the post-#451 `TarballResolution` shape. Includes the rebase of feat/438-slice-3b onto current origin/main.
…bc detection (#434 slice 2) Slice 1 (#439) wired `pacquet-package-is-installable` into the install pipeline but the `SupportedArchitectures` input was always `None`, so every install behaved as if the user had opted into "host triple only". This slice closes that gap by wiring the three upstream input sources pinned at pnpm/pnpm@94240bc046: - `Config.supported_architectures` deserialized from `pnpm-workspace.yaml`, mirroring `getOptionsFromRootManifest.ts`. - `--cpu` / `--os` / `--libc` flags on `install` / `add`, multi-valued, comma-separated. Per-axis CLI override replaces the yaml axis wholesale, matching `overrideSupportedArchitecturesWithCLI.ts`. - Real Linux libc detection — replaces the slice 1 `"unknown"` stub with a `/lib*/ld-musl-*` probe cached via `OnceLock`. Mirrors `detect-libc.familySync()` semantics: `"musl"` / `"glibc"` on Linux, `"unknown"` everywhere else. No new dep — open-coded `read_dir` is cheaper than spawning `ldd --version` and works in slim containers without `ldd` on PATH. Threaded through `Install` / `InstallFrozenLockfile` / `Add` as an explicit field instead of mutating `State.config`, because `State.config` is `&'static Config` — the CLI override merge happens at the CLI layer and the fully-resolved value lands on the install pipeline. Tests: - `workspace_yaml/tests.rs` — yaml round-trip across `os` / `cpu` / `libc`, omission, and partial-axis cases. - `installability/tests.rs` — `supportedArchitectures` widens the accept set so a darwin-only package stays on a linux host when the user lists `darwin`; conversely, listing `linux` keeps the darwin-only package skipped (no implicit host include). Out of scope (umbrella slice 3+): `.modules.yaml.skipped` persistence, current-lockfile diffing for `removeOptionalDependenciesThatAreNotUsed`, and `--no-optional` plumbing. Closes #453.
…bc detection (#434 slice 2) (#456) Slice 2 of the [#434 umbrella](#434) (`Proper optionalDependencies support`). Slice 1 (#439) wired `pacquet-package-is-installable` into the install pipeline but the `SupportedArchitectures` input was always `None`, so every install behaved as if the user had opted into \"host triple only\". This PR closes that gap and replaces the slice 1 libc stub with a real Linux probe. Mirrors the three upstream input sources pinned at [pnpm/pnpm@94240bc046](https://github.com/pnpm/pnpm/tree/94240bc046): - **`Config.supported_architectures` from `pnpm-workspace.yaml`** — same shape as upstream's `getOptionsFromRootManifest.ts` ([`config/reader/src/getOptionsFromRootManifest.ts#L23`](https://github.com/pnpm/pnpm/blob/94240bc046/config/reader/src/getOptionsFromRootManifest.ts#L23)). - **`--cpu` / `--os` / `--libc` flags on `install` / `add`** — multi-valued, comma-separated. Per-axis CLI override replaces the yaml axis wholesale, matching upstream's [`overrideSupportedArchitecturesWithCLI.ts`](https://github.com/pnpm/pnpm/blob/94240bc046/config/reader/src/overrideSupportedArchitecturesWithCLI.ts). - **Real Linux libc detection** — replaces the slice 1 `\"unknown\"` stub with a `/lib*/ld-musl-*` probe cached via `OnceLock`. Mirrors `detect-libc.familySync()` semantics: `\"musl\"` / `\"glibc\"` on Linux, `\"unknown\"` everywhere else. No new dep — open-coded `read_dir` is cheaper than spawning `ldd --version` and works in slim containers without `ldd` on PATH. Threaded through `Install` / `InstallFrozenLockfile` / `Add` as an explicit field instead of mutating `State.config`, because `State.config` is `&'static Config` — the CLI override merge happens at the CLI layer and the fully-resolved value lands on the install pipeline. ## Test plan Unit tests added; full suite ran via `just ready` + `cargo doc --no-deps` + `taplo format --check` + `just dylint`. - [x] `cargo test -p pacquet-config workspace_yaml::tests::*supported_architectures*` — yaml round-trip across `os` / `cpu` / `libc`, omission, partial-axis (3 tests). - [x] `cargo test -p pacquet-package-manager --lib installability::tests::supported_architectures*` — `supportedArchitectures` widens the accept set so a darwin-only package stays on a linux host when the user lists `darwin`; conversely, listing `linux` keeps the darwin-only package skipped (no implicit host include). - [x] `just ready` — typos + fmt + check + nextest (792 tests pass) + clippy. - [x] `taplo format --check`. - [x] `just dylint` (perfectionist). - [x] `RUSTDOCFLAGS=\"-D warnings\" cargo doc --no-deps --workspace`. ## Out of scope - `.modules.yaml.skipped` persistence (umbrella slice 3). - Current-lockfile diffing for `removeOptionalDependenciesThatAreNotUsed` (umbrella slice 6). - `--no-optional` plumbing (umbrella slice 5). Closes #453.
Address PR #445 review feedback after the rebase pulled in #439 (installability check / `SkippedSnapshots`) + #443 (workspace install). - **Honor `SkippedSnapshots` in the hoist pass** (Copilot, real bug). `install_frozen_lockfile.rs` was passing `HashSet::new()` to `HoistInputs.skipped` even though `compute_skipped_snapshots` already produces the optional+platform-incompatible skip set earlier in the same function. Effect of the fix: hoist no longer creates symlinks to slots that were never extracted, and an alias that would have been claimed by a skipped snapshot can now be claimed by a non-skipped sibling at a deeper level. The `HoistInputs` shape stays `&HashSet<PackageKey>` so `hoist.rs` doesn't depend on `installability`; the cheap conversion happens at the call site. - **Refresh `build_direct_deps_by_importer` doc comment** (Copilot). Doc said "currently only installs the root importer" — wrong since #443 (workspace install) and the follow-up that switched the call site to pass the full `importers` map. Updated to describe the current per-importer walk and the `link:` filter inside the loop. - **Refresh `skipped_optional_deps` known-failure reason** (Copilot). The reason claimed pacquet doesn't compute skip sets — wrong since #439. The actual gap is the test fixture: upstream's `@pnpm.e2e/not-compatible-with-any-os` isn't in pacquet's mocked registry, so the end-to-end skip-then-hoist path can't be exercised yet. Reason updated to point at the fixture gap, not the (already-implemented) skip computation. Tests: `just ready` 902/902 pass; `cargo doc --workspace --no-deps` with `RUSTDOCFLAGS="-D warnings"` clean; `just dylint` clean. The 36 CLI hoist tests + 12 unit tests still pass with the real skip set threaded through. Out of scope: Copilot's comment about `satisfies_package_manifest` only validating the root importer is about main's #447 freshness check (not this PR's hoist work). Worth a follow-up issue but not addressed here.
Pull in 28 commits from upstream main, including the `pacquet-npmrc` → `pacquet-config` rename (#420) plus features: - supportedArchitectures + --cpu/--os/--libc (#456) - frozen-lockfile (#442, #443, #447, #450) - git-fetcher (#436 / #446, #451, #454) - side-effects cache (#421 / #422, #423, #424) - real-hoist + global-virtual-store (#432 / #438 / #444, #449, #452) - patchedDependencies + allow-builds (#425, #427, #428) - engine/platform installability (#434 / #439) Conflicts resolved: - `crates/npmrc/` files migrated under the renamed `crates/config/` directory; `Npmrc` → `Config` everywhere except `NpmrcAuth` (which keeps the `.npmrc`-domain name). - `Config::current` reads the env-var DI generic `Api: EnvVar` for ${VAR}-substitution in `.npmrc`. Production turbofish in `cli_args.rs` is `Config::current::<RealApi, _, _, _, _>(...)`. - Two-phase `NpmrcAuth::apply_*` retained so default-registry creds key at the yaml-resolved registry URL. - New `Config::auth_headers` field plumbed through `install_package_by_snapshot`'s `DownloadTarballToStore`. - Tests under `crates/config/src/workspace_yaml/tests.rs` pick up the new ParseYaml unit test added on this branch.
… slice 3) (#467) ## Summary Slice 3 of the [#434 umbrella](#434) (`Proper optionalDependencies support`). Slice 1 (#439) computed a `SkippedSnapshots` set on every frozen-lockfile install but never serialized it; slice 2 (#456) closed the input side. This PR closes the loop by mirroring three upstream sites pinned at [pnpm/pnpm@94240bc046](https://github.com/pnpm/pnpm/tree/94240bc046): - **Write** — `Modules.skipped` was declared at [`crates/modules-yaml/src/lib.rs:197`](https://github.com/pnpm/pacquet/blob/b13b8180/crates/modules-yaml/src/lib.rs#L197) with sort-on-write but always serialized empty. `build_modules_manifest` now takes `&SkippedSnapshots` and produces the depPath string list pnpm writes at [`installing/deps-installer/src/install/index.ts:1625`](https://github.com/pnpm/pnpm/blob/94240bc046/installing/deps-installer/src/install/index.ts#L1625). - **Seed** — `install_frozen_lockfile::run` reads `.modules.yaml.skipped` before computing the in-memory set and passes the parsed `PackageKey`s as the seed to `compute_skipped_snapshots`. Mirrors upstream's load at [`installing/read-projects-context/src/index.ts:79`](https://github.com/pnpm/pnpm/blob/94240bc046/installing/read-projects-context/src/index.ts#L79) plus the early return at [`deps/graph-builder/src/lockfileToDepGraph.ts:194`](https://github.com/pnpm/pnpm/blob/94240bc046/deps/graph-builder/src/lockfileToDepGraph.ts#L194). - **Short-circuit** — `compute_skipped_snapshots` returns the seed verbatim on the fast path (no constraints in the lockfile) and, on the slow path, skips the per-snapshot re-check for keys already in the seed without emitting a duplicate `pnpm:skipped-optional-dependency` event. Non-seeded snapshots still re-run `package_is_installable`, covering the \"host arch changed since last install\" case upstream guards at [`:206-215`](https://github.com/pnpm/pnpm/blob/94240bc046/deps/graph-builder/src/lockfileToDepGraph.ts#L206-L215). `InstallFrozenLockfile::run` now returns a small `InstallFrozenLockfileOutput { hoisted_dependencies, skipped }` struct so `Install::run` can thread both into `.modules.yaml`. Read errors on `.modules.yaml` degrade to an empty seed — the file is a cache artifact, not authoritative.
…e payload (#434 slice 4) (#474) Slice 4 of the [#434 umbrella](#434) (`Proper optionalDependencies support`). Slices 1–3 (#439, #456, #467) close the installability-driven skip path; this slice adds the second skip pathway upstream handles in the frozen install: an `optional: true` snapshot whose **fetch / extract** step fails at install time must not abort the install. Local-materialization errors (`CreateVirtualDir`) and config-shape errors still abort even for optional snapshots — matching upstream's `linkPkg` path which sits outside the catch. Mirrors the silent catch sites at [`deps/graph-builder/src/lockfileToDepGraph.ts:294-298`](https://github.com/pnpm/pnpm/blob/94240bc046/deps/graph-builder/src/lockfileToDepGraph.ts#L294-L298) and [`installing/deps-restorer/src/index.ts:912-921`](https://github.com/pnpm/pnpm/blob/94240bc046/installing/deps-restorer/src/index.ts#L912-L921): ```ts try { ... fetch ... } catch (err) { if (pkgSnapshot.optional) return; throw err } ``` ## Changes - **Reporter**: `SkippedOptionalPackage` becomes a `#[serde(untagged)]` enum with `Installed { id, name, version }` and `ResolutionFailure { name?, version?, bareSpecifier }` variants. Models upstream's discriminated union at [`core-loggers/src/skippedOptionalDependencyLogger.ts:10-31`](https://github.com/pnpm/pnpm/blob/94240bc046/core/core-loggers/src/skippedOptionalDependencyLogger.ts#L10-L31). The new variant is wire-shape-only in slice 4 — pacquet has no resolver yet, but a future resolver port at the upstream emit site ([`resolveDependencies.ts:1376-1383`](https://github.com/pnpm/pnpm/blob/94240bc046/installing/deps-resolver/src/resolveDependencies.ts#L1376-L1383)) lands without re-touching this type. - **`SkippedSnapshots`**: gains a `fetch_failed` subset disjoint from the existing `installability` subset. `contains` / `iter` return the union (downstream consumers treat both as absent); `iter_installability` returns only the persistent subset that `.modules.yaml.skipped` records, matching upstream's behavior of never updating `opts.skipped` at the fetch-failure catch sites. - **`CreateVirtualStore`**: cold-batch dispatch swallows per-snapshot errors when `snapshot.optional` is true **and** the error is fetch-side. A new `is_fetch_side_failure` helper restricts the swallow to `InstallPackageBySnapshotError::DownloadTarball` and `::GitFetch` — the same surface upstream wraps in `storeController.fetchPackage`. Local-materialization (`CreateVirtualDir`) and config-shape errors (`MissingTarballIntegrity` / `UnsupportedResolution`) propagate even for optional snapshots, matching upstream's `linkPkg` path which sits outside the catch. Side benefit: the warm-batch path (which only surfaces `CreateVirtualDir` errors) stays consistently abort-on-error without a parallel swallow site. The per-install `fetch_failed: HashSet<PackageKey>` rides out on `CreateVirtualStoreOutput`. - **`InstallFrozenLockfile::run`**: folds the returned `fetch_failed` into its mutable `SkippedSnapshots` so downstream phases (`SymlinkDirectDependencies`, `LinkVirtualStoreBins`, `BuildModules`, hoist) treat the dropped snapshots as absent through the same gate they already use for installability skips. ## Test plan - [x] `reporter::tests::skipped_optional_resolution_failure_event_matches_pnpm_wire_shape` — full payload (`bareSpecifier` camelCase, no `id`). - [x] `reporter::tests::skipped_optional_resolution_failure_omits_absent_name_and_version` — optional-field shape. - [x] `install::tests::frozen_install_silently_swallows_unreachable_optional_tarball` — ports [`installing/deps-restorer/test/index.ts:340-360`](https://github.com/pnpm/pnpm/blob/94240bc046/installing/deps-restorer/test/index.ts#L340-L360): unreachable optional tarball (`http://127.0.0.1:1/...`), install succeeds, slot not created, `.modules.yaml.skipped` left empty. **Test-the-test verified** by inverting the `optional` guard in the cold-batch match arm. Runs with `enable_global_virtual_store = false` so the slot-path assertion targets the legacy layout. - [x] `install::tests::frozen_install_propagates_non_optional_fetch_failure` — pins the polarity: same fixture, non-optional snapshot, install must error. - [x] `just ready` (typos + fmt + check + nextest + clippy). - [x] `taplo format --check`. - [x] `just dylint` (perfectionist). - [x] `RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --workspace`. ## Out of scope - Resolver-side `resolution_failure` emit site — needs a resolver, tracked separately. - `--no-optional` / `--omit=optional` plumbing — umbrella slice 5. - Surfacing fetch failures to the reporter on the frozen path — upstream itself is silent here; only the resolver-side emit fires `pnpm:skipped-optional-dependency`. Closes #471.
…#485) Slice 5 of the [#434 umbrella](#434) (`Proper optionalDependencies support`). Slices 1–4 (#439, #456, #467, #474) handle installability + the fetch-failure swallow. This slice closes the user-facing `--no-optional` flag: the CLI flag already exists and threads through `Install::dependency_groups`, but only `SymlinkDirectDependencies` and the on-disk `included` field actually honored it. The rest of the install pipeline walked `optional_dependencies` unconditionally, so the optional subtree was still extracted, linked, and built. Mirrors upstream's depNode filter at [`installing/deps-installer/src/install/link.ts:109-111`](https://github.com/pnpm/pnpm/blob/94240bc046/installing/deps-installer/src/install/link.ts#L109-L111): ```ts if (!opts.include.optionalDependencies) { depNodes = depNodes.filter(({ optional }) => !optional) } ``` ## Changes - **`SkippedSnapshots`**: gains a third disjoint subset `optional_excluded` alongside `installability` (slice 1) and `fetch_failed` (slice 4). The existing `contains` / `iter` already return the union, so every downstream consumer that filters by skip-set (`CreateVirtualStore`, `SymlinkDirectDependencies`, `BuildModules`, hoist, `link_bins`) now respects `--no-optional` through the same gate. `iter_installability` still returns only the persistent subset for `.modules.yaml.skipped` writes — `--no-optional` exclusions are transient (matching upstream's behavior of never updating `opts.skipped` at the filter site). - **`InstallFrozenLockfile::run`**: iterates the lockfile snapshots once and inserts every `snap.optional == true` entry into the new subset when the dispatch list doesn't contain `DependencyGroup::Optional`. The `SnapshotEntry::optional` flag is set by the resolver only when the snapshot is reachable **exclusively** through optional edges, so a snapshot reachable through any non-optional edge carries `optional: false` and survives the filter — covers [`optionalDependencies.ts:712`](https://github.com/pnpm/pnpm/blob/94240bc046/installing/deps-installer/test/install/optionalDependencies.ts#L712) (`dependency that is both optional and non-optional is installed`).
Summary
Slice 1 of #434 — foundational installability gate. Ports
@pnpm/config.package-is-installableand threads the resulting skip-set through every phase of the
frozen-lockfile install pipeline.
Closes #266 once shipped (covers the "install respects every snapshot
— no os/cpu/libc filter" gap). Does not close #434 — that umbrella
has six more slices to follow.
Upstream reference: pnpm/pnpm@94240bc046.
What landed
New crate:
pacquet-package-is-installablePorts the upstream
config/package-is-installablepackage's threehelpers:
check_platform(Option<&[String]>for eachos/cpu/libcaxis, plus a
SupportedArchitecturesoverride) — returnsOption<UnsupportedPlatformError>matching upstream'sERR_PNPM_UNSUPPORTED_PLATFORMcode, message shape, and JSONpayload. Handles negation entries (
!foo), theanysentinel,the
currentplaceholder, and thecurrentLibc !== 'unknown'skip from
checkPlatform.ts:38.check_engine— evaluatesengines.node/engines.pnpmvianode-semver. Approximates npm-semver'sincludePrerelease: truevia a strip-prerelease fallback; one over-acceptance edge case
(
>=X.Y.ZagainstX.Y.Z-rc1) is pinned in the known-failuresintegration test for follow-up.
package_is_installable— composes the two, returns thetri-state verdict matching upstream's
boolean | null(Installable/ SkipOptional / ProceedWithWarning), plus an
Errarm forengine_strictaborts andERR_PNPM_INVALID_NODE_VERSION.InstallabilityOptions<'a>borrows its host strings so a callerrunning through many snapshots in a row can build the host part
once and only toggle
optionalper snapshot.WantedPlatformRef<'a>plays the same role for the manifest axes so
check_platformrunsthe happy path without any allocation.
New module:
pacquet_package_manager::installabilitycompute_skipped_snapshotsis the per-install entry point. For eachsnapshot:
PackageMetadata.check_package(cached per peer-strippedmetadata_keysopeer-resolved variants of the same package share one verdict).
(verdict, snapshot.optional, host.engine_strict):Installable: nothing to do.SkipOptional+optional: add toSkippedSnapshots, emitpnpm:skipped-optional-dependency(deduped per metadata key,matching upstream's emit-per-pkgId).
Incompatible+ non-optional +engine_strict: abort.Incompatible+ non-optional + non-strict:tracing::warn!and proceed. (Upstream's
pnpm:install-checkchannel isn'twired into pacquet's reporter yet — slice 1 follow-up.)
any_installability_constraint(packages)is the caller-side fastpath: if no metadata row declares an
engines.{node,pnpm}or anon-empty / non-
["any"]cpu/os/libc, the entire installabilitypass is skipped. The probe runs synchronously in
install_frozen_lockfilebefore thetokio::task::spawn_blockingthat would invoke
node --version— so the common no-constraintslockfile pays nothing for the new pipeline, restoring main's overlap
of node-binary startup with extraction.
Install-pipeline plumbing
The
SkippedSnapshotsset is threaded into every downstream phaseof
InstallFrozenLockfile::run:CreateVirtualStore: installability-skipped snapshots aredropped from both
survivors(no virtual-store slot extracted)and
skipped_entries(no warm-cache row). Layered ahead ofmain's feat: partial install with --frozen-lockfile (#433) #442 already-installed-and-on-disk skip filter.
SymlinkDirectDependencies: a direct dep whose resolvedsnapshot is in the skip set is omitted from
node_modules/<name>(no symlink, no
pnpm:root addedevent, no bin link).LinkVirtualStoreBins: per-slot bin link skips slots whosesnapshot is installability-skipped (their virtual-store
directories don't exist).
BuildModulesviabuild_sequence:get_subgraph_to_buildconsults
skippedbefore recursion, so a skipped snapshot'ssubtree doesn't contribute to the build graph via that edge.
Descendants reachable from a non-skipped root still build
normally.
Performance
CI integrated-benchmark on the 1352-package fixture, latest run:
pacquet@HEADpacquet@mainEarlier iterations of this PR showed a ~5% cold-install regression
from the
node --versionspawn landing on the extraction criticalpath. Closed by hoisting the no-constraints fast-path probe to the
caller (commit
cf47ce51) so the spawn is gated on actualconstraint presence.
Other perf passes folded in:
compute_skipped_snapshotscaches the per-metadata-row checkverdict so peer-resolved variants share one
check_packagecall.check_platformborrows its three wanted axes throughWantedPlatformRef<'a>; the ownedWantedPlatformonlymaterialises in the error path.
Tests
pacquet-package-is-installable::tests::check_platformcheckPlatform.ts—any/currentsentinels, negation,supportedArchitecturesoverride, libc unknown-skippacquet-package-is-installable::tests::check_enginecheckEngine.ts— node/pnpm range checks, prerelease cases,ERR_PNPM_INVALID_NODE_VERSIONpacquet-package-is-installable::tests::package_is_installablepacquet-package-is-installable::tests::known_failures>=X.Y.ZvsX.Y.Z-rc1over-acceptance, picked up byjust known-failurespacquet_package_manager::installability::testsengines.npmonly,["any"]sentinel, empty lists)pacquet_package_manager::build_sequence::testsAll ported tests verified to catch regressions by temporarily
breaking the subject under test, observing the failure, then
reverting. The "test the tests" workflow from CLAUDE.md.
Deferred to follow-up slices
.modules.yaml.skippedwrite/read + headless re-check(slice 3).
supportedArchitecturesconfig +--cpu/--os/--libcCLI flags (slice 2).
pnpm:install-checkwarn channel on the reporter side(currently
tracing::warn!).host_libc()returns"unknown"today;matches non-Linux host behavior, but on Alpine/musl this
over-installs glibc-only optional packages. Slice 2.
engine_strictconfig wiring — defaults to false today, sothe error path is unreachable from production. Wired through
end-to-end so the slice that flips the config doesn't churn
the error enum.
Test plan
just readyclean (typos, fmt, check, test, lint)taplo format --checkcleanjust dylintcleanRUSTDOCFLAGS="-D warnings" cargo doccleancargo nextest run— 752 tests passCompare Benchmarksgate passes; no cold-installregression on the 1352-package fixture
code under test
Cross-references
of 7)
plans/TEST_PORTING.mdenumerates the~30 upstream
optionalDependencies.tstests this umbrella willeventually own. Slice 1's
check_platform/check_engineports are landed; the install-pipeline integration tests are
scoped to follow-up slices that need
.modules.yaml.skippedand the current-lockfile path.
Written by an agent (Claude Code, claude-opus-4-7).