execa vs shelljs vs child_process vs node-cmd
Node.js Child Process Management Libraries Comparison
1 Year
execashelljschild_processnode-cmdSimilar Packages:
What's Node.js Child Process Management Libraries?

These libraries provide various methods for executing shell commands and managing child processes in Node.js applications. They allow developers to spawn new processes, execute commands, and handle input/output streams, making it easier to integrate system-level operations into Node.js applications. Each library has its own unique features and use cases, catering to different needs for process management and command execution.

Package Weekly Downloads Trend
Github Stars Ranking
Stat Detail
Package
Downloads
Stars
Size
Issues
Publish
License
execa88,044,0187,023324 kB122 months agoMIT
shelljs7,868,78614,310212 kB118-BSD-3-Clause
child_process775,828159-48 years agoISC
node-cmd33,521285-74 years agoMIT
Feature Comparison: execa vs shelljs vs child_process vs node-cmd

Ease of Use

  • execa:

    Execa offers a user-friendly API with promise support, making it easy to execute commands and handle results. It simplifies error handling and is designed to be intuitive for developers familiar with async programming.

  • shelljs:

    Shelljs provides a familiar shell scripting syntax that is easy to understand, allowing developers to write scripts in a way that feels natural for those accustomed to shell scripting.

  • child_process:

    The 'child_process' module is part of Node.js core, which means it requires no installation. However, it has a steeper learning curve due to its complex API and requires more boilerplate code for common tasks.

  • node-cmd:

    Node-cmd is very straightforward and easy to use, allowing you to execute commands with minimal setup. It's perfect for quick scripts and simple command execution without much overhead.

Error Handling

  • execa:

    Execa automatically rejects promises on command failure, providing a simpler error handling mechanism. It allows you to catch errors easily using try/catch blocks with async/await.

  • shelljs:

    Shelljs provides a simple way to check for errors through its API, allowing you to handle errors in a straightforward manner, similar to traditional shell scripting.

  • child_process:

    Error handling in 'child_process' requires manual checks for errors in the callback or through the 'error' event. This can lead to more complex error management in your code.

  • node-cmd:

    Node-cmd does not provide advanced error handling features, which may require additional logic to handle command failures effectively.

Cross-Platform Compatibility

  • execa:

    Execa is also cross-platform and handles command execution in a way that works seamlessly across different operating systems, making it a good choice for scripts that need to run in various environments.

  • shelljs:

    Shelljs is explicitly designed for cross-platform shell scripting, providing a consistent interface for shell commands regardless of the underlying OS.

  • child_process:

    The 'child_process' module is inherently cross-platform as it is part of Node.js, but the commands executed may vary between operating systems, requiring careful command management.

  • node-cmd:

    Node-cmd is designed to be cross-platform, but the commands you run must be compatible with the OS, which may require additional checks or adjustments in your scripts.

Performance

  • execa:

    Execa is optimized for performance with a focus on modern JavaScript features. It provides efficient handling of command execution and output, making it suitable for performance-sensitive applications.

  • shelljs:

    Shelljs performance is good for shell scripting tasks, but it may not match the efficiency of lower-level libraries like 'child_process' for heavy-duty process management.

  • child_process:

    The performance of 'child_process' can vary based on how processes are spawned and managed. It allows for high performance in scenarios where fine control over process execution is needed, but may require more resources for complex tasks.

  • node-cmd:

    Node-cmd is generally fast for simple command execution, but may not be as efficient for more complex tasks compared to other libraries.

Feature Set

  • execa:

    Execa provides a rich set of features, including promise support, improved error handling, and better output management, making it a powerful choice for modern applications.

  • shelljs:

    Shelljs provides a wide range of shell commands and scripting capabilities, making it ideal for developers looking to write comprehensive scripts in Node.js.

  • child_process:

    The 'child_process' module offers a comprehensive set of features for process management, including spawning, forking, and handling input/output streams, making it suitable for complex applications.

  • node-cmd:

    Node-cmd offers a minimal feature set focused on executing commands, which may be limiting for more complex use cases that require advanced process management.

