@j-o-r/sh 0.0.1 → 0.0.3

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 CHANGED
@@ -2,8 +2,6 @@
2
2
 
3
3
  Execute shell commands from JavaScript.
4
4
 
5
- ![Alt text](https://raw.githubusercontent.com/j-o-r/sh/main/jor-sh.png "logo")
6
-
7
5
  ## Introduction
8
6
 
9
7
  BETA
@@ -52,7 +50,7 @@ const ar = within(async () => {
52
50
  ]);
53
51
  });
54
52
 
55
- ```
53
+ ```
56
54
 
57
55
  ```javascript
58
56
  const p = await retry(3, expBackoff(), () => SH`curl -s https://unreachable:`);
@@ -111,8 +109,8 @@ The `ProcessOutput` class collects output from the `ProcessPromise` shell proces
111
109
 
112
110
  ### Methods
113
111
 
114
- - **toString()**:
115
- - Returns a trimmed string combining `stderr` and `stdout`.
112
+ - **toString()**:
113
+ - Returns a trimmed string combining `stderr` and `stdout`.
116
114
  - This method is automatically invoked by various JavaScript methods for output consolidation.
117
115
  - **[inspect.custom]()**:
118
116
  - Custom inspection method used for debugging.
@@ -120,13 +118,9 @@ The `ProcessOutput` class collects output from the `ProcessPromise` shell proces
120
118
 
121
119
  ### Examples
122
120
 
123
- - Script for setting up my wokspace in [TMUX](./workspace.js)
121
+ - Script for setting up a wokspace in [TMUX](./workspace.js)
124
122
  - Take a look at the [test](./test/sh.js)
125
123
 
