@@ -4,7 +4,16 @@ import { iterDeps, isPackage, type PackageLockFile, type PackageLockTree } from
44/**
55 * Hoist package-lock dependencies in-place
66 *
7- * This happens in two phases:
7+ * Packages are declared in two different roles here:
8+ *
9+ * - "requires" indicates where a package is consumed
10+ * - "dependencies" indicates where a package is provided; it will be available
11+ * to the package it is provided under, as well as any of its children.
12+ *
13+ * This function manipulates the "dependencies" part of the package tree, minimizing
14+ * the occurrences of packages in "dependencies" while keeping all "requires" satified.
15+ *
16+ * This happens by applying two basic operations:
817 *
918 * 1) Move every package into the parent scope (as long as it introduces no conflicts).
1019 * Leave "moved" markers to indicate that a package used to be there and no
@@ -63,22 +72,31 @@ export function renderTree(tree: PackageLockTree): string[] {
6372
6473export function _addTombstones < A extends PackageLockTree > ( root : A ) : A {
6574 let tree = structuredClone ( root ) ;
66- recurse ( tree ) ;
75+ recurse ( tree , [ tree ] ) ;
6776 return tree ;
6877
69- function recurse ( node : PackageLockTree ) {
70- // For every node, all the packages they 'requires' should be in 'dependencies'.
78+ function recurse ( nodeToCheck : PackageLockTree , rootPathToAdd : PackageLockTree [ ] ) {
79+ // Rootpath is ordered deep -> shallow.
80+
81+ // For every node, all the packages they or any of their children 'requires' should be in 'dependencies'.
7182 // If it's not in 'dependencies', that must mean its at a higher level already, so we put
7283 // the 'moved' tombstone in to make sure we don't accidentally replace this package with a different version.
73- for ( const name of Object . keys ( node . requires ?? { } ) ) {
74- if ( ! node . dependencies ?. [ name ] ) {
75- node . dependencies = node . dependencies ?? { } ;
76- node . dependencies [ name ] = 'moved' ;
84+ // Also add 'moved' to all of its parents, until we find a node that has it in 'dependencies'.
85+ for ( const name of Object . keys ( nodeToCheck . requires ?? { } ) ) {
86+
87+ // For every dependency in 'nodeToCheck', add 'moved' to 'depend. As soon as we find
88+ // the dependency provided declared anywhere, we stop.
89+ for ( const nodeToAdd of rootPathToAdd ) {
90+ if ( nodeToAdd . dependencies ?. [ name ] ) {
91+ break ;
92+ }
93+ nodeToAdd . dependencies = nodeToAdd . dependencies ?? { } ;
94+ nodeToAdd . dependencies [ name ] = 'moved' ;
7795 }
7896 }
7997
80- for ( const [ _ , dep ] of iterDeps ( node ) ) {
81- recurse ( dep ) ;
98+ for ( const [ _ , dep ] of iterDeps ( nodeToCheck ) ) {
99+ recurse ( dep , [ dep , ... rootPathToAdd ] ) ;
82100 }
83101 }
84102}
0 commit comments