@push.rocks/smartshell 3.2.3 → 3.3.0
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/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.shelllog.d.ts +1 -1
- package/dist_ts/classes.smartshell.d.ts +70 -2
- package/dist_ts/classes.smartshell.js +595 -24
- package/package.json +5 -6
- package/readme.md +552 -71
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.smartshell.ts +695 -25
package/readme.md
CHANGED
|
@@ -1,136 +1,617 @@
|
|
|
1
|
-
# @push.rocks/smartshell
|
|
2
|
-
shell
|
|
1
|
+
# @push.rocks/smartshell 🐚
|
|
2
|
+
**Execute shell commands with superpowers in Node.js**
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
[](https://www.npmjs.com/package/@push.rocks/smartshell)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
## ⚠️ Security Notice
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
**IMPORTANT:** Please read the [Security Guide](#security-guide) below for critical information about command execution and input handling. Always use `execSpawn` methods for untrusted input.
|
|
10
|
+
|
|
11
|
+
## Why smartshell? 🚀
|
|
12
|
+
|
|
13
|
+
Tired of wrestling with Node.js child processes? Meet `@push.rocks/smartshell` - your promise-based shell command companion that makes executing system commands feel like a breeze. Whether you're building automation scripts, CI/CD pipelines, or need fine-grained control over shell execution, smartshell has got you covered.
|
|
14
|
+
|
|
15
|
+
### ✨ Key Features
|
|
16
|
+
|
|
17
|
+
- 🎯 **Promise-based API** - Async/await ready for modern codebases
|
|
18
|
+
- 🔇 **Silent execution modes** - Control output verbosity
|
|
19
|
+
- 📡 **Streaming support** - Real-time output for long-running processes
|
|
20
|
+
- 🎮 **Interactive commands** - Handle user input programmatically
|
|
21
|
+
- 🛡️ **Secure execution** - Shell-free methods for untrusted input
|
|
22
|
+
- ⚡ **Smart execution modes** - Strict, silent, or streaming
|
|
23
|
+
- 🔍 **Pattern matching** - Wait for specific output patterns
|
|
24
|
+
- 🌍 **Environment management** - Custom env vars and PATH handling
|
|
25
|
+
- 💾 **Memory protection** - Built-in buffer limits prevent OOM
|
|
26
|
+
- ⏱️ **Timeout support** - Automatic process termination
|
|
27
|
+
- 🖥️ **PTY support** - Full terminal emulation (optional)
|
|
28
|
+
- 🎨 **Cross-platform** - Windows, macOS, and Linux ready
|
|
29
|
+
- 🛡️ **TypeScript first** - Full type safety and IntelliSense
|
|
30
|
+
|
|
31
|
+
## Installation 📦
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# Using npm
|
|
9
35
|
npm install @push.rocks/smartshell --save
|
|
36
|
+
|
|
37
|
+
# Using yarn
|
|
38
|
+
yarn add @push.rocks/smartshell
|
|
39
|
+
|
|
40
|
+
# Using pnpm (recommended)
|
|
41
|
+
pnpm add @push.rocks/smartshell
|
|
42
|
+
|
|
43
|
+
# Optional: For PTY support (terminal emulation)
|
|
44
|
+
pnpm add --save-optional node-pty
|
|
10
45
|
```
|
|
11
46
|
|
|
12
|
-
|
|
47
|
+
## Quick Start 🏃♂️
|
|
13
48
|
|
|
14
|
-
```
|
|
15
|
-
|
|
49
|
+
```typescript
|
|
50
|
+
import { Smartshell } from '@push.rocks/smartshell';
|
|
51
|
+
|
|
52
|
+
// Create your shell instance
|
|
53
|
+
const shell = new Smartshell({
|
|
54
|
+
executor: 'bash' // or 'sh' for lighter shells
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Run a simple command
|
|
58
|
+
const result = await shell.exec('echo "Hello, World!"');
|
|
59
|
+
console.log(result.stdout); // "Hello, World!"
|
|
60
|
+
console.log(result.signal); // undefined (no signal)
|
|
61
|
+
console.log(result.stderr); // "" (no errors)
|
|
16
62
|
```
|
|
17
63
|
|
|
18
|
-
|
|
64
|
+
## Security-First Execution 🔒
|
|
65
|
+
|
|
66
|
+
### Secure Command Execution with execSpawn
|
|
19
67
|
|
|
20
|
-
|
|
68
|
+
When dealing with untrusted input, **always use execSpawn methods** which don't use shell interpretation:
|
|
21
69
|
|
|
22
|
-
|
|
70
|
+
```typescript
|
|
71
|
+
// ❌ DANGEROUS with untrusted input
|
|
72
|
+
const userInput = "file.txt; rm -rf /";
|
|
73
|
+
await shell.exec(`cat ${userInput}`); // Command injection!
|
|
23
74
|
|
|
24
|
-
|
|
75
|
+
// ✅ SAFE with untrusted input
|
|
76
|
+
await shell.execSpawn('cat', [userInput]); // Arguments are properly escaped
|
|
77
|
+
```
|
|
25
78
|
|
|
26
|
-
|
|
79
|
+
### execSpawn Family Methods
|
|
27
80
|
|
|
28
81
|
```typescript
|
|
29
|
-
|
|
82
|
+
// Basic secure execution
|
|
83
|
+
const result = await shell.execSpawn('ls', ['-la', '/home']);
|
|
84
|
+
|
|
85
|
+
// Streaming secure execution
|
|
86
|
+
const streaming = await shell.execSpawnStreaming('npm', ['install']);
|
|
87
|
+
await streaming.finalPromise;
|
|
88
|
+
|
|
89
|
+
// Interactive secure execution
|
|
90
|
+
const interactive = await shell.execSpawnInteractiveControl('cat', []);
|
|
91
|
+
await interactive.sendLine('Hello');
|
|
92
|
+
interactive.endInput();
|
|
93
|
+
await interactive.finalPromise;
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Production Features 🏭
|
|
97
|
+
|
|
98
|
+
### Resource Management
|
|
99
|
+
|
|
100
|
+
Prevent memory issues with built-in buffer limits:
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
const result = await shell.exec('large-output-command', {
|
|
104
|
+
maxBuffer: 10 * 1024 * 1024, // 10MB limit
|
|
105
|
+
onData: (chunk) => {
|
|
106
|
+
// Process chunks as they arrive
|
|
107
|
+
console.log('Received:', chunk.toString());
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Timeout Support
|
|
113
|
+
|
|
114
|
+
Automatically terminate long-running processes:
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
try {
|
|
118
|
+
const result = await shell.execSpawn('long-process', [], {
|
|
119
|
+
timeout: 5000 // 5 second timeout
|
|
120
|
+
});
|
|
121
|
+
} catch (error) {
|
|
122
|
+
console.log('Process timed out');
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Debug Mode
|
|
127
|
+
|
|
128
|
+
Enable detailed logging for troubleshooting:
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
const result = await shell.exec('command', {
|
|
132
|
+
debug: true // Logs process lifecycle events
|
|
133
|
+
});
|
|
30
134
|
```
|
|
31
135
|
|
|
32
|
-
###
|
|
136
|
+
### Custom Environment
|
|
33
137
|
|
|
34
|
-
|
|
138
|
+
Control the execution environment precisely:
|
|
35
139
|
|
|
36
140
|
```typescript
|
|
37
|
-
const
|
|
38
|
-
|
|
141
|
+
const result = await shell.execSpawn('node', ['script.js'], {
|
|
142
|
+
env: {
|
|
143
|
+
NODE_ENV: 'production',
|
|
144
|
+
PATH: '/usr/bin:/bin',
|
|
145
|
+
CUSTOM_VAR: 'value'
|
|
146
|
+
}
|
|
39
147
|
});
|
|
40
148
|
```
|
|
41
149
|
|
|
42
|
-
|
|
150
|
+
## Interactive Control 🎮
|
|
43
151
|
|
|
44
|
-
|
|
152
|
+
### Programmatic Input Control
|
|
45
153
|
|
|
46
|
-
|
|
154
|
+
Send input to processes programmatically:
|
|
47
155
|
|
|
48
156
|
```typescript
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
157
|
+
const interactive = await shell.execInteractiveControl('cat');
|
|
158
|
+
|
|
159
|
+
// Send input line by line
|
|
160
|
+
await interactive.sendLine('Line 1');
|
|
161
|
+
await interactive.sendLine('Line 2');
|
|
162
|
+
|
|
163
|
+
// Send raw input without newline
|
|
164
|
+
await interactive.sendInput('partial');
|
|
165
|
+
|
|
166
|
+
// Close stdin
|
|
167
|
+
interactive.endInput();
|
|
168
|
+
|
|
169
|
+
// Wait for completion
|
|
170
|
+
const result = await interactive.finalPromise;
|
|
53
171
|
```
|
|
54
172
|
|
|
55
|
-
|
|
173
|
+
### Passthrough Mode
|
|
56
174
|
|
|
57
|
-
|
|
175
|
+
Connect stdin for real keyboard interaction:
|
|
58
176
|
|
|
59
177
|
```typescript
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
console.log(result.stdout); // Outputs the list of files and directories
|
|
63
|
-
})();
|
|
178
|
+
// User can type directly
|
|
179
|
+
await shell.execPassthrough('vim file.txt');
|
|
64
180
|
```
|
|
65
181
|
|
|
66
|
-
|
|
182
|
+
## PTY Support - Full Terminal Emulation 🖥️
|
|
183
|
+
|
|
184
|
+
Smartshell provides two modes for executing interactive commands:
|
|
185
|
+
|
|
186
|
+
1. **Pipe Mode (Default)** - Fast, simple, no dependencies
|
|
187
|
+
2. **PTY Mode** - Full terminal emulation for advanced interactive programs
|
|
67
188
|
|
|
68
|
-
|
|
189
|
+
### When to Use Each Mode
|
|
190
|
+
|
|
191
|
+
#### Use Pipe Mode (Default) When:
|
|
192
|
+
- Running simple commands that read from stdin
|
|
193
|
+
- Using tools like `cat`, `grep`, `sed`, `awk`
|
|
194
|
+
- Running basic scripts that don't need terminal features
|
|
195
|
+
- You want maximum performance and simplicity
|
|
196
|
+
- You don't want native dependencies
|
|
197
|
+
|
|
198
|
+
#### Use PTY Mode When:
|
|
199
|
+
- Running commands that require a real terminal:
|
|
200
|
+
- Password prompts (`sudo`, `ssh`, `su`)
|
|
201
|
+
- Interactive editors (`vim`, `nano`, `emacs`)
|
|
202
|
+
- Terminal UIs (`htop`, `less`, `more`)
|
|
203
|
+
- Programs with fancy prompts (`bash read -p`)
|
|
204
|
+
- Tab completion and readline features
|
|
205
|
+
- You need terminal features:
|
|
206
|
+
- ANSI colors and escape sequences
|
|
207
|
+
- Terminal size control
|
|
208
|
+
- Signal handling (Ctrl+C, Ctrl+Z)
|
|
209
|
+
- Line discipline and special key handling
|
|
210
|
+
|
|
211
|
+
### Installing PTY Support
|
|
212
|
+
|
|
213
|
+
PTY support requires the optional `node-pty` dependency:
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
# Install as optional dependency
|
|
217
|
+
pnpm add --save-optional node-pty
|
|
218
|
+
|
|
219
|
+
# Note: node-pty requires compilation and has platform-specific requirements
|
|
220
|
+
# - On Windows: Requires Visual Studio Build Tools
|
|
221
|
+
# - On macOS/Linux: Requires Python and build tools
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### PTY Usage Examples
|
|
69
225
|
|
|
70
226
|
```typescript
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
227
|
+
// Use PTY for commands that need terminal features
|
|
228
|
+
const ptyInteractive = await shell.execInteractiveControlPty(
|
|
229
|
+
"bash -c 'read -p \"Enter name: \" name && echo \"Hello, $name\"'"
|
|
230
|
+
);
|
|
231
|
+
await ptyInteractive.sendLine('John');
|
|
232
|
+
const result = await ptyInteractive.finalPromise;
|
|
233
|
+
// With PTY, the prompt "Enter name: " will be visible in stdout
|
|
234
|
+
|
|
235
|
+
// Streaming with PTY for real-time interaction
|
|
236
|
+
const ptyStreaming = await shell.execStreamingInteractiveControlPty('vim test.txt');
|
|
237
|
+
await ptyStreaming.sendInput('i'); // Enter insert mode
|
|
238
|
+
await ptyStreaming.sendInput('Hello from PTY!');
|
|
239
|
+
await ptyStreaming.sendInput('\x1b'); // ESC key
|
|
240
|
+
await ptyStreaming.sendInput(':wq\r'); // Save and quit
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### PTY vs Pipe Mode Comparison
|
|
244
|
+
|
|
245
|
+
| Feature | Pipe Mode | PTY Mode |
|
|
246
|
+
|---------|-----------|----------|
|
|
247
|
+
| Dependencies | None | node-pty |
|
|
248
|
+
| Terminal Detection | `isatty()` returns false | `isatty()` returns true |
|
|
249
|
+
| Prompt Display | May not show | Always shows |
|
|
250
|
+
| Colors | Often disabled | Enabled |
|
|
251
|
+
| Signal Handling | Basic | Full (Ctrl+C, Ctrl+Z, etc.) |
|
|
252
|
+
| Line Ending | `\n` | `\r` (carriage return) |
|
|
253
|
+
| EOF Signal | Stream end | `\x04` (Ctrl+D) |
|
|
254
|
+
|
|
255
|
+
### Common PTY Patterns
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
// Password input (PTY required)
|
|
259
|
+
const sudo = await shell.execInteractiveControlPty('sudo ls /root');
|
|
260
|
+
await sudo.sendLine('mypassword');
|
|
261
|
+
const result = await sudo.finalPromise;
|
|
262
|
+
|
|
263
|
+
// Interactive REPL with colors
|
|
264
|
+
const node = await shell.execStreamingInteractiveControlPty('node');
|
|
265
|
+
await node.sendLine('console.log("PTY supports colors!")');
|
|
266
|
+
await node.sendLine('.exit');
|
|
267
|
+
|
|
268
|
+
// Handling terminal colors
|
|
269
|
+
const ls = await shell.execInteractiveControlPty('ls --color=always');
|
|
270
|
+
const result = await ls.finalPromise;
|
|
271
|
+
// result.stdout will contain ANSI color codes
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### PTY Fallback Strategy
|
|
275
|
+
|
|
276
|
+
Always provide a fallback for when PTY isn't available:
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
try {
|
|
280
|
+
// Try PTY mode first
|
|
281
|
+
const result = await shell.execInteractiveControlPty(command);
|
|
282
|
+
// ...
|
|
283
|
+
} catch (error) {
|
|
284
|
+
if (error.message.includes('node-pty')) {
|
|
285
|
+
// Fallback to pipe mode
|
|
286
|
+
console.warn('PTY not available, using pipe mode');
|
|
287
|
+
const result = await shell.execInteractiveControl(command);
|
|
288
|
+
// ...
|
|
76
289
|
}
|
|
77
|
-
}
|
|
290
|
+
}
|
|
78
291
|
```
|
|
79
292
|
|
|
80
|
-
|
|
293
|
+
## Advanced Pattern Matching 🔍
|
|
294
|
+
|
|
295
|
+
### Enhanced execAndWaitForLine
|
|
81
296
|
|
|
82
|
-
|
|
297
|
+
Wait for patterns with timeout and auto-termination:
|
|
83
298
|
|
|
84
299
|
```typescript
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
300
|
+
// Wait with timeout
|
|
301
|
+
await shell.execAndWaitForLine(
|
|
302
|
+
'npm start',
|
|
303
|
+
/Server listening on port/,
|
|
304
|
+
false, // silent
|
|
305
|
+
{
|
|
306
|
+
timeout: 30000, // 30 second timeout
|
|
307
|
+
terminateOnMatch: true // Kill process after match
|
|
308
|
+
}
|
|
309
|
+
);
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
## Core Concepts 💡
|
|
313
|
+
|
|
314
|
+
### The Smartshell Instance
|
|
315
|
+
|
|
316
|
+
The heart of smartshell is the `Smartshell` class. Each instance maintains its own environment and configuration:
|
|
91
317
|
|
|
92
|
-
|
|
93
|
-
|
|
318
|
+
```typescript
|
|
319
|
+
const shell = new Smartshell({
|
|
320
|
+
executor: 'bash', // Choose your shell: 'bash' or 'sh'
|
|
321
|
+
sourceFilePaths: ['/path/to/env.sh'], // Optional: source files on init
|
|
322
|
+
});
|
|
94
323
|
```
|
|
95
324
|
|
|
96
|
-
|
|
325
|
+
## Execution Modes 🎛️
|
|
326
|
+
|
|
327
|
+
### Standard Execution
|
|
328
|
+
|
|
329
|
+
Perfect for general commands where you want to see the output:
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
const result = await shell.exec('ls -la');
|
|
333
|
+
console.log(result.stdout); // Directory listing
|
|
334
|
+
console.log(result.exitCode); // 0 for success
|
|
335
|
+
console.log(result.signal); // Signal if terminated
|
|
336
|
+
console.log(result.stderr); // Error output
|
|
337
|
+
```
|
|
97
338
|
|
|
98
|
-
|
|
339
|
+
### Silent Execution
|
|
99
340
|
|
|
100
|
-
|
|
341
|
+
Run commands without printing to console - ideal for capturing output:
|
|
101
342
|
|
|
102
343
|
```typescript
|
|
103
|
-
(
|
|
104
|
-
|
|
105
|
-
|
|
344
|
+
const result = await shell.execSilent('cat /etc/hostname');
|
|
345
|
+
// Output is NOT printed to console but IS captured in result
|
|
346
|
+
console.log(result.stdout); // Access the captured output here
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### Strict Execution
|
|
350
|
+
|
|
351
|
+
Throws an error if the command fails - great for critical operations:
|
|
352
|
+
|
|
353
|
+
```typescript
|
|
354
|
+
try {
|
|
355
|
+
await shell.execStrict('critical-command');
|
|
356
|
+
console.log('✅ Command succeeded!');
|
|
357
|
+
} catch (error) {
|
|
358
|
+
console.error('❌ Command failed:', error.message);
|
|
359
|
+
// Error includes exit code or signal information
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### Streaming Execution
|
|
364
|
+
|
|
365
|
+
For long-running processes or when you need real-time output:
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
const streaming = await shell.execStreaming('npm install');
|
|
369
|
+
|
|
370
|
+
// Access the child process directly
|
|
371
|
+
streaming.childProcess.stdout.on('data', (chunk) => {
|
|
372
|
+
console.log('📦 Installing:', chunk.toString());
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
// Control the process
|
|
376
|
+
await streaming.terminate(); // SIGTERM
|
|
377
|
+
await streaming.kill(); // SIGKILL
|
|
378
|
+
await streaming.keyboardInterrupt(); // SIGINT
|
|
379
|
+
|
|
380
|
+
// Wait for completion
|
|
381
|
+
await streaming.finalPromise;
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
## Real-World Examples 🌍
|
|
385
|
+
|
|
386
|
+
### Build Pipeline
|
|
387
|
+
|
|
388
|
+
```typescript
|
|
389
|
+
const shell = new Smartshell({ executor: 'bash' });
|
|
390
|
+
|
|
391
|
+
// Clean build directory
|
|
392
|
+
await shell.execSilent('rm -rf dist');
|
|
393
|
+
|
|
394
|
+
// Run TypeScript compiler
|
|
395
|
+
const buildResult = await shell.execStrict('tsc');
|
|
396
|
+
|
|
397
|
+
// Run tests
|
|
398
|
+
await shell.execStrict('npm test');
|
|
399
|
+
|
|
400
|
+
// Build succeeded!
|
|
401
|
+
console.log('✅ Build pipeline completed successfully');
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
### CI/CD with Security
|
|
405
|
+
|
|
406
|
+
```typescript
|
|
407
|
+
async function deployApp(branch: string, untrustedTag: string) {
|
|
408
|
+
const shell = new Smartshell({ executor: 'bash' });
|
|
409
|
+
|
|
410
|
+
// Use execSpawn for untrusted input
|
|
411
|
+
await shell.execSpawnStrict('git', ['checkout', branch]);
|
|
412
|
+
await shell.execSpawnStrict('git', ['tag', untrustedTag]);
|
|
106
413
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
414
|
+
// Safe to use exec for hardcoded commands
|
|
415
|
+
await shell.execStrict('npm run build');
|
|
416
|
+
await shell.execStrict('npm run deploy');
|
|
417
|
+
}
|
|
110
418
|
```
|
|
111
419
|
|
|
112
|
-
###
|
|
420
|
+
### Docker Compose Helper
|
|
113
421
|
|
|
114
|
-
|
|
422
|
+
```typescript
|
|
423
|
+
const shell = new Smartshell({ executor: 'bash' });
|
|
424
|
+
|
|
425
|
+
// Start services and wait for readiness
|
|
426
|
+
console.log('🐳 Starting Docker services...');
|
|
427
|
+
await shell.execAndWaitForLine(
|
|
428
|
+
'docker-compose up',
|
|
429
|
+
/All services are ready/,
|
|
430
|
+
false,
|
|
431
|
+
{
|
|
432
|
+
timeout: 60000,
|
|
433
|
+
terminateOnMatch: false // Keep running after match
|
|
434
|
+
}
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
// Run migrations
|
|
438
|
+
await shell.execStrict('docker-compose exec app npm run migrate');
|
|
439
|
+
console.log('✅ Environment ready!');
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
### Development Server with Auto-Restart
|
|
443
|
+
|
|
444
|
+
```typescript
|
|
445
|
+
import { SmartExecution } from '@push.rocks/smartshell';
|
|
446
|
+
|
|
447
|
+
const shell = new Smartshell({ executor: 'bash' });
|
|
448
|
+
const devServer = new SmartExecution(shell, 'npm run dev');
|
|
449
|
+
|
|
450
|
+
// Watch for file changes and restart
|
|
451
|
+
fs.watch('./src', async () => {
|
|
452
|
+
console.log('🔄 Changes detected, restarting...');
|
|
453
|
+
await devServer.restart();
|
|
454
|
+
});
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
## API Reference 📚
|
|
458
|
+
|
|
459
|
+
### Smartshell Class
|
|
460
|
+
|
|
461
|
+
| Method | Description | Security |
|
|
462
|
+
|--------|-------------|----------|
|
|
463
|
+
| `exec(command)` | Execute with shell | ⚠️ Vulnerable to injection |
|
|
464
|
+
| `execSpawn(cmd, args)` | Execute without shell | ✅ Safe for untrusted input |
|
|
465
|
+
| `execSilent(command)` | Execute without console output | ⚠️ Vulnerable to injection |
|
|
466
|
+
| `execStrict(command)` | Execute, throw on failure | ⚠️ Vulnerable to injection |
|
|
467
|
+
| `execStreaming(command)` | Stream output in real-time | ⚠️ Vulnerable to injection |
|
|
468
|
+
| `execSpawnStreaming(cmd, args)` | Stream without shell | ✅ Safe for untrusted input |
|
|
469
|
+
| `execInteractive(command)` | Interactive terminal mode | ⚠️ Vulnerable to injection |
|
|
470
|
+
| `execInteractiveControl(command)` | Programmatic input control | ⚠️ Vulnerable to injection |
|
|
471
|
+
| `execSpawnInteractiveControl(cmd, args)` | Programmatic control without shell | ✅ Safe for untrusted input |
|
|
472
|
+
| `execPassthrough(command)` | Connect stdin passthrough | ⚠️ Vulnerable to injection |
|
|
473
|
+
| `execInteractiveControlPty(command)` | PTY with programmatic control | ⚠️ Vulnerable to injection |
|
|
474
|
+
| `execStreamingInteractiveControlPty(command)` | PTY streaming with control | ⚠️ Vulnerable to injection |
|
|
475
|
+
| `execAndWaitForLine(cmd, regex, silent, opts)` | Wait for pattern match | ⚠️ Vulnerable to injection |
|
|
476
|
+
|
|
477
|
+
### Result Interfaces
|
|
478
|
+
|
|
479
|
+
```typescript
|
|
480
|
+
interface IExecResult {
|
|
481
|
+
exitCode: number; // Process exit code
|
|
482
|
+
stdout: string; // Standard output
|
|
483
|
+
signal?: NodeJS.Signals; // Termination signal
|
|
484
|
+
stderr?: string; // Error output
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
interface IExecResultStreaming {
|
|
488
|
+
childProcess: ChildProcess; // Node.js ChildProcess instance
|
|
489
|
+
finalPromise: Promise<IExecResult>; // Resolves when process exits
|
|
490
|
+
sendInput: (input: string) => Promise<void>;
|
|
491
|
+
sendLine: (line: string) => Promise<void>;
|
|
492
|
+
endInput: () => void;
|
|
493
|
+
kill: () => Promise<void>;
|
|
494
|
+
terminate: () => Promise<void>;
|
|
495
|
+
keyboardInterrupt: () => Promise<void>;
|
|
496
|
+
customSignal: (signal: NodeJS.Signals) => Promise<void>;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
interface IExecResultInteractive extends IExecResult {
|
|
500
|
+
sendInput: (input: string) => Promise<void>;
|
|
501
|
+
sendLine: (line: string) => Promise<void>;
|
|
502
|
+
endInput: () => void;
|
|
503
|
+
finalPromise: Promise<IExecResult>;
|
|
504
|
+
}
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
### Options Interface
|
|
508
|
+
|
|
509
|
+
```typescript
|
|
510
|
+
interface IExecOptions {
|
|
511
|
+
silent?: boolean; // Suppress console output
|
|
512
|
+
strict?: boolean; // Throw on non-zero exit
|
|
513
|
+
streaming?: boolean; // Return streaming interface
|
|
514
|
+
interactive?: boolean; // Interactive mode
|
|
515
|
+
passthrough?: boolean; // Connect stdin
|
|
516
|
+
interactiveControl?: boolean; // Programmatic input
|
|
517
|
+
usePty?: boolean; // Use pseudo-terminal
|
|
518
|
+
ptyShell?: string; // Custom PTY shell
|
|
519
|
+
ptyCols?: number; // PTY columns (default 120)
|
|
520
|
+
ptyRows?: number; // PTY rows (default 30)
|
|
521
|
+
ptyTerm?: string; // Terminal type (default 'xterm-256color')
|
|
522
|
+
maxBuffer?: number; // Max output buffer (bytes)
|
|
523
|
+
onData?: (chunk: Buffer | string) => void; // Data callback
|
|
524
|
+
timeout?: number; // Execution timeout (ms)
|
|
525
|
+
debug?: boolean; // Enable debug logging
|
|
526
|
+
env?: NodeJS.ProcessEnv; // Custom environment
|
|
527
|
+
signal?: AbortSignal; // Abort signal
|
|
528
|
+
}
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
## Security Guide
|
|
532
|
+
|
|
533
|
+
### Command Injection Prevention
|
|
534
|
+
|
|
535
|
+
The standard `exec` methods use `shell: true`, which can lead to command injection vulnerabilities:
|
|
536
|
+
|
|
537
|
+
```typescript
|
|
538
|
+
// ❌ DANGEROUS - Never do this with untrusted input
|
|
539
|
+
const userInput = "file.txt; rm -rf /";
|
|
540
|
+
await smartshell.exec(`cat ${userInput}`); // Will execute rm -rf /
|
|
541
|
+
|
|
542
|
+
// ✅ SAFE - Arguments are properly escaped
|
|
543
|
+
await smartshell.execSpawn('cat', [userInput]); // Will look for literal filename
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
### Best Practices
|
|
547
|
+
|
|
548
|
+
1. **Always validate input**: Even with secure methods, validate user input
|
|
549
|
+
2. **Use execSpawn for untrusted data**: Never pass user input to shell-based methods
|
|
550
|
+
3. **Set resource limits**: Use `maxBuffer` and `timeout` for untrusted commands
|
|
551
|
+
4. **Control environment**: Don't inherit all env vars for sensitive operations
|
|
552
|
+
5. **Restrict signals**: Only allow specific signals from user input
|
|
553
|
+
|
|
554
|
+
### Path Traversal Protection
|
|
115
555
|
|
|
116
556
|
```typescript
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
})
|
|
557
|
+
// ❌ VULNERABLE
|
|
558
|
+
const file = userInput; // Could be "../../../etc/passwd"
|
|
559
|
+
await shell.exec(`cat ${file}`);
|
|
560
|
+
|
|
561
|
+
// ✅ SECURE
|
|
562
|
+
const path = require('path');
|
|
563
|
+
const safePath = path.join('/allowed/directory', path.basename(userInput));
|
|
564
|
+
await shell.execSpawn('cat', [safePath]);
|
|
120
565
|
```
|
|
121
566
|
|
|
122
|
-
|
|
567
|
+
## Tips & Best Practices 💎
|
|
568
|
+
|
|
569
|
+
1. **Security first**: Use `execSpawn` for any untrusted input
|
|
570
|
+
2. **Set resource limits**: Always use `maxBuffer` and `timeout` for untrusted commands
|
|
571
|
+
3. **Choose the right executor**: Use `bash` for full features, `sh` for minimal overhead
|
|
572
|
+
4. **Use strict mode for critical operations**: Ensures failures don't go unnoticed
|
|
573
|
+
5. **Stream long-running processes**: Better UX and memory efficiency
|
|
574
|
+
6. **Leverage silent modes**: When you only need to capture output
|
|
575
|
+
7. **Handle errors gracefully**: Check both `exitCode` and `signal`
|
|
576
|
+
8. **Clean up resources**: Streaming processes should be properly terminated
|
|
577
|
+
9. **Control environment**: Don't inherit all env vars for sensitive operations
|
|
578
|
+
10. **Enable debug mode**: For development and troubleshooting
|
|
579
|
+
11. **Use PTY for terminal UIs**: When programs need real terminal features
|
|
580
|
+
12. **Provide fallbacks**: Always handle PTY unavailability gracefully
|
|
581
|
+
|
|
582
|
+
## Environment Customization
|
|
123
583
|
|
|
124
|
-
|
|
584
|
+
Smartshell provides powerful environment management:
|
|
125
585
|
|
|
126
586
|
```typescript
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
587
|
+
// Add custom source files
|
|
588
|
+
shell.shellEnv.addSourceFiles([
|
|
589
|
+
'/home/user/.custom_env',
|
|
590
|
+
'./project.env.sh'
|
|
591
|
+
]);
|
|
592
|
+
|
|
593
|
+
// Modify PATH
|
|
594
|
+
shell.shellEnv.pathDirArray.push('/custom/bin');
|
|
595
|
+
shell.shellEnv.pathDirArray.push('/usr/local/special');
|
|
596
|
+
|
|
597
|
+
// Your custom environment is ready
|
|
598
|
+
const result = await shell.exec('my-custom-command');
|
|
131
599
|
```
|
|
132
600
|
|
|
133
|
-
|
|
601
|
+
## Shell Detection
|
|
602
|
+
|
|
603
|
+
Need to check if a command exists? We export the `which` utility:
|
|
604
|
+
|
|
605
|
+
```typescript
|
|
606
|
+
import { which } from '@push.rocks/smartshell';
|
|
607
|
+
|
|
608
|
+
try {
|
|
609
|
+
const gitPath = await which('git');
|
|
610
|
+
console.log(`Git found at: ${gitPath}`);
|
|
611
|
+
} catch (error) {
|
|
612
|
+
console.log('Git is not installed');
|
|
613
|
+
}
|
|
614
|
+
```
|
|
134
615
|
|
|
135
616
|
## License and Legal Information
|
|
136
617
|
|
|
@@ -149,4 +630,4 @@ Registered at District court Bremen HRB 35230 HB, Germany
|
|
|
149
630
|
|
|
150
631
|
For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
|
|
151
632
|
|
|
152
|
-
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
|
|
633
|
+
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
|