Skip to content

Latest commit

 

History

History
157 lines (112 loc) · 5.43 KB

transform.md

File metadata and controls

157 lines (112 loc) · 5.43 KB
execa logo

🧙 Transforms

Summary

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

Difference with iteration

Transforms operate one line at a time, just like subprocess.iterable(). However, unlike iteration, transforms:

Filtering

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); // ''

Object mode

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`;

Sharing state

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}`;
};

Finalizing

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'

Duplex/Transform streams

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

Combining

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