use crate::graph::GraphNode;
use anyhow::Result;
use serde_json::json;
pub fn generate_spdx_2_3_sbom(nodes: &[GraphNode]) -> Result<serde_json::Value> {
let created_timestamp = std::env::var("SOURCE_DATE_EPOCH")
.ok()
.and_then(|val| val.parse::<i64>().ok())
.and_then(|epoch| chrono::DateTime::from_timestamp(epoch, 0))
.map(|dt| dt.to_rfc3339())
.unwrap_or_else(|| "1970-01-01T00:00:00Z".to_string());
let mut sorted_nodes = nodes.to_vec();
sorted_nodes.sort_by(|a, b| {
a.ecosystem
.cmp(&b.ecosystem)
.then(a.package_name.cmp(&b.package_name))
.then(a.resolved_version.cmp(&b.resolved_version))
});
let packages: Vec<serde_json::Value> = sorted_nodes
.iter()
.map(|node| {
json!({
"SPDXID": format!("SPDXRef-Package-{}-{}", node.ecosystem, node.package_name),
"name": node.package_name,
"versionInfo": node.resolved_version,
"downloadLocation": "NOASSERTION",
"checksums": [
{
"algorithm": "SHA256",
"checksumValue": node.content_hash.trim_start_matches("sha256:")
}
]
})
})
.collect();
let sbom = json!({
"spdxVersion": "SPDX-2.3",
"dataLicense": "CC0-1.0",
"SPDXID": "SPDXRef-DOCUMENT",
"name": "Rivox-Unified-Monorepo-SBOM",
"documentNamespace": "https://rivox.dev/spdxdocs/monorepo-v0.1.0",
"creationInfo": {
"creators": ["Tool: Rivox-0.1.0"],
"created": created_timestamp
},
"packages": packages
});
Ok(sbom)
}