fix(pacquet): run dependency build scripts on the fresh-lockfile path#12436
Conversation
`pacquet add <pkg>` (and any non-frozen `install`) took the fresh-lockfile path, which never invoked `BuildModules`. So approved dependency lifecycle scripts never ran, and — more visibly — blocked build scripts were silently ignored with no "Ignored build scripts: …" warning, unlike `pnpm add`. Extract the build tail shared by both install modes into `run_build_phase` (`BuildModules` + `pnpm:ignored-scripts` emit + post-build top-level bin link) plus `BuildPhaseInputs` / `BuildPhaseError`, and call it from the frozen path (refactor) and the fresh path (new). On the fresh path: capture `side_effects_maps_by_snapshot` / `requires_build_by_snapshot` from `CreateVirtualStore`, surface `hoisted_pkg_root_by_key` and the public-hoist alias list, move `importing_done` + the store-index writer drain + the lockfile save to after the build, and defer the `node --version` engine probe so it overlaps `CreateVirtualStore` I/O (except under the global virtual store, whose layout needs it synchronously). Enable the `lifecycle_scripts.rs` dependency-build tests that were stubbed as `known_failures`. They placed `allowBuilds` in `package.json#pnpm`, which pnpm v11 and pacquet no longer read — moved to `pnpm-workspace.yaml`. Add a regression test that `pacquet add` reports the ignored build script. One `known_failure` remains: a warm `--frozen-lockfile` reinstall does not yet detect an `allowBuilds` policy change (a deps-status / workspace-state gap, unrelated to this fix).
Code Review by Qodo
1.
|
|
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:
📝 WalkthroughWalkthroughImplements pnpm's ChangesStrict Dependency Builds Feature
Sequence Diagram(s)sequenceDiagram
actor User
participant Install as Install::run
participant HNAI as has_newly_allowed_ignored_builds
participant BuildPhase as run_build_phase
participant Modules as .modules.yaml
User->>Install: pacquet install / pacquet add
Install->>HNAI: read Modules, check allowBuilds policy
HNAI-->>Install: fast-path bypassed if newly allowed
Install->>BuildPhase: run lifecycle scripts (frozen or fresh path)
BuildPhase-->>Install: ignored_builds Vec~String~
Install->>Modules: build_modules_manifest(ignored_builds)
alt strict_dep_builds=true and ignored_builds non-empty
Install-->>User: ERR_PNPM_IGNORED_BUILDS
else
Install-->>User: success (optional warning)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
PR Summary by Qodopacquet: run dependency build phase for fresh-lockfile installs WalkthroughsDescription• Run lifecycle-script build phase for fresh-lockfile installs and pacquet add, matching pnpm. • Share build-phase tail across install modes, preserving ERR_PNPM_* error codes. • Enable lifecycle-script integration tests and add regression for ignored build warnings. Diagramgraph TD
cli(["pacquet CLI"]) --> fresh["Fresh install"] --> cvs1["Create store"] --> build["Build phase"] --> bm["BuildModules"] --> post["Report + relink"]
cli --> frozen["Frozen install"] --> cvs2["Create store"] --> build
build -- "fresh only" --> lock[("Write lockfile")]
subgraph Legend
direction LR
_cli(["Entry"]) ~~~ _proc["Process"] ~~~ _db[("Artifact")]
end
High-Level AssessmentThe following are alternative approaches to this PR: 1. Inline build phase in fresh path
2. Create a new shared install orchestrator module
3. Always run synchronous engine probe
Recommendation: The chosen approach (extracting a shared File ChangesBug fix (1)
Refactor (1)
Tests (1)
|
Micro-Benchmark ResultsLinux |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #12436 +/- ##
==========================================
+ Coverage 87.87% 88.00% +0.13%
==========================================
Files 308 308
Lines 41210 41393 +183
==========================================
+ Hits 36212 36429 +217
+ Misses 4998 4964 -34 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@pacquet/crates/cli/tests/lifecycle_scripts.rs`:
- Line 521: Remove the allow_known_failure! macro wrapper from the
warm_reinstall_detects_allow_builds_change() test call to expose the underlying
regression. The test failure will reveal a deps-status and workspace-state
invalidation issue where a warm --frozen-lockfile reinstall fails to react to an
allowBuilds policy change. Fix the invalidation logic to properly detect policy
changes so that the test passes normally without the known-failure gate,
ensuring users get correct behavior when approving previously blocked
dependencies.
In `@pacquet/crates/package-manager/src/install_with_fresh_lockfile.rs`:
- Around line 1455-1463: The CreateVirtualStoreOutput destructuring in the
fresh-path install flow discards fetch_failed by binding it to an underscore,
whereas the frozen path folds optional fetch failures into SkippedSnapshots
before linking and building. Capture the fetch_failed result instead of
discarding it, and fold those snapshots into SkippedSnapshots before the
symlink, bin link, and BuildModules phases to prevent dangling links or build
attempts on missing snapshots. Apply this same fix pattern at the sibling
locations also mentioned (lines 1507-1509) to ensure the fresh and frozen paths
stay behaviorally synchronized.
- Around line 1388-1415: The current code detects the host Node version directly
via pacquet_graph_hasher::detect_node_major() in the detect_engine closure, but
the frozen path first checks for a runtime-pinned Node snapshot before falling
back to the host version. This creates a mismatch where fresh installs may
materialize GVS or side-effects-cache entries under the host Node key while
frozen installs use the pinned key, violating pnpm parity. Modify the
detect_engine closure to first check for a runtime-pinned Node (using the
existing frozen-path helper) and derive engine_name from that if present; only
probe the host Node version if no pinned version exists. Apply this consistently
to the engine_name assignment logic so that both GVS and side-effects-cache
cases use the same priority order (pinned first, then host).
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 6a1dcf2a-5624-4362-a854-e67c32e20c7b
📒 Files selected for processing (3)
pacquet/crates/cli/tests/lifecycle_scripts.rspacquet/crates/package-manager/src/install_frozen_lockfile.rspacquet/crates/package-manager/src/install_with_fresh_lockfile.rs
Integrated-Benchmark Report (Linux)Each scenario reports direct installs and pnpr installs. Bencher consumes pacquet@HEAD and pnpr@HEAD. Scenario: Isolated linker: fresh restore, cold cache + cold store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 4.128846565839999,
"stddev": 0.1892519916995314,
"median": 4.034592502540001,
"user": 3.8456576999999994,
"system": 3.5370765,
"min": 3.97650178904,
"max": 4.432853678040001,
"times": [
4.426610596040001,
4.432853678040001,
4.006685919040001,
4.02450838604,
4.33298937104,
3.98181101904,
4.0446766190400005,
3.97650178904,
4.06755195004,
3.99427633104
]
},
{
"command": "pacquet@main",
"mean": 4.1759889470400005,
"stddev": 0.12567972830596547,
"median": 4.17180777104,
"user": 3.9156935999999996,
"system": 3.5358456999999994,
"min": 4.00956215204,
"max": 4.37872657904,
"times": [
4.00956215204,
4.06592009704,
4.11427046304,
4.3763098320400005,
4.20635039504,
4.13940508704,
4.2049474380400005,
4.06018697204,
4.20421045504,
4.37872657904
]
},
{
"command": "pnpr@HEAD",
"mean": 2.20185612864,
"stddev": 0.15778136335444845,
"median": 2.26526966854,
"user": 2.5880654,
"system": 2.9939004000000002,
"min": 1.9197403920400002,
"max": 2.42501641904,
"times": [
2.2556281450399998,
1.9197403920400002,
2.29806696004,
2.27491119204,
2.24070170804,
2.27621060904,
2.28091001904,
2.0094590480399996,
2.03791679404,
2.42501641904
]
},
{
"command": "pnpr@main",
"mean": 2.15467592544,
"stddev": 0.12280376184505001,
"median": 2.13640014954,
"user": 2.6718437999999995,
"system": 2.9618578999999996,
"min": 2.0081639710399997,
"max": 2.29692121604,
"times": [
2.0489911190399996,
2.25598626104,
2.2007262860399996,
2.29257307004,
2.03698433204,
2.0081639710399997,
2.29692121604,
2.07207401304,
2.2906027010399996,
2.04373628504
]
}
]
}Scenario: Isolated linker: fresh restore, hot cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 0.62733460576,
"stddev": 0.012188480349408878,
"median": 0.6230127786600002,
"user": 0.36296644,
"system": 1.3450928999999998,
"min": 0.6144023591600001,
"max": 0.65457373616,
"times": [
0.61954152516,
0.65457373616,
0.63772868916,
0.6340625501600001,
0.62968048216,
0.6199436171600001,
0.61738754116,
0.6259584211600001,
0.6144023591600001,
0.6200671361600001
]
},
{
"command": "pacquet@main",
"mean": 0.6422389469600001,
"stddev": 0.016538684150652277,
"median": 0.6418975146600001,
"user": 0.37219424,
"system": 1.3490235,
"min": 0.6266990961600001,
"max": 0.6839891021600001,
"times": [
0.6311077261600001,
0.6266990961600001,
0.64349987616,
0.6475460551600001,
0.63377543416,
0.62677843716,
0.6446550241600001,
0.6402951531600001,
0.64404356516,
0.6839891021600001
]
},
{
"command": "pnpr@HEAD",
"mean": 0.6760774006600002,
"stddev": 0.013822269902968835,
"median": 0.6756643281600001,
"user": 0.37140853999999995,
"system": 1.3735849999999998,
"min": 0.6487923911600001,
"max": 0.6985042891600001,
"times": [
0.6985042891600001,
0.6487923911600001,
0.6815957921600001,
0.69005502916,
0.6753671691600001,
0.66264742216,
0.67596148716,
0.6744640041600001,
0.68226047316,
0.6711259491600001
]
},
{
"command": "pnpr@main",
"mean": 0.6756268324600001,
"stddev": 0.014436245486357704,
"median": 0.6746677026600001,
"user": 0.38651264,
"system": 1.3513084999999998,
"min": 0.6598656881600001,
"max": 0.7110467701600001,
"times": [
0.67867986816,
0.6598656881600001,
0.6627765451600001,
0.66258289016,
0.67997911716,
0.67416311216,
0.7110467701600001,
0.6778389281600001,
0.6750195331600001,
0.6743158721600001
]
}
]
}Scenario: Isolated linker: fresh install, cold cache + cold store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 4.23535109504,
"stddev": 0.05264952114282649,
"median": 4.25636112804,
"user": 3.7171297999999995,
"system": 3.4006093999999996,
"min": 4.12342524554,
"max": 4.28293845554,
"times": [
4.12342524554,
4.18429635154,
4.20046544854,
4.27324732854,
4.22180268054,
4.28293845554,
4.26806703054,
4.24465522554,
4.28149527154,
4.27311791254
]
},
{
"command": "pacquet@main",
"mean": 4.253436003740001,
"stddev": 0.0438646828125624,
"median": 4.25433366654,
"user": 3.7853761999999995,
"system": 3.4402128999999997,
"min": 4.18437296454,
"max": 4.34327830554,
"times": [
4.24699836854,
4.34327830554,
4.18437296454,
4.22735210254,
4.23608098454,
4.28127421254,
4.20980385154,
4.26166896454,
4.26762905254,
4.27590123054
]
},
{
"command": "pnpr@HEAD",
"mean": 2.20635801464,
"stddev": 0.09794325266736699,
"median": 2.20660064754,
"user": 2.4674521,
"system": 2.9879644999999995,
"min": 2.06808822454,
"max": 2.39800240854,
"times": [
2.22598323254,
2.2189668185399998,
2.11721036954,
2.12171182154,
2.39800240854,
2.06808822454,
2.19423447654,
2.3054954945399997,
2.15766129854,
2.25622600154
]
},
{
"command": "pnpr@main",
"mean": 2.24105876944,
"stddev": 0.14444427475106458,
"median": 2.3101796220399997,
"user": 2.546578099999999,
"system": 2.9728608999999997,
"min": 2.0262779485399998,
"max": 2.43131189554,
"times": [
2.0605628075399998,
2.30679281354,
2.31356643054,
2.08733595054,
2.15390235354,
2.0262779485399998,
2.43131189554,
2.34880789054,
2.32562113554,
2.3564084685399997
]
}
]
}Scenario: Isolated linker: fresh install, hot cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 1.37981285872,
"stddev": 0.022936745267379804,
"median": 1.38840563782,
"user": 1.36840678,
"system": 1.75888742,
"min": 1.32261547082,
"max": 1.39865139082,
"times": [
1.39124164382,
1.37249897782,
1.39744155582,
1.38306139582,
1.38937657382,
1.39865139082,
1.3874347018200002,
1.36296800682,
1.32261547082,
1.39283886982
]
},
{
"command": "pacquet@main",
"mean": 1.38657035832,
"stddev": 0.04836572018991289,
"median": 1.3786503673200001,
"user": 1.3592317799999998,
"system": 1.77700582,
"min": 1.34905564782,
"max": 1.51724910782,
"times": [
1.3546694378200002,
1.34905564782,
1.51724910782,
1.3620160808200001,
1.35961039182,
1.37540352982,
1.38570204382,
1.39504850382,
1.38189720482,
1.38505163482
]
},
{
"command": "pnpr@HEAD",
"mean": 0.66623077852,
"stddev": 0.0160145789007884,
"median": 0.6622220373200001,
"user": 0.33303807999999996,
"system": 1.3069930199999997,
"min": 0.6447097208200001,
"max": 0.7021222138200001,
"times": [
0.6613171078200001,
0.7021222138200001,
0.66581744482,
0.66312696682,
0.67196824982,
0.68125422682,
0.6580052608200001,
0.6533907228200001,
0.66059587082,
0.6447097208200001
]
},
{
"command": "pnpr@main",
"mean": 0.65500832622,
"stddev": 0.012659447681571019,
"median": 0.65297461032,
"user": 0.33728257999999994,
"system": 1.30387372,
"min": 0.6355342888200001,
"max": 0.6754707318200001,
"times": [
0.65154464982,
0.67274528682,
0.6641287238200001,
0.6516403488200001,
0.64875266982,
0.6355342888200001,
0.6543088718200001,
0.6414659758200001,
0.65449171482,
0.6754707318200001
]
}
]
}Scenario: Isolated linker: fresh install, cold cache + hot store
BENCHMARK_REPORT.json{
"results": [
{
"command": "pacquet@HEAD",
"mean": 3.0652881574,
"stddev": 0.04481049921486099,
"median": 3.049316383,
"user": 1.82405754,
"system": 2.0057017200000002,
"min": 3.0333767320000002,
"max": 3.176861226,
"times": [
3.033887099,
3.0333767320000002,
3.062080243,
3.0400730780000003,
3.040817752,
3.039184078,
3.063100614,
3.057815014,
3.176861226,
3.105685738
]
},
{
"command": "pacquet@main",
"mean": 3.0570357868999998,
"stddev": 0.04168967933701448,
"median": 3.0476858565000002,
"user": 1.79118924,
"system": 2.0447553199999993,
"min": 3.01822018,
"max": 3.157880955,
"times": [
3.02118187,
3.060319893,
3.0341420980000002,
3.0763600490000003,
3.0262425470000003,
3.01822018,
3.080638564,
3.054463433,
3.157880955,
3.04090828
]
},
{
"command": "pnpr@HEAD",
"mean": 0.6619190423000001,
"stddev": 0.00840302153285142,
"median": 0.6610784435,
"user": 0.34360073999999996,
"system": 1.28757552,
"min": 0.648723462,
"max": 0.6736265410000001,
"times": [
0.66096392,
0.652105606,
0.665029267,
0.661126596,
0.648723462,
0.6551327490000001,
0.673448339,
0.668003652,
0.6736265410000001,
0.661030291
]
},
{
"command": "pnpr@main",
"mean": 0.652439516,
"stddev": 0.009929338478055326,
"median": 0.6529316390000001,
"user": 0.34298043999999994,
"system": 1.29109732,
"min": 0.6408022800000001,
"max": 0.670585147,
"times": [
0.657028059,
0.656033806,
0.670585147,
0.644526679,
0.663914981,
0.6542787090000001,
0.64443259,
0.6515845690000001,
0.6408022800000001,
0.64120834
]
}
]
} |
|
| Branch | pr/12436 |
| Testbed | pacquet |
Click to view all benchmark results
| Benchmark | Latency | Benchmark Result milliseconds (ms) (Result Δ%) | Upper Boundary milliseconds (ms) (Limit %) |
|---|---|---|---|
| isolated-linker.fresh-install.cold-cache.cold-store | 📈 view plot 🚷 view threshold | 4,235.35 ms(+2.03%)Baseline: 4,151.08 ms | 4,981.30 ms (85.03%) |
| isolated-linker.fresh-install.cold-cache.hot-store | 📈 view plot 🚷 view threshold | 3,065.29 ms(+2.87%)Baseline: 2,979.70 ms | 3,575.64 ms (85.73%) |
| isolated-linker.fresh-install.hot-cache.hot-store | 📈 view plot 🚷 view threshold | 1,379.81 ms(+6.18%)Baseline: 1,299.53 ms | 1,559.43 ms (88.48%) |
| isolated-linker.fresh-restore.cold-cache.cold-store | 📈 view plot 🚷 view threshold | 4,128.85 ms(+5.12%)Baseline: 3,927.58 ms | 4,713.09 ms (87.60%) |
| isolated-linker.fresh-restore.hot-cache.hot-store | 📈 view plot 🚷 view threshold | 627.33 ms(+1.54%)Baseline: 617.79 ms | 741.35 ms (84.62%) |
|
| Branch | pr/12436 |
| Testbed | pnpr |
⚠️ WARNING: No Threshold found!Without a Threshold, no Alerts will ever be generated.
Click here to create a new Threshold
For more information, see the Threshold documentation.
To only post results if a Threshold exists, set the--ci-only-thresholdsflag.
Click to view all benchmark results
| Benchmark | Latency | milliseconds (ms) |
|---|---|---|
| isolated-linker.fresh-install.cold-cache.cold-store | 📈 view plot | 2,206.36 ms |
| isolated-linker.fresh-install.cold-cache.hot-store | 📈 view plot | 661.92 ms |
| isolated-linker.fresh-install.hot-cache.hot-store | 📈 view plot | 666.23 ms |
| isolated-linker.fresh-restore.cold-cache.cold-store | 📈 view plot | 2,201.86 ms |
| isolated-linker.fresh-restore.hot-cache.hot-store | 📈 view plot | 676.08 ms |
Implements the `strictDepBuilds` setting (default `true`, matching pnpm) so an install that blocks any dependency build script fails with `ERR_PNPM_IGNORED_BUILDS` instead of only printing a warning and exiting zero. This makes `pacquet add esbuild` behave like `pnpm add esbuild`: the dependency is added and materialized, then the command exits non-zero telling the user to approve the build. - config: add `strict_dep_builds` (default `true`), read from `pnpm-workspace.yaml` (`strictDepBuilds`) and the env overlay; move it from the not-yet-implemented parity list into the asserted defaults. - package-manager: `run_build_phase` returns the ignored-build list, threaded out of both install paths; `Install::run` raises `ERR_PNPM_IGNORED_BUILDS` after the artifacts are written when `strictDepBuilds` is on. The `pnpm:ignored-scripts` event is always emitted (empty list when nothing is ignored), mirroring pnpm. - default-reporter: suppress the "Ignored build scripts" warning box under strict mode (the error replaces it), mirroring pnpm's `reportIgnoredBuilds` gate; the CLI seeds the flag from config. Tests: `pacquet add` now fails under the strict default and warns (exit zero) with `strictDepBuilds: false`. The selective-allow / ignore tests that inspect a completed install set `strictDepBuilds: false`; the optional-failing-postinstall unit test now allows the build so the failure path is actually exercised.
|
Code review by qodo was updated up to the latest commit 572ac28 |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@pacquet/crates/cli/src/cli_args.rs`:
- Around line 271-276: The set_strict_dep_builds function is currently only
called in the install command path but not in the CliCommand::Dlx path, causing
inconsistent behavior between commands. Move the set_strict_dep_builds call to a
shared location in the config-loading path (likely within or immediately after
the config() function call) so that it applies to all command paths including
both install and dlx. This ensures the default reporter uses consistent strict
mode behavior across all commands and prevents non-strict warning behavior from
being used as a fallback in the dlx command.
In `@pacquet/crates/package-manager/src/install.rs`:
- Around line 254-270: The help text in the diagnostic attribute for the
IgnoredBuilds variant references the pnpm approve-builds command, which is not
implemented in pacquet (as noted in dlx.rs:38 where the interactive
approve-builds prompt is explicitly noted as not ported). Remove or replace the
reference to the approve-builds command in the help text of the IgnoredBuilds
diagnostic to accurately reflect what pacquet users can actually do when
encountering ignored build scripts.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 0e398a54-e40c-4736-aef0-d1b51058e044
📒 Files selected for processing (13)
pacquet/crates/cli/src/cli_args.rspacquet/crates/cli/tests/lifecycle_scripts.rspacquet/crates/config/src/env_overlay.rspacquet/crates/config/src/lib.rspacquet/crates/config/src/pnpm_default_parity.rspacquet/crates/config/src/workspace_yaml.rspacquet/crates/default-reporter/src/lib.rspacquet/crates/default-reporter/src/state.rspacquet/crates/package-manager/src/install.rspacquet/crates/package-manager/src/install/tests.rspacquet/crates/package-manager/src/install_frozen_lockfile.rspacquet/crates/package-manager/src/install_package_from_registry/tests.rspacquet/crates/package-manager/src/install_with_fresh_lockfile.rs
✅ Files skipped from review due to trivial changes (1)
- pacquet/crates/package-manager/src/install_package_from_registry/tests.rs
Resolves the PR review comments on the fresh-lockfile build phase: - Rebuild on `allowBuilds` changes (ports pnpm's `runUnignoredDependencyBuilds`). The install now records the ignored builds in `.modules.yaml`; the frozen no-op fast path no longer short-circuits when a recorded ignored build is now allowed, so a warm `--frozen-lockfile` reinstall rebuilds the newly-approved package instead of skipping as up-to-date. Removes the corresponding `known_failure` gate. - Single `node --version` probe. The fresh path detected the host once for the engine-name cache key and again (via `InstallabilityHost`) for the hoisted installability check; it now detects the host once and reuses it, mirroring the frozen path. - Honor a runtime-pinned Node. `engine_name` now prefers a `node@runtime:` pin from the lockfile before probing the host, so a fresh install keys GVS slots / side-effects-cache rows under the same major a later frozen install uses. Exposes the frozen-path `find_runtime_node_major` / `parse_major_from_version` helpers. - Fold swallowed optional-fetch failures into the skip set before the symlink / bin-link / build phases, matching the frozen path, so a fetch-failed snapshot can't leave a dangling link or a build attempt on a slot that was never extracted.
|
Code review by qodo was updated up to the latest commit f0be33b |
|
Addressed all four review findings in f0be33b:
Verified: Written by an agent (Claude Code, claude-opus-4-8). |
…r flag The default reporter's `strictDepBuilds`-suppression was seeded into a `OnceLock` from the CLI before `updateConfig` hooks ran, so a hook that changed `strict_dep_builds` left the reporter stale (warning shown/hidden inconsistently with the install's error/warning decision), and `dlx` never seeded it at all. Drop the reporter-side flag entirely and gate the emit in `run_build_phase`, which already holds the final, post-`updateConfig` `config` the strict-failure check reads. The empty-list event is still always emitted (pnpm's NDJSON contract); a non-empty list is suppressed under `strictDepBuilds` because the install fails with `ERR_PNPM_IGNORED_BUILDS` and the box would only duplicate the error. This keeps the warning and the failure decision consistent on every command path without per-path CLI seeding.
|
Code review by qodo was updated up to the latest commit f4b3d5c |
A strict install that blocked a build fails with `ERR_PNPM_IGNORED_BUILDS` but still writes `node_modules`, the lockfile, `.modules.yaml`, and the workspace-state first. A rerun then hit an up-to-date fast path and exited 0, letting the user bypass the gate. pnpm seeds `ignoredBuilds` from `.modules.yaml` on the up-to-date path and `handleIgnoredBuilds` still throws. Mirror that: all three short-circuits — the CLI pre-runtime fast path (`install_already_up_to_date`), `Install::run`'s optimistic-repeat branch, and the frozen no-op branch — now re-raise `ERR_PNPM_IGNORED_BUILDS` when `.modules.yaml` records an ignored build the current policy still leaves unapproved (`unapproved_recorded_ignored_builds`). Builds an `allowBuilds` change newly permits are excluded (they rebuild via the existing `runUnignoredDependencyBuilds` path) and explicitly-denied builds are excluded (silently skipped), matching a full install's `BuildModules`.
|
Code review by qodo was updated up to the latest commit 2d47350 |
Two performance findings from review: - Parse `.modules.yaml` once on the frozen up-to-date fast path. The consistency, newly-allowed, and (new) unapproved-ignored-builds checks each re-read and re-parsed the manifest; they now share a single parse via `modules_consistent_with` / `&Modules`-taking helpers. - Don't re-hash patch files in the build phase. `run_build_phase` resolved `patchedDependencies` again via `resolve_snapshot_patches`, even on the fresh path that already resolved the grouped record to feed the resolver. `BuildPhaseInputs` now carries an optional pre-resolved `PatchGroupRecord`; the fresh path passes its record and the frozen path resolves once internally as before.
|
Code review by qodo was updated up to the latest commit 3a11ef7 |
Only the frozen-lockfile fast path consumes the parsed modules manifest, so gate read_modules_manifest behind take_frozen_path. Fresh-resolve install/add no longer pays an avoidable file read + YAML parse.
|
Code review by qodo was updated up to the latest commit 369ce9c |
`modules_consistent_with`'s doc linked to `is_modules_yaml_consistent`, which moved into the test module, breaking `cargo doc -D warnings` (the Rust CI Doc job). Merge the two stacked doc blocks into one and drop the dangling link.
|
Code review by qodo was updated up to the latest commit f663da0 |
Two review findings: - Always emit `pnpm:ignored-scripts`. The emit was suppressed under `strictDepBuilds` with a non-empty list, dropping the structured list from NDJSON exactly on the strict-failure path. The event is now always emitted with the package names; it carries the final `strict_dep_builds` value (a `#[serde(skip)]` field, so the wire shape is unchanged) and the default reporter suppresses only the rendered warning box under strict mode — matching pnpm's split between `reportIgnoredBuilds` (display) and `handleIgnoredBuilds` (throw), without a stale reporter global. - Don't swallow `.modules.yaml` read/parse errors in the strict gate. The optimistic and pre-runtime up-to-date fast paths used `.ok().flatten()`, so a corrupt/unreadable state file read as "no recorded ignored builds" and could short-circuit to exit 0 under strict mode. They now treat a read `Err` conservatively and fall through to the full install (which re-raises `ERR_PNPM_IGNORED_BUILDS`). The frozen no-op path was already safe (its consistency check needs a parsed manifest).
|
Code review by qodo was updated up to the latest commit 3e18430 |
`unparseable` → `unparsable` in a test doc comment.
|
Code review by qodo was updated up to the latest commit f49c75a |
`unapproved_recorded_ignored_builds` resolved the build policy with `AllowBuildPolicy::from_config(config).ok()?`, swallowing a malformed `allowBuilds` spec (`ERR_PNPM_INVALID_VERSION_UNION` / `ERR_PNPM_NAME_PATTERN_IN_VERSION_UNION`) into `None`. On the strict optimistic and pre-runtime up-to-date fast paths that read as "no unapproved ignored builds", hiding the config error and risking a short-circuit to success. The helper now returns `Result` and propagates the policy error; the two vulnerable fast paths fall through to the full install on `Err`, which re-evaluates the policy and reports the real error. The frozen no-op path was already guarded by `has_newly_allowed_ignored_builds` (which returns true on the same error and skips the branch). Adds a unit test.
|
Code review by qodo was updated up to the latest commit 02dd1e3 |
* feat(pacquet): add --ignore-scripts to install `pacquet install` runs the dependency build phase on the fresh-lockfile path (since #12436), so a project with blocked build scripts (e.g. sharp, esbuild) fails with `ERR_PNPM_IGNORED_BUILDS` under the default `strictDepBuilds`. pnpm's answer is `--ignore-scripts`; pacquet had no equivalent, so there was no way to install such a project without approving every build. Add `--ignore-scripts`, mirroring pnpm: a `Config.ignore_scripts` field fed by the CLI flag (merged enable-only at the Install dispatch, like `--frozen-store`). When set, the during-install build loop bypasses its allow-build gate entirely — no script runs and nothing is recorded as an ignored build, so the install no longer fails under `strictDepBuilds`. Patches still apply, and the project's own lifecycle scripts are skipped too, matching pnpm's `ignoreScripts`. This also unblocks the vlt.sh benchmark, whose pacquet command was the only one missing the `--ignore-scripts` the pnpm command already passes, making pacquet show up as DNF on every fixture. * fix(pacquet): honor ignore_scripts for git deps and from workspace yaml/env Two follow-ups from PR review: - Git and git-hosted-tarball dependencies were fetched with `ignore_scripts: false` hardcoded, so their `prepare` script still ran during install even under `--ignore-scripts`. Thread `config.ignore_scripts` into `GitFetcher` / `GitHostedTarballFetcher`, and flip the git store-index key's `built` dimension to `!ignore_scripts` at both the write site (`install_package_by_snapshot`) and the warm-prefetch site (`snapshot_cache_key`) so the two stay in lock-step and address the same slot. - `Config.ignore_scripts` was only set by the CLI flag; `ignoreScripts` in `pnpm-workspace.yaml` and `PNPM_CONFIG_IGNORE_SCRIPTS` were silently dropped. Wire it through `WorkspaceSettings` + `apply_to` and the env overlay, mirroring `strictDepBuilds`.
Problem
pacquet add <pkg>(and any non-frozenpacquet install) took the fresh-lockfile path (InstallWithFreshLockfile::run), which never invokedBuildModules. As a result:preinstall/install/postinstall) never ran, andThis diverges from
pnpm add, which runs the build phase, reports ignored builds, and (under the defaultstrictDepBuilds) fails so the user approves the build. Reported forpacquet add esbuild, which finished with no warning about esbuild's blocked build script.Fix — part 1: run the build phase on the fresh path
Extract the build tail shared by both install modes into
run_build_phase(BuildModules+pnpm:ignored-scriptsemit + post-build top-level bin link), plusBuildPhaseInputs/BuildPhaseError. The frozen path is refactored onto it (behavior-preserving — every variant stays#[diagnostic(transparent)], so error codes are unchanged); the fresh path now calls it too.Fresh-path wiring: capture
side_effects_maps_by_snapshot/requires_build_by_snapshotfromCreateVirtualStore, surfacehoisted_pkg_root_by_keyand the public-hoist alias list, moveimporting_done+ the store-index writer drain + the lockfile save to after the build, and defer thenode --versionengine probe so it overlapsCreateVirtualStoreI/O (except under the global virtual store, whose layout needs it synchronously).Fix — part 2:
strictDepBuilds(defaulttrue)pacquet had no
strictDepBuildssetting, so it would only warn and exit zero. Implemented to match pnpm:strict_dep_builds(defaulttrue), read frompnpm-workspace.yaml(strictDepBuilds) and the env overlay; moved into the asserted-defaults parity list.run_build_phasereturns the ignored-build list, threaded out of both install paths;Install::runraisesERR_PNPM_IGNORED_BUILDS(non-zero exit) after the artifacts are written when strict — so the dependency is still added/materialized, and the user approves builds and reinstalls. Thepnpm:ignored-scriptsevent is always emitted (empty list when nothing is ignored), mirroring pnpm.reportIgnoredBuildsgate (!strictDepBuilds); the CLI seeds the flag from config.So
pacquet add esbuildnow exits non-zero withERR_PNPM_IGNORED_BUILDS, like pnpm;strictDepBuilds: falserestores the non-fatal warning + exit zero.Tests
The dependency-build tests in
lifecycle_scripts.rsalready existed but were stubbed asknown_failuresthat early-returned — they also placedallowBuildsinpackage.json#pnpm, which pnpm v11 and pacquet no longer read. Moved topnpm-workspace.yamland un-gated; all now run for real. Addedpacquet addregression tests for both the strict-default failure and the non-strict warning. Tests that intentionally leave builds ignored setstrictDepBuilds: false.One
known_failureremains, scoped to its actual cause: a warm--frozen-lockfilereinstall does not yet detect anallowBuildspolicy change (the deps-status / workspace-state check short-circuits as up-to-date) — a separate workspace-state parity gap, not part of this fix.Verification:
cargo fmt --check,cargo clippy --all-targets,cargo doc -D warnings,cargo dylint, 489pacquet-package-managerunit tests, and the fullpacquet-cliintegration suite (339) all pass.Written by an agent (Claude Code, claude-opus-4-8).
Summary by CodeRabbit
New Features
strictDepBuildsconfiguration (default: enabled) to enforce pnpm-aligned behavior when dependency build scripts are ignored.ERR_PNPM_IGNORED_BUILDSwhen unapproved.strictDepBuildscan be set viaPNPM_CONFIG_STRICT_DEP_BUILDSandpnpm-workspace.yaml.Bug Fixes
Tests