128 lines
4.4 KiB
Rust
128 lines
4.4 KiB
Rust
//! Resolve the SPA bundle embedded by `web.rs` via `include_dir!`.
|
|
//!
|
|
//! Three modes, in order:
|
|
//!
|
|
//! * `ARR_WEB_DIST` set — a prebuilt bundle (Nix, CI, release pipeline) is
|
|
//! embedded as-is. Missing or empty means a broken pipeline: fail.
|
|
//! * pnpm available — the default developer path: build `web/` and embed
|
|
//! `web/dist`. Failures in the build itself fail loudly.
|
|
//! * pnpm missing, debug profile — embed a generated placeholder page and
|
|
//! warn. This keeps `cargo clippy`/`cargo test` working on runners with no
|
|
//! node toolchain (the CI rust job, DESIGN.md §12).
|
|
//!
|
|
//! A release build never gets the placeholder: shipping a binary whose UI is
|
|
//! an apology defeats the single-binary acceptance test, so that combination
|
|
//! panics instead.
|
|
|
|
use std::env;
|
|
use std::path::PathBuf;
|
|
use std::process::Command;
|
|
|
|
fn main() {
|
|
println!("cargo:rerun-if-env-changed=ARR_WEB_DIST");
|
|
|
|
let dist = match env::var_os("ARR_WEB_DIST") {
|
|
Some(prebuilt) => PathBuf::from(prebuilt),
|
|
None => build_or_placeholder(),
|
|
};
|
|
|
|
let index = dist.join("index.html");
|
|
let bundled = std::fs::metadata(&index).is_ok_and(|m| m.is_file() && m.len() > 0);
|
|
assert!(
|
|
bundled,
|
|
"no SPA bundle at {} (missing or empty index.html) — check the vite \
|
|
outDir or the ARR_WEB_DIST override",
|
|
index.display()
|
|
);
|
|
|
|
// `include_dir!` expands `$VAR` against the env rustc is invoked with,
|
|
// which is what `cargo:rustc-env` sets.
|
|
println!("cargo:rustc-env=ARR_WEB_BUNDLE_DIR={}", dist.display());
|
|
}
|
|
|
|
fn build_or_placeholder() -> PathBuf {
|
|
let manifest_dir =
|
|
PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("cargo sets CARGO_MANIFEST_DIR"));
|
|
let web_dir = manifest_dir.join("../../web");
|
|
|
|
// Rebuild only when a frontend input changes, not on unrelated Rust
|
|
// recompiles. The dist watch matters too: the inputs decide whether this
|
|
// script reruns, but `include_dir!` reads dist, and without this watch an
|
|
// incremental build can regenerate dist yet keep the old bytes embedded.
|
|
for rel in [
|
|
"src",
|
|
"index.html",
|
|
"package.json",
|
|
"pnpm-lock.yaml",
|
|
"pnpm-workspace.yaml",
|
|
"vite.config.ts",
|
|
"tsconfig.json",
|
|
"dist",
|
|
] {
|
|
println!("cargo:rerun-if-changed={}", web_dir.join(rel).display());
|
|
}
|
|
|
|
if !tool_works("pnpm") {
|
|
let profile = env::var("PROFILE").unwrap_or_default();
|
|
assert!(
|
|
profile != "release",
|
|
"arr embeds the web SPA at build time and needs pnpm + node for a \
|
|
release build. Install them, or point ARR_WEB_DIST at a prebuilt \
|
|
bundle."
|
|
);
|
|
println!(
|
|
"cargo:warning=pnpm not found; embedding a placeholder page instead of \
|
|
the web SPA (debug builds only)"
|
|
);
|
|
return write_placeholder();
|
|
}
|
|
|
|
run(
|
|
Command::new("pnpm")
|
|
.args(["install", "--frozen-lockfile"])
|
|
.current_dir(&web_dir),
|
|
"pnpm install --frozen-lockfile",
|
|
);
|
|
run(
|
|
Command::new("pnpm").arg("build").current_dir(&web_dir),
|
|
"pnpm build",
|
|
);
|
|
|
|
web_dir.join("dist")
|
|
}
|
|
|
|
fn write_placeholder() -> PathBuf {
|
|
let out = PathBuf::from(env::var("OUT_DIR").expect("cargo sets OUT_DIR")).join("placeholder");
|
|
std::fs::create_dir_all(&out).expect("create placeholder dir");
|
|
std::fs::write(out.join("index.html"), PLACEHOLDER_HTML).expect("write placeholder");
|
|
out
|
|
}
|
|
|
|
fn tool_works(tool: &str) -> bool {
|
|
Command::new(tool)
|
|
.arg("--version")
|
|
.output()
|
|
.is_ok_and(|o| o.status.success())
|
|
}
|
|
|
|
fn run(cmd: &mut Command, label: &str) {
|
|
let status = cmd
|
|
.status()
|
|
.unwrap_or_else(|e| panic!("failed to spawn `{label}`: {e}"));
|
|
assert!(status.success(), "`{label}` failed with {status}");
|
|
}
|
|
|
|
const PLACEHOLDER_HTML: &str = r#"<!doctype html>
|
|
<meta charset="utf-8">
|
|
<title>arr — web bundle not built</title>
|
|
<style>
|
|
body { font-family: ui-sans-serif, system-ui, sans-serif; max-width: 40em;
|
|
margin: 4em auto; padding: 0 1em; line-height: 1.5; }
|
|
code { background: #eee; padding: 0.1em 0.3em; border-radius: 3px; }
|
|
</style>
|
|
<h1>arr is running</h1>
|
|
<p>but this binary was built without the web bundle (no pnpm on the build
|
|
machine). Rebuild with pnpm + node installed, or set <code>ARR_WEB_DIST</code>
|
|
to a prebuilt bundle.</p>
|
|
"#;
|