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

jest-runner-cli

v0.2.8

Published

Jest custom runner for CLI workflows with a minimal CliRunner helper

Readme

jest-runner-cli

日本語ドキュメント

English | 日本語

A custom Jest runner with an imperative CLI process helper for testing CLI applications.

jest-runner-cli is a lightweight Jest runner package that provides:

  • A custom Jest runner built with create-jest-runner for seamless integration with Jest 29.6.1+
  • A CliRunner helper class to spawn and interact with child processes in tests
  • Full support for stdout/stderr monitoring, JSON parsing, and process management

Perfect for testing CLI tools, scripts, and command-line applications in your Jest test suite.

Features

Custom Jest Runner — Drop-in replacement for running tests with a custom runner
CliRunner Helper — Easy-to-use imperative API for spawning and controlling CLI processes
Flexible Output Reading — Read stdout as lines, raw text, or parse JSON
Auto-Exit Protection — Automatically detect and terminate hung processes
Cross-Platform — Works on Windows, macOS, and Linux
Jest 29+ Compatible — Fully tested with Jest 29.6.1 through 29.7.0+
TypeScript Ready — Full type definitions included

⚠️ Limitations — Advanced retry strategies and custom signal handling not yet implemented

Installation

npm install --save-dev jest-runner-cli

Peer Dependency: Jest ^29.6.1

Quick Start

1. Configure Jest

Update jest.config.js:

// jest.config.js (ESM)
export default {
  runner: 'jest-runner-cli',
  testMatch: ['<rootDir>/test/**/*.test.ts']
};

2. Use in Tests

import { CliRunner } from 'jest-runner-cli';

describe('CLI testing', () => {
  it('runs node -v and captures output', async () => {
    const cli = new CliRunner();
    cli.start({ command: process.execPath, args: ['-v'] });
    
    const lines = await cli.readStdout().toLines(2000);
    expect(lines[0]).toMatch(/^v\d+\.\d+\.\d+/);
    
    await cli.sendCtrlC();
    cli.dispose();
  });
});

Usage Guide

Jest Runner Configuration

The package acts as a Jest custom runner. Once configured in jest.config.js, Jest will automatically use it to execute your test files.

CliRunner API

Basic Usage

import { CliRunner } from 'jest-runner-cli';

const runner = new CliRunner();

// Start a process
runner.start({
  command: 'node',
  args: ['./my-script.js'],
  cwd: process.cwd(),
  env: process.env
});

// Write to stdin
runner.writeln('input data');

// Read output
const output = await runner.readStdout().toLines();

// Gracefully stop
await runner.sendCtrlC();
runner.dispose();

Reading Output

// Read as array of lines
const lines = await runner.readStdout().toLines(2000); // timeout in ms

// Read as raw string
const text = await runner.readStdout(2000);

// Extract JSON
const json = await runner.readStdout().toJson(2000);

// Get stderr
const errors = runner.readStderr();

// Clear buffer
runner.readStdout().clear();

Handling Process Events

// Listen for process exit
runner.on('exit', ({ code, signal }) => {
  console.log(`Process exited with code ${code}`);
});

// Auto-exit on timeout (e.g., hung process)
runner.start({ command: 'node', args: ['long-running.js'] }, 5000); // 5s timeout

// Listen for auto-exit error
runner.once('error', (err) => {
  if (err.message === 'auto-exit timeout reached') {
    console.log('Process was auto-terminated');
  }
});

Complete Example

import { CliRunner } from 'jest-runner-cli';

describe('My CLI App', () => {
  let cli: CliRunner;

  beforeEach(() => {
    cli = new CliRunner();
  });

  afterEach(async () => {
    await cli.sendCtrlC().catch(() => {});
    cli.dispose();
  });

  it('displays help text', async () => {
    cli.start({ command: 'node', args: ['./bin/cli.js', '--help'] });
    const output = await cli.readStdout().toLines(2000);
    
    expect(output.join('\n')).toContain('Usage:');
  });

  it('handles JSON output', async () => {
    cli.start({ command: 'node', args: ['./bin/cli.js', '--json'] });
    const data = await cli.readStdout().toJson(2000);
    
    expect(data).toHaveProperty('version');
  });

  it('detects hung process', async () => {
    const error = await new Promise((resolve) => {
      cli.once('error', resolve);
      cli.start(
        { command: 'node', args: ['-e', 'setTimeout(() => {}, 60000)'] },
        3000 // 3s timeout
      );
    });

    expect(error.message).toBe('auto-exit timeout reached');
  });
});

API Reference

CliRunner

Methods

| Method | Parameters | Returns | Description | |--------|-----------|---------|-------------| | start() | SpawnOptions, exitWaitTimeout? | this | Spawn a child process. If exitWaitTimeout is set (ms), the process will auto-terminate if it doesn't exit within that time. | | write() | data: string | void | Write to stdin without a newline. | | writeln() | data: string | void | Write to stdin with a newline appended. | | readStdout() | timeout?: number | Promise<string> | OutputHelper | Read stdout buffer. With timeout arg, returns raw string. Without arg, returns helper with .toLines(), .toJson(), .clear() methods. | | readStderr() | — | string | Get stderr buffer (non-blocking). | | sendCtrlC() | timeout?: number | Promise<void> | Send SIGINT and wait for process exit. Falls back to SIGKILL on timeout. | | dispose() | — | void | Force-kill process and release resources. |

