mirror of
https://github.com/nicoverbruggen/NVAppUpdater.git
synced 2025-08-10 10:40:08 +02:00
Rename to NVAppUpdater
This commit is contained in:
@@ -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"),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
30
README.md
30
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.
|
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
|
||||||
|
@@ -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
|
||||||
|
@@ -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)\"")
|
||||||
|
Reference in New Issue
Block a user