diff --git a/package.json b/package.json index 9ec7e33..fad9581 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@athenna/common", - "version": "4.9.1", + "version": "4.9.2", "description": "The Athenna common helpers to use in any Node.js ESM project.", "license": "MIT", "author": "João Lenon ", diff --git a/src/exceptions/NodeCommandException.ts b/src/exceptions/NodeCommandException.ts index efe1128..01d75ac 100644 --- a/src/exceptions/NodeCommandException.ts +++ b/src/exceptions/NodeCommandException.ts @@ -13,7 +13,8 @@ export class NodeCommandException extends Exception { public constructor(command: string, error: any) { let help = '' - help = help.concat(`Command stdout:\n\n ${error.stdout}`) + help = help.concat(`Command code:\n\n ${error.exitCode}`) + help = help.concat(`\n\n Command stdout:\n\n ${error.stdout}`) help = help.concat(`\n\n Command stderr:\n\n ${error.stderr}`) help = help.concat(`\n\n ${error.stack}`) diff --git a/src/helpers/Exec.ts b/src/helpers/Exec.ts index 5d44dca..f17a4f0 100644 --- a/src/helpers/Exec.ts +++ b/src/helpers/Exec.ts @@ -9,20 +9,18 @@ import { debug } from '#src/debug' import { Is } from '#src/helpers/Is' -import { promisify } from 'node:util' import { Transform } from 'node:stream' import { File } from '#src/helpers/File' import { Uuid } from '#src/helpers/Uuid' +import { exec } from 'node:child_process' import { Options } from '#src/helpers/Options' import { request as requestHttp } from 'node:http' import { request as requestHttps } from 'node:https' import type { ExecOptions } from 'node:child_process' -import { exec as childProcessExec } from 'node:child_process' +import type { CommandOutput } from '#src/types/CommandOutput' import type { PaginationOptions, PaginatedResponse } from '#src/types' import { NodeCommandException } from '#src/exceptions/NodeCommandException' -const exec = promisify(childProcessExec) - export class Exec { /** * Sleep the code in the line that this function @@ -49,7 +47,7 @@ export class Exec { public static async command( command: string, options?: { ignoreErrors?: boolean }, - ): Promise<{ stdout: string; stderr: string }> { + ): Promise { options = Options.create(options, { ignoreErrors: false, }) @@ -62,31 +60,40 @@ export class Exec { debug('executing command: %s', command) - try { - const result = await exec(command, execOptions) + return new Promise((resolve, reject) => { + let execError = null - if (!result.stdout) result.stdout = '' - if (!result.stderr) result.stderr = '' + const result: CommandOutput = { + stdout: '', + stderr: '', + exitCode: 0, + } - debug('command executed successfully') - debug('command stdout: %s', result.stdout) - debug('command stderr: %s', result.stderr) + exec(command, execOptions, (error, stdout, stderr) => { + if (error) execError = error + if (stdout) result.stdout = stdout + if (stderr) result.stderr = stderr - return result - } catch (error) { - if (!error.stdout) error.stdout = '' - if (!error.stderr) error.stderr = '' + debug('command executed') + debug('command stdout: %s', result.stdout) + debug('command stderr: %s', result.stderr) + debug('command exitCode: %s', result.exitCode) - debug('command has failed') - debug('command stdout: %s', error.stdout) - debug('command stderr: %s', error.stderr) + if (!execError) { + return resolve(result) + } - if (options.ignoreErrors) { - return { stdout: error.stdout, stderr: error.stderr } - } + execError.stdout = result.stdout + execError.stderr = result.stderr + execError.exitCode = result.exitCode - throw new NodeCommandException(command, error) - } + if (options.ignoreErrors) { + return resolve(result) + } + + return reject(new NodeCommandException(command, execError)) + }).on('exit', exitCode => (result.exitCode = exitCode)) + }) } /** diff --git a/src/types/CommandOutput.ts b/src/types/CommandOutput.ts new file mode 100644 index 0000000..61abd48 --- /dev/null +++ b/src/types/CommandOutput.ts @@ -0,0 +1,14 @@ +/** + * @athenna/common + * + * (c) João Lenon + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +export type CommandOutput = { + stdout: string + stderr: string + exitCode: number +} diff --git a/src/types/index.ts b/src/types/index.ts index e7c1bbb..aca1aa7 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -28,6 +28,7 @@ export type { export * from '#src/types/Merge' export * from '#src/types/Except' export * from '#src/types/PathDirs' +export * from '#src/types/CommandOutput' export * from '#src/types/ObjectBuilderOptions' export * from '#src/types/json/FileJson' diff --git a/tests/unit/ExecTest.ts b/tests/unit/ExecTest.ts index 1b33a66..eff0ed8 100644 --- a/tests/unit/ExecTest.ts +++ b/tests/unit/ExecTest.ts @@ -24,8 +24,9 @@ export default class ExecTest { @Test() public async shouldBeAbleToExecuteACommandInTheVMAndGetTheStdout({ assert }: Context) { - const { stdout } = await Exec.command('ls') + const { stdout, exitCode } = await Exec.command('ls') + assert.equal(exitCode, 0) assert.isTrue(stdout.includes('README.md')) } @@ -44,8 +45,9 @@ export default class ExecTest { return } - const { stdout } = await Exec.command('echo "error thrown" && exit 255', { ignoreErrors: true }) + const { stdout, exitCode } = await Exec.command('echo "error thrown" && exit 255', { ignoreErrors: true }) + assert.equal(exitCode, 255) assert.isTrue(stdout.includes('error thrown')) }