feat/wp-schedule-and-board/main #31
@@ -0,0 +1,115 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* 把議題放進 Gitea 專案看板。
|
||||
*
|
||||
* Gitea 1.27 沒有「列出專案」的 endpoint,所以 `--project` 給名稱時只能反查:
|
||||
* 掃最近 50 筆議題,從它們身上的 projects 欄位湊出 id→名稱對照。全都沒掛看板時
|
||||
* 就湊不出來,這時請使用者直接貼專案網址(結尾即 id)。
|
||||
*
|
||||
* 本工具不建立專案——API 也沒有這個能力。
|
||||
*
|
||||
* 用法:
|
||||
* node scripts/project-add.js --repo owner/name --index 12 --project <名稱或網址>
|
||||
* [--host <網址>] [--dry-run]
|
||||
*/
|
||||
import {
|
||||
ScriptError,
|
||||
expectOk,
|
||||
giteaRequest,
|
||||
main,
|
||||
parseFlags,
|
||||
parseRepo,
|
||||
preflight,
|
||||
resolveLogin,
|
||||
} from './lib.js';
|
||||
|
||||
main(async () => {
|
||||
const flags = parseFlags(process.argv.slice(2), {
|
||||
required: ['repo', 'index', 'project'],
|
||||
optional: ['host'],
|
||||
booleans: ['dry-run'],
|
||||
});
|
||||
const repo = parseRepo(flags.repo);
|
||||
const index = parseIndex(flags.index);
|
||||
const path = `/repos/${repo}/issues/${index}`;
|
||||
|
||||
const login = resolveLogin({ host: flags.host });
|
||||
if (!flags['dry-run']) await preflight(login, repo);
|
||||
|
||||
const project = await resolveProject(login, repo, flags.project);
|
||||
const issue = expectOk(await giteaRequest(login, 'GET', path), `GET ${path}`);
|
||||
const current = (issue.projects ?? []).map((item) => item.id);
|
||||
|
||||
if (current.includes(project.id)) {
|
||||
return { repo, index, project, alreadyThere: true };
|
||||
}
|
||||
|
||||
// projects 是整份取代,不是附加:漏掉既有的等於把這顆議題踢出原本的看板
|
||||
const payload = { projects: [...current, project.id] };
|
||||
|
||||
if (flags['dry-run']) {
|
||||
return {
|
||||
dryRun: true,
|
||||
repo,
|
||||
index,
|
||||
project,
|
||||
requests: [{ method: 'PATCH', path, body: payload }],
|
||||
};
|
||||
}
|
||||
|
||||
expectOk(await giteaRequest(login, 'PATCH', path, { body: payload }), `PATCH ${path}`);
|
||||
return { repo, index, project, alreadyThere: false };
|
||||
});
|
||||
|
||||
function parseIndex(value) {
|
||||
if (!/^[1-9]\d*$/.test(value)) {
|
||||
throw new ScriptError('BAD_INDEX', `--index 需為正整數,收到的是 ${value}`);
|
||||
}
|
||||
return Number(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 把 `--project` 解析成 {id, title}。
|
||||
* 看起來像網址就取結尾的 id;否則掃議題反查名稱。
|
||||
*/
|
||||
async function resolveProject(login, repo, value) {
|
||||
if (/^https?:\/\//.test(value)) {
|
||||
const id = value.replace(/\/+$/, '').match(/(\d+)$/)?.[1];
|
||||
if (!id) {
|
||||
throw new ScriptError('PROJECT_NOT_FOUND', `專案網址的結尾必須是專案 id:${value}`);
|
||||
}
|
||||
return { id: Number(id), title: null };
|
||||
}
|
||||
|
||||
const known = await scanProjects(login, repo);
|
||||
const hit = known.find((project) => project.title === value);
|
||||
if (hit) return hit;
|
||||
|
||||
const seen = known.map((project) => project.title).join('、');
|
||||
throw new ScriptError(
|
||||
'PROJECT_NOT_FOUND',
|
||||
seen
|
||||
? `反查不到名為「${value}」的看板。掃到的有:${seen}。若它不在其中,請直接貼專案網址(結尾即 id)`
|
||||
: `最近的議題沒有任何一顆掛在看板上,無法反查看板 id。請直接貼專案網址(結尾即 id)`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 掃最近 50 筆議題,湊出這個 repo 用過的看板。
|
||||
* Gitea 沒給列出專案的 endpoint,這是唯一拿得到 id→名稱對照的辦法。
|
||||
*/
|
||||
async function scanProjects(login, repo) {
|
||||
const path = `/repos/${repo}/issues`;
|
||||
const issues = expectOk(
|
||||
await giteaRequest(login, 'GET', path, { query: { state: 'all', limit: 50 } }),
|
||||
`GET ${path}`,
|
||||
) ?? [];
|
||||
|
||||
const byId = new Map();
|
||||
for (const issue of issues) {
|
||||
for (const project of issue.projects ?? []) {
|
||||
byId.set(project.id, { id: project.id, title: project.title });
|
||||
}
|
||||
}
|
||||
return [...byId.values()];
|
||||
}
|
||||
Reference in New Issue
Block a user