How to Choose: execa vs shelljs vs child_process vs node-cmd
  • execa:

    Choose 'execa' for a modern, promise-based interface that simplifies the execution of shell commands. It is ideal for developers who prefer async/await syntax and want to handle command execution with better error handling and easier output management.

  • shelljs:

    Choose 'shelljs' if you require a comprehensive shell scripting solution in Node.js. It provides a rich set of shell commands and is great for writing scripts that need to perform multiple shell operations in a cross-platform manner.

  • child_process:

    Choose 'child_process' if you need a built-in solution without additional dependencies. It provides a robust API for spawning child processes and is suitable for advanced use cases where you need fine control over process execution and streams.

  • node-cmd:

    Choose 'node-cmd' if you want a straightforward and easy-to-use library for executing shell commands without much configuration. It is suitable for simple tasks where you need to run commands quickly without the overhead of more complex libraries.

README for execa
execa logo

Coverage Status

Process execution for humans





Execa runs commands in your script, application or library. Unlike shells, it is optimized for programmatic usage. Built on top of the child_process core module.


One of the maintainers @ehmicky is looking for a remote full-time position. Specialized in Node.js back-ends and CLIs, he led Netlify Build, Plugins and Configuration for 2.5 years. Feel free to contact him on his website or on LinkedIn!


Features

Install

npm install execa

Documentation

Execution:

Input/output:

Advanced usage:

Examples

Execution

Simple syntax

import {execa} from 'execa';

const {stdout} = await execa`npm run build`;
// Print command's output
console.log(stdout);

Script

import {$} from 'execa';

const {stdout: name} = await $`cat package.json`.pipe`grep name`;
console.log(name);

const branch = await $`git branch --show-current`;
await $`dep deploy --branch=${branch}`;

await Promise.all([
	$`sleep 1`,
	$`sleep 2`,
	$`sleep 3`,
]);

const directoryName = 'foo bar';
await $`mkdir /tmp/${directoryName}`;

Local binaries

$ npm install -D eslint
await execa({preferLocal: true})`eslint`;

Pipe multiple subprocesses

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

// Output of `npm run build | sort | head -n 2`
console.log(stdout);
// Output of `npm run build | sort`
console.log(pipedFrom[0].stdout);
// Output of `npm run build`
console.log(pipedFrom[0].pipedFrom[0].stdout);

Input/output

Interleaved output

const {all} = await execa({all: true})`npm run build`;
// stdout + stderr, interleaved
console.log(all);

Programmatic + terminal output

const {stdout} = await execa({stdout: ['pipe', 'inherit']})`npm run build`;
// stdout is also printed to the terminal
console.log(stdout);

Simple input

const getInputString = () => { /* ... */ };
const {stdout} = await execa({input: getInputString()})`sort`;
console.log(stdout);

File input

// Similar to: npm run build < input.txt
await execa({stdin: {file: 'input.txt'}})`npm run build`;

File output

// Similar to: npm run build > output.txt
await execa({stdout: {file: 'output.txt'}})`npm run build`;

Split into text lines

const {stdout} = await execa({lines: true})`npm run build`;
// Print first 10 lines
console.log(stdout.slice(0, 10).join('\n'));

Streaming

Iterate over text lines

for await (const line of execa`npm run build`) {
	if (line.includes('WARN')) {
		console.warn(line);
	}
}

Transform/filter output

let count = 0;

// Filter out secret lines, then prepend the line number
const transform = function * (line) {
	if (!line.includes('secret')) {
		yield `[${count++}] ${line}`;
	}
};

await execa({stdout: transform})`npm run build`;

Web streams

const response = await fetch('https://example.com');
await execa({stdin: response.body})`sort`;

Convert to Duplex stream

import {execa} from 'execa';
import {pipeline} from 'node:stream/promises';
import {createReadStream, createWriteStream} from 'node:fs';

