feat/wp-schedule-and-board/main #31

Merged
admin merged 17 commits from feat/wp-schedule-and-board/main into master 2026-09-17 06:09:42 +00:00
Showing only changes of commit b1025168c0 - Show all commits
+110
View File
@@ -0,0 +1,110 @@
#!/usr/bin/env node
/**
* 建立議題之間的相依。
*
* --depends 這顆被誰擋住(它的先決)
* --blocks 這顆擋住誰
*
* 冪等:先讀現況,只補缺的那幾條。重跑不會在 Gitea 上堆出重複的相依。
*
* `--dry-run` 不寫入但會讀——要列出「將建立哪幾條」就得先知道哪幾條已經存在。
*
* 用法:
* node scripts/issue-link.js --repo owner/name --index 12
* [--depends 10,11] [--blocks 13] [--host <網址>] [--dry-run]
*/
import {
ScriptError,
expectOk,
giteaRequest,
main,
parseFlags,
parseRepo,
preflight,
resolveLogin,
} from './lib.js';
/** 兩種相依各自的端點與意義 */
const KINDS = {
depends: { path: 'dependencies', label: '先決' },
blocks: { path: 'blocks', label: '阻擋' },
};
main(async () => {
const flags = parseFlags(process.argv.slice(2), {
required: ['repo', 'index'],
optional: ['depends', 'blocks', 'host'],
booleans: ['dry-run'],
});
const repo = parseRepo(flags.repo);
const [owner, name] = repo.split('/');
const index = parseIndex(flags.index, '--index');
const wanted = {
depends: parseIndexList(flags.depends, index),
blocks: parseIndexList(flags.blocks, index),
};
if (wanted.depends.length === 0 && wanted.blocks.length === 0) {
throw new ScriptError('NOTHING_TO_LINK', '至少要指定 --depends 或 --blocks 其中一個');
}
const login = resolveLogin({ host: flags.host });
if (!flags['dry-run']) await preflight(login, repo);
const added = {};
const skipped = {};
const planned = [];
for (const [kind, { path }] of Object.entries(KINDS)) {
const endpoint = `/repos/${repo}/issues/${index}/${path}`;
const existing = new Set(
(expectOk(await giteaRequest(login, 'GET', endpoint), `GET ${endpoint}`) ?? []).map(
(issue) => issue.number,
),
);
added[kind] = wanted[kind].filter((target) => !existing.has(target));
skipped[kind] = wanted[kind].filter((target) => existing.has(target));
for (const target of added[kind]) {
const body = { owner, repo: name, index: target };
if (flags['dry-run']) {
planned.push({ method: 'POST', path: endpoint, body });
continue;
}
expectOk(await giteaRequest(login, 'POST', endpoint, { body }), `POST ${endpoint}`);
}
}
if (flags['dry-run']) {
return { dryRun: true, repo, index, requests: planned, added, skipped };
}
return { repo, index, added, skipped };
});
function parseIndex(value, what) {
if (!/^[1-9]\d*$/.test(String(value))) {
throw new ScriptError('BAD_INDEX', `${what} 需為正整數,收到的是 ${value}`);
}
return Number(value);
}
/**
* 解析逗號分隔的議題編號。
* 自己依賴自己在 Gitea 上會被接受但毫無意義,而且會讓後續的拓撲排序永遠排不完,
* 所以擋在發出請求之前。
*/
function parseIndexList(value, self) {
if (value === undefined) return [];
const indexes = value
.split(',')
.map((item) => item.trim())
.filter((item) => item !== '')
.map((item) => parseIndex(item, '相依編號'));
if (indexes.includes(self)) {
throw new ScriptError('SELF_DEPENDENCY', `議題 #${self} 不能是自己的先決或阻擋對象`);
}
return indexes;
}