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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@universal-packages/sub-process

v2.3.0

Published

Process encapsulation for different exec technics

Readme

Sub Process

npm version Testing codecov

Sub process encapsulation for different exec techniques.

Install

npm install @universal-packages/sub-process

Usage

SubProcess class

The SubProcess class extends BaseRunner to provide a unified API for executing system processes with different engines. It handles process lifecycle, stream capture, and provides event-driven monitoring.

import { SubProcess } from '@universal-packages/sub-process'

const subProcess = new SubProcess({
  command: 'echo "Hello World"',
  args: ['--verbose'],
  env: { NODE_ENV: 'production' },
  captureStreams: true
})

await subProcess.run()

console.log(subProcess.stdout) // "Hello World"
console.log(subProcess.exitCode) // 0

Constructor constructor

new SubProcess(options: SubProcessOptions)

SubProcessOptions

Extends BaseRunnerOptions with the following additional options:

  • command string required Command to run. Can include arguments as part of the command string.

  • args string[] (optional) Additional arguments to pass to the command. These will be appended to any arguments already present in the command string.

  • captureStreams boolean (default: false) Whether to capture stdout and stderr streams. When enabled, the output will be available through the stdout and stderr properties.

  • engine EngineInterface | 'spawn' | 'exec' | 'fork' | 'test' (default: 'spawn') Instance of the engine to be used to execute the process or a string identifying the engine adapter.

    • 'spawn': Uses Node.js child_process.spawn (default)
    • 'exec': Uses Node.js child_process.exec
    • 'fork': Uses Node.js child_process.fork
    • 'test': Uses a test engine for unit testing
  • engineOptions Record<string, any> (optional) Options to pass to the engine if resolved as adapter.

  • env Record<string, string> (optional) Environment variables to pass to the process. These will be merged with the current process environment.

  • input string | Buffer | string[] | Buffer[] | Readable (optional) Input to pass to the process stdin automatically during the process lifecycle. When provided, all input is made available immediately when the process starts. For manual input control during execution, omit this option and use pushInput() and closeInput() methods instead. Useful when a process requires user input like yes/no questions or configuration input.

  • workingDirectory string (optional) Working directory to run the process in. Defaults to the current working directory.

  • processIndex number (optional) Process index to be used for the process. This is useful when you want to identify the process in an orchestration.

Instance Methods

In addition to BaseRunner methods, SubProcess provides:

kill(signal?: NodeJS.Signals | number) async

Kills the process if it is running. Optionally specify a signal to send to the process.

// Kill with default signal
await subProcess.kill()

// Kill with specific signal
await subProcess.kill('SIGTERM')
await subProcess.kill(9) // SIGKILL

pushInput(input: string | Buffer | string[] | Buffer[])

Sends input chunks to the running process stdin. This method is used when you want to provide input manually during the process execution rather than providing all input upfront through options.

const subProcess = new SubProcess({
  command: 'node -e "process.stdin.on(\'data\', d => console.log(d.toString()))"',
  captureStreams: true
})

await subProcess.run()

// Send input chunks manually
subProcess.pushInput('First chunk\n')
subProcess.pushInput('Second chunk\n')
subProcess.closeInput() // Signal end of input

closeInput()

Closes the stdin stream to signal that no more input will be provided. This should be called after all input chunks have been sent via pushInput().

Instance Properties

In addition to BaseRunner properties, SubProcess provides:

stdout string

String containing the stdout output of the process. Only available when captureStreams option is enabled.

const subProcess = new SubProcess({
  command: 'echo "Hello"',
  captureStreams: true
})
await subProcess.run()
console.log(subProcess.stdout) // "Hello\n"

stderr string

String containing the stderr output of the process. Only available when captureStreams option is enabled.

exitCode number

Exit code of the process. 0 indicates success, non-zero values indicate errors.

signal string | number

Signal that killed the process, if applicable.

processId number

Process ID of the running or completed process.

processIndex number

Process index of the running or completed process.

Events

SubProcess extends BaseRunner events with additional process-specific events:

stdout: Emitted when the process writes to stdout

subProcess.on('stdout', (event) => {
  console.log('Output:', event.payload.data)
})

stderr: Emitted when the process writes to stderr

subProcess.on('stderr', (event) => {
  console.log('Error output:', event.payload.data)
})

Engine System

SubProcess supports different execution engines for various use cases:

Creating a Custom Engine

import { EngineInterface } from '@universal-packages/sub-process'

import MyEngine from './MyEngine'

const subProcess = new SubProcess({ engine: new MyEngine() })

Engine Process Implementation

You need to implement an engine process representation by extending the EngineProcess class to provide a way to control your custom process.

import { EngineProcess } from '@universal-packages/sub-process'

export default class MyEngineProcess extends EngineProcess {
  killObject(signal?: NodeJS.Signals | number): void {
    this.object.sendKillSignal(signal)
  }
}