await pipeline(
	createReadStream('./input.txt'),
	execa`node ./transform.js`.duplex(),
	createWriteStream('./output.txt'),
);

IPC

Exchange messages

// parent.js
import {execaNode} from 'execa';

const subprocess = execaNode`child.js`;
await subprocess.sendMessage('Hello from parent');
const message = await subprocess.getOneMessage();
console.log(message); // 'Hello from child'
// child.js
import {getOneMessage, sendMessage} from 'execa';

const message = await getOneMessage(); // 'Hello from parent'
const newMessage = message.replace('parent', 'child'); // 'Hello from child'
await sendMessage(newMessage);

Any input type

// main.js
import {execaNode} from 'execa';

const ipcInput = [
	{task: 'lint', ignore: /test\.js/},
	{task: 'copy', files: new Set(['main.js', 'index.js']),
}];
await execaNode({ipcInput})`build.js`;
// build.js
import {getOneMessage} from 'execa';

const ipcInput = await getOneMessage();

Any output type

// main.js
import {execaNode} from 'execa';

const {ipcOutput} = await execaNode`build.js`;
console.log(ipcOutput[0]); // {kind: 'start', timestamp: date}
console.log(ipcOutput[1]); // {kind: 'stop', timestamp: date}
// build.js
import {sendMessage} from 'execa';

const runBuild = () => { /* ... */ };

await sendMessage({kind: 'start', timestamp: new Date()});
await runBuild();
await sendMessage({kind: 'stop', timestamp: new Date()});

Graceful termination

// main.js
import {execaNode} from 'execa';

const controller = new AbortController();
setTimeout(() => {
	controller.abort();
}, 5000);

await execaNode({
	cancelSignal: controller.signal,
	gracefulCancel: true,
})`build.js`;
// build.js
import {getCancelSignal} from 'execa';

const cancelSignal = await getCancelSignal();
const url = 'https://example.com/build/info';
const response = await fetch(url, {signal: cancelSignal});

Debugging

Detailed error

import {execa, ExecaError} from 'execa';

try {
	await execa`unknown command`;
} catch (error) {
	if (error instanceof ExecaError) {
		console.log(error);
	}
	/*
	ExecaError: Command failed with ENOENT: unknown command
	spawn unknown ENOENT
			at ...
			at ... {
		shortMessage: 'Command failed with ENOENT: unknown command\nspawn unknown ENOENT',
		originalMessage: 'spawn unknown ENOENT',
		command: 'unknown command',
		escapedCommand: 'unknown command',
		cwd: '/path/to/cwd',
		durationMs: 28.217566,
		failed: true,
		timedOut: false,
		isCanceled: false,
		isTerminated: false,
		isMaxBuffer: false,
		code: 'ENOENT',
		stdout: '',
		stderr: '',
		stdio: [undefined, '', ''],
		pipedFrom: []
		[cause]: Error: spawn unknown ENOENT
				at ...
				at ... {
			errno: -2,
			code: 'ENOENT',
			syscall: 'spawn unknown',
			path: 'unknown',
			spawnargs: [ 'command' ]
		}
	}
	*/
}

Verbose mode

await execa`npm run build`;
await execa`npm run test`;
execa verbose output

Custom logging

import {execa as execa_} from 'execa';
import {createLogger, transports} from 'winston';

// Log to a file using Winston
const transport = new transports.File({filename: 'logs.txt'});
const logger = createLogger({transports: [transport]});
const LOG_LEVELS = {
	command: 'info',
	output: 'verbose',
	ipc: 'verbose',
	error: 'error',
	duration: 'info',
};

const execa = execa_({
	verbose(verboseLine, {message, ...verboseObject}) {
		const level = LOG_LEVELS[verboseObject.type];
		logger[level](message, verboseObject);
	},
});

await execa`npm run build`;
await execa`npm run test`;

Related

Maintainers