execa

repository·main·Indexed 27 days ago

https://github.com/sindresorhus/execa

A tool for running commands in scripts, applications, or libraries, providing a cleaner and safer API than the built-in Node.js child_process module. Version 10.0.0 features include a promise-based syntax, a shell-like $ script interface for piping and interpolation, support for executing local binaries, and IPC communication via execaNode. It supports async iteration over output lines, duplex stream conversion, and detailed error handling with ExecaError.

Tokens
28.2K
Snippets
112
Records
191
Agent score
92%

What's inside execa

  1. Compare Execa with Bash and zx

    main

    Execa is designed as a minimalistic, cross-platform, and secure alternative to Bash and zx. Key differences include:

    • No Shell Required: Unlike Bash or zx, Execa does not use a shell by default, making it more cross-platform (e.g., works on Windows without Bash) and secure against shell injection. If a shell is required, use the shell option.
    • Simplicity & Modularity: Execa has a minimalistic API with no global variables or special binaries. Unlike zx, it does not include built-in utilities (like fetch, sleep, or chalk), allowing you to use any Node.js package instead.
    • Security: By avoiding shell execution by default, Execa prevents shell injection vulnerabilities.
    • Performance: Execa avoids the performance overhead of spawning a shell for every command.
    • Debugging: Execa provides a verbose option that includes timestamps, command duration, and IPC messages, along with detailed error messages and stack traces.
  2. Run Windows executables and scripts without a shell

    main

    On Windows, Execa resolves files without extensions using the PATHEXT environment variable. This allows you to run .exe, .cmd, .bat, or .com files directly without needing to enable the shell option or prefix commands with cmd.exe /c. Relative paths and paths containing spaces are handled automatically.

    import {execa} from 'execa';
    
    // Runs `npm.cmd`
    await execa`npm run build`;
  3. Set and retrieve the current working directory

    main

    You can specify the working directory for a command using the cwd option. After execution, the actual working directory used can be retrieved from the cwd property of the result object.

    import {execa} from 'execa';
    
    // Set the working directory
    await execa({cwd: '/path/to/cwd'})`npm run build`;
    
    // Retrieve the working directory used
    const {cwd} = await execa`npm run build`;
  4. Configure environment variables

    main

    Use the env option to pass environment variables to the subprocess. By default, subprocesses inherit the current process' environment variables. To discard the current environment and only pass the provided variables, set extendEnv: false.

    // Keep the current process' environment variables, and set `NO_COLOR`
    await execa({env: {NO_COLOR: 'true'}})`node child.js`;
    
    // Discard the current process' environment variables, only pass `NO_COLOR`
    await execa({env: {NO_COLOR: 'true'}, extendEnv: false})`node child.js`;
  5. Handle TypeScript type inference with execa

    main

    Execa uses advanced type inference. You generally do not need to provide explicit types for variables created via execa() or execaSync(). Explicit types are primarily useful when defining function parameters that accept execa-related objects.

    import {
    	execa as execa_,
    	ExecaError,
    	type Result,
    	type VerboseObject,
    } from 'execa';
    
    const execa = execa_({preferLocal: true});
    
    const printResultStdout = (result: Result) => {
    	console.log('Stdout', result.stdout);
    };
    
    const options = {
    	stdin: 'inherit',
    	stdout: 'pipe',
    	stderr: 'pipe',
    	timeout: 1000,
    	ipc: true,
    	verbose(verboseLine: string, verboseObject: VerboseObject) {
    		return verboseObject.type === 'duration' ? verboseLine : undefined;
    	},
    } as const;
    const task = 'build';
    const message = 'hello world';
    
    try {
    	const subprocess = execa(options)`npm run ${task}`;
    	await subprocess.sendMessage(message);
    	const result = await subprocess;
    	printResultStdout(result);
    } catch (error) {
    	if (error instanceof ExecaError) {
    		console.error(error);
    	}
    }
  6. Execute commands using Template string syntax

    main

    Execa supports tagged template literals for a more concise syntax. All available methods can use either array syntax or template string syntax, as they are equivalent.

    Supported interpolation types:

    • String arguments: ${'task'}
    • Number arguments: ${2}
    • Subcommands: Interpolating the result of a previous execa call.
    • Concatenation: Building paths or strings.
    • Multiple arguments: Passing an array into the template literal.
    • No arguments: Passing an empty array ${[]}.
    • Empty string argument: Passing ${''}.
    • Conditional arguments: Passing an array based on a condition.
    • Multiple lines: Using newlines within the template literal.
  7. Manage console windows on Windows

    main
    If the windowsHide option is set to false, the subprocess will run in a new console window. This is required to ensure SIGINT works correctly and to allow for proper cleanup of subprocesses that spawn their own children (e.g., running an npm script through a shell).
  8. Implement graceful termination with `gracefulCancel`

    main

    To allow a Node.js subprocess to clean up before exiting, use the gracefulCancel: true option along with a cancelSignal. Instead of sending SIGTERM, Execa allows the subprocess to retrieve the signal using getCancelSignal(). This is cross-platform and works specifically with Node.js files.

    In the subprocess, you can pass the retrieved AbortSignal to long-running Node.js methods (like fs.watch, setTimeout, or streams) to make them respond to the cancellation. When the signal is aborted, these methods throw an error. You can check if the error was a graceful cancellation using the isGracefullyCanceled property.

    // main.js
    import {execaNode} from 'execa';
    
    const controller = new AbortController();
    const cancelSignal = controller.signal;
    
    setTimeout(() => {
    	controller.abort();
    }, 5000);
    
    try {
    	await execaNode({cancelSignal, gracefulCancel: true})`build.js`;
    } catch (error) {
    	if (error.isGracefullyCanceled) {
    		console.error('Cancelled gracefully.');
    	}
    
    	throw error;
    }
    // build.js
    import {getCancelSignal} from 'execa';
    
    const cancelSignal = await getCancelSignal();
  9. Enable Inter-process communication (IPC)

    main

    To enable IPC between the current process and a subprocess, set the ipc option to true. Note that IPC only works if the subprocess is a Node.js file. When using execaNode() or the node option, ipc defaults to true.

    Unlike Node.js child_process.spawn(), you must use the ipc option instead of setting 'ipc' in the stdio option.

    import {execaNode} from 'execa';
    
    // ipc is true by default with execaNode
    const subprocess = execaNode`child.js`;
  10. Prevent exceptions using the reject option

    main

    To avoid try/catch blocks, you can set the reject: false option in the execa configuration. When reject is false, the promise will resolve with the result object instead of throwing. You must then check the failed property on the returned object to determine if the subprocess failed.

    const resultOrError = await execa({reject: false})`npm run build`;
    if (resultOrError.failed) {
    	console.error(resultOrError);
    }
  11. Use Verbose mode for logging

    main

    You can enable logging by setting the verbose option in the execa configuration object. Logs are printed to stderr.

    Modes:

    • 'short': Prints the command, duration, and error messages.
    • 'full': Prints the command, duration, error messages, and the subprocess stdout, stderr, and IPC messages.
    • 'none': Disables logging.

    Global Mode:

    Set the NODE_DEBUG=execa environment variable to make verbose: 'full' the default for all commands.

    Limitations for 'full' mode:

    Output is not logged if:

    • stdout or stderr is set to 'ignore' or 'inherit'.
    • stdout or stderr is redirected to a stream, a file, a file descriptor, or another subprocess.
    • encoding is set to binary.
  12. Transform output lines

    main

    You can use transforms to process lines as they are produced. By default, newlines are stripped from each line argument passed to the transform function.

    If preserveNewlines: true is set in the transform options:

    1. Multiple yield statements in your generator will produce a single line.
    2. If preserveNewlines is false (default), each yield produces at least one line.
    // With preserveNewlines: true, multiple yields merge into one line
    const transform = function * (line) {
    	yield 'Important note: ';
    	yield 'Read the comments below.\n';
    
    	// This is treated as: 'Important note: Read the comments below.\n'
    	yield line;
    };
    
    await execa({stdout: {transform, preserveNewlines: true}})`npm run build`;