@j-o-r/sh 1.1.2 → 1.1.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/lib/SH.js +45 -16
- package/lib/Test.js +47 -36
- package/package.json +3 -2
- package/types/SH.d.ts +32 -4
- package/types/Test.d.ts +5 -1
package/lib/SH.js
CHANGED
|
@@ -29,6 +29,15 @@
|
|
|
29
29
|
import assert from 'node:assert';
|
|
30
30
|
import SHDispatch from './SHDispatch.js';
|
|
31
31
|
import Test from './Test.js'
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @typedef {Function} RejectCallback
|
|
35
|
+
* @param {Error} error - The error object passed to the callback.
|
|
36
|
+
*/
|
|
37
|
+
/**
|
|
38
|
+
* @typedef {Function} ResolveCallback
|
|
39
|
+
* @param {any} [param] - Optional callback any value
|
|
40
|
+
*/
|
|
32
41
|
/**
|
|
33
42
|
* Creates a new SHDispatch object that represents a command to be executed.
|
|
34
43
|
*
|
|
@@ -44,6 +53,17 @@ import Test from './Test.js'
|
|
|
44
53
|
* const command = await SH`echo 'Hello, world!'`.run();
|
|
45
54
|
*/
|
|
46
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Determine a javascript type
|
|
58
|
+
*
|
|
59
|
+
* @param {any} fn - Any let type
|
|
60
|
+
* @returns {string} The "real" object / typeof name
|
|
61
|
+
*/
|
|
62
|
+
const jsType = (fn) => {
|
|
63
|
+
if (fn === undefined) return 'undefined';
|
|
64
|
+
const type = Object.prototype.toString.call(fn).slice(8, -1);
|
|
65
|
+
return type === 'Object' ? fn.constructor.name : type;
|
|
66
|
+
};
|
|
47
67
|
/**
|
|
48
68
|
* 4ms, 5s || 5
|
|
49
69
|
* @param {number|string} d
|
|
@@ -81,9 +101,12 @@ const SH = new Proxy(function(pieces, ...args) {
|
|
|
81
101
|
}
|
|
82
102
|
return new SHDispatch(cmd);
|
|
83
103
|
}, {});
|
|
104
|
+
|
|
84
105
|
/**
|
|
85
|
-
* Create a async context in
|
|
86
|
-
* @param {function} callback - async function
|
|
106
|
+
* Create a async/sync context in new execution callstack
|
|
107
|
+
* @param {function} callback - async/sync function
|
|
108
|
+
* @param {ResolveCallback} [resolve] - optional resolve/result callback
|
|
109
|
+
* @param {RejectCallback} [reject] - optional reject/error callback
|
|
87
110
|
* @example
|
|
88
111
|
* const p = within(async () => {
|
|
89
112
|
* const res = await Promise.all([
|
|
@@ -92,12 +115,29 @@ const SH = new Proxy(function(pieces, ...args) {
|
|
|
92
115
|
* sleep(2),
|
|
93
116
|
* SH`sleep 3; echo 3`.run()
|
|
94
117
|
* ]);
|
|
118
|
+
* return 'res';
|
|
119
|
+
* });
|
|
95
120
|
*/
|
|
96
|
-
const within = (callback) => {
|
|
121
|
+
const within = (callback, resolve, reject) => {
|
|
122
|
+
// optional, custom resolve/reject functions from an outside promise/function
|
|
123
|
+
const RCB = jsType(resolve) === 'Function';
|
|
124
|
+
const ECB = jsType(reject) === 'Function';
|
|
97
125
|
(async () => {
|
|
98
|
-
|
|
126
|
+
try {
|
|
127
|
+
const res = await Promise.resolve(callback());
|
|
128
|
+
if (RCB) {
|
|
129
|
+
resolve(res);
|
|
130
|
+
}
|
|
131
|
+
} catch (e) {
|
|
132
|
+
if (ECB) {
|
|
133
|
+
reject(e)
|
|
134
|
+
} else {
|
|
135
|
+
throw (e);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
99
138
|
})()
|
|
100
139
|
}
|
|
140
|
+
|
|
101
141
|
/**
|
|
102
142
|
* This function reads the standard input (stdin) from the current process.
|
|
103
143
|
* @example
|
|
@@ -178,7 +218,7 @@ const retry = async (count, a, b) => {
|
|
|
178
218
|
*
|
|
179
219
|
* @example
|
|
180
220
|
*
|
|
181
|
-
*
|
|
221
|
+
* await sleep('5s');
|
|
182
222
|
*/
|
|
183
223
|
const sleep = (duration) => {
|
|
184
224
|
return new Promise((resolve) => {
|
|
@@ -239,17 +279,6 @@ const parseArgs = (args) => {
|
|
|
239
279
|
return result;
|
|
240
280
|
}
|
|
241
281
|
|
|
242
|
-
/**
|
|
243
|
-
* Determine a javascript type
|
|
244
|
-
*
|
|
245
|
-
* @param {any} fn - Any let type
|
|
246
|
-
* @returns {string} The "real" object / typeof name
|
|
247
|
-
*/
|
|
248
|
-
const jsType = (fn) => {
|
|
249
|
-
if (fn === undefined) return 'undefined';
|
|
250
|
-
const type = Object.prototype.toString.call(fn).slice(8, -1);
|
|
251
|
-
return type === 'Object' ? fn.constructor.name : type;
|
|
252
|
-
};
|
|
253
282
|
|
|
254
283
|
export {
|
|
255
284
|
SH,
|
package/lib/Test.js
CHANGED
|
@@ -18,6 +18,7 @@ const SETTLE_ASYNC = 50;
|
|
|
18
18
|
* @typedef {Object} testReport
|
|
19
19
|
* @prop {string} description
|
|
20
20
|
* @prop {number} duration - start time in MS
|
|
21
|
+
* @prop {boolean} executed - Has it been called?
|
|
21
22
|
*/
|
|
22
23
|
|
|
23
24
|
/**
|
|
@@ -55,7 +56,7 @@ class Test {
|
|
|
55
56
|
#errors = [];
|
|
56
57
|
/** verbosed **/
|
|
57
58
|
#quite = false;
|
|
58
|
-
|
|
59
|
+
/** Timeout in ms to settle async code blocks called from sync methods */
|
|
59
60
|
#TO = SETTLE_ASYNC;
|
|
60
61
|
/**
|
|
61
62
|
* @param {boolean} [quiet] - does not output a report when true, default `false`
|
|
@@ -65,23 +66,23 @@ class Test {
|
|
|
65
66
|
this.#quite = true;
|
|
66
67
|
}
|
|
67
68
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
69
|
+
/**
|
|
70
|
+
* Set the timeout when a synced function is called.
|
|
71
|
+
* This settles async code used in a sync function
|
|
72
|
+
* and give some time to catch errors (#HACK)
|
|
73
|
+
* @param {number} timeout - in MS, default 50
|
|
74
|
+
*/
|
|
75
|
+
syncTimeout(timeout) {
|
|
76
|
+
this.#TO = timeout;
|
|
77
|
+
}
|
|
78
|
+
|
|
78
79
|
/**
|
|
79
80
|
* Activate a listener on errors outside the call stack scope
|
|
80
81
|
* @param {boolean} active - register / unregister
|
|
81
82
|
*/
|
|
82
83
|
#detectErrors(active = true) {
|
|
83
84
|
const errListener = (err) => {
|
|
84
|
-
this.#handleError(err)
|
|
85
|
+
this.#handleError(err, true);
|
|
85
86
|
}
|
|
86
87
|
if (active) {
|
|
87
88
|
if (!this.#catchErrors) {
|
|
@@ -126,36 +127,38 @@ class Test {
|
|
|
126
127
|
for (; i < len; i++) {
|
|
127
128
|
this.#currentTest = i;
|
|
128
129
|
if (execute && !execute.includes(i)) continue;
|
|
130
|
+
let duration = 0;
|
|
129
131
|
let start = getNow();
|
|
132
|
+
let executed = false;
|
|
130
133
|
const cb = this.#tests[i];
|
|
131
134
|
const type = jsType(cb.callback);
|
|
135
|
+
this.#reports[i] = { description: cb.description, duration, executed };
|
|
132
136
|
let error;
|
|
133
137
|
try {
|
|
134
|
-
|
|
138
|
+
if (!this.#quite) process.stdout.write(`${i}. ${cb.description} `);
|
|
139
|
+
await Promise.resolve(cb.callback());
|
|
140
|
+
executed = true;
|
|
135
141
|
if (type === 'Function') {
|
|
136
|
-
cb.callback();
|
|
137
142
|
// To settle async calls in sync functions
|
|
138
|
-
// (catching errors outside this call stack, that may throw later)
|
|
143
|
+
// (catching errors outside this call stack, that may throw later (=== bad practise))
|
|
139
144
|
await new Promise(resolve => setTimeout(resolve, this.#TO)); // This will pause the the current loop.
|
|
140
145
|
// add timeout for an honest execution time
|
|
141
146
|
start = start + this.#TO;
|
|
142
|
-
} else {
|
|
143
|
-
await cb.callback();
|
|
144
147
|
}
|
|
145
148
|
} catch (e) {
|
|
146
|
-
|
|
149
|
+
executed = true;
|
|
147
150
|
if (!errors) {
|
|
148
151
|
errors = true
|
|
149
152
|
}
|
|
150
153
|
error = e;
|
|
151
154
|
}
|
|
152
|
-
|
|
153
|
-
if (!this.#quite && !error) process.stdout.write(`
|
|
154
|
-
|
|
155
|
+
duration = getDuration(start);
|
|
156
|
+
if (!this.#quite && !error) process.stdout.write(`(duration: ${duration} ms)\n`);
|
|
157
|
+
this.#reports[i].duration = duration;
|
|
158
|
+
this.#reports[i].executed = executed;
|
|
155
159
|
if (error) {
|
|
156
160
|
this.#handleError(error);
|
|
157
161
|
}
|
|
158
|
-
this.#reports[i] = { description: cb.description, duration };
|
|
159
162
|
}
|
|
160
163
|
this.#currentTest = -1;
|
|
161
164
|
this.#detectErrors(false);
|
|
@@ -167,7 +170,6 @@ class Test {
|
|
|
167
170
|
#report() {
|
|
168
171
|
let duration = 0;
|
|
169
172
|
let executed = 0;
|
|
170
|
-
let errors = 0;
|
|
171
173
|
const tests = this.#tests.length;
|
|
172
174
|
let i = 0;
|
|
173
175
|
const len = this.#reports.length;
|
|
@@ -175,22 +177,21 @@ class Test {
|
|
|
175
177
|
const r = this.#reports[i];
|
|
176
178
|
if (r) {
|
|
177
179
|
duration = r.duration + duration;
|
|
178
|
-
executed
|
|
179
|
-
|
|
180
|
-
if (E) {
|
|
181
|
-
errors = 1 + errors;
|
|
180
|
+
if (r.executed) {
|
|
181
|
+
executed = 1 + executed;
|
|
182
182
|
}
|
|
183
183
|
}
|
|
184
184
|
|
|
185
185
|
}
|
|
186
|
+
const errors = this.#errors.length;
|
|
186
187
|
if (tests !== executed) {
|
|
187
188
|
if (!this.#quite) console.log('** Not all tests have been executed **');
|
|
188
189
|
}
|
|
189
190
|
if (!this.#quite) {
|
|
190
|
-
|
|
191
|
-
|
|
191
|
+
console.log('--------------------------------------------------');
|
|
192
|
+
console.log(`Total: ${tests} tests, executed: ${executed} in ${duration} ms - errors: ${errors}`);
|
|
192
193
|
}
|
|
193
|
-
return {tests, executed, duration, errors};
|
|
194
|
+
return { tests, executed, duration, errors };
|
|
194
195
|
}
|
|
195
196
|
|
|
196
197
|
/**
|
|
@@ -203,17 +204,27 @@ class Test {
|
|
|
203
204
|
}
|
|
204
205
|
/**
|
|
205
206
|
* @private
|
|
206
|
-
* Handle an error
|
|
207
|
-
* for the current test
|
|
207
|
+
* Handle an error for the current test
|
|
208
208
|
* @param {Error}
|
|
209
|
+
* @param {boolean} [outside] default false, Error is catched ouside the callscope of the test
|
|
209
210
|
*/
|
|
210
|
-
#handleError(err) {
|
|
211
|
+
#handleError(err, outside = false) {
|
|
212
|
+
// A global error is an error catched outside the callstack of the test
|
|
213
|
+
const ERR = outside? 'GLOBAL_ERROR' : 'ERROR'
|
|
211
214
|
if (this.#currentTest > -1) {
|
|
215
|
+
if (!this.#quite) process.stdout.write(`\n`);
|
|
212
216
|
// Register this error
|
|
213
|
-
// Always print out errors
|
|
214
|
-
|
|
217
|
+
// Always print out errors despite #quite
|
|
218
|
+
const description = this.#reports[this.#currentTest].description;
|
|
219
|
+
const executed = this.#reports[this.#currentTest].executed;
|
|
220
|
+
if (executed) {
|
|
221
|
+
process.stdout.write(`\x1b[31m-- ${ERR} Test: ${this.#currentTest}. ${description} --\x1b[0m\n`);
|
|
222
|
+
} else {
|
|
223
|
+
// Can been thrown from an other test
|
|
224
|
+
process.stdout.write(`\x1b[31m-- ${ERR} --\x1b[0m\n`);
|
|
225
|
+
}
|
|
215
226
|
console.error(err);
|
|
216
|
-
this.#errors
|
|
227
|
+
this.#errors.push(err);
|
|
217
228
|
}
|
|
218
229
|
}
|
|
219
230
|
}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@j-o-r/sh",
|
|
3
3
|
"author": "Jorrit Duin <j-o-r@duin.work>",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "1.1.
|
|
5
|
+
"version": "1.1.3",
|
|
6
6
|
"description": "Execute shell commands on Linux-based systems from javascript",
|
|
7
7
|
"main": "lib/SH.js",
|
|
8
8
|
"types": "types/SH.d.ts",
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
"node": ">=20.0.0"
|
|
11
11
|
},
|
|
12
12
|
"scripts": {
|
|
13
|
-
"test": "test
|
|
13
|
+
"test": "npm run test:sh",
|
|
14
|
+
"test:sh": "scenarios/sh.js",
|
|
14
15
|
"publish": "npm run release && npm publish --access public",
|
|
15
16
|
"release": "npm pack --pack-destination=release",
|
|
16
17
|
"types": "tsc",
|
package/types/SH.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export type RejectCallback = Function;
|
|
2
|
+
export type ResolveCallback = Function;
|
|
1
3
|
/**
|
|
2
4
|
* Creates a new SHDispatch object that represents a command to be executed.
|
|
3
5
|
*/
|
|
@@ -17,7 +19,7 @@ export function cd(dir: string): void;
|
|
|
17
19
|
*
|
|
18
20
|
* @example
|
|
19
21
|
*
|
|
20
|
-
*
|
|
22
|
+
* await sleep('5s');
|
|
21
23
|
*/
|
|
22
24
|
export function sleep(duration: string | number): Promise<any>;
|
|
23
25
|
/**
|
|
@@ -47,8 +49,10 @@ export function retry(count: number, a: string | typeof expBackoff | Function, b
|
|
|
47
49
|
*/
|
|
48
50
|
export function readIn(): Promise<string>;
|
|
49
51
|
/**
|
|
50
|
-
* Create a async context in
|
|
51
|
-
* @param {function} callback - async function
|
|
52
|
+
* Create a async/sync context in new execution callstack
|
|
53
|
+
* @param {function} callback - async/sync function
|
|
54
|
+
* @param {ResolveCallback} [resolve] - optional resolve/result callback
|
|
55
|
+
* @param {RejectCallback} [reject] - optional reject/error callback
|
|
52
56
|
* @example
|
|
53
57
|
* const p = within(async () => {
|
|
54
58
|
* const res = await Promise.all([
|
|
@@ -57,8 +61,10 @@ export function readIn(): Promise<string>;
|
|
|
57
61
|
* sleep(2),
|
|
58
62
|
* SH`sleep 3; echo 3`.run()
|
|
59
63
|
* ]);
|
|
64
|
+
* return 'res';
|
|
65
|
+
* });
|
|
60
66
|
*/
|
|
61
|
-
export function within(callback: Function): void;
|
|
67
|
+
export function within(callback: Function, resolve?: Function | undefined, reject?: Function | undefined): void;
|
|
62
68
|
/**
|
|
63
69
|
* Generates an exponential backoff time with a random jitter.
|
|
64
70
|
*
|
|
@@ -83,6 +89,28 @@ export function expBackoff(max?: string | undefined, rand?: string | undefined):
|
|
|
83
89
|
* - The `_` property contains an array of unbound arguments.
|
|
84
90
|
*/
|
|
85
91
|
export function parseArgs(args: string[]): object;
|
|
92
|
+
/**
|
|
93
|
+
* @typedef {Function} RejectCallback
|
|
94
|
+
* @param {Error} error - The error object passed to the callback.
|
|
95
|
+
*/
|
|
96
|
+
/**
|
|
97
|
+
* @typedef {Function} ResolveCallback
|
|
98
|
+
* @param {any} [param] - Optional callback any value
|
|
99
|
+
*/
|
|
100
|
+
/**
|
|
101
|
+
* Creates a new SHDispatch object that represents a command to be executed.
|
|
102
|
+
*
|
|
103
|
+
* @typedef {Function} Shell
|
|
104
|
+
* @property {function(Array, ...*): ProcessPromise} execute - The function to execute the command.
|
|
105
|
+
*
|
|
106
|
+
* @param {Array} pieces - An array of string literals from a template literal.
|
|
107
|
+
* @param {...*} args - The values to be interpolated into the string literals.
|
|
108
|
+
* @returns {SHDispatch} Trigger for the command.
|
|
109
|
+
* @throws {Error} Throws an error if any of the string literals in `pieces` is undefined.
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* const command = await SH`echo 'Hello, world!'`.run();
|
|
113
|
+
*/
|
|
86
114
|
/**
|
|
87
115
|
* Determine a javascript type
|
|
88
116
|
*
|
package/types/Test.d.ts
CHANGED
|
@@ -13,6 +13,10 @@ export type testReport = {
|
|
|
13
13
|
* - start time in MS
|
|
14
14
|
*/
|
|
15
15
|
duration: number;
|
|
16
|
+
/**
|
|
17
|
+
* - Has it been called?
|
|
18
|
+
*/
|
|
19
|
+
executed: boolean;
|
|
16
20
|
};
|
|
17
21
|
export type Report = {
|
|
18
22
|
tests: number;
|
|
@@ -38,7 +42,7 @@ declare class Test {
|
|
|
38
42
|
* Set the timeout when a synced function is called.
|
|
39
43
|
* This settles async code used in a sync function
|
|
40
44
|
* and give some time to catch errors (#HACK)
|
|
41
|
-
* @param {number} timeout - in MS, default
|
|
45
|
+
* @param {number} timeout - in MS, default 50
|
|
42
46
|
*/
|
|
43
47
|
syncTimeout(timeout: number): void;
|
|
44
48
|
/**
|