///|
pub enum DependencyHealthSeverity {
DependencyInfo
DependencyWarning
DependencyError
} derive(Eq, Debug)
///|
pub(all) struct DependencyIssue {
severity : DependencyHealthSeverity
code : String
dependency : String
message : String
} derive(Eq, Debug)
///|
pub(all) struct NamespaceCount {
scope : String
count : Int
} derive(Eq, Debug)
///|
pub(all) struct DependencyHealth {
total : Int
pinned : Int
unpinned : Int
unique : Int
namespaces : Array[NamespaceCount]
issues : Array[DependencyIssue]
} derive(Eq, Debug)
///|
pub fn DependencyHealth::error_count(self : DependencyHealth) -> Int {
self.issues.fold(init=0, fn(count, issue) {
if issue.severity == DependencyError {
count + 1
} else {
count
}
})
}
///|
pub fn DependencyHealth::warning_count(self : DependencyHealth) -> Int {
self.issues.fold(init=0, fn(count, issue) {
if issue.severity == DependencyWarning {
count + 1
} else {
count
}
})
}
///|
pub fn DependencyHealth::is_healthy(self : DependencyHealth) -> Bool {
self.error_count() == 0
}
///|
fn dependency_namespace(name : StringView) -> String {
match name.split_once("/") {
Some((owner, _)) => owner.to_owned()
None => ""
}
}
///|
fn dependency_package_name(name : StringView) -> String {
match name.split_once("/") {
Some((_, package_name)) => package_name.to_owned()
None => name.to_owned()
}
}
///|
fn dependency_valid_name(name : StringView) -> Bool {
guard name.split_once("/") is Some((owner, package_name)) else {
return false
}
!owner.is_empty() &&
!package_name.is_empty() &&
!owner.contains(" ") &&
!package_name.contains(" ") &&
!package_name.contains("/")
}
///|
fn dependency_version_core(version : StringView) -> StringView {
match version.split_once("-") {
Some((core, _)) => core
None =>
match version.split_once("+") {
Some((core, _)) => core
None => version
}
}
}
///|
fn dependency_is_digits(value : StringView) -> Bool {
guard !value.is_empty() else { return false }
for char in value {
guard char >= '0' && char <= '9' else { return false }
}
true
}
///|
fn dependency_valid_version(version : StringView) -> Bool {
guard !version.is_empty() else { return false }
let core = dependency_version_core(version)
let parts = core.split(".").collect()
guard parts.length() == 3 else { return false }
parts.all(dependency_is_digits)
}
///|
fn dependency_is_prerelease(version : StringView) -> Bool {
version.contains("-") &&
!version.has_suffix("-") &&
dependency_valid_version(version)
}
///|
fn dependency_add_issue(
issues : Array[DependencyIssue],
severity : DependencyHealthSeverity,
code : String,
dependency : String,
message : String,
) -> Unit {
issues.push({ severity, code, dependency, message })
}
///|
fn dependency_seen_name_version(
dependencies : Array[Dependency],
until : Int,
name : String,
version : String,
) -> Bool {
let mut index = 0
while index < until {
let dependency = dependencies[index]
if dependency.name == name && dependency.version == version {
return true
}
index += 1
}
false
}
///|
fn dependency_has_other_version(
dependencies : Array[Dependency],
name : String,
version : String,
) -> Bool {
dependencies.any(fn(dependency) {
dependency.name == name && dependency.version != version
})
}
///|
fn dependency_unique_count(dependencies : Array[Dependency]) -> Int {
let mut count = 0
let mut index = 0
while index < dependencies.length() {
let dependency = dependencies[index]
if !dependency_seen_name_version(
dependencies,
index,
dependency.name,
dependency.version,
) {
count += 1
}
index += 1
}
count
}
///|
fn dependency_namespace_counts(
dependencies : Array[Dependency],
) -> Array[NamespaceCount] {
let counts : Array[NamespaceCount] = []
for dependency in dependencies {
let scope = dependency_namespace(dependency.name)
let mut found = false
let mut index = 0
while index < counts.length() {
if counts[index].scope == scope {
let previous = counts[index]
counts[index] = { scope: previous.scope, count: previous.count + 1 }
found = true
break
}
index += 1
}
if !found {
counts.push({ scope, count: 1 })
}
}
counts
}
///|
/// Analyze direct Mooncakes dependencies for reproducibility and metadata
/// quality. The analysis is deterministic and does not require network access.
pub fn analyze_dependencies(project : Project) -> DependencyHealth {
let issues = []
let dependencies = project.dependencies
let mut pinned = 0
let mut index = 0
while index < dependencies.length() {
let dependency = dependencies[index]
if dependency.version.is_empty() {
dependency_add_issue(
issues,
DependencyWarning,
"MD001",
dependency.name,
"Dependency does not pin a version.",
)
} else {
pinned += 1
if !dependency_valid_version(dependency.version) {
dependency_add_issue(
issues,
DependencyWarning,
"MD002",
dependency.name,
"Version is not canonical semantic versioning: \{dependency.version}.",
)
} else if dependency_is_prerelease(dependency.version) {
dependency_add_issue(
issues,
DependencyInfo,
"MD003",
dependency.name,
"Dependency uses prerelease version \{dependency.version}.",
)
}
}
if !dependency_valid_name(dependency.name) {
dependency_add_issue(
issues,
DependencyError,
"MD004",
dependency.name,
"Mooncakes dependency must use namespace/package coordinates.",
)
}
if dependency_seen_name_version(
dependencies,
index,
dependency.name,
dependency.version,
) {
dependency_add_issue(
issues,
DependencyWarning,
"MD005",
dependency.name,
"Duplicate dependency declaration for \{dependency.name}@\{dependency.version}.",
)
}
if dependency_has_other_version(
dependencies,
dependency.name,
dependency.version,
) &&
!issues.any(fn(issue) {
issue.code == "MD006" && issue.dependency == dependency.name
}) {
dependency_add_issue(
issues,
DependencyError,
"MD006",
dependency.name,
"Dependency is declared with conflicting versions.",
)
}
index += 1
}
{
total: dependencies.length(),
pinned,
unpinned: dependencies.length() - pinned,
unique: dependency_unique_count(dependencies),
namespaces: dependency_namespace_counts(dependencies),
issues,
}
}
///|
fn dependency_severity_label(severity : DependencyHealthSeverity) -> String {
match severity {
DependencyInfo => "INFO"
DependencyWarning => "WARN"
DependencyError => "ERROR"
}
}
///|
pub fn dependency_purl(dependency : Dependency) -> String {
if dependency.version.is_empty() {
"pkg:mooncakes/\{dependency.name}"
} else {
"pkg:mooncakes/\{dependency.name}@\{dependency.version}"
}
}
///|
pub fn render_dependency_text(health : DependencyHealth) -> String {
let out = StringBuilder::new()
out.write_string("MoonSeal dependency health\n")
out.write_string(
"Dependencies: \{health.total} total | \{health.unique} unique | \{health.pinned} pinned | \{health.unpinned} unpinned\n",
)
if health.namespaces.length() > 0 {
out.write_string("Namespaces:")
for item in health.namespaces {
let label = if item.scope.is_empty() { "(invalid)" } else { item.scope }
out.write_string(" \{label}=\{item.count}")
}
out.write_string("\n")
}
if health.issues.is_empty() {
out.write_string("\nHEALTHY No dependency metadata issues found.\n")
} else {
out.write_string("\n")
for issue in health.issues {
out.write_string(
"\{dependency_severity_label(issue.severity)} \{issue.code} [\{issue.dependency}]: \{issue.message}\n",
)
}
}
out.to_string()
}
///|
pub fn dependency_health_json(health : DependencyHealth) -> Json {
let namespaces : Array[Json] = health.namespaces.map(fn(item) {
{ "namespace": item.scope, "count": item.count }
})
let issues : Array[Json] = health.issues.map(fn(issue) {
{
"severity": dependency_severity_label(issue.severity),
"code": issue.code,
"dependency": issue.dependency,
"message": issue.message,
}
})
{
"healthy": health.is_healthy(),
"summary": {
"total": health.total,
"unique": health.unique,
"pinned": health.pinned,
"unpinned": health.unpinned,
"errors": health.error_count(),
"warnings": health.warning_count(),
},
"namespaces": namespaces,
"issues": issues,
}
}
///|
pub fn render_dependency_json(
health : DependencyHealth,
indent? : Int = 2,
) -> String {
dependency_health_json(health).stringify(indent~)
}
///|
pub fn dependency_inventory(project : Project) -> Json {
let dependencies : Array[Json] = project.dependencies.map(fn(dependency) {
{
"name": dependency.name,
"namespace": dependency_namespace(dependency.name),
"package": dependency_package_name(dependency.name),
"version": dependency.version,
"pinned": !dependency.version.is_empty(),
"purl": dependency_purl(dependency),
}
})
{
"project": project.name,
"version": project.version,
"dependencies": dependencies,
}
}