feat(shorebird_cli): upload ipa as part of shorebird release ios (#705)
This commit is contained in:
@@ -442,6 +442,29 @@ aar artifact already exists, continuing...''',
|
||||
createArtifactProgress.complete();
|
||||
}
|
||||
|
||||
/// Uploads a release ipa to the Shorebird server.
|
||||
Future<void> createIosReleaseArtifact({
|
||||
required int releaseId,
|
||||
required String ipaPath,
|
||||
}) async {
|
||||
final createArtifactProgress = logger.progress('Creating artifacts');
|
||||
final ipaFile = File(ipaPath);
|
||||
try {
|
||||
await codePushClient.createReleaseArtifact(
|
||||
releaseId: releaseId,
|
||||
artifactPath: ipaPath,
|
||||
arch: 'ipa',
|
||||
platform: 'ios',
|
||||
hash: sha256.convert(await ipaFile.readAsBytes()).toString(),
|
||||
);
|
||||
} catch (error) {
|
||||
createArtifactProgress.fail('Error uploading ipa: $error');
|
||||
exit(ExitCode.software.code);
|
||||
}
|
||||
|
||||
createArtifactProgress.complete();
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
Future<Patch> createPatch({required int releaseId}) async {
|
||||
final createPatchProgress = logger.progress('Creating patch');
|
||||
|
||||
@@ -160,16 +160,20 @@ ${summary.join('\n')}
|
||||
return ExitCode.software.code;
|
||||
}
|
||||
|
||||
if (existingRelease == null) {
|
||||
await codePushClientWrapper.createRelease(
|
||||
appId: appId,
|
||||
version: releaseVersion,
|
||||
flutterRevision: shorebirdFlutterRevision,
|
||||
);
|
||||
}
|
||||
final release = existingRelease ??
|
||||
await codePushClientWrapper.createRelease(
|
||||
appId: appId,
|
||||
version: releaseVersion,
|
||||
flutterRevision: shorebirdFlutterRevision,
|
||||
);
|
||||
|
||||
final relativeIpaPath = p.relative(ipaPath);
|
||||
|
||||
await codePushClientWrapper.createIosReleaseArtifact(
|
||||
releaseId: release.id,
|
||||
ipaPath: ipaPath,
|
||||
);
|
||||
|
||||
logger
|
||||
..success('\n✅ Published Release!')
|
||||
..info('''
|
||||
|
||||
@@ -1308,6 +1308,113 @@ Please bump your version number and try again.''',
|
||||
});
|
||||
});
|
||||
|
||||
group('createIosReleaseArtifact', () {
|
||||
final ipaPath = p.join('path', 'to', 'app.ipa');
|
||||
|
||||
Directory setUpTempDir({String? flavor}) {
|
||||
final tempDir = Directory.systemTemp.createTempSync();
|
||||
File(p.join(tempDir.path, ipaPath)).createSync(recursive: true);
|
||||
return tempDir;
|
||||
}
|
||||
|
||||
setUp(() {
|
||||
when(
|
||||
() => codePushClient.createReleaseArtifact(
|
||||
artifactPath: any(named: 'artifactPath'),
|
||||
releaseId: any(named: 'releaseId'),
|
||||
arch: any(named: 'arch'),
|
||||
platform: any(named: 'platform'),
|
||||
hash: any(named: 'hash'),
|
||||
),
|
||||
).thenAnswer((_) async => {});
|
||||
});
|
||||
|
||||
test('exits with code 70 when artifact creation fails', () async {
|
||||
const error = 'something went wrong';
|
||||
when(
|
||||
() => codePushClient.createReleaseArtifact(
|
||||
artifactPath: any(named: 'artifactPath'),
|
||||
releaseId: any(named: 'releaseId'),
|
||||
arch: any(named: 'arch'),
|
||||
platform: any(named: 'platform'),
|
||||
hash: any(named: 'hash'),
|
||||
),
|
||||
).thenThrow(error);
|
||||
final tempDir = setUpTempDir();
|
||||
|
||||
await IOOverrides.runZoned(
|
||||
() async => expectLater(
|
||||
() async => runWithOverrides(
|
||||
() async => codePushClientWrapper.createIosReleaseArtifact(
|
||||
releaseId: releaseId,
|
||||
ipaPath: p.join(tempDir.path, ipaPath),
|
||||
),
|
||||
),
|
||||
exitsWithCode(ExitCode.software),
|
||||
),
|
||||
getCurrentDirectory: () => tempDir,
|
||||
);
|
||||
|
||||
verify(() => progress.fail(any(that: contains(error)))).called(1);
|
||||
});
|
||||
|
||||
test('exits with code 70 when uploading ipa that already exists',
|
||||
() async {
|
||||
const error = 'something went wrong';
|
||||
when(
|
||||
() => codePushClient.createReleaseArtifact(
|
||||
artifactPath: any(named: 'artifactPath', that: endsWith('.ipa')),
|
||||
releaseId: any(named: 'releaseId'),
|
||||
arch: any(named: 'arch'),
|
||||
platform: any(named: 'platform'),
|
||||
hash: any(named: 'hash'),
|
||||
),
|
||||
).thenThrow(const CodePushConflictException(message: error));
|
||||
final tempDir = setUpTempDir();
|
||||
|
||||
await IOOverrides.runZoned(
|
||||
() async => expectLater(
|
||||
() async => runWithOverrides(
|
||||
() async => codePushClientWrapper.createIosReleaseArtifact(
|
||||
releaseId: releaseId,
|
||||
ipaPath: p.join(tempDir.path, ipaPath),
|
||||
),
|
||||
),
|
||||
exitsWithCode(ExitCode.software),
|
||||
),
|
||||
getCurrentDirectory: () => tempDir,
|
||||
);
|
||||
|
||||
verify(() => progress.fail(any(that: contains(error)))).called(1);
|
||||
});
|
||||
|
||||
test('completes successfully when artifact is created', () async {
|
||||
when(
|
||||
() => codePushClient.createReleaseArtifact(
|
||||
artifactPath: any(named: 'artifactPath'),
|
||||
releaseId: any(named: 'releaseId'),
|
||||
arch: any(named: 'arch'),
|
||||
platform: any(named: 'platform'),
|
||||
hash: any(named: 'hash'),
|
||||
),
|
||||
).thenAnswer((_) async => {});
|
||||
final tempDir = setUpTempDir();
|
||||
|
||||
await runWithOverrides(
|
||||
() async => IOOverrides.runZoned(
|
||||
() async => codePushClientWrapper.createIosReleaseArtifact(
|
||||
releaseId: releaseId,
|
||||
ipaPath: p.join(tempDir.path, ipaPath),
|
||||
),
|
||||
getCurrentDirectory: () => tempDir,
|
||||
),
|
||||
);
|
||||
|
||||
verify(() => progress.complete()).called(1);
|
||||
verifyNever(() => progress.fail(any()));
|
||||
});
|
||||
});
|
||||
|
||||
group('patch', () {
|
||||
group('createPatch', () {
|
||||
test('exits with code 70 when creating patch fails', () async {
|
||||
|
||||
@@ -213,6 +213,12 @@ flutter:
|
||||
flutterRevision: any(named: 'flutterRevision'),
|
||||
),
|
||||
).thenAnswer((_) async => release);
|
||||
when(
|
||||
() => codePushClientWrapper.createIosReleaseArtifact(
|
||||
releaseId: any(named: 'releaseId'),
|
||||
ipaPath: any(named: 'ipaPath'),
|
||||
),
|
||||
).thenAnswer((_) async => release);
|
||||
when(() => flutterValidator.validate(any())).thenAnswer((_) async => []);
|
||||
|
||||
command = runWithOverrides(
|
||||
@@ -389,6 +395,12 @@ error: exportArchive: No signing certificate "iOS Distribution" found
|
||||
|
||||
expect(exitCode, ExitCode.success.code);
|
||||
verify(() => logger.info('Aborting.')).called(1);
|
||||
verifyNever(
|
||||
() => codePushClientWrapper.createIosReleaseArtifact(
|
||||
releaseId: release.id,
|
||||
ipaPath: any(named: 'ipaPath', that: endsWith('.ipa')),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('throws error when unable to detect flutter revision', () async {
|
||||
@@ -449,6 +461,12 @@ error: exportArchive: No signing certificate "iOS Distribution" found
|
||||
),
|
||||
),
|
||||
).called(1);
|
||||
verify(
|
||||
() => codePushClientWrapper.createIosReleaseArtifact(
|
||||
releaseId: release.id,
|
||||
ipaPath: any(named: 'ipaPath', that: endsWith('.ipa')),
|
||||
),
|
||||
).called(1);
|
||||
expect(exitCode, ExitCode.success.code);
|
||||
});
|
||||
|
||||
@@ -485,6 +503,12 @@ flavors:
|
||||
),
|
||||
),
|
||||
).called(1);
|
||||
verify(
|
||||
() => codePushClientWrapper.createIosReleaseArtifact(
|
||||
releaseId: release.id,
|
||||
ipaPath: any(named: 'ipaPath', that: endsWith('.ipa')),
|
||||
),
|
||||
).called(1);
|
||||
expect(exitCode, ExitCode.success.code);
|
||||
});
|
||||
|
||||
@@ -511,6 +535,12 @@ flavors:
|
||||
flutterRevision: any(named: 'flutterRevision'),
|
||||
),
|
||||
);
|
||||
verify(
|
||||
() => codePushClientWrapper.createIosReleaseArtifact(
|
||||
releaseId: release.id,
|
||||
ipaPath: any(named: 'ipaPath', that: endsWith('.ipa')),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
test('provides appropriate ExportOptions.plist to build ipa command',
|
||||
|
||||
Reference in New Issue
Block a user