use std::{
process::Command,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
time::Duration,
};
#[derive(Debug)]
pub enum OsedaRunError {
BuildError(String),
ServeError(String),
}
impl std::error::Error for OsedaRunError {}
impl std::fmt::Display for OsedaRunError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::BuildError(msg) => write!(f, "Oseda Build Error: {}", msg),
Self::ServeError(msg) => write!(f, "Oseda Serve Error: {}", msg),
}
}
}
pub fn run() -> Result<(), OsedaRunError> {
run_with_shutdown(Arc::new(AtomicBool::new(false)))
}
pub fn run_with_shutdown(shutdown_flag: Arc<AtomicBool>) -> Result<(), OsedaRunError> {
match Command::new("npx").arg("vite").arg("build").status() {
Ok(status) => {
if !status.success() {
println!("Error: `npx vite build` exited with a failure.");
println!("Please ensure that npx and vite are installed properly.");
return Err(OsedaRunError::BuildError(
"could not 'npx vite build'".to_string(),
));
}
}
Err(e) => {
println!("Error: failed to execute `npx vite build`: {e}");
println!("Please ensure that `npx` and `vite` are installed and in your PATH.");
return Err(OsedaRunError::BuildError(
"could not 'npx vite build'".to_string(),
));
}
}
let mut child = Command::new("npx")
.arg("serve")
.arg("dist")
.spawn()
.map_err(|e| {
println!("Error starting `serve dist`: {e}");
OsedaRunError::ServeError("failed to start serve".into())
})?;
let ctrlc_flag = shutdown_flag.clone();
ctrlc::set_handler(move || {
println!("\nSIGINT received. Attempting graceful shutdown...");
ctrlc_flag.store(true, Ordering::SeqCst);
})
.map_err(|e| {
println!("Error setting ctrl+c handler: {e}");
OsedaRunError::ServeError("failed to set handler".into())
})?;
while !shutdown_flag.load(Ordering::SeqCst) {
std::thread::sleep(Duration::from_millis(100));
}
if let Err(e) = child.kill() {
println!("Failed to kill `serve`: {e}");
} else {
println!("`serve` process terminated.");
}
let _ = child.wait();
Ok(())
}