fad-checker 2.0.0 → 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 +15 -0
- package/README.md +58 -10
- package/data/eol-mapping.json +5 -5
- package/fad-checker.js +289 -203
- package/lib/codecs/npm/collect.js +26 -17
- package/lib/codecs/npm/parse.js +114 -10
- package/lib/codecs/npm/registry.js +4 -3
- package/lib/codecs/npm.codec.js +1 -1
- package/lib/codecs/nuget.codec.js +4 -2
- package/lib/codecs/pypi/parse.js +104 -12
- package/lib/codecs/pypi.codec.js +27 -6
- package/lib/core.js +35 -1
- package/lib/cve-match.js +15 -5
- package/lib/deps-descriptor.js +110 -0
- package/lib/nvd.js +4 -3
- package/lib/osv.js +7 -4
- package/lib/outdated.js +7 -2
- package/lib/retire.js +77 -13
- package/lib/transitive.js +3 -3
- package/lib/ui.js +87 -0
- package/package.json +3 -2
- package/CLAUDE.md +0 -147
- package/docs/ARCHITECTURE.md +0 -182
- package/docs/USAGE.md +0 -187
- package/docs/superpowers/plans/2026-05-29-codec-composer.md +0 -556
- package/docs/superpowers/plans/2026-05-29-codec-foundation.md +0 -851
- package/docs/superpowers/plans/2026-05-29-codec-nuget.md +0 -432
- package/docs/superpowers/plans/2026-05-29-codec-pypi.md +0 -450
- package/docs/superpowers/specs/2026-05-29-codecs-multi-ecosystem-design.md +0 -251
- package/test/cli-ecosystem.test.js +0 -30
- package/test/codec-edge-cases.test.js +0 -131
- package/test/codec-integration.test.js +0 -119
- package/test/codecs.test.js +0 -92
- package/test/composer.test.js +0 -71
- 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 -180
- package/test/dep-record.test.js +0 -31
- 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/csharp-config/packages.config +0 -4
- package/test/fixtures/csharp-csproj/Directory.Packages.props +0 -5
- package/test/fixtures/csharp-csproj/app.csproj +0 -7
- package/test/fixtures/csharp-lock/packages.lock.json +0 -6
- 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 -10
- 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/php-app/composer.json +0 -5
- package/test/fixtures/php-app/composer.lock +0 -10
- package/test/fixtures/polyglot/cs/packages.lock.json +0 -1
- package/test/fixtures/polyglot/js/package-lock.json +0 -1
- package/test/fixtures/polyglot/js/package.json +0 -1
- package/test/fixtures/polyglot/mvn/pom.xml +0 -7
- package/test/fixtures/polyglot/php/composer.lock +0 -1
- package/test/fixtures/polyglot/py/poetry.lock +0 -3
- 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/python-pipenv/Pipfile.lock +0 -1
- package/test/fixtures/python-poetry/poetry.lock +0 -7
- package/test/fixtures/python-reqs/requirements.txt +0 -5
- package/test/fixtures/python-uv/uv.lock +0 -3
- 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 -150
- package/test/nuget.test.js +0 -66
- package/test/osv.test.js +0 -62
- package/test/outdated.test.js +0 -101
- package/test/pypi.test.js +0 -72
- package/test/snyk.test.js +0 -64
- package/test/transitive.test.js +0 -305
- package/test/webjar.test.js +0 -33
package/test/pypi.test.js
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
const test = require("node:test");
|
|
2
|
-
const assert = require("node:assert");
|
|
3
|
-
const path = require("path");
|
|
4
|
-
const { pep503, parsePoetryLock, parsePipfileLock, parseUvLock, parseRequirementsTxt } = require("../lib/codecs/pypi/parse");
|
|
5
|
-
|
|
6
|
-
const F = n => path.join(__dirname, "fixtures", n);
|
|
7
|
-
|
|
8
|
-
test("pep503 normalizes names (lowercase, collapse separators to -)", () => {
|
|
9
|
-
assert.strictEqual(pep503("Flask-SQLAlchemy"), "flask-sqlalchemy");
|
|
10
|
-
assert.strictEqual(pep503("zope.interface"), "zope-interface");
|
|
11
|
-
assert.strictEqual(pep503("My__Pkg"), "my-pkg");
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
test("parsePoetryLock returns PEP503 names + versions", () => {
|
|
15
|
-
const r = parsePoetryLock(F("python-poetry/poetry.lock"));
|
|
16
|
-
const m = Object.fromEntries(r.deps.map(d => [d.name, d.version]));
|
|
17
|
-
assert.strictEqual(m["requests"], "2.31.0");
|
|
18
|
-
assert.strictEqual(m["flask-sqlalchemy"], "3.0.5"); // normalized
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
test("parsePipfileLock splits default/develop, strips ==", () => {
|
|
22
|
-
const r = parsePipfileLock(F("python-pipenv/Pipfile.lock"));
|
|
23
|
-
const m = Object.fromEntries(r.deps.map(d => [d.name, d]));
|
|
24
|
-
assert.strictEqual(m["django"].version, "4.2.0");
|
|
25
|
-
assert.strictEqual(m["django"].scope, "prod");
|
|
26
|
-
assert.strictEqual(m["pytest"].scope, "dev");
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
test("parseUvLock reads [[package]]", () => {
|
|
30
|
-
const r = parseUvLock(F("python-uv/uv.lock"));
|
|
31
|
-
assert.strictEqual(r.deps.find(d => d.name === "numpy").version, "1.26.0");
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
test("parseRequirementsTxt keeps == pins, skips ranges/flags/comments", () => {
|
|
35
|
-
const r = parseRequirementsTxt(F("python-reqs/requirements.txt"));
|
|
36
|
-
const names = r.deps.map(d => d.name).sort();
|
|
37
|
-
assert.deepStrictEqual(names, ["fastapi", "urllib3"]);
|
|
38
|
-
assert.strictEqual(r.skipped, 1); // flask>=2.0
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
const { pypiToFindings } = require("../lib/codecs/pypi/registry");
|
|
42
|
-
test("pypiToFindings extracts latest, yanked-for-version, inactive classifier", () => {
|
|
43
|
-
const data = {
|
|
44
|
-
info: { version: "2.1.0", classifiers: ["Development Status :: 7 - Inactive"] },
|
|
45
|
-
releases: { "2.0.4": [{ yanked: true, yanked_reason: "security" }], "2.1.0": [{ yanked: false }] },
|
|
46
|
-
};
|
|
47
|
-
const f = pypiToFindings(data, { version: "2.0.4" });
|
|
48
|
-
assert.strictEqual(f.outdated.latest, "2.1.0");
|
|
49
|
-
assert.strictEqual(f.yanked.reason, "security");
|
|
50
|
-
assert.strictEqual(f.inactive, true);
|
|
51
|
-
const f2 = pypiToFindings(data, { version: "2.1.0" });
|
|
52
|
-
assert.strictEqual(f2.yanked, null);
|
|
53
|
-
assert.strictEqual(f2.outdated, null);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
const pypi = require("../lib/codecs/pypi.codec");
|
|
57
|
-
const { assertCodecShape } = require("../lib/codecs/codec.interface");
|
|
58
|
-
test("pypi codec: shape, detect, collect, coordKey pypi:<name>", async () => {
|
|
59
|
-
assertCodecShape(pypi);
|
|
60
|
-
assert.strictEqual(pypi.detect(F("python-poetry")), true);
|
|
61
|
-
const { deps } = await pypi.collect(F("python-poetry"), {});
|
|
62
|
-
const r = deps.get("pypi:requests");
|
|
63
|
-
assert.ok(r);
|
|
64
|
-
assert.strictEqual(r.ecosystem, "pypi");
|
|
65
|
-
assert.strictEqual(pypi.osvPackageName(r), "requests");
|
|
66
|
-
});
|
|
67
|
-
test("pypi collect: requirements.txt fallback warns + scans pins only", async () => {
|
|
68
|
-
const { deps, warnings } = await pypi.collect(F("python-reqs"), {});
|
|
69
|
-
assert.ok(deps.has("pypi:fastapi"));
|
|
70
|
-
assert.ok(!deps.has("pypi:flask"));
|
|
71
|
-
assert.ok(warnings.find(w => w.type === "no-lockfile"));
|
|
72
|
-
});
|
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/codecs/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
|
-
});
|