@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 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) {
@@ -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
- if (!this.#quite) process.stdout.write(`${i}. ${cb.description}\n`);
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
- process.stdout.write(`\x1b[31mERROR: ${i}. ${cb.description}\x1b[0m\n`);
151
+ executed = true;
147
152
  if (!errors) {
148
153
  errors = true
149
154
  }
150
155
  error = e;
151
156
  }
152
- const duration = getDuration(start);
153
- if (!this.#quite && !error) process.stdout.write(`Finished in: ${duration} ms\n`);
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 = 1 + executed;
179
- const E = this.#errors[i];
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
- console.log('--------------------------------------------------');
191
- console.log(`Total: ${tests} tests, executed: ${executed} in ${duration} ms - errors: ${errors}`);
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 outside the callstack
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
- // Despite #quite
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[this.#currentTest] = err;
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.2",
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/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
  /**
@@ -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): void;
55
+ add(description: string, callback: Function | AsyncFunction): Test;
51
56
  /**
52
57
  * Execute tests
53
58
  * @param {number[]} [execute] - limit the execution tests