bb548b6fdf
leo 2026-08-12:「根本就不應該在 CLI,我要的是一個大家都可以用到的規則。」
「這個實例該用哪些資源」換到安裝器就要重寫一次 ⇒ 依 rules/07-thin-shell.md 的判準
它是**能力**,而它原本住在 cli/src/lib/resource-resolver.ts ⇒ 那本身就是違規。
後果已經真的發生:acr 那條有 Arcrun#97 的修法、安裝器那條沒有,於是安裝器照名字
找、找不到就建一顆空的綁上去 ⇒「我按了更新,工作流和登入全不見了」。
規則搬到 shared/resource-rule/(零依賴 ESM,Node 與 Workers runtime 都直接跑):
· rule.mjs 規則本體+把 CF 回應讀成事實的 normalizeLive*
· cf-resource-api.mjs ResourceApi 的 CF REST 實作——**眼睛也共用**:
兩條路各自解讀 CF 回應,只要一邊看不到既有綁定就會去新建,
#97 不需要規則寫錯就能重演
· installer-entry.mjs 安裝器唯一該碰的入口 resolveInstanceResources()
不是做成 cypher 端點的理由(自舉):這條規則要在「決定怎麼裝」的當下就用得到,
而那時 cypher 可能還不存在(安裝器的工作正是把它生出來);且輸入是使用者自己帳號的
綁定狀態,不該送去平台換答案。它是純函式,用不著變成服務。
只有一份,機械看守:
· 安裝器直接 import repo archive 裡的原稿,**不需要副本**
· acr 因為 npm pack 打不進套件目錄外的檔案,帶一份逐位元組鏡射
(scripts/sync-resource-rule.mjs 產生;build/test 先跑 --check,差一位元組就紅)
——同 cli/harness/ 產生物+世代閘的既有慣例
· cli/tests/single-implementation.test.ts 掃全 repo:7 支規則函式的實作只有一處
CLI 淨 -496 行(邏輯是搬走,不是複製)。cf-api.ts 的 CfAccountClient 保留公開介面,
ResourceApi 那七個方法全部委派共用 client。
驗證:cli 58/58 綠(含新增的兩條路一致性 fixture + 三種情境),tsc --noEmit 乾淨。
61 lines
2.4 KiB
JavaScript
61 lines
2.4 KiB
JavaScript
// @ts-check
|
||
/**
|
||
* demo.mjs — 安裝器那條路的**可獨立執行**證明。
|
||
*
|
||
* node shared/resource-rule/tests/demo.mjs
|
||
*
|
||
* 這支只 import `shared/resource-rule/`,**沒有 node_modules、沒有建置步驟**——
|
||
* 跑得起來本身就是「安裝器把 repo archive 拉下來就能直接用」這句話的證據。
|
||
* (對照組:`acr` 那條要先 npm ci + TS 轉譯才跑得動。兩條路差在外殼,判斷是同一份。)
|
||
*
|
||
* 三種情境各跑一次,印出每個 binding 選到哪顆資源、以及這一趟建了幾顆。
|
||
*/
|
||
|
||
import { resolveInstanceResources } from '../installer-entry.mjs';
|
||
import { makeAccount, SCENARIOS, WORKER_NEEDS } from './fixture-account.mjs';
|
||
|
||
/** 用 fixture 的需求組出各 worker 的 wrangler.toml 內容。 */
|
||
function tomls() {
|
||
return Object.entries(WORKER_NEEDS).map(([script, need]) => {
|
||
let t = `name = "${script}"\ncompatibility_date = "2025-02-19"\n`;
|
||
for (const b of need.kv) t += `\n[[kv_namespaces]]\nbinding = "${b}"\nid = "PLACEHOLDER"\n`;
|
||
for (const d of need.d1) {
|
||
t += `\n[[d1_databases]]\nbinding = "${d.binding}"\ndatabase_name = "${d.database_name}"\ndatabase_id = "PLACEHOLDER"\n`;
|
||
}
|
||
return t;
|
||
});
|
||
}
|
||
|
||
const order = /** @type {const} */ (['fresh', 'installed', 'renamed']);
|
||
|
||
console.log('安裝器那條路(只 import shared/resource-rule/,零依賴、零建置)\n');
|
||
|
||
for (const scenario of order) {
|
||
const mode = scenario === 'fresh' ? 'init' : 'update';
|
||
const account = makeAccount(scenario);
|
||
const r = await resolveInstanceResources({
|
||
accountId: 'acct-demo',
|
||
apiToken: 'tok-demo',
|
||
wranglerTomls: tomls(),
|
||
mode,
|
||
fetch: account.fetch,
|
||
});
|
||
|
||
console.log(`── ${scenario}(mode=${mode}):${SCENARIOS[scenario].label}`);
|
||
if (r.blocked) {
|
||
console.log(' ⛔ 停手,一顆資源都沒建:');
|
||
for (const b of r.blockers) console.log(` • ${b}`);
|
||
console.log('');
|
||
continue;
|
||
}
|
||
for (const key of Object.keys(r.bindings).sort()) {
|
||
console.log(` ${key.padEnd(30)} → ${r.bindings[key].padEnd(26)} ${r.origin[key]}`);
|
||
}
|
||
console.log(
|
||
` 本趟新建:KV ${account.created.kv.length} 顆、D1 ${account.created.d1.length} 顆、` +
|
||
`Vectorize ${account.created.vectorize.length} 顆` +
|
||
`|沿用既有版本標籤 ARCRUN_BUNDLE_VERSION=` +
|
||
`${r.liveVars['arcrun-cypher-executor']?.ARCRUN_BUNDLE_VERSION ?? '(無,全新安裝)'}\n`,
|
||
);
|
||
}
|