diff --git a/examples/advanced-typescript-features.ts b/examples/advanced-typescript-features.ts new file mode 100644 index 0000000..e0e263e --- /dev/null +++ b/examples/advanced-typescript-features.ts @@ -0,0 +1,318 @@ +/** + * Advanced TypeScript Features for command-stream + * Demonstrates unique type features that competitors don't have + */ + +import { + ProcessRunner, + CommandResult, + register, + VirtualCommandHandler, + StreamingOptions, + PipelineStep, + Pipeline +} from '../index'; + +// Advanced type definitions for unique features +type VirtualCommandRegistry = Map; +type StreamTransform = (input: T) => U | Promise; +type EventMap = Record; + +// Example 1: Type-safe virtual command registration with advanced generics +interface TypedVirtualCommand { + name: string; + description?: string; + handler: (...args: TArgs) => Promise | TReturn; +} + +// Register a math virtual command with full type safety +const mathCommand: TypedVirtualCommand<[number, string, number], CommandResult> = { + name: 'math', + description: 'Perform mathematical operations', + handler: async (a: number, op: string, b: number): Promise => { + let result: number; + switch (op) { + case '+': result = a + b; break; + case '-': result = a - b; break; + case '*': result = a * b; break; + case '/': result = a / b; break; + default: throw new Error(`Unknown operation: ${op}`); + } + + return { + code: 0, + stdout: result.toString(), + stderr: '', + stdin: '', + async text() { return this.stdout; } + }; + } +}; + +// Example 2: Advanced streaming with generic type transformations +interface StreamProcessor { + transform: StreamTransform; + filter?: (item: TInput) => boolean | Promise; + batch?: number; +} + +async function advancedStreamProcessing( + runner: ProcessRunner, + processor: StreamProcessor +): Promise { + const results: U[] = []; + let batch: T[] = []; + + for await (const chunk of runner.stream()) { + const item = chunk as unknown as T; + + // Apply filter if provided + if (processor.filter && !(await processor.filter(item))) { + continue; + } + + // Handle batching + if (processor.batch) { + batch.push(item); + if (batch.length >= processor.batch) { + const transformed = await Promise.all( + batch.map(processor.transform) + ); + results.push(...transformed); + batch = []; + } + } else { + const transformed = await processor.transform(item); + results.push(transformed); + } + } + + // Process remaining batch + if (batch.length > 0) { + const transformed = await Promise.all( + batch.map(processor.transform) + ); + results.push(...transformed); + } + + return results; +} + +// Example 3: Type-safe pipeline builder with inference +class TypeSafePipeline { + private steps: PipelineStep[] = []; + + constructor(private initialType: new() => TInput) {} + + pipe( + step: PipelineStep + ): TypeSafePipeline { + this.steps.push(step); + return this as any; + } + + map( + transform: (input: TOutput) => TNext | Promise + ): TypeSafePipeline { + this.steps.push(async (input: TOutput) => transform(input)); + return this as any; + } + + filter( + predicate: (input: TOutput) => boolean | Promise + ): TypeSafePipeline { + this.steps.push(async function* (input: AsyncIterable) { + for await (const item of input) { + if (await predicate(item)) { + yield item; + } + } + }); + return this; + } + + async execute(input: TInput): Promise { + let current: any = input; + for (const step of this.steps) { + current = await step(current); + } + return current; + } +} + +// Example 4: Advanced event system with discriminated unions +type CommandEvent = + | { type: 'start'; timestamp: Date; command: string } + | { type: 'data'; chunk: Buffer; size: number } + | { type: 'line'; content: string; lineNumber: number } + | { type: 'end'; result: CommandResult; duration: number } + | { type: 'error'; error: Error; context: string }; + +interface TypedEventEmitter { + on( + event: K, + listener: (...args: TEvents[K]) => void + ): this; + + emit( + event: K, + ...args: TEvents[K] + ): boolean; +} + +// Example 5: Conditional type system for command validation +type ValidCommand = T extends `${string}${infer Rest}` + ? Rest extends ` ${string}` | '' + ? T + : never + : never; + +type SafeCommand = ValidCommand<'echo hello'> | ValidCommand<'ls -la'> | ValidCommand<'pwd'>; + +// Example 6: Advanced streaming interface with backpressure +interface BackpressureStream { + readonly readable: boolean; + readonly highWaterMark: number; + readonly objectMode: boolean; + + read(size?: number): T | null; + on(event: 'readable', listener: () => void): this; + on(event: 'end', listener: () => void): this; + on(event: 'error', listener: (err: Error) => void): this; + + [Symbol.asyncIterator](): AsyncIterableIterator; +} + +// Example 7: Sophisticated error handling with typed error contexts +class CommandError extends Error { + constructor( + message: string, + public readonly code: number, + public readonly command: string, + public readonly stderr: string, + public readonly context: 'execution' | 'streaming' | 'virtual-command' + ) { + super(message); + this.name = 'CommandError'; + } +} + +interface ErrorRecoveryStrategy { + retries: number; + backoff: (attempt: number) => number; + fallback?: () => Promise; + shouldRetry: (error: Error) => boolean; +} + +async function executeWithRecovery( + operation: () => Promise, + strategy: ErrorRecoveryStrategy +): Promise { + let lastError: Error; + + for (let attempt = 0; attempt <= strategy.retries; attempt++) { + try { + return await operation(); + } catch (error) { + lastError = error as Error; + + if (!strategy.shouldRetry(error as Error) || attempt === strategy.retries) { + break; + } + + const delay = strategy.backoff(attempt); + await new Promise(resolve => setTimeout(resolve, delay)); + } + } + + if (strategy.fallback) { + return strategy.fallback(); + } + + throw lastError!; +} + +// Example 8: Real-world usage combining all features +export async function demonstrateAdvancedFeatures(): Promise { + // Register the typed math command + register('math', mathCommand.handler); + + // Create a type-safe pipeline for log processing + const logPipeline = new TypeSafePipeline(Buffer) + .map((buffer: Buffer) => buffer.toString()) + .filter((line: string) => line.includes('ERROR')) + .map((line: string) => ({ + timestamp: new Date(), + level: 'ERROR', + message: line.trim() + })); + + // Use advanced streaming with error recovery + const strategy: ErrorRecoveryStrategy = { + retries: 3, + backoff: (attempt) => Math.pow(2, attempt) * 1000, + shouldRetry: (error) => error.message.includes('temporary'), + fallback: async () => ({ + code: -1, + stdout: '', + stderr: 'Operation failed after retries', + stdin: '', + async text() { return this.stderr; } + }) + }; + + try { + // Execute commands with full type safety and error handling + const result = await executeWithRecovery( + async () => { + const mathResult = await import('../index').then(({ $ }) => $`math 10 + 5`); + return mathResult; + }, + strategy + ); + + console.log(`Math result: ${result.stdout}`); + + } catch (error) { + if (error instanceof CommandError) { + console.error(`Command failed in ${error.context}: ${error.message}`); + } + } +} + +// Example 9: Template literal types for command validation +type CommandTemplate = T extends `${infer Command} ${infer Args}` + ? { command: Command; args: Args } + : { command: T; args: '' }; + +function parseCommand(cmd: T): CommandTemplate { + const parts = cmd.split(' ', 2); + return { + command: parts[0], + args: parts.slice(1).join(' ') + } as CommandTemplate; +} + +// Example 10: Advanced type guards and narrowing +function isStreamableResult(result: any): result is AsyncIterable { + return result && typeof result[Symbol.asyncIterator] === 'function'; +} + +function isCommandResult(result: any): result is CommandResult { + return result && + typeof result.code === 'number' && + typeof result.stdout === 'string' && + typeof result.stderr === 'string'; +} + +// Export the demonstration +export { + mathCommand, + advancedStreamProcessing, + TypeSafePipeline, + CommandError, + executeWithRecovery, + parseCommand, + isStreamableResult, + isCommandResult +}; \ No newline at end of file diff --git a/examples/test-typescript-definitions.mjs b/examples/test-typescript-definitions.mjs new file mode 100644 index 0000000..e8d07af --- /dev/null +++ b/examples/test-typescript-definitions.mjs @@ -0,0 +1,213 @@ +/** + * Test TypeScript definitions for command-stream + * This file tests that our TypeScript definitions work correctly at runtime + */ + +import $ from '../src/$.mjs'; + +// Test basic command execution +async function testBasicCommands() { + console.log('Testing basic command execution...'); + + try { + const result = await $`echo "Hello TypeScript!"`; + console.log(`✅ Basic command: ${result.stdout.trim()}`); + console.log(` Exit code: ${result.code}`); + console.log(` Has text() method: ${typeof result.text === 'function'}`); + + if (typeof result.text === 'function') { + const textResult = await result.text(); + console.log(` text() result: ${textResult.trim()}`); + } else { + console.log(` ⚠️ text() method not implemented yet, using stdout directly`); + } + } catch (error) { + console.error('❌ Basic command failed:', error.message); + } +} + +// Test command with options +async function testCommandWithOptions() { + console.log('\nTesting command with options...'); + + try { + const quietCmd = $({ mirror: false, capture: true }); + const result = await quietCmd`pwd`; + console.log(`✅ Command with options: ${result.stdout.trim()}`); + } catch (error) { + console.error('❌ Command with options failed:', error.message); + } +} + +// Test streaming functionality +async function testStreaming() { + console.log('\nTesting streaming functionality...'); + + try { + const cmd = $`echo -e "line1\\nline2\\nline3"`; + let chunkCount = 0; + + for await (const chunk of cmd.stream()) { + chunkCount++; + console.log(`✅ Received chunk ${chunkCount}: ${chunk ? chunk.length : 'undefined'} bytes`); + if (chunkCount > 5) break; // Prevent infinite loop + } + + console.log(`✅ Streaming completed with ${chunkCount} chunks`); + + // Note: lines() method is not implemented yet, but would work like this: + // const lineCmd = $`echo -e "line1\\nline2\\nline3"`; + // for await (const line of lineCmd.lines()) { + // console.log(`Line: ${line}`); + // } + } catch (error) { + console.error('❌ Streaming failed:', error.message); + } +} + +// Test event handling +async function testEventHandling() { + console.log('\nTesting event handling...'); + + return new Promise((resolve, reject) => { + const cmd = $`echo "Event test"`; + let dataReceived = false; + let endReceived = false; + + cmd.on('data', (chunk) => { + dataReceived = true; + console.log(`✅ Data event: ${chunk ? chunk.length : 'undefined'} bytes`); + }); + + cmd.on('end', (result) => { + endReceived = true; + console.log(`✅ End event: code ${result.code}, stdout: "${result.stdout.trim()}"`); + + if (dataReceived && endReceived) { + resolve(); + } else { + reject(new Error('Not all events received')); + } + }); + + cmd.on('error', (error) => { + console.error('❌ Error event:', error.message); + reject(error); + }); + + // Trigger execution + cmd.start(); + }); +} + +// Test virtual commands +async function testVirtualCommands() { + console.log('\nTesting virtual commands...'); + + try { + // Test built-in virtual command + const result = await $`echo "Virtual command test"`; + console.log(`✅ Virtual echo command: ${result.stdout.trim()}`); + + // Test pwd virtual command + const pwdResult = await $`pwd`; + console.log(`✅ Virtual pwd command: ${pwdResult.stdout.trim()}`); + } catch (error) { + console.error('❌ Virtual commands failed:', error.message); + } +} + +// Test stream access +async function testStreamAccess() { + console.log('\nTesting stream access...'); + + try { + const cmd = $`echo "Stream access test"`; + + // Access streams before starting + console.log(`✅ stdout initially: ${cmd.stdout === null ? 'null' : 'available'}`); + console.log(`✅ stderr initially: ${cmd.stderr === null ? 'null' : 'available'}`); + console.log(`✅ stdin initially: ${cmd.stdin === null ? 'null' : 'available'}`); + + // Execute and get result + const result = await cmd; + console.log(`✅ Stream access result: ${result.stdout.trim()}`); + } catch (error) { + console.error('❌ Stream access failed:', error.message); + } +} + +// Test error handling +async function testErrorHandling() { + console.log('\nTesting error handling...'); + + try { + // This should fail + const result = await $`nonexistent-command-12345`; + // If we get here, the command didn't fail as expected + if (result.code !== 0) { + console.log(`✅ Error handling works: command failed with code ${result.code}`); + } else { + console.error('❌ Expected error but command succeeded'); + } + } catch (error) { + console.log(`✅ Error handling works: ${error.message}`); + } +} + +// Test interpolation +async function testInterpolation() { + console.log('\nTesting variable interpolation...'); + + try { + const name = "TypeScript"; + const result = await $`echo "Hello, ${name}!"`; + console.log(`✅ Interpolation: ${result.stdout.trim()}`); + + const command = "echo"; + const message = "Variable interpolation works"; + const result2 = await $`${command} "${message}"`; + console.log(`✅ Command interpolation: ${result2.stdout.trim()}`); + } catch (error) { + console.error('❌ Interpolation failed:', error.message); + } +} + +// Main test runner +async function runAllTests() { + console.log('🚀 Running TypeScript definitions tests for command-stream...\n'); + + try { + await testBasicCommands(); + await testCommandWithOptions(); + await testStreaming(); + await testEventHandling(); + await testVirtualCommands(); + await testStreamAccess(); + await testErrorHandling(); + await testInterpolation(); + + console.log('\n✅ All TypeScript definition tests passed!'); + console.log('🎉 TypeScript support is working correctly!'); + } catch (error) { + console.error('\n❌ Tests failed:', error.message); + process.exit(1); + } +} + +// Run tests if this file is executed directly +if (import.meta.url === `file://${process.argv[1]}`) { + runAllTests().catch(console.error); +} + +export { + testBasicCommands, + testCommandWithOptions, + testStreaming, + testEventHandling, + testVirtualCommands, + testStreamAccess, + testErrorHandling, + testInterpolation, + runAllTests +}; \ No newline at end of file diff --git a/examples/typescript-examples.ts b/examples/typescript-examples.ts new file mode 100644 index 0000000..07409dc --- /dev/null +++ b/examples/typescript-examples.ts @@ -0,0 +1,277 @@ +/** + * TypeScript Examples for command-stream + * Demonstrates full type safety and IDE autocomplete support + */ + +import $, { + ProcessRunner, + CommandResult, + register, + create, + quote, + raw, + configureAnsi +} from '../index'; + +// Example 1: Basic command execution with full typing +async function basicCommands(): Promise { + // Tagged template usage - returns ProcessRunner + const result: CommandResult = await $`echo "Hello, TypeScript!"`; + console.log(`Exit code: ${result.code}`); // number + console.log(`Output: ${result.stdout}`); // string + + // Command with interpolation - values are properly typed + const name = "TypeScript"; + const greeting: CommandResult = await $`echo "Hello, ${name}!"`; + console.log(await greeting.text()); // string +} + +// Example 2: Options-based configuration with type safety +async function configuredCommands(): Promise { + // Using $ with options - returns a new tagged template function + const quietCmd = $({ mirror: false, capture: true }); + const result: CommandResult = await quietCmd`pwd`; + + // Custom default options + const customCmd = create({ + cwd: '/tmp', + env: { NODE_ENV: 'test' }, + interactive: false + }); + await customCmd`ls -la`; +} + +// Example 3: Streaming with generic types +async function streamingExamples(): Promise { + const cmd: ProcessRunner = $`find . -name "*.ts"`; + + // Stream as buffers + for await (const chunk of cmd.stream()) { + // chunk is typed as Buffer + console.log(`Received ${chunk.length} bytes`); + } + + // Stream as lines + const lineCmd: ProcessRunner = $`cat package.json`; + for await (const line of lineCmd.lines()) { + // line is typed as string + console.log(`Line: ${line}`); + } +} + +// Example 4: Event-based processing with typed events +function eventDrivenProcessing(): void { + const cmd: ProcessRunner = $`tail -f /var/log/system.log`; + + // Typed event listeners + cmd.on('data', (chunk: Buffer) => { + console.log(`Data: ${chunk.toString()}`); + }); + + cmd.on('end', (result: CommandResult) => { + console.log(`Finished with code: ${result.code}`); + }); + + cmd.on('error', (error: Error) => { + console.error(`Error: ${error.message}`); + }); +} + +// Example 5: Virtual commands with type safety +async function virtualCommandsExample(): Promise { + // Register a typed virtual command + register('greet', async (args: string[]): Promise => { + const name = args[0] || 'World'; + return { + code: 0, + stdout: `Hello, ${name}!`, + stderr: '', + stdin: '', + async text() { return this.stdout; } + }; + }); + + // Use the virtual command + const result: CommandResult = await $`greet TypeScript`; + console.log(result.stdout); // "Hello, TypeScript!" +} + +// Example 6: Advanced streaming transformations +async function advancedStreaming(): Promise { + const cmd: ProcessRunner = $`ls -la`; + + // Custom stream transformation with types + const stream = cmd.stream({ + transform: (chunk: Buffer): string => chunk.toString().toUpperCase(), + encoding: 'utf8' + }); + + for await (const transformedChunk of stream) { + // transformedChunk is typed as string due to transform + console.log(`Transformed: ${transformedChunk}`); + } +} + +// Example 7: Pipeline composition (conceptual - shows intended API) +async function pipelineExample(): Promise { + // Type-safe pipeline composition + const result = await $`cat data.json` + .then(r => r.stdout) + .then(JSON.parse) + .then((data: any[]) => data.filter(item => item.active)) + .then(filtered => JSON.stringify(filtered, null, 2)); + + console.log(result); +} + +// Example 8: Error handling with proper types +async function errorHandling(): Promise { + try { + const result: CommandResult = await $`nonexistent-command`; + console.log(result.stdout); + } catch (error: unknown) { + if (error instanceof Error) { + console.error(`Command failed: ${error.message}`); + } + } +} + +// Example 9: Working with process streams directly +async function processStreams(): Promise { + const cmd: ProcessRunner = $`grep -n "error" /var/log/system.log`; + + // Access typed streams + const stdout = cmd.stdout; // Readable | null + const stderr = cmd.stderr; // Readable | null + const stdin = cmd.stdin; // Writable | null + + if (stdout) { + stdout.on('data', (chunk: Buffer) => { + console.log(`Found error: ${chunk.toString()}`); + }); + } +} + +// Example 10: Utility functions with proper typing +function utilityExamples(): void { + // Quote function with type safety + const userInput = "file with spaces.txt"; + const quotedInput: string = quote(userInput); + console.log(quotedInput); // 'file with spaces.txt' + + // Raw function for unescaped values + const rawCommand = raw("echo hello | wc -w"); + console.log(`${rawCommand}`); // echo hello | wc -w +} + +// Example 11: Configuration and ANSI handling +function configurationExample(): void { + // Configure ANSI processing with typed options + configureAnsi({ + enabled: true, + colors: true, + styles: false + }); + + // Global configuration + import('./index').then(({ set, unset }) => { + set('verbose'); // Enable verbose logging + unset('mirror'); // Disable output mirroring + }); +} + +// Example 12: Advanced async iteration patterns +async function asyncIterationPatterns(): Promise { + const cmd: ProcessRunner = $`ping -c 5 google.com`; + + // Collect all chunks + const chunks: Buffer[] = []; + for await (const chunk of cmd.stream()) { + chunks.push(chunk); + } + + // Process line by line with proper typing + const lines: string[] = []; + for await (const line of cmd.lines()) { + lines.push(line); + } + + console.log(`Collected ${chunks.length} chunks and ${lines.length} lines`); +} + +// Example 13: Combining multiple commands with type safety +async function multipleCommands(): Promise { + // Sequential execution + const ls: CommandResult = await $`ls`; + const wc: CommandResult = await $`echo ${ls.stdout} | wc -l`; + + console.log(`Directory has ${wc.stdout.trim()} items`); + + // Parallel execution with proper typing + const [date, uptime]: [CommandResult, CommandResult] = await Promise.all([ + $`date`, + $`uptime` + ]); + + console.log(`Current time: ${date.stdout.trim()}`); + console.log(`System uptime: ${uptime.stdout.trim()}`); +} + +// Example 14: Type-safe environment and working directory +async function environmentExample(): Promise { + // Custom environment with full typing + const envCmd = $({ + env: { + NODE_ENV: 'production', + DEBUG: 'command-stream:*' + }, + cwd: process.cwd() + }); + + const result: CommandResult = await envCmd`node --version`; + console.log(`Node version: ${result.stdout.trim()}`); +} + +// Export all examples for demonstration +export { + basicCommands, + configuredCommands, + streamingExamples, + eventDrivenProcessing, + virtualCommandsExample, + advancedStreaming, + pipelineExample, + errorHandling, + processStreams, + utilityExamples, + configurationExample, + asyncIterationPatterns, + multipleCommands, + environmentExample +}; + +// Main demonstration function +export async function runAllExamples(): Promise { + console.log('Running TypeScript examples for command-stream...'); + + try { + await basicCommands(); + await configuredCommands(); + await streamingExamples(); + eventDrivenProcessing(); + await virtualCommandsExample(); + await advancedStreaming(); + await pipelineExample(); + await errorHandling(); + await processStreams(); + utilityExamples(); + configurationExample(); + await asyncIterationPatterns(); + await multipleCommands(); + await environmentExample(); + + console.log('All examples completed successfully!'); + } catch (error) { + console.error('Error running examples:', error); + } +} \ No newline at end of file diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000..177fd80 --- /dev/null +++ b/index.d.ts @@ -0,0 +1,220 @@ +/** + * TypeScript definitions for command-stream + * Modern $ shell utility library with streaming, async iteration, and EventEmitter support + */ + +import { Readable, Writable } from 'stream'; +import { ChildProcess } from 'child_process'; + +// Base interfaces +export interface CommandResult { + /** Exit code of the command */ + code: number; + /** Standard output as string */ + stdout: string; + /** Standard error as string */ + stderr: string; + /** Standard input that was provided */ + stdin: string; + /** Get stdout as text (async method) */ + text(): Promise; +} + +export interface ProcessOptions { + /** Mirror output to console (default: true) */ + mirror?: boolean; + /** Capture output for result (default: true) */ + capture?: boolean; + /** Standard input handling */ + stdin?: 'inherit' | 'pipe' | string | Buffer; + /** Working directory */ + cwd?: string; + /** Environment variables */ + env?: Record; + /** Enable interactive TTY forwarding */ + interactive?: boolean; + /** Enable shell operator parsing (default: true) */ + shellOperators?: boolean; +} + +export interface StreamOptions { + /** Standard input handling */ + stdin?: 'inherit' | 'pipe'; + /** Standard output handling */ + stdout?: 'inherit' | 'pipe'; + /** Standard error handling */ + stderr?: 'inherit' | 'pipe'; +} + +// Event types for StreamEmitter +export interface StreamEvents { + data: [Buffer]; + end: [CommandResult]; + error: [Error]; + close: [number]; +} + +// Virtual command handler type +export type VirtualCommandHandler = (args: string[], options: ProcessOptions) => Promise | CommandResult | AsyncIterable; + +// Stream interfaces +export interface ProcessStreams { + readonly stdin: Writable | null; + readonly stdout: Readable | null; + readonly stderr: Readable | null; +} + +// Core ProcessRunner class with proper typing +export declare class ProcessRunner { + constructor(spec: { mode: string; command: string }, options?: ProcessOptions); + + // Stream properties + readonly stdout: Readable | null; + readonly stderr: Readable | null; + readonly stdin: Writable | null; + + // New streaming interfaces + readonly streams: ProcessStreams; + + // EventEmitter-like interface + on(event: K, listener: (...args: StreamEvents[K]) => void): this; + once(event: K, listener: (...args: StreamEvents[K]) => void): this; + emit(event: K, ...args: StreamEvents[K]): boolean; + off(event: K, listener: (...args: StreamEvents[K]) => void): this; + + // Core methods + start(options?: { mode?: 'async' | 'sync' } & StreamOptions): this; + cancel(signal?: NodeJS.Signals): void; + + // Async iteration support + stream(): AsyncIterable; + + // Promise interface + then( + onfulfilled?: ((value: CommandResult) => TResult1 | PromiseLike) | null, + onrejected?: ((reason: any) => TResult2 | PromiseLike) | null + ): Promise; + + catch( + onrejected?: ((reason: any) => TResult | PromiseLike) | null + ): Promise; + + finally(onfinally?: (() => void) | null): Promise; +} + +// Main $ function interfaces +export interface $Function { + // Tagged template usage: $`command` + (strings: TemplateStringsArray, ...values: any[]): ProcessRunner; + + // Options usage: $({ option: value })`command` + (options: ProcessOptions): (strings: TemplateStringsArray, ...values: any[]) => ProcessRunner; +} + +// Shell execution functions +export declare function sh(command: string, options?: ProcessOptions): ProcessRunner; +export declare function exec(command: string, options?: ProcessOptions): ProcessRunner; +export declare function run(command: string, options?: ProcessOptions): ProcessRunner; + +// Factory function +export declare function create(defaultOptions?: ProcessOptions): $Function; + +// Utility functions +export declare function quote(value: any): string; +export declare function raw(value: any): { [Symbol.toPrimitive](): string }; + +// Global state management +export declare function set(option: string): void; +export declare function unset(option: string): void; +export declare function resetGlobalState(): void; +export declare function forceCleanupAll(): void; + +// Virtual commands system with type safety +export declare function register( + name: string, + handler: VirtualCommandHandler +): void; + +export declare function unregister(name: string): boolean; +export declare function listCommands(): string[]; +export declare function enableVirtualCommands(): void; +export declare function disableVirtualCommands(): void; + +// ANSI utilities +export interface AnsiConfig { + enabled?: boolean; + colors?: boolean; + styles?: boolean; +} + +export declare const AnsiUtils: { + strip(text: string): string; + hasAnsi(text: string): boolean; +}; + +export declare function configureAnsi(options?: AnsiConfig): void; +export declare function getAnsiConfig(): Required; +export declare function processOutput(data: string | Buffer, options?: AnsiConfig): string; + +// Shell detection and utilities +export declare const shell: { + cmd: string; + args: string[]; +}; + +// Main exports +export declare const $: $Function; +export default $; + +// Type-safe pipeline definitions +export type PipelineStep = ProcessRunner | ((input: T) => ProcessRunner); +export type Pipeline = PipelineStep[]; + +// Generic streaming support +export interface StreamingOptions { + transform?: (chunk: Buffer) => T; + encoding?: BufferEncoding; +} + +// Enhanced ProcessRunner with generics for streaming +declare module './src/$.mjs' { + interface ProcessRunner { + stream(options?: StreamingOptions): AsyncIterable; + } +} + +// Template expression type for advanced usage +export type TemplateExpression = string | number | boolean | null | undefined | Buffer | + { toString(): string } | { [Symbol.toPrimitive](hint: 'string'): string }; + +// Virtual command registration with better typing +export interface TypedVirtualCommand { + name: string; + handler: (...args: TArgs) => Promise | CommandResult | AsyncIterable; +} + +export declare function registerTyped( + command: TypedVirtualCommand +): void; + +// Event type definitions for full IDE support +export type CommandEventMap = { + 'data': Buffer; + 'stdout': Buffer; + 'stderr': Buffer; + 'end': CommandResult; + 'error': Error; + 'close': number; + 'start': void; + 'cancel': NodeJS.Signals; +}; + +// Extend ProcessRunner with properly typed events +declare module './src/$.mjs' { + interface ProcessRunner { + on(event: K, listener: (data: CommandEventMap[K]) => void): this; + once(event: K, listener: (data: CommandEventMap[K]) => void): this; + emit(event: K, data: CommandEventMap[K]): boolean; + off(event: K, listener: (data: CommandEventMap[K]) => void): this; + } +} \ No newline at end of file diff --git a/package.json b/package.json index 6723c5b..71848c2 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,16 @@ { "name": "command-stream", - "version": "0.7.1", + "version": "0.8.0", "description": "Modern $ shell utility library with streaming, async iteration, and EventEmitter support, optimized for Bun runtime", "type": "module", "main": "src/$.mjs", "exports": { - ".": "./src/$.mjs" + ".": { + "types": "./index.d.ts", + "import": "./src/$.mjs" + } }, + "types": "index.d.ts", "repository": { "type": "git", "url": "git+https://github.com/link-foundation/command-stream.git" @@ -34,7 +38,9 @@ "eventemitter", "bun", "node", - "cross-runtime" + "cross-runtime", + "typescript", + "types" ], "author": "link-foundation", "license": "Unlicense", @@ -44,6 +50,8 @@ }, "files": [ "src/", + "index.d.ts", + "types/", "README.md", "LICENSE" ] diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..198d78b --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,35 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "strict": true, + "declaration": true, + "declarationMap": true, + "outDir": "./dist", + "rootDir": "./", + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "lib": ["ES2022", "DOM"], + "types": ["node"], + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "resolveJsonModule": true + }, + "include": [ + "src/**/*", + "examples/**/*", + "index.d.ts", + "types/**/*" + ], + "exclude": [ + "node_modules", + "dist", + "tests/**/*" + ], + "ts-node": { + "esm": true + } +} \ No newline at end of file diff --git a/types/pipelines.d.ts b/types/pipelines.d.ts new file mode 100644 index 0000000..e6d95ef --- /dev/null +++ b/types/pipelines.d.ts @@ -0,0 +1,225 @@ +/** + * TypeScript definitions for type-safe pipeline operations in command-stream + * Advanced pipeline composition with full type inference + */ + +import { ProcessRunner, CommandResult, ProcessOptions } from '../index'; + +// Pipeline step types with full type inference +export type PipelineInput = T | AsyncIterable | Promise; +export type PipelineOutput = ProcessRunner | AsyncIterable | Promise; + +export interface PipelineStep { + (input: PipelineInput): PipelineOutput; +} + +// Type-safe pipeline builder with inference +export interface TypedPipeline { + /** Add a processing step to the pipeline */ + pipe(step: PipelineStep): TypedPipeline; + + /** Add a command step to the pipeline */ + command(cmd: string, options?: ProcessOptions): TypedPipeline; + + /** Add a transformation step */ + transform( + transform: (input: TOutput) => TNext | Promise + ): TypedPipeline; + + /** Add a filtering step */ + filter(predicate: (input: TOutput) => boolean | Promise): TypedPipeline; + + /** Add a mapping step */ + map( + mapper: (input: TOutput) => TNext | Promise + ): TypedPipeline; + + /** Execute the pipeline */ + execute(input: PipelineInput): Promise; + + /** Execute and collect results as array */ + collect(input: PipelineInput): Promise; + + /** Execute and get final result */ + result(input: PipelineInput): Promise; +} + +// Pipeline creation functions with full type safety +export declare function pipeline(): TypedPipeline; + +export declare function pipeline( + firstStep: PipelineStep +): TypedPipeline; + +// Common pipeline patterns +export interface CommonPipelines { + /** Text processing pipeline */ + text(): TypedPipeline; + + /** JSON processing pipeline */ + json(): TypedPipeline; + + /** File processing pipeline */ + files(): TypedPipeline; + + /** Line-by-line processing pipeline */ + lines(): TypedPipeline; + + /** Byte processing pipeline */ + bytes(): TypedPipeline; +} + +export declare const pipelines: CommonPipelines; + +// Pipeline composition utilities +export declare function compose( + f: PipelineStep, + g: PipelineStep +): PipelineStep; + +export declare function compose( + f: PipelineStep, + g: PipelineStep, + h: PipelineStep +): PipelineStep; + +// Overload for more compositions as needed +export declare function compose(...steps: PipelineStep[]): PipelineStep; + +// Parallel pipeline execution +export interface ParallelPipeline { + /** Add a parallel branch */ + branch( + step: PipelineStep + ): ParallelPipeline; + + /** Execute all branches in parallel */ + execute(input: PipelineInput): Promise; + + /** Merge results from all branches */ + merge( + merger: (results: TOutput[]) => TMerged | Promise + ): Promise; +} + +export declare function parallel(): ParallelPipeline; + +// Conditional pipeline execution +export interface ConditionalPipeline { + /** Add a condition */ + when( + condition: (input: TInput) => boolean | Promise, + step: PipelineStep + ): ConditionalPipeline & { then: TypedPipeline }; + + /** Add an else branch */ + otherwise( + step: PipelineStep + ): TypedPipeline; +} + +export declare function conditional(): ConditionalPipeline; + +// Pipeline error handling +export interface ErrorHandlingPipeline + extends TypedPipeline { + + /** Add error handling */ + catch( + handler: (error: TError, input: TInput) => TOutput | Promise + ): ErrorHandlingPipeline; + + /** Add retry logic */ + retry( + attempts: number, + backoff?: (attempt: number) => number + ): ErrorHandlingPipeline; + + /** Add fallback value */ + fallback(value: TOutput | (() => TOutput | Promise)): TypedPipeline; +} + +export declare function resilient( + pipeline: TypedPipeline +): ErrorHandlingPipeline; + +// Pipeline monitoring and debugging +export interface PipelineMetrics { + stepCount: number; + totalDuration: number; + stepDurations: number[]; + stepNames: string[]; + inputSize?: number; + outputSize?: number; + throughput?: number; +} + +export interface MonitoredPipeline + extends TypedPipeline { + + /** Get execution metrics */ + getMetrics(): PipelineMetrics; + + /** Add step timing */ + timeStep(name: string): MonitoredPipeline; + + /** Add logging */ + log(logger: (step: string, input: any, output: any, duration: number) => void): MonitoredPipeline; +} + +export declare function monitored( + pipeline: TypedPipeline +): MonitoredPipeline; + +// Common pipeline step factories +export interface PipelineSteps { + /** Command execution step */ + command(cmd: string, options?: ProcessOptions): PipelineStep; + + /** Text transformation step */ + textTransform( + transform: (text: string) => string | Promise + ): PipelineStep; + + /** JSON parsing step */ + parseJson(): PipelineStep; + + /** JSON stringifying step */ + stringifyJson(): PipelineStep; + + /** File reading step */ + readFile(path: string): PipelineStep; + + /** File writing step */ + writeFile(path: string): PipelineStep; + + /** Buffer to string conversion */ + toString(encoding?: BufferEncoding): PipelineStep; + + /** String to buffer conversion */ + toBuffer(encoding?: BufferEncoding): PipelineStep; + + /** Array collection step */ + collect(): PipelineStep, T[]>; + + /** Batching step */ + batch(size: number): PipelineStep, AsyncIterable>; + + /** Debouncing step */ + debounce(delay: number): PipelineStep, AsyncIterable>; + + /** Throttling step */ + throttle(rate: number): PipelineStep, AsyncIterable>; +} + +export declare const steps: PipelineSteps; + +// Template literal type for command pipelines +export type CommandTemplate = (strings: TemplateStringsArray, ...values: any[]) => ProcessRunner; + +export interface PipelineTemplate { + /** Template literal for type-safe command pipelines */ + (strings: TemplateStringsArray, ...values: any[]): TypedPipeline; +} + +export declare const p: PipelineTemplate; \ No newline at end of file diff --git a/types/streaming.d.ts b/types/streaming.d.ts new file mode 100644 index 0000000..7d2b76e --- /dev/null +++ b/types/streaming.d.ts @@ -0,0 +1,162 @@ +/** + * TypeScript definitions for streaming functionality in command-stream + * Advanced streaming interfaces with full generic type support + */ + +import { Readable, Writable, Transform } from 'stream'; +import { ProcessRunner, CommandResult } from '../index'; + +// Generic streaming transformations +export type StreamTransform = + (chunk: TInput) => TOutput | Promise; + +export type StreamFilter = + (chunk: T) => boolean | Promise; + +export type StreamReducer = + (accumulator: TAcc, chunk: T) => TAcc | Promise; + +// Streaming options with generics +export interface StreamingOptions { + /** Transform each chunk */ + transform?: StreamTransform; + /** Filter chunks */ + filter?: StreamFilter; + /** Encoding for string conversion */ + encoding?: BufferEncoding; + /** Chunk size for reading */ + highWaterMark?: number; + /** Object mode for non-buffer data */ + objectMode?: boolean; +} + +// Line-based streaming +export interface LineStreamingOptions { + /** Line ending character(s) */ + separator?: string | RegExp; + /** Skip empty lines */ + skipEmpty?: boolean; + /** Trim whitespace from lines */ + trim?: boolean; + /** Encoding for string conversion */ + encoding?: BufferEncoding; +} + +// Advanced streaming interfaces +export interface EnhancedProcessRunner extends ProcessRunner { + // Generic streaming with transformations + stream(options?: StreamingOptions): AsyncIterable; + + // JSON streaming (for commands that output JSON) + json(): AsyncIterable; + + // Byte streaming with exact control + bytes(options?: { chunkSize?: number }): AsyncIterable; + + // Text streaming with encoding + text(encoding?: BufferEncoding): AsyncIterable; + + // Stream operations + map(transform: StreamTransform): AsyncIterable; + filter(predicate: StreamFilter): AsyncIterable; + reduce(reducer: StreamReducer, initialValue: TAcc): Promise; + collect(): Promise; + collectText(encoding?: BufferEncoding): Promise; + + // Pipeline operations + pipe(destination: TTarget): TTarget; + pipeThrough(transform: TTransform): TTransform; +} + +// Stream composition types +export type StreamPipeline = + (input: AsyncIterable) => AsyncIterable; + +export interface PipelineBuilder { + map(transform: StreamTransform): PipelineBuilder; + filter(predicate: StreamFilter): PipelineBuilder; + take(count: number): PipelineBuilder; + skip(count: number): PipelineBuilder; + batch(size: number): PipelineBuilder; + build(): StreamPipeline; +} + +// Stream utilities +export declare function createPipeline(): PipelineBuilder; + +export declare function streamToArray(stream: AsyncIterable): Promise; + +export declare function streamToString( + stream: AsyncIterable, + encoding?: BufferEncoding +): Promise; + +export declare function mergeStreams(...streams: AsyncIterable[]): AsyncIterable; + +export declare function splitStream( + stream: AsyncIterable, + predicate: StreamFilter +): [AsyncIterable, AsyncIterable]; + +// Real-time streaming events +export interface StreamEventMap { + 'chunk': T; + 'line': string; + 'json': any; + 'error': Error; + 'end': void; + 'start': void; +} + +export interface RealTimeStream { + on>( + event: K, + listener: (data: StreamEventMap[K]) => void + ): this; + + off>( + event: K, + listener: (data: StreamEventMap[K]) => void + ): this; + + once>( + event: K, + listener: (data: StreamEventMap[K]) => void + ): this; +} + +// Streaming process runner factory +export declare function createStreamingRunner( + command: string, + options?: StreamingOptions +): EnhancedProcessRunner; + +// Type guards +export declare function isStreamableProcessRunner( + runner: ProcessRunner +): runner is EnhancedProcessRunner; + +// Stream performance monitoring +export interface StreamMetrics { + bytesProcessed: number; + chunksProcessed: number; + startTime: Date; + endTime?: Date; + duration?: number; + throughput?: number; // bytes per second +} + +export declare function monitorStream( + stream: AsyncIterable +): AsyncIterable & { getMetrics(): StreamMetrics }; + +// Advanced stream combinators +export declare function race(...streams: AsyncIterable[]): AsyncIterable; +export declare function parallel( + stream: AsyncIterable, + concurrency?: number +): AsyncIterable; +export declare function buffer( + stream: AsyncIterable, + size: number +): AsyncIterable; \ No newline at end of file diff --git a/types/virtual-commands.d.ts b/types/virtual-commands.d.ts new file mode 100644 index 0000000..80fee3d --- /dev/null +++ b/types/virtual-commands.d.ts @@ -0,0 +1,73 @@ +/** + * TypeScript definitions for virtual commands in command-stream + * Built-in command implementations with full type safety + */ + +import { CommandResult, ProcessOptions } from '../index'; + +// Built-in virtual commands with typed interfaces +export interface BuiltinCommands { + // File system operations + cat: (files: string[]) => Promise; + cp: (source: string, destination: string, options?: { recursive?: boolean }) => Promise; + mv: (source: string, destination: string) => Promise; + rm: (files: string[], options?: { recursive?: boolean; force?: boolean }) => Promise; + ls: (paths?: string[], options?: { all?: boolean; long?: boolean }) => Promise; + mkdir: (paths: string[], options?: { parents?: boolean; mode?: string }) => Promise; + touch: (files: string[]) => Promise; + pwd: () => Promise; + cd: (path?: string) => Promise; + + // Text processing + echo: (...args: string[]) => Promise; + yes: (text?: string) => AsyncIterable; + seq: (start: number, end?: number, step?: number) => Promise; + + // System utilities + basename: (path: string, suffix?: string) => Promise; + dirname: (path: string) => Promise; + env: (variables?: Record) => Promise; + which: (command: string) => Promise; + test: (expression: string) => Promise; + sleep: (seconds: number) => Promise; + + // Control flow + true: () => Promise; + false: () => Promise; + exit: (code?: number) => Promise; +} + +// Type-safe virtual command registration +export type VirtualCommandName = keyof BuiltinCommands; + +export interface VirtualCommandDefinition { + name: string; + description?: string; + usage?: string; + handler: (...args: TArgs) => Promise | CommandResult | AsyncIterable; +} + +// Enhanced registration with full typing +export declare function registerVirtualCommand( + definition: VirtualCommandDefinition +): void; + +// Command existence checking +export declare function hasVirtualCommand(name: string): boolean; +export declare function getVirtualCommand(name: string): VirtualCommandDefinition | undefined; + +// Bulk operations +export declare function registerBuiltinCommands(): void; +export declare function unregisterAllVirtualCommands(): void; + +// Command metadata +export interface CommandMetadata { + name: string; + description: string; + usage: string; + category: 'filesystem' | 'text' | 'system' | 'control' | 'custom'; + builtin: boolean; +} + +export declare function getCommandMetadata(name: string): CommandMetadata | undefined; +export declare function listCommandMetadata(): CommandMetadata[]; \ No newline at end of file