Files
zerobyte/app/server/utils/spawn.ts
Nico 61dc07b36b Controllers tests (#187)
* test: backups service

* refactor: create hono app in a separate file

To avoid side effects like db migration or startup scripts when testing

test(backups): add security tests to the backups controller

* ci: run typechecks, build and tests on PR

* test: controllers security tests

* chore: update lock file

* refactor: pr feedbacks
2025-12-19 19:25:21 +01:00

79 lines
1.6 KiB
TypeScript

import { spawn } from "node:child_process";
export interface SafeSpawnParams {
command: string;
args: string[];
env?: NodeJS.ProcessEnv;
signal?: AbortSignal;
onStdout?: (data: string) => void;
onStderr?: (error: string) => void;
onError?: (error: Error) => Promise<void> | void;
onClose?: (code: number | null) => Promise<void> | void;
finally?: () => Promise<void> | void;
}
type SpawnResult = {
exitCode: number;
stdout: string;
stderr: string;
};
export const safeSpawn = (params: SafeSpawnParams) => {
const { command, args, env = {}, signal, ...callbacks } = params;
return new Promise<SpawnResult>((resolve) => {
let stdoutData = "";
let stderrData = "";
const child = spawn(command, args, {
env: { ...process.env, ...env },
signal: signal,
});
child.stdout.on("data", (data) => {
if (callbacks.onStdout) {
callbacks.onStdout(data.toString());
} else {
stdoutData += data.toString();
}
});
child.stderr.on("data", (data) => {
if (callbacks.onStderr) {
callbacks.onStderr(data.toString());
}
stderrData += data.toString();
});
child.on("error", async (error) => {
if (callbacks.onError) {
await callbacks.onError(error);
}
if (callbacks.finally) {
await callbacks.finally();
}
resolve({
exitCode: -1,
stdout: stdoutData,
stderr: stderrData,
});
});
child.on("close", async (code) => {
if (callbacks.onClose) {
await callbacks.onClose(code);
}
if (callbacks.finally) {
await callbacks.finally();
}
resolve({
exitCode: code === null ? -1 : code,
stdout: stdoutData,
stderr: stderrData,
});
});
});
};