The GitHub release data structure includes all necessary information:
UpdateService.swift:3-30
Copy
Ask AI
struct GitHubRelease: Decodable { let tagName: String let htmlUrl: String let body: String? let assets: [GitHubAsset] enum CodingKeys: String, CodingKey { case tagName = "tag_name" case htmlUrl = "html_url" case body case assets } /// The browser download URL for the first .dmg asset, if present. var dmgDownloadURL: URL? { assets.first(where: { $0.name.hasSuffix(".dmg") }).flatMap { URL(string: $0.browserDownloadUrl) } }}struct GitHubAsset: Decodable { let name: String let browserDownloadUrl: String enum CodingKeys: String, CodingKey { case name case browserDownloadUrl = "browser_download_url" }}
The app checks for updates on startup and can be triggered manually:
AppViewModel.swift:637-659
Copy
Ask AI
/// - Parameter userInitiated: Pass `true` when triggered by the user (e.g. menu item)./// Shows the sheet even when already up to date. Automatic startup checks only show/// the sheet when a new version is actually available.func checkForUpdates(userInitiated: Bool = false) { guard !updateStatus.isChecking else { return } updateStatus = .checking Task { do { if let release = try await UpdateService.checkForUpdate() { updateStatus = .available(release) showUpdateSheet = true } else { updateStatus = .upToDate if userInitiated { showUpdateSheet = true } } } catch { updateStatus = .failed(error.localizedDescription) if userInitiated { showUpdateSheet = true } } }}
enum UpdateStatus { case idle case checking case upToDate case available(GitHubRelease) case failed(String) var isChecking: Bool { if case .checking = self { return true } return false }}var updateStatus: UpdateStatus = .idlevar showUpdateSheet = false
Automatic checks on startup only show the update sheet when a new version is available. Manual checks always show the sheet, even if you’re already up to date.
When an update is available, the app presents an update sheet with:
Current version vs. new version
Release notes from GitHub
Download button linking to the .dmg file
“View on GitHub” link for full release details
The dmgDownloadURL property automatically finds the DMG installer:
UpdateService.swift:17-19
Copy
Ask AI
/// The browser download URL for the first .dmg asset, if present.var dmgDownloadURL: URL? { assets.first(where: { $0.name.hasSuffix(".dmg") }).flatMap { URL(string: $0.browserDownloadUrl) }}
1
Update Available
The update sheet appears showing the new version and release notes
2
Download DMG
Click the download button to get the latest .dmg installer
3
Install
Open the downloaded DMG and drag r2Vault to Applications