chore(scripts): build test-servers unconditionally from one shared helper

Five `scripts/*.mjs` consumers each carried their own `ensureTestServer()`
that returned early when `test-servers/build` was already on disk. Presence
is not freshness: once that directory exists — which it does on any machine
that has run the smokes once — an edit to `test-servers/src` was never
picked up again, and the smoke went on driving the previously-built fixture.

The failure is silent and misattributed. It does not read as "your fixture
is stale"; it reads as a product bug in whatever the smoke is checking.

`scripts/lib/ensure-test-servers.mjs` is now the one place that build is
produced for a script consumer, and it builds unconditionally — memoized
once per process per repo root, so pack-and-verify's two asks stay one tsc
pass while a fresh run always rebuilds. Callers name the entries they
depend on and get one actionable failure message, replacing five that had
drifted. pack-and-verify also stops shelling out to `npx tsc`, which is
what made it the one script unrunnable on Windows (#1939).

`test-servers/tsconfig.json` gains `incremental` so the up-to-date case is
cheap: 3.0s cold, 1.6s incremental here.

Unconditional emit is still not a clean — `--noCheck` leaves the stale .js
of a deleted src file behind — which is unchanged from before and recorded
in the helper's header and AGENTS.md.

Closes #2111

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JTHVxSu8AUgHRLvo1ntZ8H
Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-08-25 00:57:48 -04:00
commit e59452ea8c

View file

@ -27,29 +27,28 @@
* length in smoke-web-browser.mjs.
*
* Expects `clients/web/dist` and `clients/launcher/build` to be built first.
* `test-servers/build` is built on demand if missing, as in smoke:web:app.
* `test-servers/build` is rebuilt on every run, as in smoke:web:app — see
* `scripts/lib/ensure-test-servers.mjs` for why presence isn't freshness.
*/
import { spawn, spawnSync } from "node:child_process";
import { spawn } from "node:child_process";
import { existsSync, mkdirSync } from "node:fs";
import { createRequire } from "node:module";
import { setTimeout as delay } from "node:timers/promises";
import { join, resolve } from "node:path";
import { startProdWebServer } from "./lib/prod-web-server.mjs";
import { stopChild } from "./lib/child-cleanup.mjs";
import { resolveNodeBin } from "./lib/resolve-node-bin.mjs";
import {
ensureTestServers,
testServerEntryPath,
} from "./lib/ensure-test-servers.mjs";
const repoRoot = resolve(import.meta.dirname, "..");
const requireFromWeb = createRequire(
resolve(repoRoot, "clients/web/package.json"),
);
const composableServer = join(
repoRoot,
"test-servers",
"build",
"server-composable.js",
);
const composableServer = testServerEntryPath(repoRoot, "composable");
const configPath = (name) =>
join(repoRoot, "test-servers", "configs", `${name}.json`);
@ -97,30 +96,6 @@ async function fail(message) {
process.exit(1);
}
/** Build the composable test server bundle if it isn't present yet. */
function ensureTestServer() {
if (existsSync(composableServer)) return;
console.log(
"smoke:web:elicit — building test-servers (missing build output)...",
);
const r = spawnSync(
process.execPath,
[
resolveNodeBin("typescript", "tsc", repoRoot),
"-p",
"test-servers",
"--noCheck",
],
{ cwd: repoRoot, stdio: "inherit" },
);
if (r.status !== 0 || !existsSync(composableServer)) {
throw new Error(
"could not build the test servers (test-servers/build/server-composable.js). " +
"Run `npm run test-servers:build` from clients/web.",
);
}
}
/**
* Spawn a composable test server and wait for the URL it announces.
*
@ -229,7 +204,12 @@ async function runTool(page) {
}
try {
ensureTestServer();
// Rebuilt on every run — presence is not freshness (#2111).
ensureTestServers({
repoRoot,
label: "smoke:web:elicit",
requires: ["composable"],
});
const appUrl = await startMcpServer("app-elicitation-http");
const nativeUrl = await startMcpServer("app-elicitation-native-http");
await web.waitForReady();