teen_process 3.0.6 → 4.0.0
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.
- package/build/lib/exec.d.ts +32 -107
- package/build/lib/exec.d.ts.map +1 -1
- package/build/lib/exec.js +60 -103
- package/build/lib/exec.js.map +1 -1
- package/build/lib/helpers.d.ts +1 -12
- package/build/lib/helpers.d.ts.map +1 -1
- package/build/lib/helpers.js +17 -13
- package/build/lib/helpers.js.map +1 -1
- package/build/lib/index.d.ts +4 -6
- package/build/lib/index.d.ts.map +1 -1
- package/build/lib/index.js +7 -43
- package/build/lib/index.js.map +1 -1
- package/build/lib/subprocess.d.ts +106 -61
- package/build/lib/subprocess.d.ts.map +1 -1
- package/build/lib/subprocess.js +145 -143
- package/build/lib/subprocess.js.map +1 -1
- package/build/lib/types.d.ts +79 -0
- package/build/lib/types.d.ts.map +1 -0
- package/build/lib/types.js +3 -0
- package/build/lib/types.js.map +1 -0
- package/build/test/circular-buffer-specs.d.ts +2 -0
- package/build/test/circular-buffer-specs.d.ts.map +1 -0
- package/build/test/circular-buffer-specs.js +40 -0
- package/build/test/circular-buffer-specs.js.map +1 -0
- package/build/test/exec-specs.d.ts +2 -0
- package/build/test/exec-specs.d.ts.map +1 -0
- package/build/test/exec-specs.js +167 -0
- package/build/test/exec-specs.js.map +1 -0
- package/build/test/fixtures/bigbuffer.d.ts +3 -0
- package/build/test/fixtures/bigbuffer.d.ts.map +1 -0
- package/build/test/fixtures/bigbuffer.js +13 -0
- package/build/test/fixtures/bigbuffer.js.map +1 -0
- package/build/test/helpers.d.ts +3 -0
- package/build/test/helpers.d.ts.map +1 -0
- package/build/test/helpers.js +15 -0
- package/build/test/helpers.js.map +1 -0
- package/build/test/subproc-specs.d.ts +2 -0
- package/build/test/subproc-specs.d.ts.map +1 -0
- package/build/test/subproc-specs.js +414 -0
- package/build/test/subproc-specs.js.map +1 -0
- package/build/tsconfig.tsbuildinfo +1 -0
- package/lib/circular-buffer.ts +1 -1
- package/lib/exec.ts +158 -0
- package/lib/helpers.ts +44 -0
- package/lib/index.ts +8 -0
- package/lib/subprocess.ts +353 -0
- package/lib/types.ts +95 -0
- package/package.json +5 -5
- package/index.js +0 -1
- package/lib/exec.js +0 -191
- package/lib/helpers.js +0 -38
- package/lib/index.js +0 -9
- 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
|
-
*
|
|
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
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
*
|
|
41
|
-
*
|
|
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
|
-
|
|
72
|
+
start(startDetector?: StartDetector<TSubProcessOptions> | number | boolean | null, timeoutMs?: number | boolean | null, detach?: boolean): Promise<void>;
|
|
44
73
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
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
|
-
* @
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
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
|
-
|
|
93
|
+
stop(signal?: NodeJS.Signals, timeout?: number): Promise<void>;
|
|
54
94
|
/**
|
|
55
|
-
*
|
|
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
|
-
|
|
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
|
-
* @
|
|
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
|
|
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.
|
|
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"}
|
package/build/lib/subprocess.js
CHANGED
|
@@ -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
|
|
8
|
-
const
|
|
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
|
-
*
|
|
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
|
|
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');
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
|
72
|
-
*
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
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 =
|
|
97
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
141
|
-
|
|
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
|
-
|
|
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
|
-
|
|
156
|
-
|
|
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(
|
|
152
|
+
reject(error);
|
|
159
153
|
this.proc?.unref();
|
|
160
154
|
this.proc = null;
|
|
161
155
|
});
|
|
162
|
-
const handleStreamLines = (
|
|
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
|
-
|
|
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
|
-
|
|
189
|
-
|
|
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
|
-
|
|
215
|
-
|
|
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
|
-
*
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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
|
-
*
|
|
243
|
-
*
|
|
244
|
-
*
|
|
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.
|
|
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
|
-
*
|
|
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
|
|
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
|