refactor(shorebird_cli): split platform logic for shorebird preview (#906)
This commit is contained in:
@@ -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 => []);
|
||||
|
||||
Reference in New Issue
Block a user