use std::path::{Path, PathBuf};
use std::process::Command;
fn main() {
let crate_root = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap_or_default());
println!("cargo:rerun-if-env-changed=SKIP_UI_BUILD");
println!("cargo:rerun-if-env-changed=FORCE_UI_BUILD");
println!("cargo:rerun-if-changed=ui/package.json");
println!("cargo:rerun-if-changed=ui/vite.config.js");
println!("cargo:rerun-if-changed=ui/index.html");
println!("cargo:rerun-if-changed=ui/src");
println!("cargo:rerun-if-changed=ui-search/package.json");
println!("cargo:rerun-if-changed=ui-search/vite.config.js");
println!("cargo:rerun-if-changed=ui-search/index.html");
println!("cargo:rerun-if-changed=ui-search/src");
build_bundle(
&crate_root,
&crate_root.join("ui"),
&crate_root.join("ui").join("dist"),
"trusty-console",
);
build_bundle(
&crate_root,
&crate_root.join("ui-search"),
&crate_root.join("ui-search-dist"),
"trusty-console-search",
);
}
fn build_bundle(crate_root: &Path, ui_dir: &Path, dist_dir: &Path, stamp_key: &str) {
if std::env::var("FORCE_UI_BUILD").as_deref() != Ok("1")
&& committed_bundle_is_fresh(crate_root, dist_dir, stamp_key)
{
return;
}
if build_svelte_ui(ui_dir, dist_dir, stamp_key) {
restamp_bundle(crate_root, stamp_key);
}
}
fn committed_bundle_is_fresh(crate_root: &Path, dist_dir: &Path, crate_name: &str) -> bool {
if !dist_dir.join("index.html").exists() {
return false;
}
let Some(repo_root) = crate_root.parent().and_then(Path::parent) else {
return true;
};
let script = repo_root.join("scripts/check-ui-bundle-freshness.sh");
if !script.exists() {
return true;
}
match Command::new("bash")
.arg(&script)
.arg(crate_name)
.current_dir(repo_root)
.output()
{
Ok(out) if out.status.success() => true,
Ok(out) if out.status.code() == Some(1) => false,
_ => {
println!(
"cargo:warning={crate_name}: could not check UI bundle freshness — \
keeping the committed bundle (set FORCE_UI_BUILD=1 to rebuild)."
);
true
}
}
}
fn restamp_bundle(crate_root: &Path, crate_name: &str) {
let Some(repo_root) = crate_root.parent().and_then(Path::parent) else {
return;
};
let script = repo_root.join("scripts/stamp-ui-bundle.sh");
if !script.exists() {
return;
}
let ok = Command::new("bash")
.arg(&script)
.arg(crate_name)
.current_dir(repo_root)
.status()
.map(|s| s.success())
.unwrap_or(false);
if !ok {
println!(
"cargo:warning={crate_name}: the bundle was rebuilt but stamp-ui-bundle.sh \
failed — run it by hand before committing the bundle."
);
}
}
fn build_svelte_ui(ui_dir: &Path, dist_dir: &Path, crate_name: &str) -> bool {
if std::env::var("SKIP_UI_BUILD").as_deref() == Ok("1") {
if !dist_dir.join("index.html").exists() {
println!(
"cargo:warning=SKIP_UI_BUILD=1 but {dist}/ is empty — \
run `pnpm --dir ui install && pnpm --dir ui build` before publishing.",
dist = dist_dir.display()
);
ensure_placeholder(dist_dir, crate_name);
}
return false;
}
if !ui_dir.join("package.json").exists() {
ensure_placeholder(dist_dir, crate_name);
return false;
}
let Some(pm) = detect_pm() else {
println!(
"cargo:warning={crate_name}: no pnpm/npm on PATH — skipping UI \
build (set SKIP_UI_BUILD=1 to silence, or install pnpm)."
);
ensure_placeholder(dist_dir, crate_name);
return false;
};
let mut install_args = vec!["install"];
if pm == "pnpm" && ui_dir.join("pnpm-lock.yaml").exists() {
install_args.push("--frozen-lockfile");
}
let install_ok = Command::new(pm)
.args(&install_args)
.current_dir(ui_dir)
.status()
.map(|s| s.success())
.unwrap_or(false);
if !install_ok {
println!("cargo:warning={crate_name}: `{pm} install` failed — embedding placeholder UI.");
ensure_placeholder(dist_dir, crate_name);
return false;
}
let build_ok = Command::new(pm)
.args(["run", "build"])
.current_dir(ui_dir)
.status()
.map(|s| s.success())
.unwrap_or(false);
if !build_ok {
println!("cargo:warning={crate_name}: `{pm} run build` failed — embedding placeholder UI.");
ensure_placeholder(dist_dir, crate_name);
}
build_ok
}
fn ensure_placeholder(dist_dir: &Path, crate_name: &str) {
if dist_dir.join("index.html").exists() {
return;
}
let _ = std::fs::create_dir_all(dist_dir);
let html = format!(
"<!doctype html><html><body><p>{crate_name}: UI assets not built. \
Run <code>pnpm --dir ui install && pnpm --dir ui build</code> \
and rebuild.</p></body></html>"
);
let _ = std::fs::write(dist_dir.join("index.html"), html);
}
fn detect_pm() -> Option<&'static str> {
let ok = |bin: &str| {
Command::new(bin)
.arg("--version")
.output()
.map(|o| o.status.success())
.unwrap_or(false)
};
if ok("pnpm") {
Some("pnpm")
} else if ok("npm") {
Some("npm")
} else {
None
}
}