From 3c379fe2e70a1836c2a879b274b35b0896e77680 Mon Sep 17 00:00:00 2001 From: Rob Becker Date: Tue, 31 Mar 2026 07:17:37 -0600 Subject: [PATCH 1/3] Detect ready dart 3 --- lib/src/serve_command.dart | 66 ++++++++++++++++++++++++++++++++++++++ lib/src/webdev_server.dart | 31 +++++++++++++++++- 2 files changed, 96 insertions(+), 1 deletion(-) diff --git a/lib/src/serve_command.dart b/lib/src/serve_command.dart index 5749dba..d729f8e 100644 --- a/lib/src/serve_command.dart +++ b/lib/src/serve_command.dart @@ -16,6 +16,7 @@ import 'dart:async'; import 'dart:io'; import 'package:args/command_runner.dart'; +import 'package:http/http.dart' as http; import 'package:io/ansi.dart'; import 'package:io/io.dart'; @@ -149,6 +150,8 @@ class ServeCommand extends Command { for (final dir in portsToServeByDir.keys) dir: await findUnusedPort() }; + final startupTimer = Stopwatch()..start(); + // Start the underlying `webdev serve` process. webdevServer = await WebdevServer.start([ if (hostname != 'localhost') '--hostname=$hostname', @@ -186,6 +189,69 @@ class ServeCommand extends Command { } } + if (!proxiesFailed) { + final hostToProbe = hostname == 'any' ? 'localhost' : hostname; + final readyByDir = { + for (final dir in portsToServeByDir.keys) dir: Completer() + }; + + for (final dir in portsToServeByDir.keys) { + unawaited(() async { + try { + await _waitForProxyToServe( + host: hostToProbe, + port: portsToServeByDir[dir]!, + ); + if (!readyByDir[dir]!.isCompleted) { + readyByDir[dir]!.complete(); + } + } catch (e, stackTrace) { + if (!readyByDir[dir]!.isCompleted) { + readyByDir[dir]!.completeError(e, stackTrace); + } + } + }()); + } + // to reduce exception noise, wait a few seconds + await Future.delayed(const Duration(seconds: 3)); + + await Future.any([ + Future.wait(readyByDir.values.map((completer) => completer.future)), + exitCodeCompleter.future, + ]); + + if (!exitCodeCompleter.isCompleted) { + final elapsedSeconds = + (startupTimer.elapsedMilliseconds / 1000).toStringAsFixed(1); + stdout.writeln('[INFO] Succeeded after $elapsedSeconds seconds'); + startupTimer.stop(); + } + } + return exitCodeCompleter.future; } + + static Future _waitForProxyToServe({ + required String host, + required int port, + }) async { + const pollInterval = Duration(milliseconds: 1000); + + while (true) { + final uri = Uri.parse('http://$host:$port'); + try { + stdout.writeln('[INFO] Pinging server $uri to detect responsiveness...an exception from the server means it is not ready.'); + final response = await http.get(uri); + if (response.statusCode == 200) { + stdout.writeln('[INFO] $uri responded with 200, server is ready!'); + return; + } + stdout.writeln('[INFO] $uri responded with ${response.statusCode}, server is not ready yet.'); + } catch (_) { + // Keep polling until webdev starts listening and serving successfully. + stdout.writeln('[INFO] $uri responded with an error, server is not ready yet.'); + } + await Future.delayed(pollInterval); + } + } } diff --git a/lib/src/webdev_server.dart b/lib/src/webdev_server.dart index 757cedf..2ec92ff 100644 --- a/lib/src/webdev_server.dart +++ b/lib/src/webdev_server.dart @@ -13,8 +13,10 @@ // limitations under the License. import 'dart:async'; +import 'dart:convert'; import 'dart:io'; +import 'package:http/http.dart' as http; import 'package:webdev_proxy/src/logging.dart'; /// A simple abstraction over a `webdev serve ...` process. @@ -39,13 +41,40 @@ class WebdevServer { /// [WebdevServer] abstraction over said process. static Future start(List args, {ProcessStartMode mode = ProcessStartMode.inheritStdio}) async { - final webdevArgs = ['pub', 'global', 'run', 'webdev', 'serve', ...args]; + final webdevArgs = [ + 'pub', + 'global', + 'run', + 'webdev', + 'serve', + '-v', + ...args + ]; log.fine('Running `dart ${webdevArgs.join(' ')}'); final process = await Process.start( 'dart', webdevArgs, mode: mode, ); + + + // `inheritStdio` and `detached` do not expose readable stdout streams. + // if (mode == ProcessStartMode.normal || + // mode == ProcessStartMode.detachedWithStdio) { + // // `webdev serve` doesn't expose a dedicated "ready" signal, so key off + // // the "Built with" log line. + // var loggedReadyMessage = false; + // unawaited(process.stdout + // .transform(utf8.decoder) + // .transform(const LineSplitter()) + // .listen((line) { + // if (!loggedReadyMessage && line.contains('[INFO] : Built with')) { + // loggedReadyMessage = true; + // stdout.writeln('[INFO] Succeeded after'); + // } + // }).asFuture()); + // } + return WebdevServer._(process); } } From 720369607ab0293c2526d14687785eb539be12e5 Mon Sep 17 00:00:00 2001 From: Rob Becker Date: Tue, 31 Mar 2026 08:35:52 -0600 Subject: [PATCH 2/3] format and changelog --- CHANGELOG.md | 12 ++++++++++++ lib/src/serve_command.dart | 10 ++++++---- lib/src/webdev_server.dart | 1 - 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f4e0dd..17bab90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +## 0.1.14 + +- #66 Detect readiness of the server and output ready message + +## 0.1.13 + +- # 50 Bump Workiva/gha-dart-oss from 0.1.5 to 0.1.7 in the gha group across 1 directory + +- #51 Raise the meta dependency min to v1.16.0 + +- #52 raise_min_dart_sdk_2_19 + ## 0.1.12 - Update ranges of dependencies so that in Dart 3 we can resolve to analyzer 6, while still working with Dart 2.19. https://github.com/Workiva/webdev_proxy/pull/46 diff --git a/lib/src/serve_command.dart b/lib/src/serve_command.dart index d729f8e..a1038aa 100644 --- a/lib/src/serve_command.dart +++ b/lib/src/serve_command.dart @@ -214,7 +214,6 @@ class ServeCommand extends Command { } // to reduce exception noise, wait a few seconds await Future.delayed(const Duration(seconds: 3)); - await Future.any([ Future.wait(readyByDir.values.map((completer) => completer.future)), exitCodeCompleter.future, @@ -240,16 +239,19 @@ class ServeCommand extends Command { while (true) { final uri = Uri.parse('http://$host:$port'); try { - stdout.writeln('[INFO] Pinging server $uri to detect responsiveness...an exception from the server means it is not ready.'); + stdout.writeln( + '[INFO] Pinging server $uri to detect responsiveness...an exception from the server means it is not ready.'); final response = await http.get(uri); if (response.statusCode == 200) { stdout.writeln('[INFO] $uri responded with 200, server is ready!'); return; } - stdout.writeln('[INFO] $uri responded with ${response.statusCode}, server is not ready yet.'); + stdout.writeln( + '[INFO] $uri responded with ${response.statusCode}, server is not ready yet.'); } catch (_) { // Keep polling until webdev starts listening and serving successfully. - stdout.writeln('[INFO] $uri responded with an error, server is not ready yet.'); + stdout.writeln( + '[INFO] $uri responded with an error, server is not ready yet.'); } await Future.delayed(pollInterval); } diff --git a/lib/src/webdev_server.dart b/lib/src/webdev_server.dart index 2ec92ff..c61f6d6 100644 --- a/lib/src/webdev_server.dart +++ b/lib/src/webdev_server.dart @@ -57,7 +57,6 @@ class WebdevServer { mode: mode, ); - // `inheritStdio` and `detached` do not expose readable stdout streams. // if (mode == ProcessStartMode.normal || // mode == ProcessStartMode.detachedWithStdio) { From da2552ac38bda66c8ec43d2a604037ce35df7fa5 Mon Sep 17 00:00:00 2001 From: Rob Becker Date: Tue, 31 Mar 2026 08:44:11 -0600 Subject: [PATCH 3/3] cleanup --- lib/src/webdev_server.dart | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/lib/src/webdev_server.dart b/lib/src/webdev_server.dart index c61f6d6..f6a469c 100644 --- a/lib/src/webdev_server.dart +++ b/lib/src/webdev_server.dart @@ -13,10 +13,8 @@ // limitations under the License. import 'dart:async'; -import 'dart:convert'; import 'dart:io'; -import 'package:http/http.dart' as http; import 'package:webdev_proxy/src/logging.dart'; /// A simple abstraction over a `webdev serve ...` process. @@ -57,23 +55,6 @@ class WebdevServer { mode: mode, ); - // `inheritStdio` and `detached` do not expose readable stdout streams. - // if (mode == ProcessStartMode.normal || - // mode == ProcessStartMode.detachedWithStdio) { - // // `webdev serve` doesn't expose a dedicated "ready" signal, so key off - // // the "Built with" log line. - // var loggedReadyMessage = false; - // unawaited(process.stdout - // .transform(utf8.decoder) - // .transform(const LineSplitter()) - // .listen((line) { - // if (!loggedReadyMessage && line.contains('[INFO] : Built with')) { - // loggedReadyMessage = true; - // stdout.writeln('[INFO] Succeeded after'); - // } - // }).asFuture()); - // } - return WebdevServer._(process); } }