Files
pnpm/.changeset/napi-install-engine-perf.md
T
Zoltan Kochan 0f1c46762e fix(napi): deterministic overrides, hook-free install options, and large-workspace install fixes (#13492)
The napi install options deserialized `overrides` into a HashMap, so
the JS object's key order was lost and pnpm-lock.yaml#overrides was
rewritten in a random order on every install driven through the addon
(the freshness comparison is order-insensitive, so this churned the
file without invalidating it). Deserialize into an IndexMap via napi's
object_indexmap feature - matching the order the TypeScript engine and
the pacquet CLI record - and collect the getPeerDependencyIssues JSON
path through the same order-preserving shape.

Also wire pacquet_diagnostics::enable_tracing_by_env into the addon's
module init so the TRACE env var works for napi consumers like it does
for the CLI, and log the staleness reason when a preferred-frozen
install falls through to a fresh resolve - both were needed to diagnose
a Bit workspace that re-resolved on every install.

Batch the readPackage hook dispatch (per-manifest threadsafe calls cost
roughly one event-loop tick each, serializing large resolutions), expose
dedupePeers in the install options (its absence made the freshness gate
treat every Bit lockfile as outdated), and try pick_package's read-only
mirror fast paths before the per-name fetch semaphore so version-pinned
picks stop queueing behind concurrent refreshes of the same package.

Resolve workspace importers concurrently - children-ownership claims
are rank-ordered, so the result is arrival-order independent - which
absorbs the JS hook round-trip latency on large workspaces.

Add projects[].dependencyManifest so hosts can express their readPackage
hook logic engine-side with zero JS round trips (per-manifest deletions
use the existing overrides removal syntax; neverBuiltDependencies stays
rejected in favor of allowBuilds), and apply overrides after the
readPackage hook to match the TypeScript engine's createReadPackageHook
order - a hook that replaced a manifest wholesale (raw-manifest
substitution for injected workspace instances) previously erased the
overrides from that manifest.

Stop repeating the release-age abbreviated->full metadata upgrade fetch
per dependency edge: coalesce it on a per-document permit and remember a
304 Not Modified for the rest of the install, and share the resolver's
workspace-packages map behind an Arc instead of deep-copying every
project manifest on each ResolveOptions clone. Full resolution of a
345-importer workspace: 105 s -> 36 s.

Cut the peer walker's per-node map churn: share ParentRefs behind Arc
copy-on-write, build the collision overlay lazily, and reuse the
per-child parent-package snapshots when the context is unchanged
(peer-heavy 331-importer benchmark: 3.95 s -> 2.78 s).

Share the run-resolved preferred-versions fold across importers: each
importer replayed the whole workspace history into a private map every
hoist round; the fold now lives once on the workspace context and the
hoist call sites materialize merged buckets for just the names they
query (full-workspace benchmark: 886 ms -> 424 ms).
2026-07-30 10:10:39 +02:00

506 B

pacquet
pacquet
patch

Installs driven through @pnpm/napi got three fixes for large workspaces: the readPackage hook is now dispatched to JavaScript in batches instead of one event-loop roundtrip per manifest, the dedupePeers setting can be passed through the install options (so an existing lockfile generated with it is no longer treated as outdated), and version-pinned dependencies are served from the metadata mirror without queueing behind concurrent registry refreshes of the same package.