command-stream 0.3.0 → 0.3.1
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/$.mjs +95 -675
- package/README.md +4 -1
- package/package.json +1 -1
package/$.mjs
CHANGED
|
@@ -625,7 +625,37 @@ class ProcessRunner extends StreamEmitter {
|
|
|
625
625
|
start(options = {}) {
|
|
626
626
|
const mode = options.mode || 'async';
|
|
627
627
|
|
|
628
|
-
trace('ProcessRunner', () => `start ENTER | ${JSON.stringify({ mode, options }, null, 2)}`);
|
|
628
|
+
trace('ProcessRunner', () => `start ENTER | ${JSON.stringify({ mode, options, started: this.started }, null, 2)}`);
|
|
629
|
+
|
|
630
|
+
// Merge new options with existing options before starting
|
|
631
|
+
if (Object.keys(options).length > 0 && !this.started) {
|
|
632
|
+
trace('ProcessRunner', () => `BRANCH: options => MERGE | ${JSON.stringify({
|
|
633
|
+
oldOptions: this.options,
|
|
634
|
+
newOptions: options
|
|
635
|
+
}, null, 2)}`);
|
|
636
|
+
|
|
637
|
+
// Create a new options object merging the current ones with the new ones
|
|
638
|
+
this.options = { ...this.options, ...options };
|
|
639
|
+
|
|
640
|
+
// Reinitialize chunks based on updated capture option
|
|
641
|
+
if ('capture' in options) {
|
|
642
|
+
trace('ProcessRunner', () => `BRANCH: capture => REINIT_CHUNKS | ${JSON.stringify({
|
|
643
|
+
capture: this.options.capture
|
|
644
|
+
}, null, 2)}`);
|
|
645
|
+
|
|
646
|
+
this.outChunks = this.options.capture ? [] : null;
|
|
647
|
+
this.errChunks = this.options.capture ? [] : null;
|
|
648
|
+
this.inChunks = this.options.capture && this.options.stdin === 'inherit' ? [] :
|
|
649
|
+
this.options.capture && (typeof this.options.stdin === 'string' || Buffer.isBuffer(this.options.stdin)) ?
|
|
650
|
+
[Buffer.from(this.options.stdin)] : [];
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
trace('ProcessRunner', () => `OPTIONS_MERGED | ${JSON.stringify({
|
|
654
|
+
finalOptions: this.options
|
|
655
|
+
}, null, 2)}`);
|
|
656
|
+
} else if (Object.keys(options).length > 0 && this.started) {
|
|
657
|
+
trace('ProcessRunner', () => `BRANCH: options => IGNORED_ALREADY_STARTED | ${JSON.stringify({}, null, 2)}`);
|
|
658
|
+
}
|
|
629
659
|
|
|
630
660
|
if (mode === 'sync') {
|
|
631
661
|
trace('ProcessRunner', () => `BRANCH: mode => sync | ${JSON.stringify({}, null, 2)}`);
|
|
@@ -646,6 +676,12 @@ class ProcessRunner extends StreamEmitter {
|
|
|
646
676
|
return this.start({ mode: 'async' });
|
|
647
677
|
}
|
|
648
678
|
|
|
679
|
+
// Alias for start() method
|
|
680
|
+
run(options = {}) {
|
|
681
|
+
trace('ProcessRunner', () => `run ENTER | ${JSON.stringify({ options }, null, 2)}`);
|
|
682
|
+
return this.start(options);
|
|
683
|
+
}
|
|
684
|
+
|
|
649
685
|
async _startAsync() {
|
|
650
686
|
if (this.started) return this.promise;
|
|
651
687
|
if (this.promise) return this.promise;
|
|
@@ -694,7 +730,7 @@ class ProcessRunner extends StreamEmitter {
|
|
|
694
730
|
isVirtual: true,
|
|
695
731
|
args: parsed.args
|
|
696
732
|
}, null, 2)}`);
|
|
697
|
-
return await this._runVirtual(parsed.cmd, parsed.args);
|
|
733
|
+
return await this._runVirtual(parsed.cmd, parsed.args, this.spec.command);
|
|
698
734
|
}
|
|
699
735
|
}
|
|
700
736
|
}
|
|
@@ -791,8 +827,8 @@ class ProcessRunner extends StreamEmitter {
|
|
|
791
827
|
|
|
792
828
|
const resultData = {
|
|
793
829
|
code: code ?? 0, // Default to 0 if exit code is null/undefined
|
|
794
|
-
stdout: this.options.capture ? Buffer.concat(this.outChunks).toString('utf8') : undefined,
|
|
795
|
-
stderr: this.options.capture ? Buffer.concat(this.errChunks).toString('utf8') : undefined,
|
|
830
|
+
stdout: this.options.capture ? (this.outChunks && this.outChunks.length > 0 ? Buffer.concat(this.outChunks).toString('utf8') : '') : undefined,
|
|
831
|
+
stderr: this.options.capture ? (this.errChunks && this.errChunks.length > 0 ? Buffer.concat(this.errChunks).toString('utf8') : '') : undefined,
|
|
796
832
|
stdin: this.options.capture && this.inChunks ? Buffer.concat(this.inChunks).toString('utf8') : undefined,
|
|
797
833
|
child: this.child
|
|
798
834
|
};
|
|
@@ -927,8 +963,8 @@ class ProcessRunner extends StreamEmitter {
|
|
|
927
963
|
return { type: 'pipeline', commands };
|
|
928
964
|
}
|
|
929
965
|
|
|
930
|
-
async _runVirtual(cmd, args) {
|
|
931
|
-
trace('ProcessRunner', () => `_runVirtual ENTER | ${JSON.stringify({ cmd, args }, null, 2)}`);
|
|
966
|
+
async _runVirtual(cmd, args, originalCommand = null) {
|
|
967
|
+
trace('ProcessRunner', () => `_runVirtual ENTER | ${JSON.stringify({ cmd, args, originalCommand }, null, 2)}`);
|
|
932
968
|
|
|
933
969
|
const handler = virtualCommands.get(cmd);
|
|
934
970
|
if (!handler) {
|
|
@@ -955,10 +991,10 @@ class ProcessRunner extends StreamEmitter {
|
|
|
955
991
|
|
|
956
992
|
// Shell tracing for virtual commands
|
|
957
993
|
if (globalShellSettings.xtrace) {
|
|
958
|
-
console.log(`+ ${cmd} ${argValues.join(' ')}`);
|
|
994
|
+
console.log(`+ ${originalCommand || `${cmd} ${argValues.join(' ')}`}`);
|
|
959
995
|
}
|
|
960
996
|
if (globalShellSettings.verbose) {
|
|
961
|
-
console.log(`${cmd} ${argValues.join(' ')}`);
|
|
997
|
+
console.log(`${originalCommand || `${cmd} ${argValues.join(' ')}`}`);
|
|
962
998
|
}
|
|
963
999
|
|
|
964
1000
|
let result;
|
|
@@ -1031,11 +1067,11 @@ class ProcessRunner extends StreamEmitter {
|
|
|
1031
1067
|
result = await handler({ args: argValues, stdin: stdinData, ...this.options });
|
|
1032
1068
|
|
|
1033
1069
|
result = {
|
|
1070
|
+
...result,
|
|
1034
1071
|
code: result.code ?? 0,
|
|
1035
1072
|
stdout: this.options.capture ? (result.stdout ?? '') : undefined,
|
|
1036
1073
|
stderr: this.options.capture ? (result.stderr ?? '') : undefined,
|
|
1037
|
-
stdin: this.options.capture ? stdinData : undefined
|
|
1038
|
-
...result
|
|
1074
|
+
stdin: this.options.capture ? stdinData : undefined
|
|
1039
1075
|
};
|
|
1040
1076
|
|
|
1041
1077
|
// Mirror and emit output
|
|
@@ -1097,6 +1133,7 @@ class ProcessRunner extends StreamEmitter {
|
|
|
1097
1133
|
this.emit('exit', result.code);
|
|
1098
1134
|
|
|
1099
1135
|
if (globalShellSettings.errexit) {
|
|
1136
|
+
error.result = result;
|
|
1100
1137
|
throw error;
|
|
1101
1138
|
}
|
|
1102
1139
|
|
|
@@ -1758,11 +1795,11 @@ class ProcessRunner extends StreamEmitter {
|
|
|
1758
1795
|
// Regular async function
|
|
1759
1796
|
result = await handler({ args: argValues, stdin: currentInput, ...this.options });
|
|
1760
1797
|
result = {
|
|
1798
|
+
...result,
|
|
1761
1799
|
code: result.code ?? 0,
|
|
1762
1800
|
stdout: this.options.capture ? (result.stdout ?? '') : undefined,
|
|
1763
1801
|
stderr: this.options.capture ? (result.stderr ?? '') : undefined,
|
|
1764
|
-
stdin: this.options.capture ? currentInput : undefined
|
|
1765
|
-
...result
|
|
1802
|
+
stdin: this.options.capture ? currentInput : undefined
|
|
1766
1803
|
};
|
|
1767
1804
|
}
|
|
1768
1805
|
|
|
@@ -2614,673 +2651,56 @@ function disableVirtualCommands() {
|
|
|
2614
2651
|
return virtualCommandsEnabled;
|
|
2615
2652
|
}
|
|
2616
2653
|
|
|
2654
|
+
// Import virtual commands
|
|
2655
|
+
import cdCommand from './commands/$.cd.mjs';
|
|
2656
|
+
import pwdCommand from './commands/$.pwd.mjs';
|
|
2657
|
+
import echoCommand from './commands/$.echo.mjs';
|
|
2658
|
+
import sleepCommand from './commands/$.sleep.mjs';
|
|
2659
|
+
import trueCommand from './commands/$.true.mjs';
|
|
2660
|
+
import falseCommand from './commands/$.false.mjs';
|
|
2661
|
+
import createWhichCommand from './commands/$.which.mjs';
|
|
2662
|
+
import createExitCommand from './commands/$.exit.mjs';
|
|
2663
|
+
import envCommand from './commands/$.env.mjs';
|
|
2664
|
+
import catCommand from './commands/$.cat.mjs';
|
|
2665
|
+
import lsCommand from './commands/$.ls.mjs';
|
|
2666
|
+
import mkdirCommand from './commands/$.mkdir.mjs';
|
|
2667
|
+
import rmCommand from './commands/$.rm.mjs';
|
|
2668
|
+
import mvCommand from './commands/$.mv.mjs';
|
|
2669
|
+
import cpCommand from './commands/$.cp.mjs';
|
|
2670
|
+
import touchCommand from './commands/$.touch.mjs';
|
|
2671
|
+
import basenameCommand from './commands/$.basename.mjs';
|
|
2672
|
+
import dirnameCommand from './commands/$.dirname.mjs';
|
|
2673
|
+
import yesCommand from './commands/$.yes.mjs';
|
|
2674
|
+
import seqCommand from './commands/$.seq.mjs';
|
|
2675
|
+
import testCommand from './commands/$.test.mjs';
|
|
2676
|
+
|
|
2617
2677
|
// Built-in commands that match Bun.$ functionality
|
|
2618
2678
|
function registerBuiltins() {
|
|
2619
|
-
//
|
|
2620
|
-
register('cd',
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
register('
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
});
|
|
2642
|
-
|
|
2643
|
-
// echo - print arguments
|
|
2644
|
-
register('echo', async ({ args }) => {
|
|
2645
|
-
trace('VirtualCommand', () => `echo: processing | ${JSON.stringify({ argsCount: args.length }, null, 2)}`);
|
|
2646
|
-
|
|
2647
|
-
let output = args.join(' ');
|
|
2648
|
-
if (args.includes('-n')) {
|
|
2649
|
-
// Don't add newline
|
|
2650
|
-
trace('VirtualCommand', () => `BRANCH: echo => NO_NEWLINE | ${JSON.stringify({}, null, 2)}`);
|
|
2651
|
-
output = args.filter(arg => arg !== '-n').join(' ');
|
|
2652
|
-
} else {
|
|
2653
|
-
output += '\n';
|
|
2654
|
-
}
|
|
2655
|
-
return VirtualUtils.success(output);
|
|
2656
|
-
});
|
|
2657
|
-
|
|
2658
|
-
// sleep - wait for specified time
|
|
2659
|
-
register('sleep', async ({ args }) => {
|
|
2660
|
-
const seconds = parseFloat(args[0] || 0);
|
|
2661
|
-
trace('VirtualCommand', () => `sleep: starting | ${JSON.stringify({ seconds }, null, 2)}`);
|
|
2662
|
-
|
|
2663
|
-
if (isNaN(seconds) || seconds < 0) {
|
|
2664
|
-
trace('VirtualCommand', () => `sleep: invalid interval | ${JSON.stringify({ input: args[0] }, null, 2)}`);
|
|
2665
|
-
return { stderr: 'sleep: invalid time interval', code: 1 };
|
|
2666
|
-
}
|
|
2667
|
-
|
|
2668
|
-
await new Promise(resolve => setTimeout(resolve, seconds * 1000));
|
|
2669
|
-
trace('VirtualCommand', () => `sleep: completed | ${JSON.stringify({ seconds }, null, 2)}`);
|
|
2670
|
-
return { stdout: '', code: 0 };
|
|
2671
|
-
});
|
|
2672
|
-
|
|
2673
|
-
// true - always succeed
|
|
2674
|
-
register('true', async () => {
|
|
2675
|
-
return VirtualUtils.success();
|
|
2676
|
-
});
|
|
2677
|
-
|
|
2678
|
-
// false - always fail
|
|
2679
|
-
register('false', async () => {
|
|
2680
|
-
return { stdout: '', code: 1 };
|
|
2681
|
-
});
|
|
2682
|
-
|
|
2683
|
-
// which - locate command
|
|
2684
|
-
register('which', async ({ args }) => {
|
|
2685
|
-
const argError = VirtualUtils.validateArgs(args, 1, 'which');
|
|
2686
|
-
if (argError) return argError;
|
|
2687
|
-
|
|
2688
|
-
const cmd = args[0];
|
|
2689
|
-
|
|
2690
|
-
if (virtualCommands.has(cmd)) {
|
|
2691
|
-
return VirtualUtils.success(`${cmd}: shell builtin\n`);
|
|
2692
|
-
}
|
|
2693
|
-
|
|
2694
|
-
const paths = (process.env.PATH || '').split(process.platform === 'win32' ? ';' : ':');
|
|
2695
|
-
const extensions = process.platform === 'win32' ? ['', '.exe', '.cmd', '.bat'] : [''];
|
|
2696
|
-
|
|
2697
|
-
for (const pathDir of paths) {
|
|
2698
|
-
for (const ext of extensions) {
|
|
2699
|
-
const fullPath = path.join(pathDir, cmd + ext);
|
|
2700
|
-
try {
|
|
2701
|
-
if (fs.statSync(fullPath).isFile()) {
|
|
2702
|
-
return VirtualUtils.success(fullPath + '\n');
|
|
2703
|
-
}
|
|
2704
|
-
} catch { }
|
|
2705
|
-
}
|
|
2706
|
-
}
|
|
2707
|
-
|
|
2708
|
-
return VirtualUtils.error(`which: no ${cmd} in PATH`);
|
|
2709
|
-
});
|
|
2710
|
-
|
|
2711
|
-
// exit - exit with code
|
|
2712
|
-
register('exit', async ({ args }) => {
|
|
2713
|
-
const code = parseInt(args[0] || 0);
|
|
2714
|
-
if (globalShellSettings.errexit || code !== 0) {
|
|
2715
|
-
// For virtual commands, we simulate exit by returning the code
|
|
2716
|
-
return { stdout: '', code };
|
|
2717
|
-
}
|
|
2718
|
-
return { stdout: '', code: 0 };
|
|
2719
|
-
});
|
|
2720
|
-
|
|
2721
|
-
// env - print environment variables
|
|
2722
|
-
register('env', async ({ args, stdin, env }) => {
|
|
2723
|
-
if (args.length === 0) {
|
|
2724
|
-
// Use custom env if provided, otherwise use process.env
|
|
2725
|
-
const envVars = env || process.env;
|
|
2726
|
-
const output = Object.entries(envVars)
|
|
2727
|
-
.map(([key, value]) => `${key}=${value}`)
|
|
2728
|
-
.join('\n') + '\n';
|
|
2729
|
-
return { stdout: output, code: 0 };
|
|
2730
|
-
}
|
|
2731
|
-
|
|
2732
|
-
// TODO: Support env VAR=value command syntax
|
|
2733
|
-
return { stderr: 'env: command execution not yet supported', code: 1 };
|
|
2734
|
-
});
|
|
2735
|
-
|
|
2736
|
-
// cat - read and display file contents
|
|
2737
|
-
register('cat', async ({ args, stdin, cwd }) => {
|
|
2738
|
-
if (args.length === 0) {
|
|
2739
|
-
// Read from stdin if no files specified
|
|
2740
|
-
return { stdout: stdin || '', code: 0 };
|
|
2741
|
-
}
|
|
2742
|
-
|
|
2743
|
-
try {
|
|
2744
|
-
let output = '';
|
|
2745
|
-
|
|
2746
|
-
for (const filename of args) {
|
|
2747
|
-
if (filename === '-n') continue; // Line numbering (basic support)
|
|
2748
|
-
|
|
2749
|
-
try {
|
|
2750
|
-
// Resolve path relative to cwd if provided
|
|
2751
|
-
const basePath = cwd || process.cwd();
|
|
2752
|
-
const fullPath = path.isAbsolute(filename) ? filename : path.join(basePath, filename);
|
|
2753
|
-
|
|
2754
|
-
const content = fs.readFileSync(fullPath, 'utf8');
|
|
2755
|
-
output += content;
|
|
2756
|
-
} catch (error) {
|
|
2757
|
-
const errorMsg = error.code === 'ENOENT' ? 'No such file or directory' : error.message;
|
|
2758
|
-
return {
|
|
2759
|
-
stderr: `cat: ${filename}: ${errorMsg}`,
|
|
2760
|
-
stdout: output,
|
|
2761
|
-
code: 1
|
|
2762
|
-
};
|
|
2763
|
-
}
|
|
2764
|
-
}
|
|
2765
|
-
|
|
2766
|
-
return { stdout: output, code: 0 };
|
|
2767
|
-
} catch (error) {
|
|
2768
|
-
return { stderr: `cat: ${error.message}`, code: 1 };
|
|
2769
|
-
}
|
|
2770
|
-
});
|
|
2771
|
-
|
|
2772
|
-
// ls - list directory contents
|
|
2773
|
-
register('ls', async ({ args, stdin, cwd }) => {
|
|
2774
|
-
try {
|
|
2775
|
-
|
|
2776
|
-
const flags = args.filter(arg => arg.startsWith('-'));
|
|
2777
|
-
const paths = args.filter(arg => !arg.startsWith('-'));
|
|
2778
|
-
const isLongFormat = flags.includes('-l');
|
|
2779
|
-
const showAll = flags.includes('-a');
|
|
2780
|
-
const showAlmostAll = flags.includes('-A');
|
|
2781
|
-
|
|
2782
|
-
// Default to current directory if no paths specified
|
|
2783
|
-
const targetPaths = paths.length > 0 ? paths : ['.'];
|
|
2784
|
-
|
|
2785
|
-
let output = '';
|
|
2786
|
-
|
|
2787
|
-
for (const targetPath of targetPaths) {
|
|
2788
|
-
// Resolve path relative to cwd if provided
|
|
2789
|
-
const basePath = cwd || process.cwd();
|
|
2790
|
-
const fullPath = path.isAbsolute(targetPath) ? targetPath : path.join(basePath, targetPath);
|
|
2791
|
-
|
|
2792
|
-
try {
|
|
2793
|
-
const stat = fs.statSync(fullPath);
|
|
2794
|
-
|
|
2795
|
-
if (stat.isFile()) {
|
|
2796
|
-
// Just show the file name if it's a file
|
|
2797
|
-
output += path.basename(targetPath) + '\n';
|
|
2798
|
-
} else if (stat.isDirectory()) {
|
|
2799
|
-
const entries = fs.readdirSync(fullPath);
|
|
2800
|
-
|
|
2801
|
-
// Filter hidden files unless -a or -A is specified
|
|
2802
|
-
let filteredEntries = entries;
|
|
2803
|
-
if (!showAll && !showAlmostAll) {
|
|
2804
|
-
filteredEntries = entries.filter(entry => !entry.startsWith('.'));
|
|
2805
|
-
} else if (showAlmostAll) {
|
|
2806
|
-
filteredEntries = entries.filter(entry => entry !== '.' && entry !== '..');
|
|
2807
|
-
}
|
|
2808
|
-
|
|
2809
|
-
if (isLongFormat) {
|
|
2810
|
-
for (const entry of filteredEntries) {
|
|
2811
|
-
const entryPath = path.join(fullPath, entry);
|
|
2812
|
-
try {
|
|
2813
|
-
const entryStat = fs.statSync(entryPath);
|
|
2814
|
-
const isDir = entryStat.isDirectory();
|
|
2815
|
-
const permissions = isDir ? 'drwxr-xr-x' : '-rw-r--r--';
|
|
2816
|
-
const size = entryStat.size.toString().padStart(8);
|
|
2817
|
-
const date = entryStat.mtime.toISOString().slice(0, 16).replace('T', ' ');
|
|
2818
|
-
output += `${permissions} 1 user group ${size} ${date} ${entry}\n`;
|
|
2819
|
-
} catch {
|
|
2820
|
-
output += `?????????? 1 user group ? ??? ?? ??:?? ${entry}\n`;
|
|
2821
|
-
}
|
|
2822
|
-
}
|
|
2823
|
-
} else {
|
|
2824
|
-
output += filteredEntries.join('\n') + (filteredEntries.length > 0 ? '\n' : '');
|
|
2825
|
-
}
|
|
2826
|
-
}
|
|
2827
|
-
} catch (error) {
|
|
2828
|
-
return {
|
|
2829
|
-
stderr: `ls: cannot access '${targetPath}': ${error.message}`,
|
|
2830
|
-
code: 2
|
|
2831
|
-
};
|
|
2832
|
-
}
|
|
2833
|
-
}
|
|
2834
|
-
|
|
2835
|
-
return { stdout: output, code: 0 };
|
|
2836
|
-
} catch (error) {
|
|
2837
|
-
return { stderr: `ls: ${error.message}`, code: 1 };
|
|
2838
|
-
}
|
|
2839
|
-
});
|
|
2840
|
-
|
|
2841
|
-
register('mkdir', async ({ args, stdin, cwd }) => {
|
|
2842
|
-
const argError = VirtualUtils.validateArgs(args, 1, 'mkdir');
|
|
2843
|
-
if (argError) return argError;
|
|
2844
|
-
|
|
2845
|
-
try {
|
|
2846
|
-
|
|
2847
|
-
const flags = args.filter(arg => arg.startsWith('-'));
|
|
2848
|
-
const dirs = args.filter(arg => !arg.startsWith('-'));
|
|
2849
|
-
const recursive = flags.includes('-p');
|
|
2850
|
-
|
|
2851
|
-
for (const dir of dirs) {
|
|
2852
|
-
try {
|
|
2853
|
-
const basePath = cwd || process.cwd();
|
|
2854
|
-
const fullPath = path.isAbsolute(dir) ? dir : path.join(basePath, dir);
|
|
2855
|
-
|
|
2856
|
-
if (recursive) {
|
|
2857
|
-
fs.mkdirSync(fullPath, { recursive: true });
|
|
2858
|
-
} else {
|
|
2859
|
-
fs.mkdirSync(fullPath);
|
|
2860
|
-
}
|
|
2861
|
-
} catch (error) {
|
|
2862
|
-
return {
|
|
2863
|
-
stderr: `mkdir: cannot create directory '${dir}': ${error.message}`,
|
|
2864
|
-
code: 1
|
|
2865
|
-
};
|
|
2866
|
-
}
|
|
2867
|
-
}
|
|
2868
|
-
|
|
2869
|
-
return { stdout: '', code: 0 };
|
|
2870
|
-
} catch (error) {
|
|
2871
|
-
return { stderr: `mkdir: ${error.message}`, code: 1 };
|
|
2872
|
-
}
|
|
2873
|
-
});
|
|
2874
|
-
|
|
2875
|
-
// rm - remove files and directories
|
|
2876
|
-
register('rm', async ({ args, stdin, cwd }) => {
|
|
2877
|
-
const argError = VirtualUtils.validateArgs(args, 1, 'rm');
|
|
2878
|
-
if (argError) return argError;
|
|
2879
|
-
|
|
2880
|
-
try {
|
|
2881
|
-
|
|
2882
|
-
const flags = args.filter(arg => arg.startsWith('-'));
|
|
2883
|
-
const targets = args.filter(arg => !arg.startsWith('-'));
|
|
2884
|
-
const recursive = flags.includes('-r') || flags.includes('-R');
|
|
2885
|
-
const force = flags.includes('-f');
|
|
2886
|
-
|
|
2887
|
-
for (const target of targets) {
|
|
2888
|
-
try {
|
|
2889
|
-
const basePath = cwd || process.cwd();
|
|
2890
|
-
const fullPath = path.isAbsolute(target) ? target : path.join(basePath, target);
|
|
2891
|
-
|
|
2892
|
-
const stat = fs.statSync(fullPath);
|
|
2893
|
-
|
|
2894
|
-
if (stat.isDirectory()) {
|
|
2895
|
-
if (!recursive) {
|
|
2896
|
-
return {
|
|
2897
|
-
stderr: `rm: cannot remove '${target}': Is a directory`,
|
|
2898
|
-
code: 1
|
|
2899
|
-
};
|
|
2900
|
-
}
|
|
2901
|
-
fs.rmSync(fullPath, { recursive: true, force });
|
|
2902
|
-
} else {
|
|
2903
|
-
fs.unlinkSync(fullPath);
|
|
2904
|
-
}
|
|
2905
|
-
} catch (error) {
|
|
2906
|
-
if (!force) {
|
|
2907
|
-
return {
|
|
2908
|
-
stderr: `rm: cannot remove '${target}': ${error.message}`,
|
|
2909
|
-
code: 1
|
|
2910
|
-
};
|
|
2911
|
-
}
|
|
2912
|
-
}
|
|
2913
|
-
}
|
|
2914
|
-
|
|
2915
|
-
return { stdout: '', code: 0 };
|
|
2916
|
-
} catch (error) {
|
|
2917
|
-
return { stderr: `rm: ${error.message}`, code: 1 };
|
|
2918
|
-
}
|
|
2919
|
-
});
|
|
2920
|
-
|
|
2921
|
-
// mv - move/rename files and directories
|
|
2922
|
-
register('mv', async ({ args, stdin, cwd }) => {
|
|
2923
|
-
const argError = VirtualUtils.validateArgs(args, 2, 'mv');
|
|
2924
|
-
if (argError) return VirtualUtils.invalidArgumentError('mv', 'missing destination file operand');
|
|
2925
|
-
|
|
2926
|
-
try {
|
|
2927
|
-
|
|
2928
|
-
const basePath = cwd || process.cwd();
|
|
2929
|
-
|
|
2930
|
-
if (args.length === 2) {
|
|
2931
|
-
// Simple rename/move
|
|
2932
|
-
const [source, dest] = args;
|
|
2933
|
-
const sourcePath = path.isAbsolute(source) ? source : path.join(basePath, source);
|
|
2934
|
-
let destPath = path.isAbsolute(dest) ? dest : path.join(basePath, dest);
|
|
2935
|
-
|
|
2936
|
-
try {
|
|
2937
|
-
try {
|
|
2938
|
-
const destStat = fs.statSync(destPath);
|
|
2939
|
-
if (destStat.isDirectory()) {
|
|
2940
|
-
// Move file into the directory
|
|
2941
|
-
const fileName = path.basename(source);
|
|
2942
|
-
destPath = path.join(destPath, fileName);
|
|
2943
|
-
}
|
|
2944
|
-
} catch {
|
|
2945
|
-
// Destination doesn't exist, proceed with direct rename
|
|
2946
|
-
}
|
|
2947
|
-
|
|
2948
|
-
fs.renameSync(sourcePath, destPath);
|
|
2949
|
-
} catch (error) {
|
|
2950
|
-
return {
|
|
2951
|
-
stderr: `mv: cannot move '${source}' to '${dest}': ${error.message}`,
|
|
2952
|
-
code: 1
|
|
2953
|
-
};
|
|
2954
|
-
}
|
|
2955
|
-
} else {
|
|
2956
|
-
// Multiple sources to directory
|
|
2957
|
-
const sources = args.slice(0, -1);
|
|
2958
|
-
const dest = args[args.length - 1];
|
|
2959
|
-
const destPath = path.isAbsolute(dest) ? dest : path.join(basePath, dest);
|
|
2960
|
-
|
|
2961
|
-
try {
|
|
2962
|
-
const destStat = fs.statSync(destPath);
|
|
2963
|
-
if (!destStat.isDirectory()) {
|
|
2964
|
-
return {
|
|
2965
|
-
stderr: `mv: target '${dest}' is not a directory`,
|
|
2966
|
-
code: 1
|
|
2967
|
-
};
|
|
2968
|
-
}
|
|
2969
|
-
} catch {
|
|
2970
|
-
return {
|
|
2971
|
-
stderr: `mv: cannot access '${dest}': No such file or directory`,
|
|
2972
|
-
code: 1
|
|
2973
|
-
};
|
|
2974
|
-
}
|
|
2975
|
-
|
|
2976
|
-
for (const source of sources) {
|
|
2977
|
-
try {
|
|
2978
|
-
const sourcePath = path.isAbsolute(source) ? source : path.join(basePath, source);
|
|
2979
|
-
const fileName = path.basename(source);
|
|
2980
|
-
const newDestPath = path.join(destPath, fileName);
|
|
2981
|
-
fs.renameSync(sourcePath, newDestPath);
|
|
2982
|
-
} catch (error) {
|
|
2983
|
-
return {
|
|
2984
|
-
stderr: `mv: cannot move '${source}' to '${dest}': ${error.message}`,
|
|
2985
|
-
code: 1
|
|
2986
|
-
};
|
|
2987
|
-
}
|
|
2988
|
-
}
|
|
2989
|
-
}
|
|
2990
|
-
|
|
2991
|
-
return { stdout: '', code: 0 };
|
|
2992
|
-
} catch (error) {
|
|
2993
|
-
return { stderr: `mv: ${error.message}`, code: 1 };
|
|
2994
|
-
}
|
|
2995
|
-
});
|
|
2996
|
-
|
|
2997
|
-
// cp - copy files and directories
|
|
2998
|
-
register('cp', async ({ args, stdin, cwd }) => {
|
|
2999
|
-
const argError = VirtualUtils.validateArgs(args, 2, 'cp');
|
|
3000
|
-
if (argError) return VirtualUtils.invalidArgumentError('cp', 'missing destination file operand');
|
|
3001
|
-
|
|
3002
|
-
try {
|
|
3003
|
-
|
|
3004
|
-
const flags = args.filter(arg => arg.startsWith('-'));
|
|
3005
|
-
const paths = args.filter(arg => !arg.startsWith('-'));
|
|
3006
|
-
const recursive = flags.includes('-r') || flags.includes('-R');
|
|
3007
|
-
|
|
3008
|
-
const basePath = cwd || process.cwd();
|
|
3009
|
-
|
|
3010
|
-
if (paths.length === 2) {
|
|
3011
|
-
// Simple copy
|
|
3012
|
-
const [source, dest] = paths;
|
|
3013
|
-
const sourcePath = path.isAbsolute(source) ? source : path.join(basePath, source);
|
|
3014
|
-
const destPath = path.isAbsolute(dest) ? dest : path.join(basePath, dest);
|
|
3015
|
-
|
|
3016
|
-
try {
|
|
3017
|
-
const sourceStat = fs.statSync(sourcePath);
|
|
3018
|
-
|
|
3019
|
-
if (sourceStat.isDirectory()) {
|
|
3020
|
-
if (!recursive) {
|
|
3021
|
-
return {
|
|
3022
|
-
stderr: `cp: -r not specified; omitting directory '${source}'`,
|
|
3023
|
-
code: 1
|
|
3024
|
-
};
|
|
3025
|
-
}
|
|
3026
|
-
fs.cpSync(sourcePath, destPath, { recursive: true });
|
|
3027
|
-
} else {
|
|
3028
|
-
fs.copyFileSync(sourcePath, destPath);
|
|
3029
|
-
}
|
|
3030
|
-
} catch (error) {
|
|
3031
|
-
return {
|
|
3032
|
-
stderr: `cp: cannot copy '${source}' to '${dest}': ${error.message}`,
|
|
3033
|
-
code: 1
|
|
3034
|
-
};
|
|
3035
|
-
}
|
|
3036
|
-
} else {
|
|
3037
|
-
// Multiple sources to directory
|
|
3038
|
-
const sources = paths.slice(0, -1);
|
|
3039
|
-
const dest = paths[paths.length - 1];
|
|
3040
|
-
const destPath = path.isAbsolute(dest) ? dest : path.join(basePath, dest);
|
|
3041
|
-
|
|
3042
|
-
try {
|
|
3043
|
-
const destStat = fs.statSync(destPath);
|
|
3044
|
-
if (!destStat.isDirectory()) {
|
|
3045
|
-
return {
|
|
3046
|
-
stderr: `cp: target '${dest}' is not a directory`,
|
|
3047
|
-
code: 1
|
|
3048
|
-
};
|
|
3049
|
-
}
|
|
3050
|
-
} catch {
|
|
3051
|
-
return {
|
|
3052
|
-
stderr: `cp: cannot access '${dest}': No such file or directory`,
|
|
3053
|
-
code: 1
|
|
3054
|
-
};
|
|
3055
|
-
}
|
|
3056
|
-
|
|
3057
|
-
for (const source of sources) {
|
|
3058
|
-
try {
|
|
3059
|
-
const sourcePath = path.isAbsolute(source) ? source : path.join(basePath, source);
|
|
3060
|
-
const fileName = path.basename(source);
|
|
3061
|
-
const newDestPath = path.join(destPath, fileName);
|
|
3062
|
-
|
|
3063
|
-
const sourceStat = fs.statSync(sourcePath);
|
|
3064
|
-
if (sourceStat.isDirectory()) {
|
|
3065
|
-
if (!recursive) {
|
|
3066
|
-
return {
|
|
3067
|
-
stderr: `cp: -r not specified; omitting directory '${source}'`,
|
|
3068
|
-
code: 1
|
|
3069
|
-
};
|
|
3070
|
-
}
|
|
3071
|
-
fs.cpSync(sourcePath, newDestPath, { recursive: true });
|
|
3072
|
-
} else {
|
|
3073
|
-
fs.copyFileSync(sourcePath, newDestPath);
|
|
3074
|
-
}
|
|
3075
|
-
} catch (error) {
|
|
3076
|
-
return {
|
|
3077
|
-
stderr: `cp: cannot copy '${source}' to '${dest}': ${error.message}`,
|
|
3078
|
-
code: 1
|
|
3079
|
-
};
|
|
3080
|
-
}
|
|
3081
|
-
}
|
|
3082
|
-
}
|
|
3083
|
-
|
|
3084
|
-
return { stdout: '', code: 0 };
|
|
3085
|
-
} catch (error) {
|
|
3086
|
-
return { stderr: `cp: ${error.message}`, code: 1 };
|
|
3087
|
-
}
|
|
3088
|
-
});
|
|
3089
|
-
|
|
3090
|
-
// touch - create or update file timestamps
|
|
3091
|
-
register('touch', async ({ args, stdin, cwd }) => {
|
|
3092
|
-
const argError = VirtualUtils.validateArgs(args, 1, 'touch');
|
|
3093
|
-
if (argError) return VirtualUtils.missingOperandError('touch', 'touch: missing file operand');
|
|
3094
|
-
|
|
3095
|
-
try {
|
|
3096
|
-
|
|
3097
|
-
const basePath = cwd || process.cwd();
|
|
3098
|
-
|
|
3099
|
-
for (const file of args) {
|
|
3100
|
-
try {
|
|
3101
|
-
const fullPath = path.isAbsolute(file) ? file : path.join(basePath, file);
|
|
3102
|
-
|
|
3103
|
-
// Try to update timestamps if file exists
|
|
3104
|
-
try {
|
|
3105
|
-
const now = new Date();
|
|
3106
|
-
fs.utimesSync(fullPath, now, now);
|
|
3107
|
-
} catch {
|
|
3108
|
-
fs.writeFileSync(fullPath, '', { flag: 'w' });
|
|
3109
|
-
}
|
|
3110
|
-
} catch (error) {
|
|
3111
|
-
return {
|
|
3112
|
-
stderr: `touch: cannot touch '${file}': ${error.message}`,
|
|
3113
|
-
code: 1
|
|
3114
|
-
};
|
|
3115
|
-
}
|
|
3116
|
-
}
|
|
3117
|
-
|
|
3118
|
-
return { stdout: '', code: 0 };
|
|
3119
|
-
} catch (error) {
|
|
3120
|
-
return { stderr: `touch: ${error.message}`, code: 1 };
|
|
3121
|
-
}
|
|
3122
|
-
});
|
|
3123
|
-
|
|
3124
|
-
// basename - extract filename from path
|
|
3125
|
-
register('basename', async ({ args }) => {
|
|
3126
|
-
const argError = VirtualUtils.validateArgs(args, 1, 'basename');
|
|
3127
|
-
if (argError) return argError;
|
|
3128
|
-
|
|
3129
|
-
try {
|
|
3130
|
-
|
|
3131
|
-
const pathname = args[0];
|
|
3132
|
-
const suffix = args[1];
|
|
3133
|
-
|
|
3134
|
-
let result = path.basename(pathname);
|
|
3135
|
-
|
|
3136
|
-
// Remove suffix if provided
|
|
3137
|
-
if (suffix && result.endsWith(suffix)) {
|
|
3138
|
-
result = result.slice(0, -suffix.length);
|
|
3139
|
-
}
|
|
3140
|
-
|
|
3141
|
-
return { stdout: result + '\n', code: 0 };
|
|
3142
|
-
} catch (error) {
|
|
3143
|
-
return { stderr: `basename: ${error.message}`, code: 1 };
|
|
3144
|
-
}
|
|
3145
|
-
});
|
|
3146
|
-
|
|
3147
|
-
// dirname - extract directory from path
|
|
3148
|
-
register('dirname', async ({ args }) => {
|
|
3149
|
-
const argError = VirtualUtils.validateArgs(args, 1, 'dirname');
|
|
3150
|
-
if (argError) return argError;
|
|
3151
|
-
|
|
3152
|
-
try {
|
|
3153
|
-
|
|
3154
|
-
const pathname = args[0];
|
|
3155
|
-
const result = path.dirname(pathname);
|
|
3156
|
-
|
|
3157
|
-
return { stdout: result + '\n', code: 0 };
|
|
3158
|
-
} catch (error) {
|
|
3159
|
-
return { stderr: `dirname: ${error.message}`, code: 1 };
|
|
3160
|
-
}
|
|
3161
|
-
});
|
|
3162
|
-
|
|
3163
|
-
// yes - output a string repeatedly
|
|
3164
|
-
register('yes', async function* ({ args, stdin, isCancelled, signal, ...rest }) {
|
|
3165
|
-
const output = args.length > 0 ? args.join(' ') : 'y';
|
|
3166
|
-
trace('VirtualCommand', () => `yes: starting infinite generator | ${JSON.stringify({ output }, null, 2)}`);
|
|
3167
|
-
|
|
3168
|
-
// Generate infinite stream of the output
|
|
3169
|
-
while (true) {
|
|
3170
|
-
if (isCancelled && isCancelled()) {
|
|
3171
|
-
trace('VirtualCommand', () => 'yes: cancelled via function');
|
|
3172
|
-
return;
|
|
3173
|
-
}
|
|
3174
|
-
if (signal && signal.aborted) {
|
|
3175
|
-
trace('VirtualCommand', () => 'yes: cancelled via abort signal');
|
|
3176
|
-
return;
|
|
3177
|
-
}
|
|
3178
|
-
|
|
3179
|
-
yield output + '\n';
|
|
3180
|
-
|
|
3181
|
-
try {
|
|
3182
|
-
await new Promise((resolve, reject) => {
|
|
3183
|
-
const timeout = setTimeout(resolve, 0);
|
|
3184
|
-
|
|
3185
|
-
// Listen for abort signal if available
|
|
3186
|
-
if (signal) {
|
|
3187
|
-
const abortHandler = () => {
|
|
3188
|
-
clearTimeout(timeout);
|
|
3189
|
-
reject(new Error('Aborted'));
|
|
3190
|
-
};
|
|
3191
|
-
|
|
3192
|
-
if (signal.aborted) {
|
|
3193
|
-
abortHandler();
|
|
3194
|
-
} else {
|
|
3195
|
-
signal.addEventListener('abort', abortHandler, { once: true });
|
|
3196
|
-
}
|
|
3197
|
-
}
|
|
3198
|
-
});
|
|
3199
|
-
} catch (err) {
|
|
3200
|
-
// Aborted
|
|
3201
|
-
return;
|
|
3202
|
-
}
|
|
3203
|
-
}
|
|
3204
|
-
});
|
|
3205
|
-
|
|
3206
|
-
// seq - generate sequence of numbers
|
|
3207
|
-
register('seq', async ({ args }) => {
|
|
3208
|
-
const argError = VirtualUtils.validateArgs(args, 1, 'seq');
|
|
3209
|
-
if (argError) return argError;
|
|
3210
|
-
|
|
3211
|
-
try {
|
|
3212
|
-
let start, step, end;
|
|
3213
|
-
|
|
3214
|
-
if (args.length === 1) {
|
|
3215
|
-
start = 1;
|
|
3216
|
-
step = 1;
|
|
3217
|
-
end = parseInt(args[0]);
|
|
3218
|
-
} else if (args.length === 2) {
|
|
3219
|
-
start = parseInt(args[0]);
|
|
3220
|
-
step = 1;
|
|
3221
|
-
end = parseInt(args[1]);
|
|
3222
|
-
} else if (args.length === 3) {
|
|
3223
|
-
start = parseInt(args[0]);
|
|
3224
|
-
step = parseInt(args[1]);
|
|
3225
|
-
end = parseInt(args[2]);
|
|
3226
|
-
} else {
|
|
3227
|
-
return { stderr: 'seq: too many operands', code: 1 };
|
|
3228
|
-
}
|
|
3229
|
-
|
|
3230
|
-
if (isNaN(start) || isNaN(step) || isNaN(end)) {
|
|
3231
|
-
return { stderr: 'seq: invalid number', code: 1 };
|
|
3232
|
-
}
|
|
3233
|
-
|
|
3234
|
-
let output = '';
|
|
3235
|
-
if (step > 0) {
|
|
3236
|
-
for (let i = start; i <= end; i += step) {
|
|
3237
|
-
output += i + '\n';
|
|
3238
|
-
}
|
|
3239
|
-
} else if (step < 0) {
|
|
3240
|
-
for (let i = start; i >= end; i += step) {
|
|
3241
|
-
output += i + '\n';
|
|
3242
|
-
}
|
|
3243
|
-
} else {
|
|
3244
|
-
return { stderr: 'seq: invalid increment', code: 1 };
|
|
3245
|
-
}
|
|
3246
|
-
|
|
3247
|
-
return { stdout: output, code: 0 };
|
|
3248
|
-
} catch (error) {
|
|
3249
|
-
return { stderr: `seq: ${error.message}`, code: 1 };
|
|
3250
|
-
}
|
|
3251
|
-
});
|
|
3252
|
-
|
|
3253
|
-
// test - test file conditions (basic implementation)
|
|
3254
|
-
register('test', async ({ args }) => {
|
|
3255
|
-
if (args.length === 0) {
|
|
3256
|
-
return { stdout: '', code: 1 };
|
|
3257
|
-
}
|
|
3258
|
-
|
|
3259
|
-
// Very basic test implementation
|
|
3260
|
-
const arg = args[0];
|
|
3261
|
-
|
|
3262
|
-
try {
|
|
3263
|
-
if (arg === '-d' && args[1]) {
|
|
3264
|
-
// Test if directory
|
|
3265
|
-
const stat = fs.statSync(args[1]);
|
|
3266
|
-
return { stdout: '', code: stat.isDirectory() ? 0 : 1 };
|
|
3267
|
-
} else if (arg === '-f' && args[1]) {
|
|
3268
|
-
// Test if file
|
|
3269
|
-
const stat = fs.statSync(args[1]);
|
|
3270
|
-
return { stdout: '', code: stat.isFile() ? 0 : 1 };
|
|
3271
|
-
} else if (arg === '-e' && args[1]) {
|
|
3272
|
-
// Test if exists
|
|
3273
|
-
fs.statSync(args[1]);
|
|
3274
|
-
return { stdout: '', code: 0 };
|
|
3275
|
-
}
|
|
3276
|
-
} catch {
|
|
3277
|
-
return { stdout: '', code: 1 };
|
|
3278
|
-
}
|
|
3279
|
-
|
|
3280
|
-
return { stdout: '', code: 1 };
|
|
3281
|
-
});
|
|
2679
|
+
// Register all imported commands
|
|
2680
|
+
register('cd', cdCommand);
|
|
2681
|
+
register('pwd', pwdCommand);
|
|
2682
|
+
register('echo', echoCommand);
|
|
2683
|
+
register('sleep', sleepCommand);
|
|
2684
|
+
register('true', trueCommand);
|
|
2685
|
+
register('false', falseCommand);
|
|
2686
|
+
register('which', createWhichCommand(virtualCommands));
|
|
2687
|
+
register('exit', createExitCommand(globalShellSettings));
|
|
2688
|
+
register('env', envCommand);
|
|
2689
|
+
register('cat', catCommand);
|
|
2690
|
+
register('ls', lsCommand);
|
|
2691
|
+
register('mkdir', mkdirCommand);
|
|
2692
|
+
register('rm', rmCommand);
|
|
2693
|
+
register('mv', mvCommand);
|
|
2694
|
+
register('cp', cpCommand);
|
|
2695
|
+
register('touch', touchCommand);
|
|
2696
|
+
register('basename', basenameCommand);
|
|
2697
|
+
register('dirname', dirnameCommand);
|
|
2698
|
+
register('yes', yesCommand);
|
|
2699
|
+
register('seq', seqCommand);
|
|
2700
|
+
register('test', testCommand);
|
|
3282
2701
|
}
|
|
3283
2702
|
|
|
2703
|
+
|
|
3284
2704
|
// ANSI control character utilities
|
|
3285
2705
|
const AnsiUtils = {
|
|
3286
2706
|
stripAnsi(text) {
|
package/README.md
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
[](https://gitpod.io/#https://github.com/link-foundation/command-stream)
|
|
2
|
+
[](https://github.com/codespaces/new?hide_repo_select=true&ref=main&repo=link-foundation/command-stream)
|
|
3
|
+
|
|
1
4
|
# [command-$tream](https://github.com/link-foundation/command-stream)
|
|
2
5
|
|
|
3
6
|
$treamable commands executor
|
|
4
7
|
|
|
5
8
|
A modern $ shell utility library with streaming, async iteration, and EventEmitter support, optimized for Bun runtime.
|
|
6
9
|
|
|
7
|
-
<img width="
|
|
10
|
+
<img width="2752" height="1344" alt="ray-so-export" src="https://github.com/user-attachments/assets/b1656450-0a2a-43f5-917c-4f15c3ffccaa" />
|
|
8
11
|
|
|
9
12
|
## Features
|
|
10
13
|
|
package/package.json
CHANGED