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;
9
10use tokio::sync::broadcast;
11
12pub struct DevServerConfig {
13    pub port: u16,
14    pub project_root: PathBuf,
15    pub open: bool,
16}
17
18impl Default for DevServerConfig {
19    fn default() -> Self {
20        Self {
21            port: 3000,
22            project_root: PathBuf::from("."),
23            open: false,
24        }
25    }
26}
27
28/// Run the dev server with file watching and live reload.
29pub async fn run_dev_server(config: DevServerConfig) -> Result<(), ServerError> {
30    let project_root = if config.project_root == PathBuf::from(".") {
31        std::env::current_dir()?
32    } else {
33        std::fs::canonicalize(&config.project_root)?
34    };
35
36    // Initial build with live reload JS
37    eprintln!("Building site...");
38    pyohwa_core::build::pipeline::build_dev(&project_root, config.port)?;
39    eprintln!("Build complete.");
40
41    // Broadcast channel for reload signals
42    let (reload_tx, _) = broadcast::channel::<()>(16);
43
44    // Start file watcher in a blocking thread
45    let watcher_root = project_root.clone();
46    let watcher_tx = reload_tx.clone();
47    let ws_port = config.port;
48    tokio::task::spawn_blocking(move || {
49        if let Err(e) = watcher::start_watcher(watcher_root, ws_port, watcher_tx) {
50            eprintln!("Watcher error: {e}");
51        }
52    });
53
54    eprintln!("Dev server running at http://localhost:{}", config.port);
55
56    if config.open {
57        let url = format!("http://localhost:{}", config.port);
58        let _ = open_browser(&url);
59    }
60
61    // Start HTTP + WebSocket server (blocks until Ctrl+C)
62    server::start_server(&project_root, config.port, reload_tx).await?;
63
64    Ok(())
65}
66
67fn open_browser(url: &str) -> Result<(), std::io::Error> {
68    #[cfg(target_os = "macos")]
69    {
70        std::process::Command::new("open").arg(url).spawn()?;
71    }
72    #[cfg(target_os = "linux")]
73    {
74        std::process::Command::new("xdg-open").arg(url).spawn()?;
75    }
76    #[cfg(target_os = "windows")]
77    {
78        std::process::Command::new("cmd")
79            .args(["/C", "start", url])
80            .spawn()?;
81    }
82    Ok(())
83}