Skip to content

Commit 94dd87f

Browse files
authored
feat(dart_frog_cli): add host option to dev (#1114)
1 parent 6e87c6e commit 94dd87f

File tree

6 files changed

+211
-2
lines changed

6 files changed

+211
-2
lines changed

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ class DevCommand extends DartFrogCommand {
3737
abbr: 'd',
3838
defaultsTo: _defaultDartVmServicePort,
3939
help: 'Which port number the dart vm service should listen on.',
40+
)
41+
..addOption(
42+
'hostname',
43+
abbr: 'H',
44+
help: 'Which host name the server should bind to.',
45+
defaultsTo: 'localhost',
4046
);
4147
}
4248

@@ -106,15 +112,30 @@ class DevCommand extends DartFrogCommand {
106112
_ensureRuntimeCompatibility(cwd);
107113

108114
final port = io.Platform.environment['PORT'] ?? results['port'] as String;
115+
109116
final dartVmServicePort = (results['dart-vm-service-port'] as String?) ??
110117
_defaultDartVmServicePort;
111118
final generator = await _generator(dartFrogDevServerBundle);
112119

120+
final hostname = results['hostname'] as String?;
121+
122+
io.InternetAddress? ip;
123+
if (hostname != null && hostname != 'localhost') {
124+
ip = io.InternetAddress.tryParse(hostname);
125+
if (ip == null) {
126+
logger.err(
127+
'Invalid hostname "$hostname": must be a valid IPv4 or IPv6 address.',
128+
);
129+
return ExitCode.software.code;
130+
}
131+
}
132+
113133
_devServerRunner = _devServerRunnerBuilder(
114134
devServerBundleGenerator: generator,
115135
logger: logger,
116136
workingDirectory: cwd,
117137
port: port,
138+
address: ip,
118139
dartVmServicePort: dartVmServicePort,
119140
onHotReloadEnabled: _startListeningForHelpers,
120141
);

packages/dart_frog_cli/lib/src/daemon/domain/dev_server_domain.dart

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ class DevServerDomain extends DomainBase {
4242

4343
final dartVmServicePort = request.getParam<int>('dartVmServicePort');
4444

45+
final hostname = request.getParam<String?>('hostname');
46+
4547
final applicationId = getId();
4648

4749
daemon.sendEvent(
@@ -57,6 +59,16 @@ class DevServerDomain extends DomainBase {
5759

5860
final devServerBundleGenerator = await _generator(dartFrogDevServerBundle);
5961

62+
InternetAddress? ip;
63+
if (hostname != null) {
64+
ip = InternetAddress.tryParse(hostname);
65+
if (ip == null) {
66+
throw DartFrogDaemonMalformedMessageException(
67+
'invalid hostname "$hostname": must be a valid IPv4 or IPv6 address.',
68+
);
69+
}
70+
}
71+
6072
final logger = DaemonLogger(
6173
domain: domainName,
6274
params: {
@@ -71,6 +83,7 @@ class DevServerDomain extends DomainBase {
7183
final devServerRunner = _devServerRunnerBuilder(
7284
logger: logger,
7385
port: '$port',
86+
address: ip,
7487
devServerBundleGenerator: devServerBundleGenerator,
7588
dartVmServicePort: '$dartVmServicePort',
7689
workingDirectory: Directory(workingDirectory),

packages/dart_frog_cli/lib/src/dev_server_runner/dev_server_runner.dart

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ final _dartVmServiceAlreadyInUseErrorRegex = RegExp(
4141
typedef DevServerRunnerBuilder = DevServerRunner Function({
4242
required Logger logger,
4343
required String port,
44+
required io.InternetAddress? address,
4445
required MasonGenerator devServerBundleGenerator,
4546
required String dartVmServicePort,
4647
required io.Directory workingDirectory,
@@ -65,6 +66,7 @@ class DevServerRunner {
6566
DevServerRunner({
6667
required this.logger,
6768
required this.port,
69+
required this.address,
6870
required this.devServerBundleGenerator,
6971
required this.dartVmServicePort,
7072
required this.workingDirectory,
@@ -95,6 +97,12 @@ class DevServerRunner {
9597
/// Which port number the server should start on.
9698
final String port;
9799

100+
/// Which host the server should start on.
101+
/// Which host the server should start on.
102+
///
103+
/// It will default to localhost if empty.
104+
final io.InternetAddress? address;
105+
98106
/// Which port number the dart vm service should listen on.
99107
final String dartVmServicePort;
100108

@@ -142,7 +150,12 @@ class DevServerRunner {
142150

143151
Future<void> _codegen() async {
144152
logger.detail('[codegen] running pre-gen...');
145-
var vars = <String, dynamic>{'port': port};
153+
final address = this.address;
154+
logger.detail('Starting development server on host ${address?.address}');
155+
var vars = <String, dynamic>{
156+
'port': port,
157+
if (address != null) 'host': address.address,
158+
};
146159
await devServerBundleGenerator.hooks.preGen(
147160
vars: vars,
148161
workingDirectory: workingDirectory.path,
@@ -322,7 +335,9 @@ class DevServerRunner {
322335
await _codegen();
323336
await serve();
324337

325-
final localhost = link(uri: Uri.parse('http://localhost:$port'));
338+
final hostAddress = address?.address ?? 'localhost';
339+
340+
final localhost = link(uri: Uri.parse('http://$hostAddress:$port'));
326341
progress.complete('Running on $localhost');
327342

328343
final cwdPath = workingDirectory.path;

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

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ void main() {
5454
devServerRunnerBuilder: ({
5555
required logger,
5656
required port,
57+
required address,
5758
required devServerBundleGenerator,
5859
required dartVmServicePort,
5960
required workingDirectory,
@@ -78,13 +79,15 @@ void main() {
7879
(_) => Future.value(ExitCode.success),
7980
);
8081

82+
when(() => argResults['hostname']).thenReturn('192.168.1.2');
8183
when(() => argResults['port']).thenReturn('1234');
8284
when(() => argResults['dart-vm-service-port']).thenReturn('5678');
8385

8486
final cwd = Directory.systemTemp;
8587

8688
late String givenPort;
8789
late String givenDartVmServicePort;
90+
late InternetAddress? givenAddress;
8891
late MasonGenerator givenDevServerBundleGenerator;
8992
late Directory givenWorkingDirectory;
9093
late void Function()? givenOnHotReloadEnabled;
@@ -95,12 +98,14 @@ void main() {
9598
devServerRunnerBuilder: ({
9699
required logger,
97100
required port,
101+
required address,
98102
required devServerBundleGenerator,
99103
required dartVmServicePort,
100104
required workingDirectory,
101105
void Function()? onHotReloadEnabled,
102106
}) {
103107
givenPort = port;
108+
givenAddress = address;
104109
givenDartVmServicePort = dartVmServicePort;
105110
givenDevServerBundleGenerator = devServerBundleGenerator;
106111
givenWorkingDirectory = workingDirectory;
@@ -118,6 +123,7 @@ void main() {
118123
verify(() => runner.start()).called(1);
119124

120125
expect(givenPort, equals('1234'));
126+
expect(givenAddress, InternetAddress.tryParse('192.168.1.2'));
121127
expect(givenDartVmServicePort, equals('5678'));
122128
expect(givenDevServerBundleGenerator, same(generator));
123129
expect(givenWorkingDirectory, same(cwd));
@@ -131,6 +137,7 @@ void main() {
131137
devServerRunnerBuilder: ({
132138
required logger,
133139
required port,
140+
required address,
134141
required devServerBundleGenerator,
135142
required dartVmServicePort,
136143
required workingDirectory,
@@ -161,6 +168,7 @@ void main() {
161168
devServerRunnerBuilder: ({
162169
required logger,
163170
required port,
171+
required address,
164172
required devServerBundleGenerator,
165173
required dartVmServicePort,
166174
required workingDirectory,
@@ -181,6 +189,49 @@ void main() {
181189
verify(() => logger.err('oops')).called(1);
182190
});
183191

192+
test('fails if hostname is invalid', () async {
193+
when(() => runner.start()).thenAnswer((_) => Future.value());
194+
when(() => runner.exitCode).thenAnswer(
195+
(_) => Future.value(ExitCode.success),
196+
);
197+
198+
when(() => argResults['hostname']).thenReturn('ticarica');
199+
when(() => argResults['port']).thenReturn('1234');
200+
when(() => argResults['dart-vm-service-port']).thenReturn('5678');
201+
202+
final cwd = Directory.systemTemp;
203+
204+
final command = DevCommand(
205+
generator: (_) async => generator,
206+
ensureRuntimeCompatibility: (_) {},
207+
devServerRunnerBuilder: ({
208+
required logger,
209+
required port,
210+
required address,
211+
required devServerBundleGenerator,
212+
required dartVmServicePort,
213+
required workingDirectory,
214+
void Function()? onHotReloadEnabled,
215+
}) {
216+
return runner;
217+
},
218+
logger: logger,
219+
)
220+
..testStdin = stdin
221+
..testArgResults = argResults
222+
..testCwd = cwd;
223+
224+
await expectLater(command.run(), completion(ExitCode.software.code));
225+
226+
verify(
227+
() => logger.err(
228+
'Invalid hostname "ticarica": must be a valid IPv4 or IPv6 address.',
229+
),
230+
).called(1);
231+
232+
verifyNever(() => runner.start());
233+
});
234+
184235
group('listening to stdin', () {
185236
late Stdin stdin;
186237
late StreamController<List<int>> stdinController;
@@ -228,6 +279,7 @@ void main() {
228279
devServerRunnerBuilder: ({
229280
required logger,
230281
required port,
282+
required address,
231283
required devServerBundleGenerator,
232284
required dartVmServicePort,
233285
required workingDirectory,

packages/dart_frog_cli/test/src/daemon/domain/dev_server_domain_test.dart

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ void main() {
3838
devServerRunnerBuilder: ({
3939
required logger,
4040
required port,
41+
required address,
4142
required devServerBundleGenerator,
4243
required dartVmServicePort,
4344
required workingDirectory,
@@ -60,6 +61,7 @@ void main() {
6061
test('starts application', () async {
6162
late Logger passedLogger;
6263
late String passedPort;
64+
late InternetAddress? passedAddress;
6365
late MasonGenerator passedDevServerBundleGenerator;
6466
late String passedDartVmServicePort;
6567
late Directory passedWorkingDirectory;
@@ -70,13 +72,15 @@ void main() {
7072
devServerRunnerBuilder: ({
7173
required logger,
7274
required port,
75+
required address,
7376
required devServerBundleGenerator,
7477
required dartVmServicePort,
7578
required workingDirectory,
7679
void Function()? onHotReloadEnabled,
7780
}) {
7881
passedLogger = logger;
7982
passedPort = port;
83+
passedAddress = address;
8084
passedDevServerBundleGenerator = devServerBundleGenerator;
8185
passedDartVmServicePort = dartVmServicePort;
8286
passedWorkingDirectory = workingDirectory;
@@ -93,6 +97,7 @@ void main() {
9397
params: {
9498
'workingDirectory': '/',
9599
'port': 3000,
100+
'hostname': '192.168.1.2',
96101
'dartVmServicePort': 3001,
97102
},
98103
),
@@ -107,6 +112,7 @@ void main() {
107112

108113
expect(passedLogger, isA<DaemonLogger>());
109114
expect(passedPort, equals('3000'));
115+
expect(passedAddress, InternetAddress.tryParse('192.168.1.2'));
110116
expect(passedDevServerBundleGenerator, same(generator));
111117
expect(passedDartVmServicePort, equals('3001'));
112118
expect(passedWorkingDirectory.path, equals('/'));
@@ -261,6 +267,33 @@ void main() {
261267
),
262268
);
263269
});
270+
271+
test('hostname', () async {
272+
expect(
273+
await domain.handleRequest(
274+
const DaemonRequest(
275+
id: '12',
276+
domain: 'dev_server',
277+
method: 'start',
278+
params: {
279+
'workingDirectory': '/',
280+
'port': 4040,
281+
'dartVmServicePort': 4041,
282+
'hostname': 'lol',
283+
},
284+
),
285+
),
286+
equals(
287+
const DaemonResponse.error(
288+
id: '12',
289+
error: {
290+
'message': 'Malformed message, invalid hostname "lol": '
291+
'must be a valid IPv4 or IPv6 address.',
292+
},
293+
),
294+
),
295+
);
296+
});
264297
});
265298

266299
test('on dev server throw', () async {
@@ -593,6 +626,7 @@ void main() {
593626
devServerRunnerBuilder: ({
594627
required logger,
595628
required port,
629+
required address,
596630
required devServerBundleGenerator,
597631
required dartVmServicePort,
598632
required workingDirectory,

0 commit comments

Comments
 (0)