diff --git a/packages/helpers/__tests__/exec-command-wrapper.test.ts b/packages/helpers/__tests__/exec-command-wrapper.test.ts new file mode 100644 index 00000000..91d9d69d --- /dev/null +++ b/packages/helpers/__tests__/exec-command-wrapper.test.ts @@ -0,0 +1,53 @@ +import {Command} from '../src/exec-command-wrapper' +import * as io from '@actions/io' + +const IS_LINUX = process.platform === 'linux' + +describe('Command', () => { + it('creates a command object', async () => { + if (IS_LINUX) { + const toolpath = await io.which('echo', true) + const command = new Command(`"${toolpath}"`, ['hello']) + expect(command).toBeDefined() + expect(command).toBeInstanceOf(Command) + } + }) + + it('runs a command with non-zero exit code', async () => { + if (IS_LINUX) { + const nonExistentDir = 'non-existent-dir' + const toolpath = await io.which('ls', true) + const args = ['-l', nonExistentDir] + const command = new Command(`"${toolpath}"`, args) + + let failed = false + + await command.execute().catch(err => { + failed = true + expect(err.message).toContain( + `The process '${toolpath}' failed with exit code ` + ) + }) + + expect(failed).toBe(true) + } + }) + + it('runs a command with zero exit code', async () => { + if (IS_LINUX) { + const toolpath = await io.which('echo', true) + const command = new Command(`"${toolpath}"`, ['hello']) + const result = await command.execute() + expect(result).toEqual('hello') + } + }) + + it('runs a command with empty output', async () => { + if (IS_LINUX) { + const toolpath = await io.which('echo', true) + const command = new Command(`"${toolpath}"`, ['']) + const result = await command.execute() + expect(result).toEqual('') + } + }) +}) diff --git a/packages/helpers/src/exec-command-wrapper.ts b/packages/helpers/src/exec-command-wrapper.ts new file mode 100644 index 00000000..51b8206f --- /dev/null +++ b/packages/helpers/src/exec-command-wrapper.ts @@ -0,0 +1,123 @@ +import * as core from '@actions/core' +import * as exec from '@actions/exec' + +export class Command { + private readonly commandText: string + private readonly args: string[] + private readonly options: exec.ExecOptions | undefined + + private failOnError = false + private throwOnError = false + + private failOnEmptyOutput = false + private throwOnEmptyOutput = false + + constructor( + commandText: string, + args: string[], + options: exec.ExecOptions | undefined = undefined + ) { + this.commandText = commandText + this.args = args + this.options = options + } + + get failOn(): {error: () => Command; empty: () => Command} { + return { + error: this.setFailOnError, + empty: this.setFailOnEmptyOutput + } + } + + get throwOn(): {error: () => Command; empty: () => Command} { + return { + error: this.setThrowOnError, + empty: this.setThrowOnEmptyOutput + } + } + + private setFailOnError = (): Command => { + this.failOnError = true + return this + } + + private setThrowOnError = (): Command => { + this.throwOnError = true + return this + } + + private setFailOnEmptyOutput = (): Command => { + this.failOnEmptyOutput = true + return this + } + + private setThrowOnEmptyOutput = (): Command => { + this.throwOnEmptyOutput = true + return this + } + + private setFailedOnNonZeroExitCode( + command: string, + exitCode: number, + error: string + ): void { + if (exitCode !== 0) { + error = !error.trim() + ? `The '${command}' command failed with exit code: ${exitCode}` + : error + core.setFailed(error) + } + return + } + + private throwErrorOnNonZeroExitCode( + command: string, + exitCode: number, + error: string + ): void { + if (exitCode !== 0) { + error = !error.trim() + ? `The '${command}' command failed with exit code: ${exitCode}` + : error + throw new Error(error) + } + return + } + + async execute(): Promise { + const {stdout, stderr, exitCode} = await exec.getExecOutput( + this.commandText, + this.args, + this.options + ) + + if (this.failOnError) { + this.setFailedOnNonZeroExitCode(this.commandText, exitCode, stderr) + return stdout.trim() + } + + if (this.throwOnError) { + this.throwErrorOnNonZeroExitCode(this.commandText, exitCode, stderr) + return stdout.trim() + } + + if (this.failOnEmptyOutput && !stdout.trim()) { + core.setFailed( + `The '${this.commandText}' command failed with empty output` + ) + return stdout.trim() + } + + if (this.throwOnEmptyOutput && !stdout.trim()) { + throw new Error( + `The '${this.commandText}' command failed with empty output` + ) + } + + return stdout.trim() + } +} + +// new Command('echo', ['hello', 'world']) +// .failOn.error() +// .execute()