feat/issue-extract-contract/main #22

Merged
admin merged 7 commits from feat/issue-extract-contract/main into master 2026-09-17 05:04:33 +00:00
Showing only changes of commit c8ecc39dc8 - Show all commits
+47 -16
View File
@@ -443,25 +443,56 @@ export async function listLabels(login, repo) {
* @param {string} title 要找的標題
* @returns {Promise<object|null>} 找到的議題,或 null
*/
export async function findIssueByTitle(login, repo, title) {
const wanted = title.trim();
const pageSize = 50;
// 翻頁上限。查不完就停手並報錯,不能無聲回 null——那會讓呼叫端把既有議題再建一次,
// 正好是冪等查重要防的事。
const maxPages = 200;
/**
* 逐頁走訪一個回傳陣列的 Gitea 端點。
*
* 所有「必須看完整份清單」的走訪都走這裡:讀不完就要大聲報錯,不能無聲回傳半份。
* 半份清單比報錯更危險——查重會漏掉既有議題而重建一顆,數留言會少算而讓下游
* 拿著過期描述做事。
* @param {{base: string, token: string}} login
* @param {string} path
* @param {{query?: object, pageSize?: number, maxPages?: number, limitCode?: string, limitHint?: string}} options
* @returns {AsyncGenerator<object[]>} 每次 yield 一頁
*/
export async function* pages(login, path, options = {}) {
const {
query = {},
pageSize = 50,
maxPages = 200,
limitCode = 'PAGE_LIMIT',
limitHint = `${path} 的資料量超出可走訪範圍`,
} = options;
for (let page = 1; page <= maxPages; page += 1) {
const response = await giteaRequest(login, 'GET', `/repos/${repo}/issues`, {
query: { state: 'all', limit: pageSize, page },
const response = await giteaRequest(login, 'GET', path, {
query: { ...query, limit: pageSize, page },
});
const issues = expectOk(response, `GET /repos/${repo}/issues`) ?? [];
const hit = issues.find((issue) => (issue.title ?? '').trim() === wanted);
if (hit) return hit;
if (issues.length < pageSize) return null;
const items = expectOk(response, `GET ${path}`) ?? [];
yield items;
if (items.length < pageSize) return;
}
throw new ScriptError(
'DEDUPE_LIMIT',
`翻完 ${maxPages * pageSize} 筆議題仍未查完 ${repo},無法確認「${wanted}」是否已存在;請縮小範圍或手動確認`,
);
throw new ScriptError(limitCode, `${limitHint}(已讀 ${maxPages * pageSize} 筆仍未讀完)`);
}
/**
* 依標題找出既有議題,讓寫入型腳本中斷重跑時不會產生重複議題。
* 比對前後空白修掉:同一顆議題不該因為標題多了一個空格就被當成新的。
* @param {{base: string, token: string}} login
* @param {string} repo owner/name
* @param {string} title 要找的標題
* @returns {Promise<object|null>} 找到的議題,或 null
*/
export async function findIssueByTitle(login, repo, title) {
const wanted = title.trim();
for await (const issues of pages(login, `/repos/${repo}/issues`, {
query: { state: 'all' },
limitCode: 'DEDUPE_LIMIT',
limitHint: `翻不完 ${repo} 的議題,無法確認「${wanted}」是否已存在;請縮小範圍或手動確認`,
})) {
const hit = issues.find((issue) => (issue.title ?? '').trim() === wanted);
if (hit) return hit;
}
return null;
}