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 5749dba..a1038aa 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,71 @@ 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..f6a469c 100644 --- a/lib/src/webdev_server.dart +++ b/lib/src/webdev_server.dart @@ -39,13 +39,22 @@ 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, ); + return WebdevServer._(process); } }