11package com .blackduck .integration .detectable .detectables .npm .cli .parse ;
22
3+ import java .util .HashMap ;
4+ import java .util .Map ;
35import java .util .Map .Entry ;
46import java .util .Objects ;
57import java .util .Optional ;
68import java .util .Set ;
79
10+ import org .apache .commons .collections4 .MultiValuedMap ;
811import org .apache .commons .lang3 .StringUtils ;
912import org .slf4j .Logger ;
1013import org .slf4j .LoggerFactory ;
1720import com .blackduck .integration .bdio .model .externalid .ExternalIdFactory ;
1821import com .blackduck .integration .detectable .detectable .codelocation .CodeLocation ;
1922import com .blackduck .integration .detectable .detectable .util .EnumListFilter ;
23+ import com .blackduck .integration .detectable .detectables .npm .NpmAliasParser ;
2024import com .blackduck .integration .detectable .detectables .npm .NpmDependencyType ;
2125import com .blackduck .integration .detectable .detectables .npm .lockfile .result .NpmPackagerResult ;
2226import com .blackduck .integration .detectable .detectables .npm .packagejson .CombinedPackageJson ;
@@ -65,7 +69,10 @@ public NpmPackagerResult convertNpmJsonFileToCodeLocation(String npmLsOutput, Co
6569 projectVersion = projectVersionElement .getAsString ();
6670 }
6771
68- populateChildren (graph , null , npmJson .getAsJsonObject (JSON_DEPENDENCIES ), true , combinedPackageJson );
72+ // Build alias mapping once from package.json
73+ Map <String , String > aliasMapping = buildAliasMapping (combinedPackageJson );
74+
75+ populateChildren (graph , null , npmJson .getAsJsonObject (JSON_DEPENDENCIES ), true , combinedPackageJson , aliasMapping );
6976
7077 ExternalId externalId = externalIdFactory .createNameVersionExternalId (Forge .NPMJS , projectName , projectVersion );
7178
@@ -75,7 +82,46 @@ public NpmPackagerResult convertNpmJsonFileToCodeLocation(String npmLsOutput, Co
7582
7683 }
7784
78- private void populateChildren (DependencyGraph graph , Dependency parentDependency , JsonObject parentNodeChildren , boolean isRootDependency , CombinedPackageJson combinedPackageJson ) {
85+ /**
86+ * Builds a mapping of alias names to actual package names from CombinedPackageJson.
87+ * Scans all dependency maps (dependencies, devDependencies, peerDependencies, optionalDependencies)
88+ * looking for entries with "npm:" prefix.
89+ *
90+ * @param combinedPackageJson The package.json data
91+ * @return Map of alias name -> actual package name
92+ */
93+ private Map <String , String > buildAliasMapping (CombinedPackageJson combinedPackageJson ) {
94+ Map <String , String > aliasMapping = new HashMap <>();
95+
96+ // Check all dependency types for aliases
97+ scanDependenciesForAliases (combinedPackageJson .getDependencies (), aliasMapping );
98+ scanDependenciesForAliases (combinedPackageJson .getDevDependencies (), aliasMapping );
99+ scanDependenciesForAliases (combinedPackageJson .getPeerDependencies (), aliasMapping );
100+ scanDependenciesForAliases (combinedPackageJson .getOptionalDependencies (), aliasMapping );
101+
102+ return aliasMapping ;
103+ }
104+
105+ private void scanDependenciesForAliases (MultiValuedMap <String , String > dependencies , Map <String , String > aliasMapping ) {
106+ if (dependencies == null ) {
107+ return ;
108+ }
109+
110+ for (Map .Entry <String , String > entry : dependencies .entries ()) {
111+ String aliasName = entry .getKey ();
112+ String versionSpec = entry .getValue ();
113+
114+ if (NpmAliasParser .isNpmAlias (versionSpec )) {
115+ String actualPackageName = NpmAliasParser .extractPackageName (versionSpec );
116+ if (actualPackageName != null ) {
117+ aliasMapping .put (aliasName , actualPackageName );
118+ logger .debug ("Found npm alias: {} -> {}" , aliasName , actualPackageName );
119+ }
120+ }
121+ }
122+ }
123+
124+ private void populateChildren (DependencyGraph graph , Dependency parentDependency , JsonObject parentNodeChildren , boolean isRootDependency , CombinedPackageJson combinedPackageJson , Map <String , String > aliasMapping ) {
79125 if (parentNodeChildren == null ) {
80126 return ;
81127 }
@@ -97,28 +143,32 @@ private void populateChildren(DependencyGraph graph, Dependency parentDependency
97143 && combinedPackageJson .getOptionalDependencies ().containsKey (elementEntry .getKey ()));
98144 return !excludingBecauseDev && !excludingBecausePeer && !excludingBecauseOptional ;
99145 })
100- .forEach (elementEntry -> processChild (elementEntry , graph , parentDependency , isRootDependency , combinedPackageJson ));
146+ .forEach (elementEntry -> processChild (elementEntry , graph , parentDependency , isRootDependency , combinedPackageJson , aliasMapping ));
101147 }
102148
103149 private void processChild (
104150 Entry <String , JsonElement > elementEntry ,
105151 DependencyGraph graph ,
106152 Dependency parentDependency ,
107153 boolean isRootDependency ,
108- CombinedPackageJson combinedPackageJson
154+ CombinedPackageJson combinedPackageJson ,
155+ Map <String , String > aliasMapping
109156 ) {
110157 JsonObject element = elementEntry .getValue ().getAsJsonObject ();
111158 String name = elementEntry .getKey ();
159+
160+ // Check if this is an alias and resolve to actual package name
161+ String actualName = aliasMapping .getOrDefault (name , name );
112162 String version = Optional .ofNullable (element .getAsJsonPrimitive (JSON_VERSION ))
113163 .filter (JsonPrimitive ::isString )
114164 .map (JsonPrimitive ::getAsString )
115165 .orElse (null );
116166
117167 JsonObject children = element .getAsJsonObject (JSON_DEPENDENCIES );
118168
119- if (name != null && version != null ) {
120- ExternalId externalId = externalIdFactory .createNameVersionExternalId (Forge .NPMJS , name , version );
121- Dependency child = new Dependency (name , version , externalId );
169+ if (actualName != null && version != null ) {
170+ ExternalId externalId = externalIdFactory .createNameVersionExternalId (Forge .NPMJS , actualName , version );
171+ Dependency child = new Dependency (actualName , version , externalId );
122172
123173 // Any workspace dependency is considered a direct dependency
124174 boolean directWorkspaceDependency = false ;
@@ -137,15 +187,15 @@ private void processChild(
137187 combinedPackageJson .getRelativeWorkspaces ().stream ().anyMatch (workspace -> workspace .equals (convertedPossibleWorkspaceDependency ));
138188 }
139189
140- populateChildren (graph , child , children , directWorkspaceDependency , combinedPackageJson );
190+ populateChildren (graph , child , children , directWorkspaceDependency , combinedPackageJson , aliasMapping );
141191
142192 if (isRootDependency || directWorkspaceDependency ) {
143193 graph .addChildToRoot (child );
144194 } else {
145195 graph .addParentWithChild (parentDependency , child );
146196 }
147197 } else {
148- logger .trace (String .format ("Excluding Json Element missing name or version: { name: %s, version: %s }" , name , version ));
198+ logger .trace (String .format ("Excluding Json Element missing name or version: { name: %s, version: %s }" , actualName , version ));
149199 }
150200 }
151201}
0 commit comments