Run external commands and manage their output easily. You get simple success or failure objects containing exit codes, execution time, and all captured text from standard output and error streams.
- Process Management: Spawn and manage external commands with a clean API.
- Result Types: Get structured success and failure objects for every run.
- Unified Output: Combines stdout and stderr into a single stream for easier logging.
- Windows Normalization: Automatically handles
cmd.exeand path escaping for Windows. - Automated Resolution: Locate binaries automatically using PATH and file extensions.
- Process Piping: Chain multiple commands together to pass data between them.
- Abort Support: Cancel running processes using standard abort signals.
- Exit Tracking: Capture precise exit codes and signal codes from tasks.
- Execution Timing: Measure the exact duration of a command in milliseconds.
npm add @zokugun/picoexec- Node.js:
v18.20or higher - Modules: ECMAScript Modules (
ESM) or CommonJS (CJS)
Run a command and wait for the final output. Use this when you only need the end result.
import { exec } from '@zokugun/picoexec';
async function runSimpleCommand(): Promise<void> {
const result = await exec('echo', ['hello world']);
if(result.fails) {
console.error(`Failed with exit code: ${result.value.exitCode}`);
return;
}
const { stdout } = result.value;
console.log(`Output: ${stdout}`);
}Iterate over the process output in real-time.
import { exec } from '@zokugun/picoexec';
async function runPipedCommands(): Promise<void> {
const task = exec('npm', ['install']);
for await(const line of task) {
console.log(`Log: ${line}`);
}
if(task.failed) {
console.error(`Failed with exit code: ${task.exitCode}`);
}
}Chain multiple commands together using the .pipe() method. The output of the first process is passed as the input to the second.
import { exec } from '@zokugun/picoexec';
async function runPipedCommands(): Promise<void> {
const result = await exec('cat', ['package.json']).pipe('grep', ['dependencies']);
if(result.fails) {
console.log('Pipeline failed');
return;
}
console.log(`Filtered output: ${result.value.stdout}`);
}
await runPipedCommands();export function exec(
command: string,
args?: readonly string[] | null,
options?: Partial<Options>,
): Subprocess;Spawns a child process and returns a Subprocess instance. The process starts immediately on return.
Caveats:
execdoes not throw. Errors surface through thefailsflag of theResult.- On Windows, batch files (
.bat,.cmd) are wrapped incmd.exewith verbatim argument handling.
class ExecProcess implements Subprocess { ... }The class behind every subprocess returned by exec. You rarely need to construct it directly — use exec() instead.
Key properties:
| Property | Type | Description |
|---|---|---|
.aborted |
boolean |
True if the process was aborted via signal |
.error |
Error | undefined |
The error that caused failure, if any |
.exitCode |
number | undefined |
Exit code after the process closes |
.failed |
boolean |
True if exit code is non-zero or a signal was received |
.killed |
boolean |
True if kill() was called |
.pid |
number | undefined |
OS process ID |
.process |
ChildProcess | undefined |
The raw Node.js child process handle |
.signalCode |
string | undefined |
Signal code that terminated the process |
Key methods:
| Method | Returns | Description |
|---|---|---|
.kill(signal?) |
boolean |
Sends a signal to the process. Returns false if no handle exists. |
.pipe(command, args?, options?) |
Subprocess |
Chains another command, piping this stdout into its stdin. |
.spawn() |
void |
Starts the process. Called automatically by exec(). |
A discriminated union wrapping success or failure:
type ExecResult = Result<ExecSuccess, ExecFailure>;Returned when the process exits with code 0 and no error occurred.
type ExecSuccess = {
all: string; // combined stdout + stderr
command: string;
durationMs: number;
exitCode: number;
stdout: string;
stderr: string;
};Returned when the process errors, exits non-zero, or gets killed.
type ExecFailure = {
all?: string;
canceled: boolean; // true if aborted via signal
command: string;
durationMs: number;
error?: Error;
exitCode?: number;
signalCode?: string;
stderr?: string;
stdout?: string;
};The shape returned by exec(). It is:
- A promise-like — resolves to
ExecResultwhen awaited. - An async iterable — yields lines from stdout + stderr.
- A live handle — inspect
.exitCode,.pid, etc. at any time after spawn.
type Subprocess = AsyncIterable<string> & PromiseLike<ExecResult> & { ... };Extended SpawnOptions with extra fields:
| Field | Type | Default | Description |
|---|---|---|---|
cwd |
string |
process.cwd() |
Working directory |
env |
Record<string, string> |
inherited + computed | Environment variables |
localDir |
string |
cwd |
Directory to resolve local paths from |
preferLocal |
boolean |
false |
Prepends node_modules/.bin to PATH |
signal |
AbortSignal |
none | Abort signal for cancellation |
stdin |
string | Subprocess | { process } |
pipe |
Input source |
stdio |
none | Standard I/O configurations. | |
stdout / stderr |
StdioOption |
none | Stream configuration |
timeout |
number |
none | Auto-abort after N milliseconds |
Contributions are most welcome. Please:
- Open issues and feature requests under the repository discussions.
- Follow the
CONTRIBUTING.md.
Support this project by becoming a financial contributor.
| ko-fi.com/daiyam | |
| liberapay.com/daiyam/donate | |
| paypal.me/daiyam99 |
Copyright © 2026-present Baptiste Augrain
Licensed under the MIT license.