Watch the embedded web/dist output (not just frontend inputs) so a rebuilt bundle is actually re-embedded; assert pnpm build produced web/dist/index.html; strengthen the embed test to require a vite-built hashed asset reference. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
92 lines
3.1 KiB
Rust
92 lines
3.1 KiB
Rust
//! Build the embedded SPA bundle before the crate compiles.
|
|
//!
|
|
//! `static_bundle.rs` embeds `web/dist` at compile time via `include_dir!`.
|
|
//! This script guarantees that directory holds a freshly built bundle by
|
|
//! running `pnpm install --frozen-lockfile && pnpm build` in `web/`. pnpm and
|
|
//! node are HARD requirements — a missing toolchain fails the build with an
|
|
//! actionable message rather than silently shipping the placeholder.
|
|
|
|
use std::env;
|
|
use std::path::PathBuf;
|
|
use std::process::Command;
|
|
|
|
fn main() {
|
|
let manifest_dir =
|
|
PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR set by cargo"));
|
|
let web_dir = manifest_dir.join("../../web");
|
|
|
|
// Rebuild the bundle only when a frontend input changes — not on every
|
|
// unrelated Rust recompile.
|
|
for rel in [
|
|
"src",
|
|
"index.html",
|
|
"package.json",
|
|
"pnpm-lock.yaml",
|
|
"vite.config.ts",
|
|
] {
|
|
println!("cargo:rerun-if-changed={}", web_dir.join(rel).display());
|
|
}
|
|
|
|
// Also watch the embedded OUTPUT directory. The input watches above decide
|
|
// whether THIS script re-runs and regenerates `web/dist`; but `include_dir!`
|
|
// in `static_bundle.rs` reads `web/dist`, and cargo has no other declared
|
|
// dependency on those output files. Without this watch, an incremental build
|
|
// could regenerate `web/dist` yet keep the OLD bundle embedded. Tying the
|
|
// crate's recompilation to the regenerated output makes the fresh assets
|
|
// actually get re-embedded.
|
|
let dist_dir = web_dir.join("dist");
|
|
println!("cargo:rerun-if-changed={}", dist_dir.display());
|
|
|
|
require_tool("pnpm");
|
|
require_tool("node");
|
|
|
|
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",
|
|
);
|
|
|
|
// Sanity-check that pnpm build actually produced the bundle `include_dir!`
|
|
// expects. A green build that embeds an empty/placeholder dir is worse than
|
|
// a loud failure here.
|
|
let index_html = dist_dir.join("index.html");
|
|
let produced = std::fs::metadata(&index_html)
|
|
.map(|m| m.is_file() && m.len() > 0)
|
|
.unwrap_or(false);
|
|
if !produced {
|
|
panic!(
|
|
"`pnpm build` did not produce the expected bundle at {} \
|
|
(missing or empty) — check the vite `outDir`.",
|
|
index_html.display()
|
|
);
|
|
}
|
|
}
|
|
|
|
fn require_tool(tool: &str) {
|
|
let ok = Command::new(tool)
|
|
.arg("--version")
|
|
.output()
|
|
.map(|o| o.status.success())
|
|
.unwrap_or(false);
|
|
if !ok {
|
|
panic!(
|
|
"maestro-web requires `{tool}` to build its embedded SPA bundle. \
|
|
Install pnpm + node (https://pnpm.io) or use a prebuilt binary."
|
|
);
|
|
}
|
|
}
|
|
|
|
fn run(cmd: &mut Command, label: &str) {
|
|
let status = cmd
|
|
.status()
|
|
.unwrap_or_else(|e| panic!("failed to spawn `{label}`: {e}"));
|
|
if !status.success() {
|
|
panic!("`{label}` failed with {status}");
|
|
}
|
|
}
|