import class Foundation.Bundle import class Foundation.FileManager import class Foundation.NSFileAccessIntent import class Foundation.NSFileCoordinator import class Foundation.NSMutableDictionary import struct Foundation.URL import struct Foundation.UUID import var Foundation.kCFBundleIdentifierKey import var Foundation.kCFBundleNameKey // Export running app as .ipa, then return path to exported file. func exportIPA() async throws -> URL { // Path to app bundle let bundleURL = Bundle.main.bundleURL // Create Payload/ directory let temporaryDirectory = FileManager.default.temporaryDirectory .appendingPathComponent(UUID().uuidString) let payloadDirectory = temporaryDirectory .appendingPathComponent("Payload") try FileManager.default.createDirectory( at: payloadDirectory, withIntermediateDirectories: true, attributes: nil ) defer { // Remove temporary directory try? FileManager.default.removeItem(at: temporaryDirectory) } let appName = Bundle.main.object(forInfoDictionaryKey: kCFBundleNameKey as String) as? String ?? "App" let appURL = payloadDirectory.appendingPathComponent("\(appName).app") let ipaURL = FileManager.default.temporaryDirectory .appendingPathComponent("\(appName).ipa") // Copy app bundle to Payload/ try FileManager.default.copyItem(at: bundleURL, to: appURL) // Remove occurrences of "swift-playgrounds-" from bundle identifier. // (Apple forbids registering App IDs containing that string) let bundleID = Bundle.main.bundleIdentifier! let updatedBundleID = bundleID.replacingOccurrences(of: "swift-playgrounds-", with: "") // Update bundle identifier let plistURL = appURL.appendingPathComponent("Info.plist") let infoPlist = try NSMutableDictionary(contentsOf: plistURL, error: ()) infoPlist[kCFBundleIdentifierKey as String] = updatedBundleID try infoPlist.write(to: plistURL) try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in // Coordinating read access to Payload/ "forUploading" automatically zips the directory for us. let readIntent = NSFileAccessIntent.readingIntent( with: payloadDirectory, options: .forUploading ) let fileCoordinator = NSFileCoordinator() fileCoordinator.coordinate(with: [readIntent], queue: .main) { error in do { if let error = error { throw error } // Change file extension from "zip" to "ipa" _ = try FileManager.default .replaceItemAt(ipaURL, withItemAt: readIntent.url) print("Exported .ipa:", ipaURL) continuation.resume() } catch { print("Failed to export .ipa:", error) continuation.resume(throwing: error) } } } return ipaURL }