From aa9658754d133e3dad7bb28e0629519f44b9b402 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Thu, 27 Nov 2025 13:31:21 +0100 Subject: [PATCH] Use NVAlert 2.0 with urgency --- Package.resolved | 6 +-- Package.swift | 2 +- README.md | 6 ++- Sources/AppUpdater/API/UpdateCheck.swift | 54 +++++++++++++----------- 4 files changed, 37 insertions(+), 31 deletions(-) diff --git a/Package.resolved b/Package.resolved index 0db1349..e369541 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,13 +1,13 @@ { - "originHash" : "0b9d136f905253e9368420218eda771d18cfa528b15553cb08a34c9e5ecdfea7", + "originHash" : "699c69d1c5c91fc14af71a7c0e846526c570ff854487f4866cea7b5caa49b7fe", "pins" : [ { "identity" : "nvalert", "kind" : "remoteSourceControl", "location" : "https://github.com/nicoverbruggen/NVAlert", "state" : { - "revision" : "2d649465067e3fc053bc64beed0e2ffea7e1cbe2", - "version" : "1.0.0" + "revision" : "1bbaad37697e4ad4c047eec2a8fc9834c2b5d98a", + "version" : "2.0.0" } } ], diff --git a/Package.swift b/Package.swift index 14e9d87..3485dfc 100644 --- a/Package.swift +++ b/Package.swift @@ -10,7 +10,7 @@ let package = Package( .library(name: "NVAppUpdater", targets: ["NVAppUpdater"]), ], dependencies: [ - .package(url: "https://github.com/nicoverbruggen/NVAlert", from: "1.0.0") + .package(url: "https://github.com/nicoverbruggen/NVAlert", from: "2.0.0") ], targets: [ .target(name: "NVAppUpdater", dependencies: ["NVAlert"]), diff --git a/README.md b/README.md index 5c89f1c..19e0890 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,8 @@ await UpdateCheck( selfUpdaterName: "MyApp Self-Updater.app", selfUpdaterPath: "~/.config/com.example.my-app/updater", caskUrl: URL(string: "https://my-app.test/latest/build.rb")!, -).perform(promptOnFailure: true) + isInteractive: true, +).perform() ``` You can also specify what callback needs to be used to determine the correct URL for the release notes. You may need to get some information from the CaskFile, which you are free to source. For example: @@ -80,11 +81,12 @@ await UpdateCheck( selfUpdaterName: "MyApp Self-Updater.app", selfUpdaterPath: "~/.config/com.example.my-app/updater", caskUrl: URL(string: "https://my-app.test/latest/build.rb")!, + isInteractive: true, ) .resolvingReleaseNotes(with: { caskFile in return URL(string: "https://my-app.com/release-notes/\(caskFile.version)")! }) -.perform(promptOnFailure: true) +.perform() ``` ## Self-Updater diff --git a/Sources/AppUpdater/API/UpdateCheck.swift b/Sources/AppUpdater/API/UpdateCheck.swift index a429ccc..b35b80b 100644 --- a/Sources/AppUpdater/API/UpdateCheck.swift +++ b/Sources/AppUpdater/API/UpdateCheck.swift @@ -42,6 +42,7 @@ open class UpdateCheck let caskUrl: URL let selfUpdaterName: String let selfUpdaterPath: String + let isInteractive: Bool private var releaseNotesUrlCallback: ((NVCaskFile) -> URL?)? = nil private var caskFile: NVCaskFile! @@ -59,15 +60,21 @@ open class UpdateCheck * * - Parameter caskUrl: The URL where the Cask file is expected to be located. Redirects will * be followed when retrieving and validating the Cask file. + * + * - Parameter isInteractive: Whether user interaction is required when failing to check + * or no new update is found. A user usually expects a prompt if they manually searched + * for updates. */ public init( selfUpdaterName: String, selfUpdaterPath: String, - caskUrl: URL + caskUrl: URL, + isInteractive: Bool ) { self.selfUpdaterName = selfUpdaterName self.selfUpdaterPath = selfUpdaterPath self.caskUrl = caskUrl + self.isInteractive = isInteractive } /** @@ -82,15 +89,11 @@ open class UpdateCheck /** * Perform the check for a new version. - * - * - Parameter promptOnFailure: Whether user interaction is required when failing to check - * or no new update is found. A user usually expects a prompt if they manually searched - * for updates. */ - public func perform(promptOnFailure: Bool = true) async { + public func perform() async { guard let caskFile = NVCaskFile.from(url: caskUrl) else { Log.text("The contents of the CaskFile at '\(caskUrl.absoluteString)' could not be retrieved.") - return await presentCouldNotRetrieveUpdate(promptOnFailure) + return await presentCouldNotRetrieveUpdate() } self.caskFile = caskFile @@ -99,7 +102,7 @@ open class UpdateCheck guard let onlineVersion = AppVersion.from(caskFile.version) else { Log.text("The version string from the CaskFile could not be read.") - return await presentCouldNotRetrieveUpdate(promptOnFailure) + return await presentCouldNotRetrieveUpdate() } self.newerVersion = onlineVersion @@ -108,29 +111,19 @@ open class UpdateCheck Log.text("The current version is v\(currentVersion.computerReadable).") if onlineVersion > currentVersion { + // A newer version is available await presentNewerVersionAvailable() - } else if promptOnFailure { - await presentVersionIsUpToDate(promptOnFailure) + } else { + await presentVersionUpToDate() } } // MARK: - Alerts - private func presentCouldNotRetrieveUpdate(_ promptOnFailure: Bool) async { - Log.text("Could not retrieve update manifest!") + private func presentVersionUpToDate() async { + Log.text("Application is up-to-date!") - if promptOnFailure { - await Alert.confirm( - title: translations.couldNotRetrieveUpdateTitle, - description: translations.couldNotRetrieveUpdateDescription - ) - } - } - - private func presentVersionIsUpToDate(_ promptOnFailure: Bool) async { - Log.text("Version is up-to-date!") - - if promptOnFailure { + if isInteractive { await Alert.confirm( title: translations.appIsUpToDateTitle .replacingOccurrences(of: "%@", with: Executable.name), @@ -139,6 +132,17 @@ open class UpdateCheck } } + private func presentCouldNotRetrieveUpdate() async { + Log.text("Could not retrieve update manifest!") + + if isInteractive { + await Alert.confirm( + title: translations.couldNotRetrieveUpdateTitle, + description: translations.couldNotRetrieveUpdateDescription + ) + } + } + private func presentNewerVersionAvailable() async { Log.text("A newer version is available!") @@ -167,7 +171,7 @@ open class UpdateCheck } } - await alert.show() + await alert.show(urgency: isInteractive ? .bringToFront : .urgentRequestAttention) } // MARK: - Functional