fix(cli): 更新完還看得到版本號——CLI 重部署不再把版本標籤(和你的設定)洗掉

leo 08-12 實撞:更新完 leo21c,Portal 設定頁的「版本」變成
「無法讀取目前版本(知識庫服務可能正在啟動)」。版本號是 leo 唯一的驗收介面,
看不到就等於他無法自己確認任何一次更新有沒有生效。

根因(Arcrun#106):`bundle_version` 來自部署時注入的 plain_text var
`ARCRUN_BUNDLE_VERSION`,而**只有安裝器會注入**。wrangler deploy 是整份覆蓋,
toml 沒寫的 var 直接消失 ⇒ CLI 更新那條路每跑一次就把標籤洗掉一次。
#97 修好了「櫃子」(KV/D1/Vectorize 沿用既有),沒修「櫃子上的標籤」。

修法(兩種 var 走相反的規則,這是本次的判斷):
· 設定類 var = 使用者實例的事實 → **沿用**(讀綁定時同一份回應就帶回來,不多打 API)
  ——把 #97「已部署的 worker 上綁著什麼就是事實」原封不動套用到 plain_text var。
· 版本標籤 = 這份成品的屬性 → **每趟重烙,絕不沿用舊值**。
  沿用舊值會得到一個永遠停在安裝當天的假標籤——比沒有標籤更糟,
  因為它會讓人以為驗收過了。
  版號取部署當下發行頻道公告的 release(Portal/daemon 就是拿它當「最新版」比),
  另外把**真正部署的 commit** 一起烙上去(/health 多吐 `bundle_commit`)→ 漂掉查得出來。
  查不到 release 就誠實退成 `YYYY-MM-DD+<commit7>`,不掰一個 semver 假裝已是最新。

順帶(都是同一條路上的東西):
· ref 先解析成 commit sha 再用 sha 下載 archive——不可變,順手解掉 branch tarball 被快取的老病
· Portal 版本行接受帶 build metadata 的 semver(`1.4.41+d61` 這種先前一律被當成「較舊版本」)
· cli 測試在 node 22 上本來一支都跑不起來(.js→.ts 解析 + parameter property),補上 resolve hook
  ——#97 那份「使用者的東西還在不在」的迴歸守衛也在其中,跑不起來的守衛等於沒有守衛
· types.ts 的 ARCRUN_BUNDLE_VERSION 重複宣告(TS2300)併回一處

驗證見 PR:cli 49/49 綠、cypher health 4/4 綠、Portal 版本行原始碼實跑五種情境、
對真實已部署 worker 的唯讀 dry-run。**未做**:真實實例上的 acr update 端到端
(本機唯一有憑證的帳號是 leo21c=紅線禁碰,youlin 無憑證)。

Refs: Leo/Arcrun#106, #97, #95

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
uncle6me-web
2026-08-12 21:58:43 +08:00
parent f87d0e92f4
commit 53b05c6d3d
12 changed files with 645 additions and 19 deletions
+21 -2
View File
@@ -170,10 +170,13 @@ export class CfAccountClient implements ResourceApi {
const path = `/workers/scripts/${encodeURIComponent(script)}/settings`;
const res = await this.cfRaw<{ bindings?: RawWorkerBinding[] }>(path);
if (!res.ok) {
if (res.status === 404) return { deployed: false, bindings: [] };
if (res.status === 404) return { deployed: false, bindings: [], vars: {} };
throw new Error(`${script} 綁定失敗:${res.error}`);
}
return { deployed: true, bindings: normalizeBindings(res.result?.bindings ?? []) };
const raw = res.result?.bindings ?? [];
// #106:同一份回應裡也帶著 plain_text var(實測 CF `/settings` 會回 `text` 值)。
// 舊版只挑資源類、把 var 整批丟掉 → 重部署等於把它們洗掉。
return { deployed: true, bindings: normalizeBindings(raw), vars: normalizeVars(raw) };
}
/** 查 workers.dev subdomaincypher-executor WORKER_SUBDOMAIN 用,組對內 component URL)。*/
@@ -234,6 +237,22 @@ interface RawWorkerBinding {
id?: string;
database_id?: string;
index_name?: string;
/** `plain_text` 綁定的值(#106secret_text 不會回值,本來就讀不到,也不該讀)。 */
text?: string;
}
/**
* 抽出已部署 worker 上的 `plain_text` var#106)。
*
* 只收 `plain_text`——**`secret_text` 一律不碰**CF 本來就不回值,也不該被 CLI 搬來搬去;
* wrangler deploy 不會動 secret,它們自己會留著)。
*/
function normalizeVars(raw: RawWorkerBinding[]): Record<string, string> {
const out: Record<string, string> = {};
for (const b of raw) {
if (b?.type === 'plain_text' && b.name && typeof b.text === 'string') out[b.name] = b.text;
}
return out;
}
/** 把 CF 的 binding 陣列收斂成 resolver 認得的三種資源。不認得的型別直接略過。 */