@j-o-r/sh 1.1.9 → 1.1.11

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
@@ -98,10 +98,22 @@ const SH = new Proxy(function(pieces, ...args) {
98
98
  while (i < args.length) {
99
99
  let s;
100
100
  if (Array.isArray(args[i])) {
101
- s = args[i].map((x) => x).join(' ');
102
- }
103
- else {
104
- s = args[i];
101
+ s = args[i].map((x) => {
102
+ let str = String(x);
103
+ // If the string contains special characters, wrap in single quotes
104
+ if (str.match(/[ "'$`(){}[\]]/)) {
105
+ // Escape single quotes within the string
106
+ return `'${str.replace(/'/g, `'\\''`)}'`;
107
+ }
108
+ return str;
109
+ }).join(' ');
110
+ } else {
111
+ let str = String(args[i]);
112
+ if (str.match(/[ "'$`(){}[\]]/)) {
113
+ s = `'${str.replace(/'/g, `'\\''`)}'`;
114
+ } else {
115
+ s = str;
116
+ }
105
117
  }
106
118
  cmd += s + pieces[++i];
107
119
  }
@@ -144,7 +156,7 @@ const readIn = async () => {
144
156
  * Retries a given asynchronous function a specified number of times with optional delays between attempts.
145
157
  *
146
158
  * @param {number} count - The number of retry attempts.
147
- * @param {string|expBackoff|Function} a - Either a delay duration as a string, a delay generator object, or the callback function.
159
+ * @param {string|number|expBackoff|Function} a - Either a delay duration as a string, a delay generator object, or the callback function.
148
160
  * @param {Function} [b] - The callback function to retry, required if `a` is not a function.
149
161
  * @returns {Promise<*>} - The result of the callback function if it succeeds within the retry attempts.
150
162
  * @throws {Error} - The last error encountered if all retry attempts fail.
package/lib/Test.js CHANGED
@@ -1,4 +1,6 @@
1
+ import { nextTick } from 'process';
1
2
  import { jsType } from './SH.js'
3
+ import AsyncTracker from '@j-o-r/asynctracker';
2
4
  /**
3
5
  * Valid jsTypes for test methods
4
6
  */
@@ -49,6 +51,8 @@ function getDuration(start) {
49
51
  }
50
52
 
51
53
  class Test {
54
+ /** @type {AsyncTracker} */
55
+ #promiseTracker;
52
56
  #catchErrors = false;
53
57
  #currentTest = -1;
54
58
  /** @type {testDefinition[]} */
@@ -70,6 +74,9 @@ class Test {
70
74
  if (quiet) {
71
75
  this.#quite = true;
72
76
  }
77
+ // Track for unresolved promises
78
+ this.#promiseTracker = new AsyncTracker();
79
+ this.#promiseTracker.enable('PROMISE');
73
80
  }
74
81
  /**
75
82
  * Set the timeout when a synced function is called.
@@ -111,7 +118,7 @@ class Test {
111
118
  * @returns {Test}
112
119
  */
113
120
  add(description, callback) {
114
- if (typeof(description) !== 'string') {
121
+ if (typeof (description) !== 'string') {
115
122
  throw new Error(`'description' should be a string`)
116
123
  }
117
124
  if (!FNC.includes(jsType(callback))) {
@@ -191,12 +198,22 @@ class Test {
191
198
 
192
199
  }
193
200
  const errors = this.#errors.length;
194
- if (tests !== executed) {
195
- if (!this.#quite) console.log('** Not all tests have been executed **');
196
- }
197
201
  if (!this.#quite) {
198
202
  console.log('--------------------------------------------------');
199
203
  console.log(`Total: ${tests} tests, executed: ${executed} in ${duration} ms - errors: ${errors}`);
204
+ if (tests !== executed) {
205
+ if (!this.#quite) console.log('** Not all tests have been executed **');
206
+ return { tests, executed, duration, errors };
207
+ }
208
+ // Report on unresolved promises when NOT quite
209
+ // This must be on a next tick because the current Promise (where this code is in) is not resolved yet
210
+ nextTick(() => {
211
+ const count = this.#promiseTracker.report(true);
212
+ if (count > 0) {
213
+ console.log('--------------------------------------------------');
214
+ console.log(`${count} Unresolved Promises detected.`);
215
+ }
216
+ });
200
217
  }
201
218
  return { tests, executed, duration, errors };
202
219
  }
@@ -216,7 +233,7 @@ class Test {
216
233
  */
217
234
  #handleError(err, outside = false) {
218
235
  // A global error is an error catched outside the callstack of the test
219
- const ERR = outside? 'GLOBAL_ERROR' : 'ERROR'
236
+ const ERR = outside ? 'GLOBAL_ERROR' : 'ERROR'
220
237
  if (this.#currentTest > -1) {
221
238
  if (!this.#quite) process.stdout.write(`\n`);
222
239
  // Register this error
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.9",
5
+ "version": "1.1.11",
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",
@@ -23,6 +23,7 @@
23
23
  },
24
24
  "license": "Apache License, Version 2.0",
25
25
  "dependencies": {
26
+ "@j-o-r/asynctracker": "^0.0.8",
26
27
  "@types/node": "^22.10.10"
27
28
  },
28
29
  "bugs": {
@@ -50,4 +51,4 @@
50
51
  "process-promise",
51
52
  "process-output"
52
53
  ]
53
- }
54
+ }
package/types/SH.d.ts CHANGED
@@ -29,7 +29,7 @@ export function sleep(duration: string | number): Promise<any>;
29
29
  * Retries a given asynchronous function a specified number of times with optional delays between attempts.
30
30
  *
31
31
  * @param {number} count - The number of retry attempts.
32
- * @param {string|expBackoff|Function} a - Either a delay duration as a string, a delay generator object, or the callback function.
32
+ * @param {string|number|expBackoff|Function} a - Either a delay duration as a string, a delay generator object, or the callback function.
33
33
  * @param {Function} [b] - The callback function to retry, required if `a` is not a function.
34
34
  * @returns {Promise<*>} - The result of the callback function if it succeeds within the retry attempts.
35
35
  * @throws {Error} - The last error encountered if all retry attempts fail.
@@ -44,7 +44,7 @@ export function sleep(duration: string | number): Promise<any>;
44
44
  * // Retry a command 3 times with irregular intervals using exponential backoff
45
45
  * const p = await retry(3, expBackoff(), () => SH`curl -s https://flipwrsi`);
46
46
  */
47
- export function retry(count: number, a: string | typeof expBackoff | Function, b?: Function): Promise<any>;
47
+ export function retry(count: number, a: string | number | typeof expBackoff | Function, b?: Function): Promise<any>;
48
48
  /**
49
49
  * This function reads the standard input (stdin) from the current process.
50
50
  * @example