feat(shorebird_code_push_protocol): add canSideload field to CreateReleaseArtifactRequest and add support to CLI (#1274)

This commit is contained in:
Bryan Oltman
2023-09-18 11:48:51 -04:00
committed by GitHub
parent 2d047a42bd
commit d19372955f
11 changed files with 101 additions and 6 deletions
@@ -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(
@@ -200,6 +200,7 @@ ${summary.join('\n')}
releaseId: release.id,
xcarchivePath: archivePath,
runnerPath: runnerPath,
isCodesigned: codesign,
);
await codePushClientWrapper.updateReleaseStatus(
@@ -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();
@@ -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(
@@ -313,6 +313,7 @@ flutter:
xcarchivePath:
any(named: 'xcarchivePath', that: endsWith('.xcarchive')),
runnerPath: any(named: 'runnerPath', that: endsWith('Runner.app')),
isCodesigned: any(named: 'isCodesigned'),
),
);
});
@@ -36,6 +36,7 @@ Future<void> main() async {
artifactPath: '<PATH TO ARTIFACT>', // e.g. 'libapp.so'
arch: '<ARCHITECTURE>', // e.g. 'aarch64'
hash: '<HASH>', // 'sha256 hash of the artifact'
canSideload: true,
);
// Create a new patch.
@@ -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();
@@ -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<CodePushException>().having(
@@ -578,6 +581,7 @@ void main() {
arch: arch,
platform: platform,
hash: hash,
canSideload: canSideload,
),
throwsA(isA<CodePushNotFoundException>()),
);
@@ -605,6 +609,7 @@ void main() {
arch: arch,
platform: platform,
hash: hash,
canSideload: canSideload,
),
throwsA(isA<CodePushConflictException>()),
);
@@ -630,6 +635,7 @@ void main() {
arch: arch,
platform: platform,
hash: hash,
canSideload: canSideload,
),
throwsA(
isA<CodePushException>().having(
@@ -685,6 +691,7 @@ void main() {
arch: arch,
platform: platform,
hash: hash,
canSideload: canSideload,
),
throwsA(
isA<CodePushException>().having(
@@ -748,6 +755,7 @@ void main() {
arch: arch,
platform: platform,
hash: hash,
canSideload: canSideload,
),
completes,
);
@@ -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<String, dynamic> 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();
}
@@ -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<String, dynamic> _$CreateReleaseArtifactRequestToJson(
@@ -32,6 +35,8 @@ Map<String, dynamic> _$CreateReleaseArtifactRequestToJson(
'arch': instance.arch,
'platform': _$ReleasePlatformEnumMap[instance.platform]!,
'hash': instance.hash,
'can_sideload':
CreateReleaseArtifactRequest._parseBoolToString(instance.canSideload),
'size': CreateReleaseArtifactRequest._parseIntToString(instance.size),
};
@@ -9,6 +9,7 @@ void main() {
platform: ReleasePlatform.android,
hash: '1234',
size: 9876,
canSideload: true,
);
expect(
CreateReleaseArtifactRequest.fromJson(request.toJson()).toJson(),