execa vs cross-env vs shelljs vs npm-run-all vs child_process vs node-cmd
Node.js Command Execution Libraries Comparison
1 Year
execacross-envshelljsnpm-run-allchild_processnode-cmdSimilar Packages:
What's Node.js Command Execution Libraries?

These libraries facilitate the execution of shell commands and scripts in Node.js applications. They provide various functionalities for spawning child processes, managing environment variables, and running scripts concurrently, which are essential for automating tasks, building tools, and enhancing the development workflow. Each package offers unique features tailored to different use cases, making it easier for developers to choose the right tool for their specific needs.

Package Weekly Downloads Trend
Github Stars Ranking
Stat Detail
Package
Downloads
Stars
Size
Issues
Publish
License
execa85,743,5097,028324 kB123 months agoMIT
cross-env7,872,5316,365-14 years agoMIT
shelljs7,769,79014,314212 kB101-BSD-3-Clause
npm-run-all3,184,6415,779-1076 years agoMIT
child_process773,955159-48 years agoISC
node-cmd33,525285-74 years agoMIT
Feature Comparison: execa vs cross-env vs shelljs vs npm-run-all vs child_process vs node-cmd

Ease of Use

  • execa:

    Execa offers a clean and modern API that simplifies command execution. It supports promises and async/await, making it easy to integrate into modern JavaScript applications without complicated callbacks.

  • cross-env:

    Cross-env is extremely easy to use, requiring minimal setup. You simply prefix your command with cross-env followed by the environment variable assignments, making it straightforward for developers of all levels.

  • shelljs:

    Shelljs provides a familiar shell scripting syntax in JavaScript, making it easy for developers with shell scripting experience to write scripts. Its commands are intuitive and closely resemble traditional shell commands.

  • npm-run-all:

    Npm-run-all is designed for simplicity in managing npm scripts. It allows you to run scripts in parallel or sequentially with a single command, making it easy to orchestrate complex build processes.

  • child_process:

    The child_process module provides a low-level API that can be complex and verbose for simple tasks. It requires more boilerplate code to handle process management and output, which may not be ideal for quick scripts.

  • node-cmd:

    Node-cmd provides a simple interface for executing commands, making it user-friendly for beginners. It requires minimal configuration and is quick to set up for basic command execution.

Cross-Platform Compatibility

  • execa:

    Execa is also cross-platform and handles command execution seamlessly across different environments. It abstracts away many of the complexities involved in running commands on various operating systems.

  • cross-env:

    Cross-env is specifically designed for cross-platform compatibility, ensuring that environment variables are set correctly regardless of the operating system. It abstracts away the differences in syntax, making it a reliable choice.

  • shelljs:

    Shelljs is designed to be cross-platform, providing a consistent shell scripting experience across different operating systems. It abstracts away the underlying differences, allowing for easier script writing.

  • npm-run-all:

    Npm-run-all is cross-platform and works well with npm scripts across different environments, making it a great choice for projects that need to run scripts consistently regardless of the OS.

  • child_process:

    Child_process is inherently cross-platform since it is part of Node.js. However, the commands executed may vary between operating systems, requiring careful handling of command strings for compatibility.

  • node-cmd:

    Node-cmd is cross-platform but may require additional handling for certain commands that differ between operating systems. It's straightforward for basic commands but may need adjustments for more complex scenarios.

Error Handling

  • execa:

    Execa provides built-in error handling features, including promise rejections for non-zero exit codes. This makes it easier to manage errors and react accordingly in your application logic.

  • cross-env:

    Cross-env does not handle errors directly since it simply sets environment variables. Error handling depends on the commands executed after setting the variables, which may require additional consideration.

  • shelljs:

    Shelljs provides error handling through its command execution methods, allowing developers to check the exit status of commands easily. It integrates well with JavaScript's try/catch mechanism for robust error management.

  • npm-run-all:

    Npm-run-all handles errors gracefully by stopping execution of subsequent scripts if one fails. This feature is useful for maintaining the integrity of build processes and ensuring that errors are caught early.

  • child_process:

    Error handling in child_process can be cumbersome, as it requires manual checks and handling of exit codes and output streams. Developers need to implement additional logic to manage errors effectively.

  • node-cmd:

    Node-cmd has basic error handling capabilities, but it may not provide detailed error information. Developers might need to implement additional checks for more complex error management.

Performance

  • execa:

    Execa is optimized for performance and minimizes overhead when executing commands. Its promise-based API allows for efficient handling of asynchronous operations, making it suitable for high-performance applications.

  • cross-env:

    Cross-env has negligible performance overhead since it only modifies environment variables. Its impact on performance is minimal, making it an efficient choice for setting up environments before command execution.

  • shelljs:

    Shelljs performs well for shell scripting tasks, but performance can vary based on the complexity of the scripts and commands being executed. It is generally suitable for most scripting needs.

  • npm-run-all:

    Npm-run-all is designed for performance when running multiple scripts, allowing for parallel execution that can significantly reduce total execution time. It is ideal for build processes that can benefit from concurrent execution.

  • child_process:

    Child_process is efficient for executing commands, but performance can vary based on the complexity of the commands and the overhead of spawning new processes. It is suitable for most use cases but may require optimization for high-frequency executions.

  • node-cmd:

    Node-cmd is lightweight and performs well for simple command executions. However, it may not be as efficient for more complex scenarios requiring extensive command chaining or processing.

Community and Support

  • execa:

    Execa has a growing community and is well-documented, providing good support for developers. Its modern approach aligns with current JavaScript practices, making it a popular choice among developers.

  • cross-env:

    Cross-env has a strong community and is widely used, leading to ample resources and documentation available for troubleshooting and best practices.

  • shelljs:

    Shelljs has a large user base and extensive documentation, making it easy to find support and examples for various use cases. Its popularity ensures a wealth of community-driven resources.

  • npm-run-all:

    Npm-run-all has a vibrant community and is frequently updated, ensuring that developers have access to the latest features and support. Its documentation is comprehensive and user-friendly.

  • child_process:

    Being a core Node.js module, child_process has extensive documentation and community support. However, specific use cases may require additional research or community input for best practices.

  • node-cmd:

    Node-cmd has a smaller community compared to others, but it is straightforward and well-documented, making it easy to find help for common issues.

How to Choose: execa vs cross-env vs shelljs vs npm-run-all vs child_process vs node-cmd
  • execa:

    Opt for execa if you want a more user-friendly and feature-rich alternative to child_process. It simplifies the API for executing commands, supports promises and async/await, and provides better error handling, making it ideal for modern JavaScript applications.

  • cross-env:

    Select cross-env if you need to set environment variables across different operating systems (Windows, macOS, Linux) in a consistent manner. It simplifies the process of ensuring that environment variables are correctly set for scripts, especially when working in cross-platform environments.

  • shelljs:

    Select shelljs if you require a comprehensive shell scripting solution within Node.js. It provides a rich set of shell commands and utilities, enabling you to write scripts that mimic shell behavior directly in JavaScript.

  • npm-run-all:

    Choose npm-run-all if you need to run multiple npm scripts in parallel or sequentially. It is particularly useful for managing build processes and automating tasks in a project, allowing for better organization and efficiency in script execution.

  • child_process:

    Choose child_process if you need native Node.js functionality for spawning child processes without any additional dependencies. It provides a low-level API for executing commands, making it suitable for advanced use cases where fine control over process management is required.

  • node-cmd:

    Use node-cmd if you prefer a straightforward and simple API for executing shell commands. It is lightweight and easy to use, making it suitable for quick scripts and simple command executions without the need for complex configurations.

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