@j-o-r/sh 1.1.4 → 1.1.6

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
@@ -31,27 +31,34 @@ import SHDispatch from './SHDispatch.js';
31
31
  import Test from './Test.js'
32
32
 
33
33
  /**
34
- * @typedef {Function} RejectCallback
35
- * @param {Error} error - The error object passed to the callback.
36
- */
34
+ * @typedef {Object.<string, string>} ArgsObject
35
+ * @property {string} [key: string] - Any string key maps to an object
36
+ * @property {string[]} _ - Array of strings, unnamed parameters
37
+ * @description Parsed parameters result.
38
+ */
39
+
37
40
  /**
38
- * @typedef {Function} ResolveCallback
39
- * @param {any} [param] - Optional callback any value
40
- */
41
+ * @typedef {Function} RejectCallback
42
+ * @param {Error} error - The error object passed to the callback.
43
+ */
41
44
  /**
42
- * Creates a new SHDispatch object that represents a command to be executed.
43
- *
44
- * @typedef {Function} Shell
45
- * @property {function(Array, ...*): ProcessPromise} execute - The function to execute the command.
46
- *
47
- * @param {Array} pieces - An array of string literals from a template literal.
48
- * @param {...*} args - The values to be interpolated into the string literals.
49
- * @returns {SHDispatch} Trigger for the command.
50
- * @throws {Error} Throws an error if any of the string literals in `pieces` is undefined.
51
- *
52
- * @example
53
- * const command = await SH`echo 'Hello, world!'`.run();
54
- */
45
+ * @typedef {Function} ResolveCallback
46
+ * @param {any} [param] - Optional callback any value
47
+ */
48
+ /**
49
+ * Creates a new SHDispatch object that represents a command to be executed.
50
+ *
51
+ * @typedef {Function} Shell
52
+ * @property {function(Array, ...*): ProcessPromise} execute - The function to execute the command.
53
+ *
54
+ * @param {Array} pieces - An array of string literals from a template literal.
55
+ * @param {...*} args - The values to be interpolated into the string literals.
56
+ * @returns {SHDispatch} Trigger for the command.
57
+ * @throws {Error} Throws an error if any of the string literals in `pieces` is undefined.
58
+ *
59
+ * @example
60
+ * const command = await SH`echo 'Hello, world!'`.run();
61
+ */
55
62
 
56
63
  /**
57
64
  * Determine a javascript type
@@ -259,7 +266,7 @@ function* expBackoff(max = '60s', rand = '100ms') {
259
266
  * All unrecognized arguments are collected in an array under the `_` property.
260
267
  *
261
268
  * @param {string[]} args - An array of command-line arguments.
262
- * @returns {object} An object where:
269
+ * @returns {ArgsObject} An object where:
263
270
  * - Each key corresponds to an argument that starts with `--`,
264
271
  * - The value is either the next argument or `true` if no value is provided,
265
272
  * - The `_` property contains an array of unbound arguments.
package/lib/SHDispatch.js CHANGED
@@ -15,10 +15,8 @@ import SHExec from './SHExecute.js';
15
15
  * @property {string} [cwd] - The current working directory.
16
16
  * @property {NodeJS.ProcessEnv} [env] - The environment variables.
17
17
  * @property {string} [shell] - The shell to use for execution.
18
- * @property {string} [prefix] - The prefix commands to ensure a safe execution environment. e.g: prefix: 'set -euo pipefail;/usr/bin/env',
19
18
  * @property {StdioOptions|StdioOption} [stdio] - The stdio configuration.
20
19
  * @property {number} [timeout] - default 20000: a timeout error is triggerd when a process execution time is exceeded, 0 is no timeout
21
- * @property {boolean} [detached] - default false, when true it runnes as a background process
22
20
  */
23
21
  /**
24
22
  * @typedef {('pipe' | 'ignore' | 'inherit' | number)} StdioOption
@@ -45,39 +43,17 @@ import SHExec from './SHExecute.js';
45
43
  * - 'inherit': Inherit all stdio streams from the parent.
46
44
  */
