diff --git a/packages/exec/__tests__/exec.test.ts b/packages/exec/__tests__/exec.test.ts index 387c83f5..e7ee60c8 100644 --- a/packages/exec/__tests__/exec.test.ts +++ b/packages/exec/__tests__/exec.test.ts @@ -153,6 +153,23 @@ describe('@actions/exec', () => { expect(output.trim()).toBe(`hello`) }) + it('Runs exec successfully with shell wildcard substitution', async () => { + let tool: string + let args: string[] + let opts: im.ExecOptions + if (IS_WINDOWS) { + tool = 'cmd' + args = ['/c', 'echo', 'hello'] + opts = {shell: process.env.ComSpec} + } else { + tool = 'ls' + args = ['-l', '*.md'] + opts = {shell: '/bin/sh'} + } + const exitCode = await exec.exec(tool, args, opts) + expect(exitCode).toBe(0) + }) + it('Exec fails with error on bad call', async () => { const _testExecOptions = getExecOptions() @@ -286,7 +303,7 @@ describe('@actions/exec', () => { expect(stderrCalled).toBeTruthy() }) - it('Handles child process holding streams open', async function() { + it('Handles child process holding streams open', async function () { const semaphorePath = path.join( getTestTemp(), 'child-process-semaphore.txt' @@ -332,7 +349,7 @@ describe('@actions/exec', () => { fs.unlinkSync(semaphorePath) }, 10000) // this was timing out on some slower hosted macOS runs at default 5s - it('Handles child process holding streams open and non-zero exit code', async function() { + it('Handles child process holding streams open and non-zero exit code', async function () { const semaphorePath = path.join( getTestTemp(), 'child-process-semaphore.txt' @@ -386,7 +403,7 @@ describe('@actions/exec', () => { fs.unlinkSync(semaphorePath) }, 10000) // this was timing out on some slower hosted macOS runs at default 5s - it('Handles child process holding streams open and stderr', async function() { + it('Handles child process holding streams open and stderr', async function () { const semaphorePath = path.join( getTestTemp(), 'child-process-semaphore.txt' @@ -654,16 +671,16 @@ describe('@actions/exec', () => { expect(exitCode).toBe(0) expect(outStream.getContents().split(os.EOL)[0]).toBe( `[command]${exePath} /c echo` + - ` helloworld` + - ` "hello world"` + - ` "hello:\\"world again\\""` + - ` hello,world` + ` helloworld` + + ` "hello world"` + + ` "hello:\\"world again\\""` + + ` hello,world` ) expect(output.trim()).toBe( 'helloworld' + - ' "hello world"' + - ' "hello:\\"world again\\""' + - ' hello,world' + ' "hello world"' + + ' "hello:\\"world again\\""' + + ' hello,world' ) }) @@ -752,7 +769,7 @@ describe('@actions/exec', () => { ) expect(output.trim()).toBe( 'args[0]: "my arg 1"\r\n' + - 'args[1]: "my arg 2"' + 'args[1]: "my arg 2"' ) }) @@ -782,7 +799,7 @@ describe('@actions/exec', () => { ) expect(output.trim()).toBe( 'args[0]: "my arg 1"\r\n' + - 'args[1]: "my arg 2"' + 'args[1]: "my arg 2"' ) } catch (err) { process.env['Path'] = originalPath @@ -838,57 +855,57 @@ describe('@actions/exec', () => { expect(exitCode).toBe(0) expect(outStream.getContents().split(os.EOL)[0]).toBe( `[command]${process.env.ComSpec} /D /S /C ""${cmdPath}"` + - ` helloworld` + - ` "hello world"` + - ` "hello\tworld"` + - ` "hello&world"` + - ` "hello(world"` + - ` "hello)world"` + - ` "hello[world"` + - ` "hello]world"` + - ` "hello{world"` + - ` "hello}world"` + - ` "hello^world"` + - ` "hello=world"` + - ` "hello;world"` + - ` "hello!world"` + - ` "hello'world"` + - ` "hello+world"` + - ` "hello,world"` + - ` "hello\`world"` + - ` "hello~world"` + - ` "hello|world"` + - ` "helloworld"` + - ` "hello:""world again"""` + - ` "hello world\\\\"` + - `"` + ` helloworld` + + ` "hello world"` + + ` "hello\tworld"` + + ` "hello&world"` + + ` "hello(world"` + + ` "hello)world"` + + ` "hello[world"` + + ` "hello]world"` + + ` "hello{world"` + + ` "hello}world"` + + ` "hello^world"` + + ` "hello=world"` + + ` "hello;world"` + + ` "hello!world"` + + ` "hello'world"` + + ` "hello+world"` + + ` "hello,world"` + + ` "hello\`world"` + + ` "hello~world"` + + ` "hello|world"` + + ` "helloworld"` + + ` "hello:""world again"""` + + ` "hello world\\\\"` + + `"` ) expect(output.trim()).toBe( 'args[0]: "helloworld"\r\n' + - 'args[1]: "hello world"\r\n' + - 'args[2]: "hello\tworld"\r\n' + - 'args[3]: "hello&world"\r\n' + - 'args[4]: "hello(world"\r\n' + - 'args[5]: "hello)world"\r\n' + - 'args[6]: "hello[world"\r\n' + - 'args[7]: "hello]world"\r\n' + - 'args[8]: "hello{world"\r\n' + - 'args[9]: "hello}world"\r\n' + - 'args[10]: "hello^world"\r\n' + - 'args[11]: "hello=world"\r\n' + - 'args[12]: "hello;world"\r\n' + - 'args[13]: "hello!world"\r\n' + - 'args[14]: "hello\'world"\r\n' + - 'args[15]: "hello+world"\r\n' + - 'args[16]: "hello,world"\r\n' + - 'args[17]: "hello`world"\r\n' + - 'args[18]: "hello~world"\r\n' + - 'args[19]: "hello|world"\r\n' + - 'args[20]: "hello"\r\n' + - 'args[21]: "hello>world"\r\n' + - 'args[22]: "hello:world again"\r\n' + - 'args[23]: "hello world\\\\"' + 'args[1]: "hello world"\r\n' + + 'args[2]: "hello\tworld"\r\n' + + 'args[3]: "hello&world"\r\n' + + 'args[4]: "hello(world"\r\n' + + 'args[5]: "hello)world"\r\n' + + 'args[6]: "hello[world"\r\n' + + 'args[7]: "hello]world"\r\n' + + 'args[8]: "hello{world"\r\n' + + 'args[9]: "hello}world"\r\n' + + 'args[10]: "hello^world"\r\n' + + 'args[11]: "hello=world"\r\n' + + 'args[12]: "hello;world"\r\n' + + 'args[13]: "hello!world"\r\n' + + 'args[14]: "hello\'world"\r\n' + + 'args[15]: "hello+world"\r\n' + + 'args[16]: "hello,world"\r\n' + + 'args[17]: "hello`world"\r\n' + + 'args[18]: "hello~world"\r\n' + + 'args[19]: "hello|world"\r\n' + + 'args[20]: "hello"\r\n' + + 'args[21]: "hello>world"\r\n' + + 'args[22]: "hello:world again"\r\n' + + 'args[23]: "hello world\\\\"' ) }) } diff --git a/packages/exec/src/interfaces.ts b/packages/exec/src/interfaces.ts index e9219b0b..95b3c48b 100644 --- a/packages/exec/src/interfaces.ts +++ b/packages/exec/src/interfaces.ts @@ -12,6 +12,9 @@ export interface ExecOptions { /** optional. defaults to false */ silent?: boolean + /** optional. Runs command inside of a shell. A different shell can be specified as a string. Defaults to false */ + shell?: boolean | string + /** optional out stream to use. Defaults to process.stdout */ outStream?: stream.Writable diff --git a/packages/exec/src/toolrunner.ts b/packages/exec/src/toolrunner.ts index c578b258..1bcaf0e6 100644 --- a/packages/exec/src/toolrunner.ts +++ b/packages/exec/src/toolrunner.ts @@ -377,6 +377,7 @@ export class ToolRunner extends events.EventEmitter { const result = {} result.cwd = options.cwd result.env = options.env + result.shell = options.shell result['windowsVerbatimArguments'] = options.windowsVerbatimArguments || this._isCmdFile() if (options.windowsVerbatimArguments) {