From b778086f4a417cf0c6f4bee8d6eed8c6ad7f5a8b Mon Sep 17 00:00:00 2001 From: uncle6me-web Date: Sat, 13 Jun 2026 14:45:30 +0800 Subject: [PATCH] =?UTF-8?q?perf(self-hosted):=20acr=20update=20=E5=85=B1?= =?UTF-8?q?=E4=BA=AB=E4=B8=80=E6=AC=A1=20install=20=E5=8F=96=E4=BB=A3=2023?= =?UTF-8?q?=20=E5=80=8B=20worker=20=E5=90=84=E8=A3=9D=20324MB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 壓測 2026-06-12 真因:每個 worker 各 pnpm install ~324MB node_modules(23× 重複, 全是 hono+wrangler)→ 好幾分鐘。改成 tarball root 裝一次(hono+wrangler+tier2 額外 zod/mcp-sdk/yaml),各 worker 靠 node 往上 resolve(dry-run 驗證 tier1+tier2 都 bundle 成功)。 207MB×1 取代 324MB×23。疊加 manifest 跳過 → 第二次 update 近乎瞬間。 共享失敗自動退回各 worker 自裝(不破壞既有路徑)。 Co-Authored-By: Claude Opus 4.8 --- cli/src/lib/deploy.ts | 44 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/cli/src/lib/deploy.ts b/cli/src/lib/deploy.ts index 64aab4d..3a5328c 100644 --- a/cli/src/lib/deploy.ts +++ b/cli/src/lib/deploy.ts @@ -153,6 +153,35 @@ export async function downloadAndDeploy( return { implemented: true, message: `部署物中找不到任何 wrangler.toml(root=${root})。` }; } + // 2.5 共享依賴:23 個 component worker 的 runtime dep 全是 hono、devDep 全含 wrangler, + // 舊版每個 worker 各 install 一份 ~324MB node_modules(23× 重複,壓測 2026-06-12 慢的真因)。 + // 改成在 tarball root 裝「一次」hono+wrangler;component 目錄靠 node 往上 resolve(已驗證可行)。 + // → 23×4.4s install 變 1×17s。失敗不致命:退回各 worker 自裝(runWranglerDeploy 仍有 fallback)。 + let sharedBin = ''; + try { + process.stdout.write(chalk.gray(' → 安裝共享部署依賴(一次,取代每個 worker 各裝)...')); + // 含全部 worker 的 runtime deps(tier1 component 只要 hono;tier2 cypher/registry/mcp/kbdb + // 另需 zod / @hono/zod-openapi / @modelcontextprotocol/sdk / js-yaml / yaml)→ 全裝 root, + // 各 worker 往上 resolve,esbuild bundle 找得到。漏一個會讓該 worker deploy 失敗,故寧可多列。 + writeFileSync( + join(root, 'package.json'), + JSON.stringify({ name: 'arcrun-deploy-shared', private: true, type: 'module', + dependencies: { + hono: '^4.7.0', wrangler: '^4.0.0', zod: '^3.23.0', + '@hono/zod-openapi': '^0.18.0', '@modelcontextprotocol/sdk': '^1.0.0', + 'js-yaml': '^4.1.0', yaml: '^2.4.0', + } }), + ); + execFileSync('npm', ['install', '--no-audit', '--no-fund'], + { cwd: root, stdio: ['ignore', 'ignore', 'pipe'] }); + sharedBin = join(root, 'node_modules', '.bin', 'wrangler'); + console.log(existsSync(sharedBin) ? chalk.green(' ✓') : chalk.yellow(' ⚠ 退回各 worker 自裝')); + if (!existsSync(sharedBin)) sharedBin = ''; + } catch (e) { + const tail = (e as { stderr?: Buffer }).stderr?.toString().trim().split('\n').slice(-2).join(' | ').slice(0, 200) ?? ''; + console.log(chalk.yellow(` ⚠ 共享安裝失敗,退回各 worker 自裝${tail ? `:${tail}` : ''}`)); + } + // 3. 對每個 worker:注入 KV id(+ cypher WORKER_SUBDOMAIN)→ wrangler deploy。tier1 先 tier2 後。 // 逐 worker 串流進度(每個含 pnpm install + wrangler deploy,沉默會讓人以為卡住—— // 壓測 2026-06-11 richblack 觀察:「D1 ✓」後停很久其實在這個迴圈靜默部署 20+ worker)。 @@ -178,7 +207,7 @@ export async function downloadAndDeploy( console.log(chalk.gray(' ⊘ 未變動,跳過')); continue; } - runWranglerDeploy(dir, ctx); + runWranglerDeploy(dir, ctx, sharedBin); manifest[label] = hash; // 只在成功後記錄 → 失敗者下次必重試 saveManifest(manifest); deployed++; @@ -400,16 +429,19 @@ export function stripOfficialOnlyBindings(toml: string): string { return out.join('\n'); } -/** 在 worker 目錄跑 wrangler deploy(用用戶的 CF token + account)。*/ -function runWranglerDeploy(dir: string, ctx: DeployContext): void { - // 先裝依賴(cypher-executor/registry 是 TS,wrangler 內建 esbuild bundle 需 node_modules) - if (existsSync(join(dir, 'package.json'))) { +/** 在 worker 目錄跑 wrangler deploy(用用戶的 CF token + account)。 + * sharedBin:root 共享 wrangler binary 路徑(見 downloadAndDeploy 2.5)。有則用它且**跳過本地 install** + * (deps 從 root node_modules 往上 resolve);空字串則退回舊行為(各 worker 自裝)。*/ +function runWranglerDeploy(dir: string, ctx: DeployContext, sharedBin = ''): void { + if (!sharedBin && existsSync(join(dir, 'package.json'))) { + // fallback:共享安裝失敗時才走這條,各 worker 自裝 const installer = existsSync(join(dir, 'pnpm-lock.yaml')) ? ['pnpm', 'install', '--frozen-lockfile'] : ['npm', 'install', '--no-audit', '--no-fund']; runStep(installer[0], installer.slice(1), dir, process.env); } - runStep('wrangler', ['deploy'], dir, { + const wranglerCmd = sharedBin || 'wrangler'; + runStep(wranglerCmd, ['deploy'], dir, { ...process.env, CLOUDFLARE_API_TOKEN: ctx.apiToken, CLOUDFLARE_ACCOUNT_ID: ctx.accountId,