/** * 在測試暫存裡建一棵完整的假 plugin 根,並從那裡啟動指令入口。 * * 為什麼要整棵複製而不是加一個環境變數:入口回推 plugin 根的那段明寫「不依賴 cwd * 也不依賴環境變數」,為測試破它等於把要驗的東西驗掉。把 bin/ 與 scripts/ 原封複製 * 過去,回推就自然指向假根,跑的仍是真正的程式碼;順便把「plugin 目錄不完整」 * 那條路徑一起測得到——少給哪個目錄由測試自己決定。 */ import { chmodSync, cpSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; import { join } from 'node:path'; import { repoRoot, runBin, tmpRoot } from './run-script.js'; /** 一份假的流程正本,格式與真正的正本一致:頭兩行是 name 與 description */ export const fakePrompt = (name) => `name: ${name}\ndescription: 僅由 /${name} 指令叫用。假的正本,只用於測試。\n\n# ${name}\n\n第一段。\n\n## 小節\n\n第二段。\n`; /** * @param {object} t node:test 的 TestContext * @param {{prompts?: Record, omit?: string[], version?: string}} options * prompts 要放進 prompts/ 的正本,鍵為指令名; * omit 故意不建立的目錄,用來造出「plugin 目錄不完整」; * version 覆寫假根的套件版本 * @returns {{root: string, shim: string, run: (args: string[], opts?: object) => Promise}} * shim 是放著這份假 plugin 的 tea-sdlc 的目錄,預設已經加進 run 的 PATH */ export function makeFakePlugin(t, { prompts = {}, omit = [], version } = {}) { mkdirSync(tmpRoot, { recursive: true }); const root = mkdtempSync(join(tmpRoot, 'plugin-')); t.after(() => rmSync(root, { recursive: true, force: true })); // 真正的程式碼,不是替身:測到的就是使用者會執行的東西 for (const dir of ['bin', 'scripts']) { cpSync(join(repoRoot, dir), join(root, dir), { recursive: true }); } const manifest = JSON.parse(readFileSync(join(repoRoot, 'package.json'), 'utf8')); if (version !== undefined) manifest.version = version; writeFileSync(join(root, 'package.json'), `${JSON.stringify(manifest, null, 2)}\n`); for (const dir of ['prompts', 'templates', 'references']) { if (omit.includes(dir)) continue; mkdirSync(join(root, dir), { recursive: true }); } for (const [name, text] of Object.entries(prompts)) { writeFileSync(join(root, 'prompts', `${name}.md`), text); } const shim = makeShim(root); return { root, shim, // 預設把這份假 plugin 的 tea-sdlc 放進 PATH:真實使用者是 npm i -g 裝的, // 叫用鏈上本來就有這一環。要測「PATH 上找不到」的那條路徑就傳 shim: false。 run: (args, { shim: onPath = true, path, ...opts } = {}) => runBin(args, { ...opts, root, path: onPath ? [shim, path ?? process.env.PATH].join(':') : path, }), }; } /** * 替一份假 plugin 根造出可以從 PATH 叫到的 `tea-sdlc`。 * * install 的驗證會真的去 PATH 上把 tea-sdlc 找出來執行——那正是它要驗的那一環。 * 測試裡若沒有這個殼,驗到的就只是「測試環境沒有裝 tea-sdlc」,而不是待驗的東西。 * @param {string} root 假 plugin 根 * @returns {string} 殼所在的目錄,加進 PATH 就能叫到 */ function makeShim(root) { const dir = join(root, 'shim'); mkdirSync(dir, { recursive: true }); const path = join(dir, 'tea-sdlc'); writeFileSync(path, `#!/bin/sh\nexec ${JSON.stringify(process.execPath)} ${JSON.stringify(join(root, 'bin', 'tea-sdlc.js'))} "$@"\n`); chmodSync(path, 0o755); return dir; }