Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a904f9ede | ||
|
|
2f54271c38 | ||
|
|
b3878b9860 | ||
|
|
5a07f03abb | ||
|
|
2703840018 | ||
|
|
45b6abacc8 | ||
|
|
0b59ca6023 | ||
|
|
68ce92b0e5 |
@@ -16,5 +16,11 @@
|
|||||||
"role": "Leo",
|
"role": "Leo",
|
||||||
"original_finding": "extractJSONText is returning an unexpected result; suggest returning an empty array or throwing when no JSON is found.",
|
"original_finding": "extractJSONText is returning an unexpected result; suggest returning an empty array or throwing when no JSON is found.",
|
||||||
"reason": "誤報:extractJSONText 的合約刻意是「盡力擷取,找不到合法 JSON 時回傳去 fence 後的原文」,呼叫端(chatJSON 等)依賴此行為再做後續解析與 AI 修復。改成回 [] 或丟例外會破壞既有呼叫鏈,且型別不一致(回傳值為字串非陣列),故不採納。"
|
"reason": "誤報:extractJSONText 的合約刻意是「盡力擷取,找不到合法 JSON 時回傳去 fence 後的原文」,呼叫端(chatJSON 等)依賴此行為再做後續解析與 AI 修復。改成回 [] 或丟例外會破壞既有呼叫鏈,且型別不一致(回傳值為字串非陣列),故不採納。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"location": "app/config.js:25",
|
||||||
|
"role": "Bard",
|
||||||
|
"original_finding": "`getInsecureHttpsAgent` 與 `getOpenCodeHttpsAgent` 同時存在,卻指向同一個物件來源,等於替同一段旋律寫了兩個名字,容易讓後續維護者搞不清楚哪個才是正規稱呼。",
|
||||||
|
"reason": "相容性保留:`getOpenCodeHttpsAgent` 是既有公開匯出名稱,README 與測試仍引用此語意名稱;本次已補上過渡別名註解,說明新程式碼應優先使用 `getInsecureHttpsAgent`,避免破壞既有呼叫端。"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,66 +1 @@
|
|||||||
[
|
[]
|
||||||
{
|
|
||||||
"level": "warning",
|
|
||||||
"role": "Assassin",
|
|
||||||
"location": "app/git.js:67",
|
|
||||||
"problem": "這裡對所有 git 網路操作強制關閉 SSL 驗證。攻擊者若能攔截遠端回應,就能偽造 `ls-remote`、污染抓到的內容,甚至在有認證的情況下竊取 GITEA_TOKEN,讓後續審查建立在被污染的資料上。",
|
|
||||||
"suggestion": "移除 `GIT_SSL_NO_VERIFY=true`。若是內部憑證問題,改用正確的 CA bundle 或指定受信任憑證檔,不要整體關閉 git 的憑證驗證。",
|
|
||||||
"is_new": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"level": "warning",
|
|
||||||
"role": "Assassin",
|
|
||||||
"location": "app/usage.js:180",
|
|
||||||
"problem": "這裡對 OpenRouter 額度查詢也明確套用不驗證憑證的 agent。攻擊者只要攔截這條 HTTPS 請求,就能讀到 `Authorization: Bearer` 的 API key,還能回傳假 quota 資料誤導流程判斷。",
|
|
||||||
"suggestion": "改回預設的 HTTPS 憑證驗證。若必須支援自簽憑證,僅對特定內部主機單獨配置受信任 CA,且不要對外部 API 一律關閉驗證。",
|
|
||||||
"is_new": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"level": "warning",
|
|
||||||
"role": "Bard",
|
|
||||||
"location": "action.yaml:8",
|
|
||||||
"problem": "這個新加入的 `model` 輸入沒有任何描述,讓 action 的公開介面像是少了說明書的樂譜,讀者只能靠猜來理解它要吃什麼值。",
|
|
||||||
"suggestion": "補上一句簡短但明確的描述,例如說明這個欄位是「要使用的模型名稱」或「AI 助理 CLI 的模型識別字串」;讓使用者一眼看懂用途。",
|
|
||||||
"is_new": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"level": "warning",
|
|
||||||
"role": "Bard",
|
|
||||||
"location": "app/config.js:4",
|
|
||||||
"problem": "`process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'` 放在模組載入時就直接生效,這種全域副作用太早出聲,會讓讀者在還沒看到任何呼叫前,就先被迫接受一個隱晦的環境改寫。",
|
|
||||||
"suggestion": "把這種全域設定移到明確的啟動流程或初始化函式中,至少搭配更醒目的註解,讓副作用出現的位置和目的都更清楚。",
|
|
||||||
"is_new": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"level": "warning",
|
|
||||||
"role": "Bard",
|
|
||||||
"location": "app/config.js:25",
|
|
||||||
"problem": "`getInsecureHttpsAgent` 與 `getOpenCodeHttpsAgent` 同時存在,卻指向同一個物件來源,等於替同一段旋律寫了兩個名字,容易讓後續維護者搞不清楚哪個才是正規稱呼。",
|
|
||||||
"suggestion": "保留一個主名稱即可;如果一定要兼容舊名稱,請加上明確的遷移註解,說明哪個是新名字、哪個只是過渡別名。",
|
|
||||||
"is_new": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"level": "warning",
|
|
||||||
"role": "Bard",
|
|
||||||
"location": "app/llm.js:27",
|
|
||||||
"problem": "`cliArgs(provider, model, promptFile = null, prompt = null)` 這個簽名把不同 provider 的參數意義混在一起,光看位置很難分辨哪個值在某條分支才有用,讀起來像把三種節拍硬塞進同一小節。",
|
|
||||||
"suggestion": "改成物件參數或拆成各 provider 專用的組裝函式,讓每個欄位的用途能被名字直接說明,而不是靠呼叫順序猜測。",
|
|
||||||
"is_new": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"level": "warning",
|
|
||||||
"role": "Bard",
|
|
||||||
"location": "app/preflight.js:103",
|
|
||||||
"problem": "`verifyLLM` 的 JSDoc 仍然只描述 `provider` 與 `error`,但實際回傳已經多了 `command` 和 `model`。註解和程式碼不同步,會讓人以為這段函式比實際更單薄。",
|
|
||||||
"suggestion": "把 `@returns` 的型別與欄位補完整,讓文件精準反映目前回傳結構,避免讀者依舊沿用舊版心智模型。",
|
|
||||||
"is_new": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"level": "info",
|
|
||||||
"role": "Bard",
|
|
||||||
"location": "app/preflight.js:170",
|
|
||||||
"problem": "成功訊息仍寫成 `LLM provider=... CLI 可用`,語氣還停在 provider 中心,但整個流程已改成 CLI 中心,術語有些半新半舊,節奏不夠一致。",
|
|
||||||
"suggestion": "把成功訊息統一成 CLI 觀點,例如直接報 `command=...` 或改成更中性的「LLM CLI 可用」,讓文字系統保持同一把調性。",
|
|
||||||
"is_new": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ jobs:
|
|||||||
id: release-tag-version
|
id: release-tag-version
|
||||||
uses: https://gitea.jsc.idv.tw/composite-actions/release-tag-version@${{ vars.ACTION_RELEASE_TAG_VERSION }}
|
uses: https://gitea.jsc.idv.tw/composite-actions/release-tag-version@${{ vars.ACTION_RELEASE_TAG_VERSION }}
|
||||||
with:
|
with:
|
||||||
is_beta: 'true'
|
is_beta: true
|
||||||
# job 識別碼:ai-code-review
|
# job 識別碼:ai-code-review
|
||||||
ai-code-review:
|
ai-code-review:
|
||||||
# job 顯示名稱
|
# job 顯示名稱
|
||||||
@@ -34,43 +34,22 @@ jobs:
|
|||||||
needs: release-tag-version
|
needs: release-tag-version
|
||||||
# 指定執行環境的 runner 標籤:ubuntu
|
# 指定執行環境的 runner 標籤:ubuntu
|
||||||
runs-on: ubuntu
|
runs-on: ubuntu
|
||||||
# 此 job 所需的權限設定
|
|
||||||
permissions:
|
|
||||||
# 對 repository 內容的寫入權限(讀寫程式碼/檔案)
|
|
||||||
contents: write
|
|
||||||
# 對 pull request 的寫入權限(讓 AI 可在 PR 上留言/審查)
|
|
||||||
pull-requests: write
|
|
||||||
# 對 issues 的寫入權限(建立/更新 issue 留言所需)
|
|
||||||
issues: write
|
|
||||||
# job 的執行步驟
|
# job 的執行步驟
|
||||||
steps:
|
steps:
|
||||||
- name: 取得程式碼
|
- name: 取得程式碼
|
||||||
uses: actions/checkout@${{ vars.ACTION_CHECKOUT_VERSION }}
|
uses: actions/checkout@${{ vars.ACTION_CHECKOUT_VERSION }}
|
||||||
- name: 安裝 AI 工具
|
|
||||||
uses: https://gitea.jsc.idv.tw/composite-actions/codex@${{ vars.ACTION_CODEX_VERSION }}
|
|
||||||
with:
|
|
||||||
oauth: ${{ secrets.CODEX_OAUTH }}
|
|
||||||
- name: 檢查 AI 助理 CLI
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
set -e
|
|
||||||
tools="$(node --input-type=module -e "import { getLLMCLICommands } from './app/config.js'; console.log(getLLMCLICommands().join(' '));")"
|
|
||||||
found=""
|
|
||||||
for tool in $tools; do
|
|
||||||
if command -v "$tool" >/dev/null 2>&1; then
|
|
||||||
found="${found} ${tool}"
|
|
||||||
"$tool" --version || true
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
if [ -z "$found" ]; then
|
|
||||||
echo "找不到可用的 AI 助理 CLI(需要 ${tools} 其中之一)" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "可用 AI 助理 CLI:${found}"
|
|
||||||
- name: AI 程式碼審查
|
- name: AI 程式碼審查
|
||||||
# 使用外部 composite action 執行審查邏輯。
|
# 使用外部 composite action 執行審查邏輯。
|
||||||
# 版本由 release-tag-version 計算,便於在 PR 中測試目前 action 版本。
|
# 版本由 release-tag-version 計算,便於在 PR 中測試目前 action 版本。
|
||||||
uses: https://gitea.jsc.idv.tw/docker-actions/ai-code-review@v${{ needs.release-tag-version.outputs.version }}
|
uses: https://gitea.jsc.idv.tw/docker-actions/ai-code-review@v${{ needs.release-tag-version.outputs.version }}
|
||||||
|
# 此 job 所需的權限設定
|
||||||
|
permissions:
|
||||||
|
# 對 repository 內容的寫入權限(讀寫程式碼/檔案)
|
||||||
|
contents: write
|
||||||
|
# 對 pull request 的寫入權限(讓 AI 可在 PR 上留言/審查)
|
||||||
|
pull-requests: write
|
||||||
|
# 對 issues 的寫入權限(建立/更新 issue 留言所需)
|
||||||
|
issues: write
|
||||||
# 傳遞給 composite action 的輸入參數
|
# 傳遞給 composite action 的輸入參數
|
||||||
with:
|
with:
|
||||||
# 留言用 token:取自 secret COMMENT_TOKEN,供 action 在 PR 上發布審查留言
|
# 留言用 token:取自 secret COMMENT_TOKEN,供 action 在 PR 上發布審查留言
|
||||||
|
|||||||
+6
-26
@@ -1,34 +1,14 @@
|
|||||||
# =============================================================================
|
FROM gitea.jsc.idv.tw/images/codex:0.1.2
|
||||||
# 用途:建置「AI 程式碼審查」Docker action 映像檔。
|
|
||||||
# 以 Alpine Linux 為基底,安裝 bash / git / Node.js / npm 等執行環境,
|
|
||||||
# 將 app/ 程式碼與相依套件打包進映像,並透過 entrypoint.sh 作為容器進入點,
|
|
||||||
# 供 CI(Gitea Actions)以 Docker action 形式執行 AI code review 流程。
|
|
||||||
# 更新日期:2026/06/26 11:34:46
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
# 指定支援中的 Alpine Linux 固定版本;Alpine 體積小,可縮小最終映像大小並加快拉取速度。
|
RUN apt update \
|
||||||
# 釘選明確版本可避免 latest tag 隨時間變動,提升建置可重現性。
|
&& apt install -y --no-install-recommends git \
|
||||||
FROM alpine:3.24.1
|
&& apt clean \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# 安裝必要的工具
|
|
||||||
# 安裝執行 code review 所需的工具:bash(執行 entrypoint 腳本)、git(前置遠端驗證/取得 diff)、
|
|
||||||
# nodejs 與 npm(執行 app 內的 Node.js 程式)。
|
|
||||||
# --no-cache:不保留 apk 套件索引快取,避免殘留在映像層中以減少映像大小。
|
|
||||||
RUN apk add --no-cache bash git nodejs npm
|
|
||||||
|
|
||||||
# 將專案的 app/ 目錄複製到映像內的 /app;包含 Node.js 程式碼與 package.json 等相依宣告。
|
|
||||||
COPY ./app /app
|
COPY ./app /app
|
||||||
|
|
||||||
# 進入 /app 安裝 npm 相依套件,使 Node.js 程式可在容器內正常執行。
|
|
||||||
# 副作用:會在 /app/node_modules 產生套件檔案,並依 package-lock.json(若存在)解析版本。
|
|
||||||
RUN cd /app && npm install
|
RUN cd /app && npm install
|
||||||
|
|
||||||
# 將容器進入點腳本 entrypoint.sh 複製到映像根目錄 /entrypoint.sh。
|
COPY ./entrypoint.sh /entrypoint.sh
|
||||||
COPY entrypoint.sh /entrypoint.sh
|
|
||||||
|
|
||||||
# 賦予 entrypoint.sh 可執行權限,確保容器啟動時能直接執行該腳本。
|
|
||||||
RUN chmod +x /entrypoint.sh
|
RUN chmod +x /entrypoint.sh
|
||||||
|
|
||||||
# 設定容器進入點為 /entrypoint.sh(exec 形式,不經過 shell 解析);
|
|
||||||
# 容器啟動時即執行此腳本,作為 Docker action 的實際入口。
|
|
||||||
ENTRYPOINT ["/entrypoint.sh"]
|
ENTRYPOINT ["/entrypoint.sh"]
|
||||||
|
|||||||
+14
-25
@@ -4,34 +4,23 @@ author: 'Jeffery'
|
|||||||
inputs:
|
inputs:
|
||||||
token:
|
token:
|
||||||
description: ''
|
description: ''
|
||||||
required: true
|
required: false
|
||||||
comment_token:
|
comment_token:
|
||||||
description: ''
|
description: ''
|
||||||
required: false
|
required: false
|
||||||
model:
|
model:
|
||||||
description: ''
|
description: '要使用的 AI 助理 CLI 模型識別字串'
|
||||||
required: true
|
required: true
|
||||||
runs:
|
runs:
|
||||||
using: 'composite'
|
using: 'docker'
|
||||||
steps:
|
image: 'Dockerfile'
|
||||||
- name: 安裝 Node.js 相依套件
|
env:
|
||||||
shell: bash
|
GITEA_SERVER_URL: ${{ gitea.server_url }}
|
||||||
run: |
|
GITEA_REPOSITORY: ${{ gitea.repository }}
|
||||||
cd "${GITHUB_ACTION_PATH:-${GITEA_ACTION_PATH:-.}}/app"
|
GITEA_TOKEN: ${{ inputs.token || gitea.token }}
|
||||||
npm ci
|
GITEA_COMMENT_TOKEN: ${{ inputs.comment_token || inputs.token || gitea.token }}
|
||||||
- name: 執行 AI 程式碼審查
|
PR_NUMBER: ${{ gitea.event.pull_request.number }}
|
||||||
shell: bash
|
PR_HEAD_SHA: ${{ gitea.event.pull_request.head.sha }}
|
||||||
env:
|
PR_HEAD_BRANCH: ${{ gitea.event.pull_request.head.ref }}
|
||||||
GITEA_SERVER_URL: ${{ gitea.server_url }}
|
PR_BASE_BRANCH: ${{ gitea.event.pull_request.base.ref }}
|
||||||
GITEA_REPOSITORY: ${{ gitea.repository }}
|
MODEL: ${{ inputs.model }}
|
||||||
GITEA_TOKEN: ${{ inputs.token || gitea.token }}
|
|
||||||
GITEA_COMMENT_TOKEN: ${{ inputs.comment_token || inputs.token || gitea.token }}
|
|
||||||
PR_NUMBER: ${{ gitea.event.pull_request.number }}
|
|
||||||
PR_HEAD_SHA: ${{ gitea.event.pull_request.head.sha }}
|
|
||||||
PR_HEAD_BRANCH: ${{ gitea.event.pull_request.head.ref }}
|
|
||||||
PR_BASE_BRANCH: ${{ gitea.event.pull_request.base.ref }}
|
|
||||||
MODEL: ${{ inputs.model }}
|
|
||||||
run: |
|
|
||||||
echo "🚀 AI Code Review Action 啟動"
|
|
||||||
cd "${GITHUB_ACTION_PATH:-${GITEA_ACTION_PATH:-.}}/app"
|
|
||||||
node main.js
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import https from 'https';
|
import https from 'https';
|
||||||
import { execFileSync } from 'child_process';
|
import { execFileSync } from 'child_process';
|
||||||
|
|
||||||
|
// 本 action 會連接自架 Gitea / OpenCode,部署環境可能使用內部 CA 或自簽憑證。
|
||||||
|
// 對外部服務請優先使用預設 TLS 驗證;需要內部服務相容時才使用 getInsecureHttpsAgent()。
|
||||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
||||||
|
|
||||||
export const GITEA_TOKEN = process.env.GITEA_TOKEN || '';
|
export const GITEA_TOKEN = process.env.GITEA_TOKEN || '';
|
||||||
@@ -29,6 +31,7 @@ export function getInsecureHttpsAgent() {
|
|||||||
return (_insecureHttpsAgent ??= new https.Agent({ rejectUnauthorized: false }));
|
return (_insecureHttpsAgent ??= new https.Agent({ rejectUnauthorized: false }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 過渡別名:既有呼叫端仍可用 OpenCode 語意名稱;新程式碼請直接使用 getInsecureHttpsAgent。
|
||||||
export const getOpenCodeHttpsAgent = getInsecureHttpsAgent;
|
export const getOpenCodeHttpsAgent = getInsecureHttpsAgent;
|
||||||
|
|
||||||
const CLI_CANDIDATES = [
|
const CLI_CANDIDATES = [
|
||||||
|
|||||||
@@ -64,7 +64,6 @@ function withAskpass(workspace, fn, token = GITEA_TOKEN) {
|
|||||||
const credEnv = {
|
const credEnv = {
|
||||||
...process.env,
|
...process.env,
|
||||||
GIT_ASKPASS: askpassScript,
|
GIT_ASKPASS: askpassScript,
|
||||||
GIT_SSL_NO_VERIFY: 'true',
|
|
||||||
GIT_USERNAME: 'x-token',
|
GIT_USERNAME: 'x-token',
|
||||||
GIT_TOKEN: token,
|
GIT_TOKEN: token,
|
||||||
};
|
};
|
||||||
|
|||||||
+2
-2
@@ -23,7 +23,7 @@ function buildPrompt(systemPrompt, userContent) {
|
|||||||
].join('\n');
|
].join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
function cliArgs(provider, model, promptFile = null, prompt = null) {
|
function cliArgs({ provider, model, promptFile = null, prompt = null }) {
|
||||||
if (provider === 'codex') {
|
if (provider === 'codex') {
|
||||||
return ['exec', '--model', model, '--sandbox', 'read-only', '--skip-git-repo-check', '-'];
|
return ['exec', '--model', model, '--sandbox', 'read-only', '--skip-git-repo-check', '-'];
|
||||||
}
|
}
|
||||||
@@ -53,7 +53,7 @@ async function runAssistantCLI({ provider, command, model }, prompt) {
|
|||||||
promptFile = join(tempDir, 'prompt.md');
|
promptFile = join(tempDir, 'prompt.md');
|
||||||
await writeFile(promptFile, prompt);
|
await writeFile(promptFile, prompt);
|
||||||
}
|
}
|
||||||
const args = cliArgs(provider, model, promptFile, prompt);
|
const args = cliArgs({ provider, model, promptFile, prompt });
|
||||||
const maxBuffer = Number(process.env.AI_ASSISTANT_MAX_BUFFER || 20 * 1024 * 1024);
|
const maxBuffer = Number(process.env.AI_ASSISTANT_MAX_BUFFER || 20 * 1024 * 1024);
|
||||||
const timeout = Number(process.env.AI_ASSISTANT_TIMEOUT_MS || 15 * 60 * 1000);
|
const timeout = Number(process.env.AI_ASSISTANT_TIMEOUT_MS || 15 * 60 * 1000);
|
||||||
try {
|
try {
|
||||||
|
|||||||
+6
-3
@@ -100,8 +100,11 @@ export async function verifyCommentToken(token = GITEA_COMMENT_TOKEN) {
|
|||||||
*
|
*
|
||||||
* 確認目前環境可偵測到支援的 CLI,且已解析出 model。實際模型可用性由 CLI
|
* 確認目前環境可偵測到支援的 CLI,且已解析出 model。實際模型可用性由 CLI
|
||||||
* 在正式呼叫時回報;preflight 不主動送 prompt,避免額外消耗額度。
|
* 在正式呼叫時回報;preflight 不主動送 prompt,避免額外消耗額度。
|
||||||
* @returns {Promise<{ok: true, provider: string}|{ok: false, provider?: string, error: string}>}
|
* @returns {Promise<
|
||||||
* 通過時含 provider;未設定 provider 的失敗分支不含 provider 欄位。
|
* {ok: true, provider: string, command: string, model: string} |
|
||||||
|
* {ok: false, provider?: string, error: string}
|
||||||
|
* >}
|
||||||
|
* 通過時含 provider、command 與 model;未設定 provider 的失敗分支不含 provider 欄位。
|
||||||
* @remarks 設定來源為 config.js 的 getLLMConfig()。
|
* @remarks 設定來源為 config.js 的 getLLMConfig()。
|
||||||
*/
|
*/
|
||||||
export async function verifyLLM() {
|
export async function verifyLLM() {
|
||||||
@@ -170,7 +173,7 @@ export async function runPreflight(workspace = process.env.GITHUB_WORKSPACE || '
|
|||||||
error(`LLM 驗證失敗: ${llm.error}`);
|
error(`LLM 驗證失敗: ${llm.error}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ok(`LLM provider=${llm.provider} CLI 可用`);
|
ok(`LLM CLI 可用(command=${llm.command}, provider=${llm.provider}, model=${llm.model})`);
|
||||||
|
|
||||||
result(true, '前置驗證通過');
|
result(true, '前置驗證通過');
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ describe('commitAndPush', () => {
|
|||||||
|
|
||||||
for (const { args, opts } of networkCalls) {
|
for (const { args, opts } of networkCalls) {
|
||||||
assert.ok(opts?.env?.GIT_ASKPASS, `GIT_ASKPASS missing for git ${args[0]}`);
|
assert.ok(opts?.env?.GIT_ASKPASS, `GIT_ASKPASS missing for git ${args[0]}`);
|
||||||
assert.equal(opts.env.GIT_SSL_NO_VERIFY, 'true', `GIT_SSL_NO_VERIFY missing for git ${args[0]}`);
|
assert.equal(opts.env.GIT_SSL_NO_VERIFY, undefined, `GIT_SSL_NO_VERIFY must not be forced for git ${args[0]}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -267,7 +267,7 @@ describe('cloneRepo', () => {
|
|||||||
assert.ok(networkCalls.length > 0, 'expected at least one network git call');
|
assert.ok(networkCalls.length > 0, 'expected at least one network git call');
|
||||||
for (const { args, opts } of networkCalls) {
|
for (const { args, opts } of networkCalls) {
|
||||||
assert.ok(opts?.env?.GIT_ASKPASS, `GIT_ASKPASS missing for git ${args[0]}`);
|
assert.ok(opts?.env?.GIT_ASKPASS, `GIT_ASKPASS missing for git ${args[0]}`);
|
||||||
assert.equal(opts.env.GIT_SSL_NO_VERIFY, 'true', `GIT_SSL_NO_VERIFY missing for git ${args[0]}`);
|
assert.equal(opts.env.GIT_SSL_NO_VERIFY, undefined, `GIT_SSL_NO_VERIFY must not be forced for git ${args[0]}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -310,7 +310,7 @@ describe('verifyRemoteAccess', () => {
|
|||||||
const lsRemote = calls.find(c => c.args[0] === 'ls-remote');
|
const lsRemote = calls.find(c => c.args[0] === 'ls-remote');
|
||||||
assert.ok(lsRemote, 'expected git ls-remote to run');
|
assert.ok(lsRemote, 'expected git ls-remote to run');
|
||||||
assert.ok(lsRemote.opts?.env?.GIT_ASKPASS, 'expected GIT_ASKPASS env for ls-remote');
|
assert.ok(lsRemote.opts?.env?.GIT_ASKPASS, 'expected GIT_ASKPASS env for ls-remote');
|
||||||
assert.equal(lsRemote.opts.env.GIT_SSL_NO_VERIFY, 'true');
|
assert.equal(lsRemote.opts.env.GIT_SSL_NO_VERIFY, undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not leak the token in ls-remote args', () => {
|
it('does not leak the token in ls-remote args', () => {
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ describe('fetchAccountQuota', () => {
|
|||||||
const get = async (url, opts) => {
|
const get = async (url, opts) => {
|
||||||
assert.match(url, /openrouter\.ai\/api\/v1\/auth\/key$/);
|
assert.match(url, /openrouter\.ai\/api\/v1\/auth\/key$/);
|
||||||
assert.equal(opts.headers.Authorization, 'Bearer sk-or-xxx');
|
assert.equal(opts.headers.Authorization, 'Bearer sk-or-xxx');
|
||||||
assert.equal(opts.httpsAgent.options.rejectUnauthorized, false);
|
assert.equal(opts.httpsAgent, undefined);
|
||||||
return { data: { data: { usage: 12.4, limit: 100, limit_remaining: 87.6 } } };
|
return { data: { data: { usage: 12.4, limit: 100, limit_remaining: 87.6 } } };
|
||||||
};
|
};
|
||||||
const q = await fetchAccountQuota('openai', { apiKeys: ['sk-or-xxx'], baseURL: 'https://openrouter.ai/api/v1' }, { get });
|
const q = await fetchAccountQuota('openai', { apiKeys: ['sk-or-xxx'], baseURL: 'https://openrouter.ai/api/v1' }, { get });
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { getInsecureHttpsAgent } from './config.js';
|
|
||||||
import { warn } from './log.js';
|
import { warn } from './log.js';
|
||||||
|
|
||||||
/** 本次執行的 token 累計(跨所有 LLM 呼叫)。 */
|
/** 本次執行的 token 累計(跨所有 LLM 呼叫)。 */
|
||||||
@@ -177,7 +176,6 @@ async function fetchOpenRouterQuota({ apiKey, baseURL }, get) {
|
|||||||
const resp = await get(`${stripSlash(baseURL)}/auth/key`, {
|
const resp = await get(`${stripSlash(baseURL)}/auth/key`, {
|
||||||
headers: { Authorization: `Bearer ${apiKey}` },
|
headers: { Authorization: `Bearer ${apiKey}` },
|
||||||
timeout: 30000,
|
timeout: 30000,
|
||||||
httpsAgent: getInsecureHttpsAgent(),
|
|
||||||
});
|
});
|
||||||
const d = resp.data?.data || {};
|
const d = resp.data?.data || {};
|
||||||
const used = num(d.usage);
|
const used = num(d.usage);
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "ai-code-review",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "AI code review Gitea action",
|
|
||||||
"private": true,
|
|
||||||
"scripts": {
|
|
||||||
"test": "npm --prefix app test"
|
|
||||||
},
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://gitea.jsc.idv.tw/docker-actions/ai-code-review.git"
|
|
||||||
},
|
|
||||||
"author": "Jeffery",
|
|
||||||
"license": "ISC"
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user