@j-o-r/sh 1.0.0 → 1.0.1

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,7 @@
1
1
  import { spawnSync, spawn, exec } from 'node:child_process';
2
2
 
3
+ // timeout when a process becomes inresponsive
4
+
3
5
  /**
4
6
  * Kills a process and all child processes of a given process ID in Linux/Posix.
5
7
  * @param {number} processPid - The process ID.
@@ -45,11 +47,11 @@ const killProcesses = (processPid, signal) => {
45
47
  };
46
48
 
47
49
  class SHExecute {
48
- #proc;
49
- #command = '';
50
- #options = {};
51
- #stdout = '';
52
- #stderr = '';
50
+ #proc;
51
+ #command = '';
52
+ #options = {};
53
+ #stdout = '';
54
+ #stderr = '';
53
55
  constructor(command, options = {}) {
54
56
  this.#command = command;
55
57
  this.#options = options;
@@ -57,26 +59,42 @@ class SHExecute {
57
59
  this.#stdout = '';
58
60
  this.#stderr = '';
59
61
  }
60
- runSync () {
61
- let { cwd, shell, env, stdio } = this.#options;
62
+ /**
63
+ * @param {string} [payload] - data to write
64
+ * @retuns {Promise<object>}
65
+ */
66
+ runSync(payload) {
67
+ if (payload && typeof payload !== 'string') {
68
+ throw new Error('Argument is not a string');
69
+ }
70
+ let { cwd, shell, env, stdio } = this.#options;
71
+ // pipe need to be set on stdin when posting a payload
72
+ if (payload) stdio[0] = 'pipe';
73
+ const input = payload || undefined;
62
74
  return spawnSync(this.#options.prefix, [this.#command], {
63
75
  cwd,
64
76
  shell,
65
77
  stdio,
66
78
  windowsHide: true,
67
79
  env,
80
+ input
68
81
  });
69
82
  }
70
- /**
71
- * @param {string} [payload] - data to write
72
- * @retuns {Promise<string>}
73
- */
83
+ /**
84
+ * @param {string} [payload] - data to write
85
+ * @retuns {Promise<string>}
86
+ */
74
87
  run(payload) {
88
+ let to = 0;
75
89
  if (payload && typeof payload !== 'string') {
76
90
  throw new Error('Argument is not a string');
77
- }
91
+ }
92
+ if (this.#options.timeout) {
93
+ to = this.#options.timeout;
94
+ }
78
95
  let { cwd, shell, env, stdio } = this.#options;
79
- if (payload) stdio = ['pipe', 'pipe', 'pipe'];
96
+ // pipe need to be set on stdin when posting a payload
97
+ if (payload) stdio[0] = 'pipe';
80
98
  this.#proc = spawn(this.#options.prefix, [this.#command], {
81
99
  cwd,
82
100
  shell,
@@ -92,12 +110,19 @@ class SHExecute {
92
110
  this.#proc.stderr?.on('data', (data) => {
93
111
  this.#stderr += data;
94
112
  });
95
- if (payload) {
96
- this.#proc.stdin.write(payload);
97
- this.#proc.stdin.end();
98
- }
113
+ if (payload) {
114
+ this.#proc.stdin.end(payload);
115
+ }
99
116
  return new Promise((resolve, reject) => {
117
+ let timeout;
118
+ if (to > 0) {
119
+ timeout = setTimeout(async () => {
120
+ await this.#proc.kill();
121
+ reject(new Error('Process timed out'));
122
+ }, to); // options.timeout
123
+ }
100
124
  this.#proc.on('close', (code) => {
125
+ if (timeout) clearTimeout(timeout);
101
126
  if (code === 0) {
102
127
  resolve(this.#stdout.trim());
103
128
  } else {
@@ -110,9 +135,9 @@ class SHExecute {
110
135
  });
111
136
  });
112
137
  }
113
- /**
114
- * @returns {Promise<number[]>}
115
- */
138
+ /**
139
+ * @returns {Promise<number[]>}
140
+ */
116
141
  async kill(signal = 'SIGTERM') {
117
142
  if (!this.#proc) throw new Error('Trying to kill a process without creating one.');
118
143
  if (!this.#proc.pid) throw new Error('The process pid is undefined.');
@@ -138,7 +163,8 @@ class SHExecute {
138
163
  * @property {NodeJS.ProcessEnv} [env] - The environment variables.
139
164
  * @property {string} [shell] - The shell to use for execution.
140
165
  * @property {string} [prefix] - The prefix commands to ensure a safe execution environment.
141
- * @property {StdioOption|StdioOptions} [stdio] - The stdio configuration.
166
+ * @property {StdioOptions|StdioOption} [stdio] - The stdio configuration.
167
+ * @property {number} [timeout] - default 20000: a timeout error is triggerd when a process execution time is exceeded, 0 is no timeout
142
168
  */
143
169
  /**
144
170
  * @typedef {('pipe' | 'ignore' | 'inherit' | number)} StdioOption
@@ -180,9 +206,9 @@ const hasProp = (o, p) => {
180
206
 
181
207
  /**
182
208
  * 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}
209
+ * @param {SHOptions} predefined - original object
210
+ * @param {SHOptions} options - object with new values
211
+ * @returns {SHOptions}
186
212
  */
187
213
  const mergeOptions = (predefined, options) => {
188
214
  // Extract the keys from the predefined object
@@ -208,7 +234,8 @@ const defaultOptions = {
208
234
  env: process.env,
209
235
  shell: 'bash',
210
236
  prefix: 'set -euo pipefail;/usr/bin/env',
211
- stdio: ['inherit', 'pipe', 'pipe']
237
+ stdio: ['inherit', 'pipe', 'pipe'],
238
+ timeout: 10000 // when 0 there is no timeout
212
239
  };
213
240
 
214
241
 
@@ -229,6 +256,13 @@ class SHDispatch {
229
256
  * @returns {SHDispatch}
230
257
  */
231
258
  options(options) {
259
+ if (options.stdio && typeof options.stdio === 'string') {
260
+ // convert stdio to array
261
+ // This sets the default io values
262
+ // but can be overwritten when having a payload
263
+ const io = options.stdio;
264
+ options.stdio = Array(3).fill(io);
265
+ }
232
266
  this.#options = mergeOptions(defaultOptions, options);
233
267
  return this;
234
268
  }
@@ -243,11 +277,12 @@ class SHDispatch {
243
277
 
244
278
  /**
245
279
  * Works for screen takeovers like editors
280
+ * @param {string} [payload]
246
281
  * @returns {SpawnSyncResponse}
247
282
  */
248
- runSync() {
283
+ runSync(payload) {
249
284
  // @ts-ignore
250
- return new SHExecute(this.#cmd, this.#options).runSync();
285
+ return new SHExecute(this.#cmd, this.#options).runSync(payload);
251
286
  }
252
287
  async kill() {
253
288
  try {
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.1",
6
6
  "description": "Execute shell commands on Linux-based systems from javascript",
7
7
  "main": "lib/sh.js",
8
8
  "engines": {