use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
fn main() {
println!("cargo:rerun-if-env-changed=NOMOREIDE_EMBED_WEB_ROOT");
let out = PathBuf::from(std::env::var_os("OUT_DIR").expect("OUT_DIR is set by cargo"));
let generated = out.join("embedded_dashboard.rs");
let root = locate_client();
let mut files = BTreeMap::new();
if let Some(root) = root.as_deref() {
println!("cargo:rerun-if-changed={}", root.display());
collect(root, root, &mut files);
}
if files.is_empty() {
println!(
"cargo:warning=no built dashboard found; \
this binary will serve the API but no UI. Run `npm run build`."
);
}
let mut source = String::from(
"// @generated by build.rs — the dashboard, as it stood at compile time.\n\
pub(crate) static EMBEDDED_DASHBOARD: &[(&str, &[u8])] = &[\n",
);
for (relative, absolute) in &files {
source.push_str(&format!(
" ({}, include_bytes!({})),\n",
escape(relative),
escape(&absolute.to_string_lossy())
));
}
source.push_str("];\n");
std::fs::write(&generated, source).expect("writing the generated asset table");
}
fn locate_client() -> Option<PathBuf> {
if let Some(configured) = std::env::var_os("NOMOREIDE_EMBED_WEB_ROOT") {
let path = PathBuf::from(configured);
assert!(
path.join("index.html").is_file(),
"NOMOREIDE_EMBED_WEB_ROOT={} holds no index.html",
path.display()
);
return Some(path);
}
let manifest = PathBuf::from(std::env::var_os("CARGO_MANIFEST_DIR")?);
let mut current = Some(manifest.as_path());
while let Some(directory) = current {
let candidate = directory.join("dist/web/client");
if candidate.join("index.html").is_file() {
return Some(candidate);
}
current = directory.parent();
}
let vendored = manifest.join("web-client");
if vendored.join("index.html").is_file() {
return Some(vendored);
}
None
}
fn collect(root: &Path, directory: &Path, files: &mut BTreeMap<String, PathBuf>) {
let Ok(entries) = std::fs::read_dir(directory) else {
return;
};
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
println!("cargo:rerun-if-changed={}", path.display());
collect(root, &path, files);
} else if let Ok(relative) = path.strip_prefix(root) {
println!("cargo:rerun-if-changed={}", path.display());
let key = relative
.components()
.map(|component| component.as_os_str().to_string_lossy())
.collect::<Vec<_>>()
.join("/");
files.insert(key, path.clone());
}
}
}
fn escape(value: &str) -> String {
format!("\"{}\"", value.replace('\\', "\\\\").replace('"', "\\\""))
}