@j-o-r/sh 1.1.28 → 1.1.29
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/README.md +119 -154
- package/TODO.md +18 -0
- package/lib/AsyncTracker.js +126 -107
- package/lib/SH.js +158 -154
- package/lib/SHDispatch.js +105 -91
- package/lib/SHExecute.js +83 -41
- package/lib/Test.js +153 -123
- package/package.json +1 -1
- package/types/AsyncTracker.d.ts +65 -31
- package/types/SH.d.ts +157 -164
- package/types/SHDispatch.d.ts +55 -78
- package/types/SHExecute.d.ts +44 -23
- package/types/Test.d.ts +66 -30
package/lib/SHDispatch.js
CHANGED
|
@@ -1,56 +1,62 @@
|
|
|
1
|
-
import
|
|
1
|
+
import SHExecute from './SHExecute.js';
|
|
2
|
+
|
|
2
3
|
/**
|
|
3
4
|
* @typedef {Object} SpawnSyncResponse
|
|
4
|
-
* @property {number|null} status - Exit code
|
|
5
|
-
* @property {string|null} signal -
|
|
6
|
-
* @property {(string|Buffer|null)[]} output - [stdin, stdout, stderr]
|
|
7
|
-
* @property {number} pid -
|
|
8
|
-
* @property {string|Buffer|null} stdout -
|
|
9
|
-
* @property {string|Buffer|null} stderr -
|
|
5
|
+
* @property {number|null} status - Exit code (null if signal).
|
|
6
|
+
* @property {string|null} signal - Terminating signal.
|
|
7
|
+
* @property {(string|Buffer|null)[]} output - [stdin, stdout, stderr].
|
|
8
|
+
* @property {number} pid - Process ID.
|
|
9
|
+
* @property {string|Buffer|null} stdout - Captured stdout.
|
|
10
|
+
* @property {string|Buffer|null} stderr - Captured stderr.
|
|
10
11
|
*/
|
|
12
|
+
|
|
11
13
|
/**
|
|
12
|
-
* @typedef {
|
|
13
|
-
* @
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* @property {number} [gid] - Sets the group identity of the process.
|
|
19
|
-
* @property {StdioOptions|StdioOption} [stdio='pipe'] - Child's stdio configuration.
|
|
20
|
-
* @property {boolean|string} [shell='bash'] - If string or true, runs the command via that shell ('bash' if true).
|
|
21
|
-
* @property {number} [timeout=0] - Milliseconds before sending SIGTERM (0 = no timeout).
|
|
22
|
-
* @property {string|Buffer|Uint8Array} [input] - Optional stdin payload for spawnSync. Use .run(payload) for async.
|
|
14
|
+
* @typedef {('pipe' | 'ignore' | 'inherit' | number)} StdioOption
|
|
15
|
+
* @description Stdio config for a stream:
|
|
16
|
+
* - `'pipe'`: Pipe to parent.
|
|
17
|
+
* - `'ignore'`: Discard.
|
|
18
|
+
* - `'inherit'`: From/to parent.
|
|
19
|
+
* - `number`: FD.
|
|
23
20
|
*/
|
|
21
|
+
|
|
24
22
|
/**
|
|
25
|
-
* @typedef {
|
|
26
|
-
* @description
|
|
27
|
-
*
|
|
28
|
-
* - 'pipe' creates a pipe between the child process and the parent process.
|
|
29
|
-
* The parent end of the pipe is exposed as a property on the `ChildProcess` object.
|
|
30
|
-
* - 'ignore' indicates that the child process's corresponding stdio file descriptor will be ignored.
|
|
31
|
-
* - 'inherit' passes the corresponding stdio stream to/from the child process.
|
|
32
|
-
* - Stream object to be used for the stdio stream.
|
|
33
|
-
* - Positive integer representing a file descriptor to be used for the stdio stream.
|
|
23
|
+
* @typedef {Array<StdioOption> | StdioOption} StdioOptions
|
|
24
|
+
* @description Stdio array [stdin, stdout, stderr] or single value (applied to all).
|
|
25
|
+
* @example ['inherit', 'pipe', 'pipe'] // Default: inherit stdin, pipe out/err
|
|
34
26
|
*/
|
|
27
|
+
|
|
35
28
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
29
|
+
* Core options for SH/SHDispatch.
|
|
30
|
+
*
|
|
31
|
+
* Defaults (merged from global SH):
|
|
32
|
+
* - `cwd`: `process.cwd()`
|
|
33
|
+
* - `env`: `process.env`
|
|
34
|
+
* - `shell`: `'bash'` (string/true → shell; false → no-shell `/usr/bin/env -S`)
|
|
35
|
+
* - `stdio`: `['inherit', 'pipe', 'pipe']`
|
|
36
|
+
* - `timeout`: `0` (no timeout; rolling on data)
|
|
37
|
+
* - `maxBuffer`: `512000` (500 kb (per stream in SHExecute))
|
|
38
|
+
*
|
|
39
|
+
* Prefix (`.options(undefined, prefix)`) only for shell mode.
|
|
40
|
+
*
|
|
41
|
+
* @example { timeout: '5s', stdio: 'inherit', shell: false, maxBuffer: 10 * 1024}
|
|
47
42
|
*/
|
|
43
|
+
const SHOptions = {
|
|
44
|
+
cwd: process.cwd(),
|
|
45
|
+
env: process.env,
|
|
46
|
+
shell: 'bash',
|
|
47
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
48
|
+
timeout: 0,
|
|
49
|
+
};
|
|
50
|
+
|
|
48
51
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
|
|
52
|
+
* Merges user options into predefined defaults (non-destructive).
|
|
53
|
+
*
|
|
54
|
+
* Only copies defined keys.
|
|
55
|
+
*
|
|
56
|
+
* @param {typeof SHOptions} predefined - Base options.
|
|
57
|
+
* @param {Partial<typeof SHOptions>} options - Overrides.
|
|
58
|
+
* @returns {typeof SHOptions} Merged options.
|
|
59
|
+
*/
|
|
54
60
|
const mergeOptions = (predefined, options) => {
|
|
55
61
|
const mergedObj = { ...predefined };
|
|
56
62
|
|
|
@@ -61,54 +67,50 @@ const mergeOptions = (predefined, options) => {
|
|
|
61
67
|
}
|
|
62
68
|
|
|
63
69
|
return mergedObj;
|
|
64
|
-
}
|
|
70
|
+
};
|
|
65
71
|
|
|
66
72
|
/**
|
|
67
|
-
*
|
|
73
|
+
* High-level command dispatcher.
|
|
74
|
+
*
|
|
75
|
+
* Created by {@link SH`cmd`}; chain `.options()` then `.run()`.
|
|
68
76
|
*
|
|
69
|
-
*
|
|
70
|
-
* - cwd: process.cwd()
|
|
71
|
-
* - env: process.env
|
|
72
|
-
* - shell: 'bash' (string). If a string, that shell is used. If true, 'bash' is used. If false/undefined, no shell is used.
|
|
73
|
-
* - stdio: ['inherit', 'pipe', 'pipe'] — inherit stdin, capture stdout/stderr
|
|
74
|
-
* - timeout: 0 — no timeout
|
|
75
|
-
* - maxBuffer?: number — optional (bytes per stream). Passed through to SHExecute.
|
|
77
|
+
* Delegates to {@link SHExecute} for exec/timeout/buffer/kill.
|
|
76
78
|
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
79
|
+
* @example
|
|
80
|
+
* const dispatch = SH`ls -la`.options({ timeout: '2s' });
|
|
81
|
+
* const out = await dispatch.run();
|
|
80
82
|
*/
|
|
81
|
-
|
|
82
|
-
|
|
83
83
|
class SHDispatch {
|
|
84
|
-
// #prefix = 'set -euo pipefail;/usr/bin/env'
|
|
85
|
-
// #prefix = 'set -euo pipefail'
|
|
86
84
|
#prefix = '';
|
|
87
85
|
#cmd = '';
|
|
88
|
-
#options =
|
|
89
|
-
/**
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
#proc;
|
|
86
|
+
#options = SHOptions;
|
|
87
|
+
/** @type {SHExecute | null} */
|
|
88
|
+
#proc = null;
|
|
89
|
+
|
|
93
90
|
/**
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
91
|
+
* @param {string} cmd - Command string.
|
|
92
|
+
* @param {Partial<typeof SHOptions>} [options] - Initial options.
|
|
93
|
+
* @param {string} [prefix] - Shell prefix (e.g., 'set -euo pipefail').
|
|
94
|
+
* @throws {Error} Invalid/empty cmd.
|
|
95
|
+
*/
|
|
96
|
+
constructor(cmd, options = {}, prefix) {
|
|
99
97
|
if (typeof cmd !== 'string' || cmd === '') {
|
|
100
98
|
throw new Error('Undefined command');
|
|
101
99
|
}
|
|
102
100
|
this.#cmd = cmd;
|
|
103
101
|
this.options(options, prefix);
|
|
104
102
|
}
|
|
103
|
+
|
|
105
104
|
/**
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
105
|
+
* Updates options/prefix; resets to defaults + user overrides (non-cumulative).
|
|
106
|
+
*
|
|
107
|
+
* Strings for `stdio` → array fill.
|
|
108
|
+
*
|
|
109
|
+
* @param {Partial<typeof SHOptions>} [options] - New options.
|
|
110
|
+
* @param {string} [prefix] - New prefix.
|
|
111
|
+
* @returns {SHDispatch} Self for chaining.
|
|
112
|
+
*/
|
|
110
113
|
options(options, prefix) {
|
|
111
|
-
// this.#options = defaultOptions
|
|
112
114
|
if (typeof prefix === 'string') {
|
|
113
115
|
this.#prefix = prefix;
|
|
114
116
|
}
|
|
@@ -117,38 +119,50 @@ class SHDispatch {
|
|
|
117
119
|
}
|
|
118
120
|
if (options.stdio && typeof options.stdio === 'string') {
|
|
119
121
|
// convert stdio to array
|
|
120
|
-
// This sets the default io values
|
|
121
|
-
// but can be overwritten when having a payload
|
|
122
122
|
const io = options.stdio;
|
|
123
123
|
options.stdio = Array(3).fill(io);
|
|
124
124
|
}
|
|
125
|
-
this.#options = mergeOptions(
|
|
125
|
+
this.#options = mergeOptions(SHOptions, options);
|
|
126
126
|
return this;
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
/**
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
129
|
+
/**
|
|
130
|
+
* Async run: Captures stdout; rejects on error/timeout.
|
|
131
|
+
*
|
|
132
|
+
* @param {string} [payload] - Stdin payload.
|
|
133
|
+
* @returns {Promise<string>} Stdout.
|
|
134
|
+
*/
|
|
133
135
|
run(payload) {
|
|
134
|
-
this.#proc = new
|
|
136
|
+
this.#proc = new SHExecute(this.#cmd, this.#prefix, this.#options);
|
|
135
137
|
return this.#proc.run(payload);
|
|
136
138
|
}
|
|
137
139
|
|
|
138
|
-
/**
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
140
|
+
/**
|
|
141
|
+
* Sync run: Full Node SpawnSyncReturns.
|
|
142
|
+
*
|
|
143
|
+
* Good for TTY takeovers (e.g., vim).
|
|
144
|
+
*
|
|
145
|
+
* @param {string} [payload] - Stdin payload.
|
|
146
|
+
* @returns {import('child_process').SpawnSyncReturns<Buffer>}
|
|
147
|
+
*/
|
|
143
148
|
runSync(payload) {
|
|
144
|
-
return new
|
|
149
|
+
return new SHExecute(this.#cmd, this.#prefix, this.#options).runSync(payload);
|
|
145
150
|
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Kills running process + children.
|
|
154
|
+
*
|
|
155
|
+
* @param {number | string} [signal='SIGTERM'] - Signal.
|
|
156
|
+
* @returns {Promise<number[]>} Killed PIDs.
|
|
157
|
+
*/
|
|
146
158
|
async kill(signal = 'SIGTERM') {
|
|
147
159
|
let res = [];
|
|
148
|
-
|
|
149
|
-
|
|
160
|
+
if (this.#proc) {
|
|
161
|
+
res = await this.#proc.kill(signal);
|
|
162
|
+
this.#proc = null;
|
|
163
|
+
}
|
|
150
164
|
return res;
|
|
151
165
|
}
|
|
152
166
|
}
|
|
153
167
|
|
|
154
|
-
export default SHDispatch
|
|
168
|
+
export default SHDispatch;
|
package/lib/SHExecute.js
CHANGED
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
import { spawnSync, spawn } from 'node:child_process';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Validates if a number is a finite positive integer (including 0).
|
|
5
|
+
*
|
|
6
|
+
* @param {unknown} n - Value to check.
|
|
7
|
+
* @returns {boolean} True if finite non-negative integer.
|
|
8
|
+
*/
|
|
4
9
|
const isFinitePosInt = (n) => Number.isFinite(n) && n >= 0;
|
|
5
|
-
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Parses a human-readable duration into milliseconds.
|
|
13
|
+
*
|
|
14
|
+
* Supports: numbers (ms), '250ms', '2s'. null/undefined → 0; invalid → throws.
|
|
15
|
+
*
|
|
16
|
+
* @param {number|string|null|undefined} d - Duration input.
|
|
17
|
+
* @returns {number} ms.
|
|
18
|
+
* @throws {Error} Invalid format/type.
|
|
19
|
+
*/
|
|
6
20
|
const parseDuration = (d) => {
|
|
7
21
|
if (typeof d === 'number') return isFinitePosInt(d) ? d : (() => { throw new Error('Invalid duration'); })();
|
|
8
22
|
if (typeof d === 'string') {
|
|
@@ -15,7 +29,12 @@ const parseDuration = (d) => {
|
|
|
15
29
|
throw new Error('Invalid duration type');
|
|
16
30
|
};
|
|
17
31
|
|
|
18
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Retrieves child PIDs of a given parent PID using pgrep -P.
|
|
34
|
+
*
|
|
35
|
+
* @param {number} pid - Parent PID.
|
|
36
|
+
* @returns {Promise<number[]>} Array of child PIDs.
|
|
37
|
+
*/
|
|
19
38
|
const childrenOf = (pid) => new Promise((resolve, reject) => {
|
|
20
39
|
const p = spawn('pgrep', ['-P', String(pid)], { stdio: ['ignore', 'pipe', 'ignore'] });
|
|
21
40
|
const out = [];
|
|
@@ -32,21 +51,32 @@ const childrenOf = (pid) => new Promise((resolve, reject) => {
|
|
|
32
51
|
});
|
|
33
52
|
|
|
34
53
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
54
|
+
* @typedef {import('../SH.js').SHOptions & { maxBuffer?: number }} SHExecuteOptions
|
|
55
|
+
* @description Extended options for SHExecute: adds `maxBuffer` (bytes per stream, default 1MB).
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Low-level process executor for shell commands.
|
|
60
|
+
*
|
|
61
|
+
* Used internally by {@link SHDispatch}.
|
|
62
|
+
*
|
|
63
|
+
* Key features:
|
|
64
|
+
* - **Shell mode**: If `options.shell` is string/true, runs `${prefix}; ${command}` via shell ('bash' default).
|
|
65
|
+
* - **No-shell mode**: Uses `/usr/bin/env -S ${command}` for direct exec (ignores prefix).
|
|
66
|
+
* - **Rolling timeout**: `options.timeout` (ms/'2s'); resets on stdout/stderr data. SIGTERM on expiry.
|
|
67
|
+
* - **Buffering**: Captures stdout/stderr up to `maxBuffer` (1MB default); appends truncation markers.
|
|
68
|
+
* - **Payload**: `run(payload)` writes string to stdin (forces pipe).
|
|
69
|
+
* - **Detached**: If `options.detached`, resolves early (~1s) and unrefs.
|
|
70
|
+
* - **Kill**: Terminates process + children via pgrep.
|
|
37
71
|
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* - Buffering: Captures stdout/stderr up to maxBuffer bytes per stream (default 40 MiB). Appends "[stdout truncated]" / "[stderr truncated]" markers if exceeded.
|
|
42
|
-
* - Payload: Passing a payload writes it to stdin and forces stdin to be a pipe.
|
|
43
|
-
* - Detached mode: If options.detached is true, run() resolves to '' after ~1s and unrefs the process.
|
|
44
|
-
* - Kill support: kill(signal) attempts to terminate the process (and some children) and causes run() to reject with "Process killed (forced).".
|
|
72
|
+
* @example
|
|
73
|
+
* const exec = new SHExecute('ls', 'set -euo pipefail', { timeout: '5s' });
|
|
74
|
+
* const out = await exec.run();
|
|
45
75
|
*/
|
|
46
76
|
class SHExecute {
|
|
47
77
|
#forcedKill = false;
|
|
48
|
-
/** @type {import('child_process').ChildProcess} */
|
|
49
|
-
#proc;
|
|
78
|
+
/** @type {import('child_process').ChildProcess | null} */
|
|
79
|
+
#proc = null;
|
|
50
80
|
#prefix = '';
|
|
51
81
|
#command = '';
|
|
52
82
|
/** @type {import('child_process').SpawnOptions | import('child_process').SpawnSyncOptions} */
|
|
@@ -55,14 +85,14 @@ class SHExecute {
|
|
|
55
85
|
#stderrChunks = [];
|
|
56
86
|
#stdoutLen = 0;
|
|
57
87
|
#stderrLen = 0;
|
|
58
|
-
#maxBuffer =
|
|
88
|
+
#maxBuffer = 500 * 1024; // 500KB per stream
|
|
59
89
|
#truncated = { stdout: false, stderr: false };
|
|
60
90
|
#timedOut = false;
|
|
61
91
|
|
|
62
92
|
/**
|
|
63
|
-
* @param {string} command -
|
|
64
|
-
* @param {string} prefix
|
|
65
|
-
* @param {
|
|
93
|
+
* @param {string} command - Command to execute.
|
|
94
|
+
* @param {string} prefix - Shell prelude (e.g., 'set -euo pipefail'); ignored in no-shell.
|
|
95
|
+
* @param {SHExecuteOptions} [options] - Spawn options + maxBuffer/timeout.
|
|
66
96
|
*/
|
|
67
97
|
constructor(command, prefix, options = {}) {
|
|
68
98
|
this.#prefix = prefix;
|
|
@@ -70,18 +100,14 @@ class SHExecute {
|
|
|
70
100
|
const { maxBuffer, ...spawnOpts } = options ?? {};
|
|
71
101
|
this.#maxBuffer = Number.isFinite(maxBuffer) && maxBuffer > 0 ? maxBuffer : this.#maxBuffer;
|
|
72
102
|
this.#options = spawnOpts;
|
|
73
|
-
this.#proc = null;
|
|
74
|
-
this.#stdoutChunks = [];
|
|
75
|
-
this.#stderrChunks = [];
|
|
76
|
-
this.#stdoutLen = 0;
|
|
77
|
-
this.#stderrLen = 0;
|
|
78
|
-
this.#truncated = { stdout: false, stderr: false };
|
|
79
|
-
this.#timedOut = false;
|
|
80
103
|
}
|
|
81
104
|
|
|
82
105
|
/**
|
|
83
|
-
*
|
|
84
|
-
*
|
|
106
|
+
* Synchronous execution.
|
|
107
|
+
*
|
|
108
|
+
* @param {string} [payload] - Stdin data (forces pipe).
|
|
109
|
+
* @returns {import('child_process').SpawnSyncReturns<Buffer>}
|
|
110
|
+
* @throws {Error} Invalid payload type.
|
|
85
111
|
*/
|
|
86
112
|
runSync(payload) {
|
|
87
113
|
this.#forcedKill = false;
|
|
@@ -109,15 +135,20 @@ class SHExecute {
|
|
|
109
135
|
}
|
|
110
136
|
|
|
111
137
|
/**
|
|
112
|
-
*
|
|
113
|
-
*
|
|
138
|
+
* Asynchronous execution with buffering/timeout/kill.
|
|
139
|
+
*
|
|
140
|
+
* Resolves stdout (trimmed) on success; rejects on error/timeout/kill.
|
|
141
|
+
*
|
|
142
|
+
* @param {string} [payload] - Stdin data (forces pipe).
|
|
143
|
+
* @returns {Promise<string>} Trimmed UTF-8 stdout (+ truncation marker if exceeded).
|
|
144
|
+
* @throws {Error} Command failure (incl. code, stderr), timeout, kill, spawn error.
|
|
114
145
|
*/
|
|
115
146
|
run(payload) {
|
|
116
147
|
this.#forcedKill = false;
|
|
117
148
|
if (payload && typeof payload !== 'string') {
|
|
118
149
|
throw new Error('Argument is not a string');
|
|
119
150
|
}
|
|
120
|
-
/** @type {import('
|
|
151
|
+
/** @type {import('child_process').SpawnOptions} */
|
|
121
152
|
const options = { ...this.#options, shell: false };
|
|
122
153
|
if (payload) {
|
|
123
154
|
const stdio = Array.isArray(options.stdio) ? [...options.stdio] : ['pipe', 'pipe', 'pipe'];
|
|
@@ -147,20 +178,27 @@ class SHExecute {
|
|
|
147
178
|
|
|
148
179
|
const ms = parseDuration(this.#options?.timeout ?? 0);
|
|
149
180
|
let timeoutId = null;
|
|
150
|
-
|
|
151
|
-
timeoutId
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
181
|
+
const resetTimeout = () => {
|
|
182
|
+
if (timeoutId !== null) clearTimeout(timeoutId);
|
|
183
|
+
if (ms > 0) {
|
|
184
|
+
timeoutId = setTimeout(() => {
|
|
185
|
+
this.#timedOut = true;
|
|
186
|
+
this.kill('SIGTERM').catch(() => {});
|
|
187
|
+
}, ms);
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
if (ms > 0) resetTimeout();
|
|
156
192
|
|
|
157
193
|
this.#proc.stdout?.on('data', (chunk) => {
|
|
158
194
|
this.#stdoutLen += chunk.length;
|
|
195
|
+
// prevent TOKEN bombs
|
|
159
196
|
if (this.#stdoutLen <= this.#maxBuffer) {
|
|
160
197
|
this.#stdoutChunks.push(chunk);
|
|
161
198
|
} else {
|
|
162
199
|
this.#truncated.stdout = true;
|
|
163
200
|
}
|
|
201
|
+
resetTimeout();
|
|
164
202
|
});
|
|
165
203
|
|
|
166
204
|
this.#proc.stderr?.on('data', (chunk) => {
|
|
@@ -170,18 +208,20 @@ class SHExecute {
|
|
|
170
208
|
} else {
|
|
171
209
|
this.#truncated.stderr = true;
|
|
172
210
|
}
|
|
211
|
+
resetTimeout();
|
|
173
212
|
});
|
|
174
213
|
|
|
175
214
|
return new Promise((resolve, reject) => {
|
|
176
215
|
if (options.detached) {
|
|
177
216
|
setTimeout(() => {
|
|
217
|
+
if (timeoutId !== null) clearTimeout(timeoutId);
|
|
178
218
|
resolve('');
|
|
179
219
|
this.#proc.unref();
|
|
180
220
|
}, 1000);
|
|
181
221
|
}
|
|
182
222
|
|
|
183
223
|
this.#proc.on('close', (code, signal) => {
|
|
184
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
224
|
+
if (timeoutId !== null) clearTimeout(timeoutId);
|
|
185
225
|
if (this.#forcedKill) {
|
|
186
226
|
reject(new Error('Process killed (forced).'));
|
|
187
227
|
return;
|
|
@@ -208,9 +248,11 @@ class SHExecute {
|
|
|
208
248
|
}
|
|
209
249
|
|
|
210
250
|
/**
|
|
211
|
-
*
|
|
212
|
-
*
|
|
213
|
-
* @
|
|
251
|
+
* Terminates process and its children (via pgrep).
|
|
252
|
+
*
|
|
253
|
+
* @param {number | string} [signal='SIGTERM'] - Signal to send.
|
|
254
|
+
* @returns {Promise<number[]>} Killed PIDs.
|
|
255
|
+
* @throws {Error} No process/PID.
|
|
214
256
|
*/
|
|
215
257
|
async kill(signal = 'SIGTERM') {
|
|
216
258
|
if (!this.#proc) throw new Error('Trying to kill a process without creating one.');
|
|
@@ -225,7 +267,7 @@ class SHExecute {
|
|
|
225
267
|
}
|
|
226
268
|
try { process.kill(pid, signal); killed.push(pid); } catch (e) { if (!e || e.code !== 'ESRCH') throw e; }
|
|
227
269
|
} finally {
|
|
228
|
-
this.#proc =
|
|
270
|
+
this.#proc = null;
|
|
229
271
|
}
|
|
230
272
|
return killed;
|
|
231
273
|
}
|