fix(cli): acr update 假綠與沉默修復——部署部分失敗必須印出、deploy 迴圈逐 worker 進度、migrate 錯誤印 server 回應
- update.ts: result.message 含失敗清單時不再被「✓ 部署完成」蓋掉(404 重跑 3 次找不到根因的元兇) - update.ts: migrate-cron-index 非 2xx 印 response body 前 200 字 + 404/500 含義提示 - deploy.ts: downloadAndDeploy 逐 worker 印 [i/N] 進度(原本 20+ worker 靜默部署像當機) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -84,7 +84,15 @@ export async function cmdUpdate(): Promise<void> {
|
|||||||
const result = await downloadAndDeploy(ctx);
|
const result = await downloadAndDeploy(ctx);
|
||||||
|
|
||||||
if (result.implemented) {
|
if (result.implemented) {
|
||||||
|
// message 含部分失敗清單(「部署 X/Y 成功,N 失敗:✗ ...」)——必須印出來,
|
||||||
|
// 否則 worker 失敗被綠勾蓋掉(假綠):cypher 沒部上 → 後面 migrate 打舊 worker 404,
|
||||||
|
// 用戶重跑 N 次都不知道根因(壓測 2026-06-11 實證)。
|
||||||
|
if (result.message?.includes('失敗')) {
|
||||||
|
console.log(chalk.yellow(`\n ⚠ 部署部分失敗:`));
|
||||||
|
console.log(chalk.yellow(' ' + result.message.split('\n').join('\n ')));
|
||||||
|
} else {
|
||||||
console.log(chalk.green('\n ✓ 部署完成'));
|
console.log(chalk.green('\n ✓ 部署完成'));
|
||||||
|
}
|
||||||
// 重跑 seed(薄殼:呼叫 API /init/seed;冪等,覆寫既有)。
|
// 重跑 seed(薄殼:呼叫 API /init/seed;冪等,覆寫既有)。
|
||||||
// 修壓測 §4.1.3「update 不做 seed,但 init 提示說 update 會重試 seed」的矛盾。
|
// 修壓測 §4.1.3「update 不做 seed,但 init 提示說 update 會重試 seed」的矛盾。
|
||||||
const cypherUrl = config.cypher_executor_url
|
const cypherUrl = config.cypher_executor_url
|
||||||
@@ -108,10 +116,18 @@ export async function cmdUpdate(): Promise<void> {
|
|||||||
process.stdout.write(chalk.gray(' → 遷移 cron index(舊 per-key → 集中 key,冪等)...'));
|
process.stdout.write(chalk.gray(' → 遷移 cron index(舊 per-key → 集中 key,冪等)...'));
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${cypherUrl}/webhooks/named/migrate-cron-index`, { method: 'POST' });
|
const res = await fetch(`${cypherUrl}/webhooks/named/migrate-cron-index`, { method: 'POST' });
|
||||||
const body = await res.json().catch(() => null) as { success?: boolean; migrated?: number; skipped?: number } | null;
|
const rawText = await res.text();
|
||||||
console.log(res.ok && body?.success
|
let body: { success?: boolean; migrated?: number; skipped?: number; errors?: string[] } | null = null;
|
||||||
? chalk.green(` ✓ migrated ${body.migrated ?? 0}, skipped ${body.skipped ?? 0}`)
|
try { body = JSON.parse(rawText); } catch { /* 非 JSON(如 CF 錯誤頁)→ 用原文 */ }
|
||||||
: chalk.yellow(` ⚠ HTTP ${res.status}(可重跑 acr update)`));
|
if (res.ok && body?.success) {
|
||||||
|
console.log(chalk.green(` ✓ migrated ${body.migrated ?? 0}, skipped ${body.skipped ?? 0}`));
|
||||||
|
} else {
|
||||||
|
// 印 server 回的錯誤內容(截前 200 字)——只回 HTTP status 沒人能診斷
|
||||||
|
// (壓測 2026-06-11:404→500 重跑 3 次都看不到根因)。
|
||||||
|
const detail = (body?.errors?.join('; ') ?? rawText).slice(0, 200);
|
||||||
|
console.log(chalk.yellow(` ⚠ HTTP ${res.status}${detail ? `:${detail}` : ''}`));
|
||||||
|
console.log(chalk.yellow(' 404 = cypher worker 還是舊版(看上方部署是否有失敗);500 = server 端錯誤(看上行錯誤內容)'));
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(chalk.yellow(` ⚠ cron index 遷移失敗(${e instanceof Error ? e.message : e})`));
|
console.log(chalk.yellow(` ⚠ cron index 遷移失敗(${e instanceof Error ? e.message : e})`));
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-1
@@ -101,16 +101,25 @@ export async function downloadAndDeploy(ctx: DeployContext, ref = 'main'): Promi
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 3. 對每個 worker:注入 KV id(+ cypher WORKER_SUBDOMAIN)→ wrangler deploy。tier1 先 tier2 後。
|
// 3. 對每個 worker:注入 KV id(+ cypher WORKER_SUBDOMAIN)→ wrangler deploy。tier1 先 tier2 後。
|
||||||
|
// 逐 worker 串流進度(每個含 pnpm install + wrangler deploy,沉默會讓人以為卡住——
|
||||||
|
// 壓測 2026-06-11 richblack 觀察:「D1 ✓」後停很久其實在這個迴圈靜默部署 20+ worker)。
|
||||||
|
const allDirs = [...tier1, ...tier2];
|
||||||
const failures: string[] = [];
|
const failures: string[] = [];
|
||||||
let deployed = 0;
|
let deployed = 0;
|
||||||
for (const dir of [...tier1, ...tier2]) {
|
console.log(chalk.gray(` → 部署 ${allDirs.length} 個 worker(每個含 install + deploy,依序進行)...`));
|
||||||
|
for (let i = 0; i < allDirs.length; i++) {
|
||||||
|
const dir = allDirs[i];
|
||||||
const tomlPath = join(dir, 'wrangler.toml');
|
const tomlPath = join(dir, 'wrangler.toml');
|
||||||
|
const label = dir.replace(/^.*\.component-builds\//, '').replace(/^.*\//, '');
|
||||||
|
process.stdout.write(chalk.gray(` [${i + 1}/${allDirs.length}] ${label} ...`));
|
||||||
try {
|
try {
|
||||||
injectWranglerConfig(tomlPath, ctx);
|
injectWranglerConfig(tomlPath, ctx);
|
||||||
runWranglerDeploy(dir, ctx);
|
runWranglerDeploy(dir, ctx);
|
||||||
deployed++;
|
deployed++;
|
||||||
|
console.log(chalk.green(' ✓'));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
failures.push(`${dir}: ${e instanceof Error ? e.message : String(e)}`);
|
failures.push(`${dir}: ${e instanceof Error ? e.message : String(e)}`);
|
||||||
|
console.log(chalk.yellow(' ⚠'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user