From eeccc1ad5b27bad6f8d41b6e5fc2276bd7a6d9ae Mon Sep 17 00:00:00 2001 From: naps62-yolo Date: Thu, 28 May 2026 19:56:21 +0000 Subject: [PATCH] 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) --- crates/maestro-installer/src/main.rs | 75 ++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/crates/maestro-installer/src/main.rs b/crates/maestro-installer/src/main.rs index e9a8688..beff358 100644 --- a/crates/maestro-installer/src/main.rs +++ b/crates/maestro-installer/src/main.rs @@ -345,6 +345,39 @@ fn default_web_exec_start() -> Result> { .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); + } +}