diff --git a/.gitignore b/.gitignore index ba6efe0..9689e33 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,4 @@ rust_guardian.* # Test artifacts *.profraw lcov.info +update_analyzer.sh diff --git a/Cargo.lock b/Cargo.lock index ffcdb40..8a9e2fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -831,7 +831,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -1996,7 +1996,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -2797,7 +2797,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.12.1", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -3539,7 +3539,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix 1.1.4", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -4265,7 +4265,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.48.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index b5c5a1d..fe03c37 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,6 +68,7 @@ chrono = { version = "0.4", features = ["serde"] } # Utilities bytes = "1" futures = "0.3" +tokio-stream = { version = "0.1.18", features = ["sync"] } async-trait = "0.1" dashmap = "6" indexmap = { version = "2", features = ["serde"] } diff --git a/cli/src/commands/dev.rs b/cli/src/commands/dev.rs index 2454e8a..b971de7 100644 --- a/cli/src/commands/dev.rs +++ b/cli/src/commands/dev.rs @@ -7,6 +7,9 @@ use forge_runtime::dev::dev_server::{start_dev_server, DevServerConfig}; #[derive(Debug, Args)] pub struct DevArgs { + /// Host to bind to (default: 127.0.0.1) + #[arg(long, default_value = "127.0.0.1")] + pub host: String, /// Port for the dev server (default: 3000) #[arg(long, default_value = "3000")] pub port: u16, @@ -16,10 +19,17 @@ pub struct DevArgs { } pub async fn run(args: DevArgs) -> Result<()> { - crate::output::info(&format!("Starting dev server on :{}", args.port)); - crate::output::info(&format!("Forge Studio on :{}", args.studio_port)); + crate::output::info(&format!( + "Starting dev server on {}:{}", + args.host, args.port + )); + crate::output::info(&format!( + "Forge Studio on {}:{}", + args.host, args.studio_port + )); let config = DevServerConfig { + host: args.host, port: args.port, studio_port: args.studio_port, project_root: Utf8PathBuf::from("."), diff --git a/runtime/src/dev/dev_server.rs b/runtime/src/dev/dev_server.rs index 627d2fc..fb16b4e 100644 --- a/runtime/src/dev/dev_server.rs +++ b/runtime/src/dev/dev_server.rs @@ -15,6 +15,8 @@ use tracing::{error, info}; /// Configuration for the development server. #[derive(Debug, Clone)] pub struct DevServerConfig { + /// Host to bind to (default: 127.0.0.1) + pub host: String, /// Port for the dev server (default: 3000) pub port: u16, /// Port for Forge Studio (default: 3001) @@ -26,6 +28,7 @@ pub struct DevServerConfig { impl Default for DevServerConfig { fn default() -> Self { Self { + host: "127.0.0.1".to_string(), port: 3000, studio_port: 3001, project_root: Utf8PathBuf::from("."), @@ -42,21 +45,35 @@ pub async fn start_dev_server(config: DevServerConfig) -> Result<(), RuntimeErro let mut watcher = RecommendedWatcher::new( move |res| { - let _ = tx.blocking_send(res); + if let Err(e) = tx.try_send(res) { + tracing::warn!("Failed to send watcher event: {}", e); + } }, Config::default(), ) .map_err(|e| RuntimeError::Internal(format!("Failed to initialize watcher: {}", e)))?; - // Watch the src directory - let src_dir = config.project_root.join("src"); - if src_dir.exists() { - watcher - .watch(src_dir.as_std_path(), RecursiveMode::Recursive) - .map_err(|e| RuntimeError::Internal(format!("Failed to watch src directory: {}", e)))?; - info!("Watching {} for changes", src_dir); - } else { - error!("src directory not found in {}", config.project_root); + // Watch relevant directories + let watch_dirs = ["app", "public", "src"]; + let mut watching_any = false; + for dir in watch_dirs { + let path = config.project_root.join(dir); + if path.exists() { + watcher + .watch(path.as_std_path(), RecursiveMode::Recursive) + .map_err(|e| { + RuntimeError::Internal(format!("Failed to watch {} directory: {}", dir, e)) + })?; + info!("Watching {} for changes", path); + watching_any = true; + } + } + + if !watching_any { + error!( + "No recognizable source directories (app, public, src) found in {}", + config.project_root + ); } // Spawn a background task to process file watcher events and trigger HMR @@ -82,7 +99,7 @@ pub async fn start_dev_server(config: DevServerConfig) -> Result<(), RuntimeErro // 2. Main App Server (incorporates HMR router) // TODO: Combine with the actual app router let app = hmr_router(hmr_state.clone()); - let addr = format!("0.0.0.0:{}", config.port); + let addr = format!("{}:{}", config.host, config.port); let listener = TcpListener::bind(&addr).await.map_err(RuntimeError::Io)?; // 3. Studio Server @@ -90,7 +107,7 @@ pub async fn start_dev_server(config: DevServerConfig) -> Result<(), RuntimeErro "/", axum::routing::get(|| async { "Forge Studio (Coming soon)" }), ); - let studio_addr = format!("0.0.0.0:{}", config.studio_port); + let studio_addr = format!("{}:{}", config.host, config.studio_port); let studio_listener = TcpListener::bind(&studio_addr) .await .map_err(RuntimeError::Io)?; diff --git a/runtime/src/dev/hot_reload.rs b/runtime/src/dev/hot_reload.rs index 98d0607..9c41566 100644 --- a/runtime/src/dev/hot_reload.rs +++ b/runtime/src/dev/hot_reload.rs @@ -61,7 +61,7 @@ impl HmrState { } /// Create the axum router for the HMR endpoint. -pub fn hmr_router(state: HmrState) -> Router { +pub fn hmr_router(state: HmrState) -> Router<()> { Router::new() .route("/_forge/hmr", get(hmr_endpoint)) .with_state(state) @@ -74,10 +74,13 @@ async fn hmr_endpoint( let rx = state.tx.subscribe(); let stream = BroadcastStream::new(rx).filter_map(|msg| { match msg { - Ok(msg) => { - let json = serde_json::to_string(&msg).unwrap_or_default(); - Some(Ok(Event::default().data(json))) - } + Ok(msg) => match serde_json::to_string(&msg) { + Ok(json) => Some(Ok(Event::default().data(json))), + Err(e) => { + tracing::error!("Failed to serialize HMR message: {}", e); + None + } + }, // Ignore lag errors Err(_) => None, }