mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-05-16 19:44:14 +00:00
Merge pull request #310 from getagentseal/fix/menubar-wake-recovery
Fix menubar wake recovery and release asset selection
This commit is contained in:
commit
2301577e03
5 changed files with 81 additions and 23 deletions
8
.github/workflows/release-menubar.yml
vendored
8
.github/workflows/release-menubar.yml
vendored
|
|
@ -45,7 +45,9 @@ jobs:
|
|||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: CodeBurnMenubar-${{ steps.version.outputs.value }}
|
||||
path: mac/.build/dist/CodeBurnMenubar-*.zip
|
||||
path: |
|
||||
mac/.build/dist/CodeBurnMenubar-${{ steps.version.outputs.value }}.zip
|
||||
mac/.build/dist/CodeBurnMenubar-${{ steps.version.outputs.value }}.zip.sha256
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Create / update GitHub Release
|
||||
|
|
@ -66,6 +68,6 @@ jobs:
|
|||
and macOS shows "cannot verify developer", right-click the app in Finder and
|
||||
pick Open to whitelist it once.
|
||||
files: |
|
||||
mac/.build/dist/CodeBurnMenubar-*.zip
|
||||
mac/.build/dist/CodeBurnMenubar-*.zip.sha256
|
||||
mac/.build/dist/CodeBurnMenubar-${{ steps.version.outputs.value }}.zip
|
||||
mac/.build/dist/CodeBurnMenubar-${{ steps.version.outputs.value }}.zip.sha256
|
||||
fail_on_unmatched_files: true
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
|
|||
self?.forceRefreshTask = nil
|
||||
self?.forceRefreshStartedAt = nil
|
||||
self?.forceRefreshGeneration &+= 1
|
||||
self?.store.resetLoadingState()
|
||||
self?.refreshLoopTask?.cancel()
|
||||
self?.refreshLoopTask = nil
|
||||
}
|
||||
|
|
@ -110,9 +111,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
|
|||
queue: .main
|
||||
) { [weak self] _ in
|
||||
Task { @MainActor in
|
||||
self?.store.resetLoadingState()
|
||||
self?.forceRefresh()
|
||||
if self?.refreshLoopTask == nil { self?.startRefreshLoop() }
|
||||
self?.recoverRefreshPipelineAfterInterruption(resetLoading: true)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -121,7 +120,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
|
|||
object: nil,
|
||||
queue: .main
|
||||
) { [weak self] _ in
|
||||
Task { @MainActor in self?.forceRefresh() }
|
||||
Task { @MainActor in
|
||||
self?.recoverRefreshPipelineAfterInterruption(resetLoading: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -131,10 +132,24 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
|
|||
object: nil,
|
||||
queue: .main
|
||||
) { [weak self] _ in
|
||||
Task { @MainActor in self?.forceRefresh() }
|
||||
Task { @MainActor in
|
||||
self?.recoverRefreshPipelineAfterInterruption(resetLoading: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func recoverRefreshPipelineAfterInterruption(resetLoading: Bool) {
|
||||
if resetLoading {
|
||||
store.resetLoadingState()
|
||||
} else {
|
||||
_ = store.clearStaleLoadingIfNeeded()
|
||||
}
|
||||
if refreshLoopTask == nil {
|
||||
startRefreshLoop()
|
||||
}
|
||||
forceRefresh()
|
||||
}
|
||||
|
||||
private func installLaunchAgentIfNeeded() {
|
||||
let fm = FileManager.default
|
||||
let agentName = "com.codeburn.refresh.plist"
|
||||
|
|
@ -232,6 +247,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
|
|||
private func forceRefresh() {
|
||||
let now = Date()
|
||||
_ = clearStaleForceRefreshIfNeeded(now: now)
|
||||
guard forceRefreshTask == nil else { return }
|
||||
guard now.timeIntervalSince(lastRefreshTime) > 5 else { return }
|
||||
lastRefreshTime = now
|
||||
forceRefreshStartedAt = now
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ final class UpdateChecker {
|
|||
let (data, _) = try await URLSession.shared.data(for: request)
|
||||
let release = try JSONDecoder().decode(GitHubRelease.self, from: data)
|
||||
guard let asset = release.assets.first(where: {
|
||||
$0.name.hasPrefix("CodeBurnMenubar-") && $0.name.hasSuffix(".zip")
|
||||
$0.name.hasPrefix("CodeBurnMenubar-v") && $0.name.hasSuffix(".zip")
|
||||
}) else { return }
|
||||
|
||||
let version = asset.name
|
||||
|
|
|
|||
|
|
@ -11,17 +11,28 @@ import { Readable } from 'node:stream'
|
|||
/// newest tagged release; we filter its assets list for our zipped .app bundle.
|
||||
const RELEASE_API = 'https://api.github.com/repos/getagentseal/codeburn/releases/latest'
|
||||
const APP_BUNDLE_NAME = 'CodeBurnMenubar.app'
|
||||
const ASSET_PATTERN = /^CodeBurnMenubar-.*\.zip$/
|
||||
const CHECKSUM_PATTERN = /^CodeBurnMenubar-.*\.zip\.sha256$/
|
||||
const VERSIONED_ASSET_PATTERN = /^CodeBurnMenubar-v.+\.zip$/
|
||||
const APP_PROCESS_NAME = 'CodeBurnMenubar'
|
||||
const SUPPORTED_OS = 'darwin'
|
||||
const MIN_MACOS_MAJOR = 14
|
||||
|
||||
export type InstallResult = { installedPath: string; launched: boolean }
|
||||
|
||||
type ReleaseAsset = { name: string; browser_download_url: string }
|
||||
type ReleaseResponse = { tag_name: string; assets: ReleaseAsset[] }
|
||||
type ResolvedAssets = { zip: ReleaseAsset; checksum: ReleaseAsset | null }
|
||||
export type ReleaseAsset = { name: string; browser_download_url: string }
|
||||
export type ReleaseResponse = { tag_name: string; assets: ReleaseAsset[] }
|
||||
export type ResolvedAssets = { zip: ReleaseAsset; checksum: ReleaseAsset | null }
|
||||
|
||||
export function resolveMenubarReleaseAssets(release: ReleaseResponse): ResolvedAssets {
|
||||
const zip = release.assets.find(a => VERSIONED_ASSET_PATTERN.test(a.name))
|
||||
if (!zip) {
|
||||
throw new Error(
|
||||
`No ${APP_BUNDLE_NAME} versioned zip found in release ${release.tag_name}. ` +
|
||||
`Check https://github.com/getagentseal/codeburn/releases.`
|
||||
)
|
||||
}
|
||||
const checksum = release.assets.find(a => a.name === `${zip.name}.sha256`) ?? null
|
||||
return { zip, checksum }
|
||||
}
|
||||
|
||||
function userApplicationsDir(): string {
|
||||
return join(homedir(), 'Applications')
|
||||
|
|
@ -71,15 +82,7 @@ async function fetchLatestReleaseAssets(): Promise<ResolvedAssets> {
|
|||
throw new Error(`GitHub release lookup failed: HTTP ${response.status}`)
|
||||
}
|
||||
const body = await response.json() as ReleaseResponse
|
||||
const zip = body.assets.find(a => ASSET_PATTERN.test(a.name))
|
||||
if (!zip) {
|
||||
throw new Error(
|
||||
`No ${APP_BUNDLE_NAME} zip found in release ${body.tag_name}. ` +
|
||||
`Check https://github.com/getagentseal/codeburn/releases.`
|
||||
)
|
||||
}
|
||||
const checksum = body.assets.find(a => CHECKSUM_PATTERN.test(a.name)) ?? null
|
||||
return { zip, checksum }
|
||||
return resolveMenubarReleaseAssets(body)
|
||||
}
|
||||
|
||||
async function verifyChecksum(archivePath: string, checksumUrl: string): Promise<void> {
|
||||
|
|
|
|||
37
tests/menubar-installer.test.ts
Normal file
37
tests/menubar-installer.test.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import { describe, expect, it } from 'vitest'
|
||||
import { resolveMenubarReleaseAssets, type ReleaseResponse } from '../src/menubar-installer.js'
|
||||
|
||||
function asset(name: string) {
|
||||
return { name, browser_download_url: `https://example.test/${name}` }
|
||||
}
|
||||
|
||||
describe('resolveMenubarReleaseAssets', () => {
|
||||
it('ignores dev zips and pairs the checksum with the versioned zip', () => {
|
||||
const release: ReleaseResponse = {
|
||||
tag_name: 'mac-v0.9.8',
|
||||
assets: [
|
||||
asset('CodeBurnMenubar-dev.zip'),
|
||||
asset('CodeBurnMenubar-dev.zip.sha256'),
|
||||
asset('CodeBurnMenubar-v0.9.8.zip'),
|
||||
asset('CodeBurnMenubar-v0.9.8.zip.sha256'),
|
||||
],
|
||||
}
|
||||
|
||||
const resolved = resolveMenubarReleaseAssets(release)
|
||||
|
||||
expect(resolved.zip.name).toBe('CodeBurnMenubar-v0.9.8.zip')
|
||||
expect(resolved.checksum?.name).toBe('CodeBurnMenubar-v0.9.8.zip.sha256')
|
||||
})
|
||||
|
||||
it('fails when a release only contains dev assets', () => {
|
||||
const release: ReleaseResponse = {
|
||||
tag_name: 'mac-v0.9.8',
|
||||
assets: [
|
||||
asset('CodeBurnMenubar-dev.zip'),
|
||||
asset('CodeBurnMenubar-dev.zip.sha256'),
|
||||
],
|
||||
}
|
||||
|
||||
expect(() => resolveMenubarReleaseAssets(release)).toThrow(/versioned zip/)
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue