From a4f1f0e33d5d2e2373790214eea0dcf71faf0d21 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Fri, 31 May 2024 23:15:36 +0200 Subject: [PATCH] Rename to NVAppUpdater --- Package.swift | 8 +++--- README.md | 32 ++++++++++++++++-------- Sources/AppUpdater/API/SelfUpdater.swift | 12 ++++----- Sources/AppUpdater/API/UpdateCheck.swift | 15 +++++------ 4 files changed, 39 insertions(+), 28 deletions(-) diff --git a/Package.swift b/Package.swift index 769a7ce..7e7daf1 100644 --- a/Package.swift +++ b/Package.swift @@ -4,13 +4,13 @@ import PackageDescription let package = Package( - name: "AppUpdater", - defaultLocalization: "en" + name: "NVAppUpdater", + defaultLocalization: "en", platforms: [.macOS(.v11)], products: [ - .library(name: "AppUpdater", targets: ["AppUpdater"]), + .library(name: "NVAppUpdater", targets: ["NVAppUpdater"]), ], targets: [ - .target(name: "AppUpdater"), + .target(name: "NVAppUpdater"), ] ) diff --git a/README.md b/README.md index b3e56fd..5192ebe 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,28 @@ -# AppUpdater Package +# NVAppUpdater Package -## What this does +**Important**: 👷‍♂️ This package is currently **under construction**, and may change at any time. -This is a package that helps you build a self-updater for a given macOS application. It is currently based on code for PHP Monitor. +## What is this? -This package contains code that can be used for the self-updater app that you can ship with your app, and code that you can use in your main app. +This is a package that helps you build a self-updater for a given macOS application. It is supposed to act as an alternative to [Sparkle](https://sparkle-project.org/). + +This was originally written as part of my "zero non first-party dependencies" policy for PHP Monitor, where [the original code](https://github.com/nicoverbruggen/phpmon/tree/641328760684472a9a3c6191d15dcab249d92271/phpmon-updater) has been responsible for serving updates for many users over the last few years. + +This package contains code that can be used to ship a self-updater app that you can ship alongside your app, with code that you can use in your main app. Your app must ship the self-updater as a separate sub-app, so that it can be launched independently from the main executable, which is terminated upon launching the sub-app. +## How does it work? + Here's how it works: -- The updater checks if a newer manifest file is available. If there is, it is downloaded to the `UpdaterPath`. +- From within the main app, you can perform a so-called `UpdateCheck`. This will connect to a URL of your choice where you have made a manifest file available. That manifest file is then checked and compared to the current version. -- If the user chooses to install the update, the main app is terminated once the self-updater app has launched. +- If the version specified in the manifest file is newer, then the user will see a message prompting them to update the app. If the user chooses to update to the newer version, details of the upgrade URL and checksum are written to a temporary file as a JSON file. -- The self-updater will download the .zip file and validate it using the checksum provided in the manifest file. If the checksum is valid, the app is (re)placed in `/Applications` and finally launched. +- If the user chooses to install the update, the main app is terminated once the self-updater app has launched. To do this, you must specify the correct bundle ID(s) in the self-updater, or the app won't be terminated. + +- The self-updater will download the .zip file and validate it using the checksum provided in the manifest file. If the checksum is valid, the app is (re)placed in `/Applications` and finally (re-)launched. ## Checking for updates @@ -56,9 +64,11 @@ You must always place the CaskFile at the same URL, and you will specify where t To check for updates, simply create a new `UpdateCheck` instance with the correct configuration, and call `perform()`: ```swift +import NVAppUpdater + await UpdateCheck( selfUpdaterName: "MyApp Self-Updater.app", - selfUpdaterDirectory: "~/.config/com.example.my-app/updater", + selfUpdaterPath: "~/.config/com.example.my-app/updater", caskUrl: URL(string: "https://my-app.test/latest/build.rb")!, promptOnFailure: true ).perform() @@ -70,16 +80,16 @@ As a separate target (for a macOS app), you need to add the following file: ```swift import Cocoa -import AppUpdater +import NVAppUpdater let delegate = SelfUpdater( appName: "My App", bundleIdentifiers: ["com.example.my-app"], - baseUpdaterPath: "~/.config/com.example.my-app/updater" + selfUpdaterPath: "~/.config/com.example.my-app/updater" ) NSApplication.shared.delegate = delegate _ = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv) ``` -You must then make sure that this app is included as a sub-app for the main target. It needs to be referenced correctly as part of the `selfUpdaterName` parameter of `UpdateCheck` (see the previous section). \ No newline at end of file +You must then make sure that this app is included as a sub-app for the main target. It needs to be referenced correctly as part of the `selfUpdaterName` parameter of `UpdateCheck` (see the previous section). diff --git a/Sources/AppUpdater/API/SelfUpdater.swift b/Sources/AppUpdater/API/SelfUpdater.swift index 3f53aa4..35c5586 100644 --- a/Sources/AppUpdater/API/SelfUpdater.swift +++ b/Sources/AppUpdater/API/SelfUpdater.swift @@ -9,10 +9,10 @@ open class SelfUpdater: NSObject, NSApplicationDelegate { // MARK: - Requires Configuration - public init(appName: String, bundleIdentifiers: [String], baseUpdaterPath: String) { + public init(appName: String, bundleIdentifiers: [String], selfUpdaterPath: String) { self.appName = appName self.bundleIdentifiers = bundleIdentifiers - self.baseUpdaterPath = baseUpdaterPath + self.selfUpdaterPath = selfUpdaterPath } // MARK: - Regular Updater Flow @@ -20,7 +20,7 @@ open class SelfUpdater: NSObject, NSApplicationDelegate { // Set by the user private var appName: String private var bundleIdentifiers: [String] - private var baseUpdaterPath: String + private var selfUpdaterPath: String // Determined during the flow of the updater private var updaterPath: String = "" @@ -48,7 +48,7 @@ open class SelfUpdater: NSObject, NSApplicationDelegate { Log.text("Configured for \(self.appName) bundles: \(self.bundleIdentifiers)") - self.updaterPath = self.baseUpdaterPath + self.updaterPath = self.selfUpdaterPath .replacingOccurrences(of: "~", with: NSHomeDirectory()) Log.text("Updater directory set to: \(self.updaterPath)") @@ -137,7 +137,7 @@ open class SelfUpdater: NSObject, NSApplicationDelegate { // Make sure the updater directory exists var isDirectory: ObjCBool = true if !FileManager.default.fileExists(atPath: "\(updaterPath)/extracted", isDirectory: &isDirectory) { - await Alert.upgradeFailure(description: "The updater directory is missing. The automatic updater will quit. Make sure that `\(baseUpdaterPath)` is writeable.") + await Alert.upgradeFailure(description: "The updater directory is missing. The automatic updater will quit. Make sure that `\(selfUpdaterPath)` is writeable.") } // Unzip the file @@ -151,7 +151,7 @@ open class SelfUpdater: NSObject, NSApplicationDelegate { // Make sure the file was extracted if app.isEmpty { - await Alert.upgradeFailure(description: "The downloaded file could not be extracted. The automatic updater will quit. Make sure that `\(baseUpdaterPath)` is writeable.") + await Alert.upgradeFailure(description: "The downloaded file could not be extracted. The automatic updater will quit. Make sure that `\(selfUpdaterPath)` is writeable.") } // Remove the original app diff --git a/Sources/AppUpdater/API/UpdateCheck.swift b/Sources/AppUpdater/API/UpdateCheck.swift index 7783f18..53e387c 100644 --- a/Sources/AppUpdater/API/UpdateCheck.swift +++ b/Sources/AppUpdater/API/UpdateCheck.swift @@ -11,7 +11,7 @@ open class UpdateCheck let caskUrl: URL let promptOnFailure: Bool let selfUpdaterName: String - let selfUpdaterDirectory: String + let selfUpdaterPath: String var caskFile: CaskFile! var newerVersion: AppVersion! @@ -22,8 +22,9 @@ open class UpdateCheck * - Parameter selfUpdaterName: The name of the self-updater .app file. For example, "App Self-Updater.app". * This binary should exist as a resource of the current application. * - * - Parameter selfUpdaterDirectory: The directory that is used by the self-updater. A file `update.json` - * will be placed in this directory and this should be correspond to the `baseUpdaterPath` in `SelfUpdater`. + * - Parameter selfUpdaterPath: The directory that is used by the self-updater. + * A small manifest named `update.json` will be placed in this directory and this + * should be correspond to the `selfUpdaterPath` in `SelfUpdater`. * * - Parameter caskUrl: The URL where the Cask file is expected to be located. Redirects will * be followed when retrieving and validating the Cask file. @@ -34,12 +35,12 @@ open class UpdateCheck */ public init( selfUpdaterName: String, - selfUpdaterDirectory: String, + selfUpdaterPath: String, caskUrl: URL, promptOnFailure: Bool ) { self.selfUpdaterName = selfUpdaterName - self.selfUpdaterDirectory = selfUpdaterDirectory + self.selfUpdaterPath = selfUpdaterPath self.caskUrl = caskUrl self.promptOnFailure = promptOnFailure } @@ -126,9 +127,9 @@ open class UpdateCheck private func launchSelfUpdater() { let updater = Bundle.main.resourceURL!.path + "/\(selfUpdaterName)" - system_quiet("mkdir -p \(selfUpdaterDirectory) 2> /dev/null") + system_quiet("mkdir -p \(selfUpdaterPath) 2> /dev/null") - let updaterDirectory = selfUpdaterDirectory + let updaterDirectory = selfUpdaterPath .replacingOccurrences(of: "~", with: NSHomeDirectory()) system_quiet("cp -R \"\(updater)\" \"\(updaterDirectory)/\(selfUpdaterName)\"")