Proposal
Let users opt into the pacquet Rust engine for pnpm install --frozen-lockfile by declaring it as a configDependencies entry in pnpm-workspace.yaml. The pnpm CLI auto-detects the presence of pacquet and delegates the install to its native binary; absent the entry, behavior is unchanged.
# pnpm-workspace.yaml
configDependencies:
pacquet: "^0.1.0"
Why --frozen-lockfile is the right entry point
Frozen install skips dependency resolution and just materializes what the lockfile already pins. Resolution is the most complex (and still TS-only) part of the codebase; pacquet today implements install but not resolution, so the two surfaces line up exactly. Users get the perf win on the install path that dominates CI time, without us having to wait for pacquet's resolver to reach parity.
Why configDependencies over alternatives
We considered three shipping models:
- Bundle pacquet binaries into the
pnpm package via optionalDependencies (esbuild/swc/turbo pattern). Zero opt-in, but adds 5–15 MB to every install regardless of whether the user wants it, and couples pacquet's release cadence to pnpm's.
- Lazy download into the store on first frozen install. Smallest default footprint, but introduces a first-run network/auth/integrity step that's awkward in CI.
configDependencies opt-in (this proposal). Per-project, version-pinned with integrity, rolls back automatically if a team removes it, no global state, no flag flipping.
Bootstrap order works out: pnpm already resolves configDependencies before the main install runs, so the small config-deps install (just pacquet + its platform binary) is handled by the JS installer, then the main install is handed off to pacquet.
Sketch of the integration
- After config deps are resolved, the install command checks
context.configDependencies for pacquet.
- If present and
--frozen-lockfile is set and no incompatible features are in play (see "Parity gating" below), spawn the pacquet binary with the resolved config; otherwise fall through to the JS installer.
- pacquet emits the same JSON log events that
@pnpm/cli.default-reporter already parses, so progress UI is preserved.
Things to design carefully
- Version compatibility. A project might pin an old pacquet against a new pnpm or vice versa. pacquet's
package.json should declare a pnpm-cli-compat range; on mismatch, fall back to the JS installer with a one-line warning rather than failing.
- Platform binary delivery. pacquet itself should use the
optionalDependencies trick internally (pacquet-darwin-arm64, pacquet-linux-x64, etc.) so a single config-dep entry works across a team's machines.
- Parity gating. Until parity is proven for hooks, patches, side-effects-cache, lifecycle scripts, and any other corners, gate delegation behind a feature-detection check and fall back to JS for unsupported scenarios. A
usePacquet: false escape hatch in pnpm-workspace.yaml lets users disable it without removing the config dep.
- Discovery / UX. Users have to know to add it. A docs page plus a one-time "tip: install pacquet for faster frozen installs" hint after a slow
--frozen-lockfile would help adoption without forcing it.
Out of scope (for now)
- Delegating non-frozen
pnpm install (needs pacquet to ship resolution).
- Delegating
update, add, remove, and other commands pacquet hasn't ported yet.
- Replacing the JS installer outright.
Written by an agent (Claude Code, claude-opus-4-7).
Proposal
Let users opt into the pacquet Rust engine for
pnpm install --frozen-lockfileby declaring it as aconfigDependenciesentry inpnpm-workspace.yaml. The pnpm CLI auto-detects the presence of pacquet and delegates the install to its native binary; absent the entry, behavior is unchanged.Why
--frozen-lockfileis the right entry pointFrozen install skips dependency resolution and just materializes what the lockfile already pins. Resolution is the most complex (and still TS-only) part of the codebase; pacquet today implements
installbut not resolution, so the two surfaces line up exactly. Users get the perf win on the install path that dominates CI time, without us having to wait for pacquet's resolver to reach parity.Why
configDependenciesover alternativesWe considered three shipping models:
pnpmpackage viaoptionalDependencies(esbuild/swc/turbo pattern). Zero opt-in, but adds 5–15 MB to every install regardless of whether the user wants it, and couples pacquet's release cadence to pnpm's.configDependenciesopt-in (this proposal). Per-project, version-pinned with integrity, rolls back automatically if a team removes it, no global state, no flag flipping.Bootstrap order works out: pnpm already resolves
configDependenciesbefore the main install runs, so the small config-deps install (just pacquet + its platform binary) is handled by the JS installer, then the main install is handed off to pacquet.Sketch of the integration
context.configDependenciesforpacquet.--frozen-lockfileis set and no incompatible features are in play (see "Parity gating" below), spawn the pacquet binary with the resolved config; otherwise fall through to the JS installer.@pnpm/cli.default-reporteralready parses, so progress UI is preserved.Things to design carefully
package.jsonshould declare apnpm-cli-compatrange; on mismatch, fall back to the JS installer with a one-line warning rather than failing.optionalDependenciestrick internally (pacquet-darwin-arm64,pacquet-linux-x64, etc.) so a single config-dep entry works across a team's machines.usePacquet: falseescape hatch inpnpm-workspace.yamllets users disable it without removing the config dep.--frozen-lockfilewould help adoption without forcing it.Out of scope (for now)
pnpm install(needs pacquet to ship resolution).update,add,remove, and other commands pacquet hasn't ported yet.Written by an agent (Claude Code, claude-opus-4-7).