Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ A modern $ shell utility library with streaming, async iteration, and EventEmitt
- ⚑ **Performance**: Memory-efficient streaming prevents large buffer accumulation
- 🎯 **Backward Compatible**: Existing `await $` syntax continues to work + Bun.$ `.text()` method
- πŸ›‘οΈ **Type Safe**: Full TypeScript support (coming soon)
- πŸ”§ **Built-in Commands**: 18 essential commands work identically across platforms
- πŸ”§ **Built-in Commands**: 19 essential commands work identically across platforms

## Comparison with Other Libraries

Expand All @@ -50,7 +50,7 @@ A modern $ shell utility library with streaming, async iteration, and EventEmitt
| **Stdout Support** | βœ… Real-time streaming + events | βœ… Node.js streams + interleaved | βœ… Inherited/buffered | βœ… Shell redirection + buffered | βœ… Direct output | βœ… Readable streams + `.pipe.stdout` |
| **Stderr Support** | βœ… Real-time streaming + events | βœ… Streams + interleaved output | βœ… Inherited/buffered | βœ… Redirection + `.quiet()` access | βœ… Error output | βœ… Readable streams + `.pipe.stderr` |
| **Stdin Support** | βœ… string/Buffer/inherit/ignore | βœ… Input/output streams | βœ… Full stdio support | βœ… Pipe operations | 🟑 Basic | βœ… Basic stdin |
| **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 |
| **Built-in Commands** | βœ… **19 commands**: cat, ls, mkdir, rm, mv, cp, touch, basename, dirname, seq, yes, tee + all Bun.$ commands | ❌ Uses system | ❌ Uses system | βœ… echo, cd, etc. | βœ… **20+ commands**: cat, ls, mkdir, rm, mv, cp, etc. | ❌ Uses system |
| **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 |
| **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 |
| **Bundle Size** | πŸ“¦ **~20KB gzipped** | πŸ“¦ ~400KB+ (packagephobia) | πŸ“¦ ~2KB gzipped | 🎯 0KB (built-in) | πŸ“¦ ~15KB gzipped | πŸ“¦ ~50KB+ (estimated) |
Expand All @@ -75,7 +75,7 @@ A modern $ shell utility library with streaming, async iteration, and EventEmitt
- **πŸ†“ Truly Free**: **Unlicense (Public Domain)** - No restrictions, no attribution required, use however you want
- **πŸš€ Revolutionary Virtual Commands**: **World's first** fully customizable virtual commands engine - register JavaScript functions as shell commands!
- **πŸ”— Advanced Pipeline System**: **Only library** where virtual commands work seamlessly in pipelines with built-ins and system commands
- **πŸ”§ Built-in Commands**: **18 essential commands** work identically across all platforms - no system dependencies!
- **πŸ”§ Built-in Commands**: **19 essential commands** work identically across all platforms - no system dependencies!
- **πŸ“‘ Real-time Processing**: Only library with true streaming and async iteration
- **πŸ”„ Flexible Patterns**: Multiple usage patterns (await, events, iteration, mixed)
- **🐚 Shell Replacement**: Dynamic error handling with `set -e`/`set +e` equivalents for .sh file replacement
Expand All @@ -87,7 +87,7 @@ A modern $ shell utility library with streaming, async iteration, and EventEmitt

## Built-in Commands (πŸš€ NEW!)

command-stream now includes **18 built-in commands** that work identically to their bash/sh counterparts, providing true cross-platform shell scripting without system dependencies:
command-stream now includes **19 built-in commands** that work identically to their bash/sh counterparts, providing true cross-platform shell scripting without system dependencies:

### πŸ“ **File System Commands**
- `cat` - Read and display file contents
Expand All @@ -103,6 +103,7 @@ command-stream now includes **18 built-in commands** that work identically to th
- `dirname` - Extract directory from path
- `seq` - Generate number sequences
- `yes` - Output string repeatedly (streaming)
- `tee` - Read from stdin and write to both stdout and files (supports `-a` append)

### ⚑ **System Commands**
- `cd` - Change directory
Expand Down Expand Up @@ -138,6 +139,7 @@ await $`rm -r project-backup`;

