diff --git a/cli/src/commands/update.ts b/cli/src/commands/update.ts index 596228b..09441c9 100644 --- a/cli/src/commands/update.ts +++ b/cli/src/commands/update.ts @@ -84,7 +84,15 @@ export async function cmdUpdate(): Promise { const result = await downloadAndDeploy(ctx); if (result.implemented) { - console.log(chalk.green('\n ✓ 部署完成')); + // 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 ✓ 部署完成')); + } // 重跑 seed(薄殼:呼叫 API /init/seed;冪等,覆寫既有)。 // 修壓測 §4.1.3「update 不做 seed,但 init 提示說 update 會重試 seed」的矛盾。 const cypherUrl = config.cypher_executor_url @@ -108,10 +116,18 @@ export async function cmdUpdate(): Promise { process.stdout.write(chalk.gray(' → 遷移 cron index(舊 per-key → 集中 key,冪等)...')); try { 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; - console.log(res.ok && body?.success - ? chalk.green(` ✓ migrated ${body.migrated ?? 0}, skipped ${body.skipped ?? 0}`) - : chalk.yellow(` ⚠ HTTP ${res.status}(可重跑 acr update)`)); + const rawText = await res.text(); + let body: { success?: boolean; migrated?: number; skipped?: number; errors?: string[] } | null = null; + try { body = JSON.parse(rawText); } catch { /* 非 JSON(如 CF 錯誤頁)→ 用原文 */ } + 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) { console.log(chalk.yellow(` ⚠ cron index 遷移失敗(${e instanceof Error ? e.message : e})`)); } diff --git a/cli/src/lib/deploy.ts b/cli/src/lib/deploy.ts index 9793238..1577d0f 100644 --- a/cli/src/lib/deploy.ts +++ b/cli/src/lib/deploy.ts @@ -101,16 +101,25 @@ export async function downloadAndDeploy(ctx: DeployContext, ref = 'main'): Promi } // 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[] = []; 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 label = dir.replace(/^.*\.component-builds\//, '').replace(/^.*\//, ''); + process.stdout.write(chalk.gray(` [${i + 1}/${allDirs.length}] ${label} ...`)); try { injectWranglerConfig(tomlPath, ctx); runWranglerDeploy(dir, ctx); deployed++; + console.log(chalk.green(' ✓')); } catch (e) { failures.push(`${dir}: ${e instanceof Error ? e.message : String(e)}`); + console.log(chalk.yellow(' ⚠')); } }