@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 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.1",
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",
@@ -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, psTree } from './utils.js';
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
- super(p)
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
- this.child = spawn(ENV.prefix + this.#command, {
123
+ const shell = ENV['shell'];
124
+ this.child = spawn(ENV.prefix, [this.#command], {
98
125
  cwd,
99
- // cwd: $.cwd ?? $[processCwd],
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
- let children = await psTree(this.child.pid);
242
- for (const p of children) {
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
- if (process.platform == 'win32') {
44
- defaults.shell = which.sync('powershell.exe');
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 { promisify } from 'node:util';
29
- import psTreeModule from 'ps-tree';
30
- export const psTree = promisify(psTreeModule);
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;