@push.rocks/smartshell 3.2.4 → 3.3.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
@@ -4,6 +4,10 @@
4
4
  [![npm version](https://img.shields.io/npm/v/@push.rocks/smartshell.svg)](https://www.npmjs.com/package/@push.rocks/smartshell)
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
6
 
7
+ ## ⚠️ Security Notice
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
+
7
11
  ## Why smartshell? 🚀
8
12
 
9
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.
@@ -13,10 +17,15 @@ Tired of wrestling with Node.js child processes? Meet `@push.rocks/smartshell` -
13
17
  - 🎯 **Promise-based API** - Async/await ready for modern codebases
14
18
  - 🔇 **Silent execution modes** - Control output verbosity
15
19
  - 📡 **Streaming support** - Real-time output for long-running processes
16
- - 🎮 **Interactive commands** - Handle user input when needed
20
+ - 🎮 **Interactive commands** - Handle user input programmatically
21
+ - 🛡️ **Secure execution** - Shell-free methods for untrusted input
17
22
  - ⚡ **Smart execution modes** - Strict, silent, or streaming
18
23
  - 🔍 **Pattern matching** - Wait for specific output patterns
19
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
20
29
  - 🛡️ **TypeScript first** - Full type safety and IntelliSense
21
30
 
22
31
  ## Installation 📦
@@ -30,6 +39,9 @@ yarn add @push.rocks/smartshell
30
39
 
31
40
  # Using pnpm (recommended)
32
41
  pnpm add @push.rocks/smartshell
42
+
43
+ # Optional: For PTY support (terminal emulation)
44
+ pnpm add --save-optional node-pty
33
45
  ```
34
46
 
35
47
  ## Quick Start 🏃‍♂️
@@ -45,413 +57,256 @@ const shell = new Smartshell({
45
57
  // Run a simple command
46
58
  const result = await shell.exec('echo "Hello, World!"');
47
59
  console.log(result.stdout); // "Hello, World!"
60
+ console.log(result.signal); // undefined (no signal)
61
+ console.log(result.stderr); // "" (no errors)
48
62
  ```
49
63
 
50
- ## Core Concepts 💡
64
+ ## Security-First Execution 🔒
51
65
 
52
- ### The Smartshell Instance
66
+ ### Secure Command Execution with execSpawn
53
67
 
54
- The heart of smartshell is the `Smartshell` class. Each instance maintains its own environment and configuration:
68
+ When dealing with untrusted input, **always use execSpawn methods** which don't use shell interpretation:
55
69
 
56
70
  ```typescript
57
- const shell = new Smartshell({
58
- executor: 'bash', // Choose your shell: 'bash' or 'sh'
59
- sourceFilePaths: ['/path/to/env.sh'], // Optional: source files on init
60
- });
71
+ // DANGEROUS with untrusted input
72
+ const userInput = "file.txt; rm -rf /";
73
+ await shell.exec(`cat ${userInput}`); // Command injection!
74
+
75
+ // ✅ SAFE with untrusted input
76
+ await shell.execSpawn('cat', [userInput]); // Arguments are properly escaped
61
77
  ```
62
78
 
63
- ## Execution Modes 🎛️
79
+ ### execSpawn Family Methods
64
80
 
65
- ### Standard Execution
81
+ ```typescript
82
+ // Basic secure execution
83
+ const result = await shell.execSpawn('ls', ['-la', '/home']);
66
84
 
67
- Perfect for general commands where you want to see the output:
85
+ // Streaming secure execution
86
+ const streaming = await shell.execSpawnStreaming('npm', ['install']);
87
+ await streaming.finalPromise;
68
88
 
69
- ```typescript
70
- const result = await shell.exec('ls -la');
71
- console.log(result.stdout); // Directory listing
72
- console.log(result.exitCode); // 0 for success
89
+ // Interactive secure execution
90
+ const interactive = await shell.execSpawnInteractiveControl('cat', []);
91
+ await interactive.sendLine('Hello');
92
+ interactive.endInput();
93
+ await interactive.finalPromise;
73
94
  ```
74
95
 
75
- ### Silent Execution
96
+ ## Production Features 🏭
76
97
 
77
- Run commands without printing to console - ideal for capturing output:
98
+ ### Resource Management
99
+
100
+ Prevent memory issues with built-in buffer limits:
78
101
 
79
102
  ```typescript
80
- const result = await shell.execSilent('cat /etc/hostname');
81
- // Output is NOT printed to console but IS captured in result
82
- console.log(result.stdout); // Access the captured output here
83
- console.log(result.exitCode); // Check exit code (0 = success)
84
-
85
- // Example: Process output programmatically
86
- const files = await shell.execSilent('ls -la');
87
- const fileList = files.stdout.split('
88
- ');
89
- fileList.forEach(file => {
90
- // Process each file entry
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
+ }
91
109
  });
92
110
  ```
93
111
 
94
- **Key Point:** Silent methods (`execSilent`, `execStrictSilent`, `execStreamingSilent`) suppress console output but still capture everything in the result object for programmatic access.
95
-
96
- ### Strict Execution
112
+ ### Timeout Support
97
113
 
98
- Throws an error if the command fails - great for critical operations:
114
+ Automatically terminate long-running processes:
99
115
 
100
116
  ```typescript
101
117
  try {
102
- await shell.execStrict('critical-command');
103
- console.log('✅ Command succeeded!');
118
+ const result = await shell.execSpawn('long-process', [], {
119
+ timeout: 5000 // 5 second timeout
120
+ });
104
121
  } catch (error) {
105
- console.error(' Command failed:', error);
122
+ console.log('Process timed out');
106
123
  }
107
124
  ```
108
125
 
109
- ### Streaming Execution
126
+ ### Debug Mode
110
127
 
111
- For long-running processes or when you need real-time output:
128
+ Enable detailed logging for troubleshooting:
112
129
 
113
130
  ```typescript
114
- const streaming = await shell.execStreaming('npm install');
115
-
116
- // Access the child process directly
117
- streaming.childProcess.stdout.on('data', (chunk) => {
118
- console.log('📦 Installing:', chunk.toString());
131
+ const result = await shell.exec('command', {
132
+ debug: true // Logs process lifecycle events
119
133
  });
120
-
121
- // Wait for completion
122
- await streaming.finalPromise;
123
- ```
124
-
125
- ### Interactive Execution
126
-
127
- When commands need user input:
128
-
129
- ```typescript
130
- // This will connect to your terminal for input
131
- await shell.execInteractive('npm init');
132
134
  ```
133
135
 
134
- ## Advanced Features 🔥
135
-
136
- ### Wait for Specific Output
136
+ ### Custom Environment
137
137
 
138
- Perfect for waiting on services to start:
138
+ Control the execution environment precisely:
139
139
 
140
140
  ```typescript
141
- // Wait for a specific line before continuing
142
- await shell.execAndWaitForLine(
143
- 'npm run dev',
144
- /Server started on port 3000/
145
- );
146
- console.log('🚀 Server is ready!');
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
+ }
147
+ });
147
148
  ```
148
149
 
149
- ### Silent Pattern Waiting
150
-
151
- Same as above, but without console output:
150
+ ## Interactive Control 🎮
152
151
 
153
- ```typescript
154
- await shell.execAndWaitForLineSilent(
155
- 'docker-compose up',
156
- /database system is ready to accept connections/
157
- );
158
- // The command output is suppressed from console but the pattern matching still works
159
- ```
152
+ ### Programmatic Input Control
160
153
 
161
- ### Environment Customization
162
-
163
- Smartshell provides powerful environment management:
154
+ Send input to processes programmatically:
164
155
 
165
156
  ```typescript
166
- // Add custom source files
167
- shell.shellEnv.addSourceFiles([
168
- '/home/user/.custom_env',
169
- './project.env.sh'
170
- ]);
171
-
172
- // Modify PATH
173
- shell.shellEnv.pathDirArray.push('/custom/bin');
174
- shell.shellEnv.pathDirArray.push('/usr/local/special');
157
+ const interactive = await shell.execInteractiveControl('cat');
175
158
 
176
- // Your custom environment is ready
177
- const result = await shell.exec('my-custom-command');
178
- ```
179
-
180
- ### Smart Execution Utility
181
-
182
- The `SmartExecution` class enables restartable streaming processes:
183
-
184
- ```typescript
185
- import { SmartExecution } from '@push.rocks/smartshell';
159
+ // Send input line by line
160
+ await interactive.sendLine('Line 1');
161
+ await interactive.sendLine('Line 2');
186
162
 
187
- const execution = new SmartExecution(shell, 'npm run watch');
163
+ // Send raw input without newline
164
+ await interactive.sendInput('partial');
188
165
 
189
- // Restart the process whenever needed
190
- await execution.restart();
166
+ // Close stdin
167
+ interactive.endInput();
191
168
 
192
- // Access the current streaming execution
193
- if (execution.currentStreamingExecution) {
194
- execution.currentStreamingExecution.childProcess.stdout.on('data', (data) => {
195
- console.log(data.toString());
196
- });
197
- }
169
+ // Wait for completion
170
+ const result = await interactive.finalPromise;
198
171
  ```
199
172
 
200
- ### Shell Detection
173
+ ### Passthrough Mode
201
174
 
202
- Need to check if a command exists? We export the `which` utility:
175
+ Connect stdin for real keyboard interaction:
203
176
 
204
177
  ```typescript
205
- import { which } from '@push.rocks/smartshell';
206
-
207
- try {
208
- const gitPath = await which('git');
209
- console.log(`Git found at: ${gitPath}`);
210
- } catch (error) {
211
- console.log('Git is not installed');
212
- }
178
+ // User can type directly
179
+ await shell.execPassthrough('vim file.txt');
213
180
  ```
214
181
 
215
- ## Real-World Examples 🌍
182
+ ## PTY Support - Full Terminal Emulation 🖥️
216
183
 
217
- ### Build Pipeline
184
+ Smartshell provides two modes for executing interactive commands:
218
185
 
219
- ```typescript
220
- const shell = new Smartshell({ executor: 'bash' });
186
+ 1. **Pipe Mode (Default)** - Fast, simple, no dependencies
187
+ 2. **PTY Mode** - Full terminal emulation for advanced interactive programs
221
188
 
222
- // Clean build directory
223
- await shell.execSilent('rm -rf dist');
189
+ ### When to Use Each Mode
224
190
 
225
- // Run TypeScript compiler
226
- const buildResult = await shell.execStrict('tsc');
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
227
197
 
228
- // Run tests
229
- await shell.execStrict('npm test');
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
230
210
 
231
- // Build succeeded!
232
- console.log('✅ Build pipeline completed successfully');
233
- ```
211
+ ### Installing PTY Support
234
212
 
235
- ### Development Server with Auto-Restart
213
+ PTY support requires the optional `node-pty` dependency:
236
214
 
237
- ```typescript
238
- const shell = new Smartshell({ executor: 'bash' });
239
- const devServer = new SmartExecution(shell, 'npm run dev');
215
+ ```bash
216
+ # Install as optional dependency
217
+ pnpm add --save-optional node-pty
240
218
 
241
- // Watch for file changes and restart
242
- fs.watch('./src', async () => {
243
- console.log('🔄 Changes detected, restarting...');
244
- await devServer.restart();
245
- });
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
246
222
  ```
247
223
 
248
- ### Docker Compose Helper
224
+ ### PTY Usage Examples
249
225
 
250
226
  ```typescript
251
- const shell = new Smartshell({ executor: 'bash' });
252
-
253
- // Start services and wait for readiness
254
- console.log('🐳 Starting Docker services...');
255
- await shell.execAndWaitForLine(
256
- 'docker-compose up',
257
- /All services are ready/,
258
- { timeout: 60000 }
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\"'"
259
230
  );
231
+ await ptyInteractive.sendLine('John');
232
+ const result = await ptyInteractive.finalPromise;
233
+ // With PTY, the prompt "Enter name: " will be visible in stdout
260
234
 
261
- // Run migrations
262
- await shell.execStrict('docker-compose exec app npm run migrate');
263
- console.log('✅ Environment ready!');
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
264
241
  ```
265
242
 
266
- ### CI/CD Integration
267
-
268
- ```typescript
269
- const shell = new Smartshell({ executor: 'bash' });
270
-
271
- async function runCIPipeline() {
272
- // Install dependencies
273
- await shell.execStrict('pnpm install --frozen-lockfile');
274
-
275
- // Run linting
276
- const lintResult = await shell.execSilent('npm run lint');
277
- if (lintResult.exitCode !== 0) {
278
- throw new Error(`Linting failed:
279
- ${lintResult.stdout}`);
280
- }
281
-
282
- // Run tests with coverage
283
- const testResult = await shell.exec('npm run test:coverage');
284
-
285
- // Build project
286
- await shell.execStrict('npm run build');
287
-
288
- // Deploy if on main branch
289
- if (process.env.BRANCH === 'main') {
290
- await shell.execStrict('npm run deploy');
291
- }
292
- }
293
- ```
294
-
295
- ## API Reference 📚
296
-
297
- ### Smartshell Class
243
+ ### PTY vs Pipe Mode Comparison
298
244
 
299
- | Method | Description | Returns |
300
- |--------|-------------|---------|
301
- | `exec(command)` | Execute command with output | `Promise<IExecResult>` |
302
- | `execSilent(command)` | Execute without console output | `Promise<IExecResult>` |
303
- | `execStrict(command)` | Execute, throw on failure | `Promise<IExecResult>` |
304
- | `execStrictSilent(command)` | Strict + silent execution | `Promise<IExecResult>` |
305
- | `execStreaming(command)` | Stream output in real-time | `Promise<IExecResultStreaming>` |
306
- | `execStreamingSilent(command)` | Stream without console output | `Promise<IExecResultStreaming>` |
307
- | `execInteractive(command)` | Interactive terminal mode | `Promise<void>` |
308
- | `execAndWaitForLine(command, regex)` | Wait for pattern match | `Promise<void>` |
309
- | `execAndWaitForLineSilent(command, regex)` | Silent pattern waiting | `Promise<void>` |
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) |
310
254
 