// Mix built-ins with pipelines and virtual commands
await $`seq 1 5 | cat > numbers.txt`;
await $`echo "Important data" | tee backup.txt | cat`; // Saves to file AND continues pipeline
await $`basename /path/to/file.txt .txt`; // β†’ "file"
```

Expand Down Expand Up @@ -1007,10 +1009,10 @@ async function streamingHandler({ args, stdin, abortSignal, cwd, env, options, i

### Built-in Commands

18 cross-platform commands that work identically everywhere:
19 cross-platform commands that work identically everywhere:

**File System**: `cat`, `ls`, `mkdir`, `rm`, `mv`, `cp`, `touch`
**Utilities**: `basename`, `dirname`, `seq`, `yes`
**Utilities**: `basename`, `dirname`, `seq`, `yes`, `tee`
**System**: `cd`, `pwd`, `echo`, `sleep`, `true`, `false`, `which`, `exit`, `env`, `test`

All built-in commands support:
Expand Down
123 changes: 123 additions & 0 deletions examples/tee-interactive-demo.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
#!/usr/bin/env bun

/**
* Interactive demo of the tee command implementation
* This shows how tee can be implemented in pure JavaScript and work in interactive mode
*/

import { $ } from '../src/$.mjs';

console.log('=== Tee Command Interactive Demo ===\n');

console.log('🎯 Issue #14: How `tee` command is implemented? Is it possible to reproduce it in pure js?\n');

console.log('βœ… Answer: YES! The tee command has been successfully implemented as a virtual command in pure JavaScript.\n');

console.log('=== Understanding the tee Command ===');
console.log('The Unix `tee` command reads from standard input and writes to both:');
console.log('1. Standard output (so data continues through pipelines)');
console.log('2. One or more files simultaneously\n');

console.log('Think of it like a "T" junction in plumbing - input flows to multiple outputs.\n');

console.log('=== Live Demonstration ===\n');

// Demo 1: Basic tee functionality
console.log('πŸ“ Demo 1: Basic tee functionality');
console.log('Command: echo "Hello World" | tee demo-output.txt');
const result1 = await $`echo "Hello World" | tee demo-output.txt`;
console.log(`πŸ“€ Stdout: "${result1.stdout.trim()}"`);
console.log(`πŸ“ File content: "${await $`cat demo-output.txt`.then(r => r.stdout.trim())}"`);
console.log('✨ Notice: Same content goes to both stdout AND file!\n');

// Demo 2: Multiple files
console.log('πŸ“ Demo 2: Multiple files simultaneously');
console.log('Command: echo "Multiple outputs" | tee file1.txt file2.txt file3.txt');
const result2 = await $`echo "Multiple outputs" | tee file1.txt file2.txt file3.txt`;
console.log(`πŸ“€ Stdout: "${result2.stdout.trim()}"`);
console.log('πŸ“ All files now contain:');
for (let i = 1; i <= 3; i++) {
const content = await $`cat file${i}.txt`.then(r => r.stdout.trim());
console.log(` file${i}.txt: "${content}"`);
}
console.log('✨ All files identical to stdout!\n');

// Demo 3: Append mode
console.log('πŸ“ Demo 3: Append mode (-a flag)');
console.log('Command: echo "First line" | tee append-demo.txt');
await $`echo "First line" | tee append-demo.txt`;
console.log('Command: echo "Second line" | tee -a append-demo.txt');
const result3 = await $`echo "Second line" | tee -a append-demo.txt`;
const appendContent = await $`cat append-demo.txt`.then(r => r.stdout);
console.log('πŸ“ Final file content:');
console.log(appendContent.split('\n').map(line => ` ${line}`).join('\n'));
console.log('✨ Second call appended instead of overwriting!\n');

// Demo 4: Pipeline compatibility
console.log('πŸ“ Demo 4: Pipeline compatibility');
console.log('Command: echo "pipeline data" | tee pipeline.txt | sort | tee sorted.txt');
const result4 = await $`echo "pipeline data" | tee pipeline.txt | sort | tee sorted.txt`;
console.log(`πŸ“€ Final output: "${result4.stdout.trim()}"`);
console.log(`πŸ“ pipeline.txt: "${await $`cat pipeline.txt`.then(r => r.stdout.trim())}"`);
console.log(`πŸ“ sorted.txt: "${await $`cat sorted.txt`.then(r => r.stdout.trim())}"`);
console.log('✨ Data flows through entire pipeline while being saved at each tee!\n');

// Demo 5: Interactive mode simulation
console.log('πŸ“ Demo 5: Interactive mode (simulated)');
console.log('In interactive mode, you would type input and tee would duplicate it to files.');
console.log('Here\'s a simulation with multi-line input:\n');

const interactiveInput = `Line 1: User input
Line 2: More data
Line 3: Final line`;

console.log('Simulating user typing:');
console.log(interactiveInput.split('\n').map(line => `> ${line}`).join('\n'));
console.log('\nCommand: tee interactive-output.txt (with simulated input)');

const result5 = await $({ stdin: interactiveInput })`tee interactive-output.txt`;
console.log('\nπŸ“€ Tee output to stdout:');
console.log(result5.stdout.split('\n').map(line => ` ${line}`).join('\n'));
console.log('\nπŸ“ File content:');
const interactiveFileContent = await $`cat interactive-output.txt`.then(r => r.stdout);
console.log(interactiveFileContent.split('\n').map(line => ` ${line}`).join('\n'));
console.log('✨ Interactive mode works perfectly!\n');

console.log('=== Implementation Details ===');
console.log('πŸ“š The virtual tee command is implemented in pure JavaScript:');
console.log(' β€’ File: src/commands/$.tee.mjs');
console.log(' β€’ Features: Append mode (-a), multiple files, error handling');
console.log(' β€’ Pipeline support: Full stdin/stdout compatibility');
console.log(' β€’ Interactive: Supports real-time input processing');
console.log(' β€’ Error handling: Graceful degradation on file write errors\n');

console.log('=== Key Behaviors ===');
console.log('1. βœ… Reads from stdin (pipeline or direct input)');
console.log('2. βœ… Writes to stdout (maintains pipeline flow)');
console.log('3. βœ… Writes to one or more files simultaneously');
console.log('4. βœ… Supports append mode with -a flag');
console.log('5. βœ… Handles errors gracefully (continues output on file errors)');
console.log('6. βœ… Works in interactive mode (real-time processing)');
console.log('7. βœ… Cross-platform (no system dependencies)\n');

console.log('=== Answer to Issue #14 ===');
console.log('πŸŽ‰ YES, the tee command can be reproduced in pure JavaScript!');
console.log('πŸ› οΈ It\'s now available as a built-in virtual command');
console.log('πŸ”„ It supports interactive mode through stdin handling');
console.log('πŸ“¦ No external dependencies - pure JavaScript implementation');
console.log('🌍 Works identically on all platforms (Windows, macOS, Linux)\n');

// Cleanup
console.log('🧹 Cleaning up demo files...');
try {
await $`rm -f demo-output.txt file1.txt file2.txt file3.txt append-demo.txt pipeline.txt sorted.txt interactive-output.txt`;
console.log('βœ… Cleanup completed!');
} catch (error) {
console.log('⚠️ Cleanup had issues, but that\'s okay');
}

console.log('\n=== Try it yourself! ===');
console.log('You can now use the tee command in command-stream:');
console.log(' import { $ } from "command-stream";');
console.log(' await $`echo "test" | tee output.txt`;');
console.log(' await $`seq 1 5 | tee numbers.txt | sort -r`;');
97 changes: 97 additions & 0 deletions examples/test-unix-tee.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#!/usr/bin/env bun

/**
* Understanding how Unix tee command works
*
* tee reads from stdin and writes to both stdout and files
* - By default, tee overwrites output files
* - With -a flag, tee appends to output files
* - tee can handle multiple output files
* - tee supports interactive mode (reads from stdin continuously)
*/

import { $ } from '../src/$.mjs';

console.log('=== Testing Unix tee behavior ===\n');

// Test 1: Basic tee with file output
console.log('Test 1: Basic tee functionality');
try {
const result = await $`echo "Hello World" | tee test-output.txt`;
console.log('stdout:', result.stdout);
console.log('stderr:', result.stderr);
console.log('code:', result.code);

// Check if file was created
const fileContent = await $`cat test-output.txt`.catch(() => ({ stdout: 'File not found' }));
console.log('File content:', fileContent.stdout);
} catch (error) {
console.log('Error:', error.message);
}

console.log('\n---\n');

// Test 2: Interactive tee (if available)
console.log('Test 2: Does tee support interactive mode?');
try {
// This should show that tee can work interactively
console.log('Testing if tee supports interactive input...');
console.log('(This would normally wait for user input)');

// Instead of true interactive test, let's see what happens with stdin
const interactiveTest = $({ stdin: "line 1\nline 2\nline 3\n" })`tee interactive-test.txt`;
const result = await interactiveTest;
console.log('Interactive result stdout:', result.stdout);

const fileContent = await $`cat interactive-test.txt`.catch(() => ({ stdout: 'File not found' }));
console.log('Interactive file content:', fileContent.stdout);
} catch (error) {
console.log('Interactive test failed:', error.message);
}

console.log('\n---\n');

// Test 3: Multiple output files
console.log('Test 3: Multiple output files');
try {
const result = await $`echo "Multiple files" | tee file1.txt file2.txt file3.txt`;
console.log('Multiple files stdout:', result.stdout);

console.log('Checking all files...');
const file1 = await $`cat file1.txt`.catch(() => ({ stdout: 'File not found' }));
const file2 = await $`cat file2.txt`.catch(() => ({ stdout: 'File not found' }));
const file3 = await $`cat file3.txt`.catch(() => ({ stdout: 'File not found' }));

console.log('file1.txt:', file1.stdout);
console.log('file2.txt:', file2.stdout);
console.log('file3.txt:', file3.stdout);
} catch (error) {
console.log('Multiple files test failed:', error.message);
}

console.log('\n---\n');

// Test 4: Append mode
console.log('Test 4: Append mode (-a flag)');
try {
// First write
await $`echo "First line" | tee append-test.txt`;

// Append
const result = await $`echo "Second line" | tee -a append-test.txt`;
console.log('Append mode stdout:', result.stdout);

const fileContent = await $`cat append-test.txt`.catch(() => ({ stdout: 'File not found' }));
console.log('Append file content:', fileContent.stdout);
} catch (error) {
console.log('Append test failed:', error.message);
}

// Cleanup
console.log('\nCleaning up test files...');
try {
await $`rm -f test-output.txt interactive-test.txt file1.txt file2.txt file3.txt append-test.txt`;
console.log('Cleanup completed.');
} catch (error) {
console.log('Cleanup failed:', error.message);
}
Loading