@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 +4 -10
- package/lib/ProcessOutput.d.ts +44 -0
- package/lib/ProcessPromise.d.ts +147 -0
- package/{src → lib}/ProcessPromise.js +52 -21
- package/lib/sh.d.ts +74 -0
- package/{src → lib}/sh.js +21 -28
- package/lib/utils.d.ts +20 -0
- package/{src → lib}/utils.js +46 -5
- package/package.json +16 -13
- package/release/j-o-r-sh-0.0.3.tgz +0 -0
- /package/{src → lib}/ProcessOutput.js +0 -0
package/README.md
CHANGED
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
Execute shell commands from JavaScript.
|
|
4
4
|
|
|
5
|
-

|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
128
|
+
const shell = ENV['shell'];
|
|
129
|
+
this.child = spawn(ENV.prefix, [this.#command], {
|
|
98
130
|
cwd,
|
|
99
|
-
|
|
100
|
-
|
|
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
|
|
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
|
-
|
|
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) { }
|
|
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';
|
package/{src → lib}/sh.js
RENAMED
|
@@ -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
|
-
|
|
44
|
-
|
|
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
|
-
* @
|
|
70
|
-
* @
|
|
71
|
-
*
|
|
72
|
-
* @
|
|
73
|
-
* @
|
|
74
|
-
*
|
|
75
|
-
* @
|
|
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';
|
package/{src → lib}/utils.js
RENAMED
|
@@ -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;
|
package/package.json
CHANGED
|
@@ -1,35 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@j-o-r/sh",
|
|
3
|
-
"author": "Jorrit Duin <
|
|
3
|
+
"author": "Jorrit Duin <j-o-r@duin.work>",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "0.0.
|
|
6
|
-
"description": "Execute shell commands from javascript",
|
|
7
|
-
"main": "
|
|
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": ">=
|
|
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://
|
|
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://
|
|
29
|
+
"url": "https://codeberg.org/duin/sh/issues"
|
|
29
30
|
},
|
|
30
|
-
"homepage": "https://
|
|
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
|