fad-checker 1.0.6 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +62 -0
- package/README.md +77 -15
- package/completions/fad-checker.bash +4 -1
- package/completions/fad-checker.zsh +9 -0
- package/data/eol-mapping.json +17 -0
- package/fad-checker.js +353 -241
- package/lib/codecs/codec.interface.js +27 -0
- package/lib/codecs/composer/parse.js +59 -0
- package/lib/codecs/composer/registry.js +92 -0
- package/lib/codecs/composer.codec.js +93 -0
- package/lib/codecs/index.js +37 -0
- package/lib/codecs/maven.codec.js +71 -0
- package/lib/{npm → codecs/npm}/collect.js +58 -35
- package/lib/{npm → codecs/npm}/parse.js +114 -10
- package/lib/{npm → codecs/npm}/registry.js +4 -3
- package/lib/codecs/npm.codec.js +52 -0
- package/lib/codecs/nuget/parse.js +75 -0
- package/lib/codecs/nuget/registry.js +88 -0
- package/lib/codecs/nuget.codec.js +94 -0
- package/lib/codecs/pypi/parse.js +163 -0
- package/lib/codecs/pypi/registry.js +89 -0
- package/lib/codecs/pypi.codec.js +102 -0
- package/lib/codecs/recipes.js +108 -0
- package/lib/codecs/select.js +27 -0
- package/lib/codecs/yarn.codec.js +19 -0
- package/lib/core.js +35 -1
- package/lib/cve-match.js +18 -11
- package/lib/cve-report.js +34 -70
- package/lib/dep-record.js +60 -0
- package/lib/deps-descriptor.js +110 -0
- package/lib/nvd.js +4 -3
- package/lib/osv.js +29 -18
- package/lib/outdated.js +20 -4
- package/lib/retire.js +77 -13
- package/lib/transitive.js +3 -3
- package/lib/ui.js +87 -0
- package/package.json +4 -2
- package/CLAUDE.md +0 -129
- package/docs/ARCHITECTURE.md +0 -154
- package/docs/USAGE.md +0 -179
- package/test/core.test.js +0 -153
- package/test/cpe.test.js +0 -166
- package/test/cve-download.test.js +0 -39
- package/test/cve-match.test.js +0 -217
- package/test/cve-report.test.js +0 -171
- package/test/fixtures/complex-enterprise/api/pom.xml +0 -32
- package/test/fixtures/complex-enterprise/build/pom.xml +0 -46
- package/test/fixtures/complex-enterprise/dao/pom.xml +0 -37
- package/test/fixtures/complex-enterprise/pom.xml +0 -66
- package/test/fixtures/complex-enterprise/web/pom.xml +0 -77
- package/test/fixtures/cve-samples/cve-non-java.json +0 -19
- package/test/fixtures/cve-samples/cve-product-only.json +0 -31
- package/test/fixtures/cve-samples/cve-with-packagename.json +0 -37
- package/test/fixtures/cve-samples/nvd-log4shell.json +0 -40
- package/test/fixtures/cve-samples/nvd-npm-lodash.json +0 -22
- package/test/fixtures/monorepo-mixed/libs/common-bom/pom.xml +0 -26
- package/test/fixtures/monorepo-mixed/packages/cli/package.json +0 -14
- package/test/fixtures/monorepo-mixed/packages/cli/yarn.lock +0 -41
- package/test/fixtures/monorepo-mixed/packages/no-lock/package.json +0 -9
- package/test/fixtures/monorepo-mixed/packages/web-app/package-lock.json +0 -71
- package/test/fixtures/monorepo-mixed/packages/web-app/package.json +0 -17
- package/test/fixtures/monorepo-mixed/pom.xml +0 -29
- package/test/fixtures/monorepo-mixed/services/api/pom.xml +0 -27
- package/test/fixtures/monorepo-mixed/services/worker/pom.xml +0 -28
- package/test/fixtures/private-lib-detection/core/pom.xml +0 -26
- package/test/fixtures/private-lib-detection/plugin/pom.xml +0 -23
- package/test/fixtures/private-lib-detection/pom.xml +0 -35
- package/test/fixtures/simple/app/pom.xml +0 -28
- package/test/fixtures/simple/lib/pom.xml +0 -18
- package/test/fixtures/simple/pom.xml +0 -24
- package/test/maven-repo.test.js +0 -111
- package/test/maven-version.test.js +0 -57
- package/test/monorepo.test.js +0 -132
- package/test/npm-registry.test.js +0 -64
- package/test/npm.test.js +0 -146
- package/test/outdated.test.js +0 -101
- package/test/snyk.test.js +0 -64
- package/test/transitive.test.js +0 -305
- package/test/webjar.test.js +0 -33
package/test/outdated.test.js
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
const { test } = require("node:test");
|
|
2
|
-
const assert = require("node:assert/strict");
|
|
3
|
-
const { checkObsoleteDeps, checkObsolete, findEolProduct, KNOWN_OBSOLETE } = require("../lib/outdated");
|
|
4
|
-
|
|
5
|
-
test("known-obsolete.json contains the obvious historical hazards", () => {
|
|
6
|
-
assert.ok(KNOWN_OBSOLETE["log4j:log4j"], "log4j 1.x must be flagged");
|
|
7
|
-
assert.ok(KNOWN_OBSOLETE["commons-logging:commons-logging"]);
|
|
8
|
-
assert.ok(KNOWN_OBSOLETE["org.codehaus.jackson:jackson-databind"] || KNOWN_OBSOLETE["org.codehaus.jackson:jackson-mapper-asl"]);
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
test("checkObsoleteDeps flags log4j 1.x and jackson 1.x", () => {
|
|
12
|
-
const deps = new Map([
|
|
13
|
-
["log4j:log4j", { groupId: "log4j", artifactId: "log4j", version: "1.2.17" }],
|
|
14
|
-
["org.codehaus.jackson:jackson-mapper-asl", { groupId: "org.codehaus.jackson", artifactId: "jackson-mapper-asl", version: "1.9.13" }],
|
|
15
|
-
["com.fasterxml.jackson.core:jackson-databind", { groupId: "com.fasterxml.jackson.core", artifactId: "jackson-databind", version: "2.16.0" }],
|
|
16
|
-
]);
|
|
17
|
-
const out = checkObsoleteDeps(deps);
|
|
18
|
-
const ids = out.map(o => `${o.dep.groupId}:${o.dep.artifactId}`);
|
|
19
|
-
assert.ok(ids.includes("log4j:log4j"));
|
|
20
|
-
assert.ok(ids.includes("org.codehaus.jackson:jackson-mapper-asl"));
|
|
21
|
-
assert.ok(!ids.includes("com.fasterxml.jackson.core:jackson-databind"));
|
|
22
|
-
const log4j = out.find(o => o.dep.artifactId === "log4j");
|
|
23
|
-
assert.equal(log4j.severity, "CRITICAL");
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
test("checkObsoleteDeps deduplicates by g:a", () => {
|
|
27
|
-
const deps = new Map([
|
|
28
|
-
["log4j:log4j", { groupId: "log4j", artifactId: "log4j", version: "1.2.17" }],
|
|
29
|
-
]);
|
|
30
|
-
// Call twice — should still report once
|
|
31
|
-
const out1 = checkObsoleteDeps(deps);
|
|
32
|
-
const out2 = checkObsoleteDeps(deps);
|
|
33
|
-
assert.equal(out1.length, 1);
|
|
34
|
-
assert.equal(out2.length, 1);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
test("checkObsolete single-dep returns details or null", () => {
|
|
38
|
-
const o = checkObsolete({ groupId: "log4j", artifactId: "log4j", version: "1.2" });
|
|
39
|
-
assert.ok(o);
|
|
40
|
-
assert.equal(o.severity, "CRITICAL");
|
|
41
|
-
assert.equal(checkObsolete({ groupId: "com.fasterxml.jackson.core", artifactId: "jackson-databind" }), null);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
test("findEolProduct matches Spring Boot by exact coord and by prefix", () => {
|
|
45
|
-
const sb = findEolProduct({ groupId: "org.springframework.boot", artifactId: "spring-boot-starter-parent" });
|
|
46
|
-
assert.equal(sb.product, "spring-boot");
|
|
47
|
-
|
|
48
|
-
const sbcustom = findEolProduct({ groupId: "org.springframework.boot", artifactId: "spring-boot-starter-anything" });
|
|
49
|
-
assert.equal(sbcustom.product, "spring-boot", "prefix-only mapping must still match");
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test("findEolProduct picks longest prefix match", () => {
|
|
53
|
-
const sec = findEolProduct({ groupId: "org.springframework.security", artifactId: "made-up" });
|
|
54
|
-
assert.equal(sec.product, "spring-framework");
|
|
55
|
-
assert.equal(sec.label, "Spring Security");
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test("findEolProduct maps the npm 'angular' package to AngularJS 1.x", () => {
|
|
59
|
-
// The literal npm package named "angular" IS AngularJS (1.x), EOL since 2022.
|
|
60
|
-
const a = findEolProduct({ ecosystem: "npm", groupId: "", artifactId: "angular" });
|
|
61
|
-
assert.equal(a.product, "angularjs");
|
|
62
|
-
assert.equal(a.label, "AngularJS");
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
test("findEolProduct maps @angular/* scoped packages to modern Angular", () => {
|
|
66
|
-
const core = findEolProduct({ ecosystem: "npm", groupId: "", artifactId: "@angular/core" });
|
|
67
|
-
assert.equal(core.product, "angular");
|
|
68
|
-
const router = findEolProduct({ ecosystem: "npm", groupId: "", artifactId: "@angular/router" });
|
|
69
|
-
assert.equal(router.product, "angular");
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
test("findEolProduct maps react / react-dom / jquery / vue / bootstrap", () => {
|
|
73
|
-
assert.equal(findEolProduct({ ecosystem: "npm", artifactId: "react" }).product, "react");
|
|
74
|
-
assert.equal(findEolProduct({ ecosystem: "npm", artifactId: "react-dom" }).product, "react");
|
|
75
|
-
assert.equal(findEolProduct({ ecosystem: "npm", artifactId: "jquery" }).product, "jquery");
|
|
76
|
-
assert.equal(findEolProduct({ ecosystem: "npm", artifactId: "vue" }).product, "vue");
|
|
77
|
-
assert.equal(findEolProduct({ ecosystem: "npm", artifactId: "bootstrap" }).product, "bootstrap");
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
test("findEolProduct returns null for an unmapped npm package", () => {
|
|
81
|
-
assert.equal(findEolProduct({ ecosystem: "npm", artifactId: "left-pad" }), null);
|
|
82
|
-
// A Maven groupId must never leak into the npm lookup.
|
|
83
|
-
assert.equal(findEolProduct({ ecosystem: "npm", artifactId: "org.springframework" }), null);
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
test("findEolProduct maps WebJars (client-side JS shipped as Maven artifacts)", () => {
|
|
87
|
-
// org.webjars:angularjs:1.8.3 — AngularJS 1.x, EOL since 2021.
|
|
88
|
-
const ajs = findEolProduct({ groupId: "org.webjars", artifactId: "angularjs", version: "1.8.3" });
|
|
89
|
-
assert.ok(ajs, "org.webjars:angularjs must map");
|
|
90
|
-
assert.equal(ajs.product, "angularjs");
|
|
91
|
-
|
|
92
|
-
assert.equal(findEolProduct({ groupId: "org.webjars", artifactId: "jquery" }).product, "jquery");
|
|
93
|
-
assert.equal(findEolProduct({ groupId: "org.webjars", artifactId: "bootstrap" }).product, "bootstrap");
|
|
94
|
-
// org.webjars.npm mirrors npm names; scope slash is encoded as "__".
|
|
95
|
-
assert.equal(findEolProduct({ groupId: "org.webjars.npm", artifactId: "vue" }).product, "vue");
|
|
96
|
-
assert.equal(findEolProduct({ groupId: "org.webjars.npm", artifactId: "angular__core" }).product, "angular");
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
test("findEolProduct returns null for an unmapped WebJar artifact", () => {
|
|
100
|
-
assert.equal(findEolProduct({ groupId: "org.webjars", artifactId: "datatables" }), null);
|
|
101
|
-
});
|
package/test/snyk.test.js
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
const { test } = require("node:test");
|
|
2
|
-
const assert = require("node:assert/strict");
|
|
3
|
-
const { parseSnykResults, parseSnykStdout, mergeWithFadResults } = require("../lib/snyk");
|
|
4
|
-
|
|
5
|
-
const snykSample = {
|
|
6
|
-
vulnerabilities: [
|
|
7
|
-
{
|
|
8
|
-
id: "SNYK-JAVA-LOG4J-2314720",
|
|
9
|
-
packageName: "org.apache.logging.log4j:log4j-core",
|
|
10
|
-
version: "2.14.0",
|
|
11
|
-
severity: "critical",
|
|
12
|
-
cvssScore: 10,
|
|
13
|
-
title: "Remote Code Execution",
|
|
14
|
-
fixedIn: ["2.15.0", "2.16.0"],
|
|
15
|
-
identifiers: { CVE: ["CVE-2021-44228"] },
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
id: "SNYK-JAVA-JACKSON-2421244",
|
|
19
|
-
packageName: "com.fasterxml.jackson.core:jackson-databind",
|
|
20
|
-
version: "2.13.0",
|
|
21
|
-
severity: "high",
|
|
22
|
-
cvssScore: 7.5,
|
|
23
|
-
title: "Denial of Service",
|
|
24
|
-
fixedIn: ["2.13.5"],
|
|
25
|
-
identifiers: { CVE: ["CVE-2022-42003"] },
|
|
26
|
-
},
|
|
27
|
-
],
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
test("parseSnykStdout accepts a JSON object or array", () => {
|
|
31
|
-
assert.deepEqual(parseSnykStdout(""), []);
|
|
32
|
-
const arr = parseSnykStdout(JSON.stringify([snykSample]));
|
|
33
|
-
assert.equal(arr.length, 1);
|
|
34
|
-
const obj = parseSnykStdout(JSON.stringify(snykSample));
|
|
35
|
-
assert.equal(obj.length, 1);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test("parseSnykResults normalises to fad-checker match shape", () => {
|
|
39
|
-
const out = parseSnykResults(snykSample);
|
|
40
|
-
assert.equal(out.length, 2);
|
|
41
|
-
assert.equal(out[0].dep.groupId, "org.apache.logging.log4j");
|
|
42
|
-
assert.equal(out[0].dep.artifactId, "log4j-core");
|
|
43
|
-
assert.equal(out[0].cve.id, "CVE-2021-44228");
|
|
44
|
-
assert.equal(out[0].cve.severity, "CRITICAL");
|
|
45
|
-
assert.equal(out[0].cve.fixVersion, "2.15.0");
|
|
46
|
-
assert.equal(out[0].source, "snyk");
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test("mergeWithFadResults tags overlap as 'both' and keeps Snyk-only as 'snyk'", () => {
|
|
50
|
-
const fadMatches = [
|
|
51
|
-
{
|
|
52
|
-
dep: { groupId: "org.apache.logging.log4j", artifactId: "log4j-core", version: "2.14.0" },
|
|
53
|
-
cve: { id: "CVE-2021-44228", severity: "CRITICAL" },
|
|
54
|
-
confidence: "exact",
|
|
55
|
-
},
|
|
56
|
-
];
|
|
57
|
-
const snykMatches = parseSnykResults(snykSample);
|
|
58
|
-
const merged = mergeWithFadResults(fadMatches, snykMatches);
|
|
59
|
-
assert.equal(merged.length, 2);
|
|
60
|
-
const log4j = merged.find(m => m.cve.id === "CVE-2021-44228");
|
|
61
|
-
assert.equal(log4j.source, "both");
|
|
62
|
-
const jackson = merged.find(m => m.cve.id === "CVE-2022-42003");
|
|
63
|
-
assert.equal(jackson.source, "snyk");
|
|
64
|
-
});
|
package/test/transitive.test.js
DELETED
|
@@ -1,305 +0,0 @@
|
|
|
1
|
-
const { test } = require("node:test");
|
|
2
|
-
const assert = require("node:assert/strict");
|
|
3
|
-
const fs = require("fs");
|
|
4
|
-
const os = require("os");
|
|
5
|
-
const path = require("path");
|
|
6
|
-
const { parsePomXml, resolveProps, buildMgmt, SCOPE_MATRIX, resolveTransitiveDeps } = require("../lib/transitive");
|
|
7
|
-
|
|
8
|
-
// Per-test cache dir so we don't poison ~/.fad-checker/poms-cache during tests
|
|
9
|
-
function freshCache() {
|
|
10
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "fad-checker-pom-cache-"));
|
|
11
|
-
return dir;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
// ---------- POM fixtures expressed as XML strings ----------
|
|
15
|
-
|
|
16
|
-
const POM_A = `<?xml version="1.0"?>
|
|
17
|
-
<project>
|
|
18
|
-
<modelVersion>4.0.0</modelVersion>
|
|
19
|
-
<groupId>com.example</groupId>
|
|
20
|
-
<artifactId>a</artifactId>
|
|
21
|
-
<version>1.0.0</version>
|
|
22
|
-
<dependencies>
|
|
23
|
-
<dependency>
|
|
24
|
-
<groupId>com.example</groupId>
|
|
25
|
-
<artifactId>b</artifactId>
|
|
26
|
-
<version>2.0.0</version>
|
|
27
|
-
</dependency>
|
|
28
|
-
<dependency>
|
|
29
|
-
<groupId>com.example</groupId>
|
|
30
|
-
<artifactId>test-only</artifactId>
|
|
31
|
-
<version>3.0.0</version>
|
|
32
|
-
<scope>test</scope>
|
|
33
|
-
</dependency>
|
|
34
|
-
<dependency>
|
|
35
|
-
<groupId>com.example</groupId>
|
|
36
|
-
<artifactId>optional-lib</artifactId>
|
|
37
|
-
<version>4.0.0</version>
|
|
38
|
-
<optional>true</optional>
|
|
39
|
-
</dependency>
|
|
40
|
-
</dependencies>
|
|
41
|
-
</project>`;
|
|
42
|
-
|
|
43
|
-
const POM_B = `<?xml version="1.0"?>
|
|
44
|
-
<project>
|
|
45
|
-
<modelVersion>4.0.0</modelVersion>
|
|
46
|
-
<groupId>com.example</groupId>
|
|
47
|
-
<artifactId>b</artifactId>
|
|
48
|
-
<version>2.0.0</version>
|
|
49
|
-
<dependencies>
|
|
50
|
-
<dependency>
|
|
51
|
-
<groupId>com.example</groupId>
|
|
52
|
-
<artifactId>c</artifactId>
|
|
53
|
-
<version>5.0.0</version>
|
|
54
|
-
</dependency>
|
|
55
|
-
</dependencies>
|
|
56
|
-
</project>`;
|
|
57
|
-
|
|
58
|
-
const POM_C = `<?xml version="1.0"?>
|
|
59
|
-
<project>
|
|
60
|
-
<modelVersion>4.0.0</modelVersion>
|
|
61
|
-
<groupId>com.example</groupId>
|
|
62
|
-
<artifactId>c</artifactId>
|
|
63
|
-
<version>5.0.0</version>
|
|
64
|
-
</project>`;
|
|
65
|
-
|
|
66
|
-
// Build an in-memory fetcher
|
|
67
|
-
function fakeFetcher(responses) {
|
|
68
|
-
return async (url) => {
|
|
69
|
-
if (responses[url]) {
|
|
70
|
-
return { ok: true, status: 200, text: async () => responses[url] };
|
|
71
|
-
}
|
|
72
|
-
return { ok: false, status: 404, text: async () => "" };
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const MC = "https://repo1.maven.org/maven2";
|
|
77
|
-
|
|
78
|
-
// ---------- Tests ----------
|
|
79
|
-
|
|
80
|
-
test("parsePomXml extracts coords, deps, scope, optional, exclusions", async () => {
|
|
81
|
-
const xml = `<?xml version="1.0"?>
|
|
82
|
-
<project>
|
|
83
|
-
<groupId>g</groupId><artifactId>a</artifactId><version>1</version>
|
|
84
|
-
<dependencies>
|
|
85
|
-
<dependency>
|
|
86
|
-
<groupId>org.foo</groupId><artifactId>bar</artifactId><version>2.0</version>
|
|
87
|
-
<scope>runtime</scope><optional>true</optional>
|
|
88
|
-
<exclusions>
|
|
89
|
-
<exclusion><groupId>x</groupId><artifactId>y</artifactId></exclusion>
|
|
90
|
-
</exclusions>
|
|
91
|
-
</dependency>
|
|
92
|
-
</dependencies>
|
|
93
|
-
</project>`;
|
|
94
|
-
const pom = await parsePomXml(xml);
|
|
95
|
-
assert.equal(pom.groupId, "g");
|
|
96
|
-
assert.equal(pom.deps[0].scope, "runtime");
|
|
97
|
-
assert.equal(pom.deps[0].optional, true);
|
|
98
|
-
assert.deepEqual(pom.deps[0].exclusions, [{ groupId: "x", artifactId: "y" }]);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
test("parsePomXml handles parent + inherits groupId/version from it", async () => {
|
|
102
|
-
const xml = `<?xml version="1.0"?>
|
|
103
|
-
<project>
|
|
104
|
-
<parent><groupId>g</groupId><artifactId>p</artifactId><version>3.0</version></parent>
|
|
105
|
-
<artifactId>child</artifactId>
|
|
106
|
-
</project>`;
|
|
107
|
-
const pom = await parsePomXml(xml);
|
|
108
|
-
assert.equal(pom.groupId, "g");
|
|
109
|
-
assert.equal(pom.version, "3.0");
|
|
110
|
-
assert.equal(pom.parent.artifactId, "p");
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
test("resolveProps substitutes ${prop} including project.* builtins", () => {
|
|
114
|
-
const props = { "spring.version": "5.3.20", "log4j": "2.20.0" };
|
|
115
|
-
const builtins = { "project.version": "1.0" };
|
|
116
|
-
assert.equal(resolveProps("${spring.version}", props, builtins), "5.3.20");
|
|
117
|
-
assert.equal(resolveProps("${project.version}", props, builtins), "1.0");
|
|
118
|
-
assert.equal(resolveProps("v=${log4j}", props, builtins), "v=2.20.0");
|
|
119
|
-
assert.equal(resolveProps("${unknown}", props, builtins), "${unknown}");
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
test("resolveProps follows chained property references", () => {
|
|
123
|
-
const props = { a: "${b}", b: "${c}", c: "final" };
|
|
124
|
-
assert.equal(resolveProps("${a}", props, {}), "final");
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
test("SCOPE_MATRIX implements Maven's scope-propagation rules", () => {
|
|
128
|
-
// compile direct + compile transitive → compile
|
|
129
|
-
assert.equal(SCOPE_MATRIX.compile.compile, "compile");
|
|
130
|
-
// compile + runtime → runtime
|
|
131
|
-
assert.equal(SCOPE_MATRIX.compile.runtime, "runtime");
|
|
132
|
-
// compile + test → not propagated
|
|
133
|
-
assert.equal(SCOPE_MATRIX.compile.test, null);
|
|
134
|
-
// test + compile → test
|
|
135
|
-
assert.equal(SCOPE_MATRIX.test.compile, "test");
|
|
136
|
-
// provided + runtime → provided
|
|
137
|
-
assert.equal(SCOPE_MATRIX.provided.runtime, "provided");
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
test("buildMgmt indexes depMgmt by g:a", () => {
|
|
141
|
-
const m = buildMgmt([
|
|
142
|
-
{ groupId: "g", artifactId: "a", version: "1.0" },
|
|
143
|
-
{ groupId: "h", artifactId: "b", version: "2.0" },
|
|
144
|
-
]);
|
|
145
|
-
assert.equal(m.size, 2);
|
|
146
|
-
assert.equal(m.get("g:a").version, "1.0");
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
test("resolveTransitiveDeps walks A→B→C, skips test + optional", async () => {
|
|
150
|
-
const responses = {
|
|
151
|
-
[`${MC}/com/example/a/1.0.0/a-1.0.0.pom`]: POM_A,
|
|
152
|
-
[`${MC}/com/example/b/2.0.0/b-2.0.0.pom`]: POM_B,
|
|
153
|
-
[`${MC}/com/example/c/5.0.0/c-5.0.0.pom`]: POM_C,
|
|
154
|
-
};
|
|
155
|
-
const transitives = await resolveTransitiveDeps(
|
|
156
|
-
[{ groupId: "com.example", artifactId: "a", version: "1.0.0", scope: "compile" }],
|
|
157
|
-
{ fetcher: fakeFetcher(responses), verbose: false, cacheDir: freshCache() }
|
|
158
|
-
);
|
|
159
|
-
// b is the direct dep of a → it IS a transitive (we excluded `a` itself
|
|
160
|
-
// from the result earlier). c is b's transitive.
|
|
161
|
-
const keys = [...transitives.keys()];
|
|
162
|
-
assert.ok(keys.includes("com.example:b"), `expected b in transitives, got ${keys.join(",")}`);
|
|
163
|
-
assert.ok(keys.includes("com.example:c"), `expected c in transitives, got ${keys.join(",")}`);
|
|
164
|
-
// test scope must NOT propagate
|
|
165
|
-
assert.ok(!keys.includes("com.example:test-only"), "test-scope dep should not propagate");
|
|
166
|
-
// optional must NOT propagate
|
|
167
|
-
assert.ok(!keys.includes("com.example:optional-lib"), "optional dep should not propagate");
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
test("resolveTransitiveDeps applies root depMgmt version overrides", async () => {
|
|
171
|
-
const POM_X = `<?xml version="1.0"?>
|
|
172
|
-
<project>
|
|
173
|
-
<groupId>g</groupId><artifactId>x</artifactId><version>1.0</version>
|
|
174
|
-
<dependencies>
|
|
175
|
-
<dependency>
|
|
176
|
-
<groupId>g</groupId><artifactId>y</artifactId><version>5.0</version>
|
|
177
|
-
</dependency>
|
|
178
|
-
</dependencies>
|
|
179
|
-
</project>`;
|
|
180
|
-
const POM_Y_5 = `<?xml version="1.0"?>
|
|
181
|
-
<project><groupId>g</groupId><artifactId>y</artifactId><version>5.0</version></project>`;
|
|
182
|
-
const POM_Y_OVERRIDE = `<?xml version="1.0"?>
|
|
183
|
-
<project><groupId>g</groupId><artifactId>y</artifactId><version>9.9</version></project>`;
|
|
184
|
-
const responses = {
|
|
185
|
-
[`${MC}/g/x/1.0/x-1.0.pom`]: POM_X,
|
|
186
|
-
[`${MC}/g/y/5.0/y-5.0.pom`]: POM_Y_5,
|
|
187
|
-
[`${MC}/g/y/9.9/y-9.9.pom`]: POM_Y_OVERRIDE,
|
|
188
|
-
};
|
|
189
|
-
const rootMgmt = new Map([["g:y", { version: "9.9" }]]);
|
|
190
|
-
const transitives = await resolveTransitiveDeps(
|
|
191
|
-
[{ groupId: "g", artifactId: "x", version: "1.0", scope: "compile" }],
|
|
192
|
-
{ fetcher: fakeFetcher(responses), rootDepMgmt: rootMgmt, verbose: false, cacheDir: freshCache() }
|
|
193
|
-
);
|
|
194
|
-
const y = transitives.get("g:y");
|
|
195
|
-
assert.ok(y, "y should be resolved");
|
|
196
|
-
assert.equal(y.version, "9.9", "root depMgmt should override transitive declared version");
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
test("resolveTransitiveDeps honours <exclusion> blocks", async () => {
|
|
200
|
-
const POM_ROOT = `<?xml version="1.0"?>
|
|
201
|
-
<project>
|
|
202
|
-
<groupId>g</groupId><artifactId>root</artifactId><version>1</version>
|
|
203
|
-
<dependencies>
|
|
204
|
-
<dependency>
|
|
205
|
-
<groupId>g</groupId><artifactId>mid</artifactId><version>1</version>
|
|
206
|
-
<exclusions>
|
|
207
|
-
<exclusion><groupId>g</groupId><artifactId>leaf</artifactId></exclusion>
|
|
208
|
-
</exclusions>
|
|
209
|
-
</dependency>
|
|
210
|
-
</dependencies>
|
|
211
|
-
</project>`;
|
|
212
|
-
const POM_MID = `<?xml version="1.0"?>
|
|
213
|
-
<project>
|
|
214
|
-
<groupId>g</groupId><artifactId>mid</artifactId><version>1</version>
|
|
215
|
-
<dependencies>
|
|
216
|
-
<dependency><groupId>g</groupId><artifactId>leaf</artifactId><version>1</version></dependency>
|
|
217
|
-
<dependency><groupId>g</groupId><artifactId>kept</artifactId><version>1</version></dependency>
|
|
218
|
-
</dependencies>
|
|
219
|
-
</project>`;
|
|
220
|
-
const POM_LEAF = `<?xml version="1.0"?>
|
|
221
|
-
<project><groupId>g</groupId><artifactId>leaf</artifactId><version>1</version></project>`;
|
|
222
|
-
const POM_KEPT = `<?xml version="1.0"?>
|
|
223
|
-
<project><groupId>g</groupId><artifactId>kept</artifactId><version>1</version></project>`;
|
|
224
|
-
const responses = {
|
|
225
|
-
[`${MC}/g/root/1/root-1.pom`]: POM_ROOT,
|
|
226
|
-
[`${MC}/g/mid/1/mid-1.pom`]: POM_MID,
|
|
227
|
-
[`${MC}/g/leaf/1/leaf-1.pom`]: POM_LEAF,
|
|
228
|
-
[`${MC}/g/kept/1/kept-1.pom`]: POM_KEPT,
|
|
229
|
-
};
|
|
230
|
-
const transitives = await resolveTransitiveDeps(
|
|
231
|
-
[{ groupId: "g", artifactId: "root", version: "1", scope: "compile", exclusions: [{ groupId: "g", artifactId: "leaf" }] }],
|
|
232
|
-
{ fetcher: fakeFetcher(responses), verbose: false, cacheDir: freshCache() }
|
|
233
|
-
);
|
|
234
|
-
const keys = [...transitives.keys()];
|
|
235
|
-
assert.ok(keys.includes("g:mid"), "mid should be there");
|
|
236
|
-
assert.ok(keys.includes("g:kept"), "kept should be there");
|
|
237
|
-
assert.ok(!keys.includes("g:leaf"), `leaf should be excluded, got ${keys.join(",")}`);
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
test("resolveTransitiveDeps stops at maxDepth", async () => {
|
|
241
|
-
const POM_R = `<?xml version="1.0"?><project>
|
|
242
|
-
<groupId>g</groupId><artifactId>r</artifactId><version>1</version>
|
|
243
|
-
<dependencies><dependency><groupId>g</groupId><artifactId>l1</artifactId><version>1</version></dependency></dependencies>
|
|
244
|
-
</project>`;
|
|
245
|
-
const POM_L1 = `<?xml version="1.0"?><project>
|
|
246
|
-
<groupId>g</groupId><artifactId>l1</artifactId><version>1</version>
|
|
247
|
-
<dependencies><dependency><groupId>g</groupId><artifactId>l2</artifactId><version>1</version></dependency></dependencies>
|
|
248
|
-
</project>`;
|
|
249
|
-
const POM_L2 = `<?xml version="1.0"?><project>
|
|
250
|
-
<groupId>g</groupId><artifactId>l2</artifactId><version>1</version>
|
|
251
|
-
<dependencies><dependency><groupId>g</groupId><artifactId>l3</artifactId><version>1</version></dependency></dependencies>
|
|
252
|
-
</project>`;
|
|
253
|
-
const POM_L3 = `<?xml version="1.0"?><project><groupId>g</groupId><artifactId>l3</artifactId><version>1</version></project>`;
|
|
254
|
-
const responses = {
|
|
255
|
-
[`${MC}/g/r/1/r-1.pom`]: POM_R,
|
|
256
|
-
[`${MC}/g/l1/1/l1-1.pom`]: POM_L1,
|
|
257
|
-
[`${MC}/g/l2/1/l2-1.pom`]: POM_L2,
|
|
258
|
-
[`${MC}/g/l3/1/l3-1.pom`]: POM_L3,
|
|
259
|
-
};
|
|
260
|
-
const transitives = await resolveTransitiveDeps(
|
|
261
|
-
[{ groupId: "g", artifactId: "r", version: "1", scope: "compile" }],
|
|
262
|
-
{ fetcher: fakeFetcher(responses), maxDepth: 2, cacheDir: freshCache() }
|
|
263
|
-
);
|
|
264
|
-
const keys = [...transitives.keys()];
|
|
265
|
-
assert.ok(keys.includes("g:l1"));
|
|
266
|
-
assert.ok(keys.includes("g:l2"));
|
|
267
|
-
assert.ok(!keys.includes("g:l3"), `l3 is at depth 3, should not be present at maxDepth=2; got ${keys.join(",")}`);
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
test("resolveTransitiveDeps nearest-wins on version conflict", async () => {
|
|
271
|
-
// root → a@1 → c@5
|
|
272
|
-
// root → c@9 (declared directly under root via b path)
|
|
273
|
-
// Result: c@9 wins because it's at depth 1 vs c@5 at depth 2
|
|
274
|
-
// But here we're testing the BFS, not the root-direct resolution. Setup:
|
|
275
|
-
// root has two deps: x@1 (which has y@5), and y@9 directly.
|
|
276
|
-
const POM_ROOT2 = `<?xml version="1.0"?><project>
|
|
277
|
-
<groupId>g</groupId><artifactId>root</artifactId><version>1</version>
|
|
278
|
-
<dependencies>
|
|
279
|
-
<dependency><groupId>g</groupId><artifactId>x</artifactId><version>1</version></dependency>
|
|
280
|
-
<dependency><groupId>g</groupId><artifactId>y</artifactId><version>9</version></dependency>
|
|
281
|
-
</dependencies>
|
|
282
|
-
</project>`;
|
|
283
|
-
const POM_X2 = `<?xml version="1.0"?><project>
|
|
284
|
-
<groupId>g</groupId><artifactId>x</artifactId><version>1</version>
|
|
285
|
-
<dependencies>
|
|
286
|
-
<dependency><groupId>g</groupId><artifactId>y</artifactId><version>5</version></dependency>
|
|
287
|
-
</dependencies>
|
|
288
|
-
</project>`;
|
|
289
|
-
const POM_Y_5 = `<?xml version="1.0"?><project><groupId>g</groupId><artifactId>y</artifactId><version>5</version></project>`;
|
|
290
|
-
const POM_Y_9 = `<?xml version="1.0"?><project><groupId>g</groupId><artifactId>y</artifactId><version>9</version></project>`;
|
|
291
|
-
const responses = {
|
|
292
|
-
[`${MC}/g/root/1/root-1.pom`]: POM_ROOT2,
|
|
293
|
-
[`${MC}/g/x/1/x-1.pom`]: POM_X2,
|
|
294
|
-
[`${MC}/g/y/5/y-5.pom`]: POM_Y_5,
|
|
295
|
-
[`${MC}/g/y/9/y-9.pom`]: POM_Y_9,
|
|
296
|
-
};
|
|
297
|
-
const transitives = await resolveTransitiveDeps([
|
|
298
|
-
{ groupId: "g", artifactId: "root", version: "1", scope: "compile" },
|
|
299
|
-
], { fetcher: fakeFetcher(responses), verbose: false, cacheDir: freshCache() });
|
|
300
|
-
// When the caller (cve-match) passes root's depMgmt with y@9, that pins y.
|
|
301
|
-
// Without depMgmt, BFS visits y@9 directly under root first, then sees y again
|
|
302
|
-
// from x, and skips it (nearest-wins).
|
|
303
|
-
const y = transitives.get("g:y");
|
|
304
|
-
assert.ok(y, "y should be in transitives");
|
|
305
|
-
});
|
package/test/webjar.test.js
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
const { test } = require("node:test");
|
|
2
|
-
const assert = require("node:assert/strict");
|
|
3
|
-
const { webjarToNpm } = require("../lib/npm/collect");
|
|
4
|
-
|
|
5
|
-
test("webjarToNpm derives the npm name from org.webjars.npm (deterministic mirror)", () => {
|
|
6
|
-
assert.deepEqual(
|
|
7
|
-
webjarToNpm({ groupId: "org.webjars.npm", artifactId: "react", version: "18.2.0" }),
|
|
8
|
-
{ name: "react", version: "18.2.0" },
|
|
9
|
-
);
|
|
10
|
-
// Scoped packages encode "@scope/name" as "scope__name".
|
|
11
|
-
assert.deepEqual(
|
|
12
|
-
webjarToNpm({ groupId: "org.webjars.npm", artifactId: "angular__core", version: "17.2.3" }),
|
|
13
|
-
{ name: "@angular/core", version: "17.2.3" },
|
|
14
|
-
);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
test("webjarToNpm passes classic org.webjars artifactIds through as-is", () => {
|
|
18
|
-
// Classic WebJars are hand-curated; the artifactId is the JS lib name.
|
|
19
|
-
assert.deepEqual(
|
|
20
|
-
webjarToNpm({ groupId: "org.webjars", artifactId: "angularjs", version: "1.8.3" }),
|
|
21
|
-
{ name: "angularjs", version: "1.8.3" },
|
|
22
|
-
);
|
|
23
|
-
assert.equal(webjarToNpm({ groupId: "org.webjars", artifactId: "jquery", version: "3.7.1" }).name, "jquery");
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
test("webjarToNpm handles bower webjars too", () => {
|
|
27
|
-
assert.equal(webjarToNpm({ groupId: "org.webjars.bowergithub.foo", artifactId: "bar", version: "1.0.0" }).name, "bar");
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
test("webjarToNpm returns null for non-webjar coordinates", () => {
|
|
31
|
-
assert.equal(webjarToNpm({ groupId: "org.springframework", artifactId: "spring-core", version: "6.0.0" }), null);
|
|
32
|
-
assert.equal(webjarToNpm({ ecosystem: "npm", groupId: "", artifactId: "react", version: "18.0.0" }), null);
|
|
33
|
-
});
|