use std::path::Path;
use anyhow::{Result, bail};
use crate::model::App;
use crate::store;
pub fn run(command_name: &str, app_name: Option<String>) -> Result<()> {
let apps = store::load_apps()?;
let app = resolve(&apps, app_name)?;
let Some(command_line) = app.commands.get(command_name) else {
bail!(
"app '{0}' has no '{1}' command - add it with `turnout app edit {0} --command {1}=CMD`",
app.name,
command_name
);
};
let dir = crate::utils::project_dir(Path::new(&app.path))?;
eprintln!("[{}] {command_line}", app.name);
let status = crate::utils::run_in_dir(command_line, &dir)?;
let code = if crate::term::interrupted() { 130 } else { status.code().unwrap_or(1) };
std::process::exit(code);
}
pub(crate) fn resolve(apps: &[App], name: Option<String>) -> Result<&App> {
match name {
Some(name) => apps
.iter()
.find(|a| a.name == name)
.ok_or_else(|| anyhow::anyhow!("no app named '{name}' - see `turnout app list`")),
None => {
let cwd = std::env::current_dir()?;
let cwd = std::fs::canonicalize(&cwd).unwrap_or(cwd);
let here = apps
.iter()
.filter(|a| {
let path = Path::new(&a.path);
let path = std::fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf());
cwd.starts_with(&path)
})
.max_by_key(|a| a.path.len());
if let Some(app) = here {
return Ok(app);
}
crate::pick::ensure_interactive("not inside a known app directory - pass the app name or see `turnout app list`")?;
let picked = crate::pick::app(apps, &store::load_state()?, "App")?;
apps.iter().find(|a| a.name == picked).ok_or_else(|| anyhow::anyhow!("no app named '{picked}'"))
}
}
}