126
- ## Contributing
127
-
128
- Contributions are welcome. Please submit issues and pull requests on our [GitHub repository](https://github.com/j-o-r/sh).
129
-
130
124
  ## License
131
125
 
132
126
  This project is licensed under the Apache License, Version 2.0.
@@ -0,0 +1,44 @@
1
+ /// <reference types="node" />
2
+ export default ProcessOutput;
3
+ /**
4
+ * The ProcessPromise returns a ProcessOutput even when it fails, by rejecting it.
5
+ * The extension of the Error class is implemented to ensure compatibility when an Error is expected upon rejection.
6
+ */
7
+ declare class ProcessOutput extends Error {
8
+ /**
9
+ * @param {number} code - exit code
10
+ * @param {string} signal - SIGTERM ...
11
+ * @param {string} stdout - std reponse string
12
+ * @param {string} stderr - error reponse string
13
+ * @param {string} combined - stderr + stdout
14
+ * @param {string} message - Error message
15
+ */
16
+ constructor(code: number, signal: string, stdout?: string, stderr?: string, combined?: string, message?: string);
17
+ /**
18
+ * This string represents the standard output (stdout) from the child process.
19
+ * @returns {string}
20
+ */
21
+ get stdout(): string;
22
+ /**
23
+ * This string represents the error output (stderr) from the child process.
24
+ * @returns {string}
25
+ */
26
+ get stderr(): string;
27
+ /**
28
+ * This represents the exit code returned by the external process.
29
+ * @returns {number} The exit
30
+ */
31
+ get exitCode(): number;
32
+ /**
33
+ * This represents the exit signal, for example, "SIGTERM", received from the child process.
34
+ * @returns {string} The exit signal from the child
35
+ */
36
+ get signal(): string;
37
+ /**
38
+ * This method is used for debugging purposes. It displays the current state of the object
39
+ * when passed to the console.log function.
40
+ */
41
+ [inspect.custom](): string;
42
+ #private;
43
+ }
44
+ import { inspect } from 'node:util';
@@ -0,0 +1,147 @@
1
+ /// <reference types="node" />
2
+ /// <reference types="node" />
3
+ export default ProcessPromise;
4
+ export type resolver = Function;
5
+ export type rejecter = Function;
6
+ export type PromiseConstruct = Function;
7
+ export type StdioOption = ('pipe' | 'ignore' | 'inherit' | number);
8
+ export type StdioOptions = Array<StdioOption> | StdioOption;
9
+ /**
10
+ * @typedef {Function} resolver
11
+ * @param {ProcessOutput} value
12
+ */
13
+ /**
14
+ * @typedef {Function} rejecter
15
+ * @param {ProcessOutput} value
16
+ */
17
+ /**
18
+ * @typedef {Function} PromiseConstruct
19
+ * @param {resolver} resolve
20
+ * @param {rejecter} reject
21
+ */
22
+ /**
23
+ * @typedef {('pipe' | 'ignore' | 'inherit' | number)} StdioOption
24
+ * @description Defines the stdio configuration for each of the standard streams.
25
+ *
26
+ * - 'pipe' creates a pipe between the child process and the parent process.
27
+ * The parent end of the pipe is exposed as a property on the `ChildProcess` object.
28
+ * - 'ignore' indicates that the child process's corresponding stdio file descriptor will be ignored.
29
+ * - 'inherit' passes the corresponding stdio stream to/from the child process.
30
+ * - Stream object to be used for the stdio stream.
31
+ * - Positive integer representing a file descriptor to be used for the stdio stream.
32
+ */
33
+ /**
34
+ * @typedef {Array<StdioOption>|StdioOption} StdioOptions
35
+ * @description
36
+ * Configures the stdio streams for the child process. This can be an array or a single StdioOption.
37
+ *
38
+ * Array Form: Specify the configuration for [stdin, stdout, stderr].
39
+ * - If array length is more than 3, additional positions correspond to extra streams.
40
+ * Single Value: This value will be applied to stdin, stdout, and stderr.
41
+ *
42
+ * Examples:
43
+ * - ['pipe', 'pipe', 'ignore']: Pipe stdin and stdout, ignore stderr.
44
+ * - 'inherit': Inherit all stdio streams from the parent.
45
+ */
46
+ /**
47
+ * class extends promise
48
+ */
49
+ declare class ProcessPromise extends Promise<any> {
50
+ /**
51
+ * @param {PromiseConstruct} p - A function that takes two arguments, resolve and reject.
52
+ */
53
+ constructor(p: PromiseConstruct);
54
+ /**
55
+ * Set the environment
56
+ * and the
57
+ * @param {string} cmd - Command to execute
58
+ * @param {string} from - Position in the codfe where this is triggred from
59
+ * @param {function} resolve - Promise resolve method
60
+ * @param {function} reject - Reject method
61
+ * @param {object} options - Settings (options default)
62
+ */
63
+ _bind(cmd: string, from: string, resolve: Function, reject: Function, options: object): void;
64
+ /**
65
+ * Run the promise
66
+ */
67
+ run(): this;
68
+ child: import("child_process").ChildProcessWithoutNullStreams & import("child_process").ChildProcessByStdio<import("stream").Writable, import("stream").Readable, import("stream").Readable> & import("child_process").ChildProcessByStdio<import("stream").Writable, import("stream").Readable, null> & import("child_process").ChildProcessByStdio<import("stream").Writable, null, import("stream").Readable> & import("child_process").ChildProcessByStdio<null, import("stream").Readable, import("stream").Readable> & import("child_process").ChildProcessByStdio<import("stream").Writable, null, null> & import("child_process").ChildProcessByStdio<null, import("stream").Readable, null> & import("child_process").ChildProcessByStdio<null, null, import("stream").Readable> & import("child_process").ChildProcessByStdio<null, null, null> & import("child_process").ChildProcess;
69
+ /**
70
+ * stdin child stream
71
+ * @retruns {Writeable}
72
+ */
73
+ get stdin(): null;
74
+ /**
75
+ * stdout child stream
76
+ * @retruns {Readable}
77
+ */
78
+ get stdout(): null;
79
+ /**
80
+ * stderr child stream
81
+ * @retruns {Readable}
82
+ */
83
+ get stderr(): null;
84
+ /**
85
+ * process exit code
86
+ * @returns {Promise<number>}
87
+ */
88
+ get exitCode(): Promise<number>;
89
+ catch(onrejected: any): Promise<any>;
90
+ /**
91
+ * Pipe the output to the input to the next Promise
92
+ * @example
93
+ * const res = await SH`ls -FLa`.pipe(SH`grep package.json`);
94
+ */
95
+ pipe(dest: any): ProcessPromise;
96
+ /**
97
+ * @private
98
+ * Set a postrun action, internal use only
99
+ * @param {function} f
100
+ */
101
+ private set _postrun(f);
102
+ /**
103
+ * Send a KILL signal to the child process
104
+ * @returns {Promise<number[]>} the pid numbers that has been killed
105
+ */
106
+ kill(signal?: string): Promise<number[]>;
107
+ stdio(stdin: any, stdout?: string, stderr?: string): this;
108
+ /**
109
+ * Do not throw
110
+ */
111
+ nothrow(): this;
112
+ /**
113
+ * supress log output
114
+ * SH.verbose = false; does the same
115
+ */
116
+ quiet(): this;
117
+ /**
118
+ * Show log output in the console
119
+ */
120
+ verbose(): this;
121
+ _quiet: boolean;
122
+ /**
123
+ * Set a timeout to kill a process
124
+ *
125
+ * @param {string} d - 10s, 1000ms
126
+ * @param {string} [signal] - default "SIGTERM" Signal to send to kill the proces
127
+ */
128
+ timeout(d: string, signal?: string): this;
129
+ _timeout: number;
130
+ _timeoutSignal: string;
131
+ /**
132
+ * stop execution for the next step
133
+ */
134
+ halt(): this;
135
+ /**
136
+ * @private
137
+ * Set a prerun action, internal use only
138
+ * @param {function} f
139
+ */
140
+ private set _prerun(f);
141
+ /**
142
+ * Is this promise halted?
143
+ * @returns {boolean}
144
+ */
145
+ get isHalted(): boolean;
146
+ #private;
147
+ }
@@ -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,35 @@ import ProcessOutput from './ProcessOutput.js';
42
43
  * @param {resolver} resolve
43
44
  * @param {rejecter} reject
44
45
  */
46
+ /**
47
+ * @typedef {('pipe' | 'ignore' | 'inherit' | 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
+ */
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
+ */
45
71
 
72
+ /**
73
+ * class extends promise
74
+ */
46
75
  class ProcessPromise extends Promise {
47
76
  #command = '';
48
77
  #from = '';
@@ -51,6 +80,7 @@ class ProcessPromise extends Promise {
51
80
  /** @type {rejecter} */
52
81
  #reject = () => { };
53
82
  #snapshot = {};
83
+ /** @type {StdioOptions} */
54
84
  #stdio = ['inherit', 'pipe', 'pipe'];
55
85
  #nothrow = false;
56
86
  #quiet = false;
@@ -63,7 +93,8 @@ class ProcessPromise extends Promise {
63
93
  * @param {PromiseConstruct} p - A function that takes two arguments, resolve and reject.
64
94
  */
65
95
  constructor(p) {
66
- super(p)
96
+ // @ts-ignore
97
+ super(p)
67
98
  }
68
99
  /**
69
100
  * Set the environment
@@ -94,10 +125,11 @@ class ProcessPromise extends Promise {
94
125
  verbose: ENV.verbose && !this.#quiet,
95
126
  });
96
127
  const cwd = ENV['processCwd'];
97
- this.child = spawn(ENV.prefix + this.#command, {
128
+ const shell = ENV['shell'];
129
+ this.child = spawn(ENV.prefix, [this.#command], {
98
130
  cwd,
99
- // cwd: $.cwd ?? $[processCwd],
100
- shell: typeof ENV.shell === 'string' ? ENV.shell : true,
131
+ shell,
132
+ // @ts-ignore
101
133
  stdio: this.#stdio,
102
134
  windowsHide: true,
103
135
  env: ENV.env,
@@ -122,7 +154,9 @@ class ProcessPromise extends Promise {
122
154
  });
123
155
  this.child.on('error', (err) => {
124
156
  const message = `${err.message}\n` +
157
+ // @ts-ignore
125
158
  ` errno: ${err.errno} (${errnoMessage(err.errno)})\n` +
159
+ // @ts-ignore
126
160
  ` code: ${err.code}\n` +
127
161
  ` at ${this.#from}`;
128
162
  this.#reject(new ProcessOutput(null, null, stdout, stderr, combined, message));
@@ -142,7 +176,9 @@ class ProcessPromise extends Promise {
142
176
  combined += data;
143
177
  };
144
178
  if (!this.#piped)
179
+ // @ts-ignore
145
180
  this.child.stdout?.on('data', onStdout); // If process is piped, don't collect or print output.
181
+ // @ts-ignore
146
182
  this.child.stderr?.on('data', onStderr); // Stderr should be printed regardless of piping.
147
183
  this.#postrun(); // In case $1.pipe($2), after both subprocesses are running, we can pipe $1.stdout to $2.stdin.
148
184
  if (this._timeout && this._timeoutSignal) {
@@ -190,9 +226,9 @@ class ProcessPromise extends Promise {
190
226
  * @returns {Promise<number>}
191
227
  */
192
228
  get exitCode() {
193
- return this.then((p) => p.exitCode, (p) => p.exitCode);
229
+ return this.#then((p) => p.exitCode, (p) => p.exitCode);
194
230
  }
195
- then(onfulfilled, onrejected) {
231
+ #then(onfulfilled, onrejected) {
196
232
  if (this.isHalted && !this.child) {
197
233
  throw new Error('The process is halted!');
198
234
  }
@@ -211,6 +247,7 @@ class ProcessPromise extends Promise {
211
247
  throw new Error('The pipe() method does not take strings. Forgot SH?');
212
248
  if (this.#resolved) {
213
249
  if (dest instanceof ProcessPromise)
250
+ // @ts-ignore
214
251
  dest.stdin.end(); // In case of piped stdin, we may want to close stdin of dest as well.
215
252
  throw new Error("The pipe() method shouldn't be called after promise is already resolved!");
216
253
  }
@@ -221,34 +258,28 @@ class ProcessPromise extends Promise {
221
258
  dest._postrun = () => {
222
259
  if (!dest.child)
223
260
  throw new Error('Access to stdin of pipe destination without creation a subprocess.');
261
+ // @ts-ignore
224
262
  this.stdout.pipe(dest.stdin);
225
263
  };
226
264
  return dest;
227
265
  }
228
266
  else {
267
+ // @ts-ignore
229
268
  this._postrun = () => this.stdout.pipe(dest);
230
269
  return this;
231
270
  }
232
271
  }
233
272
  /**
234
273
  * Send a KILL signal to the child process
274
+ * @returns {Promise<number[]>} the pid numbers that has been killed
235
275
  */
236
276
  async kill(signal = 'SIGTERM') {
237
277
  if (!this.child)
238
278
  throw new Error('Trying to kill a process without creating one.');
239
279
  if (!this.child.pid)
240
280
  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) { }
281
+
282
+ return await killProcesses(this.child.pid, signal)
252
283
  }
253
284
  stdio(stdin, stdout = 'pipe', stderr = 'pipe') {
254
285
  this.#stdio = [stdin, stdout, stderr];
@@ -287,7 +318,7 @@ class ProcessPromise extends Promise {
287
318
  this._timeoutSignal = signal;
288
319
  return this;
289
320
  }
290
- /**
321
+ /**
291
322
  * stop execution for the next step
292
323
  */
293
324
  halt() {
@@ -312,7 +343,7 @@ class ProcessPromise extends Promise {
312
343
  // @ts-ignore
313
344
  this.#postrun = f;
314
345
  }
315
- /**
346
+ /**
316
347
  * Is this promise halted?
317
348
  * @returns {boolean}
318
349
  */
package/lib/sh.d.ts ADDED
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Creates a new ProcessPromise object that represents a command to be executed.
3
+ */
4
+ export type Shell = Function;
5
+ /**
6
+ * Creates a new ProcessPromise object that represents a command to be executed.
7
+ *
8
+ * @typedef {Function} Shell
9
+ * @property {function(Array, ...*): ProcessPromise} execute - The function to execute the command.
10
+ * @property {boolean} verbose - A property to control verbosity.
11
+ *
12
+ * @param {Array} pieces - An array of string literals from a template literal.
13
+ * @param {...*} args - The values to be interpolated into the string literals.
14
+ * @returns {ProcessPromise} A ProcessPromise object that represents the command.
15
+ * @throws {Error} Throws an error if any of the string literals in `pieces` is undefined.
16
+ *
17
+ * @example
18
+ * const command = await SH`echo 'Hello, world!'`;
19
+ */
20
+ /** @type {Shell & { (pieces: TemplateStringsArray, ...args: any[]): ProcessPromise }} */
21
+ export const SH: Function & ((pieces: TemplateStringsArray, ...args: any[]) => ProcessPromise);
22
+ /**
23
+ * Change working directory
24
+ * @param {string} dir
25
+ */
26
+ export function cd(dir: string): void;
27
+ /**
28
+ * This function pauses or "sleeps" code execution for a specified duration.
29
+ * @param {string|number} duration - The duration to pause execution for, e.g., '100ms' or '3s'.
30
+ *
31
+ * @example
32
+ *
33
+ * const res = await Promise.all([
34
+ * SH`sleep 2; echo 2`, // Sleep for 2 seconds
35
+ * sleep(2), // Sleep for 2 seconds
36
+ * SH`sleep 3; echo 3` // Sleep for 3 seconds
37
+ * ]);
38
+ */
39
+ export function sleep(duration: string | number): Promise<any>;
40
+ /**
41
+ * This function retries a command a specified number of times.
42
+ * @example
43
+ * // Retry a command 3 times
44
+ * const p = await retry(3, () => SH`curl -s https://flipwrsi`);
45
+ *
46
+ * // Retry a command 3 times with an interval of 1 second between each try
47
+ * const p = await retry(3, '1s', () => SH`curl -s https://flipwrsi`);
48
+ *
49
+ * // Retry a command 3 times with irregular intervals using exponential backoff
50
+ * const p = await retry(3, expBackoff(), () => SH`curl -s https://flipwrsi`);
51
+ */
52
+ export function retry(count: any, a: any, b: any): Promise<any>;
53
+ /**
54
+ * This function reads the standard input (stdin) for the current process.
55
+ * It is used to get piped content into a script.
56
+ * @example
57
+ * const content = await stdin();
58
+ */
59
+ export function stdin(): Promise<string>;
60
+ /**
61
+ * Create a async context in an sync block
62
+ * @param {function} callback - async function
63
+ * @example
64
+ * const p = within(async () => {
65
+ * const res = await Promise.all([
66
+ * SH`sleep 1; echo 1`,
67
+ * SH`sleep 2; echo 2`,
68
+ * sleep(2),
69
+ * SH`sleep 3; echo 3`
70
+ * ]);
71
+ */
72
+ export function within(callback: Function): any;
73
+ export function expBackoff(max?: string, rand?: string): Generator<number, void, unknown>;
74
+ import ProcessPromise from './ProcessPromise.js';
@@ -27,27 +27,22 @@
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
- // const processCwd = Symbol('processCwd');
34
32
  const storage = new AsyncLocalStorage();
35
33
 
36
34
  const defaults = {
37
35
  processCwd: '',
38
36
  verbose: false,
39
37
  env: {},
40
- shell: '',
38
+ shell: 'bash',
41
39
  prefix: '',
42
40
  };
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
- }
41
+ defaults.prefix = 'set -euo pipefail;/usr/bin/env';
42
+
49
43
  /**
50
44
  * Escape CLI arguments
45
+ * @param {string[]} arg
51
46
  * @retruns {string}
52
47
  */
53
48
  const sanitizeArg = (arg) => {
@@ -61,23 +56,23 @@ const sanitizeArg = (arg) => {
61
56
  const getStore = () => {
62
57
  return storage.getStore() || defaults;
63
58
  }
64
-
65
59
  /**
66
- * Creates a new ProcessPromise object that represents a command to be executed.
67
- *
68
- * @typedef {Function} Shell
69
- * @type {function}
70
- * @param {Array} pieces - An array of string literals from a template literal.
71
- * @param {...*} args - The values to be interpolated into the string literals.
72
- * @returns {ProcessPromise} A ProcessPromise object that represents the command.
73
- * @throws {Error} Throws an error if any of the string literals in `pieces` is undefined.
74
- *
75
- * @property {boolean} verbose
76
- *
77
- * @example
78
- * const command = await SH`echo 'Hello, world!'`;
79
- */
80
- /** @type {Shell} */
60
+ * Creates a new ProcessPromise object that represents a command to be executed.
61
+ *
62
+ * @typedef {Function} Shell
63
+ * @property {function(Array, ...*): ProcessPromise} execute - The function to execute the command.
64
+ * @property {boolean} verbose - A property to control verbosity.
65
+ *
66
+ * @param {Array} pieces - An array of string literals from a template literal.
67
+ * @param {...*} args - The values to be interpolated into the string literals.
68
+ * @returns {ProcessPromise} A ProcessPromise object that represents the command.
69
+ * @throws {Error} Throws an error if any of the string literals in `pieces` is undefined.
70
+ *
71
+ * @example
72
+ * const command = await SH`echo 'Hello, world!'`;
73
+ */
74
+ /** @type {Shell & { (pieces: TemplateStringsArray, ...args: any[]): ProcessPromise }} */
75
+ // @ts-ignore
81
76
  const SH = new Proxy(function(pieces, ...args) {
82
77
  const from = new Error().stack.split(/^\s*at\s/m)[2].trim();
83
78
  if (pieces.some((p) => p == undefined)) {
@@ -177,7 +172,6 @@ const retry = async (count, a, b) => {
177
172
  else {
178
173
  delayStatic = parseDuration(a);
179
174
  }
180
- // console.log(assert(b));
181
175
  assert(b);
182
176
  callback = b;
183
177
  }
@@ -211,7 +205,7 @@ const retry = async (count, a, b) => {
211
205
  }
212
206
  /**
213
207
  * This function pauses or "sleeps" code execution for a specified duration.
214
- * @param {string} duration - The duration to pause execution for, e.g., '100ms' or '3s'.
208
+ * @param {string|number} duration - The duration to pause execution for, e.g., '100ms' or '3s'.
215
209
  *
216
210
  * @example
217
211
  *
@@ -250,7 +244,6 @@ function* expBackoff(max = '60s', rand = '100ms') {
250
244
  // process.chdir(SH['processCwd']);
251
245
  // }
252
246
  export {
253
- /** @type {Shell} */
254
247
  SH,
255
248
  cd,
256
249
  sleep,
package/lib/utils.d.ts ADDED
@@ -0,0 +1,20 @@
1
+ /// <reference types="node" />
2
+ /**
3
+ * Kills a process and all child processes of a given process ID in Linux/Posix.
4
+ * @param {number} processPid - The process ID.
5
+ * @param {string} signal - Signal to send.
6
+ * @retruns {Promise<number[]>} array with killed pid numbers
7
+ */
8
+ export function killProcesses(processPid: number, signal: string): Promise<any>;
9
+ export function noop(): void;
10
+ export function randomId(): string;
11
+ export function isString(obj: any): boolean;
12
+ export function quote(arg: any): any;
13
+ export function quotePowerShell(arg: any): any;
14
+ export function log(entry: any): void;
15
+ export function exitCodeInfo(exitCode: any): any;
16
+ export function errnoMessage(errno: any): any;
17
+ export function parseDuration(d: any): number;
18
+ export function formatCmd(cmd: any): string;
19
+ export { spawn };
20
+ import { spawn } from 'node:child_process';
@@ -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;
package/package.json CHANGED
@@ -1,35 +1,38 @@
1
1
  {
2
2
  "name": "@j-o-r/sh",
3
- "author": "Jorrit Duin <jorrit.duin@gmail.com>",
3
+ "author": "Jorrit Duin <j-o-r@duin.work>",
4
4
  "type": "module",
5
- "version": "0.0.1",
6
- "description": "Execute shell commands from javascript",
7
- "main": "src/sh.js",
5
+ "version": "0.0.3",
6
+ "description": "Execute shell commands on Linux-based systems from javascript",
7
+ "main": "lib/sh.js",
8
8
  "engines": {
9
- "node": ">=18.0.0"
9
+ "node": ">=20.0.0"
10
10
  },
11
11
  "scripts": {
12
- "test": "test/sh.js"
12
+ "test": "test/sh.js",
13
+ "publish": "npm run release && npm publish --access public",
14
+ "release": "npm pack --pack-destination=release",
15
+ "clear:types": "rm lib/*.d.ts",
16
+ "types": "tsc lib/*.js --declaration --allowJs --emitDeclarationOnly"
13
17
  },
14
18
  "repository": {
15
19
  "type": "git",
16
- "url": "https://github.com/j-o-r/sh.git"
20
+ "url": "https://codeberg.org/duin/sh"
17
21
  },
18
22
  "license": "Apache License, Version 2.0",
19
- "dependencies": {
20
- "ps-tree": "^1.2.0",
21
- "which": "^4.0.0"
22
- },
23
+ "dependencies": {},
23
24
  "devDependencies": {
24
25
  "@types/node": "^20.8.10",
25
26
  "uvu": "^0.5.6"
26
27
  },
27
28
  "bugs": {
28
- "url": "https://github.com/j-o-r/sh/issues"
29
+ "url": "https://codeberg.org/duin/sh/issues"
29
30
  },
30
- "homepage": "https://github.com/j-o-r/sh",
31
+ "homepage": "https://codeberg.org/duin",
31
32
  "keywords": [
32
33
  "shell",
34
+ "posix",
35
+ "linux",
33
36
  "command-line",
34
37
  "shell-script",
35
38
  "nodejs",
Binary file
File without changes