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
28pub 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 eprintln!("Building site...");
38 pyohwa_core::build::pipeline::build_dev(&project_root, config.port)?;
39 eprintln!("Build complete.");
40
41 let (reload_tx, _) = broadcast::channel::<()>(16);
43
44 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 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}