teen_process 3.0.6 → 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/build/lib/exec.d.ts +32 -107
  2. package/build/lib/exec.d.ts.map +1 -1
  3. package/build/lib/exec.js +60 -103
  4. package/build/lib/exec.js.map +1 -1
  5. package/build/lib/helpers.d.ts +1 -12
  6. package/build/lib/helpers.d.ts.map +1 -1
  7. package/build/lib/helpers.js +17 -13
  8. package/build/lib/helpers.js.map +1 -1
  9. package/build/lib/index.d.ts +4 -6
  10. package/build/lib/index.d.ts.map +1 -1
  11. package/build/lib/index.js +7 -43
  12. package/build/lib/index.js.map +1 -1
  13. package/build/lib/subprocess.d.ts +106 -61
  14. package/build/lib/subprocess.d.ts.map +1 -1
  15. package/build/lib/subprocess.js +145 -143
  16. package/build/lib/subprocess.js.map +1 -1
  17. package/build/lib/types.d.ts +79 -0
  18. package/build/lib/types.d.ts.map +1 -0
  19. package/build/lib/types.js +3 -0
  20. package/build/lib/types.js.map +1 -0
  21. package/build/test/circular-buffer-specs.d.ts +2 -0
  22. package/build/test/circular-buffer-specs.d.ts.map +1 -0
  23. package/build/test/circular-buffer-specs.js +40 -0
  24. package/build/test/circular-buffer-specs.js.map +1 -0
  25. package/build/test/exec-specs.d.ts +2 -0
  26. package/build/test/exec-specs.d.ts.map +1 -0
  27. package/build/test/exec-specs.js +167 -0
  28. package/build/test/exec-specs.js.map +1 -0
  29. package/build/test/fixtures/bigbuffer.d.ts +3 -0
  30. package/build/test/fixtures/bigbuffer.d.ts.map +1 -0
  31. package/build/test/fixtures/bigbuffer.js +13 -0
  32. package/build/test/fixtures/bigbuffer.js.map +1 -0
  33. package/build/test/helpers.d.ts +3 -0
  34. package/build/test/helpers.d.ts.map +1 -0
  35. package/build/test/helpers.js +15 -0
  36. package/build/test/helpers.js.map +1 -0
  37. package/build/test/subproc-specs.d.ts +2 -0
  38. package/build/test/subproc-specs.d.ts.map +1 -0
  39. package/build/test/subproc-specs.js +414 -0
  40. package/build/test/subproc-specs.js.map +1 -0
  41. package/build/tsconfig.tsbuildinfo +1 -0
  42. package/lib/circular-buffer.ts +1 -1
  43. package/lib/exec.ts +158 -0
  44. package/lib/helpers.ts +44 -0
  45. package/lib/index.ts +8 -0
  46. package/lib/subprocess.ts +353 -0
  47. package/lib/types.ts +95 -0
  48. package/package.json +5 -5
  49. package/index.js +0 -1
  50. package/lib/exec.js +0 -191
  51. package/lib/helpers.js +0 -38
  52. package/lib/index.js +0 -9
  53. package/lib/subprocess.js +0 -329
@@ -1,79 +1,124 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import type { SubProcessOptions, StartDetector } from './types';
1
3
  /**
2
- * @template {SubProcessOptions} TSubProcessOptions
4
+ * A wrapper around Node's spawn that provides event-driven process management.
5
+ *
6
+ * Extends EventEmitter to provide real-time output streaming and lifecycle events.
7
+ *
8
+ * @template TSubProcessOptions - Options type extending SubProcessOptions
9
+ *
10
+ * @fires SubProcess#output - Emitted when stdout or stderr receives data
11
+ * @fires SubProcess#line-stdout - Emitted for each line of stdout
12
+ * @fires SubProcess#line-stderr - Emitted for each line of stderr
13
+ * @fires SubProcess#lines-stdout - Legacy event emitting stdout lines (deprecated)
14
+ * @fires SubProcess#lines-stderr - Legacy event emitting stderr lines (deprecated)
15
+ * @fires SubProcess#stream-line - Emitted for combined stdout/stderr lines
16
+ * @fires SubProcess#exit - Emitted when process exits
17
+ * @fires SubProcess#stop - Emitted when process is stopped intentionally
18
+ * @fires SubProcess#die - Emitted when process dies unexpectedly with non-zero code
19
+ * @fires SubProcess#end - Emitted when process ends normally with code 0
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * const proc = new SubProcess('tail', ['-f', 'logfile.txt']);
24
+ *
25
+ * proc.on('output', (stdout, stderr) => {
26
+ * console.log('Output:', stdout);
27
+ * });
28
+ *
29
+ * proc.on('line-stdout', (line) => {
30
+ * console.log('Line:', line);
31
+ * });
32
+ *
33
+ * await proc.start();
34
+ * // ... later
35
+ * await proc.stop();
36
+ * ```
3
37
  */
