Skip to main content

max / makenotwork

server: self-install frontend deps in build.rs (npm ci on fresh checkout) The frontend build soft-skipped when node_modules was absent, so a fresh checkout or new build host would silently ship static/dist empty — no dispatcher, no islands. Run `npm ci` (clean, from the committed lockfile) when node_modules is missing, then build. Skipped once installed; a Node-less host still soft-skips. Removes the manual `npm install` gate before a deploy. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-07-08 16:55 UTC
Commit: 8aec19f57fb83fd2446c2ec41ff49a6991e7f91c
Parent: bdd88fb
1 file changed, +26 insertions, -13 deletions
M server/build.rs +26 -13
@@ -87,30 +87,43 @@ fn write_if_changed(path: &Path, contents: &str) {
87 87 /// Compile the TypeScript frontend (`frontend/`) to browser ESM in
88 88 /// `static/dist/` via `npm run build` (which runs `tsc`).
89 89 ///
90 - /// Best-effort and non-fatal: a missing `node_modules` (deps not installed),
91 - /// an absent Node, or a compile error only emits a `cargo::warning` and leaves
92 - /// the Rust build to succeed against whatever `static/dist/` already holds.
93 - /// Dependency install (`npm install` in `frontend/`) is a manual/deploy step,
94 - /// not build.rs's job. Set `MNW_SKIP_FRONTEND_BUILD=1` to opt out entirely.
95 - ///
96 - /// Prereq for the deploy pipeline once a template references `static/dist/`:
97 - /// the build host must have Node and have run `npm install` in `frontend/`.
90 + /// Self-contained: on a fresh checkout or a new build host (no `node_modules`)
91 + /// it runs `npm ci` first, so there is no manual install gate before a deploy.
92 + /// Best-effort and non-fatal otherwise — an absent Node or a compile error only
93 + /// emits a `cargo::warning` and leaves the Rust build to succeed against
94 + /// whatever `static/dist/` already holds. Set `MNW_SKIP_FRONTEND_BUILD=1` to
95 + /// opt out entirely (e.g. a Node-less CI that doesn't need the JS).
98 96 fn build_frontend() {
99 97 // Re-run the whole build script when the TS sources or its config change.
100 98 println!("cargo::rerun-if-changed=frontend/src");
101 99 println!("cargo::rerun-if-changed=frontend/package.json");
100 + println!("cargo::rerun-if-changed=frontend/package-lock.json");
102 101 println!("cargo::rerun-if-changed=frontend/tsconfig.json");
103 102
104 103 if std::env::var_os("MNW_SKIP_FRONTEND_BUILD").is_some() {
105 104 println!("cargo::warning=frontend build skipped (MNW_SKIP_FRONTEND_BUILD set)");
106 105 return;
107 106 }
107 + // Fresh checkout / new build host: install deps once (clean, from the
108 + // lockfile) so the frontend build needs no manual `npm install` gate before
109 + // a deploy. Skipped once node_modules exists; needs network on this run.
108 110 if !Path::new("frontend/node_modules").is_dir() {
109 - println!(
110 - "cargo::warning=frontend/node_modules missing — run `npm install` in server/frontend; \
111 - skipping TypeScript build (serving existing static/dist)"
112 - );
113 - return;
111 + match Command::new("npm").args(["ci"]).current_dir("frontend").status() {
112 + Ok(s) if s.success() => {}
113 + Ok(s) => {
114 + println!(
115 + "cargo::warning=npm ci failed (exit {:?}); skipping frontend build (serving existing static/dist)",
116 + s.code()
117 + );
118 + return;
119 + }
120 + Err(e) => {
121 + println!(
122 + "cargo::warning=could not run npm ({e}); is Node installed? skipping frontend build (serving existing static/dist)"
123 + );
124 + return;
125 + }
126 + }
114 127 }
115 128 match Command::new("npm")
116 129 .args(["run", "build"])