///|
/// Release-oriented facts derived from an inventory, manifest and policy.
pub(all) struct ReleaseReadiness {
profile : DoctorProfile
module_name : String?
version : String?
repository : String?
license : String?
readme : String?
description : String?
keywords : Array[String]
has_license_file : Bool
has_ci : Bool
has_examples : Bool
has_tests : Bool
has_changelog : Bool
has_contributing : Bool
has_acceptance : Bool
required_commands : Array[String]
} derive(Debug, Eq)
///|
pub fn ReleaseReadiness::summary(self : ReleaseReadiness) -> String {
let mut metadata = 0
for
value in [
self.module_name,
self.version,
self.repository,
self.license,
self.description,
] {
if value is Some(_) {
metadata += 1
}
}
"profile=\{self.profile.label()}, metadata=\{metadata}/5, tests=\{self.has_tests}, ci=\{self.has_ci}"
}
///|
pub fn ReleaseReadiness::missing_items(
self : ReleaseReadiness,
) -> Array[String] {
let missing : Array[String] = []
if self.module_name is None {
missing.push("moon.mod name")
}
if self.version is None {
missing.push("moon.mod version")
}
if self.repository is None {
missing.push("moon.mod repository")
}
if self.license is None || !self.has_license_file {
missing.push("license metadata or LICENSE")
}
if self.description is None {
missing.push("moon.mod description")
}
if self.keywords.length() == 0 {
missing.push("moon.mod keywords")
}
if !self.has_tests {
missing.push("tests")
}
if !self.has_ci {
missing.push("CI workflow")
}
if !self.has_examples {
missing.push("examples")
}
missing
}
///|
fn manifest_text(manifest : MoonManifest?, key : String) -> String? {
match manifest {
Some(value) => value.text(key)
None => None
}
}
///|
fn manifest_keywords(manifest : MoonManifest?) -> Array[String] {
match manifest {
Some(value) => value.text_array("keywords")
None => []
}
}
///|
fn inventory_has_tests(inventory : ProjectInventory) -> Bool {
inventory.files.any(file => {
file.path.has_suffix("_test.mbt") || file.path.has_suffix("_wbtest.mbt")
})
}
///|
fn inventory_has_examples(inventory : ProjectInventory) -> Bool {
inventory
.files_under("examples")
.any(file => {
file.path.has_suffix(".mbt") ||
file.path.has_suffix(".md") ||
file.path.has_suffix("moon.pkg")
})
}
///|
fn inventory_has_ci(inventory : ProjectInventory) -> Bool {
analyze_workflows(inventory).any(workflow => {
workflow.has_moon_check && workflow.has_moon_test
})
}
///|
/// Derive a release status object without running package or network commands.
pub fn analyze_release_readiness(
inventory : ProjectInventory,
manifest : MoonManifest?,
config : DoctorConfig,
) -> ReleaseReadiness {
{
profile: config.profile,
module_name: manifest_text(manifest, "name"),
version: manifest_text(manifest, "version"),
repository: manifest_text(manifest, "repository"),
license: manifest_text(manifest, "license"),
readme: manifest_text(manifest, "readme"),
description: manifest_text(manifest, "description"),
keywords: manifest_keywords(manifest),
has_license_file: inventory.has_file("LICENSE"),
has_ci: inventory_has_ci(inventory),
has_examples: inventory_has_examples(inventory),
has_tests: inventory_has_tests(inventory),
has_changelog: inventory.has_file("CHANGELOG.md"),
has_contributing: inventory.has_file("CONTRIBUTING.md"),
has_acceptance: inventory.has_file("ACCEPTANCE.md"),
required_commands: config.required_commands,
}
}
///|
fn optional_json(value : String?) -> Json {
match value {
Some(text) => Json::string(text)
None => Json::null()
}
}
///|
pub fn release_readiness_to_json(readiness : ReleaseReadiness) -> Json {
Json::object({
"profile": Json::string(readiness.profile.label()),
"module_name": optional_json(readiness.module_name),
"version": optional_json(readiness.version),
"repository": optional_json(readiness.repository),
"license": optional_json(readiness.license),
"keywords": Json::array(readiness.keywords.map(Json::string)),
"has_license_file": Json::boolean(readiness.has_license_file),
"has_ci": Json::boolean(readiness.has_ci),
"has_examples": Json::boolean(readiness.has_examples),
"has_tests": Json::boolean(readiness.has_tests),
"has_changelog": Json::boolean(readiness.has_changelog),
"has_contributing": Json::boolean(readiness.has_contributing),
"has_acceptance": Json::boolean(readiness.has_acceptance),
"required_commands": Json::array(
readiness.required_commands.map(Json::string),
),
"missing_items": Json::array(readiness.missing_items().map(Json::string)),
})
}
///|
pub fn render_release_readiness(readiness : ReleaseReadiness) -> String {
let builder = StringBuilder()
builder.write_string("Moon Doctor release readiness\n")
builder.write_string(readiness.summary() + "\n\n")
let missing = readiness.missing_items()
if missing.length() == 0 {
builder.write_string(
"All release metadata and repository signals are present.\n",
)
} else {
builder.write_string("Missing or incomplete signals:\n")
for item in missing {
builder.write_string("- " + item + "\n")
}
}
builder.write_string("\nRequired commands:\n")
for command in readiness.required_commands {
builder.write_string("- " + command + "\n")
}
builder.to_string()
}