Skip to content

Update cache-manager to version 2.2.0 🚀#434

Closed
greenkeeperio-bot wants to merge 1 commit into
masterfrom
greenkeeper-cache-manager-2.2.0
Closed

Update cache-manager to version 2.2.0 🚀#434
greenkeeperio-bot wants to merge 1 commit into
masterfrom
greenkeeper-cache-manager-2.2.0

Conversation

@greenkeeperio-bot

Copy link
Copy Markdown
Contributor

Hello lovely humans,

cache-manager just published its new version 2.2.0.

State Update 🚀
Dependency cache-manager
New version 2.2.0
Type dependency

This version is not covered by your current version range.

Without accepting this pull request your project will work just like it did before. There might be a bunch of new features, fixes and perf improvements that the maintainers worked on for you though.

I recommend you look into these changes and try to get onto the latest version of cache-manager.
Given that you have a decent test suite, a passing build is a strong indicator that you can take advantage of these changes by merging the proposed change into your project. Otherwise this branch is a great starting point for you to work on the update.

Do you have any ideas how I could improve these pull requests? Did I report anything you think isn’t right?
Are you unsure about how things are supposed to work?

There is a collection of frequently asked questions and while I’m just a bot, there is a group of people who are happy to teach me new things. Let them know.

Good luck with your project ✨

You rock!

🌴


