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:
@@ -10,11 +10,10 @@
|
|||||||
* node scripts/issue-extract.js --repo owner/name --index 7 [--host <網址>] [--dry-run]
|
* node scripts/issue-extract.js --repo owner/name --index 7 [--host <網址>] [--dry-run]
|
||||||
*/
|
*/
|
||||||
import {
|
import {
|
||||||
ScriptError,
|
UNMERGED_COMMENT_NOTE,
|
||||||
expectOk,
|
countUnmergedComments,
|
||||||
giteaRequest,
|
fetchIssue,
|
||||||
main,
|
main,
|
||||||
pages,
|
|
||||||
parseFlags,
|
parseFlags,
|
||||||
parseIndex,
|
parseIndex,
|
||||||
parseRepo,
|
parseRepo,
|
||||||
@@ -43,14 +42,14 @@ main(async () => {
|
|||||||
{ method: 'GET', path: issuePath },
|
{ method: 'GET', path: issuePath },
|
||||||
{ method: 'GET', path: commentsPath },
|
{ method: 'GET', path: commentsPath },
|
||||||
],
|
],
|
||||||
note: '每則留言還會各查一次 reaction,用來數出未整併的則數;則數取決於留言數,事前無法列舉。',
|
note: UNMERGED_COMMENT_NOTE,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const login = resolveLogin({ host: flags.host });
|
const login = resolveLogin({ host: flags.host });
|
||||||
await preflight(login, repo);
|
await preflight(login, repo);
|
||||||
|
|
||||||
const issue = await fetchIssue(login, repo, index, issuePath);
|
const issue = await fetchIssue(login, repo, index);
|
||||||
const sections = parseSections(issue.body);
|
const sections = parseSections(issue.body);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -67,41 +66,7 @@ main(async () => {
|
|||||||
驗收標準: listSection(sections, '驗收標準'),
|
驗收標準: listSection(sections, '驗收標準'),
|
||||||
影響範圍: listSection(sections, '影響範圍'),
|
影響範圍: 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;
|
|
||||||
}
|
|
||||||
|
|||||||
+62
-1
@@ -1,12 +1,13 @@
|
|||||||
/**
|
/**
|
||||||
* tea-sdlc 所有腳本的共用地基。
|
* tea-sdlc 所有腳本的共用地基。
|
||||||
*
|
*
|
||||||
* 這一層負責五件事,其餘腳本只寫自己的業務:
|
* 這一層負責六件事,其餘腳本只寫自己的業務:
|
||||||
* 1. 具名 flag 解析與單行 JSON 輸出({ok, data, error:{code, message}})
|
* 1. 具名 flag 解析與單行 JSON 輸出({ok, data, error:{code, message}})
|
||||||
* 2. Gitea API 呼叫 —— 全專案唯一的 HTTP 出口
|
* 2. Gitea API 呼叫 —— 全專案唯一的 HTTP 出口
|
||||||
* 3. git 執行 —— 全專案唯一的子行程出口
|
* 3. git 執行 —— 全專案唯一的子行程出口
|
||||||
* 4. 四層前置檢查
|
* 4. 四層前置檢查
|
||||||
* 5. 冪等查重
|
* 5. 冪等查重
|
||||||
|
* 6. 兩支抽取腳本共用的議題讀取
|
||||||
*
|
*
|
||||||
* 外部相依集中在 giteaRequest 與 runGit 兩個函式,測試才有地方替身。
|
* 外部相依集中在 giteaRequest 與 runGit 兩個函式,測試才有地方替身。
|
||||||
*/
|
*/
|
||||||
@@ -431,6 +432,66 @@ function checkTimeTracker(info) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── 議題讀取:兩支抽取腳本共用 ────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 讀一顆議題。「不存在」與「沒有讀取權」要分得開——前者是編號打錯,
|
||||||
|
* 後者是權限沒開,兩種的下一步完全不同。
|
||||||
|
* @param {{base: string, token: string}} login
|
||||||
|
* @param {string} repo owner/name
|
||||||
|
* @param {number} index
|
||||||
|
* @returns {Promise<object>}
|
||||||
|
*/
|
||||||
|
export async function fetchIssue(login, repo, index) {
|
||||||
|
const path = `/repos/${repo}/issues/${index}`;
|
||||||
|
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}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 抽取腳本 `--dry-run` 的共同附註:留言的 reaction 要逐則查,事前列不出來。
|
||||||
|
* 與 countUnmergedComments 同進退——說明的是它發出的那些請求。
|
||||||
|
*/
|
||||||
|
export const UNMERGED_COMMENT_NOTE =
|
||||||
|
'每則留言還會各查一次 reaction,用來數出未整併的則數;則數取決於留言數,事前無法列舉。';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 數出尚未被整併回描述的留言則數。
|
||||||
|
*
|
||||||
|
* 抽取契約只讀 body 不讀留言,這個數字是下游判斷「手上的描述是不是過期了」的唯一依據。
|
||||||
|
* 已整併的留言會被打上 `+1` reaction(由 sdlc-sync 負責標記),而 Gitea 的留言物件
|
||||||
|
* 不含 reaction,所以只能逐則再查一次。留言多時請求數會跟著長,但這個數字要準
|
||||||
|
* ——它決定下游會不會拿著過期的描述做事,所以留言也要逐頁讀完,讀不完寧可報錯。
|
||||||
|
*
|
||||||
|
* @param {{base: string, token: string}} login
|
||||||
|
* @param {string} repo owner/name
|
||||||
|
* @param {number} index
|
||||||
|
* @returns {Promise<number>}
|
||||||
|
*/
|
||||||
|
export async function countUnmergedComments(login, repo, index) {
|
||||||
|
const commentsPath = `/repos/${repo}/issues/${index}/comments`;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
// ── 標籤 ───────────────────────────────────────────────────────────
|
// ── 標籤 ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user