@j-o-r/sh 1.1.2 → 1.1.4
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 +49 -36
- package/package.json +3 -2
- package/types/SH.d.ts +32 -4
- package/types/Test.d.ts +7 -2
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) {
|
|
@@ -102,6 +103,7 @@ class Test {
|
|
|
102
103
|
* @param {string} description
|
|
103
104
|
* @param {Function|AsyncFunction} callback - sync / async function
|
|
104
105
|
* @throws Error when conditions are not met
|
|
106
|
+
* @returns {Test}
|
|
105
107
|
*/
|
|
106
108
|
add(description, callback) {
|
|
107
109
|
if (jsType(description) !== 'String') {
|
|
@@ -111,6 +113,7 @@ class Test {
|
|
|
111
113
|
throw new Error(`'callback' should be a (async) Function`)
|
|
112
114
|
}
|
|
113
115
|
this.#tests.push({ description, callback });
|
|
116
|
+
return this;
|
|
114
117
|
}
|
|
115
118
|
/**
|
|
116
119
|
* Execute tests
|
|
@@ -126,36 +129,38 @@ class Test {
|
|
|
126
129
|
for (; i < len; i++) {
|
|
127
130
|
this.#currentTest = i;
|
|
128
131
|
if (execute && !execute.includes(i)) continue;
|
|
132
|
+
let duration = 0;
|
|
129
133
|
let start = getNow();
|
|
134
|
+
let executed = false;
|
|
130
135
|
const cb = this.#tests[i];
|
|
131
136
|
const type = jsType(cb.callback);
|
|
137
|
+
this.#reports[i] = { description: cb.description, duration, executed };
|
|
132
138
|
let error;
|
|
133
139
|
try {
|
|
134
|
-
|
|
140
|
+
if (!this.#quite) process.stdout.write(`${i}. ${cb.description} `);
|
|
141
|
+
await Promise.resolve(cb.callback());
|
|
142
|
+
executed = true;
|
|
135
143
|
if (type === 'Function') {
|
|
136
|
-
cb.callback();
|
|
137
144
|
// To settle async calls in sync functions
|
|
138
|
-
// (catching errors outside this call stack, that may throw later)
|
|
145
|
+
// (catching errors outside this call stack, that may throw later (=== bad practise))
|
|
139
146
|
await new Promise(resolve => setTimeout(resolve, this.#TO)); // This will pause the the current loop.
|
|
140
147
|
// add timeout for an honest execution time
|
|
141
148
|
start = start + this.#TO;
|
|
142
|
-
} else {
|
|
143
|
-
await cb.callback();
|
|
144
149
|
}
|
|
145
150
|
} catch (e) {
|
|
146
|
-
|
|
151
|
+
executed = true;
|
|
147
152
|
if (!errors) {
|
|
148
153
|
errors = true
|
|
149
154
|
}
|
|
150
155
|
error = e;
|
|
151
156
|
}
|
|
152
|
-
|
|
153
|
-
if (!this.#quite && !error) process.stdout.write(`
|
|
154
|
-
|
|
157
|
+
duration = getDuration(start);
|
|
158
|
+
if (!this.#quite && !error) process.stdout.write(`(duration: ${duration} ms)\n`);
|
|
159
|
+
this.#reports[i].duration = duration;
|
|
160
|
+
this.#reports[i].executed = executed;
|
|
155
161
|
if (error) {
|
|
156
162
|
this.#handleError(error);
|
|
157
163
|
}
|
|
158
|
-
this.#reports[i] = { description: cb.description, duration };
|
|
159
164
|
}
|
|
160
165
|
this.#currentTest = -1;
|
|
161
166
|
this.#detectErrors(false);
|
|
@@ -167,7 +172,6 @@ class Test {
|
|
|
167
172
|
#report() {
|
|
168
173
|
let duration = 0;
|
|
169
174
|
let executed = 0;
|
|
170
|
-
let errors = 0;
|
|
171
175
|
const tests = this.#tests.length;
|
|
172
176
|
let i = 0;
|
|
173
177
|
const len = this.#reports.length;
|
|
@@ -175,22 +179,21 @@ class Test {
|
|
|
175
179
|
const r = this.#reports[i];
|
|
176
180
|
if (r) {
|
|
177
181
|
duration = r.duration + duration;
|
|
178
|
-
executed
|
|
179
|
-
|
|
180
|
-
if (E) {
|
|
181
|
-
errors = 1 + errors;
|
|
182
|
+
if (r.executed) {
|
|
183
|
+
executed = 1 + executed;
|
|
182
184
|
}
|
|
183
185
|
}
|
|
184
186
|
|
|
185
187
|
}
|
|
188
|
+
const errors = this.#errors.length;
|
|
186
189
|
if (tests !== executed) {
|
|
187
190
|
if (!this.#quite) console.log('** Not all tests have been executed **');
|
|
188
191
|
}
|
|
189
192
|
if (!this.#quite) {
|
|
190
|
-
|
|
191
|
-
|
|
193
|
+
console.log('--------------------------------------------------');
|
|
194
|
+
console.log(`Total: ${tests} tests, executed: ${executed} in ${duration} ms - errors: ${errors}`);
|
|
192
195
|
}
|
|
193
|
-
return {tests, executed, duration, errors};
|
|
196
|
+
return { tests, executed, duration, errors };
|
|
194
197
|
}
|
|
195
198
|
|
|
196
199
|
/**
|
|
@@ -203,17 +206,27 @@ class Test {
|
|
|
203
206
|
}
|
|
204
207
|
/**
|
|
205
208
|
* @private
|
|
206
|
-
* Handle an error
|
|
207
|
-
* for the current test
|
|
209
|
+
* Handle an error for the current test
|
|
208
210
|
* @param {Error}
|
|
211
|
+
* @param {boolean} [outside] default false, Error is catched ouside the callscope of the test
|
|
209
212
|
*/
|
|
210
|
-
#handleError(err) {
|
|
213
|
+
#handleError(err, outside = false) {
|
|
214
|
+
// A global error is an error catched outside the callstack of the test
|
|
215
|
+
const ERR = outside? 'GLOBAL_ERROR' : 'ERROR'
|
|
211
216
|
if (this.#currentTest > -1) {
|
|
217
|
+
if (!this.#quite) process.stdout.write(`\n`);
|
|
212
218
|
// Register this error
|
|
213
|
-
// Always print out errors
|
|
214
|
-
|
|
219
|
+
// Always print out errors despite #quite
|
|
220
|
+
const description = this.#reports[this.#currentTest].description;
|
|
221
|
+
const executed = this.#reports[this.#currentTest].executed;
|
|
222
|
+
if (executed) {
|
|
223
|
+
process.stdout.write(`\x1b[31m-- ${ERR} Test: ${this.#currentTest}. ${description} --\x1b[0m\n`);
|
|
224
|
+
} else {
|
|
225
|
+
// Can been thrown from an other test
|
|
226
|
+
process.stdout.write(`\x1b[31m-- ${ERR} --\x1b[0m\n`);
|
|
227
|
+
}
|
|
215
228
|
console.error(err);
|
|
216
|
-
this.#errors
|
|
229
|
+
this.#errors.push(err);
|
|
217
230
|
}
|
|
218
231
|
}
|
|
219
232
|
}
|
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.4",
|
|
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
|
/**
|
|
@@ -46,8 +50,9 @@ declare class Test {
|
|
|
46
50
|
* @param {string} description
|
|
47
51
|
* @param {Function|AsyncFunction} callback - sync / async function
|
|
48
52
|
* @throws Error when conditions are not met
|
|
53
|
+
* @returns {Test}
|
|
49
54
|
*/
|
|
50
|
-
add(description: string, callback: Function | AsyncFunction):
|
|
55
|
+
add(description: string, callback: Function | AsyncFunction): Test;
|
|
51
56
|
/**
|
|
52
57
|
* Execute tests
|
|
53
58
|
* @param {number[]} [execute] - limit the execution tests
|