Sub Process
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 thestdout
andstderr
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. 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.
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
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.
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
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()
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.