@j-o-r/sh 1.0.0 → 1.0.2

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/README.md CHANGED
@@ -75,7 +75,7 @@ This class is returned by the `SH` function. Here's a summary of its methods and
75
75
 
76
76
  - **options(options)**: Sets options for the command execution.
77
77
  - **run(payload?)**: Executes the command and returns a promise that resolves with the command's output.
78
- - **runSync()**: Executes the command synchronously and returns a `SpawnSyncResponse`.
78
+ - **runSync(payload?)**: Executes the command synchronously and returns a `SpawnSyncResponse`.
79
79
  - **kill()**: Sends a kill signal to the child process.
80
80
 
81
81
  ### Examples
@@ -113,7 +113,26 @@ This class is returned by the `SH` function. Here's a summary of its methods and
113
113
  console.error('Retry failed:', e);
114
114
  }
115
115
  ```
116
+ - Method for copying data to the clipboard:
117
+ ```javascript
118
+ /**
119
+ * Copy text to the clipboard
120
+ * @param {string} text
121
+ * @retruns {Promise<string>}
122
+ */
123
+ const copyToClipboard = async (text) => {
124
+ const prams = [
125
+ '-selection',
126
+ 'clipboard'
127
+ ]
128
+ return SH`xclip ${prams}`.options({stdio: 'inherit'}).run(text);
129
+ }
130
+ ```
116
131
 
132
+ - Open the 'vim' editor
133
+ ```javascript
134
+ SH`vim`.options({stdio: 'inherit'}).runSync();
135
+ ```
117
136
  ## License
118
137
 
119
138
  This project is licensed under the Apache License, Version 2.0.
package/lib/sh.d.ts CHANGED
@@ -47,7 +47,11 @@ export type SHOptions = {
47
47
  /**
48
48
  * - The stdio configuration.
49
49
  */
50
- stdio?: StdioOption | StdioOptions;
50
+ stdio?: StdioOptions | StdioOption;
51
+ /**
52
+ * - default 20000: a timeout error is triggerd when a process execution time is exceeded, 0 is no timeout
53
+ */
54
+ timeout?: number;
51
55
  };
52
56
  export type StdioOption = ('pipe' | 'ignore' | 'inherit' | number);
53
57
  export type StdioOptions = Array<StdioOption> | StdioOption;
@@ -138,9 +142,10 @@ declare class SHDispatch {
138
142
  run(payload?: string): Promise<string>;
139
143
  /**
140
144
  * Works for screen takeovers like editors
145
+ * @param {string} [payload]
141
146
  * @returns {SpawnSyncResponse}
142
147
  */
143
- runSync(): SpawnSyncResponse;
148
+ runSync(payload?: string): SpawnSyncResponse;
144
149
  kill(): Promise<void>;
145
150
  #private;
146
151
  }
package/lib/sh.js CHANGED
@@ -1,5 +1,8 @@
1
+ import assert from 'node:assert';
1
2
  import { spawnSync, spawn, exec } from 'node:child_process';
2
3
 
4
+ // timeout when a process becomes inresponsive
5
+
3
6
  /**
4
7
  * Kills a process and all child processes of a given process ID in Linux/Posix.
5
8
  * @param {number} processPid - The process ID.
@@ -45,11 +48,11 @@ const killProcesses = (processPid, signal) => {
45
48
  };
46
49
 
47
50
  class SHExecute {
48
- #proc;
49
- #command = '';
50
- #options = {};
51
- #stdout = '';
52
- #stderr = '';
51
+ #proc;
52
+ #command = '';
53
+ #options = {};
54
+ #stdout = '';
55
+ #stderr = '';
53
56
  constructor(command, options = {}) {
54
57
  this.#command = command;
55
58
  this.#options = options;
@@ -57,26 +60,42 @@ class SHExecute {
57
60
  this.#stdout = '';
58
61
  this.#stderr = '';
59
62
  }
60
- runSync () {
61
- let { cwd, shell, env, stdio } = this.#options;
63
+ /**
64
+ * @param {string} [payload] - data to write
65
+ * @retuns {Promise<object>}
66
+ */
67
+ runSync(payload) {
68
+ if (payload && typeof payload !== 'string') {
69
+ throw new Error('Argument is not a string');
70
+ }
71
+ let { cwd, shell, env, stdio } = this.#options;
72
+ // pipe need to be set on stdin when posting a payload
73
+ if (payload) stdio[0] = 'pipe';
74
+ const input = payload || undefined;
62
75
  return spawnSync(this.#options.prefix, [this.#command], {
63
76
  cwd,
64
77
  shell,
65
78
  stdio,
66
79
  windowsHide: true,
67
80
  env,
81
+ input
68
82
  });
69
83
  }
70
- /**
71
- * @param {string} [payload] - data to write
72
- * @retuns {Promise<string>}
73
- */
84
+ /**
85
+ * @param {string} [payload] - data to write
86
+ * @retuns {Promise<string>}
87
+ */
74
88
  run(payload) {
89
+ let to = 0;
75
90
  if (payload && typeof payload !== 'string') {
76
91
  throw new Error('Argument is not a string');
77
- }
92
+ }
93
+ if (this.#options.timeout) {
94
+ to = this.#options.timeout;
95
+ }
78
96
  let { cwd, shell, env, stdio } = this.#options;
79
- if (payload) stdio = ['pipe', 'pipe', 'pipe'];
97
+ // pipe need to be set on stdin when posting a payload
98
+ if (payload) stdio[0] = 'pipe';
80
99
  this.#proc = spawn(this.#options.prefix, [this.#command], {
81
100
  cwd,
82
101
  shell,
@@ -92,12 +111,19 @@ class SHExecute {
92
111
  this.#proc.stderr?.on('data', (data) => {
93
112
  this.#stderr += data;
94
113
  });
95
- if (payload) {
96
- this.#proc.stdin.write(payload);
97
- this.#proc.stdin.end();
98
- }
114
+ if (payload) {
115
+ this.#proc.stdin.end(payload);
116
+ }
99
117
  return new Promise((resolve, reject) => {
118
+ let timeout;
119
+ if (to > 0) {
120
+ timeout = setTimeout(async () => {
121
+ await this.#proc.kill();
122
+ reject(new Error(`Process timed out: ${this.#command}`));
123
+ }, to); // options.timeout
124
+ }
100
125
  this.#proc.on('close', (code) => {
