scripts/generate-cua-sbom.mjs
Rama main · réplica de lectura del repositorio original.
// Maintainer tool: merge cargo-about's root-scoped views of the two shipped
// executables into deterministic notices, a license report, and a CycloneDX
// inventory. Raw workspace-wide `cargo metadata` must not be used here: its
// feature-unified resolve graph includes dependencies enabled only by other
// workspace members.
import { createHash } from "node:crypto";
import { mkdir, readFile, writeFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
const [
driverReportPath,
cursorThemeReportPath,
cargoLockPath,
outputDirectory,
] = process.argv.slice(2);
if (
!driverReportPath ||
!cursorThemeReportPath ||
!cargoLockPath ||
!outputDirectory
) {
throw new Error(
"usage: node scripts/generate-cua-sbom.mjs " +
"<cua-driver.cargo-about.json> <cursor-theme.cargo-about.json> " +
"<upstream-Cargo.lock> <output-directory>",
);
}
const SOURCE_COMMIT = "bd0e299c67dec6397b3d41a259452b2ea13a1cd5";
const SOURCE_ROOT = `https://github.com/trycua/cua/tree/${SOURCE_COMMIT}/libs/cua-driver/rust`;
const RELEASE_VERSION = "0.41.3";
const ARCHIVE_SHA256 =
"a00a7bc4a648adbcbc93bfbf5a058daf71ce8838ee172e33b197aaf649ecee14";
const DRIVER_SHA256 =
"83030028e7c9e3f4d753b3b27025135d44eb8d52f562cce0aaaf95777bc1b41f";
const CURSOR_THEME_SHA256 =
"d39efb960d207e7328d7a9dd48e3b6565f74be073f9c2ca8dcba86a4d0f28312";
const CARGO_LOCK_SHA256 =
"02ee8ebd8415e097036870a7fd39ba379c5e0d212a116715ca0660e83116f447";
const INTER_LICENSE_SHA256 =
"ecfea75d8a36217d19528567070745a516072ffe907c9fffecbab92eb3b6fc59";
const INTER_FONT_SHA256 =
"29160a80ff49ddcab2c97711247e08b1fab27a484a329ce8b813d820dc559031";
const INTER_VERSION = "4.001";
const INTER_COMMIT = "66647c0bbbe41a850d79d9c76fb13add3378940f";
const REGISTRY_SOURCE_PREFIX =
"registry+https://github.com/rust-lang/crates.io-index";
const EXPECTED_DRIVER_REGISTRY_COUNT = 376;
const EXPECTED_CURSOR_THEME_REGISTRY_COUNT = 239;
const EXPECTED_REGISTRY_UNION_COUNT = 381;
// cua-telemetry lives in the separate libs/cua workspace and carries that
// workspace's version; every other Cua package is versioned with the driver.
const TELEMETRY_VERSION = "0.12.1";
const EXPECTED_DRIVER_LOCAL = Object.freeze([
`cua-driver@${RELEASE_VERSION}`,
`cua-telemetry@${TELEMETRY_VERSION}`,
`cursor-overlay@${RELEASE_VERSION}`,
`pip-preview@${RELEASE_VERSION}`,
`platform-linux@${RELEASE_VERSION}`,
]);
const EXPECTED_CURSOR_THEME_LOCAL = Object.freeze([
`cursor-overlay@${RELEASE_VERSION}`,
]);
const EXPECTED_CURSOR_THEME_ONLY = Object.freeze([
"bumpalo@3.20.2",
"typed-path@0.12.3",
"zip@8.6.0",
"zlib-rs@0.6.6",
"zopfli@0.8.3",
]);
const EXPECTED_MPL_COMPONENTS = Object.freeze([
"option-ext@0.2.0",
"uniffi@0.31.0",
"uniffi_core@0.31.0",
"uniffi_internal_macros@0.31.0",
"uniffi_macros@0.31.0",
"uniffi_meta@0.31.0",
"uniffi_pipeline@0.31.0",
]);
// Every Cua path package in the two shipped graphs (`cargo tree -e normal,build`
// for the release target), including publish = false crates that cargo-about
// leaves out of its local roots.
const TRYCUA_COMPONENTS = Object.freeze(
[
"cua-cursor-motion",
"cua-driver",
"cua-driver-contract",
"cua-driver-core",
"cua-driver-sdk",
"cursor-overlay",
"cursor-theme-cli",
"pip-preview",
"platform-linux",
]
.map((name) =>
Object.freeze({
name,
version: RELEASE_VERSION,
url: `${SOURCE_ROOT}/crates/${name}`,
}),
)
.concat(
Object.freeze({
name: "cua-telemetry",
version: TELEMETRY_VERSION,
url: `https://github.com/trycua/cua/tree/${SOURCE_COMMIT}/libs/cua/crates/cua-telemetry`,
}),
),
);
const TRYCUA_COUNT_WORD = "ten";
function sha256(bytes) {
return createHash("sha256").update(bytes).digest("hex");
}
// A stable name-based UUID (version 5 layout) derived from the archive digest,
// so a regenerated SBOM for the same archive keeps the same serial number.
function archiveSerialNumber(digest) {
const hex = digest.slice(0, 32).split("");
hex[12] = "5";
hex[16] = ((Number.parseInt(hex[16], 16) & 0x3) | 0x8).toString(16);
const value = hex.join("");
return `urn:uuid:${value.slice(0, 8)}-${value.slice(8, 12)}-${value.slice(12, 16)}-${value.slice(16, 20)}-${value.slice(20, 32)}`;
}
function packageKey(pkg) {
return `${pkg.name}@${pkg.version}`;
}
function sorted(values) {
return [...values].sort((left, right) => left.localeCompare(right));
}
function assertExactSet(actual, expected, label) {
const actualSorted = sorted(actual);
const expectedSorted = sorted(expected);
if (JSON.stringify(actualSorted) !== JSON.stringify(expectedSorted)) {
throw new Error(
`${label} differs; expected ${expectedSorted.join(", ")}; found ${actualSorted.join(", ")}`,
);
}
}
function isRegistryPackage(pkg) {
return (
typeof pkg?.source === "string" &&
pkg.source.startsWith(REGISTRY_SOURCE_PREFIX)
);
}
function inspectCargoAboutReport(report, label) {
if (!Array.isArray(report?.crates) || !Array.isArray(report?.licenses)) {
throw new Error(`${label} is not cargo-about JSON output`);
}
const discovered = new Map();
const local = new Set();
for (const entry of report.crates) {
const pkg = entry?.package;
if (!pkg?.id || !pkg?.name || !pkg?.version) {
throw new Error(`${label} contains incomplete package metadata`);
}
if (isRegistryPackage(pkg)) {
if (discovered.has(pkg.id))
throw new Error(`${label} contains duplicate ${pkg.id}`);
discovered.set(pkg.id, {
...pkg,
resolvedLicense: entry.license ?? pkg.license,
});
} else if (pkg.source == null) {
local.add(packageKey(pkg));
} else {
throw new Error(
`${label} contains an unreviewed package source: ${pkg.source}`,
);
}
}
const licensed = new Set();
for (const license of report.licenses) {
for (const usage of license.used_by ?? []) {
const pkg = usage.crate;
if (isRegistryPackage(pkg)) licensed.add(pkg.id);
}
}
assertExactSet(
licensed,
discovered.keys(),
`${label} licensed registry package set`,
);
return { report, discovered, local };
}
function mergeRegistryPackages(left, right) {
const merged = new Map();
for (const pkg of [...left.values(), ...right.values()]) {
const prior = merged.get(pkg.id);
if (
prior &&
JSON.stringify({
name: prior.name,
version: prior.version,
source: prior.source,
license: prior.resolvedLicense,
}) !==
JSON.stringify({
name: pkg.name,
version: pkg.version,
source: pkg.source,
license: pkg.resolvedLicense,
})
) {
throw new Error(`cargo-about reports disagree about ${pkg.id}`);
}
merged.set(pkg.id, prior ?? pkg);
}
return merged;
}
function parseCargoLock(lockText) {
const packages = new Map();
for (const block of lockText.split(/(?=^\[\[package\]\]\s*$)/m)) {
if (!block.startsWith("[[package]]")) continue;
const value = (name) => {
const match = block.match(
new RegExp(`^${name} = ("(?:[^"\\\\]|\\\\.)*")$`, "m"),
);
return match ? JSON.parse(match[1]) : undefined;
};
const name = value("name");
const version = value("version");
const source = value("source");
const checksum = value("checksum");
if (!name || !version || !source?.startsWith(REGISTRY_SOURCE_PREFIX))
continue;
if (!/^[a-f0-9]{64}$/.test(checksum ?? "")) {
throw new Error(
`Cargo.lock has no SHA-256 checksum for ${name}@${version}`,
);
}
const key = `${name}@${version}`;
const existing = packages.get(key);
if (existing && existing.checksum !== checksum) {
throw new Error(`Cargo.lock contains ambiguous sources for ${key}`);
}
packages.set(key, { checksum, source });
}
return packages;
}
function normalizeLicense(expression, label) {
if (!expression) throw new Error(`missing license expression for ${label}`);
return expression.replaceAll("/", " OR ").replace(/\s+/g, " ").trim();
}
function cargoReference(name, version) {
return `pkg:cargo/${encodeURIComponent(name)}@${encodeURIComponent(version)}`;
}
function registryComponent(pkg, cargoLockPackages) {
const key = packageKey(pkg);
const locked = cargoLockPackages.get(key);
if (!locked)
throw new Error(`Cargo.lock does not contain selected package ${key}`);
const distribution = `https://crates.io/api/v1/crates/${encodeURIComponent(pkg.name)}/${encodeURIComponent(pkg.version)}/download`;
return {
type: "library",
"bom-ref": cargoReference(pkg.name, pkg.version),
group: "crates.io",
name: pkg.name,
version: pkg.version,
purl: cargoReference(pkg.name, pkg.version),
hashes: [{ alg: "SHA-256", content: locked.checksum }],
licenses: [
{ expression: normalizeLicense(pkg.license ?? pkg.resolvedLicense, key) },
],
...(pkg.description ? { description: pkg.description } : {}),
externalReferences: [
{ type: "distribution", url: distribution },
...(typeof pkg.repository === "string" &&
/^https?:\/\//.test(pkg.repository)
? [{ type: "vcs", url: pkg.repository }]
: []),
],
properties: [
{ name: "openmausbot:cargo:package-id", value: pkg.id },
{ name: "openmausbot:cargo:crate-sha256", value: locked.checksum },
],
};
}
function trycuaComponent({ name, version, url }) {
return {
type: "library",
"bom-ref": cargoReference(name, version),
group: "trycua",
name,
version,
purl: cargoReference(name, version),
licenses: [{ license: { id: "MIT" } }],
externalReferences: [{ type: "vcs", url }],
};
}
function interComponent() {
return {
type: "file",
"bom-ref": `pkg:generic/inter@${INTER_VERSION}`,
group: "rsms",
name: "Inter",
version: INTER_VERSION,
purl: `pkg:generic/inter@${INTER_VERSION}`,
hashes: [{ alg: "SHA-256", content: INTER_FONT_SHA256 }],
licenses: [{ license: { id: "OFL-1.1" } }],
copyright:
"Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter)",
externalReferences: [
{
type: "vcs",
url: `https://github.com/rsms/inter/commit/${INTER_COMMIT}`,
},
{
type: "distribution",
url: `${SOURCE_ROOT}/crates/cursor-overlay/assets/Inter.ttf`,
},
],
properties: [
{
name: "openmausbot:font:name-table-version",
value: "Version 4.001;git-66647c0bb",
},
{ name: "openmausbot:font:embedded-sha256", value: INTER_FONT_SHA256 },
],
};
}
function mergeLicenseRecords(reports, selectedRegistry, interLicenseText) {
const records = new Map();
const registryCoverage = new Set();
const mplComponents = new Set();
for (const report of reports) {
for (const license of report.licenses) {
const usedBy = (license.used_by ?? [])
.map((usage) => usage.crate)
.filter(
(pkg) => isRegistryPackage(pkg) && selectedRegistry.has(pkg.id),
);
if (!usedBy.length) continue;
const recordKey = sha256(
`${license.id}\0${license.name}\0${license.text}`,
);
const record = records.get(recordKey) ?? {
id: license.id,
name: license.name,
text: license.text,
usedBy: new Map(),
};
for (const pkg of usedBy) {
record.usedBy.set(pkg.id, pkg);
registryCoverage.add(pkg.id);
if (license.id === "MPL-2.0") mplComponents.add(packageKey(pkg));
}
records.set(recordKey, record);
}
}
assertExactSet(
registryCoverage,
selectedRegistry.keys(),
"merged license report coverage",
);
assertExactSet(
mplComponents,
EXPECTED_MPL_COMPONENTS,
"MPL-2.0 component set",
);
records.set("inter-ofl-1.1", {
id: "OFL-1.1",
name: "SIL Open Font License 1.1",
text: interLicenseText,
usedBy: new Map([
[
`pkg:generic/inter@${INTER_VERSION}`,
{
id: `pkg:generic/inter@${INTER_VERSION}`,
name: "Inter",
version: INTER_VERSION,
source: `https://github.com/rsms/inter/commit/${INTER_COMMIT}`,
},
],
]),
});
return [...records.values()].sort(
(left, right) =>
left.id.localeCompare(right.id) ||
left.name.localeCompare(right.name) ||
sha256(left.text).localeCompare(sha256(right.text)),
);
}
function escapeHtml(value) {
return String(value)
.replaceAll("&", "&")
.replaceAll("<", "<")
.replaceAll(">", ">")
.replaceAll('"', """)
.replaceAll("'", "'");
}
function normalizeEmbeddedText(value) {
return String(value)
.replaceAll("\r\n", "\n")
.replaceAll("\r", "\n")
.replace(/[ \t]+$/gm, "");
}
function componentLink(pkg) {
if (pkg.name === "Inter")
return `https://github.com/rsms/inter/commit/${INTER_COMMIT}`;
return `https://crates.io/crates/${encodeURIComponent(pkg.name)}/${encodeURIComponent(pkg.version)}`;
}
function renderLicenseHtml(records, registryPackages) {
const overview = new Map();
for (const record of records) {
const entry = overview.get(record.id) ?? {
name: record.name,
components: new Set(),
};
for (const key of record.usedBy.keys()) entry.components.add(key);
overview.set(record.id, entry);
}
const overviewHtml = [...overview]
.sort(([left], [right]) => left.localeCompare(right))
.map(
([id, entry]) =>
`<li><a href="#${escapeHtml(id)}">${escapeHtml(entry.name)}</a> (${entry.components.size})</li>`,
)
.join("");
const inventoryHtml = [...registryPackages.values()]
.sort((left, right) => packageKey(left).localeCompare(packageKey(right)))
.map(
(pkg) =>
`<tr data-package-id="${escapeHtml(pkg.id)}"><td><a href="${escapeHtml(componentLink(pkg))}">${escapeHtml(pkg.name)}</a></td><td>${escapeHtml(pkg.version)}</td><td><code>${escapeHtml(normalizeLicense(pkg.license ?? pkg.resolvedLicense, packageKey(pkg)))}</code></td></tr>`,
)
.join("\n");
let previousId;
const licensesHtml = records
.map((record) => {
const heading =
record.id === previousId
? ""
: `<h3 id="${escapeHtml(record.id)}">${escapeHtml(record.name)}</h3>`;
previousId = record.id;
const usedBy = [...record.usedBy.values()]
.sort((left, right) =>
packageKey(left).localeCompare(packageKey(right)),
)
.map(
(pkg) =>
`<li data-component-id="${escapeHtml(pkg.id)}"><a href="${escapeHtml(componentLink(pkg))}">${escapeHtml(pkg.name)} ${escapeHtml(pkg.version)}</a></li>`,
)
.join("");
return `<section>${heading}<h4>${escapeHtml(record.name)}</h4><p>Used by:</p><ul>${usedBy}</ul><pre>${escapeHtml(normalizeEmbeddedText(record.text))}</pre></section>`;
})
.join("\n");
return `<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>OpenMausBot — Cua Driver third-party licenses</title>
<style>body{font:14px system-ui,sans-serif;line-height:1.45;max-width:960px;margin:2rem auto;padding:0 1rem;color:#171717}pre{white-space:pre-wrap;border:1px solid #ddd;border-radius:6px;padding:1rem;overflow-wrap:anywhere}a{color:#2563eb}table{border-collapse:collapse;width:100%}th,td{border-bottom:1px solid #ddd;padding:.45rem;text-align:left;vertical-align:top}code{overflow-wrap:anywhere}</style>
</head>
<body>
<h1>Cua Driver ${RELEASE_VERSION} third-party licenses</h1>
<p>This report covers all ${EXPECTED_REGISTRY_UNION_COUNT} crates.io packages in the root-scoped release build graphs for <code>cua-driver</code> and <code>cua-cursor-theme</code>, plus the embedded Inter font. Cua's ${TRYCUA_COUNT_WORD} workspace packages are covered by the accompanying MIT <code>LICENSE.md</code>.</p>
<p>Source commit: <a href="https://github.com/trycua/cua/commit/${SOURCE_COMMIT}"><code>${SOURCE_COMMIT}</code></a>. Cargo.lock SHA-256: <code>${CARGO_LOCK_SHA256}</code>.</p>
<h2>License overview</h2>
<ul>${overviewHtml}</ul>
<h2>Package inventory</h2>
<table><thead><tr><th>Package</th><th>Version</th><th>Declared license</th></tr></thead><tbody>
${inventoryHtml}
</tbody></table>
<h2>License texts and attribution</h2>
${licensesHtml}
</body>
</html>
`;
}
const [driverBytes, cursorThemeBytes, cargoLockBytes] = await Promise.all([
readFile(driverReportPath),
readFile(cursorThemeReportPath),
readFile(cargoLockPath),
]);
if (sha256(cargoLockBytes) !== CARGO_LOCK_SHA256) {
throw new Error(
`Cargo.lock does not match the reviewed Cua ${RELEASE_VERSION} source commit`,
);
}
const repositoryRoot = path.resolve(
path.dirname(fileURLToPath(import.meta.url)),
"..",
);
const recordsDirectory = path.join(repositoryRoot, "third_party", "cua-driver");
const [interLicenseBytes, cuaLicenseBytes] = await Promise.all([
readFile(path.join(recordsDirectory, "Inter-OFL-1.1.txt")),
readFile(path.join(recordsDirectory, "LICENSE.md")),
]);
if (sha256(interLicenseBytes) !== INTER_LICENSE_SHA256) {
throw new Error("Inter license file differs from the reviewed OFL-1.1 text");
}
if (
sha256(cuaLicenseBytes) !==
"c0779290c1d4783169aa3dbfb55feb505e563ef8a004bbf55298ceffcfbda8d9"
) {
throw new Error("Cua MIT license differs from the reviewed upstream source");
}
const driver = inspectCargoAboutReport(
JSON.parse(driverBytes),
"cua-driver cargo-about report",
);
const cursorTheme = inspectCargoAboutReport(
JSON.parse(cursorThemeBytes),
"cursor-theme cargo-about report",
);
if (driver.discovered.size !== EXPECTED_DRIVER_REGISTRY_COUNT) {
throw new Error(
`cua-driver report must contain ${EXPECTED_DRIVER_REGISTRY_COUNT} registry packages`,
);
}
if (cursorTheme.discovered.size !== EXPECTED_CURSOR_THEME_REGISTRY_COUNT) {
throw new Error(
`cursor-theme report must contain ${EXPECTED_CURSOR_THEME_REGISTRY_COUNT} registry packages`,
);
}
assertExactSet(
driver.local,
EXPECTED_DRIVER_LOCAL,
"cua-driver local package roots",
);
assertExactSet(
cursorTheme.local,
EXPECTED_CURSOR_THEME_LOCAL,
"cursor-theme local package roots",
);
const registryPackages = mergeRegistryPackages(
driver.discovered,
cursorTheme.discovered,
);
if (registryPackages.size !== EXPECTED_REGISTRY_UNION_COUNT) {
throw new Error(
`root-scoped registry union must contain ${EXPECTED_REGISTRY_UNION_COUNT} packages`,
);
}
assertExactSet(
[...cursorTheme.discovered.values()]
.filter((pkg) => !driver.discovered.has(pkg.id))
.map(packageKey),
EXPECTED_CURSOR_THEME_ONLY,
"cursor-theme-only registry package set",
);
const cargoLockPackages = parseCargoLock(cargoLockBytes.toString("utf8"));
const registryComponents = [...registryPackages.values()].map((pkg) =>
registryComponent(pkg, cargoLockPackages),
);
const components = [
...registryComponents,
...TRYCUA_COMPONENTS.map(trycuaComponent),
interComponent(),
].sort(
(left, right) =>
left.name.localeCompare(right.name) ||
left.version.localeCompare(right.version) ||
left.group.localeCompare(right.group),
);
const componentReferences = new Set(
components.map((component) => component["bom-ref"]),
);
if (componentReferences.size !== components.length)
throw new Error("SBOM contains duplicate bom-ref values");
if (
components.length !==
EXPECTED_REGISTRY_UNION_COUNT + TRYCUA_COMPONENTS.length + 1
) {
throw new Error(
`SBOM component count is not the reviewed ${EXPECTED_REGISTRY_UNION_COUNT} + ${TRYCUA_COMPONENTS.length} + 1 inventory`,
);
}
const licenseRecords = mergeLicenseRecords(
[driver.report, cursorTheme.report],
registryPackages,
interLicenseBytes.toString("utf8"),
);
const html = renderLicenseHtml(licenseRecords, registryPackages);
const rootReference = `pkg:generic/cua-driver-linux-x64@${RELEASE_VERSION}`;
const bom = {
bomFormat: "CycloneDX",
specVersion: "1.5",
serialNumber: archiveSerialNumber(ARCHIVE_SHA256),
version: 1,
metadata: {
component: {
type: "application",
"bom-ref": rootReference,
name: "Cua Driver Linux x64 runtime",
version: RELEASE_VERSION,
licenses: [{ license: { id: "MIT" } }],
externalReferences: [
{
type: "distribution",
url: `https://github.com/trycua/cua/releases/tag/cua-driver-rs-v${RELEASE_VERSION}`,
},
{
type: "vcs",
url: `https://github.com/trycua/cua/commit/${SOURCE_COMMIT}`,
},
],
properties: [
{ name: "openmausbot:archive:sha256", value: ARCHIVE_SHA256 },
{ name: "openmausbot:cargo-lock:sha256", value: CARGO_LOCK_SHA256 },
{ name: "openmausbot:target", value: "x86_64-unknown-linux-gnu" },
{ name: "openmausbot:feature", value: "cua-driver/portal-input" },
{
name: "openmausbot:registry-component-count",
value: String(EXPECTED_REGISTRY_UNION_COUNT),
},
{
name: "openmausbot:trycua-component-count",
value: String(TRYCUA_COMPONENTS.length),
},
{
name: "openmausbot:file:cua-driver:sha256",
value: DRIVER_SHA256,
},
{
name: "openmausbot:file:cua-cursor-theme:sha256",
value: CURSOR_THEME_SHA256,
},
],
},
},
components,
dependencies: [
{ ref: rootReference, dependsOn: sorted(componentReferences) },
],
};
const registryRows = [...registryPackages.values()]
.sort((left, right) => packageKey(left).localeCompare(packageKey(right)))
.map((pkg) => {
const checksum = cargoLockPackages.get(packageKey(pkg)).checksum;
return `| ${pkg.name.replaceAll("|", "\\|")} | ${pkg.version} | ${normalizeLicense(pkg.license ?? pkg.resolvedLicense, packageKey(pkg)).replaceAll("|", "\\|")} | \`${checksum}\` |`;
});
const trycuaRows = TRYCUA_COMPONENTS.map(
({ name, version }) => `| ${name} | ${version} | MIT |`,
);
const mplSourceRows = EXPECTED_MPL_COMPONENTS.map((component) => {
const separator = component.lastIndexOf("@");
const name = component.slice(0, separator);
const version = component.slice(separator + 1);
const checksum = cargoLockPackages.get(component)?.checksum;
if (!checksum)
throw new Error(
`Cargo.lock has no checksum for MPL component ${component}`,
);
return `- [${component} source](https://crates.io/api/v1/crates/${encodeURIComponent(name)}/${encodeURIComponent(version)}/download) — crate SHA-256 \`${checksum}\`;`;
});
const notices = `# Cua Driver ${RELEASE_VERSION} third-party notices
OpenMausBot redistributes two executables from the official Cua Driver ${RELEASE_VERSION} Linux x64 release: \`cua-driver\` and \`cua-cursor-theme\`.
- Upstream source commit: [\`${SOURCE_COMMIT}\`](https://github.com/trycua/cua/commit/${SOURCE_COMMIT})
- Upstream \`Cargo.lock\` SHA-256: \`${CARGO_LOCK_SHA256}\`
- Release archive SHA-256: \`${ARCHIVE_SHA256}\`
- Cua's ${TRYCUA_COUNT_WORD} workspace components remain under the accompanying MIT \`LICENSE.md\`.
- The embedded Inter ${INTER_VERSION} font remains under the accompanying SIL OFL 1.1 \`Inter-OFL-1.1.txt\`.
- Full license texts and attribution for all ${EXPECTED_REGISTRY_UNION_COUNT} crates.io packages and Inter are in \`THIRD_PARTY_LICENSES.html\`.
- The machine-readable inventory contains ${EXPECTED_REGISTRY_UNION_COUNT} registry packages, ${TRYCUA_COMPONENTS.length} Cua packages, and Inter in \`SBOM.cdx.json\`.
## MPL-2.0 source availability
The shipped build graph contains exactly the following MPL-2.0 components. Their corresponding Source Code Form is available from the versioned crates below:
${mplSourceRows.join("\n")}
The complete MPL-2.0 text and package copyright notices appear in \`THIRD_PARTY_LICENSES.html\`. These files remain under their original licenses; OpenMausBot's and Cua's MIT licenses do not replace them.
## Scope and method
The registry inventory is the union of two root-scoped \`cargo-about 0.8.4\` graphs: \`cua-driver\` with \`portal-input\` for \`x86_64-unknown-linux-gnu\`, and \`cursor-theme-cli\` for the same target. Development-only dependencies are excluded; build dependencies are retained. Raw workspace-unified Cargo metadata is not used as the inventory because features enabled only by unrelated workspace members would overstate the shipped graph.
### Cua workspace components
| Package | Version | License |
|---|---:|---|
${trycuaRows.join("\n")}
### crates.io release-build inventory
| Package | Version | Declared license expression | Crate SHA-256 |
|---|---:|---|---|
${registryRows.join("\n")}
### Embedded non-Cargo asset
| Component | Version | License | Embedded file SHA-256 | Source |
|---|---:|---|---|---|
| Inter | ${INTER_VERSION} | OFL-1.1 | \`${INTER_FONT_SHA256}\` | [Inter commit ${INTER_COMMIT.slice(0, 9)}](https://github.com/rsms/inter/commit/${INTER_COMMIT}) |
`;
await mkdir(outputDirectory, { recursive: true });
await Promise.all([
writeFile(
path.join(outputDirectory, "SBOM.cdx.json"),
`${JSON.stringify(bom, null, 2)}\n`,
),
writeFile(path.join(outputDirectory, "THIRD_PARTY_NOTICES.md"), notices),
writeFile(path.join(outputDirectory, "THIRD_PARTY_LICENSES.html"), html),
]);
console.log(
`Generated CUA compliance records: ${registryPackages.size} registry + ` +
`${TRYCUA_COMPONENTS.length} Cua + 1 Inter component; ${EXPECTED_MPL_COMPONENTS.length} MPL components.`,
);