mrkeyoor.com_
Sat 08 Aug 21:01 UTC
npmCLI & Toolingupdated 08 Aug 2026

spawndamnit

spawndamnit is a very small CommonJS wrapper around `child_process.spawn()`. It uses `cross-spawn` for command lookup, returns a Promise that resolves when the child closes, buffers stdout and stderr, emits each output chunk as it arrives, and sends SIGTERM to children still active when the parent exits. Its entire public surface is essentially one function plus the unusual Promise and EventEmitter hybrid it returns.

Verdict

A compact fit for old-style CommonJS tools that want exactly buffered output, live chunk events, and an awaitable close result. New code usually gets safer defaults, types, cancellation, and better failure handling from execa, or clearer control from built-in `spawn()`.

API stability4/5The package exposes one function and has kept the same Promise-result shape and `stdout` and `stderr` events across its small release history. Version 3 updated dependencies without expanding the surface. That simplicity lowers accidental churn, but the contract is only lightly documented and there are no TypeScript declarations to catch option or return-shape mistakes before runtime.
Docs2/5The README accurately lists the four core behaviors and provides one runnable example showing live events and the final `{ code, stdout, stderr }` result. It does not document spawn options, nonzero-exit semantics, inherited stdio, spawn-error rejection, memory behavior, ESM interop, child termination limits, or the exported `ChildProcessPromise`, so important behavior must be learned from roughly two source files.
Maintenance2/5Version 3.0.1 and the last repository push both date to November 18, 2024. That release did refresh `cross-spawn` and `signal-exit`, the repository is not archived, and GitHub reports only 9 open issues and pull requests. There has been no visible activity for well over a year, though, and the published package omits tests and modern type metadata, so maintenance looks occasional rather than continuous.
Ecosystem3/5The package recorded 4,244,606 downloads for the measured week, but that reach is likely dominated by transitive use in established toolchains rather than a broad direct-user ecosystem. It sensibly builds on `cross-spawn` and `signal-exit`, works with ordinary Node spawn options, and has no plugins, framework bindings, TypeScript types, or extension model of its own.

Use it if

  • You maintain a CommonJS CLI that wants `spawn()` argument safety plus a simple awaitable result
  • You need both live stdout and stderr events and final Buffer values without writing the collection code yourself
  • You need Windows-friendly executable lookup through cross-spawn
  • You accept that a nonzero exit code is a normal resolved result and will check it explicitly
Skip it if

Setup reality

`npm install spawndamnit` is enough and there are no peer dependencies or native builds. The runtime is CommonJS, so use `require()` in CommonJS or Node's default-import interop from ESM; there is no `exports` map and no TypeScript declaration file. The function accepts the same command, argument array, and options shape as `child_process.spawn()`, but the README does not document those options. Pass arguments as an array and leave `shell` off whenever input can be influenced by users. The returned object is both a Promise and an EventEmitter: attach `stdout` and `stderr` listeners before awaiting it, because fast commands may emit immediately. A normal nonzero exit does not reject, so every caller must inspect `code`. A command that cannot be spawned rejects. Output is always accumulated into memory when pipes exist, even if you only care about the exit code; there is no cap, streaming-only switch, timeout, or AbortSignal support. Setting `stdio: 'inherit'` means the child streams are absent and the result Buffers remain empty. The wrapper tracks active children globally and sends SIGTERM when the parent exits, but it does not expose each ChildProcess, so callers cannot directly read the PID or kill a single task through the documented API. If you need those controls, use Node's built-in process APIs or a fuller runner such as execa.

Patterns

Run a command and inspect its exit coderun-command

const spawn = require('spawndamnit');

const { code, stdout, stderr } = await spawn('git', ['status', '--short']);
if (code !== 0) {
  throw new Error(stderr.toString('utf8') || `git exited ${code}`);
}
console.log(stdout.toString('utf8'));

A nonzero exit code resolves rather than rejects, so treating failure as an exception is the caller's job.

Print output while retaining the final buffersstream-live-output

const child = spawn('npm', ['run', 'build']);
child.on('stdout', (chunk) => process.stdout.write(chunk));
child.on('stderr', (chunk) => process.stderr.write(chunk));

const result = await child;
process.exitCode = result.code ?? 1;

Attach listeners before awaiting. The wrapper still buffers every chunk, so this is not safe for commands with unlimited output.

Run in a specific working directorypass-working-directory

const result = await spawn('npm', ['test'], {
  cwd: '/srv/my-project',
  env: process.env,
});

The third argument is forwarded to `cross-spawn`; the package README does not enumerate these standard spawn options.

Extend the child environmentadd-environment-variable

await spawn('node', ['script.js'], {
  env: {
    ...process.env,
    NODE_ENV: 'production',
  },
});

Spreading `process.env` matters. Supplying only your custom key discards PATH and other environment values the command may need.

Let an interactive command use the terminalinherit-terminal-stdio

const { code, stdout, stderr } = await spawn('npm', ['init'], {
  stdio: 'inherit',
});

console.log(code, stdout.length, stderr.length);

With inherited stdio, stdout and stderr are not piped, so the returned Buffers are empty and no output events are emitted.

Distinguish a spawn error from an exit failurehandle-missing-command

try {
  const result = await spawn('command-that-may-not-exist', []);
  if (result.code !== 0) console.error('process failed', result.code);
} catch (error) {
  if (error.code === 'ENOENT') console.error('executable not found');
  else throw error;
}

Only the child `error` event rejects the Promise; an executable that starts and then exits 1 produces a resolved result.

Import the CommonJS package from Node ESMuse-from-esm

import spawn from 'spawndamnit';

const { code } = await spawn(process.execPath, ['--version']);
if (code !== 0) throw new Error(`node exited ${code}`);

This relies on Node's CommonJS default-import interop. The package itself has no ESM export map or TypeScript declarations.

Launch the same Node executable portablyrun-current-node

const { code, stdout } = await spawn(process.execPath, [
  '-e',
  'process.stdout.write(process.version)',
]);
console.log(code, stdout.toString());

Use `process.execPath` rather than assuming the executable is named `node` or available through the child's PATH.

Pass user data as an argument, not shell textavoid-shell-injection

const branch = userSuppliedBranch;
const { code } = await spawn('git', ['show', '--stat', '--', branch], {
  shell: false,
});

Keep `shell` disabled for untrusted values. Argument arrays avoid shell interpolation, while `--` stops Git from treating the value as an option.

Parse JSON output after a successful exitdecode-json-output

const result = await spawn('npm', ['view', 'react', '--json']);
if (result.code !== 0) {
  throw new Error(result.stderr.toString('utf8'));
}
const metadata = JSON.parse(result.stdout.toString('utf8'));

stdout and stderr are Buffers, not strings. Decode explicitly, and only parse after checking the exit code.

Alternatives

PackageRegistryPick it when
execanpmChoose it for typed ESM, rich errors, cancellation, timeouts, piping, input handling, and configurable output limits
zxnpmChoose it for script-like automation where a shell-oriented template API and bundled utilities improve readability
cross-spawnnpmChoose it when you only need Windows-correct spawning and want to keep the real ChildProcess handle