From fae9a004f764337da48247f55b4ebf10562ac1af Mon Sep 17 00:00:00 2001 From: t Date: Mon, 1 Jun 2026 22:42:23 +0530 Subject: [PATCH] Make the root prepare script not fail npm install in a build context The prepare runs `node scripts/git-worktree-safe.mjs`. In a Docker / Nixpacks build, npm install runs after COPY package.json but before the repo is fully copied, so scripts/ is not in the image yet: node exits 1 (module not found) and the build fails. This broke all four deployed services (#189 introduced the unguarded prepare; the prior one ended in `|| true`). Restore the guard: `node scripts/git-worktree-safe.mjs 2>/dev/null || true`, so a missing script (or any git error) is a no-op and never fails the install. The dev-machine self-heal still runs when the script is present. Regression test runs the actual prepare command in a scriptless temp dir and asserts exit 0. --- package.json | 2 +- test/repo-health/prepare-build-safe.test.mjs | 38 ++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 test/repo-health/prepare-build-safe.test.mjs diff --git a/package.json b/package.json index 5cced9516..eda363313 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "node": ">=24.0.0" }, "scripts": { - "prepare": "node scripts/git-worktree-safe.mjs", + "prepare": "node scripts/git-worktree-safe.mjs 2>/dev/null || true", "fix:git": "node scripts/git-worktree-safe.mjs", "check:git": "node scripts/git-worktree-safe.mjs --check", "dev": "node scripts/dev-all.js", diff --git a/test/repo-health/prepare-build-safe.test.mjs b/test/repo-health/prepare-build-safe.test.mjs new file mode 100644 index 000000000..b7c0d86cd --- /dev/null +++ b/test/repo-health/prepare-build-safe.test.mjs @@ -0,0 +1,38 @@ +// Regression guard: the root `prepare` script must NOT fail `npm install` when +// scripts/git-worktree-safe.mjs is absent. +// +// In a Docker / Nixpacks build, `npm install` runs after `COPY package.json` +// but before the rest of the repo is copied, so `scripts/` is not in the image +// yet. A bare `node scripts/git-worktree-safe.mjs` then exits 1 (module not +// found) and fails the build. This broke all four deployed services. The +// prepare command must tolerate the script being absent (exit 0). + +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { execSync } from 'node:child_process'; +import { mkdtempSync, rmSync, readFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..'); + +test('root prepare script tolerates a missing scripts/ dir (build context)', () => { + const prepare = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf8')).scripts.prepare; + assert.ok(prepare, 'root package.json defines a prepare script'); + + // Run the EXACT prepare command in a temp dir that does NOT contain + // scripts/git-worktree-safe.mjs, the way a Docker build runs it before the + // repo is fully copied. It must exit 0 so `npm install` does not fail. + const dir = mkdtempSync(join(tmpdir(), 'webjs-prepare-')); + try { + execSync(prepare, { cwd: dir, stdio: 'ignore' }); // throws on non-zero exit + } catch (err) { + assert.fail( + `prepare command "${prepare}" failed when scripts/ is absent (exit ${err.status}); ` + + `it must end in a guard like "|| true" so a build context cannot break npm install`, + ); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +});