73 lines
2.2 KiB
JavaScript
73 lines
2.2 KiB
JavaScript
import { spawnSync } from 'node:child_process';
|
||
|
||
/**
|
||
* 執行外部指令並回傳結果(不會因為非零結束碼而 throw)。
|
||
*
|
||
* @param {string} command 要執行的指令
|
||
* @param {string[]} args 指令參數
|
||
* @param {object} [options] spawnSync 額外設定(cwd、env、input、maxBuffer...)
|
||
* @returns {{ status: number, stdout: string, stderr: string }}
|
||
*/
|
||
export function run(command, args = [], options = {}) {
|
||
const result = spawnSync(command, args, {
|
||
encoding: 'utf8',
|
||
maxBuffer: 64 * 1024 * 1024, // 64MB,避免大型 diff 被截斷
|
||
...options,
|
||
});
|
||
|
||
if (result.error) {
|
||
return { status: 1, stdout: '', stderr: String(result.error.message || result.error) };
|
||
}
|
||
|
||
return {
|
||
status: typeof result.status === 'number' ? result.status : 1,
|
||
stdout: result.stdout || '',
|
||
stderr: result.stderr || '',
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 執行外部指令,若結束碼非零則 throw。
|
||
*
|
||
* @param {string} command
|
||
* @param {string[]} args
|
||
* @param {object} [options]
|
||
* @returns {string} stdout(已 trim)
|
||
*/
|
||
export function runOrThrow(command, args = [], options = {}) {
|
||
const result = run(command, args, options);
|
||
if (result.status !== 0) {
|
||
const detail = (result.stderr || result.stdout || '').trim();
|
||
throw new Error(`指令失敗 (${result.status}): ${command} ${args.join(' ')}\n${detail}`);
|
||
}
|
||
return result.stdout.trim();
|
||
}
|
||
|
||
const ICONS = { info: 'ℹ️', warn: '⚠️', error: '❌', success: '✅', step: '▶️' };
|
||
|
||
/** 簡單的分級日誌輸出。 */
|
||
export const log = {
|
||
info: (msg) => console.log(`${ICONS.info} ${msg}`),
|
||
warn: (msg) => console.log(`${ICONS.warn} ${msg}`),
|
||
error: (msg) => console.error(`${ICONS.error} ${msg}`),
|
||
success: (msg) => console.log(`${ICONS.success} ${msg}`),
|
||
step: (msg) => console.log(`\n${ICONS.step} ${msg}`),
|
||
};
|
||
|
||
/**
|
||
* 將敏感字串(如 token)從文字中遮蔽,避免寫入日誌。
|
||
*
|
||
* @param {string} text
|
||
* @param {string[]} secrets
|
||
* @returns {string}
|
||
*/
|
||
export function maskSecrets(text, secrets = []) {
|
||
let masked = String(text ?? '');
|
||
for (const secret of secrets) {
|
||
if (secret && secret.length >= 4) {
|
||
masked = masked.split(secret).join('***');
|
||
}
|
||
}
|
||
return masked;
|
||
}
|