// Run one guest on the fixture page and print its stdio as it arrives, for debugging a // guest that hangs. Needs `npm run serve`. Usage: // node scripts/probe-guest.mjs [stdin text] [--keep-open] [--timeout=ms] // node scripts/probe-guest.mjs --pty=80x24 [--keys='["a","\\r"]'] [--timeout=ms] // With --pty the guest runs on a terminal; each of --keys (JSON) is typed 100 ms apart, and // stdout is printed JSON-quoted so control sequences show. import { readFileSync } from "node:fs"; import { basename } from "node:path"; import { chromium } from "@playwright/test"; const args = process.argv.slice(2); const file = args[0]; const stdin = args[1] && !args[1].startsWith("--") ? args[1] : ""; const keepOpen = args.includes("--keep-open"); const timeout = Number(args.find((a) => a.startsWith("--timeout="))?.slice(10) ?? 5000); const repeat = Number(args.find((a) => a.startsWith("--repeat-stdin="))?.slice(15) ?? 1); const ptyArg = args.find((a) => a.startsWith("--pty="))?.slice(6); const pty = ptyArg ? { cols: Number(ptyArg.split("x")[0]), rows: Number(ptyArg.split("x")[1]) } : null; const keys = JSON.parse(args.find((a) => a.startsWith("--keys="))?.slice(7) ?? "[]"); const browser = await chromium.launch(); const page = await browser.newPage(); page.on("console", (m) => console.log(`[page:${m.type()}]`, m.text())); page.on("pageerror", (e) => console.log("[pageerror]", e.message)); page.on("exposebinding", () => {}); await page.exposeFunction("report", (stream, text) => process.stdout.write(`[${stream}] ${text}`)); await page.goto("http://localhost:8931/demo/fixture.html"); await page.waitForFunction(() => window.__runtime !== undefined, undefined, { timeout: 20_000 }); const name = `/app/${basename(file)}`; const started = Date.now(); console.log("page ready"); const result = await Promise.race([ page.evaluate(async ({ name, source, stdin, keepOpen, pty, keys }) => { const runtime = window.__runtime; runtime.writeTree({ [name]: source }); if (pty) { const proc = runtime.exec(name, { pty }); const dec = new TextDecoder(); void proc.stdout.pipeTo(new WritableStream({ write(c) { window.report("pty", `${JSON.stringify(dec.decode(c))}\n`); } })); void (async () => { for (const key of keys) { await new Promise((r) => setTimeout(r, 100)); proc.pty.write(key); } })(); return { code: await proc.exited, signal: await proc.signal }; } const proc = runtime.exec(name); const dec = new TextDecoder(); void proc.stdout.pipeTo(new WritableStream({ write(c) { window.report("stdout", dec.decode(c)); } })); void proc.stderr.pipeTo(new WritableStream({ write(c) { window.report("stderr", dec.decode(c)); } })); const writer = proc.stdin.getWriter(); if (stdin) void writer.write(new TextEncoder().encode(stdin)); if (!keepOpen) void writer.close(); return { code: await proc.exited, signal: await proc.signal }; }, { name, source: readFileSync(file, "utf8"), stdin: stdin.repeat(repeat), keepOpen, pty, keys }), new Promise((r) => setTimeout(() => r("timeout"), timeout)), ]); console.log("\nresult", JSON.stringify(result), `after ${Date.now() - started} ms`); await browser.close(); console.log(`closed after ${Date.now() - started} ms`);