diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index fca139765962d9290c371a7dc94176beffb28238..7df97cf632e83419e752ba0d351d32c4052f1921 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 823087fe34d069b64a1c91ae1081e853d24d97d3..6d276b67d44731fb2af58e35cf6c0b17ed7c284b 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 c3d443cfe14156dd661443c4cdbf10be2e4305e0..6cfdd056d2270d438d3a64db56da3af49b10d06a 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 300ab469fb80b2d9fd0ad7fdb0ccd2ab1c3ea70d..d8737fe03b6a6fd1354d5af0b46d36484de83819 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 () => {