From a8f77a47c8e8b592f73a97f9520a398da3b272a5 Mon Sep 17 00:00:00 2001 From: Dan Allen Date: Wed, 30 Oct 2024 16:35:50 -0600 Subject: [PATCH] resolves #47 emit stderr lines from command in sequence --- CHANGELOG.adoc | 1 + .../modules/ROOT/pages/configure-run.adoc | 7 +++++-- .../lib/util/run-command.js | 16 +++++++------- .../test/collector-extension-test.js | 21 +++++++++++-------- 4 files changed, 25 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index fca1397..7df97cf 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -9,6 +9,7 @@ For a detailed view of what's changed, refer to the {url-repo}/commits[commit hi === Changed * better detect and report command not found across platforms +* emit stderr lines from command in sequence (#47) === Fixed diff --git a/docs/collector-extension/modules/ROOT/pages/configure-run.adoc b/docs/collector-extension/modules/ROOT/pages/configure-run.adoc index 823087f..6d276b6 100644 --- a/docs/collector-extension/modules/ROOT/pages/configure-run.adoc +++ b/docs/collector-extension/modules/ROOT/pages/configure-run.adoc @@ -146,9 +146,12 @@ This alternation is not often needed if the command has arguments. By default, both the stdout (output stream) and stderr (error stream) of the command are directed to the current terminal. The extension also logs the command it's about to run at the info level. + +NOTE: The output and error lines may get interleaved differently compared to what was emitted by the command due to how Node.js works. + If you want to suppress the stdout and the info log message, you can set the `runtime.quiet` key in the playbook to true. -If you also want to suppress the stderr, you can set the `runtime.silent` key in the playbook to true. -If the command fails, the stderr lines will still be appended to the error message. +If you also want to suppress the stderr, you can set the `runtime.silent` key in the playbook to true (or redirect stderr to /dev/null). +If the command fails, the stderr lines are always appended to the error message. === Use the current Node.js diff --git a/packages/collector-extension/lib/util/run-command.js b/packages/collector-extension/lib/util/run-command.js index c3d443c..6cfdd05 100644 --- a/packages/collector-extension/lib/util/run-command.js +++ b/packages/collector-extension/lib/util/run-command.js @@ -75,20 +75,18 @@ async function spawnCommand (cmdv, opts, output) { const ps = spawn(cmd, args, opts) ps.on('close', (exitCode, signalCode) => { if (exitCode === 0) { - if (stderr.length && output.stderr) process.stderr.write(stderr.join('')) - resolve() + return resolve() } else if (exitCode === 127 || (IS_WIN && exitCode === 1 && /not recognized as .* command/.test(stderr[0]))) { - reject(new Error(`Command not found: ${cmd}`)) - } else { - const result = signalCode ? `terminated with signal ${signalCode}` : `failed with exit code ${exitCode ?? -1}` - let msg = `Command ${result}: ${ps.spawnargs.join(' ')}` - if (stderr.length) msg += '\n' + stderr.join('').replace(/\r(?=\n)/g, '') - reject(new Error(msg)) + return reject(new Error(`Command not found: ${cmd}`)) } + const result = signalCode ? `terminated with signal ${signalCode}` : `failed with exit code ${exitCode ?? -1}` + let msg = `Command ${result}: ${ps.spawnargs.join(' ')}` + if (stderr.length) msg += '\n' + stderr.join('').replace(/\r(?=\n)/g, '') + reject(new Error(msg)) }) ps.on('error', (err) => reject(err.code === 'ENOENT' ? new Error(`Command not found: ${cmd}`) : err)) ps.stdout.on('data', (data) => output.stdout && process.stdout.write(data)) - ps.stderr.on('data', (data) => stderr.push(data)) + ps.stderr.on('data', (data) => stderr.push(data) && output.stderr && process.stderr.write(data)) ps.stdin.end() } catch (err) { reject(err) diff --git a/packages/collector-extension/test/collector-extension-test.js b/packages/collector-extension/test/collector-extension-test.js index 300ab46..d8737fe 100644 --- a/packages/collector-extension/test/collector-extension-test.js +++ b/packages/collector-extension/test/collector-extension-test.js @@ -2120,7 +2120,7 @@ describe('collector extension', () => { const expectedMessage = '(@antora/collector-extension): Command not found: no-such-command ' + `(url: http://localhost:${gitServerPort}/at-root/.git | branch: main)` - assert.throws(await trapAsyncError(() => runScenario({ repoName: 'at-root', collectorConfig })), { + assert.throws(await trapAsyncError(() => runScenario({ repoName: 'at-root', collectorConfig, silent: true })), { name: Error.name, message: expectedMessage, }) @@ -2168,14 +2168,17 @@ describe('collector extension', () => { const expectedMessage = '(@antora/collector-extension): Command failed with exit code 1: ' + (windows ? '' : `${process.execPath} .gen-failure.js (url: `) - assert.throws(await trapAsyncError(() => runScenario({ repoName: 'at-root', collectorConfig })), (err) => { - assert(err instanceof Error) - assert.equal(err.name, Error.name) - assert(err.message.startsWith(expectedMessage)) - assert(err.message.includes('we expect this to fail')) - assert(!err.message.includes('\r\n')) - return true - }) + assert.throws( + await trapAsyncError(() => runScenario({ repoName: 'at-root', collectorConfig, silent: true })), + (err) => { + assert(err instanceof Error) + assert.equal(err.name, Error.name) + assert(err.message.startsWith(expectedMessage)) + assert(err.message.includes('we expect this to fail')) + assert(!err.message.includes('\r\n')) + return true + } + ) }) it('should ignore error if command fails when on_failure is ignore', async () => { -- GitLab