use std::path::Path;
use std::process::Command;
use crate::cli::OutputFormat;
use crate::error::CliError;
struct DepStatus {
name: String,
available: bool,
version: Option<String>,
}
pub fn run(path: &Path, output: &OutputFormat) -> Result<(), CliError> {
let deps = vec![check_dep("bun"), check_dep("node"), check_dep("opencode")];
let write_ok = path
.metadata()
.map(|m| !m.permissions().readonly())
.unwrap_or(false);
let node_modules_ok = path.join(".opencode").join("node_modules").exists();
let all_ok = deps.iter().all(|d| d.available) && write_ok;
match output {
OutputFormat::Json => {
let obj: Vec<_> = deps
.iter()
.map(|d| {
serde_json::json!({
"name": d.name,
"available": d.available,
"version": d.version,
})
})
.collect();
let json = serde_json::to_string_pretty(&serde_json::json!({
"status": if all_ok { "ok" } else { "degraded" },
"dependencies": obj,
"write_permission": write_ok,
"node_modules_installed": node_modules_ok,
}))
.unwrap_or_default();
println!("{json}");
}
_ => {
for dep in &deps {
let status = if dep.available { "OK " } else { "MISSING" };
let ver = dep.version.as_deref().unwrap_or("not detected");
println!("{status} {}: {ver}", dep.name);
}
let wp_status = if write_ok { "OK " } else { "MISSING" };
println!("{wp_status} write_permission: {}", path.display());
let nm_status = if node_modules_ok {
"OK "
} else {
"MISSING"
};
println!("{nm_status} node_modules: run bun install or npm install");
println!("\nStatus: {}", if all_ok { "ok" } else { "degraded" });
}
}
if !all_ok {
return Err(CliError::Generic(
"missing dependencies detected".to_string(),
));
}
Ok(())
}
fn check_dep(name: &str) -> DepStatus {
match Command::new(name).arg("--version").output() {
Ok(out) if out.status.success() => {
let version = String::from_utf8_lossy(&out.stdout)
.lines()
.next()
.map(str::trim)
.map(String::from);
DepStatus {
name: name.to_string(),
available: true,
version,
}
}
_ => DepStatus {
name: name.to_string(),
available: false,
version: None,
},
}
}