diff --git a/packages/shorebird_cli/lib/src/command_runner.dart b/packages/shorebird_cli/lib/src/command_runner.dart index a6cbdf00..092746c1 100644 --- a/packages/shorebird_cli/lib/src/command_runner.dart +++ b/packages/shorebird_cli/lib/src/command_runner.dart @@ -1,15 +1,24 @@ +import 'dart:io'; + import 'package:args/args.dart'; import 'package:args/command_runner.dart'; import 'package:cli_completion/cli_completion.dart'; import 'package:mason_logger/mason_logger.dart'; import 'package:pub_updater/pub_updater.dart'; import 'package:shorebird_cli/src/commands/commands.dart'; +import 'package:shorebird_cli/src/flutter_engine_revision.dart'; import 'package:shorebird_cli/src/version.dart'; const executableName = 'shorebird'; const packageName = 'shorebird_cli'; const description = 'The shorebird command-line tool'; +typedef RunProcess = Future Function( + String executable, + List arguments, { + bool runInShell, +}); + /// {@template shorebird_cli_command_runner} /// A [CommandRunner] for the CLI. /// @@ -19,9 +28,13 @@ const description = 'The shorebird command-line tool'; /// {@endtemplate} class ShorebirdCliCommandRunner extends CompletionCommandRunner { /// {@macro shorebird_cli_command_runner} - ShorebirdCliCommandRunner({PubUpdater? pubUpdater, Logger? logger}) - : _logger = logger ?? Logger(), + ShorebirdCliCommandRunner({ + PubUpdater? pubUpdater, + Logger? logger, + RunProcess? runProcess, + }) : _logger = logger ?? Logger(), _pubUpdater = pubUpdater ?? PubUpdater(), + _runProcess = runProcess ?? Process.run, super(executableName, description) { argParser ..addFlag( @@ -50,6 +63,7 @@ class ShorebirdCliCommandRunner extends CompletionCommandRunner { final Logger _logger; final PubUpdater _pubUpdater; + final RunProcess _runProcess; @override Future run(Iterable args) async { @@ -58,6 +72,30 @@ class ShorebirdCliCommandRunner extends CompletionCommandRunner { if (topLevelResults['verbose'] == true) { _logger.level = Level.verbose; } + + try { + final flutterEngineRevision = await _getFlutterEngineRevision(); + if (flutterEngineRevision != requiredFlutterEngineRevision) { + _logger.err( + ''' +Shorebird only works with the latest stable channel at this time. + +To use the latest stable channel, run: + flutter channel stable + flutter upgrade + +If you believe you're already on the latest stable channel, please ask on Discord, we're happy to help! + +Required engine revision: "$requiredFlutterEngineRevision" +Detected engine revision: "$flutterEngineRevision"''', + ); + return ExitCode.software.code; + } + } catch (error) { + _logger.err('Failed to get Flutter engine revision.\n$error'); + return ExitCode.software.code; + } + return await runCommand(topLevelResults) ?? ExitCode.success.code; } on FormatException catch (e, stackTrace) { // On format errors, show the commands error message, root usage and @@ -104,6 +142,23 @@ class ShorebirdCliCommandRunner extends CompletionCommandRunner { return exitCode; } + Future _getFlutterEngineRevision() async { + final result = await _runProcess( + 'flutter', + ['--version'], + runInShell: true, + ); + if (result.exitCode != 0) throw Exception('${result.stderr}'); + + final output = result.stdout as String; + final regexp = RegExp(r'Engine • revision (.*?$)', multiLine: true); + final flutterEngineRevision = regexp.firstMatch(output)?.group(1); + if (flutterEngineRevision == null) { + throw Exception('Unable to determine the Flutter engine revision.'); + } + return flutterEngineRevision; + } + /// Checks if the current version (set by the build runner on the /// version.dart file) is the most recent one. If not, show a prompt to the /// user. diff --git a/packages/shorebird_cli/lib/src/flutter_engine_revision.dart b/packages/shorebird_cli/lib/src/flutter_engine_revision.dart new file mode 100644 index 00000000..95b74a95 --- /dev/null +++ b/packages/shorebird_cli/lib/src/flutter_engine_revision.dart @@ -0,0 +1,3 @@ +/// The hash of the Flutter engine required by Shorebird. +/// Currently, Shorebird only supports the latest stable Flutter channel. +const requiredFlutterEngineRevision = '1837b5be5f'; diff --git a/packages/shorebird_cli/test/src/command_runner_test.dart b/packages/shorebird_cli/test/src/command_runner_test.dart index a190b6cc..9e1d24e5 100644 --- a/packages/shorebird_cli/test/src/command_runner_test.dart +++ b/packages/shorebird_cli/test/src/command_runner_test.dart @@ -41,13 +41,82 @@ void main() { processResult = _MockProcessResult(); when(() => processResult.exitCode).thenReturn(ExitCode.success.code); + when(() => processResult.stdout).thenReturn( + ''' +Flutter 3.7.7 • channel stable • git@github.com:flutter/flutter.git +Framework • revision 2ad6cd72c0 (12 days ago) • 2023-03-08 09:41:59 -0800 +Engine • revision 1837b5be5f +Tools • Dart 2.19.4 • DevTools 2.20.1 +''', + ); commandRunner = ShorebirdCliCommandRunner( logger: logger, pubUpdater: pubUpdater, + runProcess: ( + String executable, + List arguments, { + bool runInShell = false, + }) async { + return processResult; + }, ); }); + test('exits when Flutter is not installed', () async { + const error = 'oops something went wrong'; + when(() => processResult.exitCode).thenReturn(1); + when(() => processResult.stderr).thenReturn(error); + + final result = await commandRunner.run(['--version']); + expect(result, equals(ExitCode.software.code)); + verify(() => logger.err(any(that: contains(error)))).called(1); + }); + + test('exits when unable to detect the Flutter engine revision', () async { + when(() => processResult.exitCode).thenReturn(0); + when(() => processResult.stdout).thenReturn( + ''' +Flutter 3.7.7 • channel stable • +Framework • revision 2ad6cd72c0 (12 days ago) • 2023-03-08 09:41:59 -0800 +Tools • Dart 2.19.4 • DevTools 2.20.1 +''', + ); + + final result = await commandRunner.run(['--version']); + expect(result, equals(ExitCode.software.code)); + verify( + () => logger.err( + any( + that: contains('Unable to determine the Flutter engine revision.'), + ), + ), + ).called(1); + }); + + test('exits when there is an incompatible Flutter engine', () async { + when(() => processResult.stdout).thenReturn( + ''' +Flutter 3.7.7 • channel stable • +Framework • revision 2ad6cd72c0 (12 days ago) • 2023-03-08 09:41:59 -0800 +Engine • revision 639e313f99 +Tools • Dart 2.19.4 • DevTools 2.20.1 +''', + ); + + final result = await commandRunner.run(['--version']); + expect(result, equals(ExitCode.software.code)); + verify( + () => logger.err( + any( + that: contains( + '''Shorebird only works with the latest stable channel at this time.''', + ), + ), + ), + ).called(1); + }); + test('shows update message when newer version exists', () async { when( () => pubUpdater.getLatestVersion(any()),