Files
Arcrun/cli/tests/deploy-url.test.ts
T
Leo 794e3eba3a fix(cli): acr update/deploy 下載源由 GitHub codeload 改指 Gitea archive(Arcrun#4)
動機:D20 防 flag 鐵律下 self-hosted 用戶(如 Mira)不能碰 GitHub,
而「init 之後才新增的零件」(如 code 零件)唯一重裝管道 acr update 綁死
GitHub codeload → 沒有乾淨重裝路徑。

改動(只碰 CLI 下載邏輯,不動 README/cypher/mcp/tools):
- deploy.ts:ARCRUN_REPO 預設 uncle6me-web/Arcrun → Leo/Arcrun;
  新增 ARCRUN_GITEA_BASE(預設 https://git.uncle6.me)與 giteaToken()
  (ARCRUN_GITEA_TOKEN > GITEA_TOKEN,不寫死)。
- 抽出純函式 buildArchiveUrl / buildDownloadHeaders 走 Gitea archive API
  GET {base}/api/v1/repos/{owner}/{repo}/archive/{ref}.tar.gz,保留 #13 P2
  cache-buster + no-cache 防 stale;private repo 帶 Authorization: token。
- downloadRepoTarball 改用上述;401/403 給「設 GITEA_TOKEN」提示。
- update.ts docstring 對齊(GitHub release → Gitea archive;標註 install≈update)。

install≈update:init 與 update 共用 downloadAndDeploy,其內容指紋 manifest
天然「新零件補、內容未變者略過」,故用戶跑 acr update 即補裝新零件。

測試:cli/tests/deploy-url.test.ts(Node 內建 test runner,零新依賴)8 顆全綠,
涵蓋 URL 組裝、repo/base 覆蓋、token→header、public 無 token、env 優先序。
真正打 Gitea 下載/部署是 leo 的閘,不在本 PR。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015d5jDbuqT5Htwv3Q88XXKk
2026-07-07 08:13:05 +00:00

73 lines
3.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* deploy-url.test.ts — 離線驗證下載源組裝(Arcrun#4codeload → Gitea archive)。
*
* 涵蓋純函式:buildArchiveUrlURL 組裝)、buildDownloadHeaderstoken → header)。
* 打真 Gitea 下載/部署是 leo 的閘,不在單元測試做。
*
* 用 Node 內建 test runnernode --test,零額外依賴;Node ≥22.18 自帶 TS type-stripping)。
*/
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { buildArchiveUrl, buildDownloadHeaders } from '../src/lib/deploy.ts';
test('buildArchiveUrl 指向 Gitea archive API,不再是 GitHub codeload', () => {
const url = buildArchiveUrl('main', 'bust123');
assert.ok(url.startsWith('https://git.uncle6.me/api/v1/repos/Leo/Arcrun/archive/main.tar.gz'),
`預期 Gitea archive API,實得:${url}`);
assert.ok(!/codeload|github\.com/.test(url), `不應含 GitHub 下載源:${url}`);
});
test('buildArchiveUrl 帶 cache-buster query#13 P2 stale 防護沿用)', () => {
const url = buildArchiveUrl('main', 'abc def'); // 含空白 → 應被 encode
assert.match(url, /[?&]_cb=abc%20def$/);
});
test('buildArchiveUrl ref 進 archive 檔名(tag/branch 皆走同路徑)', () => {
const url = buildArchiveUrl('v1.2.3', 'x');
assert.ok(url.includes('/archive/v1.2.3.tar.gz?'), url);
});
test('buildArchiveUrl 可用 repo / base 覆蓋(fork/自架站台)', () => {
const url = buildArchiveUrl('main', 'x', 'Mira/Arcrun', 'https://gitea.example.org');
assert.ok(url.startsWith('https://gitea.example.org/api/v1/repos/Mira/Arcrun/archive/main.tar.gz'), url);
});
test('buildDownloadHeaders:有 token → 帶 Gitea 慣用 Authorization: token <T>', () => {
const h = buildDownloadHeaders('secrettok');
assert.equal(h.Authorization, 'token secrettok');
assert.equal(h['Cache-Control'], 'no-cache');
assert.equal(h.Pragma, 'no-cache');
});
test('buildDownloadHeaders:無 tokenpublic repo)→ 不帶 Authorization,仍 no-cache', () => {
// 顯式傳 undefined 會觸發預設參數 giteaToken()(讀 env);要驗 public 路徑需清掉 env token。
const saved = { a: process.env.ARCRUN_GITEA_TOKEN, g: process.env.GITEA_TOKEN };
delete process.env.ARCRUN_GITEA_TOKEN;
delete process.env.GITEA_TOKEN;
try {
const h = buildDownloadHeaders(); // 走預設 → giteaToken() → undefined
assert.equal(h.Authorization, undefined);
assert.equal(h['Cache-Control'], 'no-cache');
} finally {
if (saved.a !== undefined) process.env.ARCRUN_GITEA_TOKEN = saved.a;
if (saved.g !== undefined) process.env.GITEA_TOKEN = saved.g;
}
});
test('buildDownloadHeadersARCRUN_GITEA_TOKEN 優先於 GITEA_TOKENenv 機制,不寫死)', () => {
const saved = { a: process.env.ARCRUN_GITEA_TOKEN, g: process.env.GITEA_TOKEN };
process.env.ARCRUN_GITEA_TOKEN = 'preferred';
process.env.GITEA_TOKEN = 'fallback';
try {
assert.equal(buildDownloadHeaders().Authorization, 'token preferred');
} finally {
if (saved.a === undefined) delete process.env.ARCRUN_GITEA_TOKEN; else process.env.ARCRUN_GITEA_TOKEN = saved.a;
if (saved.g === undefined) delete process.env.GITEA_TOKEN; else process.env.GITEA_TOKEN = saved.g;
}
});
test('buildDownloadHeaders:空字串 token 視為無(避免送出 "token "', () => {
const h = buildDownloadHeaders('');
assert.equal(h.Authorization, undefined);
});