refactor(shorebird_cli): split platform logic for shorebird preview (#906)

This commit is contained in:
Felix Angelov
2023-07-25 13:07:57 -05:00
committed by GitHub
parent 5422bfe5fc
commit ed7eefc212
2 changed files with 110 additions and 48 deletions
@@ -1,6 +1,7 @@
import 'dart:async';
import 'dart:convert';
import 'package:collection/collection.dart';
import 'package:mason_logger/mason_logger.dart';
import 'package:path/path.dart' as p;
import 'package:shorebird_cli/src/adb.dart';
@@ -33,6 +34,15 @@ class PreviewCommand extends ShorebirdCommand
..addOption(
'release-version',
help: 'The version of the release (e.g. "1.0.0").',
)
..addOption(
'platform',
allowed: [ReleasePlatform.android.name, ReleasePlatform.ios.name],
allowedHelp: {
ReleasePlatform.android.name: 'Android',
ReleasePlatform.ios.name: 'iOS',
},
help: 'The platform of the release.',
);
}
@@ -52,7 +62,6 @@ class PreviewCommand extends ShorebirdCommand
return error.exitCode.code;
}
const platform = ReleasePlatform.android;
final appId = results['app-id'] as String? ?? await promptForApp();
if (appId == null) {
@@ -60,32 +69,75 @@ class PreviewCommand extends ShorebirdCommand
return ExitCode.success.code;
}
final releaseVersion = results['release-version'] as String? ??
await promptForReleaseVersion(appId);
final releases = await codePushClientWrapper.getReleases(appId: appId);
if (releaseVersion == null) {
final releaseVersion = results['release-version'] as String? ??
await promptForReleaseVersion(releases);
final release = releases.firstWhereOrNull(
(r) => r.version == releaseVersion,
);
if (releaseVersion == null || release == null) {
logger.info('No releases found');
return ExitCode.success.code;
}
final platform = ReleasePlatform.values.byName(
results['platform'] as String? ?? await promptForPlatform(release),
);
return switch (platform) {
ReleasePlatform.android => installAndLaunchAndroid(appId, release),
ReleasePlatform.ios => installAndLaunchIos(appId, release),
};
}
Future<String?> promptForApp() async {
final apps = await codePushClientWrapper.getApps();
if (apps.isEmpty) return null;
final app = logger.chooseOne(
'Which app would you like to preview?',
choices: apps,
display: (app) => app.displayName,
);
return app.appId;
}
Future<String?> promptForReleaseVersion(List<Release> releases) async {
if (releases.isEmpty) return null;
final release = logger.chooseOne(
'Which release would you like to preview?',
choices: releases,
display: (release) => release.version,
);
return release.version;
}
Future<String> promptForPlatform(Release release) async {
final platforms = release.platformStatuses.keys.map((p) => p.name).toList();
final platform = logger.chooseOne(
'Which platform would you like to preview?',
choices: platforms,
);
return platform;
}
Future<int> installAndLaunchAndroid(String appId, Release release) async {
const platform = ReleasePlatform.android;
final previewDirectory = cache.getPreviewDirectory(appId);
final aabPath = p.join(
previewDirectory.path,
'${platform}_$releaseVersion.aab',
'${platform}_${release.version}.aab',
);
if (!File(aabPath).existsSync()) {
final downloadArtifactProgress = logger.progress('Downloading release');
try {
final release = await codePushClientWrapper.getRelease(
appId: appId,
releaseVersion: releaseVersion,
);
final releaseAabArtifact =
await codePushClientWrapper.getReleaseArtifact(
appId: appId,
releaseId: release.id,
// TODO(felangel): add iOS support
arch: 'aab',
platform: platform,
);
@@ -110,7 +162,7 @@ class PreviewCommand extends ShorebirdCommand
final apksPath = p.join(
previewDirectory.path,
'${platform}_$releaseVersion.apks',
'${platform}_${release.version}.apks',
);
if (!File(apksPath).existsSync()) {
@@ -153,26 +205,8 @@ class PreviewCommand extends ShorebirdCommand
return process.exitCode;
}
Future<String?> promptForApp() async {
final apps = await codePushClientWrapper.getApps();
if (apps.isEmpty) return null;
final app = logger.chooseOne(
'Which app would you like to preview?',
choices: apps,
display: (app) => app.displayName,
);
return app.appId;
}
Future<String?> promptForReleaseVersion(String appId) async {
final releases = await codePushClientWrapper.getReleases(appId: appId);
if (releases.isEmpty) return null;
final release = logger.chooseOne(
'Which release would you like to preview?',
choices: releases,
display: (release) => release.version,
);
return release.version;
Future<int> installAndLaunchIos(String appId, Release release) async {
return ExitCode.unavailable.code;
}
}
@@ -130,6 +130,7 @@ void main() {
when(() => argResults['app-id']).thenReturn(appId);
when(() => argResults['release-version']).thenReturn(releaseVersion);
when(() => argResults['platform']).thenReturn(platform.name);
when(() => auth.isAuthenticated).thenReturn(true);
when(() => cache.getPreviewDirectory(any())).thenReturn(previewDirectory);
when(
@@ -138,12 +139,6 @@ void main() {
when(
() => codePushClientWrapper.getReleases(appId: appId),
).thenAnswer((_) async => [release]);
when(
() => codePushClientWrapper.getRelease(
appId: any(named: 'appId'),
releaseVersion: any(named: 'releaseVersion'),
),
).thenAnswer((_) async => release);
when(
() => codePushClientWrapper.getReleaseArtifact(
appId: any(named: 'appId'),
@@ -156,6 +151,10 @@ void main() {
when(() => app.displayName).thenReturn(appDisplayName);
when(() => release.id).thenReturn(releaseId);
when(() => release.version).thenReturn(releaseVersion);
when(() => release.platformStatuses).thenReturn({
ReleasePlatform.android: ReleaseStatus.active,
ReleasePlatform.ios: ReleaseStatus.active,
});
when(() => releaseArtifact.url).thenReturn(releaseArtifactUrl);
when(() => logger.progress(any())).thenReturn(progress);
when(
@@ -195,21 +194,17 @@ void main() {
expect(result, ExitCode.noUser.code);
});
test('exits with code 70 when querying for release fails', () async {
test('exits with code 70 when querying for releases fails', () async {
final exception = Exception('oops');
when(
() => codePushClientWrapper.getRelease(
appId: any(named: 'appId'),
releaseVersion: any(named: 'releaseVersion'),
),
() => codePushClientWrapper.getReleases(appId: any(named: 'appId')),
).thenThrow(exception);
final result = await runWithOverrides(command.run);
expect(result, equals(ExitCode.software.code));
await expectLater(
() => runWithOverrides(command.run),
throwsA(exception),
);
verify(
() => codePushClientWrapper.getRelease(
appId: appId,
releaseVersion: releaseVersion,
),
() => codePushClientWrapper.getReleases(appId: appId),
).called(1);
});
@@ -353,6 +348,39 @@ void main() {
verify(() => codePushClientWrapper.getApps()).called(1);
});
test('prompts for platforms when platform is not specified', () async {
when(() => argResults['platform']).thenReturn(null);
when(
() => logger.chooseOne<String>(
any(),
choices: any(named: 'choices'),
display: any(named: 'display'),
),
).thenReturn(platform.name);
final result = await runWithOverrides(command.run);
expect(result, equals(ExitCode.success.code));
final platforms = verify(
() => logger.chooseOne<String>(
any(),
choices: captureAny(named: 'choices'),
display: any(named: 'display'),
),
).captured.single as List<String>;
expect(
platforms,
equals([
ReleasePlatform.android.name,
ReleasePlatform.ios.name,
]),
);
});
test('exits with unavailable when platform is ios', () async {
when(() => argResults['platform']).thenReturn(ReleasePlatform.ios.name);
final result = await runWithOverrides(command.run);
expect(result, equals(ExitCode.unavailable.code));
});
test('exits early when no apps are found', () async {
when(() => argResults['app-id']).thenReturn(null);
when(() => codePushClientWrapper.getApps()).thenAnswer((_) async => []);