47
45
  /**
48
- * 'Code Safe' has own prop
49
- *
50
- * @param {any} o - object to examine
51
- * @param {string} p - property to look for
52
- * @returns {boolean}
53
- */
54
- const hasProp = (o, p) => {
55
- if (typeof o === 'undefined') {
56
- return false;
57
- }
58
- return Object.prototype.hasOwnProperty.call(o, p);
59
- };
60
-
61
- /**
62
- * Merge property values while maintaining the fixed set of props in the original object
63
- * @param {SHOptions} predefined - original object
64
- * @param {SHOptions} options - object with new values
46
+ * Merge property values while maintaining the fixed set of props from the predefined object
47
+ * @param {SHOptions} predefined - options
48
+ * @param {import('child_process').SpawnOptions | import('child_process').SpawnSyncOptions} options
65
49
  * @returns {SHOptions}
66
50
  */
67
51
  const mergeOptions = (predefined, options) => {
68
- // Extract the keys from the predefined object
69
52
  const keys = Object.keys(predefined);
70
-
71
- // Use reduce to accumulate only the predefined properties from sourceObj
72
53
  const mergedObj = keys.reduce((acc, key) => {
73
- if (hasProp(options, key)) {
74
- acc[key] = options[key];
75
- } else {
76
- acc[key] = predefined[key];
77
- }
54
+ acc[key] = options[key] !== undefined ? options[key] : predefined[key];
78
55
  return acc;
79
56
  }, {});
80
-
81
57
  return mergedObj;
82
58
  }
83
59
 
@@ -87,8 +63,6 @@ const defaultOptions = {
87
63
  cwd: process.cwd(),
88
64
  env: process.env,
89
65
  shell: 'bash',
90
- detached: false,
91
- prefix: '/usr/bin/env',
92
66
  stdio: ['inherit', 'pipe', 'pipe'],
93
67
  timeout: 10000 // when 0 there is no timeout
94
68
  };
