//! Build script for `monetize-admin`. Pure Rust, no subprocess.
//!
//! 1. **The build's commit** (`MONETIZE_ADMIN_GIT_HASH`), read from the on-disk
//! git plumbing — offered to the installer as `version`.
//! 2. **The built console**: monetize-ui-web's `dist/` (the Build Thing's
//! `[[build.web_app]] monetize-ui-web`) embedded, so the medium stays one
//! binary (korp-start execs exactly one file) — `MONETIZE_ADMIN_CONSOLE_DIST`,
//! else `../monetize-ui-web/dist`. An absent console is NOT a build failure
//! (the tests build without it): the binary knows it (`DIST_SOURCE` is
//! `None`), serves the console's API and says so; a `cargo:warning` says so
//! here (holger-estate-front's pattern). A bundle OLDER than monetize-ui-web's
//! sources is refused by name, `[console-stale]` (`build/console_fresh.rs`).
#[path = "build/console_fresh.rs"]
mod console_fresh;
use std::fmt::Write as _;
use std::fs;
use std::path::{Path, PathBuf};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let manifest = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR")?);
let out = PathBuf::from(std::env::var("OUT_DIR")?);
let hash = git_short_hash(&manifest).unwrap_or_else(|| "unknown".to_string());
println!("cargo:rustc-env=MONETIZE_ADMIN_GIT_HASH={hash}");
println!("cargo:rerun-if-env-changed=MONETIZE_ADMIN_CONSOLE_DIST");
let dist = std::env::var_os("MONETIZE_ADMIN_CONSOLE_DIST").map(PathBuf::from).unwrap_or_else(|| manifest.join("../monetize-ui-web/dist"));
let mut files = Vec::new();
println!("cargo:rerun-if-changed={}", dist.display());
let web = manifest.join("../monetize-ui-web");
for s in console_fresh::SOURCES {
println!("cargo:rerun-if-changed={}", web.join(s).display());
}
if dist.join("index.html").is_file() {
// A bundle older than its sources is refused by name: it would ship
// yesterday's console (the 2026-10-12 ONE box) without a word.
console_fresh::check(&dist, &web)?;
walk(&dist, &dist, &mut files);
for (_, abs) in &files {
println!("cargo:rerun-if-changed={}", abs.display());
}
} else {
println!("cargo:warning=monetize-admin: no built console at {} (index.html absent) — this binary serves the console's API, not the bundle", dist.display());
}
let mut code = String::new();
let _ = writeln!(code, "/// The built console's files, `(path, bytes)`.\npub static FILES: &[(&str, &[u8])] = &[");
for (rel, abs) in &files {
let _ = writeln!(code, " ({rel:?}, include_bytes!({:?})),", abs.display().to_string());
}
let _ = writeln!(code, "];");
let source = (!files.is_empty()).then(|| dist.canonicalize().unwrap_or(dist.clone()).display().to_string());
let _ = writeln!(code, "/// Where they were read from at build time; `None` = not embedded.\npub const DIST_SOURCE: Option<&str> = {source:?};");
fs::write(out.join("console_dist.rs"), code)?;
Ok(())
}
fn walk(dir: &Path, base: &Path, out: &mut Vec<(String, PathBuf)>) {
let Ok(rd) = fs::read_dir(dir) else { return };
let mut entries: Vec<_> = rd.flatten().collect();
entries.sort_by_key(|e| e.file_name());
for e in entries {
let p = e.path();
if p.is_dir() {
walk(&p, base, out);
} else if let Ok(rel) = p.strip_prefix(base) {
out.push((rel.to_string_lossy().replace('\\', "/"), p.canonicalize().unwrap_or(p.clone())));
}
}
}
/// The short commit of the checkout this crate is in: `.git/HEAD` → the ref →
/// its object id (loose ref, else `packed-refs`). A worktree's `.git` is a file
/// naming the real git dir. `None` outside a checkout.
fn git_short_hash(manifest: &Path) -> Option<String> {
let mut dir = Some(manifest);
let git = loop {
let d = dir?;
let g = d.join(".git");
if g.is_dir() {
break g;
}
if g.is_file() {
let t = fs::read_to_string(&g).ok()?;
let p = PathBuf::from(t.trim().strip_prefix("gitdir:")?.trim());
break if p.is_absolute() { p } else { d.join(p) };
}
dir = d.parent();
};
println!("cargo:rerun-if-changed={}", git.join("HEAD").display());
let head = fs::read_to_string(git.join("HEAD")).ok()?;
let head = head.trim();
let full = match head.strip_prefix("ref: ") {
None => head.to_string(),
Some(r) => {
// A worktree keeps its refs in the common dir.
let common = fs::read_to_string(git.join("commondir")).ok().map(|c| git.join(c.trim())).unwrap_or_else(|| git.clone());
println!("cargo:rerun-if-changed={}", common.join(r).display());
match fs::read_to_string(common.join(r)) {
Ok(v) => v.trim().to_string(),
Err(_) => fs::read_to_string(common.join("packed-refs")).ok()?.lines().find(|l| l.ends_with(&format!(" {r}"))).map(|l| l.split_whitespace().next().unwrap_or("").to_string())?,
}
}
};
(full.len() >= 7).then(|| full[..7].to_string())
}