release: WRNexusJS 0.7.0
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
import console from "node:console";
|
||||
import { createHash } from "node:crypto";
|
||||
import { readFileSync, readdirSync, writeFileSync } from "node:fs";
|
||||
import { dirname, join, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const rootManifest = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
|
||||
const components = [];
|
||||
|
||||
function npmPurl(name, version) {
|
||||
if (name.startsWith("@")) {
|
||||
const slash = name.indexOf("/");
|
||||
return `pkg:npm/%40${encodeURIComponent(name.slice(1, slash))}/${encodeURIComponent(name.slice(slash + 1))}@${version}`;
|
||||
}
|
||||
return `pkg:npm/${encodeURIComponent(name)}@${version}`;
|
||||
}
|
||||
|
||||
for (const entry of readdirSync(join(root, "packages"), { withFileTypes: true })) {
|
||||
if (!entry.isDirectory()) continue;
|
||||
const file = join(root, "packages", entry.name, "package.json");
|
||||
try {
|
||||
const manifest = JSON.parse(readFileSync(file, "utf8"));
|
||||
if (!manifest.name?.startsWith("@wrnexus/")) continue;
|
||||
components.push({
|
||||
type: "library",
|
||||
name: manifest.name,
|
||||
version: manifest.version,
|
||||
"bom-ref": npmPurl(manifest.name, manifest.version),
|
||||
purl: npmPurl(manifest.name, manifest.version),
|
||||
properties: [
|
||||
{ name: "wrnexus:workspace", value: "true" },
|
||||
{ name: "wrnexus:private", value: String(manifest.publishConfig?.access !== "public") },
|
||||
],
|
||||
});
|
||||
} catch {
|
||||
// Not a workspace package.
|
||||
}
|
||||
}
|
||||
|
||||
// Bun's text lockfile is JSON with optional trailing commas. Package tuples use
|
||||
// ["name@version", resolved, metadata, integrity], which is enough to include
|
||||
// every resolved transitive dependency without requiring node_modules.
|
||||
try {
|
||||
const lockSource = readFileSync(join(root, "bun.lock"), "utf8").replace(/,\s*([}\]])/g, "$1");
|
||||
const lock = JSON.parse(lockSource);
|
||||
const external = new Map();
|
||||
for (const value of Object.values(lock.packages ?? {})) {
|
||||
if (!Array.isArray(value) || typeof value[0] !== "string") continue;
|
||||
const specifier = value[0];
|
||||
const split = specifier.lastIndexOf("@");
|
||||
if (split <= 0) continue;
|
||||
const name = specifier.slice(0, split);
|
||||
const version = specifier.slice(split + 1);
|
||||
if (!name || !version || name.startsWith("@wrnexus/")) continue;
|
||||
const ref = npmPurl(name, version);
|
||||
const component = {
|
||||
type: "library",
|
||||
name,
|
||||
version,
|
||||
"bom-ref": ref,
|
||||
purl: ref,
|
||||
properties: [{ name: "wrnexus:dependency-source", value: "bun.lock" }],
|
||||
};
|
||||
const integrity = typeof value[3] === "string" ? value[3] : "";
|
||||
if (integrity.startsWith("sha512-")) {
|
||||
component.hashes = [{ alg: "SHA-512", content: integrity.slice("sha512-".length) }];
|
||||
}
|
||||
external.set(ref, component);
|
||||
}
|
||||
components.push(...external.values());
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Unable to parse bun.lock for the SBOM: ${error instanceof Error ? error.message : String(error)}`,
|
||||
{ cause: error },
|
||||
);
|
||||
}
|
||||
|
||||
components.sort((a, b) => {
|
||||
const left = `${a.name}@${a.version}`;
|
||||
const right = `${b.name}@${b.version}`;
|
||||
return left < right ? -1 : left > right ? 1 : 0;
|
||||
});
|
||||
|
||||
function deterministicUuid(value) {
|
||||
const hash = createHash("sha256").update(value).digest("hex").slice(0, 32).split("");
|
||||
hash[12] = "5";
|
||||
hash[16] = ((Number.parseInt(hash[16], 16) & 0x3) | 0x8).toString(16);
|
||||
const hex = hash.join("");
|
||||
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
||||
}
|
||||
|
||||
const version = String(rootManifest.version);
|
||||
const identity = JSON.stringify({ framework: "WRNexusJS", version, components });
|
||||
const document = {
|
||||
bomFormat: "CycloneDX",
|
||||
specVersion: "1.5",
|
||||
serialNumber: `urn:uuid:${deterministicUuid(identity)}`,
|
||||
version: 1,
|
||||
metadata: {
|
||||
component: {
|
||||
type: "framework",
|
||||
name: "WRNexusJS",
|
||||
version,
|
||||
"bom-ref": `pkg:generic/WRNexusJS@${version}`,
|
||||
},
|
||||
},
|
||||
components,
|
||||
};
|
||||
const file = join(root, `SBOM-${version}.cdx.json`);
|
||||
writeFileSync(file, `${JSON.stringify(document, null, 2)}\n`);
|
||||
console.log(`Generated ${file} with ${components.length} workspace and transitive components.`);
|
||||
Reference in New Issue
Block a user