Files
pnpm/installing
Zoltan Kochan d976edf4ec perf: content-check modified manifests and fall back to the current lockfile on the repeat-install fast path (pacquet + pnpm) (#12315)
## Why

On [benchmarks.vlt.sh](https://benchmarks.vlt.sh/) (2026-06-10 run, pacquet 0.11.2), pacquet ranked **8th–9th of 10** in every `lockfile+node_modules` variation — slower than pnpm, npm, yarn and vlt — e.g. astro: pacquet 936 ms vs pnpm 502 ms; babylon: pacquet 9.08 s vs pnpm 0.85 s. It also trailed vlt/npm in the `node_modules` and `cache+node_modules` variations (astro 1.5 s / 0.7 s, babylon 8.9 s / 6.4 s).

### Root cause

Tracing the actual runner (a `pacquet` PATH shim logging per-invocation file stats) showed the harness's prepare step rewrites `package.json` with **identical content but a fresh mtime** before every timed run, while `clean_all_cache` wipes `~/.cache/pnpm` (the packument cache and `lockfile-verified.jsonl`), and the `node_modules` variations additionally delete `pnpm-lock.yaml`.

- **pnpm**: `checkDepsStatus`'s modified-manifests branch re-checks the *content* against the lockfile (`assertWantedLockfileUpToDate`, `assertLockfilesEqual`, `linkedPackagesAreUpToDate`) and reports "Already up to date" with zero network — ~0.5 s is just Node startup. Verified locally: with all caches wiped and the network blocked, `pnpm install` still prints "Already up to date" in 228 ms.
- **pacquet**: the optimistic repeat-install check bailed on *any* newer manifest mtime, fell into the full pipeline, and the awaited `minimumReleaseAge` lockfile-verification gate — its verdict cache wiped — re-fetched **one packument per locked package** per run: 0.94 s on astro, 9.1 s on babylon.
- With `pnpm-lock.yaml` deleted, both stacks pay a similar fan-out on the synthesized-from-current lockfile (`tryLockfileVerificationCache` bails before the content-hash index when the lockfile file can't be stat'd), which is why even pnpm needs 2.2–11.6 s there.

## What

**Commit 1 — port the modified-manifests branch of `checkDepsStatus`** (at pnpm/pnpm@cc4ff817aa) into `optimistic_repeat_install`:

- a manifest whose mtime is newer than `lastValidatedTimestamp` is re-checked against the wanted lockfile instead of invalidating the fast path: lockfile-settings drift (`getOutdatedLockfileSetting`), per-importer `satisfiesPackageManifest`, and a port of `linkedPackagesAreUpToDate` for workspace links (`isLocalFileDepUpdated` for `file:` directory specifiers is not ported — those conservatively fall through to the full install);
- `assertLockfilesEqual` runs when the wanted lockfile is newer than the reference (workspace: `lastValidatedTimestamp`; single-project: the current lockfile's mtime, mirroring upstream's branch shapes);
- the workspace branch refreshes `lastValidatedTimestamp` after a passing content check, like upstream's `updateWorkspaceState` call;
- the frozen-dispatch freshness gate is split into reusable pieces (`parse_config_overrides`, `check_lockfile_settings_drift`, `check_importer_satisfies`) shared with the new check, and the per-importer slice is no longer hard-wired to the root importer.

**Commit 2 — treat the current lockfile as the wanted one when `pnpm-lock.yaml` is missing (pacquet)** (requested by @zkochan): when `node_modules` is intact, `<virtual_store_dir>/lock.yaml` — the record of what the previous install materialized — stands in as the wanted lockfile for the same content checks, and `pnpm-lock.yaml` is regenerated from it (byte-identical to what the full install's synthesize-from-current path would write) before the fast path reports "Already up to date". Single-project installs with no lockfile on either side still refuse the fast path; `lockfile: false` skips the regeneration; a manifest that no longer matches (e.g. `pacquet add`) still takes the full resolve.

## Validation

Re-ran the actual vlt.sh harness (same scripts, ubuntu-24.04-arm runner) with the patched binary swapped into the npm-installed pacquet; all hyperfine runs exited 0:

| fixture, variation | pacquet 0.11.2 (official run) | patched | pnpm (same validation run) |
|---|---|---|---|
| astro, `lockfile+node_modules` | 935.6 ms (rank 9/10) | **38–39 ms** | 599–621 ms |
| babylon, `lockfile+node_modules` | 9 084 ms (rank 8/10) | **86.6 ms ± 0.6** | 767.7 ms |
| astro, `node_modules` | 1 501 ms (rank 4/10) | **41.2 ms ± 0.8** | 2 226 ms |
| astro, `cache+node_modules` | 704 ms (rank 5/10) | **42.9 ms ± 0.9** | 2 017 ms |
| babylon, `node_modules` | 8 962 ms (rank 6/10) | **107.8 ms ± 1.0** | 11 566 ms |

After this change only aube (~5 ms) and bun (~8 ms) stay ahead in these five variations.

`cargo nextest run -p pacquet-package-manager` (438 tests), `-p pacquet-cli` install suites, workspace clippy `-D warnings`, dylint, fmt, taplo and `typos pacquet` are clean. New tests cover the touched-but-identical manifest, a manifest that adds a dependency, a diverged wanted-vs-current lockfile, the state-timestamp refresh, linked siblings inside/outside the manifest range, lockfile regeneration (modified and unmodified manifests, workspace state bump), and `lockfile: false`. Two offline e2e tests additionally pin the "zero network, zero pipeline" property through `Install::run`'s real dispatch: a real install, registry dropped, caches wiped, repeat install pointed at a dead port — both verified discriminating by temporarily disabling the content check.

Two existing tests were adjusted: `fresh_install_records_lockfile_verification_for_mtime_bypassed_noop` now disables the optimistic check explicitly so it keeps guarding the verification-cache wiring it was written for, and `optimistic_repeat_install_does_not_short_circuit_when_lockfile_missing` now passes `lockfile: None` (matching the CLI contract for a missing file) and documents that the guard requires *both* lockfiles to be absent.

**Commit `1ee88c5107` — the same fallback in the pnpm CLI** (`@pnpm/deps.status` + `@pnpm/installing.commands`): `checkDepsStatus` lets the current lockfile stand in when `pnpm-lock.yaml` is missing (workspace shared-lockfile branch and single-project branch), runs the same content checks against it, and returns it as `wantedLockfileToRestore`; `installDeps` writes `pnpm-lock.yaml` back from it before reporting "Already up to date". Guard rails: no lockfile on either side still refuses the fast path, `useLockfile: false` skips the restore, a failed restore falls through to the full install, and the stand-in is disabled under `useGitBranchLockfile` (there a missing plain `pnpm-lock.yaml` is the steady state and the branch lockfile may legitimately differ from the current one). Verified with the bundled CLI: install → delete `pnpm-lock.yaml` → `pnpm install --registry=http://127.0.0.1:9/` prints "Already up to date" in 29 ms and restores the lockfile byte-identically. Covered by 5 new `checkDepsStatus` unit tests and an `installing/commands` integration test that runs the repeat install against a dead registry. Changeset bumps `@pnpm/deps.status`, `@pnpm/installing.commands`, and `pnpm` (minor).
2026-06-10 21:24:47 +02:00
..
2026-06-10 12:40:29 +02:00
2026-06-10 12:40:29 +02:00
2026-06-10 12:40:29 +02:00
2026-06-10 12:40:29 +02:00