From 1b388456f44d8f479bd67b048e327067ce5bc669 Mon Sep 17 00:00:00 2001 From: Felix Angelov Date: Thu, 19 Dec 2024 14:55:35 -0600 Subject: [PATCH] refactor(shorebird_cli): upgrade analysis_options (#2720) --- packages/shorebird_cli/analysis_options.yaml | 2 +- packages/shorebird_cli/lib/shorebird_cli.dart | 10 ---- .../lib/src/artifact_builder.dart | 2 +- packages/shorebird_cli/lib/src/auth/auth.dart | 13 ++++-- packages/shorebird_cli/lib/src/cache.dart | 35 ++++++++++++-- .../lib/src/code_push_client_wrapper.dart | 7 +-- .../lib/src/commands/doctor_command.dart | 13 +++--- .../flutter_versions_list_command.dart | 2 +- .../lib/src/commands/init_command.dart | 8 ++-- .../lib/src/commands/login_ci_command.dart | 6 +-- .../lib/src/commands/login_command.dart | 2 +- .../lib/src/commands/patch/aar_patcher.dart | 4 +- .../src/commands/patch/android_patcher.dart | 4 +- .../commands/patch/ios_framework_patcher.dart | 6 +-- .../lib/src/commands/patch/ios_patcher.dart | 11 ++--- .../lib/src/commands/patch/macos_patcher.dart | 8 ++-- .../lib/src/commands/patch/patch_command.dart | 25 ++++++++-- .../lib/src/commands/patch/patcher.dart | 4 +- .../lib/src/commands/preview_command.dart | 37 +++++++++------ .../src/commands/release/aar_releaser.dart | 2 +- .../commands/release/android_releaser.dart | 2 +- .../release/ios_framework_releaser.dart | 2 +- .../src/commands/release/ios_releaser.dart | 2 +- .../src/commands/release/macos_releaser.dart | 2 +- .../src/commands/release/release_command.dart | 4 +- .../commands/releases/get_apks_command.dart | 4 +- packages/shorebird_cli/lib/src/doctor.dart | 2 +- .../shorebird_cli/lib/src/engine_config.dart | 12 ++++- .../executables/devicectl/apple_device.dart | 18 +++++++- .../src/executables/devicectl/devicectl.dart | 15 +++--- .../src/executables/devicectl/nserror.dart | 33 ++++++++++++- .../lib/src/executables/gradlew.dart | 9 +++- .../lib/src/executables/idevicesyslog.dart | 1 + .../lib/src/executables/ios_deploy.dart | 33 ++++++++----- .../lib/src/extensions/arg_results.dart | 4 +- .../lib/src/extensions/string.dart | 5 +- .../lib/src/extensions/version.dart | 4 +- .../lib/src/http_client/retrying_client.dart | 10 ++-- .../metadata/build_environment_metadata.dart | 4 +- .../src/metadata/create_patch_metadata.dart | 4 +- .../src/metadata/update_release_metadata.dart | 4 +- .../lib/src/network_checker.dart | 2 +- .../shorebird_cli/lib/src/platform/ios.dart | 10 +++- .../shorebird_cli/lib/src/release_type.dart | 4 +- .../lib/src/shorebird_android_artifacts.dart | 15 ++++-- .../lib/src/shorebird_artifacts.dart | 1 + .../lib/src/shorebird_cli_command_runner.dart | 17 +++++-- .../lib/src/shorebird_command.dart | 7 ++- .../shorebird_cli/lib/src/shorebird_env.dart | 12 +++-- .../lib/src/shorebird_flutter.dart | 16 +++++-- .../lib/src/shorebird_validator.dart | 9 +++- .../shorebird_flutter_validator.dart | 4 +- packages/shorebird_cli/pubspec.lock | 4 +- packages/shorebird_cli/pubspec.yaml | 2 +- .../test/src/artifact_builder_test.dart | 4 +- .../test/src/auth/auth_test.dart | 15 ++++++ .../src/code_push_client_wrapper_test.dart | 7 +-- .../src/commands/doctor_command_test.dart | 12 ++++- .../flutter_versions_list_command_test.dart | 4 +- .../src/commands/login_ci_command_test.dart | 14 +++++- .../test/src/commands/login_command_test.dart | 9 +++- .../patch/ios_framework_patcher_test.dart | 11 ++++- .../src/commands/patch/ios_patcher_test.dart | 17 +++++-- .../commands/patch/macos_patcher_test.dart | 10 ++-- .../src/commands/upgrade_command_test.dart | 4 +- .../executables/devicectl/devicectl_test.dart | 9 ++++ .../test/src/executables/ios_deploy_test.dart | 2 +- .../test/src/extensions/version_test.dart | 9 +++- .../formatters/file_size_formatter_test.dart | 1 + .../test/src/network_checker_test.dart | 2 +- .../shorebird_cli_command_runner_test.dart | 12 +++-- .../test/src/shorebird_env_test.dart | 46 +++++++++++++++++++ .../test/src/shorebird_flutter_test.dart | 40 ++++++++++++++++ 73 files changed, 509 insertions(+), 197 deletions(-) delete mode 100644 packages/shorebird_cli/lib/shorebird_cli.dart diff --git a/packages/shorebird_cli/analysis_options.yaml b/packages/shorebird_cli/analysis_options.yaml index f5ad80bd..4c64d26a 100644 --- a/packages/shorebird_cli/analysis_options.yaml +++ b/packages/shorebird_cli/analysis_options.yaml @@ -1,4 +1,4 @@ -include: package:very_good_analysis/analysis_options.5.1.0.yaml +include: package:very_good_analysis/analysis_options.7.0.0.yaml analyzer: exclude: - lib/**.g.dart diff --git a/packages/shorebird_cli/lib/shorebird_cli.dart b/packages/shorebird_cli/lib/shorebird_cli.dart deleted file mode 100644 index f328272e..00000000 --- a/packages/shorebird_cli/lib/shorebird_cli.dart +++ /dev/null @@ -1,10 +0,0 @@ -/// shorebird_cli, The shorebird command-line tool -/// -/// ```sh -/// # activate shorebird_cli -/// dart pub global activate shorebird_cli -/// -/// # see usage -/// shorebird --help -/// ``` -library shorebird_cli; diff --git a/packages/shorebird_cli/lib/src/artifact_builder.dart b/packages/shorebird_cli/lib/src/artifact_builder.dart index 21349254..8bd0e9be 100644 --- a/packages/shorebird_cli/lib/src/artifact_builder.dart +++ b/packages/shorebird_cli/lib/src/artifact_builder.dart @@ -82,7 +82,7 @@ final artifactBuilderRef = create(ArtifactBuilder.new); ArtifactBuilder get artifactBuilder => read(artifactBuilderRef); extension on String { - /// Converts this base64-encoded public key into the Map: + /// Converts this base64-encoded public key into the `Map`: /// {'SHOREBIRD_PUBLIC_KEY': this} /// /// SHOREBIRD_PUBLIC_KEY is the name expected by the Shorebird's Flutter tool diff --git a/packages/shorebird_cli/lib/src/auth/auth.dart b/packages/shorebird_cli/lib/src/auth/auth.dart index 03312dbc..1bf03a05 100644 --- a/packages/shorebird_cli/lib/src/auth/auth.dart +++ b/packages/shorebird_cli/lib/src/auth/auth.dart @@ -1,4 +1,3 @@ -// ignore_for_file: public_member_api_docs // cspell:words googleapis bryanoltman endtemplate CLI tgvek orctktiabrek // cspell:words GOCSPX googleusercontent Pkkwp Entra import 'dart:convert'; @@ -162,7 +161,7 @@ class AuthenticatedClient extends http.BaseClient { client, authEndpoints: authEndpoints, ); - } catch (e, s) { + } on Exception catch (e, s) { logger ..err('Failed to refresh credentials.') ..info( @@ -328,7 +327,9 @@ class Auth { json.decode(contents) as Map, ); _email = _credentials?.email; - } catch (_) {} + } on Exception { + // Swallow json decode exceptions. + } } } @@ -365,7 +366,7 @@ extension JwtClaims on oauth2.AccessCredentials { final Jwt jwt; try { jwt = Jwt.parse(token); - } catch (_) { + } on Exception { return null; } @@ -409,12 +410,15 @@ extension OauthAuthProvider on Jwt { } } +/// Extension on [AuthProvider] which exposes OAuth 2.0 values. extension OauthValues on AuthProvider { + /// The OAuth 2.0 endpoints for the provider. oauth2.AuthEndpoints get authEndpoints => switch (this) { (AuthProvider.google) => const oauth2.GoogleAuthEndpoints(), (AuthProvider.microsoft) => MicrosoftAuthEndpoints(), }; + /// The OAuth 2.0 client ID for the provider. oauth2.ClientId get clientId { switch (this) { case AuthProvider.google: @@ -442,6 +446,7 @@ extension OauthValues on AuthProvider { } } + /// The OAuth 2.0 scopes for the provider. List get scopes => switch (this) { (AuthProvider.google) => [ 'openid', diff --git a/packages/shorebird_cli/lib/src/cache.dart b/packages/shorebird_cli/lib/src/cache.dart index b3fe8ec5..1d6dbc68 100644 --- a/packages/shorebird_cli/lib/src/cache.dart +++ b/packages/shorebird_cli/lib/src/cache.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'dart:io' hide Platform; import 'package:http/http.dart' as http; @@ -32,10 +30,10 @@ class CacheUpdateFailure implements Exception { String toString() => 'CacheUpdateFailure: $message'; } -// A reference to a [Cache] instance. +/// A reference to a [Cache] instance. final cacheRef = create(Cache.new); -// The [Cache] instance available in the current zone. +/// The [Cache] instance available in the current zone. Cache get cache => read(cacheRef); /// {@template cache} @@ -46,12 +44,14 @@ Cache get cache => read(cacheRef); /// [ShorebirdArtifacts] since uses the current Shorebird environment. /// {@endtemplate} class Cache { + /// {@macro cache} Cache() { registerArtifact(PatchArtifact(cache: this, platform: platform)); registerArtifact(BundleToolArtifact(cache: this, platform: platform)); registerArtifact(AotToolsArtifact(cache: this, platform: platform)); } + /// Register a new [CachedArtifact] with the cache. void registerArtifact(CachedArtifact artifact) => _artifacts.add(artifact); /// Update all artifacts in the cache. @@ -119,10 +119,13 @@ class Cache { final List _artifacts = []; + /// The storage base url. String get storageBaseUrl => 'https://storage.googleapis.com'; + /// The storage bucket host. String get storageBucket => 'download.shorebird.dev'; + /// Clear the cache. Future clear() async { final cacheDir = shorebirdCacheDirectory; if (cacheDir.existsSync()) { @@ -131,10 +134,17 @@ class Cache { } } +/// {@template cached_artifact} +/// An artifact which is cached by Shorebird. +/// {@endtemplate} abstract class CachedArtifact { + /// {@macro cached_artifact} CachedArtifact({required this.cache, required this.platform}); + /// The cache instance to use. final Cache cache; + + /// The platform to use. final Platform platform; /// The on-disk name of the artifact. @@ -156,6 +166,7 @@ abstract class CachedArtifact { /// is assumed to be correct. String? get checksum; + /// Extract the artifact from the provided [stream] to the [outputPath]. Future extractArtifact(http.ByteStream stream, String outputPath) { final file = File(p.join(outputPath, fileName)) ..createSync(recursive: true); @@ -169,6 +180,7 @@ abstract class CachedArtifact { /// Used to validate that the artifact was fully downloaded and extracted. File get stampFile => File('${file.path}.stamp'); + /// Whether the artifact is valid (has a matching checksum). Future isValid() async { if (!file.existsSync() || !stampFile.existsSync()) { return false; @@ -184,6 +196,7 @@ abstract class CachedArtifact { return checksumChecker.checkFile(file, checksum!); } + /// Re-fetch the artifact from the storage URL. Future update() async { // Clear any existing artifact files. await _delete(); @@ -271,7 +284,12 @@ allowed to access $storageUrl.''', } } +/// {@template aot_tools_artifact} +/// The aot_tools.dill artifact. +/// Used for linking and generating optimized AOT snapshots. +/// {@endtemplate} class AotToolsArtifact extends CachedArtifact { + /// {@macro aot_tools_artifact} AotToolsArtifact({required super.cache, required super.platform}); @override @@ -301,7 +319,11 @@ class AotToolsArtifact extends CachedArtifact { String? get checksum => null; } +/// {@template patch_artifact} +/// The patch artifact which is used to apply binary patches. +/// {@endtemplate} class PatchArtifact extends CachedArtifact { + /// {@macro patch_artifact} PatchArtifact({required super.cache, required super.platform}); @override @@ -342,7 +364,12 @@ class PatchArtifact extends CachedArtifact { String? get checksum => null; } +/// {@template bundle_tool_artifact} +/// The bundletool.jar artifact. +/// Used for interacting with Android app bundles (aab). +/// {@endtemplate} class BundleToolArtifact extends CachedArtifact { + /// {@macro bundle_tool_artifact} BundleToolArtifact({required super.cache, required super.platform}); @override diff --git a/packages/shorebird_cli/lib/src/code_push_client_wrapper.dart b/packages/shorebird_cli/lib/src/code_push_client_wrapper.dart index a24208bf..28906614 100644 --- a/packages/shorebird_cli/lib/src/code_push_client_wrapper.dart +++ b/packages/shorebird_cli/lib/src/code_push_client_wrapper.dart @@ -1,3 +1,4 @@ +// TODO(felangel): Add public member API docs and remove the ignore. // ignore_for_file: public_member_api_docs // cspell:words endtemplate pubspec sideloadable bryanoltman archs sideload // cspell:words xcarchive codesigned xcframework @@ -87,11 +88,7 @@ class CodePushClientWrapper { }) async { late final String displayName; if (appName == null) { - String? defaultAppName; - try { - defaultAppName = shorebirdEnv.getPubspecYaml()?.name; - } catch (_) {} - + final defaultAppName = shorebirdEnv.getPubspecYaml()?.name; displayName = logger.prompt( '${lightGreen.wrap('?')} How should we refer to this app?', defaultValue: defaultAppName, diff --git a/packages/shorebird_cli/lib/src/commands/doctor_command.dart b/packages/shorebird_cli/lib/src/commands/doctor_command.dart index 974a6589..9079d300 100644 --- a/packages/shorebird_cli/lib/src/commands/doctor_command.dart +++ b/packages/shorebird_cli/lib/src/commands/doctor_command.dart @@ -93,10 +93,9 @@ Android Toolchain • Gradle: ${gradlewVersion ?? notDetected}'''); } - logger.info(output.toString()); - - // ignore: cascade_invocations - logger.info('URL Reachability'); + logger + ..info(output.toString()) + ..info('URL Reachability'); await networkChecker.checkReachability(); logger.info(''); @@ -111,7 +110,7 @@ Android Toolchain ); } on NetworkCheckerException catch (error) { uploadProgress.fail('GCP upload speed test failed: ${error.message}'); - } catch (error) { + } on Exception catch (error) { uploadProgress.fail('GCP upload speed test failed: $error'); } @@ -127,7 +126,7 @@ Android Toolchain downloadProgress.fail( 'GCP download speed test failed: ${error.message}', ); - } catch (error) { + } on Exception catch (error) { downloadProgress.fail( 'GCP download speed test failed: $error', ); @@ -143,7 +142,7 @@ Android Toolchain Future _tryGetFlutterVersion() async { try { return await shorebirdFlutter.getVersionString(); - } catch (error) { + } on Exception catch (error) { logger.detail('Unable to determine Flutter version.\n$error'); return null; } diff --git a/packages/shorebird_cli/lib/src/commands/flutter/versions/flutter_versions_list_command.dart b/packages/shorebird_cli/lib/src/commands/flutter/versions/flutter_versions_list_command.dart index a9b6d53d..b8bcf70e 100644 --- a/packages/shorebird_cli/lib/src/commands/flutter/versions/flutter_versions_list_command.dart +++ b/packages/shorebird_cli/lib/src/commands/flutter/versions/flutter_versions_list_command.dart @@ -34,7 +34,7 @@ class FlutterVersionsListCommand extends ShorebirdCommand { try { versions = await shorebirdFlutter.getVersions(); progress.cancel(); - } catch (error) { + } on Exception catch (error) { progress.fail('Failed to fetch Flutter versions.'); logger.err('$error'); return ExitCode.software.code; diff --git a/packages/shorebird_cli/lib/src/commands/init_command.dart b/packages/shorebird_cli/lib/src/commands/init_command.dart index b38296ce..c7b4dde3 100644 --- a/packages/shorebird_cli/lib/src/commands/init_command.dart +++ b/packages/shorebird_cli/lib/src/commands/init_command.dart @@ -60,7 +60,7 @@ Please make sure you are running "shorebird init" from within your Flutter proje '''); return ExitCode.noInput.code; } - } catch (error) { + } on Exception catch (error) { logger.err('Error parsing "pubspec.yaml": $error'); return ExitCode.software.code; } @@ -108,7 +108,7 @@ Please make sure you are running "shorebird init" from within your Flutter proje logger.info(' - $flavor'); } } - } catch (error) { + } on Exception catch (error) { detectFlavorsProgress.fail(); logger.err('Unable to extract product flavors.\n$error'); return ExitCode.software.code; @@ -138,7 +138,7 @@ Please make sure you are running "shorebird init" from within your Flutter proje existingApp = await codePushClientWrapper.getApp( appId: shorebirdYaml!.appId, ); - } catch (e) { + } on Exception catch (e) { updateShorebirdYamlProgress.fail('Failed to get existing app info: $e'); return ExitCode.software.code; } @@ -227,7 +227,7 @@ Please make sure you are running "shorebird init" from within your Flutter proje flavors = values; appId = flavors.values.first; } - } catch (error) { + } on Exception catch (error) { logger.err('$error'); return ExitCode.software.code; } diff --git a/packages/shorebird_cli/lib/src/commands/login_ci_command.dart b/packages/shorebird_cli/lib/src/commands/login_ci_command.dart index 87e390b0..85973daf 100644 --- a/packages/shorebird_cli/lib/src/commands/login_ci_command.dart +++ b/packages/shorebird_cli/lib/src/commands/login_ci_command.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'package:mason_logger/mason_logger.dart'; import 'package:shorebird_cli/src/auth/auth.dart'; import 'package:shorebird_cli/src/logging/logging.dart'; @@ -12,6 +10,7 @@ import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart' /// Login as a CI user. /// {@endtemplate} class LoginCiCommand extends ShorebirdCommand { + /// {@macro login_ci_command} LoginCiCommand() { argParser.addOption( 'provider', @@ -54,7 +53,7 @@ We could not find a Shorebird account for ${error.email}.''', '''If you have not yet created an account, go to "${link(uri: Uri.parse('https://console.shorebird.dev'))}" to create one. If you believe this is an error, please reach out to us via Discord, we're happy to help!''', ); return ExitCode.software.code; - } catch (error) { + } on Exception catch (error) { logger.err(error.toString()); return ExitCode.software.code; } @@ -72,6 +71,7 @@ ${lightCyan.wrap('export $shorebirdTokenEnvVar="\$SHOREBIRD_TOKEN" && shorebird return ExitCode.success.code; } + /// Prompt the user to visit the provided [url] to authorize the CLI. void prompt(String url) { logger.info(''' The Shorebird CLI needs your authorization to manage apps, releases, and patches on your behalf. diff --git a/packages/shorebird_cli/lib/src/commands/login_command.dart b/packages/shorebird_cli/lib/src/commands/login_command.dart index a649ef5d..1737019c 100644 --- a/packages/shorebird_cli/lib/src/commands/login_command.dart +++ b/packages/shorebird_cli/lib/src/commands/login_command.dart @@ -61,7 +61,7 @@ We could not find a Shorebird account for ${error.email}.''', """If you have not yet created an account, you can do so at "${link(uri: consoleUri)}". If you believe this is an error, please reach out to us via Discord, we're happy to help!""", ); return ExitCode.software.code; - } catch (error) { + } on Exception catch (error) { logger.err(error.toString()); return ExitCode.software.code; } diff --git a/packages/shorebird_cli/lib/src/commands/patch/aar_patcher.dart b/packages/shorebird_cli/lib/src/commands/patch/aar_patcher.dart index 6ea8983c..67fd5143 100644 --- a/packages/shorebird_cli/lib/src/commands/patch/aar_patcher.dart +++ b/packages/shorebird_cli/lib/src/commands/patch/aar_patcher.dart @@ -125,7 +125,7 @@ class AarPatcher extends Patcher { Uri.parse(releaseArtifact.value.url), ); releaseArtifactPaths[releaseArtifact.key] = releaseArtifactFile.path; - } catch (error) { + } on Exception catch (error) { downloadReleaseArtifactProgress.fail('$error'); throw ProcessExit(ExitCode.software.code); } @@ -163,7 +163,7 @@ class AarPatcher extends Patcher { hash: hash, size: await File(diffPath).length(), ); - } catch (error) { + } on Exception catch (error) { createDiffProgress.fail('$error'); throw ProcessExit(ExitCode.software.code); } diff --git a/packages/shorebird_cli/lib/src/commands/patch/android_patcher.dart b/packages/shorebird_cli/lib/src/commands/patch/android_patcher.dart index 015eb8f6..79ddcb72 100644 --- a/packages/shorebird_cli/lib/src/commands/patch/android_patcher.dart +++ b/packages/shorebird_cli/lib/src/commands/patch/android_patcher.dart @@ -176,7 +176,7 @@ Please refer to ${link(uri: Uri.parse('https://github.com/shorebirdtech/shorebir message: 'Downloading release artifact ${i + 1}/$numArtifacts', ); releaseArtifactPaths[releaseArtifact.key] = releaseArtifactFile.path; - } catch (error) { + } on Exception { throw ProcessExit(ExitCode.software.code); } } @@ -227,7 +227,7 @@ Please refer to ${link(uri: Uri.parse('https://github.com/shorebirdtech/shorebir size: await File(diffPath).length(), hashSignature: hashSignature, ); - } catch (error) { + } on Exception catch (error) { createDiffProgress.fail('$error'); throw ProcessExit(ExitCode.software.code); } diff --git a/packages/shorebird_cli/lib/src/commands/patch/ios_framework_patcher.dart b/packages/shorebird_cli/lib/src/commands/patch/ios_framework_patcher.dart index 036d2459..333e1d43 100644 --- a/packages/shorebird_cli/lib/src/commands/patch/ios_framework_patcher.dart +++ b/packages/shorebird_cli/lib/src/commands/patch/ios_framework_patcher.dart @@ -131,7 +131,7 @@ class IosFrameworkPatcher extends Patcher { genSnapshotArtifact: ShorebirdArtifact.genSnapshotIos, additionalArgs: IosPatcher.splitDebugInfoArgs(splitDebugInfoPath), ); - } catch (error) { + } on Exception catch (error) { buildProgress.fail('$error'); throw ProcessExit(ExitCode.software.code); } @@ -259,7 +259,7 @@ class IosFrameworkPatcher extends Patcher { releaseSnapshot: releaseArtifactFile, ); patchBaseProgress.complete(); - } catch (error) { + } on Exception catch (error) { patchBaseProgress.fail('$error'); throw ProcessExit(ExitCode.software.code); } @@ -339,7 +339,7 @@ class IosFrameworkPatcher extends Patcher { workingDirectory: buildDirectory.path, additionalArgs: IosPatcher.splitDebugInfoArgs(splitDebugInfoPath), ); - } catch (error) { + } on Exception catch (error) { linkProgress.fail('Failed to link AOT files: $error'); throw ProcessExit(ExitCode.software.code); } diff --git a/packages/shorebird_cli/lib/src/commands/patch/ios_patcher.dart b/packages/shorebird_cli/lib/src/commands/patch/ios_patcher.dart index 4d388634..a2aa7490 100644 --- a/packages/shorebird_cli/lib/src/commands/patch/ios_patcher.dart +++ b/packages/shorebird_cli/lib/src/commands/patch/ios_patcher.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'dart:async'; import 'dart:io'; @@ -79,6 +77,7 @@ class IosPatcher extends Patcher { return p.join(p.absolute(directory), splitDebugInfoFileName); } + /// The last build's link percentage. @visibleForTesting double? lastBuildLinkPercentage; @@ -227,7 +226,7 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', ipaBuildResult.kernelFile.copySync(_appDillCopyPath); buildProgress.complete(); - } catch (_) { + } on Exception { throw ProcessExit(ExitCode.software.code); } @@ -354,7 +353,7 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', releaseSnapshot: releaseArtifactFile, ); patchBaseProgress.complete(); - } catch (error) { + } on Exception catch (error) { patchBaseProgress.fail('$error'); throw ProcessExit(ExitCode.software.code); } @@ -407,7 +406,7 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', final plist = Plist(file: plistFile); try { return plist.versionNumber; - } catch (error) { + } on Exception catch (error) { logger.err( 'Failed to determine release version from ${plistFile.path}: $error', ); @@ -478,7 +477,7 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', dumpDebugInfoPath: dumpDebugInfoDir?.path, additionalArgs: splitDebugInfoArgs(splitDebugInfoPath), ); - } catch (error) { + } on Exception catch (error) { linkProgress.fail('Failed to link AOT files: $error'); return (exitCode: ExitCode.software.code, linkPercentage: null); } finally { diff --git a/packages/shorebird_cli/lib/src/commands/patch/macos_patcher.dart b/packages/shorebird_cli/lib/src/commands/patch/macos_patcher.dart index 65146383..98b80661 100644 --- a/packages/shorebird_cli/lib/src/commands/patch/macos_patcher.dart +++ b/packages/shorebird_cli/lib/src/commands/patch/macos_patcher.dart @@ -228,7 +228,7 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', macosBuildResult.kernelFile.copySync(_appDillCopyPath); buildProgress.complete(); - } catch (_) { + } on Exception { throw ProcessExit(ExitCode.software.code); } @@ -345,7 +345,7 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', releaseSnapshot: releaseArtifactFile, ); patchBaseProgress.complete(); - } catch (error) { + } on Exception catch (error) { patchBaseProgress.fail('$error'); throw ProcessExit(ExitCode.software.code); } @@ -396,7 +396,7 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', final plist = Plist(file: plistFile); try { return plist.versionNumber; - } catch (error) { + } on Exception catch (error) { logger.err( 'Failed to determine release version from ${plistFile.path}: $error', ); @@ -467,7 +467,7 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', dumpDebugInfoPath: dumpDebugInfoDir?.path, additionalArgs: splitDebugInfoArgs(splitDebugInfoPath), ); - } catch (error) { + } on Exception catch (error) { linkProgress.fail('Failed to link AOT files: $error'); return (exitCode: ExitCode.software.code, linkPercentage: null); } finally { diff --git a/packages/shorebird_cli/lib/src/commands/patch/patch_command.dart b/packages/shorebird_cli/lib/src/commands/patch/patch_command.dart index d3176f5a..00dcd9f5 100644 --- a/packages/shorebird_cli/lib/src/commands/patch/patch_command.dart +++ b/packages/shorebird_cli/lib/src/commands/patch/patch_command.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'dart:io'; import 'package:collection/collection.dart'; @@ -29,9 +27,15 @@ import 'package:shorebird_cli/src/third_party/flutter_tools/lib/flutter_tools.da import 'package:shorebird_cli/src/version.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; +/// Signature for a function that returns a [Patcher] for a given [ReleaseType]. typedef ResolvePatcher = Patcher Function(ReleaseType releaseType); +/// {@template patch_command} +/// A command that creates a shorebird patch for the provided target platforms. +/// `shorebird patch --platforms=android,ios` +/// {@endtemplate} class PatchCommand extends ShorebirdCommand { + /// {@macro patch_command} PatchCommand({ ResolvePatcher? resolvePatcher, }) { @@ -142,10 +146,12 @@ of the iOS app that is using this module.''', ); } + /// Warning message for when native code diffs are detected. static final allowNativeDiffsHelpText = ''' Patch even if native code diffs are detected. NOTE: this is ${styleBold.wrap('not')} recommended. Native code changes cannot be included in a patch and attempting to do so can cause your app to crash or behave unexpectedly.'''; + /// Warning message for when asset diffs are detected. static final allowAssetDiffsHelpText = ''' Patch even if asset diffs are detected. NOTE: this is ${styleBold.wrap('not')} recommended. Asset changes cannot be included in a patch can cause your app to behave unexpectedly.'''; @@ -177,8 +183,10 @@ NOTE: this is ${styleBold.wrap('not')} recommended. Asset changes cannot be incl /// Whether --no-confirm was passed. bool get noConfirm => results['no-confirm'] == true; + /// Whether the patch is for the staging environment. bool get isStaging => track == DeploymentTrack.staging; + /// The deployment track to publish the patch to. DeploymentTrack get track { final channel = results['track'] as String; return DeploymentTrack.values.firstWhere((t) => t.channel == channel); @@ -207,6 +215,7 @@ NOTE: this is ${styleBold.wrap('not')} recommended. Asset changes cannot be incl return ExitCode.success.code; } + /// Returns a [Patcher] for the given [ReleaseType]. @visibleForTesting Patcher getPatcher(ReleaseType releaseType) { switch (releaseType) { @@ -248,8 +257,10 @@ NOTE: this is ${styleBold.wrap('not')} recommended. Asset changes cannot be incl } } + /// The last built Flutter revision. String? lastBuiltFlutterRevision; + /// Creates a patch using the provided [patcher]. @visibleForTesting Future createPatch(Patcher patcher) async { await patcher.assertPreconditions(); @@ -291,7 +302,7 @@ NOTE: this is ${styleBold.wrap('not')} recommended. Asset changes cannot be incl try { await shorebirdFlutter.installRevision(revision: release.flutterRevision); - } catch (_) { + } on Exception { throw ProcessExit(ExitCode.software.code); } @@ -395,6 +406,7 @@ NOTE: this is ${styleBold.wrap('not')} recommended. Asset changes cannot be incl ); } + /// Prompts the user for the specific release to patch. Future promptForRelease() async { final releases = await codePushClientWrapper.getReleases( appId: appId, @@ -414,6 +426,7 @@ NOTE: this is ${styleBold.wrap('not')} recommended. Asset changes cannot be incl ); } + /// Asserts that the release contains a platform for the given [patcher]. void assertReleaseContainsPlatform({ required Release release, required Patcher patcher, @@ -429,6 +442,7 @@ NOTE: this is ${styleBold.wrap('not')} recommended. Asset changes cannot be incl } } + /// Asserts that the provided [release] is active. void assertReleaseIsActive({ required Release release, required Patcher patcher, @@ -443,6 +457,7 @@ Please re-run the release command for this version or create a new release.'''); } } + /// Ensures the diff between the release and patch archives is safe to patch. Future assertUnpatchableDiffs({ required ReleaseArtifact releaseArtifact, required File patchArchive, @@ -463,6 +478,7 @@ Please re-run the release command for this version or create a new release.'''); } } + /// Confirms the patch creation (including a summary). Future confirmCreatePatch({ required AppMetadata app, required String releaseVersion, @@ -511,6 +527,7 @@ ${summary.join('\n')} } } + /// Downloads the given [releaseArtifact]. Future downloadReleaseArtifact({ required ReleaseArtifact releaseArtifact, }) async { @@ -520,7 +537,7 @@ ${summary.join('\n')} Uri.parse(releaseArtifact.url), message: 'Downloading ${releaseArtifact.arch}', ); - } catch (_) { + } on Exception { throw ProcessExit(ExitCode.software.code); } diff --git a/packages/shorebird_cli/lib/src/commands/patch/patcher.dart b/packages/shorebird_cli/lib/src/commands/patch/patcher.dart index 830e460e..a86687e8 100644 --- a/packages/shorebird_cli/lib/src/commands/patch/patcher.dart +++ b/packages/shorebird_cli/lib/src/commands/patch/patcher.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'dart:io'; import 'package:args/args.dart'; @@ -31,7 +29,7 @@ abstract class Patcher { required this.target, }); - // Link percentage that is considered the minimum before a user might notice. + /// Link percentage that is considered the minimum before a user might notice. static const double minLinkPercentage = 75; /// The standard link percentage warning. diff --git a/packages/shorebird_cli/lib/src/commands/preview_command.dart b/packages/shorebird_cli/lib/src/commands/preview_command.dart index b625fed8..3199be72 100644 --- a/packages/shorebird_cli/lib/src/commands/preview_command.dart +++ b/packages/shorebird_cli/lib/src/commands/preview_command.dart @@ -1,4 +1,3 @@ -// ignore_for_file: public_member_api_docs // cspell:words devicectl endtemplate bryanoltman sideloadable previewable apks // cspell:words bundletool import 'dart:async'; @@ -252,6 +251,7 @@ class PreviewCommand extends ShorebirdCommand { }; } + /// Prompts the user to choose an app to preview. Future promptForApp() async { final apps = await codePushClientWrapper.getApps(); if (apps.isEmpty) return null; @@ -263,6 +263,7 @@ class PreviewCommand extends ShorebirdCommand { return app.appId; } + /// Prompts the user to choose a release version to preview. Future promptForReleaseVersion(List releases) async { if (releases.isEmpty) return null; final release = logger.chooseOne( @@ -273,6 +274,7 @@ class PreviewCommand extends ShorebirdCommand { return release.version; } + /// Prompts the user to choose a platform to preview. Future promptForPlatform( List platforms, ) async { @@ -284,6 +286,7 @@ class PreviewCommand extends ShorebirdCommand { return ReleasePlatform.values.firstWhere((p) => p.displayName == platform); } + /// Installs and launches the release on macOS. Future installAndLaunchMacos({ required String appId, required Release release, @@ -300,7 +303,7 @@ class PreviewCommand extends ShorebirdCommand { arch: 'app', platform: platform, ); - } catch (e, s) { + } on Exception catch (e, s) { logger ..err('Error getting release artifact: $e') ..detail('Stack trace: $s'); @@ -332,7 +335,7 @@ class PreviewCommand extends ShorebirdCommand { destination: appDirectory.path, ); downloadArtifactProgress.complete(); - } catch (error) { + } on Exception catch (error) { downloadArtifactProgress.fail('$error'); return ExitCode.software.code; } @@ -349,6 +352,7 @@ class PreviewCommand extends ShorebirdCommand { return completer.future.then((_) => ExitCode.success.code); } + /// Installs and launches the release on Android. Future installAndLaunchAndroid({ required String appId, required Release release, @@ -368,7 +372,7 @@ class PreviewCommand extends ShorebirdCommand { arch: 'aab', platform: platform, ); - } catch (e, s) { + } on Exception catch (e, s) { logger ..err('Error getting release artifact: $e') ..detail('Stack trace: $s'); @@ -396,7 +400,7 @@ class PreviewCommand extends ShorebirdCommand { } downloadArtifactProgress.complete(); - } catch (error) { + } on Exception catch (error) { downloadArtifactProgress.fail('$error'); return ExitCode.software.code; } @@ -414,7 +418,7 @@ class PreviewCommand extends ShorebirdCommand { try { await setChannelOnAab(aabFile: aabFile, channel: track.channel); progress.complete(); - } catch (error) { + } on Exception catch (error) { progress.fail('$error'); return ExitCode.software.code; } @@ -424,7 +428,7 @@ class PreviewCommand extends ShorebirdCommand { try { package = await bundletool.getPackageName(aabFile.path); extractMetadataProgress.complete(); - } catch (error) { + } on Exception catch (error) { extractMetadataProgress.fail('$error'); return ExitCode.software.code; } @@ -434,7 +438,7 @@ class PreviewCommand extends ShorebirdCommand { await bundletool.buildApks(bundle: aabFile.path, output: apksPath); final apksLink = link(uri: Uri.parse(apksPath)); buildApksProgress.complete('Built apks: ${cyan.wrap(apksLink)}'); - } catch (error) { + } on Exception catch (error) { buildApksProgress.fail('$error'); return ExitCode.software.code; } @@ -443,7 +447,7 @@ class PreviewCommand extends ShorebirdCommand { try { await bundletool.installApks(apks: apksPath, deviceId: deviceId); installApksProgress.complete(); - } catch (error) { + } on Exception catch (error) { installApksProgress.fail('$error'); return ExitCode.software.code; } @@ -453,7 +457,7 @@ class PreviewCommand extends ShorebirdCommand { await adb.clearAppData(package: package, deviceId: deviceId); await adb.startApp(package: package, deviceId: deviceId); startAppProgress.complete(); - } catch (error) { + } on Exception catch (error) { startAppProgress.fail('$error'); return ExitCode.software.code; } @@ -469,6 +473,7 @@ class PreviewCommand extends ShorebirdCommand { return process.exitCode; } + /// Installs and launches the release on iOS. Future installAndLaunchIos({ required String appId, required Release release, @@ -488,7 +493,7 @@ class PreviewCommand extends ShorebirdCommand { arch: 'runner', platform: platform, ); - } catch (e, s) { + } on Exception catch (e, s) { logger ..err('Error getting release artifact: $e') ..detail('Stack trace: $s'); @@ -520,7 +525,7 @@ class PreviewCommand extends ShorebirdCommand { outputDirectory: runnerDirectory, ); downloadArtifactProgress.complete(); - } catch (error) { + } on Exception catch (error) { downloadArtifactProgress.fail('$error'); return ExitCode.software.code; } @@ -533,7 +538,7 @@ class PreviewCommand extends ShorebirdCommand { channel: track.channel, ); progress.complete(); - } catch (error) { + } on Exception catch (error) { progress.fail('$error'); return ExitCode.software.code; } @@ -576,12 +581,13 @@ class PreviewCommand extends ShorebirdCommand { } return installExitCode; - } catch (error, stackTrace) { + } on Exception catch (error, stackTrace) { logger.detail('Error launching app. $error $stackTrace'); return ExitCode.software.code; } } + /// Resolves the artifact path for the given parameters. String getArtifactPath({ required String appId, required Release release, @@ -695,7 +701,10 @@ class PreviewCommand extends ShorebirdCommand { } } +/// Extension on [Release] that exposes the active platforms (e.g. platforms +/// that can be previewed). extension Previewable on Release { + /// Returns the platforms that can be previewed. List get activePlatforms => platformStatuses.entries .where((e) => e.value == ReleaseStatus.active) .map((e) => e.key) diff --git a/packages/shorebird_cli/lib/src/commands/release/aar_releaser.dart b/packages/shorebird_cli/lib/src/commands/release/aar_releaser.dart index 75f31b4c..b50ad394 100644 --- a/packages/shorebird_cli/lib/src/commands/release/aar_releaser.dart +++ b/packages/shorebird_cli/lib/src/commands/release/aar_releaser.dart @@ -84,7 +84,7 @@ class AarReleaser extends Releaser { targetPlatforms: architectures, args: argResults.forwardedArgs, ); - } catch (e) { + } on Exception catch (e) { logger.err('Failed to build aar: $e'); throw ProcessExit(ExitCode.software.code); } diff --git a/packages/shorebird_cli/lib/src/commands/release/android_releaser.dart b/packages/shorebird_cli/lib/src/commands/release/android_releaser.dart index cd3fb2a2..a4f1e66d 100644 --- a/packages/shorebird_cli/lib/src/commands/release/android_releaser.dart +++ b/packages/shorebird_cli/lib/src/commands/release/android_releaser.dart @@ -159,7 +159,7 @@ Please comment and upvote ${link(uri: Uri.parse('https://github.com/shorebirdtec releaseArtifactRoot.path, ); releaseVersionProgress.complete('Release version: $releaseVersion'); - } catch (error) { + } on Exception catch (error) { releaseVersionProgress.fail('$error'); throw ProcessExit(ExitCode.software.code); } diff --git a/packages/shorebird_cli/lib/src/commands/release/ios_framework_releaser.dart b/packages/shorebird_cli/lib/src/commands/release/ios_framework_releaser.dart index ad1c39cb..e78b6ae1 100644 --- a/packages/shorebird_cli/lib/src/commands/release/ios_framework_releaser.dart +++ b/packages/shorebird_cli/lib/src/commands/release/ios_framework_releaser.dart @@ -99,7 +99,7 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', try { await artifactBuilder.buildIosFramework(args: argResults.forwardedArgs); - } catch (error) { + } on Exception catch (error) { buildProgress.fail('Failed to build iOS framework: $error'); throw ProcessExit(ExitCode.software.code); } diff --git a/packages/shorebird_cli/lib/src/commands/release/ios_releaser.dart b/packages/shorebird_cli/lib/src/commands/release/ios_releaser.dart index 877b2c3e..18a25046 100644 --- a/packages/shorebird_cli/lib/src/commands/release/ios_releaser.dart +++ b/packages/shorebird_cli/lib/src/commands/release/ios_releaser.dart @@ -165,7 +165,7 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', try { return Plist(file: plistFile).versionNumber; - } catch (error) { + } on Exception catch (error) { logger.err( '''Failed to determine release version from ${plistFile.path}: $error''', ); diff --git a/packages/shorebird_cli/lib/src/commands/release/macos_releaser.dart b/packages/shorebird_cli/lib/src/commands/release/macos_releaser.dart index 4c55b20c..1bff39f3 100644 --- a/packages/shorebird_cli/lib/src/commands/release/macos_releaser.dart +++ b/packages/shorebird_cli/lib/src/commands/release/macos_releaser.dart @@ -148,7 +148,7 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', try { return Plist(file: plistFile).versionNumber; - } catch (error) { + } on Exception catch (error) { logger.err( '''Failed to determine release version from ${plistFile.path}: $error''', ); diff --git a/packages/shorebird_cli/lib/src/commands/release/release_command.dart b/packages/shorebird_cli/lib/src/commands/release/release_command.dart index 5deddd66..dc15c626 100644 --- a/packages/shorebird_cli/lib/src/commands/release/release_command.dart +++ b/packages/shorebird_cli/lib/src/commands/release/release_command.dart @@ -250,7 +250,7 @@ of the iOS app that is using this module. (aar and ios-framework only)''', final targetFlutterRevision = await resolveTargetFlutterRevision(); try { await shorebirdFlutter.installRevision(revision: targetFlutterRevision); - } catch (_) { + } on Exception { throw ProcessExit(ExitCode.software.code); } @@ -331,7 +331,7 @@ of the iOS app that is using this module. (aar and ios-framework only)''', revision = await shorebirdFlutter.resolveFlutterRevision( flutterVersionArg!, ); - } catch (error) { + } on Exception catch (error) { logger.err( ''' Unable to determine revision for Flutter version: $flutterVersionArg. diff --git a/packages/shorebird_cli/lib/src/commands/releases/get_apks_command.dart b/packages/shorebird_cli/lib/src/commands/releases/get_apks_command.dart index a54a69ed..d8f6b601 100644 --- a/packages/shorebird_cli/lib/src/commands/releases/get_apks_command.dart +++ b/packages/shorebird_cli/lib/src/commands/releases/get_apks_command.dart @@ -115,7 +115,7 @@ class GetApksCommand extends ShorebirdCommand { universal: results['universal'] as bool, ); buildApksProgress.complete(); - } catch (error) { + } on Exception catch (error) { buildApksProgress.fail('$error'); return ExitCode.software.code; } @@ -178,7 +178,7 @@ class GetApksCommand extends ShorebirdCommand { Uri.parse(releaseArtifact.url), message: 'Downloading aab', ); - } catch (_) { + } on Exception catch (_) { throw ProcessExit(ExitCode.software.code); } diff --git a/packages/shorebird_cli/lib/src/doctor.dart b/packages/shorebird_cli/lib/src/doctor.dart index efc8962b..f3bea7bd 100644 --- a/packages/shorebird_cli/lib/src/doctor.dart +++ b/packages/shorebird_cli/lib/src/doctor.dart @@ -69,7 +69,7 @@ class Doctor { for (final issue in fixableIssues) { try { await issue.fix!(); - } catch (error) { + } on Exception catch (error) { failedFixes[issue] = error; } } diff --git a/packages/shorebird_cli/lib/src/engine_config.dart b/packages/shorebird_cli/lib/src/engine_config.dart index 5b908f88..58fed0ec 100644 --- a/packages/shorebird_cli/lib/src/engine_config.dart +++ b/packages/shorebird_cli/lib/src/engine_config.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'package:scoped_deps/scoped_deps.dart'; /// A reference to an [EngineConfig] instance. @@ -8,20 +6,30 @@ final engineConfigRef = create(() => const EngineConfig.empty()); /// The [EngineConfig] instance available in the current zone. EngineConfig get engineConfig => read(engineConfigRef); +/// {@template engine_config} +/// An object that contains a local engine configuration. +/// {@endtemplate} class EngineConfig { + /// {@macro engine_config} const EngineConfig({ required this.localEngineSrcPath, required this.localEngine, required this.localEngineHost, }); + /// An empty [EngineConfig] instance. const EngineConfig.empty() : localEngineSrcPath = null, localEngine = null, localEngineHost = null; + /// The path to the local engine source. final String? localEngineSrcPath; + + /// The local engine name. final String? localEngine; + + /// The local engine host. final String? localEngineHost; @override diff --git a/packages/shorebird_cli/lib/src/executables/devicectl/apple_device.dart b/packages/shorebird_cli/lib/src/executables/devicectl/apple_device.dart index caf113cc..94bbcf63 100644 --- a/packages/shorebird_cli/lib/src/executables/devicectl/apple_device.dart +++ b/packages/shorebird_cli/lib/src/executables/devicectl/apple_device.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'package:json_annotation/json_annotation.dart'; import 'package:pub_semver/pub_semver.dart'; import 'package:shorebird_cli/src/extensions/version.dart'; @@ -15,6 +13,7 @@ part 'apple_device.g.dart'; /// {@macro apple_device} class AppleDevice { + /// {@macro apple_device} const AppleDevice({ required this.deviceProperties, required this.hardwareProperties, @@ -62,8 +61,12 @@ class AppleDevice { String toString() => '$name ($osVersionString ${hardwareProperties.udid})'; } +/// {@template hardware_properties} +/// The hardware properties of a device. +/// {@endtemplate} @JsonSerializable(createToJson: false, fieldRename: FieldRename.none) class HardwareProperties { + /// {@macro hardware_properties} const HardwareProperties({required this.platform, required this.udid}); /// The device's platform (e.g., "iOS"). @@ -72,12 +75,17 @@ class HardwareProperties { /// The unique identifier of this device final String udid; + /// Creates a [HardwareProperties] from [json]. static HardwareProperties fromJson(Json json) => _$HardwarePropertiesFromJson(json); } +/// {@template device_properties} +/// The device properties for a given apple device. +/// {@endtemplate} @JsonSerializable(createToJson: false, fieldRename: FieldRename.none) class DeviceProperties { + /// {@macro device_properties} const DeviceProperties({required this.name, this.osVersionNumber}); /// Human-readable name of the device (e.g., "Joe's iPhone"). @@ -86,12 +94,17 @@ class DeviceProperties { /// The device's OS version as a string (e.g., "14.4.1"). final String? osVersionNumber; + /// Creates a [DeviceProperties] from [json]. static DeviceProperties fromJson(Json json) => _$DevicePropertiesFromJson(json); } +/// {@template connection_properties} +/// The connection properties of a device. +/// {@endtemplate} @JsonSerializable(createToJson: false, fieldRename: FieldRename.none) class ConnectionProperties { + /// {@macro connection_properties} const ConnectionProperties({required this.tunnelState, this.transportType}); /// How the device is connected. Values seen in development include @@ -106,6 +119,7 @@ class ConnectionProperties { /// - "unavailable" when the device is not connected via USB or wifi. final String tunnelState; + /// Creates a [ConnectionProperties] from [json]. static ConnectionProperties fromJson(Json json) => _$ConnectionPropertiesFromJson(json); } diff --git a/packages/shorebird_cli/lib/src/executables/devicectl/devicectl.dart b/packages/shorebird_cli/lib/src/executables/devicectl/devicectl.dart index 8e395917..f51c6765 100644 --- a/packages/shorebird_cli/lib/src/executables/devicectl/devicectl.dart +++ b/packages/shorebird_cli/lib/src/executables/devicectl/devicectl.dart @@ -1,4 +1,3 @@ -// ignore_for_file: public_member_api_docs import 'dart:async'; import 'dart:convert'; import 'dart:io'; @@ -15,6 +14,7 @@ import 'package:shorebird_cli/src/logging/logging.dart'; import 'package:shorebird_cli/src/shorebird_process.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; +/// Typedef for a bundle identifier string. typedef BundleId = String; /// {@template devicectl_exception} @@ -48,10 +48,11 @@ Devicectl get devicectl => read(devicectlRef); /// A wrapper around the `devicectl` command. class Devicectl { + /// The executable name (`xcrun`). static const executableName = 'xcrun'; - static const baseArgs = [ - 'devicectl', - ]; + + /// The base arguments for the `devicectl` command. + static const baseArgs = ['devicectl']; /// Whether the `devicectl` command is available. Future _isAvailable() async { @@ -61,7 +62,7 @@ class Devicectl { '--version', ]); return result.exitCode == ExitCode.success.code; - } catch (_) { + } on Exception { return false; } } @@ -186,7 +187,7 @@ class Devicectl { deviceId: device.udid, runnerApp: runnerAppDirectory, ); - } catch (error) { + } on Exception catch (error) { installProgress.fail('Failed to install app: $error'); return ExitCode.software.code; } @@ -195,7 +196,7 @@ class Devicectl { final launchProgress = logger.progress('Launching app'); try { await launchApp(deviceId: device.udid, bundleId: bundleId); - } catch (error) { + } on Exception catch (error) { launchProgress.fail('Failed to launch app: $error'); return ExitCode.software.code; } diff --git a/packages/shorebird_cli/lib/src/executables/devicectl/nserror.dart b/packages/shorebird_cli/lib/src/executables/devicectl/nserror.dart index 71822e61..3f3e8f67 100644 --- a/packages/shorebird_cli/lib/src/executables/devicectl/nserror.dart +++ b/packages/shorebird_cli/lib/src/executables/devicectl/nserror.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; @@ -20,13 +18,19 @@ class NSError extends Equatable { required this.userInfo, }); + /// The error code. final int code; + + /// The error domain. final String domain; + + /// Additional information about the error. final UserInfo userInfo; /// Creates an [NSError] from JSON. static NSError fromJson(Json json) => _$NSErrorFromJson(json); + /// Converts this [NSError] to [Json]. Json toJson() => _$NSErrorToJson(this); @override @@ -45,8 +49,12 @@ NSError( ]; } +/// {@template user_info} +/// A pared-down representation of the userInfo property of an NSError. +/// {@endtemplate} @JsonSerializable(fieldRename: FieldRename.none) class UserInfo extends Equatable { + /// {@macro user_info} const UserInfo({ this.description, this.localizedDescription, @@ -54,22 +62,29 @@ class UserInfo extends Equatable { this.underlyingError, }); + /// A description of the error. @JsonKey(name: 'NSDescription') final StringContainer? description; + /// A localized description of the error. @JsonKey(name: 'NSLocalizedDescription') final StringContainer? localizedDescription; + /// A localized description of the failure reason. @JsonKey(name: 'NSLocalizedFailureReason') final StringContainer? localizedFailureReason; + /// The underlying error, if any. @JsonKey(name: 'NSUnderlyingError') final NSUnderlyingError? underlyingError; + /// An empty [UserInfo] instance. static const nullInfo = UserInfo(); + /// Creates a [UserInfo] from [Json]. static UserInfo fromJson(Json json) => _$UserInfoFromJson(json); + /// Converts this [UserInfo] to [Json]. Json toJson() => _$UserInfoToJson(this); @override @@ -90,14 +105,21 @@ UserInfo( ]; } +/// {@template string_container} +/// A container for a string value. +/// {@endtemplate} @JsonSerializable(fieldRename: FieldRename.none) class StringContainer extends Equatable { + /// {@macro string_container} const StringContainer(this.string); + /// The string value. final String string; + /// Creates a [StringContainer] from [Json]. static StringContainer fromJson(Json json) => _$StringContainerFromJson(json); + /// Converts this [StringContainer] to [Json]. Json toJson() => _$StringContainerToJson(this); @override @@ -107,15 +129,22 @@ class StringContainer extends Equatable { List get props => [string]; } +/// {@template ns_underlying_error} +/// A pared-down representation of the NSUnderlyingError class. +/// {@endtemplate} @JsonSerializable(fieldRename: FieldRename.none) class NSUnderlyingError extends Equatable { + /// {@macro ns_underlying_error} const NSUnderlyingError({required this.error}); + /// The underlying error. final NSError? error; + /// Creates an [NSUnderlyingError] from [Json]. static NSUnderlyingError fromJson(Json json) => _$NSUnderlyingErrorFromJson(json); + /// Converts this [NSUnderlyingError] to [Json]. Json toJson() => _$NSUnderlyingErrorToJson(this); @override diff --git a/packages/shorebird_cli/lib/src/executables/gradlew.dart b/packages/shorebird_cli/lib/src/executables/gradlew.dart index 682fce6b..416be250 100644 --- a/packages/shorebird_cli/lib/src/executables/gradlew.dart +++ b/packages/shorebird_cli/lib/src/executables/gradlew.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'dart:io'; import 'package:collection/collection.dart'; @@ -12,7 +10,9 @@ import 'package:shorebird_cli/src/platform.dart'; import 'package:shorebird_cli/src/shorebird_documentation.dart'; import 'package:shorebird_cli/src/shorebird_process.dart'; +/// Exception thrown when the Gradle version is incompatible. class IncompatibleGradleException implements Exception { + /// The error pattern used to identify the exception. static const errorPattern = 'Unsupported class file major version'; @override @@ -50,15 +50,19 @@ To add android, run "flutter create . --platforms android"'''; } } +/// {@template missing_gradle_wrapper_exception} /// Thrown when the gradle wrapper cannot be found. /// This has been resolved on the master channel but /// on the stable channel currently creating an app via /// `flutter create` does not generate a gradle wrapper which /// means we're not able to accurately detect flavors until /// the user has run `flutter build apk` at least once. +/// {@endtemplate} class MissingGradleWrapperException implements Exception { + /// {@macro missing_gradle_wrapper_exception} const MissingGradleWrapperException(this.executablePath); + /// The path to the gradle wrapper executable. final String executablePath; @override @@ -77,6 +81,7 @@ Gradlew get gradlew => read(gradlewRef); /// A wrapper around the gradle wrapper (gradlew). class Gradlew { + /// The name of the executable. String get executable => platform.isWindows ? 'gradlew.bat' : 'gradlew'; Future _run( diff --git a/packages/shorebird_cli/lib/src/executables/idevicesyslog.dart b/packages/shorebird_cli/lib/src/executables/idevicesyslog.dart index 9419188c..9d4702ba 100644 --- a/packages/shorebird_cli/lib/src/executables/idevicesyslog.dart +++ b/packages/shorebird_cli/lib/src/executables/idevicesyslog.dart @@ -63,6 +63,7 @@ class IDeviceSysLog { /// idevicesyslog tails all logs produced by the device (similar to what is /// shown in Console.app). This is very noisy and we only want to show logs /// that are produced by the app. These log lines are of the form: + // ignore: unintended_html_in_doc_comment /// Nov 10 14:46:57 Runner(Flutter)[1044] : flutter: hello static RegExp appLogLineRegex = RegExp(r'\(Flutter\)\[\d+\] : (.*)$'); diff --git a/packages/shorebird_cli/lib/src/executables/ios_deploy.dart b/packages/shorebird_cli/lib/src/executables/ios_deploy.dart index 2a11166d..3905fb75 100644 --- a/packages/shorebird_cli/lib/src/executables/ios_deploy.dart +++ b/packages/shorebird_cli/lib/src/executables/ios_deploy.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'dart:async'; import 'dart:convert'; import 'dart:io'; @@ -25,13 +23,18 @@ enum _DebuggerState { attached, } +/// {@template ios_deploy} /// Wrapper around the `ios-deploy` command cached by the Flutter tool. /// https://github.com/ios-control/ios-deploy +/// {@endtemplate} class IOSDeploy { - IOSDeploy({ProcessSignal? sigint}) : _sigint = sigint ?? ProcessSignal.sigint; + /// {@macro ios_deploy} + const IOSDeploy({ProcessSignal? sigint}) + : _sigint = sigint ?? ProcessSignal.sigint; final ProcessSignal _sigint; + /// The location of the ios-deploy executable. @visibleForTesting static File get iosDeployExecutable => File( p.join( @@ -76,19 +79,25 @@ class IOSDeploy { // Print backtrace for all threads while app is stopped. static const String _backTraceAll = 'thread backtrace all'; - // No provision profile errors. + /// No provision profile errors. + /// One of the possible errors when there is no provisioning profile. static const noProvisioningProfileErrorOne = 'Error 0xe8008015'; + + /// Another possible error when there is no provisioning profile. static const noProvisioningProfileErrorTwo = 'Error 0xe8000067'; - // Device locked errors. + /// Device locked errors. + /// Error when the device is locked. static const deviceLockedError = 'e80000e2'; + + /// Error message when the device is locked. static const deviceLockedErrorMessage = 'the device was not, or could not be, unlocked'; - // Unknown launch error. + /// Unknown launch error. static const unknownAppLaunchError = 'Error 0xe8000022'; - // Message when there is an unknown error. + /// Message when there is an unknown error. static const unknownErrorFixInstructions = ''' ═══════════════════════════════════════════════════════════════════════════════════ Error launching app. Try launching from within Xcode via: @@ -97,13 +106,13 @@ Error launching app. Try launching from within Xcode via: Your Xcode version may be too old for your iOS version. ═══════════════════════════════════════════════════════════════════════════════════'''; - // Message when the device is locked. + /// Message when the device is locked. static const deviceLockedFixInstructions = ''' ═══════════════════════════════════════════════════════════════════════════════════ Your device is locked. Unlock your device first before running. ═══════════════════════════════════════════════════════════════════════════════════'''; - // Message when there is no development team selected. + /// Message when there is no development team selected. static const developmentTeamFixInstructions = ''' 1- Open the Flutter project's Xcode target with open ios/Runner.xcworkspace @@ -297,7 +306,7 @@ Or run on an iOS simulator without code signing unawaited(stdoutSubscription.cancel()); unawaited(stderrSubscription.cancel()); return status; - } catch (exception, stackTrace) { + } on Exception catch (exception, stackTrace) { logger.detail('[ios-deploy] failed: $exception\n$stackTrace'); debuggerState = _DebuggerState.detached; logger.err('[ios-deploy] failed: $exception'); @@ -328,8 +337,8 @@ Or run on an iOS simulator without code signing } } -// Handles interpreting stdout line and logs errors accordingly. -// Always returns the original line. +/// Handles interpreting stdout line and logs errors accordingly. +/// Always returns the original line. @visibleForTesting String detectFailures(String line, Logger logger) { final isMissingProvisioningProfile = diff --git a/packages/shorebird_cli/lib/src/extensions/arg_results.dart b/packages/shorebird_cli/lib/src/extensions/arg_results.dart index 44286bca..40a7aa44 100644 --- a/packages/shorebird_cli/lib/src/extensions/arg_results.dart +++ b/packages/shorebird_cli/lib/src/extensions/arg_results.dart @@ -1,4 +1,3 @@ -// ignore_for_file: public_member_api_docs import 'dart:io'; import 'package:args/args.dart'; @@ -11,6 +10,7 @@ import 'package:shorebird_cli/src/logging/logging.dart'; import 'package:shorebird_cli/src/release_type.dart'; import 'package:shorebird_cli/src/third_party/flutter_tools/lib/flutter_tools.dart'; +/// Extension on [ArgResults] to make it easier to work with options. extension OptionFinder on ArgResults { /// Detects flags even when passed to underlying commands via a `--` /// separator. @@ -50,6 +50,7 @@ extension OptionFinder on ArgResults { } } +/// Extension on [ArgResults] to provide code signing related extensions. extension CodeSign on ArgResults { /// Asserts that either there is no public key argument /// or that the path received exists. @@ -101,6 +102,7 @@ extension FileArgs on ArgResults { } } +/// Extension on [ArgResults] to provide forwarded arguments. extension ForwardedArgs on ArgResults { bool _isPositionalArgPlatform(String arg) => ReleaseType.values.any((target) => target.cliName == arg); diff --git a/packages/shorebird_cli/lib/src/extensions/string.dart b/packages/shorebird_cli/lib/src/extensions/string.dart index 4a8777e1..a6eb02f5 100644 --- a/packages/shorebird_cli/lib/src/extensions/string.dart +++ b/packages/shorebird_cli/lib/src/extensions/string.dart @@ -1,10 +1,10 @@ -// ignore_for_file: public_member_api_docs - +/// Extension on [String] to provide null or empty getter. extension NullOrEmpty on String? { /// Returns `true` if this string is null or empty. bool get isNullOrEmpty => this == null || this!.isEmpty; } +/// Extension on [String] to provide Ansi escape code helpers. extension AnsiEscapes on String { /// Removes ANSI escape codes (usually the result of a lightCyan.wrap or /// similar) from this string. Used to clean up @@ -29,6 +29,7 @@ extension AnsiEscapes on String { } } +/// Extension on [String] to provide an `isUpperCase` getter. extension IsUpperCase on String { /// Returns `true` if this string is in uppercase. bool isUpperCase() => this == toUpperCase(); diff --git a/packages/shorebird_cli/lib/src/extensions/version.dart b/packages/shorebird_cli/lib/src/extensions/version.dart index c7ad88ac..7d26d10c 100644 --- a/packages/shorebird_cli/lib/src/extensions/version.dart +++ b/packages/shorebird_cli/lib/src/extensions/version.dart @@ -17,10 +17,8 @@ Version? tryParseVersion(String versionString, {bool strict = true}) { // and try again. try { return Version.parse('$versionString.0'); - } catch (_) { + } on FormatException { return null; } - } catch (_) { - return null; } } diff --git a/packages/shorebird_cli/lib/src/http_client/retrying_client.dart b/packages/shorebird_cli/lib/src/http_client/retrying_client.dart index 72be4b5c..f745f1bd 100644 --- a/packages/shorebird_cli/lib/src/http_client/retrying_client.dart +++ b/packages/shorebird_cli/lib/src/http_client/retrying_client.dart @@ -13,11 +13,11 @@ http.Client retryingHttpClient(http.Client client) => RetryClient( /// Returns `true` if the [exception] is a retryable exception. bool isRetryableException(Object exception, StackTrace _) { return switch (exception.runtimeType) { - http.ClientException => true, - HttpException => true, - TlsException => true, - SocketException => true, - WebSocketException => true, + const (http.ClientException) => true, + const (HttpException) => true, + const (TlsException) => true, + const (SocketException) => true, + const (WebSocketException) => true, _ => false, }; } diff --git a/packages/shorebird_cli/lib/src/metadata/build_environment_metadata.dart b/packages/shorebird_cli/lib/src/metadata/build_environment_metadata.dart index 0aab57a0..179740ea 100644 --- a/packages/shorebird_cli/lib/src/metadata/build_environment_metadata.dart +++ b/packages/shorebird_cli/lib/src/metadata/build_environment_metadata.dart @@ -47,11 +47,11 @@ class BuildEnvironmentMetadata extends Equatable { ); // coverage:ignore-end - /// Converts a Map to a [BuildEnvironmentMetadata] + /// Converts a `Map` to a [BuildEnvironmentMetadata] factory BuildEnvironmentMetadata.fromJson(Map json) => _$BuildEnvironmentMetadataFromJson(json); - /// Converts a [BuildEnvironmentMetadata] to a Map + /// Converts a [BuildEnvironmentMetadata] to a `Map` Map toJson() => _$BuildEnvironmentMetadataToJson(this); /// Creates a copy of this [BuildEnvironmentMetadata] with the given fields diff --git a/packages/shorebird_cli/lib/src/metadata/create_patch_metadata.dart b/packages/shorebird_cli/lib/src/metadata/create_patch_metadata.dart index a428abfc..f20c2fd3 100644 --- a/packages/shorebird_cli/lib/src/metadata/create_patch_metadata.dart +++ b/packages/shorebird_cli/lib/src/metadata/create_patch_metadata.dart @@ -51,11 +51,11 @@ class CreatePatchMetadata extends Equatable { ); // coverage:ignore-end - /// Converts a Map to a [CreatePatchMetadata] + /// Converts a `Map` to a [CreatePatchMetadata] factory CreatePatchMetadata.fromJson(Map json) => _$CreatePatchMetadataFromJson(json); - /// Converts a [CreatePatchMetadata] to a Map + /// Converts a [CreatePatchMetadata] to a `Map` Map toJson() => _$CreatePatchMetadataToJson(this); /// Returns a copy of this [CreatePatchMetadata] with the given fields diff --git a/packages/shorebird_cli/lib/src/metadata/update_release_metadata.dart b/packages/shorebird_cli/lib/src/metadata/update_release_metadata.dart index 15acc86f..58f6fcaf 100644 --- a/packages/shorebird_cli/lib/src/metadata/update_release_metadata.dart +++ b/packages/shorebird_cli/lib/src/metadata/update_release_metadata.dart @@ -42,11 +42,11 @@ class UpdateReleaseMetadata extends Equatable { ); // coverage:ignore-end - /// Converts a Map to a [UpdateReleaseMetadata]. + /// Converts a `Map` to a [UpdateReleaseMetadata]. factory UpdateReleaseMetadata.fromJson(Map json) => _$UpdateReleaseMetadataFromJson(json); - /// Converts a [UpdateReleaseMetadata] to a Map. + /// Converts a [UpdateReleaseMetadata] to a `Map`. Map toJson() => _$UpdateReleaseMetadataToJson(this); /// Returns a copy of this [UpdateReleaseMetadata] with the given fields diff --git a/packages/shorebird_cli/lib/src/network_checker.dart b/packages/shorebird_cli/lib/src/network_checker.dart index f511b9b3..41d1366b 100644 --- a/packages/shorebird_cli/lib/src/network_checker.dart +++ b/packages/shorebird_cli/lib/src/network_checker.dart @@ -51,7 +51,7 @@ class NetworkChecker { try { await httpClient.get(url); progress.complete('$url ${lightGreen.wrap('OK')}'); - } catch (e) { + } on Exception catch (e) { progress.fail('$url unreachable'); logger.detail('Failed to reach $url: $e'); } diff --git a/packages/shorebird_cli/lib/src/platform/ios.dart b/packages/shorebird_cli/lib/src/platform/ios.dart index a056bca0..c740f0d0 100644 --- a/packages/shorebird_cli/lib/src/platform/ios.dart +++ b/packages/shorebird_cli/lib/src/platform/ios.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'dart:io'; import 'package:collection/collection.dart'; @@ -32,17 +30,24 @@ To add iOS, run "flutter create . --platforms ios"'''; /// Acceptable values can be found by running `flutter build ipa -h`. /// {@endtemplate} enum ExportMethod { + /// Upload to the App Store. appStore('app-store', 'Upload to the App Store'), + + /// Ad-hoc distribution. adHoc( 'ad-hoc', ''' Test on designated devices that do not need to be registered with the Apple developer account. Requires a distribution certificate.''', ), + + /// Development distribution. development( 'development', '''Test only on development devices registered with the Apple developer account.''', ), + + /// Enterprise distribution. enterprise( 'enterprise', 'Distribute an app registered with the Apple Developer Enterprise Program.', @@ -81,6 +86,7 @@ final iosRef = create(Ios.new); /// The [Ios] instance available in the current zone. Ios get ios => read(iosRef); +/// A class that provides information about the iOS platform. class Ios { /// Returns the set of flavors for the iOS project, if the project has an /// iOS platform configured. diff --git a/packages/shorebird_cli/lib/src/release_type.dart b/packages/shorebird_cli/lib/src/release_type.dart index 570d1e2a..d5028244 100644 --- a/packages/shorebird_cli/lib/src/release_type.dart +++ b/packages/shorebird_cli/lib/src/release_type.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'package:args/args.dart'; import 'package:collection/collection.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; @@ -54,7 +52,9 @@ enum ReleaseType { } } +/// Extension on [ArgResults] to get the release types from the CLI arguments. extension ReleaseTypeArgs on ArgResults { + /// The release types specified in the CLI arguments. Iterable get releaseTypes { List? releaseTypeCliNames; if (wasParsed('platforms')) { diff --git a/packages/shorebird_cli/lib/src/shorebird_android_artifacts.dart b/packages/shorebird_cli/lib/src/shorebird_android_artifacts.dart index 5a632520..5a0c13b2 100644 --- a/packages/shorebird_cli/lib/src/shorebird_android_artifacts.dart +++ b/packages/shorebird_cli/lib/src/shorebird_android_artifacts.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'dart:io'; import 'package:collection/collection.dart'; @@ -33,14 +31,20 @@ class MultipleArtifactsFoundException implements Exception { } } +/// {@template artifact_not_found_exception} /// Thrown when no artifact is found in the build directory. +/// {@endtemplate} class ArtifactNotFoundException implements Exception { - ArtifactNotFoundException({ + /// {@macro artifact_not_found_exception} + const ArtifactNotFoundException({ required this.artifactName, required this.buildDir, }); + /// The name of the artifact. final String artifactName; + + /// The build directory where the artifact was expected to be. final String buildDir; @override @@ -77,8 +81,10 @@ extension on String { String get artifactId => replaceAll(RegExp(r'\W'), '').toLowerCase(); } +/// The reference to the [ShorebirdAndroidArtifacts] instance. final shorebirdAndroidArtifactsRef = create(ShorebirdAndroidArtifacts.new); +/// The [ShorebirdAndroidArtifacts] instance available in the current zone. ShorebirdAndroidArtifacts get shorebirdAndroidArtifacts => read(shorebirdAndroidArtifactsRef); @@ -168,6 +174,7 @@ class ShorebirdAndroidArtifacts { ); } + /// The path to the aar library. static String get aarLibraryPath { final projectRoot = shorebirdEnv.getShorebirdProjectRoot()!; return p.joinAll([ @@ -179,6 +186,7 @@ class ShorebirdAndroidArtifacts { ]); } + /// The path to the aar directory. static String aarArtifactDirectory({ required String packageName, required String buildNumber, @@ -190,6 +198,7 @@ class ShorebirdAndroidArtifacts { buildNumber, ]); + /// The path to the aar artifact. static String aarArtifactPath({ required String packageName, required String buildNumber, diff --git a/packages/shorebird_cli/lib/src/shorebird_artifacts.dart b/packages/shorebird_cli/lib/src/shorebird_artifacts.dart index 3e67fdb3..f8319f58 100644 --- a/packages/shorebird_cli/lib/src/shorebird_artifacts.dart +++ b/packages/shorebird_cli/lib/src/shorebird_artifacts.dart @@ -1,3 +1,4 @@ +// Allowing one member abstracts for consistency/namespace/ease of testing. // ignore_for_file: one_member_abstracts import 'dart:io'; diff --git a/packages/shorebird_cli/lib/src/shorebird_cli_command_runner.dart b/packages/shorebird_cli/lib/src/shorebird_cli_command_runner.dart index 9ca2c1a2..90ca391f 100644 --- a/packages/shorebird_cli/lib/src/shorebird_cli_command_runner.dart +++ b/packages/shorebird_cli/lib/src/shorebird_cli_command_runner.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'dart:async'; import 'package:args/args.dart'; @@ -19,14 +17,19 @@ import 'package:shorebird_cli/src/shorebird_version.dart'; import 'package:shorebird_cli/src/third_party/flutter_tools/lib/flutter_tools.dart'; import 'package:shorebird_cli/src/version.dart'; +/// The name of the executable. const executableName = 'shorebird'; + +/// The name of the package (e.g. name in the pubspec.yaml). const packageName = 'shorebird_cli'; + +/// The package description. const description = 'The shorebird command-line tool'; /// {@template shorebird_cli_command_runner} /// A [CommandRunner] for the CLI. /// -/// ``` +/// ```sh /// $ shorebird --version /// ``` /// {@endtemplate} @@ -209,6 +212,10 @@ Engine • revision ${shorebirdEnv.shorebirdEngineRevision}'''); // When on an usage exception we don't need to show the "if you aren't // sure" message, so we do an early return here. return ExitCode.usage.code; + + // We explicitly want to catch all exceptions here to log them and show + // the user a friendly message. + // ignore: avoid_catches_without_on_clauses } catch (error, stackTrace) { logger ..err('$error') @@ -248,7 +255,7 @@ ${currentRunLogFile.absolute.path} Future _tryGetFlutterVersion() async { try { return await shorebirdFlutter.getVersionString(); - } catch (error) { + } on Exception catch (error) { logger.detail('Unable to determine Flutter version.\n$error'); return null; } @@ -266,7 +273,7 @@ ${currentRunLogFile.absolute.path} ..info('A new version of shorebird is available!') ..info('Run ${lightCyan.wrap('shorebird upgrade')} to upgrade.'); } - } catch (error) { + } on Exception catch (error) { logger.detail('Unable to check for updates.\n$error'); } } diff --git a/packages/shorebird_cli/lib/src/shorebird_command.dart b/packages/shorebird_cli/lib/src/shorebird_command.dart index 02c0c49d..a165fc5a 100644 --- a/packages/shorebird_cli/lib/src/shorebird_command.dart +++ b/packages/shorebird_cli/lib/src/shorebird_command.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'dart:io'; import 'package:args/args.dart'; @@ -15,17 +13,22 @@ typedef HashFunction = String Function(List bytes); /// Signature for a function which takes a path to a zip file. typedef UnzipFn = Future Function(String zipFilePath, String outputDir); +/// Signature for a function which builds a [CodePushClient]. typedef CodePushClientBuilder = CodePushClient Function({ required http.Client httpClient, Uri? hostedUri, }); +/// Signature for a function which starts a process (e.g. [Process.start]). typedef StartProcess = Future Function( String executable, List arguments, { bool runInShell, }); +/// {@template shorebird_command} +/// A command in the Shorebird CLI. +/// {@endtemplate} abstract class ShorebirdCommand extends Command { // We don't currently have a test involving both a CommandRunner // and a Command, so we can't test this getter. diff --git a/packages/shorebird_cli/lib/src/shorebird_env.dart b/packages/shorebird_cli/lib/src/shorebird_env.dart index 0aa60f9e..3f1b8684 100644 --- a/packages/shorebird_cli/lib/src/shorebird_env.dart +++ b/packages/shorebird_cli/lib/src/shorebird_env.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'dart:io' hide Platform; import 'package:checked_yaml/checked_yaml.dart'; @@ -26,6 +24,7 @@ class ShorebirdEnv { const ShorebirdEnv({String? flutterRevisionOverride}) : _flutterRevisionOverride = flutterRevisionOverride; + /// Copy the [ShorebirdEnv] and optionally override the flutter revision. ShorebirdEnv copyWith({String? flutterRevisionOverride}) => ShorebirdEnv( flutterRevisionOverride: flutterRevisionOverride ?? _flutterRevisionOverride, @@ -50,6 +49,7 @@ class ShorebirdEnv { return File(platform.script.toFilePath()).parent.parent.parent; } + /// The Shorebird engine revision. String get shorebirdEngineRevision { return File( p.join( @@ -61,6 +61,7 @@ class ShorebirdEnv { ).readAsStringSync().trim(); } + /// Set the Shorebird Flutter revision. set flutterRevision(String revision) { if (revision == flutterRevision) return; File( @@ -72,6 +73,7 @@ class ShorebirdEnv { if (snapshot.existsSync()) snapshot.deleteSync(); } + /// Get the Shorebird Flutter revision. String get flutterRevision { return _flutterRevisionOverride ?? File( @@ -176,10 +178,10 @@ class ShorebirdEnv { Pubspec? getPubspecYaml() { final root = getFlutterProjectRoot(); if (root == null) return null; - final yaml = getPubspecYamlFile(cwd: root).readAsStringSync(); try { + final yaml = getPubspecYamlFile(cwd: root).readAsStringSync(); return Pubspec.parse(yaml, lenient: true); - } catch (_) { + } on Exception { return null; } } @@ -217,7 +219,7 @@ class ShorebirdEnv { final baseUrl = platform.environment['SHOREBIRD_HOSTED_URL'] ?? getShorebirdYaml()?.baseUrl; return baseUrl == null ? null : Uri.tryParse(baseUrl); - } catch (_) { + } on Exception { return null; } } diff --git a/packages/shorebird_cli/lib/src/shorebird_flutter.dart b/packages/shorebird_cli/lib/src/shorebird_flutter.dart index c815f0b1..ce23d059 100644 --- a/packages/shorebird_cli/lib/src/shorebird_flutter.dart +++ b/packages/shorebird_cli/lib/src/shorebird_flutter.dart @@ -1,4 +1,3 @@ -// ignore_for_file: public_member_api_docs import 'dart:convert'; import 'dart:io'; @@ -26,7 +25,10 @@ class ShorebirdFlutter { /// {@macro shorebird_flutter} const ShorebirdFlutter(); + /// The executable name. static const executable = 'flutter'; + + /// The Shorebird Flutter fork git URL. static const String flutterGitUrl = 'https://github.com/shorebirdtech/flutter.git'; @@ -41,6 +43,7 @@ class ShorebirdFlutter { return p.join(shorebirdEnv.flutterDirectory.parent.path, revision); } + /// Install the provided Flutter [revision]. Future installRevision({required String revision}) async { final targetDirectory = Directory(_workingDirectory(revision: revision)); if (targetDirectory.existsSync()) return; @@ -85,7 +88,7 @@ class ShorebirdFlutter { runInShell: true, ); precacheProgress.complete(); - } catch (_) { + } on Exception { precacheProgress.fail('Failed to precache Flutter $version'); logger.info( '''This is not a critical error, but your next build make take longer than usual.''', @@ -149,7 +152,7 @@ class ShorebirdFlutter { try { version = await getVersionString(); - } catch (_) { + } on Exception { version = 'unknown'; } @@ -223,7 +226,7 @@ class ShorebirdFlutter { if (version != null) { return versionOrHash; } - } catch (_) { + } on Exception { return null; } @@ -245,7 +248,7 @@ class ShorebirdFlutter { final versionString = await getVersionForRevision(flutterRevision: versionOrHash); return versionString != null ? tryParseVersion(versionString) : null; - } catch (_) { + } on Exception { return null; } } @@ -260,6 +263,7 @@ class ShorebirdFlutter { return LineSplitter.split(result).toList().firstOrNull; } + /// Get the list of Flutter versions for the given [revision]. Future> getVersions({String? revision}) async { final result = await git.forEachRef( format: '%(refname:short)', @@ -271,6 +275,7 @@ class ShorebirdFlutter { .toList(); } + /// Use the provided [version] of Flutter. Future useVersion({required String version}) async { final revision = await git.revParse( revision: 'origin/flutter_release/$version', @@ -280,6 +285,7 @@ class ShorebirdFlutter { await useRevision(revision: revision); } + /// Use the provided [revision] of Flutter. Future useRevision({required String revision}) async { await installRevision(revision: revision); diff --git a/packages/shorebird_cli/lib/src/shorebird_validator.dart b/packages/shorebird_cli/lib/src/shorebird_validator.dart index 1a9b4631..547cd76d 100644 --- a/packages/shorebird_cli/lib/src/shorebird_validator.dart +++ b/packages/shorebird_cli/lib/src/shorebird_validator.dart @@ -1,5 +1,3 @@ -// ignore_for_file: public_member_api_docs - import 'package:collection/collection.dart'; import 'package:mason_logger/mason_logger.dart'; import 'package:scoped_deps/scoped_deps.dart'; @@ -9,25 +7,31 @@ import 'package:shorebird_cli/src/platform.dart'; import 'package:shorebird_cli/src/shorebird_env.dart'; import 'package:shorebird_cli/src/validators/validators.dart'; +/// An exception thrown when a precondition for running a command is not met. abstract interface class PreconditionFailedException implements Exception { + /// The exit code to use when the precondition fails. ExitCode get exitCode; } +/// An exception thrown when Shorebird has not been initialized. class ShorebirdNotInitializedException implements PreconditionFailedException { @override ExitCode get exitCode => ExitCode.config; } +/// An exception thrown when the user is not authorized to run a command. class UserNotAuthorizedException implements PreconditionFailedException { @override ExitCode get exitCode => ExitCode.noUser; } +/// An exception thrown when validation fails. class ValidationFailedException implements PreconditionFailedException { @override ExitCode get exitCode => ExitCode.config; } +/// An exception thrown when a command is run in an unsupported context. class UnsupportedContextException implements PreconditionFailedException { // coverage:ignore-start @override @@ -35,6 +39,7 @@ class UnsupportedContextException implements PreconditionFailedException { // coverage:ignore-end } +/// An exception thrown when the operating system is not supported. class UnsupportedOperatingSystemException implements PreconditionFailedException { @override diff --git a/packages/shorebird_cli/lib/src/validators/shorebird_flutter_validator.dart b/packages/shorebird_cli/lib/src/validators/shorebird_flutter_validator.dart index 9c345d37..2f354847 100644 --- a/packages/shorebird_cli/lib/src/validators/shorebird_flutter_validator.dart +++ b/packages/shorebird_cli/lib/src/validators/shorebird_flutter_validator.dart @@ -54,7 +54,7 @@ class ShorebirdFlutterValidator extends Validator { String? shorebirdFlutterVersionString; try { shorebirdFlutterVersionString = await _getFlutterVersion(); - } catch (error) { + } on Exception catch (error) { issues.add( ValidationIssue.error( message: 'Failed to determine Shorebird Flutter version. $error', @@ -69,7 +69,7 @@ class ShorebirdFlutterValidator extends Validator { ); } on CommandNotFoundException catch (_) { // If there is no system Flutter, we don't throw a validation exception. - } catch (error) { + } on Exception catch (error) { issues.add( ValidationIssue.error( message: 'Failed to determine path Flutter version. $error', diff --git a/packages/shorebird_cli/pubspec.lock b/packages/shorebird_cli/pubspec.lock index 4dcb8e25..c3dc3b7c 100644 --- a/packages/shorebird_cli/pubspec.lock +++ b/packages/shorebird_cli/pubspec.lock @@ -799,10 +799,10 @@ packages: dependency: "direct dev" description: name: very_good_analysis - sha256: "1fb637c0022034b1f19ea2acb42a3603cbd8314a470646a59a2fb01f5f3a8629" + sha256: "62d2b86d183fb81b2edc22913d9f155d26eb5cf3855173adb1f59fac85035c63" url: "https://pub.dev" source: hosted - version: "6.0.0" + version: "7.0.0" vm_service: dependency: transitive description: diff --git a/packages/shorebird_cli/pubspec.yaml b/packages/shorebird_cli/pubspec.yaml index 95c22040..03bba26d 100644 --- a/packages/shorebird_cli/pubspec.yaml +++ b/packages/shorebird_cli/pubspec.yaml @@ -55,7 +55,7 @@ dev_dependencies: json_serializable: ^6.9.0 mocktail: ^1.0.3 test: ^1.25.9 - very_good_analysis: ^6.0.0 + very_good_analysis: ^7.0.0 executables: shorebird: diff --git a/packages/shorebird_cli/test/src/artifact_builder_test.dart b/packages/shorebird_cli/test/src/artifact_builder_test.dart index 347cd8d8..03d5a24c 100644 --- a/packages/shorebird_cli/test/src/artifact_builder_test.dart +++ b/packages/shorebird_cli/test/src/artifact_builder_test.dart @@ -331,7 +331,7 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod flavor: any(named: 'flavor'), ), ).thenThrow( - ArtifactNotFoundException( + const ArtifactNotFoundException( artifactName: 'app-release.aab', buildDir: 'buildDir', ), @@ -573,7 +573,7 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod flavor: any(named: 'flavor'), ), ).thenThrow( - ArtifactNotFoundException( + const ArtifactNotFoundException( artifactName: 'app-release.aab', buildDir: 'buildDir', ), diff --git a/packages/shorebird_cli/test/src/auth/auth_test.dart b/packages/shorebird_cli/test/src/auth/auth_test.dart index 289b045d..36854a61 100644 --- a/packages/shorebird_cli/test/src/auth/auth_test.dart +++ b/packages/shorebird_cli/test/src/auth/auth_test.dart @@ -209,6 +209,21 @@ void main() { }); group('AuthenticatedClient', () { + group('isAuthenticated', () { + group('when credentials are malformed', () { + setUp(() { + File( + p.join(credentialsDir, 'credentials.json'), + ).writeAsStringSync('invalid credentials'); + auth = buildAuth(); + }); + + test('returns false', () { + expect(auth.isAuthenticated, isFalse); + }); + }); + }); + group('token', () { test('does not require an onRefreshCredentials callback', () { expect( diff --git a/packages/shorebird_cli/test/src/code_push_client_wrapper_test.dart b/packages/shorebird_cli/test/src/code_push_client_wrapper_test.dart index 215d845e..a5361eef 100644 --- a/packages/shorebird_cli/test/src/code_push_client_wrapper_test.dart +++ b/packages/shorebird_cli/test/src/code_push_client_wrapper_test.dart @@ -158,6 +158,7 @@ void main() { late CodePushClient codePushClient; late Ditto ditto; late ShorebirdLogger logger; + late ShorebirdEnv shorebirdEnv; late ShorebirdFlutter shorebirdFlutter; late Progress progress; late CodePushClientWrapper codePushClientWrapper; @@ -171,6 +172,7 @@ void main() { dittoRef.overrideWith(() => ditto), loggerRef.overrideWith(() => logger), platformRef.overrideWith(() => platform), + shorebirdEnvRef.overrideWith(() => shorebirdEnv), shorebirdFlutterRef.overrideWith(() => shorebirdFlutter), }, ); @@ -193,6 +195,7 @@ void main() { () => CodePushClientWrapper(codePushClient: codePushClient), ); + shorebirdEnv = MockShorebirdEnv(); shorebirdFlutter = MockShorebirdFlutter(); when( @@ -233,9 +236,7 @@ void main() { displayName: appName, organizationId: any(named: 'organizationId'), ), - ).thenAnswer( - (_) async => app, - ); + ).thenAnswer((_) async => app); await runWithOverrides( () => codePushClientWrapper.createApp( diff --git a/packages/shorebird_cli/test/src/commands/doctor_command_test.dart b/packages/shorebird_cli/test/src/commands/doctor_command_test.dart index 69ebf687..1c8f9be5 100644 --- a/packages/shorebird_cli/test/src/commands/doctor_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/doctor_command_test.dart @@ -99,6 +99,7 @@ void main() { when( () => doctor.runValidators(any(), applyFixes: any(named: 'applyFixes')), ).thenAnswer((_) async => {}); + when(shorebirdFlutter.getVersionString).thenAnswer((_) async => null); command = runWithOverrides(DoctorCommand.new) ..testArgResults = argResults; @@ -106,7 +107,11 @@ void main() { test( 'prints shorebird version, flutter revision, ' - 'and engine revision', () async { + 'and engine revision ' + 'when unable to determine Flutter version', () async { + when( + () => shorebirdFlutter.getVersionString(), + ).thenThrow(Exception('oops')); await runWithOverrides(command.run); verify( @@ -117,6 +122,11 @@ Flutter • revision ${shorebirdEnv.flutterRevision} Engine • revision $shorebirdEngineRevision '''), ).called(1); + verify( + () => logger.detail( + 'Unable to determine Flutter version.\nException: oops', + ), + ).called(1); }); test( diff --git a/packages/shorebird_cli/test/src/commands/flutter/versions/flutter_versions_list_command_test.dart b/packages/shorebird_cli/test/src/commands/flutter/versions/flutter_versions_list_command_test.dart index c2f6f643..7f11bb54 100644 --- a/packages/shorebird_cli/test/src/commands/flutter/versions/flutter_versions_list_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/flutter/versions/flutter_versions_list_command_test.dart @@ -46,7 +46,7 @@ void main() { when( () => shorebirdFlutter.getVersionString(), ).thenAnswer((_) async => '1.0.0'); - when(() => shorebirdFlutter.getVersions()).thenThrow('error'); + when(() => shorebirdFlutter.getVersions()).thenThrow(Exception('error')); await expectLater( runWithOverrides(command.run), completion(equals(ExitCode.software.code)), @@ -56,7 +56,7 @@ void main() { () => shorebirdFlutter.getVersionString(), () => shorebirdFlutter.getVersions(), () => progress.fail('Failed to fetch Flutter versions.'), - () => logger.err('error'), + () => logger.err('Exception: error'), ]); }); diff --git a/packages/shorebird_cli/test/src/commands/login_ci_command_test.dart b/packages/shorebird_cli/test/src/commands/login_ci_command_test.dart index 32144126..1af9c8f2 100644 --- a/packages/shorebird_cli/test/src/commands/login_ci_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/login_ci_command_test.dart @@ -44,6 +44,15 @@ void main() { when(() => results.wasParsed('provider')).thenReturn(false); when(() => results['provider']).thenReturn(null); when(() => auth.client).thenReturn(httpClient); + when( + () => auth.loginCI(any(), prompt: any(named: 'prompt')), + ).thenAnswer( + (_) async => const CiToken( + // "shorebird-token" in base64 + refreshToken: 'c2hvcmViaXJkLXRva2Vu', // cspell:disable-line + authProvider: AuthProvider.google, + ), + ); when( () => logger.chooseOne( any(), @@ -52,8 +61,9 @@ void main() { ), ).thenReturn(AuthProvider.google); - command = - runWithOverrides(() => LoginCiCommand()..testArgResults = results); + command = runWithOverrides( + () => LoginCiCommand()..testArgResults = results, + ); }); group('provider', () { diff --git a/packages/shorebird_cli/test/src/commands/login_command_test.dart b/packages/shorebird_cli/test/src/commands/login_command_test.dart index 6f0e156e..b5a575b6 100644 --- a/packages/shorebird_cli/test/src/commands/login_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/login_command_test.dart @@ -53,6 +53,10 @@ void main() { when(() => auth.credentialsFilePath).thenReturn( p.join(applicationConfigHome.path, 'credentials.json'), ); + when( + () => auth.login(any(), prompt: any(named: 'prompt')), + ).thenAnswer((_) async {}); + when( () => logger.chooseOne( any(), @@ -61,8 +65,9 @@ void main() { ), ).thenReturn(AuthProvider.google); - command = - runWithOverrides(() => LoginCommand()..testArgResults = results); + command = runWithOverrides( + () => LoginCommand()..testArgResults = results, + ); }); group('provider', () { diff --git a/packages/shorebird_cli/test/src/commands/patch/ios_framework_patcher_test.dart b/packages/shorebird_cli/test/src/commands/patch/ios_framework_patcher_test.dart index 337fca2c..4c6405f7 100644 --- a/packages/shorebird_cli/test/src/commands/patch/ios_framework_patcher_test.dart +++ b/packages/shorebird_cli/test/src/commands/patch/ios_framework_patcher_test.dart @@ -440,7 +440,9 @@ void main() { test('forwards --split-debug-info to builder', () async { try { await runWithOverrides(patcher.buildPatchArtifact); - } catch (_) {} + } on Exception { + // ignore + } verify( () => artifactBuilder.buildElfAotSnapshot( appDillPath: any(named: 'appDillPath'), @@ -643,6 +645,9 @@ void main() { additionalArgs: any(named: 'additionalArgs'), ), ).thenAnswer((_) async => linkPercentage); + when( + aotTools.isGeneratePatchDiffBaseSupported, + ).thenAnswer((_) async => false); when( () => shorebirdEnv.flutterRevision, ).thenReturn(postLinkerFlutterRevision); @@ -735,7 +740,9 @@ void main() { releaseArtifact: releaseArtifactFile, ), ); - } catch (_) {} + } on Exception { + // ignore + } verify( () => aotTools.link( base: any(named: 'base'), diff --git a/packages/shorebird_cli/test/src/commands/patch/ios_patcher_test.dart b/packages/shorebird_cli/test/src/commands/patch/ios_patcher_test.dart index 614e8fd4..360e98cd 100644 --- a/packages/shorebird_cli/test/src/commands/patch/ios_patcher_test.dart +++ b/packages/shorebird_cli/test/src/commands/patch/ios_patcher_test.dart @@ -691,7 +691,9 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', test('forwards --split-debug-info to builder', () async { try { await runWithOverrides(patcher.buildPatchArtifact); - } catch (_) {} + } on Exception { + // ignore + } verify( () => artifactBuilder.buildElfAotSnapshot( appDillPath: any(named: 'appDillPath'), @@ -1011,6 +1013,9 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', additionalArgs: any(named: 'additionalArgs'), ), ).thenAnswer((_) async => linkPercentage); + when( + aotTools.isGeneratePatchDiffBaseSupported, + ).thenAnswer((_) async => false); when( () => artifactManager.getIosAppDirectory( xcarchiveDirectory: any(named: 'xcarchiveDirectory'), @@ -1167,7 +1172,9 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', releaseArtifact: releaseArtifactFile, ), ); - } catch (_) {} + } on Exception { + // ignore + } verify( () => aotTools.link( base: any(named: 'base'), @@ -1519,8 +1526,6 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', kernel: any(named: 'kernel'), outputPath: any(named: 'outputPath'), workingDirectory: any(named: 'workingDirectory'), - // ignore: avoid_redundant_argument_values - dumpDebugInfoPath: null, ), ).called(1); }); @@ -1691,7 +1696,9 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', 'Info.plist', ), ).deleteSync(recursive: true); - } catch (_) {} + } on Exception { + // ignore + } }); test('exit with code 70', () async { diff --git a/packages/shorebird_cli/test/src/commands/patch/macos_patcher_test.dart b/packages/shorebird_cli/test/src/commands/patch/macos_patcher_test.dart index 575de8ae..d3022393 100644 --- a/packages/shorebird_cli/test/src/commands/patch/macos_patcher_test.dart +++ b/packages/shorebird_cli/test/src/commands/patch/macos_patcher_test.dart @@ -718,7 +718,9 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', test('forwards --split-debug-info to builder', () async { try { await runWithOverrides(patcher.buildPatchArtifact); - } catch (_) {} + } on Exception { + // ignore + } verify( () => artifactBuilder.buildElfAotSnapshot( appDillPath: any(named: 'appDillPath'), @@ -1582,8 +1584,6 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', kernel: any(named: 'kernel'), outputPath: any(named: 'outputPath'), workingDirectory: any(named: 'workingDirectory'), - // ignore: avoid_redundant_argument_values - dumpDebugInfoPath: null, ), ).called(1); }); @@ -1621,7 +1621,9 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', 'Info.plist', ), ).deleteSync(recursive: true); - } catch (_) {} + } on Exception { + // ignore + } }); test('exit with code 70', () async { diff --git a/packages/shorebird_cli/test/src/commands/upgrade_command_test.dart b/packages/shorebird_cli/test/src/commands/upgrade_command_test.dart index cb54984c..440680de 100644 --- a/packages/shorebird_cli/test/src/commands/upgrade_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/upgrade_command_test.dart @@ -47,8 +47,8 @@ void main() { () => shorebirdVersion.attemptReset(revision: any(named: 'revision')), ).thenAnswer((_) async => {}); - when(() => progress.complete(any())).thenAnswer((_) { - final message = _.positionalArguments.elementAt(0) as String?; + when(() => progress.complete(any())).thenAnswer((invocation) { + final message = invocation.positionalArguments.elementAt(0) as String?; if (message != null) progressLogs.add(message); }); when(() => logger.progress(any())).thenReturn(progress); diff --git a/packages/shorebird_cli/test/src/executables/devicectl/devicectl_test.dart b/packages/shorebird_cli/test/src/executables/devicectl/devicectl_test.dart index 8a7001cd..8dfd7a58 100644 --- a/packages/shorebird_cli/test/src/executables/devicectl/devicectl_test.dart +++ b/packages/shorebird_cli/test/src/executables/devicectl/devicectl_test.dart @@ -83,6 +83,15 @@ void main() { ); }); + test('returns null if devicectl availability check throws', () async { + exitCode = ExitCode.software; + when(() => process.run(any(), any())).thenThrow(Exception('oops')); + await expectLater( + await runWithOverrides(() => devicectl.deviceForLaunch()), + isNull, + ); + }); + test('returns null if no CoreDevice with the given deviceID can be found', () async { exitCode = ExitCode.success; diff --git a/packages/shorebird_cli/test/src/executables/ios_deploy_test.dart b/packages/shorebird_cli/test/src/executables/ios_deploy_test.dart index 0e76ff34..4cb68ec2 100644 --- a/packages/shorebird_cli/test/src/executables/ios_deploy_test.dart +++ b/packages/shorebird_cli/test/src/executables/ios_deploy_test.dart @@ -47,7 +47,7 @@ void main() { progress = MockProgress(); ioSink = MockIOSink(); shorebirdEnv = MockShorebirdEnv(); - iosDeploy = IOSDeploy(); + iosDeploy = const IOSDeploy(); final tempDir = Directory.systemTemp.createTempSync(); diff --git a/packages/shorebird_cli/test/src/extensions/version_test.dart b/packages/shorebird_cli/test/src/extensions/version_test.dart index b29d3126..6470ae45 100644 --- a/packages/shorebird_cli/test/src/extensions/version_test.dart +++ b/packages/shorebird_cli/test/src/extensions/version_test.dart @@ -10,7 +10,7 @@ void main() { }); test('returns null if string is of the format major.minor', () { - expect(tryParseVersion('1.2'), isNull); + expect(tryParseVersion('-1.2.-3'), isNull); }); test('returns null if string is in an invalid format', () { @@ -27,6 +27,13 @@ void main() { expect(tryParseVersion('1.2', strict: false), Version(1, 2, 0)); }); + test('returns null if string contains bigint', () { + expect( + tryParseVersion('999999999999999999999999999.0', strict: false), + isNull, + ); + }); + test('returns null if string is in an invalid format', () { expect(tryParseVersion('asdf', strict: false), isNull); }); diff --git a/packages/shorebird_cli/test/src/formatters/file_size_formatter_test.dart b/packages/shorebird_cli/test/src/formatters/file_size_formatter_test.dart index 62725e9b..e387da35 100644 --- a/packages/shorebird_cli/test/src/formatters/file_size_formatter_test.dart +++ b/packages/shorebird_cli/test/src/formatters/file_size_formatter_test.dart @@ -41,6 +41,7 @@ void main() { test('returns correct number of decimal places', () { expect(formatBytes(1524, decimals: 0), equals('1 KB')); expect(formatBytes(1524, decimals: 1), equals('1.5 KB')); + // Being explicit for test readability. // ignore: avoid_redundant_argument_values expect(formatBytes(1524, decimals: 2), equals('1.49 KB')); expect(formatBytes(1524, decimals: 3), equals('1.488 KB')); diff --git a/packages/shorebird_cli/test/src/network_checker_test.dart b/packages/shorebird_cli/test/src/network_checker_test.dart index b8a3456d..21a9aae4 100644 --- a/packages/shorebird_cli/test/src/network_checker_test.dart +++ b/packages/shorebird_cli/test/src/network_checker_test.dart @@ -73,7 +73,7 @@ void main() { group('when endpoints are not reachable', () { setUp(() { - when(() => httpClient.send(any())).thenThrow(Exception('oops')); + when(() => httpClient.get(any())).thenThrow(Exception('oops')); }); test('logs reachability for each checked url', () async { diff --git a/packages/shorebird_cli/test/src/shorebird_cli_command_runner_test.dart b/packages/shorebird_cli/test/src/shorebird_cli_command_runner_test.dart index a9987851..7cae552b 100644 --- a/packages/shorebird_cli/test/src/shorebird_cli_command_runner_test.dart +++ b/packages/shorebird_cli/test/src/shorebird_cli_command_runner_test.dart @@ -355,13 +355,15 @@ Engine • revision $shorebirdEngineRevision''', test('gracefully handles case when latest version cannot be determined', () async { - when(shorebirdVersion.isLatest).thenThrow('error'); + when(shorebirdVersion.isLatest).thenThrow(Exception('error')); final result = await runWithOverrides( () => commandRunner.run(['--version']), ); expect(result, equals(ExitCode.success.code)); verify( - () => logger.detail('Unable to check for updates.\nerror'), + () => logger.detail( + 'Unable to check for updates.\nException: error', + ), ).called(1); }); @@ -403,13 +405,15 @@ Engine • revision $shorebirdEngineRevision''', test( 'gracefully handles case when flutter version cannot be determined', () async { - when(shorebirdFlutter.getVersionString).thenThrow('error'); + when(shorebirdFlutter.getVersionString).thenThrow(Exception('error')); final result = await runWithOverrides( () => commandRunner.run(['--version']), ); expect(result, equals(ExitCode.success.code)); verify( - () => logger.detail('Unable to determine Flutter version.\nerror'), + () => logger.detail( + 'Unable to determine Flutter version.\nException: error', + ), ).called(1); }); }); diff --git a/packages/shorebird_cli/test/src/shorebird_env_test.dart b/packages/shorebird_cli/test/src/shorebird_env_test.dart index 29626257..e92e48ae 100644 --- a/packages/shorebird_cli/test/src/shorebird_env_test.dart +++ b/packages/shorebird_cli/test/src/shorebird_env_test.dart @@ -276,6 +276,28 @@ void main() { ); }); + test( + 'returns null when error occurs reading pubspec.yaml', + () { + final tempDir = Directory.systemTemp.createTempSync(); + final file = File( + p.join(tempDir.path, 'pubspec.yaml'), + )..writeAsStringSync('name: test'); + // Make the file unreadable. + Process.runSync('chmod', ['000', file.path]); + expect( + IOOverrides.runZoned( + () => runWithOverrides(() => shorebirdEnv.getPubspecYaml()), + getCurrentDirectory: () => tempDir, + ), + isNull, + ); + }, + onPlatform: { + 'windows': const Skip('chmod is not available on Windows'), + }, + ); + test('returns value when pubspec.yaml exists', () { final tempDir = Directory.systemTemp.createTempSync(); File( @@ -639,6 +661,30 @@ base_url: https://example.com'''); test('returns null when there is no env override or shorebird.yaml', () { expect(runWithOverrides(() => shorebirdEnv.hostedUri), isNull); }); + + test( + 'returns null when unable to read shorebird.yaml', + () { + final directory = Directory.systemTemp.createTempSync(); + final file = File(p.join(directory.path, 'shorebird.yaml')) + ..writeAsStringSync(''' +app_id: test-id +base_url: https://example.com'''); + // Make the file unreadable. + Process.runSync('chmod', ['000', file.path]); + + expect( + IOOverrides.runZoned( + () => runWithOverrides(() => shorebirdEnv.hostedUri), + getCurrentDirectory: () => directory, + ), + isNull, + ); + }, + onPlatform: { + 'windows': const Skip('chmod is not available on Windows'), + }, + ); }); group('canAcceptUserInput', () { diff --git a/packages/shorebird_cli/test/src/shorebird_flutter_test.dart b/packages/shorebird_cli/test/src/shorebird_flutter_test.dart index d07fc51e..22083892 100644 --- a/packages/shorebird_cli/test/src/shorebird_flutter_test.dart +++ b/packages/shorebird_cli/test/src/shorebird_flutter_test.dart @@ -294,6 +294,26 @@ Tools • Dart 3.0.6 • DevTools 2.23.1'''); expect(revision, isNull); }); }); + + group('when exception occurs doing revision lookup', () { + setUp(() { + when( + () => git.forEachRef( + directory: any(named: 'directory'), + contains: any(named: 'contains'), + format: any(named: 'format'), + pattern: any(named: 'pattern'), + ), + ).thenThrow(Exception('oops')); + }); + + test('returns null', () async { + final revision = await runWithOverrides( + () => shorebirdFlutter.resolveFlutterRevision('not-a-version'), + ); + expect(revision, isNull); + }); + }); }); group('resolveFlutterVersion', () { @@ -327,6 +347,26 @@ Tools • Dart 3.0.6 • DevTools 2.23.1'''); }); }); + group('when commit lookup fails', () { + setUp(() { + when( + () => git.forEachRef( + directory: any(named: 'directory'), + contains: any(named: 'contains'), + format: any(named: 'format'), + pattern: any(named: 'pattern'), + ), + ).thenThrow(Exception('oops')); + }); + + test('returns null', () async { + final revision = await runWithOverrides( + () => shorebirdFlutter.resolveFlutterVersion('not-a-version'), + ); + expect(revision, isNull); + }); + }); + group('when input is a recognized commit hash', () { setUp(() { when(