UNPKG

@halospv3/hce.shared-config

Version:

Automate commit message quality, changelogs, and CI/CD releases. Its `main` entry point is a Semantic Release config. Functions and classes are exposed for customization. An ESLint config, a Commitlint config, and addl. resources for .NET projects are als

91 lines (82 loc) 3.18 kB
/* eslint-disable jsdoc/no-defaults */ import { type, type Type } from 'arktype'; import { exec, type ExecException } from 'node:child_process'; import { constants } from 'node:os'; import { promisify } from 'node:util'; import { isError } from './isError.ts'; /** * 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 [shouldSetStderrAsCause=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, shouldSetStderrAsCause = false): Promise<{ stdout: string; stderr: string; }> { try { return await promisify(exec)(command); } catch (error: unknown) { if (!isError(error)) throw new Error(JSON.stringify(error), { cause: error }); if (shouldSetStderrAsCause && '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< Error & Omit<ExecException, 'cmd' | 'code' | 'signal'> & { cmd?: ExecException['cmd']; code?: number | string | undefined; signal?: ExecException['signal'] | null; } > = type.instanceOf(Error).and({ 'cmd?': 'string', 'code?': 'number', 'killed?': 'boolean', 'signal?': type((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))) .or('null'), 'stdout?': 'string', 'stderr?': 'string', }); // A class can only implement an identifier/qualified-name with optional type arguments. ts(2500) type _ExecException = typeof T_ExecException.inferOut; export class ChildProcessSpawnException extends Error implements _ExecException { 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; 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; } }