@mitre/hdf-diff 3.3.2 → 3.4.0
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/README.md +20 -4
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +161 -180
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -89,16 +89,32 @@ hdf diff old-results.json new-results.json
|
|
|
89
89
|
hdf diff old-results.json new-results.json --format markdown
|
|
90
90
|
hdf diff old-results.json new-results.json --json
|
|
91
91
|
|
|
92
|
-
# System drift detection
|
|
92
|
+
# System drift detection (auto-detected from document type)
|
|
93
93
|
hdf diff old-system.json new-system.json
|
|
94
94
|
|
|
95
95
|
# SBOM comparison
|
|
96
|
-
hdf diff --sbom old
|
|
96
|
+
hdf diff --sbom old.cdx.json new.cdx.json
|
|
97
97
|
|
|
98
|
-
#
|
|
99
|
-
hdf diff
|
|
98
|
+
# Encode the security outcome in the exit code (10=fixes, 11=regressions, 12=mixed, 13=baseline change)
|
|
99
|
+
hdf diff old-results.json new-results.json --detailed-exitcode
|
|
100
100
|
```
|
|
101
101
|
|
|
102
|
+
Example output — a scan where one requirement regressed and one was added:
|
|
103
|
+
|
|
104
|
+
```console
|
|
105
|
+
$ hdf diff old-results.json new-results.json
|
|
106
|
+
HDF Comparison: old-results.json → new-results.json
|
|
107
|
+
|
|
108
|
+
ID Title Old Status New Status State
|
|
109
|
+
------- ----------------------------- ---------- ---------- ---------
|
|
110
|
+
REQ-001 Test Requirement passed failed regressed
|
|
111
|
+
REQ-002 Audit logging must be enabled - passed new
|
|
112
|
+
|
|
113
|
+
Summary: 0 fixed, 1 regressed, 1 new, 0 absent, 0 unchanged, 0 updated (2 total)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The same comparison with `--format markdown` renders a GitHub-friendly table; `--json` emits an HDF Comparison document (`formatVersion`, `summary`, `requirementDiffs[]`) suitable for CI gating.
|
|
117
|
+
|
|
102
118
|
## Go usage
|
|
103
119
|
|
|
104
120
|
The diff engine is also available as a Go module:
|
package/dist/index.d.ts
CHANGED
|
@@ -235,9 +235,13 @@ interface HDFComparison {
|
|
|
235
235
|
baselineDiffs: BaselineDiff[];
|
|
236
236
|
/** Per-requirement diffs, sorted by id */
|
|
237
237
|
requirementDiffs: RequirementDiff[];
|
|
238
|
-
/** Per-component diffs (systemDrift mode only)
|
|
238
|
+
/** Per-component diffs (systemDrift mode only). A component's BOM changes
|
|
239
|
+
* surface as a field change on its boms[]. */
|
|
239
240
|
componentDiffs?: ComponentDiff[];
|
|
240
|
-
/**
|
|
241
|
+
/** RESERVED — not emitted by the current systemDrift comparison (component
|
|
242
|
+
* BOM changes surface via componentDiffs boms[] field changes; standalone SBOM
|
|
243
|
+
* package diffing is a separate `hdf diff <sbom> <sbom>` output). Retained for
|
|
244
|
+
* a future normalized package-level diff. */
|
|
241
245
|
packageDiffs?: PackageDiff[];
|
|
242
246
|
/** URI identifying the system being compared (systemDrift mode only) */
|
|
243
247
|
systemRef?: string;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/sbom.ts","../src/types.ts","../src/diff.ts","../src/status.ts","../src/summary.ts","../src/normalize.ts","../src/matching/types.ts","../src/matching/exact-id.ts","../src/matching/mapped-id.ts","../src/matching/cci-match.ts","../src/matching/fuzzy-match.ts","../src/matching/srg-deterministic.ts","../src/matching/srg-cci-tiebreak.ts","../src/matching/vendor-fuzzy-title.ts","../src/matching/index.ts","../src/validate.ts","../src/exit-codes.ts","../src/renderers/types.ts","../src/renderers/json.ts","../src/renderers/markdown.ts","../src/renderers/terminal.ts","../src/renderers/csv.ts","../src/renderers/index.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/sbom.ts","../src/types.ts","../src/diff.ts","../src/status.ts","../src/summary.ts","../src/normalize.ts","../src/matching/types.ts","../src/matching/exact-id.ts","../src/matching/mapped-id.ts","../src/matching/cci-match.ts","../src/matching/fuzzy-match.ts","../src/matching/srg-deterministic.ts","../src/matching/srg-cci-tiebreak.ts","../src/matching/vendor-fuzzy-title.ts","../src/matching/index.ts","../src/validate.ts","../src/exit-codes.ts","../src/renderers/types.ts","../src/renderers/json.ts","../src/renderers/markdown.ts","../src/renderers/terminal.ts","../src/renderers/csv.ts","../src/renderers/index.ts"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1203,165 +1203,6 @@ function validateComparison(doc) {
|
|
|
1203
1203
|
};
|
|
1204
1204
|
}
|
|
1205
1205
|
//#endregion
|
|
1206
|
-
//#region src/sbom.ts
|
|
1207
|
-
/**
|
|
1208
|
-
* Compare two SBOM documents (CycloneDX or SPDX JSON strings) and return
|
|
1209
|
-
* package-level diffs.
|
|
1210
|
-
*
|
|
1211
|
-
* @param oldJson - JSON string of the old SBOM document
|
|
1212
|
-
* @param newJson - JSON string of the new SBOM document
|
|
1213
|
-
* @returns Structured diff result with per-package state and aggregate counts
|
|
1214
|
-
* @throws Error if either input is not valid JSON or not a recognized SBOM format
|
|
1215
|
-
*/
|
|
1216
|
-
function diffSboms(oldJson, newJson) {
|
|
1217
|
-
const oldParsed = JSON.parse(oldJson);
|
|
1218
|
-
const newParsed = JSON.parse(newJson);
|
|
1219
|
-
const oldPackages = extractPackages(oldParsed);
|
|
1220
|
-
const newPackages = extractPackages(newParsed);
|
|
1221
|
-
const oldMap = buildPackageMap(oldPackages);
|
|
1222
|
-
const newMap = buildPackageMap(newPackages);
|
|
1223
|
-
const diffs = [];
|
|
1224
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1225
|
-
for (const [key, oldPkg] of oldMap) {
|
|
1226
|
-
seen.add(key);
|
|
1227
|
-
const newPkg = newMap.get(key);
|
|
1228
|
-
if (newPkg) if (oldPkg.version !== newPkg.version) diffs.push({
|
|
1229
|
-
purl: newPkg.purl || newPkg.name,
|
|
1230
|
-
name: newPkg.name,
|
|
1231
|
-
state: "updated",
|
|
1232
|
-
oldVersion: oldPkg.version,
|
|
1233
|
-
newVersion: newPkg.version,
|
|
1234
|
-
licenses: newPkg.licenses.length > 0 ? newPkg.licenses : void 0
|
|
1235
|
-
});
|
|
1236
|
-
else diffs.push({
|
|
1237
|
-
purl: oldPkg.purl || oldPkg.name,
|
|
1238
|
-
name: oldPkg.name,
|
|
1239
|
-
state: "unchanged"
|
|
1240
|
-
});
|
|
1241
|
-
else diffs.push({
|
|
1242
|
-
purl: oldPkg.purl || oldPkg.name,
|
|
1243
|
-
name: oldPkg.name,
|
|
1244
|
-
state: "removed",
|
|
1245
|
-
oldVersion: oldPkg.version
|
|
1246
|
-
});
|
|
1247
|
-
}
|
|
1248
|
-
for (const [key, newPkg] of newMap) if (!seen.has(key)) diffs.push({
|
|
1249
|
-
purl: newPkg.purl || newPkg.name,
|
|
1250
|
-
name: newPkg.name,
|
|
1251
|
-
state: "added",
|
|
1252
|
-
newVersion: newPkg.version,
|
|
1253
|
-
licenses: newPkg.licenses.length > 0 ? newPkg.licenses : void 0
|
|
1254
|
-
});
|
|
1255
|
-
diffs.sort((a, b) => a.name.localeCompare(b.name));
|
|
1256
|
-
let added = 0;
|
|
1257
|
-
let removed = 0;
|
|
1258
|
-
let updated = 0;
|
|
1259
|
-
let unchanged = 0;
|
|
1260
|
-
for (const d of diffs) switch (d.state) {
|
|
1261
|
-
case "added":
|
|
1262
|
-
added++;
|
|
1263
|
-
break;
|
|
1264
|
-
case "removed":
|
|
1265
|
-
removed++;
|
|
1266
|
-
break;
|
|
1267
|
-
case "updated":
|
|
1268
|
-
updated++;
|
|
1269
|
-
break;
|
|
1270
|
-
case "unchanged":
|
|
1271
|
-
unchanged++;
|
|
1272
|
-
break;
|
|
1273
|
-
}
|
|
1274
|
-
return {
|
|
1275
|
-
packageDiffs: diffs,
|
|
1276
|
-
added,
|
|
1277
|
-
removed,
|
|
1278
|
-
updated,
|
|
1279
|
-
unchanged
|
|
1280
|
-
};
|
|
1281
|
-
}
|
|
1282
|
-
/**
|
|
1283
|
-
* Extract packages from an SBOM document, auto-detecting the format.
|
|
1284
|
-
*/
|
|
1285
|
-
function extractPackages(doc) {
|
|
1286
|
-
if (doc["bomFormat"] === "CycloneDX") return extractCycloneDXPackages(doc);
|
|
1287
|
-
if (typeof doc["spdxVersion"] === "string") return extractSPDXPackages(doc);
|
|
1288
|
-
throw new Error("Unrecognized SBOM format: expected CycloneDX (bomFormat) or SPDX (spdxVersion)");
|
|
1289
|
-
}
|
|
1290
|
-
/**
|
|
1291
|
-
* Extract packages from a CycloneDX document.
|
|
1292
|
-
* Reads components[] with purl, name, version fields.
|
|
1293
|
-
*/
|
|
1294
|
-
function extractCycloneDXPackages(doc) {
|
|
1295
|
-
const components = doc["components"];
|
|
1296
|
-
if (!Array.isArray(components)) return [];
|
|
1297
|
-
return components.map((comp) => {
|
|
1298
|
-
const licenses = extractCycloneDXLicenses(comp);
|
|
1299
|
-
return {
|
|
1300
|
-
purl: comp["purl"] ?? "",
|
|
1301
|
-
name: comp["name"] ?? "",
|
|
1302
|
-
version: comp["version"] ?? "",
|
|
1303
|
-
licenses
|
|
1304
|
-
};
|
|
1305
|
-
});
|
|
1306
|
-
}
|
|
1307
|
-
/**
|
|
1308
|
-
* Extract license strings from a CycloneDX component.
|
|
1309
|
-
* CycloneDX licenses can be in `licenses[].license.id` or `licenses[].license.name`.
|
|
1310
|
-
*/
|
|
1311
|
-
function extractCycloneDXLicenses(comp) {
|
|
1312
|
-
const licensesArr = comp["licenses"];
|
|
1313
|
-
if (!Array.isArray(licensesArr)) return [];
|
|
1314
|
-
const result = [];
|
|
1315
|
-
for (const entry of licensesArr) {
|
|
1316
|
-
const license = entry["license"];
|
|
1317
|
-
if (license) {
|
|
1318
|
-
const id = license["id"];
|
|
1319
|
-
const name = license["name"];
|
|
1320
|
-
if (id) result.push(id);
|
|
1321
|
-
else if (name) result.push(name);
|
|
1322
|
-
}
|
|
1323
|
-
}
|
|
1324
|
-
return result;
|
|
1325
|
-
}
|
|
1326
|
-
/**
|
|
1327
|
-
* Extract packages from an SPDX document.
|
|
1328
|
-
* Reads packages[] with name, versionInfo, and externalRefs (for PURL).
|
|
1329
|
-
*/
|
|
1330
|
-
function extractSPDXPackages(doc) {
|
|
1331
|
-
const packages = doc["packages"];
|
|
1332
|
-
if (!Array.isArray(packages)) return [];
|
|
1333
|
-
return packages.map((pkg) => {
|
|
1334
|
-
const purl = extractSPDXPurl(pkg);
|
|
1335
|
-
const license = pkg["licenseConcluded"] ?? pkg["licenseDeclared"] ?? "";
|
|
1336
|
-
return {
|
|
1337
|
-
purl,
|
|
1338
|
-
name: pkg["name"] ?? "",
|
|
1339
|
-
version: pkg["versionInfo"] ?? "",
|
|
1340
|
-
licenses: license && license !== "NOASSERTION" ? [license] : []
|
|
1341
|
-
};
|
|
1342
|
-
});
|
|
1343
|
-
}
|
|
1344
|
-
/**
|
|
1345
|
-
* Extract a PURL from an SPDX package's externalRefs array.
|
|
1346
|
-
*/
|
|
1347
|
-
function extractSPDXPurl(pkg) {
|
|
1348
|
-
const refs = pkg["externalRefs"];
|
|
1349
|
-
if (!Array.isArray(refs)) return "";
|
|
1350
|
-
for (const ref of refs) if (ref["referenceType"] === "purl") return ref["referenceLocator"] ?? "";
|
|
1351
|
-
return "";
|
|
1352
|
-
}
|
|
1353
|
-
/**
|
|
1354
|
-
* Build a lookup map of packages indexed by name (version-independent key).
|
|
1355
|
-
*/
|
|
1356
|
-
function buildPackageMap(packages) {
|
|
1357
|
-
const map = /* @__PURE__ */ new Map();
|
|
1358
|
-
for (const pkg of packages) {
|
|
1359
|
-
const key = pkg.name || pkg.purl;
|
|
1360
|
-
if (key) map.set(key, pkg);
|
|
1361
|
-
}
|
|
1362
|
-
return map;
|
|
1363
|
-
}
|
|
1364
|
-
//#endregion
|
|
1365
1206
|
//#region src/diff.ts
|
|
1366
1207
|
const DEFAULT_TRACKED_FIELDS = [
|
|
1367
1208
|
"impact",
|
|
@@ -2197,7 +2038,8 @@ const COMPONENT_TRACKED_FIELDS = [
|
|
|
2197
2038
|
"description",
|
|
2198
2039
|
"baselineRefs",
|
|
2199
2040
|
"inputOverrides",
|
|
2200
|
-
"
|
|
2041
|
+
"boms",
|
|
2042
|
+
"integrity",
|
|
2201
2043
|
"targetSelector"
|
|
2202
2044
|
];
|
|
2203
2045
|
/**
|
|
@@ -2279,8 +2121,6 @@ function diffSystems(oldSystem, newSystem, options) {
|
|
|
2279
2121
|
const dataFlowChanges = diffDataFlows(oldSystem, newSystem);
|
|
2280
2122
|
if (dataFlowChanges.length > 0) extensions["dataFlowChanges"] = dataFlowChanges;
|
|
2281
2123
|
if (Object.keys(extensions).length > 0) comparison.extensions = extensions;
|
|
2282
|
-
const allPackageDiffs = diffEmbeddedSboms(pairs);
|
|
2283
|
-
if (allPackageDiffs.length > 0) comparison.packageDiffs = allPackageDiffs;
|
|
2284
2124
|
if (options?.validateOutput) {
|
|
2285
2125
|
const result = validateComparison(comparison);
|
|
2286
2126
|
/* c8 ignore start */
|
|
@@ -2391,24 +2231,6 @@ function diffDataFlows(oldSystem, newSystem) {
|
|
|
2391
2231
|
return changes;
|
|
2392
2232
|
}
|
|
2393
2233
|
/**
|
|
2394
|
-
* Diff embedded SBOMs across matched component pairs.
|
|
2395
|
-
*/
|
|
2396
|
-
function diffEmbeddedSboms(pairs) {
|
|
2397
|
-
const allDiffs = [];
|
|
2398
|
-
for (const { oldComp, newComp } of pairs) {
|
|
2399
|
-
if (!oldComp || !newComp) continue;
|
|
2400
|
-
const oldSbom = oldComp["sbom"];
|
|
2401
|
-
const newSbom = newComp["sbom"];
|
|
2402
|
-
if (!oldSbom || !newSbom) continue;
|
|
2403
|
-
if (typeof oldSbom !== "object" || typeof newSbom !== "object") continue;
|
|
2404
|
-
try {
|
|
2405
|
-
const result = diffSboms(JSON.stringify(oldSbom), JSON.stringify(newSbom));
|
|
2406
|
-
allDiffs.push(...result.packageDiffs);
|
|
2407
|
-
} catch {}
|
|
2408
|
-
}
|
|
2409
|
-
return allDiffs;
|
|
2410
|
-
}
|
|
2411
|
-
/**
|
|
2412
2234
|
* Compute field-level changes for component or system fields.
|
|
2413
2235
|
* Uses JSON Patch-like op/path format.
|
|
2414
2236
|
*/
|
|
@@ -2894,6 +2716,165 @@ function render(comparison, format, options) {
|
|
|
2894
2716
|
}
|
|
2895
2717
|
}
|
|
2896
2718
|
//#endregion
|
|
2719
|
+
//#region src/sbom.ts
|
|
2720
|
+
/**
|
|
2721
|
+
* Compare two SBOM documents (CycloneDX or SPDX JSON strings) and return
|
|
2722
|
+
* package-level diffs.
|
|
2723
|
+
*
|
|
2724
|
+
* @param oldJson - JSON string of the old SBOM document
|
|
2725
|
+
* @param newJson - JSON string of the new SBOM document
|
|
2726
|
+
* @returns Structured diff result with per-package state and aggregate counts
|
|
2727
|
+
* @throws Error if either input is not valid JSON or not a recognized SBOM format
|
|
2728
|
+
*/
|
|
2729
|
+
function diffSboms(oldJson, newJson) {
|
|
2730
|
+
const oldParsed = JSON.parse(oldJson);
|
|
2731
|
+
const newParsed = JSON.parse(newJson);
|
|
2732
|
+
const oldPackages = extractPackages(oldParsed);
|
|
2733
|
+
const newPackages = extractPackages(newParsed);
|
|
2734
|
+
const oldMap = buildPackageMap(oldPackages);
|
|
2735
|
+
const newMap = buildPackageMap(newPackages);
|
|
2736
|
+
const diffs = [];
|
|
2737
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2738
|
+
for (const [key, oldPkg] of oldMap) {
|
|
2739
|
+
seen.add(key);
|
|
2740
|
+
const newPkg = newMap.get(key);
|
|
2741
|
+
if (newPkg) if (oldPkg.version !== newPkg.version) diffs.push({
|
|
2742
|
+
purl: newPkg.purl || newPkg.name,
|
|
2743
|
+
name: newPkg.name,
|
|
2744
|
+
state: "updated",
|
|
2745
|
+
oldVersion: oldPkg.version,
|
|
2746
|
+
newVersion: newPkg.version,
|
|
2747
|
+
licenses: newPkg.licenses.length > 0 ? newPkg.licenses : void 0
|
|
2748
|
+
});
|
|
2749
|
+
else diffs.push({
|
|
2750
|
+
purl: oldPkg.purl || oldPkg.name,
|
|
2751
|
+
name: oldPkg.name,
|
|
2752
|
+
state: "unchanged"
|
|
2753
|
+
});
|
|
2754
|
+
else diffs.push({
|
|
2755
|
+
purl: oldPkg.purl || oldPkg.name,
|
|
2756
|
+
name: oldPkg.name,
|
|
2757
|
+
state: "removed",
|
|
2758
|
+
oldVersion: oldPkg.version
|
|
2759
|
+
});
|
|
2760
|
+
}
|
|
2761
|
+
for (const [key, newPkg] of newMap) if (!seen.has(key)) diffs.push({
|
|
2762
|
+
purl: newPkg.purl || newPkg.name,
|
|
2763
|
+
name: newPkg.name,
|
|
2764
|
+
state: "added",
|
|
2765
|
+
newVersion: newPkg.version,
|
|
2766
|
+
licenses: newPkg.licenses.length > 0 ? newPkg.licenses : void 0
|
|
2767
|
+
});
|
|
2768
|
+
diffs.sort((a, b) => a.name.localeCompare(b.name));
|
|
2769
|
+
let added = 0;
|
|
2770
|
+
let removed = 0;
|
|
2771
|
+
let updated = 0;
|
|
2772
|
+
let unchanged = 0;
|
|
2773
|
+
for (const d of diffs) switch (d.state) {
|
|
2774
|
+
case "added":
|
|
2775
|
+
added++;
|
|
2776
|
+
break;
|
|
2777
|
+
case "removed":
|
|
2778
|
+
removed++;
|
|
2779
|
+
break;
|
|
2780
|
+
case "updated":
|
|
2781
|
+
updated++;
|
|
2782
|
+
break;
|
|
2783
|
+
case "unchanged":
|
|
2784
|
+
unchanged++;
|
|
2785
|
+
break;
|
|
2786
|
+
}
|
|
2787
|
+
return {
|
|
2788
|
+
packageDiffs: diffs,
|
|
2789
|
+
added,
|
|
2790
|
+
removed,
|
|
2791
|
+
updated,
|
|
2792
|
+
unchanged
|
|
2793
|
+
};
|
|
2794
|
+
}
|
|
2795
|
+
/**
|
|
2796
|
+
* Extract packages from an SBOM document, auto-detecting the format.
|
|
2797
|
+
*/
|
|
2798
|
+
function extractPackages(doc) {
|
|
2799
|
+
if (doc["bomFormat"] === "CycloneDX") return extractCycloneDXPackages(doc);
|
|
2800
|
+
if (typeof doc["spdxVersion"] === "string") return extractSPDXPackages(doc);
|
|
2801
|
+
throw new Error("Unrecognized SBOM format: expected CycloneDX (bomFormat) or SPDX (spdxVersion)");
|
|
2802
|
+
}
|
|
2803
|
+
/**
|
|
2804
|
+
* Extract packages from a CycloneDX document.
|
|
2805
|
+
* Reads components[] with purl, name, version fields.
|
|
2806
|
+
*/
|
|
2807
|
+
function extractCycloneDXPackages(doc) {
|
|
2808
|
+
const components = doc["components"];
|
|
2809
|
+
if (!Array.isArray(components)) return [];
|
|
2810
|
+
return components.map((comp) => {
|
|
2811
|
+
const licenses = extractCycloneDXLicenses(comp);
|
|
2812
|
+
return {
|
|
2813
|
+
purl: comp["purl"] ?? "",
|
|
2814
|
+
name: comp["name"] ?? "",
|
|
2815
|
+
version: comp["version"] ?? "",
|
|
2816
|
+
licenses
|
|
2817
|
+
};
|
|
2818
|
+
});
|
|
2819
|
+
}
|
|
2820
|
+
/**
|
|
2821
|
+
* Extract license strings from a CycloneDX component.
|
|
2822
|
+
* CycloneDX licenses can be in `licenses[].license.id` or `licenses[].license.name`.
|
|
2823
|
+
*/
|
|
2824
|
+
function extractCycloneDXLicenses(comp) {
|
|
2825
|
+
const licensesArr = comp["licenses"];
|
|
2826
|
+
if (!Array.isArray(licensesArr)) return [];
|
|
2827
|
+
const result = [];
|
|
2828
|
+
for (const entry of licensesArr) {
|
|
2829
|
+
const license = entry["license"];
|
|
2830
|
+
if (license) {
|
|
2831
|
+
const id = license["id"];
|
|
2832
|
+
const name = license["name"];
|
|
2833
|
+
if (id) result.push(id);
|
|
2834
|
+
else if (name) result.push(name);
|
|
2835
|
+
}
|
|
2836
|
+
}
|
|
2837
|
+
return result;
|
|
2838
|
+
}
|
|
2839
|
+
/**
|
|
2840
|
+
* Extract packages from an SPDX document.
|
|
2841
|
+
* Reads packages[] with name, versionInfo, and externalRefs (for PURL).
|
|
2842
|
+
*/
|
|
2843
|
+
function extractSPDXPackages(doc) {
|
|
2844
|
+
const packages = doc["packages"];
|
|
2845
|
+
if (!Array.isArray(packages)) return [];
|
|
2846
|
+
return packages.map((pkg) => {
|
|
2847
|
+
const purl = extractSPDXPurl(pkg);
|
|
2848
|
+
const license = pkg["licenseConcluded"] ?? pkg["licenseDeclared"] ?? "";
|
|
2849
|
+
return {
|
|
2850
|
+
purl,
|
|
2851
|
+
name: pkg["name"] ?? "",
|
|
2852
|
+
version: pkg["versionInfo"] ?? "",
|
|
2853
|
+
licenses: license && license !== "NOASSERTION" ? [license] : []
|
|
2854
|
+
};
|
|
2855
|
+
});
|
|
2856
|
+
}
|
|
2857
|
+
/**
|
|
2858
|
+
* Extract a PURL from an SPDX package's externalRefs array.
|
|
2859
|
+
*/
|
|
2860
|
+
function extractSPDXPurl(pkg) {
|
|
2861
|
+
const refs = pkg["externalRefs"];
|
|
2862
|
+
if (!Array.isArray(refs)) return "";
|
|
2863
|
+
for (const ref of refs) if (ref["referenceType"] === "purl") return ref["referenceLocator"] ?? "";
|
|
2864
|
+
return "";
|
|
2865
|
+
}
|
|
2866
|
+
/**
|
|
2867
|
+
* Build a lookup map of packages indexed by name (version-independent key).
|
|
2868
|
+
*/
|
|
2869
|
+
function buildPackageMap(packages) {
|
|
2870
|
+
const map = /* @__PURE__ */ new Map();
|
|
2871
|
+
for (const pkg of packages) {
|
|
2872
|
+
const key = pkg.name || pkg.purl;
|
|
2873
|
+
if (key) map.set(key, pkg);
|
|
2874
|
+
}
|
|
2875
|
+
return map;
|
|
2876
|
+
}
|
|
2877
|
+
//#endregion
|
|
2897
2878
|
export { EXIT_DETAILED_BASELINE_CHANGED, EXIT_DETAILED_DRIFT_ONLY, EXIT_DETAILED_ERROR, EXIT_DETAILED_FIXES_ONLY, EXIT_DETAILED_IDENTICAL, EXIT_DETAILED_MIXED, EXIT_DETAILED_REGRESSIONS_ONLY, EXIT_DIFFERENCES, EXIT_ERROR, EXIT_IDENTICAL, classifyChangeReasons, classifyDiffStatus, computeDetailedExitCode, computeEffectiveStatus, computeExitCode, computeSummary, createCciMatchStrategy, createExactIdStrategy, createFuzzyTitleStrategy, createMappedIdStrategy, createSrgCciTiebreakStrategy, createSrgDeterministicStrategy, createVendorFuzzyTitleStrategy, diffBaselines, diffHdf, diffSboms, diffSystems, isV1Format, jaccardSimilarity, levenshteinDistance, matchRequirements, normalizeToV2, normalizedLevenshtein, render, renderCsv, renderJson, renderMarkdown, renderTerminal, tokenize, validateComparison };
|
|
2898
2879
|
|
|
2899
2880
|
//# sourceMappingURL=index.js.map
|