n8n/packages/@n8n/node-cli/src/utils/child-process.ts
2025-09-29 17:54:03 +03:00

84 lines
2.0 KiB
TypeScript

import { spawn, type SpawnOptions, type StdioOptions } from 'node:child_process';
import { detectPackageManager } from './package-manager';
export class ChildProcessError extends Error {
constructor(
message: string,
public code: number | null,
public signal: NodeJS.Signals | null,
) {
super(message);
}
}
export async function runCommand(
cmd: string,
args: string[] = [],
opts: {
cwd?: string;
env?: NodeJS.ProcessEnv;
stdio?: StdioOptions;
context?: 'local' | 'global';
printOutput?: (options: { stdout: Buffer[]; stderr: Buffer[] }) => void;
} = {},
): Promise<void> {
const packageManager = (await detectPackageManager()) ?? 'npm';
return await new Promise((resolve, reject) => {
const options: SpawnOptions = {
cwd: opts.cwd,
env: { ...process.env, ...opts.env },
stdio: opts.stdio ?? ['ignore', 'pipe', 'pipe'],
shell: process.platform === 'win32',
};
const child =
opts.context === 'local'
? spawn(packageManager, ['exec', '--', cmd, ...args], options)
: spawn(cmd, args, options);
const stdoutBuffers: Buffer[] = [];
const stderrBuffers: Buffer[] = [];
child.stdout?.on('data', (data: Buffer) => {
stdoutBuffers.push(data);
});
child.stderr?.on('data', (data: Buffer) => {
stderrBuffers.push(data);
});
function printOutput() {
if (opts.printOutput) {
opts.printOutput({ stdout: stdoutBuffers, stderr: stderrBuffers });
return;
}
for (const buffer of stdoutBuffers) {
process.stdout.write(buffer);
}
for (const buffer of stderrBuffers) {
process.stderr.write(buffer);
}
}
child.on('error', (error) => {
printOutput();
reject(new ChildProcessError(error.message, null, null));
});
child.on('close', (code, signal) => {
if (code === 0) {
resolve();
} else {
printOutput();
reject(
new ChildProcessError(
`${cmd} exited with code ${code}${signal ? ` (signal: ${signal})` : ''}`,
code,
signal,
),
);
}
});
});
}