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} */ 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}`); } }