import AppKit import Sparkle func log(_ message: String) { FileHandle.standardError.write(Data((message + "\n").utf8)) } @MainActor final class Driver: NSObject, SPUUserDriver { func show(_ request: SPUUpdatePermissionRequest, reply: @escaping (SUUpdatePermissionResponse) -> Void) { reply(SUUpdatePermissionResponse(automaticUpdateChecks: true, sendSystemProfile: false)) } func showUserInitiatedUpdateCheck(cancellation: @escaping () -> Void) {} func showUpdateFound(with appcastItem: SUAppcastItem, state: SPUUserUpdateState, reply: @escaping (SPUUserUpdateChoice) -> Void) { log("installing \(appcastItem.displayVersionString) (\(appcastItem.versionString))") reply(.install) } func showUpdateReleaseNotes(with downloadData: SPUDownloadData) {} func showUpdateReleaseNotesFailedToDownloadWithError(_ error: any Error) {} func showUpdateNotFoundWithError(_ error: any Error, acknowledgement: @escaping () -> Void) { log("no update: \(error.localizedDescription)") exit(0) } func showUpdaterError(_ error: any Error, acknowledgement: @escaping () -> Void) { log("update failed: \(error.localizedDescription)") exit(1) } func showDownloadInitiated(cancellation: @escaping () -> Void) {} func showDownloadDidReceiveExpectedContentLength(_ expectedContentLength: UInt64) {} func showDownloadDidReceiveData(ofLength length: UInt64) {} func showDownloadDidStartExtractingUpdate() {} func showExtractionReceivedProgress(_ progress: Double) {} func showReady(toInstallAndRelaunch reply: @escaping (SPUUserUpdateChoice) -> Void) { reply(.install) } func showInstallingUpdate(withApplicationTerminated applicationTerminated: Bool, retryTerminatingApplication: @escaping () -> Void) { log("installing") } func showUpdateInstalledAndRelaunched(_ relaunched: Bool, acknowledgement: @escaping () -> Void) { log("installed") exit(0) } func dismissUpdateInstallation() { exit(0) } } let app = Bundle.main.executableURL!.resolvingSymlinksInPath().deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent() if NSWorkspace.shared.runningApplications.contains(where: { $0.bundleURL?.standardizedFileURL == app.standardizedFileURL }) { log("\(app.path) is running; its own updater handles updates") exit(0) } let bundle = Bundle(url: app)! let driver = Driver() let updater = SPUUpdater(hostBundle: bundle, applicationBundle: bundle, userDriver: driver, delegate: nil) do { try updater.start() } catch { log("start updater: \(error.localizedDescription)") exit(1) } updater.checkForUpdates() RunLoop.main.run()