149 lines
5.3 KiB
Text
149 lines
5.3 KiB
Text
|
|
#!/usr/bin/env nu
|
||
|
|
# Execute the constraint checks declared by accepted ADRs and report per constraint.
|
||
|
|
#
|
||
|
|
# Constraints live in `.ontoref/adrs/adr-*.ncl` at the constellation root. Each carries a
|
||
|
|
# `check` in one of three shapes:
|
||
|
|
#
|
||
|
|
# NuCmd { cmd, expect_exit } run cmd, compare exit code
|
||
|
|
# Grep { pattern, paths, must_be_empty }
|
||
|
|
# FileExists { path, present }
|
||
|
|
#
|
||
|
|
# Reported states:
|
||
|
|
# ✓ the check ran and passed
|
||
|
|
# ✗ the check ran and failed
|
||
|
|
# ⊘ no check ran — the constraint declares `cmd = "exit 0"` (a no-op that cannot fail)
|
||
|
|
# or its shape is unknown. Reported apart from ✓ on purpose: a constraint that cannot
|
||
|
|
# fail is not a satisfied constraint, and counting it green is the lie this runner exists
|
||
|
|
# to prevent.
|
||
|
|
#
|
||
|
|
# Only `Accepted` ADRs gate. `Proposed` ones are listed as skipped.
|
||
|
|
#
|
||
|
|
# Exits 0 regardless of findings unless `--strict` is passed, so this can be read before it
|
||
|
|
# is enforced.
|
||
|
|
|
||
|
|
def adrs_dir [] { $env.FILE_PWD | path join ".." ".." ".ontoref" "adrs" | path expand }
|
||
|
|
|
||
|
|
# A cmd that cannot fail is not a check. Extend this list as no-op idioms appear.
|
||
|
|
def is_noop [cmd: string] { ($cmd | str trim) in ["exit 0", "true", ""] }
|
||
|
|
|
||
|
|
def run_nucmd [check: record] {
|
||
|
|
if (is_noop ($check.cmd? | default "")) {
|
||
|
|
return { state: "skip", detail: "check is `exit 0` — a no-op that cannot fail" }
|
||
|
|
}
|
||
|
|
let expect = ($check.expect_exit? | default 0)
|
||
|
|
let r = (do --ignore-errors { ^nu -c $check.cmd } | complete)
|
||
|
|
if $r.exit_code == $expect {
|
||
|
|
{ state: "pass", detail: $"exit ($r.exit_code) == expected ($expect)" }
|
||
|
|
} else {
|
||
|
|
{ state: "fail", detail: $"exit ($r.exit_code), expected ($expect)" }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
def run_grep [check: record] {
|
||
|
|
let paths = ($check.paths? | default [])
|
||
|
|
if ($paths | is-empty) {
|
||
|
|
return { state: "skip", detail: "Grep check declares no paths" }
|
||
|
|
}
|
||
|
|
let existing = ($paths | where { |p| ($p | path exists) })
|
||
|
|
if ($existing | is-empty) {
|
||
|
|
return { state: "skip", detail: $"none of the declared paths exist: ($paths | str join ', ')" }
|
||
|
|
}
|
||
|
|
let r = (do --ignore-errors { ^rg -n --no-heading $check.pattern ...$existing } | complete)
|
||
|
|
let hits = if ($r.stdout | str trim | is-empty) { 0 } else { $r.stdout | lines | length }
|
||
|
|
let must_be_empty = ($check.must_be_empty? | default true)
|
||
|
|
if $must_be_empty and $hits > 0 {
|
||
|
|
{ state: "fail", detail: $"($hits) hits, must_be_empty=true" }
|
||
|
|
} else if (not $must_be_empty) and $hits == 0 {
|
||
|
|
{ state: "fail", detail: "0 hits, must_be_empty=false" }
|
||
|
|
} else {
|
||
|
|
{ state: "pass", detail: $"($hits) hits" }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
def run_fileexists [check: record] {
|
||
|
|
let want = ($check.present? | default true)
|
||
|
|
let got = ($check.path | path exists)
|
||
|
|
if $got == $want {
|
||
|
|
{ state: "pass", detail: $"present=($got), expected ($want)" }
|
||
|
|
} else {
|
||
|
|
{ state: "fail", detail: $"present=($got), expected ($want) — ($check.path)" }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
def run_check [check: record] {
|
||
|
|
match ($check.tag? | default "UNKNOWN") {
|
||
|
|
"NuCmd" => (run_nucmd $check),
|
||
|
|
"Grep" => (run_grep $check),
|
||
|
|
"FileExists" => (run_fileexists $check),
|
||
|
|
_ => { state: "skip", detail: $"unknown check shape: ($check.tag? | default '<none>')" },
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
def main [
|
||
|
|
--strict # exit 1 if any constraint fails
|
||
|
|
--verbose # print the detail line for passing constraints too
|
||
|
|
] {
|
||
|
|
let dir = (adrs_dir)
|
||
|
|
let files = (glob ($dir | path join "adr-*.ncl")
|
||
|
|
| where { |f| not (($f | path basename) starts-with "_") }
|
||
|
|
| where { |f| ($f | path basename) not-in ["adr-defaults.ncl" "adr-schema.ncl" "adr-constraints.ncl"] }
|
||
|
|
| sort)
|
||
|
|
|
||
|
|
mut rows = []
|
||
|
|
mut skipped_adrs = []
|
||
|
|
|
||
|
|
for f in $files {
|
||
|
|
let export = (do --ignore-errors { ^nickel export $f } | complete)
|
||
|
|
if $export.exit_code != 0 {
|
||
|
|
print $"(ansi red)✗(ansi reset) ($f | path basename) — nickel export failed"
|
||
|
|
print ($export.stderr | lines | first 3 | str join "\n")
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
let adr = ($export.stdout | from json)
|
||
|
|
if $adr.status != "Accepted" {
|
||
|
|
$skipped_adrs = ($skipped_adrs | append $"($adr.id) [($adr.status)]")
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
for c in $adr.constraints {
|
||
|
|
let res = (run_check $c.check)
|
||
|
|
$rows = ($rows | append {
|
||
|
|
adr: $adr.id, id: $c.id, severity: ($c.severity? | default "Hard"),
|
||
|
|
state: $res.state, detail: $res.detail,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
print $"(ansi default_bold)ADR constraints(ansi reset) — ($rows | length) from accepted ADRs\n"
|
||
|
|
|
||
|
|
for r in $rows {
|
||
|
|
let mark = match $r.state {
|
||
|
|
"pass" => $"(ansi green)✓(ansi reset)",
|
||
|
|
"fail" => $"(ansi red)✗(ansi reset)",
|
||
|
|
_ => $"(ansi yellow)⊘(ansi reset)",
|
||
|
|
}
|
||
|
|
if $r.state == "pass" and (not $verbose) {
|
||
|
|
print $" ($mark) ($r.adr) ($r.id)"
|
||
|
|
} else {
|
||
|
|
print $" ($mark) ($r.adr) ($r.id)"
|
||
|
|
print $" ($r.detail)"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
let passed = ($rows | where state == "pass" | length)
|
||
|
|
let failed = ($rows | where state == "fail" | length)
|
||
|
|
let skipped = ($rows | where state == "skip" | length)
|
||
|
|
|
||
|
|
print ""
|
||
|
|
print $" (ansi green)($passed) ✓(ansi reset) · (ansi red)($failed) ✗(ansi reset) · (ansi yellow)($skipped) ⊘(ansi reset)"
|
||
|
|
if not ($skipped_adrs | is-empty) {
|
||
|
|
print $" not gating \(status\): ($skipped_adrs | str join ', ')"
|
||
|
|
}
|
||
|
|
if $skipped > 0 {
|
||
|
|
print $" (ansi yellow)⊘(ansi reset) = declared but unexecutable — neither green nor red. A no-op check is not a check."
|
||
|
|
}
|
||
|
|
|
||
|
|
if $strict and $failed > 0 {
|
||
|
|
exit 1
|
||
|
|
}
|
||
|
|
}
|