@sentzunhat/zacatl
Version:
A modular, high-performance TypeScript microservice framework for Node.js, featuring layered architecture, dependency injection, and robust validation for building scalable APIs and distributed systems.
78 lines • 2.64 kB
JavaScript
import { spawn } from 'child_process';
import { validateCommandSpec } from './policy.js';
import { commandSpecSchema } from './types.js';
const SIGKILL_GRACE_MS = 2_000;
export const runCommand = (spec, policy) => {
return new Promise((resolve, reject) => {
const parsed = commandSpecSchema.safeParse(spec);
if (!parsed.success) {
reject(parsed.error);
return;
}
try {
validateCommandSpec(parsed.data, policy);
}
catch (err) {
reject(err);
return;
}
const startMs = Date.now();
let stdout = '';
let stderr = '';
let timedOut = false;
let outputBytes = 0;
const env = policy.inheritEnv
? { ...process.env, ...(parsed.data.env ?? {}) }
: { ...(parsed.data.env ?? {}) };
const child = spawn(parsed.data.cmd, parsed.data.args, {
shell: false,
cwd: parsed.data.cwd,
env,
stdio: ['ignore', 'pipe', 'pipe'],
});
const killTimer = setTimeout(() => {
timedOut = true;
child.kill('SIGTERM');
const forceKill = setTimeout(() => child.kill('SIGKILL'), SIGKILL_GRACE_MS);
if (typeof forceKill.unref === 'function')
forceKill.unref();
}, policy.timeoutMs);
child.stdout.on('data', (chunk) => {
outputBytes += chunk.byteLength;
if (outputBytes <= policy.maxOutputBytes) {
stdout += chunk.toString('utf-8');
}
});
child.stderr.on('data', (chunk) => {
outputBytes += chunk.byteLength;
if (outputBytes <= policy.maxOutputBytes) {
stderr += chunk.toString('utf-8');
}
});
child.on('close', (code) => {
clearTimeout(killTimer);
resolve({
cmd: parsed.data.cmd,
args: parsed.data.args,
exitCode: code,
stdout,
stderr,
timedOut,
durationMs: Date.now() - startMs,
});
});
child.on('error', (err) => {
clearTimeout(killTimer);
resolve({
cmd: parsed.data.cmd,
args: parsed.data.args,
exitCode: null,
stdout,
stderr: stderr ? `${stderr}\n${err.message}` : err.message,
timedOut,
durationMs: Date.now() - startMs,
});
});
});
};
//# sourceMappingURL=runner.js.map