// 一条命令跑全流程:起两个假上游 -> 逐个预设驱动真酒馆与本仓库 -> 出对比报告。 // 用法: node run-all.mjs [--only all|st|wa|diff] [--headful] [--preset 路径(可多次)] [--message 你好] import { spawn } from "node:child_process"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { waitFor } from "./cdp.mjs"; const HERE = path.dirname(fileURLToPath(import.meta.url)); const DEFAULT_PRESETS = [ "C:/Users/zed/Downloads/Reborn2.4.json", "C:/Users/zed/Downloads/linux_1.3.json", ]; function readArgs(argv) { const out = { only: "all", presets: [], message: "你好", stCapturePort: 8801, waCapturePort: 8802, stPort: 8123, headful: false, card: "tokens", out: path.join(HERE, "captures") }; for (let i = 0; i < argv.length; i++) { const k = argv[i]; const v = argv[i + 1]; if (k === "--only") { out.only = String(v); i++; } else if (k === "--preset") { out.presets.push(path.resolve(String(v))); i++; } else if (k === "--message") { out.message = String(v); i++; } else if (k === "--st-port") { out.stPort = Number(v); i++; } else if (k === "--card") { out.card = String(v); i++; } else if (k === "--headful") out.headful = true; } if (out.presets.length === 0) out.presets = DEFAULT_PRESETS.slice(); return out; } function run(script, args) { return new Promise((resolve) => { const child = spawn(process.execPath, [path.join(HERE, script), ...args], { cwd: HERE, stdio: ["ignore", "pipe", "pipe"] }); let out = ""; const pipe = (chunk) => { const text = String(chunk); out += text; process.stdout.write(text); }; child.stdout.on("data", pipe); child.stderr.on("data", pipe); child.on("close", (code) => resolve({ code, out })); }); } function lastJson(out) { const lines = out.split(/\r?\n/).filter((l) => l.trim().startsWith("{") && l.includes("\"capture\"")); if (!lines.length) return null; try { return JSON.parse(lines[lines.length - 1]); } catch (e) { return null; } } async function main() { const args = readArgs(process.argv.slice(2)); const wantSt = args.only === "all" || args.only === "st"; const wantWa = args.only === "all" || args.only === "wa"; const wantDiff = args.only === "all" || args.only === "diff"; const captures = []; const startCapture = (label, port) => { const child = spawn(process.execPath, [path.join(HERE, "capture-server.mjs"), "--label", label, "--port", String(port), "--out", args.out], { cwd: HERE, stdio: "inherit" }); captures.push(child); }; const waitMock = (port) => waitFor(async () => (await fetch("http://127.0.0.1:" + port + "/v1/models").catch(() => null)) !== null, { timeoutMs: 15000, label: "假上游 " + port }); try { if (wantSt || wantWa) { startCapture("st", args.stCapturePort); startCapture("wa", args.waCapturePort); await waitMock(args.stCapturePort); await waitMock(args.waCapturePort); } const pairs = []; for (const preset of args.presets) { const pair = { preset }; if (wantSt) { const r = await run("drive-st.mjs", ["--preset", preset, "--message", args.message, "--capture-port", String(args.stCapturePort), "--st-port", String(args.stPort), "--card", args.card, "--out", args.out, ...(args.headful ? ["--headful"] : [])]); if (r.code !== 0) throw new Error("驱动酒馆失败: " + preset); const meta = lastJson(r.out); if (meta) pair.st = meta.capture; } if (wantWa) { const r = await run("drive-wa.mjs", ["--preset", preset, "--message", args.message, "--capture-port", String(args.waCapturePort), "--out", args.out]); if (r.code !== 0) throw new Error("驱动本仓库失败: " + preset); const meta = lastJson(r.out); if (meta) pair.wa = meta.capture; } pairs.push(pair); } if (wantDiff) { for (const pair of pairs) { if (!pair.st || !pair.wa) continue; const r = await run("diff.mjs", ["--st", pair.st, "--wa", pair.wa, "--preset", pair.preset]); if (r.code !== 0) throw new Error("对比失败: " + pair.preset); } } } finally { for (const child of captures) { try { child.kill(); } catch (e) {} } } } await main();