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.
Files changed (79) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/README.md +77 -15
  3. package/completions/fad-checker.bash +4 -1
  4. package/completions/fad-checker.zsh +9 -0
  5. package/data/eol-mapping.json +17 -0
  6. package/fad-checker.js +353 -241
  7. package/lib/codecs/codec.interface.js +27 -0
  8. package/lib/codecs/composer/parse.js +59 -0
  9. package/lib/codecs/composer/registry.js +92 -0
  10. package/lib/codecs/composer.codec.js +93 -0
  11. package/lib/codecs/index.js +37 -0
  12. package/lib/codecs/maven.codec.js +71 -0
  13. package/lib/{npm → codecs/npm}/collect.js +58 -35
  14. package/lib/{npm → codecs/npm}/parse.js +114 -10
  15. package/lib/{npm → codecs/npm}/registry.js +4 -3
  16. package/lib/codecs/npm.codec.js +52 -0
  17. package/lib/codecs/nuget/parse.js +75 -0
  18. package/lib/codecs/nuget/registry.js +88 -0
  19. package/lib/codecs/nuget.codec.js +94 -0
  20. package/lib/codecs/pypi/parse.js +163 -0
  21. package/lib/codecs/pypi/registry.js +89 -0
  22. package/lib/codecs/pypi.codec.js +102 -0
  23. package/lib/codecs/recipes.js +108 -0
  24. package/lib/codecs/select.js +27 -0
  25. package/lib/codecs/yarn.codec.js +19 -0
  26. package/lib/core.js +35 -1
  27. package/lib/cve-match.js +18 -11
  28. package/lib/cve-report.js +34 -70
  29. package/lib/dep-record.js +60 -0
  30. package/lib/deps-descriptor.js +110 -0
  31. package/lib/nvd.js +4 -3
  32. package/lib/osv.js +29 -18
  33. package/lib/outdated.js +20 -4
  34. package/lib/retire.js +77 -13
  35. package/lib/transitive.js +3 -3
  36. package/lib/ui.js +87 -0
  37. package/package.json +4 -2
  38. package/CLAUDE.md +0 -129
  39. package/docs/ARCHITECTURE.md +0 -154
  40. package/docs/USAGE.md +0 -179
  41. package/test/core.test.js +0 -153
  42. package/test/cpe.test.js +0 -166
  43. package/test/cve-download.test.js +0 -39
  44. package/test/cve-match.test.js +0 -217
  45. package/test/cve-report.test.js +0 -171
  46. package/test/fixtures/complex-enterprise/api/pom.xml +0 -32
  47. package/test/fixtures/complex-enterprise/build/pom.xml +0 -46
  48. package/test/fixtures/complex-enterprise/dao/pom.xml +0 -37
  49. package/test/fixtures/complex-enterprise/pom.xml +0 -66
  50. package/test/fixtures/complex-enterprise/web/pom.xml +0 -77
  51. package/test/fixtures/cve-samples/cve-non-java.json +0 -19
  52. package/test/fixtures/cve-samples/cve-product-only.json +0 -31
  53. package/test/fixtures/cve-samples/cve-with-packagename.json +0 -37
  54. package/test/fixtures/cve-samples/nvd-log4shell.json +0 -40
  55. package/test/fixtures/cve-samples/nvd-npm-lodash.json +0 -22
  56. package/test/fixtures/monorepo-mixed/libs/common-bom/pom.xml +0 -26
  57. package/test/fixtures/monorepo-mixed/packages/cli/package.json +0 -14
  58. package/test/fixtures/monorepo-mixed/packages/cli/yarn.lock +0 -41
  59. package/test/fixtures/monorepo-mixed/packages/no-lock/package.json +0 -9
  60. package/test/fixtures/monorepo-mixed/packages/web-app/package-lock.json +0 -71
  61. package/test/fixtures/monorepo-mixed/packages/web-app/package.json +0 -17
  62. package/test/fixtures/monorepo-mixed/pom.xml +0 -29
  63. package/test/fixtures/monorepo-mixed/services/api/pom.xml +0 -27
  64. package/test/fixtures/monorepo-mixed/services/worker/pom.xml +0 -28
  65. package/test/fixtures/private-lib-detection/core/pom.xml +0 -26
  66. package/test/fixtures/private-lib-detection/plugin/pom.xml +0 -23
  67. package/test/fixtures/private-lib-detection/pom.xml +0 -35
  68. package/test/fixtures/simple/app/pom.xml +0 -28
  69. package/test/fixtures/simple/lib/pom.xml +0 -18
  70. package/test/fixtures/simple/pom.xml +0 -24
  71. package/test/maven-repo.test.js +0 -111
  72. package/test/maven-version.test.js +0 -57
  73. package/test/monorepo.test.js +0 -132
  74. package/test/npm-registry.test.js +0 -64
  75. package/test/npm.test.js +0 -146
  76. package/test/outdated.test.js +0 -101
  77. package/test/snyk.test.js +0 -64
  78. package/test/transitive.test.js +0 -305
  79. package/test/webjar.test.js +0 -33
@@ -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
- });
@@ -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
- });
@@ -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
- });