/** * 验证 stdio 模式的标准输出只包含合法 JSON-RPC 消息,不混入诊断文本。 * * @packageDocumentation */ import assert from "node:assert/strict"; import { spawn } from "node:child_process"; import path from "node:path"; import test from "node:test"; import { fileURLToPath } from "node:url"; test("stdio protocol mode writes only JSON-RPC messages to stdout", async () => { const packageRoot = path.resolve(fileURLToPath(new URL("..", import.meta.url))); const child = spawn(process.execPath, ["--import", "tsx", "test/stdioFixture.ts"], { cwd: packageRoot, shell: false, windowsHide: true, stdio: ["pipe", "pipe", "pipe"], }); let stdout = ""; let stderr = ""; child.stdout.setEncoding("utf8").on("data", (chunk) => { stdout += String(chunk); }); child.stderr.setEncoding("utf8").on("data", (chunk) => { stderr += String(chunk); }); const messages = [ { jsonrpc: "2.0", id: 1, method: "initialize", params: { protocolVersion: "2025-11-25", capabilities: {}, clientInfo: { name: "stdio-test", version: "1.0.0" } } }, { jsonrpc: "2.0", method: "notifications/initialized" }, { jsonrpc: "2.0", id: 2, method: "tools/list", params: { cursor: "next" } }, ]; child.stdin.write(messages.map((message) => JSON.stringify(message)).join("\n") + "\n"); await waitFor(() => stdout.split(/\r?\n/).filter(Boolean).some((line) => { try { return (JSON.parse(line) as { id?: number }).id === 2; } catch { return false; } }), 3000); child.stdin.end(); await new Promise((resolve) => { const timeout = setTimeout(() => { child.kill(); resolve(); }, 1000); child.once("close", () => { clearTimeout(timeout); resolve(); }); }); const lines = stdout.split(/\r?\n/).filter(Boolean); assert.ok(lines.length >= 2); const parsed = lines.map((line) => JSON.parse(line) as { jsonrpc: string; id?: number; result?: unknown }); assert.ok(parsed.every((message) => message.jsonrpc === "2.0")); assert.ok(parsed.some((message) => message.id === 1)); assert.ok(parsed.some((message) => message.id === 2)); assert.equal(stderr, ""); }); async function waitFor(predicate: () => boolean, timeoutMs: number): Promise { const started = Date.now(); while (!predicate()) { if (Date.now() - started > timeoutMs) throw new Error("timed out waiting for stdio response"); await new Promise((resolve) => setTimeout(resolve, 10)); } }