UNPKG

@cyclonedx/cdxgen

Version:

Creates CycloneDX Software Bill of Materials (SBOM) from source or container image

98 lines (93 loc) 2.69 kB
import { PackageURL } from "packageurl-js"; import { isSpdxJsonLd } from "./bomUtils.js"; const toArray = (value) => { if (Array.isArray(value)) { return value; } if (value !== undefined && value !== null) { return [value]; } return []; }; const toCycloneDxLikeComponent = (spdxElement) => { const purl = spdxElement?.software_packageUrl; let group = ""; let name = spdxElement?.name || spdxElement?.spdxId || "unnamed-component"; let version = spdxElement?.software_packageVersion || ""; if (purl) { try { const purlObj = PackageURL.fromString(purl); group = purlObj.namespace || ""; name = purlObj.name || name; version = purlObj.version || version; } catch (_err) { // Keep SPDX element values when purl parsing fails. } } return { type: spdxElement?.type === "software_File" ? "file" : "library", group, name, version, purl, "bom-ref": purl || spdxElement?.spdxId || name, description: spdxElement?.description, }; }; export const toCycloneDxLikeBom = (bomJson) => { if (!isSpdxJsonLd(bomJson)) { return bomJson; } const graphElements = toArray(bomJson?.["@graph"]); const packageElements = graphElements.filter((element) => ["software_Package", "software_File"].includes(element?.type), ); const components = packageElements.map(toCycloneDxLikeComponent); const spdxIdToRef = new Map(); for (let index = 0; index < packageElements.length; index++) { const spdxId = packageElements[index]?.spdxId; if (spdxId) { spdxIdToRef.set(spdxId, components[index]["bom-ref"]); } } const dependencyMap = new Map(); for (const component of components) { dependencyMap.set(component["bom-ref"], new Set()); } for (const element of graphElements) { if ( element?.type !== "Relationship" || element?.relationshipType !== "dependsOn" ) { continue; } if (!element?.from || typeof element.from !== "string") { continue; } const fromRef = spdxIdToRef.get(element.from) || element.from; const toRefs = toArray(element?.to).map( (toRef) => spdxIdToRef.get(toRef) || toRef, ); if (!dependencyMap.has(fromRef)) { dependencyMap.set(fromRef, new Set()); } const dependsOnSet = dependencyMap.get(fromRef); for (const toRef of toRefs) { if (toRef) { dependsOnSet.add(toRef); } } } const dependencies = []; for (const [ref, dependsOnSet] of dependencyMap.entries()) { dependencies.push({ ref, dependsOn: Array.from(dependsOnSet).sort(), }); } return { ...bomJson, components, dependencies, }; };