fix: prefer existing tree nodes for peerOptional deps (#9249)#9283
fix: prefer existing tree nodes for peerOptional deps (#9249)#9283everett1992 wants to merge 3 commits intonpm:latestfrom
Conversation
When a peerOptional edge conflicts, search descendants for a satisfying node before fetching from the registry. This prevents extraneous packages from blocking hoisting of required deps.
| if (!dep && edge.type === 'peerOptional') { | ||
| dep = this.#findHoistableNode(edge.from.resolveParent || edge.from, edge) | ||
| } |
There was a problem hiding this comment.
| if (!dep && edge.type === 'peerOptional') { | |
| dep = this.#findHoistableNode(edge.from.resolveParent || edge.from, edge) | |
| } | |
| // Skip the shortcut when the user has signaled an explicit re-fetch intent (npm update by name, explicit request, or audit fix), so we honor those signals rather than silently keeping the existing node. | |
| const skipExistingShortcut = this[_updateNames].includes(edge.name) | |
| || this.#explicitRequests.has(edge) | |
| || (edge.to && this.auditReport?.isVulnerable(edge.to)) | |
| if (!dep && edge.type === 'peerOptional' && !skipExistingShortcut) { | |
| dep = this.#findHoistableNode(edge.from.resolveParent || edge.from, edge) | |
| } |
I think if we add this check, we get to keep the dedupe behavior for normal installs while still letting npm update, npm audit fix, and explicit installs do their thing.
There was a problem hiding this comment.
This is only in problemEdges tho, wouldn't there be other chances earlier to apply requested updates?
Aside: I noticed there is a test that peerOptionals are installed when explicitly requested, and that still passes npm install a-peer-optional.
Tho I noticed that even before this change the test would fail on re-install, or when package-lock or node_modules was already present. That seems bad, npm's only respecting the explciit request when it's not building from a lock.
There was a problem hiding this comment.
Oh, I see those count as problems and land here.
| // A peerOptional conflict can be resolved by finding an existing | ||
| // node in the tree that satisfies the edge, avoiding a registry | ||
| // fetch that may introduce an extraneous package. |
There was a problem hiding this comment.
| // A peerOptional conflict can be resolved by finding an existing | |
| // node in the tree that satisfies the edge, avoiding a registry | |
| // fetch that may introduce an extraneous package. | |
| // A peerOptional conflict can be resolved by finding an existing node in the tree that satisfies the edge, avoiding a registry fetch that may introduce an extraneous package. |
we try to break only at natural boundaries for screen reader accessibility
|
@owlstronaut Do you have ideas for alternative fixes? I really want a solution that fixes existing lockfiles, that have the old peerOptional conflict resolution, is an extraneous dependency that will be pruned. This is causing friction updating to npm 11 (I know, I'm behind the curve). |
When a peerOptional edge conflicts, search descendants for a satisfying node before fetching from the registry. This prevents extraneous packages from blocking hoisting of required deps.
This fixes 1/2 or 1/3 of #9249. Before this change a clean install would resolve
nm/jest-util@30when resolving the conflict at nm/jest-util between ts-jest's jest-util@^29||^30, and expect's ^28, which had been placed at root.#nodeFromEdgewould create a brand new node, matching greatest ^30. A subequent install would mark nm/jest-util@30 as extraneous and prune it.This tree is valid, but ts-jest's peerOptional jest-util is unsatisfied, while compatible jest-util are installed and duplicated.
This change reduces duplication and can prevent peerOptionals from actively installing.
#nodeFromEdgeand install an extraneous edge.I think a better solution to all three issues would be:
I think this solves all three issues. I didn't implement it because I couldn't find a way to resolve the conflict by leaving a hole in the tree..