Repository navigation
npm dedup drops a required dependency edge in a retained peer-only package #37022
Description
Version: Deno 2.9.5 (stable, release, x86_64-unknown-linux-gnu)
V8: 15.0.245.2-rusty
TypeScript: 6.0.3
Incremental lockfile-only installation can retain a peer-bound npm package while removing one of its required ordinary dependency mappings during version deduplication. This report concerns the generated snapshot's completeness, not a demonstrated runtime execution failure.
Independent public-package reproduction
This fixture was created independently from three public npm packages. No application manifest, application lockfile, application source, private registry, or synthetic package/import is used. Both locks below were generated by Deno, never hand-edited.
Save as package.before.json:
{
"dependencies": {
"@tiptap/core": "3.27.4",
"@tiptap/extension-code-block": "3.27.4",
"@tiptap/pm": "3.27.4"
},
"overrides": {
"prosemirror-view": "1.42.1"
}
}Save as package.after.json:
{
"dependencies": {
"@tiptap/core": "3.30.5",
"@tiptap/extension-code-block": "3.27.4",
"@tiptap/pm": "3.30.5"
},
"overrides": {
"prosemirror-view": "1.42.3"
}
}Save as required-edge.test.ts:
Deno.test("every retained TipTap model records its required view dependency", () => {
const lock = JSON.parse(Deno.readTextFileSync("deno.lock")) as {
npm: Record<string, { dependencies?: string[] }>;
};
const models = Object.entries(lock.npm).filter(([name]) => name.startsWith("@tiptap/pm@"));
if (models.length === 0) throw new Error("No TipTap model records found");
for (const [name, record] of models) {
if (!record.dependencies?.some((dependency) => /^prosemirror-view(?:@|$)/.test(dependency))) {
throw new Error(`${name} omits its required prosemirror-view dependency`);
}
}
});In a directory containing those files:
cp package.before.json package.json
deno install --lockfile-only --cached-only --no-remote --node-modules-dir=none --frozen=false --minimum-dependency-age=P7D
cp deno.lock before.lock
deno test --no-config --no-lock --no-npm --no-remote --allow-read=deno.lock required-edge.test.ts
cp package.after.json package.json
deno install --lockfile-only --cached-only --no-remote --node-modules-dir=none --frozen=false --minimum-dependency-age=P7D
cp deno.lock after.lock
deno test --no-config --no-lock --no-npm --no-remote --allow-read=deno.lock required-edge.test.tsVerified on October 14, 2026 with already-cached public npm metadata. --cached-only requires that cache; a cold-cache run was not verified. The exact generated initial lock is included below so that evolving transitive ranges do not obscure the observed case. No package code or lifecycle script was executed: installation was lockfile-only, and the test only parses JSON.
Expected and actual
Before generation/test: install exits 0; test reports 1 passed | 0 failed.
After generation/test: install exits 0, with peer-version warnings for the retained code-block extension; test reports 0 passed | 1 failed:
Error: @tiptap/pm@3.27.4 omits its required prosemirror-view dependency
The warning is not suppressed. It says the old extension's peers resolve to 3.30.5, but the generated lock still records its older peer context:
@tiptap/extension-code-block@3.27.4_<old peer context>
-> @tiptap/core@3.27.4_@tiptap+pm@3.27.4
-> @tiptap/pm@3.27.4
The retained pm 3.27.4 record loses prosemirror-view from dependencies; pm 3.30.5 records it, and the only view record is 1.42.3. Official npm metadata for pm 3.27.4 declares prosemirror-view: ^1.41.9 as a required dependency, not an optional or peer dependency. Its package integrity is unchanged between the two locks.
I expected the required dependency mapping to remain on every retained pm package, or the obsolete peer context to be consistently rebound/removed, rather than retaining a referenced package with that mapping absent.
Replaying the update twice from the same untouched generated before.lock, identical after manifest and the same metadata cache produced identical output:
- Before lock SHA-256:
8065b9f346fcc445b400339277a9a9a91248e12aefd64ebd886bf12d9ab59ef7 - Both after outputs SHA-256:
83586dc1efdde1629dfd379692b7df971bc12e4c333b903becfa7d20d8e60ea0
Versioned source trace and runtime limitation
Source references are at Deno v2.9.5 commit 17fadf33a8df3af9488b9f42efd1f2290d6dc7a3:
- Dedup removes children pointing to consolidated-away versions, then queues roots for repair.
- Phase 1 skips peer dependencies, so a retained peer-only pm subtree can miss that ordinary-edge repair.
- Existing child reuse also explains why old peer contexts can survive incremental resolution despite current overrides/root changes. This is not a claim that Deno lacks override support.
- Runtime resolution checks the direct map, then falls back to the best globally available
*version. With only view 1.42.3 present, that fallback selects it. I have not demonstrated a package-execution failure, and this report should not be read as one.
Exact initial generated lock
This lock belongs only to the independent public fixture above. It contains public npm identities/integrities and no remote/JSR section or application data.
before.lock (generated, unedited)
{
"version": "5",
"specifiers": {
"npm:@tiptap/core@3.27.4": "3.27.4_@tiptap+pm@3.27.4",
"npm:@tiptap/extension-code-block@3.27.4": "3.27.4_@tiptap+core@3.27.4__@tiptap+pm@3.27.4_@tiptap+pm@3.27.4",
"npm:@tiptap/pm@3.27.4": "3.27.4"
},
"npm": {
"@tiptap/core@3.27.4_@tiptap+pm@3.27.4": {
"integrity": "sha512-8W/GwlEn0JwNdpyVfTWcXwHYUpj9BWwO++YxtizmgjJzlwigSh7/xLVJMwVykuQHQ2fCq5rkUvmBRtpHOMLUQA==",
"dependencies": [
"@tiptap/pm"
]
},
"@tiptap/extension-code-block@3.27.4_@tiptap+core@3.27.4__@tiptap+pm@3.27.4_@tiptap+pm@3.27.4": {
"integrity": "sha512-a5caWfWN6Z6usy48vzJDDOhWoA6+rFFCHGpQM7jXn/7rRzYPcvBzTZUGptjEbltj4YqtrQ2tVwTJcCtbb+mknA==",
"dependencies": [
"@tiptap/core",
"@tiptap/pm"
]
},
"@tiptap/pm@3.27.4": {
"integrity": "sha512-UB8lcyomfWk7YGI2PZKNqcYXfyRA+PFj+QntlsUXyrsiA5JJIaE8SHKYjxKlGG/xtW3EtPm1b0p38T9Mk4xiFw==",
"dependencies": [
"prosemirror-changeset",
"prosemirror-commands",
"prosemirror-dropcursor",
"prosemirror-gapcursor",
"prosemirror-history",
"prosemirror-inputrules",
"prosemirror-keymap",
"prosemirror-model",
"prosemirror-schema-list",
"prosemirror-state",
"prosemirror-tables",
"prosemirror-transform",
"prosemirror-view"
]
},
"orderedmap@2.1.1": {
"integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g=="
},
"prosemirror-changeset@2.4.4": {
"integrity": "sha512-Gyuz9jYdrAeA9I7AholyOes9KUmv/lEwsC5O9s5xF5VB3EgTPsXMGUxz4+H1B+wKVPdZbvRcg1Cp3x8qX+8ofA==",
"dependencies": [
"prosemirror-transform"
]
},
"prosemirror-commands@1.7.2": {
"integrity": "sha512-q6Q6szxqdu9Xd6EcdKsqXghu5nQdZTpB4Q9yd04WRc7/jt763e/rT60Owh0L1GYY+T46o5rD+9lEN36dZS43tw==",
"dependencies": [
"prosemirror-model",
"prosemirror-state",
"prosemirror-transform"
]
},
"prosemirror-dropcursor@1.8.4": {
"integrity": "sha512-VmKxhcA6a+h5oPSYmfF1ZNeQqPbyYaIxqZsKqeYCyTlPtrjUgNaHL3P8vVaO19oqAV0zuaM/axkowZHrB1P3Tg==",
"dependencies": [
"prosemirror-state",
"prosemirror-transform",
"prosemirror-view"
]
},
"prosemirror-gapcursor@1.4.1": {
"integrity": "sha512-pMdYaEnjNMSwl11yjEGtgTmLkR08m/Vl+Jj443167p9eB3HVQKhYCc4gmHVDsLPODfZfjr/MmirsdyZziXbQKw==",
"dependencies": [
"prosemirror-keymap",
"prosemirror-model",
"prosemirror-state",
"prosemirror-view"
]
},
"prosemirror-history@1.5.1": {
"integrity": "sha512-onlwnqKvgtFg6srZOmDOAhlAy1FIHQpOExORybX0S+DvJepE5tPALrB9iGet/2sqqqrelSnB10OoiOJqAvu2DA==",
"dependencies": [
"prosemirror-state",
"prosemirror-transform",
"prosemirror-view",
"rope-sequence"
]
},
"prosemirror-inputrules@1.5.1": {
"integrity": "sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==",
"dependencies": [
"prosemirror-state",
"prosemirror-transform"
]
},
"prosemirror-keymap@1.2.3": {
"integrity": "sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==",
"dependencies": [
"prosemirror-state",
"w3c-keyname"
]
},
"prosemirror-model@1.25.12": {
"integrity": "sha512-Ue2gTmXMa7EhpLNhC7J+h4+ykD8ha12K6rrZFFKKJHBForfIStw5gJ6Zrf1mqqAa9NmpmEB4wp9bKmA4eUVjWg==",
"dependencies": [
"orderedmap"
]
},
"prosemirror-schema-list@1.5.1": {
"integrity": "sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==",
"dependencies": [
"prosemirror-model",
"prosemirror-state",
"prosemirror-transform"
]
},
"prosemirror-state@1.4.4": {
"integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==",
"dependencies": [
"prosemirror-model",
"prosemirror-transform",
"prosemirror-view"
]
},
"prosemirror-tables@1.8.5": {
"integrity": "sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==",
"dependencies": [
"prosemirror-keymap",
"prosemirror-model",
"prosemirror-state",
"prosemirror-transform",
"prosemirror-view"
]
},
"prosemirror-transform@1.12.2": {
"integrity": "sha512-PE/aY0HEY4zczvmqrilgkUK/WautF0chvMqkmM/iN5/aPHvwrCWaQZjS6CIZp+K84YrvPFqrL+YArrfJX8xX7g==",
"dependencies": [
"prosemirror-model"
]
},
"prosemirror-view@1.42.1": {
"integrity": "sha512-rRqzZnRgkyh69XoOMrfFJHwauHscLBmHbq772kwbic1ymQAM8gXjzEbJse5j1ep2UO2HRIAQL0bY3kZ/RoqjVw==",
"dependencies": [
"prosemirror-model",
"prosemirror-state",
"prosemirror-transform"
]
},
"rope-sequence@1.3.4": {
"integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ=="
},
"w3c-keyname@2.2.8": {
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="
}
},
"workspace": {
"packageJson": {
"dependencies": [
"npm:@tiptap/core@3.27.4",
"npm:@tiptap/extension-code-block@3.27.4",
"npm:@tiptap/pm@3.27.4"
],
"overrides": {
"prosemirror-view": "1.42.1"
}
}
}
}
Activity
CloudCorpRecords commentedon Oct 17, 2026
Hi, I'm CloudCorpRecords. I'd like to take this on: npm dedup drops a required dependency edge in a retained peer-only package.
I'm commenting first per the repo's contribution guidelines, and I'll follow the contribution process. I'll open a PR against the issue once the work is ready.
eta-days: 4
Claimed under the Greenlight Claim Protocol: this claim renews or expires
automatically, and releases the moment a PR is linked or I say so.
fix(npm): repair dedup-removed edges in retained peer-only subtrees