upload static method
Future<void>
upload({
- required String ipaPath,
- required AppleCredentials credentials,
- ProcessRunner processRunner = const SystemProcessRunner(),
- String? homeDirectory,
Uploads an IPA with App Store Connect API credentials.
Implementation
static Future<void> upload({
required String ipaPath,
required AppleCredentials credentials,
ProcessRunner processRunner = const SystemProcessRunner(),
String? homeDirectory,
}) async {
SmfError.check(
RegExp(r'^[A-Za-z0-9]+$').hasMatch(credentials.keyId),
'The App Store Connect key ID must contain only letters and digits.',
SmfErrorCode.invalidCredential,
);
final home = p.normalize(
p.absolute(
homeDirectory ?? Platform.environment['HOME'] ?? Directory.current.path,
),
);
final privateKeysDirectory = p.join(
home,
'.appstoreconnect',
'private_keys',
);
final keyPath = p.join(
privateKeysDirectory,
'AuthKey_${credentials.keyId}.p8',
);
await _rejectSymbolicLinks(
home,
keyPath,
description: 'The App Store Connect private-key path',
code: SmfErrorCode.privateKeyCollision,
);
final didPrivateKeysDirectoryExist = await Directory(
privateKeysDirectory,
).exists();
final didKeyExist = await File(keyPath).exists();
if (didKeyExist) {
final existing = (await File(keyPath).readAsString()).trim();
SmfError.check(
existing == credentials.privateKey.trim(),
'$keyPath already exists with different contents.',
SmfErrorCode.privateKeyCollision,
);
} else {
await Directory(privateKeysDirectory).create(recursive: true);
await _rejectSymbolicLinks(
home,
keyPath,
description: 'The App Store Connect private-key path',
code: SmfErrorCode.privateKeyCollision,
);
if (!didPrivateKeysDirectoryExist) {
await processRunner.run('/bin/chmod', <String>[
'700',
privateKeysDirectory,
]);
}
await File(keyPath).writeAsString(credentials.privateKey);
}
await processRunner.run('/bin/chmod', <String>['600', keyPath]);
try {
await processRunner.run('xcrun', <String>[
'altool',
'--upload-app',
'--type',
'ios',
'-f',
ipaPath,
'--apiKey',
credentials.keyId,
'--apiIssuer',
credentials.issuerId,
]);
} finally {
if (!didKeyExist) {
try {
await File(keyPath).delete();
} on FileSystemException {
// Best-effort cleanup must not replace the upload result.
}
}
}
}