command-stream 0.3.2 โ 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +444 -41
- package/package.json +1 -1
- package/src/$.mjs +1535 -220
- package/src/commands/$.cat.mjs +7 -1
- package/src/commands/$.sleep.mjs +63 -6
- package/src/commands/$.yes.mjs +23 -9
package/README.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
[](https://npmjs.com/command-stream)
|
|
2
|
+
[](https://github.com/link-foundation/command-stream/blob/main/LICENSE)
|
|
3
|
+
[](https://github.com/link-foundation/command-stream/stargazers)
|
|
4
|
+
|
|
1
5
|
[](https://gitpod.io/#https://github.com/link-foundation/command-stream)
|
|
2
6
|
[](https://github.com/codespaces/new?hide_repo_select=true&ref=main&repo=link-foundation/command-stream)
|
|
3
7
|
|
|
@@ -23,31 +27,48 @@ A modern $ shell utility library with streaming, async iteration, and EventEmitt
|
|
|
23
27
|
|
|
24
28
|
## Comparison with Other Libraries
|
|
25
29
|
|
|
26
|
-
| Feature | [command-stream](https://github.com/link-foundation/command-stream) | [
|
|
27
|
-
|
|
28
|
-
|
|
|
29
|
-
|
|
|
30
|
-
|
|
|
31
|
-
| **
|
|
32
|
-
| **
|
|
33
|
-
| **
|
|
34
|
-
| **
|
|
35
|
-
| **
|
|
36
|
-
| **
|
|
37
|
-
| **
|
|
38
|
-
| **
|
|
39
|
-
| **
|
|
40
|
-
| **
|
|
41
|
-
| **
|
|
42
|
-
| **
|
|
43
|
-
| **
|
|
44
|
-
| **
|
|
45
|
-
| **
|
|
46
|
-
| **
|
|
47
|
-
| **
|
|
48
|
-
| **
|
|
49
|
-
| **
|
|
50
|
-
| **
|
|
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** | โ
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** | โ
**410 tests, 909 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
|
+
- **โญ 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
|
+
- **๐ 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
|
+
- **๐ 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)
|
|
70
|
+
|
|
71
|
+
**โญ Help Us Grow!** If command-stream's **revolutionary virtual commands** and **advanced streaming capabilities** help your project, [**please star us on GitHub**](https://github.com/link-foundation/command-stream) to help the project grow!
|
|
51
72
|
|
|
52
73
|
### Why Choose command-stream?
|
|
53
74
|
|
|
@@ -60,7 +81,9 @@ A modern $ shell utility library with streaming, async iteration, and EventEmitt
|
|
|
60
81
|
- **๐ Shell Replacement**: Dynamic error handling with `set -e`/`set +e` equivalents for .sh file replacement
|
|
61
82
|
- **โก Bun Optimized**: Designed for Bun with Node.js fallback compatibility
|
|
62
83
|
- **๐พ Memory Efficient**: Streaming prevents large buffer accumulation
|
|
63
|
-
- **๐ก๏ธ Production Ready**:
|
|
84
|
+
- **๐ก๏ธ Production Ready**: **410 tests, 909 assertions** with comprehensive coverage including CI reliability
|
|
85
|
+
- **๐ฏ Advanced Signal Handling**: Robust SIGINT/SIGTERM forwarding with proper child process cleanup
|
|
86
|
+
- **๐ Debug-Friendly**: Comprehensive VERBOSE tracing for CI debugging and troubleshooting
|
|
64
87
|
|
|
65
88
|
## Built-in Commands (๐ NEW!)
|
|
66
89
|
|
|
@@ -140,6 +163,42 @@ console.log(result.stdout);
|
|
|
140
163
|
console.log(result.code); // exit code
|
|
141
164
|
```
|
|
142
165
|
|
|
166
|
+
### Custom Options with $({ options }) Syntax (NEW!)
|
|
167
|
+
|
|
168
|
+
```javascript
|
|
169
|
+
import { $ } from 'command-stream';
|
|
170
|
+
|
|
171
|
+
// Create a $ with custom options
|
|
172
|
+
const $silent = $({ mirror: false, capture: true });
|
|
173
|
+
const result = await $silent`echo "quiet operation"`;
|
|
174
|
+
|
|
175
|
+
// Options for stdin handling
|
|
176
|
+
const $withInput = $({ stdin: 'input data\n' });
|
|
177
|
+
await $withInput`cat`; // Pipes the input to cat
|
|
178
|
+
|
|
179
|
+
// Custom environment variables
|
|
180
|
+
const $withEnv = $({ env: { ...process.env, MY_VAR: 'value' } });
|
|
181
|
+
await $withEnv`printenv MY_VAR`; // Prints: value
|
|
182
|
+
|
|
183
|
+
// Custom working directory
|
|
184
|
+
const $inTmp = $({ cwd: '/tmp' });
|
|
185
|
+
await $inTmp`pwd`; // Prints: /tmp
|
|
186
|
+
|
|
187
|
+
// Combine multiple options
|
|
188
|
+
const $custom = $({
|
|
189
|
+
stdin: 'test data',
|
|
190
|
+
mirror: false,
|
|
191
|
+
capture: true,
|
|
192
|
+
cwd: '/tmp'
|
|
193
|
+
});
|
|
194
|
+
await $custom`cat > output.txt`; // Writes to /tmp/output.txt silently
|
|
195
|
+
|
|
196
|
+
// Reusable configurations
|
|
197
|
+
const $prod = $({ env: { NODE_ENV: 'production' }, capture: true });
|
|
198
|
+
await $prod`npm start`;
|
|
199
|
+
await $prod`npm test`;
|
|
200
|
+
```
|
|
201
|
+
|
|
143
202
|
### Execution Control (NEW!)
|
|
144
203
|
|
|
145
204
|
```javascript
|
|
@@ -242,6 +301,83 @@ syncCmd.on('end', result => {
|
|
|
242
301
|
const syncResult = syncCmd.sync();
|
|
243
302
|
```
|
|
244
303
|
|
|
304
|
+
### Streaming Interfaces
|
|
305
|
+
|
|
306
|
+
Advanced streaming interfaces for fine-grained process control:
|
|
307
|
+
|
|
308
|
+
```javascript
|
|
309
|
+
import { $ } from 'command-stream';
|
|
310
|
+
|
|
311
|
+
// ๐ฏ STDIN CONTROL: Send data to interactive commands (real-time)
|
|
312
|
+
const grepCmd = $`grep "important"`;
|
|
313
|
+
const stdin = await grepCmd.streams.stdin; // Available immediately
|
|
314
|
+
|
|
315
|
+
stdin.write('ignore this line\n');
|
|
316
|
+
stdin.write('important message\n');
|
|
317
|
+
stdin.write('skip this too\n');
|
|
318
|
+
stdin.end();
|
|
319
|
+
|
|
320
|
+
const result = await grepCmd;
|
|
321
|
+
console.log(result.stdout); // "important message\n"
|
|
322
|
+
|
|
323
|
+
// ๐ง BINARY DATA: Access raw buffers (after command finishes)
|
|
324
|
+
const cmd = $`echo "Hello World"`;
|
|
325
|
+
const buffer = await cmd.buffers.stdout; // Complete snapshot
|
|
326
|
+
console.log(buffer.length); // 12
|
|
327
|
+
|
|
328
|
+
// ๐ TEXT DATA: Access as strings (after command finishes)
|
|
329
|
+
const textCmd = $`echo "Hello World"`;
|
|
330
|
+
const text = await textCmd.strings.stdout; // Complete snapshot
|
|
331
|
+
console.log(text.trim()); // "Hello World"
|
|
332
|
+
|
|
333
|
+
// โก PROCESS CONTROL: Kill commands that ignore stdin
|
|
334
|
+
const pingCmd = $`ping google.com`;
|
|
335
|
+
|
|
336
|
+
// Some commands ignore stdin input
|
|
337
|
+
const pingStdin = await pingCmd.streams.stdin;
|
|
338
|
+
if (pingStdin) {
|
|
339
|
+
pingStdin.write('q\n'); // ping ignores this
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Use kill() for forceful termination
|
|
343
|
+
setTimeout(() => pingCmd.kill(), 2000);
|
|
344
|
+
const pingResult = await pingCmd;
|
|
345
|
+
console.log('Ping stopped with code:', pingResult.code); // 143 (SIGTERM)
|
|
346
|
+
|
|
347
|
+
// ๐ MIXED STDOUT/STDERR: Handle both streams (complete snapshots)
|
|
348
|
+
const mixedCmd = $`sh -c 'echo "out" && echo "err" >&2'`;
|
|
349
|
+
const [stdout, stderr] = await Promise.all([
|
|
350
|
+
mixedCmd.strings.stdout, // Available after finish
|
|
351
|
+
mixedCmd.strings.stderr // Available after finish
|
|
352
|
+
]);
|
|
353
|
+
console.log('Out:', stdout.trim()); // "out"
|
|
354
|
+
console.log('Err:', stderr.trim()); // "err"
|
|
355
|
+
|
|
356
|
+
// ๐โโ๏ธ AUTO-START: Streams auto-start processes when accessed
|
|
357
|
+
const cmd = $`echo "test"`;
|
|
358
|
+
console.log('Started?', cmd.started); // false
|
|
359
|
+
|
|
360
|
+
const output = await cmd.streams.stdout; // Auto-starts, immediate access
|
|
361
|
+
console.log('Started?', cmd.started); // true
|
|
362
|
+
|
|
363
|
+
// ๐ BACKWARD COMPATIBLE: Traditional await still works
|
|
364
|
+
const traditional = await $`echo "still works"`;
|
|
365
|
+
console.log(traditional.stdout); // "still works\n"
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
**Key Features:**
|
|
369
|
+
- `command.streams.stdin/stdout/stderr` - Direct access to Node.js streams
|
|
370
|
+
- `command.buffers.stdin/stdout/stderr` - Binary data as Buffer objects
|
|
371
|
+
- `command.strings.stdin/stdout/stderr` - Text data as strings
|
|
372
|
+
- `command.kill()` - Forceful process termination
|
|
373
|
+
- **Auto-start behavior:** Process starts only when accessing stream properties
|
|
374
|
+
- **Perfect for:** Interactive commands (grep, sort, bc), data processing, real-time control
|
|
375
|
+
- **Network commands (ping, wget) ignore stdin** โ Use `kill()` method instead
|
|
376
|
+
|
|
377
|
+
**๐ Streams vs Buffers/Strings:**
|
|
378
|
+
- **`streams.*`** - Available **immediately** when command starts, for real-time interaction
|
|
379
|
+
- **`buffers.*` & `strings.*`** - Complete **snapshots** available only **after** command finishes
|
|
380
|
+
|
|
245
381
|
### Shell Replacement (.sh โ .mjs)
|
|
246
382
|
|
|
247
383
|
Replace bash scripts with JavaScript while keeping shell semantics:
|
|
@@ -318,7 +454,7 @@ Create custom commands that work seamlessly alongside built-ins:
|
|
|
318
454
|
import { $, register, unregister, listCommands } from 'command-stream';
|
|
319
455
|
|
|
320
456
|
// Register a custom command
|
|
321
|
-
register('greet', async (args, stdin) => {
|
|
457
|
+
register('greet', async ({ args, stdin }) => {
|
|
322
458
|
const name = args[0] || 'World';
|
|
323
459
|
return { stdout: `Hello, ${name}!\n`, code: 0 };
|
|
324
460
|
});
|
|
@@ -328,7 +464,7 @@ await $`greet Alice`; // โ "Hello, Alice!"
|
|
|
328
464
|
await $`echo "Bob" | greet`; // โ "Hello, Bob!"
|
|
329
465
|
|
|
330
466
|
// Streaming virtual commands with async generators
|
|
331
|
-
register('countdown', async function* (args) {
|
|
467
|
+
register('countdown', async function* ({ args }) {
|
|
332
468
|
const start = parseInt(args[0] || 5);
|
|
333
469
|
for (let i = start; i >= 0; i--) {
|
|
334
470
|
yield `${i}\n`;
|
|
@@ -365,7 +501,7 @@ await execa('node', ['script.js']); // execa: separate processes
|
|
|
365
501
|
await $`node script.js`; // zx: shell commands only
|
|
366
502
|
|
|
367
503
|
// โ
command-stream: JavaScript functions AS shell commands
|
|
368
|
-
register('deploy', async (args) => {
|
|
504
|
+
register('deploy', async ({ args }) => {
|
|
369
505
|
const env = args[0] || 'staging';
|
|
370
506
|
await deployToEnvironment(env);
|
|
371
507
|
return { stdout: `Deployed to ${env}!\n`, code: 0 };
|
|
@@ -401,11 +537,11 @@ await $`seq 1 5 | cat > numbers.txt`;
|
|
|
401
537
|
await $`git log --oneline | head -n 5`;
|
|
402
538
|
|
|
403
539
|
// ๐ UNIQUE: Virtual command piping
|
|
404
|
-
register('uppercase', async (args, stdin) => {
|
|
540
|
+
register('uppercase', async ({ args, stdin }) => {
|
|
405
541
|
return { stdout: stdin.toUpperCase(), code: 0 };
|
|
406
542
|
});
|
|
407
543
|
|
|
408
|
-
register('reverse', async (args, stdin) => {
|
|
544
|
+
register('reverse', async ({ args, stdin }) => {
|
|
409
545
|
return { stdout: stdin.split('').reverse().join(''), code: 0 };
|
|
410
546
|
});
|
|
411
547
|
|
|
@@ -433,12 +569,12 @@ import { $, register } from 'command-stream';
|
|
|
433
569
|
const result = await $`echo "hello"`.pipe($`echo "World: $(cat)"`);
|
|
434
570
|
|
|
435
571
|
// ๐ Virtual command chaining
|
|
436
|
-
register('add-prefix', async (args, stdin) => {
|
|
572
|
+
register('add-prefix', async ({ args, stdin }) => {
|
|
437
573
|
const prefix = args[0] || 'PREFIX:';
|
|
438
574
|
return { stdout: `${prefix} ${stdin.trim()}\n`, code: 0 };
|
|
439
575
|
});
|
|
440
576
|
|
|
441
|
-
register('add-suffix', async (args, stdin) => {
|
|
577
|
+
register('add-suffix', async ({ args, stdin }) => {
|
|
442
578
|
const suffix = args[0] || 'SUFFIX';
|
|
443
579
|
return { stdout: `${stdin.trim()} ${suffix}\n`, code: 0 };
|
|
444
580
|
});
|
|
@@ -463,7 +599,7 @@ try {
|
|
|
463
599
|
}
|
|
464
600
|
|
|
465
601
|
// โ
Complex data processing
|
|
466
|
-
register('json-parse', async (args, stdin) => {
|
|
602
|
+
register('json-parse', async ({ args, stdin }) => {
|
|
467
603
|
try {
|
|
468
604
|
const data = JSON.parse(stdin);
|
|
469
605
|
return { stdout: JSON.stringify(data, null, 2), code: 0 };
|
|
@@ -472,7 +608,7 @@ register('json-parse', async (args, stdin) => {
|
|
|
472
608
|
}
|
|
473
609
|
});
|
|
474
610
|
|
|
475
|
-
register('extract-field', async (args, stdin) => {
|
|
611
|
+
register('extract-field', async ({ args, stdin }) => {
|
|
476
612
|
const field = args[0];
|
|
477
613
|
try {
|
|
478
614
|
const data = JSON.parse(stdin);
|
|
@@ -660,7 +796,8 @@ The enhanced `$` function returns a `ProcessRunner` instance that extends `Event
|
|
|
660
796
|
- `env: object` - Environment variables
|
|
661
797
|
|
|
662
798
|
**Override defaults:**
|
|
663
|
-
- Use
|
|
799
|
+
- Use `$({ options })` syntax for one-off configurations with template literals
|
|
800
|
+
- Use `sh(command, options)` for one-off overrides with string commands
|
|
664
801
|
- Use `create(defaultOptions)` to create custom `$` with different defaults
|
|
665
802
|
|
|
666
803
|
### Shell Settings API
|
|
@@ -676,6 +813,38 @@ Control shell behavior like bash `set`/`unset` commands:
|
|
|
676
813
|
- `unset(option)`: Disable shell option
|
|
677
814
|
- `shell.settings()`: Get current settings object
|
|
678
815
|
|
|
816
|
+
#### Error Handling Modes
|
|
817
|
+
|
|
818
|
+
```javascript
|
|
819
|
+
import { $, shell } from 'command-stream';
|
|
820
|
+
|
|
821
|
+
// โ
Default behavior: Commands don't throw on non-zero exit
|
|
822
|
+
const result = await $`ls nonexistent-file`; // Won't throw
|
|
823
|
+
console.log(result.code); // โ 2 (non-zero, but no exception)
|
|
824
|
+
|
|
825
|
+
// โ
Enable errexit: Commands throw on non-zero exit
|
|
826
|
+
shell.errexit(true);
|
|
827
|
+
try {
|
|
828
|
+
await $`ls nonexistent-file`; // Throws error
|
|
829
|
+
} catch (error) {
|
|
830
|
+
console.log('Command failed:', error.code); // โ 2
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// โ
Disable errexit: Back to non-throwing behavior
|
|
834
|
+
shell.errexit(false);
|
|
835
|
+
await $`ls nonexistent-file`; // Won't throw, returns result with code 2
|
|
836
|
+
|
|
837
|
+
// โ
One-time override without changing global settings
|
|
838
|
+
try {
|
|
839
|
+
const result = await $`ls nonexistent-file`;
|
|
840
|
+
if (result.code !== 0) {
|
|
841
|
+
throw new Error(`Command failed with code ${result.code}`);
|
|
842
|
+
}
|
|
843
|
+
} catch (error) {
|
|
844
|
+
console.log('Manual error handling');
|
|
845
|
+
}
|
|
846
|
+
```
|
|
847
|
+
|
|
679
848
|
### Virtual Commands API
|
|
680
849
|
|
|
681
850
|
Control and extend the command system with custom JavaScript functions:
|
|
@@ -690,11 +859,79 @@ Control and extend the command system with custom JavaScript functions:
|
|
|
690
859
|
- `enableVirtualCommands()`: Enable virtual command processing
|
|
691
860
|
- `disableVirtualCommands()`: Disable virtual commands (use system commands only)
|
|
692
861
|
|
|
862
|
+
#### Advanced Virtual Command Features
|
|
863
|
+
|
|
864
|
+
```javascript
|
|
865
|
+
import { $, register } from 'command-stream';
|
|
866
|
+
|
|
867
|
+
// โ
Cancellation support with AbortController
|
|
868
|
+
register('cancellable', async function* ({ args, stdin, abortSignal }) {
|
|
869
|
+
for (let i = 0; i < 10; i++) {
|
|
870
|
+
if (abortSignal?.aborted) {
|
|
871
|
+
break; // Proper cancellation handling
|
|
872
|
+
}
|
|
873
|
+
yield `Count: ${i}\n`;
|
|
874
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
875
|
+
}
|
|
876
|
+
});
|
|
877
|
+
|
|
878
|
+
// โ
Access to all process options
|
|
879
|
+
// All original options (built-in + custom) are available in the 'options' object
|
|
880
|
+
// Common options like cwd, env are also available at top level for convenience
|
|
881
|
+
// Runtime additions: isCancelled function, abortSignal
|
|
882
|
+
register('debug-info', async ({ args, stdin, cwd, env, options, isCancelled }) => {
|
|
883
|
+
return {
|
|
884
|
+
stdout: JSON.stringify({
|
|
885
|
+
args,
|
|
886
|
+
cwd, // Available at top level for convenience
|
|
887
|
+
env: Object.keys(env || {}), // Available at top level for convenience
|
|
888
|
+
stdinLength: stdin?.length || 0,
|
|
889
|
+
allOptions: options, // All original options (built-in + custom)
|
|
890
|
+
mirror: options.mirror, // Built-in option from options object
|
|
891
|
+
capture: options.capture, // Built-in option from options object
|
|
892
|
+
customOption: options.customOption || 'not provided', // Custom option
|
|
893
|
+
isCancelledAvailable: typeof isCancelled === 'function'
|
|
894
|
+
}, null, 2),
|
|
895
|
+
code: 0
|
|
896
|
+
};
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
// โ
Error handling and non-zero exit codes
|
|
900
|
+
register('maybe-fail', async ({ args }) => {
|
|
901
|
+
if (Math.random() > 0.5) {
|
|
902
|
+
return {
|
|
903
|
+
stdout: 'Success!\n',
|
|
904
|
+
code: 0
|
|
905
|
+
};
|
|
906
|
+
} else {
|
|
907
|
+
return {
|
|
908
|
+
stdout: '',
|
|
909
|
+
stderr: 'Random failure occurred\n',
|
|
910
|
+
code: 1
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
});
|
|
914
|
+
|
|
915
|
+
// โ
Example: User options flow through to virtual commands
|
|
916
|
+
register('show-options', async ({ args, stdin, options, cwd }) => {
|
|
917
|
+
return {
|
|
918
|
+
stdout: `Custom: ${options.customValue || 'none'}, CWD: ${cwd || options.cwd || 'default'}\n`,
|
|
919
|
+
code: 0
|
|
920
|
+
};
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
// Usage example showing options passed to virtual command:
|
|
924
|
+
const result = await $({ customValue: 'hello world', cwd: '/tmp' })`show-options`;
|
|
925
|
+
console.log(result.stdout); // Output: Custom: hello world, CWD: /tmp
|
|
926
|
+
```
|
|
927
|
+
|
|
693
928
|
#### Handler Function Signature
|
|
694
929
|
|
|
695
930
|
```javascript
|
|
696
931
|
// Regular async function
|
|
697
|
-
async function handler(args, stdin, options) {
|
|
932
|
+
async function handler({ args, stdin, abortSignal, cwd, env, options, isCancelled }) {
|
|
933
|
+
// All original options available in 'options': options.mirror, options.capture, options.customValue, etc.
|
|
934
|
+
// Common options like cwd, env also available at top level for convenience
|
|
698
935
|
return {
|
|
699
936
|
code: 0, // Exit code (number)
|
|
700
937
|
stdout: "output", // Standard output (string)
|
|
@@ -703,10 +940,13 @@ async function handler(args, stdin, options) {
|
|
|
703
940
|
}
|
|
704
941
|
|
|
705
942
|
// Async generator for streaming
|
|
706
|
-
async function
|
|
943
|
+
async function streamingHandler({ args, stdin, abortSignal, cwd, env, options, isCancelled }) {
|
|
944
|
+
// Access both built-in and custom options from 'options' object
|
|
945
|
+
if (options.customFlag) {
|
|
946
|
+
yield "custom behavior\n";
|
|
947
|
+
}
|
|
707
948
|
yield "chunk1\n";
|
|
708
949
|
yield "chunk2\n";
|
|
709
|
-
// Each yield sends a chunk in real-time
|
|
710
950
|
}
|
|
711
951
|
```
|
|
712
952
|
|
|
@@ -771,10 +1011,172 @@ const result4 = await $`echo "pipe test"`.pipe($`cat`);
|
|
|
771
1011
|
const text4 = await result4.text(); // "pipe test\n"
|
|
772
1012
|
```
|
|
773
1013
|
|
|
1014
|
+
## Signal Handling (CTRL+C Support)
|
|
1015
|
+
|
|
1016
|
+
The library provides **advanced CTRL+C handling** that properly manages signals across different scenarios:
|
|
1017
|
+
|
|
1018
|
+
### How It Works
|
|
1019
|
+
|
|
1020
|
+
1. **Smart Signal Forwarding**: CTRL+C is forwarded **only when child processes are active**
|
|
1021
|
+
2. **User Handler Preservation**: When no children are running, your custom SIGINT handlers work normally
|
|
1022
|
+
3. **Process Groups**: Child processes use detached spawning for proper signal isolation
|
|
1023
|
+
4. **TTY Mode Support**: Raw TTY mode is properly managed and restored on interruption
|
|
1024
|
+
5. **Graceful Termination**: Uses SIGTERM โ SIGKILL escalation for robust process cleanup
|
|
1025
|
+
6. **Exit Code Standards**: Proper signal exit codes (130 for SIGINT, 143 for SIGTERM)
|
|
1026
|
+
|
|
1027
|
+
### Advanced Signal Behavior
|
|
1028
|
+
|
|
1029
|
+
```javascript
|
|
1030
|
+
// โ
Smart signal handling - only interferes when necessary
|
|
1031
|
+
import { $ } from 'command-stream';
|
|
1032
|
+
|
|
1033
|
+
// Case 1: No children active - your handlers work normally
|
|
1034
|
+
process.on('SIGINT', () => {
|
|
1035
|
+
console.log('My custom handler runs!');
|
|
1036
|
+
process.exit(42); // Custom exit code
|
|
1037
|
+
});
|
|
1038
|
+
// Press CTRL+C โ Your handler runs, exits with code 42
|
|
1039
|
+
|
|
1040
|
+
// Case 2: Children active - automatic forwarding
|
|
1041
|
+
await $`ping 8.8.8.8`; // Press CTRL+C โ Forwards to ping, exits with code 130
|
|
1042
|
+
|
|
1043
|
+
// Case 3: Multiple processes - all interrupted
|
|
1044
|
+
await Promise.all([
|
|
1045
|
+
$`sleep 100`,
|
|
1046
|
+
$`ping google.com`
|
|
1047
|
+
]); // Press CTRL+C โ All processes terminated, exits with code 130
|
|
1048
|
+
```
|
|
1049
|
+
|
|
1050
|
+
### Examples
|
|
1051
|
+
|
|
1052
|
+
```javascript
|
|
1053
|
+
// Long-running command that can be interrupted with CTRL+C
|
|
1054
|
+
try {
|
|
1055
|
+
await $`ping 8.8.8.8`; // Press CTRL+C to stop
|
|
1056
|
+
} catch (error) {
|
|
1057
|
+
console.log('Command interrupted:', error.code); // Exit code 130 (SIGINT)
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
// Multiple concurrent processes - CTRL+C stops all
|
|
1061
|
+
try {
|
|
1062
|
+
await Promise.all([
|
|
1063
|
+
$`sleep 100`,
|
|
1064
|
+
$`ping google.com`,
|
|
1065
|
+
$`tail -f /var/log/system.log`
|
|
1066
|
+
]);
|
|
1067
|
+
} catch (error) {
|
|
1068
|
+
// All processes are terminated when you press CTRL+C
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
// Works with streaming patterns too
|
|
1072
|
+
try {
|
|
1073
|
+
for await (const chunk of $`ping 8.8.8.8`.stream()) {
|
|
1074
|
+
console.log(chunk);
|
|
1075
|
+
// Press CTRL+C to break the loop and stop the process
|
|
1076
|
+
}
|
|
1077
|
+
} catch (error) {
|
|
1078
|
+
console.log('Streaming interrupted');
|
|
1079
|
+
}
|
|
1080
|
+
```
|
|
1081
|
+
|
|
1082
|
+
### Signal Handling Behavior
|
|
1083
|
+
|
|
1084
|
+
- **๐ฏ Smart Detection**: Only forwards CTRL+C when child processes are active
|
|
1085
|
+
- **๐ก๏ธ Non-Interference**: Preserves user SIGINT handlers when no children running
|
|
1086
|
+
- **โก Interactive Commands**: Commands like `vim`, `less`, `top` work with their own signal handling
|
|
1087
|
+
- **๐ Process Groups**: Detached spawning ensures proper signal isolation
|
|
1088
|
+
- **๐งน TTY Cleanup**: Raw terminal mode properly restored on interruption
|
|
1089
|
+
- **๐ Standard Exit Codes**:
|
|
1090
|
+
- `130` - SIGINT interruption (CTRL+C)
|
|
1091
|
+
- `143` - SIGTERM termination (programmatic kill)
|
|
1092
|
+
- `137` - SIGKILL force termination
|
|
1093
|
+
|
|
1094
|
+
### Command Resolution Priority
|
|
1095
|
+
|
|
1096
|
+
```javascript
|
|
1097
|
+
// Understanding how commands are resolved:
|
|
1098
|
+
|
|
1099
|
+
// 1. Virtual Commands (highest priority)
|
|
1100
|
+
register('echo', () => ({ stdout: 'virtual!\n', code: 0 }));
|
|
1101
|
+
await $`echo test`; // โ "virtual!"
|
|
1102
|
+
|
|
1103
|
+
// 2. Built-in Commands (if no virtual match)
|
|
1104
|
+
unregister('echo');
|
|
1105
|
+
await $`echo test`; // โ Uses built-in echo
|
|
1106
|
+
|
|
1107
|
+
// 3. System Commands (if no built-in/virtual match)
|
|
1108
|
+
await $`unknown-command`; // โ Uses system PATH lookup
|
|
1109
|
+
|
|
1110
|
+
// 4. Virtual Bypass (special case)
|
|
1111
|
+
await $({ stdin: 'data' })`sleep 1`; // Bypasses virtual sleep, uses system sleep
|
|
1112
|
+
```
|
|
1113
|
+
|
|
1114
|
+
## Execution Patterns Deep Dive
|
|
1115
|
+
|
|
1116
|
+
### When to Use Different Patterns
|
|
1117
|
+
|
|
1118
|
+
```javascript
|
|
1119
|
+
import { $ } from 'command-stream';
|
|
1120
|
+
|
|
1121
|
+
// โ
Use await for simple command execution
|
|
1122
|
+
const result = await $`ls -la`;
|
|
1123
|
+
|
|
1124
|
+
// โ
Use .sync() when you need blocking execution with events
|
|
1125
|
+
const syncCmd = $`build-script`
|
|
1126
|
+
.on('stdout', chunk => updateProgress(chunk))
|
|
1127
|
+
.sync(); // Events fire after completion
|
|
1128
|
+
|
|
1129
|
+
// โ
Use .start() for non-blocking execution with real-time events
|
|
1130
|
+
const asyncCmd = $`long-running-server`
|
|
1131
|
+
.on('stdout', chunk => logOutput(chunk))
|
|
1132
|
+
.start(); // Events fire in real-time
|
|
1133
|
+
|
|
1134
|
+
// โ
Use .stream() for processing large outputs efficiently
|
|
1135
|
+
for await (const chunk of $`generate-big-file`.stream()) {
|
|
1136
|
+
processChunkInRealTime(chunk);
|
|
1137
|
+
} // Memory efficient - processes chunks as they arrive
|
|
1138
|
+
|
|
1139
|
+
// โ
Use EventEmitter pattern for complex workflows
|
|
1140
|
+
$`deployment-script`
|
|
1141
|
+
.on('stdout', chunk => {
|
|
1142
|
+
if (chunk.toString().includes('ERROR')) {
|
|
1143
|
+
handleError(chunk);
|
|
1144
|
+
}
|
|
1145
|
+
})
|
|
1146
|
+
.on('stderr', chunk => logError(chunk))
|
|
1147
|
+
.on('end', result => {
|
|
1148
|
+
if (result.code === 0) {
|
|
1149
|
+
notifySuccess();
|
|
1150
|
+
}
|
|
1151
|
+
})
|
|
1152
|
+
.start();
|
|
1153
|
+
```
|
|
1154
|
+
|
|
1155
|
+
### Performance Considerations
|
|
1156
|
+
|
|
1157
|
+
```javascript
|
|
1158
|
+
// ๐ Memory Efficient: For large outputs, use streaming
|
|
1159
|
+
for await (const chunk of $`cat huge-file.log`.stream()) {
|
|
1160
|
+
processChunk(chunk); // Processes incrementally
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
// ๐ Memory Inefficient: Buffers entire output in memory
|
|
1164
|
+
const result = await $`cat huge-file.log`;
|
|
1165
|
+
processFile(result.stdout); // Loads everything into memory
|
|
1166
|
+
|
|
1167
|
+
// โก Fastest: Sync execution for small, quick commands
|
|
1168
|
+
const quickResult = $`pwd`.sync();
|
|
1169
|
+
|
|
1170
|
+
// ๐ Best for UX: Async with events for long-running commands
|
|
1171
|
+
$`npm install`
|
|
1172
|
+
.on('stdout', showProgress)
|
|
1173
|
+
.start();
|
|
1174
|
+
```
|
|
1175
|
+
|
|
774
1176
|
## Testing
|
|
775
1177
|
|
|
776
1178
|
```bash
|
|
777
|
-
# Run comprehensive test suite (
|
|
1179
|
+
# Run comprehensive test suite (270+ tests)
|
|
778
1180
|
bun test
|
|
779
1181
|
|
|
780
1182
|
# Run tests with coverage report
|
|
@@ -782,9 +1184,10 @@ bun test --coverage
|
|
|
782
1184
|
|
|
783
1185
|
# Run specific test categories
|
|
784
1186
|
npm run test:features # Feature comparison tests
|
|
785
|
-
npm run test:builtin # Built-in commands tests
|
|
1187
|
+
npm run test:builtin # Built-in commands tests
|
|
786
1188
|
npm run test:pipe # .pipe() method tests
|
|
787
1189
|
npm run test:sync # Synchronous execution tests
|
|
1190
|
+
npm run test:signal # CTRL+C signal handling tests
|
|
788
1191
|
```
|
|
789
1192
|
|
|
790
1193
|
## Requirements
|
package/package.json
CHANGED