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