@@ -96,6 +70,8 @@ const defaultOptions = {
96
70
 
97
71
 
98
72
  class SHDispatch {
73
+ // #prefix = 'set -euo pipefail;/usr/bin/env'
74
+ #prefix = 'set -euo pipefail;/usr/bin/env -S'
99
75
  #cmd = '';
100
76
  #options = {};
101
77
  /**
@@ -106,14 +82,21 @@ class SHDispatch {
106
82
  * @param {string} cmd - cmd to execute
107
83
  */
108
84
  constructor(cmd) {
85
+ if (!cmd || cmd === '') {
86
+ throw new Error('Undefined command');
87
+ }
109
88
  this.#cmd = cmd;
110
89
  this.#options = defaultOptions
111
90
  }
112
91
  /**
113
- * @param {SHOptions} options
92
+ * @param {import('child_process').SpawnOptions | import('child_process').SpawnSyncOptions} options
93
+ * @param {string} [prefix] - command prefix e.g (default) '/usr/bin/env'
114
94
  * @returns {SHDispatch}
115
95
  */
116
- options(options) {
96
+ options(options, prefix) {
97
+ if (prefix && typeof prefix === 'string') {
98
+ this.#prefix = prefix;
99
+ }
117
100
  if (options.stdio && typeof options.stdio === 'string') {
118
101
  // convert stdio to array
119
102
  // This sets the default io values
@@ -129,18 +112,17 @@ class SHDispatch {
129
112
  * @returns {Promise<string>}
130
113
  */
131
114
  run(payload) {
132
- this.#proc = new SHExec(this.#cmd, this.#options);
115
+ this.#proc = new SHExec(this.#cmd, this.#prefix, this.#options);
133
116
  return this.#proc.run(payload);
134
117
  }
135
118
 
136
119
  /**
137
120
  * Works for screen takeovers like editors
138
121
  * @param {string} [payload]
139
- * @returns {SpawnSyncResponse}
122
+ * @returns {import('child_process').SpawnSyncReturns}
140
123
  */
141
124
  runSync(payload) {
142
- // @ts-ignore
143
- return new SHExec(this.#cmd, this.#options).runSync(payload);
125
+ return new SHExec(this.#cmd, this.#prefix, this.#options).runSync(payload);
144
126
  }
145
127
  async kill(signal = 'SIGTERM') {
146
128
  let res = [];
package/lib/SHExecute.js CHANGED
@@ -3,7 +3,7 @@ import { spawnSync, spawn, exec } from 'node:child_process';
3
3
  /**
4
4
  * Kills a process and all child processes of a given process ID in Linux/Posix.
5
5
  * @param {number} processPid - The process ID.
6
- * @param {string} signal - Signal to send.
6
+ * @param {string|number} signal - Signal to send.
7
7
  * @retruns {Promise<number[]>} array with killed pid numbers
8
8
  */
9
9
  const killProcesses = (processPid, signal) => {
@@ -45,20 +45,24 @@ const killProcesses = (processPid, signal) => {
45
45
  }
46
46
 
47
47
  class SHExecute {
48
+ #forcedKill = false;
48
49
  /**
49
50
  * @type {import('child_process').ChildProcess}
50
51
  */
51
- #forcedKill = false;
52
52
  #proc;
53
+ #prefix = '';
53
54
  #command = '';
55
+ /** @type {import('child_process').SpawnOptions | import('child_process').SpawnSyncOptions} */
54
56
  #options = {};
55
57
  #stdout = '';
56
58
  #stderr = '';
57
59
  /**
58
60
  * @param {string} command - linux command to be executed
59
- * @param {import('./SHDispatch').SHOptions} [options] - ChildProcess options
61
+ * @param {string} prefix - command prefix (bash, sh etc.)
62
+ * @param {import('child_process').SpawnOptions | import('child_process').SpawnSyncOptions} options
60
63
  */
61
- constructor(command, options = {}) {
64
+ constructor(command, prefix, options = {}) {
65
+ this.#prefix = prefix;
62
66
  this.#command = command;
63
67
  this.#options = options;
64
68
  this.#proc = null;
@@ -73,19 +77,16 @@ class SHExecute {
73
77
  if (payload && typeof payload !== 'string') {
74
78
  throw new Error('Argument is not a string');
75
79
  }
76
- let { cwd, shell, env, stdio, detached } = this.#options;
80
+ /** @type {import('node:child_process').SpawnSyncOptions} */
81
+ const options = this.#options;
77
82
  // pipe need to be set on stdin when posting a payload
78
- if (payload) stdio[0] = 'pipe';
83
+ // @ts-ignore
84
+ if (payload) options['stdio'][0] = 'pipe';
79
85
  const input = payload || undefined;
80
- return spawnSync(this.#options.prefix, [this.#command], {
81
- cwd,
82
- shell,
83
- stdio,
84
- detached,
85
- windowsHide: true,
86
- env,
87
- input
88
- });
86
+ if (input) {
87
+ options.input = input
88
+ }
89
+ return spawnSync(this.#prefix, [this.#command], options);
89
90
  }
90
91
  /**
91
92
  * @param {string} [payload] - data to write
@@ -99,16 +100,12 @@ class SHExecute {
99
100
  if (this.#options.timeout) {
100
101
  to = this.#options.timeout;
101
102
  }
102
- let { cwd, shell, env, stdio } = this.#options;
103
+ /** @type {import('node:child_process').SpawnOptions} */
104
+ const options = this.#options;
103
105
  // pipe need to be set on stdin when posting a payload
104
- if (payload) stdio[0] = 'pipe';
105
- this.#proc = spawn(this.#options.prefix, [this.#command], {
106
- cwd,
107
- shell,
108
- stdio,
109
- windowsHide: true,
110
- env,
111
- });
106
+ // @ts-ignore
107
+ if (payload) options.stdio[0] = 'pipe';
108
+ this.#proc = spawn(this.#prefix, [this.#command], options);
112
109
  this.#proc.stdout?.on('data', (data) => {
113
110
  this.#stdout += data;
114
111
  });
@@ -148,7 +145,7 @@ class SHExecute {
148
145
  }
149
146
  /**
150
147
  * Kill this process and possible child processes
151
- * @param {string} signal - kill signal
148
+ * @param {number | string} signal - kill signal
152
149
  * @returns {Promise<number[]>}
153
150
  */
154
151
  async kill(signal = 'SIGTERM') {
@@ -163,6 +160,7 @@ class SHExecute {
163
160
  if (!res.includes(this.#proc.pid)) {
164
161
  // Kill self if I am not allready killed
165
162
  res.push(this.#proc.pid);
163
+ // @ts-ignore
166
164
  this.#proc.kill(signal);
167
165
  }
168
166
  this.#proc = undefined;
package/lib/Test.js CHANGED
@@ -1,5 +1,7 @@
1
- import assert from 'node:assert/strict';
2
1
  import { jsType } from './SH.js'
2
+ /**
3
+ * Valid jsTypes for test methods
4
+ */
3
5
  const FNC = ['Function', 'AsyncFunction'];
4
6
  // Settle async calls in a SYNC function
5
7
  const SETTLE_ASYNC = 50;
@@ -53,6 +55,9 @@ class Test {
53
55
  #tests = [];
54
56
  /** @type {testReport[]} */
55
57
  #reports = [];
58
+ /**
59
+ * @type {Error[]}
60
+ */
56
61
  #errors = [];
57
62
  /** verbosed **/
58
63
  #quite = false;
@@ -106,7 +111,7 @@ class Test {
106
111
  * @returns {Test}
107
112
  */
108
113
  add(description, callback) {
109
- if (jsType(description) !== 'String') {
114
+ if (typeof(description) !== 'string') {
110
115
  throw new Error(`'description' should be a string`)
111
116
  }
112
117
  if (!FNC.includes(jsType(callback))) {
@@ -205,9 +210,8 @@ class Test {
205
210
  this.#errors = [];
206
211
  }
207
212
  /**
208
- * @private
209
213
  * Handle an error for the current test
210
- * @param {Error}
214
+ * @param {Error} err
211
215
  * @param {boolean} [outside] default false, Error is catched ouside the callscope of the test
212
216
  */
213
217
  #handleError(err, outside = false) {
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.4",
5
+ "version": "1.1.6",
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",
@@ -14,7 +14,7 @@
14
14
  "test:sh": "scenarios/sh.js",
15
15
  "publish": "npm run release && npm publish --access public",
16
16
  "release": "npm pack --pack-destination=release",
17
- "types": "tsc",
17
+ "types": "tsc -p tsc.json",
18
18
  "clear:types": "rm types/*.d.ts"
19
19
  },
20
20
  "repository": {
package/types/SH.d.ts CHANGED
@@ -1,3 +1,6 @@
1
+ export type ArgsObject = {
2
+ [x: string]: string;
3
+ };
1
4
  export type RejectCallback = Function;
2
5
  export type ResolveCallback = Function;
3
6
  /**
@@ -83,34 +86,40 @@ export function expBackoff(max?: string | undefined, rand?: string | undefined):
83
86
  * All unrecognized arguments are collected in an array under the `_` property.
84
87
  *
85
88
  * @param {string[]} args - An array of command-line arguments.
86
- * @returns {object} An object where:
89
+ * @returns {ArgsObject} An object where:
87
90
  * - Each key corresponds to an argument that starts with `--`,
88
91
  * - The value is either the next argument or `true` if no value is provided,
89
92
  * - The `_` property contains an array of unbound arguments.
90
93
  */
91
- export function parseArgs(args: string[]): object;
94
+ export function parseArgs(args: string[]): ArgsObject;
92
95
  /**
93
- * @typedef {Function} RejectCallback
94
- * @param {Error} error - The error object passed to the callback.
95
- */
96
+ * @typedef {Object.<string, string>} ArgsObject
97
+ * @property {string} [key: string] - Any string key maps to an object
98
+ * @property {string[]} _ - Array of strings, unnamed parameters
99
+ * @description Parsed parameters result.
100
+ */
96
101
  /**
97
- * @typedef {Function} ResolveCallback
98
- * @param {any} [param] - Optional callback any value
99
- */
102
+ * @typedef {Function} RejectCallback
103
+ * @param {Error} error - The error object passed to the callback.
104
+ */
100
105
  /**
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
- */
106
+ * @typedef {Function} ResolveCallback
107
+ * @param {any} [param] - Optional callback any value
108
+ */
109
+ /**
110
+ * Creates a new SHDispatch object that represents a command to be executed.
111
+ *
112
+ * @typedef {Function} Shell
113
+ * @property {function(Array, ...*): ProcessPromise} execute - The function to execute the command.
114
+ *
115
+ * @param {Array} pieces - An array of string literals from a template literal.
116
+ * @param {...*} args - The values to be interpolated into the string literals.
117
+ * @returns {SHDispatch} Trigger for the command.
118
+ * @throws {Error} Throws an error if any of the string literals in `pieces` is undefined.
119
+ *
120
+ * @example
121
+ * const command = await SH`echo 'Hello, world!'`.run();
122
+ */
114
123
  /**
115
124
  * Determine a javascript type
116
125
  *
@@ -41,10 +41,6 @@ export type SHOptions = {
41
41
  * - The shell to use for execution.
42
42
  */
43
43
  shell?: string | undefined;
44
- /**
45
- * - The prefix commands to ensure a safe execution environment. e.g: prefix: 'set -euo pipefail;/usr/bin/env',
46
- */
47
- prefix?: string | undefined;
48
44
  /**
49
45
  * - The stdio configuration.
50
46
  */
@@ -53,10 +49,6 @@ export type SHOptions = {
53
49
  * - default 20000: a timeout error is triggerd when a process execution time is exceeded, 0 is no timeout
54
50
  */
55
51
  timeout?: number | undefined;
56
- /**
57
- * - default false, when true it runnes as a background process
58
- */
59
- detached?: boolean | undefined;
60
52
  };
61
53
  export type StdioOption = ("pipe" | "ignore" | "inherit" | number);
62
54
  export type StdioOptions = Array<StdioOption> | StdioOption;
@@ -66,10 +58,11 @@ declare class SHDispatch {
66
58
  */
67
59
  constructor(cmd: string);
68
60
  /**
69
- * @param {SHOptions} options
61
+ * @param {import('child_process').SpawnOptions | import('child_process').SpawnSyncOptions} options
62
+ * @param {string} [prefix] - command prefix e.g (default) '/usr/bin/env'
70
63
  * @returns {SHDispatch}
71
64
  */
72
- options(options: SHOptions): SHDispatch;
65
+ options(options: import("child_process").SpawnOptions | import("child_process").SpawnSyncOptions, prefix?: string | undefined): SHDispatch;
73
66
  /**
74
67
  * @param {string} [payload]
75
68
  * @returns {Promise<string>}
@@ -78,9 +71,9 @@ declare class SHDispatch {
78
71
  /**
79
72
  * Works for screen takeovers like editors
80
73
  * @param {string} [payload]
81
- * @returns {SpawnSyncResponse}
74
+ * @returns {import('child_process').SpawnSyncReturns}
82
75
  */
83
- runSync(payload?: string | undefined): SpawnSyncResponse;
76
+ runSync(payload?: string | undefined): import("child_process").SpawnSyncReturns<any>;
84
77
  kill(signal?: string): Promise<number[]>;
85
78
  #private;
86
79
  }
@@ -2,14 +2,15 @@ export default SHExecute;
2
2
  declare class SHExecute {
3
3
  /**
4
4
  * @param {string} command - linux command to be executed
5
- * @param {import('./SHDispatch').SHOptions} [options] - ChildProcess options
5
+ * @param {string} prefix - command prefix (bash, sh etc.)
6
+ * @param {import('child_process').SpawnOptions | import('child_process').SpawnSyncOptions} options
6
7
  */
7
- constructor(command: string, options?: any);
8
+ constructor(command: string, prefix: string, options?: import("child_process").SpawnOptions | import("child_process").SpawnSyncOptions);
8
9
  /**
9
10
  * @param {string} [payload] - data to write
10
11
  * @retuns {Promise<object>}
11
12
  */
12
- runSync(payload?: string | undefined): import("child_process").SpawnSyncReturns<Buffer> & import("child_process").SpawnSyncReturns<string> & import("child_process").SpawnSyncReturns<string | Buffer>;
13
+ runSync(payload?: string | undefined): import("child_process").SpawnSyncReturns<string | Buffer>;
13
14
  /**
14
15
  * @param {string} [payload] - data to write
15
16
  * @retuns {Promise<string>}
@@ -17,9 +18,9 @@ declare class SHExecute {
17
18
  run(payload?: string | undefined): Promise<any>;
18
19
  /**
19
20
  * Kill this process and possible child processes
20
- * @param {string} signal - kill signal
21
+ * @param {number | string} signal - kill signal
21
22
  * @returns {Promise<number[]>}
22
23
  */
23
- kill(signal?: string): Promise<number[]>;
24
+ kill(signal?: number | string): Promise<number[]>;
24
25
  #private;
25
26
  }