Files
tea-sdlc/test/helpers/run-script.js
T
jiantw83andClaude Opus 5 4ae097f1e1 fix(腳本契約): 長輸出不再被截斷,查重翻頁加上上限
process.stdout.write 之後立刻 process.exit 會截斷輸出——stdout 接到 pipe 時
寫入是非同步的。改為等 write 的 callback 回來再退出。實測舊寫法在約 83KB 處
被切斷,新增的回歸測試以 8000 筆標籤覆蓋這條路徑。

findIssueByTitle 的翻頁原本沒有上限,Gitea 若持續回滿一頁就會無限打下去。
加上 200 頁上限,超過即以 DEDUPE_LIMIT 報錯而非無聲回 null——無聲回 null 會
讓呼叫端把既有議題再建一次,正好是冪等查重要防的事。

parseFlags 取值時不再於三元運算式內遞增迴圈變數,改為獨立敘述。

測試工具的 maxBuffer 調高到 64MB:預設 1MB 會在長輸出時砍掉子行程,那是測試
工具的限制而非腳本的問題。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 04:26:51 +00:00

80 lines
3.0 KiB
JavaScript
Raw 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.
/**
* 以子行程執行 scripts/ 底下的腳本,回傳它印出的 JSON 與 exit code。
* 這是本專案唯一的測試接縫:測到的東西就是使用者真正會執行的東西。
*/
import { execFile, execFileSync } from 'node:child_process';
import { mkdtempSync, mkdirSync, symlinkSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
/** 本檔位置 → repo 根,讓測試不依賴 cwd */
export const repoRoot = join(dirname(fileURLToPath(import.meta.url)), '..', '..');
/** 所有測試暫存的落點;已被 .gitignore 忽略,也不會被 node --test 探索到 */
export const tmpRoot = join(repoRoot, '.tmp');
/**
* @param {string} name 腳本檔名,例如 "labels-list.js"
* @param {string[]} args 具名 flag 陣列
* @param {{env?: Record<string,string>, cwd?: string, path?: string}} opts
* env 額外環境變數;path 覆寫 PATH(用來模擬缺少 git / tea)
* @returns {Promise<{code: number, stdout: string, stderr: string, json: object}>}
*/
export function runScript(name, args = [], opts = {}) {
const { env = {}, cwd = repoRoot, path } = opts;
// 先把繼承來的 TEA_SDLC_* 清乾淨,測試結果才不會隨開發者的 shell 而變
const inherited = Object.fromEntries(
Object.entries(process.env).filter(([k]) => !k.startsWith('TEA_SDLC_')),
);
const childEnv = { ...inherited, ...env };
if (path !== undefined) childEnv.PATH = path;
return new Promise((resolve) => {
execFile(
process.execPath,
[join(repoRoot, 'scripts', name), ...args],
// 預設 1MB 會在長輸出時砍掉子行程,那是測試工具的限制而非腳本的問題
{ cwd, env: childEnv, maxBuffer: 64 * 1024 * 1024 },
(error, stdout, stderr) => {
resolve({
code: typeof error?.code === 'number' ? error.code : error ? 1 : 0,
stdout,
stderr,
json: parseSingleLine(stdout),
});
},
);
});
}
/**
* 腳本的輸出契約是「單行 JSON」。這裡順便把契約本身斷言掉:
* 多印一行、印出非 JSON,都會在這裡就炸掉。
*/
function parseSingleLine(stdout) {
const lines = stdout.split('\n').filter((l) => l.trim() !== '');
if (lines.length !== 1) {
throw new Error(`預期單行 JSON,實際印出 ${lines.length} 行:\n${stdout}`);
}
return JSON.parse(lines[0]);
}
/**
* 造一個只放得下指定執行檔的 PATH,用來讓「缺少 git / tea」的情境精確成立。
* 直接從真實 PATH 裡剔除目錄行不通:git、tea、node 常住在同一個 /usr/bin。
* @param {string[]} binaries 要保留的執行檔名
* @returns {string} 只含一個目錄的 PATH
*/
export function pathWithOnly(binaries) {
mkdirSync(tmpRoot, { recursive: true });
const dir = mkdtempSync(join(tmpRoot, 'path-'));
for (const binary of binaries) {
symlinkSync(which(binary), join(dir, binary));
}
return dir;
}
function which(binary) {
return execFileSync('sh', ['-c', `command -v ${binary}`], { encoding: 'utf8' }).trim();
}