#!/usr/bin/env node import { readFile, mkdir, writeFile } from "node:fs/promises"; import { dirname, resolve } from "node:path"; const output = resolve(process.argv[2] || "artifacts/mebbling.spdx.json"); const lock = JSON.parse(await readFile("package-lock.json", "utf8")); const packages = Object.entries(lock.packages) .filter(([path, value]) => path.startsWith("node_modules/") && value.version) .map(([path, value]) => { const name = path.slice("node_modules/".length); return { SPDXID: `SPDXRef-Package-${name.replace(/[^A-Za-z0-9.-]/g, "-")}`, name, versionInfo: value.version, downloadLocation: value.resolved || "NOASSERTION", licenseConcluded: "NOASSERTION", licenseDeclared: value.license || "NOASSERTION", checksums: value.integrity ? [{ algorithm: "SHA512", checksumValue: value.integrity.replace(/^sha512-/, "") }] : [], externalRefs: [{ referenceCategory: "PACKAGE-MANAGER", referenceType: "purl", referenceLocator: `pkg:npm/${encodeURIComponent(name).replace("%40", "@")}@${value.version}` }] }; }); const sbom = { spdxVersion: "SPDX-2.3", dataLicense: "CC0-1.0", SPDXID: "SPDXRef-DOCUMENT", name: "mebbling", documentNamespace: `https://gitea.fishking.studio/tangsongdayo/Mebbling/sbom/${lock.version}`, creationInfo: { created: new Date().toISOString(), creators: ["Tool: Mebbling package-lock SBOM generator"] }, packages: [{ SPDXID: "SPDXRef-Mebbling", name: lock.name, versionInfo: lock.version, downloadLocation: "NOASSERTION", licenseConcluded: "NOASSERTION", licenseDeclared: lock.packages[""].license || "NOASSERTION" }, ...packages], relationships: packages.map((item) => ({ spdxElementId: "SPDXRef-Mebbling", relationshipType: "DEPENDS_ON", relatedSpdxElement: item.SPDXID })) }; await mkdir(dirname(output), { recursive: true }); await writeFile(output, `${JSON.stringify(sbom, null, 2)}\n`); console.log(`Generated SPDX SBOM with ${packages.length} packages: ${output}`);