311
- ### Result Interfaces
255
+ ### Common PTY Patterns
312
256
 
313
257
  ```typescript
314
- interface IExecResult {
315
- exitCode: number; // Process exit code
316
- stdout: string; // Standard output
317
- }
318
-
319
- interface IExecResultStreaming {
320
- childProcess: ChildProcess; // Node.js ChildProcess instance
321
- finalPromise: Promise<void>; // Resolves when process exits
322
- }
323
- ```
324
-
325
- ## Understanding Silent Modes 🤫
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;
326
262
 
327
- Silent execution modes are perfect when you need to capture command output for processing without cluttering the console. Here's what you need to know:
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');
328
267
 
329
- ### How Silent Modes Work
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
+ ```
330
273
 
331
- 1. **Output is captured, not lost**: All stdout content is stored in the result object
332
- 2. **Console stays clean**: Nothing is printed during execution
333
- 3. **Full programmatic access**: Process the output however you need
274
+ ### PTY Fallback Strategy
334
275
 
335
- ### Available Silent Methods
276
+ Always provide a fallback for when PTY isn't available:
336
277
 
337
278
  ```typescript
338
- // Basic silent execution
339
- const result = await shell.execSilent('ls -la');
340
- console.log(result.stdout); // Access captured output
341
- console.log(result.exitCode); // Check success/failure
342
-
343
- // Strict + Silent (throws on error)
344
279
  try {
345
- const result = await shell.execStrictSilent('important-command');
346
- const output = result.stdout; // Process the output
280
+ // Try PTY mode first
281
+ const result = await shell.execInteractiveControlPty(command);
282
+ // ...
347
283
  } catch (error) {
348
- // Handle failure
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
+ // ...
289
+ }
349
290
  }
