Skip to content

Commit 0cda2e8

Browse files
feat(dart_frog_cli): Add dart-vm-port option to dev command (#599)
* feat(dart_frog_cli): Add dart-vm-port option to dev command * test(dart_frog_cli): Add test for --dart-vm-port option of dev command * docs(dart_frog_cli): Add doc for dev command --dart-vm-port option * chore(dart_frog_cli): Apply requested changes * test(dart_frog_cli): Add e2e tests for --dart-vm-port option * test(dart_frog_cli): Update e2e tests for dev command --dart-vm-port option * test(dart_frog_cli): Update tests for dev command * chore(dart_frog_cli): Change dev command option --dart-vm-port to dart-vm-service-port * test(dart_frog_cli): Revert back test tearDownAll since it fails on windows --------- Co-authored-by: Erick <[email protected]>
1 parent 1b898be commit 0cda2e8

File tree

5 files changed

+226
-11
lines changed

5 files changed

+226
-11
lines changed

docs/docs/overview.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ dart_frog dev
5050
By default port `8080` is used. A custom port can be used via the `--port` option.
5151
:::
5252

53+
:::tip
54+
It's also possible to set a custom port for the dart vm service using `--dart-vm-service-port` option.
55+
56+
This is required when trying to run `dart_frog dev` multiple times.
57+
:::
58+
5359
:::caution
5460
Each release of the `dart_frog_cli` supports a specific version range of the `dart_frog` runtime. If the current version of the `dart_frog` runtime is incompatible with the installed `dart_frog_cli` version, an error will be reported and you will need to update your `dart_frog_cli` version or `dart_frog` version accordingly.
5561
:::

packages/dart_frog_cli/e2e/test/dev_test.dart

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,80 @@ void main() {
4747
expect(response.body, 'Route not found');
4848
});
4949
});
50+
51+
group('dart_frog dev when ran multiple times', () {
52+
const projectName1 = 'example1';
53+
const projectName2 = 'example2';
54+
55+
final tempDirectory = Directory.systemTemp.createTempSync();
56+
57+
Process? process1;
58+
Process? process2;
59+
60+
setUp(() async {
61+
await dartFrogCreate(projectName: projectName1, directory: tempDirectory);
62+
await dartFrogCreate(projectName: projectName2, directory: tempDirectory);
63+
});
64+
65+
tearDown(() async {
66+
if (process1 != null) {
67+
killDartFrogServer(process1!.pid).ignore();
68+
}
69+
70+
if (process2 != null) {
71+
killDartFrogServer(process2!.pid).ignore();
72+
}
73+
});
74+
75+
tearDownAll(() {
76+
tempDirectory.delete(recursive: true).ignore();
77+
});
78+
79+
test(
80+
'running two different dart_frog dev command will fail '
81+
'when different dart vm port is not set',
82+
() async {
83+
process1 = await dartFrogDev(
84+
directory: Directory(path.join(tempDirectory.path, projectName1)),
85+
);
86+
87+
try {
88+
await dartFrogDev(
89+
directory: Directory(path.join(tempDirectory.path, projectName2)),
90+
exitOnError: false,
91+
).then((process) => process2 = process);
92+
93+
fail('exception not thrown');
94+
} catch (e) {
95+
expect(
96+
e,
97+
contains(
98+
'''Could not start the VM service: localhost:8181 is already in use.''',
99+
),
100+
);
101+
}
102+
},
103+
);
104+
105+
test(
106+
'runs two different dart_frog dev servers without any issues',
107+
() async {
108+
expect(
109+
dartFrogDev(
110+
directory: Directory(path.join(tempDirectory.path, projectName1)),
111+
).then((process) => process1 = process),
112+
completes,
113+
);
114+
115+
expect(
116+
dartFrogDev(
117+
directory: Directory(path.join(tempDirectory.path, projectName2)),
118+
exitOnError: false,
119+
args: ['--dart-vm-service-port', '9191'],
120+
).then((process) => process2 = process),
121+
completes,
122+
);
123+
},
124+
);
125+
});
50126
}

