Files
mnote/scripts/run-s2-s7-smokes.js
T
Agent Board b798f628ee chore: land tree view-state, vault, Pi module split, and repo hygiene
Persist PageTree expand state via control-plane view-state and align
chevron/DOM with restored expansion; keep Sidex-style shallow page-tree
scan and drop the unused recursive scanner that only added cargo noise.

Add password vault workbench routes/runtime/skill/CLI, split page_ai_pi
into a module package, and retire Hermes/ACP/OpenHub recycle + root
harness evidence from the index while gitignoring recycle and local
diag dumps.

Archive superseded design/bugs docs under old/, point architecture at
ARCHITECTURE.md, and refresh smokes for Pi S1–S7, vault, and editor
regressions so the working tree can stay clean.
2026-07-21 05:13:05 +08:00

184 lines
5.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
// Start mnote-web (mock Pi) on a free port and run S2S7 acceptance smokes.
// Does not touch unrelated workspace state. Prefer existing BASE if set.
"use strict";
const { spawn } = require("node:child_process");
const fs = require("node:fs");
const net = require("node:net");
const os = require("node:os");
const path = require("node:path");
const { pathToFileURL } = require("node:url");
const REPO = path.resolve(__dirname, "..");
const RUST_DIR = path.join(REPO, "rust");
const BINARY = path.join(RUST_DIR, "target/debug/mnote-web");
const TIMEOUT_MS = Number(process.env.MNOTE_S2_S7_TIMEOUT_MS || 120_000);
const EXTERNAL_BASE = (process.env.MNOTE_PI_LAB_BASE || process.env.MNOTE_S2_BASE || "").replace(
/\/+$/,
"",
);
const SMOKES = [
"task-s2-skills-mcp-delete-no-ghost-smoke.js",
"task-s3-directory-grants-allowed-roots-smoke.js",
"task-s4-pi-abort-idempotent-smoke.js",
"task-s5-rag-health-summary-filetree-smoke.js",
"task-s6-pi-resume-history-hydrate-smoke.js",
"task-s7-agent-target-dirty-guard-smoke.js",
];
function findFreePort() {
return new Promise((resolve, reject) => {
const server = net.createServer();
server.listen(0, "127.0.0.1", () => {
const address = server.address();
if (!address || typeof address === "string") {
server.close(() => reject(new Error("无法分配测试端口")));
return;
}
const port = address.port;
server.close(() => resolve(port));
});
server.on("error", reject);
});
}
async function waitForGateway(baseUrl) {
const deadline = Date.now() + TIMEOUT_MS;
let lastError = null;
while (Date.now() < deadline) {
try {
const response = await fetch(`${baseUrl}/api/gateway/health`, {
redirect: "manual",
cache: "no-store",
});
if (response.ok && response.headers.get("x-mnote-web-owner") === "mnote-web") {
return;
}
lastError = new Error(`gateway health: ${response.status}`);
} catch (error) {
lastError = error;
}
await new Promise((r) => setTimeout(r, 300));
}
throw lastError || new Error("gateway health 未就绪");
}
function startGateway(port, controlPlaneDb) {
const env = {
...process.env,
MNOTE_WEB_ALLOW_DEV_FIXTURES: "1",
MNOTE_WEB_BIND: `127.0.0.1:${port}`,
MNOTE_WEB_PUBLIC_BIND: `127.0.0.1:${port}`,
MNOTE_CONTROL_PLANE_BACKEND: "libsql-local",
MNOTE_TURSO_LOCAL_PATH: controlPlaneDb,
MNOTE_PAGE_AI_PI_LAB: "1",
MNOTE_PAGE_AI_PI_LAB_RUNTIME: "mock",
MNOTE_PAGE_AI_PI_WARMUP: "0",
MNOTE_PAGE_AI_PI_WARMUP_SEND: "0",
RUST_LOG: process.env.RUST_LOG || "warn",
};
const cmd = fs.existsSync(BINARY)
? [BINARY]
: ["cargo", "run", "-q", "-p", "mnote-web", "--bin", "mnote-web"];
const child = spawn(cmd[0], cmd.slice(1), {
cwd: RUST_DIR,
env,
stdio: ["ignore", "pipe", "pipe"],
});
let stderr = "";
child.stderr.on("data", (chunk) => {
stderr += chunk.toString();
if (stderr.length > 20_000) stderr = stderr.slice(-20_000);
});
child.stdout.on("data", () => {});
child._s2s7Stderr = () => stderr;
return child;
}
function runSmoke(script, base) {
return new Promise((resolve, reject) => {
const env = {
...process.env,
MNOTE_PI_LAB_BASE: base,
MNOTE_S2_BASE: base,
MNOTE_S3_BASE: base,
MNOTE_S4_BASE: base,
MNOTE_S5_BASE: base,
MNOTE_S6_BASE: base,
MNOTE_S7_BASE: base,
};
const child = spawn(process.execPath, [path.join(__dirname, script)], {
cwd: REPO,
env,
stdio: ["ignore", "pipe", "pipe"],
});
let out = "";
let err = "";
child.stdout.on("data", (c) => {
out += c.toString();
process.stdout.write(c);
});
child.stderr.on("data", (c) => {
err += c.toString();
process.stderr.write(c);
});
child.on("exit", (code) => {
if (code === 0) resolve({ script, out });
else reject(new Error(`${script} exited ${code}\n${err || out}`));
});
});
}
async function main() {
let child = null;
let base = EXTERNAL_BASE;
const tmpDbDir = fs.mkdtempSync(path.join(os.tmpdir(), "mnote-s2s7-cp-"));
const controlPlaneDb = path.join(tmpDbDir, "control-plane-s2s7.db");
try {
if (!base) {
const port = await findFreePort();
base = `http://127.0.0.1:${port}`;
console.log(`\n🚀 starting mnote-web mock on ${base}\n`);
child = startGateway(port, controlPlaneDb);
child.on("exit", (code, signal) => {
if (code && code !== 0) {
console.error(`mnote-web exited code=${code} signal=${signal}`);
console.error(child._s2s7Stderr && child._s2s7Stderr());
}
});
await waitForGateway(base);
console.log(" gateway ready\n");
} else {
console.log(`\n🔗 using external BASE ${base}\n`);
await waitForGateway(base);
}
const results = [];
for (const script of SMOKES) {
console.log(`── ${script} ──`);
await runSmoke(script, base);
results.push(script);
}
console.log(`\n✅ S2S7 all passed (${results.length}):\n ${results.join("\n ")}\n`);
} finally {
if (child && !child.killed) {
child.kill("SIGTERM");
await new Promise((r) => setTimeout(r, 500));
try {
child.kill("SIGKILL");
} catch {
/* ignore */
}
}
}
}
main().catch((err) => {
console.error("\n❌ S2S7 runner failed:", err.message || err);
process.exit(1);
});