feat: report lockfile verification progress#11712
Conversation
The lockfile resolution verifier introduced in #11705 runs an unbounded registry round-trip on cache miss and was previously silent — on a cold registry cache users saw nothing for several seconds. Emit pnpm:lockfile-verification log events (started/done) around the actual verification pass and render them in the default reporter as a transient progress line that collapses into a final "verified" summary with entry count and elapsed time. The cached short-circuit stays silent.
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughAdds a typed pnpm:lockfile-verification logger, emits started/done/failed progress events during lockfile verification using a deduplicated candidate set, routes those events through the default reporter with optional lockfile-path and elapsed-time formatting, and tests the rendered output. ChangesLockfile Verification Progress Logging
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
Adds user-visible progress messages around the lockfile resolution verifier (introduced in #11705). The verifier emits started / done events on the path that actually performs the registry round-trip, and the default reporter renders them as a transient progress line.
Changes:
- New
pnpm:lockfile-verificationlog type in@pnpm/core-loggerswithstarted/donevariants (plus entry count and elapsed ms). verifyLockfileResolutionsfactors outcollectCandidatesand emitsstartedbefore the fan-out anddoneafter a violation-free pass; cached/empty branches stay silent.@pnpm/cli.default-reporterwires a newreportLockfileVerificationreporter and a matching push stream intoOutput$, with unit tests for plural/singular rendering.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| core/core-loggers/src/lockfileVerificationLogger.ts | New logger and message type definitions for verification progress. |
| core/core-loggers/src/index.ts | Adds LockfileVerificationLog to the union Log. |
| core/core-loggers/src/all.ts | Re-exports the new logger module. |
| installing/deps-installer/src/install/verifyLockfileResolutions.ts | Extracts collectCandidates, emits started/done around the verification pass. |
| cli/default-reporter/src/reporterForClient/reportLockfileVerification.ts | New reporter that maps verification events to one inner observable. |
| cli/default-reporter/src/reporterForClient/index.ts | Wires the new reporter into the client reporter list. |
| cli/default-reporter/src/index.ts | Adds the new push stream and pnpm:lockfile-verification dispatch. |
| cli/default-reporter/test/reportingLockfileVerification.ts | Unit tests for plural/singular and done rendering. |
| .changeset/lockfile-verification-progress-logs.md | Changeset describing the new logs and reporter behavior. |
Comments suppressed due to low confidence (1)
installing/deps-installer/src/install/verifyLockfileResolutions.ts:175
- When
iterateLockfileViolationsreturns a non-empty violations list, the function throws without ever emitting a terminatinglockfile-verificationevent. In ansi-diff mode the transient "? Verifying lockfile (N entries)..." line stays as the last rendered frame for this reporter just before the error is printed, which is misleading (it suggests verification is still in progress while the error is being shown). Consider emitting a terminal event (e.g.status: 'done'or a newstatus: 'failed') on the throw path so the reporter can overwrite/clear the transient line before the error surfaces.
lockfileVerificationLogger.debug({ status: 'started', entries: candidates.size })
const violations = await iterateLockfileViolations(candidates, verifiers, options?.concurrency)
if (violations.length === 0) {
lockfileVerificationLogger.debug({
status: 'done',
entries: candidates.size,
elapsedMs: Date.now() - startedAt,
})
// Persist the success so the next install can stat-only the lockfile.
if (cache) {
recordVerification(cache.cacheDir, {
lockfilePath: cache.lockfilePath,
verifiers,
hashLockfile,
}, cachePrecomputed)
}
return
}
// Stable order so the error output is deterministic.
violations.sort((a, b) => `${a.name}@${a.version}`.localeCompare(`${b.name}@${b.version}`))
// Pick the throw code: a single-code batch keeps the per-policy code
// (so existing handlers / docs / search keywords still route correctly);
// a mixed batch (e.g. minimumReleaseAge + trust-downgrade on the same
// lockfile) escalates to the generic `LOCKFILE_RESOLUTION_VERIFICATION`
// and the per-entry code goes into the breakdown so the user can see
// which policy each entry tripped.
const distinctCodes = new Set(violations.map((v) => v.code))
const isMixed = distinctCodes.size > 1
const errorCode = isMixed ? 'LOCKFILE_RESOLUTION_VERIFICATION' : violations[0].code
const visible = violations.slice(0, MAX_VIOLATIONS_TO_PRINT)
const omitted = violations.length - visible.length
const formatEntry = isMixed
? (v: ResolutionPolicyViolation): string => ` ${v.name}@${v.version} [${v.code}] ${v.reason}`
: (v: ResolutionPolicyViolation): string => ` ${v.name}@${v.version} ${v.reason}`
const breakdown = visible.map(formatEntry).join('\n')
const details = omitted > 0
? `${breakdown}\n …and ${omitted} more`
: breakdown
throw new PnpmError(
errorCode,
`${violations.length} lockfile entries failed verification:\n${details}`,
{
hint: 'The lockfile contains entries that the active policies reject. ' +
'This can mean the lockfile is stale, or that someone committed a ' +
'lockfile that bypassed the policy locally — inspect recent changes ' +
'to pnpm-lock.yaml before trusting it. If the changes look expected, ' +
'run "pnpm clean --lockfile" and then "pnpm install" to rebuild from ' +
'a fresh resolution. Alternatively, relax the policy that flagged ' +
'them.',
}
)
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…tandard Add `lockfilePath` to the `pnpm:lockfile-verification` event payload so consumers always know which lockfile a `started`/`done` pair refers to. In the default reporter, render the path in the message only when the lockfile lives outside the workspace root (or, for non-workspace installs, outside cwd) — the common case stays uncluttered, while custom `lockfileDir` setups now surface in the verification line.
…ered message
"Verifying lockfile" was opaque about *what* was being verified. Reword
the rendered messages to explicitly name the check ("supply-chain
policies"), so users on a cold-cache pause understand what's happening
instead of just seeing the pause.
A non-empty lockfile.packages whose snapshots all fail name/version extraction would still emit a "Verifying lockfile (0 entries)" line even though no verifier work runs. Bail before emission when the candidate map is empty so the no-op branch stays silent, matching the contract for the other no-op branches (empty verifiers, no lockfile.packages).
Address two Copilot review points on #11712: 1. The verifier emitted `started` but no terminal event when violations were found or when the registry fan-out threw, leaving "Verifying lockfile…" as the last frame for that block in ansi-diff mode (and an unmatched line in CI logs). Add a `failed` status to the logger, wrap the fan-out in try/finally so a terminal event is emitted on every exit path that emitted `started`, and render a brief failure line so the spinner-style frame is replaced before the PnpmError block prints. 2. The path-suppression heuristic used strict `===` between path.dirname(lockfilePath) and expectedDir, which broke on trailing separators and slash-direction differences. Switch to a path.relative-based check so a workspaceDir like `/repo/` or a Windows path with mixed slashes still correctly suppresses the redundant "at <path>" suffix.
The lockfile verifier now emits log events during the registry round-trip pass, improving user visibility into the process.
origin/main advanced with two PRs: - #11712 (feat: report lockfile verification progress) — TS only. Adds a new `lockfile_verification` logger channel and renders its progress in `cli/default-reporter`. No pacquet code touched. - #11713 (test(pacquet): do not tolerate the lacking of tools) — hardens `git-fetcher/src/fetcher/tests.rs` so that missing host tools fail the test instead of being silently skipped, plus a paragraph in `pacquet/AGENTS.md` and a checkbox in `pacquet/plans/TEST_PORTING.md`. Both auto-merged cleanly with this branch — only one touched a file this branch also changed (`fetcher/tests.rs`, where rc.14 renamed two closure params), and git's merge driver resolved it without manual intervention. `cargo check --workspace --tests --locked`, `cargo dylint --all -- --all-targets --workspace`, and `cargo fmt --check` all clean post-merge.
Summary
The lockfile resolution verifier added in #11705 runs an unbounded registry round-trip on cache miss and was previously silent — on a cold registry cache the user sees nothing for several seconds, then either install resumes or an error appears. This wires user-visible progress around that pass.
pnpm:lockfile-verificationlog (@pnpm/core-loggers) withstatus: 'started' | 'done', an entry count, and elapsed ms ondone.verifyLockfileResolutions. Emission happens only on the path that does real work; the cache hit, empty-verifiers, and empty-lockfile branches stay silent — no noise when nothing is happening.reportLockfileVerificationin@pnpm/cli.default-reporter. One inner observable so thedonemessage overwrites the transientstartedline in ansi-diff mode; in appendOnly (CI) both lines print.Rendered output:
(Singular form
1 entrywhen there's only one.)Pacquet parity
Not ported — pacquet doesn't carry the resolution-policy lockfile verifier yet (see the parity note on #11705). When pacquet ports the verifier, the equivalent log event will travel with it.
Test plan
pnpm --filter @pnpm/core-loggers run compile— cleanpnpm --filter @pnpm/cli.default-reporter run compile— cleanpnpm --filter @pnpm/installing.deps-installer run compile— cleanpnpm --filter pnpm run compile— bundle rebuilds cleanlytoOutput$with syntheticstarted+doneevents; confirmed both messages render correctly with plural and singular entry countscli/default-reporter/test/reportingLockfileVerification.ts— could not be executed in this environment because every test in the package errors out with the pre-existing Jest ESMmodule is already linkederror (reproduced against unrelated existing tests on Node 22.22 and 24.15). Test will run in CI on the supported Node version.Written by an agent (Claude Code, claude-opus-4-7).
Summary by CodeRabbit
New Features
Refactor
Tests