@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/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.smartshell.d.ts +70 -2
- package/dist_ts/classes.smartshell.js +595 -24
- package/npmextra.json +9 -0
- package/package.json +9 -8
- package/readme.md +334 -516
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.smartshell.ts +695 -25
package/readme.md
CHANGED
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/@push.rocks/smartshell)
|
|
5
5
|
[](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
|
|
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
|
-
##
|
|
64
|
+
## Security-First Execution 🔒
|
|
51
65
|
|
|
52
|
-
###
|
|
66
|
+
### Secure Command Execution with execSpawn
|
|
53
67
|
|
|
54
|
-
|
|
68
|
+
When dealing with untrusted input, **always use execSpawn methods** which don't use shell interpretation:
|
|
55
69
|
|
|
56
70
|
```typescript
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
79
|
+
### execSpawn Family Methods
|
|
64
80
|
|
|
65
|
-
|
|
81
|
+
```typescript
|
|
82
|
+
// Basic secure execution
|
|
83
|
+
const result = await shell.execSpawn('ls', ['-la', '/home']);
|
|
66
84
|
|
|
67
|
-
|
|
85
|
+
// Streaming secure execution
|
|
86
|
+
const streaming = await shell.execSpawnStreaming('npm', ['install']);
|
|
87
|
+
await streaming.finalPromise;
|
|
68
88
|
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
96
|
+
## Production Features 🏭
|
|
76
97
|
|
|
77
|
-
|
|
98
|
+
### Resource Management
|
|
99
|
+
|
|
100
|
+
Prevent memory issues with built-in buffer limits:
|
|
78
101
|
|
|
79
102
|
```typescript
|
|
80
|
-
const result = await shell.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
### Strict Execution
|
|
112
|
+
### Timeout Support
|
|
97
113
|
|
|
98
|
-
|
|
114
|
+
Automatically terminate long-running processes:
|
|
99
115
|
|
|
100
116
|
```typescript
|
|
101
117
|
try {
|
|
102
|
-
await shell.
|
|
103
|
-
|
|
118
|
+
const result = await shell.execSpawn('long-process', [], {
|
|
119
|
+
timeout: 5000 // 5 second timeout
|
|
120
|
+
});
|
|
104
121
|
} catch (error) {
|
|
105
|
-
console.
|
|
122
|
+
console.log('Process timed out');
|
|
106
123
|
}
|
|
107
124
|
```
|
|
108
125
|
|
|
109
|
-
###
|
|
126
|
+
### Debug Mode
|
|
110
127
|
|
|
111
|
-
|
|
128
|
+
Enable detailed logging for troubleshooting:
|
|
112
129
|
|
|
113
130
|
```typescript
|
|
114
|
-
const
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
### Wait for Specific Output
|
|
136
|
+
### Custom Environment
|
|
137
137
|
|
|
138
|
-
|
|
138
|
+
Control the execution environment precisely:
|
|
139
139
|
|
|
140
140
|
```typescript
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
Same as above, but without console output:
|
|
150
|
+
## Interactive Control 🎮
|
|
152
151
|
|
|
153
|
-
|
|
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
|
-
|
|
162
|
-
|
|
163
|
-
Smartshell provides powerful environment management:
|
|
154
|
+
Send input to processes programmatically:
|
|
164
155
|
|
|
165
156
|
```typescript
|
|
166
|
-
|
|
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
|
-
//
|
|
177
|
-
|
|
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
|
-
|
|
163
|
+
// Send raw input without newline
|
|
164
|
+
await interactive.sendInput('partial');
|
|
188
165
|
|
|
189
|
-
//
|
|
190
|
-
|
|
166
|
+
// Close stdin
|
|
167
|
+
interactive.endInput();
|
|
191
168
|
|
|
192
|
-
//
|
|
193
|
-
|
|
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
|
-
###
|
|
173
|
+
### Passthrough Mode
|
|
201
174
|
|
|
202
|
-
|
|
175
|
+
Connect stdin for real keyboard interaction:
|
|
203
176
|
|
|
204
177
|
```typescript
|
|
205
|
-
|
|
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
|
-
##
|
|
182
|
+
## PTY Support - Full Terminal Emulation 🖥️
|
|
216
183
|
|
|
217
|
-
|
|
184
|
+
Smartshell provides two modes for executing interactive commands:
|
|
218
185
|
|
|
219
|
-
|
|
220
|
-
|
|
186
|
+
1. **Pipe Mode (Default)** - Fast, simple, no dependencies
|
|
187
|
+
2. **PTY Mode** - Full terminal emulation for advanced interactive programs
|
|
221
188
|
|
|
222
|
-
|
|
223
|
-
await shell.execSilent('rm -rf dist');
|
|
189
|
+
### When to Use Each Mode
|
|
224
190
|
|
|
225
|
-
|
|
226
|
-
|
|
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
|
-
|
|
229
|
-
|
|
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
|
-
|
|
232
|
-
console.log('✅ Build pipeline completed successfully');
|
|
233
|
-
```
|
|
211
|
+
### Installing PTY Support
|
|
234
212
|
|
|
235
|
-
|
|
213
|
+
PTY support requires the optional `node-pty` dependency:
|
|
236
214
|
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
|
|
215
|
+
```bash
|
|
216
|
+
# Install as optional dependency
|
|
217
|
+
pnpm add --save-optional node-pty
|
|
240
218
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
-
###
|
|
224
|
+
### PTY Usage Examples
|
|
249
225
|
|
|
250
226
|
```typescript
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
-
//
|
|
262
|
-
await shell.
|
|
263
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
|
300
|
-
|
|
301
|
-
|
|
|
302
|
-
| `
|
|
303
|
-
|
|
|
304
|
-
|
|
|
305
|
-
|
|
|
306
|
-
|
|
|
307
|
-
|
|
|
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
|
-
###
|
|
255
|
+
### Common PTY Patterns
|
|
312
256
|
|
|
313
257
|
```typescript
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
346
|
-
const
|
|
280
|
+
// Try PTY mode first
|
|
281
|
+
const result = await shell.execInteractiveControlPty(command);
|
|
282
|
+
// ...
|
|
347
283
|
} catch (error) {
|
|
348
|
-
|
|
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
|
-
##
|
|
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
|
-
###
|
|
295
|
+
### Enhanced execAndWaitForLine
|
|
398
296
|
|
|
399
|
-
|
|
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
|
-
[](https://www.npmjs.com/package/@push.rocks/smartshell)
|
|
412
|
-
[](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
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
//
|
|
453
|
-
|
|
454
|
-
|
|
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
|
-
###
|
|
404
|
+
### CI/CD with Security
|
|
643
405
|
|
|
644
406
|
```typescript
|
|
645
|
-
|
|
646
|
-
const
|
|
647
|
-
|
|
648
|
-
//
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
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 |
|
|
707
|
-
|
|
708
|
-
| `exec(command)` | Execute
|
|
709
|
-
| `
|
|
710
|
-
| `
|
|
711
|
-
| `
|
|
712
|
-
| `execStreaming(command)` | Stream output in real-time |
|
|
713
|
-
| `
|
|
714
|
-
| `execInteractive(command)` | Interactive terminal mode |
|
|
715
|
-
| `
|
|
716
|
-
| `
|
|
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;
|
|
723
|
-
stdout: string;
|
|
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<
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
531
|
+
## Security Guide
|
|
737
532
|
|
|
738
|
-
|
|
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
|
-
|
|
535
|
+
The standard `exec` methods use `shell: true`, which can lead to command injection vulnerabilities:
|
|
743
536
|
|
|
744
537
|
```typescript
|
|
745
|
-
//
|
|
746
|
-
const
|
|
747
|
-
|
|
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
|
-
//
|
|
751
|
-
|
|
752
|
-
|
|
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
|
-
|
|
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
|
-
|
|
766
|
-
|
|
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
|
-
###
|
|
554
|
+
### Path Traversal Protection
|
|
770
555
|
|
|
771
556
|
```typescript
|
|
772
|
-
//
|
|
773
|
-
const
|
|
774
|
-
|
|
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
|
-
|
|
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
|
-
|
|
781
|
-
|
|
782
|
-
|
|
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
|
-
|
|
785
|
-
|
|
786
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
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
|
|