diff --git a/.serena/cache/typescript/document_symbols_cache_v23-06-25.pkl b/.serena/cache/typescript/document_symbols_cache_v23-06-25.pkl index c912649..7ab121d 100644 Binary files a/.serena/cache/typescript/document_symbols_cache_v23-06-25.pkl and b/.serena/cache/typescript/document_symbols_cache_v23-06-25.pkl differ diff --git a/test/test.passthrough.ts b/test/test.passthrough.ts new file mode 100644 index 0000000..f4c1112 --- /dev/null +++ b/test/test.passthrough.ts @@ -0,0 +1,42 @@ +import { expect, tap } from '@git.zone/tstest/tapbundle'; +import * as smartshell from '../ts/index.js'; + +tap.test('should handle passthrough for interactive commands', async () => { + const testSmartshell = new smartshell.Smartshell({ + executor: 'bash', + sourceFilePaths: [], + }); + + // Test with a simple echo command that doesn't need input + const result = await testSmartshell.execPassthrough('echo "Testing passthrough"'); + expect(result.exitCode).toEqual(0); + expect(result.stdout).toContain('Testing passthrough'); +}); + +tap.test('should handle streaming passthrough', async () => { + const testSmartshell = new smartshell.Smartshell({ + executor: 'bash', + sourceFilePaths: [], + }); + + // Test streaming passthrough with a simple command + const streamingResult = await testSmartshell.execStreamingPassthrough('echo "Testing streaming passthrough"'); + const finalResult = await streamingResult.finalPromise; + + expect(finalResult.exitCode).toEqual(0); + expect(finalResult.stdout).toContain('Testing streaming passthrough'); +}); + +tap.test('should allow normal exec without passthrough', async () => { + const testSmartshell = new smartshell.Smartshell({ + executor: 'bash', + sourceFilePaths: [], + }); + + // Regular exec should still work as before + const result = await testSmartshell.exec('echo "Normal exec"'); + expect(result.exitCode).toEqual(0); + expect(result.stdout).toContain('Normal exec'); +}); + +export default tap.start(); \ No newline at end of file diff --git a/ts/classes.smartshell.ts b/ts/classes.smartshell.ts index ce8ebcd..a3c5c45 100644 --- a/ts/classes.smartshell.ts +++ b/ts/classes.smartshell.ts @@ -25,6 +25,7 @@ interface IExecOptions { strict?: boolean; streaming?: boolean; interactive?: boolean; + passthrough?: boolean; } export class Smartshell { @@ -87,6 +88,11 @@ export class Smartshell { this.smartexit.addProcess(execChildProcess); + // Connect stdin if passthrough is enabled + if (options.passthrough && execChildProcess.stdin) { + process.stdin.pipe(execChildProcess.stdin); + } + // Capture stdout and stderr output. execChildProcess.stdout.on('data', (data) => { if (!options.silent) { @@ -107,6 +113,11 @@ export class Smartshell { execChildProcess.on('exit', (code, signal) => { this.smartexit.removeProcess(execChildProcess); + // Unpipe stdin when process ends if passthrough was enabled + if (options.passthrough) { + process.stdin.unpipe(execChildProcess.stdin); + } + const execResult: IExecResult = { exitCode: typeof code === 'number' ? code : (signal ? 1 : 0), stdout: shellLogInstance.logStore.toString(), @@ -121,6 +132,10 @@ export class Smartshell { execChildProcess.on('error', (error) => { this.smartexit.removeProcess(execChildProcess); + // Unpipe stdin when process errors if passthrough was enabled + if (options.passthrough && execChildProcess.stdin) { + process.stdin.unpipe(execChildProcess.stdin); + } reject(error); }); }); @@ -181,6 +196,14 @@ export class Smartshell { await this._exec({ commandString, interactive: true }); } + public async execPassthrough(commandString: string): Promise { + return await this._exec({ commandString, passthrough: true }) as IExecResult; + } + + public async execStreamingPassthrough(commandString: string): Promise { + return await this._exec({ commandString, streaming: true, passthrough: true }) as IExecResultStreaming; + } + public async execAndWaitForLine( commandString: string, regex: RegExp,