@@ -60,7 +60,12 @@ export default async function convertPackageLockToShrinkwrap(workspaceRootDir, t
6060
6161 // Using the keys, extract relevant package-entries from package-lock.json
6262 const extractedPackages = Object . create ( null ) ;
63+ const resolvedConflicts = new Set ( ) ;
6364 for ( let [ packageLoc , node ] of relevantPackageLocations ) {
65+ if ( resolvedConflicts . has ( packageLoc ) ) {
66+ // This package location was already moved due to a conflict
67+ continue ;
68+ }
6469 let pkg = packageLockJson . packages [ packageLoc ] ;
6570 if ( pkg . link ) {
6671 pkg = packageLockJson . packages [ pkg . resolved ] ;
@@ -72,15 +77,47 @@ export default async function convertPackageLockToShrinkwrap(workspaceRootDir, t
7277 throw new Error ( `Duplicate root package entry for "${ targetPackageName } "` ) ;
7378 }
7479 } else {
75- packageLoc = normalizePackageLocation ( packageLoc , node , targetPackageName , tree . packageName ) ;
76- }
77- if ( packageLoc !== "" && ! pkg . resolved ) {
78- // For all but the root package, ensure that "resolved" and "integrity" fields are present
79- // These are always missing for locally linked packages, but sometimes also for others (e.g. if installed
80- // from local cache)
81- const { resolved, integrity} = await fetchPackageMetadata ( node . packageName , node . version , workspaceRootDir ) ;
82- pkg . resolved = resolved ;
83- pkg . integrity = integrity ;
80+ if ( ! pkg . resolved ) {
81+ // For all but the root package, ensure that "resolved" and "integrity" fields are present
82+ // These are always missing for locally linked packages, but sometimes also for others
83+ // (e.g. if installed from local cache)
84+ const { resolved, integrity} = await fetchPackageMetadata (
85+ node . packageName , node . version , workspaceRootDir ) ;
86+ pkg . resolved = resolved ;
87+ pkg . integrity = integrity ;
88+ }
89+ // Align package locations with new target
90+ const newPackageLoc = normalizePackageLocation ( packageLoc , node , targetPackageName , tree . packageName ) ;
91+ // Detect conflicts with dependencies hoisted to root level for packages other than the target
92+ const existingPackageAtNewLocation = relevantPackageLocations . get ( newPackageLoc ) ;
93+ if ( newPackageLoc !== packageLoc && existingPackageAtNewLocation ) {
94+ if ( pkg . version !== existingPackageAtNewLocation . version ) {
95+ console . log (
96+ `Hoisting conflict: Package "${ node . packageName } " (from "${ packageLoc } ") already exists at ` +
97+ `new location ${ newPackageLoc } in version ${ existingPackageAtNewLocation . version } ` ) ;
98+ resolvedConflicts . add ( newPackageLoc ) ;
99+ const conflictPkg = packageLockJson . packages [ newPackageLoc ] ;
100+ if ( ! conflictPkg . resolved ) {
101+ // For all but the root package, ensure that "resolved" and "integrity" fields are present
102+ // These are always missing for locally linked packages, but sometimes also for others
103+ // (e.g. if installed from local cache)
104+ const { resolved, integrity} = await fetchPackageMetadata (
105+ existingPackageAtNewLocation . packageName , existingPackageAtNewLocation . version ,
106+ workspaceRootDir ) ;
107+ conflictPkg . resolved = resolved ;
108+ conflictPkg . integrity = integrity ;
109+ }
110+ // Move existing package to a package-specific subdirectories to avoid conflict
111+ for ( const edge of existingPackageAtNewLocation . edgesIn ) {
112+ const parentPackage = edge . from . top . packageName ;
113+ console . log ( `Moving conflicting package "${ node . packageName } " under ` +
114+ `"node_modules/${ parentPackage } /node_modules/"` ) ;
115+ const subPath = `node_modules/${ parentPackage } /node_modules/${ node . packageName } ` ;
116+ extractedPackages [ subPath ] = structuredClone ( conflictPkg ) ;
117+ }
118+ }
119+ }
120+ packageLoc = newPackageLoc ;
84121 }
85122 extractedPackages [ packageLoc ] = pkg ;
86123 }
0 commit comments