diff --git a/packages/shorebird_cli/lib/src/code_push_client_wrapper.dart b/packages/shorebird_cli/lib/src/code_push_client_wrapper.dart index 5a877cc53..a842ffc1a 100644 --- a/packages/shorebird_cli/lib/src/code_push_client_wrapper.dart +++ b/packages/shorebird_cli/lib/src/code_push_client_wrapper.dart @@ -386,6 +386,7 @@ Please create a release using "shorebird release" and try again. arch: archMetadata.arch, platform: platform, hash: hash, + canSideload: false, ); } on CodePushConflictException catch (_) { // Newlines are due to how logger.info interacts with logger.progress. @@ -412,6 +413,7 @@ ${archMetadata.arch} artifact already exists, continuing...''', arch: 'aab', platform: platform, hash: sha256.convert(await File(aabPath).readAsBytes()).toString(), + canSideload: true, ); } on CodePushConflictException catch (_) { // Newlines are due to how logger.info interacts with logger.progress. @@ -460,6 +462,7 @@ aab artifact already exists, continuing...''', arch: archMetadata.arch, platform: platform, hash: hash, + canSideload: false, ); } on CodePushConflictException catch (_) { // Newlines are due to how logger.info interacts with logger.progress. @@ -486,6 +489,7 @@ ${archMetadata.arch} artifact already exists, continuing...''', arch: 'aar', platform: platform, hash: sha256.convert(await File(aarPath).readAsBytes()).toString(), + canSideload: false, ); } on CodePushConflictException catch (_) { // Newlines are due to how logger.info interacts with logger.progress. @@ -527,6 +531,7 @@ aar artifact already exists, continuing...''', required int releaseId, required String xcarchivePath, required String runnerPath, + required bool isCodesigned, }) async { final createArtifactProgress = logger.progress('Creating artifacts'); final thinnedArchiveDirectory = @@ -540,6 +545,7 @@ aar artifact already exists, continuing...''', arch: 'xcarchive', platform: ReleasePlatform.ios, hash: sha256.convert(await zippedArchive.readAsBytes()).toString(), + canSideload: false, ); } catch (error) { _handleErrorAndExit( @@ -558,6 +564,7 @@ aar artifact already exists, continuing...''', arch: 'runner', platform: ReleasePlatform.ios, hash: sha256.convert(await zippedRunner.readAsBytes()).toString(), + canSideload: isCodesigned, ); } catch (error) { _handleErrorAndExit( @@ -593,6 +600,7 @@ aar artifact already exists, continuing...''', hash: sha256 .convert(await zippedAppFrameworkFile.readAsBytes()) .toString(), + canSideload: false, ); } catch (error) { _handleErrorAndExit( diff --git a/packages/shorebird_cli/lib/src/commands/release/release_ios_command.dart b/packages/shorebird_cli/lib/src/commands/release/release_ios_command.dart index f7f89c785..c3b19980d 100644 --- a/packages/shorebird_cli/lib/src/commands/release/release_ios_command.dart +++ b/packages/shorebird_cli/lib/src/commands/release/release_ios_command.dart @@ -200,6 +200,7 @@ ${summary.join('\n')} releaseId: release.id, xcarchivePath: archivePath, runnerPath: runnerPath, + isCodesigned: codesign, ); await codePushClientWrapper.updateReleaseStatus( diff --git a/packages/shorebird_cli/test/src/code_push_client_wrapper_test.dart b/packages/shorebird_cli/test/src/code_push_client_wrapper_test.dart index 2100d6dde..1274fd41e 100644 --- a/packages/shorebird_cli/test/src/code_push_client_wrapper_test.dart +++ b/packages/shorebird_cli/test/src/code_push_client_wrapper_test.dart @@ -994,6 +994,7 @@ Please bump your version number and try again.''', arch: any(named: 'arch'), platform: any(named: 'platform'), hash: any(named: 'hash'), + canSideload: any(named: 'canSideload'), ), ).thenAnswer((_) async => {}); }); @@ -1008,6 +1009,7 @@ Please bump your version number and try again.''', arch: any(named: 'arch'), platform: any(named: 'platform'), hash: any(named: 'hash'), + canSideload: any(named: 'canSideload'), ), ).thenThrow(error); final tempDir = setUpTempDir(); @@ -1041,6 +1043,7 @@ Please bump your version number and try again.''', arch: any(named: 'arch'), platform: any(named: 'platform'), hash: any(named: 'hash'), + canSideload: any(named: 'canSideload'), ), ).thenThrow(error); final tempDir = setUpTempDir(); @@ -1075,6 +1078,7 @@ Please bump your version number and try again.''', arch: any(named: 'arch'), platform: any(named: 'platform'), hash: any(named: 'hash'), + canSideload: any(named: 'canSideload'), ), ).thenThrow(const CodePushConflictException(message: error)); final tempDir = setUpTempDir(); @@ -1111,6 +1115,7 @@ Please bump your version number and try again.''', arch: any(named: 'arch'), platform: any(named: 'platform'), hash: any(named: 'hash'), + canSideload: any(named: 'canSideload'), ), ).thenThrow(const CodePushConflictException(message: error)); final tempDir = setUpTempDir(); @@ -1145,6 +1150,7 @@ Please bump your version number and try again.''', arch: any(named: 'arch'), platform: any(named: 'platform'), hash: any(named: 'hash'), + canSideload: any(named: 'canSideload'), ), ).thenAnswer((_) async => {}); final tempDir = setUpTempDir(); @@ -1176,6 +1182,7 @@ Please bump your version number and try again.''', arch: any(named: 'arch'), platform: any(named: 'platform'), hash: any(named: 'hash'), + canSideload: any(named: 'canSideload'), ), ).thenAnswer((_) async => {}); final tempDir = setUpTempDir(flavor: flavorName); @@ -1205,6 +1212,7 @@ Please bump your version number and try again.''', arch: any(named: 'arch'), platform: releasePlatform, hash: any(named: 'hash'), + canSideload: any(named: 'canSideload'), ), ).called(ShorebirdBuildMixin.allAndroidArchitectures.length); verify(() => progress.complete()).called(1); @@ -1255,6 +1263,7 @@ Please bump your version number and try again.''', arch: any(named: 'arch'), platform: any(named: 'platform'), hash: any(named: 'hash'), + canSideload: any(named: 'canSideload'), ), ).thenAnswer((_) async => {}); }); @@ -1269,6 +1278,7 @@ Please bump your version number and try again.''', arch: any(named: 'arch'), platform: any(named: 'platform'), hash: any(named: 'hash'), + canSideload: any(named: 'canSideload'), ), ).thenThrow(error); final tempDir = setUpTempDir(); @@ -1304,6 +1314,7 @@ Please bump your version number and try again.''', arch: any(named: 'arch'), platform: any(named: 'platform'), hash: any(named: 'hash'), + canSideload: any(named: 'canSideload'), ), ).thenThrow(error); final tempDir = setUpTempDir(); @@ -1340,6 +1351,7 @@ Please bump your version number and try again.''', arch: any(named: 'arch'), platform: any(named: 'platform'), hash: any(named: 'hash'), + canSideload: any(named: 'canSideload'), ), ).thenThrow(const CodePushConflictException(message: error)); final tempDir = setUpTempDir(); @@ -1378,6 +1390,7 @@ Please bump your version number and try again.''', arch: any(named: 'arch'), platform: any(named: 'platform'), hash: any(named: 'hash'), + canSideload: any(named: 'canSideload'), ), ).thenThrow(const CodePushConflictException(message: error)); final tempDir = setUpTempDir(); @@ -1414,6 +1427,7 @@ Please bump your version number and try again.''', arch: any(named: 'arch'), platform: any(named: 'platform'), hash: any(named: 'hash'), + canSideload: any(named: 'canSideload'), ), ).thenAnswer((_) async => {}); final tempDir = setUpTempDir(); @@ -1447,6 +1461,7 @@ Please bump your version number and try again.''', arch: any(named: 'arch'), platform: any(named: 'platform'), hash: any(named: 'hash'), + canSideload: any(named: 'canSideload'), ), ).thenAnswer((_) async => {}); final tempDir = setUpTempDir(flavor: flavorName); @@ -1474,6 +1489,7 @@ Please bump your version number and try again.''', arch: any(named: 'arch'), platform: releasePlatform, hash: any(named: 'hash'), + canSideload: any(named: 'canSideload'), ), ).called(ShorebirdBuildMixin.allAndroidArchitectures.length + 1); verify(() => progress.complete()).called(1); @@ -1503,6 +1519,7 @@ Please bump your version number and try again.''', arch: any(named: 'arch'), platform: any(named: 'platform'), hash: any(named: 'hash'), + canSideload: any(named: 'canSideload'), ), ).thenAnswer((_) async => {}); }); @@ -1519,6 +1536,7 @@ Please bump your version number and try again.''', arch: any(named: 'arch'), platform: any(named: 'platform'), hash: any(named: 'hash'), + canSideload: any(named: 'canSideload'), ), ).thenThrow(error); final tempDir = setUpTempDir(); @@ -1531,6 +1549,7 @@ Please bump your version number and try again.''', releaseId: releaseId, xcarchivePath: p.join(tempDir.path, xcarchivePath), runnerPath: p.join(tempDir.path, runnerPath), + isCodesigned: true, ), ), exitsWithCode(ExitCode.software), @@ -1553,6 +1572,7 @@ Please bump your version number and try again.''', arch: any(named: 'arch'), platform: any(named: 'platform'), hash: any(named: 'hash'), + canSideload: any(named: 'canSideload'), ), ).thenThrow(const CodePushConflictException(message: error)); final tempDir = setUpTempDir(); @@ -1565,6 +1585,7 @@ Please bump your version number and try again.''', releaseId: releaseId, xcarchivePath: p.join(tempDir.path, xcarchivePath), runnerPath: p.join(tempDir.path, runnerPath), + isCodesigned: false, ), ), exitsWithCode(ExitCode.software), @@ -1589,6 +1610,7 @@ Please bump your version number and try again.''', arch: any(named: 'arch'), platform: any(named: 'platform'), hash: any(named: 'hash'), + canSideload: any(named: 'canSideload'), ), ).thenThrow(error); final tempDir = setUpTempDir(); @@ -1601,6 +1623,7 @@ Please bump your version number and try again.''', releaseId: releaseId, xcarchivePath: p.join(tempDir.path, xcarchivePath), runnerPath: p.join(tempDir.path, runnerPath), + isCodesigned: false, ), ), exitsWithCode(ExitCode.software), @@ -1620,6 +1643,7 @@ Please bump your version number and try again.''', arch: any(named: 'arch'), platform: any(named: 'platform'), hash: any(named: 'hash'), + canSideload: any(named: 'canSideload'), ), ).thenAnswer((_) async => {}); final tempDir = setUpTempDir(); @@ -1631,6 +1655,7 @@ Please bump your version number and try again.''', releaseId: releaseId, xcarchivePath: p.join(tempDir.path, xcarchivePath), runnerPath: p.join(tempDir.path, runnerPath), + isCodesigned: true, ), getCurrentDirectory: () => tempDir, ), @@ -1661,6 +1686,7 @@ Please bump your version number and try again.''', arch: any(named: 'arch'), platform: any(named: 'platform'), hash: any(named: 'hash'), + canSideload: any(named: 'canSideload'), ), ).thenThrow( Exception('oh no'), @@ -1689,6 +1715,7 @@ Please bump your version number and try again.''', arch: any(named: 'arch'), platform: any(named: 'platform'), hash: any(named: 'hash'), + canSideload: any(named: 'canSideload'), ), ).thenAnswer((_) async => {}); final tempDir = setUpTempDir(); diff --git a/packages/shorebird_cli/test/src/commands/release/release_ios_command_test.dart b/packages/shorebird_cli/test/src/commands/release/release_ios_command_test.dart index 0763c005b..1f03ef5e5 100644 --- a/packages/shorebird_cli/test/src/commands/release/release_ios_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/release/release_ios_command_test.dart @@ -264,6 +264,7 @@ flutter: releaseId: any(named: 'releaseId'), xcarchivePath: any(named: 'xcarchivePath'), runnerPath: any(named: 'runnerPath'), + isCodesigned: any(named: 'isCodesigned'), ), ).thenAnswer((_) async => release); when( @@ -393,6 +394,27 @@ flutter: ), ).called(1); }); + + test('creates unsigned release artifacts', () async { + final tempDir = setUpTempDir(); + final result = await IOOverrides.runZoned( + () => runWithOverrides(command.run), + getCurrentDirectory: () => tempDir, + ); + + expect(result, equals(ExitCode.success.code)); + + verify( + () => codePushClientWrapper.createIosReleaseArtifacts( + appId: appId, + releaseId: release.id, + xcarchivePath: + any(named: 'xcarchivePath', that: endsWith('.xcarchive')), + runnerPath: any(named: 'runnerPath', that: endsWith('Runner.app')), + isCodesigned: false, + ), + ).called(1); + }); }); test('exits with code 70 when build fails with non-zero exit code', @@ -495,6 +517,7 @@ error: exportArchive: No signing certificate "iOS Distribution" found xcarchivePath: any(named: 'xcarchivePath', that: endsWith('.xcarchive')), runnerPath: any(named: 'runnerPath', that: endsWith('Runner.app')), + isCodesigned: any(named: 'isCodesigned'), ), ); }); @@ -666,6 +689,7 @@ error: exportArchive: No signing certificate "iOS Distribution" found xcarchivePath: any(named: 'xcarchivePath', that: endsWith('.xcarchive')), runnerPath: any(named: 'runnerPath', that: endsWith('Runner.app')), + isCodesigned: true, ), ).called(1); verify( @@ -738,6 +762,7 @@ flavors: xcarchivePath: any(named: 'xcarchivePath', that: endsWith('.xcarchive')), runnerPath: any(named: 'runnerPath', that: endsWith('Runner.app')), + isCodesigned: true, ), ).called(1); expect(exitCode, ExitCode.success.code); @@ -774,6 +799,7 @@ flavors: xcarchivePath: any(named: 'xcarchivePath', that: endsWith('.xcarchive')), runnerPath: any(named: 'runnerPath', that: endsWith('Runner.app')), + isCodesigned: true, ), ).called(1); verify( diff --git a/packages/shorebird_cli/test/src/commands/release/release_ios_framework_command_test.dart b/packages/shorebird_cli/test/src/commands/release/release_ios_framework_command_test.dart index e6c973d46..556a3a1c0 100644 --- a/packages/shorebird_cli/test/src/commands/release/release_ios_framework_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/release/release_ios_framework_command_test.dart @@ -313,6 +313,7 @@ flutter: xcarchivePath: any(named: 'xcarchivePath', that: endsWith('.xcarchive')), runnerPath: any(named: 'runnerPath', that: endsWith('Runner.app')), + isCodesigned: any(named: 'isCodesigned'), ), ); }); diff --git a/packages/shorebird_code_push_client/example/main.dart b/packages/shorebird_code_push_client/example/main.dart index dd84ea48c..f5d21eccd 100644 --- a/packages/shorebird_code_push_client/example/main.dart +++ b/packages/shorebird_code_push_client/example/main.dart @@ -36,6 +36,7 @@ Future main() async { artifactPath: '', // e.g. 'libapp.so' arch: '', // e.g. 'aarch64' hash: '', // 'sha256 hash of the artifact' + canSideload: true, ); // Create a new patch. diff --git a/packages/shorebird_code_push_client/lib/src/code_push_client.dart b/packages/shorebird_code_push_client/lib/src/code_push_client.dart index 0d4f36d66..bc1dd5803 100644 --- a/packages/shorebird_code_push_client/lib/src/code_push_client.dart +++ b/packages/shorebird_code_push_client/lib/src/code_push_client.dart @@ -198,18 +198,23 @@ class CodePushClient { required String arch, required ReleasePlatform platform, required String hash, + required bool canSideload, }) async { final request = http.MultipartRequest( 'POST', Uri.parse('$_v1/apps/$appId/releases/$releaseId/artifacts'), ); final file = await http.MultipartFile.fromPath('file', artifactPath); - request.fields.addAll({ - 'arch': arch, - 'platform': platform.name, - 'hash': hash, - 'size': '${file.length}', - }); + + final payload = CreateReleaseArtifactRequest( + arch: arch, + platform: platform, + hash: hash, + size: file.length, + canSideload: canSideload, + ).toJson().map((key, value) => MapEntry(key, '$value')); + request.fields.addAll(payload); + final response = await _httpClient.send(request); final body = await response.stream.bytesToString(); diff --git a/packages/shorebird_code_push_client/test/src/code_push_client_test.dart b/packages/shorebird_code_push_client/test/src/code_push_client_test.dart index 059d52c68..fed693208 100644 --- a/packages/shorebird_code_push_client/test/src/code_push_client_test.dart +++ b/packages/shorebird_code_push_client/test/src/code_push_client_test.dart @@ -497,6 +497,7 @@ void main() { const platform = ReleasePlatform.android; const hash = 'test-hash'; const size = 42; + const canSideload = true; test('makes the correct request', () async { final tempDir = Directory.systemTemp.createTempSync(); @@ -511,6 +512,7 @@ void main() { arch: arch, platform: platform, hash: hash, + canSideload: canSideload, ); } catch (_) {} @@ -545,6 +547,7 @@ void main() { arch: arch, platform: platform, hash: hash, + canSideload: canSideload, ), throwsA( isA().having( @@ -578,6 +581,7 @@ void main() { arch: arch, platform: platform, hash: hash, + canSideload: canSideload, ), throwsA(isA()), ); @@ -605,6 +609,7 @@ void main() { arch: arch, platform: platform, hash: hash, + canSideload: canSideload, ), throwsA(isA()), ); @@ -630,6 +635,7 @@ void main() { arch: arch, platform: platform, hash: hash, + canSideload: canSideload, ), throwsA( isA().having( @@ -685,6 +691,7 @@ void main() { arch: arch, platform: platform, hash: hash, + canSideload: canSideload, ), throwsA( isA().having( @@ -748,6 +755,7 @@ void main() { arch: arch, platform: platform, hash: hash, + canSideload: canSideload, ), completes, ); diff --git a/packages/shorebird_code_push_protocol/lib/src/messages/create_release_artifact/create_release_artifact_request.dart b/packages/shorebird_code_push_protocol/lib/src/messages/create_release_artifact/create_release_artifact_request.dart index 4affb2e1e..f03ffff49 100644 --- a/packages/shorebird_code_push_protocol/lib/src/messages/create_release_artifact/create_release_artifact_request.dart +++ b/packages/shorebird_code_push_protocol/lib/src/messages/create_release_artifact/create_release_artifact_request.dart @@ -5,6 +5,9 @@ part 'create_release_artifact_request.g.dart'; /// {@template create_release_artifact_request} /// The request body for POST /api/v1/artifacts/:id/artifacts +/// +/// Because this request is sent as a http.MultipartRequest, all fields +/// serialize to strings. /// {@endtemplate} @JsonSerializable() class CreateReleaseArtifactRequest { @@ -14,6 +17,7 @@ class CreateReleaseArtifactRequest { required this.platform, required this.hash, required this.size, + required this.canSideload, }); /// Converts a Map to a [CreateReleaseArtifactRequest] @@ -32,6 +36,10 @@ class CreateReleaseArtifactRequest { /// The hash of the artifact. final String hash; + /// Whether the artifact can installed and run on a device/emulator as-is. + @JsonKey(fromJson: _parseStringToBool, toJson: _parseBoolToString) + final bool canSideload; + /// The size of the artifact in bytes. @JsonKey(fromJson: _parseStringToInt, toJson: _parseIntToString) final int size; @@ -39,4 +47,8 @@ class CreateReleaseArtifactRequest { static int _parseStringToInt(dynamic value) => int.parse(value as String); static String _parseIntToString(dynamic value) => value.toString(); + + static bool _parseStringToBool(dynamic value) => value == 'true'; + + static String _parseBoolToString(dynamic value) => value.toString(); } diff --git a/packages/shorebird_code_push_protocol/lib/src/messages/create_release_artifact/create_release_artifact_request.g.dart b/packages/shorebird_code_push_protocol/lib/src/messages/create_release_artifact/create_release_artifact_request.g.dart index 8a56b8a20..c37ab0acf 100644 --- a/packages/shorebird_code_push_protocol/lib/src/messages/create_release_artifact/create_release_artifact_request.g.dart +++ b/packages/shorebird_code_push_protocol/lib/src/messages/create_release_artifact/create_release_artifact_request.g.dart @@ -21,9 +21,12 @@ CreateReleaseArtifactRequest _$CreateReleaseArtifactRequestFromJson( hash: $checkedConvert('hash', (v) => v as String), size: $checkedConvert( 'size', (v) => CreateReleaseArtifactRequest._parseStringToInt(v)), + canSideload: $checkedConvert('can_sideload', + (v) => CreateReleaseArtifactRequest._parseStringToBool(v)), ); return val; }, + fieldKeyMap: const {'canSideload': 'can_sideload'}, ); Map _$CreateReleaseArtifactRequestToJson( @@ -32,6 +35,8 @@ Map _$CreateReleaseArtifactRequestToJson( 'arch': instance.arch, 'platform': _$ReleasePlatformEnumMap[instance.platform]!, 'hash': instance.hash, + 'can_sideload': + CreateReleaseArtifactRequest._parseBoolToString(instance.canSideload), 'size': CreateReleaseArtifactRequest._parseIntToString(instance.size), }; diff --git a/packages/shorebird_code_push_protocol/test/src/messages/create_release_artifact/create_release_artifact_request_test.dart b/packages/shorebird_code_push_protocol/test/src/messages/create_release_artifact/create_release_artifact_request_test.dart index 28d4e94ba..9a272a982 100644 --- a/packages/shorebird_code_push_protocol/test/src/messages/create_release_artifact/create_release_artifact_request_test.dart +++ b/packages/shorebird_code_push_protocol/test/src/messages/create_release_artifact/create_release_artifact_request_test.dart @@ -9,6 +9,7 @@ void main() { platform: ReleasePlatform.android, hash: '1234', size: 9876, + canSideload: true, ); expect( CreateReleaseArtifactRequest.fromJson(request.toJson()).toJson(),