@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 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 an sync block
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
- return await callback()
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
- * const res = await sleep('5s');
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
- /** Timeout in ms to settle async code blocks called from sync methods */
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
- * Set the timeout when a synced function is called.
70
- * This settles async code used in a sync function
71
- * and give some time to catch errors (#HACK)
72
- * @param {number} timeout - in MS, default 100
73
- */
74
- syncTimeout(timeout) {
75
- this.#TO = timeout;
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
- if (!this.#quite) process.stdout.write(`${i}. ${cb.description}\n`);
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
- process.stdout.write(`\x1b[31mERROR: ${i}. ${cb.description}\x1b[0m\n`);
149
+ executed = true;
147
150
  if (!errors) {
148
151
  errors = true
149
152
  }
150
153
  error = e;
151
154
  }
152
- const duration = getDuration(start);
153
- if (!this.#quite && !error) process.stdout.write(`Finished in: ${duration} ms\n`);
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 = 1 + executed;
179
- const E = this.#errors[i];
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
- console.log('--------------------------------------------------');
191
- console.log(`Total: ${tests} tests, executed: ${executed} in ${duration} ms - errors: ${errors}`);
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 outside the callstack
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
- // Despite #quite
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[this.#currentTest] = err;
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.2",
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/sh.js",
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
- * const res = await sleep('5s');
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 an sync block
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 100
45
+ * @param {number} timeout - in MS, default 50
42
46
  */
43
47
  syncTimeout(timeout: number): void;
44
48
  /**