@j-o-r/sh 0.0.1 → 0.0.2
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/package.json +5 -6
- package/src/ProcessPromise.js +37 -19
- package/src/sh.js +3 -8
- package/src/utils.js +46 -5
package/package.json
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"name": "@j-o-r/sh",
|
|
3
3
|
"author": "Jorrit Duin <jorrit.duin@gmail.com>",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "0.0.
|
|
6
|
-
"description": "Execute shell commands from javascript",
|
|
5
|
+
"version": "0.0.2",
|
|
6
|
+
"description": "Execute shell commands on Linux-based systems from javascript",
|
|
7
7
|
"main": "src/sh.js",
|
|
8
8
|
"engines": {
|
|
9
9
|
"node": ">=18.0.0"
|
|
@@ -16,10 +16,7 @@
|
|
|
16
16
|
"url": "https://github.com/j-o-r/sh.git"
|
|
17
17
|
},
|
|
18
18
|
"license": "Apache License, Version 2.0",
|
|
19
|
-
"dependencies": {
|
|
20
|
-
"ps-tree": "^1.2.0",
|
|
21
|
-
"which": "^4.0.0"
|
|
22
|
-
},
|
|
19
|
+
"dependencies": {},
|
|
23
20
|
"devDependencies": {
|
|
24
21
|
"@types/node": "^20.8.10",
|
|
25
22
|
"uvu": "^0.5.6"
|
|
@@ -30,6 +27,8 @@
|
|
|
30
27
|
"homepage": "https://github.com/j-o-r/sh",
|
|
31
28
|
"keywords": [
|
|
32
29
|
"shell",
|
|
30
|
+
"posix",
|
|
31
|
+
"linux",
|
|
33
32
|
"command-line",
|
|
34
33
|
"shell-script",
|
|
35
34
|
"nodejs",
|
package/src/ProcessPromise.js
CHANGED
|
@@ -25,10 +25,11 @@
|
|
|
25
25
|
// - The namespace has been changed from '$' to 'SH'.
|
|
26
26
|
// Modified by: jorrit.duin+sh[AT]gmail.com
|
|
27
27
|
|
|
28
|
-
import { spawn } from 'node:child_process';
|
|
28
|
+
// import { spawn, exec } from 'node:child_process';
|
|
29
29
|
import assert from 'node:assert';
|
|
30
|
-
import { log, errnoMessage, exitCodeInfo, noop, parseDuration
|
|
30
|
+
import { killProcesses, spawn, log, errnoMessage, exitCodeInfo, noop, parseDuration } from './utils.js';
|
|
31
31
|
import ProcessOutput from './ProcessOutput.js';
|
|
32
|
+
|
|
32
33
|
/**
|
|
33
34
|
* @typedef {Function} resolver
|
|
34
35
|
* @param {ProcessOutput} value
|
|
@@ -42,7 +43,31 @@ import ProcessOutput from './ProcessOutput.js';
|
|
|
42
43
|
* @param {resolver} resolve
|
|
43
44
|
* @param {rejecter} reject
|
|
44
45
|
*/
|
|
46
|
+
/**
|
|
47
|
+
* @typedef {('pipe' | 'ignore' | 'inherit' | Stream | number)} StdioOption
|
|
48
|
+
* @description Defines the stdio configuration for each of the standard streams.
|
|
49
|
+
*
|
|
50
|
+
* - 'pipe' creates a pipe between the child process and the parent process.
|
|
51
|
+
* The parent end of the pipe is exposed as a property on the `ChildProcess` object.
|
|
52
|
+
* - 'ignore' indicates that the child process's corresponding stdio file descriptor will be ignored.
|
|
53
|
+
* - 'inherit' passes the corresponding stdio stream to/from the child process.
|
|
54
|
+
* - Stream object to be used for the stdio stream.
|
|
55
|
+
* - Positive integer representing a file descriptor to be used for the stdio stream.
|
|
56
|
+
*/
|
|
45
57
|
|
|
58
|
+
/**
|
|
59
|
+
* @typedef {Array<StdioOption>|StdioOption} StdioOptions
|
|
60
|
+
* @description
|
|
61
|
+
* Configures the stdio streams for the child process. This can be an array or a single StdioOption.
|
|
62
|
+
*
|
|
63
|
+
* Array Form: Specify the configuration for [stdin, stdout, stderr].
|
|
64
|
+
* - If array length is more than 3, additional positions correspond to extra streams.
|
|
65
|
+
* Single Value: This value will be applied to stdin, stdout, and stderr.
|
|
66
|
+
*
|
|
67
|
+
* Examples:
|
|
68
|
+
* - ['pipe', 'pipe', 'ignore']: Pipe stdin and stdout, ignore stderr.
|
|
69
|
+
* - 'inherit': Inherit all stdio streams from the parent.
|
|
70
|
+
*/
|
|
46
71
|
class ProcessPromise extends Promise {
|
|
47
72
|
#command = '';
|
|
48
73
|
#from = '';
|
|
@@ -51,6 +76,7 @@ class ProcessPromise extends Promise {
|
|
|
51
76
|
/** @type {rejecter} */
|
|
52
77
|
#reject = () => { };
|
|
53
78
|
#snapshot = {};
|
|
79
|
+
/** @type {StdioOptions} */
|
|
54
80
|
#stdio = ['inherit', 'pipe', 'pipe'];
|
|
55
81
|
#nothrow = false;
|
|
56
82
|
#quiet = false;
|
|
@@ -63,7 +89,7 @@ class ProcessPromise extends Promise {
|
|
|
63
89
|
* @param {PromiseConstruct} p - A function that takes two arguments, resolve and reject.
|
|
64
90
|
*/
|
|
65
91
|
constructor(p) {
|
|
66
|
-
|
|
92
|
+
super(p)
|
|
67
93
|
}
|
|
68
94
|
/**
|
|
69
95
|
* Set the environment
|
|
@@ -94,10 +120,10 @@ class ProcessPromise extends Promise {
|
|
|
94
120
|
verbose: ENV.verbose && !this.#quiet,
|
|
95
121
|
});
|
|
96
122
|
const cwd = ENV['processCwd'];
|
|
97
|
-
|
|
123
|
+
const shell = ENV['shell'];
|
|
124
|
+
this.child = spawn(ENV.prefix, [this.#command], {
|
|
98
125
|
cwd,
|
|
99
|
-
|
|
100
|
-
shell: typeof ENV.shell === 'string' ? ENV.shell : true,
|
|
126
|
+
shell,
|
|
101
127
|
stdio: this.#stdio,
|
|
102
128
|
windowsHide: true,
|
|
103
129
|
env: ENV.env,
|
|
@@ -232,23 +258,15 @@ class ProcessPromise extends Promise {
|
|
|
232
258
|
}
|
|
233
259
|
/**
|
|
234
260
|
* Send a KILL signal to the child process
|
|
261
|
+
* @returns {Promise<number[]>} the pid numbers that has been killed
|
|
235
262
|
*/
|
|
236
263
|
async kill(signal = 'SIGTERM') {
|
|
237
264
|
if (!this.child)
|
|
238
265
|
throw new Error('Trying to kill a process without creating one.');
|
|
239
266
|
if (!this.child.pid)
|
|
240
267
|
throw new Error('The process pid is undefined.');
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
try {
|
|
244
|
-
process.kill(+p.PID, signal);
|
|
245
|
-
}
|
|
246
|
-
catch (e) { }
|
|
247
|
-
}
|
|
248
|
-
try {
|
|
249
|
-
process.kill(this.child.pid, signal);
|
|
250
|
-
}
|
|
251
|
-
catch (e) { }
|
|
268
|
+
|
|
269
|
+
return await killProcesses(this.child.pid, signal)
|
|
252
270
|
}
|
|
253
271
|
stdio(stdin, stdout = 'pipe', stderr = 'pipe') {
|
|
254
272
|
this.#stdio = [stdin, stdout, stderr];
|
|
@@ -287,7 +305,7 @@ class ProcessPromise extends Promise {
|
|
|
287
305
|
this._timeoutSignal = signal;
|
|
288
306
|
return this;
|
|
289
307
|
}
|
|
290
|
-
|
|
308
|
+
/**
|
|
291
309
|
* stop execution for the next step
|
|
292
310
|
*/
|
|
293
311
|
halt() {
|
|
@@ -312,7 +330,7 @@ class ProcessPromise extends Promise {
|
|
|
312
330
|
// @ts-ignore
|
|
313
331
|
this.#postrun = f;
|
|
314
332
|
}
|
|
315
|
-
|
|
333
|
+
/**
|
|
316
334
|
* Is this promise halted?
|
|
317
335
|
* @returns {boolean}
|
|
318
336
|
*/
|
package/src/sh.js
CHANGED
|
@@ -27,7 +27,6 @@
|
|
|
27
27
|
|
|
28
28
|
import assert from 'node:assert';
|
|
29
29
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
30
|
-
import which from 'which';
|
|
31
30
|
import { log, parseDuration, quote, quotePowerShell, } from './utils.js';
|
|
32
31
|
import ProcessPromise from './ProcessPromise.js';
|
|
33
32
|
// const processCwd = Symbol('processCwd');
|
|
@@ -37,15 +36,11 @@ const defaults = {
|
|
|
37
36
|
processCwd: '',
|
|
38
37
|
verbose: false,
|
|
39
38
|
env: {},
|
|
40
|
-
shell: '',
|
|
39
|
+
shell: 'bash',
|
|
41
40
|
prefix: '',
|
|
42
41
|
};
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
} else {
|
|
46
|
-
defaults.shell = which.sync('bash');
|
|
47
|
-
defaults.prefix = 'set -euo pipefail;';
|
|
48
|
-
}
|
|
42
|
+
defaults.prefix = 'set -euo pipefail;/usr/bin/env';
|
|
43
|
+
|
|
49
44
|
/**
|
|
50
45
|
* Escape CLI arguments
|
|
51
46
|
* @retruns {string}
|
package/src/utils.js
CHANGED
|
@@ -25,9 +25,51 @@
|
|
|
25
25
|
// - The namespace has been changed from '$' to 'SH'.
|
|
26
26
|
// Modified by: jorrit.duin+sh[AT]gmail.com
|
|
27
27
|
|
|
28
|
-
import {
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
import { spawn, exec } from 'node:child_process';
|
|
29
|
+
export { spawn };
|
|
30
|
+
/**
|
|
31
|
+
* Kills a process and all child processes of a given process ID in Linux/Posix.
|
|
32
|
+
* @param {number} processPid - The process ID.
|
|
33
|
+
* @param {string} signal - Signal to send.
|
|
34
|
+
* @retruns {Promise<number[]>} array with killed pid numbers
|
|
35
|
+
*/
|
|
36
|
+
export function killProcesses(processPid, signal) {
|
|
37
|
+
const killed = [];
|
|
38
|
+
return new Promise((resolve, reject) => {
|
|
39
|
+
// Command to get child PIDs of the given process
|
|
40
|
+
const cmd = `pgrep -P ${processPid}`;
|
|
41
|
+
exec(cmd, (error, stdout, stderr) => {
|
|
42
|
+
if (error) {
|
|
43
|
+
reject(error);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (stderr) {
|
|
47
|
+
reject(new Error(stderr));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const pids = stdout.split(/\r?\n/).filter(pid => pid);
|
|
51
|
+
// Kill each child process
|
|
52
|
+
try {
|
|
53
|
+
for (const pid of pids) {
|
|
54
|
+
process.kill(parseInt(pid), signal);
|
|
55
|
+
killed.push(parseInt(pid));
|
|
56
|
+
}
|
|
57
|
+
} catch (err) {
|
|
58
|
+
reject(err);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
// Kill the parent process after all child processes have been killed
|
|
62
|
+
try {
|
|
63
|
+
process.kill(processPid, signal);
|
|
64
|
+
killed.push(processPid);
|
|
65
|
+
} catch (err) {
|
|
66
|
+
reject(err);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
resolve(killed);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
}
|
|
31
73
|
export function noop() { }
|
|
32
74
|
export function randomId() {
|
|
33
75
|
return Math.random().toString(36).slice(2);
|
|
@@ -57,8 +99,7 @@ export function quotePowerShell(arg) {
|
|
|
57
99
|
}
|
|
58
100
|
return `'` + arg.replace(/'/g, "''") + `'`;
|
|
59
101
|
}
|
|
60
|
-
|
|
61
|
-
export function log (entry) {
|
|
102
|
+
export function log(entry) {
|
|
62
103
|
switch (entry.kind) {
|
|
63
104
|
case 'cmd':
|
|
64
105
|
if (!entry.verbose) return;
|