Skip to content

Commit 3463595

Browse files
authored
Add hierarchical dependencies for npm (#446)
* Bugfix - build-info-extractor-npm generates wrong dependency scopes
1 parent 5ecf241 commit 3463595

File tree

10 files changed

+133
-88
lines changed

10 files changed

+133
-88
lines changed

Diff for: build-info-extractor-npm/src/main/java/org/jfrog/build/extractor/npm/extractor/NpmBuildInfoExtractor.java

+7-7
Original file line numberDiff line numberDiff line change
@@ -238,12 +238,12 @@ private List<Dependency> collectDependencies(Path workingDir) throws Exception {
238238
if (scopes.isEmpty()) {
239239
return new ArrayList<>();
240240
}
241-
List<String> extraListArgs = new ArrayList<>();
242-
if (scopes.size() == 1) {
243-
extraListArgs.add("--only=" + scopes.get(0));
241+
for (NpmScope scope : scopes) {
242+
List<String> extraListArgs = new ArrayList<>();
243+
extraListArgs.add("--only=" + scope);
244+
JsonNode jsonNode = npmDriver.list(workingDir.toFile(), extraListArgs);
245+
populateDependenciesMap(dependencies, getDependenciesMapFromLatestBuild(), jsonNode, scope);
244246
}
245-
JsonNode jsonNode = npmDriver.list(workingDir.toFile(), extraListArgs);
246-
populateDependenciesMap(dependencies, getDependenciesMapFromLatestBuild(), jsonNode);
247247

248248
return new ArrayList<>(dependencies.values());
249249
}
@@ -286,10 +286,10 @@ private Build createBuild(List<Dependency> dependencies, String moduleId) {
286286
* 1. Create npm dependencies tree from root node of 'npm ls' command tree. Populate each node with name, version and scope.
287287
* 2. For each dependency, retrieve sha1 and md5 from Artifactory. Use the producer-consumer mechanism to parallelize it.
288288
*/
289-
private void populateDependenciesMap(Map<String, Dependency> dependencies, Map<String, Dependency> previousBuildDependencies, JsonNode npmDependenciesTree) throws Exception {
289+
private void populateDependenciesMap(Map<String, Dependency> dependencies, Map<String, Dependency> previousBuildDependencies, JsonNode npmDependenciesTree, NpmScope scope) throws Exception {
290290
// Set of packages that could not be found in Artifactory.
291291
Set<NpmPackageInfo> badPackages = Collections.synchronizedSet(new HashSet<>());
292-
DefaultMutableTreeNode rootNode = NpmDependencyTree.createDependenciesTree(npmDependenciesTree);
292+
DefaultMutableTreeNode rootNode = NpmDependencyTree.createDependenciesTree(npmDependenciesTree, scope);
293293
try (ArtifactoryDependenciesClient dependenciesClient = dependenciesClientBuilder.build()) {
294294
// Create producer Runnable.
295295
ProducerRunnableBase[] producerRunnable = new ProducerRunnableBase[]{new NpmExtractorProducer(rootNode)};

Diff for: build-info-extractor-npm/src/main/java/org/jfrog/build/extractor/npm/extractor/NpmDependencyTree.java

+17-20
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.fasterxml.jackson.databind.JsonNode;
44
import org.apache.commons.lang.StringUtils;
5+
import org.apache.commons.lang3.ArrayUtils;
56
import org.jfrog.build.extractor.npm.types.NpmPackageInfo;
67
import org.jfrog.build.extractor.npm.types.NpmScope;
78
import org.jfrog.build.extractor.scan.DependenciesTree;
@@ -24,51 +25,47 @@ public class NpmDependencyTree {
2425
* @return Tree of npm PackageInfos.
2526
* @see NpmPackageInfo
2627
*/
27-
public static DependenciesTree createDependenciesTree(JsonNode npmList) {
28+
public static DependenciesTree createDependenciesTree(JsonNode npmList, NpmScope scope) {
2829
DependenciesTree rootNode = new DependenciesTree();
29-
populateDependenciesTree(rootNode, npmList.get("dependencies"));
30+
populateDependenciesTree(rootNode, npmList.get("dependencies"), new String[]{npmList.get("name").asText() + ":" + npmList.get("version").asText()}, scope);
3031
for (DependenciesTree child : rootNode.getChildren()) {
3132
NpmPackageInfo packageInfo = (NpmPackageInfo) child.getUserObject();
3233
child.setScopes(getScopes(packageInfo.getName(), packageInfo.getScope()));
3334
}
3435
return rootNode;
3536
}
3637

37-
private static void populateDependenciesTree(DependenciesTree scanTreeNode, JsonNode dependencies) {
38-
if (dependencies == null) {
38+
/**
39+
* Parses npm dependencies recursively and adds the collected dependencies to scanTreeNode.
40+
*
41+
* @param scanTreeNode - Output - The DependenciesTree to populate.
42+
* @param dependencies - The dependencies json object generated by npm ls.
43+
* @param pathToRoot - A path-to-root dependency list. The structure of each dependency in the list is 'dependency-name:dependency-version'.
44+
*/
45+
private static void populateDependenciesTree(DependenciesTree scanTreeNode, JsonNode dependencies, String[] pathToRoot, NpmScope scope) {
46+
if (dependencies == null || pathToRoot == null) {
3947
return;
4048
}
4149

4250
dependencies.fields().forEachRemaining(stringJsonNodeEntry -> {
4351
String name = stringJsonNodeEntry.getKey();
4452
JsonNode versionNode = stringJsonNodeEntry.getValue().get("version");
4553
if (versionNode != null) {
46-
addSubtree(stringJsonNodeEntry, scanTreeNode, name, versionNode.asText()); // Mutual recursive call
54+
addSubtree(stringJsonNodeEntry, scanTreeNode, name, versionNode.asText(), pathToRoot, scope); // Mutual recursive call
4755
}
4856
});
4957
}
5058

51-
private static void addSubtree(Map.Entry<String, JsonNode> stringJsonNodeEntry, DependenciesTree node, String name, String version) {
59+
private static void addSubtree(Map.Entry<String, JsonNode> stringJsonNodeEntry, DependenciesTree node, String name, String version, String[] pathToRoot, NpmScope scope) {
5260
JsonNode jsonNode = stringJsonNodeEntry.getValue();
53-
String devScope = (isDev(jsonNode) ? NpmScope.DEVELOPMENT : NpmScope.PRODUCTION).toString();
54-
NpmPackageInfo npmPackageInfo = new NpmPackageInfo(name, version, devScope);
61+
String devScope = scope.toString();
62+
NpmPackageInfo npmPackageInfo = new NpmPackageInfo(name, version, devScope, pathToRoot);
5563
JsonNode childDependencies = jsonNode.get("dependencies");
5664
DependenciesTree childTreeNode = new DependenciesTree(npmPackageInfo);
57-
populateDependenciesTree(childTreeNode, childDependencies); // Mutual recursive call
65+
populateDependenciesTree(childTreeNode, childDependencies, ArrayUtils.insert(0, pathToRoot, npmPackageInfo.toString()), scope); // Mutual recursive call
5866
node.add(childTreeNode);
5967
}
6068

61-
/**
62-
* Return true if the input dependency is a dev dependency.
63-
*
64-
* @param jsonNode - The dependency node in the 'npm ls' results
65-
* @return true if the input dependency is a dev dependency
66-
*/
67-
private static boolean isDev(JsonNode jsonNode) {
68-
JsonNode development = jsonNode.get("_development");
69-
return development != null && development.asBoolean(false);
70-
}
71-
7269
/**
7370
* Return a set of the relevant scopes. The set contains 'development' or 'production'. If the dependency has a
7471
* custom scope, add it too.

Diff for: build-info-extractor-npm/src/main/java/org/jfrog/build/extractor/npm/extractor/NpmExtractorConsumer.java

+1
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ private boolean appendDependency(NpmPackageInfo npmPackageInfo) {
8282
} else {
8383
dependency.getScopes().add(npmPackageInfo.getScope());
8484
}
85+
dependency.addRequiredBy(npmPackageInfo.getPathToRoot());
8586
return true;
8687
}
8788

Diff for: build-info-extractor-npm/src/main/java/org/jfrog/build/extractor/npm/types/NpmPackageInfo.java

+17-1
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,22 @@ public class NpmPackageInfo implements Serializable, ProducerConsumerItem {
1616
private String name;
1717
private String version;
1818
private String scope;
19+
/**
20+
* A path-to-root dependency list that directly depends on this dependency.
21+
* The structure of each dependency in the list is 'dependency-name:dependency-version'
22+
* Used for 'RequiredBy' in {@link org.jfrog.build.api.Dependency}.
23+
*/
24+
private String[] pathToRoot;
1925

2026
@SuppressWarnings("unused")
2127
public NpmPackageInfo() {
2228
}
2329

24-
public NpmPackageInfo(String name, String version, String scope) {
30+
public NpmPackageInfo(String name, String version, String scope, String[] pathToRoot) {
2531
this.name = name;
2632
this.version = version;
2733
this.scope = scope;
34+
this.pathToRoot = pathToRoot;
2835
}
2936

3037
public String getName() {
@@ -75,6 +82,7 @@ public void readPackageInfo(InputStream inputStream) throws IOException {
7582

7683
setName(npmPackageInfo.getName());
7784
splitScopeFromName();
85+
setPathToRoot(npmPackageInfo.getPathToRoot());
7886
}
7987

8088
public String getModuleId() {
@@ -119,4 +127,12 @@ public int hashCode() {
119127
public String toString() {
120128
return name + ":" + version;
121129
}
130+
131+
public String[] getPathToRoot() {
132+
return pathToRoot;
133+
}
134+
135+
public void setPathToRoot(String[] pathToRoot) {
136+
this.pathToRoot = pathToRoot;
137+
}
122138
}

0 commit comments

Comments
 (0)