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

feat: apply patchedDependencies before postinstall (#397 item 9)#427

Merged
zkochan merged 4 commits into
mainfrom
claude/patched-deps-slice-c-apply
May 12, 2026
Merged

feat: apply patchedDependencies before postinstall (#397 item 9)#427
zkochan merged 4 commits into
mainfrom
claude/patched-deps-slice-c-apply

Conversation

@zkochan

@zkochan zkochan commented May 12, 2026

Copy link
Copy Markdown
Member

Summary

Completes #397 item 9 (patchedDependencies support). Patches now actually land on disk: pacquet applies configured patches to extracted package directories before postinstall hooks run, includes patched-only packages in the build trigger, and surfaces the four upstream diagnostic codes.

Slices A + B (PR #425, merged) landed the foundation, Config plumbing, and the side-effects-cache key wiring. This PR wires the rest.

Pure-Rust patch applier

pacquet_patching::apply_patch_to_dir ports upstream's applyPatchToDir using the diffy crate (MIT OR Apache-2.0, pure Rust, no subprocess). Upstream's own comment notes that patch is unavailable on Windows and git apply is awkward inside an existing git repo — pnpm vendored @pnpm/patch-package for that reason; pacquet sidesteps it the same way with an in-process applier.

Supported FileOperations: Modify, Create, Delete. Rename and Copy fall through to ERR_PNPM_PATCH_FAILED with a descriptive message — they don't appear in patch-package-style patches in practice.

Build pipeline

  • build_sequence::get_subgraph_to_build now considers patch.is_some() alongside requires_build. A patched-only package becomes a build candidate the same way upstream's filter at buildSequence.ts:47,60 treats them.
  • BuildModules::run per-snapshot loop refactored to mirror upstream's flow:
    • Inner build trigger becomes requires_build || patch.is_some().
    • allowBuilds check only gates scripts (upstream's if (node.requiresBuild) at during-install/src/index.ts:88-101). Disallow / ignored sets should_run_scripts = false rather than continue — patches still apply.
    • cache_key's include_dep_graph_hash is now should_run_scripts (was unconditionally true), so a patched-only snapshot's cache key omits the deps-hash, matching upstream's includeDepGraphHash: hasSideEffects at line 202.
    • Patch application happens before run_postinstall_hooks, mirroring during-install/src/index.ts:171-178.
    • Cache-write gate becomes (is_patched || has_side_effects) && side_effects_cache_write (was just side_effects_cache_write), matching upstream's (isPatched || hasSideEffects) at line 199.

Diagnostics

Four upstream codes surface via BuildModulesError:

  • ERR_PNPM_PATCH_NOT_FOUND — patch file missing on disk
  • ERR_PNPM_INVALID_PATCH — unified-diff parse error
  • ERR_PNPM_PATCH_FAILED — hunk wouldn't apply, target missing, IO error on a target path
  • ERR_PNPM_PATCH_FILE_PATH_MISSING — resolved patch entry has a hash but no path (lockfile-only shape with no live config); mirrors during-install/src/index.ts:172-176

Dependency

New workspace dep: diffy = "0.5.0". MIT OR Apache-2.0, both in the deny.toml allow-list. cargo deny check licenses ok.

Test plan

  • pacquet_patching::apply::tests — 8 cases: happy-path Modify against the upstream is-positive@1.0.0 body, Create, Delete, missing patch file (PATCH_NOT_FOUND), malformed patch (INVALID_PATCH), unmatching hunk (PATCH_FAILED), missing target file (PATCH_FAILED).
  • patch_only_snapshot_gets_patched_via_build_modules drives BuildModules with a patched no-scripts snapshot end-to-end and verifies the patch file lands on disk.
  • missing_patch_file_path_errors_with_diagnostic verifies ERR_PNPM_PATCH_FILE_PATH_MISSING propagates.
  • write_path_cache_key_includes_patch_hash (from slices A+B) updated to provide a real patch file the applier can succeed against.
  • just ready clean — 575 tests pass.
  • just dylint (perfectionist) clean.
  • RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --workspace --document-private-items clean.
  • taplo format --check clean.
  • cargo deny check licenses ok.

Out of scope

  • E2E parity test with upstream's simple-with-patch fixture via Install::run through the live mock registry. Unit-level coverage of the same patched-package flow is in place above; the registry-mocked E2E mirrors the pattern from pacquet#419's optional-dep test and would fit a follow-up.
  • Rename and Copy FileOperations — they don't appear in patch-package-style patches; the applier surfaces them as ERR_PNPM_PATCH_FAILED with a clear message.

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

Summary by CodeRabbit

  • New Features

    • Automatic patch application during build runs, including for packages without build scripts
    • In-process patch applier supporting Modify/Create/Delete operations with path-safety checks
  • Bug Fixes

    • Improved validation and clearer errors for missing or malformed patch metadata
    • Side-effects cache now permits uploads when patching or postinstall scripts ran
  • Tests & Docs

    • Extensive tests and updated crate documentation for patching/apply behavior

Review Change Stack

Completes upstream's `patchedDependencies` support: pacquet now
applies configured patches to extracted package directories before
postinstall hooks run, includes patched-only packages in the build
trigger, and surfaces the four upstream diagnostic codes.

## Pure-Rust patch applier

New `pacquet_patching::apply_patch_to_dir` ports upstream's
[`applyPatchToDir`](https://github.com/pnpm/pnpm/blob/b4f8f47ac2/patching/apply-patch/src/index.ts)
using the [`diffy`](https://crates.io/crates/diffy) crate (MIT OR
Apache-2.0, pure Rust, no_std-capable, no subprocess). Upstream's
own comment notes that `patch` is unavailable on Windows and `git
apply` is awkward inside an existing git repo — pnpm vendored
`@pnpm/patch-package` for that reason; pacquet sidesteps it the
same way with an in-process applier.

Supported `FileOperation`s: `Modify`, `Create`, `Delete`. `Rename`
and `Copy` fall through to `ERR_PNPM_PATCH_FAILED` with a
descriptive message — they don't appear in `patch-package`-style
patches in practice.

New `PatchApplyError` enum surfaces the upstream diagnostic codes:
- `ERR_PNPM_PATCH_NOT_FOUND` — patch file missing
- `ERR_PNPM_INVALID_PATCH` — parse error
- `ERR_PNPM_PATCH_FAILED` — hunk wouldn't apply, target missing,
  IO error on a target path

## Build pipeline

- `build_sequence::get_subgraph_to_build` now considers
  `patch.is_some()` alongside `requires_build`. A patched-only
  package becomes a build candidate the same way upstream's
  `node.requiresBuild || node.patch != null` filter at
  [`buildSequence.ts:47,60`](https://github.com/pnpm/pnpm/blob/b4f8f47ac2/building/during-install/src/buildSequence.ts#L40-L67)
  treats them.
- `BuildModules::run` per-snapshot loop refactored:
  - Inner build trigger: `requires_build || patch.is_some()`.
  - `allowBuilds` check only gates scripts (mirrors upstream's
    `if (node.requiresBuild)` at
    [`during-install/src/index.ts:88-101`](https://github.com/pnpm/pnpm/blob/b4f8f47ac2/building/during-install/src/index.ts#L82-L106)).
    Disallow / ignored sets `should_run_scripts = false` rather
    than `continue` — patches still apply.
  - `cache_key`'s `include_dep_graph_hash` is now
    `should_run_scripts` rather than unconditionally `true`, so a
    patched-only snapshot's cache key omits the deps-hash, matching
    upstream's `includeDepGraphHash: hasSideEffects` at
    [`during-install/src/index.ts:202`](https://github.com/pnpm/pnpm/blob/b4f8f47ac2/building/during-install/src/index.ts#L199-L204).
  - Patch application happens before `run_postinstall_hooks`,
    mirroring [`during-install/src/index.ts:171-178`](https://github.com/pnpm/pnpm/blob/b4f8f47ac2/building/during-install/src/index.ts#L168-L191).
    `is_patched` tracks whether the call ran.
  - Cache-write gate becomes `(is_patched || has_side_effects)
    && side_effects_cache_write`, mirroring upstream's `(isPatched
    || hasSideEffects) && opts.sideEffectsCacheWrite` at line 199.

## Diagnostics

New `BuildModulesError::PatchFilePathMissing` surfaces
`ERR_PNPM_PATCH_FILE_PATH_MISSING` when a snapshot's resolved
`ExtendedPatchInfo.patch_file_path` is `None` (mirrors
[`during-install/src/index.ts:172-176`](https://github.com/pnpm/pnpm/blob/b4f8f47ac2/building/during-install/src/index.ts#L172-L176)).
New `BuildModulesError::PatchApply` wraps the four
`PatchApplyError` variants from the patching crate.

## Dependency

New workspace dep: `diffy = "0.5.0"` (MIT OR Apache-2.0). Used by
`pacquet-patching` only.

## Tests

- `pacquet-patching::apply::tests` (8 cases): happy-path Modify
  against the upstream `is-positive@1.0.0` body, Create,
  Delete, the three error variants, and a "missing target file
  errors PATCH_FAILED" case.
- `patch_only_snapshot_gets_patched_via_build_modules` drives
  `BuildModules` with a patched no-scripts snapshot end-to-end and
  verifies the patch file lands on disk.
- `missing_patch_file_path_errors_with_diagnostic` verifies
  `ERR_PNPM_PATCH_FILE_PATH_MISSING` propagates.
- Updated `write_path_cache_key_includes_patch_hash` (from item 9
  slices A+B) to provide a real patch file the applier can succeed
  against.

`just ready` (575 tests), `just dylint`, `cargo doc -D warnings
--document-private-items`, `taplo format --check` all clean.
`cargo deny check licenses` ok.

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

coderabbitai Bot commented May 12, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds an in-process unified-diff applier (diffy), exposes apply_patch_to_dir/PatchApplyError, threads optional patch metadata into build_sequence, and makes build_modules apply patches before postinstall hooks while adjusting candidacy, cache-key composition, and side-effects-cache write gating.

Changes

Patch application implementation and build integration

Layer / File(s) Summary
Patch applier foundation: diffy dependency and error handling
Cargo.toml, crates/patching/Cargo.toml, crates/patching/src/apply.rs
Adds diffy and implements PatchApplyError plus apply_patch_to_dir; parses gitdiff-style patches and applies per-file Modify/Create/Delete operations with path validation and detailed errors.
Patch applier test coverage
crates/patching/src/apply/tests.rs
End-to-end tests covering success paths, missing/malformed patches, hunk mismatches, path-escaping rejections, create/delete semantics, lossy-UTF8 handling, and filesystem assertions.
Build sequence patch awareness
crates/package-manager/src/build_sequence.rs, crates/package-manager/src/build_sequence/tests.rs
build_sequence gains an optional patches map parameter and treats snapshots with peer-stripped patch entries as build candidates alongside requires_build. Tests updated to new signature.
Build modules patch integration
crates/package-manager/src/build_modules.rs
Adds patch-related BuildModulesError variants, treats patched-only nodes as build candidates even when scripts disallowed, applies patches before postinstall hooks, tweaks cache-key dep-graph inclusion, and gates side-effects-cache writes on patch or hook activity.
Build modules patch tests
crates/package-manager/src/build_modules/tests.rs
Tests for cache-key inclusion of patch file hash, patch-only snapshot application, and missing patch_file_path diagnostic behavior.
Patching crate public API and documentation
crates/patching/src/lib.rs
Re-exports PatchApplyError and apply_patch_to_dir and updates crate docs to enumerate exported patching helpers.
Documentation and comment updates
crates/patching/src/resolve.rs, crates/patching/src/resolve/tests.rs, crates/patching/src/verify/tests.rs
Small comment changes referencing specific PR/issue context and test contract wording.

Sequence Diagram

sequenceDiagram
  participant Chunk as Build chunk
  participant BM as BuildModules
  participant Apply as apply_patch_to_dir
  participant Hooks as Postinstall hooks
  Chunk->>BM: package snapshot
  BM->>BM: check requires_build OR has_patch
  alt has_patch
    BM->>Apply: apply patch
    Apply-->>BM: is_patched=true
  end
  alt should_run_scripts
    BM->>Hooks: run postinstall
    Hooks-->>BM: has_side_effects
  end
  BM->>BM: update cache if is_patched OR has_side_effects
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • pnpm/pacquet#425: Related earlier PR touching patching and build pipeline; both modify patching/build wiring.
  • pnpm/pacquet#419: Also changes crates/package-manager/src/build_modules.rs and build decision logic; related at code level.
  • pnpm/pacquet#423: Modifies BuildModules behavior and error handling; overlaps in affected areas.

Suggested reviewers

  • KSXGitHub
  • anthonyshew

Poem

🐰 I nibble diffs by moonlight bright,

apply hunks, keep files polite,
Before hooks sing their little tune,
Patches snug beneath the rune,
Cache hums softly through the night.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: applying patchedDependencies before postinstall hooks, directly addressing PR #397 item 9.
Description check ✅ Passed The description comprehensively covers all required sections: Summary, Linked issue (#397 item 9), Upstream reference, and Checklist with passing verification of all items.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/patched-deps-slice-c-apply

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

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

codecov Bot commented May 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.44385% with 16 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.71%. Comparing base (6af026f) to head (bfb8049).

Files with missing lines Patch % Lines
crates/patching/src/apply.rs 84.46% 16 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #427      +/-   ##
==========================================
+ Coverage   86.68%   86.71%   +0.03%     
==========================================
  Files          91       92       +1     
  Lines        6344     6481     +137     
==========================================
+ Hits         5499     5620     +121     
- Misses        845      861      +16     

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

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

@github-actions

github-actions Bot commented May 12, 2026

Copy link
Copy Markdown

Micro-Benchmark Results

Linux

group                          main                                   pr
-----                          ----                                   --
tarball/download_dependency    1.00     15.7±0.19ms   276.2 KB/sec    1.01     15.9±0.33ms   273.1 KB/sec

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Implements the remaining patchedDependencies install pipeline behavior by applying patches onto extracted package directories before postinstall hooks run, and by treating “patched-only” packages as build candidates (matching pnpm’s during-install flow).

Changes:

  • Add a pure-Rust unified-diff patch applier (apply_patch_to_dir) using diffy, plus unit tests for modify/create/delete and error diagnostics.
  • Update build sequencing to include patched-only snapshots (requires_build || patch.is_some() via the patches map).
  • Refactor BuildModules to (1) apply patches before scripts, (2) gate allowBuilds only for scripts, and (3) align side-effects cache key/write gating with upstream.

Reviewed changes

Copilot reviewed 12 out of 13 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
crates/patching/src/verify/tests.rs Minor test doc comment cleanup.
crates/patching/src/resolve/tests.rs Minor test doc comment cleanup.
crates/patching/src/resolve.rs Update documentation comment reference.
crates/patching/src/lib.rs Update crate docs and re-export new patch-apply API; add apply module.
crates/patching/src/apply.rs New patch application implementation and diagnostics.
crates/patching/src/apply/tests.rs New unit tests for patch application behavior and error cases.
crates/patching/Cargo.toml Add diffy dependency for patch application.
crates/package-manager/src/build_sequence.rs Treat patched snapshots as build candidates in subgraph selection.
crates/package-manager/src/build_sequence/tests.rs Update tests for new build_sequence signature (patches parameter).
crates/package-manager/src/build_modules.rs Apply patches before postinstall; adjust allowBuilds gating and cache-key/write gating.
crates/package-manager/src/build_modules/tests.rs Update cache-key test to use a real patch file; add tests for patch-only snapshot + missing patch path diagnostic.
Cargo.toml Add workspace dependency on diffy.
Cargo.lock Lockfile updates for diffy and transitive dependency changes.

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

Comment thread crates/patching/src/apply.rs
Comment thread crates/patching/src/apply.rs
Comment thread crates/patching/src/apply.rs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (4)
crates/patching/src/apply.rs (1)

10-10: ⚡ Quick win

Switch upstream references to blob/main links.

The changed porting docs use SHA-permalink URLs; use blob/main URLs so references track current upstream behavior.

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

Also applies to: 53-53

🤖 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/patching/src/apply.rs` at line 10, The doc comment referencing the
upstream pnpm file uses a SHA-permalink; update the URL to the non-permalink
path so it tracks the main branch (use
https://github.com/pnpm/pnpm/blob/main/...), replacing the current SHA-based
link in the comment that references applyPatchToDir and any other similar
occurrences (e.g., the second occurrence around the symbol or comment near line
53) so both links point to blob/main instead of the SHA.
crates/package-manager/src/build_sequence/tests.rs (1)

64-64: ⚡ Quick win

Add one test that passes Some(patches) to cover the new branch.

Right now this file only exercises the None path; a patched-only node scenario would protect the new build trigger from regressions.

Also applies to: 78-78, 95-95, 114-114, 139-139, 168-168

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

In `@crates/package-manager/src/build_sequence/tests.rs` at line 64, Add tests
that call build_sequence with Some(patches) instead of None to exercise the new
patched-only branch: construct a patches HashMap (keys matching package/node ids
used in the existing tests) and pass Some(patches) as the second argument to
build_sequence in the same test cases that currently call
build_sequence(&HashMap::new(), None, ...). Update the tests at the same
locations (currently calling build_sequence with None) to include at least one
scenario where patches contains entries for a node that has no other inputs so
the patched-only build trigger path in build_sequence is executed; reuse
existing test names and assertions but assert the expected behavior when patches
are present. Ensure you use the same function name build_sequence to locate the
code and mirror the existing test structure.
crates/package-manager/src/build_sequence.rs (1)

25-30: ⚡ Quick win

Use blob/main upstream links in the porting docs.

These changed comments still point to SHA-permalink URLs; switch them to https://github.com/pnpm/pnpm/blob/main/... so references stay aligned with upstream tip.

As per coding guidelines: “When porting features... open files from https://github.com/pnpm/pnpm/blob/main/... rather than from a permalinked SHA.”

Also applies to: 146-147

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

In `@crates/package-manager/src/build_sequence.rs` around lines 25 - 30, The
comment in build_sequence.rs currently links upstream using a SHA-permalink;
update the URL(s) that reference getSubgraphToBuild (and the other link at lines
146-147) to use the moving tip form (https://github.com/pnpm/pnpm/blob/main/...)
instead of the SHA-based permalink so the porting docs follow the guideline;
locate the comment block referencing getSubgraphToBuild and any other upstream
links in this file and replace the permalinked paths with blob/main equivalents.
crates/package-manager/src/build_modules/tests.rs (1)

1258-1336: ⚡ Quick win

Add a cache-enabled variant of the patch-only snapshot test.

This test currently runs with cache disabled and no engine name, so it doesn’t exercise the patched-only cache-key path. Adding one variant with cache inputs enabled would close that regression gap.

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

In `@crates/package-manager/src/build_modules/tests.rs` around lines 1258 - 1336,
Add a second test variant that exercises the patched-only cache-key path by
duplicating patch_only_snapshot_gets_patched_via_build_modules but enabling
cache inputs: set BuildModules.side_effects_cache = true and
side_effects_cache_write = true and provide an engine_name (e.g.,
Some("test-engine")), while keeping store_dir/store_index_writer/patches the
same; ensure the test uses the same setup (virtual_store_dir, pkg_dir, patch
file, patches map) and asserts the patched file was created, so the
cache-enabled code path is executed and covered.
🤖 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/patching/src/apply.rs`:
- Around line 127-166: The code currently joins patch paths into patched_dir in
the FileOperation::Modify, Create and Delete arms (variables modified, path ->
target) without validation, so paths with absolute components or ".." can escape
patched_dir; add a path-sanitization check before constructing/using target:
reject any path where path.as_ref().is_absolute() or path.as_ref().components()
contains Component::ParentDir (and on Windows reject Component::Prefix),
returning an error (e.g. failed("invalid path")) and only proceed to build
patched_dir.join(...) and perform fs::read_to_string, diffy::apply, fs::write or
fs::remove_file after the check; apply the same validation for both modified (in
FileOperation::Modify) and path (in Create/Delete) so no patch operation can
reference files outside patched_dir.

---

Nitpick comments:
In `@crates/package-manager/src/build_modules/tests.rs`:
- Around line 1258-1336: Add a second test variant that exercises the
patched-only cache-key path by duplicating
patch_only_snapshot_gets_patched_via_build_modules but enabling cache inputs:
set BuildModules.side_effects_cache = true and side_effects_cache_write = true
and provide an engine_name (e.g., Some("test-engine")), while keeping
store_dir/store_index_writer/patches the same; ensure the test uses the same
setup (virtual_store_dir, pkg_dir, patch file, patches map) and asserts the
patched file was created, so the cache-enabled code path is executed and
covered.

In `@crates/package-manager/src/build_sequence.rs`:
- Around line 25-30: The comment in build_sequence.rs currently links upstream
using a SHA-permalink; update the URL(s) that reference getSubgraphToBuild (and
the other link at lines 146-147) to use the moving tip form
(https://github.com/pnpm/pnpm/blob/main/...) instead of the SHA-based permalink
so the porting docs follow the guideline; locate the comment block referencing
getSubgraphToBuild and any other upstream links in this file and replace the
permalinked paths with blob/main equivalents.

In `@crates/package-manager/src/build_sequence/tests.rs`:
- Line 64: Add tests that call build_sequence with Some(patches) instead of None
to exercise the new patched-only branch: construct a patches HashMap (keys
matching package/node ids used in the existing tests) and pass Some(patches) as
the second argument to build_sequence in the same test cases that currently call
build_sequence(&HashMap::new(), None, ...). Update the tests at the same
locations (currently calling build_sequence with None) to include at least one
scenario where patches contains entries for a node that has no other inputs so
the patched-only build trigger path in build_sequence is executed; reuse
existing test names and assertions but assert the expected behavior when patches
are present. Ensure you use the same function name build_sequence to locate the
code and mirror the existing test structure.

In `@crates/patching/src/apply.rs`:
- Line 10: The doc comment referencing the upstream pnpm file uses a
SHA-permalink; update the URL to the non-permalink path so it tracks the main
branch (use https://github.com/pnpm/pnpm/blob/main/...), replacing the current
SHA-based link in the comment that references applyPatchToDir and any other
similar occurrences (e.g., the second occurrence around the symbol or comment
near line 53) so both links point to blob/main instead of the SHA.
🪄 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: ec66e452-e31e-46aa-bf35-f0ba54da3cc8

📥 Commits

Reviewing files that changed from the base of the PR and between 6af026f and 0806ced.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (12)
  • Cargo.toml
  • crates/package-manager/src/build_modules.rs
  • crates/package-manager/src/build_modules/tests.rs
  • crates/package-manager/src/build_sequence.rs
  • crates/package-manager/src/build_sequence/tests.rs
  • crates/patching/Cargo.toml
  • crates/patching/src/apply.rs
  • crates/patching/src/apply/tests.rs
  • crates/patching/src/lib.rs
  • crates/patching/src/resolve.rs
  • crates/patching/src/resolve/tests.rs
  • crates/patching/src/verify/tests.rs
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Agent
  • GitHub Check: Run benchmark on ubuntu-latest
  • GitHub Check: Run benchmark on ubuntu-latest
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

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

Applied to files:

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

Applied to files:

  • crates/patching/src/resolve/tests.rs
  • crates/patching/src/verify/tests.rs
  • crates/patching/src/apply/tests.rs
  • crates/package-manager/src/build_sequence/tests.rs
  • crates/package-manager/src/build_modules/tests.rs
🔇 Additional comments (11)
crates/patching/src/resolve.rs (1)

31-43: Looks good.

This comment update is clear and keeps the ordering rationale explicit.

crates/patching/src/verify/tests.rs (1)

16-27: Looks good.

The updated wording keeps the regression intent precise.

crates/patching/src/resolve/tests.rs (1)

103-107: Looks good.

The contract note remains accurate and aligned with the test body.

Cargo.toml (1)

45-45: Looks good.

Adding diffy at the workspace level is consistent with centralized dependency management.

crates/patching/Cargo.toml (1)

15-15: Looks good.

Using the workspace-pinned diffy here is the right integration pattern.

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

183-185: Patch-aware build candidacy is correctly integrated.

Including has_patch alongside requires_build here matches the intended behavior.

crates/patching/src/lib.rs (1)

46-53: Public API exposure looks correct for downstream integration.

Re-exporting apply_patch_to_dir and PatchApplyError at the crate root cleanly matches the new build-modules wiring and diagnostics surface.

crates/patching/src/apply/tests.rs (1)

58-177: Great test coverage for the new patch applier paths.

This suite exercises happy-path apply plus the core failure diagnostics and file operations (modify/create/delete) in a way that should catch regressions early.

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

392-415: Patch application ordering is correct.

Applying the patch before postinstall hooks and surfacing PatchFilePathMissing/PatchApply directly in this flow matches the intended upstream behavior.


248-257: The original concern does not represent an actual problem in the code.

The design automatically prevents the hypothetical issue raised. At line 313, should_run_scripts is initialized to requires_build and only updated within if requires_build { ... }. For patched-only snapshots (where requires_build=false and has_patch=true), should_run_scripts remains false.

At line 349, include_dep_graph_hash is set to should_run_scripts. When include_dep_graph_hash=false (patched-only case), calc_dep_state never invokes calc_dep_graph_hash and therefore never attempts to access the bounded dep_graph. The bounded graph (rooted at requires_build nodes only) is only consulted when should_run_scripts=true, which only occurs for nodes that are actually in the graph.

			> Likely an incorrect or invalid review comment.
crates/package-manager/src/build_modules/tests.rs (1)

1346-1404: Nice diagnostic-path coverage for missing patch file path.

This test cleanly validates the PatchFilePathMissing branch and keeps the new error contract pinned.

Comment thread crates/patching/src/apply.rs
@github-actions

github-actions Bot commented May 12, 2026

Copy link
Copy Markdown

Integrated-Benchmark Report (Linux)

Scenario: Frozen Lockfile

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 3.665 ± 0.905 2.708 5.677 1.09 ± 0.29
pacquet@main 3.366 ± 0.350 2.752 3.795 1.00
pnpm 5.675 ± 0.404 5.259 6.598 1.69 ± 0.21
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 3.664882128880001,
      "stddev": 0.9048358852973348,
      "median": 3.4399575257799997,
      "user": 1.9972860799999999,
      "system": 2.6404005999999995,
      "min": 2.7075113157799997,
      "max": 5.67689092078,
      "times": [
        4.77583810478,
        3.4613261137799998,
        3.8032086327799997,
        3.11998000478,
        5.67689092078,
        3.24254942578,
        3.52132897978,
        3.41858893778,
        2.92159885278,
        2.7075113157799997
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 3.3664580158799993,
      "stddev": 0.3500405740722681,
      "median": 3.5008425597799997,
      "user": 1.9815288800000002,
      "system": 2.6223821,
      "min": 2.7521231557799997,
      "max": 3.79465069978,
      "times": [
        2.7521231557799997,
        3.4942538007799997,
        2.9216837277799996,
        3.0339659427799996,
        3.3305212847799996,
        3.50843019478,
        3.5074313187799997,
        3.6978010217799997,
        3.62371901178,
        3.79465069978
      ]
    },
    {
      "command": "pnpm",
      "mean": 5.67480259078,
      "stddev": 0.40364366293803244,
      "median": 5.55619796478,
      "user": 6.77015288,
      "system": 3.3212460999999998,
      "min": 5.2585748487799995,
      "max": 6.59812904278,
      "times": [
        6.59812904278,
        5.2585748487799995,
        5.53248394078,
        5.57991198878,
        5.368530671779999,
        5.52760202278,
        5.86639575278,
        5.62714671078,
        5.33679704578,
        6.05245388278
      ]
    }
  ]
}

Scenario: Frozen Lockfile (Hot Cache)

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 1.162 ± 0.258 0.904 1.668 1.00
pacquet@main 1.595 ± 0.939 0.844 3.435 1.37 ± 0.86
pnpm 3.646 ± 1.207 2.427 6.614 3.14 ± 1.25
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 1.16234781366,
      "stddev": 0.2575188742253623,
      "median": 1.12173603186,
      "user": 0.2715786,
      "system": 1.15910562,
      "min": 0.90410221036,
      "max": 1.6679770443600002,
      "times": [
        1.6679770443600002,
        1.1623153883600001,
        0.91414503436,
        1.13339924436,
        0.95419895336,
        0.9646421323600001,
        1.11007281936,
        1.3436008643600001,
        1.46902444536,
        0.90410221036
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 1.59453764006,
      "stddev": 0.9394568135637246,
      "median": 1.1121770128600001,
      "user": 0.26846509999999996,
      "system": 1.17981522,
      "min": 0.84442956536,
      "max": 3.43526917036,
      "times": [
        3.43526917036,
        2.5303406003599997,
        2.7692879753599997,
        1.1347524503600002,
        1.13471090836,
        0.9473143593600001,
        0.84442956536,
        1.08964311736,
        1.0614916353600001,
        0.99813661836
      ]
    },
    {
      "command": "pnpm",
      "mean": 3.64565535396,
      "stddev": 1.206913936907984,
      "median": 3.49074607836,
      "user": 2.310307,
      "system": 1.6488134200000002,
      "min": 2.42710232436,
      "max": 6.614476612360001,
      "times": [
        3.31757593536,
        3.73579712936,
        3.1173562263599996,
        2.91748089136,
        3.66391622136,
        2.47150238636,
        2.42710232436,
        6.614476612360001,
        4.32139419836,
        3.86995161436
      ]
    }
  ]
}

zkochan added 2 commits May 12, 2026 21:15
…tale-delete

Three review-flagged issues on #427:

1. **Path escape (CodeRabbit Critical + Copilot)** — patch-controlled
   paths were joined onto `patched_dir` without validation, so an
   `a/../../outside` header could read/write/delete outside the
   package directory. Added `resolve_target(rel)` that rejects
   absolute paths and any path with `ParentDir`, `RootDir`, or
   `Prefix` components, surfacing as `ERR_PNPM_PATCH_FAILED`.
   Applied to `Modify`, `Create`, and `Delete`.

2. **Create overwriting existing (Copilot)** — `FileOperation::Create`
   wrote unconditionally, silently clobbering an existing target.
   Matches `patch`'s and `git apply`'s footgun. Now checks
   `target.try_exists()` and fails with `ERR_PNPM_PATCH_FAILED`
   ("target already exists") before writing.

3. **Delete unlinking without hunk validation (Copilot)** —
   `FileOperation::Delete` just `fs::remove_file`-d the target,
   so a stale or wrong-target patch would silently delete the
   wrong file. Now reads the existing file, runs `diffy::apply`,
   and only unlinks when the result is empty (every hunk matched).
   Apply failures and non-empty results both surface as
   `ERR_PNPM_PATCH_FAILED`.

Five new tests:

- `parent_dir_segment_in_modify_errors`
- `parent_dir_segment_in_create_errors`
- `parent_dir_segment_in_delete_errors`
- `create_on_existing_file_errors` — and verifies original
  contents are untouched.
- `delete_with_mismatching_hunks_errors_without_unlinking` — and
  verifies the file remains on disk.

580 tests pass; `just ready`, `just dylint`, `cargo doc -D warnings
--document-private-items`, `taplo format --check` all clean.

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

`cargo fmt` collapsed my multi-line `format!(...)` call onto a
single line in apply.rs:149 but left the trailing comma in
place. `perfectionist::macro-trailing-comma` flags trailing commas
on single-line macro calls, and CI's Dylint job ran the workspace
under `-D warnings`, so the build failed on bd93ad8. Drop the
comma.
Copilot AI review requested due to automatic review settings May 12, 2026 19:18

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 12 out of 13 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

crates/patching/src/apply.rs:203

  • Delete also uses fs::read_to_string, so it will fail with InvalidData on non-UTF8 files even if the patch would otherwise apply. If the intent is to mirror Node/patch-package behavior, decode file contents lossily (bytes + from_utf8_lossy) to keep patching robust and consistent with upstream semantics.
            // diffy::apply on a delete patch produces the empty
            // string when every hunk matches.
            let original = fs::read_to_string(&target)
                .map_err(|source| failed(format!("read {}: {source}", target.display())))?;

Comment thread crates/package-manager/src/build_sequence.rs Outdated
Comment thread crates/patching/src/apply.rs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
crates/patching/src/apply/tests.rs (1)

160-223: ⚡ Quick win

Assert that failed patch applications leave the filesystem untouched.

These tests prove the error path, but not the safety property. A regression could still create/modify an escaped ../... target or rewrite to-delete.txt before returning PatchFailed, and this suite would still pass. Please add postconditions that the escaped path is absent and that the stale-delete case preserves the original file contents.

Suggested assertions
 fn parent_dir_segment_in_create_errors() {
     let patched = tempdir().unwrap();
+    let escaped = patched.path().parent().unwrap().join("planted.txt");
     let patch_dir = tempdir().unwrap();
     let patch = write_patch(
         patch_dir.path(),
@@
     let PatchApplyError::PatchFailed { message, .. } = err else {
         panic!("expected PatchFailed, got: {err:?}");
     };
     assert!(message.contains("escapes target dir"), "got: {message}");
+    assert!(!escaped.exists(), "escape path must stay absent: {}", escaped.display());
 }
@@
 fn delete_with_mismatching_hunks_errors_without_unlinking() {
@@
     let err = apply_patch_to_dir(patched.path(), &patch).expect_err("must refuse mismatch");
     assert!(matches!(err, PatchApplyError::PatchFailed { .. }), "got: {err:?}");
     assert!(target.exists(), "file must NOT be unlinked when the patch doesn't match");
+    assert_eq!(fs::read_to_string(&target).unwrap(), "actually different content\n");
 }

Also applies to: 259-280

🤖 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/patching/src/apply/tests.rs` around lines 160 - 223, Add postcondition
assertions to each test (parent_dir_segment_in_modify_errors,
parent_dir_segment_in_create_errors, parent_dir_segment_in_delete_errors) to
ensure failed patch applications leave the filesystem untouched: after calling
apply_patch_to_dir and asserting PatchFailed, verify that any escaped target
path (e.g., patched.path().join("../escape.txt"), "../planted.txt",
"../victim.txt" relative to patch_dir/patched) does not exist or was not
created, and for the delete test confirm the original file contents remain
unchanged (read the original file in patched before applying and assert its
contents are equal afterwards). Use the existing tempdir(), patched.path(),
patch_dir.path(), write_patch, and apply_patch_to_dir helpers to locate files
and perform the existence/content checks.
🤖 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/patching/src/apply/tests.rs`:
- Around line 160-223: Add postcondition assertions to each test
(parent_dir_segment_in_modify_errors, parent_dir_segment_in_create_errors,
parent_dir_segment_in_delete_errors) to ensure failed patch applications leave
the filesystem untouched: after calling apply_patch_to_dir and asserting
PatchFailed, verify that any escaped target path (e.g.,
patched.path().join("../escape.txt"), "../planted.txt", "../victim.txt" relative
to patch_dir/patched) does not exist or was not created, and for the delete test
confirm the original file contents remain unchanged (read the original file in
patched before applying and assert its contents are equal afterwards). Use the
existing tempdir(), patched.path(), patch_dir.path(), write_patch, and
apply_patch_to_dir helpers to locate files and perform the existence/content
checks.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8f9613f8-8e94-4e57-8697-2d6711ed0d50

📥 Commits

Reviewing files that changed from the base of the PR and between 0806ced and cb55cb9.

📒 Files selected for processing (2)
  • crates/patching/src/apply.rs
  • crates/patching/src/apply/tests.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/patching/src/apply.rs
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Lint and Test (windows-latest)
  • GitHub Check: Lint and Test (ubuntu-latest)
  • GitHub Check: Lint and Test (macos-latest)
  • GitHub Check: Run benchmark on ubuntu-latest
  • GitHub Check: Run benchmark on ubuntu-latest
  • GitHub Check: Code Coverage
  • GitHub Check: Agent
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

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

Applied to files:

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

Applied to files:

  • crates/patching/src/apply/tests.rs

…ild_sequence patches doc

Two Copilot comments on #427:

1. **apply.rs:158 and :200** — `Modify` and `Delete` were reading
   the existing target file with `fs::read_to_string`, which
   surfaces invalid UTF-8 as `InvalidData`. Upstream / Node uses
   `fs.readFile(..., 'utf8')` which replaces invalid bytes with
   U+FFFD and never throws, so the patch-file reader's lossy
   decoding already does the right thing; the target-file reads
   here didn't. Switched to `fs::read` + `String::from_utf8_lossy`
   for parity. New test
   `modify_target_with_invalid_utf8_bytes_does_not_error` pins
   the behavior — patch context is the three U+FFFD chars
   themselves, so the patch applies cleanly against the
   lossy-decoded target.

2. **build_sequence.rs:29** — Doc described `patches` as a
   resolved `PatchGroupRecord`, but the parameter is the
   per-snapshot lookup map (`Option<&HashMap<PackageKey,
   ExtendedPatchInfo>>`) built by `InstallFrozenLockfile::run`
   from `resolve_and_group` + `get_patch_info`. Rewrote the doc
   to match the actual shape.

581 tests pass; `just ready`, `just dylint`, `cargo doc -D warnings
--document-private-items`, `taplo format --check` all clean.

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
crates/package-manager/src/build_sequence.rs (1)

35-35: ⚡ Quick win

Use blob/main links in upstream references (not pinned SHAs).

Line 35 and Line 151 currently point to permalinked SHAs. Please switch these to https://github.com/pnpm/pnpm/blob/main/... to follow repo guidance for upstream-port references.

Suggested doc-link update
-/// [`getSubgraphToBuild`](https://github.com/pnpm/pnpm/blob/b4f8f47ac2/building/during-install/src/buildSequence.ts#L40-L67).
+/// [`getSubgraphToBuild`](https://github.com/pnpm/pnpm/blob/main/building/during-install/src/buildSequence.ts#L40-L67).
...
-/// `https://github.com/pnpm/pnpm/blob/b4f8f47ac2/building/during-install/src/buildSequence.ts`.
+/// `https://github.com/pnpm/pnpm/blob/main/building/during-install/src/buildSequence.ts`.

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

Also applies to: 151-151

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

In `@crates/package-manager/src/build_sequence.rs` at line 35, Update the upstream
GitHub links that currently use permalinked SHAs to use the blob/main form;
specifically, in the doc comments referencing getSubgraphToBuild and the other
upstream reference (the two occurrences in this file), replace URLs like
https://github.com/pnpm/pnpm/blob/<sha>/... with
https://github.com/pnpm/pnpm/blob/main/... so the links point to blob/main
instead of a pinned commit.
🤖 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/build_sequence.rs`:
- Line 35: Update the upstream GitHub links that currently use permalinked SHAs
to use the blob/main form; specifically, in the doc comments referencing
getSubgraphToBuild and the other upstream reference (the two occurrences in this
file), replace URLs like https://github.com/pnpm/pnpm/blob/<sha>/... with
https://github.com/pnpm/pnpm/blob/main/... so the links point to blob/main
instead of a pinned commit.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 451bf035-bdd7-43b0-8ca4-7b54be1b3827

📥 Commits

Reviewing files that changed from the base of the PR and between cb55cb9 and bfb8049.

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

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • crates/package-manager/src/build_sequence.rs
🧠 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/build_sequence.rs
🔇 Additional comments (1)
crates/package-manager/src/build_sequence.rs (1)

38-53: Patch-aware build candidacy is implemented correctly.

Nice change: patches is threaded cleanly and Line 188 + Line 190 correctly include patch-only snapshots in the build subgraph.

Also applies to: 161-190

@zkochan

zkochan commented May 12, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai re: the review-comment suggestion to switch blob/<sha>/... links to blob/main/... in crates/package-manager/src/build_sequence.rs:35 and :151 — skipping, this is a misreading of the repo guidance.

CLAUDE.md is explicit that citations must pin a SHA (so the link stays meaningful when upstream files are renamed or deleted) and that the freshest-main rule is for research (when reading the file to decide what to port), not for citations:

When citing upstream code anywhere — code comments, doc comments, Markdown docs, PR descriptions, or commit messages — link to a specific commit SHA, not a branch name. Branch links such as github.com/<owner>/<repo>/blob/main/... … are impermanent: their target drifts as the branch moves and may eventually 404 if the file is renamed or deleted.

The b4f8f47ac2 permalinks are doing exactly what the guide asks for. The "open files from .../blob/main/..." sentence you quoted is from the same paragraph in the context of reading upstream while researching a port; the next sentence is "The permalink rule below applies when citing upstream code; while researching it, you want the freshest main."

I'll leave the SHA links as they are. Thanks anyway!


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

@coderabbitai

coderabbitai Bot commented May 12, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@zkochan: You're absolutely right — I misread the guidance. The CLAUDE.md distinction is clear: SHA-pinned permalinks are the correct form for citations (code comments, doc comments, PR descriptions, etc.) to ensure the link remains stable, while blob/main/... is only for research purposes when actively reading upstream. The b4f8f47ac2 permalinks are exactly correct. Sorry for the noise!


✏️ Learnings added
Learnt from: zkochan
URL: https://github.com/pnpm/pacquet/pull/427

Timestamp: 2026-05-12T19:43:47.967Z
Learning: In the pnpm/pacquet repository, CLAUDE.md mandates that upstream code citations in code comments, doc comments, Markdown docs, PR descriptions, and commit messages MUST use SHA-pinned permalink URLs (e.g., `github.com/<owner>/<repo>/blob/<sha>/...`), not branch-name links like `blob/main/...`. The `blob/main/...` form is only for researching upstream while porting. Do not flag SHA-pinned upstream citation links as needing to be changed to `blob/main/...`.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@zkochan zkochan merged commit cba5422 into main May 12, 2026
16 checks passed
@zkochan zkochan deleted the claude/patched-deps-slice-c-apply branch May 12, 2026 19:54
KSXGitHub pushed a commit that referenced this pull request May 13, 2026
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.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants