command-stream 0.3.1 → 0.4.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 +322 -29
- package/package.json +4 -4
- package/{$.mjs → src/$.mjs} +1202 -213
- package/src/$.utils.mjs +81 -0
- package/src/commands/$.basename.mjs +19 -0
- package/src/commands/$.cat.mjs +44 -0
- package/src/commands/$.cd.mjs +16 -0
- package/src/commands/$.cp.mjs +112 -0
- package/src/commands/$.dirname.mjs +12 -0
- package/src/commands/$.echo.mjs +15 -0
- package/src/commands/$.env.mjs +14 -0
- package/src/commands/$.exit.mjs +9 -0
- package/src/commands/$.false.mjs +3 -0
- package/src/commands/$.ls.mjs +79 -0
- package/src/commands/$.mkdir.mjs +45 -0
- package/src/commands/$.mv.mjs +89 -0
- package/src/commands/$.pwd.mjs +8 -0
- package/src/commands/$.rm.mjs +60 -0
- package/src/commands/$.seq.mjs +48 -0
- package/src/commands/$.sleep.mjs +72 -0
- package/src/commands/$.test.mjs +59 -0
- package/src/commands/$.touch.mjs +36 -0
- package/src/commands/$.true.mjs +5 -0
- package/src/commands/$.which.mjs +32 -0
- package/src/commands/$.yes.mjs +48 -0
package/README.md
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
[](https://npmjs.com/command-stream)
|
|
2
|
+
[](https://github.com/link-foundation/command-stream/blob/main/LICENSE)
|
|
3
|
+
|
|
1
4
|
[](https://gitpod.io/#https://github.com/link-foundation/command-stream)
|
|
2
5
|
[](https://github.com/codespaces/new?hide_repo_select=true&ref=main&repo=link-foundation/command-stream)
|
|
3
6
|
|
|
@@ -23,31 +26,39 @@ A modern $ shell utility library with streaming, async iteration, and EventEmitt
|
|
|
23
26
|
|
|
24
27
|
## Comparison with Other Libraries
|
|
25
28
|
|
|
26
|
-
| Feature | [command-stream](https://github.com/link-foundation/command-stream) | [Bun.$](https://bun.sh/docs/runtime/shell) | [execa](https://github.com/sindresorhus/execa) | [zx](https://github.com/google/zx) |
|
|
27
|
-
|
|
28
|
-
| **Runtime Support** | ✅ Bun + Node.js | 🟡 Bun only | ✅ Node.js | ✅ Node.js |
|
|
29
|
-
| **Template Literals** | ✅ `` $`cmd` `` | ✅ `` $`cmd` `` | ✅ `` $`cmd` `` | ✅ `` $`cmd` `` |
|
|
30
|
-
| **Real-time Streaming** | ✅ Live output | ❌ Buffer only | 🟡 Limited | ❌ Buffer only |
|
|
31
|
-
| **Synchronous Execution** | ✅ `.sync()` with events | ❌ No | ✅ `execaSync` | ❌ No |
|
|
32
|
-
| **Async Iteration** | ✅ `for await (chunk of $.stream())` | ❌ No | ❌ No | ❌ No |
|
|
33
|
-
| **EventEmitter Pattern** | ✅ `.on('data', ...)` | ❌ No | 🟡 Limited events | ❌ No |
|
|
34
|
-
| **Mixed Patterns** | ✅ Events + await/sync | ❌ No | ❌ No | ❌ No |
|
|
35
|
-
| **Bun.$ Compatibility** | ✅ `.text()` method support | ✅ Native API | ❌ No | ❌ No |
|
|
36
|
-
| **Shell Injection Protection** | ✅ Auto-quoting | ✅ Built-in | ✅ Safe by default | ✅ Safe by default |
|
|
37
|
-
| **Cross-platform** | ✅ macOS/Linux/Windows | ✅ Yes | ✅ Yes | ✅ Yes |
|
|
38
|
-
| **Performance** | ⚡ Fast (Bun optimized) | ⚡ Very fast | 🐌 Moderate | 🐌 Slow |
|
|
39
|
-
| **Memory Efficiency** | ✅ Streaming prevents buildup | 🟡 Buffers in memory | 🟡 Buffers in memory | 🟡 Buffers in memory |
|
|
40
|
-
| **Error Handling** | ✅ Configurable (`set -e`/`set +e`, non-zero OK by default) | ✅ Throws on error | ✅ Throws on error | ✅ Throws on error |
|
|
41
|
-
| **Shell Settings** | ✅ `set -e`/`set +e` equivalent | ❌ No | ❌ No | ❌ No |
|
|
42
|
-
| **Stdout Support** | ✅ Real-time streaming + events | ✅ Shell redirection + buffered | ✅ Node.js streams + interleaved | ✅ Readable streams + `.pipe.stdout` |
|
|
43
|
-
| **Stderr Support** | ✅ Real-time streaming + events | ✅ Redirection + `.quiet()` access | ✅ Streams + interleaved output | ✅ Readable streams + `.pipe.stderr` |
|
|
44
|
-
| **Stdin Support** | ✅ string/Buffer/inherit/ignore | ✅ Pipe operations | ✅ Input/output streams | ✅ Basic stdin |
|
|
45
|
-
| **Built-in Commands** | ✅ **18 commands**: cat, ls, mkdir, rm, mv, cp, touch, basename, dirname, seq, yes + all Bun.$ commands | ✅ echo, cd, etc. | ❌ Uses system | ❌ Uses system |
|
|
46
|
-
| **Virtual Commands Engine** | ✅ **Revolutionary**: Register JavaScript functions as shell commands with full pipeline support | ❌ No extensibility | ❌ No custom commands | ❌ No custom commands |
|
|
47
|
-
| **Pipeline/Piping Support** | ✅ **Advanced**: System + Built-ins + Virtual + Mixed + `.pipe()` method | ✅ Standard shell piping | ✅ Programmatic `.pipe()` + multi-destination | ✅ Shell piping + `.pipe()` method |
|
|
48
|
-
| **Bundle Size** | 📦
|
|
49
|
-
| **
|
|
50
|
-
| **
|
|
29
|
+
| Feature | [command-stream](https://github.com/link-foundation/command-stream) | [Bun.$](https://bun.sh/docs/runtime/shell) | [execa](https://github.com/sindresorhus/execa) | [zx](https://github.com/google/zx) | [ShellJS](https://github.com/shelljs/shelljs) | [cross-spawn](https://github.com/moxystudio/node-cross-spawn) |
|
|
30
|
+
|---------|----------------|-------|-------|-----|-------|-------|
|
|
31
|
+
| **Runtime Support** | ✅ Bun + Node.js | 🟡 Bun only | ✅ Node.js | ✅ Node.js | ✅ Node.js | ✅ Node.js |
|
|
32
|
+
| **Template Literals** | ✅ `` $`cmd` `` | ✅ `` $`cmd` `` | ✅ `` $`cmd` `` | ✅ `` $`cmd` `` | ❌ Function calls | ❌ Function calls |
|
|
33
|
+
| **Real-time Streaming** | ✅ Live output | ❌ Buffer only | 🟡 Limited | ❌ Buffer only | ❌ Buffer only | ❌ Buffer only |
|
|
34
|
+
| **Synchronous Execution** | ✅ `.sync()` with events | ❌ No | ✅ `execaSync` | ❌ No | ✅ Sync by default | ✅ `spawnSync` |
|
|
35
|
+
| **Async Iteration** | ✅ `for await (chunk of $.stream())` | ❌ No | ❌ No | ❌ No | ❌ No | ❌ No |
|
|
36
|
+
| **EventEmitter Pattern** | ✅ `.on('data', ...)` | ❌ No | 🟡 Limited events | ❌ No | ❌ No | 🟡 Child process events |
|
|
37
|
+
| **Mixed Patterns** | ✅ Events + await/sync | ❌ No | ❌ No | ❌ No | ❌ No | ❌ No |
|
|
38
|
+
| **Bun.$ Compatibility** | ✅ `.text()` method support | ✅ Native API | ❌ No | ❌ No | ❌ No | ❌ No |
|
|
39
|
+
| **Shell Injection Protection** | ✅ Auto-quoting | ✅ Built-in | ✅ Safe by default | ✅ Safe by default | 🟡 Manual escaping | ✅ Safe by default |
|
|
40
|
+
| **Cross-platform** | ✅ macOS/Linux/Windows | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes | ✅ **Specialized** cross-platform |
|
|
41
|
+
| **Performance** | ⚡ Fast (Bun optimized) | ⚡ Very fast | 🐌 Moderate | 🐌 Slow | 🐌 Moderate | ⚡ Fast |
|
|
42
|
+
| **Memory Efficiency** | ✅ Streaming prevents buildup | 🟡 Buffers in memory | 🟡 Buffers in memory | 🟡 Buffers in memory | 🟡 Buffers in memory | 🟡 Buffers in memory |
|
|
43
|
+
| **Error Handling** | ✅ Configurable (`set -e`/`set +e`, non-zero OK by default) | ✅ Throws on error | ✅ Throws on error | ✅ Throws on error | ✅ Configurable | ❌ Basic (exit codes) |
|
|
44
|
+
| **Shell Settings** | ✅ `set -e`/`set +e` equivalent | ❌ No | ❌ No | ❌ No | 🟡 Limited (`set()`) | ❌ No |
|
|
45
|
+
| **Stdout Support** | ✅ Real-time streaming + events | ✅ Shell redirection + buffered | ✅ Node.js streams + interleaved | ✅ Readable streams + `.pipe.stdout` | ✅ Direct output | ✅ Inherited/buffered |
|
|
46
|
+
| **Stderr Support** | ✅ Real-time streaming + events | ✅ Redirection + `.quiet()` access | ✅ Streams + interleaved output | ✅ Readable streams + `.pipe.stderr` | ✅ Error output | ✅ Inherited/buffered |
|
|
47
|
+
| **Stdin Support** | ✅ string/Buffer/inherit/ignore | ✅ Pipe operations | ✅ Input/output streams | ✅ Basic stdin | 🟡 Basic | ✅ Full stdio support |
|
|
48
|
+
| **Built-in Commands** | ✅ **18 commands**: cat, ls, mkdir, rm, mv, cp, touch, basename, dirname, seq, yes + all Bun.$ commands | ✅ echo, cd, etc. | ❌ Uses system | ❌ Uses system | ✅ **20+ commands**: cat, ls, mkdir, rm, mv, cp, etc. | ❌ Uses system |
|
|
49
|
+
| **Virtual Commands Engine** | ✅ **Revolutionary**: Register JavaScript functions as shell commands with full pipeline support | ❌ No extensibility | ❌ No custom commands | ❌ No custom commands | ❌ No custom commands | ❌ No custom commands |
|
|
50
|
+
| **Pipeline/Piping Support** | ✅ **Advanced**: System + Built-ins + Virtual + Mixed + `.pipe()` method | ✅ Standard shell piping | ✅ Programmatic `.pipe()` + multi-destination | ✅ Shell piping + `.pipe()` method | ✅ Shell piping + `.to()` method | ❌ No piping |
|
|
51
|
+
| **Bundle Size** | 📦 **~20KB gzipped** | 🎯 0KB (built-in) | 📦 ~400KB+ (packagephobia) | 📦 ~50KB+ (estimated) | 📦 ~15KB gzipped | 📦 ~2KB gzipped |
|
|
52
|
+
| **Signal Handling** | ✅ **Advanced SIGINT/SIGTERM forwarding** with cleanup | 🟡 Basic | 🟡 Basic | 🟡 Basic | 🟡 Basic | ✅ **Excellent** cross-platform |
|
|
53
|
+
| **Process Management** | ✅ **Robust child process lifecycle** with proper termination | ❌ Basic | ✅ Good | 🟡 Limited | 🟡 Limited | ✅ **Excellent** spawn wrapper |
|
|
54
|
+
| **Debug Tracing** | ✅ **Comprehensive VERBOSE logging** for CI/debugging | ❌ No | 🟡 Limited | ❌ No | 🟡 Basic | ❌ No |
|
|
55
|
+
| **Test Coverage** | ✅ **410 tests, 909 assertions** | 🟡 Good coverage | ✅ Excellent | 🟡 Good | ✅ Good | ✅ Good |
|
|
56
|
+
| **CI Reliability** | ✅ **Platform-specific handling** (macOS/Ubuntu) | 🟡 Basic | ✅ Good | 🟡 Basic | ✅ Good | ✅ **Excellent** |
|
|
57
|
+
| **Documentation** | ✅ **Comprehensive examples + guides** | ✅ Good | ✅ Excellent | 🟡 Limited | ✅ Good | 🟡 Basic |
|
|
58
|
+
| **TypeScript** | 🔄 Coming soon | ✅ Built-in | ✅ Full support | ✅ Full support | 🟡 Community types | ✅ Built-in |
|
|
59
|
+
| **License** | ✅ **Unlicense (Public Domain)** | 🟡 MIT (+ LGPL dependencies) | 🟡 MIT | 🟡 Apache 2.0 | 🟡 BSD-3-Clause | 🟡 MIT |
|
|
60
|
+
|
|
61
|
+
**📊 Popularity (Weekly Downloads 2024):** [cross-spawn](https://www.npmjs.com/package/cross-spawn): 102M+ • [execa](https://www.npmjs.com/package/execa): 98M+ • [ShellJS](https://www.npmjs.com/package/shelljs): 9M+ • [zx](https://www.npmjs.com/package/zx): Growing fast
|
|
51
62
|
|
|
52
63
|
### Why Choose command-stream?
|
|
53
64
|
|
|
@@ -60,7 +71,9 @@ A modern $ shell utility library with streaming, async iteration, and EventEmitt
|
|
|
60
71
|
- **🐚 Shell Replacement**: Dynamic error handling with `set -e`/`set +e` equivalents for .sh file replacement
|
|
61
72
|
- **⚡ Bun Optimized**: Designed for Bun with Node.js fallback compatibility
|
|
62
73
|
- **💾 Memory Efficient**: Streaming prevents large buffer accumulation
|
|
63
|
-
- **🛡️ Production Ready**:
|
|
74
|
+
- **🛡️ Production Ready**: **410 tests, 909 assertions** with comprehensive coverage including CI reliability
|
|
75
|
+
- **🎯 Advanced Signal Handling**: Robust SIGINT/SIGTERM forwarding with proper child process cleanup
|
|
76
|
+
- **🔍 Debug-Friendly**: Comprehensive VERBOSE tracing for CI debugging and troubleshooting
|
|
64
77
|
|
|
65
78
|
## Built-in Commands (🚀 NEW!)
|
|
66
79
|
|
|
@@ -140,6 +153,42 @@ console.log(result.stdout);
|
|
|
140
153
|
console.log(result.code); // exit code
|
|
141
154
|
```
|
|
142
155
|
|
|
156
|
+
### Custom Options with $({ options }) Syntax (NEW!)
|
|
157
|
+
|
|
158
|
+
```javascript
|
|
159
|
+
import { $ } from 'command-stream';
|
|
160
|
+
|
|
161
|
+
// Create a $ with custom options
|
|
162
|
+
const $silent = $({ mirror: false, capture: true });
|
|
163
|
+
const result = await $silent`echo "quiet operation"`;
|
|
164
|
+
|
|
165
|
+
// Options for stdin handling
|
|
166
|
+
const $withInput = $({ stdin: 'input data\n' });
|
|
167
|
+
await $withInput`cat`; // Pipes the input to cat
|
|
168
|
+
|
|
169
|
+
// Custom environment variables
|
|
170
|
+
const $withEnv = $({ env: { ...process.env, MY_VAR: 'value' } });
|
|
171
|
+
await $withEnv`printenv MY_VAR`; // Prints: value
|
|
172
|
+
|
|
173
|
+
// Custom working directory
|
|
174
|
+
const $inTmp = $({ cwd: '/tmp' });
|
|
175
|
+
await $inTmp`pwd`; // Prints: /tmp
|
|
176
|
+
|
|
177
|
+
// Combine multiple options
|
|
178
|
+
const $custom = $({
|
|
179
|
+
stdin: 'test data',
|
|
180
|
+
mirror: false,
|
|
181
|
+
capture: true,
|
|
182
|
+
cwd: '/tmp'
|
|
183
|
+
});
|
|
184
|
+
await $custom`cat > output.txt`; // Writes to /tmp/output.txt silently
|
|
185
|
+
|
|
186
|
+
// Reusable configurations
|
|
187
|
+
const $prod = $({ env: { NODE_ENV: 'production' }, capture: true });
|
|
188
|
+
await $prod`npm start`;
|
|
189
|
+
await $prod`npm test`;
|
|
190
|
+
```
|
|
191
|
+
|
|
143
192
|
### Execution Control (NEW!)
|
|
144
193
|
|
|
145
194
|
```javascript
|
|
@@ -660,7 +709,8 @@ The enhanced `$` function returns a `ProcessRunner` instance that extends `Event
|
|
|
660
709
|
- `env: object` - Environment variables
|
|
661
710
|
|
|
662
711
|
**Override defaults:**
|
|
663
|
-
- Use
|
|
712
|
+
- Use `$({ options })` syntax for one-off configurations with template literals
|
|
713
|
+
- Use `sh(command, options)` for one-off overrides with string commands
|
|
664
714
|
- Use `create(defaultOptions)` to create custom `$` with different defaults
|
|
665
715
|
|
|
666
716
|
### Shell Settings API
|
|
@@ -676,6 +726,38 @@ Control shell behavior like bash `set`/`unset` commands:
|
|
|
676
726
|
- `unset(option)`: Disable shell option
|
|
677
727
|
- `shell.settings()`: Get current settings object
|
|
678
728
|
|
|
729
|
+
#### Error Handling Modes
|
|
730
|
+
|
|
731
|
+
```javascript
|
|
732
|
+
import { $, shell } from 'command-stream';
|
|
733
|
+
|
|
734
|
+
// ✅ Default behavior: Commands don't throw on non-zero exit
|
|
735
|
+
const result = await $`ls nonexistent-file`; // Won't throw
|
|
736
|
+
console.log(result.code); // → 2 (non-zero, but no exception)
|
|
737
|
+
|
|
738
|
+
// ✅ Enable errexit: Commands throw on non-zero exit
|
|
739
|
+
shell.errexit(true);
|
|
740
|
+
try {
|
|
741
|
+
await $`ls nonexistent-file`; // Throws error
|
|
742
|
+
} catch (error) {
|
|
743
|
+
console.log('Command failed:', error.code); // → 2
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// ✅ Disable errexit: Back to non-throwing behavior
|
|
747
|
+
shell.errexit(false);
|
|
748
|
+
await $`ls nonexistent-file`; // Won't throw, returns result with code 2
|
|
749
|
+
|
|
750
|
+
// ✅ One-time override without changing global settings
|
|
751
|
+
try {
|
|
752
|
+
const result = await $`ls nonexistent-file`;
|
|
753
|
+
if (result.code !== 0) {
|
|
754
|
+
throw new Error(`Command failed with code ${result.code}`);
|
|
755
|
+
}
|
|
756
|
+
} catch (error) {
|
|
757
|
+
console.log('Manual error handling');
|
|
758
|
+
}
|
|
759
|
+
```
|
|
760
|
+
|
|
679
761
|
### Virtual Commands API
|
|
680
762
|
|
|
681
763
|
Control and extend the command system with custom JavaScript functions:
|
|
@@ -690,6 +772,54 @@ Control and extend the command system with custom JavaScript functions:
|
|
|
690
772
|
- `enableVirtualCommands()`: Enable virtual command processing
|
|
691
773
|
- `disableVirtualCommands()`: Disable virtual commands (use system commands only)
|
|
692
774
|
|
|
775
|
+
#### Advanced Virtual Command Features
|
|
776
|
+
|
|
777
|
+
```javascript
|
|
778
|
+
import { $, register } from 'command-stream';
|
|
779
|
+
|
|
780
|
+
// ✅ Cancellation support with AbortController
|
|
781
|
+
register('cancellable', async function* (args, stdin, options) {
|
|
782
|
+
for (let i = 0; i < 10; i++) {
|
|
783
|
+
if (options.signal?.aborted) {
|
|
784
|
+
break; // Proper cancellation handling
|
|
785
|
+
}
|
|
786
|
+
yield `Count: ${i}\n`;
|
|
787
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
788
|
+
}
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
// ✅ Access to all process options
|
|
792
|
+
register('debug-info', async (args, stdin, options) => {
|
|
793
|
+
return {
|
|
794
|
+
stdout: JSON.stringify({
|
|
795
|
+
args,
|
|
796
|
+
cwd: options.cwd,
|
|
797
|
+
env: Object.keys(options.env || {}),
|
|
798
|
+
stdinLength: stdin.length,
|
|
799
|
+
mirror: options.mirror,
|
|
800
|
+
capture: options.capture
|
|
801
|
+
}, null, 2),
|
|
802
|
+
code: 0
|
|
803
|
+
};
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
// ✅ Error handling and non-zero exit codes
|
|
807
|
+
register('maybe-fail', async (args) => {
|
|
808
|
+
if (Math.random() > 0.5) {
|
|
809
|
+
return {
|
|
810
|
+
stdout: 'Success!\n',
|
|
811
|
+
code: 0
|
|
812
|
+
};
|
|
813
|
+
} else {
|
|
814
|
+
return {
|
|
815
|
+
stdout: '',
|
|
816
|
+
stderr: 'Random failure occurred\n',
|
|
817
|
+
code: 1
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
});
|
|
821
|
+
```
|
|
822
|
+
|
|
693
823
|
#### Handler Function Signature
|
|
694
824
|
|
|
695
825
|
```javascript
|
|
@@ -771,10 +901,172 @@ const result4 = await $`echo "pipe test"`.pipe($`cat`);
|
|
|
771
901
|
const text4 = await result4.text(); // "pipe test\n"
|
|
772
902
|
```
|
|
773
903
|
|
|
904
|
+
## Signal Handling (CTRL+C Support)
|
|
905
|
+
|
|
906
|
+
The library provides **advanced CTRL+C handling** that properly manages signals across different scenarios:
|
|
907
|
+
|
|
908
|
+
### How It Works
|
|
909
|
+
|
|
910
|
+
1. **Smart Signal Forwarding**: CTRL+C is forwarded **only when child processes are active**
|
|
911
|
+
2. **User Handler Preservation**: When no children are running, your custom SIGINT handlers work normally
|
|
912
|
+
3. **Process Groups**: Child processes use detached spawning for proper signal isolation
|
|
913
|
+
4. **TTY Mode Support**: Raw TTY mode is properly managed and restored on interruption
|
|
914
|
+
5. **Graceful Termination**: Uses SIGTERM → SIGKILL escalation for robust process cleanup
|
|
915
|
+
6. **Exit Code Standards**: Proper signal exit codes (130 for SIGINT, 143 for SIGTERM)
|
|
916
|
+
|
|
917
|
+
### Advanced Signal Behavior
|
|
918
|
+
|
|
919
|
+
```javascript
|
|
920
|
+
// ✅ Smart signal handling - only interferes when necessary
|
|
921
|
+
import { $ } from 'command-stream';
|
|
922
|
+
|
|
923
|
+
// Case 1: No children active - your handlers work normally
|
|
924
|
+
process.on('SIGINT', () => {
|
|
925
|
+
console.log('My custom handler runs!');
|
|
926
|
+
process.exit(42); // Custom exit code
|
|
927
|
+
});
|
|
928
|
+
// Press CTRL+C → Your handler runs, exits with code 42
|
|
929
|
+
|
|
930
|
+
// Case 2: Children active - automatic forwarding
|
|
931
|
+
await $`ping 8.8.8.8`; // Press CTRL+C → Forwards to ping, exits with code 130
|
|
932
|
+
|
|
933
|
+
// Case 3: Multiple processes - all interrupted
|
|
934
|
+
await Promise.all([
|
|
935
|
+
$`sleep 100`,
|
|
936
|
+
$`ping google.com`
|
|
937
|
+
]); // Press CTRL+C → All processes terminated, exits with code 130
|
|
938
|
+
```
|
|
939
|
+
|
|
940
|
+
### Examples
|
|
941
|
+
|
|
942
|
+
```javascript
|
|
943
|
+
// Long-running command that can be interrupted with CTRL+C
|
|
944
|
+
try {
|
|
945
|
+
await $`ping 8.8.8.8`; // Press CTRL+C to stop
|
|
946
|
+
} catch (error) {
|
|
947
|
+
console.log('Command interrupted:', error.code); // Exit code 130 (SIGINT)
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
// Multiple concurrent processes - CTRL+C stops all
|
|
951
|
+
try {
|
|
952
|
+
await Promise.all([
|
|
953
|
+
$`sleep 100`,
|
|
954
|
+
$`ping google.com`,
|
|
955
|
+
$`tail -f /var/log/system.log`
|
|
956
|
+
]);
|
|
957
|
+
} catch (error) {
|
|
958
|
+
// All processes are terminated when you press CTRL+C
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// Works with streaming patterns too
|
|
962
|
+
try {
|
|
963
|
+
for await (const chunk of $`ping 8.8.8.8`.stream()) {
|
|
964
|
+
console.log(chunk);
|
|
965
|
+
// Press CTRL+C to break the loop and stop the process
|
|
966
|
+
}
|
|
967
|
+
} catch (error) {
|
|
968
|
+
console.log('Streaming interrupted');
|
|
969
|
+
}
|
|
970
|
+
```
|
|
971
|
+
|
|
972
|
+
### Signal Handling Behavior
|
|
973
|
+
|
|
974
|
+
- **🎯 Smart Detection**: Only forwards CTRL+C when child processes are active
|
|
975
|
+
- **🛡️ Non-Interference**: Preserves user SIGINT handlers when no children running
|
|
976
|
+
- **⚡ Interactive Commands**: Commands like `vim`, `less`, `top` work with their own signal handling
|
|
977
|
+
- **🔄 Process Groups**: Detached spawning ensures proper signal isolation
|
|
978
|
+
- **🧹 TTY Cleanup**: Raw terminal mode properly restored on interruption
|
|
979
|
+
- **📊 Standard Exit Codes**:
|
|
980
|
+
- `130` - SIGINT interruption (CTRL+C)
|
|
981
|
+
- `143` - SIGTERM termination (programmatic kill)
|
|
982
|
+
- `137` - SIGKILL force termination
|
|
983
|
+
|
|
984
|
+
### Command Resolution Priority
|
|
985
|
+
|
|
986
|
+
```javascript
|
|
987
|
+
// Understanding how commands are resolved:
|
|
988
|
+
|
|
989
|
+
// 1. Virtual Commands (highest priority)
|
|
990
|
+
register('echo', () => ({ stdout: 'virtual!\n', code: 0 }));
|
|
991
|
+
await $`echo test`; // → "virtual!"
|
|
992
|
+
|
|
993
|
+
// 2. Built-in Commands (if no virtual match)
|
|
994
|
+
unregister('echo');
|
|
995
|
+
await $`echo test`; // → Uses built-in echo
|
|
996
|
+
|
|
997
|
+
// 3. System Commands (if no built-in/virtual match)
|
|
998
|
+
await $`unknown-command`; // → Uses system PATH lookup
|
|
999
|
+
|
|
1000
|
+
// 4. Virtual Bypass (special case)
|
|
1001
|
+
await $({ stdin: 'data' })`sleep 1`; // Bypasses virtual sleep, uses system sleep
|
|
1002
|
+
```
|
|
1003
|
+
|
|
1004
|
+
## Execution Patterns Deep Dive
|
|
1005
|
+
|
|
1006
|
+
### When to Use Different Patterns
|
|
1007
|
+
|
|
1008
|
+
```javascript
|
|
1009
|
+
import { $ } from 'command-stream';
|
|
1010
|
+
|
|
1011
|
+
// ✅ Use await for simple command execution
|
|
1012
|
+
const result = await $`ls -la`;
|
|
1013
|
+
|
|
1014
|
+
// ✅ Use .sync() when you need blocking execution with events
|
|
1015
|
+
const syncCmd = $`build-script`
|
|
1016
|
+
.on('stdout', chunk => updateProgress(chunk))
|
|
1017
|
+
.sync(); // Events fire after completion
|
|
1018
|
+
|
|
1019
|
+
// ✅ Use .start() for non-blocking execution with real-time events
|
|
1020
|
+
const asyncCmd = $`long-running-server`
|
|
1021
|
+
.on('stdout', chunk => logOutput(chunk))
|
|
1022
|
+
.start(); // Events fire in real-time
|
|
1023
|
+
|
|
1024
|
+
// ✅ Use .stream() for processing large outputs efficiently
|
|
1025
|
+
for await (const chunk of $`generate-big-file`.stream()) {
|
|
1026
|
+
processChunkInRealTime(chunk);
|
|
1027
|
+
} // Memory efficient - processes chunks as they arrive
|
|
1028
|
+
|
|
1029
|
+
// ✅ Use EventEmitter pattern for complex workflows
|
|
1030
|
+
$`deployment-script`
|
|
1031
|
+
.on('stdout', chunk => {
|
|
1032
|
+
if (chunk.toString().includes('ERROR')) {
|
|
1033
|
+
handleError(chunk);
|
|
1034
|
+
}
|
|
1035
|
+
})
|
|
1036
|
+
.on('stderr', chunk => logError(chunk))
|
|
1037
|
+
.on('end', result => {
|
|
1038
|
+
if (result.code === 0) {
|
|
1039
|
+
notifySuccess();
|
|
1040
|
+
}
|
|
1041
|
+
})
|
|
1042
|
+
.start();
|
|
1043
|
+
```
|
|
1044
|
+
|
|
1045
|
+
### Performance Considerations
|
|
1046
|
+
|
|
1047
|
+
```javascript
|
|
1048
|
+
// 🚀 Memory Efficient: For large outputs, use streaming
|
|
1049
|
+
for await (const chunk of $`cat huge-file.log`.stream()) {
|
|
1050
|
+
processChunk(chunk); // Processes incrementally
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
// 🐌 Memory Inefficient: Buffers entire output in memory
|
|
1054
|
+
const result = await $`cat huge-file.log`;
|
|
1055
|
+
processFile(result.stdout); // Loads everything into memory
|
|
1056
|
+
|
|
1057
|
+
// ⚡ Fastest: Sync execution for small, quick commands
|
|
1058
|
+
const quickResult = $`pwd`.sync();
|
|
1059
|
+
|
|
1060
|
+
// 🔄 Best for UX: Async with events for long-running commands
|
|
1061
|
+
$`npm install`
|
|
1062
|
+
.on('stdout', showProgress)
|
|
1063
|
+
.start();
|
|
1064
|
+
```
|
|
1065
|
+
|
|
774
1066
|
## Testing
|
|
775
1067
|
|
|
776
1068
|
```bash
|
|
777
|
-
# Run comprehensive test suite (
|
|
1069
|
+
# Run comprehensive test suite (270+ tests)
|
|
778
1070
|
bun test
|
|
779
1071
|
|
|
780
1072
|
# Run tests with coverage report
|
|
@@ -782,9 +1074,10 @@ bun test --coverage
|
|
|
782
1074
|
|
|
783
1075
|
# Run specific test categories
|
|
784
1076
|
npm run test:features # Feature comparison tests
|
|
785
|
-
npm run test:builtin # Built-in commands tests
|
|
1077
|
+
npm run test:builtin # Built-in commands tests
|
|
786
1078
|
npm run test:pipe # .pipe() method tests
|
|
787
1079
|
npm run test:sync # Synchronous execution tests
|
|
1080
|
+
npm run test:signal # CTRL+C signal handling tests
|
|
788
1081
|
```
|
|
789
1082
|
|
|
790
1083
|
## Requirements
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "command-stream",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Modern $ shell utility library with streaming, async iteration, and EventEmitter support, optimized for Bun runtime",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "
|
|
6
|
+
"main": "src/$.mjs",
|
|
7
7
|
"exports": {
|
|
8
|
-
".": "
|
|
8
|
+
".": "./src/$.mjs"
|
|
9
9
|
},
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"node": ">=20.0.0"
|
|
44
44
|
},
|
|
45
45
|
"files": [
|
|
46
|
-
"
|
|
46
|
+
"src/",
|
|
47
47
|
"README.md",
|
|
48
48
|
"LICENSE"
|
|
49
49
|
]
|