From cf0875fd1b6320329af9c04b389b5691009b8a02 Mon Sep 17 00:00:00 2001 From: ehmicky Date: Sun, 18 Aug 2024 15:40:40 +0100 Subject: [PATCH] Add `stdin`/`stdout`/`stderr` options --- index.js | 19 +++++++++++++++++-- test.js | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ utilities.js | 4 ++++ 3 files changed, 71 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 0e2215f..4e5ac85 100644 --- a/index.js +++ b/index.js @@ -8,7 +8,7 @@ export default function nanoSpawn(command, commandArguments = [], options = {}) ? [commandArguments, options] : [[], commandArguments]; - const subprocess = spawn(command, commandArguments, options); + const subprocess = spawn(command, commandArguments, getOptions(options)); const promise = getResult(subprocess); @@ -22,6 +22,17 @@ export default function nanoSpawn(command, commandArguments = [], options = {}) }); } +const getOptions = ({ + stdio, + stdin, + stdout, + stderr, + ...options +}) => ({ + ...options, + stdio: stdio === undefined ? [stdin, stdout, stderr] : stdio, +}); + const getResult = async subprocess => { const result = {}; const onExit = waitForExit(subprocess); @@ -55,6 +66,10 @@ const waitForExit = async subprocess => { }; const bufferOutput = async (stream, result, streamName) => { + if (!stream) { + return; + } + stream.setEncoding('utf8'); result[streamName] = ''; stream.on('data', chunk => { @@ -71,6 +86,6 @@ const getOutput = ({exitCode, signalCode}, {stdout, stderr}) => ({ stderr: stripNewline(stderr), }); -const stripNewline = input => input.at(-1) === '\n' +const stripNewline = input => input?.at(-1) === '\n' ? input.slice(0, input.at(-2) === '\r' ? -2 : -1) : input; diff --git a/test.js b/test.js index 79a26f8..2247e61 100644 --- a/test.js +++ b/test.js @@ -19,6 +19,39 @@ test('can pass options.argv0', async t => { t.is(stdout, testString); }); +test('can pass options.stdin', async t => { + const promise = nanoSpawn('node', ['--version'], {stdin: 'ignore'}); + t.is(promise.subprocess.stdin, null); + await promise; +}); + +test('can pass options.stdout', async t => { + const promise = nanoSpawn('node', ['--version'], {stdout: 'ignore'}); + t.is(promise.subprocess.stdout, null); + await promise; +}); + +test('can pass options.stderr', async t => { + const promise = nanoSpawn('node', ['--version'], {stderr: 'ignore'}); + t.is(promise.subprocess.stderr, null); + await promise; +}); + +test('can pass options.stdio', async t => { + const promise = nanoSpawn('node', ['--version'], {stdio: ['ignore', 'pipe', 'pipe', 'pipe']}); + t.is(promise.subprocess.stdin, null); + t.not(promise.subprocess.stdout, null); + t.not(promise.subprocess.stderr, null); + t.not(promise.subprocess.stdio[3], null); + await promise; +}); + +test('options.stdio has priority over options.stdout', async t => { + const promise = nanoSpawn('node', ['--version'], {stdio: ['pipe', 'pipe', 'pipe'], stdout: 'ignore'}); + t.not(promise.subprocess.stdout, null); + await promise; +}); + test('can pass options object without any arguments', async t => { const {exitCode, signalName} = await nanoSpawn('node', {timeout: 1}); t.is(exitCode, undefined); @@ -83,6 +116,12 @@ test('result.stderr strips Windows newline', async t => { t.is(stderr, '.'); }); +test('result.stdout is undefined if options.stdout "ignore"', async t => { + const {stdout, stderr} = await nanoSpawn('node', ['-e', 'console.log("."); console.error(".");'], {stdout: 'ignore'}); + t.is(stdout, undefined); + t.is(stderr, '.'); +}); + const multibyteString = '.\u{1F984}.'; const multibyteUint8Array = new TextEncoder().encode(multibyteString); const multibyteFirstHalf = multibyteUint8Array.slice(0, 3); @@ -117,6 +156,17 @@ test('promise.stderr can be iterated', async t => { t.is(stderr, 'a\nb'); }); +test('promise.stdout has no iterations if options.stdout "ignore"', async t => { + const promise = nanoSpawn('node', ['-e', 'console.log("."); console.error(".");'], {stdout: 'ignore'}); + const [stdoutLines, stderrLines] = await Promise.all([arrayFromAsync(promise.stdout), arrayFromAsync(promise.stderr)]); + t.deepEqual(stdoutLines, []); + t.deepEqual(stderrLines, ['.']); + + const {stdout, stderr} = await promise; + t.is(stdout, undefined); + t.is(stderr, '.'); +}); + test('promise can be iterated with both stdout and stderr', async t => { const promise = nanoSpawn('node', ['-e', 'console.log("a"); console.error("b"); console.log("c"); console.error("d");']); diff --git a/utilities.js b/utilities.js index e98e466..2bcc148 100644 --- a/utilities.js +++ b/utilities.js @@ -21,6 +21,10 @@ export async function * combineAsyncIterators(iterator1, iterator2) { } export async function * lineIterator(iterable) { + if (!iterable) { + return; + } + let buffer = ''; for await (const chunk of iterable) { const lines = `${buffer}${chunk}`.split(/\r?\n/);