/** * 「只有一份」的機械證明。 * * leo 的驗收條件:「改完之後,`grep` 得出『決定用哪些資源』的邏輯**只有一個地方**。 * 兩個以上呼叫端各自有一份 ⇒ 不算完成。」 * * 這份測試就是把那個 grep 寫成會紅的東西: * ① 規則的每一支函式,全 repo 只有 `shared/resource-rule/` 有實作 * (`cli/src/lib/resource-rule/` 是它的逐位元組鏡射,由 sync 腳本產生並看守,不算第二份) * ② 鏡射與原稿逐位元組相同(sync --check 的同一道閘,這裡再測一次讓 `npm test` 也擋得住) * ③ 共用層不准長出依賴——有依賴就會有某條路吃不到它 */ import { test } from 'node:test'; import assert from 'node:assert/strict'; import { readFileSync, readdirSync, statSync } from 'node:fs'; import { join, relative } from 'node:path'; import { fileURLToPath } from 'node:url'; import { createHash } from 'node:crypto'; const REPO = join(fileURLToPath(new URL('.', import.meta.url)), '..', '..'); const SOURCE_DIR = join(REPO, 'shared/resource-rule'); const MIRROR_DIR = join(REPO, 'cli/src/lib/resource-rule'); /** 規則的實作特徵:這些**宣告**只准出現在原稿目錄(與它的鏡射)裡。 */ const RULE_DECLARATIONS = [ 'function planResources', 'function applyResourcePlan', 'function shareSameResource', 'function parseWranglerRequirements', 'function normalizeLiveBindings', 'function normalizeLiveVars', 'function createCloudflareResourceApi', ]; const SKIP_DIRS = new Set([ 'node_modules', '.git', 'dist', '.wrangler', '.worker-builds', '.component-builds', '.github-public', 'coverage', ]); /** 只掃「人會寫程式的地方」;產生物與二進位不掃。 */ function walk(dir: string, out: string[] = []): string[] { for (const name of readdirSync(dir)) { if (SKIP_DIRS.has(name)) continue; const abs = join(dir, name); const st = statSync(abs); if (st.isDirectory()) walk(abs, out); else if (/\.(ts|tsx|js|mjs|cjs)$/.test(name)) out.push(abs); } return out; } const sha256 = (b: Buffer): string => createHash('sha256').update(b).digest('hex'); test('① 規則的實作全 repo 只有一份(原稿目錄 + 它的鏡射,沒有第三處)', () => { const files = walk(REPO); const offenders: string[] = []; for (const abs of files) { const rel = relative(REPO, abs); // 原稿與鏡射本來就該有;測試檔在講規則、不是實作規則 if (rel.startsWith('shared/resource-rule/')) continue; if (rel.startsWith('cli/src/lib/resource-rule/')) continue; if (rel.startsWith('cli/tests/')) continue; if (rel === 'scripts/sync-resource-rule.mjs') continue; const src = readFileSync(abs, 'utf8'); for (const decl of RULE_DECLARATIONS) { if (src.includes(decl)) offenders.push(`${rel} → ${decl}`); } } assert.deepEqual(offenders, [], '「決定用哪些資源」的實作出現在共用層之外——這正是本票要消滅的東西:\n' + offenders.map((o) => ` • ${o}`).join('\n') + '\n要改規則就改 shared/resource-rule/,呼叫端只准 import。'); console.log(`\n ① 掃過 ${files.length} 個原始碼檔,${RULE_DECLARATIONS.length} 支規則函式的實作` + ' 全部只出現在 shared/resource-rule/(+機械鏡射)'); }); test('② CLI 帶的那份與原稿逐位元組相同(漂移=第二份實作偷偷長出來)', () => { const files = readdirSync(SOURCE_DIR).filter((f) => f.endsWith('.mjs')).sort(); assert.ok(files.length > 0, 'shared/resource-rule/ 裡沒有任何 .mjs 原稿'); const mirrored = readdirSync(MIRROR_DIR).filter((f) => f.endsWith('.mjs')).sort(); assert.deepEqual(mirrored, files, '鏡射目錄的檔案清單與原稿不一致'); for (const f of files) { const a = sha256(readFileSync(join(SOURCE_DIR, f))); const b = sha256(readFileSync(join(MIRROR_DIR, f))); assert.equal(b, a, `cli/src/lib/resource-rule/${f} 與原稿不一致——不要手改產生物,` + '改 shared/resource-rule/ 後跑 node scripts/sync-resource-rule.mjs'); console.log(` ② ${f.padEnd(24)} sha256 ${a.slice(0, 16)} 原稿 = 鏡射`); } }); test('③ 共用層零外部依賴(只准 import 同目錄的兄弟檔)', () => { for (const f of readdirSync(SOURCE_DIR).filter((x) => x.endsWith('.mjs'))) { const src = readFileSync(join(SOURCE_DIR, f), 'utf8'); const imports = [...src.matchAll(/^\s*import\s[^'"]*['"]([^'"]+)['"]/gm)].map((m) => m[1]); for (const spec of imports) { assert.ok(spec.startsWith('./'), `shared/resource-rule/${f} import 了 "${spec}"——共用層一旦有外部依賴,` + '就會有某條路(Workers runtime/安裝器)吃不到它。'); } assert.doesNotMatch(src, /require\(|from\s+['"]node:/, `shared/resource-rule/${f} 用到 node 專屬 API——Cloudflare Workers 上跑不起來。`); console.log(` ③ ${f.padEnd(24)} import: ${imports.length ? imports.join(', ') : '(無)'}`); } });