//! Builds the Solid management UI for embedded Release binaries. //! //! Debug builds intentionally return before invoking npm: during development //! Vite serves `web/` independently and proxies its API client to the Rust //! listener. Release builds use the lockfile, write only into Cargo `OUT_DIR`, //! and expose a cfg consumed by `src/web.rs`. The source tree therefore never //! needs a generated `dist/` for `cargo build --release`. //! //! Set `WASMELD_BUILD_WEB=1` to exercise the embedded path in another Cargo //! profile, or `NPM=/absolute/path/to/npm` when npm is not on `PATH`. use std::{ env, fs, path::PathBuf, process::{Command, ExitStatus}, }; const WEB_INPUTS: &[&str] = &[ "web/src", "web/public", "web/index.html", "web/page.html", "web/package.json", "web/package-lock.json", "web/tsconfig.json", "web/vite.config.ts", ]; fn main() { println!("cargo:rustc-check-cfg=cfg(wasmeld_embedded_web)"); println!("cargo:rerun-if-env-changed=WASMELD_BUILD_WEB"); println!("cargo:rerun-if-env-changed=NPM"); for input in WEB_INPUTS { println!("cargo:rerun-if-changed={input}"); } let force = env::var("WASMELD_BUILD_WEB").is_ok_and(|value| value == "1"); if env::var("PROFILE").as_deref() != Ok("release") && !force { return; } let crate_dir = PathBuf::from(required_env("CARGO_MANIFEST_DIR")); let web_dir = crate_dir.join("web"); let output_dir = PathBuf::from(required_env("OUT_DIR")).join("web"); let npm = env::var_os("NPM") .map(PathBuf::from) .unwrap_or_else(|| PathBuf::from(if cfg!(windows) { "npm.cmd" } else { "npm" })); if output_dir.exists() { fs::remove_dir_all(&output_dir).unwrap_or_else(|error| { panic!( "failed to clear embedded Web output {}: {error}", output_dir.display() ) }); } // `npm ci` is deliberate here: a release must not silently rewrite the // dependency graph represented by package-lock.json. run( Command::new(&npm).current_dir(&web_dir).args([ "ci", "--ignore-scripts", "--no-audit", "--no-fund", ]), "install locked Web dependencies", ); run( Command::new(&npm) .current_dir(&web_dir) .env("WASMELD_WEB_OUT_DIR", &output_dir) .args(["run", "build"]), "build embedded Web application", ); for required in ["index.html", "page.html", "sw.js", "manifest.webmanifest"] { let path = output_dir.join(required); assert!( path.is_file(), "Web build did not produce required asset {}", path.display() ); } println!("cargo:rustc-cfg=wasmeld_embedded_web"); } fn run(command: &mut Command, action: &str) { let status = command .status() .unwrap_or_else(|error| panic!("failed to {action}: {error}")); assert_success(status, action); } fn assert_success(status: ExitStatus, action: &str) { assert!( status.success(), "failed to {action}: process exited with {status}" ); } fn required_env(name: &str) -> String { env::var(name).unwrap_or_else(|_| panic!("Cargo did not provide {name}")) }