///|
pub fn analyze_workflow(text : String) -> WorkflowInfo {
let lines = split_lines(text)
let commands : Array[WorkflowCommand] = []
let targets : Array[String] = []
let mut has_checkout = false
let mut has_moon_install = false
let mut has_check = false
let mut has_build = false
let mut has_test = false
let mut has_run = false
let mut has_dry_run = false
let mut command_count = 0
let mut moon_command_count = 0
let mut current_name = ""
let mut index = 0
while index < lines.length() {
let raw = lines[index]
let line = trim_ascii(raw)
let lower = ascii_lower(line)
if starts_with(lower, "- name:") || starts_with(lower, "name:") {
current_name = trim_ascii(
slice(line, index_of(line, ":").unwrap() + 1, line.length()),
)
}
if contains(lower, "actions/checkout") {
has_checkout = true
}
if contains(lower, "cli.moonbitlang.com") || contains(lower, "moon version") {
has_moon_install = true
}
if starts_with(lower, "run:") || starts_with(lower, "- run:") {
let command = workflow_command_text(line)
commands.push(workflow_command(current_name, command, index + 1))
command_count += 1
let command_lower = ascii_lower(command)
if starts_with(command_lower, "moon ") ||
contains(command_lower, "\nmoon ") {
moon_command_count += 1
}
update_workflow_flags(command_lower, targets)
if contains(command_lower, "moon check") {
has_check = true
}
if contains(command_lower, "moon build") {
has_build = true
}
if contains(command_lower, "moon test") {
has_test = true
}
if contains(command_lower, "moon run") {
has_run = true
}
if contains(command_lower, "moon publish --dry-run") {
has_dry_run = true
}
} else if is_command_line(line) {
commands.push(workflow_command(current_name, line, index + 1))
command_count += 1
let lower_command = ascii_lower(line)
if starts_with(lower_command, "moon ") {
moon_command_count += 1
}
update_workflow_flags(lower_command, targets)
if contains(lower_command, "moon check") {
has_check = true
}
if contains(lower_command, "moon build") {
has_build = true
}
if contains(lower_command, "moon test") {
has_test = true
}
if contains(lower_command, "moon run") {
has_run = true
}
if contains(lower_command, "moon publish --dry-run") {
has_dry_run = true
}
}
index += 1
}
workflow_info(
command_count, moon_command_count, has_checkout, has_moon_install, has_check,
has_build, has_test, has_run, has_dry_run, targets, commands,
)
}
///|
pub fn WorkflowInfo::coverage_score(self : WorkflowInfo) -> Int {
let mut score = 0
if self.has_checkout {
score += 10
}
if self.has_moon_install {
score += 20
}
if self.has_check {
score += 15
}
if self.has_build {
score += 15
}
if self.has_test {
score += 20
}
if self.has_run {
score += 10
}
if self.targets.length() > 0 {
score += 5
}
if self.has_publish_dry_run {
score += 5
}
score
}
///|
pub fn WorkflowInfo::is_acceptance_ready(self : WorkflowInfo) -> Bool {
self.has_moon_install &&
self.has_check &&
self.has_build &&
self.has_test &&
self.has_run
}
///|
pub fn WorkflowInfo::missing_commands(self : WorkflowInfo) -> Array[String] {
let missing : Array[String] = []
if !self.has_moon_install {
missing.push("install MoonBit")
}
if !self.has_check {
missing.push("moon check")
}
if !self.has_build {
missing.push("moon build")
}
if !self.has_test {
missing.push("moon test")
}
if !self.has_run {
missing.push("moon run")
}
missing
}
///|
pub fn WorkflowInfo::summary(self : WorkflowInfo) -> String {
"ci commands=\{self.command_count}, moon=\{self.moon_command_count}, score=\{self.coverage_score()}"
}
///|
pub fn WorkflowInfo::to_markdown(self : WorkflowInfo) -> String {
let mut out = "## CI Workflow Metrics\n\n"
out = out + "- Commands: " + self.command_count.to_string() + "\n"
out = out + "- Moon commands: " + self.moon_command_count.to_string() + "\n"
out = out + "- Coverage score: " + self.coverage_score().to_string() + "\n"
out = out +
"- Acceptance ready: " +
bool_text(self.is_acceptance_ready()) +
"\n\n"
out = out + "### Commands\n\n"
let mut index = 0
while index < self.commands.length() {
let command = self.commands[index]
out = out + "- L" + command.line.to_string()
if command.name.length() > 0 {
out = out + " " + command.name + ": "
} else {
out = out + " "
}
out = out + "`" + command.command + "`\n"
index += 1
}
out
}
///|
fn workflow_command_text(line : String) -> String {
match index_of(line, "run:") {
None => trim_ascii(line)
Some(pos) => trim_ascii(slice(line, pos + 4, line.length()))
}
}
///|
fn update_workflow_flags(command : String, targets : Array[String]) -> Unit {
if contains(command, "--target wasm") && !target_seen(targets, "wasm") {
targets.push("wasm")
}
if contains(command, "--target js") && !target_seen(targets, "js") {
targets.push("js")
}
if contains(command, "--target native") && !target_seen(targets, "native") {
targets.push("native")
}
if contains(command, "--target all") && !target_seen(targets, "all") {
targets.push("all")
}
}
///|
fn target_seen(targets : Array[String], name : String) -> Bool {
let mut index = 0
while index < targets.length() {
if targets[index] == name {
return true
}
index += 1
}
false
}
///|
fn bool_text(value : Bool) -> String {
if value {
"yes"
} else {
"no"
}
}
///|
pub fn workflow_command_table(info : WorkflowInfo) -> String {
let mut out = "| Line | Step | Command |\n| --- | --- | --- |\n"
let mut index = 0
while index < info.commands.length() {
let command = info.commands[index]
out = out + "| " + command.line.to_string()
out = out + " | " + command.name
out = out + " | `" + command.command + "` |\n"
index += 1
}
out
}
///|
pub fn workflow_acceptance_label(info : WorkflowInfo) -> String {
if info.is_acceptance_ready() {
"ready"
} else if info.has_check && info.has_test {
"partial"
} else {
"incomplete"
}
}