packages/dart_frog_cli/e2e/test/helpers/dart_frog_dev.dart

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@ import 'dart:async';
22
import 'dart:convert';
33
import 'dart:io';
44

5-
Future<Process> dartFrogDev({required Directory directory}) async {
5+
Future<Process> dartFrogDev({
6+
required Directory directory,
7+
List<String> args = const [],
8+
bool exitOnError = true,
9+
}) async {
610
final completer = Completer<Process>();
711

812
final process = await Process.start(
913
'dart_frog',
10-
['dev'],
14+
['dev', ...args],
1115
workingDirectory: directory.path,
1216
runInShell: true,
1317
);
@@ -29,7 +33,7 @@ Future<Process> dartFrogDev({required Directory directory}) async {
2933
stdoutSubscription.cancel();
3034
stderrSubscription.cancel();
3135
completer.completeError(message);
32-
exit(1);
36+
if (exitOnError) exit(1);
3337
});
3438

3539
return completer.future;

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

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,18 @@ class DevCommand extends DartFrogCommand {
7474
_sigint = sigint ?? io.ProcessSignal.sigint,
7575
_startProcess = startProcess ?? io.Process.start,
7676
_generatorTarget = generatorTarget ?? _defaultGeneratorTarget {
77-
argParser.addOption(
78-
'port',
79-
abbr: 'p',
80-
defaultsTo: '8080',
81-
help: 'Which port number the server should start on.',
82-
);
77+
argParser
78+
..addOption(
79+
'port',
80+
abbr: 'p',
81+
defaultsTo: '8080',
82+
help: 'Which port number the server should start on.',
83+
)
84+
..addOption(
85+
'dart-vm-service-port',
86+
abbr: 'd',
87+
help: 'Which port number the dart vm service should listen on.',
88+
);
8389
}
8490

8591
final void Function(io.Directory) _ensureRuntimeCompatibility;
@@ -111,6 +117,7 @@ class DevCommand extends DartFrogCommand {
111117
var reloading = false;
112118
var hotReloadEnabled = false;
113119
final port = io.Platform.environment['PORT'] ?? results['port'] as String;
120+
final dartVmServicePort = results['dart-vm-service-port'] as String?;
114121
final target = _generatorTarget(logger);
115122
final generator = await _generator(dartFrogDevServerBundle);
116123

@@ -140,13 +147,16 @@ class DevCommand extends DartFrogCommand {
140147
logger.detail('[codegen] reload complete.');
141148
}
142149

150+
final enableVmServiceFlag = '--enable-vm-service'
151+
'${dartVmServicePort == null ? "" : "=$dartVmServicePort"}';
152+
143153
Future<void> serve() async {
144154
logger.detail(
145-
'''[process] dart --enable-vm-service ${path.join('.dart_frog', 'server.dart')}''',
155+
'''[process] dart $enableVmServiceFlag ${path.join('.dart_frog', 'server.dart')}''',
146156
);
147157
final process = await _startProcess(
148158
'dart',
149-
['--enable-vm-service', path.join('.dart_frog', 'server.dart')],
159+
[enableVmServiceFlag, path.join('.dart_frog', 'server.dart')],
150160
runInShell: true,
151161
);
152162

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

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,125 @@ void main() {
527527
).called(1);
528528
});
529529

530+
test('dart vm port can be specified using --dart-vm-service-port',
531+
() async {
532+
late List<String> receivedArgs;
533+
when<dynamic>(() => argResults['dart-vm-service-port'])
534+
.thenReturn('1372');
535+
command.testArgResults = argResults;
536+
final generatorHooks = _MockGeneratorHooks();
537+
when(
538+
() => generatorHooks.preGen(
539+
vars: any(named: 'vars'),
540+
workingDirectory: any(named: 'workingDirectory'),
541+
onVarsChanged: any(named: 'onVarsChanged'),
542+
),
543+
).thenAnswer((invocation) async {
544+
(invocation.namedArguments[const Symbol('onVarsChanged')] as void
545+
Function(Map<String, dynamic> vars))
546+
.call(<String, dynamic>{});
547+
});
548+
when(
549+
() => generator.generate(
550+
any(),
551+
vars: any(named: 'vars'),
552+
fileConflictResolution: FileConflictResolution.overwrite,
553+
),
554+
).thenAnswer((_) async => []);
555+
when(() => generator.hooks).thenReturn(generatorHooks);
556+
when(() => process.stdout).thenAnswer((_) => const Stream.empty());
557+
when(() => process.stderr).thenAnswer((_) => const Stream.empty());
558+
when(() => directoryWatcher.events)
559+
.thenAnswer((_) => const Stream.empty());
560+
561+
command = DevCommand(
562+
logger: logger,
563+
ensureRuntimeCompatibility: (_) {},
564+
directoryWatcher: (_) => directoryWatcher,
565+
generator: (_) async => generator,
566+
startProcess: (
567+
String executable,
568+
List<String> arguments, {
569+
bool runInShell = false,
570+
}) async {
571+
receivedArgs = arguments;
572+
return process;
573+
},
574+
sigint: sigint,
575+
)..testArgResults = argResults;
576+
final exitCode = await command.run();
577+
expect(exitCode, equals(ExitCode.success.code));
578+
expect(receivedArgs[0], equals('--enable-vm-service=1372'));
579+
verify(
580+
() => generatorHooks.preGen(
581+
vars: <String, dynamic>{'port': '8080'},
582+
workingDirectory: any(named: 'workingDirectory'),
583+
onVarsChanged: any(named: 'onVarsChanged'),
584+
),
585+
).called(1);
586+
verifyNever(process.kill);
587+
});
588+
589+
test(
590+
'when dart vm port not specified, --enable-vm-service option should '
591+
'be called without any port number value',
592+
() async {
593+
late List<String> receivedArgs;
594+
command.testArgResults = argResults;
595+
final generatorHooks = _MockGeneratorHooks();
596+
when(
597+
() => generatorHooks.preGen(
598+
vars: any(named: 'vars'),
599+
workingDirectory: any(named: 'workingDirectory'),
600+
onVarsChanged: any(named: 'onVarsChanged'),
601+
),
602+
).thenAnswer((invocation) async {
603+
(invocation.namedArguments[const Symbol('onVarsChanged')] as void
604+
Function(Map<String, dynamic> vars))
605+
.call(<String, dynamic>{});
606+
});
607+
when(
608+
() => generator.generate(
609+
any(),
610+
vars: any(named: 'vars'),
611+
fileConflictResolution: FileConflictResolution.overwrite,
612+
),
613+
).thenAnswer((_) async => []);
614+
when(() => generator.hooks).thenReturn(generatorHooks);
615+
when(() => process.stdout).thenAnswer((_) => const Stream.empty());
616+
when(() => process.stderr).thenAnswer((_) => const Stream.empty());
617+
when(() => directoryWatcher.events)
618+
.thenAnswer((_) => const Stream.empty());
619+
620+
command = DevCommand(
621+
logger: logger,
622+
ensureRuntimeCompatibility: (_) {},
623+
directoryWatcher: (_) => directoryWatcher,
624+
generator: (_) async => generator,
625+
startProcess: (
626+
String executable,
627+
List<String> arguments, {
628+
bool runInShell = false,
629+
}) async {
630+
receivedArgs = arguments;
631+
return process;
632+
},
633+
sigint: sigint,
634+
)..testArgResults = argResults;
635+
final exitCode = await command.run();
636+
expect(exitCode, equals(ExitCode.success.code));
637+
expect(receivedArgs[0], equals('--enable-vm-service'));
638+
verify(
639+
() => generatorHooks.preGen(
640+
vars: <String, dynamic>{'port': '8080'},
641+
workingDirectory: any(named: 'workingDirectory'),
642+
onVarsChanged: any(named: 'onVarsChanged'),
643+
),
644+
).called(1);
645+
verifyNever(process.kill);
646+
},
647+
);
648+
530649
test('kills all child processes when sigint received on windows', () async {
531650
final generatorHooks = _MockGeneratorHooks();
532651
final processRunCalls = <List<String>>[];

0 commit comments

Comments
 (0)