1use std::{
2 process::Command,
3 sync::{
4 atomic::{AtomicBool, Ordering},
5 Arc,
6 },
7 time::Duration,
8};
9
10use crate::cmd::is_cwd_oseda_project;
11
12#[derive(Debug)]
14pub enum OsedaRunError {
15 BuildError(String),
16 ServeError(String),
17 NotOsedaProjectError(String),
18}
19
20impl std::error::Error for OsedaRunError {}
21impl std::fmt::Display for OsedaRunError {
22 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23 match self {
24 Self::BuildError(msg) => write!(f, "Oseda Build Error: {}", msg),
25 Self::ServeError(msg) => write!(f, "Oseda Serve Error: {}", msg),
26 Self::NotOsedaProjectError(msg) => write!(
27 f,
28 "Current working directory is not an Oseda project: {}",
29 msg
30 ),
31 }
32 }
33}
34
35pub fn run() -> Result<(), OsedaRunError> {
46 if !is_cwd_oseda_project() {
47 return Err(OsedaRunError::NotOsedaProjectError(
48 "oseda-config.json not found".to_string(),
49 ));
50 }
51
52 run_with_shutdown(Arc::new(AtomicBool::new(false)))
53}
54
55pub fn run_with_shutdown(shutdown_flag: Arc<AtomicBool>) -> Result<(), OsedaRunError> {
56 match Command::new("npx").arg("vite").arg("build").status() {
59 Ok(status) => {
60 if !status.success() {
61 println!("Error: `npx vite build` exited with a failure.");
62 println!("Please ensure that npx and vite are installed properly.");
63 return Err(OsedaRunError::BuildError(
64 "could not 'npx vite build'".to_string(),
65 ));
66 }
67 }
68 Err(e) => {
69 println!("Error: failed to execute `npx vite build`: {e}");
70 println!("Please ensure that `npx` and `vite` are installed and in your PATH.");
71 return Err(OsedaRunError::BuildError(
72 "could not 'npx vite build'".to_string(),
73 ));
74 }
75 }
76
77 let mut child = Command::new("npx")
78 .arg("serve")
79 .arg("dist")
80 .spawn()
81 .map_err(|e| {
82 println!("Error starting `serve dist`: {e}");
83 OsedaRunError::ServeError("failed to start serve".into())
84 })?;
85 let ctrlc_flag = shutdown_flag.clone();
90 ctrlc::set_handler(move || {
91 println!("\nSIGINT received. Attempting graceful shutdown...");
92 ctrlc_flag.store(true, Ordering::SeqCst);
93 })
94 .map_err(|e| {
95 println!("Error setting ctrl+c handler: {e}");
96 OsedaRunError::ServeError("failed to set handler".into())
97 })?;
98
99 while !shutdown_flag.load(Ordering::SeqCst) {
101 std::thread::sleep(Duration::from_millis(100));
102 }
103
104 if let Err(e) = child.kill() {
106 println!("Failed to kill `serve`: {e}");
107 } else {
108 println!("`serve` process terminated.");
109 }
110
111 let _ = child.wait();
112
113 Ok(())
114}