Files
pnpm/pacquet/crates/patching/Cargo.toml
Zoltan Kochan 13082fc1bf feat: apply patchedDependencies before postinstall (#397 item 9) (#427)
Completes pnpm/pacquet#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`](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 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.

## 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`](https://github.com/pnpm/pnpm/blob/b4f8f47ac2/building/during-install/src/buildSequence.ts#L40-L67) 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`](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` (was unconditionally `true`), so a patched-only snapshot's cache key omits the deps-hash, matching upstream's `includeDepGraphHash: hasSideEffects` at [line 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).
  - 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`](https://github.com/pnpm/pnpm/blob/b4f8f47ac2/building/during-install/src/index.ts#L172-L176)

## 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.
2026-05-12 21:54:01 +02:00

24 lines
646 B
TOML

[package]
name = "pacquet-patching"
version = "0.0.1"
publish = false
authors.workspace = true
description.workspace = true
edition.workspace = true
homepage.workspace = true
keywords.workspace = true
license.workspace = true
repository.workspace = true
[dependencies]
derive_more = { workspace = true }
diffy = { workspace = true }
indexmap = { workspace = true }
miette = { workspace = true }
node-semver = { workspace = true }
sha2 = { workspace = true }
[dev-dependencies]
pretty_assertions = { workspace = true }
tempfile = { workspace = true }