Files
xtool-mirror/Sources/XKit/Signer/AutoSigner.swift
2025-05-24 00:35:46 +05:30

100 lines
3.6 KiB
Swift

import Foundation
/// Provisions and codesigns.
public struct AutoSigner {
public enum Error: LocalizedError {
case noSigners
case errorReading(String)
case errorWriting(String)
public var errorDescription: String? {
switch self {
case .noSigners:
return NSLocalizedString("signer.error.no_signers", value: "No signers found", comment: "")
case .errorReading(let file):
return "\(file)".withCString {
String.localizedStringWithFormat(
NSLocalizedString(
"signer.error.error_reading", value: "Error while reading %s", comment: ""
), $0
)
}
case .errorWriting(let file):
return "\(file)".withCString {
String.localizedStringWithFormat(
NSLocalizedString(
"signer.error.error_writing", value: "Error while writing %s", comment: ""
), $0
)
}
}
}
}
public let context: SigningContext
public let confirmRevocation: @Sendable ([DeveloperServicesCertificate]) async -> Bool
public init(
context: SigningContext,
confirmRevocation: @escaping @Sendable ([DeveloperServicesCertificate]) async -> Bool
) {
self.context = context
self.confirmRevocation = confirmRevocation
}
public func sign(
app: URL,
status: @escaping (String) -> Void,
progress: @escaping @Sendable (Double?) -> Void,
didProvision: @escaping () throws -> Void = {}
) async throws -> String {
status(NSLocalizedString("signer.provisioning", value: "Provisioning", comment: ""))
let response = try await DeveloperServicesProvisioningOperation(
context: context,
app: app,
confirmRevocation: confirmRevocation,
progress: progress
).perform()
let provisioningDict = response.provisioningDict
let signingInfo = response.signingInfo
guard let mainInfo = provisioningDict[app] else {
throw Error.errorReading("app bundle ID")
}
try didProvision()
for (url, info) in provisioningDict {
let infoPlist = url.appendingPathComponent("Info.plist")
guard let data = try? Data(contentsOf: infoPlist),
let dict = try? PropertyListSerialization.propertyList(from: data, format: nil) as? [String: Any]
else { throw Error.errorReading(infoPlist.lastPathComponent) }
let nsDict = NSMutableDictionary(dictionary: dict)
nsDict["CFBundleIdentifier"] = info.newBundleID
guard nsDict.write(to: infoPlist, atomically: true) else {
throw Error.errorWriting(infoPlist.lastPathComponent)
}
let profURL = url.appendingPathComponent("embedded.mobileprovision")
if profURL.exists {
try FileManager.default.removeItem(at: profURL)
}
try info.mobileprovision.data().write(to: profURL)
}
let entitlements = provisioningDict.mapValues(\.entitlements)
status(NSLocalizedString("signer.signing", value: "Signing", comment: ""))
try await context.signer.sign(
app: app,
identity: .real(signingInfo.certificate, signingInfo.privateKey),
entitlementMapping: entitlements,
progress: progress
)
progress(1)
return mainInfo.newBundleID
}
}