@halospv3/hce.shared-config
Version:
Automate commit message quality, changelogs, and CI/CD releases. Exports a semantic-release shareable configuration deserialized from this package's '.releaserc.yml'. Shared resources for .NET projects are also distributed with this package.
89 lines (80 loc) • 3.57 kB
text/typescript
/* eslint-disable jsdoc/no-defaults */
import { type, type Type } from 'arktype';
import { exec } from 'node:child_process';
import { constants } from 'node:os';
import { promisify } from 'node:util';
import { isError } from './isError.js';
/**
* A `promisify(exec)` wrapper to optionally assign the child process's STDERR as the {@link Error.prototype.cause}.
* @see {@link promisify}, {@link exec}
* @param command The command to run, with space-separated arguments.
* @param [setStderrAsCause=false] If true and the child process's stderr is available, the thrown Error's {@link Error.prototype.cause} is assigned the stderr string.
* @returns A promise of the child process's STDOUT and STDERR streams as strings
* @throws {Error | ChildProcessSpawnException}
*/
export async function execAsync(command: string, setStderrAsCause = false): Promise<{
stdout: string;
stderr: string;
}> {
return await promisify(exec)(command).catch((error: unknown): never => {
if (!isError(error))
throw new Error(JSON.stringify(error));
if (setStderrAsCause && 'stderr' in error && typeof error.stderr === 'string' && error.stderr !== '')
error.cause ??= error.stderr;
if ('stdout' in error && typeof error.stdout === 'string') {
error.message
+= '\nSTDOUT:\n'
+ ` ${error.stdout.replaceAll('\n', '\n ')}`;
}
if ('stderr' in error && typeof error.stderr === 'string') {
error.message
+= '\nSTDERR:\n'
+ ` ${error.stderr.replaceAll('\n', '\n ')}`;
}
throw new ChildProcessSpawnException(error.message, error);
});
}
const T_ExecException: Type<{
name: string;
message: string;
stack?: string | undefined;
cause?: unknown;
cmd?: string | null | undefined;
killed?: boolean | null | undefined;
code?: number | null | undefined;
signal?: 'SIGABRT' | 'SIGALRM' | 'SIGBUS' | 'SIGCHLD' | 'SIGCONT' | 'SIGFPE' | 'SIGHUP' | 'SIGILL' | 'SIGINT' | 'SIGIO' | 'SIGIOT' | 'SIGKILL' | 'SIGPIPE' | 'SIGPOLL' | 'SIGPROF' | 'SIGPWR' | 'SIGQUIT' | 'SIGSEGV' | 'SIGSTKFLT' | 'SIGSTOP' | 'SIGSYS' | 'SIGTERM' | 'SIGTRAP' | 'SIGTSTP' | 'SIGTTIN' | 'SIGTTOU' | 'SIGUNUSED' | 'SIGURG' | 'SIGUSR1' | 'SIGUSR2' | 'SIGVTALRM' | 'SIGWINCH' | 'SIGXCPU' | 'SIGXFSZ' | 'SIGBREAK' | 'SIGLOST' | 'SIGINFO' | null | undefined;
stdout?: string | undefined;
stderr?: string | undefined;
}> = type('Error').and({
'cmd?': 'string | null',
'killed?': 'boolean | null',
'code?': 'number | null',
'signal?': type.null.or((Object.keys(constants.signals) as NodeJS.Signals[])
.map(v => type(`'${v}'`))
// eslint-disable-next-line unicorn/no-array-reduce
.reduce((previous, current) => previous.or(current))),
'stdout?': 'string',
'stderr?': 'string',
});
type _ExecException = typeof T_ExecException.inferOut;
export class ChildProcessSpawnException extends Error implements _ExecException {
constructor(
message: Parameters<typeof Error>[0],
options: typeof T_ExecException.inferIn,
) {
options = T_ExecException.from(options);
super(message, options);
this.cmd = options.cmd;
this.code = options.code;
this.killed = options.killed;
this.signal = options.signal;
this.stderr = options.stderr;
this.stdout = options.stdout;
}
cmd: typeof T_ExecException.inferOut.cmd;
code: typeof T_ExecException.inferOut.code;
killed: typeof T_ExecException.inferOut.killed;
signal: typeof T_ExecException.inferOut.signal;
stderr: typeof T_ExecException.inferOut.stderr;
stdout: typeof T_ExecException.inferOut.stdout;
}