This is a drop-in replacement for cross-spawn ( a cross-platform solution to node's spawn and spawnSync ) which ports its codebase to modern ESM and TypeScript.
This package must behave the same as the original package, but small changes were made during the refactoring. Please refer to the Migration guide for further explanation.
With NPM:
npm install cross-spawn-esmWith Yarn:
yarn add cross-spawn-esmWith PNPM:
pnpm add cross-spawn-esmWith Bun:
bun add cross-spawn-esmWith Deno:
deno add cross-spawn-esmUse it exactly the same way as node's spawn and spawnSync ( a drop-in replacement for them ) with the same arguments and options. There
is no default export; always use named imports.
import { spawn, spawnSync } from "cross-spawn-esm";
// Spawn NPM asynchronously
const child = spawn("npm", ["list", "-g", "-depth", "0"], { stdio: "inherit" });
// Spawn NPM synchronously
const result = spawnSync("npm", ["list", "-g", "-depth", "0"], { stdio: "inherit" });- Overall smaller bundle size (~50% smaller)
- Tree-shaking friendly
- Zero dependencies
- No need for an additional types package (
@types/cross-spawn) - Modern codebase and active maintenance
- Better documentation
Windows batch files and shell: true have platform-specific escaping rules. Batch handling is controlled by the batchMode option:
import { spawn, spawnSync } from "cross-spawn-esm";
// Preserve the previous cross-spawn behavior (default).
spawn("npm", ["install"]);
// Opt in to quote-based batch escaping and extra validation.
spawn("npm", ["install"], { batchMode: "strict" });
// Refuse to run .bat/.cmd files at all. `spawn` throws, while `spawnSync`
// surfaces the same EINVAL through `result.error`.
const result = spawnSync("some-tool.cmd", [], { batchMode: "reject" });
if (result.error) throw result.error;The default is "legacy" for backwards compatibility. Unknown runtime values also fall back to "legacy". Use "strict" or "reject"
when the command or its arguments can be influenced by untrusted input. See
SECURITY.md for the batch escaping model, the %~1 limitation, and safe
shell: true usage.
Porting from cross-spawn to cross-spawn-esm is mostly a matter of changing how you import the package and how you call its sync API.
Uninstall cross-spawn (and @types/cross-spawn if you had it) and install cross-spawn-esm:
npm remove cross-spawn @types/cross-spawn
npm install cross-spawn-esmBefore:
import spawn from "cross-spawn";
const child = spawn("npm", ["list", "-g", "-depth", "0"], { stdio: "inherit" });
const result = spawn.sync("npm", ["list", "-g", "-depth", "0"], { stdio: "inherit" });After:
import { spawn, spawnSync } from "cross-spawn-esm";
const child = spawn("npm", ["list", "-g", "-depth", "0"], { stdio: "inherit" });
const result = spawnSync("npm", ["list", "-g", "-depth", "0"], { stdio: "inherit" });If you were relying on the hidden internals:
Before: spawn._parse(...), spawn._enoent.verifyENOENT(...)
After:
import { _parse, _enoent } from "cross-spawn-esm";_enoent.verifyENOENT(status, parsed, syscall)now takes an explicitsyscallargument ("spawn"or"spawnSync"), where the original shipped two separate functions (verifyENOENT/verifyENOENTSync)._enoent.notFoundError(...)now returns a valid NodeJS.ErrnoException._parsenow contains bothparseandparseNonShellfunctions:
Before: const parsed = _parse(...)
After: const parsed = _parse.parse(...)
- New
_utilsobject exposes all the lower-level helpers. If you were relying on the original cross-spawn dependencies (path-key and shebang-command), their improved versions can be found in_utils. - Error constructors are internal, except for
_enoent.notFoundError, which the original package already exposed. The public API also exports theSpawnErrortype.
cross-spawn-esm ships its own type definitions, so the third-party @types/cross-spawn package is no longer needed. The package exports
BatchMode, BatchOptions, CrossSpawnOptions, ParsedCommand, PathKeyOptions, SpawnError, SpawnOptions, and SpawnSyncOptions;
the option types extend Node's corresponding child_process types with the additional batchMode option.
Beyond the API surface, a few implementation details intentionally differ:
-
original.argsis an independent snapshot:parseclones the args and givesoriginal.argsits own copy, so shebang rewiring andcmd.exeescaping never mutate it. -
Broader shebang support: Shebang detection on Windows reads the shebang of the resolved script and rewires the command to its interpreter.
#!/usr/bin/env <program>is resolved fromPATHand spawned directly. Any other shebang (#!/bin/sh,#!/bin/bash -e) is reduced to the interpreter's basename (plus its single argument, if any) and falls back to thecmd.exewrapper, which works when that interpreter is onPATH. -
Batch-file handling (Windows):
batchModecontrols how commands that resolve to or are named as.bat/.cmdfiles are handled."legacy"(default) preserves the previous cross-spawn caret escaping. It is kept for backwards compatibility and is not safe for untrusted input."strict"uses quote-based escaping ("becomes""), rejects%, NUL, CR, and LF withERR_INVALID_ARG_VALUE, rejects batch command names ending in a dot or space, and wraps the command incmd /d /e:on /v:off /s /c. This blocks CVE-2024-27980 for batch files that forward arguments with%*. Batch files that strip quotes themselves (%~1) and use the value unquoted can still be injected; that is outside the caller's control. Strict validation errors are thrown by bothspawn()andspawnSync()."reject"mirrors Node's CVE-2024-27980 rejection:spawn()throws anEINVALerror synchronously, whilespawnSync()returns the error inresult.error(code: "EINVAL",errno: -4071) without throwing.
batchModeonly applies on Windows and whenshellis not enabled. Withshell: true, Node's shell semantics apply and this package does not touch the command or arguments.
The following behaviors are intentionally kept identical to cross-spawn:
- When
options.shellis used, parsing, escaping, and shebang enhancements are disabled - matching both the original and Node.js behavior. - Windows-only ENOENT detection: when the process exits with code
1and the command could not be resolved, anerrorevent (async) orresult.error(sync) is produced.
| Feature | cross-spawn | cross-spawn-esm |
|---|---|---|
| Module format | CommonJS | ESM ("type": "module") |
| Async spawn | spawn (default export) |
spawn (named export) |
| Sync spawn | spawn.sync |
spawnSync |
| Parse internals | spawn._parse |
_parse |
| ENOENT internals | spawn._enoent |
_enoent |
| Other internals | not exposed | _utils |
| TypeScript types | @types/cross-spawn |
built-in |
Released under the MIT License.