Skip to content

Commit

Permalink
feat(shorebird_cli): add channel support to patch and preview com…
Browse files Browse the repository at this point in the history
…mands (#1340)
  • Loading branch information
felangel authored Oct 2, 2023
1 parent 41b0753 commit aefecff
Show file tree
Hide file tree
Showing 8 changed files with 318 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ class PatchAndroidCommand extends ShorebirdCommand
'flavor',
help: 'The product flavor to use when building the app.',
)
..addOption(
'channel',
help: 'The channel to publish the patch to.',
defaultsTo: 'stable',
)
..addFlag(
'force',
abbr: 'f',
Expand Down Expand Up @@ -97,7 +102,7 @@ class PatchAndroidCommand extends ShorebirdCommand
await cache.updateAll();

const platform = ReleasePlatform.android;
const channelName = 'stable';
final channelName = results['channel'] as String;
final flavor = results['flavor'] as String?;
final target = results['target'] as String?;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ class PatchIosCommand extends ShorebirdCommand
'flavor',
help: 'The product flavor to use when building the app.',
)
..addOption(
'channel',
help: 'The channel to publish the patch to.',
defaultsTo: 'stable',
)
..addFlag(
'codesign',
help: 'Codesign the application bundle.',
Expand Down Expand Up @@ -95,7 +100,7 @@ class PatchIosCommand extends ShorebirdCommand
}

const arch = 'aarch64';
const channelName = 'stable';
final channelName = results['channel'] as String;
const releasePlatform = ReleasePlatform.ios;
final flavor = results['flavor'] as String?;

Expand Down
148 changes: 132 additions & 16 deletions packages/shorebird_cli/lib/src/commands/preview_command.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import 'dart:async';
import 'dart:convert';
import 'dart:isolate';

import 'package:archive/archive_io.dart';
import 'package:collection/collection.dart';
import 'package:http/http.dart' as http;
import 'package:mason_logger/mason_logger.dart';
Expand All @@ -17,6 +19,7 @@ import 'package:shorebird_cli/src/logger.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';
import 'package:yaml_edit/yaml_edit.dart';

/// {@template preview_command}
/// `shorebird preview` command.
Expand Down Expand Up @@ -49,6 +52,11 @@ class PreviewCommand extends ShorebirdCommand {
ReleasePlatform.ios.name: 'iOS',
},
help: 'The platform of the release.',
)
..addOption(
'channel',
defaultsTo: 'stable',
help: 'The channel to preview the release for.',
);
}

Expand Down Expand Up @@ -103,17 +111,20 @@ class PreviewCommand extends ShorebirdCommand {
);

final deviceId = results['device-id'] as String?;
final channel = results['channel'] as String;

