refactor(抽取): 議題讀取與留言計數收進 lib,兩支抽取腳本共用

工作包的抽取契約(#9)要做的事與需求議題那一支有三件完全重疊:讀議題、把「不存在」
與「沒有讀取權」分成兩種錯誤碼、以 +1 reaction 數出未整併的留言則數。這三件事的規則
只該有一份,複製一份到新腳本等於日後改規則要記得改兩個地方。

fetchIssue、countUnmergedComments 與試跑時那句附註一起搬到 lib,issue-extract 改為
呼叫它們,行為不變。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-17 14:24:31 +08:00
co-authored by Claude Opus 5
parent 75ad4d724e
commit 5c0c8c106d
2 changed files with 68 additions and 42 deletions
+6 -41
View File
@@ -10,11 +10,10 @@
* node scripts/issue-extract.js --repo owner/name --index 7 [--host <網址>] [--dry-run]
*/
import {
ScriptError,
expectOk,
giteaRequest,
UNMERGED_COMMENT_NOTE,
countUnmergedComments,
fetchIssue,
main,
pages,
parseFlags,
parseIndex,
parseRepo,
@@ -43,14 +42,14 @@ main(async () => {
{ method: 'GET', path: issuePath },
{ method: 'GET', path: commentsPath },
],
note: '每則留言還會各查一次 reaction,用來數出未整併的則數;則數取決於留言數,事前無法列舉。',
note: UNMERGED_COMMENT_NOTE,
};
}
const login = resolveLogin({ host: flags.host });
await preflight(login, repo);
const issue = await fetchIssue(login, repo, index, issuePath);
const issue = await fetchIssue(login, repo, index);
const sections = parseSections(issue.body);
return {
@@ -67,41 +66,7 @@ main(async () => {
驗收標準: listSection(sections, '驗收標準'),
影響範圍: listSection(sections, '影響範圍'),
未決事項: listSection(sections, '未決事項'),
未處理留言數: await countUnmergedComments(login, repo, commentsPath),
未處理留言數: await countUnmergedComments(login, repo, index),
};
});
async function fetchIssue(login, repo, index, path) {
const response = await giteaRequest(login, 'GET', path);
if (response.status === 404) {
throw new ScriptError('ISSUE_NOT_FOUND', `${repo} 沒有編號 ${index} 的議題`);
}
if (response.status === 403) {
throw new ScriptError('NO_READ_ACCESS', `目前的帳號沒有 ${repo} 議題 ${index} 的讀取權`);
}
return expectOk(response, `GET ${path}`);
}
/**
* 數出尚未被整併回描述的留言則數。
*
* 已整併的留言會被打上 `+1` reaction(由 sdlc-sync 負責標記),而 Gitea 的留言物件
* 不含 reaction,所以只能逐則再查一次。留言多時請求數會跟著長,但這個數字要準
* ——它決定下游會不會拿著過期的描述做事,所以留言也要逐頁讀完,讀不完寧可報錯。
*/
async function countUnmergedComments(login, repo, commentsPath) {
let unmerged = 0;
for await (const comments of pages(login, commentsPath, {
limitCode: 'COMMENT_LIMIT',
limitHint: `${commentsPath} 的留言太多,數不完未整併的則數`,
})) {
for (const comment of comments) {
const path = `/repos/${repo}/issues/comments/${comment.id}/reactions`;
const reactions = expectOk(await giteaRequest(login, 'GET', path), `GET ${path}`) ?? [];
if (!reactions.some((reaction) => reaction.content === '+1')) unmerged += 1;
}
}
return unmerged;
}