Summary
pacquet install --lockfile-only does not yet reproduce the pnpm-lock.yaml
that the pnpm CLI produces from the same clean state. Running it on this
monorepo's own workspace (after rm -rf node_modules pnpm-lock.yaml) and
diffing against a freshly generated pnpm 11.5.2 lockfile (same clean
state, both run back-to-back against the live registry so version drift is
excluded) shows ~3,200 changed lines.
This issue tracks the lockfile-parity gaps. The deep resolver items will be
addressed together in a single parity PR; the self-contained items are
smaller follow-ups.
Reproduction
git worktree add -b parity-repro <branch>
cd <worktree>
rm -rf node_modules pnpm-lock.yaml && pacquet install --lockfile-only # pacquet
rm -rf node_modules pnpm-lock.yaml && pnpm install --lockfile-only # pnpm 11.5.2
The committed lockfile is a two-document file (doc 1 = env lockfile:
configDependencies/packageManagerDependencies; doc 2 = real lockfile).
pacquet emits only the single doc-2-equivalent document — expected and out of
scope here.
Note / correction: an earlier draft of this issue listed
minimumReleaseAge as a cause of version drift (@types/node 22.19.20 vs
22.19.19, etc.). That was a measurement artifact from a stale pnpm
packument cache in the first reference run. Re-running both tools
back-to-back against the live registry, pacquet and pnpm agree on those
versions. minimumReleaseAge is not a parity bug. The single-package
repro ({"@types/node":"^22.19.19"} + minimumReleaseAge: 1440) confirms
pnpm 11.5.2 and pacquet both pick 22.19.20 (which is now >1 day old).
Byte-level formatting
Equivalent content is byte-identical in low-level formatting: 2-space indent,
LF (no CRLF), no tabs, single trailing newline. The only leading-byte
difference is pnpm's --- document marker, present solely because pnpm's file
is multi-document (the env lockfile). No whitespace/quoting drift where
content matches.
Categories
Deep resolver work (single parity PR)
1. Cyclic / ancestor-peer suffix collapse — the dominant cause (~830 lines
touch supports-color alone; cascades widely because changing a peer suffix
changes the depPath key, rewriting every reference).
Minimal repro:
{ "dependencies": { "eslint": "10.4.1", "supports-color": "8.1.1" } }
# pnpm-workspace.yaml
autoInstallPeers: true
- '@eslint-community/eslint-utils@4.9.1(eslint@10.4.1)':
+ '@eslint-community/eslint-utils@4.9.1(eslint@10.4.1(supports-color@8.1.1))':
dependencies:
eslint: 10.4.1(supports-color@8.1.1)
eslint-visitor-keys: 3.4.3
@eslint-community/eslint-utils peer-depends on eslint. eslint is its
walk-ancestor (eslint → eslint-utils → eslint peer). When pacquet computes
eslint-utils's depPath suffix, eslint's own depPath
(eslint@10.4.1(supports-color@8.1.1)) isn't finalized yet, so
build_peer_id
hits its cycle fallback (step 4) and freezes the collapsed eslint@10.4.1.
pnpm's
calculateDepPath
defers depPath computation for nodes with not-yet-resolved peers and
awaits each pending peer's pathsByNodeIdPromises to obtain its full
depPath — falling back to name@version only for genuinely detected
cycles. pacquet treats every not-yet-known peer as a cycle. The fix is to
port pnpm's deferred two-phase depPath computation with real cycle detection.
Related manifestation in the monorepo: jest's suffix is
30.4.2(@babel/types@7.29.7)(@types/node@…) in pnpm vs
30.4.2(@babel/core@7.29.7(supports-color@8.1.1))(@types/node@…) in pacquet;
typanion is also missing from some suffixes (~58 lines).
1b. Resolved optional peer materialized as a dependency. pacquet adds
supports-color: 8.1.1 to the dependencies: of eslint /
@eslint/config-array; pnpm represents it only via the peer suffix +
transitivePeerDependencies. (Same minimal repro.)
2. npm: aliases not handled in catalogs / overrides / snapshots.
pacquet drops 8 aliased catalog entries (boxen→@zkochan/boxen,
js-yaml→@zkochan/js-yaml, execa→safe-execa, ramda→@pnpm/ramda,
which→@pnpm/which, hosted-git-info→@pnpm/hosted-git-info,
@types/pnpm__byline, @types/zkochan__table), leaves catalog: literal in
overrides instead of expanding, and resolves snapshot deps to the real
package instead of the alias:
write-yaml-file@5.0.0:
dependencies:
- js-yaml: 4.2.0
+ js-yaml: '@zkochan/js-yaml@0.0.11'
Cascades into missing @zkochan/* / @pnpm/* aliased packages.
Self-contained follow-ups
3. Self-links rendered as link:. instead of link: (204 lines):
'@pnpm-private/updater':
specifier: workspace:*
- version: link:.
+ version: 'link:'
4. node runtime dep rendered as node@runtime:26.3.0 instead of
runtime:26.3.0.
5. pnpmfileChecksum not emitted. ✅ Fixed in #12280.
6. patchedDependencies block not emitted (graceful-fs@4.2.11). ✅ Fixed in #12281.
Written by an agent (Claude Code, claude-opus-4-8).
Summary
pacquet install --lockfile-onlydoes not yet reproduce thepnpm-lock.yamlthat the pnpm CLI produces from the same clean state. Running it on this
monorepo's own workspace (after
rm -rf node_modules pnpm-lock.yaml) anddiffing against a freshly generated pnpm 11.5.2 lockfile (same clean
state, both run back-to-back against the live registry so version drift is
excluded) shows ~3,200 changed lines.
This issue tracks the lockfile-parity gaps. The deep resolver items will be
addressed together in a single parity PR; the self-contained items are
smaller follow-ups.
Reproduction
The committed lockfile is a two-document file (doc 1 = env lockfile:
configDependencies/packageManagerDependencies; doc 2 = real lockfile).pacquet emits only the single doc-2-equivalent document — expected and out of
scope here.
Byte-level formatting
Equivalent content is byte-identical in low-level formatting: 2-space indent,
LF (no CRLF), no tabs, single trailing newline. The only leading-byte
difference is pnpm's
---document marker, present solely because pnpm's fileis multi-document (the env lockfile). No whitespace/quoting drift where
content matches.
Categories
Deep resolver work (single parity PR)
1. Cyclic / ancestor-peer suffix collapse — the dominant cause (~830 lines
touch
supports-coloralone; cascades widely because changing a peer suffixchanges the depPath key, rewriting every reference).
Minimal repro:
{ "dependencies": { "eslint": "10.4.1", "supports-color": "8.1.1" } }@eslint-community/eslint-utilspeer-depends oneslint.eslintis itswalk-ancestor (eslint → eslint-utils → eslint peer). When pacquet computes
eslint-utils's depPath suffix, eslint's own depPath
(
eslint@10.4.1(supports-color@8.1.1)) isn't finalized yet, sobuild_peer_idhits its cycle fallback (step 4) and freezes the collapsed
eslint@10.4.1.pnpm's
calculateDepPathdefers depPath computation for nodes with not-yet-resolved peers and
awaits each pending peer's
pathsByNodeIdPromisesto obtain its fulldepPath — falling back to
name@versiononly for genuinely detectedcycles. pacquet treats every not-yet-known peer as a cycle. The fix is to
port pnpm's deferred two-phase depPath computation with real cycle detection.
Related manifestation in the monorepo: jest's suffix is
30.4.2(@babel/types@7.29.7)(@types/node@…)in pnpm vs30.4.2(@babel/core@7.29.7(supports-color@8.1.1))(@types/node@…)in pacquet;typanionis also missing from some suffixes (~58 lines).1b. Resolved optional peer materialized as a dependency. pacquet adds
supports-color: 8.1.1to thedependencies:ofeslint/@eslint/config-array; pnpm represents it only via the peer suffix +transitivePeerDependencies. (Same minimal repro.)2.
npm:aliases not handled in catalogs / overrides / snapshots.pacquet drops 8 aliased catalog entries (
boxen→@zkochan/boxen,js-yaml→@zkochan/js-yaml,execa→safe-execa,ramda→@pnpm/ramda,which→@pnpm/which,hosted-git-info→@pnpm/hosted-git-info,@types/pnpm__byline,@types/zkochan__table), leavescatalog:literal inoverrides instead of expanding, and resolves snapshot deps to the real
package instead of the alias:
write-yaml-file@5.0.0: dependencies: - js-yaml: 4.2.0 + js-yaml: '@zkochan/js-yaml@0.0.11'Cascades into missing
@zkochan/*/@pnpm/*aliased packages.Self-contained follow-ups
3. Self-links rendered as
link:.instead oflink:(204 lines):'@pnpm-private/updater': specifier: workspace:* - version: link:. + version: 'link:'4.
noderuntime dep rendered asnode@runtime:26.3.0instead ofruntime:26.3.0.5.
✅ Fixed in #12280.pnpmfileChecksumnot emitted.6.
(patchedDependenciesblock not emittedgraceful-fs@4.2.11). ✅ Fixed in #12281.Written by an agent (Claude Code, claude-opus-4-8).