mirror of
https://github.com/nicotsx/zerobyte.git
synced 2026-04-18 05:47:31 -04:00
* feat: export snapshot as tar file chore(mutext): prevent double lock release * chore: pr feedbacks * fix: dump single file no tar * chore: pr feedbacks
124 lines
2.9 KiB
TypeScript
124 lines
2.9 KiB
TypeScript
import { spawn, execFile, type ExecException, type ExecFileOptions } from "node:child_process";
|
|
import { createInterface } from "node:readline";
|
|
import { promisify } from "node:util";
|
|
|
|
type ExecProps = {
|
|
command: string;
|
|
args?: string[];
|
|
env?: NodeJS.ProcessEnv;
|
|
} & ExecFileOptions;
|
|
|
|
export const exec = async ({ command, args = [], env = {}, ...rest }: ExecProps) => {
|
|
const options = {
|
|
env: { ...process.env, ...env },
|
|
};
|
|
|
|
try {
|
|
const { stdout, stderr } = await promisify(execFile)(command, args, {
|
|
...options,
|
|
...rest,
|
|
encoding: "utf8",
|
|
});
|
|
|
|
return { exitCode: 0, stdout, stderr };
|
|
} catch (error) {
|
|
const execError = error as ExecException;
|
|
|
|
return {
|
|
exitCode: typeof execError.code === "number" ? execError.code : 1,
|
|
stdout: execError.stdout || "",
|
|
stderr: execError.stderr || "",
|
|
};
|
|
}
|
|
};
|
|
|
|
export interface SafeSpawnParamsBase {
|
|
command: string;
|
|
args: string[];
|
|
env?: NodeJS.ProcessEnv;
|
|
signal?: AbortSignal;
|
|
onStderr?: (error: string) => void;
|
|
onSpawn?: (child: ReturnType<typeof spawn>) => void;
|
|
}
|
|
|
|
export interface SafeSpawnParamsLines extends SafeSpawnParamsBase {
|
|
stdoutMode?: "lines";
|
|
onStdout?: (line: string) => void;
|
|
}
|
|
|
|
export interface SafeSpawnParamsRaw extends SafeSpawnParamsBase {
|
|
stdoutMode: "raw";
|
|
onStdout?: never;
|
|
}
|
|
|
|
export type SafeSpawnParams = SafeSpawnParamsLines | SafeSpawnParamsRaw;
|
|
|
|
export type SpawnResult = {
|
|
exitCode: number;
|
|
summary: string;
|
|
error: string;
|
|
};
|
|
|
|
export function safeSpawn(params: SafeSpawnParamsLines): Promise<SpawnResult>;
|
|
export function safeSpawn(params: SafeSpawnParamsRaw): Promise<SpawnResult>;
|
|
export function safeSpawn(params: SafeSpawnParams): Promise<SpawnResult> {
|
|
const { command, args, env = {}, signal, onStderr, onSpawn } = params;
|
|
const stdoutMode = params.stdoutMode ?? "lines";
|
|
const onStdout = stdoutMode === "lines" ? params.onStdout : undefined;
|
|
|
|
let lastStdout = "";
|
|
let lastStderr = "";
|
|
|
|
return new Promise<SpawnResult>((resolve) => {
|
|
const child = spawn(command, args, {
|
|
env: { ...process.env, ...env },
|
|
signal: signal,
|
|
stdio: ["ignore", "pipe", "pipe"],
|
|
});
|
|
|
|
onSpawn?.(child);
|
|
|
|
child.stderr.setEncoding("utf8");
|
|
|
|
const rlErr = createInterface({ input: child.stderr });
|
|
|
|
if (stdoutMode === "lines") {
|
|
child.stdout.setEncoding("utf8");
|
|
|
|
const rl = createInterface({ input: child.stdout });
|
|
|
|
rl.on("line", (line) => {
|
|
if (onStdout) onStdout(line);
|
|
const trimmed = line.trim();
|
|
if (trimmed.length > 0) {
|
|
lastStdout = line;
|
|
}
|
|
});
|
|
}
|
|
|
|
rlErr.on("line", (line) => {
|
|
if (onStderr) onStderr(line);
|
|
const trimmed = line.trim();
|
|
if (trimmed.length > 0) {
|
|
lastStderr = line;
|
|
}
|
|
});
|
|
|
|
child.on("error", (err) => {
|
|
resolve({
|
|
exitCode: -1,
|
|
summary: lastStdout,
|
|
error: err.message || lastStderr,
|
|
});
|
|
});
|
|
|
|
child.on("close", (code) => {
|
|
resolve({
|
|
exitCode: code ?? -1,
|
|
summary: lastStdout,
|
|
error: lastStderr,
|
|
});
|
|
});
|
|
});
|
|
}
|