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})`echo HELLO`;
console.log(stdout); // INFO: HELLOTransforms 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,
Duplexstream, Node.jsTransformstream 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 gzipconst {stdout} = await execa({
stdout: new CompressionStream('gzip'),
encoding: 'buffer',
})`npm run build`;
console.log(stdout); // `stdout` is compressed with gzipThe 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