Files
uncle6me-web bb548b6fdf refactor(shared): 「該用哪些資源」搬出 CLI——一份實作,acr 與安裝器吃同一條規則
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 乾淨。
2026-08-12 23:37:01 +08:00

61 lines
2.4 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// @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`,
);
}