use std::ffi::OsString;
use std::process::Command;
use crate::cli::DepsArgs;
use crate::error::{Error, Result};
use crate::ui::Ui;
const DEPENDENCIES: &[&str] = &["sops", "age", "age-plugin-se"];
pub const BREW_BIN: &str = "brew";
pub const BREW_BIN_ENV: &str = "SOPSY_BREW_BIN";
fn brew_bin() -> OsString {
std::env::var_os(BREW_BIN_ENV).unwrap_or_else(|| OsString::from(BREW_BIN))
}
pub fn run(ui: &Ui, args: &DepsArgs) -> Result<()> {
ui.header("sopsy deps");
let missing = report_and_collect_missing(ui);
if args.check {
return if missing.is_empty() {
ui.success("all dependencies are installed");
Ok(())
} else {
Err(Error::Validation(format!(
"{} missing dependency(ies): {}",
missing.len(),
missing.join(", ")
)))
};
}
if missing.is_empty() {
ui.success("all dependencies are already installed — nothing to do");
return Ok(());
}
let command = format!("{BREW_BIN} install {}", missing.join(" "));
if args.dry_run {
ui.info(format!("dry run — would run: {command}"));
return Ok(());
}
ensure_brew_available()?;
ui.info(format!("running: {command}"));
install(&missing)?;
ui.success(format!("installed: {}", missing.join(", ")));
Ok(())
}
fn report_and_collect_missing(ui: &Ui) -> Vec<&'static str> {
let mut missing = Vec::new();
for tool in DEPENDENCIES {
match which::which(tool) {
Ok(path) => ui.success(format!("{tool} found at {}", path.display())),
Err(_) => {
ui.warn(format!("{tool} is not installed"));
missing.push(*tool);
}
}
}
missing
}
fn ensure_brew_available() -> Result<()> {
if which::which(brew_bin()).is_err() {
return Err(Error::Validation(
"Homebrew (`brew`) is required to install dependencies but was not found on PATH. \
Install it from https://brew.sh and re-run `sopsy deps`."
.to_string(),
));
}
Ok(())
}
fn install(tools: &[&str]) -> Result<()> {
let status = Command::new(brew_bin())
.arg("install")
.args(tools)
.status()?;
if status.success() {
return Ok(());
}
Err(Error::ProcessFailed {
tool: BREW_BIN.to_string(),
code: status.code().unwrap_or(-1),
message: format!("`brew install {}` failed", tools.join(" ")),
})
}