feat(shorebird_code_push_protocol): add canSideload field to CreateReleaseArtifactRequest and add support to CLI (#1274)
This commit is contained in:
@@ -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(
|
||||
|
||||
+1
@@ -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,
|
||||
);
|
||||
|
||||
+12
@@ -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();
|
||||
}
|
||||
|
||||
+5
@@ -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),
|
||||
};
|
||||
|
||||
|
||||
+1
@@ -9,6 +9,7 @@ void main() {
|
||||
platform: ReleasePlatform.android,
|
||||
hash: '1234',
|
||||
size: 9876,
|
||||
canSideload: true,
|
||||
);
|
||||
expect(
|
||||
CreateReleaseArtifactRequest.fromJson(request.toJson()).toJson(),
|
||||
|
||||
Reference in New Issue
Block a user