Files
wasmeld/crates/wasmeld-console/build.rs
T
Maofeng f6ad94e948 feat(console): embed the management UI in release binaries
- build locked Web dependencies automatically for release profiles
- keep debug Cargo builds independent from the Vite development server
- embed generated Host, Page, manifest, and service worker assets
- serve exact control-plane assets with MIME and cache headers
- reject non-canonical and unknown paths without an SPA fallback
- cover embedded document boundaries and path validation
2026-07-30 09:22:55 +08:00

105 lines
3.2 KiB
Rust

//! 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}"))
}