diff --git a/packages/melos/lib/src/commands/bootstrap.dart b/packages/melos/lib/src/commands/bootstrap.dart index afa5573b7..073167b31 100644 --- a/packages/melos/lib/src/commands/bootstrap.dart +++ b/packages/melos/lib/src/commands/bootstrap.dart @@ -203,13 +203,21 @@ mixin _BootstrapMixin on _CleanMixin { process.stdin.writeln(r'exit $?'); } + const logTimeout = Duration(seconds: 10); + final packagePrefix = '[${AnsiStyles.blue.bold(package.name)}]: '; + void Function(String) logLineTo(void Function(String)? log) => + (line) => log?.call('$packagePrefix$line'); + // We always fully consume stdout and stderr. This is required to prevent - // leaking resources and to ensure that the process exits. We - // need stdout and stderr in case of an error. Otherwise, we don't care - // about the output, don't wait for it to finish and don't handle errors. - // We just make sure the output streams are drained. - final stdout = utf8.decodeStream(process.stdout); - final stderr = utf8.decodeStream(process.stderr); + // leaking resources and to ensure that the process exits. + final stdout = process.stdout.toStringAndLogAfterTimeout( + timeout: logTimeout, + log: logLineTo(logger?.stdout), + ); + final stderr = process.stderr.toStringAndLogAfterTimeout( + timeout: logTimeout, + log: logLineTo(logger?.stderr), + ); final exitCode = await process.exitCode; diff --git a/packages/melos/lib/src/common/utils.dart b/packages/melos/lib/src/common/utils.dart index 042d7acd9..09ff38d7b 100644 --- a/packages/melos/lib/src/common/utils.dart +++ b/packages/melos/lib/src/common/utils.dart @@ -497,3 +497,29 @@ extension StreamUtils on Stream { } } } + +extension Utf8StreamUtils on Stream> { + /// Fully consumes this stream and returns the decoded string, while also + /// starting to call [log] after [timeout] has elapsed for the previously + /// decoded lines and all subsequent lines. + Future toStringAndLogAfterTimeout({ + required Duration timeout, + required void Function(String) log, + }) async { + final bufferedLines = []; + final stopwatch = Stopwatch()..start(); + return transform(utf8.decoder).transform(const LineSplitter()).map((line) { + if (stopwatch.elapsed >= timeout) { + if (bufferedLines.isNotEmpty) { + bufferedLines.forEach(log); + bufferedLines.clear(); + } + log(line); + } else { + bufferedLines.add(line); + } + + return line; + }).join('\n'); + } +}