command-stream 0.7.1 โ 0.8.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 +265 -153
- package/package.json +36 -4
- package/src/$.mjs +3706 -1604
- package/src/$.utils.mjs +14 -6
- package/src/commands/$.basename.mjs +8 -6
- package/src/commands/$.cat.mjs +23 -6
- package/src/commands/$.cd.mjs +13 -4
- package/src/commands/$.cp.mjs +53 -26
- package/src/commands/$.dirname.mjs +6 -4
- package/src/commands/$.echo.mjs +11 -4
- package/src/commands/$.env.mjs +4 -4
- package/src/commands/$.exit.mjs +1 -1
- package/src/commands/$.false.mjs +1 -1
- package/src/commands/$.ls.mjs +29 -16
- package/src/commands/$.mkdir.mjs +25 -9
- package/src/commands/$.mv.mjs +50 -22
- package/src/commands/$.pwd.mjs +7 -4
- package/src/commands/$.rm.mjs +30 -13
- package/src/commands/$.seq.mjs +13 -9
- package/src/commands/$.sleep.mjs +83 -31
- package/src/commands/$.test.mjs +4 -4
- package/src/commands/$.touch.mjs +36 -11
- package/src/commands/$.true.mjs +1 -1
- package/src/commands/$.which.mjs +11 -6
- package/src/commands/$.yes.mjs +67 -23
- package/src/shell-parser.mjs +113 -85
package/README.md
CHANGED
|
@@ -27,43 +27,44 @@ A modern $ shell utility library with streaming, async iteration, and EventEmitt
|
|
|
27
27
|
|
|
28
28
|
## Comparison with Other Libraries
|
|
29
29
|
|
|
30
|
-
| Feature
|
|
31
|
-
|
|
32
|
-
| **๐ฆ NPM Package**
|
|
33
|
-
| **โญ GitHub Stars**
|
|
34
|
-
| **๐ Monthly Downloads**
|
|
35
|
-
| **๐ Total Downloads**
|
|
36
|
-
| **Runtime Support**
|
|
37
|
-
| **Template Literals**
|
|
38
|
-
| **Real-time Streaming**
|
|
39
|
-
| **Synchronous Execution**
|
|
40
|
-
| **Async Iteration**
|
|
41
|
-
| **EventEmitter Pattern**
|
|
42
|
-
| **Mixed Patterns**
|
|
43
|
-
| **Bun.$ Compatibility**
|
|
44
|
-
| **Shell Injection Protection** | โ
Smart auto-quoting
|
|
45
|
-
| **Cross-platform**
|
|
46
|
-
| **Performance**
|
|
47
|
-
| **Memory Efficiency**
|
|
48
|
-
| **Error Handling**
|
|
49
|
-
| **Shell Settings**
|
|
50
|
-
| **Stdout Support**
|
|
51
|
-
| **Stderr Support**
|
|
52
|
-
| **Stdin Support**
|
|
53
|
-
| **Built-in Commands**
|
|
54
|
-
| **Virtual Commands Engine**
|
|
55
|
-
| **Pipeline/Piping Support**
|
|
56
|
-
| **Bundle Size**
|
|
57
|
-
| **Signal Handling**
|
|
58
|
-
| **Process Management**
|
|
59
|
-
| **Debug Tracing**
|
|
60
|
-
| **Test Coverage**
|
|
61
|
-
| **CI Reliability**
|
|
62
|
-
| **Documentation**
|
|
63
|
-
| **TypeScript**
|
|
64
|
-
| **License**
|
|
65
|
-
|
|
66
|
-
**๐ Popularity & Adoption:**
|
|
30
|
+
| Feature | [**command-stream**](https://github.com/link-foundation/command-stream) | [**execa**](https://github.com/sindresorhus/execa) | [**cross-spawn**](https://github.com/moxystudio/node-cross-spawn) | [**Bun.$**](https://github.com/oven-sh/bun) | [**ShellJS**](https://github.com/shelljs/shelljs) | [**zx**](https://github.com/google/zx) |
|
|
31
|
+
| ------------------------------ | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
|
|
32
|
+
| **๐ฆ NPM Package** | [](https://www.npmjs.com/package/command-stream) | [](https://www.npmjs.com/package/execa) | [](https://www.npmjs.com/package/cross-spawn) | N/A (Built-in) | [](https://www.npmjs.com/package/shelljs) | [](https://www.npmjs.com/package/zx) |
|
|
33
|
+
| **โญ GitHub Stars** | [**โญ 2** (Please โญ us!)](https://github.com/link-foundation/command-stream) | [โญ 7,264](https://github.com/sindresorhus/execa) | [โญ 1,149](https://github.com/moxystudio/node-cross-spawn) | [โญ 80,169](https://github.com/oven-sh/bun) (Full Runtime) | [โญ 14,375](https://github.com/shelljs/shelljs) | [โญ 44,569](https://github.com/google/zx) |
|
|
34
|
+
| **๐ Monthly Downloads** | **893** (New project!) | **381M** | **409M** | N/A (Built-in) | **35M** | **4.2M** |
|
|
35
|
+
| **๐ Total Downloads** | **Growing** | **6B+** | **5.4B** | N/A (Built-in) | **596M** | **37M** |
|
|
36
|
+
| **Runtime Support** | โ
Bun + Node.js | โ
Node.js | โ
Node.js | ๐ก Bun only | โ
Node.js | โ
Node.js |
|
|
37
|
+
| **Template Literals** | โ
`` $`cmd` `` | โ
`` $`cmd` `` | โ Function calls | โ
`` $`cmd` `` | โ Function calls | โ
`` $`cmd` `` |
|
|
38
|
+
| **Real-time Streaming** | โ
Live output | ๐ก Limited | โ Buffer only | โ Buffer only | โ Buffer only | โ Buffer only |
|
|
39
|
+
| **Synchronous Execution** | โ
`.sync()` with events | โ
`execaSync` | โ
`spawnSync` | โ No | โ
Sync by default | โ No |
|
|
40
|
+
| **Async Iteration** | โ
`for await (chunk of $.stream())` | โ No | โ No | โ No | โ No | โ No |
|
|
41
|
+
| **EventEmitter Pattern** | โ
`.on('data', ...)` | ๐ก Limited events | ๐ก Child process events | โ No | โ No | โ No |
|
|
42
|
+
| **Mixed Patterns** | โ
Events + await/sync | โ No | โ No | โ No | โ No | โ No |
|
|
43
|
+
| **Bun.$ Compatibility** | โ
`.text()` method support | โ No | โ No | โ
Native API | โ No | โ No |
|
|
44
|
+
| **Shell Injection Protection** | โ
Smart auto-quoting | โ
Safe by default | โ
Safe by default | โ
Built-in | ๐ก Manual escaping | โ
Safe by default |
|
|
45
|
+
| **Cross-platform** | โ
macOS/Linux/Windows | โ
Yes | โ
**Specialized** cross-platform | โ
Yes | โ
Yes | โ
Yes |
|
|
46
|
+
| **Performance** | โก Fast (Bun optimized) | ๐ Moderate | โก Fast | โก Very fast | ๐ Moderate | ๐ Slow |
|
|
47
|
+
| **Memory Efficiency** | โ
Streaming prevents buildup | ๐ก Buffers in memory | ๐ก Buffers in memory | ๐ก Buffers in memory | ๐ก Buffers in memory | ๐ก Buffers in memory |
|
|
48
|
+
| **Error Handling** | โ
Configurable (`set -e`/`set +e`, non-zero OK by default) | โ
Throws on error | โ Basic (exit codes) | โ
Throws on error | โ
Configurable | โ
Throws on error |
|
|
49
|
+
| **Shell Settings** | โ
`set -e`/`set +e` equivalent | โ No | โ No | โ No | ๐ก Limited (`set()`) | โ No |
|
|
50
|
+
| **Stdout Support** | โ
Real-time streaming + events | โ
Node.js streams + interleaved | โ
Inherited/buffered | โ
Shell redirection + buffered | โ
Direct output | โ
Readable streams + `.pipe.stdout` |
|
|
51
|
+
| **Stderr Support** | โ
Real-time streaming + events | โ
Streams + interleaved output | โ
Inherited/buffered | โ
Redirection + `.quiet()` access | โ
Error output | โ
Readable streams + `.pipe.stderr` |
|
|
52
|
+
| **Stdin Support** | โ
string/Buffer/inherit/ignore | โ
Input/output streams | โ
Full stdio support | โ
Pipe operations | ๐ก Basic | โ
Basic stdin |
|
|
53
|
+
| **Built-in Commands** | โ
**18 commands**: cat, ls, mkdir, rm, mv, cp, touch, basename, dirname, seq, yes + all Bun.$ commands | โ Uses system | โ Uses system | โ
echo, cd, etc. | โ
**20+ commands**: cat, ls, mkdir, rm, mv, cp, etc. | โ Uses system |
|
|
54
|
+
| **Virtual Commands Engine** | โ
**Revolutionary**: Register JavaScript functions as shell commands with full pipeline support | โ No custom commands | โ No custom commands | โ No extensibility | โ No custom commands | โ No custom commands |
|
|
55
|
+
| **Pipeline/Piping Support** | โ
**Advanced**: System + Built-ins + Virtual + Mixed + `.pipe()` method | โ
Programmatic `.pipe()` + multi-destination | โ No piping | โ
Standard shell piping | โ
Shell piping + `.to()` method | โ
Shell piping + `.pipe()` method |
|
|
56
|
+
| **Bundle Size** | ๐ฆ **~20KB gzipped** | ๐ฆ ~400KB+ (packagephobia) | ๐ฆ ~2KB gzipped | ๐ฏ 0KB (built-in) | ๐ฆ ~15KB gzipped | ๐ฆ ~50KB+ (estimated) |
|
|
57
|
+
| **Signal Handling** | โ
**Advanced SIGINT/SIGTERM forwarding** with cleanup | ๐ก Basic | โ
**Excellent** cross-platform | ๐ก Basic | ๐ก Basic | ๐ก Basic |
|
|
58
|
+
| **Process Management** | โ
**Robust child process lifecycle** with proper termination | โ
Good | โ
**Excellent** spawn wrapper | โ Basic | ๐ก Limited | ๐ก Limited |
|
|
59
|
+
| **Debug Tracing** | โ
**Comprehensive VERBOSE logging** for CI/debugging | ๐ก Limited | โ No | โ No | ๐ก Basic | โ No |
|
|
60
|
+
| **Test Coverage** | โ
**518+ tests, 1165+ assertions** | โ
Excellent | โ
Good | ๐ก Good coverage | โ
Good | ๐ก Good |
|
|
61
|
+
| **CI Reliability** | โ
**Platform-specific handling** (macOS/Ubuntu) | โ
Good | โ
**Excellent** | ๐ก Basic | โ
Good | ๐ก Basic |
|
|
62
|
+
| **Documentation** | โ
**Comprehensive examples + guides** | โ
Excellent | ๐ก Basic | โ
Good | โ
Good | ๐ก Limited |
|
|
63
|
+
| **TypeScript** | ๐ Coming soon | โ
Full support | โ
Built-in | โ
Built-in | ๐ก Community types | โ
Full support |
|
|
64
|
+
| **License** | โ
**Unlicense (Public Domain)** | ๐ก MIT | ๐ก MIT | ๐ก MIT (+ LGPL dependencies) | ๐ก BSD-3-Clause | ๐ก Apache 2.0 |
|
|
65
|
+
|
|
66
|
+
**๐ Popularity & Adoption:**
|
|
67
|
+
|
|
67
68
|
- **โญ GitHub Stars:** [Bun: 80,169](https://github.com/oven-sh/bun) โข [zx: 44,569](https://github.com/google/zx) โข [ShellJS: 14,375](https://github.com/shelljs/shelljs) โข [execa: 7,264](https://github.com/sindresorhus/execa) โข [cross-spawn: 1,149](https://github.com/moxystudio/node-cross-spawn) โข [**command-stream: 2 โญ us!**](https://github.com/link-foundation/command-stream)
|
|
68
69
|
- **๐ Total Downloads:** [execa: 6B+](https://www.npmjs.com/package/execa) โข [cross-spawn: 5.4B](https://www.npmjs.com/package/cross-spawn) โข [ShellJS: 596M](https://www.npmjs.com/package/shelljs) โข [zx: 37M](https://www.npmjs.com/package/zx) โข [command-stream: Growing](https://www.npmjs.com/package/command-stream)
|
|
69
70
|
- **๐ Monthly Downloads:** [cross-spawn: 409M](https://www.npmjs.com/package/cross-spawn) โข [execa: 381M](https://www.npmjs.com/package/execa) โข [ShellJS: 35M](https://www.npmjs.com/package/shelljs) โข [zx: 4.2M](https://www.npmjs.com/package/zx) โข [command-stream: 893 (growing!)](https://www.npmjs.com/package/command-stream)
|
|
@@ -79,7 +80,7 @@ A modern $ shell utility library with streaming, async iteration, and EventEmitt
|
|
|
79
80
|
- **๐ก Real-time Processing**: Only library with true streaming and async iteration
|
|
80
81
|
- **๐ Flexible Patterns**: Multiple usage patterns (await, events, iteration, mixed)
|
|
81
82
|
- **๐ Shell Replacement**: Dynamic error handling with `set -e`/`set +e` equivalents for .sh file replacement
|
|
82
|
-
- **โก Bun Optimized**: Designed for Bun with Node.js fallback compatibility
|
|
83
|
+
- **โก Bun Optimized**: Designed for Bun with Node.js fallback compatibility
|
|
83
84
|
- **๐พ Memory Efficient**: Streaming prevents large buffer accumulation
|
|
84
85
|
- **๐ก๏ธ Production Ready**: **518+ tests, 1165+ assertions** with comprehensive coverage including CI reliability
|
|
85
86
|
- **๐ฏ Advanced Signal Handling**: Robust SIGINT/SIGTERM forwarding with proper child process cleanup
|
|
@@ -90,21 +91,24 @@ A modern $ shell utility library with streaming, async iteration, and EventEmitt
|
|
|
90
91
|
command-stream now includes **18 built-in commands** that work identically to their bash/sh counterparts, providing true cross-platform shell scripting without system dependencies:
|
|
91
92
|
|
|
92
93
|
### ๐ **File System Commands**
|
|
94
|
+
|
|
93
95
|
- `cat` - Read and display file contents
|
|
94
96
|
- `ls` - List directory contents (supports `-l`, `-a`, `-A`)
|
|
95
97
|
- `mkdir` - Create directories (supports `-p` recursive)
|
|
96
|
-
- `rm` - Remove files/directories (supports `-r`, `-f`)
|
|
98
|
+
- `rm` - Remove files/directories (supports `-r`, `-f`)
|
|
97
99
|
- `mv` - Move/rename files and directories
|
|
98
100
|
- `cp` - Copy files/directories (supports `-r` recursive)
|
|
99
101
|
- `touch` - Create files or update timestamps
|
|
100
102
|
|
|
101
|
-
### ๐ง **Utility Commands**
|
|
103
|
+
### ๐ง **Utility Commands**
|
|
104
|
+
|
|
102
105
|
- `basename` - Extract filename from path
|
|
103
106
|
- `dirname` - Extract directory from path
|
|
104
107
|
- `seq` - Generate number sequences
|
|
105
108
|
- `yes` - Output string repeatedly (streaming)
|
|
106
109
|
|
|
107
110
|
### โก **System Commands**
|
|
111
|
+
|
|
108
112
|
- `cd` - Change directory
|
|
109
113
|
- `pwd` - Print working directory
|
|
110
114
|
- `echo` - Print arguments (supports `-n`)
|
|
@@ -161,11 +165,11 @@ Command-stream provides intelligent auto-quoting to protect against shell inject
|
|
|
161
165
|
import { $ } from 'command-stream';
|
|
162
166
|
|
|
163
167
|
// Safe strings are NOT quoted (performance optimization)
|
|
164
|
-
await $`echo ${name}`;
|
|
165
|
-
await $`${cmd} --version`;
|
|
168
|
+
await $`echo ${name}`; // name = "hello" โ echo hello
|
|
169
|
+
await $`${cmd} --version`; // cmd = "/usr/bin/node" โ /usr/bin/node --version
|
|
166
170
|
|
|
167
171
|
// Dangerous strings are automatically quoted for safety
|
|
168
|
-
await $`echo ${userInput}`;
|
|
172
|
+
await $`echo ${userInput}`; // userInput = "test; rm -rf /" โ echo 'test; rm -rf /'
|
|
169
173
|
await $`echo ${pathWithSpaces}`; // pathWithSpaces = "/my path/file" โ echo '/my path/file'
|
|
170
174
|
|
|
171
175
|
// Special characters that trigger auto-quoting:
|
|
@@ -173,10 +177,10 @@ await $`echo ${pathWithSpaces}`; // pathWithSpaces = "/my path/file" โ echo '/
|
|
|
173
177
|
|
|
174
178
|
// User-provided quotes are preserved
|
|
175
179
|
const quotedPath = "'/path with spaces/file'";
|
|
176
|
-
await $`cat ${quotedPath}`;
|
|
180
|
+
await $`cat ${quotedPath}`; // โ cat '/path with spaces/file' (no double-quoting!)
|
|
177
181
|
|
|
178
182
|
const doubleQuoted = '"/path with spaces/file"';
|
|
179
|
-
await $`cat ${doubleQuoted}`;
|
|
183
|
+
await $`cat ${doubleQuoted}`; // โ cat '"/path with spaces/file"' (preserves intent)
|
|
180
184
|
```
|
|
181
185
|
|
|
182
186
|
### Shell Injection Protection
|
|
@@ -186,19 +190,70 @@ All interpolated values are automatically secured:
|
|
|
186
190
|
```javascript
|
|
187
191
|
// โ
SAFE - All these injection attempts are neutralized
|
|
188
192
|
const dangerous = "'; rm -rf /; echo '";
|
|
189
|
-
await $`echo ${dangerous}`;
|
|
193
|
+
await $`echo ${dangerous}`; // โ echo ''\'' rm -rf /; echo '\'''
|
|
190
194
|
|
|
191
|
-
const cmdSubstitution =
|
|
195
|
+
const cmdSubstitution = '$(whoami)';
|
|
192
196
|
await $`echo ${cmdSubstitution}`; // โ echo '$(whoami)' (literal text, not executed)
|
|
193
197
|
|
|
194
|
-
const varExpansion =
|
|
195
|
-
await $`echo ${varExpansion}`;
|
|
198
|
+
const varExpansion = '$HOME';
|
|
199
|
+
await $`echo ${varExpansion}`; // โ echo '$HOME' (literal text, not expanded)
|
|
196
200
|
|
|
197
201
|
// โ
SAFE - Even complex injection attempts
|
|
198
|
-
const complex =
|
|
199
|
-
await $`echo ${complex}`;
|
|
202
|
+
const complex = '`cat /etc/passwd`';
|
|
203
|
+
await $`echo ${complex}`; // โ echo '`cat /etc/passwd`' (literal text)
|
|
200
204
|
```
|
|
201
205
|
|
|
206
|
+
### Disabling Auto-Escape (Advanced)
|
|
207
|
+
|
|
208
|
+
**โ ๏ธ WARNING: Use with extreme caution! Only use `raw()` with trusted input to prevent shell injection attacks.**
|
|
209
|
+
|
|
210
|
+
For advanced use cases where you need to use command strings directly without auto-escaping, use the `raw()` function:
|
|
211
|
+
|
|
212
|
+
```javascript
|
|
213
|
+
import { $, raw } from 'command-stream';
|
|
214
|
+
|
|
215
|
+
// โ ๏ธ DANGEROUS - Bypasses all safety checks
|
|
216
|
+
const userCommand = 'echo "hello" && ls -la';
|
|
217
|
+
await $`${raw(userCommand)}`; // โ Executes: echo "hello" && ls -la
|
|
218
|
+
|
|
219
|
+
// โ
Safe use case: Trusted command templates
|
|
220
|
+
const trustedCommand = 'git log --oneline --graph --all';
|
|
221
|
+
await $`${raw(trustedCommand)}`;
|
|
222
|
+
|
|
223
|
+
// โ
Combining raw with safe interpolation
|
|
224
|
+
const branch = 'main'; // User input - will be auto-quoted
|
|
225
|
+
await $`${raw('git log --oneline')} ${branch}`;
|
|
226
|
+
// โ git log --oneline 'main' (raw part unescaped, branch safely quoted)
|
|
227
|
+
|
|
228
|
+
// ๐ฏ Use case: Pre-built command strings from configuration
|
|
229
|
+
const config = {
|
|
230
|
+
backupCommand: 'tar -czf backup.tar.gz --exclude="*.log" .',
|
|
231
|
+
cleanCommand: 'find . -name "*.tmp" -delete',
|
|
232
|
+
};
|
|
233
|
+
await $`${raw(config.backupCommand)}`;
|
|
234
|
+
|
|
235
|
+
// โ ๏ธ NEVER use raw() with user input
|
|
236
|
+
const userInput = req.body.command; // โ DANGEROUS!
|
|
237
|
+
await $`${raw(userInput)}`; // โ Shell injection vulnerability!
|
|
238
|
+
|
|
239
|
+
// โ
Instead, use normal interpolation for user input
|
|
240
|
+
await $`echo ${userInput}`; // โ
Safe - auto-escaped
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
**When to use `raw()`:**
|
|
244
|
+
|
|
245
|
+
- โ
Trusted command templates from your codebase
|
|
246
|
+
- โ
Configuration files you control
|
|
247
|
+
- โ
Hardcoded command strings
|
|
248
|
+
- โ
Complex shell operators that need to be preserved
|
|
249
|
+
|
|
250
|
+
**When NOT to use `raw()`:**
|
|
251
|
+
|
|
252
|
+
- โ User input (form fields, API parameters, CLI arguments)
|
|
253
|
+
- โ External data (database, API responses, files)
|
|
254
|
+
- โ Any untrusted source
|
|
255
|
+
- โ When you're unsure - use normal interpolation instead
|
|
256
|
+
|
|
202
257
|
## Usage Patterns
|
|
203
258
|
|
|
204
259
|
### Classic Await (Backward Compatible)
|
|
@@ -242,7 +297,7 @@ const $custom = $({
|
|
|
242
297
|
stdin: 'test data',
|
|
243
298
|
mirror: false,
|
|
244
299
|
capture: true,
|
|
245
|
-
cwd: '/tmp'
|
|
300
|
+
cwd: '/tmp',
|
|
246
301
|
});
|
|
247
302
|
await $custom`cat > output.txt`; // Writes to /tmp/output.txt silently
|
|
248
303
|
|
|
@@ -263,21 +318,21 @@ const cmd = $`echo "hello"`;
|
|
|
263
318
|
// Three ways to start execution:
|
|
264
319
|
|
|
265
320
|
// 1. Explicit start with options
|
|
266
|
-
cmd.start();
|
|
267
|
-
cmd.start({ mode: 'async' });
|
|
268
|
-
cmd.start({ mode: 'sync' });
|
|
321
|
+
cmd.start(); // Default async mode
|
|
322
|
+
cmd.start({ mode: 'async' }); // Explicitly async
|
|
323
|
+
cmd.start({ mode: 'sync' }); // Synchronous execution
|
|
269
324
|
|
|
270
325
|
// 2. Convenience methods
|
|
271
|
-
cmd.async();
|
|
272
|
-
cmd.sync();
|
|
326
|
+
cmd.async(); // Same as start({ mode: 'async' })
|
|
327
|
+
cmd.sync(); // Same as start({ mode: 'sync' })
|
|
273
328
|
|
|
274
329
|
// 3. Auto-start by awaiting (always async)
|
|
275
|
-
await cmd;
|
|
330
|
+
await cmd; // Auto-starts in async mode
|
|
276
331
|
|
|
277
332
|
// Event handlers can be attached before starting
|
|
278
333
|
const process = $`long-command`
|
|
279
|
-
.on('data', chunk => console.log('Received:', chunk))
|
|
280
|
-
.on('end', result => console.log('Done!'));
|
|
334
|
+
.on('data', (chunk) => console.log('Received:', chunk))
|
|
335
|
+
.on('end', (result) => console.log('Done!'));
|
|
281
336
|
|
|
282
337
|
// Start whenever you're ready
|
|
283
338
|
process.start();
|
|
@@ -293,9 +348,7 @@ const result = $`echo "hello"`.sync();
|
|
|
293
348
|
console.log(result.stdout); // "hello\n"
|
|
294
349
|
|
|
295
350
|
// Events still work but are batched after completion
|
|
296
|
-
$`echo "world"
|
|
297
|
-
.on('end', result => console.log('Done:', result))
|
|
298
|
-
.sync();
|
|
351
|
+
$`echo "world"`.on('end', (result) => console.log('Done:', result)).sync();
|
|
299
352
|
```
|
|
300
353
|
|
|
301
354
|
### Async Iteration (Real-time Streaming)
|
|
@@ -317,19 +370,18 @@ import { $ } from 'command-stream';
|
|
|
317
370
|
|
|
318
371
|
// Attach event handlers then start execution
|
|
319
372
|
$`command`
|
|
320
|
-
.on('data', chunk => {
|
|
373
|
+
.on('data', (chunk) => {
|
|
321
374
|
if (chunk.type === 'stdout') {
|
|
322
375
|
console.log('Stdout:', chunk.data.toString());
|
|
323
376
|
}
|
|
324
377
|
})
|
|
325
|
-
.on('stderr', chunk => console.log('Stderr:', chunk))
|
|
326
|
-
.on('end', result => console.log('Done:', result))
|
|
327
|
-
.on('exit', code => console.log('Exit code:', code))
|
|
378
|
+
.on('stderr', (chunk) => console.log('Stderr:', chunk))
|
|
379
|
+
.on('end', (result) => console.log('Done:', result))
|
|
380
|
+
.on('exit', (code) => console.log('Exit code:', code))
|
|
328
381
|
.start(); // Explicitly start the command
|
|
329
382
|
|
|
330
383
|
// Or auto-start by awaiting
|
|
331
|
-
const cmd = $`another-command
|
|
332
|
-
.on('data', chunk => console.log(chunk));
|
|
384
|
+
const cmd = $`another-command`.on('data', (chunk) => console.log(chunk));
|
|
333
385
|
await cmd; // Auto-starts in async mode
|
|
334
386
|
```
|
|
335
387
|
|
|
@@ -340,7 +392,7 @@ import { $ } from 'command-stream';
|
|
|
340
392
|
|
|
341
393
|
// Async mode - events fire in real-time
|
|
342
394
|
const process = $`streaming-command`;
|
|
343
|
-
process.on('data', chunk => {
|
|
395
|
+
process.on('data', (chunk) => {
|
|
344
396
|
processRealTimeData(chunk);
|
|
345
397
|
});
|
|
346
398
|
const result = await process;
|
|
@@ -348,7 +400,7 @@ console.log('Final output:', result.stdout);
|
|
|
348
400
|
|
|
349
401
|
// Sync mode - events fire after completion (batched)
|
|
350
402
|
const syncCmd = $`another-command`;
|
|
351
|
-
syncCmd.on('end', result => {
|
|
403
|
+
syncCmd.on('end', (result) => {
|
|
352
404
|
console.log('Completed with:', result.stdout);
|
|
353
405
|
});
|
|
354
406
|
const syncResult = syncCmd.sync();
|
|
@@ -401,9 +453,9 @@ console.log('Ping stopped with code:', pingResult.code); // 143 (SIGTERM)
|
|
|
401
453
|
const mixedCmd = $`sh -c 'echo "out" && echo "err" >&2'`;
|
|
402
454
|
const [stdout, stderr] = await Promise.all([
|
|
403
455
|
mixedCmd.strings.stdout, // Available after finish
|
|
404
|
-
mixedCmd.strings.stderr
|
|
456
|
+
mixedCmd.strings.stderr, // Available after finish
|
|
405
457
|
]);
|
|
406
|
-
console.log('Out:', stdout.trim()); // "out"
|
|
458
|
+
console.log('Out:', stdout.trim()); // "out"
|
|
407
459
|
console.log('Err:', stderr.trim()); // "err"
|
|
408
460
|
|
|
409
461
|
// ๐โโ๏ธ AUTO-START: Streams auto-start processes when accessed
|
|
@@ -419,7 +471,8 @@ console.log(traditional.stdout); // "still works\n"
|
|
|
419
471
|
```
|
|
420
472
|
|
|
421
473
|
**Key Features:**
|
|
422
|
-
|
|
474
|
+
|
|
475
|
+
- `command.streams.stdin/stdout/stderr` - Direct access to Node.js streams
|
|
423
476
|
- `command.buffers.stdin/stdout/stderr` - Binary data as Buffer objects
|
|
424
477
|
- `command.strings.stdin/stdout/stderr` - Text data as strings
|
|
425
478
|
- `command.kill()` - Forceful process termination
|
|
@@ -428,6 +481,7 @@ console.log(traditional.stdout); // "still works\n"
|
|
|
428
481
|
- **Network commands (ping, wget) ignore stdin** โ Use `kill()` method instead
|
|
429
482
|
|
|
430
483
|
**๐ Streams vs Buffers/Strings:**
|
|
484
|
+
|
|
431
485
|
- **`streams.*`** - Available **immediately** when command starts, for real-time interaction
|
|
432
486
|
- **`buffers.*` & `strings.*`** - Complete **snapshots** available only **after** command finishes
|
|
433
487
|
|
|
@@ -448,18 +502,18 @@ await $`npm run build`;
|
|
|
448
502
|
shell.errexit(false);
|
|
449
503
|
const cleanup = await $`rm -rf temp`; // Won't throw if fails
|
|
450
504
|
|
|
451
|
-
// set -e again for critical operations
|
|
505
|
+
// set -e again for critical operations
|
|
452
506
|
shell.errexit(true);
|
|
453
507
|
await $`cp -r build/* deploy/`;
|
|
454
508
|
|
|
455
509
|
// Other bash-like settings
|
|
456
|
-
shell.verbose(true);
|
|
457
|
-
shell.xtrace(true);
|
|
510
|
+
shell.verbose(true); // set -v: print commands
|
|
511
|
+
shell.xtrace(true); // set -x: trace execution
|
|
458
512
|
|
|
459
513
|
// Or use the bash-style API
|
|
460
|
-
set('e');
|
|
461
|
-
unset('e');
|
|
462
|
-
set('x');
|
|
514
|
+
set('e'); // set -e
|
|
515
|
+
unset('e'); // set +e
|
|
516
|
+
set('x'); // set -x
|
|
463
517
|
set('verbose'); // Long form also supported
|
|
464
518
|
```
|
|
465
519
|
|
|
@@ -489,7 +543,7 @@ console.log(content.stdout);
|
|
|
489
543
|
|
|
490
544
|
// Path operations
|
|
491
545
|
const filename = await $`basename project/src/index.js .js`; // โ "index"
|
|
492
|
-
const directory = await $`dirname project/src/index.js`;
|
|
546
|
+
const directory = await $`dirname project/src/index.js`; // โ "project/src"
|
|
493
547
|
|
|
494
548
|
// Generate sequences and process them
|
|
495
549
|
await $`seq 1 10 | cat > numbers.txt`;
|
|
@@ -513,15 +567,15 @@ register('greet', async ({ args, stdin }) => {
|
|
|
513
567
|
});
|
|
514
568
|
|
|
515
569
|
// Use it like any other command
|
|
516
|
-
await $`greet Alice`;
|
|
517
|
-
await $`echo "Bob" | greet`;
|
|
570
|
+
await $`greet Alice`; // โ "Hello, Alice!"
|
|
571
|
+
await $`echo "Bob" | greet`; // โ "Hello, Bob!"
|
|
518
572
|
|
|
519
573
|
// Streaming virtual commands with async generators
|
|
520
574
|
register('countdown', async function* ({ args }) {
|
|
521
575
|
const start = parseInt(args[0] || 5);
|
|
522
576
|
for (let i = start; i >= 0; i--) {
|
|
523
577
|
yield `${i}\n`;
|
|
524
|
-
await new Promise(r => setTimeout(r, 1000));
|
|
578
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
525
579
|
}
|
|
526
580
|
});
|
|
527
581
|
|
|
@@ -534,8 +588,8 @@ for await (const chunk of $`countdown 3`.stream()) {
|
|
|
534
588
|
}
|
|
535
589
|
|
|
536
590
|
// Management functions
|
|
537
|
-
console.log(listCommands());
|
|
538
|
-
unregister('greet');
|
|
591
|
+
console.log(listCommands()); // List all registered commands
|
|
592
|
+
unregister('greet'); // Remove custom commands
|
|
539
593
|
```
|
|
540
594
|
|
|
541
595
|
#### ๐ฅ **Why Virtual Commands Are Revolutionary**
|
|
@@ -543,28 +597,29 @@ unregister('greet'); // Remove custom commands
|
|
|
543
597
|
**No other shell library offers this level of extensibility:**
|
|
544
598
|
|
|
545
599
|
- **๐ซ Bun.$**: Fixed set of built-in commands, no extensibility API
|
|
546
|
-
- **๐ซ execa**: Transform/pipeline system, but no custom commands
|
|
600
|
+
- **๐ซ execa**: Transform/pipeline system, but no custom commands
|
|
547
601
|
- **๐ซ zx**: JavaScript functions only, no shell command integration
|
|
548
602
|
|
|
549
603
|
**command-stream breaks the barrier** between JavaScript functions and shell commands:
|
|
550
604
|
|
|
551
605
|
```javascript
|
|
552
606
|
// โ Other libraries: Choose JavaScript OR shell
|
|
553
|
-
await execa('node', ['script.js']);
|
|
554
|
-
await $`node script.js`;
|
|
607
|
+
await execa('node', ['script.js']); // execa: separate processes
|
|
608
|
+
await $`node script.js`; // zx: shell commands only
|
|
555
609
|
|
|
556
|
-
// โ
command-stream: JavaScript functions AS shell commands
|
|
610
|
+
// โ
command-stream: JavaScript functions AS shell commands
|
|
557
611
|
register('deploy', async ({ args }) => {
|
|
558
612
|
const env = args[0] || 'staging';
|
|
559
613
|
await deployToEnvironment(env);
|
|
560
614
|
return { stdout: `Deployed to ${env}!\n`, code: 0 };
|
|
561
615
|
});
|
|
562
616
|
|
|
563
|
-
await $`deploy production`;
|
|
617
|
+
await $`deploy production`; // JavaScript function as shell command!
|
|
564
618
|
await $`deploy staging | tee log.txt`; // Works in pipelines!
|
|
565
619
|
```
|
|
566
620
|
|
|
567
621
|
**Unique capabilities:**
|
|
622
|
+
|
|
568
623
|
- **Seamless Integration**: Virtual commands work exactly like built-ins
|
|
569
624
|
- **Pipeline Support**: Full stdin/stdout passing between virtual and system commands
|
|
570
625
|
- **Streaming**: Async generators for real-time output
|
|
@@ -581,9 +636,9 @@ await $`deploy staging | tee log.txt`; // Works in pipelines!
|
|
|
581
636
|
import { $, register } from 'command-stream';
|
|
582
637
|
|
|
583
638
|
// โ
Standard shell piping (like all libraries)
|
|
584
|
-
await $`echo "hello world" | wc -w`;
|
|
639
|
+
await $`echo "hello world" | wc -w`; // โ "2"
|
|
585
640
|
|
|
586
|
-
// โ
Built-in to built-in piping
|
|
641
|
+
// โ
Built-in to built-in piping
|
|
587
642
|
await $`seq 1 5 | cat > numbers.txt`;
|
|
588
643
|
|
|
589
644
|
// โ
System to built-in piping
|
|
@@ -599,10 +654,10 @@ register('reverse', async ({ args, stdin }) => {
|
|
|
599
654
|
});
|
|
600
655
|
|
|
601
656
|
// โ
Built-in to virtual piping
|
|
602
|
-
await $`echo "hello" | uppercase`;
|
|
657
|
+
await $`echo "hello" | uppercase`; // โ "HELLO"
|
|
603
658
|
|
|
604
|
-
// โ
Virtual to virtual piping
|
|
605
|
-
await $`echo "hello" | uppercase | reverse`;
|
|
659
|
+
// โ
Virtual to virtual piping
|
|
660
|
+
await $`echo "hello" | uppercase | reverse`; // โ "OLLEH"
|
|
606
661
|
|
|
607
662
|
// โ
Mixed pipelines (system + built-in + virtual)
|
|
608
663
|
await $`git log --oneline | head -n 3 | uppercase | cat > LOG.txt`;
|
|
@@ -687,17 +742,18 @@ unregister('extract-field');
|
|
|
687
742
|
|
|
688
743
|
#### **๐ How We Compare**
|
|
689
744
|
|
|
690
|
-
| Library
|
|
691
|
-
|
|
692
|
-
| **command-stream** | โ
System + Built-ins + Virtual + Mixed | โ
**Full support**
|
|
693
|
-
| **Bun.$**
|
|
694
|
-
| **execa**
|
|
695
|
-
| **zx**
|
|
745
|
+
| Library | Pipeline Types | Custom Commands in Pipes | `.pipe()` Method | Real-time Streaming |
|
|
746
|
+
| ------------------ | --------------------------------------- | ------------------------ | ----------------------------------- | ------------------- |
|
|
747
|
+
| **command-stream** | โ
System + Built-ins + Virtual + Mixed | โ
**Full support** | โ
**Full virtual command support** | โ
**Yes** |
|
|
748
|
+
| **Bun.$** | โ
System + Built-ins | โ No custom commands | โ No `.pipe()` method | โ No |
|
|
749
|
+
| **execa** | โ
Programmatic `.pipe()` | โ No shell integration | โ
Basic process piping | ๐ก Limited |
|
|
750
|
+
| **zx** | โ
Shell piping + `.pipe()` | โ No custom commands | โ
Stream piping only | โ No |
|
|
696
751
|
|
|
697
752
|
**๐ฏ Unique Advantages:**
|
|
753
|
+
|
|
698
754
|
- **Virtual commands work seamlessly in both shell pipes AND `.pipe()` method** - no other library can do this
|
|
699
755
|
- **Mixed pipeline types** - combine system, built-in, and virtual commands freely in both syntaxes
|
|
700
|
-
- **Real-time streaming** through virtual command pipelines
|
|
756
|
+
- **Real-time streaming** through virtual command pipelines
|
|
701
757
|
- **Full stdin/stdout passing** between all command types
|
|
702
758
|
- **Dual piping syntax** - use shell `|` OR programmatic `.pipe()` interchangeably
|
|
703
759
|
|
|
@@ -710,29 +766,31 @@ import { $ } from 'command-stream';
|
|
|
710
766
|
|
|
711
767
|
// This command will:
|
|
712
768
|
// 1. Print "Hello" to your terminal (stdoutโstdout)
|
|
713
|
-
// 2. Print "Error!" to your terminal (stderrโstderr)
|
|
769
|
+
// 2. Print "Error!" to your terminal (stderrโstderr)
|
|
714
770
|
// 3. Capture both outputs for programmatic access
|
|
715
771
|
const result = await $`sh -c "echo 'Hello'; echo 'Error!' >&2"`;
|
|
716
772
|
|
|
717
773
|
console.log('Captured stdout:', result.stdout); // "Hello\n"
|
|
718
774
|
console.log('Captured stderr:', result.stderr); // "Error!\n"
|
|
719
|
-
console.log('Exit code:', result.code);
|
|
775
|
+
console.log('Exit code:', result.code); // 0
|
|
720
776
|
```
|
|
721
777
|
|
|
722
778
|
**Key Default Options:**
|
|
779
|
+
|
|
723
780
|
- `mirror: true` - Live output to terminal (like shell)
|
|
724
781
|
- `capture: true` - Capture output for later use (unlike shell)
|
|
725
782
|
- `stdin: 'inherit'` - Inherit stdin from parent process
|
|
726
783
|
|
|
727
784
|
**Fully Controllable:**
|
|
785
|
+
|
|
728
786
|
```javascript
|
|
729
787
|
import { $, create, sh } from 'command-stream';
|
|
730
788
|
|
|
731
789
|
// Disable terminal output but still capture
|
|
732
790
|
const result = await sh('echo "silent"', { mirror: false });
|
|
733
791
|
|
|
734
|
-
// Custom stdin input
|
|
735
|
-
const custom = await sh('cat', { stdin:
|
|
792
|
+
// Custom stdin input
|
|
793
|
+
const custom = await sh('cat', { stdin: 'custom input' });
|
|
736
794
|
|
|
737
795
|
// Create custom $ with different defaults
|
|
738
796
|
const quiet$ = create({ mirror: false });
|
|
@@ -758,7 +816,7 @@ let logFile = null;
|
|
|
758
816
|
for await (const chunk of $`your-streaming-command`.stream()) {
|
|
759
817
|
if (chunk.type === 'stdout') {
|
|
760
818
|
const data = chunk.data.toString();
|
|
761
|
-
|
|
819
|
+
|
|
762
820
|
// Extract session ID from output
|
|
763
821
|
if (!sessionId && data.includes('session_id')) {
|
|
764
822
|
try {
|
|
@@ -770,7 +828,7 @@ for await (const chunk of $`your-streaming-command`.stream()) {
|
|
|
770
828
|
// Handle JSON parse errors
|
|
771
829
|
}
|
|
772
830
|
}
|
|
773
|
-
|
|
831
|
+
|
|
774
832
|
// Write to log file in real-time
|
|
775
833
|
if (logFile) {
|
|
776
834
|
appendFileSync(logFile, data);
|
|
@@ -826,7 +884,7 @@ The enhanced `$` function returns a `ProcessRunner` instance that extends `Event
|
|
|
826
884
|
#### Properties
|
|
827
885
|
|
|
828
886
|
- `stdout`: Direct access to child process stdout stream
|
|
829
|
-
- `stderr`: Direct access to child process stderr stream
|
|
887
|
+
- `stderr`: Direct access to child process stderr stream
|
|
830
888
|
- `stdin`: Direct access to child process stdin stream
|
|
831
889
|
|
|
832
890
|
### Default Options
|
|
@@ -843,6 +901,7 @@ The enhanced `$` function returns a `ProcessRunner` instance that extends `Event
|
|
|
843
901
|
```
|
|
844
902
|
|
|
845
903
|
**Option Details:**
|
|
904
|
+
|
|
846
905
|
- `mirror: boolean` - Whether to pipe output to terminal in real-time
|
|
847
906
|
- `capture: boolean` - Whether to capture output in result object
|
|
848
907
|
- `stdin: 'inherit' | 'ignore' | string | Buffer` - How to handle stdin
|
|
@@ -851,6 +910,7 @@ The enhanced `$` function returns a `ProcessRunner` instance that extends `Event
|
|
|
851
910
|
- `env: object` - Environment variables
|
|
852
911
|
|
|
853
912
|
**Override defaults:**
|
|
913
|
+
|
|
854
914
|
- Use `$({ options })` syntax for one-off configurations with template literals
|
|
855
915
|
- Use `sh(command, options)` for one-off overrides with string commands
|
|
856
916
|
- Use `create(defaultOptions)` to create custom `$` with different defaults
|
|
@@ -910,7 +970,7 @@ Control and extend the command system with custom JavaScript functions:
|
|
|
910
970
|
- `name`: Command name (string)
|
|
911
971
|
- `handler`: Function or async generator `(args, stdin, options) => result`
|
|
912
972
|
- `unregister(name)`: Remove a virtual command
|
|
913
|
-
- `listCommands()`: Get array of all registered command names
|
|
973
|
+
- `listCommands()`: Get array of all registered command names
|
|
914
974
|
- `enableVirtualCommands()`: Enable virtual command processing
|
|
915
975
|
- `disableVirtualCommands()`: Disable virtual commands (use system commands only)
|
|
916
976
|
|
|
@@ -926,7 +986,7 @@ register('cancellable', async function* ({ args, stdin, abortSignal }) {
|
|
|
926
986
|
break; // Proper cancellation handling
|
|
927
987
|
}
|
|
928
988
|
yield `Count: ${i}\n`;
|
|
929
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
989
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
930
990
|
}
|
|
931
991
|
});
|
|
932
992
|
|
|
@@ -934,35 +994,42 @@ register('cancellable', async function* ({ args, stdin, abortSignal }) {
|
|
|
934
994
|
// All original options (built-in + custom) are available in the 'options' object
|
|
935
995
|
// Common options like cwd, env are also available at top level for convenience
|
|
936
996
|
// Runtime additions: isCancelled function, abortSignal
|
|
937
|
-
register(
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
}
|
|
997
|
+
register(
|
|
998
|
+
'debug-info',
|
|
999
|
+
async ({ args, stdin, cwd, env, options, isCancelled }) => {
|
|
1000
|
+
return {
|
|
1001
|
+
stdout: JSON.stringify(
|
|
1002
|
+
{
|
|
1003
|
+
args,
|
|
1004
|
+
cwd, // Available at top level for convenience
|
|
1005
|
+
env: Object.keys(env || {}), // Available at top level for convenience
|
|
1006
|
+
stdinLength: stdin?.length || 0,
|
|
1007
|
+
allOptions: options, // All original options (built-in + custom)
|
|
1008
|
+
mirror: options.mirror, // Built-in option from options object
|
|
1009
|
+
capture: options.capture, // Built-in option from options object
|
|
1010
|
+
customOption: options.customOption || 'not provided', // Custom option
|
|
1011
|
+
isCancelledAvailable: typeof isCancelled === 'function',
|
|
1012
|
+
},
|
|
1013
|
+
null,
|
|
1014
|
+
2
|
|
1015
|
+
),
|
|
1016
|
+
code: 0,
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
1019
|
+
);
|
|
953
1020
|
|
|
954
1021
|
// โ
Error handling and non-zero exit codes
|
|
955
1022
|
register('maybe-fail', async ({ args }) => {
|
|
956
1023
|
if (Math.random() > 0.5) {
|
|
957
1024
|
return {
|
|
958
1025
|
stdout: 'Success!\n',
|
|
959
|
-
code: 0
|
|
1026
|
+
code: 0,
|
|
960
1027
|
};
|
|
961
1028
|
} else {
|
|
962
1029
|
return {
|
|
963
1030
|
stdout: '',
|
|
964
1031
|
stderr: 'Random failure occurred\n',
|
|
965
|
-
code: 1
|
|
1032
|
+
code: 1,
|
|
966
1033
|
};
|
|
967
1034
|
}
|
|
968
1035
|
});
|
|
@@ -971,12 +1038,15 @@ register('maybe-fail', async ({ args }) => {
|
|
|
971
1038
|
register('show-options', async ({ args, stdin, options, cwd }) => {
|
|
972
1039
|
return {
|
|
973
1040
|
stdout: `Custom: ${options.customValue || 'none'}, CWD: ${cwd || options.cwd || 'default'}\n`,
|
|
974
|
-
code: 0
|
|
1041
|
+
code: 0,
|
|
975
1042
|
};
|
|
976
1043
|
});
|
|
977
1044
|
|
|
978
1045
|
// Usage example showing options passed to virtual command:
|
|
979
|
-
const result = await $({
|
|
1046
|
+
const result = await $({
|
|
1047
|
+
customValue: 'hello world',
|
|
1048
|
+
cwd: '/tmp',
|
|
1049
|
+
})`show-options`;
|
|
980
1050
|
console.log(result.stdout); // Output: Custom: hello world, CWD: /tmp
|
|
981
1051
|
```
|
|
982
1052
|
|
|
@@ -1005,6 +1075,46 @@ async function streamingHandler({ args, stdin, abortSignal, cwd, env, options, i
|
|
|
1005
1075
|
}
|
|
1006
1076
|
```
|
|
1007
1077
|
|
|
1078
|
+
### Utility Functions API
|
|
1079
|
+
|
|
1080
|
+
Control how values are interpolated into commands:
|
|
1081
|
+
|
|
1082
|
+
#### Functions
|
|
1083
|
+
|
|
1084
|
+
- `quote(value)`: Manually quote a value using the same smart quoting logic as auto-interpolation
|
|
1085
|
+
- `raw(value)`: **โ ๏ธ Dangerous!** Bypass auto-escaping to use command strings directly (see [Disabling Auto-Escape](#disabling-auto-escape-advanced))
|
|
1086
|
+
|
|
1087
|
+
#### quote() - Manual Quoting
|
|
1088
|
+
|
|
1089
|
+
Apply the same smart quoting logic manually:
|
|
1090
|
+
|
|
1091
|
+
```javascript
|
|
1092
|
+
import { $, quote } from 'command-stream';
|
|
1093
|
+
|
|
1094
|
+
const path = '/path with spaces/file.txt';
|
|
1095
|
+
const quoted = quote(path);
|
|
1096
|
+
console.log(quoted); // โ '/path with spaces/file.txt'
|
|
1097
|
+
|
|
1098
|
+
// Use case: Pre-process values before interpolation
|
|
1099
|
+
const args = ['hello world', 'test'].map(quote);
|
|
1100
|
+
// โ ["'hello world'", 'test']
|
|
1101
|
+
```
|
|
1102
|
+
|
|
1103
|
+
#### raw() - Disable Auto-Escape
|
|
1104
|
+
|
|
1105
|
+
**โ ๏ธ WARNING: Only use with trusted input!** See [Disabling Auto-Escape](#disabling-auto-escape-advanced) section for detailed documentation and security considerations.
|
|
1106
|
+
|
|
1107
|
+
```javascript
|
|
1108
|
+
import { $, raw } from 'command-stream';
|
|
1109
|
+
|
|
1110
|
+
// Bypass auto-escaping for trusted command strings
|
|
1111
|
+
const trustedCommand = 'echo "hello" && ls -la';
|
|
1112
|
+
await $`${raw(trustedCommand)}`;
|
|
1113
|
+
// โ Executes: echo "hello" && ls -la (without escaping)
|
|
1114
|
+
|
|
1115
|
+
// โ ๏ธ NEVER use with untrusted input - shell injection risk!
|
|
1116
|
+
```
|
|
1117
|
+
|
|
1008
1118
|
### Built-in Commands
|
|
1009
1119
|
|
|
1010
1120
|
18 cross-platform commands that work identically everywhere:
|
|
@@ -1014,6 +1124,7 @@ async function streamingHandler({ args, stdin, abortSignal, cwd, env, options, i
|
|
|
1014
1124
|
**System**: `cd`, `pwd`, `echo`, `sleep`, `true`, `false`, `which`, `exit`, `env`, `test`
|
|
1015
1125
|
|
|
1016
1126
|
All built-in commands support:
|
|
1127
|
+
|
|
1017
1128
|
- Standard flags (e.g., `ls -la`, `mkdir -p`, `rm -rf`)
|
|
1018
1129
|
- Pipeline operations
|
|
1019
1130
|
- Option awareness (`cwd`, `env`, etc.)
|
|
@@ -1051,7 +1162,7 @@ import { $ } from 'command-stream';
|
|
|
1051
1162
|
const result1 = await $`echo "hello world"`;
|
|
1052
1163
|
const text1 = await result1.text(); // "hello world\n"
|
|
1053
1164
|
|
|
1054
|
-
const result2 = $`echo "sync example"`.sync();
|
|
1165
|
+
const result2 = $`echo "sync example"`.sync();
|
|
1055
1166
|
const text2 = await result2.text(); // "sync example\n"
|
|
1056
1167
|
|
|
1057
1168
|
// .text() is equivalent to accessing .stdout
|
|
@@ -1085,7 +1196,7 @@ The library provides **advanced CTRL+C handling** that properly manages signals
|
|
|
1085
1196
|
// โ
Smart signal handling - only interferes when necessary
|
|
1086
1197
|
import { $ } from 'command-stream';
|
|
1087
1198
|
|
|
1088
|
-
// Case 1: No children active - your handlers work normally
|
|
1199
|
+
// Case 1: No children active - your handlers work normally
|
|
1089
1200
|
process.on('SIGINT', () => {
|
|
1090
1201
|
console.log('My custom handler runs!');
|
|
1091
1202
|
process.exit(42); // Custom exit code
|
|
@@ -1096,10 +1207,7 @@ process.on('SIGINT', () => {
|
|
|
1096
1207
|
await $`ping 8.8.8.8`; // Press CTRL+C โ Forwards to ping, exits with code 130
|
|
1097
1208
|
|
|
1098
1209
|
// Case 3: Multiple processes - all interrupted
|
|
1099
|
-
await Promise.all([
|
|
1100
|
-
$`sleep 100`,
|
|
1101
|
-
$`ping google.com`
|
|
1102
|
-
]); // Press CTRL+C โ All processes terminated, exits with code 130
|
|
1210
|
+
await Promise.all([$`sleep 100`, $`ping google.com`]); // Press CTRL+C โ All processes terminated, exits with code 130
|
|
1103
1211
|
```
|
|
1104
1212
|
|
|
1105
1213
|
### Examples
|
|
@@ -1107,7 +1215,7 @@ await Promise.all([
|
|
|
1107
1215
|
```javascript
|
|
1108
1216
|
// Long-running command that can be interrupted with CTRL+C
|
|
1109
1217
|
try {
|
|
1110
|
-
await $`ping 8.8.8.8`;
|
|
1218
|
+
await $`ping 8.8.8.8`; // Press CTRL+C to stop
|
|
1111
1219
|
} catch (error) {
|
|
1112
1220
|
console.log('Command interrupted:', error.code); // Exit code 130 (SIGINT)
|
|
1113
1221
|
}
|
|
@@ -1117,7 +1225,7 @@ try {
|
|
|
1117
1225
|
await Promise.all([
|
|
1118
1226
|
$`sleep 100`,
|
|
1119
1227
|
$`ping google.com`,
|
|
1120
|
-
$`tail -f /var/log/system.log
|
|
1228
|
+
$`tail -f /var/log/system.log`,
|
|
1121
1229
|
]);
|
|
1122
1230
|
} catch (error) {
|
|
1123
1231
|
// All processes are terminated when you press CTRL+C
|
|
@@ -1137,11 +1245,11 @@ try {
|
|
|
1137
1245
|
### Signal Handling Behavior
|
|
1138
1246
|
|
|
1139
1247
|
- **๐ฏ Smart Detection**: Only forwards CTRL+C when child processes are active
|
|
1140
|
-
- **๐ก๏ธ Non-Interference**: Preserves user SIGINT handlers when no children running
|
|
1248
|
+
- **๐ก๏ธ Non-Interference**: Preserves user SIGINT handlers when no children running
|
|
1141
1249
|
- **โก Interactive Commands**: Use `interactive: true` option for commands like `vim`, `less`, `top` to enable proper TTY forwarding and signal handling
|
|
1142
1250
|
- **๐ Process Groups**: Detached spawning ensures proper signal isolation
|
|
1143
1251
|
- **๐งน TTY Cleanup**: Raw terminal mode properly restored on interruption
|
|
1144
|
-
- **๐ Standard Exit Codes**:
|
|
1252
|
+
- **๐ Standard Exit Codes**:
|
|
1145
1253
|
- `130` - SIGINT interruption (CTRL+C)
|
|
1146
1254
|
- `143` - SIGTERM termination (programmatic kill)
|
|
1147
1255
|
- `137` - SIGKILL force termination
|
|
@@ -1155,7 +1263,7 @@ try {
|
|
|
1155
1263
|
register('echo', () => ({ stdout: 'virtual!\n', code: 0 }));
|
|
1156
1264
|
await $`echo test`; // โ "virtual!"
|
|
1157
1265
|
|
|
1158
|
-
// 2. Built-in Commands (if no virtual match)
|
|
1266
|
+
// 2. Built-in Commands (if no virtual match)
|
|
1159
1267
|
unregister('echo');
|
|
1160
1268
|
await $`echo test`; // โ Uses built-in echo
|
|
1161
1269
|
|
|
@@ -1178,12 +1286,12 @@ const result = await $`ls -la`;
|
|
|
1178
1286
|
|
|
1179
1287
|
// โ
Use .sync() when you need blocking execution with events
|
|
1180
1288
|
const syncCmd = $`build-script`
|
|
1181
|
-
.on('stdout', chunk => updateProgress(chunk))
|
|
1289
|
+
.on('stdout', (chunk) => updateProgress(chunk))
|
|
1182
1290
|
.sync(); // Events fire after completion
|
|
1183
1291
|
|
|
1184
|
-
// โ
Use .start() for non-blocking execution with real-time events
|
|
1292
|
+
// โ
Use .start() for non-blocking execution with real-time events
|
|
1185
1293
|
const asyncCmd = $`long-running-server`
|
|
1186
|
-
.on('stdout', chunk => logOutput(chunk))
|
|
1294
|
+
.on('stdout', (chunk) => logOutput(chunk))
|
|
1187
1295
|
.start(); // Events fire in real-time
|
|
1188
1296
|
|
|
1189
1297
|
// โ
Use .stream() for processing large outputs efficiently
|
|
@@ -1193,13 +1301,13 @@ for await (const chunk of $`generate-big-file`.stream()) {
|
|
|
1193
1301
|
|
|
1194
1302
|
// โ
Use EventEmitter pattern for complex workflows
|
|
1195
1303
|
$`deployment-script`
|
|
1196
|
-
.on('stdout', chunk => {
|
|
1304
|
+
.on('stdout', (chunk) => {
|
|
1197
1305
|
if (chunk.toString().includes('ERROR')) {
|
|
1198
1306
|
handleError(chunk);
|
|
1199
1307
|
}
|
|
1200
1308
|
})
|
|
1201
|
-
.on('stderr', chunk => logError(chunk))
|
|
1202
|
-
.on('end', result => {
|
|
1309
|
+
.on('stderr', (chunk) => logError(chunk))
|
|
1310
|
+
.on('end', (result) => {
|
|
1203
1311
|
if (result.code === 0) {
|
|
1204
1312
|
notifySuccess();
|
|
1205
1313
|
}
|
|
@@ -1223,9 +1331,7 @@ processFile(result.stdout); // Loads everything into memory
|
|
|
1223
1331
|
const quickResult = $`pwd`.sync();
|
|
1224
1332
|
|
|
1225
1333
|
// ๐ Best for UX: Async with events for long-running commands
|
|
1226
|
-
$`npm install
|
|
1227
|
-
.on('stdout', showProgress)
|
|
1228
|
-
.start();
|
|
1334
|
+
$`npm install`.on('stdout', showProgress).start();
|
|
1229
1335
|
```
|
|
1230
1336
|
|
|
1231
1337
|
## Testing
|
|
@@ -1239,7 +1345,7 @@ bun test --coverage
|
|
|
1239
1345
|
|
|
1240
1346
|
# Run specific test categories
|
|
1241
1347
|
npm run test:features # Feature comparison tests
|
|
1242
|
-
npm run test:builtin # Built-in commands tests
|
|
1348
|
+
npm run test:builtin # Built-in commands tests
|
|
1243
1349
|
npm run test:pipe # .pipe() method tests
|
|
1244
1350
|
npm run test:sync # Synchronous execution tests
|
|
1245
1351
|
npm run test:signal # CTRL+C signal handling tests
|
|
@@ -1253,11 +1359,13 @@ npm run test:signal # CTRL+C signal handling tests
|
|
|
1253
1359
|
## Roadmap
|
|
1254
1360
|
|
|
1255
1361
|
### ๐ **Coming Soon**
|
|
1362
|
+
|
|
1256
1363
|
- **TypeScript Support**: Full .d.ts definitions and type safety
|
|
1257
1364
|
- **Enhanced Shell Options**: `set -u` (nounset) and `set -o pipefail` support
|
|
1258
1365
|
- **More Built-in Commands**: Additional cross-platform utilities
|
|
1259
1366
|
|
|
1260
1367
|
### ๐ก **Planned Features**
|
|
1368
|
+
|
|
1261
1369
|
- **Performance Optimizations**: Further Bun runtime optimizations
|
|
1262
1370
|
- **Advanced Error Handling**: Enhanced error context and debugging
|
|
1263
1371
|
- **Plugin System**: Extensible architecture for custom integrations
|
|
@@ -1267,6 +1375,7 @@ npm run test:signal # CTRL+C signal handling tests
|
|
|
1267
1375
|
We welcome contributions! Since command-stream is **public domain software**, your contributions will also be released into the public domain.
|
|
1268
1376
|
|
|
1269
1377
|
### ๐ **Getting Started**
|
|
1378
|
+
|
|
1270
1379
|
```bash
|
|
1271
1380
|
git clone https://github.com/link-foundation/command-stream.git
|
|
1272
1381
|
cd command-stream
|
|
@@ -1275,12 +1384,14 @@ bun test # Run the full test suite
|
|
|
1275
1384
|
```
|
|
1276
1385
|
|
|
1277
1386
|
### ๐ **Development Guidelines**
|
|
1387
|
+
|
|
1278
1388
|
- All features must have comprehensive tests
|
|
1279
1389
|
- Built-in commands should match bash/sh behavior exactly
|
|
1280
1390
|
- Maintain cross-platform compatibility (Windows, macOS, Linux)
|
|
1281
1391
|
- Follow the existing code style and patterns
|
|
1282
1392
|
|
|
1283
1393
|
### ๐งช **Running Tests**
|
|
1394
|
+
|
|
1284
1395
|
```bash
|
|
1285
1396
|
bun test # All 518+ tests
|
|
1286
1397
|
bun test tests/pipe.test.mjs # Specific test file
|
|
@@ -1300,6 +1411,7 @@ Unlike other shell utilities that require attribution (MIT, Apache 2.0), command
|
|
|
1300
1411
|
- โ
**Corporate friendly** - No license compliance overhead
|
|
1301
1412
|
|
|
1302
1413
|
This makes command-stream ideal for:
|
|
1414
|
+
|
|
1303
1415
|
- **Commercial products** where license attribution is inconvenient
|
|
1304
1416
|
- **Embedded systems** where every byte counts
|
|
1305
1417
|
- **Educational materials** that can be freely shared
|