Skip to content

Commit

Permalink
Add stdin/stdout/stderr options
Browse files Browse the repository at this point in the history
  • Loading branch information
ehmicky committed Aug 18, 2024
1 parent 547ab21 commit cf0875f
Show file tree
Hide file tree
Showing 3 changed files with 71 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 = ({
stdio,
stdin,
stdout,
stderr,
...options
}) => ({
...options,
stdio: stdio === undefined ? [stdin, stdout, stderr] : stdio,
});

const getResult = async subprocess => {
const result = {};
const onExit = waitForExit(subprocess);
Expand Down Expand Up @@ -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 => {
Expand All @@ -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;
50 changes: 50 additions & 0 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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");']);

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 cf0875f

Please sign in to comment.