Engine Implementation

The run method of the engine will be called with the command, args, input, env, and working directory to execute the process and return an EngineProcess instance.

import { EngineInterface } from '@universal-packages/sub-process'

export default class MyEngine implements EngineInterface {
  constructor(options?: any) {
    // Options passed through the adapter sub-system
  }

  async prepare(): Promise<void> {
    // Initialize any connections or resources using options
  }

  async release(): Promise<void> {
    // Release any resources or close any connections
  }

  async run(command: string, args: string[], input: Readable, env: Record<string, string>, workingDirectory?: string): Promise<EngineProcess> {
    const myExecutableObject = myExecutionMethod.exec(command, args, input, env, workingDirectory)
    const engineProcess = new MyEngineProcess(myExecutableObject.processId, myExecutableObject)

    // Set up event handlers for the process
    myExecutableObject.on('data', (data) => engineProcess.emit('stdout', data))
    myExecutableObject.on('error', (data) => engineProcess.emit('stderr', data))
    myExecutableObject.on('exit', (code) => {
      if (code === 0) {
        engineProcess.emit('success')
      } else {
        engineProcess.emit('failure', code)
      }
    })

    return engineProcess
  }
}

EngineInterface

If you are using TypeScript, implement the EngineInterface in your class to ensure the correct implementation.

import { EngineInterface } from '@universal-packages/sub-process'

export default class MyEngine implements EngineInterface {
  prepare?(): Promise<void> {
    // Optional preparation logic
  }

  release?(): Promise<void> {
    // Optional cleanup logic
  }

  run(command: string, args: string[], input: Readable, env: Record<string, string>, workingDirectory?: string): EngineProcess | Promise<EngineProcess> {
    // Required implementation
  }
}

Usage Examples

Basic Command Execution

import { SubProcess } from '@universal-packages/sub-process'

const subProcess = new SubProcess({
  command: 'ls -la',
  captureStreams: true
})

await subProcess.run()
console.log(subProcess.stdout)

Environment Variables

const subProcess = new SubProcess({
  command: 'node -e "console.log(process.env.MY_VAR)"',
  env: { MY_VAR: 'Hello World' },
  captureStreams: true
})

await subProcess.run()
console.log(subProcess.stdout) // "Hello World"

Process Input

SubProcess supports two methods for providing input to processes:

Automatic Input (via options)

When you provide input through the input option, all input is automatically provided to the process during its lifecycle:

const subProcess = new SubProcess({
  command: 'node -e "process.stdin.on(\'data\', d => console.log(d.toString()))"',
  input: 'Hello from input',
  captureStreams: true
})

await subProcess.run()
// All input is provided automatically when the process starts

Manual Input (via methods)

When you need to control input timing or provide input dynamically, omit the input option and use pushInput() and closeInput() methods:

const subProcess = new SubProcess({
  command: "node -e \"let data = ''; process.stdin.on('data', d => { data += d; }); process.stdin.on('end', () => console.log('Received:', data));\"",
  captureStreams: true
})

// Start the process
subProcess.run()

// Send input chunks manually during execution
setTimeout(() => subProcess.pushInput('First chunk\n'), 100)
setTimeout(() => subProcess.pushInput('Second chunk\n'), 200)
setTimeout(() => subProcess.closeInput(), 300) // Signal end of input

Working Directory

const subProcess = new SubProcess({
  command: 'pwd',
  workingDirectory: '/tmp',
  captureStreams: true
})

await subProcess.run()
console.log(subProcess.stdout) // "/tmp"

Event Monitoring

const subProcess = new SubProcess({
  command: 'ping -c 3 google.com',
  captureStreams: true
})

subProcess.on('stdout', (event) => {
  console.log('Real-time output:', event.payload.data)
})

subProcess.on('succeeded', () => {
  console.log('Ping completed successfully')
})

await subProcess.run()

Error Handling

const subProcess = new SubProcess({
  command: 'exit 1',
  captureStreams: true
})

try {
  await subProcess.run()
} catch (error) {
  console.log('Exit code:', subProcess.exitCode) // 1
  console.log('Error message:', error.message)
}

Different Engines

// Spawn engine (default)
const spawnProcess = new SubProcess({
  command: 'echo "Hello"',
  engine: 'spawn'
})

// Exec engine for shell commands
const execProcess = new SubProcess({
  command: 'echo "Current directory: $(pwd)"',
  engine: 'exec'
})

// Fork engine for Node.js scripts
const forkProcess = new SubProcess({
  command: 'node',
  args: ['-e', 'console.log("Fork engine")'],
  engine: 'fork'
})

Typescript

This library is developed in TypeScript and shipped fully typed.

Contributing

The development of this library happens in the open on GitHub, and we are grateful to the community for contributing bugfixes and improvements. Read below to learn how you can take part in improving this library.

License

MIT licensed.