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