From 8f52e84792a206cf22db7699a15b197afd316943 Mon Sep 17 00:00:00 2001 From: Felix Angelov <felix@shorebird.dev> Date: Wed, 9 Aug 2023 10:30:29 -0500 Subject: [PATCH] feat(shorebird_cli): add `getVersion` to `ShorebirdFlutter` (#1073) --- .../flutter_versions_use_command.dart | 8 +- .../lib/src/shorebird_flutter.dart | 26 ++++++ .../lib/src/shorebird_version.dart | 2 +- .../flutter_versions_use_command_test.dart | 8 +- .../test/src/shorebird_flutter_test.dart | 89 +++++++++++++++---- 5 files changed, 114 insertions(+), 19 deletions(-) diff --git a/packages/shorebird_cli/lib/src/commands/flutter/versions/flutter_versions_use_command.dart b/packages/shorebird_cli/lib/src/commands/flutter/versions/flutter_versions_use_command.dart index dd143a469..e23599e04 100644 --- a/packages/shorebird_cli/lib/src/commands/flutter/versions/flutter_versions_use_command.dart +++ b/packages/shorebird_cli/lib/src/commands/flutter/versions/flutter_versions_use_command.dart @@ -51,8 +51,14 @@ Usage: shorebird flutter versions use <version>'''); } if (!versions.contains(version)) { + final openIssueLink = link( + uri: Uri.parse( + 'https://github.com/shorebirdtech/shorebird/issues/new?assignees=&labels=feature&projects=&template=feature_request.md&title=feat%3A+', + ), + message: 'open an issue', + ); logger.err(''' -Version $version not found. +Version $version not found. Please $openIssueLink to request a new version. Use `shorebird flutter versions list` to list available versions.'''); return ExitCode.software.code; } diff --git a/packages/shorebird_cli/lib/src/shorebird_flutter.dart b/packages/shorebird_cli/lib/src/shorebird_flutter.dart index f69042cab..b65c2fa66 100644 --- a/packages/shorebird_cli/lib/src/shorebird_flutter.dart +++ b/packages/shorebird_cli/lib/src/shorebird_flutter.dart @@ -4,6 +4,7 @@ import 'dart:io'; import 'package:path/path.dart' as p; import 'package:scoped/scoped.dart'; import 'package:shorebird_cli/src/git.dart'; +import 'package:shorebird_cli/src/process.dart'; import 'package:shorebird_cli/src/shorebird_env.dart'; /// A reference to a [ShorebirdFlutter] instance. @@ -19,6 +20,7 @@ class ShorebirdFlutter { /// {@macro shorebird_flutter} const ShorebirdFlutter(); + static const executable = 'flutter'; static const String flutterGitUrl = 'https://github.com/shorebirdtech/flutter.git'; @@ -62,6 +64,30 @@ class ShorebirdFlutter { return status.isEmpty; } + /// Returns the current Shorebird Flutter version. + /// Throws a [ProcessException] if the version check fails. + /// Returns `null` if the version check succeeds but the version cannot be + /// parsed. + Future<String?> getVersion() async { + const args = ['--version']; + final result = await process.run(executable, args, runInShell: true); + + if (result.exitCode != 0) { + throw ProcessException( + executable, + args, + '${result.stderr}', + result.exitCode, + ); + } + + final output = result.stdout.toString(); + final flutterVersionRegex = RegExp(r'Flutter (\d+.\d+.\d+)'); + final match = flutterVersionRegex.firstMatch(output); + + return match?.group(1); + } + Future<List<String>> getVersions({String? revision}) async { final result = await git.forEachRef( format: '%(refname:short)', diff --git a/packages/shorebird_cli/lib/src/shorebird_version.dart b/packages/shorebird_cli/lib/src/shorebird_version.dart index fea9e054e..b08cb15da 100644 --- a/packages/shorebird_cli/lib/src/shorebird_version.dart +++ b/packages/shorebird_cli/lib/src/shorebird_version.dart @@ -10,7 +10,7 @@ final shorebirdVersionRef = create(ShorebirdVersion.new); /// The [ShorebirdVersion] instance available in the current zone. ShorebirdVersion get shorebirdVersion => read(shorebirdVersionRef); -/// {@template shorebird_version_manager} +/// {@template shorebird_version} /// Provides information about installed and available versions of Shorebird. /// {@endtemplate} class ShorebirdVersion { diff --git a/packages/shorebird_cli/test/src/commands/flutter/versions/flutter_versions_use_command_test.dart b/packages/shorebird_cli/test/src/commands/flutter/versions/flutter_versions_use_command_test.dart index 3583f7b75..1b29f3b24 100644 --- a/packages/shorebird_cli/test/src/commands/flutter/versions/flutter_versions_use_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/flutter/versions/flutter_versions_use_command_test.dart @@ -106,11 +106,17 @@ Usage: shorebird flutter versions use <version>'''), runWithOverrides(command.run), completion(equals(ExitCode.software.code)), ); + final openIssueLink = link( + uri: Uri.parse( + 'https://github.com/shorebirdtech/shorebird/issues/new?assignees=&labels=feature&projects=&template=feature_request.md&title=feat%3A+', + ), + message: 'open an issue', + ); verifyInOrder([ () => logger.progress('Fetching Flutter versions'), () => progress.complete(), () => logger.err(''' -Version $version not found. +Version $version not found. Please $openIssueLink to request a new version. Use `shorebird flutter versions list` to list available versions.'''), ]); }); diff --git a/packages/shorebird_cli/test/src/shorebird_flutter_test.dart b/packages/shorebird_cli/test/src/shorebird_flutter_test.dart index 703bdbf1a..6c5c53b9f 100644 --- a/packages/shorebird_cli/test/src/shorebird_flutter_test.dart +++ b/packages/shorebird_cli/test/src/shorebird_flutter_test.dart @@ -5,6 +5,7 @@ import 'package:mocktail/mocktail.dart'; import 'package:path/path.dart' as p; import 'package:scoped/scoped.dart'; import 'package:shorebird_cli/src/git.dart'; +import 'package:shorebird_cli/src/process.dart'; import 'package:shorebird_cli/src/shorebird_env.dart'; import 'package:shorebird_cli/src/shorebird_flutter.dart'; import 'package:test/test.dart'; @@ -13,6 +14,11 @@ class _MockGit extends Mock implements Git {} class _MockShorebirdEnv extends Mock implements ShorebirdEnv {} +class _MockShorebirdProcess extends Mock implements ShorebirdProcess {} + +class _MockShorebirdProcessResult extends Mock + implements ShorebirdProcessResult {} + void main() { group(ShorebirdFlutter, () { const flutterRevision = 'flutter-revision'; @@ -20,13 +26,16 @@ void main() { late Directory flutterDirectory; late Git git; late ShorebirdEnv shorebirdEnv; - late ShorebirdFlutter shorebirdFlutterManager; + late ShorebirdProcess process; + late ShorebirdProcessResult processResult; + late ShorebirdFlutter shorebirdFlutter; R runWithOverrides<R>(R Function() body) { return runScoped( body, values: { gitRef.overrideWith(() => git), + processRef.overrideWith(() => process), shorebirdEnvRef.overrideWith(() => shorebirdEnv), }, ); @@ -37,7 +46,9 @@ void main() { flutterDirectory = Directory(p.join(shorebirdRoot.path, 'flutter')); git = _MockGit(); shorebirdEnv = _MockShorebirdEnv(); - shorebirdFlutterManager = runWithOverrides(ShorebirdFlutter.new); + process = _MockShorebirdProcess(); + processResult = _MockShorebirdProcessResult(); + shorebirdFlutter = runWithOverrides(ShorebirdFlutter.new); when( () => git.clone( @@ -72,6 +83,52 @@ void main() { ).thenAnswer((_) async => flutterRevision); when(() => shorebirdEnv.flutterDirectory).thenReturn(flutterDirectory); when(() => shorebirdEnv.flutterRevision).thenReturn(flutterRevision); + when( + () => process.run('flutter', ['--version'], runInShell: true), + ).thenAnswer((_) async => processResult); + when(() => processResult.exitCode).thenReturn(0); + }); + + group('getVersion', () { + test('throws ProcessException when process exits with non-zero code', + () async { + const error = 'oops'; + when(() => processResult.exitCode).thenReturn(ExitCode.software.code); + when(() => processResult.stderr).thenReturn(error); + await expectLater( + runWithOverrides(shorebirdFlutter.getVersion), + throwsA(isA<ProcessException>()), + ); + verify( + () => process.run('flutter', ['--version'], runInShell: true), + ).called(1); + }); + + test('returns null when cannot parse version', () async { + when(() => processResult.stdout).thenReturn(''); + await expectLater( + runWithOverrides(shorebirdFlutter.getVersion), + completion(isNull), + ); + verify( + () => process.run('flutter', ['--version'], runInShell: true), + ).called(1); + }); + + test('returns version when able to parse the string', () async { + when(() => processResult.stdout).thenReturn(''' +Flutter 3.10.6 • channel stable • git@github.com:flutter/flutter.git +Framework • revision f468f3366c (4 weeks ago) • 2023-07-12 15:19:05 -0700 +Engine • revision cdbeda788a +Tools • Dart 3.0.6 • DevTools 2.23.1'''); + await expectLater( + runWithOverrides(shorebirdFlutter.getVersion), + completion(equals('3.10.6')), + ); + verify( + () => process.run('flutter', ['--version'], runInShell: true), + ).called(1); + }); }); group('getVersions', () { @@ -104,7 +161,7 @@ origin/flutter_release/3.10.6'''; ).thenAnswer((_) async => output); await expectLater( - runWithOverrides(shorebirdFlutterManager.getVersions), + runWithOverrides(shorebirdFlutter.getVersions), completion(equals(versions)), ); verify( @@ -135,7 +192,7 @@ origin/flutter_release/3.10.6'''; ); expect( - runWithOverrides(shorebirdFlutterManager.getVersions), + runWithOverrides(shorebirdFlutter.getVersions), throwsA( isA<ProcessException>().having( (e) => e.message, @@ -155,7 +212,7 @@ origin/flutter_release/3.10.6'''; ).createSync(recursive: true); await runWithOverrides( - () => shorebirdFlutterManager.installRevision(revision: revision), + () => shorebirdFlutter.installRevision(revision: revision), ); verifyNever( @@ -179,7 +236,7 @@ origin/flutter_release/3.10.6'''; await expectLater( runWithOverrides( - () => shorebirdFlutterManager.installRevision(revision: revision), + () => shorebirdFlutter.installRevision(revision: revision), ), throwsA(exception), ); @@ -204,7 +261,7 @@ origin/flutter_release/3.10.6'''; await expectLater( runWithOverrides( - () => shorebirdFlutterManager.installRevision(revision: revision), + () => shorebirdFlutter.installRevision(revision: revision), ), throwsA(exception), ); @@ -226,7 +283,7 @@ origin/flutter_release/3.10.6'''; test('completes when clone and checkout succeed', () async { await expectLater( runWithOverrides( - () => shorebirdFlutterManager.installRevision(revision: revision), + () => shorebirdFlutter.installRevision(revision: revision), ), completes, ); @@ -236,7 +293,7 @@ origin/flutter_release/3.10.6'''; group('pruneRemoteOrigin', () { test('completes when git command exits with code 0', () async { await expectLater( - runWithOverrides(() => shorebirdFlutterManager.pruneRemoteOrigin()), + runWithOverrides(() => shorebirdFlutter.pruneRemoteOrigin()), completes, ); verify( @@ -252,7 +309,7 @@ origin/flutter_release/3.10.6'''; const customRevision = 'custom-revision'; await expectLater( runWithOverrides( - () => shorebirdFlutterManager.pruneRemoteOrigin( + () => shorebirdFlutter.pruneRemoteOrigin( revision: customRevision, ), ), @@ -284,7 +341,7 @@ origin/flutter_release/3.10.6'''; ); expect( - runWithOverrides(() => shorebirdFlutterManager.pruneRemoteOrigin()), + runWithOverrides(() => shorebirdFlutter.pruneRemoteOrigin()), throwsA( isA<ProcessException>().having( (e) => e.message, @@ -299,7 +356,7 @@ origin/flutter_release/3.10.6'''; group('isPorcelain', () { test('returns true when status is empty', () async { await expectLater( - runWithOverrides(() => shorebirdFlutterManager.isPorcelain()), + runWithOverrides(() => shorebirdFlutter.isPorcelain()), completion(isTrue), ); verify( @@ -318,7 +375,7 @@ origin/flutter_release/3.10.6'''; ), ).thenAnswer((_) async => 'M some/file'); await expectLater( - runWithOverrides(() => shorebirdFlutterManager.isPorcelain()), + runWithOverrides(() => shorebirdFlutter.isPorcelain()), completion(isFalse), ); verify( @@ -347,7 +404,7 @@ origin/flutter_release/3.10.6'''; ); expect( - runWithOverrides(() => shorebirdFlutterManager.isPorcelain()), + runWithOverrides(() => shorebirdFlutter.isPorcelain()), throwsA( isA<ProcessException>().having( (e) => e.message, @@ -375,7 +432,7 @@ origin/flutter_release/3.10.6'''; test('installs revision if it does not exist', () async { await expectLater( runWithOverrides( - () => shorebirdFlutterManager.useVersion(version: version), + () => shorebirdFlutter.useVersion(version: version), ), completes, ); @@ -403,7 +460,7 @@ origin/flutter_release/3.10.6'''; .createSync(recursive: true); await expectLater( runWithOverrides( - () => shorebirdFlutterManager.useVersion(version: version), + () => shorebirdFlutter.useVersion(version: version), ), completes, );