1mod error;
2mod reload;
3mod server;
4mod watcher;
5
6pub use error::ServerError;
7
8use std::path::PathBuf;
9use std::sync::atomic::{AtomicBool, Ordering};
10use std::sync::Arc;
11
12use tokio::sync::broadcast;
13
14pub struct DevServerConfig {
15 pub port: u16,
16 pub project_root: PathBuf,
17 pub open: bool,
18}
19
20impl Default for DevServerConfig {
21 fn default() -> Self {
22 Self {
23 port: 3000,
24 project_root: PathBuf::from("."),
25 open: false,
26 }
27 }
28}
29
30pub async fn run_dev_server(config: DevServerConfig) -> Result<(), ServerError> {
32 let project_root = if config.project_root == PathBuf::from(".") {
33 std::env::current_dir()?
34 } else {
35 std::fs::canonicalize(&config.project_root)?
36 };
37
38 eprintln!("Building site...");
40 pyohwa_core::build::pipeline::build_dev(&project_root, config.port)?;
41 eprintln!("Build complete.");
42
43 let (reload_tx, _) = broadcast::channel::<()>(16);
45
46 let shutdown = Arc::new(AtomicBool::new(false));
48 let shutdown_watcher = shutdown.clone();
49
50 let watcher_root = project_root.clone();
52 let watcher_tx = reload_tx.clone();
53 let ws_port = config.port;
54 let watcher_handle = tokio::task::spawn_blocking(move || {
55 if let Err(e) = watcher::start_watcher(watcher_root, ws_port, watcher_tx, shutdown_watcher)
56 {
57 eprintln!("Watcher error: {e}");
58 }
59 });
60
61 eprintln!("Dev server running at http://localhost:{}", config.port);
62
63 if config.open {
64 let url = format!("http://localhost:{}", config.port);
65 let _ = open_browser(&url);
66 }
67
68 server::start_server(&project_root, config.port, reload_tx).await?;
70
71 shutdown.store(true, Ordering::Relaxed);
73 let _ = tokio::time::timeout(std::time::Duration::from_millis(500), watcher_handle).await;
74
75 Ok(())
76}
77
78fn open_browser(url: &str) -> Result<(), std::io::Error> {
79 #[cfg(target_os = "macos")]
80 {
81 std::process::Command::new("open").arg(url).spawn()?;
82 }
83 #[cfg(target_os = "linux")]
84 {
85 std::process::Command::new("xdg-open").arg(url).spawn()?;
86 }
87 #[cfg(target_os = "windows")]
88 {
89 std::process::Command::new("cmd")
90 .args(["/C", "start", url])
91 .spawn()?;
92 }
93 Ok(())
94}