@@ -317,7 +317,10 @@ function getLocationMap(installState: NodeModulesLocatorMap) {
317
317
return locationMap ;
318
318
}
319
319
320
- const removeDir = async ( dir : PortablePath , options ?: { innerLoop ?: boolean , excludeNodeModules ?: boolean } ) : Promise < any > => {
320
+ const removeDir = async ( dir : PortablePath , options ?: { innerLoop ?: boolean } ) : Promise < any > => {
321
+ if ( dir . split ( ppath . sep ) . indexOf ( NODE_MODULES ) < 0 )
322
+ throw new Error ( `Assertion failed: trying to remove dir that doesn't contain node_modules: ${ dir } ` ) ;
323
+
321
324
try {
322
325
if ( ! options || ! options . innerLoop ) {
323
326
const stats = await xfs . lstatPromise ( dir ) ;
@@ -330,7 +333,7 @@ const removeDir = async (dir: PortablePath, options?: {innerLoop?: boolean, excl
330
333
for ( const entry of entries ) {
331
334
const targetPath = ppath . join ( dir , toFilename ( entry . name ) ) ;
332
335
if ( entry . isDirectory ( ) ) {
333
- if ( entry . name !== NODE_MODULES || ! options || ! options . excludeNodeModules ) {
336
+ if ( entry . name !== NODE_MODULES || ( options && options . innerLoop ) ) {
334
337
await removeDir ( targetPath , { innerLoop : true } ) ;
335
338
}
336
339
} else {
@@ -447,7 +450,7 @@ const buildLocationTree = (locatorMap: NodeModulesLocatorMap | null, {skipPrefix
447
450
const symlinkPromise = async ( srcDir : PortablePath , dstDir : PortablePath ) =>
448
451
xfs . symlinkPromise ( process . platform !== 'win32' ? ppath . relative ( ppath . dirname ( dstDir ) , srcDir ) : srcDir , dstDir , process . platform === 'win32' ? 'junction' : undefined ) ;
449
452
450
- const copyPromise = async ( dstDir : PortablePath , srcDir : PortablePath , { baseFs} : { baseFs : FakeFS < PortablePath > } ) => {
453
+ const copyPromise = async ( dstDir : PortablePath , srcDir : PortablePath , { baseFs, innerLoop } : { baseFs : FakeFS < PortablePath > , innerLoop ?: boolean } ) => {
451
454
await xfs . mkdirpPromise ( dstDir ) ;
452
455
const entries = await baseFs . readdirPromise ( srcDir , { withFileTypes : true } ) ;
453
456
@@ -472,7 +475,9 @@ const copyPromise = async (dstDir: PortablePath, srcDir: PortablePath, {baseFs}:
472
475
const srcPath = ppath . join ( srcDir , toFilename ( entry . name ) ) ;
473
476
const dstPath = ppath . join ( dstDir , toFilename ( entry . name ) ) ;
474
477
if ( entry . isDirectory ( ) ) {
475
- await copyPromise ( dstPath , srcPath , { baseFs} ) ;
478
+ if ( entry . name !== NODE_MODULES || innerLoop ) {
479
+ await copyPromise ( dstPath , srcPath , { baseFs, innerLoop : true } ) ;
480
+ }
476
481
} else {
477
482
await copy ( dstPath , srcPath , entry ) ;
478
483
}
@@ -574,10 +579,9 @@ async function persistNodeModules(preinstallState: InstallState, installState: N
574
579
const locationTree = buildLocationTree ( installState , { skipPrefix : project . cwd } ) ;
575
580
576
581
const addQueue : Promise < void > [ ] = [ ] ;
577
- const addModule = async ( { srcDir, dstDir, linkType, keepNodeModules } : { srcDir : PortablePath , dstDir : PortablePath , linkType : LinkType , keepNodeModules : boolean } ) => {
582
+ const addModule = async ( { srcDir, dstDir, linkType} : { srcDir : PortablePath , dstDir : PortablePath , linkType : LinkType } ) => {
578
583
const promise : Promise < any > = ( async ( ) => {
579
584
try {
580
- await removeDir ( dstDir , { excludeNodeModules : keepNodeModules } ) ;
581
585
if ( linkType === LinkType . SOFT ) {
582
586
await xfs . mkdirpPromise ( ppath . dirname ( dstDir ) ) ;
583
587
await symlinkPromise ( ppath . resolve ( srcDir ) , dstDir ) ;
@@ -597,14 +601,12 @@ async function persistNodeModules(preinstallState: InstallState, installState: N
597
601
}
598
602
} ;
599
603
600
- const cloneModule = async ( srcDir : PortablePath , dstDir : PortablePath , options ?: { keepSrcNodeModules ?: boolean , keepDstNodeModules ?: boolean , innerLoop ?: boolean } ) => {
604
+ const cloneModule = async ( srcDir : PortablePath , dstDir : PortablePath , options ?: { innerLoop ?: boolean } ) => {
601
605
const promise : Promise < any > = ( async ( ) => {
602
- const cloneDir = async ( srcDir : PortablePath , dstDir : PortablePath , options ?: { keepSrcNodeModules ?: boolean , keepDstNodeModules ?: boolean , innerLoop ?: boolean } ) => {
606
+ const cloneDir = async ( srcDir : PortablePath , dstDir : PortablePath , options ?: { innerLoop ?: boolean } ) => {
603
607
try {
604
- if ( ! options || ! options . innerLoop ) {
605
- await removeDir ( dstDir , { excludeNodeModules : options && options . keepDstNodeModules } ) ;
608
+ if ( ! options || ! options . innerLoop )
606
609
await xfs . mkdirpPromise ( dstDir ) ;
607
- }
608
610
609
611
const entries = await xfs . readdirPromise ( srcDir , { withFileTypes : true } ) ;
610
612
for ( const entry of entries ) {
@@ -614,13 +616,13 @@ async function persistNodeModules(preinstallState: InstallState, installState: N
614
616
const src = ppath . join ( srcDir , entry . name ) ;
615
617
const dst = ppath . join ( dstDir , entry . name ) ;
616
618
617
- if ( entry . name !== NODE_MODULES || ! options || ! options . keepSrcNodeModules ) {
618
- if ( entry . isDirectory ( ) ) {
619
+ if ( entry . isDirectory ( ) ) {
620
+ if ( entry . name !== NODE_MODULES || ( options && options . innerLoop ) ) {
619
621
await xfs . mkdirpPromise ( dst ) ;
620
- await cloneDir ( src , dst , { keepSrcNodeModules : false , keepDstNodeModules : false , innerLoop : true } ) ;
621
- } else {
622
- await xfs . copyFilePromise ( src , dst , fs . constants . COPYFILE_FICLONE ) ;
622
+ await cloneDir ( src , dst , { innerLoop : true } ) ;
623
623
}
624
+ } else {
625
+ await xfs . copyFilePromise ( src , dst , fs . constants . COPYFILE_FICLONE ) ;
624
626
}
625
627
}
626
628
} catch ( e ) {
@@ -661,39 +663,53 @@ async function persistNodeModules(preinstallState: InstallState, installState: N
661
663
662
664
663
665
// Delete locations that no longer exist
664
- const deleteList : PortablePath [ ] = [ ] ;
666
+ const deleteList = new Set < PortablePath > ( ) ;
667
+ // Delete locations of inner node_modules
668
+ const innerDeleteList = new Set < PortablePath > ( ) ;
665
669
for ( const { locations} of preinstallState . locatorMap . values ( ) ) {
666
670
for ( const location of locations ) {
667
671
const { locationRoot, segments} = parseLocation ( location , {
668
672
skipPrefix : project . cwd ,
669
673
} ) ;
670
674
675
+ let prevNode = prevLocationTree . get ( locationRoot ) ;
671
676
let node = locationTree . get ( locationRoot ) ;
672
677
let curLocation = locationRoot ;
673
- if ( ! node ) {
674
- deleteList . push ( curLocation ) ;
675
- } else {
678
+ if ( node ) {
676
679
for ( const segment of segments ) {
677
680
// '.' segment exists only for top-level locator, skip it
678
681
if ( segment === '.' )
679
682
continue ;
680
683
681
684
curLocation = ppath . join ( curLocation , segment ) ;
682
685
node = node . children . get ( segment ) ;
686
+ if ( prevNode )
687
+ prevNode = prevNode . children . get ( segment ) ;
688
+
683
689
if ( ! node ) {
684
- deleteList . push ( curLocation ) ;
690
+ deleteList . add ( curLocation ) ;
691
+ // If previous install had inner node_modules folder, we should explicitely list it for
692
+ // `removeDir` to delete it, but we need to delete it first, so we add it to inner delete list
693
+ if ( prevNode && prevNode . children . has ( NODE_MODULES ) )
694
+ innerDeleteList . add ( ppath . join ( curLocation , NODE_MODULES ) ) ;
685
695
break ;
686
696
}
687
697
}
688
698
}
689
699
}
690
700
}
691
701
702
+ // Handle inner node_modules deletions first
703
+ for ( const dstDir of innerDeleteList )
704
+ await deleteModule ( dstDir ) ;
705
+ await Promise . all ( deleteQueue ) ;
706
+ deleteQueue . length = 0 ;
707
+
692
708
for ( const dstDir of deleteList )
693
709
await deleteModule ( dstDir ) ;
694
710
695
711
// Update changed locations
696
- const addList : Array < { srcDir : PortablePath , dstDir : PortablePath , linkType : LinkType , keepNodeModules : boolean } > = [ ] ;
712
+ const addList : Array < { srcDir : PortablePath , dstDir : PortablePath , linkType : LinkType } > = [ ] ;
697
713
for ( const [ prevLocator , { locations} ] of preinstallState . locatorMap . entries ( ) ) {
698
714
for ( const location of locations ) {
699
715
const { locationRoot, segments} = parseLocation ( location , {
@@ -715,9 +731,9 @@ async function persistNodeModules(preinstallState: InstallState, installState: N
715
731
const srcDir = info . target ;
716
732
const dstDir = curLocation ;
717
733
const linkType = info . linkType ;
718
- const keepNodeModules = node . children . size > 0 ;
719
734
if ( srcDir !== dstDir ) {
720
- addList . push ( { srcDir, dstDir, linkType, keepNodeModules} ) ;
735
+ deleteList . add ( dstDir ) ;
736
+ addList . push ( { srcDir, dstDir, linkType} ) ;
721
737
}
722
738
}
723
739
}
@@ -747,13 +763,15 @@ async function persistNodeModules(preinstallState: InstallState, installState: N
747
763
node = node ! . children . get ( segment ) ;
748
764
749
765
if ( ! prevTreeNode ) {
750
- addList . push ( { srcDir, dstDir, linkType, keepNodeModules : node ! . children . size > 0 } ) ;
766
+ deleteList . add ( dstDir ) ;
767
+ addList . push ( { srcDir, dstDir, linkType} ) ;
751
768
} else {
752
769
for ( const segment of segments ) {
753
770
curLocation = ppath . join ( curLocation , segment ) ;
754
771
prevTreeNode = prevTreeNode . children . get ( segment ) ;
755
772
if ( ! prevTreeNode ) {
756
- addList . push ( { srcDir, dstDir, linkType, keepNodeModules : node ! . children . size > 0 } ) ;
773
+ deleteList . add ( dstDir ) ;
774
+ addList . push ( { srcDir, dstDir, linkType} ) ;
757
775
break ;
758
776
}
759
777
}
@@ -765,18 +783,15 @@ async function persistNodeModules(preinstallState: InstallState, installState: N
765
783
const reportedProgress = report . reportProgress ( progress ) ;
766
784
767
785
try {
768
- const persistedLocations = new Map < PortablePath , {
769
- dstDir : PortablePath ,
770
- keepNodeModules : boolean ,
771
- } > ( ) ;
786
+ const persistedLocations = new Map < PortablePath , PortablePath > ( ) ;
772
787
773
788
// For the first pass we'll only want to install a single copy for each
774
789
// source directory. We'll later use the resulting install directories for
775
790
// the other instances of the same package (this will avoid us having to
776
791
// crawl the zip archives for each package).
777
792
for ( const entry of addList ) {
778
793
if ( entry . linkType === LinkType . SOFT || ! persistedLocations . has ( entry . srcDir ) ) {
779
- persistedLocations . set ( entry . srcDir , { dstDir : entry . dstDir , keepNodeModules : entry . keepNodeModules } ) ;
794
+ persistedLocations . set ( entry . srcDir , entry . dstDir ) ;
780
795
await addModule ( { ...entry } ) ;
781
796
}
782
797
}
@@ -787,9 +802,9 @@ async function persistNodeModules(preinstallState: InstallState, installState: N
787
802
788
803
// Second pass: clone module duplicates
789
804
for ( const entry of addList ) {
790
- const locationInfo = persistedLocations . get ( entry . srcDir ) ! ;
791
- if ( entry . linkType !== LinkType . SOFT && entry . dstDir !== locationInfo . dstDir ) {
792
- await cloneModule ( locationInfo . dstDir , entry . dstDir , { keepSrcNodeModules : locationInfo . keepNodeModules , keepDstNodeModules : entry . keepNodeModules } ) ;
805
+ const persistedDir = persistedLocations . get ( entry . srcDir ) ! ;
806
+ if ( entry . linkType !== LinkType . SOFT && entry . dstDir !== persistedDir ) {
807
+ await cloneModule ( persistedDir , entry . dstDir ) ;
793
808
}
794
809
}
795
810
0 commit comments