@j-o-r/sh 1.1.28 → 1.1.31
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 +145 -151
- package/TODO.md +8 -0
- package/lib/AsyncTracker.js +126 -107
- package/lib/SH.js +281 -224
- package/lib/SHDispatch.js +122 -94
- package/lib/SHExecute.js +83 -41
- package/lib/Test.js +153 -123
- package/package.json +2 -2
- package/types/AsyncTracker.d.ts +65 -31
- package/types/SH.d.ts +190 -168
- package/types/SHDispatch.d.ts +81 -66
- package/types/SHExecute.d.ts +44 -23
- package/types/Test.d.ts +66 -30
package/lib/SHDispatch.js
CHANGED
|
@@ -1,56 +1,72 @@
|
|
|
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
|
+
* @typedef {Object} SHOptions
|
|
42
|
+
* @property {string} cwd - Working directory for spawned commands.
|
|
43
|
+
* @property {NodeJS.ProcessEnv} env - Environment for spawned commands.
|
|
44
|
+
* @property {string|boolean} shell - Shell executable, true for bash, or false for no-shell mode.
|
|
45
|
+
* @property {StdioOptions} stdio - Stdio config passed to child_process.
|
|
46
|
+
* @property {number|string} timeout - Rolling timeout in ms or duration string; 0 disables timeout.
|
|
47
|
+
* @property {number} [maxBuffer] - Maximum buffered bytes per stdout/stderr stream.
|
|
48
|
+
* @property {boolean} [detached] - Run process detached and resolve early.
|
|
49
|
+
* @example { timeout: '5s', stdio: 'inherit', shell: false, maxBuffer: 10 * 1024}
|
|
47
50
|
*/
|
|
51
|
+
|
|
52
|
+
/** @type {SHOptions} */
|
|
53
|
+
const defaultSHOptions = {
|
|
54
|
+
cwd: process.cwd(),
|
|
55
|
+
env: process.env,
|
|
56
|
+
shell: 'bash',
|
|
57
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
58
|
+
timeout: 0,
|
|
59
|
+
};
|
|
60
|
+
|
|
48
61
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
|
|
62
|
+
* Merges user options into predefined defaults (non-destructive).
|
|
63
|
+
*
|
|
64
|
+
* Only copies defined keys.
|
|
65
|
+
*
|
|
66
|
+
* @param {SHOptions} predefined - Base options.
|
|
67
|
+
* @param {Partial<SHOptions>} options - Overrides.
|
|
68
|
+
* @returns {SHOptions} Merged options.
|
|
69
|
+
*/
|
|
54
70
|
const mergeOptions = (predefined, options) => {
|
|
55
71
|
const mergedObj = { ...predefined };
|
|
56
72
|
|
|
@@ -61,94 +77,106 @@ const mergeOptions = (predefined, options) => {
|
|
|
61
77
|
}
|
|
62
78
|
|
|
63
79
|
return mergedObj;
|
|
64
|
-
}
|
|
80
|
+
};
|
|
65
81
|
|
|
66
82
|
/**
|
|
67
|
-
*
|
|
83
|
+
* High-level command dispatcher.
|
|
84
|
+
*
|
|
85
|
+
* Created by {@link SH`cmd`}; chain `.options()` then `.run()`.
|
|
68
86
|
*
|
|
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.
|
|
87
|
+
* Delegates to {@link SHExecute} for exec/timeout/buffer/kill.
|
|
76
88
|
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
89
|
+
* @example
|
|
90
|
+
* const dispatch = SH`ls -la`.options({ timeout: '2s' });
|
|
91
|
+
* const out = await dispatch.run();
|
|
80
92
|
*/
|
|
81
|
-
|
|
82
|
-
|
|
83
93
|
class SHDispatch {
|
|
84
|
-
// #prefix = 'set -euo pipefail;/usr/bin/env'
|
|
85
|
-
// #prefix = 'set -euo pipefail'
|
|
86
94
|
#prefix = '';
|
|
87
95
|
#cmd = '';
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
96
|
+
/** @type {SHOptions} */
|
|
97
|
+
#options = { ...defaultSHOptions };
|
|
98
|
+
/** @type {SHExecute | null} */
|
|
99
|
+
#proc = null;
|
|
100
|
+
|
|
93
101
|
/**
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
102
|
+
* @param {string} cmd - Command string.
|
|
103
|
+
* @param {Partial<SHOptions>} [options] - Initial options.
|
|
104
|
+
* @param {string} [prefix] - Shell prefix (e.g., 'set -euo pipefail').
|
|
105
|
+
* @throws {Error} Invalid/empty cmd.
|
|
106
|
+
*/
|
|
107
|
+
constructor(cmd, options = {}, prefix) {
|
|
99
108
|
if (typeof cmd !== 'string' || cmd === '') {
|
|
100
109
|
throw new Error('Undefined command');
|
|
101
110
|
}
|
|
102
111
|
this.#cmd = cmd;
|
|
103
112
|
this.options(options, prefix);
|
|
104
113
|
}
|
|
114
|
+
|
|
105
115
|
/**
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
116
|
+
* Updates options/prefix by merging into the dispatch instance's current options.
|
|
117
|
+
*
|
|
118
|
+
* This preserves global `SH.*` defaults captured when the command was created
|
|
119
|
+
* and only overrides options explicitly supplied for this command. Strings for
|
|
120
|
+
* `stdio` are expanded to `[value, value, value]`.
|
|
121
|
+
*
|
|
122
|
+
* @param {Partial<SHOptions>} [options] - New options.
|
|
123
|
+
* @param {string} [prefix] - New prefix.
|
|
124
|
+
* @returns {SHDispatch} Self for chaining.
|
|
125
|
+
*/
|
|
110
126
|
options(options, prefix) {
|
|
111
|
-
// this.#options = defaultOptions
|
|
112
127
|
if (typeof prefix === 'string') {
|
|
113
128
|
this.#prefix = prefix;
|
|
114
129
|
}
|
|
115
130
|
if (!options) {
|
|
116
131
|
return this;
|
|
117
132
|
}
|
|
118
|
-
|
|
133
|
+
const nextOptions = { ...options };
|
|
134
|
+
if (nextOptions.stdio && typeof nextOptions.stdio === 'string') {
|
|
119
135
|
// convert stdio to array
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const io = options.stdio;
|
|
123
|
-
options.stdio = Array(3).fill(io);
|
|
136
|
+
const io = nextOptions.stdio;
|
|
137
|
+
nextOptions.stdio = Array(3).fill(io);
|
|
124
138
|
}
|
|
125
|
-
this.#options = mergeOptions(this.#options,
|
|
139
|
+
this.#options = mergeOptions(this.#options, nextOptions);
|
|
126
140
|
return this;
|
|
127
141
|
}
|
|
128
142
|
|
|
129
|
-
/**
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
143
|
+
/**
|
|
144
|
+
* Async run: Captures stdout; rejects on error/timeout.
|
|
145
|
+
*
|
|
146
|
+
* @param {string} [payload] - Stdin payload.
|
|
147
|
+
* @returns {Promise<string>} Stdout.
|
|
148
|
+
*/
|
|
133
149
|
run(payload) {
|
|
134
|
-
this.#proc = new
|
|
150
|
+
this.#proc = new SHExecute(this.#cmd, this.#prefix, this.#options);
|
|
135
151
|
return this.#proc.run(payload);
|
|
136
152
|
}
|
|
137
153
|
|
|
138
|
-
/**
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
154
|
+
/**
|
|
155
|
+
* Sync run: Full Node SpawnSyncReturns.
|
|
156
|
+
*
|
|
157
|
+
* Good for TTY takeovers (e.g., vim).
|
|
158
|
+
*
|
|
159
|
+
* @param {string} [payload] - Stdin payload.
|
|
160
|
+
* @returns {import('child_process').SpawnSyncReturns<Buffer>}
|
|
161
|
+
*/
|
|
143
162
|
runSync(payload) {
|
|
144
|
-
return new
|
|
163
|
+
return new SHExecute(this.#cmd, this.#prefix, this.#options).runSync(payload);
|
|
145
164
|
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Kills running process + children.
|
|
168
|
+
*
|
|
169
|
+
* @param {number | string} [signal='SIGTERM'] - Signal.
|
|
170
|
+
* @returns {Promise<number[]>} Killed PIDs.
|
|
171
|
+
*/
|
|
146
172
|
async kill(signal = 'SIGTERM') {
|
|
147
173
|
let res = [];
|
|
148
|
-
|
|
149
|
-
|
|
174
|
+
if (this.#proc) {
|
|
175
|
+
res = await this.#proc.kill(signal);
|
|
176
|
+
this.#proc = null;
|
|
177
|
+
}
|
|
150
178
|
return res;
|
|
151
179
|
}
|
|
152
180
|
}
|
|
153
181
|
|
|
154
|
-
export default SHDispatch
|
|
182
|
+
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
|
}
|