The new version differs by 9 commits .

  • 9f68e9b Merge branch 'release/2.2.0'
  • 44b4a1a 2.2.0
  • ebcd43c Merge branch 'master' into develop
  • 56e824e Merge pull request #63 from disjunction/feature/multi_caching_reset
  • 268876a add multi_caching.reset()
  • 08c9c2a Merge pull request #60 from theogravity/master
  • c0cf667 Add node-cache-manager-memcached-store ref (#1)
  • a9a9964 fixing ticket number in History.md
  • db550fa Merge branch 'release/2.1.2' into develop

See the full diff.


This pull request was created by greenkeeper.io.

Tired of seeing this sponsor message? ⚡ greenkeeper upgrade

@rstacruz rstacruz force-pushed the greenkeeper-cache-manager-2.2.0 branch 3 times, most recently from 0b116f1 to a687b61 Compare October 21, 2016 15:15
@zkochan zkochan closed this Oct 21, 2016
@zkochan zkochan deleted the greenkeeper-cache-manager-2.2.0 branch October 22, 2016 23:01
pull Bot pushed a commit to dwongdev/pnpm that referenced this pull request May 14, 2026
…incompatibles (pnpm#434 slice 1) (pnpm#439)

Slice 1 of pnpm#434 — foundational installability gate. Ports
[`@pnpm/config.package-is-installable`](https://github.com/pnpm/pnpm/tree/94240bc046/config/package-is-installable)
and threads the resulting skip-set through every phase of the
frozen-lockfile install pipeline.

Closes #266 once shipped (covers the "install respects every snapshot
— no os/cpu/libc filter" gap). Does **not** close pnpm#434 — that umbrella
has six more slices to follow.

Upstream reference: pnpm/pnpm@94240bc046.

## What landed

### New crate: `pacquet-package-is-installable`

Ports the upstream `config/package-is-installable` package's three
helpers:

- `check_platform` (`Option<&[String]>` for each `os`/`cpu`/`libc`
  axis, plus a `SupportedArchitectures` override) — returns
  `Option<UnsupportedPlatformError>` matching upstream's
  `ERR_PNPM_UNSUPPORTED_PLATFORM` code, message shape, and JSON
  payload. Handles negation entries (`!foo`), the `any` sentinel,
  the `current` placeholder, and the `currentLibc !== 'unknown'`
  skip from `checkPlatform.ts:38`.
- `check_engine` — evaluates `engines.node` / `engines.pnpm` via
  `node-semver`. Approximates npm-semver's `includePrerelease: true`
  via a strip-prerelease fallback; one over-acceptance edge case
  (`>=X.Y.Z` against `X.Y.Z-rc1`) is pinned in the known-failures
  integration test for follow-up.
- `package_is_installable` — composes the two, returns the
  tri-state verdict matching upstream's `boolean | null` (Installable
  / SkipOptional / ProceedWithWarning), plus an `Err` arm for
  `engine_strict` aborts and `ERR_PNPM_INVALID_NODE_VERSION`.

`InstallabilityOptions<'a>` borrows its host strings so a caller
running through many snapshots in a row can build the host part
once and only toggle `optional` per snapshot. `WantedPlatformRef<'a>`
plays the same role for the manifest axes so `check_platform` runs
the happy path without any allocation.

### New module: `pacquet_package_manager::installability`

`compute_skipped_snapshots` is the per-install entry point. For each
snapshot:

1. Look up the matching `PackageMetadata`.
2. Run `check_package` (cached per peer-stripped `metadata_key` so
   peer-resolved variants of the same package share one verdict).
3. Dispatch on `(verdict, snapshot.optional, host.engine_strict)`:
   - `Installable`: nothing to do.
   - `SkipOptional` + `optional`: add to `SkippedSnapshots`, emit
     `pnpm:skipped-optional-dependency` (deduped per metadata key,
     matching upstream's emit-per-pkgId).
   - `Incompatible` + non-optional + `engine_strict`: abort.
   - `Incompatible` + non-optional + non-strict: `tracing::warn!`
     and proceed. (Upstream's `pnpm:install-check` channel isn't
     wired into pacquet's reporter yet — slice 1 follow-up.)

`any_installability_constraint(packages)` is the caller-side fast
path: if no metadata row declares an `engines.{node,pnpm}` or a
non-empty / non-`["any"]` `cpu`/`os`/`libc`, the entire installability
pass is skipped. The probe runs synchronously in
`install_frozen_lockfile` *before* the `tokio::task::spawn_blocking`
that would invoke `node --version` — so the common no-constraints
lockfile pays nothing for the new pipeline, restoring main's overlap
of node-binary startup with extraction.

### Install-pipeline plumbing

The `SkippedSnapshots` set is threaded into every downstream phase
of `InstallFrozenLockfile::run`:

- `CreateVirtualStore`: installability-skipped snapshots are
  dropped from both `survivors` (no virtual-store slot extracted)
  and `skipped_entries` (no warm-cache row). Layered ahead of
  main's pnpm#442 already-installed-and-on-disk skip filter.
- `SymlinkDirectDependencies`: a direct dep whose resolved
  snapshot is in the skip set is omitted from `node_modules/<name>`
  (no symlink, no `pnpm:root added` event, no bin link).
- `LinkVirtualStoreBins`: per-slot bin link skips slots whose
  snapshot is installability-skipped (their virtual-store
  directories don't exist).
- `BuildModules` via `build_sequence`: `get_subgraph_to_build`
  consults `skipped` *before* recursion, so a skipped snapshot's
  subtree doesn't contribute to the build graph via that edge.
  Descendants reachable from a non-skipped root still build
  normally.

### Performance

CI integrated-benchmark on the 1352-package fixture, latest run:

| Scenario | `pacquet@HEAD` | `pacquet@main` | Relative |
|---|---|---|---|
| Frozen Lockfile (cold) | 2.476 ± 0.083 s | 2.442 ± 0.071 s | 1.01 ± 0.05 |
| Frozen Lockfile (Hot Cache) | 685.8 ± 59.3 ms | 700.2 ± 47.4 ms | 1.00 |

Earlier iterations of this PR showed a ~5% cold-install regression
from the `node --version` spawn landing on the extraction critical
path. Closed by hoisting the no-constraints fast-path probe to the
caller (commit `cf47ce51`) so the spawn is gated on actual
constraint presence.

Other perf passes folded in:

- `compute_skipped_snapshots` caches the per-metadata-row check
  verdict so peer-resolved variants share one `check_package` call.
- `check_platform` borrows its three wanted axes through
  `WantedPlatformRef<'a>`; the owned `WantedPlatform` only
  materialises in the error path.

## Tests

| Suite | Count | What it covers |
|---|---|---|
| `pacquet-package-is-installable::tests::check_platform` | 16 | Port of upstream `checkPlatform.ts` — `any`/`current` sentinels, negation, `supportedArchitectures` override, libc unknown-skip |
| `pacquet-package-is-installable::tests::check_engine` | 7 | Port of upstream `checkEngine.ts` — node/pnpm range checks, prerelease cases, `ERR_PNPM_INVALID_NODE_VERSION` |
| `pacquet-package-is-installable::tests::package_is_installable` | 6 | Tri-state verdict + optional/engine-strict dispatch |
| `pacquet-package-is-installable::tests::known_failures` | 1 | The `>=X.Y.Z` vs `X.Y.Z-rc1` over-acceptance, picked up by `just known-failures` |
| `pacquet_package_manager::installability::tests` | 11 | Per-install skip-set computation: skip on bad OS, skip on bad node engine, dedup events across peer variants, fast-path triggers, constraint predicate's edge cases (`engines.npm` only, `["any"]` sentinel, empty lists) |
| `pacquet_package_manager::build_sequence::tests` | 3 (new) | Skipped+patched doesn't enter build queue; skipped parent doesn't drag descendants in; descendant with non-skipped parent still builds |

All ported tests verified to catch regressions by temporarily
breaking the subject under test, observing the failure, then
reverting. The "test the tests" workflow from CLAUDE.md.

## Deferred to follow-up slices

- `.modules.yaml.skipped` write/read + headless re-check
  (slice 3).
- `supportedArchitectures` config + `--cpu` / `--os` / `--libc`
  CLI flags (slice 2).
- `pnpm:install-check` warn channel on the reporter side
  (currently `tracing::warn!`).
- Real libc detection — `host_libc()` returns `"unknown"` today;
  matches non-Linux host behavior, but on Alpine/musl this
  over-installs glibc-only optional packages. Slice 2.
- `engine_strict` config wiring — defaults to false today, so
  the error path is unreachable from production. Wired through
  end-to-end so the slice that flips the config doesn't churn
  the error enum.
pull Bot pushed a commit to dwongdev/pnpm that referenced this pull request May 14, 2026
…bc detection (pnpm#434 slice 2) (pnpm#456)

Slice 2 of the [pnpm#434 umbrella](pnpm/pacquet#434) (`Proper optionalDependencies support`). Slice 1 (pnpm#439) wired `pacquet-package-is-installable` into the install pipeline but the `SupportedArchitectures` input was always `None`, so every install behaved as if the user had opted into \"host triple only\". This PR closes that gap and replaces the slice 1 libc stub with a real Linux probe.

Mirrors the three upstream input sources pinned at [pnpm/pnpm@94240bc046](https://github.com/pnpm/pnpm/tree/94240bc046):

- **`Config.supported_architectures` from `pnpm-workspace.yaml`** — same shape as upstream's `getOptionsFromRootManifest.ts` ([`config/reader/src/getOptionsFromRootManifest.ts#L23`](https://github.com/pnpm/pnpm/blob/94240bc046/config/reader/src/getOptionsFromRootManifest.ts#L23)).
- **`--cpu` / `--os` / `--libc` flags on `install` / `add`** — multi-valued, comma-separated. Per-axis CLI override replaces the yaml axis wholesale, matching upstream's [`overrideSupportedArchitecturesWithCLI.ts`](https://github.com/pnpm/pnpm/blob/94240bc046/config/reader/src/overrideSupportedArchitecturesWithCLI.ts).
- **Real Linux libc detection** — replaces the slice 1 `\"unknown\"` stub with a `/lib*/ld-musl-*` probe cached via `OnceLock`. Mirrors `detect-libc.familySync()` semantics: `\"musl\"` / `\"glibc\"` on Linux, `\"unknown\"` everywhere else. No new dep — open-coded `read_dir` is cheaper than spawning `ldd --version` and works in slim containers without `ldd` on PATH.

Threaded through `Install` / `InstallFrozenLockfile` / `Add` as an explicit field instead of mutating `State.config`, because `State.config` is `&'static Config` — the CLI override merge happens at the CLI layer and the fully-resolved value lands on the install pipeline.

## Test plan

Unit tests added; full suite ran via `just ready` + `cargo doc --no-deps` + `taplo format --check` + `just dylint`.

- [x] `cargo test -p pacquet-config workspace_yaml::tests::*supported_architectures*` — yaml round-trip across `os` / `cpu` / `libc`, omission, partial-axis (3 tests).
- [x] `cargo test -p pacquet-package-manager --lib installability::tests::supported_architectures*` — `supportedArchitectures` widens the accept set so a darwin-only package stays on a linux host when the user lists `darwin`; conversely, listing `linux` keeps the darwin-only package skipped (no implicit host include).
- [x] `just ready` — typos + fmt + check + nextest (792 tests pass) + clippy.
- [x] `taplo format --check`.
- [x] `just dylint` (perfectionist).
- [x] `RUSTDOCFLAGS=\"-D warnings\" cargo doc --no-deps --workspace`.

## Out of scope

- `.modules.yaml.skipped` persistence (umbrella slice 3).
- Current-lockfile diffing for `removeOptionalDependenciesThatAreNotUsed` (umbrella slice 6).
- `--no-optional` plumbing (umbrella slice 5).

Closes pnpm#453.
pull Bot pushed a commit to dwongdev/pnpm that referenced this pull request May 14, 2026
…pm#434 slice 3) (pnpm#467)

## Summary

Slice 3 of the [pnpm#434 umbrella](pnpm/pacquet#434) (`Proper optionalDependencies support`). Slice 1 (pnpm#439) computed a `SkippedSnapshots` set on every frozen-lockfile install but never serialized it; slice 2 (pnpm#456) closed the input side. This PR closes the loop by mirroring three upstream sites pinned at [pnpm/pnpm@94240bc046](https://github.com/pnpm/pnpm/tree/94240bc046):

- **Write** — `Modules.skipped` was declared at [`crates/modules-yaml/src/lib.rs:197`](https://github.com/pnpm/pacquet/blob/97010013/crates/modules-yaml/src/lib.rs#L197) with sort-on-write but always serialized empty. `build_modules_manifest` now takes `&SkippedSnapshots` and produces the depPath string list pnpm writes at [`installing/deps-installer/src/install/index.ts:1625`](https://github.com/pnpm/pnpm/blob/94240bc046/installing/deps-installer/src/install/index.ts#L1625).

- **Seed** — `install_frozen_lockfile::run` reads `.modules.yaml.skipped` before computing the in-memory set and passes the parsed `PackageKey`s as the seed to `compute_skipped_snapshots`. Mirrors upstream's load at [`installing/read-projects-context/src/index.ts:79`](https://github.com/pnpm/pnpm/blob/94240bc046/installing/read-projects-context/src/index.ts#L79) plus the early return at [`deps/graph-builder/src/lockfileToDepGraph.ts:194`](https://github.com/pnpm/pnpm/blob/94240bc046/deps/graph-builder/src/lockfileToDepGraph.ts#L194).

- **Short-circuit** — `compute_skipped_snapshots` returns the seed verbatim on the fast path (no constraints in the lockfile) and, on the slow path, skips the per-snapshot re-check for keys already in the seed without emitting a duplicate `pnpm:skipped-optional-dependency` event. Non-seeded snapshots still re-run `package_is_installable`, covering the \"host arch changed since last install\" case upstream guards at [`:206-215`](https://github.com/pnpm/pnpm/blob/94240bc046/deps/graph-builder/src/lockfileToDepGraph.ts#L206-L215).

`InstallFrozenLockfile::run` now returns a small `InstallFrozenLockfileOutput { hoisted_dependencies, skipped }` struct so `Install::run` can thread both into `.modules.yaml`. Read errors on `.modules.yaml` degrade to an empty seed — the file is a cache artifact, not authoritative.
pull Bot pushed a commit to dwongdev/pnpm that referenced this pull request May 14, 2026
…e payload (pnpm#434 slice 4) (pnpm#474)

Slice 4 of the [pnpm#434 umbrella](pnpm/pacquet#434) (`Proper optionalDependencies support`). Slices 1–3 (pnpm#439, pnpm#456, pnpm#467) close the installability-driven skip path; this slice adds the second skip pathway upstream handles in the frozen install: an `optional: true` snapshot whose **fetch / extract** step fails at install time must not abort the install. Local-materialization errors (`CreateVirtualDir`) and config-shape errors still abort even for optional snapshots — matching upstream's `linkPkg` path which sits outside the catch.

Mirrors the silent catch sites at [`deps/graph-builder/src/lockfileToDepGraph.ts:294-298`](https://github.com/pnpm/pnpm/blob/94240bc046/deps/graph-builder/src/lockfileToDepGraph.ts#L294-L298) and [`installing/deps-restorer/src/index.ts:912-921`](https://github.com/pnpm/pnpm/blob/94240bc046/installing/deps-restorer/src/index.ts#L912-L921):

```ts
try { ... fetch ... }
catch (err) { if (pkgSnapshot.optional) return; throw err }
```

## Changes

- **Reporter**: `SkippedOptionalPackage` becomes a `#[serde(untagged)]` enum with `Installed { id, name, version }` and `ResolutionFailure { name?, version?, bareSpecifier }` variants. Models upstream's discriminated union at [`core-loggers/src/skippedOptionalDependencyLogger.ts:10-31`](https://github.com/pnpm/pnpm/blob/94240bc046/core/core-loggers/src/skippedOptionalDependencyLogger.ts#L10-L31). The new variant is wire-shape-only in slice 4 — pacquet has no resolver yet, but a future resolver port at the upstream emit site ([`resolveDependencies.ts:1376-1383`](https://github.com/pnpm/pnpm/blob/94240bc046/installing/deps-resolver/src/resolveDependencies.ts#L1376-L1383)) lands without re-touching this type.
- **`SkippedSnapshots`**: gains a `fetch_failed` subset disjoint from the existing `installability` subset. `contains` / `iter` return the union (downstream consumers treat both as absent); `iter_installability` returns only the persistent subset that `.modules.yaml.skipped` records, matching upstream's behavior of never updating `opts.skipped` at the fetch-failure catch sites.
- **`CreateVirtualStore`**: cold-batch dispatch swallows per-snapshot errors when `snapshot.optional` is true **and** the error is fetch-side. A new `is_fetch_side_failure` helper restricts the swallow to `InstallPackageBySnapshotError::DownloadTarball` and `::GitFetch` — the same surface upstream wraps in `storeController.fetchPackage`. Local-materialization (`CreateVirtualDir`) and config-shape errors (`MissingTarballIntegrity` / `UnsupportedResolution`) propagate even for optional snapshots, matching upstream's `linkPkg` path which sits outside the catch. Side benefit: the warm-batch path (which only surfaces `CreateVirtualDir` errors) stays consistently abort-on-error without a parallel swallow site. The per-install `fetch_failed: HashSet<PackageKey>` rides out on `CreateVirtualStoreOutput`.
- **`InstallFrozenLockfile::run`**: folds the returned `fetch_failed` into its mutable `SkippedSnapshots` so downstream phases (`SymlinkDirectDependencies`, `LinkVirtualStoreBins`, `BuildModules`, hoist) treat the dropped snapshots as absent through the same gate they already use for installability skips.

## Test plan

- [x] `reporter::tests::skipped_optional_resolution_failure_event_matches_pnpm_wire_shape` — full payload (`bareSpecifier` camelCase, no `id`).
- [x] `reporter::tests::skipped_optional_resolution_failure_omits_absent_name_and_version` — optional-field shape.
- [x] `install::tests::frozen_install_silently_swallows_unreachable_optional_tarball` — ports [`installing/deps-restorer/test/index.ts:340-360`](https://github.com/pnpm/pnpm/blob/94240bc046/installing/deps-restorer/test/index.ts#L340-L360): unreachable optional tarball (`http://127.0.0.1:1/...`), install succeeds, slot not created, `.modules.yaml.skipped` left empty. **Test-the-test verified** by inverting the `optional` guard in the cold-batch match arm. Runs with `enable_global_virtual_store = false` so the slot-path assertion targets the legacy layout.
- [x] `install::tests::frozen_install_propagates_non_optional_fetch_failure` — pins the polarity: same fixture, non-optional snapshot, install must error.
- [x] `just ready` (typos + fmt + check + nextest + clippy).
- [x] `taplo format --check`.
- [x] `just dylint` (perfectionist).
- [x] `RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --workspace`.

## Out of scope

- Resolver-side `resolution_failure` emit site — needs a resolver, tracked separately.
- `--no-optional` / `--omit=optional` plumbing — umbrella slice 5.
- Surfacing fetch failures to the reporter on the frozen path — upstream itself is silent here; only the resolver-side emit fires `pnpm:skipped-optional-dependency`.

Closes pnpm#471.
pull Bot pushed a commit to dwongdev/pnpm that referenced this pull request May 14, 2026
…e 5) (pnpm#485)

Slice 5 of the [pnpm#434 umbrella](pnpm/pacquet#434) (`Proper optionalDependencies support`). Slices 1–4 (pnpm#439, pnpm#456, pnpm#467, pnpm#474) handle installability + the fetch-failure swallow. This slice closes the user-facing `--no-optional` flag: the CLI flag already exists and threads through `Install::dependency_groups`, but only `SymlinkDirectDependencies` and the on-disk `included` field actually honored it. The rest of the install pipeline walked `optional_dependencies` unconditionally, so the optional subtree was still extracted, linked, and built.

Mirrors upstream's depNode filter at [`installing/deps-installer/src/install/link.ts:109-111`](https://github.com/pnpm/pnpm/blob/94240bc046/installing/deps-installer/src/install/link.ts#L109-L111):

```ts
if (!opts.include.optionalDependencies) {
  depNodes = depNodes.filter(({ optional }) => !optional)
}
```

## Changes

- **`SkippedSnapshots`**: gains a third disjoint subset `optional_excluded` alongside `installability` (slice 1) and `fetch_failed` (slice 4). The existing `contains` / `iter` already return the union, so every downstream consumer that filters by skip-set (`CreateVirtualStore`, `SymlinkDirectDependencies`, `BuildModules`, hoist, `link_bins`) now respects `--no-optional` through the same gate. `iter_installability` still returns only the persistent subset for `.modules.yaml.skipped` writes — `--no-optional` exclusions are transient (matching upstream's behavior of never updating `opts.skipped` at the filter site).
- **`InstallFrozenLockfile::run`**: iterates the lockfile snapshots once and inserts every `snap.optional == true` entry into the new subset when the dispatch list doesn't contain `DependencyGroup::Optional`. The `SnapshotEntry::optional` flag is set by the resolver only when the snapshot is reachable **exclusively** through optional edges, so a snapshot reachable through any non-optional edge carries `optional: false` and survives the filter — covers [`optionalDependencies.ts:712`](https://github.com/pnpm/pnpm/blob/94240bc046/installing/deps-installer/test/install/optionalDependencies.ts#L712) (`dependency that is both optional and non-optional is installed`).
pull Bot pushed a commit to dwongdev/pnpm that referenced this pull request May 14, 2026
…k.yaml (pnpm#434 slice 6) (pnpm#495)

Pacquet wrote the raw wanted lockfile as the current lockfile.
Upstream pnpm writes a filtered version with optional + skipped
subtrees pruned and `include` flags applied, so the next install
diffs against what was actually materialized rather than the
resolver's full ambition. Without the prune, dropped snapshots
(slice 1 installability, slice 4 fetch failures, slice 5
`--no-optional`) were claimed present in the current lockfile and
the follow-up install would skip work that should have run.

Ports
<https://github.com/pnpm/pnpm/blob/94240bc046/lockfile/filtering/src/filterLockfileByImportersAndEngine.ts#L46-L94>
→
<https://github.com/pnpm/pnpm/blob/94240bc046/installing/deps-restorer/src/index.ts#L687-L695>.

Pacquet-specific simplification: instead of re-running the engine
+ supportedArchitectures + skipped checks at filter time,
`filter_lockfile_for_current` reuses the `SkippedSnapshots` set
already produced by the install pipeline. Its three subsets
(`installability`, `fetch_failed`, `optional_excluded`) are the
exact set upstream's filter would drop — same observable result,
no duplicated walk.

## Changes

- **`pacquet-lockfile`**: `Clone` derives on `Lockfile`,
  `LockfileSettings`, `ProjectSnapshot`, `SnapshotEntry`,
  `PackageMetadata`, `PeerDependencyMeta`, `ResolvedDependencySpec`.
  Needed to build a filtered lockfile by clone-and-mutate.
- **`pacquet-package-manager/src/current_lockfile.rs`** (new):
  `filter_lockfile_for_current(lockfile, included, skipped) -> Lockfile`.
  Three-phase filter:
    1. BFS the snapshot graph from filtered importer roots,
       skipping keys in `skipped`. Produces the reachable set.
    2. Per-importer: clear dep maps whose `include` flag is false;
       trim `optional_dependencies` to entries whose target survived.
    3. `snapshots:` / `packages:` filtered to the reachable set
       (packages key off `without_peer()` so peer-variant
       survivors keep their shared metadata row).
- **`install.rs`**: replace the raw `save_current_to_virtual_store_dir`
  call with `filter_lockfile_for_current(...).save_current_to_virtual_store_dir(...)`.

## Tests

8 unit tests covering each filter behavior:

- `skipped_snapshot_pruned_from_snapshots_and_importer_optional`
- `include_optional_false_clears_importer_section`
- `transitive_under_skipped_snapshot_is_pruned`
- `snapshot_reachable_via_kept_path_survives` (mirrors upstream's
  `:712` shared-dep case at the filter level)
- `packages_filtered_to_surviving_metadata_keys` (peer-variant
  metadata sharing)
- `link_optional_entries_survive_post_filter` (workspace link:
  deps don't get post-filtered)
- `empty_skipped_and_full_include_is_identity_for_reachables`
  (baseline)
- `orphan_snapshots_are_pruned` (snapshots unreachable from any
  importer get dropped)

Test-the-test verified by breaking the BFS walker — two tests
fail.

## Out of scope

- Hoisted-linker current-lockfile variant (`:633`) — pacquet's
  hoisted node-linker isn't fully wired through the lockfile-write
  path yet; tracked separately under pnpm#438.
- `pnpm install --filter` slicing — pacquet has no `--filter` yet.

Closes pnpm#493.
pull Bot pushed a commit to dwongdev/pnpm that referenced this pull request May 14, 2026
…setting check (pnpm#434 slice 7) (pnpm#507)

* feat: ignoredOptionalDependencies config + lockfile field + outdated-setting check (pnpm#434 slice 7)

Last slice of the optional-dependencies umbrella (pnpm#434). Adds the
user-facing `ignoredOptionalDependencies` setting: a list of
dep-name patterns the user wants entirely excluded from
resolution + install.

Mirrors pnpm/pnpm@94240bc046's three surfaces:

- **Hook**: `hooks/read-package-hook/src/createOptionalDependenciesRemover.ts`
  builds a matcher and drops matching keys from
  `optionalDependencies` AND `dependencies` (a package may list
  the same dep under both for "optional only when consumed").
- **Lockfile field**: `lockfile/types/src/index.ts:19` —
  `ignoredOptionalDependencies?: string[]` at the top level
  (sibling of `lockfileVersion`/`overrides`, NOT inside
  `settings`).
- **Drift check**: `lockfile/settings-checker/src/getOutdatedLockfileSetting.ts:58-60`
  sorts both arrays and compares; mismatch triggers
  `needsFullResolution`. In pacquet's frozen-only flow this
  surfaces as `OutdatedLockfile`.

## Changes

- **`pacquet-config`**: `Config::ignored_optional_dependencies:
  Option<Vec<String>>` + `WorkspaceSettings` field + `apply_to`
  wiring.
- **`pacquet-lockfile`**: `Lockfile::ignored_optional_dependencies:
  Option<Vec<String>>` top-level field with serde round-trip;
  `check_lockfile_settings(lockfile, config_set)` sorts-and-
  compares; new `StalenessReason::IgnoredOptionalDependenciesChanged
  { lockfile, config }` variant.
- **`pacquet-lockfile::satisfies_package_manifest`** extended
  with an `is_ignored_optional: &dyn Fn(&str) -> bool` parameter.
  Skips matching names in `flat_manifest_specs` and the per-field
  check so a manifest that still lists ignored entries doesn't
  falsely surface as drift against a lockfile the resolver
  correctly built without them.
- **`pacquet-package-manager::install.rs`**: builds a matcher
  from `Config::ignored_optional_dependencies` (reuses
  `pacquet_config::matcher::create_matcher` — same glob engine as
  `hoistPattern`); calls `check_lockfile_settings` before
  `satisfies_package_manifest`; threads the matcher closure into
  the freshness check.
- **`pacquet-package-manager::current_lockfile`**: preserves the
  lockfile's `ignored_optional_dependencies` through the
  slice 6 filter so the recorded set round-trips to the current
  lockfile.

## Tests

- `pacquet_config`: yaml-parse + `apply_to` round-trip + omission
  baseline.
- `pacquet_lockfile::freshness::check_settings_*`: both-sides-
  empty, sorted-match-regardless-of-order, drift in both
  directions. Test-the-test verified by removing the sort+compare
  guard.
- `pacquet_lockfile::freshness::ignored_optional_filtered_*`:
  manifest-side filter passes when the matcher fires; polarity
  test confirms the unfiltered case surfaces as `SpecifiersDiffer`.
- `pacquet_lockfile::freshness::ignored_optional_dependencies_round_trips_through_yaml`:
  serde wire-shape round-trip.

Closes pnpm#503. Closes the pnpm#434 umbrella.

* fix(lockfile): scope ignoredOptionalDependencies filter to prod+optional + set-based predicate

CodeRabbit review on PR pnpm#507 (major): the filter wrongly applied to
`devDependencies` too. Upstream's
`hooks/read-package-hook/src/createOptionalDependenciesRemover.ts`
iterates `manifest.optionalDependencies` and deletes matches from
`optionalDependencies` AND `dependencies` only — `devDependencies`
is untouched. The previous impl applied the filter to all three
groups in both `flat_manifest_specs` and the per-field check, so a
stale lockfile could incorrectly pass when the manifest added or
removed a matching `devDependency`.

Two fixes:

1. **Group gate** in `flat_manifest_specs` and the per-field check:
   apply the filter only when the group is `Prod` or `Optional`.
   `Dev` walks ignore the closure.

2. **Set-based predicate** at the call site
   (`Install::run`): build the "to drop" set from
   `manifest.optionalDependencies ∩ pattern`, not just from the
   pattern. A name listed only in `dependencies` (not
   `optionalDependencies`) that happens to match the pattern is
   NOT removed by upstream's hook (the hook never iterates that
   name). The set-based predicate captures that nuance.

Both fixes together mirror the hook's exact semantics.

Two new regression tests pin the dev-dependency behavior:
`ignored_optional_does_not_apply_to_dev_dependencies` and
`ignored_optional_dev_only_lockfile_entry_kept`. Test-the-test
verified by dropping the group gate inside `flat_manifest_specs`
— both tests fail.
github-actions Bot pushed a commit to Eyalm321/pnpm that referenced this pull request May 18, 2026
…incompatibles (pnpm#434 slice 1) (pnpm#439)

Slice 1 of pnpm#434 — foundational installability gate. Ports
[`@pnpm/config.package-is-installable`](https://github.com/pnpm/pnpm/tree/94240bc046/config/package-is-installable)
and threads the resulting skip-set through every phase of the
frozen-lockfile install pipeline.

Closes pnpm#266 once shipped (covers the "install respects every snapshot
— no os/cpu/libc filter" gap). Does **not** close pnpm#434 — that umbrella
has six more slices to follow.

Upstream reference: pnpm/pnpm@94240bc046.

## What landed

### New crate: `pacquet-package-is-installable`

Ports the upstream `config/package-is-installable` package's three
helpers:

- `check_platform` (`Option<&[String]>` for each `os`/`cpu`/`libc`
  axis, plus a `SupportedArchitectures` override) — returns
  `Option<UnsupportedPlatformError>` matching upstream's
  `ERR_PNPM_UNSUPPORTED_PLATFORM` code, message shape, and JSON
  payload. Handles negation entries (`!foo`), the `any` sentinel,
  the `current` placeholder, and the `currentLibc !== 'unknown'`
  skip from `checkPlatform.ts:38`.
- `check_engine` — evaluates `engines.node` / `engines.pnpm` via
  `node-semver`. Approximates npm-semver's `includePrerelease: true`
  via a strip-prerelease fallback; one over-acceptance edge case
  (`>=X.Y.Z` against `X.Y.Z-rc1`) is pinned in the known-failures
  integration test for follow-up.
- `package_is_installable` — composes the two, returns the
  tri-state verdict matching upstream's `boolean | null` (Installable
  / SkipOptional / ProceedWithWarning), plus an `Err` arm for
  `engine_strict` aborts and `ERR_PNPM_INVALID_NODE_VERSION`.

`InstallabilityOptions<'a>` borrows its host strings so a caller
running through many snapshots in a row can build the host part
once and only toggle `optional` per snapshot. `WantedPlatformRef<'a>`
plays the same role for the manifest axes so `check_platform` runs
the happy path without any allocation.

### New module: `pacquet_package_manager::installability`

`compute_skipped_snapshots` is the per-install entry point. For each
snapshot:

1. Look up the matching `PackageMetadata`.
2. Run `check_package` (cached per peer-stripped `metadata_key` so
   peer-resolved variants of the same package share one verdict).
3. Dispatch on `(verdict, snapshot.optional, host.engine_strict)`:
   - `Installable`: nothing to do.
   - `SkipOptional` + `optional`: add to `SkippedSnapshots`, emit
     `pnpm:skipped-optional-dependency` (deduped per metadata key,
     matching upstream's emit-per-pkgId).
   - `Incompatible` + non-optional + `engine_strict`: abort.
   - `Incompatible` + non-optional + non-strict: `tracing::warn!`
     and proceed. (Upstream's `pnpm:install-check` channel isn't
     wired into pacquet's reporter yet — slice 1 follow-up.)

`any_installability_constraint(packages)` is the caller-side fast
path: if no metadata row declares an `engines.{node,pnpm}` or a
non-empty / non-`["any"]` `cpu`/`os`/`libc`, the entire installability
pass is skipped. The probe runs synchronously in
`install_frozen_lockfile` *before* the `tokio::task::spawn_blocking`
that would invoke `node --version` — so the common no-constraints
lockfile pays nothing for the new pipeline, restoring main's overlap
of node-binary startup with extraction.

### Install-pipeline plumbing

The `SkippedSnapshots` set is threaded into every downstream phase
of `InstallFrozenLockfile::run`:

- `CreateVirtualStore`: installability-skipped snapshots are
  dropped from both `survivors` (no virtual-store slot extracted)
  and `skipped_entries` (no warm-cache row). Layered ahead of
  main's pnpm#442 already-installed-and-on-disk skip filter.
- `SymlinkDirectDependencies`: a direct dep whose resolved
  snapshot is in the skip set is omitted from `node_modules/<name>`
  (no symlink, no `pnpm:root added` event, no bin link).
- `LinkVirtualStoreBins`: per-slot bin link skips slots whose
  snapshot is installability-skipped (their virtual-store
  directories don't exist).
- `BuildModules` via `build_sequence`: `get_subgraph_to_build`
  consults `skipped` *before* recursion, so a skipped snapshot's
  subtree doesn't contribute to the build graph via that edge.
  Descendants reachable from a non-skipped root still build
  normally.

### Performance

CI integrated-benchmark on the 1352-package fixture, latest run:

| Scenario | `pacquet@HEAD` | `pacquet@main` | Relative |
|---|---|---|---|
| Frozen Lockfile (cold) | 2.476 ± 0.083 s | 2.442 ± 0.071 s | 1.01 ± 0.05 |
| Frozen Lockfile (Hot Cache) | 685.8 ± 59.3 ms | 700.2 ± 47.4 ms | 1.00 |

Earlier iterations of this PR showed a ~5% cold-install regression
from the `node --version` spawn landing on the extraction critical
path. Closed by hoisting the no-constraints fast-path probe to the
caller (commit `cf47ce51`) so the spawn is gated on actual
constraint presence.

Other perf passes folded in:

- `compute_skipped_snapshots` caches the per-metadata-row check
  verdict so peer-resolved variants share one `check_package` call.
- `check_platform` borrows its three wanted axes through
  `WantedPlatformRef<'a>`; the owned `WantedPlatform` only
  materialises in the error path.

## Tests

| Suite | Count | What it covers |
|---|---|---|
| `pacquet-package-is-installable::tests::check_platform` | 16 | Port of upstream `checkPlatform.ts` — `any`/`current` sentinels, negation, `supportedArchitectures` override, libc unknown-skip |
| `pacquet-package-is-installable::tests::check_engine` | 7 | Port of upstream `checkEngine.ts` — node/pnpm range checks, prerelease cases, `ERR_PNPM_INVALID_NODE_VERSION` |
| `pacquet-package-is-installable::tests::package_is_installable` | 6 | Tri-state verdict + optional/engine-strict dispatch |
| `pacquet-package-is-installable::tests::known_failures` | 1 | The `>=X.Y.Z` vs `X.Y.Z-rc1` over-acceptance, picked up by `just known-failures` |
| `pacquet_package_manager::installability::tests` | 11 | Per-install skip-set computation: skip on bad OS, skip on bad node engine, dedup events across peer variants, fast-path triggers, constraint predicate's edge cases (`engines.npm` only, `["any"]` sentinel, empty lists) |
| `pacquet_package_manager::build_sequence::tests` | 3 (new) | Skipped+patched doesn't enter build queue; skipped parent doesn't drag descendants in; descendant with non-skipped parent still builds |

All ported tests verified to catch regressions by temporarily
breaking the subject under test, observing the failure, then
reverting. The "test the tests" workflow from CLAUDE.md.

## Deferred to follow-up slices

- `.modules.yaml.skipped` write/read + headless re-check
  (slice 3).
- `supportedArchitectures` config + `--cpu` / `--os` / `--libc`
  CLI flags (slice 2).
- `pnpm:install-check` warn channel on the reporter side
  (currently `tracing::warn!`).
- Real libc detection — `host_libc()` returns `"unknown"` today;
  matches non-Linux host behavior, but on Alpine/musl this
  over-installs glibc-only optional packages. Slice 2.
- `engine_strict` config wiring — defaults to false today, so
  the error path is unreachable from production. Wired through
  end-to-end so the slice that flips the config doesn't churn
  the error enum.
github-actions Bot pushed a commit to Eyalm321/pnpm that referenced this pull request May 18, 2026
…bc detection (pnpm#434 slice 2) (pnpm#456)

Slice 2 of the [pnpm#434 umbrella](pnpm/pacquet#434) (`Proper optionalDependencies support`). Slice 1 (pnpm#439) wired `pacquet-package-is-installable` into the install pipeline but the `SupportedArchitectures` input was always `None`, so every install behaved as if the user had opted into \"host triple only\". This PR closes that gap and replaces the slice 1 libc stub with a real Linux probe.

Mirrors the three upstream input sources pinned at [pnpm/pnpm@94240bc046](https://github.com/pnpm/pnpm/tree/94240bc046):

- **`Config.supported_architectures` from `pnpm-workspace.yaml`** — same shape as upstream's `getOptionsFromRootManifest.ts` ([`config/reader/src/getOptionsFromRootManifest.ts#L23`](https://github.com/pnpm/pnpm/blob/94240bc046/config/reader/src/getOptionsFromRootManifest.ts#L23)).
- **`--cpu` / `--os` / `--libc` flags on `install` / `add`** — multi-valued, comma-separated. Per-axis CLI override replaces the yaml axis wholesale, matching upstream's [`overrideSupportedArchitecturesWithCLI.ts`](https://github.com/pnpm/pnpm/blob/94240bc046/config/reader/src/overrideSupportedArchitecturesWithCLI.ts).
- **Real Linux libc detection** — replaces the slice 1 `\"unknown\"` stub with a `/lib*/ld-musl-*` probe cached via `OnceLock`. Mirrors `detect-libc.familySync()` semantics: `\"musl\"` / `\"glibc\"` on Linux, `\"unknown\"` everywhere else. No new dep — open-coded `read_dir` is cheaper than spawning `ldd --version` and works in slim containers without `ldd` on PATH.

Threaded through `Install` / `InstallFrozenLockfile` / `Add` as an explicit field instead of mutating `State.config`, because `State.config` is `&'static Config` — the CLI override merge happens at the CLI layer and the fully-resolved value lands on the install pipeline.

## Test plan

Unit tests added; full suite ran via `just ready` + `cargo doc --no-deps` + `taplo format --check` + `just dylint`.

- [x] `cargo test -p pacquet-config workspace_yaml::tests::*supported_architectures*` — yaml round-trip across `os` / `cpu` / `libc`, omission, partial-axis (3 tests).
- [x] `cargo test -p pacquet-package-manager --lib installability::tests::supported_architectures*` — `supportedArchitectures` widens the accept set so a darwin-only package stays on a linux host when the user lists `darwin`; conversely, listing `linux` keeps the darwin-only package skipped (no implicit host include).
- [x] `just ready` — typos + fmt + check + nextest (792 tests pass) + clippy.
- [x] `taplo format --check`.
- [x] `just dylint` (perfectionist).
- [x] `RUSTDOCFLAGS=\"-D warnings\" cargo doc --no-deps --workspace`.

## Out of scope

- `.modules.yaml.skipped` persistence (umbrella slice 3).
- Current-lockfile diffing for `removeOptionalDependenciesThatAreNotUsed` (umbrella slice 6).
- `--no-optional` plumbing (umbrella slice 5).

Closes pnpm#453.
github-actions Bot pushed a commit to Eyalm321/pnpm that referenced this pull request May 18, 2026
…pm#434 slice 3) (pnpm#467)

## Summary

Slice 3 of the [pnpm#434 umbrella](pnpm/pacquet#434) (`Proper optionalDependencies support`). Slice 1 (pnpm#439) computed a `SkippedSnapshots` set on every frozen-lockfile install but never serialized it; slice 2 (pnpm#456) closed the input side. This PR closes the loop by mirroring three upstream sites pinned at [pnpm/pnpm@94240bc046](https://github.com/pnpm/pnpm/tree/94240bc046):

- **Write** — `Modules.skipped` was declared at [`crates/modules-yaml/src/lib.rs:197`](https://github.com/pnpm/pacquet/blob/97010013/crates/modules-yaml/src/lib.rs#L197) with sort-on-write but always serialized empty. `build_modules_manifest` now takes `&SkippedSnapshots` and produces the depPath string list pnpm writes at [`installing/deps-installer/src/install/index.ts:1625`](https://github.com/pnpm/pnpm/blob/94240bc046/installing/deps-installer/src/install/index.ts#L1625).

- **Seed** — `install_frozen_lockfile::run` reads `.modules.yaml.skipped` before computing the in-memory set and passes the parsed `PackageKey`s as the seed to `compute_skipped_snapshots`. Mirrors upstream's load at [`installing/read-projects-context/src/index.ts:79`](https://github.com/pnpm/pnpm/blob/94240bc046/installing/read-projects-context/src/index.ts#L79) plus the early return at [`deps/graph-builder/src/lockfileToDepGraph.ts:194`](https://github.com/pnpm/pnpm/blob/94240bc046/deps/graph-builder/src/lockfileToDepGraph.ts#L194).

- **Short-circuit** — `compute_skipped_snapshots` returns the seed verbatim on the fast path (no constraints in the lockfile) and, on the slow path, skips the per-snapshot re-check for keys already in the seed without emitting a duplicate `pnpm:skipped-optional-dependency` event. Non-seeded snapshots still re-run `package_is_installable`, covering the \"host arch changed since last install\" case upstream guards at [`:206-215`](https://github.com/pnpm/pnpm/blob/94240bc046/deps/graph-builder/src/lockfileToDepGraph.ts#L206-L215).

`InstallFrozenLockfile::run` now returns a small `InstallFrozenLockfileOutput { hoisted_dependencies, skipped }` struct so `Install::run` can thread both into `.modules.yaml`. Read errors on `.modules.yaml` degrade to an empty seed — the file is a cache artifact, not authoritative.
github-actions Bot pushed a commit to Eyalm321/pnpm that referenced this pull request May 18, 2026
…e payload (pnpm#434 slice 4) (pnpm#474)

Slice 4 of the [pnpm#434 umbrella](pnpm/pacquet#434) (`Proper optionalDependencies support`). Slices 1–3 (pnpm#439, pnpm#456, pnpm#467) close the installability-driven skip path; this slice adds the second skip pathway upstream handles in the frozen install: an `optional: true` snapshot whose **fetch / extract** step fails at install time must not abort the install. Local-materialization errors (`CreateVirtualDir`) and config-shape errors still abort even for optional snapshots — matching upstream's `linkPkg` path which sits outside the catch.

Mirrors the silent catch sites at [`deps/graph-builder/src/lockfileToDepGraph.ts:294-298`](https://github.com/pnpm/pnpm/blob/94240bc046/deps/graph-builder/src/lockfileToDepGraph.ts#L294-L298) and [`installing/deps-restorer/src/index.ts:912-921`](https://github.com/pnpm/pnpm/blob/94240bc046/installing/deps-restorer/src/index.ts#L912-L921):

```ts
try { ... fetch ... }
catch (err) { if (pkgSnapshot.optional) return; throw err }
```

## Changes

- **Reporter**: `SkippedOptionalPackage` becomes a `#[serde(untagged)]` enum with `Installed { id, name, version }` and `ResolutionFailure { name?, version?, bareSpecifier }` variants. Models upstream's discriminated union at [`core-loggers/src/skippedOptionalDependencyLogger.ts:10-31`](https://github.com/pnpm/pnpm/blob/94240bc046/core/core-loggers/src/skippedOptionalDependencyLogger.ts#L10-L31). The new variant is wire-shape-only in slice 4 — pacquet has no resolver yet, but a future resolver port at the upstream emit site ([`resolveDependencies.ts:1376-1383`](https://github.com/pnpm/pnpm/blob/94240bc046/installing/deps-resolver/src/resolveDependencies.ts#L1376-L1383)) lands without re-touching this type.
- **`SkippedSnapshots`**: gains a `fetch_failed` subset disjoint from the existing `installability` subset. `contains` / `iter` return the union (downstream consumers treat both as absent); `iter_installability` returns only the persistent subset that `.modules.yaml.skipped` records, matching upstream's behavior of never updating `opts.skipped` at the fetch-failure catch sites.
- **`CreateVirtualStore`**: cold-batch dispatch swallows per-snapshot errors when `snapshot.optional` is true **and** the error is fetch-side. A new `is_fetch_side_failure` helper restricts the swallow to `InstallPackageBySnapshotError::DownloadTarball` and `::GitFetch` — the same surface upstream wraps in `storeController.fetchPackage`. Local-materialization (`CreateVirtualDir`) and config-shape errors (`MissingTarballIntegrity` / `UnsupportedResolution`) propagate even for optional snapshots, matching upstream's `linkPkg` path which sits outside the catch. Side benefit: the warm-batch path (which only surfaces `CreateVirtualDir` errors) stays consistently abort-on-error without a parallel swallow site. The per-install `fetch_failed: HashSet<PackageKey>` rides out on `CreateVirtualStoreOutput`.
- **`InstallFrozenLockfile::run`**: folds the returned `fetch_failed` into its mutable `SkippedSnapshots` so downstream phases (`SymlinkDirectDependencies`, `LinkVirtualStoreBins`, `BuildModules`, hoist) treat the dropped snapshots as absent through the same gate they already use for installability skips.

## Test plan

- [x] `reporter::tests::skipped_optional_resolution_failure_event_matches_pnpm_wire_shape` — full payload (`bareSpecifier` camelCase, no `id`).
- [x] `reporter::tests::skipped_optional_resolution_failure_omits_absent_name_and_version` — optional-field shape.
- [x] `install::tests::frozen_install_silently_swallows_unreachable_optional_tarball` — ports [`installing/deps-restorer/test/index.ts:340-360`](https://github.com/pnpm/pnpm/blob/94240bc046/installing/deps-restorer/test/index.ts#L340-L360): unreachable optional tarball (`http://127.0.0.1:1/...`), install succeeds, slot not created, `.modules.yaml.skipped` left empty. **Test-the-test verified** by inverting the `optional` guard in the cold-batch match arm. Runs with `enable_global_virtual_store = false` so the slot-path assertion targets the legacy layout.
- [x] `install::tests::frozen_install_propagates_non_optional_fetch_failure` — pins the polarity: same fixture, non-optional snapshot, install must error.
- [x] `just ready` (typos + fmt + check + nextest + clippy).
- [x] `taplo format --check`.
- [x] `just dylint` (perfectionist).
- [x] `RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --workspace`.

## Out of scope

- Resolver-side `resolution_failure` emit site — needs a resolver, tracked separately.
- `--no-optional` / `--omit=optional` plumbing — umbrella slice 5.
- Surfacing fetch failures to the reporter on the frozen path — upstream itself is silent here; only the resolver-side emit fires `pnpm:skipped-optional-dependency`.

Closes pnpm#471.
github-actions Bot pushed a commit to Eyalm321/pnpm that referenced this pull request May 18, 2026
…e 5) (pnpm#485)

Slice 5 of the [pnpm#434 umbrella](pnpm/pacquet#434) (`Proper optionalDependencies support`). Slices 1–4 (pnpm#439, pnpm#456, pnpm#467, pnpm#474) handle installability + the fetch-failure swallow. This slice closes the user-facing `--no-optional` flag: the CLI flag already exists and threads through `Install::dependency_groups`, but only `SymlinkDirectDependencies` and the on-disk `included` field actually honored it. The rest of the install pipeline walked `optional_dependencies` unconditionally, so the optional subtree was still extracted, linked, and built.

Mirrors upstream's depNode filter at [`installing/deps-installer/src/install/link.ts:109-111`](https://github.com/pnpm/pnpm/blob/94240bc046/installing/deps-installer/src/install/link.ts#L109-L111):

```ts
if (!opts.include.optionalDependencies) {
  depNodes = depNodes.filter(({ optional }) => !optional)
}
```

## Changes

- **`SkippedSnapshots`**: gains a third disjoint subset `optional_excluded` alongside `installability` (slice 1) and `fetch_failed` (slice 4). The existing `contains` / `iter` already return the union, so every downstream consumer that filters by skip-set (`CreateVirtualStore`, `SymlinkDirectDependencies`, `BuildModules`, hoist, `link_bins`) now respects `--no-optional` through the same gate. `iter_installability` still returns only the persistent subset for `.modules.yaml.skipped` writes — `--no-optional` exclusions are transient (matching upstream's behavior of never updating `opts.skipped` at the filter site).
- **`InstallFrozenLockfile::run`**: iterates the lockfile snapshots once and inserts every `snap.optional == true` entry into the new subset when the dispatch list doesn't contain `DependencyGroup::Optional`. The `SnapshotEntry::optional` flag is set by the resolver only when the snapshot is reachable **exclusively** through optional edges, so a snapshot reachable through any non-optional edge carries `optional: false` and survives the filter — covers [`optionalDependencies.ts:712`](https://github.com/pnpm/pnpm/blob/94240bc046/installing/deps-installer/test/install/optionalDependencies.ts#L712) (`dependency that is both optional and non-optional is installed`).
github-actions Bot pushed a commit to Eyalm321/pnpm that referenced this pull request May 18, 2026
…k.yaml (pnpm#434 slice 6) (pnpm#495)

Pacquet wrote the raw wanted lockfile as the current lockfile.
Upstream pnpm writes a filtered version with optional + skipped
subtrees pruned and `include` flags applied, so the next install
diffs against what was actually materialized rather than the
resolver's full ambition. Without the prune, dropped snapshots
(slice 1 installability, slice 4 fetch failures, slice 5
`--no-optional`) were claimed present in the current lockfile and
the follow-up install would skip work that should have run.

Ports
<https://github.com/pnpm/pnpm/blob/94240bc046/lockfile/filtering/src/filterLockfileByImportersAndEngine.ts#L46-L94>
→
<https://github.com/pnpm/pnpm/blob/94240bc046/installing/deps-restorer/src/index.ts#L687-L695>.

Pacquet-specific simplification: instead of re-running the engine
+ supportedArchitectures + skipped checks at filter time,
`filter_lockfile_for_current` reuses the `SkippedSnapshots` set
already produced by the install pipeline. Its three subsets
(`installability`, `fetch_failed`, `optional_excluded`) are the
exact set upstream's filter would drop — same observable result,
no duplicated walk.

## Changes

- **`pacquet-lockfile`**: `Clone` derives on `Lockfile`,
  `LockfileSettings`, `ProjectSnapshot`, `SnapshotEntry`,
  `PackageMetadata`, `PeerDependencyMeta`, `ResolvedDependencySpec`.
  Needed to build a filtered lockfile by clone-and-mutate.
- **`pacquet-package-manager/src/current_lockfile.rs`** (new):
  `filter_lockfile_for_current(lockfile, included, skipped) -> Lockfile`.
  Three-phase filter:
    1. BFS the snapshot graph from filtered importer roots,
       skipping keys in `skipped`. Produces the reachable set.
    2. Per-importer: clear dep maps whose `include` flag is false;
       trim `optional_dependencies` to entries whose target survived.
    3. `snapshots:` / `packages:` filtered to the reachable set
       (packages key off `without_peer()` so peer-variant
       survivors keep their shared metadata row).
- **`install.rs`**: replace the raw `save_current_to_virtual_store_dir`
  call with `filter_lockfile_for_current(...).save_current_to_virtual_store_dir(...)`.

## Tests

8 unit tests covering each filter behavior:

- `skipped_snapshot_pruned_from_snapshots_and_importer_optional`
- `include_optional_false_clears_importer_section`
- `transitive_under_skipped_snapshot_is_pruned`
- `snapshot_reachable_via_kept_path_survives` (mirrors upstream's
  `:712` shared-dep case at the filter level)
- `packages_filtered_to_surviving_metadata_keys` (peer-variant
  metadata sharing)
- `link_optional_entries_survive_post_filter` (workspace link:
  deps don't get post-filtered)
- `empty_skipped_and_full_include_is_identity_for_reachables`
  (baseline)
- `orphan_snapshots_are_pruned` (snapshots unreachable from any
  importer get dropped)

Test-the-test verified by breaking the BFS walker — two tests
fail.

## Out of scope

- Hoisted-linker current-lockfile variant (`:633`) — pacquet's
  hoisted node-linker isn't fully wired through the lockfile-write
  path yet; tracked separately under pnpm#438.
- `pnpm install --filter` slicing — pacquet has no `--filter` yet.

Closes pnpm#493.
github-actions Bot pushed a commit to Eyalm321/pnpm that referenced this pull request May 18, 2026
…setting check (pnpm#434 slice 7) (pnpm#507)

* feat: ignoredOptionalDependencies config + lockfile field + outdated-setting check (pnpm#434 slice 7)

Last slice of the optional-dependencies umbrella (pnpm#434). Adds the
user-facing `ignoredOptionalDependencies` setting: a list of
dep-name patterns the user wants entirely excluded from
resolution + install.

Mirrors pnpm/pnpm@94240bc046's three surfaces:

- **Hook**: `hooks/read-package-hook/src/createOptionalDependenciesRemover.ts`
  builds a matcher and drops matching keys from
  `optionalDependencies` AND `dependencies` (a package may list
  the same dep under both for "optional only when consumed").
- **Lockfile field**: `lockfile/types/src/index.ts:19` —
  `ignoredOptionalDependencies?: string[]` at the top level
  (sibling of `lockfileVersion`/`overrides`, NOT inside
  `settings`).
- **Drift check**: `lockfile/settings-checker/src/getOutdatedLockfileSetting.ts:58-60`
  sorts both arrays and compares; mismatch triggers
  `needsFullResolution`. In pacquet's frozen-only flow this
  surfaces as `OutdatedLockfile`.

## Changes

- **`pacquet-config`**: `Config::ignored_optional_dependencies:
  Option<Vec<String>>` + `WorkspaceSettings` field + `apply_to`
  wiring.
- **`pacquet-lockfile`**: `Lockfile::ignored_optional_dependencies:
  Option<Vec<String>>` top-level field with serde round-trip;
  `check_lockfile_settings(lockfile, config_set)` sorts-and-
  compares; new `StalenessReason::IgnoredOptionalDependenciesChanged
  { lockfile, config }` variant.
- **`pacquet-lockfile::satisfies_package_manifest`** extended
  with an `is_ignored_optional: &dyn Fn(&str) -> bool` parameter.
  Skips matching names in `flat_manifest_specs` and the per-field
  check so a manifest that still lists ignored entries doesn't
  falsely surface as drift against a lockfile the resolver
  correctly built without them.
- **`pacquet-package-manager::install.rs`**: builds a matcher
  from `Config::ignored_optional_dependencies` (reuses
  `pacquet_config::matcher::create_matcher` — same glob engine as
  `hoistPattern`); calls `check_lockfile_settings` before
  `satisfies_package_manifest`; threads the matcher closure into
  the freshness check.
- **`pacquet-package-manager::current_lockfile`**: preserves the
  lockfile's `ignored_optional_dependencies` through the
  slice 6 filter so the recorded set round-trips to the current
  lockfile.

## Tests

- `pacquet_config`: yaml-parse + `apply_to` round-trip + omission
  baseline.
- `pacquet_lockfile::freshness::check_settings_*`: both-sides-
  empty, sorted-match-regardless-of-order, drift in both
  directions. Test-the-test verified by removing the sort+compare
  guard.
- `pacquet_lockfile::freshness::ignored_optional_filtered_*`:
  manifest-side filter passes when the matcher fires; polarity
  test confirms the unfiltered case surfaces as `SpecifiersDiffer`.
- `pacquet_lockfile::freshness::ignored_optional_dependencies_round_trips_through_yaml`:
  serde wire-shape round-trip.

Closes pnpm#503. Closes the pnpm#434 umbrella.

* fix(lockfile): scope ignoredOptionalDependencies filter to prod+optional + set-based predicate

CodeRabbit review on PR pnpm#507 (major): the filter wrongly applied to
`devDependencies` too. Upstream's
`hooks/read-package-hook/src/createOptionalDependenciesRemover.ts`
iterates `manifest.optionalDependencies` and deletes matches from
`optionalDependencies` AND `dependencies` only — `devDependencies`
is untouched. The previous impl applied the filter to all three
groups in both `flat_manifest_specs` and the per-field check, so a
stale lockfile could incorrectly pass when the manifest added or
removed a matching `devDependency`.

Two fixes:

1. **Group gate** in `flat_manifest_specs` and the per-field check:
   apply the filter only when the group is `Prod` or `Optional`.
   `Dev` walks ignore the closure.

2. **Set-based predicate** at the call site
   (`Install::run`): build the "to drop" set from
   `manifest.optionalDependencies ∩ pattern`, not just from the
   pattern. A name listed only in `dependencies` (not
   `optionalDependencies`) that happens to match the pattern is
   NOT removed by upstream's hook (the hook never iterates that
   name). The set-based predicate captures that nuance.

Both fixes together mirror the hook's exact semantics.

Two new regression tests pin the dev-dependency behavior:
`ignored_optional_does_not_apply_to_dev_dependencies` and
`ignored_optional_dev_only_lockfile_entry_kept`. Test-the-test
verified by dropping the group gate inside `flat_manifest_specs`
— both tests fail.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants