pub mod bootstrap;
pub mod dev;
pub mod doctor;
pub mod governance;
pub mod services;
use std::process::Command;
pub type CmdResult = Result<(), CmdError>;
#[derive(Debug)]
pub struct CmdError {
pub message: String,
pub details: Option<String>,
}
impl std::fmt::Display for CmdError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "error: {}", self.message)?;
if let Some(details) = &self.details {
write!(f, "\n{details}")?;
}
Ok(())
}
}
impl CmdError {
pub fn new(message: impl Into<String>) -> Self {
Self {
message: message.into(),
details: None,
}
}
}
pub fn run_cmd(program: &str, args: &[&str]) -> Result<(), String> {
let status = Command::new(program)
.args(args)
.status()
.map_err(|e| format!("failed to execute {program}: {e}"))?;
if status.success() {
Ok(())
} else {
Err(format!(
"{program} exited with code {}",
status.code().unwrap_or(-1)
))
}
}
pub fn run_cmd_output(program: &str, args: &[&str]) -> Result<String, String> {
let output = Command::new(program)
.args(args)
.output()
.map_err(|e| format!("failed to execute {program}: {e}"))?;
if output.status.success() {
Ok(String::from_utf8_lossy(&output.stdout).to_string())
} else {
Err(String::from_utf8_lossy(&output.stderr).to_string())
}
}
pub fn command_exists(cmd: &str) -> bool {
Command::new("which")
.arg(cmd)
.output()
.map(|o| o.status.success())
.unwrap_or(false)
}
pub fn find_workspace_root() -> Option<std::path::PathBuf> {
let mut dir = std::env::current_dir().ok()?;
loop {
if dir.join("justfile").exists() || dir.join("Justfile").exists() {
return Some(dir);
}
if !dir.pop() {
return None;
}
}
}