use soothfast_spec::compat::Severity;
use crate::invoke::{self, CommonArgs};
use crate::spec_config;
use crate::spec_gen;
pub fn run(args: &[String]) -> i32 {
let mut common = CommonArgs::default();
let mut base = "origin/master".to_string();
let mut allow_breaking = false;
let mut it = args.iter();
while let Some(a) = it.next() {
match a.as_str() {
"--base" => match it.next() {
Some(b) => base = b.clone(),
None => {
eprintln!("soothfast: --base needs a git ref");
return 2;
}
},
"--allow-breaking" => allow_breaking = true,
_ => {
if !common.try_parse(a, &mut it) {
eprintln!("soothfast: unknown spec gate arg {a:?}");
return 2;
}
}
}
}
let Some(pkg) = common.pkg.clone() else {
eprintln!("soothfast: spec gate requires -p PKG");
return 2;
};
match gate(&pkg, &common, &base, allow_breaking) {
Ok(code) => code,
Err(e) => {
eprintln!("soothfast: {e}");
1
}
}
}
fn gate(pkg: &str, common: &CommonArgs, base: &str, allow_breaking: bool) -> Result<i32, String> {
let meta = invoke::pkg_meta(pkg).map_err(|e| e.to_string())?;
let cfg = spec_config::load(&meta.dir)?;
let head = spec_gen::build(pkg, common, None, &cfg, &meta)?;
if head.docs.is_empty() {
println!("spec gate: no generated spec files — nothing to gate");
return Ok(0);
}
let base_built = invoke::with_merge_base_worktree(base, |wt| {
spec_gen::build(pkg, common, Some(wt), &cfg, &meta)
})?;
let mut breaking = 0u32;
let mut additive = 0u32;
for (spec_file, new_doc) in &head.docs {
let kind = cfg.dialect_of(spec_file);
let empty = kind.empty();
let old_doc = base_built.docs.get(spec_file).unwrap_or(&empty);
let changes = kind.diff(old_doc, new_doc);
if changes.is_empty() {
println!(
"spec gate: {spec_file} [{}] — no API changes vs {base}",
kind.name()
);
continue;
}
println!("spec gate: {spec_file} [{}] — vs {base}", kind.name());
for c in &changes {
let tag = match c.severity {
Severity::Breaking => {
breaking += 1;
"BREAKING"
}
Severity::Additive => {
additive += 1;
"additive"
}
};
println!(" {tag} {}: {}", c.at, c.detail);
}
}
println!("spec gate: {breaking} breaking, {additive} additive");
if breaking > 0 && !allow_breaking {
println!(
"spec gate: FAILED — pass --allow-breaking to release these \
deliberately, after coordinating with consumers"
);
return Ok(1);
}
if breaking > 0 {
println!("spec gate: breaking changes allowed by --allow-breaking");
}
Ok(0)
}