feat/wp-extract-contract/main #33
@@ -0,0 +1,127 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* 把一顆工作包議題抽成實作階段要用的精簡 JSON。
|
||||
*
|
||||
* 與需求議題的抽取(issue-extract)同樣「只讀 body 不讀留言」,但多做三件事:
|
||||
* 1. 待辦是巢狀的——每一項待辦底下掛它自己的驗收,並各自帶回未經修改的 `raw`,
|
||||
* 下游靠 `raw` 做精確字串替換來勾選 checkbox,只改那一行,不重寫整份 body。
|
||||
* 2. 介面契約是四欄表格,四欄都要留著。
|
||||
* 3. body 說不出的三個活狀態要現查:相依、領取人、碼錶。
|
||||
*
|
||||
* 用法:
|
||||
* node scripts/wp-extract.js --repo owner/name --index 9 [--host <網址>] [--dry-run]
|
||||
*/
|
||||
import {
|
||||
UNMERGED_COMMENT_NOTE,
|
||||
countUnmergedComments,
|
||||
expectOk,
|
||||
fetchIssue,
|
||||
giteaRequest,
|
||||
main,
|
||||
pages,
|
||||
parseFlags,
|
||||
parseIndex,
|
||||
parseRepo,
|
||||
preflight,
|
||||
resolveLogin,
|
||||
} from './lib.js';
|
||||
import {
|
||||
checklistInSection,
|
||||
listSection,
|
||||
parseSections,
|
||||
referencedIndex,
|
||||
tableRows,
|
||||
textSection,
|
||||
} from './issue-body.js';
|
||||
|
||||
/** 介面契約表格由左到右的四欄 */
|
||||
const CONTRACT_COLUMNS = ['介面', '產出者', '消費者', '形狀'];
|
||||
|
||||
main(async () => {
|
||||
const flags = parseFlags(process.argv.slice(2), {
|
||||
required: ['repo', 'index'],
|
||||
optional: ['host'],
|
||||
booleans: ['dry-run'],
|
||||
});
|
||||
const repo = parseRepo(flags.repo);
|
||||
const index = parseIndex(flags.index);
|
||||
const issuePath = `/repos/${repo}/issues/${index}`;
|
||||
|
||||
if (flags['dry-run']) {
|
||||
return {
|
||||
dryRun: true,
|
||||
repo,
|
||||
index,
|
||||
requests: [
|
||||
{ method: 'GET', path: issuePath },
|
||||
{ method: 'GET', path: `${issuePath}/dependencies` },
|
||||
{ method: 'GET', path: `${issuePath}/blocks` },
|
||||
{ method: 'GET', path: '/user/stopwatches' },
|
||||
{ method: 'GET', path: `${issuePath}/comments` },
|
||||
],
|
||||
note: UNMERGED_COMMENT_NOTE,
|
||||
};
|
||||
}
|
||||
|
||||
const login = resolveLogin({ host: flags.host });
|
||||
await preflight(login, repo);
|
||||
|
||||
const issue = await fetchIssue(login, repo, index);
|
||||
const sections = parseSections(issue.body);
|
||||
// 先讀完兩種相依再組輸出:欄位順序照契約寫成 {blocks, depends},
|
||||
// 但請求順序維持「先問誰擋著我」,與 --dry-run 預告的一致。
|
||||
const depends = await fetchLinked(login, `${issuePath}/dependencies`, '先決');
|
||||
const blocks = await fetchLinked(login, `${issuePath}/blocks`, '阻擋');
|
||||
|
||||
return {
|
||||
index: issue.number,
|
||||
url: issue.html_url,
|
||||
title: issue.title,
|
||||
需求議題: referencedIndex(sections, '關聯', '需求議題'),
|
||||
描述: textSection(sections, '描述'),
|
||||
架構圖: textSection(sections, '架構圖'),
|
||||
範圍邊界: listSection(sections, '範圍邊界'),
|
||||
介面契約: tableRows(sections, '介面契約', CONTRACT_COLUMNS),
|
||||
待辦: checklistInSection(issue.body, '待辦'),
|
||||
整體驗收: listSection(sections, '整體驗收'),
|
||||
repos: listSection(sections, 'repo 列表'),
|
||||
相依: { blocks, depends },
|
||||
assignee: issue.assignee?.login ?? null,
|
||||
碼錶中: await hasRunningStopwatch(login, repo, index),
|
||||
未處理留言數: await countUnmergedComments(login, repo, index),
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* 讀一種相依關係上的議題編號。
|
||||
* 逐頁讀完:半份清單會讓下游把實作順序排錯,那比直接報錯更難發現。
|
||||
* @param {string} kind 出現在錯誤訊息裡的關係名稱
|
||||
*/
|
||||
async function fetchLinked(login, path, kind) {
|
||||
const indexes = [];
|
||||
|
||||
for await (const issues of pages(login, path, {
|
||||
limitCode: 'DEPENDENCY_LIMIT',
|
||||
limitHint: `${path} 的${kind}關係太多,讀不完整份清單`,
|
||||
})) {
|
||||
for (const issue of issues) indexes.push(issue.number);
|
||||
}
|
||||
return indexes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 這顆議題上是不是有碼錶在跑。
|
||||
*
|
||||
* Gitea 只讓人讀自己的碼錶(`/user/stopwatches`),所以這個欄位的真正語意是
|
||||
* 「**我**的碼錶正跑在這顆議題上」。它用來提醒自己忘了停錶,不是用來判斷別人有沒有在做
|
||||
* ——領取鎖看的是 assignee。
|
||||
*/
|
||||
async function hasRunningStopwatch(login, repo, index) {
|
||||
const path = '/user/stopwatches';
|
||||
const watches = expectOk(await giteaRequest(login, 'GET', path), `GET ${path}`) ?? [];
|
||||
|
||||
return watches.some(
|
||||
(watch) => `${watch.repo_owner_name}/${watch.repo_name}` === repo && watch.issue_index === index,
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user