Transforms map or filter the input or output of a subprocess. They are defined by passing a generator function or a transform options object to the stdin
, stdout
, stderr
or stdio
option. It can be async
.
import {execa} from 'execa';
const transform = function * (line) {
const prefix = line.includes('error') ? 'ERROR' : 'INFO';
yield `${prefix}: ${line}`;
};
const {stdout} = await execa({stdout: transform})`npm run build`;
console.log(stdout); // HELLO
Transforms operate one line
at a time, just like subprocess.iterable()
. However, unlike iteration, transforms:
- Modify the subprocess' output and streams.
- Can apply to the subprocess' input.
- Are defined using a generator function,
Duplex
stream, Node.jsTransform
stream or webTransformStream
.
yield
can be called 0, 1 or multiple times. Not calling yield
enables filtering a specific line.
const transform = function * (line) {
if (!line.includes('secret')) {
yield line;
}
};
const {stdout} = await execa({stdout: transform})`echo ${'This is a secret'}`;
console.log(stdout); // ''
By default, stdout
and stderr
's transforms must return a string or an Uint8Array
. However, if the objectMode
transform option is true
, any type can be returned instead, except null
or undefined
. The subprocess' result.stdout
/result.stderr
will be an array of values.
const transform = function * (line) {
yield JSON.parse(line);
};
const {stdout} = await execa({stdout: {transform, objectMode: true}})`node jsonlines-output.js`;
for (const data of stdout) {
console.log(stdout); // {...object}
}
stdin
can also use objectMode: true
.
const transform = function * (line) {
yield JSON.stringify(line);
};
const input = [{event: 'example'}, {event: 'otherExample'}];
await execa({stdin: [input, {transform, objectMode: true}]})`node jsonlines-input.js`;
State can be shared between calls of the transform
and final
functions.
let count = 0;
// Prefix line number
const transform = function * (line) {
yield `[${count++}] ${line}`;
};
To create additional lines after the last one, a final
generator function can be used.
let count = 0;
const transform = function * (line) {
count += 1;
yield line;
};
const final = function * () {
yield `Number of lines: ${count}`;
};
const {stdout} = await execa({stdout: {transform, final}})`npm run build`;
console.log(stdout); // Ends with: 'Number of lines: 54'
A Duplex
stream, Node.js Transform
stream or web TransformStream
can be used instead of a generator function.
Like generator functions, web TransformStream
can be passed either directly or as a {transform}
plain object. But Duplex
and Transform
must always be passed as a {transform}
plain object.
The objectMode
transform option can be used, but not the binary
nor preserveNewlines
options.
import {createGzip} from 'node:zlib';
import {execa} from 'execa';
const {stdout} = await execa({
stdout: {transform: createGzip()},
encoding: 'buffer',
})`npm run build`;
console.log(stdout); // `stdout` is compressed with gzip
const {stdout} = await execa({
stdout: new CompressionStream('gzip'),
encoding: 'buffer',
})`npm run build`;
console.log(stdout); // `stdout` is compressed with gzip
The stdin
, stdout
, stderr
and stdio
options can accept an array of values. While this is not specific to transforms, this can be useful with them too. For example, the following transform impacts the value printed by 'inherit'
.
await execa({stdout: [transform, 'inherit']})`npm run build`;
This also allows using multiple transforms.
await execa({stdout: [transform, otherTransform]})`npm run build`;
Or saving to archives.
await execa({stdout: [new CompressionStream('gzip'), {file: './output.gz'}]})`npm run build`;
Next: 🔀 Piping multiple subprocesses
Previous: 🤖 Binary data
Top: Table of contents