Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(dart_frog_cli): add host option to dev #1114

Merged
merged 8 commits into from
Nov 1, 2023
21 changes: 21 additions & 0 deletions packages/dart_frog_cli/lib/src/commands/dev/dev.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ class DevCommand extends DartFrogCommand {
abbr: 'd',
defaultsTo: _defaultDartVmServicePort,
help: 'Which port number the dart vm service should listen on.',
)
..addOption(
'hostname',
abbr: 'H',
help: 'Which host name the server should bind to.',
defaultsTo: 'localhost',
);
}

Expand Down Expand Up @@ -106,15 +112,30 @@ class DevCommand extends DartFrogCommand {
_ensureRuntimeCompatibility(cwd);

final port = io.Platform.environment['PORT'] ?? results['port'] as String;

final dartVmServicePort = (results['dart-vm-service-port'] as String?) ??
_defaultDartVmServicePort;
final generator = await _generator(dartFrogDevServerBundle);

final hostname = results['hostname'] as String?;

io.InternetAddress? ip;
if (hostname != null && hostname != 'localhost') {
ip = io.InternetAddress.tryParse(hostname);
if (ip == null) {
logger.err(
'Invalid hostname "$hostname": must be a valid IPv4 or IPv6 address.',
);
return ExitCode.software.code;
}
}

_devServerRunner = _devServerRunnerBuilder(
devServerBundleGenerator: generator,
logger: logger,
workingDirectory: cwd,
port: port,
address: ip,
dartVmServicePort: dartVmServicePort,
onHotReloadEnabled: _startListeningForHelpers,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ class DevServerDomain extends DomainBase {

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

final hostname = request.getParam<String?>('hostname');

final applicationId = getId();

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

final devServerBundleGenerator = await _generator(dartFrogDevServerBundle);

InternetAddress? ip;
if (hostname != null) {
ip = InternetAddress.tryParse(hostname);
if (ip == null) {
throw DartFrogDaemonMalformedMessageException(
'invalid hostname "$hostname": must be a valid IPv4 or IPv6 address.',
);
}
}

final logger = DaemonLogger(
domain: domainName,
params: {
Expand All @@ -71,6 +83,7 @@ class DevServerDomain extends DomainBase {
final devServerRunner = _devServerRunnerBuilder(
logger: logger,
port: '$port',
address: ip,
devServerBundleGenerator: devServerBundleGenerator,
dartVmServicePort: '$dartVmServicePort',
workingDirectory: Directory(workingDirectory),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ final _dartVmServiceAlreadyInUseErrorRegex = RegExp(
typedef DevServerRunnerBuilder = DevServerRunner Function({
required Logger logger,
required String port,
required io.InternetAddress? address,
required MasonGenerator devServerBundleGenerator,
required String dartVmServicePort,
required io.Directory workingDirectory,
Expand All @@ -65,6 +66,7 @@ class DevServerRunner {
DevServerRunner({
required this.logger,
required this.port,
required this.address,
required this.devServerBundleGenerator,
required this.dartVmServicePort,
required this.workingDirectory,
Expand Down Expand Up @@ -95,6 +97,12 @@ class DevServerRunner {
/// Which port number the server should start on.
final String port;

/// Which host the server should start on.
/// Which host the server should start on.
///
/// It will default to localhost if empty.
renancaraujo marked this conversation as resolved.
Show resolved Hide resolved
final io.InternetAddress? address;

/// Which port number the dart vm service should listen on.
final String dartVmServicePort;

Expand Down Expand Up @@ -142,7 +150,12 @@ class DevServerRunner {

Future<void> _codegen() async {
logger.detail('[codegen] running pre-gen...');
var vars = <String, dynamic>{'port': port};
final address = this.address;
logger.detail('Starting development server on host ${address?.address}');
var vars = <String, dynamic>{
'port': port,
if (address != null) 'host': address.address,
};
await devServerBundleGenerator.hooks.preGen(
vars: vars,
workingDirectory: workingDirectory.path,
Expand Down Expand Up @@ -322,7 +335,9 @@ class DevServerRunner {
await _codegen();
await serve();

final localhost = link(uri: Uri.parse('http://localhost:$port'));
final hostAddress = address?.address ?? 'localhost';

final localhost = link(uri: Uri.parse('http://$hostAddress:$port'));
progress.complete('Running on $localhost');

final cwdPath = workingDirectory.path;
Expand Down
52 changes: 52 additions & 0 deletions packages/dart_frog_cli/test/src/commands/dev/dev_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ void main() {
devServerRunnerBuilder: ({
required logger,
required port,
required address,
required devServerBundleGenerator,
required dartVmServicePort,
required workingDirectory,
Expand All @@ -78,13 +79,15 @@ void main() {
(_) => Future.value(ExitCode.success),
);

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

final cwd = Directory.systemTemp;

late String givenPort;
late String givenDartVmServicePort;
late InternetAddress? givenAddress;
late MasonGenerator givenDevServerBundleGenerator;
late Directory givenWorkingDirectory;
late void Function()? givenOnHotReloadEnabled;
Expand All @@ -95,12 +98,14 @@ void main() {
devServerRunnerBuilder: ({
required logger,
required port,
required address,
required devServerBundleGenerator,
required dartVmServicePort,
required workingDirectory,
void Function()? onHotReloadEnabled,
}) {
givenPort = port;
givenAddress = address;
givenDartVmServicePort = dartVmServicePort;
givenDevServerBundleGenerator = devServerBundleGenerator;
givenWorkingDirectory = workingDirectory;
Expand All @@ -118,6 +123,7 @@ void main() {
verify(() => runner.start()).called(1);

expect(givenPort, equals('1234'));
expect(givenAddress, InternetAddress.tryParse('192.168.1.2'));
expect(givenDartVmServicePort, equals('5678'));
expect(givenDevServerBundleGenerator, same(generator));
expect(givenWorkingDirectory, same(cwd));
Expand All @@ -131,6 +137,7 @@ void main() {
devServerRunnerBuilder: ({
required logger,
required port,
required address,
required devServerBundleGenerator,
required dartVmServicePort,
required workingDirectory,
Expand Down Expand Up @@ -161,6 +168,7 @@ void main() {
devServerRunnerBuilder: ({
required logger,
required port,
required address,
required devServerBundleGenerator,
required dartVmServicePort,
required workingDirectory,
Expand All @@ -181,6 +189,49 @@ void main() {
verify(() => logger.err('oops')).called(1);
});

test('fails if hostname is invalid', () async {
when(() => runner.start()).thenAnswer((_) => Future.value());
when(() => runner.exitCode).thenAnswer(
(_) => Future.value(ExitCode.success),
);

when(() => argResults['hostname']).thenReturn('ticarica');
renancaraujo marked this conversation as resolved.
Show resolved Hide resolved
when(() => argResults['port']).thenReturn('1234');
when(() => argResults['dart-vm-service-port']).thenReturn('5678');

final cwd = Directory.systemTemp;

final command = DevCommand(
generator: (_) async => generator,
ensureRuntimeCompatibility: (_) {},
devServerRunnerBuilder: ({
required logger,
required port,
required address,
required devServerBundleGenerator,
required dartVmServicePort,
required workingDirectory,
void Function()? onHotReloadEnabled,
}) {
return runner;
},
logger: logger,
)
..testStdin = stdin
..testArgResults = argResults
..testCwd = cwd;

await expectLater(command.run(), completion(ExitCode.software.code));
renancaraujo marked this conversation as resolved.
Show resolved Hide resolved

verify(
() => logger.err(
'Invalid hostname "ticarica": must be a valid IPv4 or IPv6 address.',
),
).called(1);

verifyNever(() => runner.start());
});

group('listening to stdin', () {
late Stdin stdin;
late StreamController<List<int>> stdinController;
Expand Down Expand Up @@ -228,6 +279,7 @@ void main() {
devServerRunnerBuilder: ({
required logger,
required port,
required address,
required devServerBundleGenerator,
required dartVmServicePort,
required workingDirectory,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ void main() {
devServerRunnerBuilder: ({
required logger,
required port,
required address,
required devServerBundleGenerator,
required dartVmServicePort,
required workingDirectory,
Expand All @@ -60,6 +61,7 @@ void main() {
test('starts application', () async {
late Logger passedLogger;
late String passedPort;
late InternetAddress? passedAddress;
late MasonGenerator passedDevServerBundleGenerator;
late String passedDartVmServicePort;
late Directory passedWorkingDirectory;
Expand All @@ -70,13 +72,15 @@ void main() {
devServerRunnerBuilder: ({
required logger,
required port,
required address,
required devServerBundleGenerator,
required dartVmServicePort,
required workingDirectory,
void Function()? onHotReloadEnabled,
}) {
passedLogger = logger;
passedPort = port;
passedAddress = address;
passedDevServerBundleGenerator = devServerBundleGenerator;
passedDartVmServicePort = dartVmServicePort;
passedWorkingDirectory = workingDirectory;
Expand All @@ -93,6 +97,7 @@ void main() {
params: {
'workingDirectory': '/',
'port': 3000,
'hostname': '192.168.1.2',
'dartVmServicePort': 3001,
},
),
Expand All @@ -107,6 +112,7 @@ void main() {

expect(passedLogger, isA<DaemonLogger>());
expect(passedPort, equals('3000'));
expect(passedAddress, InternetAddress.tryParse('192.168.1.2'));
expect(passedDevServerBundleGenerator, same(generator));
expect(passedDartVmServicePort, equals('3001'));
expect(passedWorkingDirectory.path, equals('/'));
Expand Down Expand Up @@ -261,6 +267,33 @@ void main() {
),
);
});

test('hostname', () async {
expect(
await domain.handleRequest(
const DaemonRequest(
id: '12',
domain: 'dev_server',
method: 'start',
params: {
'workingDirectory': '/',
'port': 4040,
'dartVmServicePort': 4041,
'hostname': 'lol',
},
),
),
equals(
const DaemonResponse.error(
id: '12',
error: {
'message': 'Malformed message, invalid hostname "lol": '
'must be a valid IPv4 or IPv6 address.',
},
),
),
);
});
});

test('on dev server throw', () async {
Expand Down Expand Up @@ -593,6 +626,7 @@ void main() {
devServerRunnerBuilder: ({
required logger,
required port,
required address,
required devServerBundleGenerator,
required dartVmServicePort,
required workingDirectory,
Expand Down
Loading