npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

node-process-utils

v1.0.1

Published

The node-process-utils package runs child processes with bounded time and output, and stops the process trees it owns: graceful then forced process-group shutdown on POSIX, and taskkill on Windows. Exit codes are returned rather than thrown, so callers de

Readme

Node Process Utils

The node-process-utils package runs child processes with bounded time and output, and stops the process trees it owns. It covers the mechanics every tool that spawns processes needs: no shell, a cap on collected output, timeouts and abort signals, and shutdown of the child together with its descendants.

Exit codes are returned rather than thrown, so callers decide which codes are acceptable and process output never ends up in an error message. Policies that depend on the caller, such as Git environment variables, credential handling or redacting diagnostics, stay in the caller's code.

The package runs on Node.js 24 and later.

Getting Started

Install the package:

npm i -S node-process-utils

Examples

Run a command and check its exit code:

import { runProcess } from 'node-process-utils';

const { exitCode, stdout } = await runProcess({
  command: 'git',
  args: ['rev-parse', 'HEAD'],
  cwd: '/path/to/repository',
  timeoutSeconds: 30,
});

if (exitCode !== 0) {
  throw new Error(`git rev-parse failed with exit code ${exitCode}`);
}
console.log(stdout.trim()); // '4c0e2814b449d5c1cee5012f7f46fb9fb1c21df0'

Stop the run when a CLI receives Ctrl+C. The child leads its own process group, so the terminal's signal reaches only your process:

import { runProcess } from 'node-process-utils';

const controller = new AbortController();
process.once('SIGINT', () => controller.abort(new Error('Interrupted')));
process.once('SIGTERM', () => controller.abort(new Error('Terminated')));

await runProcess({ command: 'npm', args: ['test'], signal: controller.signal });

Start a long-running process and stop it, with its descendants, when you are done:

import { startProcess, stopProcessTree } from 'node-process-utils';

const server = startProcess('node', ['server.js'], { env: { ...process.env, PORT: '3000' } });
// ...
await stopProcessTree(server);

API Reference

Runs one command to completion, without a shell and, on POSIX, as the leader of its own process group. It resolves with an IProcessResult: the exit code, the terminating signal, stdout and stderr decoded as UTF-8, and the duration in milliseconds.

  • Exit codes: every exit resolves, whatever its code.
  • Input: input is written to stdin, which is then closed. Input that the child never reads, because it exits first, is discarded without an error.
  • Output: stdout and stderr together can hold at most maxOutputBytes bytes (DEFAULT_MAX_OUTPUT_BYTES, 1 MiB, by default).
  • Descendants: when the child exits, descendants left in its process group are stopped. If a descendant that couldn't be reached keeps stdout or stderr open for longer than the grace period, the call rejects with PROCESS_TREE_SURVIVED instead of hanging. Examples are a descendant that left the group with setsid, or one orphaned on Windows.
  • Rejections: the call rejects only in these cases, and stops the process tree first, so the rejection can arrive up to the grace period plus two seconds after the trigger:
    • PROCESS_START_FAILED when the command can't start, with the underlying error as cause
    • PROCESS_TIMED_OUT when timeoutSeconds passes
    • PROCESS_OUTPUT_TOO_LARGE when the output exceeds maxOutputBytes
    • the signal's reason when signal aborts
    • PROCESS_TREE_SURVIVED when the process tree can't be stopped
  • Ctrl+C: because the child leads its own group, a terminal's Ctrl+C reaches only the parent. CLIs should abort the signal on SIGINT and SIGTERM, as in the example above.
import { runProcess } from 'node-process-utils';

await runProcess({
  command: 'node',
  args: ['-e', 'process.stdin.pipe(process.stdout)'],
  input: 'Hello World!',
});
// {
//   exitCode: 0,
//   signalCode: null,
//   stdout: 'Hello World!',
//   stderr: '',
//   durationMs: 41
// }

Starts a long-running child that you own and returns its ChildProcess synchronously. It runs without a shell, with stdio set to 'inherit' by default, and, on POSIX, as the leader of its own process group, so stopProcessTree can stop it together with its descendants. A command that can't start emits an 'error' event on the returned child.

import { startProcess } from 'node-process-utils';

const watcher = startProcess('npm', ['run', 'watch'], { cwd: 'packages/app' });
watcher.once('exit', (code) => console.log(`watch exited with ${code}`));

Stops a child together with its descendants and resolves once they are gone. It resolves immediately for a child without a pid.

  • POSIX: it sends SIGTERM to the child's process group, waits up to gracePeriodSeconds (DEFAULT_GRACE_PERIOD_SECONDS, 5, by default), then sends SIGKILL and waits up to two more seconds. It resolves only once the child has exited and its group is gone.
  • Precondition: it is designed for children from startProcess or runProcess, which lead their own process group. For any other child, it stops the child alone.
  • Windows: it runs taskkill /PID <pid> /T /F and waits up to 10 seconds for the exit. taskkill finds descendants by walking down from a root process that is still running, so when the root has already exited, the call resolves and orphaned descendants may remain.
  • Limits: descendants that start their own session or process group, for example with setsid, aren't stopped on POSIX, and orphaned descendants aren't stopped on Windows.
  • Failure: it rejects with PROCESS_TREE_SURVIVED when anything it can observe outlives the forced stop.
import { startProcess, stopProcessTree } from 'node-process-utils';

const child = startProcess('node', ['server.js']);
await stopProcessTree(child, 10);

Constants

The number of bytes stdout and stderr can hold together when runProcess gets no maxOutputBytes.

const DEFAULT_MAX_OUTPUT_BYTES = 1048576; // 1 MiB

The number of seconds stopProcessTree waits after SIGTERM before it sends SIGKILL. runProcess uses it too.

const DEFAULT_GRACE_PERIOD_SECONDS = 5;

Types

The options accepted by runProcess.

type IRunProcessOptions = {
  // the executable to run, without a shell
  command: string;

  // the arguments passed to the executable
  args: string[];

  // the working directory. Defaults to the current one
  cwd?: string;

  // the environment variables. Defaults to process.env
  env?: NodeJS.ProcessEnv;

  // the data written to the process's stdin before it is closed
  input?: string | Uint8Array;

  // the seconds after which the process tree is stopped and the call rejects
  timeoutSeconds?: number;

  // the bytes stdout and stderr can hold together. Defaults to DEFAULT_MAX_OUTPUT_BYTES
  maxOutputBytes?: number;

  // stops the process tree and rejects with the signal's reason when it aborts
  signal?: AbortSignal;
};

The outcome of a process that ran to completion.

type IProcessResult = {
  // the exit code, or null when a signal ended the process
  exitCode: number | null;

  // the signal that ended the process, or null when it exited on its own
  signalCode: NodeJS.Signals | null;

  // the output, decoded as UTF-8
  stdout: string;
  stderr: string;

  // the milliseconds between the start and the end of the run
  durationMs: number;
};

The options accepted by startProcess.

type IStartProcessOptions = {
  // the working directory. Defaults to the current one
  cwd?: string;

  // the environment variables. Defaults to process.env
  env?: NodeJS.ProcessEnv;

  // the stdio configuration. Defaults to 'inherit'
  stdio?: StdioOptions;
};

Built With

  • TypeScript
  • node:child_process

Running the Tests

The integration tests start real processes and run on POSIX systems; the process-group tests are skipped on Windows.

# unit and integration tests
npm run test

# tests, type checking, linting and formatting
npm run verify

# unit tests
npm run test:unit

# integration tests
npm run test:integration

License

MIT