Rename to NVAppUpdater

This commit is contained in:
2024-05-31 23:15:36 +02:00
parent 9acf308ea6
commit a4f1f0e33d
4 changed files with 39 additions and 28 deletions

View File

@@ -4,13 +4,13 @@
import PackageDescription import PackageDescription
let package = Package( let package = Package(
name: "AppUpdater", name: "NVAppUpdater",
defaultLocalization: "en" defaultLocalization: "en",
platforms: [.macOS(.v11)], platforms: [.macOS(.v11)],
products: [ products: [
.library(name: "AppUpdater", targets: ["AppUpdater"]), .library(name: "NVAppUpdater", targets: ["NVAppUpdater"]),
], ],
targets: [ targets: [
.target(name: "AppUpdater"), .target(name: "NVAppUpdater"),
] ]
) )

View File

@@ -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. 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: 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 ## 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()`: To check for updates, simply create a new `UpdateCheck` instance with the correct configuration, and call `perform()`:
```swift ```swift
import NVAppUpdater
await UpdateCheck( await UpdateCheck(
selfUpdaterName: "MyApp Self-Updater.app", 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")!, caskUrl: URL(string: "https://my-app.test/latest/build.rb")!,
promptOnFailure: true promptOnFailure: true
).perform() ).perform()
@@ -70,12 +80,12 @@ As a separate target (for a macOS app), you need to add the following file:
```swift ```swift
import Cocoa import Cocoa
import AppUpdater import NVAppUpdater
let delegate = SelfUpdater( let delegate = SelfUpdater(
appName: "My App", appName: "My App",
bundleIdentifiers: ["com.example.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 NSApplication.shared.delegate = delegate

View File

@@ -9,10 +9,10 @@ open class SelfUpdater: NSObject, NSApplicationDelegate {
// MARK: - Requires Configuration // MARK: - Requires Configuration
public init(appName: String, bundleIdentifiers: [String], baseUpdaterPath: String) { public init(appName: String, bundleIdentifiers: [String], selfUpdaterPath: String) {
self.appName = appName self.appName = appName
self.bundleIdentifiers = bundleIdentifiers self.bundleIdentifiers = bundleIdentifiers
self.baseUpdaterPath = baseUpdaterPath self.selfUpdaterPath = selfUpdaterPath
} }
// MARK: - Regular Updater Flow // MARK: - Regular Updater Flow
@@ -20,7 +20,7 @@ open class SelfUpdater: NSObject, NSApplicationDelegate {
// Set by the user // Set by the user
private var appName: String private var appName: String
private var bundleIdentifiers: [String] private var bundleIdentifiers: [String]
private var baseUpdaterPath: String private var selfUpdaterPath: String
// Determined during the flow of the updater // Determined during the flow of the updater
private var updaterPath: String = "" private var updaterPath: String = ""
@@ -48,7 +48,7 @@ open class SelfUpdater: NSObject, NSApplicationDelegate {
Log.text("Configured for \(self.appName) bundles: \(self.bundleIdentifiers)") Log.text("Configured for \(self.appName) bundles: \(self.bundleIdentifiers)")
self.updaterPath = self.baseUpdaterPath self.updaterPath = self.selfUpdaterPath
.replacingOccurrences(of: "~", with: NSHomeDirectory()) .replacingOccurrences(of: "~", with: NSHomeDirectory())
Log.text("Updater directory set to: \(self.updaterPath)") Log.text("Updater directory set to: \(self.updaterPath)")
@@ -137,7 +137,7 @@ open class SelfUpdater: NSObject, NSApplicationDelegate {
// Make sure the updater directory exists // Make sure the updater directory exists
var isDirectory: ObjCBool = true var isDirectory: ObjCBool = true
if !FileManager.default.fileExists(atPath: "\(updaterPath)/extracted", isDirectory: &isDirectory) { 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 // Unzip the file
@@ -151,7 +151,7 @@ open class SelfUpdater: NSObject, NSApplicationDelegate {
// Make sure the file was extracted // Make sure the file was extracted
if app.isEmpty { 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 // Remove the original app

View File

@@ -11,7 +11,7 @@ open class UpdateCheck
let caskUrl: URL let caskUrl: URL
let promptOnFailure: Bool let promptOnFailure: Bool
let selfUpdaterName: String let selfUpdaterName: String
let selfUpdaterDirectory: String let selfUpdaterPath: String
var caskFile: CaskFile! var caskFile: CaskFile!
var newerVersion: AppVersion! 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". * - 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. * 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` * - Parameter selfUpdaterPath: The directory that is used by the self-updater.
* will be placed in this directory and this should be correspond to the `baseUpdaterPath` in `SelfUpdater`. * 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 * - Parameter caskUrl: The URL where the Cask file is expected to be located. Redirects will
* be followed when retrieving and validating the Cask file. * be followed when retrieving and validating the Cask file.
@@ -34,12 +35,12 @@ open class UpdateCheck
*/ */
public init( public init(
selfUpdaterName: String, selfUpdaterName: String,
selfUpdaterDirectory: String, selfUpdaterPath: String,
caskUrl: URL, caskUrl: URL,
promptOnFailure: Bool promptOnFailure: Bool
) { ) {
self.selfUpdaterName = selfUpdaterName self.selfUpdaterName = selfUpdaterName
self.selfUpdaterDirectory = selfUpdaterDirectory self.selfUpdaterPath = selfUpdaterPath
self.caskUrl = caskUrl self.caskUrl = caskUrl
self.promptOnFailure = promptOnFailure self.promptOnFailure = promptOnFailure
} }
@@ -126,9 +127,9 @@ open class UpdateCheck
private func launchSelfUpdater() { private func launchSelfUpdater() {
let updater = Bundle.main.resourceURL!.path + "/\(selfUpdaterName)" 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()) .replacingOccurrences(of: "~", with: NSHomeDirectory())
system_quiet("cp -R \"\(updater)\" \"\(updaterDirectory)/\(selfUpdaterName)\"") system_quiet("cp -R \"\(updater)\" \"\(updaterDirectory)/\(selfUpdaterName)\"")