4
- export class SubProcess<TSubProcessOptions extends SubProcessOptions> extends EventEmitter<any> {
5
- /**
6
- * @param {string} cmd
7
- * @param {string[]} [args=[]]
8
- * @param {TSubProcessOptions} [opts]
9
- */
38
+ export declare class SubProcess<TSubProcessOptions extends SubProcessOptions = SubProcessOptions> extends EventEmitter {
39
+ private proc;
40
+ private args;
41
+ private cmd;
42
+ private opts;
43
+ private expectingExit;
44
+ private readonly rep;
10
45
  constructor(cmd: string, args?: string[], opts?: TSubProcessOptions);
11
- /**
12
- * @callback StartDetector
13
- * @param {TSubProcessOptions extends TIsBufferOpts ? Buffer : string} stdout
14
- * @param {TSubProcessOptions extends TIsBufferOpts ? Buffer : string} [stderr]
15
- * @returns {any}
16
- */
17
- /** @type {import('child_process').ChildProcess | null} */
18
- proc: import("child_process").ChildProcess | null;
19
- /** @type {string[]} */
20
- args: string[];
21
- /**
22
- * @type {string}
23
- */
24
- cmd: string;
25
- /**
26
- * @type {SubProcessOptions}
27
- */
28
- opts: SubProcessOptions;
29
- /**
30
- * @type {boolean}
31
- */
32
- expectingExit: boolean;
33
- /**
34
- * @type {string}
35
- */
36
- rep: string;
37
46
  get isRunning(): boolean;
38
47
  /**
48
+ * Starts the subprocess and waits for it to be ready.
49
+ *
50
+ * @param startDetector - Function to detect when process is ready, number for delay in ms,
51
+ * boolean true to detach immediately, or null for default behavior
52
+ * @param timeoutMs - Maximum time to wait for process to start (in ms), or boolean true to detach
53
+ * @param detach - Whether to detach the process (requires 'detached' option)
54
+ *
55
+ * @throws {Error} When process fails to start or times out
56
+ *
57
+ * @example
58
+ * ```typescript
59
+ * // Wait for any output
60
+ * await proc.start();
39
61
  *
40
- * @param {string} streamName
41
- * @param {Iterable<string>|string} lines
62
+ * // Wait 100ms then continue
63
+ * await proc.start(100);
64
+ *
65
+ * // Wait for specific output
66
+ * await proc.start((stdout) => stdout.includes('Server ready'));
67
+ *
68
+ * // With timeout
69
+ * await proc.start(null, 5000);
70
+ * ```
42
71
  */
43
- emitLines(streamName: string, lines: Iterable<string> | string): void;
72
+ start(startDetector?: StartDetector<TSubProcessOptions> | number | boolean | null, timeoutMs?: number | boolean | null, detach?: boolean): Promise<void>;
44
73
  /**
45
- * spawn the subprocess and return control whenever we deem that it has fully
46
- * "started"
74
+ * Stops the running subprocess by sending a signal.
75
+ *
76
+ * @param signal - Signal to send to the process (default: 'SIGTERM')
77
+ * @param timeout - Maximum time to wait for process to exit in ms (default: 10000)
78
+ *
79
+ * @throws {Error} When process is not running or doesn't exit within timeout
47
80
  *
48
- * @param {StartDetector|number?} startDetector
49
- * @param {number?} timeoutMs
50
- * @param {boolean} detach
51
- * @returns {Promise<void>}
81
+ * @example
82
+ * ```typescript
83
+ * // Graceful stop with SIGTERM
84
+ * await proc.stop();
85
+ *
86
+ * // Force kill with SIGKILL
87
+ * await proc.stop('SIGKILL');
88
+ *
89
+ * // Custom timeout
90
+ * await proc.stop('SIGTERM', 5000);
91
+ * ```
52
92
  */
53
- start(startDetector?: ((stdout: TSubProcessOptions extends TIsBufferOpts ? Buffer : string, stderr?: (TSubProcessOptions extends TIsBufferOpts ? Buffer<ArrayBufferLike> : string) | undefined) => any) | (number | null), timeoutMs?: number | null, detach?: boolean): Promise<void>;
93
+ stop(signal?: NodeJS.Signals, timeout?: number): Promise<void>;
54
94
  /**
55
- * @deprecated This method is deprecated and will be removed
95
+ * Waits for the process to exit and validates its exit code.
96
+ *
97
+ * @param allowedExitCodes - Array of acceptable exit codes (default: [0])
98
+ * @returns Promise resolving to the exit code
99
+ *
100
+ * @throws {Error} When process is not running or exits with disallowed code
101
+ *
102
+ * @example
103
+ * ```typescript
104
+ * // Wait for successful exit (code 0)
105
+ * const code = await proc.join();
106
+ *
107
+ * // Allow multiple exit codes
108
+ * const code = await proc.join([0, 1, 2]);
109
+ * ```
56
110
  */
57
- handleLastLines(): void;
111
+ join(allowedExitCodes?: number[]): Promise<number | null>;
58
112
  /**
113
+ * Detaches the process so it continues running independently.
114
+ *
115
+ * The process must have been created with the 'detached' option.
116
+ * Once detached, the process will not be killed when the parent exits.
59
117
  *
60
- * @param {NodeJS.Signals} signal
61
- * @param {number} timeout
62
- * @returns {Promise<void>}
118
+ * @throws {Error} When process was not created with 'detached' option
63
119
  */
64
- stop(signal?: NodeJS.Signals, timeout?: number): Promise<void>;
65
- join(allowedExitCodes?: number[]): Promise<any>;
66
120
  detachProcess(): void;
67
- get pid(): number | null | undefined;
121
+ get pid(): number | null;
122
+ private emitLines;
68
123
  }
69
- export default SubProcess;
70
- export type SubProcessCustomOptions = {
71
- isBuffer?: boolean | undefined;
72
- encoding?: string | undefined;
73
- };
74
- export type SubProcessOptions = SubProcessCustomOptions & import("child_process").SpawnOptionsWithoutStdio;
75
- export type TIsBufferOpts = {
76
- isBuffer: true;
77
- };
78
- import { EventEmitter } from 'events';
79
124
  //# sourceMappingURL=subprocess.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"subprocess.d.ts","sourceRoot":"","sources":["../../lib/subprocess.js"],"names":[],"mappings":"AAQA;;GAEG;AACH,wBAFiC,kBAAkB,SAArC,iBAAkB;IA+B9B;;;;OAIG;IACH,iBAJW,MAAM,SACN,MAAM,EAAE,SACR,kBAAkB,EAgB5B;IA/CD;;;;;OAKG;IAEH,0DAA0D;IAC1D,MADW,OAAO,eAAe,EAAE,YAAY,GAAG,IAAI,CACjD;IACL,uBAAuB;IACvB,MADW,MAAM,EAAE,CACd;IACL;;OAEG;IACH,KAFU,MAAM,CAEZ;IACJ;;MAEE;IACF,MAFU,iBAAiB,CAEtB;IACL;;OAEG;IACH,eAFU,OAAO,CAEH;IACd;;OAEG;IACH,KAFU,MAAM,CAEZ;IAuBJ,yBAGC;IAED;;;;OAIG;IACH,sBAHW,MAAM,SACN,QAAQ,CAAC,MAAM,CAAC,GAAC,MAAM,QAYjC;IAED;;;;;;;;OAQG;IACH,sBALW,UAzEA,kBAAkB,SAAS,aAAa,GAAG,MAAM,GAAG,MAAM,yGAExD,GAAG,KAuES,MAAM,OAAC,CAAA,cACrB,MAAM,OAAC,WACP,OAAO,GACL,OAAO,CAAC,IAAI,CAAC,CAiKzB;IAED;;OAEG;IACH,wBAGC;IAED;;;;;OAKG;IACH,cAJW,MAAM,CAAC,OAAO,YACd,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAgBzB;IAED,gDAcC;IAKD,sBAQC;IAED,qCAEC;CACF;;;;;;gCAWY,uBAAuB,GAAG,OAAO,eAAe,EAAE,wBAAwB;4BAC1E;IAAC,QAAQ,EAAE,IAAI,CAAA;CAAC;6BAtUA,QAAQ"}
1
+ {"version":3,"file":"subprocess.d.ts","sourceRoot":"","sources":["../../lib/subprocess.ts"],"names":[],"mappings":"AAEA,OAAO,EAAC,YAAY,EAAC,MAAM,aAAa,CAAC;AAOzC,OAAO,KAAK,EACR,iBAAiB,EACjB,aAAa,EAGhB,MAAM,SAAS,CAAC;AAEjB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,qBAAa,UAAU,CACrB,kBAAkB,SAAS,iBAAiB,GAAG,iBAAiB,CAChE,SAAQ,YAAY;IACpB,OAAO,CAAC,IAAI,CAAsB;IAClC,OAAO,CAAC,IAAI,CAAW;IACvB,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,IAAI,CAAqB;IACjC,OAAO,CAAC,aAAa,CAAU;IAC/B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;gBAEjB,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE,MAAM,EAAO,EAAE,IAAI,CAAC,EAAE,kBAAkB;IAwBvE,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACG,KAAK,CACT,aAAa,GAAE,aAAa,CAAC,kBAAkB,CAAC,GAAG,MAAM,GAAG,OAAO,GAAG,IAAW,EACjF,SAAS,GAAE,MAAM,GAAG,OAAO,GAAG,IAAW,EACzC,MAAM,GAAE,OAAe,GACtB,OAAO,CAAC,IAAI,CAAC;IAuIhB;;;;;;;;;;;;;;;;;;;OAmBG;IACG,IAAI,CAAC,MAAM,GAAE,MAAM,CAAC,OAAmB,EAAE,OAAO,SAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAc9E;;;;;;;;;;;;;;;;OAgBG;IACG,IAAI,CAAC,gBAAgB,GAAE,MAAM,EAAQ,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAgBpE;;;;;;;OAOG;IACH,aAAa,IAAI,IAAI;IASrB,IAAI,GAAG,IAAI,MAAM,GAAG,IAAI,CAEvB;IAED,OAAO,CAAC,SAAS;CAWlB"}
@@ -4,115 +4,121 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.SubProcess = void 0;
7
- const child_process_1 = require("child_process");
8
- const events_1 = require("events");
7
+ const node_child_process_1 = require("node:child_process");
8
+ const node_events_1 = require("node:events");
9
9
  const bluebird_1 = __importDefault(require("bluebird"));
10
10
  const shell_quote_1 = require("shell-quote");
11
11
  const lodash_1 = __importDefault(require("lodash"));
12
12
  const helpers_1 = require("./helpers");
13
13
  const node_readline_1 = require("node:readline");
14
14
  /**
15
- * @template {SubProcessOptions} TSubProcessOptions
15
+ * A wrapper around Node's spawn that provides event-driven process management.
16
+ *
17
+ * Extends EventEmitter to provide real-time output streaming and lifecycle events.
18
+ *
19
+ * @template TSubProcessOptions - Options type extending SubProcessOptions
20
+ *
21
+ * @fires SubProcess#output - Emitted when stdout or stderr receives data
22
+ * @fires SubProcess#line-stdout - Emitted for each line of stdout
23
+ * @fires SubProcess#line-stderr - Emitted for each line of stderr
24
+ * @fires SubProcess#lines-stdout - Legacy event emitting stdout lines (deprecated)
25
+ * @fires SubProcess#lines-stderr - Legacy event emitting stderr lines (deprecated)
26
+ * @fires SubProcess#stream-line - Emitted for combined stdout/stderr lines
27
+ * @fires SubProcess#exit - Emitted when process exits
28
+ * @fires SubProcess#stop - Emitted when process is stopped intentionally
29
+ * @fires SubProcess#die - Emitted when process dies unexpectedly with non-zero code
30
+ * @fires SubProcess#end - Emitted when process ends normally with code 0
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * const proc = new SubProcess('tail', ['-f', 'logfile.txt']);
35
+ *
36
+ * proc.on('output', (stdout, stderr) => {
37
+ * console.log('Output:', stdout);
38
+ * });
39
+ *
40
+ * proc.on('line-stdout', (line) => {
41
+ * console.log('Line:', line);
42
+ * });
43
+ *
44
+ * await proc.start();
45
+ * // ... later
46
+ * await proc.stop();
47
+ * ```
16
48
  */
17
- class SubProcess extends events_1.EventEmitter {
18
- /**
19
- * @callback StartDetector
20
- * @param {TSubProcessOptions extends TIsBufferOpts ? Buffer : string} stdout
21
- * @param {TSubProcessOptions extends TIsBufferOpts ? Buffer : string} [stderr]
22
- * @returns {any}
23
- */
24
- /** @type {import('child_process').ChildProcess | null} */
49
+ class SubProcess extends node_events_1.EventEmitter {
25
50
  proc;
26
- /** @type {string[]} */
27
51
  args;
28
- /**
29
- * @type {string}
30
- */
31
52
  cmd;
32
- /**
33
- * @type {SubProcessOptions}
34
- */
35
53
  opts;
36
- /**
37
- * @type {boolean}
38
- */
39
54
  expectingExit;
40
- /**
41
- * @type {string}
42
- */
43
55
  rep;
44
- /**
45
- * @param {string} cmd
46
- * @param {string[]} [args=[]]
47
- * @param {TSubProcessOptions} [opts]
48
- */
49
56
  constructor(cmd, args = [], opts) {
50
57
  super();
51
- if (!cmd)
52
- throw new Error('Command is required'); // eslint-disable-line curly
53
- if (!lodash_1.default.isString(cmd))
54
- throw new Error('Command must be a string'); // eslint-disable-line curly
55
- if (!lodash_1.default.isArray(args))
56
- throw new Error('Args must be an array'); // eslint-disable-line curly
58
+ if (!cmd) {
59
+ throw new Error('Command is required');
60
+ }
61
+ if (!lodash_1.default.isString(cmd)) {
62
+ throw new Error('Command must be a string');
63
+ }
64
+ if (!lodash_1.default.isArray(args)) {
65
+ throw new Error('Args must be an array');
66
+ }
57
67
  this.cmd = cmd;
58
68
  this.args = args;
59
69
  this.proc = null;
60
70
  this.opts = opts ?? {};
61
71
  this.expectingExit = false;
62
- // get a quoted representation of the command for error strings
63
72
  this.rep = (0, shell_quote_1.quote)([cmd, ...args]);
64
73
  }
65
74
  get isRunning() {
66
- // presence of `proc` means we have connected and started
67
75
  return !!this.proc;
68
76
  }
69
77
  /**
78
+ * Starts the subprocess and waits for it to be ready.
70
79
  *
71
- * @param {string} streamName
72
- * @param {Iterable<string>|string} lines
73
- */
74
- emitLines(streamName, lines) {
75
- const doEmit = (/** @type {string} */ line) => this.emit('stream-line', `[${streamName.toUpperCase()}] ${line}`);
76
- if (lodash_1.default.isString(lines)) {
77
- doEmit(lines);
78
- }
79
- else {
80
- for (const line of lines) {
81
- doEmit(line);
82
- }
83
- }
84
- }
85
- /**
86
- * spawn the subprocess and return control whenever we deem that it has fully
87
- * "started"
80
+ * @param startDetector - Function to detect when process is ready, number for delay in ms,
81
+ * boolean true to detach immediately, or null for default behavior
82
+ * @param timeoutMs - Maximum time to wait for process to start (in ms), or boolean true to detach
83
+ * @param detach - Whether to detach the process (requires 'detached' option)
84
+ *
85
+ * @throws {Error} When process fails to start or times out
86
+ *
87
+ * @example
88
+ * ```typescript
89
+ * // Wait for any output
90
+ * await proc.start();
88
91
  *
89
- * @param {StartDetector|number?} startDetector
90
- * @param {number?} timeoutMs
91
- * @param {boolean} detach
92
- * @returns {Promise<void>}
92
+ * // Wait 100ms then continue
93
+ * await proc.start(100);
94
+ *
95
+ * // Wait for specific output
96
+ * await proc.start((stdout) => stdout.includes('Server ready'));
97
+ *
98
+ * // With timeout
99
+ * await proc.start(null, 5000);
100
+ * ```
93
101
  */
94
102
  async start(startDetector = null, timeoutMs = null, detach = false) {
95
103
  let startDelay = 10;
96
- const genericStartDetector = /** @type {StartDetector} */ (function genericStartDetector(stdout, stderr) {
97
- return stdout || stderr;
98
- });
99
- // the default start detector simply returns true when we get any output
104
+ const genericStartDetector = (stdout, stderr) => stdout || stderr;
105
+ let detector = null;
100
106
  if (startDetector === null) {
101
- startDetector = genericStartDetector;
107
+ detector = genericStartDetector;
102
108
  }
103
- // if the user passes a number, then we simply delay a certain amount of
104
- // time before returning control, rather than waiting for a condition
105
109
  if (lodash_1.default.isNumber(startDetector)) {
106
110
  startDelay = startDetector;
107
- startDetector = null;
111
+ detector = null;
112
+ }
113
+ else if (lodash_1.default.isFunction(startDetector)) {
114
+ detector = startDetector;
108
115
  }
109
- // if the user passes in a boolean as one of the arguments, use it for `detach`
110
116
  if (lodash_1.default.isBoolean(startDetector) && startDetector) {
111
117
  if (!this.opts.detached) {
112
118
  throw new Error(`Unable to detach process that is not started with 'detached' option`);
113
119
  }
114
120
  detach = true;
115
- startDetector = genericStartDetector;
121
+ detector = genericStartDetector;
116
122
  }
117
123
  else if (lodash_1.default.isBoolean(timeoutMs) && timeoutMs) {
118
124
  if (!this.opts.detached) {
@@ -121,49 +127,35 @@ class SubProcess extends events_1.EventEmitter {
121
127
  detach = true;
122
128
  timeoutMs = null;
123
129
  }
124
- // return a promise so we can wrap the async behavior
125
130
  return await new bluebird_1.default((resolve, reject) => {
126
- // actually spawn the subproc
127
- this.proc = (0, child_process_1.spawn)(this.cmd, this.args, this.opts);
128
- // this function handles output that we collect from the subproc
129
- /**
130
- * @param { {
131
- * stdout: TSubProcessOptions extends TIsBufferOpts ? Buffer : string,
132
- * stderr: TSubProcessOptions extends TIsBufferOpts ? Buffer : string
133
- * } } streams
134
- */
131
+ this.proc = (0, node_child_process_1.spawn)(this.cmd, this.args, this.opts);
135
132
  const handleOutput = (streams) => {
136
133
  const { stdout, stderr } = streams;
137
- // if we have a startDetector, run it on the output so we can resolve/
138
- // reject and move on from start
139
134
  try {
140
- if (lodash_1.default.isFunction(startDetector) && startDetector(stdout, stderr)) {
141
- startDetector = null;
135
+ if (detector && detector(stdout, stderr)) {
136
+ detector = null;
142
137
  resolve();
143
138
  }
144
139
  }
145
140
  catch (e) {
146
141
  reject(e);
147
142
  }
148
- // emit the actual output for whomever's listening
149
143
  this.emit('output', stdout, stderr);
150
144
  };
151
- // if we get an error spawning the proc, reject and clean up the proc
152
- this.proc.on('error', /** @param {NodeJS.ErrnoException} err */ async (err) => {
145
+ this.proc.on('error', async (err) => {
153
146
  this.proc?.removeAllListeners('exit');
154
147
  this.proc?.kill('SIGINT');
155
- if (err.code === 'ENOENT') {
156
- err = await (0, helpers_1.formatEnoent)(err, this.cmd, this.opts?.cwd?.toString());
148
+ let error = err;
149
+ if (error.code === 'ENOENT') {
150
+ error = await (0, helpers_1.formatEnoent)(error, this.cmd, this.opts?.cwd?.toString());
157
151
  }
158
- reject(err);
152
+ reject(error);
159
153
  this.proc?.unref();
160
154
  this.proc = null;
161
155
  });
162
- const handleStreamLines = (/** @type {string} */ streamName, /** @type {import('stream').Readable} */ input) => {
156
+ const handleStreamLines = (streamName, input) => {
163
157
  const rl = (0, node_readline_1.createInterface)({ input });
164
158
  rl.on('line', (line) => {
165
- // This event is a legacy one
166
- // It always produces a single-item array
167
159
  if (this.listenerCount(`lines-${streamName}`)) {
168
160
  this.emit(`lines-${streamName}`, [line]);
169
161
  }
@@ -177,51 +169,34 @@ class SubProcess extends events_1.EventEmitter {
177
169
  const encoding = this.opts.encoding || 'utf8';
178
170
  if (this.proc.stdout) {
179
171
  this.proc.stdout.on('data', (chunk) => handleOutput({
180
- stdout: isBuffer ? chunk : chunk.toString(encoding),
181
- // @ts-ignore This is OK
182
- stderr: isBuffer ? Buffer.alloc(0) : '',
172
+ stdout: (isBuffer ? chunk : chunk.toString(encoding)),
173
+ stderr: (isBuffer ? Buffer.alloc(0) : ''),
183
174
  }));
184
175
  handleStreamLines('stdout', this.proc.stdout);
185
176
  }
186
177
  if (this.proc.stderr) {
187
178
  this.proc.stderr.on('data', (chunk) => handleOutput({
188
- // @ts-ignore This is OK
189
- stdout: isBuffer ? Buffer.alloc(0) : '',
190
- stderr: isBuffer ? chunk : chunk.toString(encoding)
179
+ stdout: (isBuffer ? Buffer.alloc(0) : ''),
180
+ stderr: (isBuffer ? chunk : chunk.toString(encoding)),
191
181
  }));
192
182
  handleStreamLines('stderr', this.proc.stderr);
193
183
  }
194
- // when the proc exits, we might still have a buffer of lines we were
195
- // waiting on more chunks to complete. Go ahead and emit those, then
196
- // re-emit the exit so a listener can handle the possibly-unexpected exit
197
184
  this.proc.on('exit', (code, signal) => {
198
185
  this.emit('exit', code, signal);
199
- // in addition to the bare exit event, also emit one of three other
200
- // events that contain more helpful information:
201
- // 'stop': we stopped this
202
- // 'die': the process ended out of our control with a non-zero exit
203
- // 'end': the process ended out of our control with a zero exit
204
186
  let event = this.expectingExit ? 'stop' : 'die';
205
187
  if (!this.expectingExit && code === 0) {
206
188
  event = 'end';
207
189
  }
208
190
  this.emit(event, code, signal);
209
- // finally clean up the proc and make sure to reset our exit
210
- // expectations
211
191
  this.proc = null;
212
192
  this.expectingExit = false;
213
193
  });
214
- // if the user hasn't given us a startDetector, instead just resolve
215
- // when startDelay ms have passed
216
- if (!startDetector) {
217
- setTimeout(() => { resolve(); }, startDelay);
194
+ if (!detector) {
195
+ setTimeout(() => resolve(), startDelay);
218
196
  }
219
- // if the user has given us a timeout, start the clock for rejecting
220
- // the promise if we take too long to start
221
197
  if (lodash_1.default.isNumber(timeoutMs)) {
222
198
  setTimeout(() => {
223
- reject(new Error(`The process did not start within ${timeoutMs}ms ` +
224
- `(cmd: '${this.rep}')`));
199
+ reject(new Error(`The process did not start within ${timeoutMs}ms (cmd: '${this.rep}')`));
225
200
  }, timeoutMs);
226
201
  }
227
202
  }).finally(() => {
@@ -231,40 +206,62 @@ class SubProcess extends events_1.EventEmitter {
231
206
  });
232
207
  }
233
208
  /**
234
- * @deprecated This method is deprecated and will be removed
235
- */
236
- handleLastLines() {
237
- // TODO: THis is a noop left for backward compatibility.
238
- // TODO: Remove it after the major version bump
239
- }
240
- /**
209
+ * Stops the running subprocess by sending a signal.
210
+ *
211
+ * @param signal - Signal to send to the process (default: 'SIGTERM')
212
+ * @param timeout - Maximum time to wait for process to exit in ms (default: 10000)
213
+ *
214
+ * @throws {Error} When process is not running or doesn't exit within timeout
215
+ *
216
+ * @example
217
+ * ```typescript
218
+ * // Graceful stop with SIGTERM
219
+ * await proc.stop();
220
+ *
221
+ * // Force kill with SIGKILL
222
+ * await proc.stop('SIGKILL');
241
223
  *
242
- * @param {NodeJS.Signals} signal
243
- * @param {number} timeout
244
- * @returns {Promise<void>}
224
+ * // Custom timeout
225
+ * await proc.stop('SIGTERM', 5000);
226
+ * ```
245
227
  */
246
228
  async stop(signal = 'SIGTERM', timeout = 10000) {
247
229
  if (!this.isRunning) {
248
230
  throw new Error(`Can't stop process; it's not currently running (cmd: '${this.rep}')`);
249
231
  }
250
232
  return await new bluebird_1.default((resolve, reject) => {
251
- this.proc?.on('close', resolve);
233
+ this.proc?.on('close', () => resolve());
252
234
  this.expectingExit = true;
253
235
  this.proc?.kill(signal);
254
- // this timeout needs unref() or node will wait for the timeout to fire before
255
- // exiting the process.
256
236
  setTimeout(() => {
257
237
  reject(new Error(`Process didn't end after ${timeout}ms (cmd: '${this.rep}')`));
258
238
  }, timeout).unref();
259
239
  });
260
240
  }
241
+ /**
242
+ * Waits for the process to exit and validates its exit code.
243
+ *
244
+ * @param allowedExitCodes - Array of acceptable exit codes (default: [0])
245
+ * @returns Promise resolving to the exit code
246
+ *
247
+ * @throws {Error} When process is not running or exits with disallowed code
248
+ *
249
+ * @example
250
+ * ```typescript
251
+ * // Wait for successful exit (code 0)
252
+ * const code = await proc.join();
253
+ *
254
+ * // Allow multiple exit codes
255
+ * const code = await proc.join([0, 1, 2]);
256
+ * ```
257
+ */
261
258
  async join(allowedExitCodes = [0]) {
262
259
  if (!this.isRunning) {
263
260
  throw new Error(`Cannot join process; it is not currently running (cmd: '${this.rep}')`);
264
261
  }
265
262
  return await new bluebird_1.default((resolve, reject) => {
266
263
  this.proc?.on('exit', (code) => {
267
- if (code !== null && allowedExitCodes.indexOf(code) === -1) {
264
+ if (code !== null && !allowedExitCodes.includes(code)) {
268
265
  reject(new Error(`Process ended with exitcode ${code} (cmd: '${this.rep}')`));
269
266
  }
270
267
  else {
@@ -273,12 +270,16 @@ class SubProcess extends events_1.EventEmitter {
273
270
  });
274
271
  });
275
272
  }
276
- /*
277
- * This will only work if the process is created with the `detached` option
273
+ /**
274
+ * Detaches the process so it continues running independently.
275
+ *
276
+ * The process must have been created with the 'detached' option.
277
+ * Once detached, the process will not be killed when the parent exits.
278
+ *
279
+ * @throws {Error} When process was not created with 'detached' option
278
280
  */
279
281
  detachProcess() {
280
282
  if (!this.opts.detached) {
281
- // this means that there is a misconfiguration in the calling code
282
283
  throw new Error(`Unable to detach process that is not started with 'detached' option`);
283
284
  }
284
285
  if (this.proc) {
@@ -286,18 +287,19 @@ class SubProcess extends events_1.EventEmitter {
286
287
  }
287
288
  }
288
289
  get pid() {
289
- return this.proc ? this.proc.pid : null;
290
+ return this.proc?.pid ?? null;
291
+ }
292
+ emitLines(streamName, lines) {
293
+ const doEmit = (line) => this.emit('stream-line', `[${streamName.toUpperCase()}] ${line}`);
294
+ if (lodash_1.default.isString(lines)) {
295
+ doEmit(lines);
296
+ }
297
+ else {
298
+ for (const line of lines) {
299
+ doEmit(line);
300
+ }
301
+ }
290
302
  }
291
303
  }
292
304
  exports.SubProcess = SubProcess;
293
- exports.default = SubProcess;
294
- /**
295
- * @typedef {Object} SubProcessCustomOptions
296
- * @property {boolean} [isBuffer]
297
- * @property {string} [encoding]
298
- */
299
- /**
300
- * @typedef {SubProcessCustomOptions & import('child_process').SpawnOptionsWithoutStdio} SubProcessOptions
301
- * @typedef {{isBuffer: true}} TIsBufferOpts
302
- */
303
305
  //# sourceMappingURL=subprocess.js.map