Skip to content

Latest commit

 

History

History
167 lines (125 loc) · 4.34 KB

pipe.md

File metadata and controls

167 lines (125 loc) · 4.34 KB
execa logo

🔀 Piping multiple subprocesses

Array syntax

A subprocess' output can be piped to another subprocess' input. The syntax is the same as execa(file, arguments?, options?).

import {execa} from 'execa';

// Similar to `npm run build | head -n 2` in shells
const {stdout} = await execa('npm', ['run', 'build'])
	.pipe('head', ['-n', '2']);

Template string syntax

const {stdout} = await execa`npm run build`
	.pipe`head -n 2`;

Advanced syntax

const subprocess = execa`head -n 2`;
const {stdout} = await execa`npm run build`
	.pipe(subprocess);

Options

Options can be passed to either the source or the destination subprocess. Some pipe-specific options can also be set by the destination subprocess.

const {stdout} = await execa('npm', ['run', 'build'], subprocessOptions)
	.pipe('head', ['-n', '2'], subprocessOrPipeOptions);
const {stdout} = await execa(subprocessOptions)`npm run build`
	.pipe(subprocessOrPipeOptions)`head -n 2`;
const subprocess = execa(subprocessOptions)`head -n 2`;
const {stdout} = await execa(subprocessOptions)`npm run build`
	.pipe(subprocess, pipeOptions);

Result

When both subprocesses succeed, the result of the destination subprocess is returned. The result of the source subprocess is available in a result.pipedFrom array.

const destinationResult = await execa`npm run build`
	.pipe`head -n 2`;
console.log(destinationResult.stdout); // First 2 lines of `npm run build`

const sourceResult = destinationResult.pipedFrom[0];
console.log(sourceResult.stdout); // Full output of `npm run build`

Errors

When either subprocess fails, subprocess.pipe() is rejected with that subprocess' error. If the destination subprocess fails, error.pipedFrom includes the source subprocess' result, which is useful for debugging.

try {
	await execa`npm run build`
		.pipe`head -n 2`;
} catch (error) {
	if (error.pipedFrom.length === 0) {
		// `npm run build` failure
		console.error(error);
	} else {
		// `head -n 2` failure
		console.error(error);
		// `npm run build` output
		console.error(error.pipedFrom[0].stdout);
	}

	throw error;
}

Series of subprocesses

await execa`npm run build`
	.pipe`sort`
	.pipe`head -n 2`;

1 source, multiple destinations

const subprocess = execa`npm run build`;
const [sortedResult, truncatedResult] = await Promise.all([
	subprocess.pipe`sort`,
	subprocess.pipe`head -n 2`,
]);

Multiple sources, 1 destination

const destination = execa`./log-remotely.js`;
await Promise.all([
	execa`npm run build`.pipe(destination),
	execa`npm run test`.pipe(destination),
]);

Source file descriptor

By default, the source's stdout is used, but this can be changed using the from piping option.

await execa`npm run build`
	.pipe({from: 'stderr'})`head -n 2`;

Destination file descriptor

By default, the destination's stdin is used, but this can be changed using the to piping option.

await execa`npm run build`
	.pipe({to: 'fd3'})`./log-remotely.js`;

Unpipe

Piping can be stopped using the unpipeSignal piping option.

The subprocess.pipe() method will be rejected with a cancelation error. However, each subprocess will keep running.

const abortController = new AbortController();

process.on('SIGUSR1', () => {
	abortController.abort();
});

// If the process receives SIGUSR1, `npm run build` stopped being logged remotely.
// However, it keeps running successfully.
try {
	await execa`npm run build`
		.pipe({unpipeSignal: abortController.signal})`./log-remotely.js`;
} catch (error) {
	if (!abortController.signal.aborted) {
		throw error;
	}
}

Next: ⏳️ Streams
Previous: 🧙 Transforms
Top: Table of contents