diff --git a/src/main.ts b/src/main.ts index cffa0a7..def85bf 100644 --- a/src/main.ts +++ b/src/main.ts @@ -32,6 +32,7 @@ export interface CommonOutputApi { get pid(): number | undefined; get killed(): boolean; get exitCode(): number | undefined; + get signalCode(): string | null; } export interface OutputApi extends AsyncIterable, CommonOutputApi { @@ -149,6 +150,10 @@ export class ExecProcess implements Result { return undefined; } + public get signalCode(): string | null { + return this._process?.signalCode ?? null; + } + public constructor( command: string, args?: readonly string[], @@ -224,8 +229,8 @@ export class ExecProcess implements Result { if ( this._options?.throwOnError && - this.exitCode !== 0 && - this.exitCode !== undefined + ((this.exitCode !== 0 && this.exitCode !== undefined) || + this.signalCode != null) ) { throw new NonZeroExitError(this); } @@ -265,8 +270,8 @@ export class ExecProcess implements Result { if ( this._options.throwOnError && - this.exitCode !== 0 && - this.exitCode !== undefined + ((this.exitCode !== 0 && this.exitCode !== undefined) || + this.signalCode != null) ) { throw new NonZeroExitError(this, result); } @@ -406,7 +411,8 @@ export function xSync( const stdout = spawnResult.stdout?.toString() ?? ''; const stderr = spawnResult.stderr?.toString() ?? ''; const exitCode = spawnResult.status ?? undefined; - const killed = spawnResult.signal != null; + const signalCode = spawnResult.signal ?? null; + const killed = signalCode != null; const result: SyncResult = { stdout, @@ -414,6 +420,9 @@ export function xSync( get exitCode() { return exitCode; }, + get signalCode() { + return signalCode; + }, get pid() { return spawnResult.pid; }, @@ -430,7 +439,10 @@ export function xSync( } }; - if (opts.throwOnError && exitCode !== 0 && exitCode !== undefined) { + if ( + opts.throwOnError && + ((exitCode !== 0 && exitCode !== undefined) || signalCode != null) + ) { throw new NonZeroExitError(result, result); } diff --git a/src/non-zero-exit-error.ts b/src/non-zero-exit-error.ts index 6c0e900..03166e3 100644 --- a/src/non-zero-exit-error.ts +++ b/src/non-zero-exit-error.ts @@ -8,10 +8,18 @@ export class NonZeroExitError extends Error { return undefined; } + public get signalCode(): string | null { + return this.result.signalCode; + } + public constructor( public readonly result: CommonOutputApi, public readonly output?: Output ) { - super(`Process exited with non-zero status (${result.exitCode})`); + super( + result.signalCode != null + ? `Process was killed with signal ${result.signalCode}` + : `Process exited with non-zero status (${result.exitCode})` + ); } } diff --git a/src/test/main_test.ts b/src/test/main_test.ts index 2dfb581..4ca7e7b 100644 --- a/src/test/main_test.ts +++ b/src/test/main_test.ts @@ -81,6 +81,32 @@ describe('exec (async)', () => { expect(proc.exitCode).toBe(1); }); + test('throws when throwOnError=true and process is killed by signal', async () => { + const proc = x( + 'node', + ['-e', 'process.kill(process.pid, "SIGTERM")'], + {throwOnError: true} + ); + const err = await proc.then(() => null).catch((e: unknown) => e); + expect(err).toBeInstanceOf(NonZeroExitError); + expect((err as NonZeroExitError).signalCode).toBe('SIGTERM'); + expect(proc.exitCode).toBeUndefined(); + expect(proc.signalCode).toBe('SIGTERM'); + }); + + test('async iterator throws when throwOnError=true and process is killed by signal', async () => { + const proc = x( + 'node', + ['-e', 'process.kill(process.pid, "SIGTERM")'], + {throwOnError: true} + ); + await expect(async () => { + for await (const _line of proc) { + // consume iterator + } + }).rejects.toThrow(NonZeroExitError); + }); + test('supports stdin passed as a string', async () => { let result = await x('node', ['-e', 'process.stdin.pipe(process.stdout)'], { stdin: 'foo\nbar' @@ -140,6 +166,14 @@ describe('exec (sync)', () => { xSync('node', ['-e', 'process.exit(1);'], {throwOnError: true}); }).toThrow(NonZeroExitError); }); + + test('throws when throwOnError=true and process is killed by signal', () => { + expect(() => { + xSync('node', ['-e', 'process.kill(process.pid, "SIGTERM")'], { + throwOnError: true + }); + }).toThrow(NonZeroExitError); + }); }); if (isWindows) {