Cross-platform child_process.spawn — drop-in replacement for cross-spawn with zero dependencies.
| cross-spawn | @agentine/crossbow | |
|---|---|---|
| TypeScript types included | ❌ | ✅ |
| ESM support | ❌ | ✅ |
| CJS support | ✅ | ✅ |
| Zero dependencies | ❌ (3 deps) | ✅ |
| Actively maintained | ❌ (dormant 4+ years) | ✅ |
| BatBadBut mitigation | ❌ | ✅ |
windowsHide option |
❌ | ✅ |
| Node 24 deprecation-free | ❌ | ✅ |
| Windows built-in commands | ❌ | ✅ |
- Node.js 18 or later
npm install @agentine/crossbowimport { spawn, spawnSync } from '@agentine/crossbow';
// Async — stream output
const child = spawn('node', ['--version']);
child.stdout?.on('data', (data) => console.log(data.toString()));
child.on('close', (code) => console.log(`exit: ${code}`));
// Sync — capture output
const result = spawnSync('node', ['--version']);
console.log(result.stdout.toString()); // 'v22.0.0\n'
// CommonJS
const { spawn, spawnSync } = require('@agentine/crossbow');Cross-platform child_process.spawn. On Windows, handles PATHEXT resolution, shebang interpreter detection, cmd.exe argument escaping, and Windows built-in commands. On macOS/Linux, passes through to child_process.spawn with minimal overhead.
| Parameter | Type | Description |
|---|---|---|
command |
string |
Executable name or path |
args |
readonly string[] |
Arguments to pass (optional) |
options |
CrossbowSpawnOptions |
Spawn options (optional) |
Returns a ChildProcess — identical to child_process.spawn.
import type { CrossbowSpawnOptions } from '@agentine/crossbow';
const opts: CrossbowSpawnOptions = { stdio: 'inherit', windowsHide: true };
const child = spawn('npm', ['install'], opts);
child.on('close', (code) => console.log(`exit: ${code}`));Cross-platform child_process.spawnSync. Same cross-platform handling as spawn.
| Parameter | Type | Description |
|---|---|---|
command |
string |
Executable name or path |
args |
readonly string[] |
Arguments to pass (optional) |
options |
CrossbowSpawnSyncOptions |
Spawn options (optional) |
Returns SpawnSyncReturns<Buffer> — identical to child_process.spawnSync.
const result = spawnSync('git', ['status'], { encoding: 'utf8' });
if (result.status !== 0) {
console.error(result.stderr);
} else {
console.log(result.stdout);
}import type {
CrossbowSpawnOptions, // extends Node.js SpawnOptions — adds windowsHide
CrossbowSpawnSyncOptions, // extends Node.js SpawnSyncOptions — adds windowsHide
ParsedCommand, // internal parsed command structure
WhichOptions, // options for whichSync() / whichSyncAll()
} from '@agentine/crossbow';Extends the standard Node.js SpawnOptions / SpawnSyncOptions with one additional property:
| Property | Type | Description |
|---|---|---|
windowsHide |
boolean |
Hide the spawned subprocess console window on Windows (fixes cross-spawn #143) |
All other SpawnOptions / SpawnSyncOptions properties are supported unchanged.
| Property | Type | Description |
|---|---|---|
path |
string |
Custom PATH string to search instead of process.env.PATH |
pathExt |
string |
Custom PATHEXT (Windows only) — semicolon-separated extensions |
all |
boolean |
If true, return all matches (used internally by whichSyncAll) |
Advanced utilities exported for custom integration:
import {
pathKey, // Returns the PATH env key ('PATH' or 'Path' on Windows)
whichSync, // Find an executable in PATH (first match)
whichSyncAll, // Find all matching executables in PATH
parseShebang, // Read the shebang line from a file
parseShebangCommand, // Parse a shebang string into { command, args }
escapeArg, // Escape a single argument for cmd.exe
escapeCommand, // Escape the command itself for cmd.exe
isBatchFile, // Returns true if path ends in .bat or .cmd
resolveCommand, // Resolve a command to its full path
} from '@agentine/crossbow';Returns the correct PATH key for the given environment. On Windows, the key may be PATH, Path, or path (case-insensitive lookup). On all other platforms, returns "PATH".
pathKey(); // 'PATH'
pathKey({ Path: 'C:\\Windows' }); // 'Path' (Windows)Find an executable in PATH. Returns the full resolved path or null if not found. If cmd contains a path separator, resolves it directly without PATH search.
whichSync('node'); // '/usr/local/bin/node'
whichSync('node', { path: '/usr/bin' }); // '/usr/bin/node'
whichSync('nonexistent'); // nullFind all matching executables in PATH. Returns every match across all PATH directories. Useful when multiple versions of a command are installed.
whichSyncAll('python'); // ['/usr/bin/python', '/usr/local/bin/python']
whichSyncAll('node'); // ['/usr/local/bin/node']Read the shebang line from a file. Returns the interpreter string (everything after #!) or null if no shebang is present.
parseShebang('/usr/local/bin/ts-node'); // '/usr/bin/env ts-node'
parseShebang('/usr/bin/node'); // nullParse a shebang string into its command and optional arguments. Handles env-style shebangs.
parseShebangCommand('/usr/bin/env node'); // { command: 'node', args: [] }
parseShebangCommand('/usr/bin/env ts-node -T'); // { command: 'ts-node', args: ['-T'] }
parseShebangCommand('/usr/bin/python3'); // { command: '/usr/bin/python3', args: [] }Escape a single argument for cmd.exe. Handles cmd metacharacters and wraps in quotes when necessary.
| Parameter | Type | Description |
|---|---|---|
arg |
string |
The argument to escape |
doubleEscape |
boolean |
Apply extra escaping for .bat/.cmd files (BatBadBut mitigation) |
escapeArg('hello world', false); // '"hello world"'
escapeArg('file.txt', false); // 'file.txt'
escapeArg('a&b', true); // '"a^&b"' (metachar escaped before quoting)Escape a command string for cmd.exe. Wraps in double quotes if the command contains spaces or metacharacters.
escapeCommand('node'); // 'node'
escapeCommand('C:\\My Tools\\run'); // '"C:\\My Tools\\run"'Returns true if the file path ends in .bat or .cmd (case-insensitive).
isBatchFile('script.bat'); // true
isBatchFile('script.CMD'); // true
isBatchFile('script.sh'); // falseResolve a command name to its full path. On Windows, also detects shebang interpreters and returns the interpreter path. Returns null if the command cannot be found.
resolveCommand('node'); // '/usr/local/bin/node'
resolveCommand('npm'); // '/usr/local/bin/npm'
resolveCommand('ghost'); // nullCrossbow handles the full range of Windows-specific quirks automatically:
- PATHEXT resolution — tries each extension in
PATHEXT(.COM,.EXE,.BAT,.CMD, etc.) when looking up a command - Shebang detection — reads the
#!line from script files to find the correct interpreter - Batch file routing —
.bat/.cmdfiles are automatically run throughcmd.exe /d /s /cwith proper escaping - Built-in commands — Windows shell built-ins (
dir,echo,copy,del,mkdir, etc.) are automatically routed throughcmd.exe - COMSPEC — uses the
COMSPECenvironment variable to locatecmd.exe(falls back tocmd.exe) - Argument escaping — correctly escapes arguments for
cmd.exe, including metacharacters ((,),%,!,^,",<,>,&,|) - windowsHide — supports the
windowsHideoption to suppress console windows for spawned subprocesses
On non-Windows platforms, crossbow passes commands directly to child_process.spawn / child_process.spawnSync with no transformation. There is no measurable overhead compared to calling Node.js directly.
crossbow is a drop-in replacement. Change your import and you're done:
- const { spawn, spawnSync } = require('cross-spawn');
+ import { spawn, spawnSync } from '@agentine/crossbow';Or with CommonJS:
- const { spawn, spawnSync } = require('cross-spawn');
+ const { spawn, spawnSync } = require('@agentine/crossbow');All function signatures are identical to cross-spawn. No other changes required.
Crossbow prevents the BatBadBut vulnerability — a class of argument injection attacks against .bat and .cmd files on Windows.
When a resolved command is a batch file, crossbow applies double-escaping: cmd.exe metacharacters ((, ), %, !, ^, ", <, >, &, |) are escaped with ^ before the argument is wrapped in double quotes. This prevents user-supplied input from breaking out of the argument and executing arbitrary cmd.exe commands.
cross-spawn does not implement this mitigation.
- Avoid passing unsanitized user input as command arguments on any platform.
- Use
stdio: 'pipe'orstdio: 'inherit'rather thanshell: true— crossbow handles shell routing internally for batch files and built-ins. - On Windows, prefer specifying the full path to executables when security is critical, rather than relying on PATH resolution.
MIT