return switch (platform) {
ReleasePlatform.android => installAndLaunchAndroid(
appId: appId,
release: release,
deviceId: deviceId,
channel: channel,
),
ReleasePlatform.ios => installAndLaunchIos(
appId: appId,
release: release,
deviceId: deviceId,
channel: channel,
),
};
}
Expand Down Expand Up @@ -151,6 +162,7 @@ class PreviewCommand extends ShorebirdCommand {
Future<int> installAndLaunchAndroid({
required String appId,
required Release release,
required String channel,
String? deviceId,
}) async {
const platform = ReleasePlatform.android;
Expand Down Expand Up @@ -187,6 +199,23 @@ class PreviewCommand extends ShorebirdCommand {
}
}

final apksPath = getArtifactPath(
appId: appId,
release: release,
platform: platform,
extension: 'apks',
);

if (File(apksPath).existsSync()) File(apksPath).deleteSync();
final progress = logger.progress('Using channel $channel');
try {
await setChannelOnAab(aabFile: aabFile, channel: channel);
progress.complete();
} catch (error) {
progress.fail('$error');
return ExitCode.software.code;
}

final extractMetadataProgress = logger.progress('Extracting metadata');
late String package;
try {
Expand All @@ -197,22 +226,13 @@ class PreviewCommand extends ShorebirdCommand {
return ExitCode.software.code;
}

final apksPath = getArtifactPath(
appId: appId,
release: release,
platform: platform,
extension: 'apks',
);

if (!File(apksPath).existsSync()) {
final buildApksProgress = logger.progress('Building apks');
try {
await bundletool.buildApks(bundle: aabFile.path, output: apksPath);
buildApksProgress.complete();
} catch (error) {
buildApksProgress.fail('$error');
return ExitCode.software.code;
}
final buildApksProgress = logger.progress('Building apks');
try {
await bundletool.buildApks(bundle: aabFile.path, output: apksPath);
buildApksProgress.complete();
} catch (error) {
buildApksProgress.fail('$error');
return ExitCode.software.code;
}

final installApksProgress = logger.progress('Installing apks');
Expand Down Expand Up @@ -247,6 +267,7 @@ class PreviewCommand extends ShorebirdCommand {
Future<int> installAndLaunchIos({
required String appId,
required Release release,
required String channel,
String? deviceId,
}) async {
const platform = ReleasePlatform.ios;
Expand Down Expand Up @@ -285,6 +306,18 @@ class PreviewCommand extends ShorebirdCommand {
}
}

final progress = logger.progress('Using channel $channel');
try {
await setChannelOnRunner(
runnerDirectory: runnerDirectory,
channel: channel,
);
progress.complete();
} catch (error) {
progress.fail('$error');
return ExitCode.software.code;
}

try {
final exitCode = await iosDeploy.installAndLaunchApp(
bundlePath: runnerDirectory.path,
Expand All @@ -308,4 +341,87 @@ class PreviewCommand extends ShorebirdCommand {
'${platform.name}_${release.version}.$extension',
);
}

/// Sets the channel property in the shorebird.yaml file inside the Runner.app
Future<void> setChannelOnRunner({
required Directory runnerDirectory,
required String channel,
}) async {
await Isolate.run(() async {
final shorebirdYaml = File(
p.join(
runnerDirectory.path,
'Frameworks',
'App.framework',
'flutter_assets',
'shorebird.yaml',
),
);

if (!shorebirdYaml.existsSync()) {
throw Exception('Unable to find shorebird.yaml');
}

final yaml = YamlEditor(shorebirdYaml.readAsStringSync())
..update(['channel'], channel);

shorebirdYaml.writeAsStringSync(yaml.toString(), flush: true);
});
}

/// Unzips the `.aab` and sets the channel property in the shorebird.yaml
/// file inside the base module and then re-zips the `.aab`.
Future<void> setChannelOnAab({
required File aabFile,
required String channel,
}) async {
// Getting the reference here since we cannot inside the isolate.
final extractZip = artifactManager.extractZip;

await Isolate.run(() async {
final tempDir = Directory.systemTemp.createTempSync();
final basename = p.basenameWithoutExtension(aabFile.path);
final outputPath = p.join(tempDir.path, basename);

await extractZip(
zipFile: aabFile,
outputDirectory: Directory(outputPath),
);

final shorebirdYaml = File(
p.join(
outputPath,
'base',
'assets',
'flutter_assets',
'shorebird.yaml',
),
);

if (!shorebirdYaml.existsSync()) {
throw Exception('Unable to find shorebird.yaml');
}

final yaml = YamlEditor(shorebirdYaml.readAsStringSync())
..update(['channel'], channel);

shorebirdYaml.writeAsStringSync(yaml.toString(), flush: true);

// This is equivalent to `zip --no-dir-entries`
// Which does NOT create entries in the zip archive for directories.
// It's important to do this because bundletool expects the
// .aab not to contain any directories.
final encoder = ZipFileEncoder()..create(aabFile.path);
for (final file in Directory(outputPath).listSync(recursive: true)) {
if (file is File) {
await encoder.addFile(
file,
file.path.replaceFirst('$outputPath/', ''),
);
}
}
encoder.close();
tempDir.deleteSync(recursive: true);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ void main() {
const version = '$versionName+$versionCode';
const arch = 'aarch64';
const releasePlatform = ReleasePlatform.android;
const channelName = 'stable';
const channelName = 'test-channel';
const appDisplayName = 'Test App';
final appMetadata = AppMetadata(
appId: appId,
Expand Down Expand Up @@ -786,10 +786,10 @@ Please re-run the release command for this version or create a new release.'''),
verifyNever(() => logger.confirm(any()));
verify(
() => codePushClientWrapper.publishPatch(
appId: any(named: 'appId'),
releaseId: any(named: 'releaseId'),
platform: any(named: 'platform'),
channelName: any(named: 'channelName'),
appId: appId,
releaseId: release.id,
platform: releasePlatform,
channelName: channelName,
patchArtifactBundles: any(named: 'patchArtifactBundles'),
),
).called(1);
Expand All @@ -813,10 +813,10 @@ Please re-run the release command for this version or create a new release.'''),
).called(1);
verify(
() => codePushClientWrapper.publishPatch(
appId: any(named: 'appId'),
releaseId: any(named: 'releaseId'),
platform: any(named: 'platform'),
channelName: any(named: 'channelName'),
appId: appId,
releaseId: release.id,
platform: releasePlatform,
channelName: channelName,
patchArtifactBundles: any(named: 'patchArtifactBundles'),
),
).called(1);
Expand Down Expand Up @@ -863,10 +863,10 @@ flavors:
);
verify(
() => codePushClientWrapper.publishPatch(
appId: any(named: 'appId'),
releaseId: any(named: 'releaseId'),
platform: any(named: 'platform'),
channelName: any(named: 'channelName'),
appId: appId,
releaseId: release.id,
platform: releasePlatform,
channelName: channelName,
patchArtifactBundles: any(named: 'patchArtifactBundles'),
),
).called(1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ void main() {
const versionCode = '1';
const version = '$versionName+$versionCode';
const arch = 'aarch64';
const channelName = 'test-channel';
const appDisplayName = 'Test App';
const releasePlatform = ReleasePlatform.ios;
const platformName = 'ios';
const elfAotSnapshotFileName = 'out.aot';
const ipaPath = 'build/ios/ipa/Runner.ipa';
Expand Down Expand Up @@ -252,6 +254,7 @@ flutter:
when(() => argResults['force']).thenReturn(false);
when(() => argResults['release-version']).thenReturn(release.version);
when(() => argResults['codesign']).thenReturn(true);
when(() => argResults['channel']).thenReturn(channelName);
when(() => argResults.rest).thenReturn([]);
when(() => auth.isAuthenticated).thenReturn(true);
when(() => auth.client).thenReturn(httpClient);
Expand Down Expand Up @@ -845,10 +848,10 @@ Please re-run the release command for this version or create a new release.'''),
verifyNever(() => logger.confirm(any()));
verify(
() => codePushClientWrapper.publishPatch(
appId: any(named: 'appId'),
releaseId: any(named: 'releaseId'),
platform: any(named: 'platform'),
channelName: any(named: 'channelName'),
appId: appId,
releaseId: release.id,
platform: releasePlatform,
channelName: channelName,
patchArtifactBundles: any(named: 'patchArtifactBundles'),
),
).called(1);
Expand All @@ -872,10 +875,10 @@ Please re-run the release command for this version or create a new release.'''),
).called(1);
verify(
() => codePushClientWrapper.publishPatch(
appId: any(named: 'appId'),
releaseId: any(named: 'releaseId'),
platform: any(named: 'platform'),
channelName: any(named: 'channelName'),
appId: appId,
releaseId: release.id,
platform: releasePlatform,
channelName: channelName,
patchArtifactBundles: any(named: 'patchArtifactBundles'),
),
).called(1);
Expand Down Expand Up @@ -976,10 +979,10 @@ flavors:
expect(exitCode, ExitCode.success.code);
verify(
() => codePushClientWrapper.publishPatch(
appId: any(named: 'appId'),
releaseId: any(named: 'releaseId'),
platform: any(named: 'platform'),
channelName: any(named: 'channelName'),
appId: appId,
releaseId: release.id,
platform: releasePlatform,
channelName: channelName,
patchArtifactBundles: any(named: 'patchArtifactBundles'),
),
).called(1);
Expand Down
Loading

0 comments on commit aefecff

Please sign in to comment.