feat(issue-update): 掛 Milestone、寫截止日、記人天估算

三件事經同一個 PATCH 送出,因為排時程時它們幾乎總是一起改,分開發等於多兩次往返。

Milestone 只認既有的:指到不存在的就中止並列出可選項目,本工具不建立 Milestone。

人天估算只寫進 body 的人類可讀一行。Gitea 1.27 的 API 沒有任何請求定義接受
time_estimate——它只出現在 Issue 的回應裡——所以議題的估算欄位無法由 API 寫入。
新增的 upsertLineInSection 負責就地更新那一行:重跑改估算不會累積成兩行,值沒變
就不把 body 塞進 PATCH,免得在議題上留下一筆沒有內容的編輯紀錄。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-17 06:06:05 +00:00
co-authored by Claude Opus 5
parent b45437e527
commit 45bfd99d80
2 changed files with 156 additions and 0 deletions
+37
View File
@@ -141,3 +141,40 @@ function splitRow(line) {
function isSeparator(cells) {
return cells.length > 0 && cells.every((cell) => /^:?-+:?$/.test(cell));
}
/**
* 在指定段落裡就地更新(或補上)一行「前綴+值」。
*
* 用於人天估算這種「議題上只該有一行、重跑要覆蓋而不是累積」的欄位。
* 段落不存在時補在 body 結尾——寧可放錯位置,也不要讓值靜靜消失。
*
* @param {string} body 議題 body
* @param {string} section 段落名稱,例如 '關聯'
* @param {string} line 完整的一行,例如 '估算人天:3'
* @returns {string} 更新後的 body;內容沒有變動時回傳原字串
*/
export function upsertLineInSection(body, section, line) {
const prefix = line.slice(0, line.indexOf(':') + 1);
const existing = new RegExp(`^${escapeRegExp(prefix)}.*$`, 'm');
if (existing.test(body)) {
const updated = body.replace(existing, line);
return updated;
}
const heading = `## ${section}`;
const start = body.indexOf(heading);
if (start === -1) {
return `${body.replace(/\n*$/, '')}\n\n${line}\n`;
}
// 段落的結尾是下一個標題之前,或整份 body 的結尾
const after = body.indexOf('\n## ', start + heading.length);
const end = after === -1 ? body.length : after;
const head = body.slice(0, end).replace(/\n*$/, '');
return `${head}\n${line}\n${body.slice(end).replace(/^\n+/, '\n')}`;
}
function escapeRegExp(text) {
return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}