99 lines · 3.8 KB · TypeScript
RawDownload
// Shared helper for the `local` e2e project: boot a real temporary server with// `executor web --foreground` in a recorded terminal, parse its printed one-time// `?_token=` URL, and run a body against it. Each scenario boots its OWN server// (own throwaway data dir, `--port 0`) so files can run in parallel without// colliding. The terminal stays up until the body settles, then Ctrl-C gives a// graceful shutdown (so the vite child dies and the PTY closes).import { mkdtempSync, rmSync } from "node:fs";import { tmpdir } from "node:os";import { join } from "node:path";import { fileURLToPath } from "node:url"; import { Effect } from "effect"; import type { CliSurface } from "../src/surfaces/cli";import { markFocus, markRecordingStart } from "../src/timeline"; const repoRoot = fileURLToPath(new URL("../../", import.meta.url)); /** The `Open: …/?_token=<token>` URL the CLI prints once the server is up. */export const TOKEN_URL = /http:\/\/127\.0\.0\.1:\d+\/\?_token=[A-Za-z0-9_-]+/; export interface ServerHandle {  /** The full `?_token=` bootstrap URL (origin + token). */  readonly url: string;  /** The server origin, e.g. `http://127.0.0.1:54321`. */  readonly origin: string;  /** The bearer token (the `_token` query param), in plaintext. */  readonly token: string;} /** * Boot `executor web --foreground` and run `body` against the resulting * {@link ServerHandle}. Keeps the server up until the body settles, then Ctrl-C * for a graceful shutdown. Cleans up the throwaway data dir. The body may drive * the browser, a typed API client, an MCP client — anything that needs the live * server. */export const withLocalServer = (  cli: CliSurface,  runDir: string,  body: (server: ServerHandle) => Effect.Effect<void>,): Effect.Effect<void> =>  Effect.gen(function* () {    const dataDir = mkdtempSync(join(tmpdir(), "executor-local-e2e-"));     let publishUrl!: (url: string) => void;    const urlReady = new Promise<string>((res) => {      publishUrl = res;    });    let signalBodyDone!: () => void;    const bodyDone = new Promise<void>((res) => {      signalBodyDone = res;    });     yield* Effect.all(      [        cli.session(          ["bun", "run", "dev:cli", "web", "--foreground", "--port", "0"],          async (term) => {            markRecordingStart(runDir, "terminal");            markFocus(runDir, "terminal");            const snapshot = await term.screen.waitUntil(              (current) => TOKEN_URL.test(current.text),              { timeoutMs: 120_000 },            );            const url = TOKEN_URL.exec(snapshot.text)?.[0];            if (!url) {              throw new Error(                `executor web --foreground printed no ?_token URL:\n${snapshot.text.slice(-600)}`,              );            }            publishUrl(url);            await bodyDone;            // Graceful shutdown so the vite child is killed and the PTY closes;            // otherwise the orphaned child wedges the terminal teardown.            markFocus(runDir, "terminal");            await term.keyboard.press("Control+C");          },          {            cwd: repoRoot,            env: { EXECUTOR_DATA_DIR: dataDir, EXECUTOR_SCOPE_DIR: dataDir },            record: join(runDir, "terminal.cast"),            viewport: { cols: 120, rows: 40 },          },        ),         Effect.gen(function* () {          const url = yield* Effect.promise(() => urlReady);          const parsed = new URL(url);          yield* body({            url,            origin: parsed.origin,            token: parsed.searchParams.get("_token")!,          }).pipe(Effect.ensuring(Effect.sync(() => signalBodyDone())));        }),      ],      { concurrency: "unbounded" },    ).pipe(Effect.ensuring(Effect.sync(() => rmSync(dataDir, { recursive: true, force: true }))));  });