use anyhow::Result;
use std::path::PathBuf;
use std::process::{Command, Stdio};
use crate::cli::PrecheckArgs;
struct Step {
name: &'static str,
label: &'static str,
program: ProgramSpec,
}
enum ProgramSpec {
Cargo(&'static [&'static str]),
SelfReq(&'static [&'static str]),
}
pub fn run(args: PrecheckArgs, project_file: &Option<PathBuf>) -> Result<()> {
let steps: [Step; 6] = [
Step {
name: "fmt",
label: "cargo fmt --check",
program: ProgramSpec::Cargo(&["fmt", "--all", "--", "--check"]),
},
Step {
name: "clippy",
label: "cargo clippy",
program: ProgramSpec::Cargo(&["clippy", "--all-targets", "--", "-D", "warnings"]),
},
Step {
name: "test",
label: "cargo test",
program: ProgramSpec::Cargo(&["test", "--all"]),
},
Step {
name: "validate",
label: "req validate",
program: ProgramSpec::SelfReq(&["validate"]),
},
Step {
name: "coverage",
label: "req coverage --strict",
program: ProgramSpec::SelfReq(&["coverage", "--strict"]),
},
Step {
name: "review",
label: "req review --gate",
program: ProgramSpec::SelfReq(&["review", "--gate"]),
},
];
let skip: Vec<String> = args.skip.iter().map(|s| s.to_lowercase()).collect();
for s in &skip {
if !steps.iter().any(|step| step.name == s) {
anyhow::bail!(
"unknown --skip step `{}`; known: fmt, clippy, test, validate, coverage, review",
s
);
}
}
let self_exe = std::env::current_exe()
.map_err(|e| anyhow::anyhow!("could not locate the running req binary: {}", e))?;
let mut first_failure: Option<&'static str> = None;
let total = steps
.iter()
.filter(|s| !skip.contains(&s.name.into()))
.count();
let mut idx = 0;
for step in &steps {
if skip.contains(&step.name.to_string()) {
println!("[skip] {} (--skip {})", step.label, step.name);
continue;
}
idx += 1;
println!("\n=== [{}/{}] {} ===", idx, total, step.label);
let mut cmd: Command = match step.program {
ProgramSpec::Cargo(args) => {
let mut c = Command::new("cargo");
c.args(args);
c
}
ProgramSpec::SelfReq(extra) => {
let mut c = Command::new(&self_exe);
if let Some(p) = project_file.as_ref() {
c.arg("--file").arg(p);
}
c.args(extra);
c
}
};
cmd.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit());
let status = cmd
.status()
.map_err(|e| anyhow::anyhow!("failed to spawn `{}`: {}", step.label, e))?;
if !status.success() {
eprintln!(
"\n[fail] step `{}` exited with {}",
step.name,
status
.code()
.map(|c| c.to_string())
.unwrap_or_else(|| "signal".into())
);
if first_failure.is_none() {
first_failure = Some(step.name);
}
if !args.keep_going {
eprintln!(
"\nStopping at first failure (`{}`). Fix it and re-run, or pass --keep-going to see all failures.",
step.name
);
std::process::exit(1);
}
}
}
if let Some(name) = first_failure {
eprintln!("\nprecheck FAILED (first failure: `{}`)", name);
std::process::exit(1);
}
println!("\nprecheck OK — all steps passed.");
Ok(())
}