Gunnar

nordisk/monetize

crates/monetize-admin/build.rs

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