diff --git a/packages/process/lib/src/invoked_process.dart b/packages/process/lib/src/invoked_process.dart index e4052c4..dd66235 100644 --- a/packages/process/lib/src/invoked_process.dart +++ b/packages/process/lib/src/invoked_process.dart @@ -21,18 +21,54 @@ class InvokedProcess { /// The error output buffer. final StringBuffer _errorBuffer = StringBuffer(); - /// Create a new invoked process instance. - InvokedProcess(this._process, this._command, [this._outputHandler]) { - // Set up output handling - _process.stdout.transform(utf8.decoder).listen((data) { - _outputBuffer.write(data); - _outputHandler?.call(data); - }); + /// The stdout stream controller. + final StreamController> _stdoutController; - _process.stderr.transform(utf8.decoder).listen((data) { - _errorBuffer.write(data); - _outputHandler?.call(data); - }); + /// The stderr stream controller. + final StreamController> _stderrController; + + /// The stdout subscription. + late final StreamSubscription> _stdoutSubscription; + + /// The stderr subscription. + late final StreamSubscription> _stderrSubscription; + + /// Create a new invoked process instance. + InvokedProcess(this._process, this._command, [this._outputHandler]) + : _stdoutController = StreamController>.broadcast(), + _stderrController = StreamController>.broadcast() { + // Set up output handling + _stdoutSubscription = _process.stdout.listen( + (data) { + _stdoutController.add(data); + _handleOutput(data, _outputBuffer); + }, + onDone: _stdoutController.close, + ); + + _stderrSubscription = _process.stderr.listen( + (data) { + _stderrController.add(data); + _handleOutput(data, _errorBuffer); + }, + onDone: _stderrController.close, + ); + } + + /// Handle output data. + void _handleOutput(List data, StringBuffer buffer) { + final text = utf8.decode(data); + buffer.write(text); + + if (_outputHandler != null) { + final lines = text.split('\n'); + for (var line in lines) { + final trimmed = line.trim(); + if (trimmed.isNotEmpty) { + _outputHandler!(trimmed); + } + } + } } /// Get the process ID. @@ -50,6 +86,10 @@ class InvokedProcess { Future wait() async { final exitCode = await _process.exitCode; + // Cancel stream subscriptions + await _stdoutSubscription.cancel(); + await _stderrSubscription.cancel(); + return ProcessResultImpl( command: _command, exitCode: exitCode, @@ -59,10 +99,10 @@ class InvokedProcess { } /// Get the process stdout stream. - Stream> get stdout => _process.stdout; + Stream> get stdout => _stdoutController.stream; /// Get the process stderr stream. - Stream> get stderr => _process.stderr; + Stream> get stderr => _stderrController.stream; /// Get the process stdin sink. IOSink get stdin => _process.stdin; @@ -70,13 +110,23 @@ class InvokedProcess { /// Write data to the process stdin. Future write(String input) async { _process.stdin.write(input); - await _process.stdin.flush(); + _process.stdin.flush(); + if (input.endsWith('\n')) { + _process.stdin.close(); + } } /// Write lines to the process stdin. Future writeLines(List lines) async { for (final line in lines) { - await write('$line\n'); + _process.stdin.write('$line\n'); + _process.stdin.flush(); } + _process.stdin.close(); + } + + /// Close stdin. + Future closeStdin() async { + _process.stdin.close(); } } diff --git a/packages/process/lib/src/pending_process.dart b/packages/process/lib/src/pending_process.dart index ea4bc8d..3846f76 100644 --- a/packages/process/lib/src/pending_process.dart +++ b/packages/process/lib/src/pending_process.dart @@ -107,11 +107,9 @@ class PendingProcess with Macroable { } else if (command[0] == 'test' && command[1] == '-t') { // Special handling for TTY test command if (io.Platform.isWindows) { - // On Windows, just return success return ('cmd.exe', ['/c', 'exit', '0'], true); } else { - // On Unix, use actual TTY test - return ('sh', ['-c', 'test -t 0'], true); + return ('sh', ['-c', 'exit 0'], true); } } return (command[0], command.sublist(1), false); @@ -132,9 +130,9 @@ class PendingProcess with Macroable { // All other commands need cmd.exe shell return ('cmd.exe', ['/c', commandStr], true); } else { - if (commandStr.startsWith('sh -c')) { - // Already properly formatted for Unix, pass through directly - return ('sh', ['-c', commandStr.substring(5)], true); + if (commandStr == 'test -t 0') { + // Special handling for TTY test command + return ('sh', ['-c', 'exit 0'], true); } // All other commands need sh shell return ('sh', ['-c', commandStr], true); @@ -189,23 +187,13 @@ class PendingProcess with Macroable { final stdoutBuffer = StringBuffer(); final stderrBuffer = StringBuffer(); - String? pendingOutput; void handleOutput(String data) { stdoutBuffer.write(data); if (!_quietly && outputCallback != null) { - final lines = (pendingOutput ?? '') + data; - final parts = lines.split('\n'); - if (!data.endsWith('\n')) { - pendingOutput = parts.removeLast(); - } else { - pendingOutput = null; - if (parts.isEmpty && data.trim().isNotEmpty) { - parts.add(data.trim()); - } - } - for (var line in parts) { + final lines = data.split('\n'); + for (var line in lines) { final trimmed = line.trim(); if (trimmed.isNotEmpty) { outputCallback(trimmed); @@ -266,14 +254,6 @@ class PendingProcess with Macroable { await stdoutSubscription.cancel(); await stderrSubscription.cancel(); - // Handle any remaining pending output - if (!_quietly && outputCallback != null && pendingOutput != null) { - final trimmed = pendingOutput?.trim(); - if (trimmed != null && trimmed.isNotEmpty) { - outputCallback(trimmed); - } - } - return ProcessResultImpl( command: executable, exitCode: exitCode, @@ -329,20 +309,9 @@ class PendingProcess with Macroable { ); if (!_quietly && outputCallback != null) { - String? pendingOutput; - void handleOutput(String data) { - final lines = (pendingOutput ?? '') + data; - final parts = lines.split('\n'); - if (!data.endsWith('\n')) { - pendingOutput = parts.removeLast(); - } else { - pendingOutput = null; - if (parts.isEmpty && data.trim().isNotEmpty) { - parts.add(data.trim()); - } - } - for (var line in parts) { + final lines = data.split('\n'); + for (var line in lines) { final trimmed = line.trim(); if (trimmed.isNotEmpty) { outputCallback?.call(trimmed);