Events

| Event | Callback Arguments | Description | |-------|-------------------|-------------| | exit | { code, signal } | Process exited. | | stdout | chunk: string | Data received on stdout. | | stderr | chunk: string | Data received on stderr. | | error | err: Error | Error occurred (e.g., auto-exit timeout). |

Types

type SpawnOptions = {
  command?: string;        // Required: command to execute
  args?: string[];         // Command arguments
  cwd?: string;            // Working directory
  env?: NodeJS.ProcessEnv; // Environment variables
};

Project Structure

jest-runner-cli/
├── src/
│   ├── index.ts          # Main entry point, Jest runner export
│   ├── run.ts            # Jest runner implementation
│   └── CliRunner.ts      # CliRunner class
├── test/unit/
│   └── cliRunner.test.ts # Unit tests
├── dist/                 # Compiled output (generated)
├── jest.config.js        # Jest configuration
├── tsconfig.json         # TypeScript base config
├── tsconfig.build.json   # TypeScript build config
└── package.json          # Package metadata

Development

Setup

git clone https://github.com/yourusername/jest-runner-cli.git
cd jest-runner-cli
npm install

Common Commands

npm run build      # Compile TypeScript
npm run test       # Run tests
npm run lint       # Check code quality
npm run type-check # Check TypeScript types
npm run docs       # Generate TypeDoc documentation
npm run depcruise  # Analyze dependencies

Testing

# Run all tests
npm test

# Run specific test file
npm test -- cliRunner.test.ts

# Run with coverage
npm run test:ci

Technical Details

  • Runtime: Node.js 18+, TypeScript 5.3+
  • Jest Version: 29.6.1+ (fully compatible including 29.7.0+)
  • Module Format: CommonJS (compatible with ESM via package.json exports)
  • Build: TypeScript compiled to dist/ folder with webpack bundling

Implementation Notes

  • Jest Runner Architecture: The package exports a Jest custom runner built on create-jest-runner. The run.ts file implements the create-jest-runner run file API, which receives options { testPath, globalConfig, config, ... } and delegates test execution to jest-circus/runner — Jest's standard test runner as of version 29+.

  • jest-circus Integration: Starting from Jest 29.6.1+, we use jest-circus/runner as the underlying test executor. This avoids direct references to jest-runner's non-public export paths, ensuring compatibility with Node's package exports constraints.

  • TypeScript Compilation: TypeScript is compiled with separate configs:

    • tsconfig.json — development (no emit, strict mode enabled)
    • tsconfig.build.json — build (emits to dist/, includes type definitions)
    • Webpack bundling produces the final output as CommonJS for broad compatibility
  • CliRunner Implementation: Based on Node.js child_process.spawn() with event-driven stdout/stderr buffering. Auto-exit timeout uses setTimeout to detect hung processes and escalates from SIGINT to SIGKILL.

Compatibility with Jest 29+

As of version 0.2.6+, jest-runner-cli is fully compatible with Jest 29.6.1 and later versions, including Jest 29.7.0+. The implementation correctly uses jest-circus/runner instead of direct references to jest-runner's private API paths, ensuring it works with Node's strict exports constraints.

Issue Fix (v0.2.6+): Earlier versions referenced jest-runner/build/runTest.js, which triggered ERR_PACKAGE_PATH_NOT_EXPORTED in Jest 29+ due to Node's package export restrictions. This has been resolved by delegating to the public Jest test runner interface through jest-circus/runner.

Troubleshooting

Process Not Starting

Error: No command provided

// ❌ Wrong
runner.start({});

// ✅ Correct
runner.start({ command: 'node', args: ['script.js'] });

Timeout Reading Output

Error: stdout timeout

Increase the timeout value:

// Default 2000ms, increase if needed
const output = await runner.readStdout().toLines(5000);

Process Still Running After sendCtrlC

On Windows, the process may not respond to SIGINT. The runner will auto-escalate to force-kill after timeout:

// Will escalate to taskkill after 2000ms
await runner.sendCtrlC();

// Or specify custom timeout
await runner.sendCtrlC(5000);

Changelog

v0.2.6–0.2.7 (Latest)

  • Fixed: ERR_PACKAGE_PATH_NOT_EXPORTED error with Jest 29.6.1+
    • Changed from jest-runner direct API to jest-circus/runner for test execution
    • Updated run.ts to use create-jest-runner run file API ({ testPath, globalConfig, config } signature)
    • Ensured full compatibility with Node's strict package exports constraints
  • ✅ Updated integration tests with new run function signature
  • ✅ Added repro test demonstrating Jest 29+ compatibility

v0.2.0

  • ✅ Refactored to CommonJS module format for broad compatibility
  • ✅ Integrated with create-jest-runner for Jest custom runner functionality
  • ✅ Added comprehensive TypeScript type definitions
  • ✅ Added auto-exit timeout feature for hung process detection
  • ✅ Updated test suite with async/await patterns

v0.1.0

  • Initial release with basic CliRunner functionality

License

MIT © 2026

Contributing

Contributions are welcome! Please feel free to submit issues or pull requests.

For development, ensure:

  • TypeScript strict mode is enabled
  • All tests pass (npm test)
  • Linting passes (npm run lint)
  • New features include unit tests in test/unit/