refactor(shorebird_cli): use scoped ShorebirdEnv and remove buildCodePushClient (#1005)

This commit is contained in:
Felix Angelov
2023-08-02 23:23:51 -05:00
committed by GitHub
parent 32d98935f7
commit 6b50023b64
75 changed files with 1857 additions and 2112 deletions
@@ -16,6 +16,7 @@ import 'package:shorebird_cli/src/java.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/platform.dart';
import 'package:shorebird_cli/src/process.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
import 'package:shorebird_cli/src/shorebird_version_manager.dart';
import 'package:shorebird_cli/src/xcodebuild.dart';
@@ -40,6 +41,7 @@ Future<void> main(List<String> args) async {
loggerRef,
platformRef,
processRef,
shorebirdEnvRef,
shorebirdValidatorRef,
shorebirdVersionManagerRef,
xcodeBuildRef,
+4 -4
View File
@@ -7,7 +7,8 @@ import 'package:path/path.dart' as p;
import 'package:platform/platform.dart';
import 'package:scoped/scoped.dart';
import 'package:shorebird_cli/src/http_client/http_client.dart';
import 'package:shorebird_cli/src/shorebird_environment.dart';
import 'package:shorebird_cli/src/platform.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
typedef ArchiveExtracter = Future<void> Function(
String archivePath,
@@ -32,7 +33,6 @@ class Cache {
Cache({
http.Client? httpClient,
this.extractArchive = _defaultArchiveExtractor,
Platform platform = const LocalPlatform(),
}) : httpClient = httpClient ?? retryingHttpClient(http.Client()) {
registerArtifact(PatchArtifact(cache: this, platform: platform));
registerArtifact(BundleToolArtifact(cache: this, platform: platform));
@@ -72,7 +72,7 @@ class Cache {
/// The Shorebird cache directory.
static Directory get shorebirdCacheDirectory {
return Directory(
p.join(ShorebirdEnvironment.shorebirdRoot.path, 'bin', 'cache'),
p.join(shorebirdEnv.shorebirdRoot.path, 'bin', 'cache'),
);
}
@@ -174,7 +174,7 @@ class PatchArtifact extends CachedArtifact {
artifactName += 'windows-x64.zip';
}
return '${cache.storageBaseUrl}/${cache.storageBucket}/shorebird/${ShorebirdEnvironment.shorebirdEngineRevision}/$artifactName';
return '${cache.storageBaseUrl}/${cache.storageBucket}/shorebird/${shorebirdEnv.shorebirdEngineRevision}/$artifactName';
}
}
@@ -10,7 +10,7 @@ import 'package:scoped/scoped.dart';
import 'package:shorebird_cli/src/auth/auth.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/shorebird_build_mixin.dart';
import 'package:shorebird_cli/src/shorebird_environment.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/third_party/flutter_tools/lib/flutter_tools.dart';
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
@@ -44,7 +44,7 @@ ScopedRef<CodePushClientWrapper> codePushClientWrapperRef = create(() {
return CodePushClientWrapper(
codePushClient: CodePushClient(
httpClient: auth.client,
hostedUri: ShorebirdEnvironment.hostedUri,
hostedUri: shorebirdEnv.hostedUri,
),
);
});
@@ -63,6 +63,25 @@ class CodePushClientWrapper {
final CodePushClient codePushClient;
Future<App> createApp({String? appName}) async {
late final String displayName;
if (appName == null) {
String? defaultAppName;
try {
defaultAppName = shorebirdEnv.getPubspecYaml()?.name;
} catch (_) {}
displayName = logger.prompt(
'${lightGreen.wrap('?')} How should we refer to this app?',
defaultValue: defaultAppName,
);
} else {
displayName = appName;
}
return codePushClient.createApp(displayName: displayName);
}
Future<List<AppMetadata>> getApps() async {
final fetchAppsProgress = logger.progress('Fetching apps');
try {
@@ -25,12 +25,6 @@ typedef StartProcess = Future<Process> Function(
});
abstract class ShorebirdCommand extends Command<int> {
ShorebirdCommand({
CodePushClientBuilder? buildCodePushClient,
}) : buildCodePushClient = buildCodePushClient ?? CodePushClient.new;
final CodePushClientBuilder buildCodePushClient;
// We don't currently have a test involving both a CommandRunner
// and a Command, so we can't test this getter.
// coverage:ignore-start
@@ -8,7 +8,7 @@ import 'package:scoped/scoped.dart';
import 'package:shorebird_cli/src/commands/commands.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/process.dart';
import 'package:shorebird_cli/src/shorebird_environment.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/version.dart';
const executableName = 'shorebird';
@@ -144,7 +144,7 @@ ${lightCyan.wrap('shorebird release android -- --no-pub lib/main.dart')}''',
logger.info(
'''
Shorebird $packageVersion
Shorebird Engine • revision ${ShorebirdEnvironment.shorebirdEngineRevision}''',
Shorebird Engine • revision ${shorebirdEnv.shorebirdEngineRevision}''',
);
exitCode = ExitCode.success.code;
} else {
@@ -2,15 +2,14 @@ import 'dart:async';
import 'package:intl/intl.dart';
import 'package:mason_logger/mason_logger.dart';
import 'package:shorebird_cli/src/auth/auth.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/command.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/shorebird_environment.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
class DowngradeAccountCommand extends ShorebirdCommand {
DowngradeAccountCommand({super.buildCodePushClient});
DowngradeAccountCommand();
@override
String get name => 'downgrade';
@@ -28,14 +27,10 @@ class DowngradeAccountCommand extends ShorebirdCommand {
return e.exitCode.code;
}
final client = buildCodePushClient(
httpClient: auth.client,
hostedUri: ShorebirdEnvironment.hostedUri,
);
final User user;
try {
final currentUser = await client.getCurrentUser();
final currentUser =
await codePushClientWrapper.codePushClient.getCurrentUser();
if (currentUser == null) {
throw Exception('Failed to retrieve user information.');
}
@@ -65,7 +60,8 @@ class DowngradeAccountCommand extends ShorebirdCommand {
final DateTime cancellationDate;
try {
cancellationDate = await client.cancelSubscription();
cancellationDate =
await codePushClientWrapper.codePushClient.cancelSubscription();
} catch (error) {
progress.fail('Failed to downgrade plan. Error: $error');
return ExitCode.software.code;
@@ -1,10 +1,9 @@
import 'dart:async';
import 'package:mason_logger/mason_logger.dart';
import 'package:shorebird_cli/src/auth/auth.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/command.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/shorebird_environment.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
@@ -13,7 +12,7 @@ import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
/// {@endtemplate}
class UpgradeAccountCommand extends ShorebirdCommand {
/// {@macro upgrade_account_command}
UpgradeAccountCommand({super.buildCodePushClient});
UpgradeAccountCommand();
@override
String get name => 'upgrade';
@@ -38,16 +37,11 @@ Please use $consoleLink instead.''',
return e.exitCode.code;
}
final client = buildCodePushClient(
httpClient: auth.client,
hostedUri: ShorebirdEnvironment.hostedUri,
);
final progress = logger.progress('Retrieving account information');
final User? user;
try {
user = await client.getCurrentUser();
user = await codePushClientWrapper.codePushClient.getCurrentUser();
if (user == null) {
progress.fail('''
We're having trouble retrieving your account information.
@@ -69,7 +63,8 @@ Please try logging out using ${lightCyan.wrap('shorebird logout')} and logging b
final Uri paymentLink;
try {
paymentLink = await client.createPaymentLink();
paymentLink =
await codePushClientWrapper.codePushClient.createPaymentLink();
} catch (error) {
progress.fail(error.toString());
return ExitCode.software.code;
@@ -1,10 +1,9 @@
import 'dart:async';
import 'package:mason_logger/mason_logger.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/command.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/shorebird_config_mixin.dart';
import 'package:shorebird_cli/src/shorebird_create_app_mixin.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
@@ -13,10 +12,9 @@ import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
/// `shorebird apps create`
/// Create a new app on Shorebird.
/// {@endtemplate}
class CreateAppCommand extends ShorebirdCommand
with ShorebirdConfigMixin, ShorebirdCreateAppMixin {
class CreateAppCommand extends ShorebirdCommand {
/// {@macro create_app_command}
CreateAppCommand({super.buildCodePushClient}) {
CreateAppCommand() {
argParser.addOption(
'app-name',
help: '''
@@ -44,7 +42,7 @@ Defaults to the name in "pubspec.yaml".''',
final appName = results['app-name'] as String?;
late final App app;
try {
app = await createApp(appName: appName);
app = await codePushClientWrapper.createApp(appName: appName);
} catch (error) {
logger.err('$error');
return ExitCode.software.code;
@@ -1,10 +1,10 @@
import 'dart:async';
import 'package:mason_logger/mason_logger.dart';
import 'package:shorebird_cli/src/auth/auth.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/command.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/shorebird_environment.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
/// {@template delete_app_command}
@@ -14,7 +14,7 @@ import 'package:shorebird_cli/src/shorebird_validator.dart';
/// {@endtemplate}
class DeleteAppCommand extends ShorebirdCommand {
/// {@macro delete_app_command}
DeleteAppCommand({super.buildCodePushClient}) {
DeleteAppCommand() {
argParser
..addOption(
'app-id',
@@ -60,7 +60,7 @@ Please use $consoleLink instead.''',
if (appIdArg == null) {
String? defaultAppId;
try {
defaultAppId = ShorebirdEnvironment.getShorebirdYaml()?.appId;
defaultAppId = shorebirdEnv.getShorebirdYaml()?.appId;
} catch (_) {}
appId = logger.prompt(
@@ -71,11 +71,6 @@ Please use $consoleLink instead.''',
appId = appIdArg;
}
final client = buildCodePushClient(
httpClient: auth.client,
hostedUri: ShorebirdEnvironment.hostedUri,
);
final shouldProceed =
force || logger.confirm('Deleting an app is permanent. Continue?');
if (!shouldProceed) {
@@ -84,7 +79,7 @@ Please use $consoleLink instead.''',
}
try {
await client.deleteApp(appId: appId);
await codePushClientWrapper.codePushClient.deleteApp(appId: appId);
} catch (error) {
logger.err('$error');
return ExitCode.software.code;
@@ -6,7 +6,7 @@ import 'package:path/path.dart' as p;
import 'package:shorebird_cli/src/command.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/shorebird_build_mixin.dart';
import 'package:shorebird_cli/src/shorebird_config_mixin.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
/// {@template build_aar_command}
@@ -14,8 +14,7 @@ import 'package:shorebird_cli/src/shorebird_validator.dart';
/// `shorebird build aar`
/// Build an Android aar file from your app.
/// {@endtemplate}
class BuildAarCommand extends ShorebirdCommand
with ShorebirdConfigMixin, ShorebirdBuildMixin {
class BuildAarCommand extends ShorebirdCommand with ShorebirdBuildMixin {
BuildAarCommand() {
// We would have a "target" option here, similar to what [BuildApkCommand]
// and [BuildAabCommand] have, but target cannot currently be configured in
@@ -47,7 +46,7 @@ class BuildAarCommand extends ShorebirdCommand
return e.exitCode.code;
}
if (androidPackageName == null) {
if (shorebirdEnv.androidPackageName == null) {
logger.err('Could not find androidPackage in pubspec.yaml.');
return ExitCode.config.code;
}
@@ -68,7 +67,7 @@ class BuildAarCommand extends ShorebirdCommand
'host',
'outputs',
'repo',
...androidPackageName!.split('.'),
...shorebirdEnv.androidPackageName!.split('.'),
'flutter_release',
buildNumber,
'flutter_release-$buildNumber.aar',
@@ -6,7 +6,6 @@ import 'package:shorebird_cli/src/command.dart';
import 'package:shorebird_cli/src/doctor.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/shorebird_build_mixin.dart';
import 'package:shorebird_cli/src/shorebird_config_mixin.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
/// {@template build_apk_command}
@@ -14,8 +13,7 @@ import 'package:shorebird_cli/src/shorebird_validator.dart';
/// `shorebird build apk`
/// Build an Android APK file from your app.
/// {@endtemplate}
class BuildApkCommand extends ShorebirdCommand
with ShorebirdConfigMixin, ShorebirdBuildMixin {
class BuildApkCommand extends ShorebirdCommand with ShorebirdBuildMixin {
/// {@macro build_apk_command}
BuildApkCommand() {
argParser
@@ -6,7 +6,6 @@ import 'package:shorebird_cli/src/command.dart';
import 'package:shorebird_cli/src/doctor.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/shorebird_build_mixin.dart';
import 'package:shorebird_cli/src/shorebird_config_mixin.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
/// {@template build_app_bundle_command}
@@ -14,8 +13,7 @@ import 'package:shorebird_cli/src/shorebird_validator.dart';
/// `shorebird build appbundle`
/// Build an Android App Bundle file from your app.
/// {@endtemplate}
class BuildAppBundleCommand extends ShorebirdCommand
with ShorebirdConfigMixin, ShorebirdBuildMixin {
class BuildAppBundleCommand extends ShorebirdCommand with ShorebirdBuildMixin {
/// {@macro build_app_bundle_command}
BuildAppBundleCommand() {
argParser
@@ -6,7 +6,6 @@ import 'package:shorebird_cli/src/command.dart';
import 'package:shorebird_cli/src/doctor.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/shorebird_build_mixin.dart';
import 'package:shorebird_cli/src/shorebird_config_mixin.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
/// {@template build_ipa_command}
@@ -14,8 +13,7 @@ import 'package:shorebird_cli/src/shorebird_validator.dart';
/// Builds an .xcarchive and optionally .ipa for an iOS app to be generated for
/// App Store submission.
/// {@endtemplate}
class BuildIpaCommand extends ShorebirdCommand
with ShorebirdConfigMixin, ShorebirdBuildMixin {
class BuildIpaCommand extends ShorebirdCommand with ShorebirdBuildMixin {
/// {@macro build_ipa_command}
BuildIpaCommand() {
argParser
@@ -6,13 +6,12 @@ import 'package:shorebird_cli/src/cache.dart';
import 'package:shorebird_cli/src/command.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/platform.dart';
import 'package:shorebird_cli/src/shorebird_config_mixin.dart';
/// {@template clean_cache_command}
/// `shorebird cache clean`
/// Clears the Shorebird cache directory.
/// {@endtemplate}
class CleanCacheCommand extends ShorebirdCommand with ShorebirdConfigMixin {
class CleanCacheCommand extends ShorebirdCommand {
/// {@macro clean_cache_command}
CleanCacheCommand();
@@ -1,10 +1,10 @@
import 'dart:async';
import 'package:mason_logger/mason_logger.dart';
import 'package:shorebird_cli/src/auth/auth.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/command.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/shorebird_environment.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
/// {@template add_collaborators_command}
@@ -13,7 +13,7 @@ import 'package:shorebird_cli/src/shorebird_validator.dart';
/// {@endtemplate}
class AddCollaboratorsCommand extends ShorebirdCommand {
/// {@macro add_collaborators_command}
AddCollaboratorsCommand({super.buildCodePushClient}) {
AddCollaboratorsCommand() {
argParser
..addOption(
_appIdOption,
@@ -44,13 +44,8 @@ class AddCollaboratorsCommand extends ShorebirdCommand {
return e.exitCode.code;
}
final client = buildCodePushClient(
httpClient: auth.client,
hostedUri: ShorebirdEnvironment.hostedUri,
);
final appId = results[_appIdOption] as String? ??
ShorebirdEnvironment.getShorebirdYaml()?.appId;
shorebirdEnv.getShorebirdYaml()?.appId;
if (appId == null) {
logger.err(
'''
@@ -82,7 +77,10 @@ ${styleBold.wrap(lightGreen.wrap('🚀 Ready to add a new collaborator!'))}
final progress = logger.progress('Adding collaborator');
try {
await client.createCollaborator(appId: appId, email: collaborator);
await codePushClientWrapper.codePushClient.createCollaborator(
appId: appId,
email: collaborator,
);
progress.complete();
} catch (error) {
progress.fail();
@@ -2,10 +2,10 @@ import 'dart:async';
import 'package:collection/collection.dart';
import 'package:mason_logger/mason_logger.dart';
import 'package:shorebird_cli/src/auth/auth.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/command.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/shorebird_environment.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
@@ -15,7 +15,7 @@ import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
/// {@endtemplate}
class DeleteCollaboratorsCommand extends ShorebirdCommand {
/// {@macro delete_collaborators_command}
DeleteCollaboratorsCommand({super.buildCodePushClient}) {
DeleteCollaboratorsCommand() {
argParser
..addOption(
_appIdOption,
@@ -47,13 +47,8 @@ class DeleteCollaboratorsCommand extends ShorebirdCommand {
return e.exitCode.code;
}
final client = buildCodePushClient(
httpClient: auth.client,
hostedUri: ShorebirdEnvironment.hostedUri,
);
final appId = results[_appIdOption] as String? ??
ShorebirdEnvironment.getShorebirdYaml()?.appId;
shorebirdEnv.getShorebirdYaml()?.appId;
if (appId == null) {
logger.err(
'''
@@ -71,7 +66,8 @@ You must either specify an app id via the "--$_appIdOption" flag or run this com
final getCollaboratorsProgress = logger.progress('Fetching collaborators');
final List<Collaborator> collaborators;
try {
collaborators = await client.getCollaborators(appId: appId);
collaborators = await codePushClientWrapper.codePushClient
.getCollaborators(appId: appId);
getCollaboratorsProgress.complete();
} catch (error) {
getCollaboratorsProgress.fail();
@@ -109,7 +105,7 @@ ${styleBold.wrap(lightGreen.wrap('🗑️ Ready to delete an existing collabora
final progress = logger.progress('Deleting collaborator');
try {
await client.deleteCollaborator(
await codePushClientWrapper.codePushClient.deleteCollaborator(
appId: appId,
userId: collaborator.userId,
);
@@ -2,10 +2,10 @@ import 'dart:async';
import 'package:barbecue/barbecue.dart';
import 'package:mason_logger/mason_logger.dart';
import 'package:shorebird_cli/src/auth/auth.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/command.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/shorebird_environment.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
@@ -15,7 +15,7 @@ import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
/// {@endtemplate}
class ListCollaboratorsCommand extends ShorebirdCommand {
/// {@macro list_collaborators_command}
ListCollaboratorsCommand({super.buildCodePushClient}) {
ListCollaboratorsCommand() {
argParser.addOption(
_appIdOption,
help: 'The app id to list collaborators for.',
@@ -43,13 +43,8 @@ class ListCollaboratorsCommand extends ShorebirdCommand {
return e.exitCode.code;
}
final client = buildCodePushClient(
httpClient: auth.client,
hostedUri: ShorebirdEnvironment.hostedUri,
);
final appId = results[_appIdOption] as String? ??
ShorebirdEnvironment.getShorebirdYaml()?.appId;
shorebirdEnv.getShorebirdYaml()?.appId;
if (appId == null) {
logger.err(
'''
@@ -61,7 +56,8 @@ You must either specify an app id via the "--$_appIdOption" flag or run this com
final List<Collaborator> collaborators;
try {
collaborators = await client.getCollaborators(appId: appId);
collaborators = await codePushClientWrapper.codePushClient
.getCollaborators(appId: appId);
} catch (error) {
logger.err('$error');
return ExitCode.software.code;
@@ -2,7 +2,7 @@ import 'package:mason_logger/mason_logger.dart';
import 'package:shorebird_cli/src/command.dart';
import 'package:shorebird_cli/src/doctor.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/shorebird_environment.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/version.dart';
/// {@template doctor_command}
@@ -34,7 +34,7 @@ class DoctorCommand extends ShorebirdCommand {
logger.info('''
Shorebird v$packageVersion
Shorebird Engine • revision ${ShorebirdEnvironment.shorebirdEngineRevision}
Shorebird Engine • revision ${shorebirdEnv.shorebirdEngineRevision}
''');
await doctor.runValidators(doctor.allValidators, applyFixes: shouldFix);
@@ -3,26 +3,27 @@ import 'dart:io';
import 'package:collection/collection.dart';
import 'package:mason_logger/mason_logger.dart';
import 'package:path/path.dart' as p;
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/command.dart';
import 'package:shorebird_cli/src/config/config.dart';
import 'package:shorebird_cli/src/doctor.dart';
import 'package:shorebird_cli/src/gradlew.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/platform.dart';
import 'package:shorebird_cli/src/shorebird_config_mixin.dart';
import 'package:shorebird_cli/src/shorebird_create_app_mixin.dart';
import 'package:shorebird_cli/src/shorebird_environment.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
import 'package:shorebird_cli/src/xcodebuild.dart';
import 'package:yaml/yaml.dart';
import 'package:yaml_edit/yaml_edit.dart';
/// {@template init_command}
///
/// `shorebird init`
/// Initialize Shorebird.
/// {@endtemplate}
class InitCommand extends ShorebirdCommand
with ShorebirdConfigMixin, ShorebirdCreateAppMixin {
class InitCommand extends ShorebirdCommand {
/// {@macro init_command}
InitCommand({super.buildCodePushClient}) {
InitCommand() {
argParser.addFlag(
'force',
abbr: 'f',
@@ -48,7 +49,7 @@ class InitCommand extends ShorebirdCommand
}
try {
if (!ShorebirdEnvironment.hasPubspecYaml) {
if (!shorebirdEnv.hasPubspecYaml) {
logger.err('''
Could not find a "pubspec.yaml".
Please make sure you are running "shorebird init" from the root of your Flutter project.
@@ -61,11 +62,8 @@ Please make sure you are running "shorebird init" from the root of your Flutter
}
final force = results['force'] == true;
if (force && ShorebirdEnvironment.hasShorebirdYaml) {
ShorebirdEnvironment.getShorebirdYamlFile().deleteSync();
}
if (ShorebirdEnvironment.hasShorebirdYaml) {
if (!force && shorebirdEnv.hasShorebirdYaml) {
logger.err('''
A "shorebird.yaml" already exists.
If you want to reinitialize Shorebird, please run "shorebird init --force".''');
@@ -99,7 +97,7 @@ If you want to reinitialize Shorebird, please run "shorebird init --force".''');
try {
final displayName = logger.prompt(
'${lightGreen.wrap('?')} How should we refer to this app?',
defaultValue: ShorebirdEnvironment.getPubspecYaml()?.name,
defaultValue: shorebirdEnv.getPubspecYaml()?.name,
);
final hasNoFlavors = productFlavors.isEmpty;
final hasSomeFlavors = productFlavors.isNotEmpty &&
@@ -109,15 +107,19 @@ If you want to reinitialize Shorebird, please run "shorebird init --force".''');
if (hasNoFlavors) {
// No platforms have any flavors so we just create a single app
// and assign it as the default.
appId = (await createApp(appName: displayName)).id;
final app = await codePushClientWrapper.createApp(appName: displayName);
appId = app.id;
} else if (hasSomeFlavors) {
// Some platforms have flavors and some do not so we create an app
// for the default (no flavor) and then create an app per flavor.
appId = (await createApp(appName: displayName)).id;
final app = await codePushClientWrapper.createApp(appName: displayName);
appId = app.id;
final values = <String, String>{};
for (final flavor in productFlavors) {
values[flavor] =
(await createApp(appName: '$displayName ($flavor)')).id;
final app = await codePushClientWrapper.createApp(
appName: '$displayName ($flavor)',
);
values[flavor] = app.id;
}
flavors = values;
} else {
@@ -125,8 +127,10 @@ If you want to reinitialize Shorebird, please run "shorebird init --force".''');
// and assign the default to the first flavor.
final values = <String, String>{};
for (final flavor in productFlavors) {
values[flavor] =
(await createApp(appName: '$displayName ($flavor)')).id;
final app = await codePushClientWrapper.createApp(
appName: '$displayName ($flavor)',
);
values[flavor] = app.id;
}
flavors = values;
appId = flavors.values.first;
@@ -136,10 +140,10 @@ If you want to reinitialize Shorebird, please run "shorebird init --force".''');
return ExitCode.software.code;
}
addShorebirdYamlToProject(appId, flavors: flavors);
_addShorebirdYamlToProject(appId, flavors: flavors);
if (!ShorebirdEnvironment.pubspecContainsShorebirdYaml) {
addShorebirdYamlToPubspecAssets();
if (!shorebirdEnv.pubspecContainsShorebirdYaml) {
_addShorebirdYamlToPubspecAssets();
}
await doctor.runValidators(doctor.allValidators, applyFixes: true);
@@ -207,4 +211,56 @@ For more information about Shorebird, visit ${link(uri: Uri.parse('https://shore
.toSet();
}
}
ShorebirdYaml _addShorebirdYamlToProject(
String appId, {
Map<String, String>? flavors,
}) {
const content = '''
# This file is used to configure the Shorebird updater used by your application.
# Learn more at https://shorebird.dev
# This file should be checked into version control.
# This is the unique identifier assigned to your app.
# It is used by your app to request the correct patches from Shorebird servers.
app_id:
''';
final editor = YamlEditor(content)..update(['app_id'], appId);
if (flavors != null) editor.update(['flavors'], flavors);
shorebirdEnv.getShorebirdYamlFile().writeAsStringSync(editor.toString());
return ShorebirdYaml(appId: appId);
}
void _addShorebirdYamlToPubspecAssets() {
final pubspecFile = shorebirdEnv.getPubspecYamlFile();
final pubspecContents = pubspecFile.readAsStringSync();
final yaml = loadYaml(pubspecContents, sourceUrl: pubspecFile.uri) as Map;
final editor = YamlEditor(pubspecContents);
if (!yaml.containsKey('flutter')) {
editor.update(
['flutter'],
{
'assets': ['shorebird.yaml']
},
);
} else {
if (!(yaml['flutter'] as Map).containsKey('assets')) {
editor.update(['flutter', 'assets'], ['shorebird.yaml']);
} else {
final assets = (yaml['flutter'] as Map)['assets'] as List;
if (!assets.contains('shorebird.yaml')) {
editor.update(['flutter', 'assets'], [...assets, 'shorebird.yaml']);
}
}
}
if (editor.edits.isEmpty) return;
pubspecFile.writeAsStringSync(editor.toString());
}
}
@@ -15,9 +15,9 @@ import 'package:shorebird_cli/src/formatters/file_size_formatter.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/shorebird_artifact_mixin.dart';
import 'package:shorebird_cli/src/shorebird_build_mixin.dart';
import 'package:shorebird_cli/src/shorebird_config_mixin.dart';
import 'package:shorebird_cli/src/shorebird_environment.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
import 'package:shorebird_cli/src/shorebird_version_manager.dart';
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
/// {@template patch_aar_command}
@@ -25,7 +25,7 @@ import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
/// Create a patch for an Android archive release.
/// {@endtemplate}
class PatchAarCommand extends ShorebirdCommand
with ShorebirdConfigMixin, ShorebirdBuildMixin, ShorebirdArtifactMixin {
with ShorebirdBuildMixin, ShorebirdArtifactMixin {
/// {@macro patch_aar_command}
PatchAarCommand({
HashFunction? hashFn,
@@ -105,7 +105,7 @@ of the Android app that is using this module.''',
await cache.updateAll();
if (androidPackageName == null) {
if (shorebirdEnv.androidPackageName == null) {
logger.err('Could not find androidPackage in pubspec.yaml.');
return ExitCode.config.code;
}
@@ -113,7 +113,7 @@ of the Android app that is using this module.''',
final buildNumber = results['build-number'] as String;
final releaseVersion = results['release-version'] as String;
final shorebirdYaml = ShorebirdEnvironment.getShorebirdYaml()!;
final shorebirdYaml = shorebirdEnv.getShorebirdYaml()!;
final appId = shorebirdYaml.getAppId();
final app = await codePushClientWrapper.getApp(appId: appId);
@@ -148,7 +148,8 @@ Please re-run the release command for this version or create a new release.''');
);
final String shorebirdFlutterRevision;
try {
shorebirdFlutterRevision = await getShorebirdFlutterRevision();
shorebirdFlutterRevision =
await shorebirdVersionManager.fetchCurrentGitHash();
flutterRevisionProgress.complete();
} catch (error) {
flutterRevisionProgress.fail('$error');
@@ -171,7 +172,7 @@ Either create a new release using:
${lightCyan.wrap('shorebird release aar')}
Or downgrade your Flutter version and try again using:
${lightCyan.wrap('cd ${ShorebirdEnvironment.flutterDirectory.path}')}
${lightCyan.wrap('cd ${shorebirdEnv.flutterDirectory.path}')}
${lightCyan.wrap('git checkout ${release.flutterRevision}')}
Shorebird plans to support this automatically, let us know if it's important to you:
@@ -217,7 +218,7 @@ https://github.com/shorebirdtech/shorebird/issues/472
final contentDiffs = _aarDiffer.changedFiles(
releaseAarPath,
aarArtifactPath(
packageName: androidPackageName!,
packageName: shorebirdEnv.androidPackageName!,
buildNumber: buildNumber,
),
);
@@ -247,7 +248,7 @@ https://github.com/shorebirdtech/shorebird/issues/472
}
final extractedAarDir = await extractAar(
packageName: androidPackageName!,
packageName: shorebirdEnv.androidPackageName!,
buildNumber: buildNumber,
unzipFn: _unzipFn,
);
@@ -14,10 +14,10 @@ import 'package:shorebird_cli/src/doctor.dart';
import 'package:shorebird_cli/src/formatters/formatters.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/shorebird_build_mixin.dart';
import 'package:shorebird_cli/src/shorebird_config_mixin.dart';
import 'package:shorebird_cli/src/shorebird_environment.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_release_version_mixin.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
import 'package:shorebird_cli/src/shorebird_version_manager.dart';
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
/// {@template patch_android_command}
@@ -26,10 +26,7 @@ import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
/// push server.
/// {@endtemplate}
class PatchAndroidCommand extends ShorebirdCommand
with
ShorebirdConfigMixin,
ShorebirdBuildMixin,
ShorebirdReleaseVersionMixin {
with ShorebirdBuildMixin, ShorebirdReleaseVersionMixin {
/// {@macro patch_android_command}
PatchAndroidCommand({
HashFunction? hashFn,
@@ -112,7 +109,7 @@ class PatchAndroidCommand extends ShorebirdCommand
return ExitCode.software.code;
}
final shorebirdYaml = ShorebirdEnvironment.getShorebirdYaml()!;
final shorebirdYaml = shorebirdEnv.getShorebirdYaml()!;
final appId = shorebirdYaml.getAppId(flavor: flavor);
final app = await codePushClientWrapper.getApp(appId: appId);
@@ -156,7 +153,8 @@ Please re-run the release command for this version or create a new release.''');
);
final String shorebirdFlutterRevision;
try {
shorebirdFlutterRevision = await getShorebirdFlutterRevision();
shorebirdFlutterRevision =
await shorebirdVersionManager.fetchCurrentGitHash();
flutterRevisionProgress.complete();
} catch (error) {
flutterRevisionProgress.fail('$error');
@@ -179,7 +177,7 @@ Either create a new release using:
${lightCyan.wrap('shorebird release')}
Or downgrade your Flutter version and try again using:
${lightCyan.wrap('cd ${ShorebirdEnvironment.flutterDirectory.path}')}
${lightCyan.wrap('cd ${shorebirdEnv.flutterDirectory.path}')}
${lightCyan.wrap('git checkout ${release.flutterRevision}')}
Shorebird plans to support this automatically, let us know if it's important to you:
@@ -14,16 +14,16 @@ import 'package:shorebird_cli/src/ios.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/shorebird_artifact_mixin.dart';
import 'package:shorebird_cli/src/shorebird_build_mixin.dart';
import 'package:shorebird_cli/src/shorebird_config_mixin.dart';
import 'package:shorebird_cli/src/shorebird_environment.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
import 'package:shorebird_cli/src/shorebird_version_manager.dart';
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
/// {@template patch_ios_command}
/// `shorebird patch ios-alpha` command.
/// {@endtemplate}
class PatchIosCommand extends ShorebirdCommand
with ShorebirdConfigMixin, ShorebirdBuildMixin, ShorebirdArtifactMixin {
with ShorebirdBuildMixin, ShorebirdArtifactMixin {
/// {@macro patch_ios_command}
PatchIosCommand({
HashFunction? hashFn,
@@ -92,7 +92,7 @@ class PatchIosCommand extends ShorebirdCommand
return ExitCode.usage.code;
}
final shorebirdYaml = ShorebirdEnvironment.getShorebirdYaml()!;
final shorebirdYaml = shorebirdEnv.getShorebirdYaml()!;
final appId = shorebirdYaml.getAppId(flavor: flavor);
final app = await codePushClientWrapper.getApp(appId: appId);
@@ -158,7 +158,8 @@ Please re-run the release command for this version or create a new release.''');
);
final String shorebirdFlutterRevision;
try {
shorebirdFlutterRevision = await getShorebirdFlutterRevision();
shorebirdFlutterRevision =
await shorebirdVersionManager.fetchCurrentGitHash();
flutterRevisionProgress.complete();
} catch (error) {
flutterRevisionProgress.fail('$error');
@@ -181,7 +182,7 @@ Either create a new release using:
${lightCyan.wrap('shorebird release')}
Or downgrade your Flutter version and try again using:
${lightCyan.wrap('cd ${ShorebirdEnvironment.flutterDirectory.path}')}
${lightCyan.wrap('cd ${shorebirdEnv.flutterDirectory.path}')}
${lightCyan.wrap('git checkout ${release.flutterRevision}')}
Shorebird plans to support this automatically, let us know if it's important to you:
@@ -12,13 +12,13 @@ import 'package:shorebird_cli/src/ios.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/shorebird_artifact_mixin.dart';
import 'package:shorebird_cli/src/shorebird_build_mixin.dart';
import 'package:shorebird_cli/src/shorebird_config_mixin.dart';
import 'package:shorebird_cli/src/shorebird_environment.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
import 'package:shorebird_cli/src/shorebird_version_manager.dart';
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
class PatchIosFrameworkCommand extends ShorebirdCommand
with ShorebirdConfigMixin, ShorebirdBuildMixin, ShorebirdArtifactMixin {
with ShorebirdBuildMixin, ShorebirdArtifactMixin {
PatchIosFrameworkCommand({
HashFunction? hashFn,
}) : _hashFn = hashFn ?? ((m) => sha256.convert(m).toString()) {
@@ -73,7 +73,7 @@ of the iOS app that is using this module.''',
const releasePlatform = ReleasePlatform.ios;
final releaseVersion = results['release-version'] as String;
final dryRun = results['dry-run'] == true;
final shorebirdYaml = ShorebirdEnvironment.getShorebirdYaml()!;
final shorebirdYaml = shorebirdEnv.getShorebirdYaml()!;
final appId = shorebirdYaml.getAppId();
final app = await codePushClientWrapper.getApp(appId: appId);
@@ -115,7 +115,8 @@ Please re-run the release command for this version or create a new release.''');
);
final String shorebirdFlutterRevision;
try {
shorebirdFlutterRevision = await getShorebirdFlutterRevision();
shorebirdFlutterRevision =
await shorebirdVersionManager.fetchCurrentGitHash();
flutterRevisionProgress.complete();
} catch (error) {
flutterRevisionProgress.fail('$error');
@@ -138,7 +139,7 @@ Either create a new release using:
${lightCyan.wrap('shorebird release')}
Or downgrade your Flutter version and try again using:
${lightCyan.wrap('cd ${ShorebirdEnvironment.flutterDirectory.path}')}
${lightCyan.wrap('cd ${shorebirdEnv.flutterDirectory.path}')}
${lightCyan.wrap('git checkout ${release.flutterRevision}')}
Shorebird plans to support this automatically, let us know if it's important to you:
@@ -9,10 +9,10 @@ import 'package:shorebird_cli/src/config/config.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/shorebird_artifact_mixin.dart';
import 'package:shorebird_cli/src/shorebird_build_mixin.dart';
import 'package:shorebird_cli/src/shorebird_config_mixin.dart';
import 'package:shorebird_cli/src/shorebird_environment.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_release_version_mixin.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
import 'package:shorebird_cli/src/shorebird_version_manager.dart';
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
/// {@template release_aar_command}
@@ -21,7 +21,6 @@ import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
/// {@endtemplate}
class ReleaseAarCommand extends ShorebirdCommand
with
ShorebirdConfigMixin,
ShorebirdBuildMixin,
ShorebirdReleaseVersionMixin,
ShorebirdArtifactMixin {
@@ -75,7 +74,7 @@ make smaller updates to your app.
return e.exitCode.code;
}
if (androidPackageName == null) {
if (shorebirdEnv.androidPackageName == null) {
logger.err('Could not find androidPackage in pubspec.yaml.');
return ExitCode.config.code;
}
@@ -85,7 +84,7 @@ make smaller updates to your app.
final releaseVersion = results['release-version'] as String;
final buildProgress = logger.progress('Building aar');
final shorebirdYaml = ShorebirdEnvironment.getShorebirdYaml()!;
final shorebirdYaml = shorebirdEnv.getShorebirdYaml()!;
final appId = shorebirdYaml.getAppId();
final app = await codePushClientWrapper.getApp(appId: appId);
@@ -141,7 +140,8 @@ ${summary.join('\n')}
);
final String shorebirdFlutterRevision;
try {
shorebirdFlutterRevision = await getShorebirdFlutterRevision();
shorebirdFlutterRevision =
await shorebirdVersionManager.fetchCurrentGitHash();
flutterRevisionProgress.complete();
} catch (error) {
flutterRevisionProgress.fail('$error');
@@ -168,7 +168,7 @@ ${summary.join('\n')}
final extractAarProgress = logger.progress('Creating artifacts');
final extractedAarDir = await extractAar(
packageName: androidPackageName!,
packageName: shorebirdEnv.androidPackageName!,
buildNumber: buildNumber,
unzipFn: _unzipFn,
);
@@ -179,7 +179,7 @@ ${summary.join('\n')}
releaseId: release.id,
platform: platform,
aarPath: aarArtifactPath(
packageName: androidPackageName!,
packageName: shorebirdEnv.androidPackageName!,
buildNumber: buildNumber,
),
extractedAarDir: extractedAarDir,
@@ -201,7 +201,7 @@ Your next step is to add this module as a dependency in your app's build.gradle:
${lightCyan.wrap('''
dependencies {
// ...
releaseImplementation '$androidPackageName:flutter_release:$buildNumber'
releaseImplementation '${shorebirdEnv.androidPackageName}:flutter_release:$buildNumber'
// ...
}''')}
''');
@@ -8,10 +8,10 @@ import 'package:shorebird_cli/src/config/shorebird_yaml.dart';
import 'package:shorebird_cli/src/doctor.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/shorebird_build_mixin.dart';
import 'package:shorebird_cli/src/shorebird_config_mixin.dart';
import 'package:shorebird_cli/src/shorebird_environment.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_release_version_mixin.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
import 'package:shorebird_cli/src/shorebird_version_manager.dart';
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
/// {@template release_android_command}
@@ -19,10 +19,7 @@ import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
/// Create new app releases for Android.
/// {@endtemplate}
class ReleaseAndroidCommand extends ShorebirdCommand
with
ShorebirdConfigMixin,
ShorebirdBuildMixin,
ShorebirdReleaseVersionMixin {
with ShorebirdBuildMixin, ShorebirdReleaseVersionMixin {
/// {@macro release_android_command}
ReleaseAndroidCommand() {
argParser
@@ -89,8 +86,7 @@ make smaller updates to your app.
return ExitCode.software.code;
}
final shorebirdYaml = ShorebirdEnvironment.getShorebirdYaml()!;
final shorebirdYaml = shorebirdEnv.getShorebirdYaml()!;
final appId = shorebirdYaml.getAppId(flavor: flavor);
final app = await codePushClientWrapper.getApp(appId: appId);
@@ -158,7 +154,8 @@ ${summary.join('\n')}
);
final String shorebirdFlutterRevision;
try {
shorebirdFlutterRevision = await getShorebirdFlutterRevision();
shorebirdFlutterRevision =
await shorebirdVersionManager.fetchCurrentGitHash();
flutterRevisionProgress.complete();
} catch (error) {
flutterRevisionProgress.fail('$error');
@@ -12,9 +12,9 @@ import 'package:shorebird_cli/src/ios.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/shorebird_artifact_mixin.dart';
import 'package:shorebird_cli/src/shorebird_build_mixin.dart';
import 'package:shorebird_cli/src/shorebird_config_mixin.dart';
import 'package:shorebird_cli/src/shorebird_environment.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
import 'package:shorebird_cli/src/shorebird_version_manager.dart';
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
/// {@template release_ios_command}
@@ -22,7 +22,7 @@ import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
/// Create new app releases for iOS.
/// {@endtemplate}
class ReleaseIosCommand extends ShorebirdCommand
with ShorebirdBuildMixin, ShorebirdConfigMixin, ShorebirdArtifactMixin {
with ShorebirdBuildMixin, ShorebirdArtifactMixin {
/// {@macro release_ios_command}
ReleaseIosCommand({
IpaReader? ipaReader,
@@ -75,7 +75,7 @@ make smaller updates to your app.
const releasePlatform = ReleasePlatform.ios;
final flavor = results['flavor'] as String?;
final target = results['target'] as String?;
final shorebirdYaml = ShorebirdEnvironment.getShorebirdYaml()!;
final shorebirdYaml = shorebirdEnv.getShorebirdYaml()!;
final appId = shorebirdYaml.getAppId(flavor: flavor);
final app = await codePushClientWrapper.getApp(appId: appId);
@@ -165,7 +165,8 @@ ${summary.join('\n')}
);
final String shorebirdFlutterRevision;
try {
shorebirdFlutterRevision = await getShorebirdFlutterRevision();
shorebirdFlutterRevision =
await shorebirdVersionManager.fetchCurrentGitHash();
flutterRevisionProgress.complete();
} catch (error) {
flutterRevisionProgress.fail('$error');
@@ -10,13 +10,13 @@ import 'package:shorebird_cli/src/doctor.dart';
import 'package:shorebird_cli/src/ios.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/shorebird_build_mixin.dart';
import 'package:shorebird_cli/src/shorebird_config_mixin.dart';
import 'package:shorebird_cli/src/shorebird_environment.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
import 'package:shorebird_cli/src/shorebird_version_manager.dart';
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
class ReleaseIosFrameworkCommand extends ShorebirdCommand
with ShorebirdConfigMixin, ShorebirdBuildMixin {
with ShorebirdBuildMixin {
ReleaseIosFrameworkCommand() {
argParser
..addOption(
@@ -58,7 +58,7 @@ of the iOS app that is using this module.''',
const releasePlatform = ReleasePlatform.ios;
final releaseVersion = results['release-version'] as String;
final shorebirdYaml = ShorebirdEnvironment.getShorebirdYaml()!;
final shorebirdYaml = shorebirdEnv.getShorebirdYaml()!;
final appId = shorebirdYaml.getAppId();
final app = await codePushClientWrapper.getApp(appId: appId);
@@ -113,7 +113,8 @@ ${summary.join('\n')}
);
final String shorebirdFlutterRevision;
try {
shorebirdFlutterRevision = await getShorebirdFlutterRevision();
shorebirdFlutterRevision =
await shorebirdVersionManager.fetchCurrentGitHash();
flutterRevisionProgress.complete();
} catch (error) {
flutterRevisionProgress.fail('$error');
@@ -2,11 +2,11 @@ import 'dart:async';
import 'package:collection/collection.dart';
import 'package:mason_logger/mason_logger.dart';
import 'package:shorebird_cli/src/auth/auth.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/command.dart';
import 'package:shorebird_cli/src/config/shorebird_yaml.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/shorebird_environment.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
@@ -17,7 +17,7 @@ import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
/// {@endtemplate}
class DeleteReleasesCommand extends ShorebirdCommand {
/// {@macro delete_releases_command}
DeleteReleasesCommand({super.buildCodePushClient}) {
DeleteReleasesCommand() {
argParser
..addOption(
'version',
@@ -47,18 +47,14 @@ class DeleteReleasesCommand extends ShorebirdCommand {
}
final flavor = results['flavor'] as String?;
final appId =
ShorebirdEnvironment.getShorebirdYaml()!.getAppId(flavor: flavor);
final codePushClient = buildCodePushClient(
httpClient: auth.client,
hostedUri: ShorebirdEnvironment.hostedUri,
);
final appId = shorebirdEnv.getShorebirdYaml()!.getAppId(flavor: flavor);
final List<Release> releases;
var progress = logger.progress('Fetching releases');
try {
releases = await codePushClient.getReleases(appId: appId);
releases = await codePushClientWrapper.codePushClient.getReleases(
appId: appId,
);
progress.complete('Fetched releases.');
} catch (error) {
progress.fail('$error');
@@ -89,7 +85,7 @@ class DeleteReleasesCommand extends ShorebirdCommand {
progress = logger.progress('Deleting release');
try {
await codePushClient.deleteRelease(
await codePushClientWrapper.codePushClient.deleteRelease(
appId: appId,
releaseId: releaseToDelete.id,
);
@@ -1,10 +1,10 @@
import 'package:barbecue/barbecue.dart';
import 'package:mason_logger/mason_logger.dart';
import 'package:shorebird_cli/src/auth/auth.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/command.dart';
import 'package:shorebird_cli/src/config/shorebird_yaml.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/shorebird_environment.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
@@ -15,7 +15,7 @@ import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
/// {@endtemplate}
class ListReleasesCommand extends ShorebirdCommand {
/// {@macro list_releases_command}
ListReleasesCommand({super.buildCodePushClient}) {
ListReleasesCommand() {
argParser.addOption(
'flavor',
help: 'The product flavor to use when listing releases.',
@@ -47,17 +47,13 @@ Please use $consoleLink instead.''',
}
final flavor = results['flavor'] as String?;
final appId =
ShorebirdEnvironment.getShorebirdYaml()!.getAppId(flavor: flavor);
final codePushClient = buildCodePushClient(
httpClient: auth.client,
hostedUri: ShorebirdEnvironment.hostedUri,
);
final appId = shorebirdEnv.getShorebirdYaml()!.getAppId(flavor: flavor);
final List<Release> releases;
try {
releases = await codePushClient.getReleases(appId: appId);
releases = await codePushClientWrapper.codePushClient.getReleases(
appId: appId,
);
} catch (error) {
logger.err('$error');
return ExitCode.software.code;
@@ -14,7 +14,7 @@ import 'package:shorebird_cli/src/shorebird_validator.dart';
/// {@endtemplate}
class RunCommand extends ShorebirdCommand {
/// {@macro run_command}
RunCommand({super.buildCodePushClient}) {
RunCommand() {
argParser
..addOption(
'device-id',
@@ -4,7 +4,7 @@ import 'package:mason_logger/mason_logger.dart';
import 'package:shorebird_cli/src/command.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/process.dart';
import 'package:shorebird_cli/src/shorebird_environment.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_version_manager.dart';
/// {@template upgrade_command}
@@ -91,7 +91,7 @@ class UpgradeCommand extends ShorebirdCommand {
final result = await process.run(
executable,
args,
workingDirectory: ShorebirdEnvironment.flutterDirectory.path,
workingDirectory: shorebirdEnv.flutterDirectory.path,
);
if (result.exitCode != 0) {
@@ -8,7 +8,7 @@ import 'package:path/path.dart' as p;
import 'package:scoped/scoped.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/process.dart';
import 'package:shorebird_cli/src/shorebird_environment.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
/// A reference to a [IOSDeploy] instance.
final iosDeployRef = create(IOSDeploy.new);
@@ -33,7 +33,7 @@ class IOSDeploy {
@visibleForTesting
static File get iosDeployExecutable => File(
p.join(
ShorebirdEnvironment.flutterDirectory.path,
shorebirdEnv.flutterDirectory.path,
'bin',
'cache',
'artifacts',
+2 -5
View File
@@ -3,7 +3,7 @@ import 'dart:io';
import 'package:mason_logger/mason_logger.dart';
import 'package:meta/meta.dart';
import 'package:scoped/scoped.dart';
import 'package:shorebird_cli/src/shorebird_environment.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
// A reference to a [EngineConfig] instance.
final engineConfigRef = create(() => const EngineConfig.empty());
@@ -111,10 +111,7 @@ class ShorebirdProcess {
}
String _resolveExecutable(String executable) {
if (executable == 'flutter') {
return ShorebirdEnvironment.flutterBinaryFile.path;
}
if (executable == 'flutter') return shorebirdEnv.flutterBinaryFile.path;
return executable;
}
@@ -6,7 +6,7 @@ import 'package:path/path.dart' as p;
import 'package:shorebird_cli/src/cache.dart';
import 'package:shorebird_cli/src/command.dart';
import 'package:shorebird_cli/src/process.dart';
import 'package:shorebird_cli/src/shorebird_environment.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
enum Arch {
arm64,
@@ -328,7 +328,7 @@ mixin ShorebirdBuildMixin on ShorebirdCommand {
];
final result = await process.run(
ShorebirdEnvironment.genSnapshotFile.path,
shorebirdEnv.genSnapshotFile.path,
arguments,
);
@@ -1,84 +0,0 @@
import 'dart:io';
import 'package:path/path.dart' as p;
import 'package:shorebird_cli/src/command.dart';
import 'package:shorebird_cli/src/config/config.dart';
import 'package:shorebird_cli/src/process.dart';
import 'package:shorebird_cli/src/shorebird_environment.dart';
import 'package:yaml/yaml.dart';
import 'package:yaml_edit/yaml_edit.dart';
mixin ShorebirdConfigMixin on ShorebirdCommand {
/// Returns the Android package name from the pubspec.yaml file of a Flutter
/// module.
String? get androidPackageName {
final pubspec = ShorebirdEnvironment.getPubspecYaml();
final module = pubspec?.flutter?['module'] as Map?;
return module?['androidPackage'] as String?;
}
ShorebirdYaml addShorebirdYamlToProject(
String appId, {
Map<String, String>? flavors,
}) {
const content = '''
# This file is used to configure the Shorebird updater used by your application.
# Learn more at https://shorebird.dev
# This file should be checked into version control.
# This is the unique identifier assigned to your app.
# It is used by your app to request the correct patches from Shorebird servers.
app_id:
''';
final editor = YamlEditor(content)..update(['app_id'], appId);
if (flavors != null) editor.update(['flavors'], flavors);
ShorebirdEnvironment.getShorebirdYamlFile()
.writeAsStringSync(editor.toString());
return ShorebirdYaml(appId: appId);
}
void addShorebirdYamlToPubspecAssets() {
final pubspecFile = File(p.join(Directory.current.path, 'pubspec.yaml'));
final pubspecContents = pubspecFile.readAsStringSync();
final yaml = loadYaml(pubspecContents, sourceUrl: pubspecFile.uri) as Map;
final editor = YamlEditor(pubspecContents);
if (!yaml.containsKey('flutter')) {
editor.update(
['flutter'],
{
'assets': ['shorebird.yaml']
},
);
} else {
if (!(yaml['flutter'] as Map).containsKey('assets')) {
editor.update(['flutter', 'assets'], ['shorebird.yaml']);
} else {
final assets = (yaml['flutter'] as Map)['assets'] as List;
if (!assets.contains('shorebird.yaml')) {
editor.update(['flutter', 'assets'], [...assets, 'shorebird.yaml']);
}
}
}
if (editor.edits.isEmpty) return;
pubspecFile.writeAsStringSync(editor.toString());
}
Future<String> getShorebirdFlutterRevision() async {
final result = await process.run(
'git',
['rev-parse', 'HEAD'],
workingDirectory: ShorebirdEnvironment.flutterDirectory.path,
);
if (result.exitCode != 0) {
throw Exception('Unable to determine flutter revision: ${result.stderr}');
}
return (result.stdout as String).trim();
}
}
@@ -1,32 +0,0 @@
import 'package:mason_logger/mason_logger.dart';
import 'package:shorebird_cli/src/auth/auth.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/shorebird_config_mixin.dart';
import 'package:shorebird_cli/src/shorebird_environment.dart';
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
mixin ShorebirdCreateAppMixin on ShorebirdConfigMixin {
Future<App> createApp({String? appName}) async {
late final String displayName;
if (appName == null) {
String? defaultAppName;
try {
defaultAppName = ShorebirdEnvironment.getPubspecYaml()?.name;
} catch (_) {}
displayName = logger.prompt(
'${lightGreen.wrap('?')} How should we refer to this app?',
defaultValue: defaultAppName,
);
} else {
displayName = appName;
}
final client = buildCodePushClient(
httpClient: auth.client,
hostedUri: ShorebirdEnvironment.hostedUri,
);
return client.createApp(displayName: displayName);
}
}
@@ -0,0 +1,157 @@
import 'dart:io' hide Platform;
import 'package:checked_yaml/checked_yaml.dart';
import 'package:path/path.dart' as p;
import 'package:pubspec_parse/pubspec_parse.dart';
import 'package:scoped/scoped.dart';
import 'package:shorebird_cli/src/config/shorebird_yaml.dart';
import 'package:shorebird_cli/src/platform.dart';
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
import 'package:yaml/yaml.dart';
/// A reference to a [ShorebirdEnv] instance.
final shorebirdEnvRef = create(ShorebirdEnv.new);
/// The [ShorebirdEnv] instance available in the current zone.
ShorebirdEnv get shorebirdEnv => read(shorebirdEnvRef);
/// {@template shorebird_env}
/// A class that provides access to shorebird environment metadata.
/// {@endtemplate}
class ShorebirdEnv {
/// {@macro shorebird_env}
const ShorebirdEnv();
/// The root directory of the Shorebird install.
///
/// Assumes we are running from $ROOT/bin/cache.
Directory get shorebirdRoot {
return File(platform.script.toFilePath()).parent.parent.parent;
}
String get shorebirdEngineRevision {
return File(
p.join(flutterDirectory.path, 'bin', 'internal', 'engine.version'),
).readAsStringSync().trim();
}
String get flutterRevision {
return File(
p.join(shorebirdRoot.path, 'bin', 'internal', 'flutter.version'),
).readAsStringSync().trim();
}
/// The root of the Shorebird-vended Flutter git checkout.
Directory get flutterDirectory {
return Directory(
p.join(
shorebirdRoot.path,
'bin',
'cache',
'flutter',
),
);
}
/// The Shorebird-vended Flutter binary.
File get flutterBinaryFile {
return File(
p.join(
flutterDirectory.path,
'bin',
'flutter',
),
);
}
File get genSnapshotFile {
return File(
p.join(
flutterDirectory.path,
'bin',
'cache',
'artifacts',
'engine',
'ios-release',
'gen_snapshot_arm64',
),
);
}
/// The `shorebird.yaml` file for this project.
File getShorebirdYamlFile() {
return File(p.join(Directory.current.path, 'shorebird.yaml'));
}
/// The `pubspec.yaml` file for this project.
File getPubspecYamlFile() {
return File(p.join(Directory.current.path, 'pubspec.yaml'));
}
/// The `shorebird.yaml` file for this project, parsed into a [ShorebirdYaml]
/// object.
///
/// Returns `null` if the file does not exist.
/// Throws a [ParsedYamlException] if the file exists but is invalid.
ShorebirdYaml? getShorebirdYaml() {
final file = getShorebirdYamlFile();
if (!file.existsSync()) return null;
final yaml = file.readAsStringSync();
return checkedYamlDecode(yaml, (m) => ShorebirdYaml.fromJson(m!));
}
/// The `pubspec.yaml` file for this project, parsed into a [Pubspec] object.
///
/// Returns `null` if the file does not exist.
/// Throws a [ParsedYamlException] if the file exists but is invalid.
Pubspec? getPubspecYaml() {
final file = getPubspecYamlFile();
if (!file.existsSync()) return null;
final yaml = file.readAsStringSync();
return Pubspec.parse(yaml);
}
/// Whether `shorebird init` has been run in the current project.
bool get isShorebirdInitialized {
return hasShorebirdYaml && pubspecContainsShorebirdYaml;
}
/// Whether the current project has a `shorebird.yaml` file.
bool get hasShorebirdYaml => getShorebirdYamlFile().existsSync();
/// Whether the current project has a `pubspec.yaml` file.
bool get hasPubspecYaml => getPubspecYaml() != null;
/// Whether the current project's `pubspec.yaml` file contains a reference to
/// `shorebird.yaml` in its `assets` section.
bool get pubspecContainsShorebirdYaml {
final file = File(p.join(Directory.current.path, 'pubspec.yaml'));
final pubspecContents = file.readAsStringSync();
final yaml = loadYaml(pubspecContents, sourceUrl: file.uri) as Map;
if (!yaml.containsKey('flutter')) return false;
if (!(yaml['flutter'] as Map).containsKey('assets')) return false;
final assets = (yaml['flutter'] as Map)['assets'] as List;
return assets.contains('shorebird.yaml');
}
/// Returns the Android package name from the pubspec.yaml file of a Flutter
/// module.
String? get androidPackageName {
final pubspec = getPubspecYaml();
final module = pubspec?.flutter?['module'] as Map?;
return module?['androidPackage'] as String?;
}
/// The base URL for the Shorebird code push server that overrides the default
/// used by [CodePushClient]. If none is provided, [CodePushClient] will use
/// its default.
Uri? get hostedUri {
try {
final baseUrl = platform.environment['SHOREBIRD_HOSTED_URL'] ??
getShorebirdYaml()?.baseUrl;
return baseUrl == null ? null : Uri.tryParse(baseUrl);
} catch (_) {
return null;
}
}
}
@@ -1,144 +0,0 @@
import 'dart:io' hide Platform;
import 'package:checked_yaml/checked_yaml.dart';
import 'package:meta/meta.dart';
import 'package:path/path.dart' as p;
import 'package:platform/platform.dart';
import 'package:pubspec_parse/pubspec_parse.dart';
import 'package:shorebird_cli/src/config/shorebird_yaml.dart';
import 'package:shorebird_cli/src/platform.dart';
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
import 'package:yaml/yaml.dart';
abstract class ShorebirdEnvironment {
/// Environment variables from [Platform.environment].
static Map<String, String> get environment => platform.environment;
/// The root directory of the Shorebird install.
///
/// Assumes we are running from $ROOT/bin/cache.
static Directory get shorebirdRoot =>
File(platform.script.toFilePath()).parent.parent.parent;
static String get shorebirdEngineRevision {
return _shorebirdEngineRevision ??
File(p.join(flutterDirectory.path, 'bin', 'internal', 'engine.version'))
.readAsStringSync()
.trim();
}
static String? _shorebirdEngineRevision;
@visibleForTesting
static set shorebirdEngineRevision(String revision) {
_shorebirdEngineRevision = revision;
}
static String? _flutterRevision;
@visibleForTesting
static set flutterRevision(String revision) {
_flutterRevision = revision;
}
static String get flutterRevision {
return _flutterRevision ??
File(
p.join(shorebirdRoot.path, 'bin', 'internal', 'flutter.version'),
).readAsStringSync().trim();
}
/// The root of the Shorebird-vended Flutter git checkout.
static Directory get flutterDirectory => Directory(
p.join(
shorebirdRoot.path,
'bin',
'cache',
'flutter',
),
);
/// The Shorebird-vended Flutter binary.
static File get flutterBinaryFile => File(
p.join(
flutterDirectory.path,
'bin',
'flutter',
),
);
static File get genSnapshotFile => File(
p.join(
flutterDirectory.path,
'bin',
'cache',
'artifacts',
'engine',
'ios-release',
'gen_snapshot_arm64',
),
);
/// The `shorebird.yaml` file for this project.
static File getShorebirdYamlFile() {
return File(p.join(Directory.current.path, 'shorebird.yaml'));
}
/// The `shorebird.yaml` file for this project, parsed into a [ShorebirdYaml]
/// object.
///
/// Returns `null` if the file does not exist.
/// Throws a [ParsedYamlException] if the file exists but is invalid.
static ShorebirdYaml? getShorebirdYaml() {
final file = getShorebirdYamlFile();
if (!file.existsSync()) return null;
final yaml = file.readAsStringSync();
return checkedYamlDecode(yaml, (m) => ShorebirdYaml.fromJson(m!));
}
static bool get isShorebirdInitialized {
return hasShorebirdYaml && pubspecContainsShorebirdYaml;
}
static bool get hasShorebirdYaml {
return ShorebirdEnvironment.getShorebirdYamlFile().existsSync();
}
static bool get hasPubspecYaml {
return ShorebirdEnvironment.getPubspecYaml() != null;
}
static bool get pubspecContainsShorebirdYaml {
final file = File(p.join(Directory.current.path, 'pubspec.yaml'));
final pubspecContents = file.readAsStringSync();
final yaml = loadYaml(pubspecContents, sourceUrl: file.uri) as Map;
if (!yaml.containsKey('flutter')) return false;
if (!(yaml['flutter'] as Map).containsKey('assets')) return false;
final assets = (yaml['flutter'] as Map)['assets'] as List;
return assets.contains('shorebird.yaml');
}
/// The `pubspec.yaml` file for this project, parsed into a [Pubspec] object.
///
/// Returns `null` if the file does not exist.
/// Throws a [ParsedYamlException] if the file exists but is invalid.
static Pubspec? getPubspecYaml() {
final file = File(p.join(Directory.current.path, 'pubspec.yaml'));
if (!file.existsSync()) return null;
final yaml = file.readAsStringSync();
return Pubspec.parse(yaml);
}
/// The base URL for the Shorebird code push server that overrides the default
/// used by [CodePushClient]. If none is provided, [CodePushClient] will use
/// its default.
static Uri? get hostedUri {
try {
final baseUrl = platform.environment['SHOREBIRD_HOSTED_URL'] ??
getShorebirdYaml()?.baseUrl;
return baseUrl == null ? null : Uri.tryParse(baseUrl);
} catch (_) {
return null;
}
}
}
@@ -4,7 +4,7 @@ import 'package:scoped/scoped.dart';
import 'package:shorebird_cli/src/auth/auth.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/platform.dart';
import 'package:shorebird_cli/src/shorebird_environment.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/validators/validators.dart';
abstract interface class PreconditionFailedException implements Exception {
@@ -73,8 +73,7 @@ class ShorebirdValidator {
throw UserNotAuthorizedException();
}
if (checkShorebirdInitialized &&
!ShorebirdEnvironment.isShorebirdInitialized) {
if (checkShorebirdInitialized && !shorebirdEnv.isShorebirdInitialized) {
logger.err(
'Shorebird is not initialized. Did you run "shorebird init"?',
);
@@ -1,5 +1,6 @@
import 'package:shorebird_cli/src/platform.dart';
import 'package:shorebird_cli/src/process.dart';
import 'package:shorebird_cli/src/shorebird_environment.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/validators/validators.dart';
import 'package:version/version.dart';
@@ -28,9 +29,9 @@ class ShorebirdFlutterValidator extends Validator {
Future<List<ValidationIssue>> validate() async {
final issues = <ValidationIssue>[];
if (!ShorebirdEnvironment.flutterDirectory.existsSync()) {
if (!shorebirdEnv.flutterDirectory.existsSync()) {
final message = 'No Flutter directory found at '
'${ShorebirdEnvironment.flutterDirectory}';
'${shorebirdEnv.flutterDirectory}';
issues.add(
ValidationIssue(
severity: ValidationIssueSeverity.error,
@@ -43,7 +44,7 @@ class ShorebirdFlutterValidator extends Validator {
issues.add(
ValidationIssue(
severity: ValidationIssueSeverity.warning,
message: '${ShorebirdEnvironment.flutterDirectory} has local '
message: '${shorebirdEnv.flutterDirectory} has local '
'modifications',
),
);
@@ -96,7 +97,7 @@ This can cause unexpected behavior if you are switching between the tools and th
}
final flutterStorageEnvironmentValue =
ShorebirdEnvironment.environment['FLUTTER_STORAGE_BASE_URL'];
platform.environment['FLUTTER_STORAGE_BASE_URL'];
if (flutterStorageEnvironmentValue != null &&
flutterStorageEnvironmentValue.isNotEmpty) {
issues.add(
@@ -115,7 +116,7 @@ This can cause unexpected behavior if you are switching between the tools and th
final result = await process.run(
'git',
['status', '--untracked-files=no', '--porcelain'],
workingDirectory: ShorebirdEnvironment.flutterDirectory.path,
workingDirectory: shorebirdEnv.flutterDirectory.path,
);
return result.stdout.toString().trim().isEmpty;
}
+28 -23
View File
@@ -8,7 +8,7 @@ import 'package:platform/platform.dart';
import 'package:scoped/scoped.dart';
import 'package:shorebird_cli/src/cache.dart';
import 'package:shorebird_cli/src/platform.dart';
import 'package:shorebird_cli/src/shorebird_environment.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:test/test.dart';
class _FakeBaseRequest extends Fake implements http.BaseRequest {}
@@ -17,6 +17,8 @@ class _MockHttpClient extends Mock implements http.Client {}
class _MockPlatform extends Mock implements Platform {}
class _MockShorebirdEnv extends Mock implements ShorebirdEnv {}
class TestCachedArtifact extends CachedArtifact {
TestCachedArtifact({required super.cache, required super.platform});
@@ -29,9 +31,12 @@ class TestCachedArtifact extends CachedArtifact {
void main() {
group('Cache', () {
const shorebirdEngineRevision = 'test-revision';
late Directory shorebirdRoot;
late http.Client httpClient;
late Platform platform;
late ShorebirdEnv shorebirdEnv;
late Cache cache;
R runWithOverrides<R>(R Function() body) {
@@ -40,6 +45,7 @@ void main() {
values: {
cacheRef.overrideWith(() => cache),
platformRef.overrideWith(() => platform),
shorebirdEnvRef.overrideWith(() => shorebirdEnv),
},
);
}
@@ -51,24 +57,18 @@ void main() {
setUp(() {
httpClient = _MockHttpClient();
platform = _MockPlatform();
shorebirdEnv = _MockShorebirdEnv();
shorebirdRoot = Directory.systemTemp.createTempSync();
ShorebirdEnvironment.shorebirdEngineRevision = 'test-revision';
when(
() => shorebirdEnv.shorebirdEngineRevision,
).thenReturn(shorebirdEngineRevision);
when(() => shorebirdEnv.shorebirdRoot).thenReturn(shorebirdRoot);
when(() => platform.environment).thenReturn({});
when(() => platform.isMacOS).thenReturn(true);
when(() => platform.isWindows).thenReturn(false);
when(() => platform.isLinux).thenReturn(false);
when(() => platform.script).thenReturn(
Uri.file(
p.join(
shorebirdRoot.path,
'bin',
'cache',
'shorebird.snapshot',
),
),
);
when(() => httpClient.send(any())).thenAnswer(
(_) async => http.StreamedResponse(
@@ -77,7 +77,7 @@ void main() {
),
);
cache = Cache(httpClient: httpClient, platform: platform);
cache = runWithOverrides(() => Cache(httpClient: httpClient));
});
test('can be instantiated w/out args', () {
@@ -86,7 +86,9 @@ void main() {
group('getArtifactDirectory', () {
test('returns correct directory', () {
final directory = cache.getArtifactDirectory('test');
final directory = runWithOverrides(
() => cache.getArtifactDirectory('test'),
);
expect(
directory.path.endsWith(
p.join(
@@ -103,7 +105,9 @@ void main() {
group('getPreviewDirectory', () {
test('returns correct directory', () {
final directory = cache.getPreviewDirectory('test');
final directory = runWithOverrides(
() => cache.getPreviewDirectory('test'),
);
expect(
directory.path.endsWith(
p.join(
@@ -132,17 +136,18 @@ void main() {
group('clear', () {
test('deletes the cache directory', () async {
final shorebirdCacheDirectory =
runWithOverrides(() => Cache.shorebirdCacheDirectory)
..createSync(recursive: true);
final shorebirdCacheDirectory = runWithOverrides(
() => Cache.shorebirdCacheDirectory,
)..createSync(recursive: true);
expect(shorebirdCacheDirectory.existsSync(), isTrue);
runWithOverrides(cache.clear);
expect(shorebirdCacheDirectory.existsSync(), isFalse);
});
test('does nothing if directory does not exist', () {
final shorebirdCacheDirectory =
runWithOverrides(() => Cache.shorebirdCacheDirectory);
final shorebirdCacheDirectory = runWithOverrides(
() => Cache.shorebirdCacheDirectory,
);
expect(shorebirdCacheDirectory.existsSync(), isFalse);
runWithOverrides(cache.clear);
expect(shorebirdCacheDirectory.existsSync(), isFalse);
@@ -175,7 +180,7 @@ void main() {
request.url,
equals(
Uri.parse(
'${cache.storageBaseUrl}/${cache.storageBucket}/shorebird/${ShorebirdEnvironment.shorebirdEngineRevision}/patch-darwin-x64.zip',
'${cache.storageBaseUrl}/${cache.storageBucket}/shorebird/$shorebirdEngineRevision/patch-darwin-x64.zip',
),
),
);
@@ -196,7 +201,7 @@ void main() {
request.url,
equals(
Uri.parse(
'${cache.storageBaseUrl}/${cache.storageBucket}/shorebird/${ShorebirdEnvironment.shorebirdEngineRevision}/patch-windows-x64.zip',
'${cache.storageBaseUrl}/${cache.storageBucket}/shorebird/$shorebirdEngineRevision/patch-windows-x64.zip',
),
),
);
@@ -216,7 +221,7 @@ void main() {
request.url,
equals(
Uri.parse(
'${cache.storageBaseUrl}/${cache.storageBucket}/shorebird/${ShorebirdEnvironment.shorebirdEngineRevision}/patch-linux-x64.zip',
'${cache.storageBaseUrl}/${cache.storageBucket}/shorebird/$shorebirdEngineRevision/patch-linux-x64.zip',
),
),
);
@@ -9,6 +9,7 @@ import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/platform.dart';
import 'package:shorebird_cli/src/shorebird_build_mixin.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/third_party/flutter_tools/lib/flutter_tools.dart';
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
import 'package:test/test.dart';
@@ -25,21 +26,25 @@ class _MockPlatform extends Mock implements Platform {}
class _MockProgress extends Mock implements Progress {}
class _MockShorebirdEnv extends Mock implements ShorebirdEnv {}
void main() {
group('scoped', () {
late Auth auth;
late http.Client httpClient;
late Platform platform;
late ShorebirdEnv shorebirdEnv;
setUp(() {
auth = _MockAuth();
httpClient = _MockHttpClient();
platform = _MockPlatform();
shorebirdEnv = _MockShorebirdEnv();
when(() => auth.client).thenReturn(httpClient);
when(() => platform.environment).thenReturn({
'SHOREBIRD_HOSTED_URL': 'http://example.com',
});
when(() => shorebirdEnv.hostedUri).thenReturn(
Uri.parse('http://example.com'),
);
});
test('creates instance from scoped Auth and ShorebirdEnvironment', () {
@@ -49,6 +54,7 @@ void main() {
codePushClientWrapperRef,
authRef.overrideWith(() => auth),
platformRef.overrideWith(() => platform),
shorebirdEnvRef.overrideWith(() => shorebirdEnv),
},
);
expect(
@@ -160,6 +166,43 @@ void main() {
});
group('app', () {
group('createApp', () {
test('prompts for displayName when not provided', () async {
const appName = 'test app';
const app = App(id: appId, displayName: 'Test App');
when(() => logger.prompt(any())).thenReturn(appName);
when(() => codePushClient.createApp(displayName: appName)).thenAnswer(
(_) async => app,
);
await runWithOverrides(
() => codePushClientWrapper.createApp(),
);
verify(() => logger.prompt(any())).called(1);
verify(
() => codePushClient.createApp(displayName: appName),
).called(1);
});
test('does not prompt for displayName when not provided', () async {
const appName = 'test app';
const app = App(id: appId, displayName: 'Test App');
when(() => codePushClient.createApp(displayName: appName)).thenAnswer(
(_) async => app,
);
await runWithOverrides(
() => codePushClientWrapper.createApp(appName: appName),
);
verifyNever(() => logger.prompt(any()));
verify(
() => codePushClient.createApp(displayName: appName),
).called(1);
});
});
group('getApps', () {
test('exits with code 70 when getting apps fails', () async {
const error = 'something went wrong';
@@ -1,29 +1,26 @@
import 'package:args/command_runner.dart';
import 'package:http/http.dart' as http;
import 'package:mason_logger/mason_logger.dart';
import 'package:mocktail/mocktail.dart';
import 'package:scoped/scoped.dart';
import 'package:shorebird_cli/src/auth/auth.dart' hide auth;
import 'package:shorebird_cli/src/command_runner.dart';
import 'package:shorebird_cli/src/logger.dart' hide logger;
import 'package:shorebird_cli/src/process.dart';
import 'package:shorebird_cli/src/shorebird_environment.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/version.dart';
import 'package:test/test.dart';
class _MockAuth extends Mock implements Auth {}
class _MockLogger extends Mock implements Logger {}
class _MockHttpClient extends Mock implements http.Client {}
class _MockShorebirdEnv extends Mock implements ShorebirdEnv {}
class _MockProcessResult extends Mock implements ShorebirdProcessResult {}
void main() {
group(ShorebirdCliCommandRunner, () {
late http.Client httpClient;
late Auth auth;
const shorebirdEngineRevision = 'test-revision';
late Logger logger;
late ShorebirdEnv shorebirdEnv;
late ShorebirdProcessResult processResult;
late ShorebirdCliCommandRunner commandRunner;
@@ -31,20 +28,20 @@ void main() {
return runScoped(
body,
values: {
authRef.overrideWith(() => auth),
loggerRef.overrideWith(() => logger)
loggerRef.overrideWith(() => logger),
shorebirdEnvRef.overrideWith(() => shorebirdEnv),
},
);
}
setUp(() {
httpClient = _MockHttpClient();
auth = _MockAuth();
logger = _MockLogger();
ShorebirdEnvironment.shorebirdEngineRevision = 'test-revision';
shorebirdEnv = _MockShorebirdEnv();
processResult = _MockProcessResult();
when(() => auth.client).thenReturn(httpClient);
when(() => processResult.exitCode).thenReturn(ExitCode.success.code);
when(
() => shorebirdEnv.shorebirdEngineRevision,
).thenReturn(shorebirdEngineRevision);
commandRunner = runWithOverrides(ShorebirdCliCommandRunner.new);
});
@@ -122,7 +119,7 @@ ${lightCyan.wrap('shorebird release android -- --no-pub lib/main.dart')}''',
() => logger.info(
'''
Shorebird $packageVersion
Shorebird Engine revision ${ShorebirdEnvironment.shorebirdEngineRevision}''',
Shorebird Engine revision $shorebirdEngineRevision''',
),
).called(1);
});
@@ -1,20 +1,18 @@
import 'package:http/http.dart' as http;
import 'package:mason_logger/mason_logger.dart';
import 'package:mocktail/mocktail.dart';
import 'package:scoped/scoped.dart';
import 'package:shorebird_cli/src/auth/auth.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/commands/account/account.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
import 'package:test/test.dart';
class _MockAuth extends Mock implements Auth {}
class _MockCodePushClientWrapper extends Mock
implements CodePushClientWrapper {}
class _MockCodePushClient extends Mock implements CodePushClient {}
class _MockHttpClient extends Mock implements http.Client {}
class _MockLogger extends Mock implements Logger {}
class _MockProgress extends Mock implements Progress {}
@@ -30,9 +28,8 @@ void main() {
hasActiveSubscription: true,
);
late Auth auth;
late CodePushClientWrapper codePushClientWrapper;
late CodePushClient codePushClient;
late http.Client httpClient;
late Logger logger;
late Progress progress;
late ShorebirdValidator shorebirdValidator;
@@ -42,7 +39,7 @@ void main() {
return runScoped(
body,
values: {
authRef.overrideWith(() => auth),
codePushClientWrapperRef.overrideWith(() => codePushClientWrapper),
loggerRef.overrideWith(() => logger),
shorebirdValidatorRef.overrideWith(() => shorebirdValidator),
},
@@ -50,14 +47,15 @@ void main() {
}
setUp(() {
auth = _MockAuth();
codePushClientWrapper = _MockCodePushClientWrapper();
codePushClient = _MockCodePushClient();
httpClient = _MockHttpClient();
logger = _MockLogger();
progress = _MockProgress();
shorebirdValidator = _MockShorebirdValidator();
when(() => auth.client).thenReturn(httpClient);
when(
() => codePushClientWrapper.codePushClient,
).thenReturn(codePushClient);
when(() => logger.progress(any())).thenReturn(progress);
when(
() => shorebirdValidator.validatePreconditions(
@@ -65,16 +63,7 @@ void main() {
),
).thenAnswer((_) async {});
command = runWithOverrides(
() => DowngradeAccountCommand(
buildCodePushClient: ({
required http.Client httpClient,
Uri? hostedUri,
}) {
return codePushClient;
},
),
);
command = runWithOverrides(DowngradeAccountCommand.new);
});
test('has a description', () {
@@ -100,7 +89,6 @@ void main() {
});
test('prints an error if fetch current user fails', () async {
when(() => auth.isAuthenticated).thenReturn(true);
when(() => codePushClient.getCurrentUser()).thenThrow(
Exception('an error occurred'),
);
@@ -114,7 +102,6 @@ void main() {
});
test('prints an error if fetch current user returns null', () async {
when(() => auth.isAuthenticated).thenReturn(true);
when(() => codePushClient.getCurrentUser()).thenAnswer((_) async => null);
final result = await runWithOverrides(command.run);
@@ -130,7 +117,6 @@ void main() {
test(
'prints an error if the user does not have an active subscription',
() async {
when(() => auth.isAuthenticated).thenReturn(true);
when(
() => codePushClient.getCurrentUser(),
).thenAnswer((_) async => noSubscriptionUser);
@@ -147,7 +133,6 @@ void main() {
);
test('exits successfully if the user opts not to cancel', () async {
when(() => auth.isAuthenticated).thenReturn(true);
when(
() => codePushClient.getCurrentUser(),
).thenAnswer((_) async => subscriptionUser);
@@ -162,7 +147,6 @@ void main() {
});
test('prints an error if call to cancel subscription fails', () async {
when(() => auth.isAuthenticated).thenReturn(true);
when(
() => codePushClient.getCurrentUser(),
).thenAnswer((_) async => subscriptionUser);
@@ -184,8 +168,6 @@ void main() {
test('exits successfully on subscription cancellation', () async {
// Fri Apr 14 2023 07:00:00 GMT+0000
const cancellationTimestamp = 1681455600;
when(() => auth.isAuthenticated).thenReturn(true);
when(
() => codePushClient.getCurrentUser(),
).thenAnswer((_) async => subscriptionUser);
@@ -1,20 +1,18 @@
import 'package:http/http.dart' as http;
import 'package:mason_logger/mason_logger.dart';
import 'package:mocktail/mocktail.dart';
import 'package:scoped/scoped.dart';
import 'package:shorebird_cli/src/auth/auth.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/commands/account/account.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
import 'package:test/test.dart';
class _MockAuth extends Mock implements Auth {}
class _MockCodePushClientWrapper extends Mock
implements CodePushClientWrapper {}
class _MockCodePushClient extends Mock implements CodePushClient {}
class _MockHttpClient extends Mock implements http.Client {}
class _MockLogger extends Mock implements Logger {}
class _MockProgress extends Mock implements Progress {}
@@ -26,9 +24,8 @@ class _MockUser extends Mock implements User {}
void main() {
final paymentLink = Uri.parse('https://example.com/payment-link');
late Auth auth;
late CodePushClientWrapper codePushClientWrapper;
late CodePushClient codePushClient;
late http.Client httpClient;
late Logger logger;
late Progress progress;
late ShorebirdValidator shorebirdValidator;
@@ -40,7 +37,7 @@ void main() {
return runScoped(
body,
values: {
authRef.overrideWith(() => auth),
codePushClientWrapperRef.overrideWith(() => codePushClientWrapper),
loggerRef.overrideWith(() => logger),
shorebirdValidatorRef.overrideWith(() => shorebirdValidator),
},
@@ -48,17 +45,16 @@ void main() {
}
setUp(() {
auth = _MockAuth();
codePushClientWrapper = _MockCodePushClientWrapper();
codePushClient = _MockCodePushClient();
httpClient = _MockHttpClient();
logger = _MockLogger();
progress = _MockProgress();
shorebirdValidator = _MockShorebirdValidator();
user = _MockUser();
when(() => auth.client).thenReturn(httpClient);
when(() => auth.isAuthenticated).thenReturn(true);
when(
() => codePushClientWrapper.codePushClient,
).thenReturn(codePushClient);
when(
() => codePushClient.createPaymentLink(),
).thenAnswer((_) async => paymentLink);
@@ -76,13 +72,7 @@ void main() {
when(() => user.hasActiveSubscription).thenReturn(false);
command = runWithOverrides(
() => UpgradeAccountCommand(
buildCodePushClient: ({required httpClient, hostedUri}) {
return codePushClient;
},
),
);
command = runWithOverrides(UpgradeAccountCommand.new);
});
test('has a description', () {
@@ -1,9 +1,8 @@
import 'package:args/args.dart';
import 'package:http/http.dart' as http;
import 'package:mason_logger/mason_logger.dart';
import 'package:mocktail/mocktail.dart';
import 'package:scoped/scoped.dart';
import 'package:shorebird_cli/src/auth/auth.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/commands/commands.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
@@ -12,11 +11,8 @@ import 'package:test/test.dart';
class _MockArgResults extends Mock implements ArgResults {}
class _MockHttpClient extends Mock implements http.Client {}
class _MockAuth extends Mock implements Auth {}
class _MockCodePushClient extends Mock implements CodePushClient {}
class _MockCodePushClientWrapper extends Mock
implements CodePushClientWrapper {}
class _MockLogger extends Mock implements Logger {}
@@ -25,13 +21,11 @@ class _MockShorebirdValidator extends Mock implements ShorebirdValidator {}
void main() {
group(CreateAppCommand, () {
const appId = 'app-id';
const displayName = 'Example App';
const appName = 'Example App';
late ArgResults argResults;
late http.Client httpClient;
late Auth auth;
late Logger logger;
late CodePushClient codePushClient;
late CodePushClientWrapper codePushClientWrapper;
late ShorebirdValidator shorebirdValidator;
late CreateAppCommand command;
@@ -39,7 +33,7 @@ void main() {
return runScoped(
body,
values: {
authRef.overrideWith(() => auth),
codePushClientWrapperRef.overrideWith(() => codePushClientWrapper),
loggerRef.overrideWith(() => logger),
shorebirdValidatorRef.overrideWith(() => shorebirdValidator),
},
@@ -48,30 +42,22 @@ void main() {
setUp(() {
argResults = _MockArgResults();
httpClient = _MockHttpClient();
auth = _MockAuth();
logger = _MockLogger();
codePushClient = _MockCodePushClient();
codePushClientWrapper = _MockCodePushClientWrapper();
shorebirdValidator = _MockShorebirdValidator();
when(() => auth.client).thenReturn(httpClient);
when(() => auth.isAuthenticated).thenReturn(true);
when(() => argResults['app-name']).thenReturn(appName);
when(
() => shorebirdValidator.validatePreconditions(
checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'),
),
).thenAnswer((_) async {});
when(
() => codePushClientWrapper.createApp(appName: appName),
).thenAnswer((_) async => const App(id: appId, displayName: appName));
command = runWithOverrides(
() => CreateAppCommand(
buildCodePushClient: ({
required http.Client httpClient,
Uri? hostedUri,
}) {
return codePushClient;
},
),
)..testArgResults = argResults;
command = runWithOverrides(CreateAppCommand.new)
..testArgResults = argResults;
});
test('has a description', () {
@@ -96,42 +82,28 @@ void main() {
).called(1);
});
test('prompts for app name when not provided', () async {
when(
() => logger.prompt(any(), defaultValue: any(named: 'defaultValue')),
).thenReturn(displayName);
test('calls createApp with no app-name when not provided', () async {
when(() => argResults['app-name']).thenReturn(null);
await runWithOverrides(command.run);
verify(
() => logger.prompt(any(), defaultValue: any(named: 'defaultValue')),
).called(1);
verify(
() => codePushClient.createApp(displayName: displayName),
).called(1);
verify(() => codePushClientWrapper.createApp()).called(1);
});
test('uses provided app name when provided', () async {
when(() => argResults['app-name']).thenReturn(displayName);
await runWithOverrides(command.run);
verifyNever(() => logger.prompt(any()));
verify(
() => codePushClient.createApp(displayName: displayName),
() => codePushClientWrapper.createApp(appName: appName),
).called(1);
});
test('returns success when app is created', () async {
when(() => argResults['app-name']).thenReturn(displayName);
when(
() => codePushClient.createApp(displayName: displayName),
).thenAnswer((_) async => const App(id: appId, displayName: displayName));
final result = await runWithOverrides(command.run);
expect(result, ExitCode.success.code);
});
test('returns software error when app creation fails', () async {
final error = Exception('oops');
when(() => argResults['app-name']).thenReturn(displayName);
when(
() => codePushClient.createApp(displayName: displayName),
() => codePushClientWrapper.createApp(appName: appName),
).thenThrow(error);
final result = await runWithOverrides(command.run);
expect(result, ExitCode.software.code);
@@ -1,20 +1,19 @@
import 'package:args/args.dart';
import 'package:http/http.dart' as http;
import 'package:mason_logger/mason_logger.dart';
import 'package:mocktail/mocktail.dart';
import 'package:scoped/scoped.dart';
import 'package:shorebird_cli/src/auth/auth.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/commands/commands.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
import 'package:test/test.dart';
class _MockArgResults extends Mock implements ArgResults {}
class _MockHttpClient extends Mock implements http.Client {}
class _MockAuth extends Mock implements Auth {}
class _MockCodePushClientWrapper extends Mock
implements CodePushClientWrapper {}
class _MockCodePushClient extends Mock implements CodePushClient {}
@@ -22,15 +21,17 @@ class _MockLogger extends Mock implements Logger {}
class _MockShorebirdValidator extends Mock implements ShorebirdValidator {}
class _MockShorebirdEnv extends Mock implements ShorebirdEnv {}
void main() {
group(DeleteAppCommand, () {
const appId = 'example';
late ArgResults argResults;
late http.Client httpClient;
late Auth auth;
late Logger logger;
late CodePushClientWrapper codePushClientWrapper;
late CodePushClient codePushClient;
late ShorebirdEnv shorebirdEnv;
late ShorebirdValidator shorebirdValidator;
late DeleteAppCommand command;
@@ -38,8 +39,9 @@ void main() {
return runScoped(
body,
values: {
authRef.overrideWith(() => auth),
codePushClientWrapperRef.overrideWith(() => codePushClientWrapper),
loggerRef.overrideWith(() => logger),
shorebirdEnvRef.overrideWith(() => shorebirdEnv),
shorebirdValidatorRef.overrideWith(() => shorebirdValidator),
},
);
@@ -47,30 +49,23 @@ void main() {
setUp(() {
argResults = _MockArgResults();
httpClient = _MockHttpClient();
auth = _MockAuth();
logger = _MockLogger();
codePushClientWrapper = _MockCodePushClientWrapper();
codePushClient = _MockCodePushClient();
shorebirdEnv = _MockShorebirdEnv();
shorebirdValidator = _MockShorebirdValidator();
when(() => auth.isAuthenticated).thenReturn(true);
when(() => auth.client).thenReturn(httpClient);
when(
() => shorebirdValidator.validatePreconditions(
checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'),
),
).thenAnswer((_) async {});
when(
() => codePushClientWrapper.codePushClient,
).thenReturn(codePushClient);
command = runWithOverrides(
() => DeleteAppCommand(
buildCodePushClient: ({
required http.Client httpClient,
Uri? hostedUri,
}) {
return codePushClient;
},
),
)..testArgResults = argResults;
command = runWithOverrides(DeleteAppCommand.new)
..testArgResults = argResults;
});
test('has a description', () {
@@ -1,67 +1,38 @@
import 'dart:io';
import 'package:args/args.dart';
import 'package:http/http.dart' as http;
import 'package:mason_logger/mason_logger.dart';
import 'package:mocktail/mocktail.dart';
import 'package:path/path.dart' as p;
import 'package:scoped/scoped.dart';
import 'package:shorebird_cli/src/auth/auth.dart';
import 'package:shorebird_cli/src/commands/build/build.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/process.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
import 'package:test/test.dart';
class _MockArgResults extends Mock implements ArgResults {}
class _MockAuth extends Mock implements Auth {}
class _MockHttpClient extends Mock implements http.Client {}
class _MockLogger extends Mock implements Logger {}
class _MockProcessResult extends Mock implements ShorebirdProcessResult {}
class _MockProgress extends Mock implements Progress {}
class _MockShorebirdEnv extends Mock implements ShorebirdEnv {}
class _MockShorebirdProcess extends Mock implements ShorebirdProcess {}
class _MockShorebirdValidator extends Mock implements ShorebirdValidator {}
void main() {
group(BuildAarCommand, () {
const appId = 'test-app-id';
const buildNumber = '1.0';
const noModulePubspecYamlContent = '''
name: example
version: 1.0.0
environment:
sdk: ">=2.19.0 <3.0.0"
flutter:
assets:
- shorebird.yaml''';
const pubspecYamlContent = '''
name: example
version: 1.0.0
environment:
sdk: ">=2.19.0 <3.0.0"
flutter:
module:
androidX: true
androidPackage: com.example.my_flutter_module
iosBundleIdentifier: com.example.myFlutterModule
assets:
- shorebird.yaml''';
const androidPackageName = 'com.example.my_flutter_module';
late ArgResults argResults;
late Auth auth;
late http.Client httpClient;
late Logger logger;
late Progress progress;
late ShorebirdEnv shorebirdEnv;
late ShorebirdProcess shorebirdProcess;
late ShorebirdProcessResult processResult;
late ShorebirdValidator shorebirdValidator;
@@ -71,41 +42,25 @@ flutter:
return runScoped(
body,
values: {
authRef.overrideWith(() => auth),
loggerRef.overrideWith(() => logger),
processRef.overrideWith(() => shorebirdProcess),
shorebirdEnvRef.overrideWith(() => shorebirdEnv),
shorebirdValidatorRef.overrideWith(() => shorebirdValidator),
},
);
}
Directory setUpTempDir({bool includeModule = true}) {
final tempDir = Directory.systemTemp.createTempSync();
File(
p.join(tempDir.path, 'pubspec.yaml'),
).writeAsStringSync(
includeModule ? pubspecYamlContent : noModulePubspecYamlContent,
);
File(
p.join(tempDir.path, 'shorebird.yaml'),
).writeAsStringSync('app_id: $appId');
return tempDir;
}
setUp(() {
argResults = _MockArgResults();
auth = _MockAuth();
httpClient = _MockHttpClient();
logger = _MockLogger();
processResult = _MockProcessResult();
progress = _MockProgress();
shorebirdEnv = _MockShorebirdEnv();
shorebirdProcess = _MockShorebirdProcess();
shorebirdValidator = _MockShorebirdValidator();
when(() => argResults['build-number']).thenReturn(buildNumber);
when(() => argResults.rest).thenReturn([]);
when(() => auth.client).thenReturn(httpClient);
when(() => auth.isAuthenticated).thenReturn(true);
when(() => logger.progress(any())).thenReturn(progress);
when(
@@ -125,6 +80,10 @@ flutter:
),
).thenAnswer((_) async {});
when(
() => shorebirdEnv.androidPackageName,
).thenReturn(androidPackageName);
command = runWithOverrides(BuildAarCommand.new)
..testArgResults = argResults;
});
@@ -153,35 +112,17 @@ flutter:
).called(1);
});
test('exits with 78 if no pubspec.yaml exists', () async {
final tempDir = Directory.systemTemp.createTempSync();
final result = await IOOverrides.runZoned(
() async => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
expect(result, ExitCode.config.code);
});
test('exits with 78 if no module entry exists in pubspec.yaml', () async {
final tempDir = setUpTempDir(includeModule: false);
final result = await IOOverrides.runZoned(
() async => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
when(() => shorebirdEnv.androidPackageName).thenReturn(null);
final result = await runWithOverrides(command.run);
expect(result, ExitCode.config.code);
});
test('exits with code 70 when building aar fails', () async {
when(() => processResult.exitCode).thenReturn(1);
when(() => processResult.stderr).thenReturn('oops');
final tempDir = setUpTempDir();
final result = await IOOverrides.runZoned(
() async => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final result = await runWithOverrides(command.run);
expect(result, equals(ExitCode.software.code));
verify(
@@ -204,11 +145,7 @@ flutter:
test('exits with code 0 when building aar succeeds', () async {
when(() => processResult.exitCode).thenReturn(ExitCode.success.code);
final tempDir = setUpTempDir();
final result = await IOOverrides.runZoned(
() async => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final result = await runWithOverrides(command.run);
expect(result, equals(ExitCode.success.code));
@@ -1,12 +1,10 @@
import 'dart:io';
import 'package:args/args.dart';
import 'package:http/http.dart' as http;
import 'package:mason_logger/mason_logger.dart';
import 'package:mocktail/mocktail.dart';
import 'package:path/path.dart' as p;
import 'package:scoped/scoped.dart';
import 'package:shorebird_cli/src/auth/auth.dart';
import 'package:shorebird_cli/src/commands/build/build.dart';
import 'package:shorebird_cli/src/doctor.dart';
import 'package:shorebird_cli/src/logger.dart';
@@ -17,10 +15,6 @@ import 'package:test/test.dart';
class _MockArgResults extends Mock implements ArgResults {}
class _MockHttpClient extends Mock implements http.Client {}
class _MockAuth extends Mock implements Auth {}
class _MockDoctor extends Mock implements Doctor {}
class _MockLogger extends Mock implements Logger {}
@@ -36,11 +30,11 @@ class _MockShorebirdProcess extends Mock implements ShorebirdProcess {}
class _MockShorebirdValidator extends Mock implements ShorebirdValidator {}
class _FakeShorebirdProcess extends Fake implements ShorebirdProcess {}
void main() {
group(BuildApkCommand, () {
late ArgResults argResults;
late http.Client httpClient;
late Auth auth;
late Doctor doctor;
late Logger logger;
late ShorebirdProcessResult processResult;
@@ -53,7 +47,6 @@ void main() {
return runScoped(
body,
values: {
authRef.overrideWith(() => auth),
doctorRef.overrideWith(() => doctor),
loggerRef.overrideWith(() => logger),
processRef.overrideWith(() => shorebirdProcess),
@@ -62,10 +55,12 @@ void main() {
);
}
setUpAll(() {
registerFallbackValue(_FakeShorebirdProcess());
});
setUp(() {
argResults = _MockArgResults();
httpClient = _MockHttpClient();
auth = _MockAuth();
doctor = _MockDoctor();
logger = _MockLogger();
shorebirdProcess = _MockShorebirdProcess();
@@ -73,8 +68,6 @@ void main() {
flutterValidator = _MockShorebirdFlutterValidator();
shorebirdValidator = _MockShorebirdValidator();
registerFallbackValue(shorebirdProcess);
when(
() => shorebirdProcess.run(
any(),
@@ -83,8 +76,6 @@ void main() {
),
).thenAnswer((_) async => processResult);
when(() => argResults.rest).thenReturn([]);
when(() => auth.isAuthenticated).thenReturn(true);
when(() => auth.client).thenReturn(httpClient);
when(() => logger.progress(any())).thenReturn(_MockProgress());
when(() => logger.info(any())).thenReturn(null);
when(
@@ -1,12 +1,10 @@
import 'dart:io';
import 'package:args/args.dart';
import 'package:http/http.dart' as http;
import 'package:mason_logger/mason_logger.dart';
import 'package:mocktail/mocktail.dart';
import 'package:path/path.dart' as p;
import 'package:scoped/scoped.dart';
import 'package:shorebird_cli/src/auth/auth.dart';
import 'package:shorebird_cli/src/commands/build/build.dart';
import 'package:shorebird_cli/src/doctor.dart';
import 'package:shorebird_cli/src/logger.dart';
@@ -17,10 +15,6 @@ import 'package:test/test.dart';
class _MockArgResults extends Mock implements ArgResults {}
class _MockHttpClient extends Mock implements http.Client {}
class _MockAuth extends Mock implements Auth {}
class _MockDoctor extends Mock implements Doctor {}
class _MockLogger extends Mock implements Logger {}
@@ -41,8 +35,6 @@ class _FakeShorebirdProcess extends Fake implements ShorebirdProcess {}
void main() {
group(BuildAppBundleCommand, () {
late ArgResults argResults;
late http.Client httpClient;
late Auth auth;
late Doctor doctor;
late Logger logger;
late ShorebirdProcessResult processResult;
@@ -55,7 +47,6 @@ void main() {
return runScoped(
body,
values: {
authRef.overrideWith(() => auth),
doctorRef.overrideWith(() => doctor),
engineConfigRef.overrideWith(() => const EngineConfig.empty()),
loggerRef.overrideWith(() => logger),
@@ -72,8 +63,6 @@ void main() {
setUp(() {
argResults = _MockArgResults();
doctor = _MockDoctor();
httpClient = _MockHttpClient();
auth = _MockAuth();
logger = _MockLogger();
processResult = _MockProcessResult();
flutterValidator = _MockShorebirdFlutterValidator();
@@ -88,8 +77,6 @@ void main() {
),
).thenAnswer((_) async => processResult);
when(() => argResults.rest).thenReturn([]);
when(() => auth.isAuthenticated).thenReturn(true);
when(() => auth.client).thenReturn(httpClient);
when(() => logger.progress(any())).thenReturn(_MockProgress());
when(() => logger.info(any())).thenReturn(null);
when(
@@ -1,13 +1,11 @@
import 'dart:io';
import 'package:args/args.dart';
import 'package:http/http.dart' as http;
import 'package:mason_logger/mason_logger.dart';
import 'package:mocktail/mocktail.dart';
import 'package:path/path.dart' as p;
import 'package:propertylistserialization/propertylistserialization.dart';
import 'package:scoped/scoped.dart';
import 'package:shorebird_cli/src/auth/auth.dart';
import 'package:shorebird_cli/src/commands/build/build.dart';
import 'package:shorebird_cli/src/doctor.dart';
import 'package:shorebird_cli/src/logger.dart';
@@ -18,10 +16,6 @@ import 'package:test/test.dart';
class _MockArgResults extends Mock implements ArgResults {}
class _MockHttpClient extends Mock implements http.Client {}
class _MockAuth extends Mock implements Auth {}
class _MockDoctor extends Mock implements Doctor {}
class _MockLogger extends Mock implements Logger {}
@@ -42,8 +36,6 @@ class _FakeShorebirdProcess extends Fake implements ShorebirdProcess {}
void main() {
group(BuildIpaCommand, () {
late ArgResults argResults;
late http.Client httpClient;
late Auth auth;
late Doctor doctor;
late Logger logger;
late ShorebirdProcessResult processResult;
@@ -56,7 +48,6 @@ void main() {
return runScoped(
body,
values: {
authRef.overrideWith(() => auth),
doctorRef.overrideWith(() => doctor),
loggerRef.overrideWith(() => logger),
processRef.overrideWith(() => shorebirdProcess),
@@ -71,8 +62,6 @@ void main() {
setUp(() {
argResults = _MockArgResults();
httpClient = _MockHttpClient();
auth = _MockAuth();
doctor = _MockDoctor();
logger = _MockLogger();
shorebirdProcess = _MockShorebirdProcess();
@@ -89,8 +78,6 @@ void main() {
).thenAnswer((_) async => processResult);
when(() => argResults['codesign']).thenReturn(true);
when(() => argResults.rest).thenReturn([]);
when(() => auth.isAuthenticated).thenReturn(true);
when(() => auth.client).thenReturn(httpClient);
when(() => logger.progress(any())).thenReturn(_MockProgress());
when(() => logger.info(any())).thenReturn(null);
when(() => doctor.iosCommandValidators).thenReturn([flutterValidator]);
@@ -2,13 +2,13 @@ import 'dart:io';
import 'package:mason_logger/mason_logger.dart';
import 'package:mocktail/mocktail.dart';
import 'package:path/path.dart' as p;
import 'package:platform/platform.dart';
import 'package:scoped/scoped.dart';
import 'package:shorebird_cli/src/cache.dart';
import 'package:shorebird_cli/src/commands/commands.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/platform.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:test/test.dart';
class _MockCache extends Mock implements Cache {}
@@ -19,12 +19,15 @@ class _MockPlatform extends Mock implements Platform {}
class _MockProgress extends Mock implements Progress {}
class _MockShorebirdEnv extends Mock implements ShorebirdEnv {}
void main() {
group('cache clean', () {
late Cache cache;
late Logger logger;
late Platform platform;
late Progress progress;
late ShorebirdEnv shorebirdEnv;
late CleanCacheCommand command;
R runWithOverrides<R>(R Function() body) {
@@ -34,6 +37,7 @@ void main() {
cacheRef.overrideWith(() => cache),
loggerRef.overrideWith(() => logger),
platformRef.overrideWith(() => platform),
shorebirdEnvRef.overrideWith(() => shorebirdEnv),
},
);
}
@@ -43,18 +47,12 @@ void main() {
logger = _MockLogger();
platform = _MockPlatform();
progress = _MockProgress();
shorebirdEnv = _MockShorebirdEnv();
command = runWithOverrides(CleanCacheCommand.new);
when(() => logger.progress(any())).thenReturn(progress);
when(() => platform.script).thenReturn(
Uri.file(
p.join(
Directory.systemTemp.createTempSync().path,
'bin',
'cache',
'shorebird.snapshot',
),
),
when(() => shorebirdEnv.shorebirdRoot).thenReturn(
Directory.systemTemp.createTempSync(),
);
});
@@ -1,20 +1,20 @@
import 'package:args/args.dart';
import 'package:http/http.dart' as http;
import 'package:mason_logger/mason_logger.dart';
import 'package:mocktail/mocktail.dart';
import 'package:scoped/scoped.dart';
import 'package:shorebird_cli/src/auth/auth.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/commands/commands.dart';
import 'package:shorebird_cli/src/config/config.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
import 'package:test/test.dart';
class _MockArgResults extends Mock implements ArgResults {}
class _MockHttpClient extends Mock implements http.Client {}
class _MockAuth extends Mock implements Auth {}
class _MockCodePushClientWrapper extends Mock
implements CodePushClientWrapper {}
class _MockCodePushClient extends Mock implements CodePushClient {}
@@ -22,19 +22,22 @@ class _MockLogger extends Mock implements Logger {}
class _MockProgress extends Mock implements Progress {}
class _MockShorebirdEnv extends Mock implements ShorebirdEnv {}
class _MockShorebirdValidator extends Mock implements ShorebirdValidator {}
void main() {
group(AddCollaboratorsCommand, () {
const appId = 'test-app-id';
const email = 'jane.doe@shorebird.dev';
const shorebirdYaml = ShorebirdYaml(appId: appId);
late ArgResults argResults;
late http.Client httpClient;
late Auth auth;
late CodePushClientWrapper codePushClientWrapper;
late CodePushClient codePushClient;
late Logger logger;
late Progress progress;
late ShorebirdEnv shorebirdEnv;
late ShorebirdValidator shorebirdValidator;
late AddCollaboratorsCommand command;
@@ -42,8 +45,9 @@ void main() {
return runScoped(
body,
values: {
authRef.overrideWith(() => auth),
codePushClientWrapperRef.overrideWith(() => codePushClientWrapper),
loggerRef.overrideWith(() => logger),
shorebirdEnvRef.overrideWith(() => shorebirdEnv),
shorebirdValidatorRef.overrideWith(() => shorebirdValidator),
},
);
@@ -51,35 +55,29 @@ void main() {
setUp(() {
argResults = _MockArgResults();
httpClient = _MockHttpClient();
auth = _MockAuth();
codePushClientWrapper = _MockCodePushClientWrapper();
codePushClient = _MockCodePushClient();
logger = _MockLogger();
progress = _MockProgress();
shorebirdEnv = _MockShorebirdEnv();
shorebirdValidator = _MockShorebirdValidator();
when(() => argResults['app-id']).thenReturn(appId);
when(() => argResults['email']).thenReturn(email);
when(() => auth.client).thenReturn(httpClient);
when(() => auth.isAuthenticated).thenReturn(true);
when(
() => codePushClientWrapper.codePushClient,
).thenReturn(codePushClient);
when(() => logger.confirm(any())).thenReturn(true);
when(() => logger.progress(any())).thenReturn(progress);
when(() => shorebirdEnv.getShorebirdYaml()).thenReturn(shorebirdYaml);
when(
() => shorebirdValidator.validatePreconditions(
checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'),
),
).thenAnswer((_) async {});
command = runWithOverrides(
() => AddCollaboratorsCommand(
buildCodePushClient: ({
required http.Client httpClient,
Uri? hostedUri,
}) {
return codePushClient;
},
),
)..testArgResults = argResults;
command = runWithOverrides(AddCollaboratorsCommand.new)
..testArgResults = argResults;
});
test('name is correct', () {
@@ -113,6 +111,7 @@ void main() {
test('returns ExitCode.usage when app id is missing.', () async {
when(() => argResults['app-id']).thenReturn(null);
when(() => shorebirdEnv.getShorebirdYaml()).thenReturn(null);
expect(await runWithOverrides(command.run), ExitCode.usage.code);
});
@@ -1,23 +1,23 @@
import 'dart:io';
import 'package:args/args.dart';
import 'package:http/http.dart' as http;
import 'package:mason_logger/mason_logger.dart';
import 'package:mocktail/mocktail.dart';
import 'package:path/path.dart' as p;
import 'package:scoped/scoped.dart';
import 'package:shorebird_cli/src/auth/auth.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/commands/commands.dart';
import 'package:shorebird_cli/src/config/config.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
import 'package:test/test.dart';
class _MockArgResults extends Mock implements ArgResults {}
class _MockHttpClient extends Mock implements http.Client {}
class _MockAuth extends Mock implements Auth {}
class _MockCodePushClientWrapper extends Mock
implements CodePushClientWrapper {}
class _MockCodePushClient extends Mock implements CodePushClient {}
@@ -25,12 +25,15 @@ class _MockLogger extends Mock implements Logger {}
class _MockProgress extends Mock implements Progress {}
class _MockShorebirdEnv extends Mock implements ShorebirdEnv {}
class _MockShorebirdValidator extends Mock implements ShorebirdValidator {}
void main() {
group(DeleteCollaboratorsCommand, () {
const appId = 'test-app-id';
const email = 'jane.doe@shorebird.dev';
const shorebirdYaml = ShorebirdYaml(appId: appId);
const collaborator = Collaborator(
userId: 0,
email: email,
@@ -38,11 +41,11 @@ void main() {
);
late ArgResults argResults;
late http.Client httpClient;
late Auth auth;
late CodePushClientWrapper codePushClientWrapper;
late CodePushClient codePushClient;
late Logger logger;
late Progress progress;
late ShorebirdEnv shorebirdEnv;
late ShorebirdValidator shorebirdValidator;
late DeleteCollaboratorsCommand command;
@@ -50,8 +53,9 @@ void main() {
return runScoped(
body,
values: {
authRef.overrideWith(() => auth),
codePushClientWrapperRef.overrideWith(() => codePushClientWrapper),
loggerRef.overrideWith(() => logger),
shorebirdEnvRef.overrideWith(() => shorebirdEnv),
shorebirdValidatorRef.overrideWith(() => shorebirdValidator),
},
);
@@ -59,19 +63,20 @@ void main() {
setUp(() {
argResults = _MockArgResults();
httpClient = _MockHttpClient();
auth = _MockAuth();
codePushClientWrapper = _MockCodePushClientWrapper();
codePushClient = _MockCodePushClient();
logger = _MockLogger();
progress = _MockProgress();
shorebirdEnv = _MockShorebirdEnv();
shorebirdValidator = _MockShorebirdValidator();
when(() => argResults['app-id']).thenReturn(appId);
when(() => argResults['email']).thenReturn(email);
when(() => auth.isAuthenticated).thenReturn(true);
when(() => auth.client).thenReturn(httpClient);
when(() => logger.confirm(any())).thenReturn(true);
when(() => logger.progress(any())).thenReturn(progress);
when(
() => codePushClientWrapper.codePushClient,
).thenReturn(codePushClient);
when(
() => codePushClient.getCollaborators(appId: any(named: 'appId')),
).thenAnswer((_) async => [collaborator]);
@@ -81,22 +86,15 @@ void main() {
userId: any(named: 'userId'),
),
).thenAnswer((_) async {});
when(() => shorebirdEnv.getShorebirdYaml()).thenReturn(shorebirdYaml);
when(
() => shorebirdValidator.validatePreconditions(
checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'),
),
).thenAnswer((_) async {});
command = runWithOverrides(
() => DeleteCollaboratorsCommand(
buildCodePushClient: ({
required http.Client httpClient,
Uri? hostedUri,
}) {
return codePushClient;
},
),
)..testArgResults = argResults;
command = runWithOverrides(DeleteCollaboratorsCommand.new)
..testArgResults = argResults;
});
test('description is correct', () {
@@ -126,6 +124,7 @@ void main() {
test('returns ExitCode.usage when app id is missing.', () async {
when(() => argResults['app-id']).thenReturn(null);
when(() => shorebirdEnv.getShorebirdYaml()).thenReturn(null);
expect(await runWithOverrides(command.run), ExitCode.usage.code);
});
@@ -1,36 +1,39 @@
import 'package:args/args.dart';
import 'package:http/http.dart' as http;
import 'package:mason_logger/mason_logger.dart';
import 'package:mocktail/mocktail.dart';
import 'package:scoped/scoped.dart';
import 'package:shorebird_cli/src/auth/auth.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/commands/commands.dart';
import 'package:shorebird_cli/src/config/config.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
import 'package:test/test.dart';
class _MockArgResults extends Mock implements ArgResults {}
class _MockHttpClient extends Mock implements http.Client {}
class _MockAuth extends Mock implements Auth {}
class _MockCodePushClientWrapper extends Mock
implements CodePushClientWrapper {}
class _MockCodePushClient extends Mock implements CodePushClient {}
class _MockLogger extends Mock implements Logger {}
class _MockShorebirdEnv extends Mock implements ShorebirdEnv {}
class _MockShorebirdValidator extends Mock implements ShorebirdValidator {}
void main() {
group(ListCollaboratorsCommand, () {
const appId = 'test-app-id';
const shorebirdYaml = ShorebirdYaml(appId: appId);
late ArgResults argResults;
late http.Client httpClient;
late Auth auth;
late CodePushClientWrapper codePushClientWrapper;
late CodePushClient codePushClient;
late Logger logger;
late ShorebirdEnv shorebirdEnv;
late ShorebirdValidator shorebirdValidator;
late ListCollaboratorsCommand command;
@@ -38,8 +41,9 @@ void main() {
return runScoped(
body,
values: {
authRef.overrideWith(() => auth),
codePushClientWrapperRef.overrideWith(() => codePushClientWrapper),
loggerRef.overrideWith(() => logger),
shorebirdEnvRef.overrideWith(() => shorebirdEnv),
shorebirdValidatorRef.overrideWith(() => shorebirdValidator),
},
);
@@ -47,31 +51,25 @@ void main() {
setUp(() {
argResults = _MockArgResults();
httpClient = _MockHttpClient();
auth = _MockAuth();
codePushClientWrapper = _MockCodePushClientWrapper();
codePushClient = _MockCodePushClient();
logger = _MockLogger();
shorebirdEnv = _MockShorebirdEnv();
shorebirdValidator = _MockShorebirdValidator();
when(() => argResults['app-id']).thenReturn(appId);
when(() => auth.client).thenReturn(httpClient);
when(() => auth.isAuthenticated).thenReturn(true);
when(
() => codePushClientWrapper.codePushClient,
).thenReturn(codePushClient);
when(() => shorebirdEnv.getShorebirdYaml()).thenReturn(shorebirdYaml);
when(
() => shorebirdValidator.validatePreconditions(
checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'),
),
).thenAnswer((_) async {});
command = runWithOverrides(
() => ListCollaboratorsCommand(
buildCodePushClient: ({
required http.Client httpClient,
Uri? hostedUri,
}) {
return codePushClient;
},
),
)..testArgResults = argResults;
command = runWithOverrides(ListCollaboratorsCommand.new)
..testArgResults = argResults;
});
test('name is correct', () {
@@ -109,6 +107,7 @@ void main() {
test('returns ExitCode.usage when app id is missing.', () async {
when(() => argResults['app-id']).thenReturn(null);
when(() => shorebirdEnv.getShorebirdYaml()).thenReturn(null);
expect(await runWithOverrides(command.run), ExitCode.usage.code);
});
@@ -5,7 +5,7 @@ import 'package:scoped/scoped.dart';
import 'package:shorebird_cli/src/commands/commands.dart';
import 'package:shorebird_cli/src/doctor.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/shorebird_environment.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/validators/validators.dart';
import 'package:shorebird_cli/src/version.dart';
import 'package:test/test.dart';
@@ -16,14 +16,19 @@ class _MockDoctor extends Mock implements Doctor {}
class _MockLogger extends Mock implements Logger {}
class _MockShorebirdEnv extends Mock implements ShorebirdEnv {}
class _MockValidator extends Mock implements Validator {}
void main() {
group('doctor', () {
const shorebirdEngineRevision = 'test-revision';
late ArgResults argResults;
late Doctor doctor;
late DoctorCommand command;
late Logger logger;
late ShorebirdEnv shorebirdEnv;
late Validator validator;
R runWithOverrides<R>(R Function() body) {
@@ -32,6 +37,7 @@ void main() {
values: {
doctorRef.overrideWith(() => doctor),
loggerRef.overrideWith(() => logger),
shorebirdEnvRef.overrideWith(() => shorebirdEnv),
},
);
}
@@ -40,10 +46,12 @@ void main() {
argResults = _MockArgResults();
doctor = _MockDoctor();
logger = _MockLogger();
shorebirdEnv = _MockShorebirdEnv();
validator = _MockValidator();
ShorebirdEnvironment.shorebirdEngineRevision = 'test-revision';
when(
() => shorebirdEnv.shorebirdEngineRevision,
).thenReturn(shorebirdEngineRevision);
when(() => doctor.allValidators).thenReturn([validator]);
when(
() => doctor.runValidators(any(), applyFixes: any(named: 'applyFixes')),
@@ -60,7 +68,7 @@ void main() {
() => logger.info('''
Shorebird v$packageVersion
Shorebird Engine revision ${ShorebirdEnvironment.shorebirdEngineRevision}
Shorebird Engine revision $shorebirdEngineRevision
'''),
).called(1);
});
@@ -79,8 +87,9 @@ Shorebird Engine • revision ${ShorebirdEnvironment.shorebirdEngineRevision}
final result = await runWithOverrides(command.run);
expect(result, equals(ExitCode.success.code));
verify(() => doctor.runValidators([validator], applyFixes: true))
.called(1);
verify(
() => doctor.runValidators([validator], applyFixes: true),
).called(1);
});
});
}
@@ -1,19 +1,19 @@
import 'dart:io' hide Platform;
import 'package:args/args.dart';
import 'package:http/http.dart' as http;
import 'package:mason_logger/mason_logger.dart';
import 'package:mocktail/mocktail.dart';
import 'package:path/path.dart' as p;
import 'package:platform/platform.dart';
import 'package:scoped/scoped.dart';
import 'package:shorebird_cli/src/auth/auth.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/commands/init_command.dart';
import 'package:shorebird_cli/src/doctor.dart';
import 'package:shorebird_cli/src/gradlew.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/platform.dart';
import 'package:shorebird_cli/src/process.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
import 'package:shorebird_cli/src/xcodebuild.dart';
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
@@ -21,22 +21,23 @@ import 'package:test/test.dart';
class _MockArgResults extends Mock implements ArgResults {}
class _MockHttpClient extends Mock implements http.Client {}
class _MockAuth extends Mock implements Auth {}
class _MockCodePushClient extends Mock implements CodePushClient {}
class _MockCodePushClientWrapper extends Mock
implements CodePushClientWrapper {}
class _MockDoctor extends Mock implements Doctor {}
class _MockGradlew extends Mock implements Gradlew {}
class _MockFile extends Mock implements File {}
class _MockLogger extends Mock implements Logger {}
class _MockPlatform extends Mock implements Platform {}
class _MockProgress extends Mock implements Progress {}
class _MockShorebirdEnv extends Mock implements ShorebirdEnv {}
class _MockShorebirdValidator extends Mock implements ShorebirdValidator {}
class _MockXcodeBuild extends Mock implements XcodeBuild {}
@@ -47,22 +48,22 @@ void main() {
const appId = 'test_app_id';
const appName = 'test_app_name';
const app = App(id: appId, displayName: appName);
const appMetadata = AppMetadata(appId: appId, displayName: appName);
const pubspecYamlContent = '''
name: $appName
version: $version
environment:
sdk: ">=2.19.0 <3.0.0"''';
late http.Client httpClient;
late ArgResults argResults;
late Auth auth;
late Doctor doctor;
late Gradlew gradlew;
late CodePushClient codePushClient;
late CodePushClientWrapper codePushClientWrapper;
late File shorebirdYamlFile;
late File pubspecYamlFile;
late Logger logger;
late Platform platform;
late Progress progress;
late ShorebirdEnv shorebirdEnv;
late ShorebirdValidator shorebirdValidator;
late XcodeBuild xcodeBuild;
late InitCommand command;
@@ -71,56 +72,59 @@ environment:
return runScoped(
body,
values: {
authRef.overrideWith(() => auth),
codePushClientWrapperRef.overrideWith(() => codePushClientWrapper),
doctorRef.overrideWith(() => doctor),
gradlewRef.overrideWith(() => gradlew),
loggerRef.overrideWith(() => logger),
platformRef.overrideWith(() => platform),
processRef.overrideWith(() => process),
shorebirdEnvRef.overrideWith(() => shorebirdEnv),
shorebirdValidatorRef.overrideWith(() => shorebirdValidator),
xcodeBuildRef.overrideWith(() => xcodeBuild),
},
);
}
Directory setUpAppTempDir() {
final tempDir = Directory.systemTemp.createTempSync();
Directory(p.join(tempDir.path, 'android')).createSync(recursive: true);
Directory(p.join(tempDir.path, 'ios')).createSync(recursive: true);
return tempDir;
}
setUp(() {
httpClient = _MockHttpClient();
argResults = _MockArgResults();
auth = _MockAuth();
doctor = _MockDoctor();
gradlew = _MockGradlew();
codePushClient = _MockCodePushClient();
codePushClientWrapper = _MockCodePushClientWrapper();
shorebirdYamlFile = _MockFile();
pubspecYamlFile = _MockFile();
logger = _MockLogger();
platform = _MockPlatform();
progress = _MockProgress();
shorebirdEnv = _MockShorebirdEnv();
shorebirdValidator = _MockShorebirdValidator();
xcodeBuild = _MockXcodeBuild();
when(() => auth.isAuthenticated).thenReturn(true);
when(() => auth.client).thenReturn(httpClient);
when(
() => codePushClient.createApp(displayName: any(named: 'displayName')),
() => codePushClientWrapper.createApp(appName: any(named: 'appName')),
).thenAnswer((_) async => app);
when(
() => codePushClient.getApps(),
).thenAnswer((_) async => [appMetadata]);
when(
() => doctor.runValidators(any(), applyFixes: any(named: 'applyFixes')),
).thenAnswer((_) async => {});
when(() => doctor.allValidators).thenReturn([]);
when(
() => shorebirdEnv.getShorebirdYamlFile(),
).thenReturn(shorebirdYamlFile);
when(() => shorebirdEnv.getPubspecYamlFile()).thenReturn(pubspecYamlFile);
when(
() => pubspecYamlFile.readAsStringSync(),
).thenReturn(pubspecYamlContent);
when(
() => pubspecYamlFile.uri,
).thenReturn(File(p.join('pubspec.yaml')).uri);
when(
() => logger.prompt(any(), defaultValue: any(named: 'defaultValue')),
).thenReturn(appName);
when(() => logger.progress(any())).thenReturn(progress);
when(() => gradlew.productFlavors(any())).thenAnswer((_) async => {});
when(() => platform.isMacOS).thenReturn(true);
when(() => shorebirdEnv.hasPubspecYaml).thenReturn(true);
when(() => shorebirdEnv.hasShorebirdYaml).thenReturn(false);
when(() => shorebirdEnv.pubspecContainsShorebirdYaml).thenReturn(false);
when(
() => shorebirdValidator.validatePreconditions(
checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'),
@@ -130,16 +134,7 @@ environment:
() => xcodeBuild.list(any()),
).thenAnswer((_) async => const XcodeProjectBuildInfo());
command = runWithOverrides(
() => InitCommand(
buildCodePushClient: ({
required http.Client httpClient,
Uri? hostedUri,
}) {
return codePushClient;
},
),
)..testArgResults = argResults;
command = runWithOverrides(InitCommand.new)..testArgResults = argResults;
});
test('exits when validation fails', () async {
@@ -161,11 +156,8 @@ environment:
});
test('throws no input error when pubspec.yaml is not found.', () async {
final tempDir = Directory.systemTemp.createTempSync();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
when(() => shorebirdEnv.hasPubspecYaml).thenReturn(false);
final exitCode = await runWithOverrides(command.run);
verify(
() => logger.err(
'''
@@ -178,30 +170,18 @@ Please make sure you are running "shorebird init" from the root of your Flutter
});
test('throws software error when pubspec.yaml is malformed.', () async {
final tempDir = Directory.systemTemp.createTempSync();
File(p.join(tempDir.path, 'pubspec.yaml')).createSync();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exception = Exception('oops');
when(() => shorebirdEnv.hasPubspecYaml).thenThrow(exception);
final exitCode = await runWithOverrides(command.run);
verify(
() => logger.err(
any(that: contains('Error parsing "pubspec.yaml":')),
),
() => logger.err('Error parsing "pubspec.yaml": $exception'),
).called(1);
expect(exitCode, ExitCode.software.code);
});
test('throws software error when shorebird.yaml already exists', () async {
final tempDir = Directory.systemTemp.createTempSync();
File(
p.join(tempDir.path, 'pubspec.yaml'),
).writeAsStringSync(pubspecYamlContent);
File(p.join(tempDir.path, 'shorebird.yaml')).createSync();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
when(() => shorebirdEnv.hasShorebirdYaml).thenReturn(true);
final exitCode = await runWithOverrides(command.run);
verify(
() => logger.err(
'''
@@ -213,16 +193,9 @@ If you want to reinitialize Shorebird, please run "shorebird init --force".''',
});
test('--force overwrites existing shorebird.yaml', () async {
when(() => shorebirdEnv.hasShorebirdYaml).thenReturn(true);
when(() => argResults['force']).thenReturn(true);
final tempDir = Directory.systemTemp.createTempSync();
File(
p.join(tempDir.path, 'pubspec.yaml'),
).writeAsStringSync(pubspecYamlContent);
File(p.join(tempDir.path, 'shorebird.yaml')).createSync();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
verifyNever(
() => logger.err(
'''
@@ -231,23 +204,17 @@ If you want to reinitialize Shorebird, please run "shorebird init --force".''',
),
);
expect(exitCode, ExitCode.success.code);
expect(
File(p.join(tempDir.path, 'shorebird.yaml')).readAsStringSync(),
contains('app_id: $appId'),
);
verify(
() => shorebirdYamlFile.writeAsStringSync(
any(that: contains('app_id: $appId')),
),
).called(1);
});
test('fails when an error occurs while extracting flavors', () async {
final exception = Exception('oops');
when(() => gradlew.productFlavors(any())).thenThrow(exception);
final tempDir = setUpAppTempDir();
File(
p.join(tempDir.path, 'pubspec.yaml'),
).writeAsStringSync(pubspecYamlContent);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
verify(() => logger.progress('Detecting product flavors')).called(1);
verify(
() => logger.err(
@@ -260,17 +227,10 @@ If you want to reinitialize Shorebird, please run "shorebird init --force".''',
test('throws software error when error occurs creating app.', () async {
final error = Exception('oops');
final tempDir = Directory.systemTemp.createTempSync();
File(
p.join(tempDir.path, 'pubspec.yaml'),
).writeAsStringSync(pubspecYamlContent);
when(
() => codePushClient.createApp(displayName: any(named: 'displayName')),
() => codePushClientWrapper.createApp(appName: any(named: 'appName')),
).thenThrow(error);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
verify(
() => logger.prompt(any(), defaultValue: any(named: 'defaultValue')),
).called(1);
@@ -285,9 +245,6 @@ If you want to reinitialize Shorebird, please run "shorebird init --force".''',
test('throws software error when unable to detect schemes', () async {
final tempDir = Directory.systemTemp.createTempSync();
File(
p.join(tempDir.path, 'pubspec.yaml'),
).writeAsStringSync(pubspecYamlContent);
Directory(p.join(tempDir.path, 'ios')).createSync(recursive: true);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
@@ -304,18 +261,16 @@ If you want to reinitialize Shorebird, please run "shorebird init --force".''',
test('creates shorebird for an android-only app', () async {
final tempDir = Directory.systemTemp.createTempSync();
File(
p.join(tempDir.path, 'pubspec.yaml'),
).writeAsStringSync(pubspecYamlContent);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
expect(
File(p.join(tempDir.path, 'shorebird.yaml')).readAsStringSync(),
contains('app_id: $appId'),
);
expect(exitCode, equals(ExitCode.success.code));
verify(
() => shorebirdYamlFile.writeAsStringSync(
any(that: contains('app_id: $appId')),
),
).called(1);
verifyNever(() => xcodeBuild.list(any()));
});
@@ -324,9 +279,6 @@ If you want to reinitialize Shorebird, please run "shorebird init --force".''',
when(
() => gradlew.productFlavors(any()),
).thenThrow(MissingAndroidProjectException(tempDir.path));
File(
p.join(tempDir.path, 'pubspec.yaml'),
).writeAsStringSync(pubspecYamlContent);
File(
p.join(
tempDir.path,
@@ -341,11 +293,12 @@ If you want to reinitialize Shorebird, please run "shorebird init --force".''',
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
expect(
File(p.join(tempDir.path, 'shorebird.yaml')).readAsStringSync(),
contains('app_id: $appId'),
);
expect(exitCode, equals(ExitCode.success.code));
verify(
() => shorebirdYamlFile.writeAsStringSync(
any(that: contains('app_id: $appId')),
),
).called(1);
verifyNever(() => xcodeBuild.list(any()));
});
@@ -353,19 +306,15 @@ If you want to reinitialize Shorebird, please run "shorebird init --force".''',
const appIds = ['test-appId-1', 'test-appId-2'];
var index = 0;
when(
() =>
codePushClient.createApp(displayName: any(named: 'displayName')),
() => codePushClientWrapper.createApp(appName: any(named: 'appName')),
).thenAnswer((invocation) async {
final displayName = invocation.namedArguments[#displayName] as String;
return App(id: appIds[index++], displayName: displayName);
final appName = invocation.namedArguments[#appName] as String?;
return App(id: appIds[index++], displayName: appName ?? '-');
});
final tempDir = Directory.systemTemp.createTempSync();
when(
() => gradlew.productFlavors(any()),
).thenThrow(MissingAndroidProjectException(tempDir.path));
File(
p.join(tempDir.path, 'pubspec.yaml'),
).writeAsStringSync(pubspecYamlContent);
final schemesPath = p.join(
tempDir.path,
'ios',
@@ -373,46 +322,45 @@ If you want to reinitialize Shorebird, please run "shorebird init --force".''',
'xcshareddata',
'xcschemes',
);
File(p.join(schemesPath, 'Runner.xcscheme'))
.createSync(recursive: true);
File(p.join(schemesPath, 'internal.xcscheme'))
.createSync(recursive: true);
File(p.join(schemesPath, 'stable.xcscheme'))
.createSync(recursive: true);
File(
p.join(schemesPath, 'Runner.xcscheme'),
).createSync(recursive: true);
File(
p.join(schemesPath, 'internal.xcscheme'),
).createSync(recursive: true);
File(
p.join(schemesPath, 'stable.xcscheme'),
).createSync(recursive: true);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
expect(
File(p.join(tempDir.path, 'shorebird.yaml')).readAsStringSync(),
contains('''
expect(exitCode, equals(ExitCode.success.code));
verify(
() => shorebirdYamlFile.writeAsStringSync(
any(
that: contains('''
app_id: ${appIds[0]}
flavors:
internal: ${appIds[0]}
stable: ${appIds[1]}'''),
);
),
),
).called(1);
verifyInOrder([
() => codePushClient.createApp(displayName: '$appName (internal)'),
() => codePushClient.createApp(displayName: '$appName (stable)'),
() => codePushClientWrapper.createApp(appName: '$appName (internal)'),
() => codePushClientWrapper.createApp(appName: '$appName (stable)'),
]);
expect(exitCode, equals(ExitCode.success.code));
verifyNever(() => xcodeBuild.list(any()));
});
});
test('creates shorebird.yaml for an app without flavors', () async {
final tempDir = Directory.systemTemp.createTempSync();
File(
p.join(tempDir.path, 'pubspec.yaml'),
).writeAsStringSync(pubspecYamlContent);
await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
expect(
File(p.join(tempDir.path, 'shorebird.yaml')).readAsStringSync(),
contains('app_id: $appId'),
await runWithOverrides(command.run);
verify(
() => shorebirdYamlFile.writeAsStringSync(
any(that: contains('app_id: $appId')),
),
);
});
@@ -438,26 +386,19 @@ flavors:
},
);
when(
() =>
codePushClient.createApp(displayName: any(named: 'displayName')),
() => codePushClientWrapper.createApp(appName: any(named: 'appName')),
).thenAnswer((invocation) async {
final displayName = invocation.namedArguments[#displayName] as String;
return App(id: appIds[index++], displayName: displayName);
final appName = invocation.namedArguments[#appName] as String?;
return App(id: appIds[index++], displayName: appName ?? '--');
});
final tempDir = setUpAppTempDir();
when(
() => xcodeBuild.list(any()),
).thenThrow(MissingIOSProjectException(tempDir.path));
File(
p.join(tempDir.path, 'pubspec.yaml'),
).writeAsStringSync(pubspecYamlContent);
await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
expect(
File(p.join(tempDir.path, 'shorebird.yaml')).readAsStringSync(),
contains('''
).thenThrow(const MissingIOSProjectException(''));
await runWithOverrides(command.run);
verify(
() => shorebirdYamlFile.writeAsStringSync(
any(
that: contains('''
app_id: ${appIds[0]}
flavors:
development: ${appIds[0]}
@@ -466,20 +407,27 @@ flavors:
productionInternal: ${appIds[3]}
staging: ${appIds[4]}
stagingInternal: ${appIds[5]}'''),
),
),
);
verifyInOrder([
() => codePushClient.createApp(displayName: '$appName (development)'),
() => codePushClient.createApp(
displayName: '$appName (developmentInternal)',
() => codePushClientWrapper.createApp(
appName: '$appName (development)',
),
() => codePushClient.createApp(displayName: '$appName (production)'),
() => codePushClient.createApp(
displayName: '$appName (productionInternal)',
() => codePushClientWrapper.createApp(
appName: '$appName (developmentInternal)',
),
() => codePushClient.createApp(displayName: '$appName (staging)'),
() => codePushClient.createApp(
displayName: '$appName (stagingInternal)',
() => codePushClientWrapper.createApp(
appName: '$appName (production)',
),
() => codePushClientWrapper.createApp(
appName: '$appName (productionInternal)',
),
() => codePushClientWrapper.createApp(
appName: '$appName (staging)',
),
() => codePushClientWrapper.createApp(
appName: '$appName (stagingInternal)',
),
]);
});
@@ -507,26 +455,19 @@ flavors:
),
);
when(
() =>
codePushClient.createApp(displayName: any(named: 'displayName')),
() => codePushClientWrapper.createApp(appName: any(named: 'appName')),
).thenAnswer((invocation) async {
final displayName = invocation.namedArguments[#displayName] as String;
return App(id: appIds[index++], displayName: displayName);
final appName = invocation.namedArguments[#appName] as String?;
return App(id: appIds[index++], displayName: appName ?? '-');
});
final tempDir = setUpAppTempDir();
when(
() => gradlew.productFlavors(any()),
).thenThrow(MissingAndroidProjectException(tempDir.path));
File(
p.join(tempDir.path, 'pubspec.yaml'),
).writeAsStringSync(pubspecYamlContent);
await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
expect(
File(p.join(tempDir.path, 'shorebird.yaml')).readAsStringSync(),
contains('''
).thenThrow(const MissingAndroidProjectException(''));
await runWithOverrides(command.run);
verify(
() => shorebirdYamlFile.writeAsStringSync(
any(
that: contains('''
app_id: ${appIds[0]}
flavors:
development: ${appIds[0]}
@@ -535,20 +476,27 @@ flavors:
productionInternal: ${appIds[3]}
staging: ${appIds[4]}
stagingInternal: ${appIds[5]}'''),
),
),
);
verifyInOrder([
() => codePushClient.createApp(displayName: '$appName (development)'),
() => codePushClient.createApp(
displayName: '$appName (developmentInternal)',
() => codePushClientWrapper.createApp(
appName: '$appName (development)',
),
() => codePushClient.createApp(displayName: '$appName (production)'),
() => codePushClient.createApp(
displayName: '$appName (productionInternal)',
() => codePushClientWrapper.createApp(
appName: '$appName (developmentInternal)',
),
() => codePushClient.createApp(displayName: '$appName (staging)'),
() => codePushClient.createApp(
displayName: '$appName (stagingInternal)',
() => codePushClientWrapper.createApp(
appName: '$appName (production)',
),
() => codePushClientWrapper.createApp(
appName: '$appName (productionInternal)',
),
() => codePushClientWrapper.createApp(
appName: '$appName (staging)',
),
() => codePushClientWrapper.createApp(
appName: '$appName (stagingInternal)',
),
]);
});
@@ -578,23 +526,16 @@ flavors:
);
when(() => gradlew.productFlavors(any())).thenAnswer((_) async => {});
when(
() =>
codePushClient.createApp(displayName: any(named: 'displayName')),
() => codePushClientWrapper.createApp(appName: any(named: 'appName')),
).thenAnswer((invocation) async {
final displayName = invocation.namedArguments[#displayName] as String;
return App(id: appIds[index++], displayName: displayName);
final appName = invocation.namedArguments[#appName] as String?;
return App(id: appIds[index++], displayName: appName ?? '--');
});
final tempDir = setUpAppTempDir();
File(
p.join(tempDir.path, 'pubspec.yaml'),
).writeAsStringSync(pubspecYamlContent);
await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
expect(
File(p.join(tempDir.path, 'shorebird.yaml')).readAsStringSync(),
contains('''
await runWithOverrides(command.run);
verify(
() => shorebirdYamlFile.writeAsStringSync(
any(
that: contains('''
app_id: ${appIds[0]}
flavors:
development: ${appIds[1]}
@@ -603,20 +544,27 @@ flavors:
productionInternal: ${appIds[4]}
staging: ${appIds[5]}
stagingInternal: ${appIds[6]}'''),
),
),
);
verifyInOrder([
() => codePushClient.createApp(displayName: '$appName (development)'),
() => codePushClient.createApp(
displayName: '$appName (developmentInternal)',
() => codePushClientWrapper.createApp(
appName: '$appName (development)',
),
() => codePushClient.createApp(displayName: '$appName (production)'),
() => codePushClient.createApp(
displayName: '$appName (productionInternal)',
() => codePushClientWrapper.createApp(
appName: '$appName (developmentInternal)',
),
() => codePushClient.createApp(displayName: '$appName (staging)'),
() => codePushClient.createApp(
displayName: '$appName (stagingInternal)',
() => codePushClientWrapper.createApp(
appName: '$appName (production)',
),
() => codePushClientWrapper.createApp(
appName: '$appName (productionInternal)',
),
() => codePushClientWrapper.createApp(
appName: '$appName (staging)',
),
() => codePushClientWrapper.createApp(
appName: '$appName (stagingInternal)',
),
]);
});
@@ -646,23 +594,16 @@ flavors:
},
);
when(
() =>
codePushClient.createApp(displayName: any(named: 'displayName')),
() => codePushClientWrapper.createApp(appName: any(named: 'appName')),
).thenAnswer((invocation) async {
final displayName = invocation.namedArguments[#displayName] as String;
return App(id: appIds[index++], displayName: displayName);
final appName = invocation.namedArguments[#appName] as String?;
return App(id: appIds[index++], displayName: appName ?? '-');
});
final tempDir = setUpAppTempDir();
File(
p.join(tempDir.path, 'pubspec.yaml'),
).writeAsStringSync(pubspecYamlContent);
await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
expect(
File(p.join(tempDir.path, 'shorebird.yaml')).readAsStringSync(),
contains('''
await runWithOverrides(command.run);
verify(
() => shorebirdYamlFile.writeAsStringSync(
any(
that: contains('''
app_id: ${appIds[0]}
flavors:
development: ${appIds[1]}
@@ -671,20 +612,27 @@ flavors:
productionInternal: ${appIds[4]}
staging: ${appIds[5]}
stagingInternal: ${appIds[6]}'''),
),
),
);
verifyInOrder([
() => codePushClient.createApp(displayName: '$appName (development)'),
() => codePushClient.createApp(
displayName: '$appName (developmentInternal)',
() => codePushClientWrapper.createApp(
appName: '$appName (development)',
),
() => codePushClient.createApp(displayName: '$appName (production)'),
() => codePushClient.createApp(
displayName: '$appName (productionInternal)',
() => codePushClientWrapper.createApp(
appName: '$appName (developmentInternal)',
),
() => codePushClient.createApp(displayName: '$appName (staging)'),
() => codePushClient.createApp(
displayName: '$appName (stagingInternal)',
() => codePushClientWrapper.createApp(
appName: '$appName (production)',
),
() => codePushClientWrapper.createApp(
appName: '$appName (productionInternal)',
),
() => codePushClientWrapper.createApp(
appName: '$appName (staging)',
),
() => codePushClientWrapper.createApp(
appName: '$appName (stagingInternal)',
),
]);
});
@@ -714,23 +662,16 @@ flavors:
(_) async => const XcodeProjectBuildInfo(schemes: variants),
);
when(
() =>
codePushClient.createApp(displayName: any(named: 'displayName')),
() => codePushClientWrapper.createApp(appName: any(named: 'appName')),
).thenAnswer((invocation) async {
final displayName = invocation.namedArguments[#displayName] as String;
return App(id: appIds[index++], displayName: displayName);
final appName = invocation.namedArguments[#appName] as String?;
return App(id: appIds[index++], displayName: appName ?? '-');
});
final tempDir = setUpAppTempDir();
File(
p.join(tempDir.path, 'pubspec.yaml'),
).writeAsStringSync(pubspecYamlContent);
await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
expect(
File(p.join(tempDir.path, 'shorebird.yaml')).readAsStringSync(),
contains('''
await runWithOverrides(command.run);
verify(
() => shorebirdYamlFile.writeAsStringSync(
any(
that: contains('''
app_id: ${appIds[0]}
flavors:
development: ${appIds[0]}
@@ -739,20 +680,27 @@ flavors:
productionInternal: ${appIds[3]}
staging: ${appIds[4]}
stagingInternal: ${appIds[5]}'''),
),
),
);
verifyInOrder([
() => codePushClient.createApp(displayName: '$appName (development)'),
() => codePushClient.createApp(
displayName: '$appName (developmentInternal)',
() => codePushClientWrapper.createApp(
appName: '$appName (development)',
),
() => codePushClient.createApp(displayName: '$appName (production)'),
() => codePushClient.createApp(
displayName: '$appName (productionInternal)',
() => codePushClientWrapper.createApp(
appName: '$appName (developmentInternal)',
),
() => codePushClient.createApp(displayName: '$appName (staging)'),
() => codePushClient.createApp(
displayName: '$appName (stagingInternal)',
() => codePushClientWrapper.createApp(
appName: '$appName (production)',
),
() => codePushClientWrapper.createApp(
appName: '$appName (productionInternal)',
),
() => codePushClientWrapper.createApp(
appName: '$appName (staging)',
),
() => codePushClientWrapper.createApp(
appName: '$appName (stagingInternal)',
),
]);
});
@@ -790,23 +738,16 @@ flavors:
(_) async => const XcodeProjectBuildInfo(schemes: iosVariants),
);
when(
() =>
codePushClient.createApp(displayName: any(named: 'displayName')),
() => codePushClientWrapper.createApp(appName: any(named: 'appName')),
).thenAnswer((invocation) async {
final displayName = invocation.namedArguments[#displayName] as String;
return App(id: appIds[index++], displayName: displayName);
final appName = invocation.namedArguments[#appName] as String?;
return App(id: appIds[index++], displayName: appName ?? '-');
});
final tempDir = setUpAppTempDir();
File(
p.join(tempDir.path, 'pubspec.yaml'),
).writeAsStringSync(pubspecYamlContent);
await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
expect(
File(p.join(tempDir.path, 'shorebird.yaml')).readAsStringSync(),
contains('''
await runWithOverrides(command.run);
verify(
() => shorebirdYamlFile.writeAsStringSync(
any(
that: contains('''
app_id: test-appId-1
flavors:
dev: test-appId-1
@@ -817,123 +758,114 @@ flavors:
developmentInternal: test-appId-6
staging: test-appId-7
stagingInternal: test-appId-8'''),
),
),
);
verifyInOrder([
() => codePushClient.createApp(displayName: '$appName (dev)'),
() => codePushClient.createApp(displayName: '$appName (devInternal)'),
() => codePushClient.createApp(displayName: '$appName (production)'),
() => codePushClient.createApp(
displayName: '$appName (productionInternal)',
() => codePushClientWrapper.createApp(appName: '$appName (dev)'),
() => codePushClientWrapper.createApp(
appName: '$appName (devInternal)',
),
() => codePushClient.createApp(displayName: '$appName (development)'),
() => codePushClient.createApp(
displayName: '$appName (developmentInternal)',
() => codePushClientWrapper.createApp(
appName: '$appName (production)',
),
() => codePushClient.createApp(displayName: '$appName (staging)'),
() => codePushClient.createApp(
displayName: '$appName (stagingInternal)',
() => codePushClientWrapper.createApp(
appName: '$appName (productionInternal)',
),
() => codePushClientWrapper.createApp(
appName: '$appName (development)',
),
() => codePushClientWrapper.createApp(
appName: '$appName (developmentInternal)',
),
() => codePushClientWrapper.createApp(
appName: '$appName (staging)',
),
() => codePushClientWrapper.createApp(
appName: '$appName (stagingInternal)',
),
]);
});
});
test('detects existing shorebird.yaml in pubspec.yaml assets', () async {
final tempDir = Directory.systemTemp.createTempSync();
File(
p.join(tempDir.path, 'pubspec.yaml'),
).writeAsStringSync('''
when(() => pubspecYamlFile.readAsStringSync()).thenReturn('''
$pubspecYamlContent
flutter:
assets:
- shorebird.yaml
''');
await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
expect(
File(p.join(tempDir.path, 'shorebird.yaml')).readAsStringSync(),
contains('app_id: $appId'),
await runWithOverrides(command.run);
verify(
() => shorebirdYamlFile.writeAsStringSync(
any(that: contains('app_id: $appId')),
),
);
});
test('creates flutter.assets and adds shorebird.yaml', () async {
final tempDir = Directory.systemTemp.createTempSync();
File(
p.join(tempDir.path, 'pubspec.yaml'),
).writeAsStringSync(pubspecYamlContent);
await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
expect(
File(p.join(tempDir.path, 'pubspec.yaml')).readAsStringSync(),
equals('''
await runWithOverrides(command.run);
verify(
() => pubspecYamlFile.writeAsStringSync(
any(
that: equals('''
$pubspecYamlContent
flutter:
assets:
- shorebird.yaml
'''),
),
),
);
});
test('creates assets and adds shorebird.yaml', () async {
final tempDir = Directory.systemTemp.createTempSync();
File(p.join(tempDir.path, 'pubspec.yaml')).writeAsStringSync('''
when(() => pubspecYamlFile.readAsStringSync()).thenReturn('''
$pubspecYamlContent
flutter:
uses-material-design: true
''');
await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
expect(
File(p.join(tempDir.path, 'pubspec.yaml')).readAsStringSync(),
equals('''
await runWithOverrides(command.run);
verify(
() => pubspecYamlFile.writeAsStringSync(
any(
that: equals('''
$pubspecYamlContent
flutter:
assets:
- shorebird.yaml
uses-material-design: true
'''),
),
),
);
});
test('adds shorebird.yaml to assets', () async {
final tempDir = Directory.systemTemp.createTempSync();
File(p.join(tempDir.path, 'pubspec.yaml')).writeAsStringSync('''
when(() => pubspecYamlFile.readAsStringSync()).thenReturn('''
$pubspecYamlContent
flutter:
assets:
- some/asset.txt
''');
await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
expect(
File(p.join(tempDir.path, 'pubspec.yaml')).readAsStringSync(),
equals('''
await runWithOverrides(command.run);
verify(
() => pubspecYamlFile.writeAsStringSync(
any(
that: equals('''
$pubspecYamlContent
flutter:
assets:
- some/asset.txt
- shorebird.yaml
'''),
);
),
),
).called(1);
});
test('fixes fixable validation errors', () async {
final tempDir = Directory.systemTemp.createTempSync();
File(
p.join(tempDir.path, 'pubspec.yaml'),
).writeAsStringSync(pubspecYamlContent);
await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
await runWithOverrides(command.run);
verify(() => doctor.runValidators(any(), applyFixes: true)).called(1);
});
});
@@ -12,12 +12,14 @@ import 'package:shorebird_cli/src/auth/auth.dart';
import 'package:shorebird_cli/src/cache.dart' show Cache, cacheRef;
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/commands/commands.dart';
import 'package:shorebird_cli/src/config/config.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/platform.dart';
import 'package:shorebird_cli/src/process.dart';
import 'package:shorebird_cli/src/shorebird_build_mixin.dart';
import 'package:shorebird_cli/src/shorebird_environment.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
import 'package:shorebird_cli/src/shorebird_version_manager.dart';
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
import 'package:test/test.dart';
@@ -44,16 +46,22 @@ class _MockProcessResult extends Mock implements ShorebirdProcessResult {}
class _MockHttpClient extends Mock implements http.Client {}
class _MockShorebirdEnv extends Mock implements ShorebirdEnv {}
class _MockShorebirdProcess extends Mock implements ShorebirdProcess {}
class _MockShorebirdValidator extends Mock implements ShorebirdValidator {}
class _MockShorebirdVersionManager extends Mock
implements ShorebirdVersionManager {}
class _FakeShorebirdProcess extends Fake implements ShorebirdProcess {}
void main() {
group(PatchAarCommand, () {
const flutterRevision = '83305b5088e6fe327fb3334a73ff190828d85713';
const appId = 'test-app-id';
const shorebirdYaml = ShorebirdYaml(appId: appId);
const buildNumber = '1.0';
const versionName = '1.2.3';
const versionCode = '1';
@@ -63,6 +71,7 @@ void main() {
const channelName = 'stable';
const appDisplayName = 'Test App';
const appMetadata = AppMetadata(appId: appId, displayName: appDisplayName);
const androidPackageName = 'com.example.my_flutter_module';
const releaseArtifact = ReleaseArtifact(
id: 0,
releaseId: 0,
@@ -89,45 +98,24 @@ void main() {
displayName: '1.2.3+1',
platformStatuses: {},
);
const noModulePubspecYamlContent = '''
name: example
version: 1.0.0
environment:
sdk: ">=2.19.0 <3.0.0"
flutter:
assets:
- shorebird.yaml''';
const pubspecYamlContent = '''
name: example
version: 1.0.0
environment:
sdk: ">=2.19.0 <3.0.0"
flutter:
module:
androidX: true
androidPackage: com.example.my_flutter_module
iosBundleIdentifier: com.example.myFlutterModule
assets:
- shorebird.yaml''';
late AarDiffer aarDiffer;
late ArgResults argResults;
late Auth auth;
late CodePushClientWrapper codePushClientWrapper;
late Directory shorebirdRoot;
late Directory flutterDirectory;
late Platform platform;
late Progress progress;
late Logger logger;
late ShorebirdProcessResult flutterBuildProcessResult;
late ShorebirdProcessResult flutterRevisionProcessResult;
late ShorebirdProcessResult patchProcessResult;
late http.Client httpClient;
late Cache cache;
late ShorebirdEnv shorebirdEnv;
late ShorebirdProcess shorebirdProcess;
late ShorebirdValidator shorebirdValidator;
late ShorebirdVersionManager shorebirdVersionManager;
late PatchAarCommand command;
R runWithOverrides<R>(R Function() body) {
@@ -141,23 +129,16 @@ flutter:
loggerRef.overrideWith(() => logger),
platformRef.overrideWith(() => platform),
processRef.overrideWith(() => shorebirdProcess),
shorebirdEnvRef.overrideWith(() => shorebirdEnv),
shorebirdValidatorRef.overrideWith(() => shorebirdValidator),
shorebirdVersionManagerRef.overrideWith(
() => shorebirdVersionManager,
),
},
);
}
Directory setUpTempDir({bool includeModule = true}) {
final tempDir = Directory.systemTemp.createTempSync();
File(
p.join(tempDir.path, 'pubspec.yaml'),
).writeAsStringSync(
includeModule ? pubspecYamlContent : noModulePubspecYamlContent,
);
File(
p.join(tempDir.path, 'shorebird.yaml'),
).writeAsStringSync('app_id: $appId');
return tempDir;
}
Directory setUpTempDir() => Directory.systemTemp.createTempSync();
void setUpTempArtifacts(Directory dir) {
final aarDir = p.join(
@@ -199,30 +180,30 @@ flutter:
auth = _MockAuth();
codePushClientWrapper = _MockCodePushClientWrapper();
shorebirdRoot = Directory.systemTemp.createTempSync();
flutterDirectory = Directory(
p.join(shorebirdRoot.path, 'bin', 'cache', 'flutter'),
);
platform = _MockPlatform();
progress = _MockProgress();
logger = _MockLogger();
flutterBuildProcessResult = _MockProcessResult();
flutterRevisionProcessResult = _MockProcessResult();
patchProcessResult = _MockProcessResult();
httpClient = _MockHttpClient();
cache = _MockCache();
shorebirdEnv = _MockShorebirdEnv();
shorebirdProcess = _MockShorebirdProcess();
shorebirdValidator = _MockShorebirdValidator();
shorebirdVersionManager = _MockShorebirdVersionManager();
registerFallbackValue(ReleasePlatform.android);
when(() => platform.environment).thenReturn({});
when(() => platform.script).thenReturn(
Uri.file(
p.join(
shorebirdRoot.path,
'bin',
'cache',
'shorebird.snapshot',
),
),
);
when(() => shorebirdEnv.shorebirdRoot).thenReturn(shorebirdRoot);
when(() => shorebirdEnv.flutterDirectory).thenReturn(flutterDirectory);
when(
() => shorebirdEnv.androidPackageName,
).thenReturn(androidPackageName);
when(() => shorebirdEnv.getShorebirdYaml()).thenReturn(shorebirdYaml);
when(
() => shorebirdProcess.run(
'flutter',
@@ -230,14 +211,6 @@ flutter:
runInShell: any(named: 'runInShell'),
),
).thenAnswer((_) async => flutterBuildProcessResult);
when(
() => shorebirdProcess.run(
'git',
any(),
runInShell: any(named: 'runInShell'),
workingDirectory: any(named: 'workingDirectory'),
),
).thenAnswer((_) async => flutterRevisionProcessResult);
when(
() => shorebirdProcess.run(
any(that: endsWith('patch')),
@@ -253,10 +226,12 @@ flutter:
return patchProcessResult;
});
when(() => aarDiffer.changedFiles(any(), any()))
.thenReturn(FileSetDiff.empty());
when(() => aarDiffer.containsPotentiallyBreakingAssetDiffs(any()))
.thenReturn(false);
when(
() => aarDiffer.changedFiles(any(), any()),
).thenReturn(FileSetDiff.empty());
when(
() => aarDiffer.containsPotentiallyBreakingAssetDiffs(any()),
).thenReturn(false);
when(() => argResults.rest).thenReturn([]);
when(() => argResults['channel']).thenReturn(channelName);
when(() => argResults['dry-run']).thenReturn(false);
@@ -273,14 +248,6 @@ flutter:
when(
() => flutterBuildProcessResult.exitCode,
).thenReturn(ExitCode.success.code);
when(
() => flutterRevisionProcessResult.exitCode,
).thenReturn(ExitCode.success.code);
when(
() => flutterRevisionProcessResult.stdout,
).thenReturn(flutterRevision);
when(() => patchProcessResult.exitCode).thenReturn(ExitCode.success.code);
when(() => httpClient.send(any())).thenAnswer(
(_) async => http.StreamedResponse(const Stream.empty(), HttpStatus.ok),
@@ -335,6 +302,9 @@ flutter:
checkShorebirdInitialized: any(named: 'checkShorebirdInitialized'),
),
).thenAnswer((_) async {});
when(
() => shorebirdVersionManager.fetchCurrentGitHash(),
).thenAnswer((_) async => flutterRevision);
command = runWithOverrides(
() => PatchAarCommand(
@@ -370,26 +340,15 @@ flutter:
});
test('exits with 78 if no module entry exists in pubspec.yaml', () async {
final tempDir = setUpTempDir(includeModule: false);
final result = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
expect(result, ExitCode.config.code);
when(() => shorebirdEnv.androidPackageName).thenReturn(null);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.config.code);
});
test('exits with code 70 when building fails', () async {
when(() => flutterBuildProcessResult.exitCode).thenReturn(1);
when(() => flutterBuildProcessResult.stderr).thenReturn('oops');
final tempDir = setUpTempDir();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.software.code));
});
@@ -398,11 +357,7 @@ flutter:
'both --dry-run and --force are specified', () async {
when(() => argResults['dry-run']).thenReturn(true);
when(() => argResults['force']).thenReturn(true);
final tempDir = setUpTempDir();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.usage.code));
});
@@ -469,9 +424,10 @@ Please re-run the release command for this version or create a new release.'''),
);
test('errors when unable to detect flutter revision', () async {
const error = 'oops';
when(() => flutterRevisionProcessResult.exitCode).thenReturn(1);
when(() => flutterRevisionProcessResult.stderr).thenReturn(error);
final exception = Exception('oops');
when(
() => shorebirdVersionManager.fetchCurrentGitHash(),
).thenThrow(exception);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
@@ -479,21 +435,17 @@ Please re-run the release command for this version or create a new release.'''),
getCurrentDirectory: () => tempDir,
);
expect(exitCode, ExitCode.software.code);
verify(
() => progress.fail(
'Exception: Unable to determine flutter revision: $error',
),
).called(1);
verify(() => progress.fail('$exception')).called(1);
});
test(
'errors when shorebird flutter revision '
'does not match release revision', () async {
const otherRevision = 'other-revision';
when(() => flutterRevisionProcessResult.stdout).thenReturn(otherRevision);
when(
() => shorebirdVersionManager.fetchCurrentGitHash(),
).thenAnswer((_) async => otherRevision);
final tempDir = setUpTempDir();
final flutterDir =
runWithOverrides(() => ShorebirdEnvironment.flutterDirectory.path);
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
@@ -504,21 +456,11 @@ Please re-run the release command for this version or create a new release.'''),
expect(exitCode, ExitCode.software.code);
verify(
() => logger.err('''
Flutter revision mismatch.
The release you are trying to patch was built with a different version of Flutter.
Release Flutter Revision: $flutterRevision
Current Flutter Revision: $otherRevision
'''),
).called(1);
verify(
() => logger.info('''
Either create a new release using:
${lightCyan.wrap('shorebird release aar')}
Or downgrade your Flutter version and try again using:
${lightCyan.wrap('cd $flutterDir')}
${lightCyan.wrap('cd ${shorebirdEnv.flutterDirectory.path}')}
${lightCyan.wrap('git checkout ${release.flutterRevision}')}
Shorebird plans to support this automatically, let us know if it's important to you:
@@ -14,14 +14,16 @@ import 'package:shorebird_cli/src/bundletool.dart';
import 'package:shorebird_cli/src/cache.dart' show Cache, cacheRef;
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/commands/patch/patch_android_command.dart';
import 'package:shorebird_cli/src/config/config.dart';
import 'package:shorebird_cli/src/doctor.dart';
import 'package:shorebird_cli/src/java.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/platform.dart';
import 'package:shorebird_cli/src/process.dart';
import 'package:shorebird_cli/src/shorebird_build_mixin.dart';
import 'package:shorebird_cli/src/shorebird_environment.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
import 'package:shorebird_cli/src/shorebird_version_manager.dart';
import 'package:shorebird_cli/src/validators/validators.dart';
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
import 'package:test/test.dart';
@@ -55,6 +57,8 @@ class _MockProcessResult extends Mock implements ShorebirdProcessResult {}
class _MockHttpClient extends Mock implements http.Client {}
class _MockShorebirdEnv extends Mock implements ShorebirdEnv {}
class _MockShorebirdFlutterValidator extends Mock
implements ShorebirdFlutterValidator {}
@@ -62,12 +66,16 @@ class _MockShorebirdProcess extends Mock implements ShorebirdProcess {}
class _MockShorebirdValidator extends Mock implements ShorebirdValidator {}
class _MockShorebirdVersionManager extends Mock
implements ShorebirdVersionManager {}
class _FakeShorebirdProcess extends Fake implements ShorebirdProcess {}
void main() {
group(PatchAndroidCommand, () {
const flutterRevision = '83305b5088e6fe327fb3334a73ff190828d85713';
const appId = 'test-app-id';
const shorebirdYaml = ShorebirdYaml(appId: appId);
const versionName = '1.2.3';
const versionCode = '1';
const version = '$versionName+$versionCode';
@@ -117,20 +125,22 @@ flutter:
late Auth auth;
late Bundletool bundletool;
late CodePushClientWrapper codePushClientWrapper;
late Directory flutterDirectory;
late Directory shorebirdRoot;
late Doctor doctor;
late Java java;
late Platform platform;
late Progress progress;
late Logger logger;
late ShorebirdEnv shorebirdEnv;
late ShorebirdProcessResult flutterBuildProcessResult;
late ShorebirdProcessResult flutterRevisionProcessResult;
late ShorebirdProcessResult patchProcessResult;
late http.Client httpClient;
late Cache cache;
late ShorebirdFlutterValidator flutterValidator;
late ShorebirdProcess shorebirdProcess;
late ShorebirdValidator shorebirdValidator;
late ShorebirdVersionManager shorebirdVersionManager;
late PatchAndroidCommand command;
R runWithOverrides<R>(R Function() body) {
@@ -145,9 +155,13 @@ flutter:
engineConfigRef.overrideWith(() => const EngineConfig.empty()),
javaRef.overrideWith(() => java),
loggerRef.overrideWith(() => logger),
shorebirdEnvRef.overrideWith(() => shorebirdEnv),
platformRef.overrideWith(() => platform),
processRef.overrideWith(() => shorebirdProcess),
shorebirdValidatorRef.overrideWith(() => shorebirdValidator),
shorebirdVersionManagerRef.overrideWith(
() => shorebirdVersionManager,
),
},
);
}
@@ -198,17 +212,21 @@ flutter:
doctor = _MockDoctor();
java = _MockJava();
shorebirdRoot = Directory.systemTemp.createTempSync();
flutterDirectory = Directory(
p.join(shorebirdRoot.path, 'bin', 'cache', 'flutter'),
);
platform = _MockPlatform();
progress = _MockProgress();
logger = _MockLogger();
flutterBuildProcessResult = _MockProcessResult();
flutterRevisionProcessResult = _MockProcessResult();
patchProcessResult = _MockProcessResult();
httpClient = _MockHttpClient();
flutterValidator = _MockShorebirdFlutterValidator();
cache = _MockCache();
shorebirdEnv = _MockShorebirdEnv();
shorebirdProcess = _MockShorebirdProcess();
shorebirdValidator = _MockShorebirdValidator();
shorebirdVersionManager = _MockShorebirdVersionManager();
command = runWithOverrides(
() => PatchAndroidCommand(
aabDiffer: aabDiffer,
@@ -216,16 +234,9 @@ flutter:
),
)..testArgResults = argResults;
when(() => platform.script).thenReturn(
Uri.file(
p.join(
shorebirdRoot.path,
'bin',
'cache',
'shorebird.snapshot',
),
),
);
when(() => shorebirdEnv.getShorebirdYaml()).thenReturn(shorebirdYaml);
when(() => shorebirdEnv.shorebirdRoot).thenReturn(shorebirdRoot);
when(() => shorebirdEnv.flutterDirectory).thenReturn(flutterDirectory);
when(
() => shorebirdProcess.run(
'flutter',
@@ -233,14 +244,6 @@ flutter:
runInShell: any(named: 'runInShell'),
),
).thenAnswer((_) async => flutterBuildProcessResult);
when(
() => shorebirdProcess.run(
'git',
any(),
runInShell: any(named: 'runInShell'),
workingDirectory: any(named: 'workingDirectory'),
),
).thenAnswer((_) async => flutterRevisionProcessResult);
when(
() => shorebirdProcess.run(
any(that: endsWith('patch')),
@@ -255,17 +258,23 @@ flutter:
..writeAsStringSync('diff');
return patchProcessResult;
});
when(() => patchProcessResult.exitCode).thenReturn(ExitCode.success.code);
when(() => aabDiffer.changedFiles(any(), any()))
.thenReturn(FileSetDiff.empty());
when(() => aabDiffer.assetsFileSetDiff(any()))
.thenReturn(FileSetDiff.empty());
when(() => aabDiffer.nativeFileSetDiff(any()))
.thenReturn(FileSetDiff.empty());
when(() => aabDiffer.containsPotentiallyBreakingAssetDiffs(any()))
.thenReturn(false);
when(() => aabDiffer.containsPotentiallyBreakingNativeDiffs(any()))
.thenReturn(false);
when(
() => aabDiffer.changedFiles(any(), any()),
).thenReturn(FileSetDiff.empty());
when(
() => aabDiffer.assetsFileSetDiff(any()),
).thenReturn(FileSetDiff.empty());
when(
() => aabDiffer.nativeFileSetDiff(any()),
).thenReturn(FileSetDiff.empty());
when(
() => aabDiffer.containsPotentiallyBreakingAssetDiffs(any()),
).thenReturn(false);
when(
() => aabDiffer.containsPotentiallyBreakingNativeDiffs(any()),
).thenReturn(false);
when(() => argResults.rest).thenReturn([]);
when(() => argResults['arch']).thenReturn(arch);
when(() => argResults['channel']).thenReturn(channelName);
@@ -281,13 +290,6 @@ flutter:
when(
() => flutterBuildProcessResult.exitCode,
).thenReturn(ExitCode.success.code);
when(
() => flutterRevisionProcessResult.exitCode,
).thenReturn(ExitCode.success.code);
when(() => patchProcessResult.exitCode).thenReturn(ExitCode.success.code);
when(
() => flutterRevisionProcessResult.stdout,
).thenReturn(flutterRevision);
when(() => httpClient.send(any())).thenAnswer(
(_) async => http.StreamedResponse(const Stream.empty(), HttpStatus.ok),
);
@@ -354,6 +356,9 @@ flutter:
validators: any(named: 'validators'),
),
).thenAnswer((_) async {});
when(
() => shorebirdVersionManager.fetchCurrentGitHash(),
).thenAnswer((_) async => flutterRevision);
});
test('has a description', () {
@@ -483,9 +488,10 @@ Please re-run the release command for this version or create a new release.'''),
);
test('errors when unable to detect flutter revision', () async {
const error = 'oops';
when(() => flutterRevisionProcessResult.exitCode).thenReturn(1);
when(() => flutterRevisionProcessResult.stderr).thenReturn(error);
final exception = Exception('oops');
when(
() => shorebirdVersionManager.fetchCurrentGitHash(),
).thenThrow(exception);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
@@ -493,45 +499,32 @@ Please re-run the release command for this version or create a new release.'''),
getCurrentDirectory: () => tempDir,
);
expect(exitCode, ExitCode.software.code);
verify(
() => progress.fail(
'Exception: Unable to determine flutter revision: $error',
),
).called(1);
verify(() => progress.fail('$exception')).called(1);
});
test(
'errors when shorebird flutter revision '
'does not match release revision', () async {
const otherRevision = 'other-revision';
when(() => flutterRevisionProcessResult.stdout).thenReturn(otherRevision);
when(
() => shorebirdVersionManager.fetchCurrentGitHash(),
).thenAnswer((_) async => otherRevision);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
expect(exitCode, ExitCode.software.code);
final shorebirdFlutterPath = runWithOverrides(
() => ShorebirdEnvironment.flutterDirectory.path,
);
verify(
() => logger.err('''
Flutter revision mismatch.
The release you are trying to patch was built with a different version of Flutter.
Release Flutter Revision: $flutterRevision
Current Flutter Revision: $otherRevision
'''),
).called(1);
verify(
() => logger.info('''
Either create a new release using:
${lightCyan.wrap('shorebird release')}
${lightCyan.wrap('shorebird release aar')}
Or downgrade your Flutter version and try again using:
${lightCyan.wrap('cd $shorebirdFlutterPath')}
${lightCyan.wrap('cd ${shorebirdEnv.flutterDirectory.path}')}
${lightCyan.wrap('git checkout ${release.flutterRevision}')}
Shorebird plans to support this automatically, let us know if it's important to you:
@@ -846,7 +839,6 @@ https://github.com/shorebirdtech/shorebird/issues/472
).called(1);
verify(() => logger.success('\n✅ Published Patch!')).called(1);
expect(exitCode, ExitCode.success.code);
// expect(capturedHostedUri, isNull);
});
test(
@@ -12,12 +12,14 @@ import 'package:shorebird_cli/src/archive_analysis/archive_analysis.dart';
import 'package:shorebird_cli/src/auth/auth.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/commands/patch/patch.dart';
import 'package:shorebird_cli/src/config/config.dart';
import 'package:shorebird_cli/src/doctor.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/platform.dart';
import 'package:shorebird_cli/src/process.dart';
import 'package:shorebird_cli/src/shorebird_environment.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
import 'package:shorebird_cli/src/shorebird_version_manager.dart';
import 'package:shorebird_cli/src/third_party/flutter_tools/lib/flutter_tools.dart';
import 'package:shorebird_cli/src/validators/validators.dart';
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
@@ -48,6 +50,8 @@ class _MockProcessResult extends Mock implements ShorebirdProcessResult {}
class _MockHttpClient extends Mock implements http.Client {}
class _MockShorebirdEnv extends Mock implements ShorebirdEnv {}
class _MockShorebirdFlutterValidator extends Mock
implements ShorebirdFlutterValidator {}
@@ -55,11 +59,15 @@ class _MockShorebirdProcess extends Mock implements ShorebirdProcess {}
class _MockShorebirdValidator extends Mock implements ShorebirdValidator {}
class _MockShorebirdVersionManager extends Mock
implements ShorebirdVersionManager {}
class _FakeShorebirdProcess extends Fake implements ShorebirdProcess {}
void main() {
const flutterRevision = '83305b5088e6fe327fb3334a73ff190828d85713';
const appId = 'test-app-id';
const shorebirdYaml = ShorebirdYaml(appId: appId);
const versionName = '1.2.3';
const versionCode = '1';
const version = '$versionName+$versionCode';
@@ -101,6 +109,9 @@ flutter:
late ArgResults argResults;
late Auth auth;
late CodePushClientWrapper codePushClientWrapper;
late Directory flutterDirectory;
late Directory shorebirdRoot;
late File genSnapshotFile;
late Doctor doctor;
late Ipa ipa;
late IpaReader ipaReader;
@@ -109,11 +120,12 @@ flutter:
late Platform platform;
late ShorebirdProcessResult aotBuildProcessResult;
late ShorebirdProcessResult flutterBuildProcessResult;
late ShorebirdProcessResult flutterRevisionProcessResult;
late http.Client httpClient;
late ShorebirdEnv shorebirdEnv;
late ShorebirdFlutterValidator flutterValidator;
late ShorebirdProcess shorebirdProcess;
late ShorebirdValidator shorebirdValidator;
late ShorebirdVersionManager shorebirdVersionManager;
late PatchIosCommand command;
R runWithOverrides<R>(R Function() body) {
@@ -126,7 +138,11 @@ flutter:
loggerRef.overrideWith(() => logger),
platformRef.overrideWith(() => platform),
processRef.overrideWith(() => shorebirdProcess),
shorebirdEnvRef.overrideWith(() => shorebirdEnv),
shorebirdValidatorRef.overrideWith(() => shorebirdValidator),
shorebirdVersionManagerRef.overrideWith(
() => shorebirdVersionManager,
),
},
);
}
@@ -178,6 +194,21 @@ flutter:
auth = _MockAuth();
codePushClientWrapper = _MockCodePushClientWrapper();
doctor = _MockDoctor();
shorebirdRoot = Directory.systemTemp.createTempSync();
flutterDirectory = Directory(
p.join(shorebirdRoot.path, 'bin', 'cache', 'flutter'),
);
genSnapshotFile = File(
p.join(
flutterDirectory.path,
'bin',
'cache',
'artifacts',
'engine',
'ios-release',
'gen_snapshot_arm64',
),
);
ipaReader = _MockIpaReader();
ipa = _MockIpa();
progress = _MockProgress();
@@ -185,11 +216,12 @@ flutter:
platform = _MockPlatform();
aotBuildProcessResult = _MockProcessResult();
flutterBuildProcessResult = _MockProcessResult();
flutterRevisionProcessResult = _MockProcessResult();
httpClient = _MockHttpClient();
shorebirdEnv = _MockShorebirdEnv();
flutterValidator = _MockShorebirdFlutterValidator();
shorebirdProcess = _MockShorebirdProcess();
shorebirdValidator = _MockShorebirdValidator();
shorebirdVersionManager = _MockShorebirdVersionManager();
when(() => argResults['arch']).thenReturn(arch);
when(() => argResults['dry-run']).thenReturn(false);
@@ -223,33 +255,16 @@ flutter:
when(() => logger.progress(any())).thenReturn(progress);
when(() => platform.operatingSystem).thenReturn(Platform.macOS);
when(() => platform.environment).thenReturn({});
when(() => platform.script).thenReturn(
Uri.file(
p.join(
Directory.systemTemp.createTempSync().path,
'bin',
'cache',
'shorebird.snapshot',
),
),
);
when(() => aotBuildProcessResult.exitCode)
.thenReturn(ExitCode.success.code);
when(() => flutterBuildProcessResult.exitCode)
.thenReturn(ExitCode.success.code);
when(() => flutterRevisionProcessResult.exitCode)
.thenReturn(ExitCode.success.code);
when(() => shorebirdEnv.getShorebirdYaml()).thenReturn(shorebirdYaml);
when(() => shorebirdEnv.shorebirdRoot).thenReturn(shorebirdRoot);
when(() => shorebirdEnv.flutterDirectory).thenReturn(flutterDirectory);
when(() => shorebirdEnv.genSnapshotFile).thenReturn(genSnapshotFile);
when(
() => flutterRevisionProcessResult.stdout,
).thenReturn(flutterRevision);
() => aotBuildProcessResult.exitCode,
).thenReturn(ExitCode.success.code);
when(
() => shorebirdProcess.run(
'git',
any(),
runInShell: any(named: 'runInShell'),
workingDirectory: any(named: 'workingDirectory'),
),
).thenAnswer((_) async => flutterRevisionProcessResult);
() => flutterBuildProcessResult.exitCode,
).thenReturn(ExitCode.success.code);
when(
() => shorebirdProcess.run(
'flutter',
@@ -272,6 +287,9 @@ flutter:
supportedOperatingSystems: any(named: 'supportedOperatingSystems'),
),
).thenAnswer((_) async {});
when(
() => shorebirdVersionManager.fetchCurrentGitHash(),
).thenAnswer((_) async => flutterRevision);
command = runWithOverrides(() => PatchIosCommand(ipaReader: ipaReader))
..testArgResults = argResults;
@@ -478,9 +496,10 @@ Please re-run the release command for this version or create a new release.'''),
);
test('errors when unable to detect flutter revision', () async {
const error = 'oops';
when(() => flutterRevisionProcessResult.exitCode).thenReturn(1);
when(() => flutterRevisionProcessResult.stderr).thenReturn(error);
final exception = Exception('oops');
when(
() => shorebirdVersionManager.fetchCurrentGitHash(),
).thenThrow(exception);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
@@ -488,45 +507,32 @@ Please re-run the release command for this version or create a new release.'''),
getCurrentDirectory: () => tempDir,
);
expect(exitCode, ExitCode.software.code);
verify(
() => progress.fail(
'Exception: Unable to determine flutter revision: $error',
),
).called(1);
verify(() => progress.fail('$exception')).called(1);
});
test(
'errors when shorebird flutter revision '
'does not match release revision', () async {
const otherRevision = 'other-revision';
when(() => flutterRevisionProcessResult.stdout).thenReturn(otherRevision);
when(
() => shorebirdVersionManager.fetchCurrentGitHash(),
).thenAnswer((_) async => otherRevision);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
expect(exitCode, ExitCode.software.code);
final shorebirdFlutterPath = runWithOverrides(
() => ShorebirdEnvironment.flutterDirectory.path,
);
verify(
() => logger.err('''
Flutter revision mismatch.
The release you are trying to patch was built with a different version of Flutter.
Release Flutter Revision: $flutterRevision
Current Flutter Revision: $otherRevision
'''),
).called(1);
verify(
() => logger.info('''
Either create a new release using:
${lightCyan.wrap('shorebird release')}
${lightCyan.wrap('shorebird release aar')}
Or downgrade your Flutter version and try again using:
${lightCyan.wrap('cd $shorebirdFlutterPath')}
${lightCyan.wrap('cd ${shorebirdEnv.flutterDirectory.path}')}
${lightCyan.wrap('git checkout ${release.flutterRevision}')}
Shorebird plans to support this automatically, let us know if it's important to you:
@@ -9,12 +9,14 @@ import 'package:scoped/scoped.dart';
import 'package:shorebird_cli/src/auth/auth.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/commands/patch/patch.dart';
import 'package:shorebird_cli/src/config/config.dart';
import 'package:shorebird_cli/src/doctor.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/platform.dart';
import 'package:shorebird_cli/src/process.dart';
import 'package:shorebird_cli/src/shorebird_environment.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
import 'package:shorebird_cli/src/shorebird_version_manager.dart';
import 'package:shorebird_cli/src/validators/validators.dart';
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
import 'package:test/test.dart';
@@ -36,6 +38,8 @@ class _MockProgress extends Mock implements Progress {}
class _MockProcessResult extends Mock implements ShorebirdProcessResult {}
class _MockShorebirdEnv extends Mock implements ShorebirdEnv {}
class _MockShorebirdFlutterValidator extends Mock
implements ShorebirdFlutterValidator {}
@@ -43,10 +47,14 @@ class _MockShorebirdProcess extends Mock implements ShorebirdProcess {}
class _MockShorebirdValidator extends Mock implements ShorebirdValidator {}
class _MockShorebirdVersionManager extends Mock
implements ShorebirdVersionManager {}
void main() {
group(PatchIosFrameworkCommand, () {
const appDisplayName = 'Test App';
const appId = 'test-app-id';
const shorebirdYaml = ShorebirdYaml(appId: appId);
const versionName = '1.2.3';
const versionCode = '1';
const version = '$versionName+$versionCode';
@@ -74,6 +82,8 @@ flutter:
late ArgResults argResults;
late CodePushClientWrapper codePushClientWrapper;
late Directory shorebirdRoot;
late Directory flutterDirectory;
late File genSnapshotFile;
late Doctor doctor;
late Platform platform;
late Auth auth;
@@ -81,10 +91,11 @@ flutter:
late Logger logger;
late ShorebirdProcessResult aotBuildProcessResult;
late ShorebirdProcessResult flutterBuildProcessResult;
late ShorebirdProcessResult flutterRevisionProcessResult;
late ShorebirdEnv shorebirdEnv;
late ShorebirdFlutterValidator flutterValidator;
late ShorebirdProcess shorebirdProcess;
late ShorebirdValidator shorebirdValidator;
late ShorebirdVersionManager shorebirdVersionManager;
late PatchIosFrameworkCommand command;
R runWithOverrides<R>(R Function() body) {
@@ -97,7 +108,11 @@ flutter:
loggerRef.overrideWith(() => logger),
platformRef.overrideWith(() => platform),
processRef.overrideWith(() => shorebirdProcess),
shorebirdEnvRef.overrideWith(() => shorebirdEnv),
shorebirdValidatorRef.overrideWith(() => shorebirdValidator),
shorebirdVersionManagerRef.overrideWith(
() => shorebirdVersionManager,
),
},
);
}
@@ -142,15 +157,30 @@ flutter:
doctor = _MockDoctor();
platform = _MockPlatform();
shorebirdRoot = Directory.systemTemp.createTempSync();
flutterDirectory = Directory(
p.join(shorebirdRoot.path, 'bin', 'cache', 'flutter'),
);
genSnapshotFile = File(
p.join(
flutterDirectory.path,
'bin',
'cache',
'artifacts',
'engine',
'ios-release',
'gen_snapshot_arm64',
),
);
auth = _MockAuth();
progress = _MockProgress();
logger = _MockLogger();
aotBuildProcessResult = _MockProcessResult();
flutterBuildProcessResult = _MockProcessResult();
flutterRevisionProcessResult = _MockProcessResult();
shorebirdEnv = _MockShorebirdEnv();
flutterValidator = _MockShorebirdFlutterValidator();
shorebirdProcess = _MockShorebirdProcess();
shorebirdValidator = _MockShorebirdValidator();
shorebirdVersionManager = _MockShorebirdVersionManager();
when(
() => shorebirdProcess.run(
@@ -166,14 +196,6 @@ flutter:
runInShell: any(named: 'runInShell'),
),
).thenAnswer((_) async => aotBuildProcessResult);
when(
() => shorebirdProcess.run(
'git',
any(),
runInShell: any(named: 'runInShell'),
workingDirectory: any(named: 'workingDirectory'),
),
).thenAnswer((_) async => flutterRevisionProcessResult);
when(() => argResults['force']).thenReturn(false);
when(() => argResults['release-version']).thenReturn(version);
when(() => argResults.rest).thenReturn([]);
@@ -183,25 +205,16 @@ flutter:
when(() => logger.progress(any())).thenReturn(progress);
when(() => logger.confirm(any())).thenReturn(true);
when(() => platform.operatingSystem).thenReturn(Platform.macOS);
when(() => platform.script).thenReturn(
Uri.file(
p.join(
shorebirdRoot.path,
'bin',
'cache',
'shorebird.snapshot',
),
),
);
when(() => aotBuildProcessResult.exitCode)
.thenReturn(ExitCode.success.code);
when(() => flutterBuildProcessResult.exitCode)
.thenReturn(ExitCode.success.code);
when(() => flutterRevisionProcessResult.exitCode)
.thenReturn(ExitCode.success.code);
when(() => shorebirdEnv.getShorebirdYaml()).thenReturn(shorebirdYaml);
when(() => shorebirdEnv.shorebirdRoot).thenReturn(shorebirdRoot);
when(() => shorebirdEnv.flutterDirectory).thenReturn(flutterDirectory);
when(() => shorebirdEnv.genSnapshotFile).thenReturn(genSnapshotFile);
when(
() => flutterRevisionProcessResult.stdout,
).thenReturn(flutterRevision);
() => aotBuildProcessResult.exitCode,
).thenReturn(ExitCode.success.code);
when(
() => flutterBuildProcessResult.exitCode,
).thenReturn(ExitCode.success.code);
when(
() => codePushClientWrapper.getApp(appId: any(named: 'appId')),
).thenAnswer((_) async => appMetadata);
@@ -228,6 +241,9 @@ flutter:
supportedOperatingSystems: any(named: 'supportedOperatingSystems'),
),
).thenAnswer((_) async {});
when(
() => shorebirdVersionManager.fetchCurrentGitHash(),
).thenAnswer((_) async => flutterRevision);
command = runWithOverrides(PatchIosFrameworkCommand.new)
..testArgResults = argResults;
@@ -350,9 +366,10 @@ Please re-run the release command for this version or create a new release.'''),
});
test('errors when unable to detect flutter revision', () async {
const error = 'oops';
when(() => flutterRevisionProcessResult.exitCode).thenReturn(1);
when(() => flutterRevisionProcessResult.stderr).thenReturn(error);
final exception = Exception('oops');
when(
() => shorebirdVersionManager.fetchCurrentGitHash(),
).thenThrow(exception);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
@@ -360,45 +377,32 @@ Please re-run the release command for this version or create a new release.'''),
getCurrentDirectory: () => tempDir,
);
expect(exitCode, ExitCode.software.code);
verify(
() => progress.fail(
'Exception: Unable to determine flutter revision: $error',
),
).called(1);
verify(() => progress.fail('$exception')).called(1);
});
test(
'errors when shorebird flutter revision '
'does not match release revision', () async {
const otherRevision = 'other-revision';
when(() => flutterRevisionProcessResult.stdout).thenReturn(otherRevision);
when(
() => shorebirdVersionManager.fetchCurrentGitHash(),
).thenAnswer((_) async => otherRevision);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
expect(exitCode, ExitCode.software.code);
final shorebirdFlutterPath = runWithOverrides(
() => ShorebirdEnvironment.flutterDirectory.path,
);
verify(
() => logger.err('''
Flutter revision mismatch.
The release you are trying to patch was built with a different version of Flutter.
Release Flutter Revision: $flutterRevision
Current Flutter Revision: $otherRevision
'''),
).called(1);
verify(
() => logger.info('''
Either create a new release using:
${lightCyan.wrap('shorebird release')}
${lightCyan.wrap('shorebird release aar')}
Or downgrade your Flutter version and try again using:
${lightCyan.wrap('cd $shorebirdFlutterPath')}
${lightCyan.wrap('cd ${shorebirdEnv.flutterDirectory.path}')}
${lightCyan.wrap('git checkout ${release.flutterRevision}')}
Shorebird plans to support this automatically, let us know if it's important to you:
@@ -10,12 +10,15 @@ import 'package:scoped/scoped.dart';
import 'package:shorebird_cli/src/auth/auth.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/commands/commands.dart';
import 'package:shorebird_cli/src/config/config.dart';
import 'package:shorebird_cli/src/java.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/platform.dart';
import 'package:shorebird_cli/src/process.dart';
import 'package:shorebird_cli/src/shorebird_build_mixin.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
import 'package:shorebird_cli/src/shorebird_version_manager.dart';
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
import 'package:test/test.dart';
@@ -40,8 +43,13 @@ class _MockCodePushClientWrapper extends Mock
class _MockShorebirdProcess extends Mock implements ShorebirdProcess {}
class _MockShorebirdEnv extends Mock implements ShorebirdEnv {}
class _MockShorebirdValidator extends Mock implements ShorebirdValidator {}
class _MockShorebirdVersionManager extends Mock
implements ShorebirdVersionManager {}
class _FakeRelease extends Fake implements Release {}
class _FakeShorebirdProcess extends Fake implements ShorebirdProcess {}
@@ -50,6 +58,8 @@ void main() {
group(ReleaseAarCommand, () {
const appDisplayName = 'Test App';
const appId = 'test-app-id';
const shorebirdYaml = ShorebirdYaml(appId: appId);
const androidPackageName = 'com.example.my_flutter_module';
const appMetadata = AppMetadata(appId: appId, displayName: appDisplayName);
const flutterRevision = '83305b5088e6fe327fb3334a73ff190828d85713';
const versionName = '1.2.3';
@@ -65,29 +75,6 @@ void main() {
);
const releasePlatform = ReleasePlatform.android;
const buildNumber = '1.0';
const noModulePubspecYamlContent = '''
name: example
version: 1.0.0
environment:
sdk: ">=2.19.0 <3.0.0"
flutter:
assets:
- shorebird.yaml''';
const pubspecYamlContent = '''
name: example
version: 1.0.0
environment:
sdk: ">=2.19.0 <3.0.0"
flutter:
module:
androidX: true
androidPackage: com.example.my_flutter_module
iosBundleIdentifier: com.example.myFlutterModule
assets:
- shorebird.yaml''';
late ArgResults argResults;
late http.Client httpClient;
@@ -99,9 +86,10 @@ flutter:
late Progress progress;
late Logger logger;
late ShorebirdProcessResult flutterBuildProcessResult;
late ShorebirdProcessResult flutterRevisionProcessResult;
late ShorebirdEnv shorebirdEnv;
late ShorebirdProcess shorebirdProcess;
late ShorebirdValidator shorebirdValidator;
late ShorebirdVersionManager shorebirdVersionManager;
late ReleaseAarCommand command;
R runWithOverrides<R>(R Function() body) {
@@ -115,25 +103,17 @@ flutter:
loggerRef.overrideWith(() => logger),
platformRef.overrideWith(() => platform),
processRef.overrideWith(() => shorebirdProcess),
shorebirdEnvRef.overrideWith(() => shorebirdEnv),
shorebirdValidatorRef.overrideWith(() => shorebirdValidator),
shorebirdVersionManagerRef.overrideWith(
() => shorebirdVersionManager,
),
},
);
}
Directory setUpTempDir({bool includeModule = true}) {
final tempDir = Directory.systemTemp.createTempSync();
File(
p.join(tempDir.path, 'pubspec.yaml'),
).writeAsStringSync(
includeModule ? pubspecYamlContent : noModulePubspecYamlContent,
);
File(
p.join(tempDir.path, 'shorebird.yaml'),
).writeAsStringSync('app_id: $appId');
return tempDir;
}
void setUpTempArtifacts(Directory dir) {
Directory setUpTempArtifacts() {
final dir = Directory.systemTemp.createTempSync();
final aarDir = p.join(
dir.path,
'build',
@@ -159,6 +139,7 @@ flutter:
File(artifactPath).createSync(recursive: true);
}
File(aarPath).createSync(recursive: true);
return dir;
}
setUpAll(() {
@@ -178,10 +159,11 @@ flutter:
progress = _MockProgress();
logger = _MockLogger();
flutterBuildProcessResult = _MockProcessResult();
flutterRevisionProcessResult = _MockProcessResult();
shorebirdProcess = _MockShorebirdProcess();
shorebirdRoot = Directory.systemTemp.createTempSync();
shorebirdEnv = _MockShorebirdEnv();
shorebirdValidator = _MockShorebirdValidator();
shorebirdVersionManager = _MockShorebirdVersionManager();
when(() => auth.client).thenReturn(httpClient);
when(() => argResults['build-number']).thenReturn(buildNumber);
@@ -191,26 +173,15 @@ flutter:
when(() => logger.confirm(any())).thenReturn(true);
when(() => logger.progress(any())).thenReturn(progress);
when(() => platform.script).thenReturn(
Uri.file(
p.join(
shorebirdRoot.path,
'bin',
'cache',
'shorebird.snapshot',
),
),
);
when(() => flutterBuildProcessResult.exitCode)
.thenReturn(ExitCode.success.code);
when(() => shorebirdEnv.getShorebirdYaml()).thenReturn(shorebirdYaml);
when(() => shorebirdEnv.shorebirdRoot).thenReturn(shorebirdRoot);
when(
() => shorebirdEnv.androidPackageName,
).thenReturn(androidPackageName);
when(
() => flutterRevisionProcessResult.exitCode,
() => flutterBuildProcessResult.exitCode,
).thenReturn(ExitCode.success.code);
when(
() => flutterRevisionProcessResult.stdout,
).thenReturn(flutterRevision);
when(
() => shorebirdProcess.run(
@@ -221,14 +192,6 @@ flutter:
).thenAnswer((invocation) async {
return flutterBuildProcessResult;
});
when(
() => shorebirdProcess.run(
'git',
any(),
runInShell: any(named: 'runInShell'),
workingDirectory: any(named: 'workingDirectory'),
),
).thenAnswer((_) async => flutterRevisionProcessResult);
when(
() => codePushClientWrapper.getApp(appId: any(named: 'appId')),
@@ -278,6 +241,9 @@ flutter:
checkShorebirdInitialized: any(named: 'checkShorebirdInitialized'),
),
).thenAnswer((_) async {});
when(
() => shorebirdVersionManager.fetchCurrentGitHash(),
).thenAnswer((_) async => flutterRevision);
command = runWithOverrides(
() => ReleaseAarCommand(unzipFn: (_, __) async {}),
@@ -308,24 +274,10 @@ flutter:
).called(1);
});
test('exits with 78 if no pubspec.yaml exists', () async {
final tempDir = Directory.systemTemp.createTempSync();
final result = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
expect(result, ExitCode.config.code);
});
test('exits with 78 if no module entry exists in pubspec.yaml', () async {
final tempDir = setUpTempDir(includeModule: false);
when(() => shorebirdEnv.androidPackageName).thenReturn(null);
final result = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final result = await runWithOverrides(command.run);
expect(result, ExitCode.config.code);
});
@@ -333,13 +285,7 @@ flutter:
test('exits with code 70 when building aar fails', () async {
when(() => flutterBuildProcessResult.exitCode).thenReturn(1);
when(() => flutterBuildProcessResult.stderr).thenReturn('oops');
final tempDir = setUpTempDir();
final result = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final result = await runWithOverrides(command.run);
expect(result, equals(ExitCode.software.code));
verify(
() => shorebirdProcess.run(
@@ -354,8 +300,9 @@ flutter:
runInShell: any(named: 'runInShell'),
),
).called(1);
verify(() => progress.fail(any(that: contains('Failed to build'))))
.called(1);
verify(
() => progress.fail(any(that: contains('Failed to build'))),
).called(1);
});
test('aborts when user opts out', () async {
@@ -366,7 +313,7 @@ flutter:
defaultValue: any(named: 'defaultValue'),
),
).thenAnswer((_) => '1.0.0');
final tempDir = setUpTempDir();
final tempDir = setUpTempArtifacts();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
@@ -376,32 +323,22 @@ flutter:
});
test('throws error when unable to detect flutter revision', () async {
const error = 'oops';
when(() => flutterRevisionProcessResult.exitCode).thenReturn(1);
when(() => flutterRevisionProcessResult.stderr).thenReturn(error);
final tempDir = setUpTempDir();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exception = Exception('oops');
when(
() => shorebirdVersionManager.fetchCurrentGitHash(),
).thenThrow(exception);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.software.code);
verify(
() => progress.fail(
'Exception: Unable to determine flutter revision: $error',
),
).called(1);
verify(() => progress.fail('$exception')).called(1);
});
test('does not prompt for confirmation when --force is used', () async {
when(() => argResults['force']).thenReturn(true);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final tempDir = setUpTempArtifacts();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
expect(exitCode, ExitCode.success.code);
verify(() => logger.success('\n✅ Published Release!')).called(1);
verifyNever(
@@ -410,14 +347,11 @@ flutter:
});
test('succeeds when release is successful', () async {
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final tempDir = setUpTempArtifacts();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
expect(exitCode, ExitCode.success.code);
verify(() => logger.success('\n✅ Published Release!')).called(1);
verify(
@@ -458,14 +392,11 @@ flutter:
releaseVersion: any(named: 'releaseVersion'),
),
).thenAnswer((_) async => release);
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final tempDir = setUpTempArtifacts();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
expect(exitCode, ExitCode.success.code);
verifyNever(
() => codePushClientWrapper.createRelease(
@@ -12,12 +12,15 @@ import 'package:shorebird_cli/src/bundletool.dart';
import 'package:shorebird_cli/src/cache.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/commands/commands.dart';
import 'package:shorebird_cli/src/config/config.dart';
import 'package:shorebird_cli/src/doctor.dart';
import 'package:shorebird_cli/src/java.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/platform.dart';
import 'package:shorebird_cli/src/process.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
import 'package:shorebird_cli/src/shorebird_version_manager.dart';
import 'package:shorebird_cli/src/validators/validators.dart';
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
import 'package:test/test.dart';
@@ -50,8 +53,13 @@ class _MockShorebirdFlutterValidator extends Mock
class _MockShorebirdProcess extends Mock implements ShorebirdProcess {}
class _MockShorebirdEnv extends Mock implements ShorebirdEnv {}
class _MockShorebirdValidator extends Mock implements ShorebirdValidator {}
class _MockShorebirdVersionManager extends Mock
implements ShorebirdVersionManager {}
class _MockJava extends Mock implements Java {}
class _FakeRelease extends Fake implements Release {}
@@ -61,6 +69,7 @@ class _FakeShorebirdProcess extends Fake implements ShorebirdProcess {}
void main() {
group(ReleaseAndroidCommand, () {
const appId = 'test-app-id';
const shorebirdYaml = ShorebirdYaml(appId: appId);
const flutterRevision = '83305b5088e6fe327fb3334a73ff190828d85713';
const versionName = '1.2.3';
const versionCode = '1';
@@ -78,16 +87,6 @@ void main() {
platformStatuses: {},
);
const pubspecYamlContent = '''
name: example
version: $version
environment:
sdk: ">=2.19.0 <3.0.0"
flutter:
assets:
- shorebird.yaml''';
const javaHome = 'test-java-home';
late ArgResults argResults;
@@ -103,10 +102,11 @@ flutter:
late Progress progress;
late Logger logger;
late ShorebirdProcessResult flutterBuildProcessResult;
late ShorebirdProcessResult flutterRevisionProcessResult;
late ShorebirdFlutterValidator flutterValidator;
late ShorebirdProcess shorebirdProcess;
late ShorebirdEnv shorebirdEnv;
late ShorebirdValidator shorebirdValidator;
late ShorebirdVersionManager shorebirdVersionManager;
late ReleaseAndroidCommand command;
R runWithOverrides<R>(R Function() body) {
@@ -123,22 +123,15 @@ flutter:
loggerRef.overrideWith(() => logger),
platformRef.overrideWith(() => platform),
processRef.overrideWith(() => shorebirdProcess),
shorebirdEnvRef.overrideWith(() => shorebirdEnv),
shorebirdValidatorRef.overrideWith(() => shorebirdValidator),
shorebirdVersionManagerRef.overrideWith(
() => shorebirdVersionManager,
),
},
);
}
Directory setUpTempDir() {
final tempDir = Directory.systemTemp.createTempSync();
File(
p.join(tempDir.path, 'pubspec.yaml'),
).writeAsStringSync(pubspecYamlContent);
File(
p.join(tempDir.path, 'shorebird.yaml'),
).writeAsStringSync('app_id: $appId');
return tempDir;
}
setUpAll(() {
registerFallbackValue(ReleasePlatform.android);
registerFallbackValue(ReleaseStatus.draft);
@@ -160,21 +153,15 @@ flutter:
progress = _MockProgress();
logger = _MockLogger();
flutterBuildProcessResult = _MockProcessResult();
flutterRevisionProcessResult = _MockProcessResult();
flutterValidator = _MockShorebirdFlutterValidator();
shorebirdProcess = _MockShorebirdProcess();
shorebirdEnv = _MockShorebirdEnv();
shorebirdValidator = _MockShorebirdValidator();
shorebirdVersionManager = _MockShorebirdVersionManager();
when(() => shorebirdEnv.getShorebirdYaml()).thenReturn(shorebirdYaml);
when(() => shorebirdEnv.shorebirdRoot).thenReturn(shorebirdRoot);
when(() => platform.script).thenReturn(
Uri.file(
p.join(
shorebirdRoot.path,
'bin',
'cache',
'shorebird.snapshot',
),
),
);
when(
() => shorebirdProcess.run(
'flutter',
@@ -182,14 +169,7 @@ flutter:
runInShell: any(named: 'runInShell'),
),
).thenAnswer((_) async => flutterBuildProcessResult);
when(
() => shorebirdProcess.run(
'git',
any(),
runInShell: any(named: 'runInShell'),
workingDirectory: any(named: 'workingDirectory'),
),
).thenAnswer((_) async => flutterRevisionProcessResult);
when(() => argResults.rest).thenReturn([]);
when(() => argResults['arch']).thenReturn(arch);
when(() => argResults['platform']).thenReturn(releasePlatform);
@@ -208,12 +188,6 @@ flutter:
when(
() => flutterBuildProcessResult.exitCode,
).thenReturn(ExitCode.success.code);
when(
() => flutterRevisionProcessResult.exitCode,
).thenReturn(ExitCode.success.code);
when(
() => flutterRevisionProcessResult.stdout,
).thenReturn(flutterRevision);
when(
() => codePushClientWrapper.getApp(appId: any(named: 'appId')),
).thenAnswer((_) async => appMetadata);
@@ -273,6 +247,9 @@ flutter:
supportedOperatingSystems: any(named: 'supportedOperatingSystems'),
),
).thenAnswer((_) async {});
when(
() => shorebirdVersionManager.fetchCurrentGitHash(),
).thenAnswer((_) async => flutterRevision);
command = runWithOverrides(ReleaseAndroidCommand.new)
..testArgResults = argResults;
@@ -307,13 +284,7 @@ flutter:
test('exits with code 70 when building fails', () async {
when(() => flutterBuildProcessResult.exitCode).thenReturn(1);
when(() => flutterBuildProcessResult.stderr).thenReturn('oops');
final tempDir = setUpTempDir();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.software.code));
});
@@ -322,13 +293,7 @@ flutter:
'Failed to extract version name from app bundle: oops',
);
when(() => bundletool.getVersionName(any())).thenThrow(exception);
final tempDir = setUpTempDir();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.software.code);
verify(() => progress.fail('$exception')).called(1);
});
@@ -338,13 +303,7 @@ flutter:
'Failed to extract version code from app bundle: oops',
);
when(() => bundletool.getVersionCode(any())).thenThrow(exception);
final tempDir = setUpTempDir();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.software.code);
verify(() => progress.fail('$exception')).called(1);
});
@@ -357,13 +316,7 @@ flutter:
defaultValue: any(named: 'defaultValue'),
),
).thenAnswer((_) => '1.0.0');
final tempDir = setUpTempDir();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.success.code);
verify(() => logger.info('Aborting.')).called(1);
verifyNever(
@@ -377,22 +330,13 @@ flutter:
});
test('throws error when unable to detect flutter revision', () async {
const error = 'oops';
when(() => flutterRevisionProcessResult.exitCode).thenReturn(1);
when(() => flutterRevisionProcessResult.stderr).thenReturn(error);
final tempDir = setUpTempDir();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exception = Exception('oops');
when(
() => shorebirdVersionManager.fetchCurrentGitHash(),
).thenThrow(exception);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.software.code);
verify(
() => progress.fail(
'Exception: Unable to determine flutter revision: $error',
),
).called(1);
verify(() => progress.fail('$exception')).called(1);
});
test(
@@ -400,13 +344,7 @@ flutter:
'when --release-version and --force are used', () async {
when(() => argResults['force']).thenReturn(true);
when(() => argResults['release-version']).thenReturn(version);
final tempDir = setUpTempDir();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
verify(() => logger.success('\n✅ Published Release!')).called(1);
expect(exitCode, ExitCode.success.code);
verifyNever(
@@ -415,13 +353,7 @@ flutter:
});
test('succeeds when release is successful', () async {
final tempDir = setUpTempDir();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
verify(() => logger.success('\n✅ Published Release!')).called(1);
verify(
() => codePushClientWrapper.createAndroidReleaseArtifacts(
@@ -445,13 +377,7 @@ flutter:
test('succeeds when release is successful (with apk)', () async {
when(() => argResults['artifact']).thenReturn('apk');
final tempDir = setUpTempDir();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
verify(() => logger.success('\n✅ Published Release!')).called(1);
verify(
() => codePushClientWrapper.createAndroidReleaseArtifacts(
@@ -480,18 +406,12 @@ flutter:
final target = p.join('lib', 'main_development.dart');
when(() => argResults['flavor']).thenReturn(flavor);
when(() => argResults['target']).thenReturn(target);
final tempDir = setUpTempDir();
File(
p.join(tempDir.path, 'shorebird.yaml'),
).writeAsStringSync('''
app_id: productionAppId
flavors:
development: $appId''');
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
const shorebirdYaml = ShorebirdYaml(
appId: 'productionAppId',
flavors: {flavor: appId},
);
when(() => shorebirdEnv.getShorebirdYaml()).thenReturn(shorebirdYaml);
final exitCode = await runWithOverrides(command.run);
verify(() => logger.success('\n✅ Published Release!')).called(1);
verify(
@@ -523,13 +443,7 @@ flavors:
releaseVersion: any(named: 'releaseVersion'),
),
).thenAnswer((_) async => release);
final tempDir = setUpTempDir();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.success.code);
verifyNever(
() => codePushClientWrapper.createRelease(
@@ -11,11 +11,14 @@ import 'package:shorebird_cli/src/archive_analysis/archive_analysis.dart';
import 'package:shorebird_cli/src/auth/auth.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/commands/commands.dart';
import 'package:shorebird_cli/src/config/config.dart';
import 'package:shorebird_cli/src/doctor.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/platform.dart';
import 'package:shorebird_cli/src/process.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
import 'package:shorebird_cli/src/shorebird_version_manager.dart';
import 'package:shorebird_cli/src/validators/validators.dart';
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
import 'package:test/test.dart';
@@ -46,8 +49,13 @@ class _MockShorebirdFlutterValidator extends Mock
class _MockShorebirdProcess extends Mock implements ShorebirdProcess {}
class _MockShorebirdEnv extends Mock implements ShorebirdEnv {}
class _MockShorebirdValidator extends Mock implements ShorebirdValidator {}
class _MockShorebirdVersionManager extends Mock
implements ShorebirdVersionManager {}
class _FakeRelease extends Fake implements Release {}
class _FakeShorebirdProcess extends Fake implements ShorebirdProcess {}
@@ -55,6 +63,7 @@ class _FakeShorebirdProcess extends Fake implements ShorebirdProcess {}
void main() {
group(ReleaseIosCommand, () {
const appId = 'test-app-id';
const shorebirdYaml = ShorebirdYaml(appId: appId);
const flutterRevision = '83305b5088e6fe327fb3334a73ff190828d85713';
const versionName = '1.2.3';
const versionCode = '1';
@@ -102,10 +111,11 @@ flutter:
late Progress progress;
late Logger logger;
late ShorebirdProcessResult flutterBuildProcessResult;
late ShorebirdProcessResult flutterRevisionProcessResult;
late ShorebirdFlutterValidator flutterValidator;
late ShorebirdProcess shorebirdProcess;
late ShorebirdEnv shorebirdEnv;
late ShorebirdValidator shorebirdValidator;
late ShorebirdVersionManager shorebirdVersionManager;
late ReleaseIosCommand command;
R runWithOverrides<R>(R Function() body) {
@@ -118,7 +128,11 @@ flutter:
loggerRef.overrideWith(() => logger),
platformRef.overrideWith(() => platform),
processRef.overrideWith(() => shorebirdProcess),
shorebirdEnvRef.overrideWith(() => shorebirdEnv),
shorebirdValidatorRef.overrideWith(() => shorebirdValidator),
shorebirdVersionManagerRef.overrideWith(
() => shorebirdVersionManager,
),
},
);
}
@@ -159,21 +173,14 @@ flutter:
progress = _MockProgress();
logger = _MockLogger();
flutterBuildProcessResult = _MockProcessResult();
flutterRevisionProcessResult = _MockProcessResult();
flutterValidator = _MockShorebirdFlutterValidator();
shorebirdProcess = _MockShorebirdProcess();
shorebirdEnv = _MockShorebirdEnv();
shorebirdValidator = _MockShorebirdValidator();
shorebirdVersionManager = _MockShorebirdVersionManager();
when(() => platform.script).thenReturn(
Uri.file(
p.join(
shorebirdRoot.path,
'bin',
'cache',
'shorebird.snapshot',
),
),
);
when(() => shorebirdEnv.getShorebirdYaml()).thenReturn(shorebirdYaml);
when(() => shorebirdEnv.shorebirdRoot).thenReturn(shorebirdRoot);
when(
() => shorebirdProcess.run(
'flutter',
@@ -181,14 +188,6 @@ flutter:
runInShell: any(named: 'runInShell'),
),
).thenAnswer((_) async => flutterBuildProcessResult);
when(
() => shorebirdProcess.run(
'git',
any(),
runInShell: any(named: 'runInShell'),
workingDirectory: any(named: 'workingDirectory'),
),
).thenAnswer((_) async => flutterRevisionProcessResult);
when(() => argResults.rest).thenReturn([]);
when(() => argResults['arch']).thenReturn(arch);
when(() => argResults['platform']).thenReturn(releasePlatform);
@@ -204,12 +203,6 @@ flutter:
when(
() => flutterBuildProcessResult.exitCode,
).thenReturn(ExitCode.success.code);
when(
() => flutterRevisionProcessResult.exitCode,
).thenReturn(ExitCode.success.code);
when(
() => flutterRevisionProcessResult.stdout,
).thenReturn(flutterRevision);
when(
() => codePushClientWrapper.getApp(appId: any(named: 'appId')),
).thenAnswer((_) async => appMetadata);
@@ -259,6 +252,9 @@ flutter:
supportedOperatingSystems: any(named: 'supportedOperatingSystems'),
),
).thenAnswer((_) async {});
when(
() => shorebirdVersionManager.fetchCurrentGitHash(),
).thenAnswer((_) async => flutterRevision);
command = runWithOverrides(() => ReleaseIosCommand(ipaReader: ipaReader))
..testArgResults = argResults;
@@ -455,9 +451,10 @@ error: exportArchive: No signing certificate "iOS Distribution" found
});
test('throws error when unable to detect flutter revision', () async {
const error = 'oops';
when(() => flutterRevisionProcessResult.exitCode).thenReturn(1);
when(() => flutterRevisionProcessResult.stderr).thenReturn(error);
final exception = Exception('oops');
when(
() => shorebirdVersionManager.fetchCurrentGitHash(),
).thenThrow(exception);
final tempDir = setUpTempDir();
final exitCode = await IOOverrides.runZoned(
@@ -465,11 +462,7 @@ error: exportArchive: No signing certificate "iOS Distribution" found
getCurrentDirectory: () => tempDir,
);
expect(exitCode, ExitCode.software.code);
verify(
() => progress.fail(
'Exception: Unable to determine flutter revision: $error',
),
).called(1);
verify(() => progress.fail('$exception')).called(1);
});
test(
@@ -9,11 +9,14 @@ import 'package:scoped/scoped.dart';
import 'package:shorebird_cli/src/auth/auth.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/commands/release/release.dart';
import 'package:shorebird_cli/src/config/config.dart';
import 'package:shorebird_cli/src/doctor.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/platform.dart';
import 'package:shorebird_cli/src/process.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
import 'package:shorebird_cli/src/shorebird_version_manager.dart';
import 'package:shorebird_cli/src/validators/validators.dart';
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
import 'package:test/test.dart';
@@ -40,8 +43,13 @@ class _MockShorebirdFlutterValidator extends Mock
class _MockShorebirdProcess extends Mock implements ShorebirdProcess {}
class _MockShorebirdEnv extends Mock implements ShorebirdEnv {}
class _MockShorebirdValidator extends Mock implements ShorebirdValidator {}
class _MockShorebirdVersionManager extends Mock
implements ShorebirdVersionManager {}
class _FakeRelease extends Fake implements Release {}
class _FakeShorebirdProcess extends Fake implements ShorebirdProcess {}
@@ -49,6 +57,7 @@ class _FakeShorebirdProcess extends Fake implements ShorebirdProcess {}
void main() {
group(ReleaseIosFrameworkCommand, () {
const appId = 'test-app-id';
const shorebirdYaml = ShorebirdYaml(appId: appId);
const flutterRevision = '83305b5088e6fe327fb3334a73ff190828d85713';
const versionName = '1.2.3';
const versionCode = '1';
@@ -83,10 +92,11 @@ flutter:
late Progress progress;
late Logger logger;
late ShorebirdProcessResult flutterBuildProcessResult;
late ShorebirdProcessResult flutterRevisionProcessResult;
late ShorebirdFlutterValidator flutterValidator;
late ShorebirdProcess shorebirdProcess;
late ShorebirdEnv shorebirdEnv;
late ShorebirdValidator shorebirdValidator;
late ShorebirdVersionManager shorebirdVersionManager;
late ReleaseIosFrameworkCommand command;
R runWithOverrides<R>(R Function() body) {
@@ -99,7 +109,11 @@ flutter:
loggerRef.overrideWith(() => logger),
platformRef.overrideWith(() => platform),
processRef.overrideWith(() => shorebirdProcess),
shorebirdEnvRef.overrideWith(() => shorebirdEnv),
shorebirdValidatorRef.overrideWith(() => shorebirdValidator),
shorebirdVersionManagerRef.overrideWith(
() => shorebirdVersionManager,
),
},
);
}
@@ -132,21 +146,14 @@ flutter:
progress = _MockProgress();
logger = _MockLogger();
flutterBuildProcessResult = _MockProcessResult();
flutterRevisionProcessResult = _MockProcessResult();
flutterValidator = _MockShorebirdFlutterValidator();
shorebirdProcess = _MockShorebirdProcess();
shorebirdEnv = _MockShorebirdEnv();
shorebirdValidator = _MockShorebirdValidator();
shorebirdVersionManager = _MockShorebirdVersionManager();
when(() => platform.script).thenReturn(
Uri.file(
p.join(
shorebirdRoot.path,
'bin',
'cache',
'shorebird.snapshot',
),
),
);
when(() => shorebirdEnv.getShorebirdYaml()).thenReturn(shorebirdYaml);
when(() => shorebirdEnv.shorebirdRoot).thenReturn(shorebirdRoot);
when(
() => shorebirdProcess.run(
'flutter',
@@ -154,14 +161,6 @@ flutter:
runInShell: any(named: 'runInShell'),
),
).thenAnswer((_) async => flutterBuildProcessResult);
when(
() => shorebirdProcess.run(
'git',
any(),
runInShell: any(named: 'runInShell'),
workingDirectory: any(named: 'workingDirectory'),
),
).thenAnswer((_) async => flutterRevisionProcessResult);
when(() => argResults['release-version']).thenReturn(version);
when(() => argResults['force']).thenReturn(false);
when(() => argResults.rest).thenReturn([]);
@@ -170,12 +169,6 @@ flutter:
when(
() => flutterBuildProcessResult.exitCode,
).thenReturn(ExitCode.success.code);
when(
() => flutterRevisionProcessResult.exitCode,
).thenReturn(ExitCode.success.code);
when(
() => flutterRevisionProcessResult.stdout,
).thenReturn(flutterRevision);
when(() => logger.progress(any())).thenReturn(progress);
when(() => logger.confirm(any())).thenReturn(true);
when(() => platform.operatingSystem).thenReturn(Platform.macOS);
@@ -225,6 +218,9 @@ flutter:
supportedOperatingSystems: any(named: 'supportedOperatingSystems'),
),
).thenAnswer((_) async {});
when(
() => shorebirdVersionManager.fetchCurrentGitHash(),
).thenAnswer((_) async => flutterRevision);
command = runWithOverrides(ReleaseIosFrameworkCommand.new)
..testArgResults = argResults;
@@ -319,9 +315,10 @@ flutter:
});
test('throws error when unable to detect flutter revision', () async {
const error = 'oops';
when(() => flutterRevisionProcessResult.exitCode).thenReturn(1);
when(() => flutterRevisionProcessResult.stderr).thenReturn(error);
final exception = Exception('oops');
when(
() => shorebirdVersionManager.fetchCurrentGitHash(),
).thenThrow(exception);
final tempDir = setUpTempDir();
final exitCode = await IOOverrides.runZoned(
@@ -329,11 +326,7 @@ flutter:
getCurrentDirectory: () => tempDir,
);
expect(exitCode, ExitCode.software.code);
verify(
() => progress.fail(
'Exception: Unable to determine flutter revision: $error',
),
).called(1);
verify(() => progress.fail('$exception')).called(1);
});
test(
@@ -1,55 +1,45 @@
import 'dart:io';
import 'package:args/args.dart';
import 'package:http/http.dart' as http;
import 'package:mason_logger/mason_logger.dart';
import 'package:mocktail/mocktail.dart';
import 'package:path/path.dart' as p;
import 'package:scoped/scoped.dart';
import 'package:shorebird_cli/src/auth/auth.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/commands/releases/releases.dart';
import 'package:shorebird_cli/src/config/config.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
import 'package:test/test.dart';
class _MockArgResults extends Mock implements ArgResults {}
class _MockAuth extends Mock implements Auth {}
class _MockCodePushClientWrapper extends Mock
implements CodePushClientWrapper {}
class _MockCodePushClient extends Mock implements CodePushClient {}
class _MockHttpClient extends Mock implements http.Client {}
class _MockLogger extends Mock implements Logger {}
class _MockProgress extends Mock implements Progress {}
class _MockShorebirdEnv extends Mock implements ShorebirdEnv {}
class _MockShorebirdValidator extends Mock implements ShorebirdValidator {}
void main() {
group(DeleteReleasesCommand, () {
const appId = 'test-app-id';
const shorebirdYaml = ShorebirdYaml(appId: appId);
const flutterRevision = '83305b5088e6fe327fb3334a73ff190828d85713';
const releaseId = 3;
const versionNumber = '1.0.0';
const pubspecYamlContent = '''
name: example
version: 1.0.1
environment:
sdk: ">=2.19.0 <3.0.0"
flutter:
assets:
- shorebird.yaml''';
late ArgResults argResults;
late http.Client httpClient;
late Auth auth;
late Logger logger;
late CodePushClientWrapper codePushClientWrapper;
late CodePushClient codePushClient;
late Progress progress;
late ShorebirdEnv shorebirdEnv;
late ShorebirdValidator shorebirdValidator;
late DeleteReleasesCommand command;
@@ -57,38 +47,28 @@ flutter:
return runScoped(
body,
values: {
authRef.overrideWith(() => auth),
codePushClientWrapperRef.overrideWith(() => codePushClientWrapper),
loggerRef.overrideWith(() => logger),
shorebirdEnvRef.overrideWith(() => shorebirdEnv),
shorebirdValidatorRef.overrideWith(() => shorebirdValidator),
},
);
}
Directory setUpTempDir() {
final tempDir = Directory.systemTemp.createTempSync();
File(
p.join(tempDir.path, 'pubspec.yaml'),
).writeAsStringSync(pubspecYamlContent);
File(
p.join(tempDir.path, 'shorebird.yaml'),
).writeAsStringSync('app_id: $appId');
return tempDir;
}
setUp(() {
argResults = _MockArgResults();
httpClient = _MockHttpClient();
auth = _MockAuth();
logger = _MockLogger();
codePushClientWrapper = _MockCodePushClientWrapper();
codePushClient = _MockCodePushClient();
progress = _MockProgress();
shorebirdEnv = _MockShorebirdEnv();
shorebirdValidator = _MockShorebirdValidator();
when(() => argResults['version']).thenReturn(versionNumber);
when(() => auth.isAuthenticated).thenReturn(true);
when(() => auth.client).thenReturn(httpClient);
when(
() => codePushClientWrapper.codePushClient,
).thenReturn(codePushClient);
when(() => shorebirdEnv.getShorebirdYaml()).thenReturn(shorebirdYaml);
when(() => codePushClient.getReleases(appId: any(named: 'appId')))
.thenAnswer(
(_) async => [
@@ -129,16 +109,8 @@ flutter:
),
).thenAnswer((_) async {});
command = runWithOverrides(
() => DeleteReleasesCommand(
buildCodePushClient: ({
required http.Client httpClient,
Uri? hostedUri,
}) {
return codePushClient;
},
),
)..testArgResults = argResults;
command = runWithOverrides(DeleteReleasesCommand.new)
..testArgResults = argResults;
});
test('returns correct description', () {
@@ -172,11 +144,7 @@ flutter:
when(() => argResults['version']).thenReturn(null);
when(() => logger.prompt(any())).thenReturn(versionNumber);
final tempDir = setUpTempDir();
await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
await runWithOverrides(command.run);
verify(
() => logger.prompt(
@@ -187,11 +155,7 @@ flutter:
test('does not prompt for version if user provides it with a flag',
() async {
final tempDir = setUpTempDir();
await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
await runWithOverrides(command.run);
verifyNever(() => logger.prompt(any()));
});
@@ -200,11 +164,7 @@ flutter:
() => codePushClient.getReleases(appId: any(named: 'appId')),
).thenThrow(Exception('oops'));
final tempDir = setUpTempDir();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.software.code);
});
@@ -212,11 +172,7 @@ flutter:
test('aborts when user does not confirm', () async {
when(() => logger.confirm(any())).thenReturn(false);
final tempDir = setUpTempDir();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.success.code);
verifyNever(
@@ -231,11 +187,7 @@ flutter:
test('returns software error when release is not found', () async {
when(() => argResults['version']).thenReturn('asdf');
final tempDir = setUpTempDir();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.software.code);
verify(() => logger.err('No release found for version "asdf"')).called(1);
@@ -255,11 +207,7 @@ flutter:
),
).thenThrow(Exception('oops'));
final tempDir = setUpTempDir();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.software.code);
verify(() => progress.fail(any(that: contains('oops')))).called(1);
@@ -276,11 +224,7 @@ flutter:
),
).thenAnswer((_) async {});
final tempDir = setUpTempDir();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.success.code);
verify(
@@ -294,13 +238,11 @@ flutter:
test('uses correct app_id when flavor is specified', () async {
const flavor = 'development';
when(() => argResults['flavor']).thenReturn(flavor);
final tempDir = setUpTempDir();
File(
p.join(tempDir.path, 'shorebird.yaml'),
).writeAsStringSync('''
app_id: productionAppId
flavors:
$flavor: $appId''');
const shorebirdYaml = ShorebirdYaml(
appId: 'productionAppId',
flavors: {flavor: appId},
);
when(() => shorebirdEnv.getShorebirdYaml()).thenReturn(shorebirdYaml);
when(
() => codePushClient.deleteRelease(
appId: any(named: 'appId'),
@@ -308,10 +250,7 @@ flavors:
),
).thenAnswer((_) async {});
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.success.code);
verify(() => codePushClient.getReleases(appId: appId)).called(1);
@@ -1,86 +1,67 @@
import 'dart:io';
import 'package:args/args.dart';
import 'package:http/http.dart' as http;
import 'package:mason_logger/mason_logger.dart';
import 'package:mocktail/mocktail.dart';
import 'package:path/path.dart' as p;
import 'package:scoped/scoped.dart';
import 'package:shorebird_cli/src/auth/auth.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/commands/commands.dart';
import 'package:shorebird_cli/src/config/config.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
import 'package:test/test.dart';
class _MockArgResults extends Mock implements ArgResults {}
class _MockAuth extends Mock implements Auth {}
class _MockCodePushClientWrapper extends Mock
implements CodePushClientWrapper {}
class _MockCodePushClient extends Mock implements CodePushClient {}
class _MockHttpClient extends Mock implements http.Client {}
class _MockLogger extends Mock implements Logger {}
class _MockShorebirdEnv extends Mock implements ShorebirdEnv {}
class _MockShorebirdValidator extends Mock implements ShorebirdValidator {}
void main() {
group(ListReleasesCommand, () {
const appId = 'test-app-id';
const shorebirdYaml = ShorebirdYaml(appId: appId);
const flutterRevision = '83305b5088e6fe327fb3334a73ff190828d85713';
late ArgResults argResults;
late Auth auth;
late http.Client httpClient;
late CodePushClientWrapper codePushClientWrapper;
late CodePushClient codePushClient;
late Logger logger;
late ShorebirdEnv shorebirdEnv;
late ShorebirdValidator shorebirdValidator;
late ListReleasesCommand command;
const pubspecYamlContent = '''
name: example
version: 1.0.1
environment:
sdk: ">=2.19.0 <3.0.0"
flutter:
assets:
- shorebird.yaml''';
R runWithOverrides<R>(R Function() body) {
return runScoped(
body,
values: {
authRef.overrideWith(() => auth),
codePushClientWrapperRef.overrideWith(() => codePushClientWrapper),
loggerRef.overrideWith(() => logger),
shorebirdEnvRef.overrideWith(() => shorebirdEnv),
shorebirdValidatorRef.overrideWith(() => shorebirdValidator),
},
);
}
Directory setUpTempDir() {
final tempDir = Directory.systemTemp.createTempSync();
File(
p.join(tempDir.path, 'pubspec.yaml'),
).writeAsStringSync(pubspecYamlContent);
File(
p.join(tempDir.path, 'shorebird.yaml'),
).writeAsStringSync('app_id: $appId');
return tempDir;
}
setUp(() {
argResults = _MockArgResults();
auth = _MockAuth();
codePushClientWrapper = _MockCodePushClientWrapper();
codePushClient = _MockCodePushClient();
httpClient = _MockHttpClient();
logger = _MockLogger();
shorebirdEnv = _MockShorebirdEnv();
shorebirdValidator = _MockShorebirdValidator();
when(() => auth.client).thenReturn(httpClient);
when(() => auth.isAuthenticated).thenReturn(true);
when(
() => codePushClientWrapper.codePushClient,
).thenReturn(codePushClient);
when(() => shorebirdEnv.getShorebirdYaml()).thenReturn(shorebirdYaml);
when(
() => shorebirdValidator.validatePreconditions(
checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'),
@@ -88,13 +69,8 @@ flutter:
),
).thenAnswer((_) async {});
command = runWithOverrides(
() => ListReleasesCommand(
buildCodePushClient: ({required httpClient, hostedUri}) {
return codePushClient;
},
),
)..testArgResults = argResults;
command = runWithOverrides(ListReleasesCommand.new)
..testArgResults = argResults;
});
test('description is correct', () {
@@ -125,25 +101,18 @@ flutter:
when(
() => codePushClient.getReleases(appId: any(named: 'appId')),
).thenThrow(Exception());
final tempDir = setUpTempDir();
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.software.code);
});
test('returns ExitCode.success when releases is empty', () async {
when(() => codePushClient.getReleases(appId: appId))
.thenAnswer((_) async => []);
final tempDir = setUpTempDir();
when(
() => codePushClient.getReleases(appId: appId),
).thenAnswer((_) async => []);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.success.code);
verify(() => logger.info('(empty)')).called(1);
@@ -152,13 +121,11 @@ flutter:
test('uses correct app_id when flavor is specified', () async {
const flavor = 'development';
when(() => argResults['flavor']).thenReturn(flavor);
final tempDir = setUpTempDir();
File(
p.join(tempDir.path, 'shorebird.yaml'),
).writeAsStringSync('''
app_id: productionAppId
flavors:
$flavor: $appId''');
const shorebirdYaml = ShorebirdYaml(
appId: 'productionAppId',
flavors: {flavor: appId},
);
when(() => shorebirdEnv.getShorebirdYaml()).thenReturn(shorebirdYaml);
when(() => codePushClient.getReleases(appId: appId)).thenAnswer(
(_) async => [
const Release(
@@ -172,10 +139,7 @@ flavors:
],
);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.success.code);
verify(
@@ -190,8 +154,6 @@ flavors:
test('returns ExitCode.success and prints releases when releases exist',
() async {
final tempDir = setUpTempDir();
when(() => codePushClient.getReleases(appId: appId)).thenAnswer(
(_) async => [
const Release(
@@ -213,10 +175,7 @@ flavors:
],
);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, ExitCode.success.code);
verify(
@@ -3,26 +3,19 @@ import 'dart:convert';
import 'dart:io';
import 'package:args/args.dart';
import 'package:http/http.dart' as http;
import 'package:mason_logger/mason_logger.dart';
import 'package:mocktail/mocktail.dart';
import 'package:scoped/scoped.dart';
import 'package:shorebird_cli/src/auth/auth.dart';
import 'package:shorebird_cli/src/commands/run_command.dart';
import 'package:shorebird_cli/src/doctor.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/process.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
import 'package:shorebird_cli/src/validators/validators.dart';
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
import 'package:test/test.dart';
class _MockArgResults extends Mock implements ArgResults {}
class _MockHttpClient extends Mock implements http.Client {}
class _MockAuth extends Mock implements Auth {}
class _MockDoctor extends Mock implements Doctor {}
class _MockLogger extends Mock implements Logger {}
@@ -31,8 +24,6 @@ class _MockProgress extends Mock implements Progress {}
class _MockProcess extends Mock implements Process {}
class _MockCodePushClient extends Mock implements CodePushClient {}
class _MockShorebirdProcess extends Mock implements ShorebirdProcess {}
class _MockIOSink extends Mock implements IOSink {}
@@ -44,12 +35,9 @@ class _MockValidator extends Mock implements Validator {}
void main() {
group(RunCommand, () {
late ArgResults argResults;
late http.Client httpClient;
late Auth auth;
late Doctor doctor;
late Logger logger;
late Process process;
late CodePushClient codePushClient;
late ShorebirdProcess shorebirdProcess;
late IOSink ioSink;
late ShorebirdValidator shorebirdValidator;
@@ -60,7 +48,6 @@ void main() {
return runScoped(
body,
values: {
authRef.overrideWith(() => auth),
doctorRef.overrideWith(() => doctor),
loggerRef.overrideWith(() => logger),
processRef.overrideWith(() => shorebirdProcess),
@@ -75,13 +62,10 @@ void main() {
setUp(() {
argResults = _MockArgResults();
httpClient = _MockHttpClient();
auth = _MockAuth();
doctor = _MockDoctor();
logger = _MockLogger();
process = _MockProcess();
shorebirdProcess = _MockShorebirdProcess();
codePushClient = _MockCodePushClient();
ioSink = _MockIOSink();
shorebirdValidator = _MockShorebirdValidator();
validator = _MockValidator();
@@ -94,8 +78,6 @@ void main() {
),
).thenAnswer((_) async => process);
when(() => argResults.rest).thenReturn([]);
when(() => auth.isAuthenticated).thenReturn(true);
when(() => auth.client).thenReturn(httpClient);
when(() => doctor.allValidators).thenReturn([validator]);
when(() => logger.progress(any())).thenReturn(_MockProgress());
when(() => ioSink.addStream(any())).thenAnswer((_) async {});
@@ -106,16 +88,7 @@ void main() {
),
).thenAnswer((_) async {});
command = runWithOverrides(
() => RunCommand(
buildCodePushClient: ({
required http.Client httpClient,
Uri? hostedUri,
}) {
return codePushClient;
},
),
)..testArgResults = argResults;
command = runWithOverrides(RunCommand.new)..testArgResults = argResults;
});
test('command is hidden', () {
@@ -6,6 +6,7 @@ import 'package:scoped/scoped.dart';
import 'package:shorebird_cli/src/commands/commands.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/process.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_version_manager.dart';
import 'package:test/test.dart';
@@ -17,6 +18,8 @@ class _MockProgress extends Mock implements Progress {}
class _MockShorebirdProcess extends Mock implements ShorebirdProcess {}
class _MockShorebirdEnv extends Mock implements ShorebirdEnv {}
class _MockShorebirdVersionManager extends Mock
implements ShorebirdVersionManager {}
@@ -28,6 +31,7 @@ void main() {
late Logger logger;
late ShorebirdProcessResult pruneFlutterOriginResult;
late ShorebirdProcess shorebirdProcess;
late ShorebirdEnv shorebirdEnv;
late ShorebirdVersionManager shorebirdVersionManager;
late UpgradeCommand command;
@@ -37,8 +41,10 @@ void main() {
values: {
loggerRef.overrideWith(() => logger),
processRef.overrideWith(() => shorebirdProcess),
shorebirdVersionManagerRef
.overrideWith(() => shorebirdVersionManager),
shorebirdEnvRef.overrideWith(() => shorebirdEnv),
shorebirdVersionManagerRef.overrideWith(
() => shorebirdVersionManager,
),
},
);
}
@@ -50,9 +56,13 @@ void main() {
logger = _MockLogger();
pruneFlutterOriginResult = _MockProcessResult();
shorebirdProcess = _MockShorebirdProcess();
shorebirdEnv = _MockShorebirdEnv();
shorebirdVersionManager = _MockShorebirdVersionManager();
command = runWithOverrides(UpgradeCommand.new);
when(
() => shorebirdEnv.flutterDirectory,
).thenReturn(Directory('flutter'));
when(
shorebirdVersionManager.fetchCurrentGitHash,
).thenAnswer((_) async => currentShorebirdRevision);
@@ -11,6 +11,7 @@ import 'package:shorebird_cli/src/ios_deploy.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/platform.dart';
import 'package:shorebird_cli/src/process.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:test/test.dart';
class _MockLogger extends Mock implements Logger {}
@@ -27,6 +28,8 @@ class _MockIOSink extends Mock implements IOSink {}
class _MockProcessSignal extends Mock implements ProcessSignal {}
class _MockShorebirdEnv extends Mock implements ShorebirdEnv {}
void main() {
group(IOSDeploy, () {
late Logger logger;
@@ -35,6 +38,7 @@ void main() {
late ShorebirdProcess shorebirdProcess;
late Process process;
late IOSink ioSink;
late ShorebirdEnv shorebirdEnv;
late IOSDeploy iosDeploy;
R runWithOverrides<R>(R Function() body) {
@@ -44,6 +48,7 @@ void main() {
loggerRef.overrideWith(() => logger),
platformRef.overrideWith(() => platform),
processRef.overrideWith(() => shorebirdProcess),
shorebirdEnvRef.overrideWith(() => shorebirdEnv),
},
);
}
@@ -55,14 +60,15 @@ void main() {
process = _MockProcess();
progress = _MockProgress();
ioSink = _MockIOSink();
shorebirdEnv = _MockShorebirdEnv();
iosDeploy = IOSDeploy();
final tempDir = Directory.systemTemp.createTempSync();
final shorebirdScriptFile = File(
p.join(tempDir.path, 'bin', 'cache', 'shorebird.snapshot'),
)..create(recursive: true);
when(() => platform.script).thenReturn(shorebirdScriptFile.uri);
when(() => shorebirdEnv.shorebirdRoot).thenReturn(tempDir);
when(() => shorebirdEnv.flutterDirectory).thenReturn(
Directory(p.join(tempDir.path, 'bin', 'cache', 'flutter')),
);
when(() => logger.progress(any())).thenReturn(progress);
when(
() => shorebirdProcess.start(any(), any()),
@@ -0,0 +1,419 @@
import 'dart:io';
import 'package:mocktail/mocktail.dart';
import 'package:path/path.dart' as p;
import 'package:platform/platform.dart';
import 'package:pubspec_parse/pubspec_parse.dart';
import 'package:scoped/scoped.dart';
import 'package:shorebird_cli/src/platform.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:test/test.dart';
class _MockPlatform extends Mock implements Platform {}
void main() {
group(ShorebirdEnv, () {
late Platform platform;
late Directory shorebirdRoot;
late Uri platformScript;
late ShorebirdEnv shorebirdEnv;
R runWithOverrides<R>(R Function() body) {
return runScoped(
() => body(),
values: {
platformRef.overrideWith(() => platform),
},
);
}
setUp(() {
shorebirdRoot = Directory.systemTemp.createTempSync();
platformScript = Uri.file(
p.join(shorebirdRoot.path, 'bin', 'cache', 'shorebird.snapshot'),
);
platform = _MockPlatform();
shorebirdEnv = runWithOverrides(ShorebirdEnv.new);
when(() => platform.environment).thenReturn(const {});
when(() => platform.script).thenReturn(platformScript);
});
group('flutterBinaryFile', () {
test('returns correct path', () {
expect(
runWithOverrides(() => shorebirdEnv.flutterBinaryFile.path),
equals(
p.join(
shorebirdRoot.path,
'bin',
'cache',
'flutter',
'bin',
'flutter',
),
),
);
});
});
group('genSnapshotFile', () {
test('returns correct path', () {
expect(
runWithOverrides(() => shorebirdEnv.genSnapshotFile.path),
equals(
p.join(
shorebirdRoot.path,
'bin',
'cache',
'flutter',
'bin',
'cache',
'artifacts',
'engine',
'ios-release',
'gen_snapshot_arm64',
),
),
);
});
});
group('getPubspecYamlFile', () {
test('returns correct file', () {
final tempDir = Directory('temp');
expect(
IOOverrides.runZoned(
() {
return runWithOverrides(
() => shorebirdEnv.getPubspecYamlFile().path,
);
},
getCurrentDirectory: () => tempDir,
),
equals(p.join(tempDir.path, 'pubspec.yaml')),
);
});
});
group('getPubspecYaml', () {
test('returns null when pubspec.yaml does not exist', () {
final tempDir = Directory('temp');
expect(
IOOverrides.runZoned(
() => runWithOverrides(() => shorebirdEnv.getPubspecYaml()),
getCurrentDirectory: () => tempDir,
),
isNull,
);
});
test('returns value when pubspec.yaml exists', () {
final tempDir = Directory.systemTemp.createTempSync();
File(
p.join(tempDir.path, 'pubspec.yaml'),
).writeAsStringSync('name: test');
expect(
IOOverrides.runZoned(
() => runWithOverrides(() => shorebirdEnv.getPubspecYaml()),
getCurrentDirectory: () => tempDir,
),
isA<Pubspec>().having((p) => p.name, 'name', 'test'),
);
});
});
group('hasPubspecYaml', () {
test('returns false when pubspec.yaml does not exist', () {
final tempDir = Directory('temp');
expect(
IOOverrides.runZoned(
() => runWithOverrides(() => shorebirdEnv.hasPubspecYaml),
getCurrentDirectory: () => tempDir,
),
isFalse,
);
});
test('returns true when pubspec.yaml does exist', () {
final tempDir = Directory.systemTemp.createTempSync();
File(
p.join(tempDir.path, 'pubspec.yaml'),
).writeAsStringSync('name: test');
expect(
IOOverrides.runZoned(
() => runWithOverrides(() => shorebirdEnv.hasPubspecYaml),
getCurrentDirectory: () => tempDir,
),
isTrue,
);
});
});
group('hasShorebirdYaml', () {
test('returns false when shorebird.yaml does not exist', () {
final tempDir = Directory('temp');
expect(
IOOverrides.runZoned(
() => runWithOverrides(() => shorebirdEnv.hasShorebirdYaml),
getCurrentDirectory: () => tempDir,
),
isFalse,
);
});
test('returns true when shorebird.yaml does exist', () {
final tempDir = Directory.systemTemp.createTempSync();
File(
p.join(tempDir.path, 'shorebird.yaml'),
).writeAsStringSync('app_id: test-app-id');
expect(
IOOverrides.runZoned(
() => runWithOverrides(() => shorebirdEnv.hasShorebirdYaml),
getCurrentDirectory: () => tempDir,
),
isTrue,
);
});
});
group('pubspecContainsShorebirdYaml', () {
test(
'returns false when pubspec.yaml does not '
'contain shorebird.yaml in assets', () {
final tempDir = Directory.systemTemp.createTempSync();
File(
p.join(tempDir.path, 'pubspec.yaml'),
).writeAsStringSync('name: test');
expect(
IOOverrides.runZoned(
() => runWithOverrides(
() => shorebirdEnv.pubspecContainsShorebirdYaml,
),
getCurrentDirectory: () => tempDir,
),
isFalse,
);
});
test(
'returns true when pubspec.yaml does '
'contain shorebird.yaml in assets', () {
final tempDir = Directory.systemTemp.createTempSync();
File(
p.join(tempDir.path, 'pubspec.yaml'),
).writeAsStringSync('''
name: test
flutter:
assets:
- shorebird.yaml
''');
expect(
IOOverrides.runZoned(
() => runWithOverrides(
() => shorebirdEnv.pubspecContainsShorebirdYaml,
),
getCurrentDirectory: () => tempDir,
),
isTrue,
);
});
});
group('androidPackageName', () {
test('returns null when pubspec.yaml does not contain android module',
() {
final tempDir = Directory.systemTemp.createTempSync();
File(
p.join(tempDir.path, 'pubspec.yaml'),
).writeAsStringSync('name: test');
expect(
IOOverrides.runZoned(
() => runWithOverrides(() => shorebirdEnv.androidPackageName),
getCurrentDirectory: () => tempDir,
),
isNull,
);
});
test(
'returns correct package name when '
'pubspec.yaml contains android module', () {
final tempDir = Directory.systemTemp.createTempSync();
File(
p.join(tempDir.path, 'pubspec.yaml'),
).writeAsStringSync('''
name: test
flutter:
module:
androidPackage: test-package
''');
expect(
IOOverrides.runZoned(
() => runWithOverrides(() => shorebirdEnv.androidPackageName),
getCurrentDirectory: () => tempDir,
),
equals('test-package'),
);
});
});
group('isShorebirdInitialized', () {
test('returns false when shorebird.yaml does not exist', () {
final tempDir = Directory('temp');
expect(
IOOverrides.runZoned(
() => runWithOverrides(() => shorebirdEnv.isShorebirdInitialized),
getCurrentDirectory: () => tempDir,
),
isFalse,
);
});
test(
'returns false when shorebird.yaml exists '
'but pubspec does not contain shorebird.yaml', () {
final tempDir = Directory.systemTemp.createTempSync();
File(
p.join(tempDir.path, 'shorebird.yaml'),
).writeAsStringSync('app_id: test-app-id');
File(
p.join(tempDir.path, 'pubspec.yaml'),
).writeAsStringSync('name: test');
expect(
IOOverrides.runZoned(
() => runWithOverrides(() => shorebirdEnv.isShorebirdInitialized),
getCurrentDirectory: () => tempDir,
),
isFalse,
);
});
test(
'returns false when shorebird.yaml does not exist '
'but pubspec contains shorebird.yaml', () {
final tempDir = Directory.systemTemp.createTempSync();
File(
p.join(tempDir.path, 'pubspec.yaml'),
).writeAsStringSync('''
name: test
flutter:
assets:
- shorebird.yaml''');
expect(
IOOverrides.runZoned(
() => runWithOverrides(() => shorebirdEnv.isShorebirdInitialized),
getCurrentDirectory: () => tempDir,
),
isFalse,
);
});
test(
'returns true when shorebird.yaml exists '
'and pubspec contains shorebird.yaml', () {
final tempDir = Directory.systemTemp.createTempSync();
File(
p.join(tempDir.path, 'shorebird.yaml'),
).writeAsStringSync('app_id: test-app-id');
File(
p.join(tempDir.path, 'pubspec.yaml'),
).writeAsStringSync('''
name: test
flutter:
assets:
- shorebird.yaml''');
expect(
IOOverrides.runZoned(
() => runWithOverrides(() => shorebirdEnv.isShorebirdInitialized),
getCurrentDirectory: () => tempDir,
),
isTrue,
);
});
});
group('flutterRevision', () {
test('returns correct revision', () {
const revision = 'test-revision';
File(p.join(shorebirdRoot.path, 'bin', 'internal', 'flutter.version'))
..createSync(recursive: true)
..writeAsStringSync(revision, flush: true);
expect(
runWithOverrides(() => shorebirdEnv.flutterRevision),
equals(revision),
);
});
test('trims revision file content', () {
const revision = '''
test-revision
\r\n
''';
File(p.join(shorebirdRoot.path, 'bin', 'internal', 'flutter.version'))
..createSync(recursive: true)
..writeAsStringSync(revision, flush: true);
expect(
runWithOverrides(() => shorebirdEnv.flutterRevision),
'test-revision',
);
});
});
group('shorebirdEngineRevision', () {
test('returns correct revision', () {
const revision = 'test-revision';
File(
p.join(
shorebirdRoot.path,
'bin',
'cache',
'flutter',
'bin',
'internal',
'engine.version',
),
)
..createSync(recursive: true)
..writeAsStringSync(revision, flush: true);
expect(
runWithOverrides(() => shorebirdEnv.shorebirdEngineRevision),
equals(revision),
);
});
});
group('hostedUrl', () {
test('returns hosted url from env if available', () {
when(() => platform.environment).thenReturn({
'SHOREBIRD_HOSTED_URL': 'https://example.com',
});
expect(
runWithOverrides(() => shorebirdEnv.hostedUri),
equals(Uri.parse('https://example.com')),
);
});
test('falls back to shorebird.yaml', () {
final directory = Directory.systemTemp.createTempSync();
File(p.join(directory.path, 'shorebird.yaml')).writeAsStringSync('''
app_id: test-id
base_url: https://example.com''');
expect(
IOOverrides.runZoned(
() => runWithOverrides(() => shorebirdEnv.hostedUri),
getCurrentDirectory: () => directory,
),
equals(Uri.parse('https://example.com')),
);
});
test('returns null when there is no env override or shorebird.yaml', () {
expect(runWithOverrides(() => shorebirdEnv.hostedUri), isNull);
});
});
});
}
@@ -1,122 +0,0 @@
import 'dart:io';
import 'package:mocktail/mocktail.dart';
import 'package:path/path.dart' as p;
import 'package:platform/platform.dart';
import 'package:scoped/scoped.dart';
import 'package:shorebird_cli/src/platform.dart';
import 'package:shorebird_cli/src/shorebird_environment.dart';
import 'package:test/test.dart';
class _MockPlatform extends Mock implements Platform {}
void main() {
group('ShorebirdEnvironment', () {
late Platform platform;
late Directory shorebirdRoot;
late Uri platformScript;
R runWithOverrides<R>(R Function() body) {
return runScoped(
() => body(),
values: {
platformRef.overrideWith(() => platform),
},
);
}
setUp(() {
shorebirdRoot = Directory.systemTemp.createTempSync();
platformScript = Uri.file(
p.join(shorebirdRoot.path, 'bin', 'cache', 'shorebird.snapshot'),
);
platform = _MockPlatform();
when(() => platform.environment).thenReturn(const {});
when(() => platform.script).thenReturn(platformScript);
});
group('flutterRevision', () {
test('returns correct revision', () {
const revision = 'test-revision';
File(p.join(shorebirdRoot.path, 'bin', 'internal', 'flutter.version'))
..createSync(recursive: true)
..writeAsStringSync(revision, flush: true);
expect(
runWithOverrides(() => ShorebirdEnvironment.flutterRevision),
equals(revision),
);
});
test('trims revision file content', () {
const revision = '''
test-revision
\r\n
''';
File(p.join(shorebirdRoot.path, 'bin', 'internal', 'flutter.version'))
..createSync(recursive: true)
..writeAsStringSync(revision, flush: true);
expect(
runWithOverrides(() => ShorebirdEnvironment.flutterRevision),
'test-revision',
);
});
});
group('shorebirdEngineRevision', () {
test('returns correct revision', () {
const revision = 'test-revision';
File(
p.join(
shorebirdRoot.path,
'bin',
'cache',
'flutter',
'bin',
'internal',
'engine.version',
),
)
..createSync(recursive: true)
..writeAsStringSync(revision, flush: true);
expect(
runWithOverrides(() => ShorebirdEnvironment.shorebirdEngineRevision),
equals(revision),
);
});
});
group('hostedUrl', () {
test('returns hosted url from env if available', () {
when(() => platform.environment).thenReturn({
'SHOREBIRD_HOSTED_URL': 'https://example.com',
});
expect(
runWithOverrides(() => ShorebirdEnvironment.hostedUri),
equals(Uri.parse('https://example.com')),
);
});
test('falls back to shorebird.yaml', () {
final directory = Directory.systemTemp.createTempSync();
File(p.join(directory.path, 'shorebird.yaml')).writeAsStringSync('''
app_id: test-id
base_url: https://example.com''');
expect(
IOOverrides.runZoned(
() => runWithOverrides(() => ShorebirdEnvironment.hostedUri),
getCurrentDirectory: () => directory,
),
equals(Uri.parse('https://example.com')),
);
});
test('returns null when there is no env override or shorebird.yaml', () {
expect(runWithOverrides(() => ShorebirdEnvironment.hostedUri), isNull);
});
});
});
}
@@ -2,7 +2,9 @@ import 'dart:io';
import 'package:mocktail/mocktail.dart';
import 'package:path/path.dart' as p;
import 'package:scoped/scoped.dart';
import 'package:shorebird_cli/src/process.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:test/test.dart';
class _MockProcess extends Mock implements Process {}
@@ -11,6 +13,8 @@ class _MockProcessResult extends Mock implements ShorebirdProcessResult {}
class _MockProcessWrapper extends Mock implements ProcessWrapper {}
class _MockShorebirdEnv extends Mock implements ShorebirdEnv {}
void main() {
group('ShorebirdProcess', () {
const flutterStorageBaseUrlEnv = {
@@ -20,16 +24,32 @@ void main() {
late ProcessWrapper processWrapper;
late Process startProcess;
late ShorebirdProcessResult runProcessResult;
late ShorebirdEnv shorebirdEnv;
late ShorebirdProcess shorebirdProcess;
R runWithOverrides<R>(R Function() body) {
return runScoped(
() => body(),
values: {
shorebirdEnvRef.overrideWith(() => shorebirdEnv),
},
);
}
setUp(() {
processWrapper = _MockProcessWrapper();
runProcessResult = _MockProcessResult();
startProcess = _MockProcess();
shorebirdProcess = ShorebirdProcess(
processWrapper: processWrapper,
shorebirdEnv = _MockShorebirdEnv();
shorebirdProcess = runWithOverrides(
() => ShorebirdProcess(processWrapper: processWrapper),
);
when(() => shorebirdEnv.flutterBinaryFile).thenReturn(
File(
p.join('bin', 'cache', 'flutter', 'bin', 'flutter'),
),
);
when(
() => processWrapper.run(
any(),
@@ -78,11 +98,13 @@ void main() {
});
test('replaces "flutter" with our local flutter', () async {
await shorebirdProcess.run(
'flutter',
['--version'],
runInShell: true,
workingDirectory: '~',
await runWithOverrides(
() => shorebirdProcess.run(
'flutter',
['--version'],
runInShell: true,
workingDirectory: '~',
),
);
verify(
@@ -178,7 +200,7 @@ void main() {
),
);
await shorebirdProcess.run('flutter', []);
await runWithOverrides(() => shorebirdProcess.run('flutter', []));
verify(
() => processWrapper.run(
@@ -209,7 +231,9 @@ void main() {
});
test('replaces "flutter" with our local flutter', () async {
await shorebirdProcess.start('flutter', ['run'], runInShell: true);
await runWithOverrides(
() => shorebirdProcess.start('flutter', ['run'], runInShell: true),
);
verify(
() => processWrapper.start(
@@ -247,11 +271,13 @@ void main() {
});
test('Updates environment if useVendedFlutter is true', () async {
await shorebirdProcess.start(
'flutter',
['--version'],
runInShell: true,
environment: {'ENV_VAR': 'asdfasdf'},
await runWithOverrides(
() => shorebirdProcess.start(
'flutter',
['--version'],
runInShell: true,
environment: {'ENV_VAR': 'asdfasdf'},
),
);
verify(
@@ -5,6 +5,7 @@ import 'package:scoped/scoped.dart';
import 'package:shorebird_cli/src/auth/auth.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/platform.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
import 'package:shorebird_cli/src/validators/validators.dart';
import 'package:test/test.dart';
@@ -15,6 +16,8 @@ class _MockLogger extends Mock implements Logger {}
class _MockPlatform extends Mock implements Platform {}
class _MockShorebirdEnv extends Mock implements ShorebirdEnv {}
class _MockValidator extends Mock implements Validator {}
void main() {
@@ -23,6 +26,7 @@ void main() {
late Logger logger;
late Platform platform;
late Validator validator;
late ShorebirdEnv shorebirdEnv;
late ShorebirdValidator shorebirdValidator;
R runWithOverrides<R>(R Function() body) {
@@ -32,6 +36,7 @@ void main() {
authRef.overrideWith(() => auth),
loggerRef.overrideWith(() => logger),
platformRef.overrideWith(() => platform),
shorebirdEnvRef.overrideWith(() => shorebirdEnv),
},
);
}
@@ -40,6 +45,7 @@ void main() {
auth = _MockAuth();
logger = _MockLogger();
platform = _MockPlatform();
shorebirdEnv = _MockShorebirdEnv();
validator = _MockValidator();
shorebirdValidator = runWithOverrides(ShorebirdValidator.new);
});
@@ -103,6 +109,7 @@ void main() {
test(
'throws ShorebirdNotInitializedException '
'when shorebird has not been initialized', () async {
when(() => shorebirdEnv.isShorebirdInitialized).thenReturn(false);
await expectLater(
runWithOverrides(
() => shorebirdValidator.validatePreconditions(
@@ -6,7 +6,7 @@ import 'package:platform/platform.dart';
import 'package:scoped/scoped.dart';
import 'package:shorebird_cli/src/platform.dart';
import 'package:shorebird_cli/src/process.dart';
import 'package:shorebird_cli/src/shorebird_environment.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/validators/validators.dart';
import 'package:test/test.dart';
@@ -14,6 +14,8 @@ class _MockProcessResult extends Mock implements ShorebirdProcessResult {}
class _MockPlatform extends Mock implements Platform {}
class _MockShorebirdEnv extends Mock implements ShorebirdEnv {}
class _MockShorebirdProcess extends Mock implements ShorebirdProcess {}
void main() {
@@ -40,6 +42,7 @@ Tools • Dart 2.19.6 • DevTools 2.20.1
late ShorebirdProcessResult shorebirdFlutterVersionProcessResult;
late ShorebirdProcessResult gitStatusProcessResult;
late ShorebirdProcess shorebirdProcess;
late ShorebirdEnv shorebirdEnv;
late Platform platform;
R runWithOverrides<R>(R Function() body) {
@@ -48,6 +51,7 @@ Tools • Dart 2.19.6 • DevTools 2.20.1
values: {
platformRef.overrideWith(() => platform),
processRef.overrideWith(() => shorebirdProcess),
shorebirdEnvRef.overrideWith(() => shorebirdEnv),
},
);
}
@@ -68,8 +72,12 @@ Tools • Dart 2.19.6 • DevTools 2.20.1
setUp(() {
tempDir = setupTempDirectory();
platform = _MockPlatform();
shorebirdEnv = _MockShorebirdEnv();
ShorebirdEnvironment.flutterRevision = flutterRevision;
when(() => shorebirdEnv.flutterRevision).thenReturn(flutterRevision);
when(
() => shorebirdEnv.flutterDirectory,
).thenReturn(flutterDirectory(tempDir));
when(() => platform.script).thenReturn(shorebirdScriptFile(tempDir).uri);
when(() => platform.environment).thenReturn({});