Skip to content

Commit cee316a

Browse files
chore: Add more typedefs to the SubProcess class (#448)
1 parent f420054 commit cee316a

File tree

2 files changed

+58
-29
lines changed

2 files changed

+58
-29
lines changed

README.md

+18-5
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,21 @@ async function tailFileForABit () {
111111

112112
Errors with start/stop are thrown in the calling context.
113113

114+
### Options
115+
116+
The instance constructor accepts same options that Node.js's
117+
[spawn](https://nodejs.org/api/child_process.html#child_processspawncommand-args-options)
118+
API plus some custom options:
119+
120+
* `isBuffer` - If set to `true` then both stdout and stderr chunks are delivered
121+
as buffers rather than strings to the following entities:
122+
- `startDetector` argument
123+
- `output` event
124+
* `encoding` - If provided then arguments of the `startDetector` argument and of the
125+
`output` event will be encoded to strings using this given encoding, unless `isBuffer`
126+
is set to `true`. If no explicit encoding is provided then `utf8` encoding is used by
127+
default.
128+
114129
### Events
115130

116131
You can listen to 8 events:
@@ -151,11 +166,9 @@ proc.on('output', (stdout, stderr) => {
151166
console.log(`stderr: ${stderr}`);
152167
});
153168

154-
// lines-stderr is just the same
155-
proc.on('lines-stdout', lines => {
156-
console.log(lines);
157-
// ['foo', 'bar', 'baz']
158-
// automatically handles rejoining lines across stream chunks
169+
// line-stderr is just the same
170+
proc.on('line-stdout', (line) => {
171+
console.log(line);
159172
});
160173

161174
// stream-line gives you one line at a time, with [STDOUT] or [STDERR]

lib/subprocess.js

+40-24
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,49 @@
11
import { spawn } from 'child_process';
2-
import events from 'events';
3-
const { EventEmitter } = events;
2+
import { EventEmitter } from 'events';
43
import B from 'bluebird';
54
import { quote } from 'shell-quote';
65
import _ from 'lodash';
76
import { formatEnoent } from './helpers';
87
import { createInterface } from 'node:readline';
98

10-
class SubProcess extends EventEmitter {
9+
/**
10+
* @template {SubProcessOptions} TSubProcessOptions
11+
*/
12+
export class SubProcess extends EventEmitter {
13+
/**
14+
* @callback StartDetector
15+
* @param {TSubProcessOptions extends TIsBufferOpts ? Buffer : string} stdout
16+
* @param {TSubProcessOptions extends TIsBufferOpts ? Buffer : string} [stderr]
17+
* @returns {any}
18+
*/
1119

12-
/** @type {import('child_process').ChildProcess?} */
20+
/** @type {import('child_process').ChildProcess | null} */
1321
proc;
14-
1522
/** @type {string[]} */
1623
args;
17-
1824
/**
1925
* @type {string}
2026
*/
2127
cmd;
22-
2328
/**
24-
* @type {any}
29+
* @type {SubProcessOptions}
2530
*/
2631
opts;
27-
2832
/**
2933
* @type {boolean}
3034
*/
3135
expectingExit;
32-
3336
/**
3437
* @type {string}
3538
*/
3639
rep;
3740

3841
/**
3942
* @param {string} cmd
40-
* @param {string[]} [args]
41-
* @param {any} [opts]
43+
* @param {string[]} [args=[]]
44+
* @param {TSubProcessOptions} [opts]
4245
*/
43-
constructor (cmd, args = [], opts = {}) {
46+
constructor (cmd, args = [], opts) {
4447
super();
4548
if (!cmd) throw new Error('Command is required'); // eslint-disable-line curly
4649
if (!_.isString(cmd)) throw new Error('Command must be a string'); // eslint-disable-line curly
@@ -49,7 +52,7 @@ class SubProcess extends EventEmitter {
4952
this.cmd = cmd;
5053
this.args = args;
5154
this.proc = null;
52-
this.opts = opts;
55+
this.opts = opts ?? {};
5356
this.expectingExit = false;
5457

5558
// get a quoted representation of the command for error strings
@@ -128,8 +131,10 @@ class SubProcess extends EventEmitter {
128131

129132
// this function handles output that we collect from the subproc
130133
/**
131-
*
132-
* @param { {stdout: string, stderr: string} } streams
134+
* @param { {
135+
* stdout: TSubProcessOptions extends TIsBufferOpts ? Buffer : string,
136+
* stderr: TSubProcessOptions extends TIsBufferOpts ? Buffer : string
137+
* } } streams
133138
*/
134139
const handleOutput = (streams) => {
135140
const {stdout, stderr} = streams;
@@ -154,7 +159,7 @@ class SubProcess extends EventEmitter {
154159
this.proc?.kill('SIGINT');
155160

156161
if (err.code === 'ENOENT') {
157-
err = await formatEnoent(err, this.cmd, this.opts?.cwd);
162+
err = await formatEnoent(err, this.cmd, this.opts?.cwd?.toString());
158163
}
159164
reject(err);
160165

@@ -182,14 +187,22 @@ class SubProcess extends EventEmitter {
182187

183188
if (this.proc.stdout) {
184189
this.proc.stdout.on('data', (chunk) =>
185-
handleOutput({stdout: isBuffer ? chunk : chunk.toString(encoding), stderr: ''}),
190+
handleOutput({
191+
stdout: isBuffer ? chunk : chunk.toString(encoding),
192+
// @ts-ignore This is OK
193+
stderr: isBuffer ? Buffer.alloc(0) : '',
194+
}),
186195
);
187196
handleStreamLines('stdout', this.proc.stdout);
188197
}
189198

190199
if (this.proc.stderr) {
191200
this.proc.stderr.on('data', (chunk) =>
192-
handleOutput({stdout: '', stderr: isBuffer ? chunk : chunk.toString(encoding)}),
201+
handleOutput({
202+
// @ts-ignore This is OK
203+
stdout: isBuffer ? Buffer.alloc(0) : '',
204+
stderr: isBuffer ? chunk : chunk.toString(encoding)
205+
}),
193206
);
194207
handleStreamLines('stderr', this.proc.stderr);
195208
}
@@ -302,12 +315,15 @@ class SubProcess extends EventEmitter {
302315
}
303316
}
304317

305-
export { SubProcess };
306318
export default SubProcess;
307319

308320
/**
309-
* @callback StartDetector
310-
* @param {string} stdout
311-
* @param {string} [stderr]
312-
* @returns {any}
321+
* @typedef {Object} SubProcessCustomOptions
322+
* @property {boolean} [isBuffer]
323+
* @property {string} [encoding]
324+
*/
325+
326+
/**
327+
* @typedef {SubProcessCustomOptions & import('child_process').SpawnOptionsWithoutStdio} SubProcessOptions
328+
* @typedef {{isBuffer: true}} TIsBufferOpts
313329
*/

0 commit comments

Comments
 (0)