Skip to content

Commit

Permalink
Fix error.stdout|stderr|all type when encoding option is used (#652)
Browse files Browse the repository at this point in the history
  • Loading branch information
ehmicky committed Jan 6, 2024
1 parent 7701e9e commit cff6847
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 21 deletions.
43 changes: 22 additions & 21 deletions lib/stream.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import {Buffer} from 'node:buffer';
import {once} from 'node:events';
import {finished} from 'node:stream/promises';
import process from 'node:process';
import getStream, {getStreamAsBuffer, getStreamAsArrayBuffer} from 'get-stream';
import getStream, {getStreamAsArrayBuffer} from 'get-stream';
import mergeStreams from '@sindresorhus/merge-streams';

// `all` interleaves `stdout` and `stderr`
Expand All @@ -24,40 +25,40 @@ export const makeAllStream = (spawned, {all}) => {
// On failure, `result.stdout|stderr|all` should contain the currently buffered stream
// They are automatically closed and flushed by Node.js when the child process exits
// We guarantee this by calling `childProcess.kill()`
const getBufferedData = async (stream, streamPromise) => {
// When `buffer` is `false`, `streamPromise` is `undefined` and there is no buffered data to retrieve
if (!stream || streamPromise === undefined) {
return;
}

// When `buffer` is `false`, `streamPromise` is `undefined` and there is no buffered data to retrieve
const getBufferedData = async (streamPromise, encoding) => {
try {
return await streamPromise;
} catch (error) {
return error.bufferedData;
return error.bufferedData === undefined ? undefined : applyEncoding(error.bufferedData, encoding);
}
};

const getStreamPromise = (stream, {encoding, buffer, maxBuffer}) => {
const getStreamPromise = async (stream, {encoding, buffer, maxBuffer}) => {
if (!stream || !buffer) {
return;
}

// eslint-disable-next-line unicorn/text-encoding-identifier-case
if (encoding === 'utf8' || encoding === 'utf-8') {
return getStream(stream, {maxBuffer});
const contents = isUtf8Encoding(encoding)
? await getStream(stream, {maxBuffer})
: await getStreamAsArrayBuffer(stream, {maxBuffer});
return applyEncoding(contents, encoding);
};

const applyEncoding = (contents, encoding) => {
if (isUtf8Encoding(encoding)) {
return contents;
}

if (encoding === 'buffer') {
return getStreamAsArrayBuffer(stream, {maxBuffer}).then(arrayBuffer => new Uint8Array(arrayBuffer));
return new Uint8Array(contents);
}

return applyEncoding(stream, maxBuffer, encoding);
return Buffer.from(contents).toString(encoding);
};

const applyEncoding = async (stream, maxBuffer, encoding) => {
const buffer = await getStreamAsBuffer(stream, {maxBuffer});
return buffer.toString(encoding);
};
// eslint-disable-next-line unicorn/text-encoding-identifier-case
const isUtf8Encoding = encoding => encoding === 'utf8' || encoding === 'utf-8';

// Some `stdout`/`stderr` options create a stream, e.g. when passing a file path.
// The `.pipe()` method automatically ends that stream when `childProcess.stdout|stderr` ends.
Expand Down Expand Up @@ -116,9 +117,9 @@ export const getSpawnedResult = async (
spawned.kill();
const results = await Promise.all([
{error, signal: error.signal, timedOut: error.timedOut},
getBufferedData(spawned.stdout, stdoutPromise),
getBufferedData(spawned.stderr, stderrPromise),
getBufferedData(spawned.all, allPromise),
getBufferedData(stdoutPromise, encoding),
getBufferedData(stderrPromise, encoding),
getBufferedData(allPromise, encoding),
]);
cleanupStdioStreams(stdioStreams, error);
return results;
Expand Down
15 changes: 15 additions & 0 deletions test/stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,21 @@ const testMaxBuffer = async (t, streamName) => {
test('maxBuffer affects stdout', testMaxBuffer, 'stdout');
test('maxBuffer affects stderr', testMaxBuffer, 'stderr');

test('maxBuffer works with encoding buffer', async t => {
const {stdout} = await t.throwsAsync(
execa('max-buffer.js', ['stdout', '11'], {maxBuffer: 10, encoding: 'buffer'}),
);
t.true(stdout instanceof Uint8Array);
t.is(Buffer.from(stdout).toString(), '.'.repeat(10));
});

test('maxBuffer works with other encodings', async t => {
const {stdout} = await t.throwsAsync(
execa('max-buffer.js', ['stdout', '11'], {maxBuffer: 10, encoding: 'hex'}),
);
t.is(stdout, Buffer.from('.'.repeat(10)).toString('hex'));
});

const testNoMaxBuffer = async (t, streamName) => {
const promise = execa('max-buffer.js', [streamName, '10'], {buffer: false});
const [result, output] = await Promise.all([
Expand Down

0 comments on commit cff6847

Please sign in to comment.