350
-
351
- // Streaming + Silent
352
- const streaming = await shell.execStreamingSilent('long-running-process');
353
- streaming.childProcess.stdout.on('data', (chunk) => {
354
- // Process chunks as they arrive
355
- const data = chunk.toString();
356
- });
357
-
358
- // Pattern matching + Silent
359
- await shell.execAndWaitForLineSilent('server-start', /Ready on port/);
360
- ```
361
-
362
- ### Common Use Cases for Silent Execution
363
-
364
- ```typescript
365
- // Parse JSON output
366
- const jsonResult = await shell.execSilent('aws s3 ls --output json');
367
- const buckets = JSON.parse(jsonResult.stdout);
368
-
369
- // Count lines
370
- const wcResult = await shell.execSilent('wc -l huge-file.txt');
371
- const lineCount = parseInt(wcResult.stdout.split(' ')[0]);
372
-
373
- // Check if command exists
374
- const whichResult = await shell.execSilent('which docker');
375
- const dockerPath = whichResult.exitCode === 0 ? whichResult.stdout.trim() : null;
376
-
377
- // Collect system info
378
- const unameResult = await shell.execSilent('uname -a');
379
- const systemInfo = unameResult.stdout.trim();
380
291
  ```
381
292
 
382
- ## Tips & Best Practices 💎
383
-
384
- 1. **Choose the right executor**: Use `bash` for full features, `sh` for minimal overhead
385
- 2. **Use strict mode for critical operations**: Ensures failures don't go unnoticed
386
- 3. **Stream long-running processes**: Better UX and memory efficiency
387
- 4. **Leverage silent modes**: When you only need to capture output
388
- 5. **Handle errors gracefully**: Always wrap strict executions in try-catch
389
- 6. **Clean up resources**: Streaming processes should be properly terminated
390
-
391
- ## License and Legal Information
392
-
393
- This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository.
394
-
395
- **Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
293
+ ## Advanced Pattern Matching 🔍
396
294
 
397
- ### Trademarks
295
+ ### Enhanced execAndWaitForLine
398
296
 
399
- This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.
400
-
401
- ### Company Information
402
-
403
- Task Venture Capital GmbH
404
- Registered at District court Bremen HRB 35230 HB, Germany
405
-
406
- For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
407
-
408
- 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.# @push.rocks/smartshell 🐚
409
- **Execute shell commands with superpowers in Node.js**
410
-
411
- [![npm version](https://img.shields.io/npm/v/@push.rocks/smartshell.svg)](https://www.npmjs.com/package/@push.rocks/smartshell)
412
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
413
-
414
- ## Why smartshell? 🚀
415
-
416
- 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.
417
-
418
- ### ✨ Key Features
419
-
420
- - 🎯 **Promise-based API** - Async/await ready for modern codebases
421
- - 🔇 **Silent execution modes** - Control output verbosity
422
- - 📡 **Streaming support** - Real-time output for long-running processes
423
- - 🎮 **Interactive commands** - Handle user input when needed
424
- - ⚡ **Smart execution modes** - Strict, silent, or streaming
425
- - 🔍 **Pattern matching** - Wait for specific output patterns
426
- - 🌍 **Environment management** - Custom env vars and PATH handling
427
- - 🛡️ **TypeScript first** - Full type safety and IntelliSense
428
-
429
- ## Installation 📦
430
-
431
- ```bash
432
- # Using npm
433
- npm install @push.rocks/smartshell --save
434
-
435
- # Using yarn
436
- yarn add @push.rocks/smartshell
437
-
438
- # Using pnpm (recommended)
439
- pnpm add @push.rocks/smartshell
440
- ```
441
-
442
- ## Quick Start 🏃‍♂️
297
+ Wait for patterns with timeout and auto-termination:
443
298
 
444
299
  ```typescript
