@@ -25,11 +25,15 @@ export class NpmNodeModulesCollector extends NodeModulesCollector<NpmDependency,
2525 }
2626
2727 protected async collectAllDependencies ( tree : NpmDependency ) {
28- for ( const [ , value ] of Object . entries ( tree . dependencies || { } ) ) {
28+ for ( const [ key , value ] of Object . entries ( tree . dependencies || { } ) ) {
2929 if ( this . isDuplicatedNpmDependency ( value ) ) {
3030 continue
3131 }
32- this . allDependencies . set ( this . packageVersionString ( value ) , value )
32+ // Use the key (alias name) instead of value.name for npm aliased packages
33+ // e.g., { "foo": { name: "@scope /bar", ... } } should be stored as "foo@version"
34+ // This ensures aliased packages are copied to the correct location in node_modules
35+ const normalizedDep : NpmDependency = key !== value . name ? { ...value , name : key } : value
36+ this . allDependencies . set ( this . packageVersionString ( normalizedDep ) , normalizedDep )
3337 await this . collectAllDependencies ( value )
3438 }
3539 }
@@ -53,7 +57,9 @@ export class NpmNodeModulesCollector extends NodeModulesCollector<NpmDependency,
5357 continue
5458 }
5559 const dependency = resolvedDeps [ packageName ]
56- const childDependencyId = this . packageVersionString ( dependency )
60+ // Use the key (alias name) for aliased packages to match how they're stored in allDependencies
61+ const normalizedName = packageName !== dependency . name ? packageName : dependency . name
62+ const childDependencyId = `${ normalizedName } @${ dependency . version } `
5763 await this . extractProductionDependencyGraph ( dependency , childDependencyId )
5864 collectedDependencies . push ( childDependencyId )
5965 }
@@ -83,8 +89,12 @@ export class NpmNodeModulesCollector extends NodeModulesCollector<NpmDependency,
8389
8490 /**
8591 * Recursively builds dependency tree starting from a package directory.
92+ * @param packageDir - The directory of the package to process
93+ * @param aliasName - Optional alias name for npm aliased dependencies (e.g., "foo": "npm:@scope/bar@1.0.0")
94+ * When provided, this name is used instead of the package.json name for the module name,
95+ * ensuring the package is copied to the correct location in node_modules.
8696 */
87- const buildFromPackage = async ( packageDir : string ) : Promise < NpmDependency > => {
97+ const buildFromPackage = async ( packageDir : string , aliasName ?: string ) : Promise < NpmDependency > => {
8898 const pkgPath = path . join ( packageDir , "package.json" )
8999
90100 log . debug ( { pkgPath } , "building dependency node from package.json" )
@@ -96,11 +106,15 @@ export class NpmNodeModulesCollector extends NodeModulesCollector<NpmDependency,
96106 const pkg : PackageJson = await this . cache . packageJson [ pkgPath ]
97107 const resolvedPackageDir = await this . cache . realPath [ packageDir ]
98108
109+ // Use the alias name if provided, otherwise fall back to the package.json name
110+ // This ensures npm aliased packages are copied to the correct location
111+ const moduleName = aliasName ?? pkg . name
112+
99113 // Use resolved path as the unique identifier to prevent circular dependencies
100114 if ( visited . has ( resolvedPackageDir ) ) {
101- log . debug ( { name : pkg . name , version : pkg . version , path : resolvedPackageDir } , "skipping already visited package" )
115+ log . debug ( { name : moduleName , version : pkg . version , path : resolvedPackageDir } , "skipping already visited package" )
102116 return {
103- name : pkg . name ,
117+ name : moduleName ,
104118 version : pkg . version ,
105119 path : resolvedPackageDir ,
106120 }
@@ -120,12 +134,12 @@ export class NpmNodeModulesCollector extends NodeModulesCollector<NpmDependency,
120134 const depPath = await this . locatePackageVersion ( resolvedPackageDir , depName , depVersion )
121135
122136 if ( ! depPath || depPath . packageDir . length === 0 ) {
123- log . warn ( { package : pkg . name , dependency : depName , version : depVersion } , "dependency not found, skipping" )
137+ log . warn ( { package : moduleName , dependency : depName , version : depVersion } , "dependency not found, skipping" )
124138 continue
125139 }
126140
127141 const resolvedDepPath = await this . cache . realPath [ depPath . packageDir ]
128- const logFields = { package : pkg . name , dependency : depName , resolvedPath : resolvedDepPath }
142+ const logFields = { package : moduleName , dependency : depName , resolvedPath : resolvedDepPath }
129143
130144 // Skip if this dependency resolves to the base directory or any parent we're already processing
131145 if ( resolvedDepPath === resolvedPackageDir || resolvedDepPath === ( await this . cache . realPath [ baseDir ] ) ) {
@@ -136,14 +150,15 @@ export class NpmNodeModulesCollector extends NodeModulesCollector<NpmDependency,
136150 log . debug ( logFields , "processing production dependency" )
137151
138152 // Recursively build the dependency tree for this dependency
139- prodDeps [ depName ] = await buildFromPackage ( resolvedDepPath )
153+ // Pass depName as the alias - it will be used as the module name if different from the actual package name
154+ prodDeps [ depName ] = await buildFromPackage ( resolvedDepPath , depName )
140155 } catch ( error : any ) {
141- log . warn ( { package : pkg . name , dependency : depName , error : error . message } , "failed to process dependency, skipping" )
156+ log . warn ( { package : moduleName , dependency : depName , error : error . message } , "failed to process dependency, skipping" )
142157 }
143158 }
144159
145160 return {
146- name : pkg . name ,
161+ name : moduleName ,
147162 version : pkg . version ,
148163 path : resolvedPackageDir ,
149164 dependencies : Object . keys ( prodDeps ) . length > 0 ? prodDeps : undefined ,
0 commit comments