mirror of https://github.com/buster-so/buster.git
145 lines
3.5 KiB
TypeScript
145 lines
3.5 KiB
TypeScript
import { spawn } from 'node:child_process';
|
|
|
|
export interface BashCommandParams {
|
|
command: string;
|
|
description?: string | undefined;
|
|
timeout?: number | undefined;
|
|
}
|
|
|
|
export interface BashExecuteResult {
|
|
command: string;
|
|
stdout: string;
|
|
stderr?: string | undefined;
|
|
exitCode: number;
|
|
success: boolean;
|
|
error?: string | undefined;
|
|
}
|
|
|
|
async function executeSingleBashCommand(
|
|
command: string,
|
|
timeout?: number
|
|
): Promise<{
|
|
stdout: string;
|
|
stderr: string;
|
|
exitCode: number;
|
|
}> {
|
|
return new Promise((resolve, reject) => {
|
|
const child = spawn('bash', ['-c', command], {
|
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
});
|
|
|
|
let stdout = '';
|
|
let stderr = '';
|
|
let timeoutId: NodeJS.Timeout | undefined;
|
|
|
|
if (timeout) {
|
|
timeoutId = setTimeout(() => {
|
|
child.kill('SIGTERM');
|
|
reject(new Error(`Command timed out after ${timeout}ms`));
|
|
}, timeout);
|
|
}
|
|
|
|
child.stdout?.on('data', (data) => {
|
|
stdout += data.toString();
|
|
});
|
|
|
|
child.stderr?.on('data', (data) => {
|
|
stderr += data.toString();
|
|
});
|
|
|
|
child.on('close', (code) => {
|
|
if (timeoutId) {
|
|
clearTimeout(timeoutId);
|
|
}
|
|
resolve({
|
|
stdout: stdout.trim(),
|
|
stderr: stderr.trim(),
|
|
exitCode: code || 0,
|
|
});
|
|
});
|
|
|
|
child.on('error', (error) => {
|
|
if (timeoutId) {
|
|
clearTimeout(timeoutId);
|
|
}
|
|
reject(error);
|
|
});
|
|
});
|
|
}
|
|
|
|
export async function executeBashCommandsSafely(
|
|
commands: BashCommandParams[]
|
|
): Promise<BashExecuteResult[]> {
|
|
const results: BashExecuteResult[] = [];
|
|
|
|
for (const cmd of commands) {
|
|
try {
|
|
const result = await executeSingleBashCommand(cmd.command, cmd.timeout);
|
|
|
|
results.push({
|
|
command: cmd.command,
|
|
stdout: result.stdout,
|
|
stderr: result.stderr ? result.stderr : undefined,
|
|
exitCode: result.exitCode,
|
|
success: result.exitCode === 0,
|
|
error: result.exitCode !== 0 ? result.stderr || 'Command failed' : undefined,
|
|
});
|
|
} catch (error) {
|
|
results.push({
|
|
command: cmd.command,
|
|
stdout: '',
|
|
stderr: undefined,
|
|
exitCode: 1,
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Unknown execution error',
|
|
});
|
|
}
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
export function generateBashExecuteCode(commands: BashCommandParams[]): string {
|
|
return `
|
|
const { spawnSync } = require('child_process');
|
|
|
|
function executeSingleBashCommand(command, timeout) {
|
|
try {
|
|
const options = {
|
|
shell: '/bin/bash',
|
|
encoding: 'utf8',
|
|
timeout: timeout || undefined,
|
|
};
|
|
|
|
const result = spawnSync('bash', ['-c', command], options);
|
|
|
|
return {
|
|
command,
|
|
stdout: result.stdout ? result.stdout.trim() : '',
|
|
stderr: result.stderr ? result.stderr.trim() : undefined,
|
|
exitCode: result.status !== null ? result.status : 1,
|
|
success: result.status === 0,
|
|
error: result.status !== 0 ? (result.stderr ? result.stderr.trim() : 'Command failed') : undefined,
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
command,
|
|
stdout: '',
|
|
stderr: undefined,
|
|
exitCode: 1,
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Unknown execution error',
|
|
};
|
|
}
|
|
}
|
|
|
|
function executeBashCommandsConcurrently(commands) {
|
|
return commands.map((cmd) => executeSingleBashCommand(cmd.command, cmd.timeout));
|
|
}
|
|
|
|
const commands = ${JSON.stringify(commands)};
|
|
const results = executeBashCommandsConcurrently(commands);
|
|
console.log(JSON.stringify(results));
|
|
`.trim();
|
|
}
|