use anyhow::{anyhow, Result};
use colored::Colorize;
use std::time::{Duration, Instant};
const READY_TIMEOUT: Duration = Duration::from_secs(10);
const POLL_INTERVAL: Duration = Duration::from_millis(500);
const PROBE_TIMEOUT: Duration = Duration::from_millis(750);
async fn probe_health(base: &str) -> bool {
let client = match reqwest::Client::builder()
.timeout(PROBE_TIMEOUT)
.connect_timeout(PROBE_TIMEOUT)
.build()
{
Ok(c) => c,
Err(_) => return false,
};
match client.get(format!("{}/health", base)).send().await {
Ok(r) => r.status().is_success(),
Err(_) => false,
}
}
fn spawn_daemon() -> Result<()> {
let exe = std::env::current_exe().map_err(|e| anyhow!("could not resolve current_exe: {e}"))?;
std::process::Command::new(&exe)
.arg("start")
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.spawn()
.map_err(|e| anyhow!("could not spawn `{} start`: {e}", exe.display()))?;
Ok(())
}
pub async fn ensure_daemon_running(base: &str) -> Result<()> {
if probe_health(base).await {
return Ok(());
}
eprintln!("{} Starting trusty-search daemon…", "◉".cyan());
spawn_daemon()?;
let deadline = Instant::now() + READY_TIMEOUT;
loop {
tokio::time::sleep(POLL_INTERVAL).await;
if probe_health(base).await {
return Ok(());
}
if Instant::now() >= deadline {
return Err(anyhow!(
"daemon did not become ready within {}s at {} — \
try `trusty-search start` manually to see the error",
READY_TIMEOUT.as_secs(),
base
));
}
}
}
pub async fn ensure_daemon_running_or_exit(base: &str) {
if let Err(e) = ensure_daemon_running(base).await {
eprintln!("{} {}", "✗".red(), e);
std::process::exit(1);
}
}