diff --git a/packages/shorebird_cli/lib/src/commands/patch/patch_ios_command.dart b/packages/shorebird_cli/lib/src/commands/patch/patch_ios_command.dart index dd37b693..e12cc823 100644 --- a/packages/shorebird_cli/lib/src/commands/patch/patch_ios_command.dart +++ b/packages/shorebird_cli/lib/src/commands/patch/patch_ios_command.dart @@ -5,17 +5,16 @@ import 'package:crypto/crypto.dart'; import 'package:mason_logger/mason_logger.dart'; import 'package:path/path.dart' as p; import 'package:shorebird_cli/src/archive_analysis/archive_analysis.dart'; +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/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_code_push_client_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_validation_mixin.dart'; -import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; /// {@template patch_ios_command} /// `shorebird patch ios-preview` command. @@ -25,12 +24,11 @@ class PatchIosCommand extends ShorebirdCommand ShorebirdConfigMixin, ShorebirdBuildMixin, ShorebirdValidationMixin, - ShorebirdArtifactMixin, - ShorebirdCodePushClientMixin { + ShorebirdArtifactMixin { /// {@macro patch_ios_command} PatchIosCommand({ super.auth, - super.buildCodePushClient, + super.codePushClientWrapper, super.validators, HashFunction? hashFn, IpaReader? ipaReader, @@ -100,21 +98,7 @@ class PatchIosCommand extends ShorebirdCommand final shorebirdYaml = getShorebirdYaml()!; final appId = shorebirdYaml.getAppId(flavor: flavor); - final App? app; - try { - app = await getApp(appId: appId, flavor: flavor); - } catch (_) { - return ExitCode.software.code; - } - - if (app == null) { - logger.err( - ''' -Could not find app with id: "$appId". -Did you forget to run "shorebird init"?''', - ); - return ExitCode.software.code; - } + final app = await codePushClientWrapper.getApp(appId: appId); final buildProgress = logger.progress('Building release'); try { @@ -160,24 +144,10 @@ Did you forget to run "shorebird init"?''', return ExitCode.software.code; } - final Release? release; - try { - release = await getRelease(appId: appId, releaseVersion: releaseVersion); - } catch (_) { - return ExitCode.software.code; - } - - if (release == null) { - logger.err( - ''' -Release not found: "$releaseVersion" - -Patches can only be published for existing releases. -Please create a release using "shorebird release" and try again. -''', - ); - return ExitCode.software.code; - } + final release = await codePushClientWrapper.getRelease( + appId: appId, + releaseVersion: releaseVersion, + ); final flutterRevisionProgress = logger.progress( 'Fetching Flutter revision', @@ -224,14 +194,14 @@ https://github.com/shorebirdtech/shorebird/issues/472 return ExitCode.success.code; } - final size = formatBytes(aotFile.statSync().size); + final aotFileSize = aotFile.statSync().size; final summary = [ - '''šŸ“± App: ${lightCyan.wrap(app.displayName)} ${lightCyan.wrap('(${app.id})')}''', + '''šŸ“± App: ${lightCyan.wrap(app.displayName)} ${lightCyan.wrap('($appId)')}''', if (flavor != null) 'šŸ§ Flavor: ${lightCyan.wrap(flavor)}', 'šŸ“¦ Release Version: ${lightCyan.wrap(releaseVersion)}', 'šŸ“ŗ Channel: ${lightCyan.wrap(channelName)}', - '''šŸ•¹ļø Platform: ${lightCyan.wrap(platform)} ${lightCyan.wrap('[arm64 ($size)]')}''', + '''šŸ•¹ļø Platform: ${lightCyan.wrap(platform)} ${lightCyan.wrap('[arm64 (${formatBytes(aotFileSize)})]')}''', ]; logger.info( @@ -243,6 +213,8 @@ ${summary.join('\n')} ''', ); + // TODO(bryanoltman): check for asset changes + final needsConfirmation = !force; if (needsConfirmation) { final confirm = logger.confirm('Would you like to continue?'); @@ -253,64 +225,21 @@ ${summary.join('\n')} } } - final Patch patch; - try { - patch = await createPatch(releaseId: release.id); - } catch (e) { - return ExitCode.software.code; - } - - final codePushClient = buildCodePushClient( - httpClient: auth.client, - hostedUri: hostedUri, + await codePushClientWrapper.publishPatch( + appId: appId, + releaseId: release.id, + platform: platform, + channelName: channelName, + patchArtifactBundles: { + Arch.arm64: PatchArtifactBundle( + arch: 'arm64', + path: aotFile.path, + hash: _hashFn(aotFile.readAsBytesSync()), + size: aotFileSize, + ), + }, ); - // TODO(bryanoltman): check for asset changes - - final createArtifactProgress = logger.progress('Uploading artifacts'); - try { - await codePushClient.createPatchArtifact( - patchId: patch.id, - artifactPath: aotFile.path, - arch: 'arm64', - platform: 'ios', - hash: _hashFn(await aotFile.readAsBytes()), - ); - } catch (error) { - createArtifactProgress.fail('$error'); - return ExitCode.software.code; - } - createArtifactProgress.complete(); - - Channel? channel; - try { - channel = await getChannel(appId: appId, name: channelName); - } catch (_) { - return ExitCode.software.code; - } - - if (channel == null) { - try { - channel = await createChannel(appId: appId, name: channelName); - } catch (_) { - return ExitCode.software.code; - } - } - - final publishPatchProgress = logger.progress( - 'Promoting patch to ${channel.name}', - ); - try { - await codePushClient.promotePatch( - patchId: patch.id, - channelId: channel.id, - ); - publishPatchProgress.complete(); - } catch (error) { - publishPatchProgress.fail('$error'); - return ExitCode.software.code; - } - logger.success('\nāœ… Published Patch!'); return ExitCode.success.code; } diff --git a/packages/shorebird_cli/lib/src/shorebird_code_push_client_mixin.dart b/packages/shorebird_cli/lib/src/shorebird_code_push_client_mixin.dart index 673e146c..cec2cd91 100644 --- a/packages/shorebird_cli/lib/src/shorebird_code_push_client_mixin.dart +++ b/packages/shorebird_cli/lib/src/shorebird_code_push_client_mixin.dart @@ -124,23 +124,4 @@ mixin ShorebirdCodePushClientMixin on ShorebirdConfigMixin { fetchReleaseArtifactProgress.complete(); return releaseArtifacts; } - - Future createPatch({required int releaseId}) async { - final codePushClient = buildCodePushClient( - httpClient: auth.client, - hostedUri: hostedUri, - ); - - final Patch patch; - final createPatchProgress = logger.progress('Creating patch'); - try { - patch = await codePushClient.createPatch(releaseId: releaseId); - createPatchProgress.complete(); - } catch (error) { - createPatchProgress.fail('$error'); - rethrow; - } - - return patch; - } } diff --git a/packages/shorebird_cli/test/src/commands/patch/patch_ios_command_test.dart b/packages/shorebird_cli/test/src/commands/patch/patch_ios_command_test.dart index 0ef89a48..5b553c33 100644 --- a/packages/shorebird_cli/test/src/commands/patch/patch_ios_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/patch/patch_ios_command_test.dart @@ -8,6 +8,7 @@ import 'package:path/path.dart' as p; import 'package:scoped/scoped.dart'; 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/logger.dart'; import 'package:shorebird_cli/src/shorebird_environment.dart'; @@ -22,6 +23,9 @@ class _MockArgResults extends Mock implements ArgResults {} class _MockAuth extends Mock implements Auth {} +class _MockCodePushClientWrapper extends Mock + implements CodePushClientWrapper {} + class _MockIpaReader extends Mock implements IpaReader {} class _MockIpa extends Mock implements Ipa {} @@ -34,8 +38,6 @@ class _MockProcessResult extends Mock implements ShorebirdProcessResult {} class _MockHttpClient extends Mock implements http.Client {} -class _MockCodePushClient extends Mock implements CodePushClient {} - class _MockShorebirdFlutterValidator extends Mock implements ShorebirdFlutterValidator {} @@ -51,7 +53,6 @@ void main() { const version = '$versionName+$versionCode'; const arch = 'aarch64'; const appDisplayName = 'Test App'; - const channelName = 'stable'; const platform = 'ios'; const pubspecYamlContent = ''' name: example @@ -64,17 +65,6 @@ flutter: - shorebird.yaml'''; const appMetadata = AppMetadata(appId: appId, displayName: appDisplayName); - const channel = Channel(id: 0, appId: appId, name: channelName); - const patch = Patch(id: 0, number: 1); - const patchArtifact = PatchArtifact( - id: 0, - patchId: 0, - arch: arch, - platform: platform, - hash: '#', - size: 42, - url: 'https://example.com', - ); const release = Release( id: 0, appId: appId, @@ -86,6 +76,7 @@ flutter: group(PatchIosCommand, () { late ArgResults argResults; late Auth auth; + late CodePushClientWrapper codePushClientWrapper; late Ipa ipa; late IpaReader ipaReader; late Progress progress; @@ -94,8 +85,6 @@ flutter: late ShorebirdProcessResult flutterBuildProcessResult; late ShorebirdProcessResult flutterRevisionProcessResult; late http.Client httpClient; - late CodePushClient codePushClient; - late Uri? capturedHostedUri; late ShorebirdFlutterValidator flutterValidator; late ShorebirdProcess shorebirdProcess; late PatchIosCommand command; @@ -140,6 +129,7 @@ flutter: setUp(() { argResults = _MockArgResults(); auth = _MockAuth(); + codePushClientWrapper = _MockCodePushClientWrapper(); ipaReader = _MockIpaReader(); ipa = _MockIpa(); progress = _MockProgress(); @@ -148,7 +138,6 @@ flutter: flutterBuildProcessResult = _MockProcessResult(); flutterRevisionProcessResult = _MockProcessResult(); httpClient = _MockHttpClient(); - codePushClient = _MockCodePushClient(); flutterValidator = _MockShorebirdFlutterValidator(); shorebirdProcess = _MockShorebirdProcess(); @@ -159,36 +148,21 @@ flutter: when(() => auth.isAuthenticated).thenReturn(true); when(() => auth.client).thenReturn(httpClient); when( - () => codePushClient.getApps(), - ).thenAnswer((_) async => [appMetadata]); + () => codePushClientWrapper.getApp(appId: any(named: 'appId')), + ).thenAnswer((_) async => appMetadata); when( - () => codePushClient.getChannels(appId: any(named: 'appId')), - ).thenAnswer((_) async => [channel]); - when( - () => codePushClient.getReleases(appId: any(named: 'appId')), - ).thenAnswer((_) async => [release]); - when( - () => codePushClient.createChannel( + () => codePushClientWrapper.getRelease( appId: any(named: 'appId'), - channel: any(named: 'channel'), + releaseVersion: any(named: 'releaseVersion'), ), - ).thenAnswer((_) async => channel); + ).thenAnswer((_) async => release); when( - () => codePushClient.createPatch(releaseId: any(named: 'releaseId')), - ).thenAnswer((_) async => patch); - when( - () => codePushClient.createPatchArtifact( - artifactPath: any(named: 'artifactPath'), - patchId: any(named: 'patchId'), - arch: any(named: 'arch'), + () => codePushClientWrapper.publishPatch( + appId: any(named: 'appId'), + releaseId: any(named: 'releaseId'), platform: any(named: 'platform'), - hash: any(named: 'hash'), - ), - ).thenAnswer((_) async => patchArtifact); - when( - () => codePushClient.promotePatch( - patchId: any(named: 'patchId'), - channelId: any(named: 'channelId'), + channelName: any(named: 'channelName'), + patchArtifactBundles: any(named: 'patchArtifactBundles'), ), ).thenAnswer((_) async {}); when(() => ipa.versionNumber).thenReturn(version); @@ -230,15 +204,9 @@ flutter: command = PatchIosCommand( auth: auth, + codePushClientWrapper: codePushClientWrapper, ipaReader: ipaReader, validators: [flutterValidator], - buildCodePushClient: ({ - required http.Client httpClient, - Uri? hostedUri, - }) { - capturedHostedUri = hostedUri; - return codePushClient; - }, ) ..testArgResults = argResults ..testProcess = shorebirdProcess @@ -285,40 +253,6 @@ flutter: expect(exitCode, equals(ExitCode.usage.code)); }); - test('throws error when fetching apps fails.', () async { - const error = 'something went wrong'; - when(() => codePushClient.getApps()).thenThrow(error); - final tempDir = setUpTempDir(); - setUpTempArtifacts(tempDir); - final exitCode = await IOOverrides.runZoned( - () => runWithOverrides(command.run), - getCurrentDirectory: () => tempDir, - ); - verify(() => progress.fail(error)).called(1); - expect(exitCode, ExitCode.software.code); - }); - - test('throws error when app does not exist fails.', () async { - when( - () => logger.prompt(any(), defaultValue: any(named: 'defaultValue')), - ).thenReturn(appDisplayName); - when(() => codePushClient.getApps()).thenAnswer((_) async => []); - final tempDir = setUpTempDir(); - setUpTempArtifacts(tempDir); - final exitCode = await IOOverrides.runZoned( - () => runWithOverrides(command.run), - getCurrentDirectory: () => tempDir, - ); - verify( - () => logger.err( - ''' -Could not find app with id: "$appId". -Did you forget to run "shorebird init"?''', - ), - ).called(1); - expect(exitCode, ExitCode.software.code); - }); - test('aborts when user opts out', () async { when(() => logger.confirm(any())).thenReturn(false); final tempDir = setUpTempDir(); @@ -387,44 +321,6 @@ https://github.com/shorebirdtech/shorebird/issues/472 ).called(1); }); - test('throws error when fetching releases fails.', () async { - const error = 'something went wrong'; - when( - () => codePushClient.getReleases(appId: any(named: 'appId')), - ).thenThrow(error); - final tempDir = setUpTempDir(); - setUpTempArtifacts(tempDir); - final exitCode = await IOOverrides.runZoned( - () => runWithOverrides(command.run), - getCurrentDirectory: () => tempDir, - ); - verify(() => progress.fail(error)).called(1); - expect(exitCode, ExitCode.software.code); - }); - - test('throws error when release does not exist.', () async { - when( - () => codePushClient.getReleases(appId: any(named: 'appId')), - ).thenAnswer((_) async => []); - final tempDir = setUpTempDir(); - setUpTempArtifacts(tempDir); - final exitCode = await IOOverrides.runZoned( - () => runWithOverrides(command.run), - getCurrentDirectory: () => tempDir, - ); - verify( - () => logger.err( - ''' -Release not found: "$version" - -Patches can only be published for existing releases. -Please create a release using "shorebird release" and try again. -''', - ), - ).called(1); - expect(exitCode, ExitCode.software.code); - }); - test('exits with code 70 when release version cannot be determiend', () async { when(() => ipa.versionNumber).thenThrow(Exception('oops')); @@ -470,7 +366,9 @@ Please create a release using "shorebird release" and try again. ); expect(exitCode, equals(ExitCode.success.code)); verifyNever( - () => codePushClient.createPatch(releaseId: any(named: 'releaseId')), + () => codePushClientWrapper.createPatch( + releaseId: any(named: 'releaseId'), + ), ); verify(() => logger.info('No issues detected.')).called(1); }); @@ -486,101 +384,14 @@ Please create a release using "shorebird release" and try again. expect(exitCode, equals(ExitCode.success.code)); verifyNever(() => logger.confirm(any())); verify( - () => codePushClient.createPatch(releaseId: any(named: 'releaseId')), - ).called(1); - }); - - test('throws error when creating patch fails.', () async { - const error = 'something went wrong'; - when( - () => codePushClient.createPatch(releaseId: any(named: 'releaseId')), - ).thenThrow(error); - final tempDir = setUpTempDir(); - setUpTempArtifacts(tempDir); - final exitCode = await IOOverrides.runZoned( - () => runWithOverrides(command.run), - getCurrentDirectory: () => tempDir, - ); - verify(() => progress.fail(error)).called(1); - expect(exitCode, ExitCode.software.code); - }); - - test('throws error when uploading patch artifact fails.', () async { - const error = 'something went wrong'; - when( - () => codePushClient.createPatchArtifact( - artifactPath: any(named: 'artifactPath'), - patchId: any(named: 'patchId'), - arch: any(named: 'arch'), - platform: any(named: 'platform'), - hash: any(named: 'hash'), - ), - ).thenThrow(error); - final tempDir = setUpTempDir(); - setUpTempArtifacts(tempDir); - final exitCode = await IOOverrides.runZoned( - () => runWithOverrides(command.run), - getCurrentDirectory: () => tempDir, - ); - verify(() => progress.fail(error)).called(1); - expect(exitCode, ExitCode.software.code); - }); - - test('throws error when fetching channels fails.', () async { - const error = 'something went wrong'; - when( - () => codePushClient.getChannels(appId: any(named: 'appId')), - ).thenThrow(error); - final tempDir = setUpTempDir(); - setUpTempArtifacts(tempDir); - final exitCode = await IOOverrides.runZoned( - () => runWithOverrides(command.run), - getCurrentDirectory: () => tempDir, - ); - verify(() => progress.fail(error)).called(1); - expect(exitCode, ExitCode.software.code); - }); - - test('throws error when creating channel fails.', () async { - const error = 'something went wrong'; - when( - () => codePushClient.getChannels(appId: any(named: 'appId')), - ).thenAnswer((_) async => []); - when( - () => codePushClient.createChannel( + () => codePushClientWrapper.publishPatch( appId: any(named: 'appId'), - channel: any(named: 'channel'), + releaseId: any(named: 'releaseId'), + platform: any(named: 'platform'), + channelName: any(named: 'channelName'), + patchArtifactBundles: any(named: 'patchArtifactBundles'), ), - ).thenThrow(error); - final tempDir = setUpTempDir(); - setUpTempArtifacts(tempDir); - final exitCode = await IOOverrides.runZoned( - () => runWithOverrides(command.run), - getCurrentDirectory: () => tempDir, - ); - verify(() => progress.fail(error)).called(1); - expect(exitCode, ExitCode.software.code); - }); - - test('throws error when promoting patch fails.', () async { - const error = 'something went wrong'; - when( - () => codePushClient.getChannels(appId: any(named: 'appId')), - ).thenAnswer((_) async => []); - when( - () => codePushClient.promotePatch( - patchId: any(named: 'patchId'), - channelId: any(named: 'channelId'), - ), - ).thenThrow(error); - final tempDir = setUpTempDir(); - setUpTempArtifacts(tempDir); - final exitCode = await IOOverrides.runZoned( - () => runWithOverrides(command.run), - getCurrentDirectory: () => tempDir, - ); - verify(() => progress.fail(error)).called(1); - expect(exitCode, ExitCode.software.code); + ).called(1); }); test('succeeds when patch is successful', () async { @@ -601,7 +412,6 @@ Please create a release using "shorebird release" and try again. ).called(1); verify(() => logger.success('\nāœ… Published Patch!')).called(1); expect(exitCode, ExitCode.success.code); - expect(capturedHostedUri, isNull); }); test( @@ -625,7 +435,6 @@ flavors: ); verify(() => logger.success('\nāœ… Published Patch!')).called(1); expect(exitCode, ExitCode.success.code); - expect(capturedHostedUri, isNull); }); test('succeeds when patch is successful using custom base_url', () async { @@ -643,7 +452,6 @@ base_url: $baseUrl''', () => runWithOverrides(command.run), getCurrentDirectory: () => tempDir, ); - expect(capturedHostedUri, equals(Uri.parse(baseUrl))); }); test('prints flutter validation warnings', () async {