fix(shorebird_cli): print error message if user attempts to preview iOS on linux or windows (#1424)

This commit is contained in:
Bryan Oltman
2023-10-23 11:42:27 -04:00
committed by GitHub
parent 93547c0e25
commit ff7ba81efd
3 changed files with 110 additions and 27 deletions
@@ -15,6 +15,7 @@ import 'package:shorebird_cli/src/deployment_track.dart';
import 'package:shorebird_cli/src/executables/executables.dart';
import 'package:shorebird_cli/src/http_client/http_client.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/platform.dart';
import 'package:shorebird_cli/src/shorebird_validator.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';
@@ -45,10 +46,9 @@ class PreviewCommand extends ShorebirdCommand {
)
..addOption(
'platform',
allowed: [ReleasePlatform.android.name, ReleasePlatform.ios.name],
allowed: ReleasePlatform.values.map((e) => e.name),
allowedHelp: {
ReleasePlatform.android.name: 'Android',
ReleasePlatform.ios.name: 'iOS',
for (final p in ReleasePlatform.values) p.name: p.displayName,
},
help: 'The platform of the release.',
)
@@ -59,6 +59,17 @@ class PreviewCommand extends ShorebirdCommand {
);
}
/// Returns the platforms that can be previewed on the current OS.
static List<ReleasePlatform> get supportedReleasePlatforms {
if (platform.isMacOS) {
return ReleasePlatform.values;
} else {
return ReleasePlatform.values
.where((p) => p != ReleasePlatform.ios)
.toList();
}
}
final http.Client _httpClient;
@override
@@ -99,22 +110,38 @@ class PreviewCommand extends ShorebirdCommand {
);
if (releaseVersion == null || release == null) {
// TODO(bryanoltman): link to FAQ explaining which releases are
// previewable.
logger.info('No previewable releases found');
return ExitCode.success.code;
}
final platform = ReleasePlatform.values.byName(
results['platform'] as String? ?? await promptForPlatform(release),
);
final availablePlatforms = release.activePlatforms
.where((p) => supportedReleasePlatforms.contains(p))
.toList();
if (availablePlatforms.isEmpty) {
final activePlatformsString =
release.activePlatforms.map((p) => p.displayName).join(', ');
logger.err(
'''This release can only be previewed on platforms that support $activePlatformsString''',
);
return ExitCode.software.code;
}
final ReleasePlatform releasePlatform;
if (availablePlatforms.length == 1) {
releasePlatform = release.activePlatforms.first;
} else {
releasePlatform = ReleasePlatform.values.byName(
results['platform'] as String? ??
await promptForPlatform(availablePlatforms),
);
}
final deviceId = results['device-id'] as String?;
final isStaging = results['staging'] == true;
final track =
isStaging ? DeploymentTrack.staging : DeploymentTrack.production;
return switch (platform) {
return switch (releasePlatform) {
ReleasePlatform.android => installAndLaunchAndroid(
appId: appId,
release: release,
@@ -151,11 +178,11 @@ class PreviewCommand extends ShorebirdCommand {
return release.version;
}
Future<String> promptForPlatform(Release release) async {
final platforms = release.platformStatuses.keys.map((p) => p.name).toList();
Future<String> promptForPlatform(List<ReleasePlatform> platforms) async {
final platformNames = platforms.map((p) => p.displayName).toList();
final platform = logger.chooseOne(
'Which platform would you like to preview?',
choices: platforms,
choices: platformNames,
);
return platform;
}
@@ -444,3 +471,10 @@ class PreviewCommand extends ShorebirdCommand {
});
}
}
extension Previewable on Release {
List<ReleasePlatform> get activePlatforms => platformStatuses.entries
.where((e) => e.value == ReleaseStatus.active)
.map((e) => e.key)
.toList();
}
@@ -1,11 +1,12 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:io' hide Platform;
import 'package:args/args.dart';
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/artifact_manager.dart';
import 'package:shorebird_cli/src/auth/auth.dart';
@@ -15,6 +16,7 @@ import 'package:shorebird_cli/src/commands/commands.dart';
import 'package:shorebird_cli/src/deployment_track.dart';
import 'package:shorebird_cli/src/executables/executables.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/platform.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';
@@ -37,6 +39,7 @@ void main() {
late CodePushClientWrapper codePushClientWrapper;
late Logger logger;
late Directory previewDirectory;
late Platform platform;
late Progress progress;
late Release release;
late ReleaseArtifact releaseArtifact;
@@ -53,6 +56,7 @@ void main() {
cacheRef.overrideWith(() => cache),
codePushClientWrapperRef.overrideWith(() => codePushClientWrapper),
loggerRef.overrideWith(() => logger),
platformRef.overrideWith(() => platform),
shorebirdValidatorRef.overrideWith(() => shorebirdValidator),
},
),
@@ -76,6 +80,7 @@ void main() {
cache = MockCache();
codePushClientWrapper = MockCodePushClientWrapper();
logger = MockLogger();
platform = MockPlatform();
previewDirectory = Directory.systemTemp.createTempSync();
progress = MockProgress();
release = MockRelease();
@@ -120,6 +125,10 @@ void main() {
checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'),
),
).thenAnswer((_) async {});
when(() => platform.isLinux).thenReturn(false);
when(() => platform.isMacOS).thenReturn(false);
when(() => platform.isWindows).thenReturn(false);
});
test('exits when validation fails', () async {
@@ -160,8 +169,30 @@ void main() {
).called(1);
});
group('when release is not supported on the current OS', () {
setUp(() {
when(() => platform.isLinux).thenReturn(false);
when(() => platform.isMacOS).thenReturn(false);
when(() => platform.isWindows).thenReturn(true);
when(() => release.platformStatuses).thenReturn({
ReleasePlatform.ios: ReleaseStatus.active,
});
});
test('prints error message and exits with code 70', () async {
final result = await runWithOverrides(command.run);
expect(result, ExitCode.software.code);
verify(
() => logger.err(
'This release can only be previewed on platforms that support iOS',
),
).called(1);
});
});
group('android', () {
const platform = ReleasePlatform.android;
const releasePlatform = ReleasePlatform.android;
const releaseArtifactUrl = 'https://example.com/release.aab';
const packageName = 'com.example.app';
@@ -171,12 +202,12 @@ void main() {
String aabPath() => p.join(
previewDirectory.path,
'${platform.name}_$releaseVersion.aab',
'${releasePlatform.name}_$releaseVersion.aab',
);
String apksPath() => p.join(
previewDirectory.path,
'${platform.name}_$releaseVersion.apks',
'${releasePlatform.name}_$releaseVersion.apks',
);
R runWithOverrides<R>(R Function() body) {
@@ -193,6 +224,7 @@ void main() {
() => codePushClientWrapper,
),
loggerRef.overrideWith(() => logger),
platformRef.overrideWith(() => platform),
shorebirdValidatorRef.overrideWith(() => shorebirdValidator),
},
),
@@ -218,7 +250,7 @@ void main() {
bundletool = MockBundleTool();
process = MockProcess();
when(() => argResults['platform']).thenReturn(platform.name);
when(() => argResults['platform']).thenReturn(releasePlatform.name);
when(
() => artifactManager.downloadFile(
any(),
@@ -271,6 +303,10 @@ void main() {
when(() => process.stdout).thenAnswer((_) => const Stream.empty());
when(() => process.stderr).thenAnswer((_) => const Stream.empty());
when(() => releaseArtifact.url).thenReturn(releaseArtifactUrl);
when(() => release.platformStatuses).thenReturn({
ReleasePlatform.android: ReleaseStatus.active,
ReleasePlatform.ios: ReleaseStatus.active,
});
});
test('exits with code 70 when querying for release artifact fails',
@@ -291,7 +327,7 @@ void main() {
appId: appId,
releaseId: releaseId,
arch: 'aab',
platform: platform,
platform: releasePlatform,
),
).called(1);
});
@@ -496,6 +532,8 @@ void main() {
outputDirectory: any(named: 'outputDirectory'),
),
).thenAnswer(createShorebirdYaml);
// We only prompt when there are multiple platforms to choose from
when(() => platform.isMacOS).thenReturn(true);
when(() => argResults['platform']).thenReturn(null);
when(
@@ -504,7 +542,7 @@ void main() {
choices: any(named: 'choices'),
display: any(named: 'display'),
),
).thenReturn(platform.name);
).thenReturn(releasePlatform.name);
final result = await runWithOverrides(command.run);
expect(result, equals(ExitCode.success.code));
final platforms = verify(
@@ -517,8 +555,8 @@ void main() {
expect(
platforms,
equals([
ReleasePlatform.android.name,
ReleasePlatform.ios.name,
ReleasePlatform.android.displayName,
ReleasePlatform.ios.displayName,
]),
);
});
@@ -633,13 +671,13 @@ void main() {
group('ios', () {
const releaseArtifactUrl = 'https://example.com/runner.app';
const platform = ReleasePlatform.ios;
const releasePlatform = ReleasePlatform.ios;
late Devicectl devicectl;
late IOSDeploy iosDeploy;
String runnerPath() => p.join(
previewDirectory.path,
'${platform.name}_$releaseVersion.app',
'${releasePlatform.name}_$releaseVersion.app',
);
R runWithOverrides<R>(R Function() body) {
@@ -657,6 +695,7 @@ void main() {
devicectlRef.overrideWith(() => devicectl),
iosDeployRef.overrideWith(() => iosDeploy),
loggerRef.overrideWith(() => logger),
platformRef.overrideWith(() => platform),
shorebirdValidatorRef.overrideWith(() => shorebirdValidator),
},
),
@@ -666,7 +705,7 @@ void main() {
setUp(() {
devicectl = MockDevicectl();
iosDeploy = MockIOSDeploy();
when(() => argResults['platform']).thenReturn(platform.name);
when(() => argResults['platform']).thenReturn(releasePlatform.name);
when(
() => artifactManager.downloadFile(
any(),
@@ -694,7 +733,12 @@ void main() {
deviceId: any(named: 'deviceId'),
),
).thenAnswer((_) async => ExitCode.success.code);
when(() => release.platformStatuses).thenReturn({
ReleasePlatform.android: ReleaseStatus.active,
ReleasePlatform.ios: ReleaseStatus.active,
});
when(() => releaseArtifact.url).thenReturn(releaseArtifactUrl);
when(() => platform.isMacOS).thenReturn(true);
});
File setupShorebirdYaml() => File(
@@ -727,7 +771,7 @@ void main() {
appId: appId,
releaseId: releaseId,
arch: 'runner',
platform: platform,
platform: releasePlatform,
),
).called(1);
});
@@ -1,7 +1,12 @@
/// A platform to which a Shorebird release can be deployed.
enum ReleasePlatform {
// ignore: public_member_api_docs
android,
android('Android'),
// ignore: public_member_api_docs
ios;
ios('iOS');
const ReleasePlatform(this.displayName);
/// The display name of the platform.
final String displayName;
}