diff --git a/index.js b/index.js index bd52145..e5337e8 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,6 @@ import {spawn} from 'node:child_process'; import {once} from 'node:events'; +import process from 'node:process'; import {finished} from 'node:stream/promises'; import {lineIterator, combineAsyncIterators} from './utilities.js'; @@ -7,10 +8,11 @@ export default function nanoSpawn(command, commandArguments = [], options = {}) [commandArguments, options] = Array.isArray(commandArguments) ? [commandArguments, options] : [[], commandArguments]; + const start = process.hrtime.bigint(); const subprocess = spawn(command, commandArguments, getOptions(options)); - const promise = getResult(subprocess); + const promise = getResult(subprocess, start); const stdoutLines = lineIterator(subprocess.stdout); const stderrLines = lineIterator(subprocess.stderr); @@ -33,7 +35,7 @@ const getOptions = ({ stdio, }); -const getResult = async subprocess => { +const getResult = async (subprocess, start) => { const result = {}; const onExit = waitForExit(subprocess); const onStdoutDone = bufferOutput(subprocess.stdout, result, 'stdout'); @@ -41,12 +43,12 @@ const getResult = async subprocess => { try { await Promise.all([onExit, onStdoutDone, onStderrDone]); - const output = getOutput(subprocess, result); + const output = getOutput(subprocess, result, start); checkFailure(output); return output; } catch (error) { await Promise.allSettled([onExit, onStdoutDone, onStderrDone]); - throw Object.assign(error, getOutput(subprocess, result)); + throw Object.assign(error, getOutput(subprocess, result, start)); } }; @@ -80,12 +82,13 @@ const bufferOutput = async (stream, result, streamName) => { await finished(stream, {cleanup: true}); }; -const getOutput = ({exitCode, signalCode}, {stdout, stderr}) => ({ +const getOutput = ({exitCode, signalCode}, {stdout, stderr}, start) => ({ // `exitCode` can be a negative number (`errno`) when the `error` event is emitted on the subprocess ...(exitCode === null || exitCode < 0 ? {} : {exitCode}), ...(signalCode === null ? {} : {signalName: signalCode}), stdout: stripNewline(stdout), stderr: stripNewline(stderr), + durationMs: Number(process.hrtime.bigint() - start) / 1e6, }); const stripNewline = input => input?.at(-1) === '\n' diff --git a/test.js b/test.js index 4792646..ef3fabd 100644 --- a/test.js +++ b/test.js @@ -279,6 +279,18 @@ test('Handles stderr error', async t => { t.is(await t.throwsAsync(promise), error); }); +test('result.durationMs is set', async t => { + const {durationMs} = await nanoSpawn('node', ['--version']); + t.true(Number.isFinite(durationMs)); + t.true(durationMs > 0); +}); + +test('error.durationMs is set', async t => { + const {durationMs} = await t.throwsAsync(nanoSpawn('node', ['--unknown'])); + t.true(Number.isFinite(durationMs)); + t.true(durationMs > 0); +}); + if (isWindows) { test('Can run .exe file', async t => { t.is(path.extname(process.execPath), '.exe');