445
- import { Smartshell } from '@push.rocks/smartshell';
446
-
447
- // Create your shell instance
448
- const shell = new Smartshell({
449
- executor: 'bash' // or 'sh' for lighter shells
450
- });
451
-
452
- // Run a simple command
453
- const result = await shell.exec('echo "Hello, World!"');
454
- console.log(result.stdout); // "Hello, World!"
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
+ );
455
310
  ```
456
311
 
457
312
  ## Core Concepts 💡
@@ -477,6 +332,8 @@ Perfect for general commands where you want to see the output:
477
332
  const result = await shell.exec('ls -la');
478
333
  console.log(result.stdout); // Directory listing
479
334
  console.log(result.exitCode); // 0 for success
335
+ console.log(result.signal); // Signal if terminated
336
+ console.log(result.stderr); // Error output
480
337
  ```
481
338
 
482
339
  ### Silent Execution
@@ -487,19 +344,8 @@ Run commands without printing to console - ideal for capturing output:
487
344
  const result = await shell.execSilent('cat /etc/hostname');
488
345
  // Output is NOT printed to console but IS captured in result
489
346
  console.log(result.stdout); // Access the captured output here
490
- console.log(result.exitCode); // Check exit code (0 = success)
491
-
492
- // Example: Process output programmatically
493
- const files = await shell.execSilent('ls -la');
494
- const fileList = files.stdout.split('
495
- ');
496
- fileList.forEach(file => {
497
- // Process each file entry
498
- });
499
347
  ```
500
348
 
501
- **Key Point:** Silent methods (`execSilent`, `execStrictSilent`, `execStreamingSilent`) suppress console output but still capture everything in the result object for programmatic access.
502
-
503
349
  ### Strict Execution
504
350
 
505
351
  Throws an error if the command fails - great for critical operations:
@@ -509,7 +355,8 @@ try {
509
355
  await shell.execStrict('critical-command');
510
356
  console.log('✅ Command succeeded!');
511
357
  } catch (error) {
512
- console.error('❌ Command failed:', error);
358
+ console.error('❌ Command failed:', error.message);
359
+ // Error includes exit code or signal information
513
360
  }
514
361
  ```
515
362
 
@@ -525,100 +372,15 @@ streaming.childProcess.stdout.on('data', (chunk) => {
525
372
  console.log('📦 Installing:', chunk.toString());
526
373
  });
527
374
 
375
+ // Control the process
376
+ await streaming.terminate(); // SIGTERM
377
+ await streaming.kill(); // SIGKILL
378
+ await streaming.keyboardInterrupt(); // SIGINT
379
+
528
380
  // Wait for completion
529
381
  await streaming.finalPromise;
530
382
  ```
531
383
 
532
- ### Interactive Execution
533
-
534
- When commands need user input:
535
-
536
- ```typescript
537
- // This will connect to your terminal for input
538
- await shell.execInteractive('npm init');
539
- ```
540
-
541
- ## Advanced Features 🔥
542
-
543
- ### Wait for Specific Output
544
-
545
- Perfect for waiting on services to start:
546
-
547
- ```typescript
548
- // Wait for a specific line before continuing
549
- await shell.execAndWaitForLine(
550
- 'npm run dev',
551
- /Server started on port 3000/
552
- );
553
- console.log('🚀 Server is ready!');
554
- ```
555
-
556
- ### Silent Pattern Waiting
557
-
558
- Same as above, but without console output:
559
-
560
- ```typescript
561
- await shell.execAndWaitForLineSilent(
562
- 'docker-compose up',
563
- /database system is ready to accept connections/
564
- );
565
- // The command output is suppressed from console but the pattern matching still works
566
- ```
567
-
568
- ### Environment Customization
569
-
570
- Smartshell provides powerful environment management:
571
-
572
- ```typescript
573
- // Add custom source files
574
- shell.shellEnv.addSourceFiles([
575
- '/home/user/.custom_env',
576
- './project.env.sh'
577
- ]);
578
-
579
- // Modify PATH
580
- shell.shellEnv.pathDirArray.push('/custom/bin');
581
- shell.shellEnv.pathDirArray.push('/usr/local/special');
582
-
583
- // Your custom environment is ready
584
- const result = await shell.exec('my-custom-command');
585
- ```
586
-
587
- ### Smart Execution Utility
588
-
589
- The `SmartExecution` class enables restartable streaming processes:
590
-
591
- ```typescript
592
- import { SmartExecution } from '@push.rocks/smartshell';
593
-
594
- const execution = new SmartExecution(shell, 'npm run watch');
595
-
596
- // Restart the process whenever needed
597
- await execution.restart();
598
-
599
- // Access the current streaming execution
600
- if (execution.currentStreamingExecution) {
601
- execution.currentStreamingExecution.childProcess.stdout.on('data', (data) => {
602
- console.log(data.toString());
603
- });
604
- }
605
- ```
606
-
607
- ### Shell Detection
608
-
609
- Need to check if a command exists? We export the `which` utility:
610
-
611
- ```typescript
612
- import { which } from '@push.rocks/smartshell';
613
-
614
- try {
615
- const gitPath = await which('git');
616
- console.log(`Git found at: ${gitPath}`);
617
- } catch (error) {
618
- console.log('Git is not installed');
619
- }
620
- ```
621
-
622
384
  ## Real-World Examples 🌍
623
385
 
624
386
  ### Build Pipeline
@@ -639,17 +401,20 @@ await shell.execStrict('npm test');
639
401
  console.log('✅ Build pipeline completed successfully');
640
402
  ```
641
403
 
642
- ### Development Server with Auto-Restart
404
+ ### CI/CD with Security
643
405
 
644
406
  ```typescript
645
- const shell = new Smartshell({ executor: 'bash' });
646
- const devServer = new SmartExecution(shell, 'npm run dev');
647
-
648
- // Watch for file changes and restart
649
- fs.watch('./src', async () => {
650
- console.log('🔄 Changes detected, restarting...');
651
- await devServer.restart();
652
- });
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]);
413
+
414
+ // Safe to use exec for hardcoded commands
415
+ await shell.execStrict('npm run build');
416
+ await shell.execStrict('npm run deploy');
417
+ }
653
418
  ```
654
419
 
655
420
  ### Docker Compose Helper
@@ -662,7 +427,11 @@ console.log('🐳 Starting Docker services...');
662
427
  await shell.execAndWaitForLine(
663
428
  'docker-compose up',
664
429
  /All services are ready/,
665
- { timeout: 60000 }
430
+ false,
431
+ {
432
+ timeout: 60000,
433
+ terminateOnMatch: false // Keep running after match
434
+ }
666
435
  );
667
436
 
668
437
  // Run migrations
@@ -670,130 +439,179 @@ await shell.execStrict('docker-compose exec app npm run migrate');
670
439
  console.log('✅ Environment ready!');
671
440
  ```
672
441
 
673
- ### CI/CD Integration
442
+ ### Development Server with Auto-Restart
674
443
 
675
444
  ```typescript
445
+ import { SmartExecution } from '@push.rocks/smartshell';
446
+
676
447
  const shell = new Smartshell({ executor: 'bash' });
448
+ const devServer = new SmartExecution(shell, 'npm run dev');
677
449
 
678
- async function runCIPipeline() {
679
- // Install dependencies
680
- await shell.execStrict('pnpm install --frozen-lockfile');
681
-
682
- // Run linting
683
- const lintResult = await shell.execSilent('npm run lint');
684
- if (lintResult.exitCode !== 0) {
685
- throw new Error(`Linting failed:
686
- ${lintResult.stdout}`);
687
- }
688
-
689
- // Run tests with coverage
690
- const testResult = await shell.exec('npm run test:coverage');
691
-
692
- // Build project
693
- await shell.execStrict('npm run build');
694
-
695
- // Deploy if on main branch
696
- if (process.env.BRANCH === 'main') {
697
- await shell.execStrict('npm run deploy');
698
- }
699
- }
450
+ // Watch for file changes and restart
451
+ fs.watch('./src', async () => {
452
+ console.log('🔄 Changes detected, restarting...');
453
+ await devServer.restart();
454
+ });
700
455
  ```
701
456
 
702
457
  ## API Reference 📚
703
458
 
704
459
  ### Smartshell Class
705
460
 
706
- | Method | Description | Returns |
707
- |--------|-------------|---------|
708
- | `exec(command)` | Execute command with output | `Promise<IExecResult>` |
709
- | `execSilent(command)` | Execute without console output | `Promise<IExecResult>` |
710
- | `execStrict(command)` | Execute, throw on failure | `Promise<IExecResult>` |
711
- | `execStrictSilent(command)` | Strict + silent execution | `Promise<IExecResult>` |
712
- | `execStreaming(command)` | Stream output in real-time | `Promise<IExecResultStreaming>` |
713
- | `execStreamingSilent(command)` | Stream without console output | `Promise<IExecResultStreaming>` |
714
- | `execInteractive(command)` | Interactive terminal mode | `Promise<void>` |
715
- | `execAndWaitForLine(command, regex)` | Wait for pattern match | `Promise<void>` |
716
- | `execAndWaitForLineSilent(command, regex)` | Silent pattern waiting | `Promise<void>` |
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 |
717
476
 
718
477
  ### Result Interfaces
719
478
 
720
479
  ```typescript
721
480
  interface IExecResult {
722
- exitCode: number; // Process exit code
723
- stdout: string; // Standard output
481
+ exitCode: number; // Process exit code
482
+ stdout: string; // Standard output
483
+ signal?: NodeJS.Signals; // Termination signal
484
+ stderr?: string; // Error output
724
485
  }
725
486
 
726
487
  interface IExecResultStreaming {
727
488
  childProcess: ChildProcess; // Node.js ChildProcess instance
728
- finalPromise: Promise<void>; // Resolves when process exits
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>;
729
497
  }
730
- ```
731
498
 
732
- ## Understanding Silent Modes 🤫
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
+ ```
733
506
 
734
- Silent execution modes are perfect when you need to capture command output for processing without cluttering the console. Here's what you need to know:
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
+ ```
735
530
 
736
- ### How Silent Modes Work
531
+ ## Security Guide
737
532
 
738
- 1. **Output is captured, not lost**: All stdout content is stored in the result object
739
- 2. **Console stays clean**: Nothing is printed during execution
740
- 3. **Full programmatic access**: Process the output however you need
533
+ ### Command Injection Prevention
741
534
 
742
- ### Available Silent Methods
535
+ The standard `exec` methods use `shell: true`, which can lead to command injection vulnerabilities:
743
536
 
744
537
  ```typescript
745
- // Basic silent execution
746
- const result = await shell.execSilent('ls -la');
747
- console.log(result.stdout); // Access captured output
748
- console.log(result.exitCode); // Check success/failure
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 /
749
541
 
750
- // Strict + Silent (throws on error)
751
- try {
752
- const result = await shell.execStrictSilent('important-command');
753
- const output = result.stdout; // Process the output
754
- } catch (error) {
755
- // Handle failure
756
- }
542
+ // SAFE - Arguments are properly escaped
543
+ await smartshell.execSpawn('cat', [userInput]); // Will look for literal filename
544
+ ```
757
545
 
758
- // Streaming + Silent
759
- const streaming = await shell.execStreamingSilent('long-running-process');
760
- streaming.childProcess.stdout.on('data', (chunk) => {
761
- // Process chunks as they arrive
762
- const data = chunk.toString();
763
- });
546
+ ### Best Practices
764
547
 
765
- // Pattern matching + Silent
766
- await shell.execAndWaitForLineSilent('server-start', /Ready on port/);
767
- ```
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
768
553
 
769
- ### Common Use Cases for Silent Execution
554
+ ### Path Traversal Protection
770
555
 
771
556
  ```typescript
772
- // Parse JSON output
773
- const jsonResult = await shell.execSilent('aws s3 ls --output json');
774
- const buckets = JSON.parse(jsonResult.stdout);
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]);
565
+ ```
775
566
 
776
- // Count lines
777
- const wcResult = await shell.execSilent('wc -l huge-file.txt');
778
- const lineCount = parseInt(wcResult.stdout.split(' ')[0]);
567
+ ## Tips & Best Practices 💎
779
568
 
780
- // Check if command exists
781
- const whichResult = await shell.execSilent('which docker');
782
- const dockerPath = whichResult.exitCode === 0 ? whichResult.stdout.trim() : null;
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
783
583
 
784
- // Collect system info
785
- const unameResult = await shell.execSilent('uname -a');
786
- const systemInfo = unameResult.stdout.trim();
584
+ Smartshell provides powerful environment management:
585
+
586
+ ```typescript
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');
787
599
  ```
788
600
 
789
- ## Tips & Best Practices 💎
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';
790
607
 
791
- 1. **Choose the right executor**: Use `bash` for full features, `sh` for minimal overhead
792
- 2. **Use strict mode for critical operations**: Ensures failures don't go unnoticed
793
- 3. **Stream long-running processes**: Better UX and memory efficiency
794
- 4. **Leverage silent modes**: When you only need to capture output
795
- 5. **Handle errors gracefully**: Always wrap strict executions in try-catch
796
- 6. **Clean up resources**: Streaming processes should be properly terminated
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
+ ```
797
615
 
798
616
  ## License and Legal Information
799
617