126
+ if (timeout) clearTimeout(timeout);
101
127
  if (code === 0) {
102
128
  resolve(this.#stdout.trim());
103
129
  } else {
@@ -110,9 +136,9 @@ class SHExecute {
110
136
  });
111
137
  });
112
138
  }
113
- /**
114
- * @returns {Promise<number[]>}
115
- */
139
+ /**
140
+ * @returns {Promise<number[]>}
141
+ */
116
142
  async kill(signal = 'SIGTERM') {
117
143
  if (!this.#proc) throw new Error('Trying to kill a process without creating one.');
118
144
  if (!this.#proc.pid) throw new Error('The process pid is undefined.');
@@ -138,7 +164,8 @@ class SHExecute {
138
164
  * @property {NodeJS.ProcessEnv} [env] - The environment variables.
139
165
  * @property {string} [shell] - The shell to use for execution.
140
166
  * @property {string} [prefix] - The prefix commands to ensure a safe execution environment.
141
- * @property {StdioOption|StdioOptions} [stdio] - The stdio configuration.
167
+ * @property {StdioOptions|StdioOption} [stdio] - The stdio configuration.
168
+ * @property {number} [timeout] - default 20000: a timeout error is triggerd when a process execution time is exceeded, 0 is no timeout
142
169
  */
143
170
  /**
144
171
  * @typedef {('pipe' | 'ignore' | 'inherit' | number)} StdioOption
@@ -180,9 +207,9 @@ const hasProp = (o, p) => {
180
207
 
181
208
  /**
182
209
  * Merge property values while maintaining the fixed set of props in the original object
183
- * @param {object} predefined - original object
184
- * @param {object} options - object with new values
185
- * @retuns {object}
210
+ * @param {SHOptions} predefined - original object
211
+ * @param {SHOptions} options - object with new values
212
+ * @returns {SHOptions}
186
213
  */
187
214
  const mergeOptions = (predefined, options) => {
188
215
  // Extract the keys from the predefined object
@@ -208,7 +235,8 @@ const defaultOptions = {
208
235
  env: process.env,
209
236
  shell: 'bash',
210
237
  prefix: 'set -euo pipefail;/usr/bin/env',
211
- stdio: ['inherit', 'pipe', 'pipe']
238
+ stdio: ['inherit', 'pipe', 'pipe'],
239
+ timeout: 10000 // when 0 there is no timeout
212
240
  };
213
241
 
214
242
 
@@ -229,6 +257,13 @@ class SHDispatch {
229
257
  * @returns {SHDispatch}
230
258
  */
231
259
  options(options) {
260
+ if (options.stdio && typeof options.stdio === 'string') {
261
+ // convert stdio to array
262
+ // This sets the default io values
263
+ // but can be overwritten when having a payload
264
+ const io = options.stdio;
265
+ options.stdio = Array(3).fill(io);
266
+ }
232
267
  this.#options = mergeOptions(defaultOptions, options);
233
268
  return this;
234
269
  }
@@ -243,11 +278,12 @@ class SHDispatch {
243
278
 
244
279
  /**
245
280
  * Works for screen takeovers like editors
281
+ * @param {string} [payload]
246
282
  * @returns {SpawnSyncResponse}
247
283
  */
248
- runSync() {
284
+ runSync(payload) {
249
285
  // @ts-ignore
250
- return new SHExecute(this.#cmd, this.#options).runSync();
286
+ return new SHExecute(this.#cmd, this.#options).runSync(payload);
251
287
  }
252
288
  async kill() {
253
289
  try {
@@ -287,7 +323,6 @@ class SHDispatch {
287
323
  // Modified by: jorrit.duin+sh[AT]gmail.com
288
324
 
289
325
 
290
-
291
326
  /**
292
327
  * Creates a new SHDispatch object that represents a command to be executed.
293
328
  *
@@ -450,6 +485,7 @@ const retry = async (count, a, b) => {
450
485
  let delay = 0;
451
486
  if (delayStatic > 0)
452
487
  delay = delayStatic;
488
+ // @ts-ignore
453
489
  if (delayGen) delay = delayGen.next().value;
454
490
  lastErr = err;
455
491
  if (count == 0)
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.0.0",
5
+ "version": "1.0.2",
6
6
  "description": "Execute shell commands on Linux-based systems from javascript",
7
7
  "main": "lib/sh.js",
8
8
  "engines": {