dragoman 0.5.13

Server for scholarly metadata in commonmeta format stored in a SQLite database.
fn main() {
    build_ui();
    embed_assets();
}

fn build_ui() {
    println!("cargo:rerun-if-changed=src/");
    println!("cargo:rerun-if-changed=ui/src/");
    println!("cargo:rerun-if-changed=ui/index.html");
    println!("cargo:rerun-if-changed=ui/package.json");
    println!("cargo:rerun-if-changed=ui/vite.config.js");
    println!("cargo:rerun-if-changed=ui/svelte.config.js");

    // pnpm may live under Homebrew on macOS; prepend common prefix to PATH.
    let path = std::env::var("PATH").unwrap_or_default();
    let path = format!("/opt/homebrew/bin:/usr/local/bin:{path}");

    // During `cargo publish` verification the package is extracted to
    // target/package/<name>/ and build.rs is re-run there. pnpm would write
    // node_modules into that directory (outside OUT_DIR), failing the check.
    // The dist is already in the tarball, so we can safely skip the build.
    let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_default();
    if manifest_dir.contains("/target/package/") {
        return;
    }

    // If pnpm is unavailable (e.g. crates.io build environment), accept a
    // pre-built dist rather than failing.
    let pnpm_found = std::process::Command::new("pnpm")
        .arg("--version")
        .env("PATH", &path)
        .output()
        .map(|o| o.status.success())
        .unwrap_or(false);

    if !pnpm_found {
        if !std::path::Path::new("ui/dist/index.html").exists() {
            panic!(
                "pnpm not found and ui/dist/index.html is missing; \
                 run: pnpm --dir ui install && pnpm --dir ui build"
            );
        }
        return;
    }

    let installed = std::process::Command::new("pnpm")
        .args(["--dir", "ui", "install", "--frozen-lockfile"])
        .env("PATH", &path)
        .status()
        .map(|s| s.success())
        .unwrap_or(false);

    let built = installed
        && std::process::Command::new("pnpm")
            .args(["--dir", "ui", "build"])
            .env("PATH", &path)
            .status()
            .map(|s| s.success())
            .unwrap_or(false);

    if !built {
        panic!(
            "UI build failed; run manually: \
             pnpm --dir ui install && pnpm --dir ui build"
        );
    }
}

/// Write `$OUT_DIR/ui_assets.rs`: every file of `ui/dist/assets` as
/// `(name, bytes)`, for the server to serve under `/assets/`. Vite names each by
/// its content hash, so a new build adds names rather than changing what an old
/// name holds.
fn embed_assets() {
    let dir =
        std::path::Path::new(&std::env::var("CARGO_MANIFEST_DIR").unwrap()).join("ui/dist/assets");
    let mut names: Vec<String> = std::fs::read_dir(&dir)
        .unwrap_or_else(|e| panic!("reading {}: {e}; build the UI first", dir.display()))
        .filter_map(|entry| entry.ok())
        .filter(|entry| entry.path().is_file())
        .map(|entry| entry.file_name().to_string_lossy().into_owned())
        .collect();
    names.sort();
    let mut out = String::from("pub static UI_ASSETS: &[(&str, &[u8])] = &[\n");
    for name in &names {
        out.push_str(&format!(
            "    ({name:?}, include_bytes!({:?})),\n",
            dir.join(name).display().to_string()
        ));
    }
    out.push_str("];\n");
    let dest = std::path::Path::new(&std::env::var("OUT_DIR").unwrap()).join("ui_assets.rs");
    std::fs::write(&dest, out).unwrap_or_else(|e| panic!("writing {}: {e}", dest.display()));
}