Background
PR #12044 adds Tier 4 pnpmfile hooks to pacquet (the Rust port). It landed the two hooks that affect resolution output and the lockfile, plus the infrastructure to make them efficient:
readPackage — wired into resolution with pnpm's requirePnpmfile contract (defaults the four dependency fields to {} before the hook; validates the returned manifest; a throwing/invalid hook or a hook that returns nothing aborts the install with PNPMFILE_FAIL).
afterAllResolved — wired into the lockfile write; the lockfile round-trips through serde_json::Value (workspace preserve_order) so hook-added keys survive to disk; a throwing hook aborts the install.
preResolution — wired (one-shot node invocation with an info/warn logger).
- Persistent Node worker — replaces the fresh
node -e process per hook call with one long-lived worker per pnpmfile, multiplexing concurrent readPackage calls over a newline-delimited JSON protocol and forwarding each context.log(...) back to the call's HookContext.
Ported pnpm tests: readPackage validation/normalization + install-level pinning/failure, afterAllResolved mutation/failure, worker concurrency + log forwarding.
This issue tracks the pnpmfile-hook behavior that pnpm has but pacquet does not yet implement, so the remaining upstream hook tests can be ported.
Remaining work
1. pnpm:hook log channel
The worker already forwards context.log(...) to the call's HookContext, but at the readPackage call site (resolve_dependency_tree) and the afterAllResolved call site the log closure is a no-op. Wire a LogEvent::Hook reporter variant (payload parity with pnpm's hookLogger: name: 'pnpm:hook', prefix, from, hook, message) and thread a reporter-backed log closure into the hook call sites. Note: resolve_dependency_tree is not currently generic over Reporter, so this needs plumbing.
Unblocks porting: "pnpmfile: pass log function to readPackage hook", "...of global and local pnpmfile", "pnpmfile: run afterAllResolved hook", "...run async afterAllResolved hook".
2. filterLog wired into the reporter
filter_log is implemented in the worker but never called in the install path. Reporter::emit is a stateless, synchronous static method, so running an async worker hook from it requires the reporter to carry the configured hook(s) — a reporter redesign. Until then, log filtering is a no-op.
Unblocks porting: "filterLog hook filters peer dependency warning", "filterLog hook combines with the global hook".
3. pnpmfile-location CLI flags: --ignore-pnpmfile, --pnpmfile, --global-pnpmfile
All three are pure CLI flags (in upstream's excludedPnpmKeys), so they thread from clap into the install path rather than living in Config. Each needs a new field on the Install struct (~68 construction sites across the crate, mostly tests) plus clap threading across install/add/remove/update, and the install path must skip / redirect finder::load_pnpmfile accordingly.
Unblocks porting: "readPackage hook from custom location", "...from global pnpmfile", "...from global pnpmfile and local pnpmfile" (+ async), "ignore .pnpmfile.cjs when --ignore-pnpmfile is used" (+ during update).
4. Multiple / global pnpmfiles
pnpm loads a global pnpmfile plus the default plus any --pnpmfile entries, and combines array hooks (readPackage hooks run in sequence; filterLog hooks are AND-ed). pacquet currently discovers a single default pnpmfile. Needs the finder to return an ordered list and the bridge/worker to run them in sequence.
Unblocks porting: "readPackage hooks array", "loading multiple pnpmfiles", "filterLog hook combines with the global hook", "pass readPackage with shared lockfile" (workspace).
5. pnpmfile checksum
pnpm records a pnpmfile checksum in workspace state and re-runs hooks when it changes (adding or changing pnpmfile should change pnpmfileChecksum and module structure). pacquet's workspace-state has no pnpmfile-checksum field, so changing a pnpmfile does not invalidate an up-to-date install.
6. updateConfig hook
Not implemented. pnpm runs updateConfig(config) from the pnpmfile and errors if it returns undefined ("updateConfig throws an error if it returns undefined").
7. Finders / resolvers / fetchers
Not implemented (finders, resolvers, fetchers exports). pnpm merges finders across pnpmfiles and errors on duplicate finder names ("requireHooks merges all the finders", "...two finders with the same name").
Notes
- Each item should be its own PR with the corresponding upstream tests ported (see
pnpm/test/install/hooks.ts, pnpm/test/hooks.ts, installing/deps-installer/test/install/hooks.ts, hooks/pnpmfile/test/index.ts).
- Suggested order: (1)
pnpm:hook channel and (3) --ignore-pnpmfile are the most self-contained; (2) filterLog and (4) multiple pnpmfiles are the larger architectural pieces.
Written by an agent (Claude Code, claude-opus-4-8).
Background
PR #12044 adds Tier 4 pnpmfile hooks to pacquet (the Rust port). It landed the two hooks that affect resolution output and the lockfile, plus the infrastructure to make them efficient:
readPackage— wired into resolution with pnpm'srequirePnpmfilecontract (defaults the four dependency fields to{}before the hook; validates the returned manifest; a throwing/invalid hook or a hook that returns nothing aborts the install withPNPMFILE_FAIL).afterAllResolved— wired into the lockfile write; the lockfile round-trips throughserde_json::Value(workspacepreserve_order) so hook-added keys survive to disk; a throwing hook aborts the install.preResolution— wired (one-shotnodeinvocation with aninfo/warnlogger).node -eprocess per hook call with one long-lived worker per pnpmfile, multiplexing concurrentreadPackagecalls over a newline-delimited JSON protocol and forwarding eachcontext.log(...)back to the call'sHookContext.Ported pnpm tests: readPackage validation/normalization + install-level pinning/failure, afterAllResolved mutation/failure, worker concurrency + log forwarding.
This issue tracks the pnpmfile-hook behavior that pnpm has but pacquet does not yet implement, so the remaining upstream hook tests can be ported.
Remaining work
1.
pnpm:hooklog channelThe worker already forwards
context.log(...)to the call'sHookContext, but at thereadPackagecall site (resolve_dependency_tree) and theafterAllResolvedcall site the log closure is a no-op. Wire aLogEvent::Hookreporter variant (payload parity with pnpm'shookLogger:name: 'pnpm:hook',prefix,from,hook,message) and thread a reporter-backed log closure into the hook call sites. Note:resolve_dependency_treeis not currently generic overReporter, so this needs plumbing.Unblocks porting: "pnpmfile: pass log function to readPackage hook", "...of global and local pnpmfile", "pnpmfile: run afterAllResolved hook", "...run async afterAllResolved hook".
2.
filterLogwired into the reporterfilter_logis implemented in the worker but never called in the install path.Reporter::emitis a stateless, synchronous static method, so running an async worker hook from it requires the reporter to carry the configured hook(s) — a reporter redesign. Until then, log filtering is a no-op.Unblocks porting: "filterLog hook filters peer dependency warning", "filterLog hook combines with the global hook".
3. pnpmfile-location CLI flags:
--ignore-pnpmfile,--pnpmfile,--global-pnpmfileAll three are pure CLI flags (in upstream's
excludedPnpmKeys), so they thread from clap into the install path rather than living inConfig. Each needs a new field on theInstallstruct (~68 construction sites across the crate, mostly tests) plus clap threading acrossinstall/add/remove/update, and the install path must skip / redirectfinder::load_pnpmfileaccordingly.Unblocks porting: "readPackage hook from custom location", "...from global pnpmfile", "...from global pnpmfile and local pnpmfile" (+ async), "ignore .pnpmfile.cjs when --ignore-pnpmfile is used" (+ during update).
4. Multiple / global pnpmfiles
pnpm loads a global pnpmfile plus the default plus any
--pnpmfileentries, and combines array hooks (readPackagehooks run in sequence;filterLoghooks are AND-ed). pacquet currently discovers a single default pnpmfile. Needs the finder to return an ordered list and the bridge/worker to run them in sequence.Unblocks porting: "readPackage hooks array", "loading multiple pnpmfiles", "filterLog hook combines with the global hook", "pass readPackage with shared lockfile" (workspace).
5. pnpmfile checksum
pnpm records a pnpmfile checksum in workspace state and re-runs hooks when it changes (
adding or changing pnpmfile should change pnpmfileChecksum and module structure). pacquet's workspace-state has no pnpmfile-checksum field, so changing a pnpmfile does not invalidate an up-to-date install.6.
updateConfighookNot implemented. pnpm runs
updateConfig(config)from the pnpmfile and errors if it returnsundefined("updateConfig throws an error if it returns undefined").7. Finders / resolvers / fetchers
Not implemented (
finders,resolvers,fetchersexports). pnpm merges finders across pnpmfiles and errors on duplicate finder names ("requireHooks merges all the finders", "...two finders with the same name").Notes
pnpm/test/install/hooks.ts,pnpm/test/hooks.ts,installing/deps-installer/test/install/hooks.ts,hooks/pnpmfile/test/index.ts).pnpm:hookchannel and (3)--ignore-pnpmfileare the most self-contained; (2)filterLogand (4) multiple pnpmfiles are the larger architectural pieces.Written by an agent (Claude Code, claude-opus-4-8).