Skip to content

Commit

Permalink
Add stdin/stdout/stderr options (#26)
Browse files Browse the repository at this point in the history
  • Loading branch information
ehmicky authored Aug 23, 2024
1 parent f34fd0e commit 4cb38a0
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 2 deletions.
19 changes: 17 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -22,6 +22,17 @@ export default function nanoSpawn(command, commandArguments = [], options = {})
});
}

const getOptions = ({
stdin,
stdout,
stderr,
stdio = [stdin, stdout, stderr],
...options
}) => ({
...options,
stdio,
});

const getResult = async subprocess => {
const result = {};
const onExit = waitForExit(subprocess);
Expand Down Expand Up @@ -57,6 +68,10 @@ const waitForExit = async subprocess => {
};

const bufferOutput = async (stream, result, streamName) => {
if (!stream) {
return;
}

stream.setEncoding('utf8');
result[streamName] = '';
stream.on('data', chunk => {
Expand All @@ -73,7 +88,7 @@ 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;

Expand Down
65 changes: 65 additions & 0 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,54 @@ 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 array', 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('can pass options.stdio string', async t => {
const promise = nanoSpawn('node', ['--version'], {stdio: 'ignore'});
t.is(promise.subprocess.stdin, null);
t.is(promise.subprocess.stdout, null);
t.is(promise.subprocess.stderr, null);
t.is(promise.subprocess.stdio.length, 3);
await promise;
});

test('options.stdio array 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('options.stdio string has priority over options.stdout', async t => {
const promise = nanoSpawn('node', ['--version'], {stdio: '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 t.throwsAsync(nanoSpawn('node', {timeout: 1}));
t.is(exitCode, undefined);
Expand Down Expand Up @@ -109,6 +157,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);
Expand Down Expand Up @@ -143,6 +197,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");']);

Expand Down
4 changes: 4 additions & 0 deletions utilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -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/);
Expand Down

0 comments on commit 4cb38a0

Please sign in to comment.