Files
Kokorone/scripts/smoke/run.mjs
T
jiantw83andClaude Sonnet 5 1bea06e3cd feat(遊戲化): 新增大廳、附身、時間流、後日談進度等遊戲化系統功能
新增角色大廳(lobby)邀約與通話、附身(possession)、時鐘(clock)抽象、
時間流與睡眠負債、後日談進度、行為選擇與旁觀反應等模組,並補齊對應冒煙
測試(R~V)與單元測試;同步調整 web/mobile 對應頁面與元件。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-20 09:10:26 +08:00

77 lines
2.9 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
import "dotenv/config";
import path from "node:path";
import { readdir } from "node:fs/promises";
import { existsSync } from "node:fs";
import { fileURLToPath, pathToFileURL } from "node:url";
import { log } from "../../packages/shared/dist/index.js";
const SMOKE_DIR = path.dirname(fileURLToPath(import.meta.url));
async function listGroupFiles() {
const files = await readdir(SMOKE_DIR);
return files.filter((f) => f.endsWith(".mjs") && f !== "run.mjs").sort();
}
// 連續跑多個群組時,偶爾會在某個群組剛結束、下一個群組第一個請求時遇到暫時性的連線錯誤
// (observed:長時間跑(如 T 群組的重試/全域暫停測試)結束後,緊接著的下一個群組第一個 fetch
// 偶發 "fetch failed"/連線被對方關閉——與程式邏輯無關,是連線在群組交接瞬間的暫時性狀態)。
// 每個群組檔案的既有慣例是「一進來就先 cleanup() 清掉自己上一輪可能留下的資料」,因此整個
// default() 重跑一次是安全、可重複的,不會因為半途失敗留下的殘留資料造成誤判。
const TRANSIENT_ERROR_PATTERN = /fetch failed|ECONNRESET|ECONNREFUSED|socket|other side closed|network/i;
async function runOne(file, attempt = 1) {
const group = file.replace(/\.mjs$/, "");
const modUrl = pathToFileURL(path.join(SMOKE_DIR, file)).href;
log("冒煙測試", "INF", `開始執行 ${group} 群組冒煙測試${attempt > 1 ? `(第 ${attempt} 次嘗試)` : ""}`);
try {
const mod = await import(modUrl);
if (typeof mod.default !== "function") {
throw new Error(`${file} 未匯出預設測試函式`);
}
await mod.default();
log("冒煙測試", "INF", `${group} 群組冒煙測試全綠`);
return true;
} catch (err) {
log("冒煙測試", "ERR", `${group} 群組冒煙測試失敗:${err.message}`);
if (attempt === 1 && TRANSIENT_ERROR_PATTERN.test(err.message)) {
log("冒煙測試", "WRN", `${group} 疑似暫時性連線錯誤,重試一次`);
return runOne(file, attempt + 1);
}
return false;
}
}
async function main() {
const arg = process.argv[2];
if (!arg) {
log("冒煙測試", "ERR", "請指定群組代號,例如:npm run smoke -- A(或 all 執行全部群組)");
process.exit(1);
}
let files;
if (arg === "all") {
files = await listGroupFiles();
if (files.length === 0) {
log("冒煙測試", "WRN", "尚無任何冒煙測試檔案");
}
} else {
const file = `${arg}.mjs`;
if (!existsSync(path.join(SMOKE_DIR, file))) {
log("冒煙測試", "ERR", `找不到群組 ${arg} 的冒煙測試檔案(scripts/smoke/${file})`);
process.exit(1);
}
files = [file];
}
let allOk = true;
for (const file of files) {
const ok = await runOne(file);
allOk = allOk && ok;
}
process.exit(allOk ? 0 : 1);
}
main();