diff --git a/server/src/builder.rs b/server/src/builder.rs index 839d30d..ad27ffa 100644 --- a/server/src/builder.rs +++ b/server/src/builder.rs @@ -1,5 +1,5 @@ use chrono::Utc; -use tokio::io::{AsyncBufReadExt, BufReader}; +use tokio::io::{AsyncReadExt, BufReader}; use tokio::process::Command; use uuid::Uuid; @@ -136,28 +136,47 @@ async fn run_build(state: &AppState, deploy_id: &str) -> anyhow::Result<()> { let stdout = child.stdout.take().expect("piped stdout"); let stderr = child.stderr.take().expect("piped stderr"); - // Stream stdout and stderr concurrently into the deploy log. + // Read stdout/stderr in 4 KB chunks so we stream incrementally AND capture + // any partial last line that has no trailing newline (which lines() drops). let db1 = state.db.clone(); let id1 = deploy_id.to_string(); let stdout_task = tokio::spawn(async move { - let mut lines = BufReader::new(stdout).lines(); - while let Ok(Some(line)) = lines.next_line().await { - let _ = append_log(&db1, &id1, &format!("{}\n", line)).await; + let mut reader = BufReader::new(stdout); + let mut buf = vec![0u8; 4096]; + loop { + match reader.read(&mut buf).await { + Ok(0) | Err(_) => break, + Ok(n) => { + let chunk = String::from_utf8_lossy(&buf[..n]).into_owned(); + let _ = append_log(&db1, &id1, &chunk).await; + } + } } }); let db2 = state.db.clone(); let id2 = deploy_id.to_string(); let stderr_task = tokio::spawn(async move { - let mut lines = BufReader::new(stderr).lines(); - while let Ok(Some(line)) = lines.next_line().await { - let _ = append_log(&db2, &id2, &format!("{}\n", line)).await; + let mut reader = BufReader::new(stderr); + let mut buf = vec![0u8; 4096]; + loop { + match reader.read(&mut buf).await { + Ok(0) | Err(_) => break, + Ok(n) => { + let chunk = String::from_utf8_lossy(&buf[..n]).into_owned(); + let _ = append_log(&db2, &id2, &chunk).await; + } + } } }); let exit_status = child.wait().await?; let _ = tokio::join!(stdout_task, stderr_task); + // Always record the exit code — the one line that survives even silent failures. + let code = exit_status.code().map(|c| c.to_string()).unwrap_or_else(|| "signal".into()); + append_log(&state.db, deploy_id, &format!("\n[hiy] exit code: {}\n", code)).await?; + let final_status = if exit_status.success() { "success" } else { "failed" }; let finished = Utc::now().to_rfc3339();