794e3eba3a
動機: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
73 lines
3.3 KiB
TypeScript
73 lines
3.3 KiB
TypeScript
/**
|
||
* deploy-url.test.ts — 離線驗證下載源組裝(Arcrun#4:codeload → Gitea archive)。
|
||
*
|
||
* 涵蓋純函式:buildArchiveUrl(URL 組裝)、buildDownloadHeaders(token → header)。
|
||
* 打真 Gitea 下載/部署是 leo 的閘,不在單元測試做。
|
||
*
|
||
* 用 Node 內建 test runner(node --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:無 token(public 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('buildDownloadHeaders:ARCRUN_GITEA_TOKEN 優先於 GITEA_TOKEN(env 機制,不寫死)', () => {
|
||
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);
|
||
});
|