diff --git a/build.rs b/build.rs index 01afed645..2320503bb 100644 --- a/build.rs +++ b/build.rs @@ -1,22 +1,30 @@ +use std::fs; use std::path::Path; use std::process::Command; +use std::time::SystemTime; fn main() { let dist_dir = Path::new("web/dist"); let web_dir = Path::new("web"); - // Tell Cargo to re-run this script when web source files change. + // Tell Cargo to re-run this script when web sources or bundled assets change. println!("cargo:rerun-if-changed=web/src"); + println!("cargo:rerun-if-changed=web/public"); println!("cargo:rerun-if-changed=web/index.html"); println!("cargo:rerun-if-changed=web/package.json"); + println!("cargo:rerun-if-changed=web/package-lock.json"); + println!("cargo:rerun-if-changed=web/tsconfig.json"); + println!("cargo:rerun-if-changed=web/tsconfig.app.json"); + println!("cargo:rerun-if-changed=web/tsconfig.node.json"); println!("cargo:rerun-if-changed=web/vite.config.ts"); + println!("cargo:rerun-if-changed=web/dist"); // Attempt to build the web frontend if npm is available and web/dist is // missing or stale. The build is best-effort: when Node.js is not // installed (e.g. CI containers, cross-compilation, minimal dev setups) // we fall back to the existing stub/empty dist directory so the Rust // build still succeeds. - let needs_build = !dist_dir.join("index.html").exists(); + let needs_build = web_build_required(web_dir, dist_dir); if needs_build && web_dir.join("package.json").exists() { if let Ok(npm) = which_npm() { @@ -77,6 +85,49 @@ fn main() { ensure_dist_dir(dist_dir); } +fn web_build_required(web_dir: &Path, dist_dir: &Path) -> bool { + let Some(dist_mtime) = latest_modified(dist_dir) else { + return true; + }; + + [ + web_dir.join("src"), + web_dir.join("public"), + web_dir.join("index.html"), + web_dir.join("package.json"), + web_dir.join("package-lock.json"), + web_dir.join("tsconfig.json"), + web_dir.join("tsconfig.app.json"), + web_dir.join("tsconfig.node.json"), + web_dir.join("vite.config.ts"), + ] + .into_iter() + .filter_map(|path| latest_modified(&path)) + .any(|mtime| mtime > dist_mtime) +} + +fn latest_modified(path: &Path) -> Option { + let metadata = fs::metadata(path).ok()?; + if metadata.is_file() { + return metadata.modified().ok(); + } + if !metadata.is_dir() { + return None; + } + + let mut latest = metadata.modified().ok(); + let entries = fs::read_dir(path).ok()?; + for entry in entries.flatten() { + if let Some(child_mtime) = latest_modified(&entry.path()) { + latest = Some(match latest { + Some(current) if current >= child_mtime => current, + _ => child_mtime, + }); + } + } + latest +} + /// Ensure the dist directory exists so `rust-embed` does not fail at compile /// time even when the web frontend is not built. fn ensure_dist_dir(dist_dir: &Path) {