Files
mailadmin/backend/src/utils/shell.ts
2026-04-26 16:19:09 -05:00

72 lines
1.7 KiB
TypeScript

import { spawn } from 'node:child_process';
export interface RunResult {
stdout: string;
stderr: string;
code: number;
}
export function run(
command: string,
args: string[] = [],
timeoutMs = 120000,
): Promise<RunResult> {
return new Promise((resolve, reject) => {
const printable = [command, ...args].join(' ');
console.log(`[shell] running: ${printable}`);
const child = spawn(command, args, {
stdio: ['ignore', 'pipe', 'pipe'],
env: process.env,
});
let stdout = '';
let stderr = '';
const timer = setTimeout(() => {
child.kill('SIGKILL');
reject(new Error(`Command timed out after ${timeoutMs}ms: ${printable}`));
}, timeoutMs);
child.stdout.on('data', (chunk) => {
stdout += chunk.toString();
});
child.stderr.on('data', (chunk) => {
stderr += chunk.toString();
});
child.on('error', (err) => {
clearTimeout(timer);
console.error(`[shell] failed to start: ${printable}`);
console.error(`[shell] error: ${err.message}`);
reject(err);
});
child.on('close', (code) => {
clearTimeout(timer);
const exitCode = code ?? -1;
if (stdout.trim()) {
console.log(`[shell] stdout for ${printable}:\n${stdout.trim()}`);
}
if (stderr.trim()) {
console.warn(`[shell] stderr for ${printable}:\n${stderr.trim()}`);
}
if (exitCode !== 0) {
const err = new Error(
`Command failed with exit code ${exitCode}: ${printable}\n${stderr || stdout}`,
);
reject(err);
return;
}
console.log(`[shell] completed: ${printable}`);
resolve({ stdout, stderr, code: exitCode });
});
});
}