Files
ai-pull-request/app/lib/gitea.js
T

99 lines
2.7 KiB
JavaScript

import { log } from './util.js';
/**
* 極簡的 Gitea API client,只實作這個 action 需要的 PR 相關操作。
*/
export class GiteaClient {
/**
* @param {object} opts
* @param {string} opts.serverUrl Gitea base URL(不含結尾斜線)
* @param {string} opts.owner
* @param {string} opts.repo
* @param {string} opts.token
*/
constructor({ serverUrl, owner, repo, token }) {
this.apiBase = `${serverUrl}/api/v1`;
this.owner = owner;
this.repo = repo;
this.token = token;
}
async _request(method, path, body) {
const url = `${this.apiBase}${path}`;
const res = await fetch(url, {
method,
headers: {
Authorization: `token ${this.token}`,
'Content-Type': 'application/json',
Accept: 'application/json',
},
body: body ? JSON.stringify(body) : undefined,
});
const text = await res.text();
let json;
try {
json = text ? JSON.parse(text) : {};
} catch {
json = { message: text };
}
return { ok: res.ok, status: res.status, json };
}
/**
* 查詢 head -> base 是否已存在開啟中的 PR。
*
* @param {string} head 來源分支
* @param {string} base 目標分支
* @returns {Promise<object|null>}
*/
async findOpenPull(head, base) {
// Gitea pulls 不直接支援 head/base 過濾,這裡撈開啟中的 PR 自行比對
const { ok, json } = await this._request(
'GET',
`/repos/${this.owner}/${this.repo}/pulls?state=open&limit=50`,
);
if (!ok || !Array.isArray(json)) return null;
return (
json.find(
(pr) => pr?.head?.ref === head && pr?.base?.ref === base,
) || null
);
}
/**
* 建立 Pull Request。若已存在相同 head/base 的 PR 則回傳既有 PR。
*
* @param {object} opts
* @param {string} opts.head 來源分支
* @param {string} opts.base 目標分支
* @param {string} opts.title
* @param {string} opts.body
* @returns {Promise<{ pull: object, created: boolean }>}
*/
async createPull({ head, base, title, body }) {
log.info(`建立 PR: ${head}${base}`);
const { ok, status, json } = await this._request(
'POST',
`/repos/${this.owner}/${this.repo}/pulls`,
{ head, base, title, body },
);
if (ok) {
return { pull: json, created: true };
}
// 422 通常代表 PR 已存在
if (status === 422 || status === 409) {
const existing = await this.findOpenPull(head, base);
if (existing) {
log.warn(`PR 已存在: #${existing.number}`);
return { pull: existing, created: false };
}
}
const message = json?.message || JSON.stringify(json);
throw new Error(`建立 PR 失敗 (${status}): ${message}`);
}
}