Skip to content

Commit ab782fc

Browse files
feat: added default port number for --dart-vm-service-port (#697)
* feat: added default port number * test: improved test name * refactor: moved port to single variable * feat: recommend using `--dart-vm-service-port` on VM port failure (#698) * refactor: moved tests to multiple_dev_test.dart * feat: added recommendation * refactor: fixed typo * feat: added "try" * docs: removed example * refactor: removed superfluos comment * feat: used .fail over .cancel * feat: add debugger info * test: hit coverage * refactor: removed moving e2e * test: included process.fail verification * test: simplified output * test: removed registerFallbackValue * test: adde "helpful" on test name * test: reason typo * refactor: simplified killing logic * refactor: removed Please --------- Co-authored-by: Renan <[email protected]>
1 parent 6b09cad commit ab782fc

File tree

2 files changed

+104
-10
lines changed

2 files changed

+104
-10
lines changed

packages/dart_frog_cli/lib/src/commands/dev/dev.dart

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,13 @@ typedef Exit = dynamic Function(int exitCode);
4141
/// Regex for detecting warnings in the output of `dart run`.
4242
final _warningRegex = RegExp(r'^.*:\d+:\d+: Warning: .*', multiLine: true);
4343

44+
/// Regex for detecting when the `dart_frog dev` fails to run for using a
45+
/// Dart VM Service with an already used port.
46+
final _dartVmServiceAlreadyInUseErrorRegex = RegExp(
47+
'^Could not start the VM service: localhost:.* is already in use.',
48+
multiLine: true,
49+
);
50+
4451
RestorableDirectoryGeneratorTarget _defaultGeneratorTarget(Logger? logger) {
4552
return RestorableDirectoryGeneratorTarget(
4653
io.Directory(
@@ -84,10 +91,13 @@ class DevCommand extends DartFrogCommand {
8491
..addOption(
8592
'dart-vm-service-port',
8693
abbr: 'd',
94+
defaultsTo: _defaultDartVmServicePort,
8795
help: 'Which port number the dart vm service should listen on.',
8896
);
8997
}
9098

99+
static const _defaultDartVmServicePort = '8181';
100+
91101
final void Function(io.Directory) _ensureRuntimeCompatibility;
92102
final DirectoryWatcherBuilder _directoryWatcher;
93103
final GeneratorBuilder _generator;
@@ -117,7 +127,8 @@ class DevCommand extends DartFrogCommand {
117127
var reloading = false;
118128
var hotReloadEnabled = false;
119129
final port = io.Platform.environment['PORT'] ?? results['port'] as String;
120-
final dartVmServicePort = results['dart-vm-service-port'] as String?;
130+
final dartVmServicePort = (results['dart-vm-service-port'] as String?) ??
131+
_defaultDartVmServicePort;
121132
final target = _generatorTarget(logger);
122133
final generator = await _generator(dartFrogDevServerBundle);
123134

@@ -147,9 +158,7 @@ class DevCommand extends DartFrogCommand {
147158
logger.detail('[codegen] reload complete.');
148159
}
149160

150-
final enableVmServiceFlag = '--enable-vm-service'
151-
'${dartVmServicePort == null ? "" : "=$dartVmServicePort"}';
152-
161+
final enableVmServiceFlag = '--enable-vm-service=$dartVmServicePort';
153162
Future<void> serve() async {
154163
logger.detail(
155164
'''[process] dart $enableVmServiceFlag ${path.join('.dart_frog', 'server.dart')}''',
@@ -174,16 +183,24 @@ class DevCommand extends DartFrogCommand {
174183
final message = utf8.decode(_).trim();
175184
if (message.isEmpty) return;
176185

177-
/// Do not kill the process if the error is a warning from the SDK.
186+
final isDartVMServiceAlreadyInUseError =
187+
_dartVmServiceAlreadyInUseErrorRegex.hasMatch(message);
178188
final isSDKWarning = _warningRegex.hasMatch(message);
179189

180-
if (isSDKWarning) {
190+
if (isDartVMServiceAlreadyInUseError) {
191+
logger.err(
192+
'$message '
193+
'''Try specifying a different port using the `--dart-vm-service-port` argument when running `dart_frog dev`.''',
194+
);
195+
} else if (isSDKWarning) {
196+
/// Do not kill the process if the error is a warning from the SDK.
181197
logger.warn(message);
182198
} else {
183199
logger.err(message);
184200
}
185201

186-
if (!hotReloadEnabled && !isSDKWarning) {
202+
if ((!hotReloadEnabled && !isSDKWarning) ||
203+
isDartVMServiceAlreadyInUseError) {
187204
await _killProcess(process);
188205
logger.detail('[process] exit(1)');
189206
_exit(1);

packages/dart_frog_cli/test/src/commands/dev/dev_test.dart

Lines changed: 80 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -587,8 +587,7 @@ void main() {
587587
});
588588

589589
test(
590-
'when dart vm port not specified, --enable-vm-service option should '
591-
'be called without any port number value',
590+
'''when --dart-vm-service-port is not specified, calls --enable-vm-service with port 8181''',
592591
() async {
593592
late List<String> receivedArgs;
594593
command.testArgResults = argResults;
@@ -634,7 +633,7 @@ void main() {
634633
)..testArgResults = argResults;
635634
final exitCode = await command.run();
636635
expect(exitCode, equals(ExitCode.success.code));
637-
expect(receivedArgs[0], equals('--enable-vm-service'));
636+
expect(receivedArgs[0], equals('--enable-vm-service=8181'));
638637
verify(
639638
() => generatorHooks.preGen(
640639
vars: <String, dynamic>{'port': '8080'},
@@ -902,6 +901,84 @@ lib/my_model.g.dart:53:20: Warning: Operand of null-aware operation '!' has type
902901
expect(processRunCalls, isEmpty);
903902
verify(() => process.kill()).called(1);
904903
});
904+
905+
test(
906+
'''kills process with helpful message when Dart VM is already in use''',
907+
() async {
908+
final generatorHooks = _MockGeneratorHooks();
909+
final processRunCalls = <List<String>>[];
910+
int? exitCode;
911+
when(
912+
() => generatorHooks.preGen(
913+
vars: any(named: 'vars'),
914+
workingDirectory: any(named: 'workingDirectory'),
915+
onVarsChanged: any(named: 'onVarsChanged'),
916+
),
917+
).thenAnswer((invocation) async {
918+
(invocation.namedArguments[const Symbol('onVarsChanged')] as void
919+
Function(Map<String, dynamic> vars))
920+
.call(<String, dynamic>{});
921+
});
922+
when(
923+
() => generator.generate(
924+
any(),
925+
vars: any(named: 'vars'),
926+
fileConflictResolution: FileConflictResolution.overwrite,
927+
),
928+
).thenAnswer((_) async => []);
929+
when(() => generator.hooks).thenReturn(generatorHooks);
930+
when(() => process.stdout).thenAnswer((_) => const Stream.empty());
931+
const errorMessage =
932+
'Could not start the VM service: localhost:8181 is already in use.';
933+
when(() => process.stderr).thenAnswer(
934+
(_) => Stream.value(utf8.encode(errorMessage)),
935+
);
936+
when(() => process.kill()).thenReturn(true);
937+
when(
938+
() => directoryWatcher.events,
939+
).thenAnswer((_) => StreamController<WatchEvent>().stream);
940+
when(() => sigint.watch()).thenAnswer((_) => const Stream.empty());
941+
command = DevCommand(
942+
logger: logger,
943+
ensureRuntimeCompatibility: (_) {},
944+
directoryWatcher: (_) => directoryWatcher,
945+
generator: (_) async => generator,
946+
exit: (code) => exitCode = code,
947+
startProcess: (
948+
String executable,
949+
List<String> arguments, {
950+
bool runInShell = false,
951+
}) async {
952+
return process;
953+
},
954+
sigint: sigint,
955+
)
956+
..testArgResults = argResults
957+
..testRunProcess = (String executable, List<String> arguments) async {
958+
processRunCalls.add([executable, ...arguments]);
959+
return processResult;
960+
};
961+
command.run().ignore();
962+
963+
await untilCalled(() => process.kill());
964+
expect(
965+
exitCode,
966+
equals(1),
967+
reason: 'Should exit when VM service is already in use.',
968+
);
969+
expect(
970+
processRunCalls,
971+
isEmpty,
972+
reason: 'Should not run the serve process.',
973+
);
974+
verify(() => process.kill()).called(1);
975+
verify(
976+
() => logger.err(
977+
'$errorMessage '
978+
'''Try specifying a different port using the `--dart-vm-service-port` argument when running `dart_frog dev`.''',
979+
),
980+
).called(1);
981+
});
905982
});
906983

907984
group('CachedFile', () {

0 commit comments

Comments
 (0)