diff --git a/packages/exec/__tests__/exec.test.ts b/packages/exec/__tests__/exec.test.ts index 387c83f5..f4dfab0f 100644 --- a/packages/exec/__tests__/exec.test.ts +++ b/packages/exec/__tests__/exec.test.ts @@ -286,6 +286,52 @@ describe('@actions/exec', () => { expect(stderrCalled).toBeTruthy() }) + it('Handles stdin shell', async () => { + let command: string + if (IS_WINDOWS) { + command = 'wait-for-input.cmd' + } else { + command = 'wait-for-input.sh' + } + + const waitForInput: string = path.join(__dirname, 'scripts', command) + + const _testExecOptions = getExecOptions() + + _testExecOptions.listeners = { + stdout: (data: Buffer) => { + expect(data).toEqual(Buffer.from(`this is my input${os.EOL}`)) + } + } + + _testExecOptions.input = Buffer.from('this is my input') + + const exitCode = await exec.exec(`"${waitForInput}"`, [], _testExecOptions) + expect(exitCode).toBe(0) + }) + + it('Handles stdin js', async () => { + const waitForInput: string = path.join( + __dirname, + 'scripts', + 'wait-for-input.js' + ) + + const _testExecOptions = getExecOptions() + + _testExecOptions.listeners = { + stdout: (data: Buffer) => { + expect(data).toEqual(Buffer.from(`this is my input`)) + } + } + + _testExecOptions.input = Buffer.from('this is my input') + + const nodePath = await io.which('node', true) + const exitCode = await exec.exec(nodePath, [waitForInput], _testExecOptions) + expect(exitCode).toBe(0) + }) + it('Handles child process holding streams open', async function() { const semaphorePath = path.join( getTestTemp(), diff --git a/packages/exec/__tests__/scripts/wait-for-input.cmd b/packages/exec/__tests__/scripts/wait-for-input.cmd new file mode 100755 index 00000000..1ba3ec4b --- /dev/null +++ b/packages/exec/__tests__/scripts/wait-for-input.cmd @@ -0,0 +1,3 @@ +@echo off +set /p var= +echo %var% diff --git a/packages/exec/__tests__/scripts/wait-for-input.js b/packages/exec/__tests__/scripts/wait-for-input.js new file mode 100755 index 00000000..82b1e307 --- /dev/null +++ b/packages/exec/__tests__/scripts/wait-for-input.js @@ -0,0 +1,3 @@ +var fs = require('fs') +var data = fs.readFileSync(0, 'utf-8') +process.stdout.write(data) diff --git a/packages/exec/__tests__/scripts/wait-for-input.sh b/packages/exec/__tests__/scripts/wait-for-input.sh new file mode 100755 index 00000000..1e8cd850 --- /dev/null +++ b/packages/exec/__tests__/scripts/wait-for-input.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +read var +echo $var diff --git a/packages/exec/src/interfaces.ts b/packages/exec/src/interfaces.ts index e9219b0b..436fc0ac 100644 --- a/packages/exec/src/interfaces.ts +++ b/packages/exec/src/interfaces.ts @@ -30,6 +30,9 @@ export interface ExecOptions { /** optional. How long in ms to wait for STDIO streams to close after the exit event of the process before terminating. defaults to 10000 */ delay?: number + /** optional. input to write to the process on STDIN. */ + input?: Buffer + /** optional. Listeners for output. Callback functions that will be called on these events */ listeners?: { stdout?: (data: Buffer) => void diff --git a/packages/exec/src/toolrunner.ts b/packages/exec/src/toolrunner.ts index c578b258..01e7cd1f 100644 --- a/packages/exec/src/toolrunner.ts +++ b/packages/exec/src/toolrunner.ts @@ -524,6 +524,14 @@ export class ToolRunner extends events.EventEmitter { resolve(exitCode) } }) + + if (this.options.input) { + if (!cp.stdin) { + throw new Error('child process missing stdin') + } + + cp.stdin.end(this.options.input) + } }) } }