feat(maestro): preflight pnpm + node at installer web step

The opt-in maestro-web step now checks for the frontend toolchain its
build.rs requires; missing tools prompt abort/skip (interactive) or skip
the refresh (non-interactive) instead of failing cargo with a raw panic.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-28 22:02:11 +00:00
co-authored by Claude Opus 4.8
parent c355f9bd2e
commit eeccc1ad5b
+75
View File
@@ -345,6 +345,39 @@ fn default_web_exec_start() -> Result<PathBuf, Box<dyn std::error::Error>> {
.ok_or_else(|| "could not resolve $HOME (needed for web ExecStart)".into())
}
/// What to do at the (opt-in) web step given whether pnpm + node are present.
#[derive(Debug, PartialEq, Eq)]
enum WebPrereq {
/// Toolchain present — install maestro-web normally.
Ok,
/// Missing and we can ask: prompt the operator to abort or skip.
PromptMissing,
/// Missing and we can't prompt (non-interactive refresh): warn + skip.
SkipMissing,
}
fn classify_web_prereq(toolchain_present: bool, interactive: bool) -> WebPrereq {
if toolchain_present {
WebPrereq::Ok
} else if interactive {
WebPrereq::PromptMissing
} else {
WebPrereq::SkipMissing
}
}
/// True iff both `pnpm` and `node` respond to `--version` on PATH. maestro-web's
/// build.rs needs them to build the embedded SPA bundle.
fn frontend_toolchain_present() -> bool {
["pnpm", "node"].iter().all(|tool| {
Command::new(tool)
.arg("--version")
.output()
.map(|o| o.status.success())
.unwrap_or(false)
})
}
/// Optional step: install `maestro-web` + its systemd user unit (NAP-249).
/// Returns `true` iff the binary was installed (with or without systemd).
fn handle_web(
@@ -390,6 +423,27 @@ fn handle_web(
return Ok(false);
}
// maestro-web's build.rs requires pnpm + node. Check before cargo install so
// a missing toolchain surfaces a clear choice instead of a raw build panic.
match classify_web_prereq(frontend_toolchain_present(), interactive) {
WebPrereq::Ok => {}
WebPrereq::PromptMissing => {
eprintln!("\nmaestro-web needs pnpm + node, which aren't installed.");
if confirm("Abort the whole install? (No = skip web UI)", false)? {
return Err("aborted: maestro-web prerequisites (pnpm + node) are missing".into());
}
eprintln!("Skipping maestro-web install.");
return Ok(false);
}
WebPrereq::SkipMissing => {
eprintln!(
"\nSkipping maestro-web refresh: pnpm + node not installed. \
Existing unit left in place."
);
return Ok(false);
}
}
// Install the binary. Even if cargo-install fails we keep going so the
// operator can fix the cause and re-run; the existing daemon work
// doesn't need to be undone.
@@ -514,3 +568,24 @@ fn print_summary(configured: bool, systemd_enabled: bool, web_installed: bool) {
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn prereq_ok_when_toolchain_present() {
assert_eq!(classify_web_prereq(true, true), WebPrereq::Ok);
assert_eq!(classify_web_prereq(true, false), WebPrereq::Ok);
}
#[test]
fn prereq_prompts_when_missing_and_interactive() {
assert_eq!(classify_web_prereq(false, true), WebPrereq::PromptMissing);
}
#[test]
fn prereq_skips_when_missing_and_noninteractive() {
assert_eq!(classify_web_prereq(false, false), WebPrereq::SkipMissing);
}
}