use std::path::{Path, PathBuf};
use tokio::signal::unix::{SignalKind, signal};
use tokio::sync::oneshot;
use crate::doctor;
use crate::error::CliError;
use crate::preflight::ensure_ports_available;
use crate::project::App;
use super::boot::{
BootOutcome, exit_verdict, forward_and_detect_url, spawn_host, stop_child, wait_until_ready,
};
use super::build::{build_application, host_binary};
pub fn run() -> Result<(), CliError> {
let app = App::find_from_current_dir()?;
doctor::require(doctor::BUILD_PREREQUISITES, "frame run")?;
build_application(&app)?;
let config = app.load_config()?;
ensure_ports_available(&config)?;
let binary = host_binary(&app);
let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.map_err(|source| CliError::Runtime { source })?;
runtime.block_on(serve(&app.root, &binary))
}
async fn serve(root: &Path, binary: &PathBuf) -> Result<(), CliError> {
let mut interrupt =
signal(SignalKind::interrupt()).map_err(|source| CliError::Runtime { source })?;
let mut terminate =
signal(SignalKind::terminate()).map_err(|source| CliError::Runtime { source })?;
let mut child = spawn_host(root, binary, &[])?;
let stdout = child.stdout.take().ok_or_else(|| CliError::CommandSpawn {
command: binary.display().to_string(),
source: std::io::Error::other("child host stdout was not captured"),
})?;
let (url_tx, url_rx) = oneshot::channel();
let pump = tokio::spawn(forward_and_detect_url(stdout, url_tx));
let addr = match wait_until_ready(&mut child, url_rx, &mut interrupt, &mut terminate).await? {
BootOutcome::Signaled => {
let verdict = stop_child(binary, child).await;
pump.abort();
return verdict;
}
BootOutcome::Ready(addr) => addr,
};
println!("serving at http://{addr} (Ctrl-C to stop)");
let verdict = tokio::select! {
status = child.wait() => {
let status = status.map_err(|source| CliError::CommandSpawn {
command: binary.display().to_string(),
source,
})?;
exit_verdict(binary, status)
}
_ = interrupt.recv() => stop_child(binary, child).await,
_ = terminate.recv() => stop_child(binary, child).await,
};
pump.abort();
verdict
}