'use strict'; const fs = require('fs'); const path = require('path'); /** * 彙整本次 action 執行的上下文:讀取 runner 注入的 GITHUB_* 執行期環境變數、 * INPUT_* 輸入參數與事件 payload 檔(GITHUB_EVENT_PATH),組出後續呼叫 * Gitea API 與執行 AI review 所需的全部資訊。 * * 事件 payload 不存在或 JSON 壞損時以空物件續行、不拋錯, * 由呼叫端檢查 prNumber 是否為 null 判斷是否處於 PR 情境。 * * @returns {{ * serverUrl: string, * repository: string, * owner: string, * repo: string, * apiBase: string, * token: string, * model: string, * createIssue: boolean, * event: Object, * pr: (Object|null), * prNumber: (number|null), * prTitle: string, * prBody: string, * baseRef: string, * headRef: string, * headSha: string, * runId: string, * runNumber: string, * workspace: string, * actionPath: string * }} 執行上下文物件: * - serverUrl:Gitea 伺服器網址(GITHUB_SERVER_URL,已去除尾端斜線)。 * - repository:`owner/repo` 全名(GITHUB_REPOSITORY)。 * - owner / repo:自 repository 拆出的擁有者與專案名,缺值時為空字串。 * - apiBase:Gitea REST API 基底網址(`/api/v1`)。 * - token:action input `token`(INPUT_TOKEN),用於 Gitea API 認證,以及 push findings/exclusions * commit 回 repo;建議為「能觸發 CI 的 PAT」(自動 token 推送不會再觸發 CI)。缺值時為空字串。 * - model:action input `model`(INPUT_MODEL,已 trim),指定 AI 模型,缺值時為空字串。 * - createIssue:action input `create-issue`(INPUT_CREATE-ISSUE),是否將問題建到 * 存取庫的問題追蹤(建問題模式);trim + 小寫後與字串 'true' 嚴格比對,預設 false。 * - event:事件 payload 解析後的完整物件;讀取失敗時為空物件。 * - pr:payload 內的 pull_request 物件;非 PR 事件時為 null。 * - prNumber:PR 編號,優先取 payload,退而從 GITHUB_REF(refs/pull/N/...)解析;皆無時為 null。 * - prTitle / prBody:PR 標題與描述(缺省或非 PR 情境時為空字串); * 建問題模式下作為新 issue 的標題與本文素材。 * - baseRef / headRef:PR 的目標/來源分支名;非 PR 情境時為空字串。 * - headSha:來源分支最新 commit SHA,優先取 payload,退而 GITHUB_SHA。 * - runId / runNumber:本次 workflow 執行識別(GITHUB_RUN_ID / GITHUB_RUN_NUMBER)。 * - workspace:工作目錄(GITHUB_WORKSPACE,退而 process.cwd())。 * - actionPath:action 根目錄(GITHUB_ACTION_PATH,退而以原始碼位置推算), * 用於定位 action 自帶檔案(如角色提示),不依賴呼叫端工作目錄。 * * @remarks * 使用情境:action 主程式(src/index.js)啟動時最先呼叫一次, * 取得上下文後傳遞給後續各模組使用。前置條件:需在 Gitea / GitHub Actions * runner 環境下執行(GITHUB_* 環境變數已注入);於本機直接執行時所有欄位 * 退回預設值(空字串 / null / process.cwd()),不會拋錯。呼叫端應先檢查 * prNumber 與 token 是否有值再進行 PR review 流程。注意:若 payload 含 * pull_request 但缺 base/head 結構,讀取 baseRef/headRef 時會拋 TypeError。 */ function loadContext() { const serverUrl = (process.env.GITHUB_SERVER_URL || '').replace(/\/+$/, ''); const repository = process.env.GITHUB_REPOSITORY || ''; const [owner = '', repo = ''] = repository.split('/'); // 讀取事件 payload(pull_request 事件時含 PR 完整資訊)。 let event = {}; const eventPath = process.env.GITHUB_EVENT_PATH; if (eventPath && fs.existsSync(eventPath)) { try { event = JSON.parse(fs.readFileSync(eventPath, 'utf8')); } catch { event = {}; // payload 壞損時以空物件續行,由呼叫端檢查 prNumber } } const pr = event.pull_request || null; // PR 編號:優先取 payload,退而從 GITHUB_REF(refs/pull/N/...)解析。 const refMatch = /refs\/pull\/(\d+)\//.exec(process.env.GITHUB_REF || ''); const prNumber = (pr && pr.number) || (refMatch ? Number(refMatch[1]) : null); return { serverUrl, repository, owner, repo, apiBase: `${serverUrl}/api/v1`, token: process.env.INPUT_TOKEN || '', model: (process.env.INPUT_MODEL || '').trim(), // 是否將問題建到存取庫的問題追蹤(input: create-issue,字串 'true' 才啟用,預設否)。 createIssue: (process.env['INPUT_CREATE-ISSUE'] || '').trim().toLowerCase() === 'true', event, pr, prNumber, prTitle: pr ? pr.title || '' : '', prBody: pr ? pr.body || '' : '', baseRef: pr ? pr.base.ref : '', headRef: pr ? pr.head.ref : '', headSha: (pr && pr.head.sha) || process.env.GITHUB_SHA || '', runId: process.env.GITHUB_RUN_ID || '', runNumber: process.env.GITHUB_RUN_NUMBER || '', workspace: process.env.GITHUB_WORKSPACE || process.cwd(), // action 自帶檔案(角色提示等)以 action 根目錄定位,不依賴呼叫端工作目錄。 actionPath: process.env.GITHUB_ACTION_PATH || path.resolve(__dirname, '..', '..'), }; } module.exports = { loadContext };