Top Related Projects
Node.js JavaScript runtime ✨🐢🚀✨
:shell: Portable Unix shell commands for Node.js
Node.js: extra methods for the fs object like copy(), remove(), mkdirs()
glob functionality for node.js
node.js command-line interfaces made easy
Quick Overview
Execa is a Node.js library that provides a better, more robust alternative to the built-in child_process
methods. It offers a promise-based API for executing external commands, with improved error handling, streaming, and cross-platform support.
Pros
- Simplified API with promise support and easier error handling
- Cross-platform compatibility, including better Windows support
- Built-in streaming capabilities for stdout and stderr
- Automatic escaping of command arguments for improved security
Cons
- Adds an external dependency to your project
- May have a slight performance overhead compared to native
child_process
methods - Learning curve for developers used to the built-in Node.js methods
- Limited to Node.js environments (not usable in browsers)
Code Examples
- Basic usage:
import execa from 'execa';
const {stdout} = await execa('echo', ['Hello world']);
console.log(stdout);
// Output: Hello world
- Handling errors:
import execa from 'execa';
try {
await execa('unknown-command');
} catch (error) {
console.log(error);
// Error: Command failed with ENOENT: unknown-command
}
- Streaming output:
import execa from 'execa';
const stream = execa('echo', ['Hello', 'world']).stdout;
stream.pipe(process.stdout);
// Output: Hello world
- Shell commands:
import execa from 'execa';
const {stdout} = await execa('echo $HOME', {shell: true});
console.log(stdout);
// Output: /home/username
Getting Started
To use Execa in your project, follow these steps:
- Install the package:
npm install execa
- Import and use in your code:
import execa from 'execa';
async function runCommand() {
try {
const {stdout} = await execa('ls', ['-l']);
console.log('Command output:', stdout);
} catch (error) {
console.error('Error:', error);
}
}
runCommand();
This example demonstrates how to execute a simple ls -l
command using Execa and handle its output or potential errors.
Competitor Comparisons
Node.js JavaScript runtime ✨🐢🚀✨
Pros of Node
- Comprehensive JavaScript runtime environment with built-in modules
- Extensive ecosystem and community support
- Native performance optimizations and low-level system access
Cons of Node
- Larger codebase and more complex setup for simple tasks
- Steeper learning curve for beginners
- Less focused on cross-platform child process management
Code Comparison
Node:
const { spawn } = require('child_process');
const ls = spawn('ls', ['-lh', '/usr']);
ls.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
});
Execa:
const execa = require('execa');
(async () => {
const {stdout} = await execa('ls', ['-lh', '/usr']);
console.log(stdout);
})();
Key Differences
- Execa provides a simpler, promise-based API for child process management
- Node offers more granular control over process execution and I/O
- Execa includes built-in features like improved Windows support and automatic escaping
- Node provides a broader set of tools and APIs for various system-level operations
Use Cases
- Choose Node for building full-scale applications or when low-level control is needed
- Opt for Execa when focusing on cross-platform child process execution with a clean API
:shell: Portable Unix shell commands for Node.js
Pros of ShellJS
- Cross-platform compatibility: Provides a unified interface for shell commands across different operating systems
- Synchronous API: Offers a more straightforward approach for sequential operations
- Built-in command set: Includes a wide range of pre-implemented shell commands
Cons of ShellJS
- Performance: Generally slower than native shell commands or Execa
- Limited async support: Primarily focuses on synchronous operations
- Larger package size: Includes more dependencies and a larger codebase
Code Comparison
ShellJS:
const shell = require('shelljs');
if (shell.exec('npm run build').code !== 0) {
shell.echo('Error: Build failed');
shell.exit(1);
}
Execa:
const execa = require('execa');
try {
await execa('npm', ['run', 'build']);
} catch (error) {
console.error('Error: Build failed');
process.exit(1);
}
Summary
ShellJS provides a more shell-like syntax and cross-platform compatibility, making it easier for developers familiar with shell scripting. However, Execa offers better performance, native Promise support, and a more modern JavaScript approach. The choice between the two depends on specific project requirements and developer preferences.
Node.js: extra methods for the fs object like copy(), remove(), mkdirs()
Pros of fs-extra
- Provides a comprehensive set of file system operations, including promise-based versions
- Offers additional utility functions not found in Node.js core
fs
module - Maintains compatibility with the native
fs
module, allowing for easy integration
Cons of fs-extra
- Focused solely on file system operations, unlike execa's process execution capabilities
- May have a larger footprint due to its extensive feature set
- Lacks the advanced child process management features of execa
Code Comparison
fs-extra:
const fs = require('fs-extra')
async function copyFiles() {
await fs.copy('/path/to/source', '/path/to/destination')
console.log('Files copied successfully')
}
execa:
const execa = require('execa')
async function runCommand() {
const {stdout} = await execa('echo', ['Hello, world!'])
console.log(stdout)
}
Summary
fs-extra is a powerful library for file system operations, extending Node.js's native fs
module with additional functionality and promise-based versions of methods. It's ideal for projects requiring extensive file manipulation.
execa, on the other hand, focuses on process execution and child process management, offering features like better Windows support and improved error handling for shell commands.
While both libraries serve different primary purposes, they can be complementary in projects requiring both file system operations and advanced process execution capabilities.
glob functionality for node.js
Pros of glob
- Specialized for file and directory pattern matching
- Supports advanced globbing patterns and options
- Widely used and battle-tested in many projects
Cons of glob
- Limited to file system operations
- Not designed for general-purpose command execution
- May require additional libraries for complex file manipulations
Code Comparison
glob:
const glob = require('glob');
glob('**/*.js', (err, files) => {
console.log(files);
});
execa:
const execa = require('execa');
(async () => {
const {stdout} = await execa('ls', ['-l']);
console.log(stdout);
})();
Key Differences
- glob focuses on file pattern matching, while execa is for running shell commands
- glob operates synchronously or with callbacks, execa supports promises and async/await
- execa provides more control over child processes, including stdio handling and killing processes
Use Cases
- Use glob for file searching, listing, and pattern matching tasks
- Choose execa for running shell commands, scripts, or external programs with advanced process control
Community and Maintenance
Both projects are well-maintained and have large user bases. glob has been around longer and is more specialized, while execa is part of a larger ecosystem of utilities by Sindre Sorhus.
node.js command-line interfaces made easy
Pros of Commander.js
- Specifically designed for building command-line interfaces (CLIs)
- Provides a rich set of features for parsing arguments and options
- Offers built-in help generation and version information
Cons of Commander.js
- Limited to CLI applications, not suitable for general process execution
- Requires more setup and configuration for basic command execution
- Heavier dependency compared to Execa for simple use cases
Code Comparison
Commander.js:
const { program } = require('commander');
program
.option('-d, --debug', 'output extra debugging')
.option('-s, --small', 'small pizza size')
.parse(process.argv);
const options = program.opts();
console.log(options);
Execa:
const execa = require('execa');
(async () => {
const { stdout } = await execa('echo', ['hello world']);
console.log(stdout);
})();
Summary
Commander.js is tailored for building complex CLI applications with advanced argument parsing and help generation. It excels in creating user-friendly command-line interfaces but may be overkill for simple command execution tasks.
Execa, on the other hand, focuses on streamlined process execution and is more suitable for running external commands or scripts within Node.js applications. It offers a simpler API for basic command execution but lacks the CLI-specific features of Commander.js.
Choose Commander.js for building full-fledged CLI tools, and Execa for general-purpose command execution in Node.js projects.
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual CopilotREADME
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
- Simple syntax: promises and template strings, like
zx
. - Script interface.
- No escaping nor quoting needed. No risk of shell injection.
- Execute locally installed binaries without
npx
. - Improved Windows support: shebangs,
PATHEXT
, graceful termination, and more. - Detailed errors, verbose mode and custom logging, for debugging.
- Pipe multiple subprocesses better than in shells: retrieve intermediate results, use multiple sources/destinations, unpipe.
- Split the output into text lines, or iterate progressively over them.
- Strip unnecessary newlines.
- Pass any input to the subprocess: files, strings,
Uint8Array
s, iterables, objects and almost any other type. - Return almost any type from the subprocess, or redirect it to files.
- Get interleaved output from
stdout
andstderr
similar to what is printed on the terminal. - Retrieve the output programmatically and print it on the console at the same time.
- Transform or filter the input and output with simple functions.
- Pass Node.js streams or web streams to subprocesses, or convert subprocesses to a stream.
- Exchange messages with the subprocess.
- Ensure subprocesses exit even when they intercept termination signals, or when the current process ends abruptly.
Install
npm install execa
Documentation
Execution:
- â¶ï¸ Basic execution
- ð¬ Escaping/quoting
- ð» Shell
- ð Scripts
- ð¢ Node.js files
- ð Environment
- â Errors
- ð Termination
Input/output:
- ð¹ Input
- ð¢ Output
- ð Text lines
- ð¤ Binary data
- ð§ Transforms
Advanced usage:
- ð Piping multiple subprocesses
- â³ï¸ Streams
- ð Inter-process communication
- ð Debugging
- ð Windows
- ð Difference with Bash and zx
- ð¤ TypeScript
- ð API reference
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`;
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
- gulp-execa - Gulp plugin for Execa
- nvexeca - Run Execa using any Node.js version
Maintainers
Top Related Projects
Node.js JavaScript runtime ✨🐢🚀✨
:shell: Portable Unix shell commands for Node.js
Node.js: extra methods for the fs object like copy(), remove(), mkdirs()
glob functionality for node.js
node.js command-line interfaces made easy
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual Copilot