From 72a2612e0c42ff7fcc72f06fafe16979f8858541 Mon Sep 17 00:00:00 2001 From: Dusan Trickovic Date: Tue, 19 Sep 2023 12:00:52 +0200 Subject: [PATCH] Implement new logic for the command wrapper and add tests --- .../__tests__/exec-command-wrapper.test.ts | 94 ++++++---- packages/helpers/src/exec-command-wrapper.ts | 168 +++++++----------- 2 files changed, 124 insertions(+), 138 deletions(-) diff --git a/packages/helpers/__tests__/exec-command-wrapper.test.ts b/packages/helpers/__tests__/exec-command-wrapper.test.ts index 91d9d69d..04401a3f 100644 --- a/packages/helpers/__tests__/exec-command-wrapper.test.ts +++ b/packages/helpers/__tests__/exec-command-wrapper.test.ts @@ -1,53 +1,83 @@ -import {Command} from '../src/exec-command-wrapper' +import CommandHelper from '../src/exec-command-wrapper' import * as io from '@actions/io' -const IS_LINUX = process.platform === 'linux' +const IS_WINDOWS = process.platform === 'win32' 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) + let toolpath: string + let args: string[] + if (IS_WINDOWS) { + toolpath = await io.which('cmd', true) + args = ['/c', 'echo', 'hello'] + } else { + toolpath = await io.which('echo', true) + args = ['hello'] } + const command = new CommandHelper(`"${toolpath}"`, args) + expect(command).toBeDefined() + expect(command).toBeInstanceOf(CommandHelper) }) 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) + let toolpath: string + let args: string[] + if (IS_WINDOWS) { + toolpath = await io.which('cmd', true) + args = ['/c', 'dir', 'non-existent-dir'] + } else { + toolpath = await io.which('ls', true) + args = ['-l', 'non-existent-dir'] + } + const command = new CommandHelper(`"${toolpath}"`, args, undefined, { + throwOnEmptyOutput: true + }) + try { + const result = await command.execute() + expect(result.exitCode).not.toEqual(0) + } catch (err) { + expect(err.message).toContain( + `The process '${toolpath}' failed with exit code ` + ) } }) 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') + let toolpath: string + let args: string[] + if (IS_WINDOWS) { + toolpath = await io.which('cmd', true) + args = ['/c', 'echo', 'hello'] + } else { + toolpath = await io.which('echo', true) + args = ['hello'] } + const command = new CommandHelper(`"${toolpath}"`, args) + const result = await command.execute() + + expect(result.stdout).toContain('hello') + expect(result.exitCode).toEqual(0) }) it('runs a command with empty output', async () => { - if (IS_LINUX) { - const toolpath = await io.which('echo', true) - const command = new Command(`"${toolpath}"`, ['']) + let toolpath: string + let args: string[] + if (IS_WINDOWS) { + toolpath = await io.which('cmd', true) + args = ['/c', 'echo.'] + } else { + toolpath = await io.which('echo', true) + args = [''] + } + + const command = new CommandHelper(`"${toolpath}"`, args, undefined, { + throwOnEmptyOutput: true + }) + try { const result = await command.execute() - expect(result).toEqual('') + expect(result.stdout).toBe('') + } catch (err) { + expect(err.message).toContain('Command produced empty output.') } }) }) diff --git a/packages/helpers/src/exec-command-wrapper.ts b/packages/helpers/src/exec-command-wrapper.ts index 51b8206f..3882a208 100644 --- a/packages/helpers/src/exec-command-wrapper.ts +++ b/packages/helpers/src/exec-command-wrapper.ts @@ -1,123 +1,79 @@ -import * as core from '@actions/core' import * as exec from '@actions/exec' +import * as core from '@actions/core' -export class Command { - private readonly commandText: string - private readonly args: string[] - private readonly options: exec.ExecOptions | undefined +export default class CommandHelper { + private commandText: string + private args: string[] + private options: exec.ExecOptions | undefined - private failOnError = false - private throwOnError = false - - private failOnEmptyOutput = false - private throwOnEmptyOutput = false + private throwOnError: boolean + private throwOnEmptyOutput: boolean + private failOnError: boolean + private failOnEmptyOutput: boolean constructor( commandText: string, - args: string[], - options: exec.ExecOptions | undefined = undefined + args: string[] = [], + options: exec.ExecOptions | undefined = {}, + config: { + throwOnError?: boolean + throwOnEmptyOutput?: boolean + failOnError?: boolean + failOnEmptyOutput?: boolean + } = {} ) { this.commandText = commandText this.args = args this.options = options + this.throwOnError = config.throwOnError ?? false + this.throwOnEmptyOutput = config.throwOnEmptyOutput ?? false + this.failOnError = config.failOnError ?? false + this.failOnEmptyOutput = config.failOnEmptyOutput ?? false } - 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` + async execute(): Promise { + try { + const output = await exec.getExecOutput( + this.commandText, + this.args, + this.options ) - return stdout.trim() - } - if (this.throwOnEmptyOutput && !stdout.trim()) { - throw new Error( - `The '${this.commandText}' command failed with empty output` - ) - } + if (this.throwOnError && output.stderr) { + this.onError(output.stderr).throw() + } - return stdout.trim() + if (this.throwOnEmptyOutput && output.stdout.trim() === '') { + this.onError('Command produced empty output.').throw() + } + + if (this.failOnError && output.stderr) { + this.onError(output.stderr).fail() + } + + if (this.failOnEmptyOutput && output.stdout.trim() === '') { + this.onError('Command produced empty output.').fail() + } + + return output + } catch (error) { + throw new Error((error as Error).message) + } + } + + private onError(errorMessage: string): { + throw: () => never + fail: () => void + } { + core.error(`Error occurred: ${errorMessage}`) + + return { + throw: () => { + throw new Error(errorMessage) + }, + fail: () => { + core.setFailed(errorMessage) + } + } } } - -// new Command('echo', ['hello', 'world']) -// .failOn.error() -// .execute()