Ships `maestro`, `maestro-web` and the SPA bundle as flake packages, plus a `services.maestro` Home Manager module that runs the daemon and the web UI as systemd user services. `maestro-web`'s build.rs now honours MAESTRO_WEB_DIST and hands the resolved bundle path to `include_dir!` through MAESTRO_WEB_BUNDLE_DIR. Without it the Nix build would have to run `pnpm install` mid-compile, which the sandbox has no network for. Unset, the pnpm path is unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
117 lines
3.9 KiB
Rust
117 lines
3.9 KiB
Rust
//! Resolve the SPA bundle that gets embedded at compile time.
|
|
//!
|
|
//! `static_bundle.rs` embeds a directory via `include_dir!`, and the directory
|
|
//! it reads is whatever this script puts in `MAESTRO_WEB_BUNDLE_DIR`.
|
|
//!
|
|
//! Two modes:
|
|
//!
|
|
//! * `MAESTRO_WEB_DIST` set — the bundle was built elsewhere (Nix, CI, a
|
|
//! release pipeline) and that directory is embedded as-is. No node, no pnpm.
|
|
//! * unset — the default developer path: run `pnpm install --frozen-lockfile
|
|
//! && pnpm build` in `web/` and embed `web/dist`. pnpm and node are HARD
|
|
//! requirements here; a missing toolchain fails with an actionable message
|
|
//! rather than silently shipping the placeholder.
|
|
|
|
use std::env;
|
|
use std::path::PathBuf;
|
|
use std::process::Command;
|
|
|
|
fn main() {
|
|
println!("cargo:rerun-if-env-changed=MAESTRO_WEB_DIST");
|
|
|
|
let dist_dir = match env::var_os("MAESTRO_WEB_DIST") {
|
|
Some(prebuilt) => PathBuf::from(prebuilt),
|
|
None => build_with_pnpm(),
|
|
};
|
|
|
|
// Sanity-check that the directory really holds a bundle. 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!(
|
|
"no SPA bundle at {} (missing or empty index.html) — \
|
|
check the vite `outDir`, or the MAESTRO_WEB_DIST override.",
|
|
index_html.display()
|
|
);
|
|
}
|
|
|
|
// `include_dir!` expands `$VAR` in its path argument against the env rustc
|
|
// is invoked with, which is what `cargo:rustc-env` sets.
|
|
println!(
|
|
"cargo:rustc-env=MAESTRO_WEB_BUNDLE_DIR={}",
|
|
dist_dir.display()
|
|
);
|
|
}
|
|
|
|
/// The default path: build `web/` in-tree with pnpm and return `web/dist`.
|
|
fn build_with_pnpm() -> PathBuf {
|
|
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 that directory, 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",
|
|
);
|
|
|
|
dist_dir
|
|
}
|
|
|
|
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 point MAESTRO_WEB_DIST \
|
|
at a prebuilt bundle."
|
|
);
|
|
}
|
|
}
|
|
|
|
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}");
|
|
}
|
|
}
|