Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 66d93abe24 | |||
| a4b87f9108 | |||
| 09533ff741 | |||
| e217b18c62 | |||
| cd0ced1b7f | |||
| 65cf45c558 | |||
| 09c78835e7 | |||
| ec05ce7869 | |||
| 323be94a72 | |||
| 0063f3282f | |||
| d7336dbe6c | |||
| c1f8aa3c72 | |||
| 8a28d1f1ef | |||
| d04f4dd2bb | |||
| f5cf5950bd | |||
| 8c3d0d9a6d | |||
| eae73092ad | |||
| 69624a542e | |||
| 8aa273b8bd | |||
| 3849bb2168 | |||
| 00458d4eb2 | |||
| 379938d6dc | |||
| 894ece033b | |||
| 5bf39966d0 | |||
| fe2a513fbb | |||
| 2193bdd4d6 | |||
| af51ffacee | |||
| 3509a882e1 | |||
| d9c55ca347 | |||
| 1d2e8236de | |||
| 21fb9c1d94 | |||
| 607c9b82ea | |||
| 8acea007e7 | |||
| d8423c74b1 | |||
| 953951145f | |||
| 1576e783fb | |||
| e017705c64 | |||
| 94e974b5dc | |||
| 5f77b83a0f | |||
| da43cb02b0 | |||
| 577a930438 | |||
| a9a0b43ea5 | |||
| aa8234b5c7 | |||
| b0f2d45c11 | |||
| 3fd9a7e13d | |||
| 39cc5c932c | |||
| 255adbabe4 | |||
| a10fc8f176 | |||
| 9b39908394 |
@@ -1 +1,58 @@
|
||||
[]
|
||||
[
|
||||
{
|
||||
"level": "critical",
|
||||
"role": "Leo",
|
||||
"location": "app/git.js:11",
|
||||
"suggestion": "GITEA_TOKEN 直接嵌入 URL 中,可能導致憑證洩漏。建議使用環境變數或安全的憑證管理方式來處理敏感資訊。",
|
||||
"is_new": true
|
||||
},
|
||||
{
|
||||
"level": "critical",
|
||||
"role": "Maya",
|
||||
"location": "app/git.js:1",
|
||||
"suggestion": "缺少對 commitAndPush 函數的單元測試,應該為其添加測試以確保其正確性。",
|
||||
"is_new": true
|
||||
},
|
||||
{
|
||||
"level": "warning",
|
||||
"role": "Leo",
|
||||
"location": "app/git.js:25",
|
||||
"suggestion": "在使用 fs.existsSync 檢查目錄是否存在時,應考慮使用非同步方法以避免阻塞事件循環。",
|
||||
"is_new": true
|
||||
},
|
||||
{
|
||||
"level": "warning",
|
||||
"role": "Leo",
|
||||
"location": "app/git.js:29",
|
||||
"suggestion": "在 git clone 時使用 --depth=1 可能會導致未來需要完整歷史紀錄時的性能問題,建議根據實際需求調整。",
|
||||
"is_new": true
|
||||
},
|
||||
{
|
||||
"level": "warning",
|
||||
"role": "Leo",
|
||||
"location": "app/git.js:11",
|
||||
"suggestion": "在使用 fs.copyFileSync 時,未檢查目標檔案是否存在,可能會覆蓋重要資料。建議在複製之前檢查檔案是否存在。",
|
||||
"is_new": true
|
||||
},
|
||||
{
|
||||
"level": "warning",
|
||||
"role": "Leo",
|
||||
"location": "app/git.js:11",
|
||||
"suggestion": "在 commitAndPush 函數中,對於 git 操作的錯誤處理不夠完善,應該添加更多的測試來驗證不同情況下的行為。",
|
||||
"is_new": true
|
||||
},
|
||||
{
|
||||
"level": "info",
|
||||
"role": "Leo",
|
||||
"location": ".gitea/workflows/review.yaml:5",
|
||||
"suggestion": "建議在 'branches-ignore' 前加上空行,以提高可讀性。",
|
||||
"is_new": true
|
||||
},
|
||||
{
|
||||
"level": "info",
|
||||
"role": "Leo",
|
||||
"location": "app/git.js:45",
|
||||
"suggestion": "考慮使用 async/await 來處理 fs.copyFileSync,以提高可讀性和錯誤處理能力。",
|
||||
"is_new": true
|
||||
}
|
||||
]
|
||||
@@ -1,6 +1,11 @@
|
||||
name: AI
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref }}
|
||||
cancel-in-progress: true
|
||||
on:
|
||||
pull_request:
|
||||
branches-ignore:
|
||||
- master
|
||||
types: [opened, synchronize]
|
||||
jobs:
|
||||
version:
|
||||
|
||||
+5
-4
@@ -1,8 +1,9 @@
|
||||
FROM node:20-slim
|
||||
FROM alpine
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
git \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
RUN apk add --no-cache bash nodejs npm git \
|
||||
&& node --version \
|
||||
&& npm --version \
|
||||
&& git --version
|
||||
|
||||
WORKDIR /action
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
---
|
||||
|
||||
|
||||
每個階段都會加上明確的 log,並確保即使部分功能未完成也能降級執行、不會中斷 pipeline。
|
||||
|
||||
每次執行後請貼 log,我會協助 debug。
|
||||
|
||||
|
||||
+43
-17
@@ -1,25 +1,51 @@
|
||||
import { spawnSync } from 'child_process';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { commitFile } from './gitea.js';
|
||||
import { FINDINGS_PATH } from './config.js';
|
||||
import { GITEA_SERVER_URL, GITEA_REPOSITORY, GITEA_TOKEN, PR_HEAD_BRANCH, FINDINGS_PATH } from './config.js';
|
||||
|
||||
function git(args, cwd) {
|
||||
const result = spawnSync('git', args, { cwd, encoding: 'utf8' });
|
||||
if (result.error) throw result.error;
|
||||
if (result.status !== 0) throw new Error((result.stderr || result.stdout || '').trim());
|
||||
return (result.stdout || '').trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 透過 Gitea API 將 findings.json push 到來源分支(不需要 git binary)
|
||||
*/
|
||||
export async function commitAndPush(workspace) {
|
||||
const remoteUrl = GITEA_SERVER_URL.replace(/\/$/, '')
|
||||
.replace('https://', `https://${GITEA_TOKEN}@`)
|
||||
.replace('http://', `http://${GITEA_TOKEN}@`) + `/${GITEA_REPOSITORY}.git`;
|
||||
|
||||
const repoDir = path.join(workspace, 'repo');
|
||||
|
||||
try {
|
||||
const fullPath = path.join(workspace, FINDINGS_PATH);
|
||||
const content = fs.readFileSync(fullPath, 'utf8');
|
||||
console.log(` [debug] FINDINGS_PATH=${FINDINGS_PATH} branch=${process.env.PR_HEAD_BRANCH} token=${process.env.GITEA_TOKEN ? '***' : 'EMPTY'}`);
|
||||
const result = await commitFile(
|
||||
FINDINGS_PATH,
|
||||
content,
|
||||
'chore: update ai-review findings [skip ci]'
|
||||
);
|
||||
const commitHash = result.commit?.sha?.slice(0, 7) || 'unknown';
|
||||
console.log(` ✅ persisted findings commit=${commitHash} push=${process.env.PR_HEAD_BRANCH}`);
|
||||
if (!fs.existsSync(repoDir)) {
|
||||
git(['clone', '--depth=1', '--branch', PR_HEAD_BRANCH, remoteUrl, repoDir], workspace);
|
||||
}
|
||||
|
||||
git(['config', 'user.email', 'ai-review[bot]@gitea'], repoDir);
|
||||
git(['config', 'user.name', 'AI Review Bot'], repoDir);
|
||||
git(['fetch', 'origin', PR_HEAD_BRANCH], repoDir);
|
||||
git(['checkout', PR_HEAD_BRANCH], repoDir);
|
||||
|
||||
// 將 findings.json 從 workspace 複製到 clone 的 repo
|
||||
const srcFindings = path.join(workspace, FINDINGS_PATH);
|
||||
const destFindings = path.join(repoDir, FINDINGS_PATH);
|
||||
fs.mkdirSync(path.dirname(destFindings), { recursive: true });
|
||||
fs.copyFileSync(srcFindings, destFindings);
|
||||
|
||||
git(['add', FINDINGS_PATH], repoDir);
|
||||
|
||||
const status = git(['status', '--porcelain'], repoDir);
|
||||
if (!status) {
|
||||
console.log(' findings.json 無變更,跳過 commit');
|
||||
return;
|
||||
}
|
||||
|
||||
const out = git(['commit', '-m', 'chore: update ai-review findings [skip ci]'], repoDir);
|
||||
const commitHash = out.match(/\[.+ ([a-f0-9]+)\]/)?.[1] || 'unknown';
|
||||
git(['push', remoteUrl, PR_HEAD_BRANCH], repoDir);
|
||||
console.log(` ✅ persisted findings commit=${commitHash} push=${PR_HEAD_BRANCH}`);
|
||||
} catch (e) {
|
||||
const detail = e.response?.data ? JSON.stringify(e.response.data) : e.message;
|
||||
console.log(` ⚠️ Runner failed: commit/push 失敗: ${e.response?.status || ''} ${detail}`);
|
||||
console.log(` ⚠️ Runner failed: commit/push 失敗: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-35
@@ -1,6 +1,6 @@
|
||||
import axios from 'axios';
|
||||
import https from 'https';
|
||||
import { GITEA_TOKEN, GITEA_SERVER_URL, GITEA_REPOSITORY, PR_NUMBER, PR_HEAD_BRANCH } from './config.js';
|
||||
import { GITEA_TOKEN, GITEA_SERVER_URL, GITEA_REPOSITORY, PR_NUMBER } from './config.js';
|
||||
|
||||
const httpsAgent = new https.Agent({ rejectUnauthorized: false });
|
||||
const headers = () => ({ Authorization: `token ${GITEA_TOKEN}`, 'Content-Type': 'application/json' });
|
||||
@@ -15,37 +15,3 @@ export async function postComment(body) {
|
||||
const resp = await axios.post(api(`/repos/${GITEA_REPOSITORY}/issues/${PR_NUMBER}/comments`), { body }, { headers: headers(), timeout: 30000, httpsAgent });
|
||||
return resp.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 透過 Gitea API 建立或更新檔案(不需要 git binary)
|
||||
*/
|
||||
export async function commitFile(filePath, content, message) {
|
||||
const encoded = Buffer.from(content).toString('base64');
|
||||
const url = api(`/repos/${GITEA_REPOSITORY}/contents/${filePath}`);
|
||||
|
||||
// 先嘗試取得現有檔案的 SHA
|
||||
let sha;
|
||||
try {
|
||||
const existing = await axios.get(`${url}?ref=${PR_HEAD_BRANCH}`, { headers: headers(), httpsAgent, timeout: 15000 });
|
||||
sha = existing.data.sha;
|
||||
} catch {
|
||||
sha = undefined;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
message,
|
||||
content: encoded,
|
||||
branch: PR_HEAD_BRANCH,
|
||||
...(sha ? { sha } : {}),
|
||||
};
|
||||
|
||||
const resp = await axios.request({
|
||||
method: sha ? 'put' : 'post',
|
||||
url,
|
||||
headers: headers(),
|
||||
httpsAgent,
|
||||
timeout: 30000,
|
||||
data: payload,
|
||||
});
|
||||
return resp.data;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user