Skip to main content

pyohwa_server/
lib.rs

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
30/// Run the dev server with file watching and live reload.
31pub 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    // Initial build with live reload JS
39    eprintln!("Building site...");
40    pyohwa_core::build::pipeline::build_dev(&project_root, config.port)?;
41    eprintln!("Build complete.");
42
43    // Broadcast channel for reload signals
44    let (reload_tx, _) = broadcast::channel::<()>(16);
45
46    // Shutdown flag for the file watcher
47    let shutdown = Arc::new(AtomicBool::new(false));
48    let shutdown_watcher = shutdown.clone();
49
50    // Start file watcher in a blocking thread
51    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    // Start HTTP + WebSocket server (blocks until Ctrl+C)
69    server::start_server(&project_root, config.port, reload_tx).await?;
70
71    // Signal the watcher to shut down and wait briefly
72    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}