///|
fn package_parts(name : StringView) -> (String, String) {
match name.split_once("/") {
Some((group, package_name)) => (group.to_owned(), package_name.to_owned())
None => ("", name.to_owned())
}
}
///|
fn purl(name : StringView, version : StringView) -> String {
let suffix = if version.is_empty() { "" } else { "@\{version}" }
"pkg:mooncakes/\{name}\{suffix}"
}
///|
fn component_json(
name : String,
version : String,
license : String,
kind : String,
) -> Json {
let (group, package_name) = package_parts(name)
let licenses : Array[Json] = if license.is_empty() {
[]
} else {
[{ "license": { "id": license } }]
}
{
"type": kind,
"bom-ref": purl(name, version),
"group": group,
"name": package_name,
"version": version,
"purl": purl(name, version),
"licenses": licenses,
}
}
///|
/// Generate a deterministic CycloneDX 1.6 SBOM as JSON.
pub fn cyclonedx(project : Project) -> Json {
let components : Array[Json] = []
let dependency_refs : Array[Json] = []
let graph : Array[Json] = []
for dependency in project.dependencies {
components.push(
component_json(dependency.name, dependency.version, "", "library"),
)
dependency_refs.push(
Json::string(purl(dependency.name, dependency.version)),
)
graph.push({
"ref": purl(dependency.name, dependency.version),
"dependsOn": [],
})
}
graph.push({
"ref": purl(project.name, project.version),
"dependsOn": dependency_refs,
})
{
"$schema": "https://cyclonedx.org/schema/bom-1.6.schema.json",
"bomFormat": "CycloneDX",
"specVersion": "1.6",
"version": 1,
"metadata": {
"tools": {
"components": [
{ "type": "application", "name": "MoonSeal", "version": "0.2.1" },
],
},
"component": component_json(
project.name,
project.version,
normalize_license(project.license),
"application",
),
},
"components": components,
"dependencies": graph,
}
}
///|
pub fn cyclonedx_string(project : Project, indent? : Int = 2) -> String {
cyclonedx(project).stringify(indent~)
}