use anyhow::{Context, Result};
use std::path::Path;
use std::process::Stdio;
pub async fn resolve_deps(
manifest_toml: &str,
tap_root: &Path,
status_cb: Option<&(dyn Fn(&str) + Send + Sync)>,
) -> Result<()> {
let entries = parse_dep_entries(manifest_toml)?;
if entries.is_empty() {
return Ok(());
}
let deps_root = tap_root.join("deps");
for entry in &entries {
if let Some(cb) = status_cb {
cb(&format!("Checking dep: {entry}"));
} else {
crate::log_debug!("checking dep: {}", entry);
}
run_dep_script(entry, &deps_root)
.with_context(|| format!("Dependency '{entry}' failed — cannot start session"))?;
}
Ok(())
}
fn parse_dep_entries(toml_str: &str) -> Result<Vec<String>> {
let value: toml::Value =
toml::from_str(toml_str).context("Failed to parse manifest TOML for deps")?;
let Some(deps) = value.get("deps") else {
return Ok(vec![]);
};
let Some(require) = deps.get("require") else {
return Ok(vec![]);
};
let toml::Value::Array(arr) = require else {
anyhow::bail!("[deps] require must be an array of strings");
};
arr.iter()
.map(|v| match v {
toml::Value::String(s) => Ok(s.clone()),
_ => anyhow::bail!("[deps] require entries must be strings"),
})
.collect()
}
fn run_dep_script(entry: &str, deps_root: &Path) -> Result<()> {
let script_path = deps_root.join(format!("{entry}.sh"));
if !script_path.exists() {
anyhow::bail!(
"Dep script not found: {} (looked in {})",
entry,
script_path.display()
);
}
crate::log_debug!("running dep script: {}", entry);
let output = std::process::Command::new("bash")
.arg(&script_path)
.stdin(Stdio::null()) .stdout(Stdio::null()) .stderr(Stdio::piped()) .output()
.with_context(|| format!("Failed to execute dep script: {}", script_path.display()))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
let stderr_msg = if stderr.trim().is_empty() {
String::new()
} else {
format!("\n{}", stderr.trim())
};
anyhow::bail!(
"Dep script '{}' exited with status {}{}",
entry,
output.status.code().unwrap_or(-1),
stderr_msg
);
}
Ok(())
}