From dcafcb2d8bdaf7af37afa305afb10396a840825b Mon Sep 17 00:00:00 2001 From: Roshane Pascual <rtpascual@users.noreply.github.com> Date: Wed, 3 Apr 2024 13:16:58 -0700 Subject: [PATCH] fix(amplify-cli-core): gracefully handle execa race condition (#13692) --- .../src/__tests__/hooks/hooksExecutor.test.ts | 36 +++++++++++++++++++ .../src/hooks/hooksExecutor.ts | 10 +++++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/packages/amplify-cli-core/src/__tests__/hooks/hooksExecutor.test.ts b/packages/amplify-cli-core/src/__tests__/hooks/hooksExecutor.test.ts index 6aca81697fc..e307c606ccf 100644 --- a/packages/amplify-cli-core/src/__tests__/hooks/hooksExecutor.test.ts +++ b/packages/amplify-cli-core/src/__tests__/hooks/hooksExecutor.test.ts @@ -178,4 +178,40 @@ describe('hooksExecutioner tests', () => { duplicateErrorThrown, ); }); + + test('should not exit process if execa fails with exitCode being 0', async () => { + const execaMock = execa as jest.Mocked<typeof execa>; + (execaMock as any).mockReturnValue({ + exitCode: 0, + errNo: -32, + code: 'EPIPE', + syscall: 'write', + originalMessage: 'write EPIPE', + shortMessage: 'Command failed with EPIPE', + escapedCommand: 'testCommand', + stderr: '', + failed: true, + timedOut: false, + isCanceled: false, + killed: false, + }); + const processExitMock = jest.spyOn(process, 'exit').mockImplementation(() => undefined as never); + await executeHooks(HooksMeta.getInstance({ command: 'add', plugin: 'auth' } as CommandLineInput, 'pre')); + expect(processExitMock).toBeCalledTimes(0); + }); + + test('should exit process with exit code 76 if execa fails with exitCode other than 0', async () => { + const execaMock = execa as jest.Mocked<typeof execa>; + (execaMock as any).mockReturnValue({ + exitCode: 1, + stderr: '', + failed: true, + timedOut: false, + isCanceled: false, + killed: false, + }); + const processExitMock = jest.spyOn(process, 'exit').mockImplementation(() => undefined as never); + await executeHooks(HooksMeta.getInstance({ command: 'add', plugin: 'auth' } as CommandLineInput, 'pre')); + expect(processExitMock).toBeCalledWith(76); + }); }); diff --git a/packages/amplify-cli-core/src/hooks/hooksExecutor.ts b/packages/amplify-cli-core/src/hooks/hooksExecutor.ts index 758652bf5db..b0c430719c6 100644 --- a/packages/amplify-cli-core/src/hooks/hooksExecutor.ts +++ b/packages/amplify-cli-core/src/hooks/hooksExecutor.ts @@ -86,9 +86,17 @@ const execHelper = async ( error: errorParameter, }), stripFinalNewline: false, + stdout: 'inherit', + // added to do further checks before throwing due to EPIPE error + reject: false, }); - childProcess?.stdout?.pipe(process.stdout); const childProcessResult = await childProcess; + + // throw if child process ended with anything other than exitCode 0 + if (childProcessResult && childProcess.exitCode !== 0) { + throw childProcessResult; + } + if (!childProcessResult?.stdout?.endsWith(EOL)) { printer.blankLine(); }