#!/usr/bin/env node /** * sync-resource-rule.mjs — 把「該用哪些資源」這條規則的**唯一原稿**同步給需要打包的呼叫端。 * * 【為什麼需要這支】 * 規則的原稿在 `shared/resource-rule/rule.mjs`(理由見該檔開頭)。 * 兩條路取用它的方式不同: * * · **安裝器 / 任何 Worker**:本來就會下載這個 repo 的 archive 當部署來源, * 直接 import `shared/resource-rule/rule.mjs`。**不需要副本,本支不管它。** * * · **`acr` CLI**:`arcrun` 是獨立 npm 套件,`npm pack` 打不進套件目錄外的檔案 * ⇒ 套件裡必須有一份。這支就是產生那一份的地方。 * * 【這算不算「第二份實作」】 * 不算,而且是機械保證的:產生物是**逐位元組副本**,`--check` 一有差就 exit 1, * 而 `npm run build` 與 `npm test` 都會先跑 `--check`。 * 也就是說「有人手改了 CLI 那一份」= build 紅、publish 擋下。 * ——同 `cli/harness/`(產生物進 repo + `check:harness` 世代閘)的既有慣例, * 不是為本票新發明的做法。 * * 用法: * node scripts/sync-resource-rule.mjs 產生/更新副本 * node scripts/sync-resource-rule.mjs --check 只檢查,有漂移就 exit 1(不寫檔) */ import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from 'node:fs'; import { join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import { createHash } from 'node:crypto'; // REPO 一律由本檔位置推導,不吃 cwd(比照 scripts/build-worker-artifacts.mjs)。 const REPO = resolve(fileURLToPath(new URL('.', import.meta.url)), '..'); const SOURCE_DIR = join(REPO, 'shared/resource-rule'); /** * 需要「套件內自帶一份」的呼叫端。**整個目錄原樣鏡射**(不是挑檔案)—— * 檔名與相對位置保持一致,`cf-resource-api.mjs` 裡的 `./rule.mjs` 才不用改寫。 * 安裝器不在此列:它直接讀 repo archive 裡的原稿,連副本都不需要。 */ const MIRRORS = ['cli/src/lib/resource-rule']; const CHECK_ONLY = process.argv.includes('--check'); /** @param {string|Buffer} b */ const sha256 = (b) => createHash('sha256').update(b).digest('hex'); if (!existsSync(SOURCE_DIR)) { console.error(`❌ 找不到規則原稿目錄:${SOURCE_DIR}`); process.exit(1); } /** 原稿目錄裡所有 .mjs(README / 測試不進副本)。 */ const FILES = readdirSync(SOURCE_DIR).filter((f) => f.endsWith('.mjs')).sort(); if (FILES.length === 0) { console.error(`❌ ${SOURCE_DIR} 裡沒有任何 .mjs 原稿`); process.exit(1); } let drifted = 0; for (const mirror of MIRRORS) { for (const file of FILES) { const src = readFileSync(join(SOURCE_DIR, file)); const srcHash = sha256(src); const rel = `${mirror}/${file}`; const abs = join(REPO, mirror, file); const had = existsSync(abs) ? readFileSync(abs) : null; if (had !== null && sha256(had) === srcHash) { console.log(`✔ ${rel} = 原稿(sha256 ${srcHash.slice(0, 12)})`); continue; } if (CHECK_ONLY) { drifted++; console.error( had === null ? `✗ ${rel} 不存在——跑 \`node scripts/sync-resource-rule.mjs\` 產生。` : `✗ ${rel} 與原稿不一致(副本 ${sha256(had).slice(0, 12)} ≠ 原稿 ${srcHash.slice(0, 12)})。\n` + ` 這一份是**產生物**,不要手改:規則要改就改 shared/resource-rule/${file},` + `然後跑 \`node scripts/sync-resource-rule.mjs\`。`, ); continue; } mkdirSync(join(REPO, mirror), { recursive: true }); writeFileSync(abs, src); console.log(`↻ ${rel} ← shared/resource-rule/${file}(sha256 ${srcHash.slice(0, 12)})`); } // 副本目錄裡多出來的 .mjs = 有人在產生物旁邊自己加了一支(第二份實作的常見長法)。 const mirrorAbs = join(REPO, mirror); const extra = existsSync(mirrorAbs) ? readdirSync(mirrorAbs).filter((f) => f.endsWith('.mjs') && !FILES.includes(f)) : []; for (const f of extra) { drifted++; console.error(`✗ ${mirror}/${f} 在原稿目錄裡不存在——副本目錄不是放自己東西的地方。`); } } if (drifted > 0) { console.error( `\n❌ ${drifted} 項與規則原稿脫節。` + `\n「該用哪些資源」只能有一份實作(.claude/rules/07-thin-shell.md)——` + `副本漂移就是第二份實作偷偷長出來的樣子。`, ); process.exit(1); }