-
Notifications
You must be signed in to change notification settings - Fork 153
/
NpmDependencyTree.java
111 lines (101 loc) · 4.6 KB
/
NpmDependencyTree.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
package org.jfrog.build.extractor.npm.extractor;
import com.fasterxml.jackson.databind.JsonNode;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.jfrog.build.extractor.npm.types.NpmPackageInfo;
import org.jfrog.build.extractor.npm.types.NpmScope;
import org.jfrog.build.extractor.scan.DependencyTree;
import org.jfrog.build.extractor.scan.Scope;
import java.nio.file.Path;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.swing.tree.TreeNode;
/**
* @author Yahav Itzhak
*/
@SuppressWarnings({"WeakerAccess"})
public class NpmDependencyTree {
/**
* Create a npm dependency tree from the results of 'npm ls' command.
*
* @param npmList - Results of 'npm ls' command
* @param scope - Dependency scope
* @param workingDir - The package.json directory
* @return Tree of npm PackageInfos.
* @see NpmPackageInfo
*/
public static DependencyTree createDependencyTree(JsonNode npmList, NpmScope scope, Path workingDir) {
DependencyTree rootNode = new DependencyTree();
populateDependenciesTree(rootNode, npmList.get("dependencies"), new String[]{getProjectName(npmList, workingDir)}, scope);
for (TreeNode child : rootNode.getChildren()) {
DependencyTree dependencyTree = (DependencyTree) child;
NpmPackageInfo packageInfo = (NpmPackageInfo) dependencyTree.getUserObject();
dependencyTree.setScopes(getScopes(packageInfo.getName(), packageInfo.getScope()));
}
return rootNode;
}
/**
* Get npm project name to populate the root node.
*
* @param npmList - Results of 'npm ls' command
* @param workingDir - The package.json directory
* @return <name>:<version>, <name> or <directory-name>
*/
static String getProjectName(JsonNode npmList, Path workingDir) {
JsonNode name = npmList.get("name");
JsonNode version = npmList.get("version");
if (name != null) {
if (version != null) {
return name.asText() + ":" + version.asText();
}
return name.asText();
}
return workingDir.getFileName().toString();
}
/**
* Parses npm dependencies recursively and adds the collected dependencies to scanTreeNode.
*
* @param scanTreeNode - Output - The DependenciesTree to populate.
* @param dependencies - The dependencies json object generated by npm ls.
* @param pathToRoot - A path-to-root dependency list. The structure of each dependency in the list is 'dependency-name:dependency-version'.
*/
private static void populateDependenciesTree(DependencyTree scanTreeNode, JsonNode dependencies, String[] pathToRoot, NpmScope scope) {
if (dependencies == null || pathToRoot == null) {
return;
}
dependencies.fields().forEachRemaining(stringJsonNodeEntry -> {
String name = stringJsonNodeEntry.getKey();
JsonNode versionNode = stringJsonNodeEntry.getValue().get("version");
if (versionNode != null) {
addSubtree(stringJsonNodeEntry, scanTreeNode, name, versionNode.asText(), pathToRoot, scope); // Mutual recursive call
}
});
}
private static void addSubtree(Map.Entry<String, JsonNode> stringJsonNodeEntry, DependencyTree node, String name, String version, String[] pathToRoot, NpmScope scope) {
JsonNode jsonNode = stringJsonNodeEntry.getValue();
String devScope = scope.toString();
NpmPackageInfo npmPackageInfo = new NpmPackageInfo(name, version, devScope, pathToRoot);
JsonNode childDependencies = jsonNode.get("dependencies");
DependencyTree childTreeNode = new DependencyTree(npmPackageInfo);
populateDependenciesTree(childTreeNode, childDependencies, ArrayUtils.insert(0, pathToRoot, npmPackageInfo.toString()), scope); // Mutual recursive call
node.add(childTreeNode);
}
/**
* Return a set of the relevant scopes. The set contains 'development' or 'production'. If the dependency has a
* custom scope, add it too.
*
* @param name - The name of the dependency
* @param devScope - 'development' or 'production'
* @return set of the relevant scopes
*/
private static Set<Scope> getScopes(String name, String devScope) {
Set<Scope> scopes = new HashSet<>();
scopes.add(new Scope(devScope));
String customScope = StringUtils.substringBetween(name, "@", "/");
if (customScope != null) {
scopes.add(new Scope(customScope));
}
return scopes;
}
}