fix(腳本契約): 長輸出不再被截斷,查重翻頁加上上限

process.stdout.write 之後立刻 process.exit 會截斷輸出——stdout 接到 pipe 時
寫入是非同步的。改為等 write 的 callback 回來再退出。實測舊寫法在約 83KB 處
被切斷,新增的回歸測試以 8000 筆標籤覆蓋這條路徑。

findIssueByTitle 的翻頁原本沒有上限,Gitea 若持續回滿一頁就會無限打下去。
加上 200 頁上限,超過即以 DEDUPE_LIMIT 報錯而非無聲回 null——無聲回 null 會
讓呼叫端把既有議題再建一次,正好是冪等查重要防的事。

parseFlags 取值時不再於三元運算式內遞增迴圈變數,改為獨立敘述。

測試工具的 maxBuffer 調高到 64MB:預設 1MB 會在長輸出時砍掉子行程,那是測試
工具的限制而非腳本的問題。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-17 04:26:51 +00:00
co-authored by Claude Opus 5
parent 28dec83498
commit 4ae097f1e1
4 changed files with 55 additions and 9 deletions
+20 -8
View File
@@ -72,7 +72,8 @@ export function parseFlags(argv, spec = {}) {
flags[name] = true;
continue;
}
const value = eq === -1 ? argv[(i += 1)] : arg.slice(eq + 1);
if (eq === -1) i += 1;
const value = eq === -1 ? argv[i] : arg.slice(eq + 1);
if (value === undefined || value.startsWith('--')) {
throw new ScriptError('MISSING_FLAG', `--${name} 需要一個值`);
}
@@ -110,18 +111,21 @@ export function parseRepo(value) {
export async function main(run) {
try {
const data = await run();
write({ ok: true, data });
process.exit(0);
write({ ok: true, data }, 0);
} catch (error) {
const code = error instanceof ScriptError ? error.code : 'UNEXPECTED';
const message = error?.message ?? String(error);
write({ ok: false, error: { code, message } });
process.exit(1);
write({ ok: false, error: { code, message } }, 1);
}
}
function write(payload) {
process.stdout.write(`${JSON.stringify(payload)}\n`);
/**
* 印出結果後才結束行程。
* stdout 接到 pipe 時寫入是非同步的,直接 process.exit 會截斷長輸出,
* 所以要等 write 的 callback 回來再退出。
*/
function write(payload, exitCode) {
process.stdout.write(`${JSON.stringify(payload)}\n`, () => process.exit(exitCode));
}
// ── 認證來源 ───────────────────────────────────────────────────────
@@ -427,8 +431,11 @@ function checkTimeTracker(info) {
export async function findIssueByTitle(login, repo, title) {
const wanted = title.trim();
const pageSize = 50;
// 翻頁上限。查不完就停手並報錯,不能無聲回 null——那會讓呼叫端把既有議題再建一次,
// 正好是冪等查重要防的事。
const maxPages = 200;
for (let page = 1; ; page += 1) {
for (let page = 1; page <= maxPages; page += 1) {
const response = await giteaRequest(login, 'GET', `/repos/${repo}/issues`, {
query: { state: 'all', limit: pageSize, page },
});
@@ -437,4 +444,9 @@ export async function findIssueByTitle(login, repo, title) {
if (hit) return hit;
if (issues.length < pageSize) return null;
}
throw new ScriptError(
'DEDUPE_LIMIT',
`翻完 ${maxPages * pageSize} 筆議題仍未查完 ${repo},無法確認「${wanted}」是否已存在;請縮小範圍或手動確認`,
);
}