Skip to content
Clone
971c37c
135 lines 3.6 KB Rust
use std::collections::BTreeMap;
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
fn main() {
println!("cargo:rerun-if-changed=build.rs");
for watched in [
"src",
"index.html",
"vite.config.ts",
"package.json",
"bun.lock",
"tsconfig.json",
"tsconfig.app.json",
"tsconfig.node.json",
] {
println!("cargo:rerun-if-changed={watched}");
}
let root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR"));
let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR"));
if env::var_os("ADMIN_UI_SKIP_FRONTEND_BUILD").is_none() && !build_frontend(&root) {
println!(
"cargo:warning=admin frontend build via bun/npm unavailable or failed; \
embedding existing dist/ as-is"
);
}
let dist = root.join("dist");
let index = dist.join("index.html");
if !index.is_file() {
panic!(
"dist/index.html not found — run `bun install && bun run build` \
(or `npm run build`) inside admin/ first"
);
}
let mut files: BTreeMap<String, PathBuf> = BTreeMap::new();
collect(&dist, &dist, &mut files);
let mut src = String::from("// @generated by build.rs — embedded admin frontend (dist/)\n\n");
src.push_str(&format!(
"pub const INDEX: &[u8] = include_bytes!({:?});\n\n",
index
));
src.push_str("pub const FILES: &[(&str, &[u8])] = &[\n");
for (rel, abs) in &files {
if rel == "index.html" {
continue;
}
src.push_str(&format!(" ({:?}, include_bytes!({:?})),\n", rel, abs));
}
src.push_str("];\n\npub const PATHS: &[&str] = &[\n");
for rel in files.keys() {
src.push_str(&format!(" {:?},\n", rel));
}
src.push_str("];\n");
fs::write(out_dir.join("dist_embed.rs"), src).expect("write dist_embed.rs");
}
fn build_frontend(root: &Path) -> bool {
if !root.join("package.json").is_file() {
return false;
}
let tool = if command_ok("bun") {
"bun"
} else if command_ok("npm") {
npm_cmd()
} else {
return false;
};
let mut steps: Vec<(&str, &[&str])> = Vec::new();
if !root.join("node_modules").is_dir() {
steps.push((tool, &["install"]));
}
steps.push((tool, &["run", "build"]));
for (t, args) in steps {
eprintln!(
"cargo:warning=running `{t} {}` in {}",
args.join(" "),
root.display()
);
let ok = Command::new(t)
.args(args)
.current_dir(root)
.status()
.map(|s| s.success())
.unwrap_or(false);
if !ok {
return false;
}
}
true
}
#[cfg(windows)]
fn npm_cmd() -> &'static str {
"npm.cmd"
}
#[cfg(not(windows))]
fn npm_cmd() -> &'static str {
"npm"
}
fn command_ok(name: &str) -> bool {
Command::new(name)
.arg("--version")
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.is_ok_and(|s| s.success())
}
fn collect(base: &Path, dir: &Path, out: &mut BTreeMap<String, PathBuf>) {
for entry in fs::read_dir(dir).expect("read dist dir") {
let path = entry.expect("dist entry").path();
if path.is_dir() {
collect(base, &path, out);
} else {
let rel = path
.strip_prefix(base)
.unwrap()
.to_string_lossy()
.replace('\\', "/");
out.insert(rel, path);
}
}
}