chore: three small cleanups from the v2.2.0 merge review

1. clients/web/.npmignore's explanatory comment still said the root "files"
   allowlist restricts publishing to build/ and dist/ and that everything else
   under clients/web "stays out regardless". #1934 added clients/web/static to
   that allowlist, so the claim was false — in a file that exists precisely
   because a packaging contract was misread once. Name static/ and say why it
   ships (read from disk at runtime by sandbox-controller.ts, at a path
   relative to the runner dir).

2. Replace the four unjustified `as unknown as Response` doubles in
   useServers.test.tsx with real Responses over real ReadableStreams: a
   null-body Response for the `!res.body` guard, a stream whose pull throws for
   the reader-rejects case, and pull-driven streams for the multi-frame and
   priming-comment cases (pull runs once per read, so the counting and the
   blocking second read behave exactly as the hand-rolled reader doubles did).
   The doubles are now structurally type-checked against the Response API.

3. scripts/smoke-web-app.mjs orphaned the MCP test server on a readiness
   timeout: startMcpServer() returned the child only once the announcement
   line matched, so a child that was alive but never announced left `mcpServer`
   unassigned, shutdown() could not stop it, and process.exit(1) left it
   holding its port. Publish the child to `mcpServer` the moment it is spawned,
   so every throw path is covered by teardown.

Closes #2000

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hzwzS8UvBsr4yZm7o7sev
Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-08-23 12:02:55 -04:00
commit 031991cc90

View file

@ -169,6 +169,14 @@ function ensureTestServer() {
* this times out with an empty diagnostic. Piping both also keeps the child's
* noise out of the smoke's own output while still making it available in the
* failure message.
*
* The child is published to `mcpServer` the moment it is spawned rather than
* handed back on success, so `shutdown()` can stop it on every throw path.
* Returning it only after the announcement matched left the readiness timeout
* — a child that is alive but never announces — with no reachable handle, so
* `process.exit(1)` orphaned a live server still holding its port. The
* `spawnError` and `exited` paths never had that problem (the child is already
* gone there), but they cost nothing to cover the same way.
*/
async function startMcpServer() {
const child = spawn(
@ -176,6 +184,7 @@ async function startMcpServer() {
[composableServer, "--config", appConfig],
{ cwd: repoRoot, stdio: ["ignore", "pipe", "pipe"] },
);
mcpServer = child;
let out = "";
child.stdout.on("data", (d) => (out += d));
child.stderr.on("data", (d) => (out += d));
@ -193,7 +202,7 @@ async function startMcpServer() {
for (let attempt = 0; attempt < 120; attempt++) {
// Take the port the server actually bound, not the one we asked for.
const announced = out.match(/listening at (http:\/\/\S+)/i);
if (announced) return { child, url: announced[1] };
if (announced) return { url: announced[1] };
if (spawnError) {
throw new Error(
`could not spawn the MCP test server (${composableServer}): ${spawnError.message}`,
@ -247,7 +256,9 @@ try {
}
ensureTestServer();
({ child: mcpServer, url: mcpUrl } = await startMcpServer());
// `startMcpServer` publishes the child to `mcpServer` itself, so teardown
// reaches it even when this throws before returning.
({ url: mcpUrl } = await startMcpServer());
await server.waitForReady();
browser = await loadChromium();
const page = await browser.newPage();