use std::{
process::Command,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
time::Duration,
};
use clap::Args;
use crate::cmd::run::is_cwd_oseda_project;
#[derive(Args, Debug, Clone)]
pub struct DevOptions {
#[arg(long, default_value_t = 3000)]
pub port: u16,
}
#[derive(Debug)]
pub enum OsedaDevError {
NotOsedaProjectError(String),
ServeError(String),
}
impl std::error::Error for OsedaDevError {}
impl std::fmt::Display for OsedaDevError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NotOsedaProjectError(msg) => write!(
f,
"Current working directory is not an Oseda project: {}",
msg
),
Self::ServeError(msg) => write!(f, "Oseda Dev Server Error: {}", msg),
}
}
}
pub fn dev(opts: DevOptions) -> Result<(), OsedaDevError> {
dev_with_shutdown(opts, Arc::new(AtomicBool::new(false)))
}
pub fn dev_with_shutdown(
opts: DevOptions,
shutdown_flag: Arc<AtomicBool>,
) -> Result<(), OsedaDevError> {
if !is_cwd_oseda_project() {
return Err(OsedaDevError::NotOsedaProjectError(
"oseda-config.json not found".to_string(),
));
}
let mut cmd = Command::new("npx");
cmd.arg("vite")
.arg("--port")
.arg(opts.port.to_string())
.arg("--strictPort");
let mut child = cmd.spawn().map_err(|e| {
println!("Error starting `npx vite`: {e}");
println!("Please ensure that `npx` and `vite` are installed and in your PATH.");
OsedaDevError::ServeError("failed to start vite dev server".into())
})?;
let ctrlc_flag = shutdown_flag.clone();
ctrlc::set_handler(move || {
println!("\nSIGINT received. Shutting down dev server...");
ctrlc_flag.store(true, Ordering::SeqCst);
})
.map_err(|e| {
println!("Error setting ctrl+c handler: {e}");
OsedaDevError::ServeError("failed to set handler".into())
})?;
while !shutdown_flag.load(Ordering::SeqCst) {
if let Ok(Some(status)) = child.try_wait() {
println!("`vite` exited on its own with status: {status}");
return Err(OsedaDevError::ServeError(
"vite dev server exited unexpectedly".into(),
));
}
std::thread::sleep(Duration::from_millis(100));
}
if let Err(e) = child.kill() {
println!("Failed to kill `vite`: {e}");
} else {
println!("`vite` dev server terminated.");
}
let _ = child.wait();
Ok(())
}