Files
rea/src/process/ExecFileOutput.ts
T

68 lines
2.1 KiB
TypeScript
Raw Normal View History

import {
execFile,
type ExecFileOptionsWithStringEncoding,
} from "node:child_process";
type ExecFileOutputOptions = Omit<
ExecFileOptionsWithStringEncoding,
"encoding" | "maxBuffer"
> & { readonly maxBuffer?: number };
/** Captured subprocess output read from an execFileOutput rejection. */
export interface ExecFileOutputFailure {
readonly stdout: string;
readonly stderr: string;
readonly code: string | number | null;
readonly signal: string | null;
readonly killed: boolean;
readonly outputTruncated: boolean;
}
/** Read captured output metadata from a failed execFileOutput invocation. */
export const execFileOutputFailure = (
cause: unknown,
): ExecFileOutputFailure | undefined => {
if (!(cause instanceof Error)) return undefined;
const stdout = Reflect.get(cause, "stdout");
const stderr = Reflect.get(cause, "stderr");
const code = Reflect.get(cause, "code");
const signal = Reflect.get(cause, "signal");
const killed = Reflect.get(cause, "killed");
if (typeof stdout !== "string" || typeof stderr !== "string")
return undefined;
return {
stdout,
stderr,
code: typeof code === "string" || typeof code === "number" ? code : null,
signal: typeof signal === "string" ? signal : null,
killed: killed === true,
outputTruncated: code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER",
};
};
/** Run a shell-free command while capturing its complete UTF-8 output. */
export const execFileOutput = (
command: string,
arguments_: readonly string[],
options: ExecFileOutputOptions = {},
): Promise<{ readonly stdout: string; readonly stderr: string }> =>
new Promise((resolve, reject) => {
execFile(
command,
[...arguments_],
{
...options,
encoding: "utf8",
maxBuffer: options.maxBuffer ?? Number.POSITIVE_INFINITY,
},
(error, stdout, stderr) => {
if (error !== null) {
Reflect.set(error, "stdout", stdout);
Reflect.set(error, "stderr", stderr);
reject(error);
return;
}
resolve({ stdout, stderr });
},
);
});