Normalize menubar version display

This commit is contained in:
iamtoruk 2026-05-11 11:21:39 -07:00
parent cd8c646818
commit f058f36dbd
7 changed files with 75 additions and 13 deletions

View file

@ -9,6 +9,8 @@
set -euo pipefail
VERSION="${1:-dev}"
ASSET_VERSION="${VERSION#mac-}"
BUNDLE_VERSION="${ASSET_VERSION#v}"
BUNDLE_NAME="CodeBurnMenubar.app"
BUNDLE_ID="org.agentseal.codeburn-menubar"
EXECUTABLE_NAME="CodeBurnMenubar"
@ -66,9 +68,9 @@ cat > "${BUNDLE}/Contents/Info.plist" <<PLIST
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>${VERSION}</string>
<string>${BUNDLE_VERSION}</string>
<key>CFBundleVersion</key>
<string>${VERSION}</string>
<string>${BUNDLE_VERSION}</string>
<key>LSMinimumSystemVersion</key>
<string>${MIN_MACOS}</string>
<key>LSUIElement</key>
@ -93,7 +95,7 @@ echo "▸ Ad-hoc signing..."
codesign --force --sign - --timestamp=none --deep "${BUNDLE}" 2>/dev/null || true
codesign --verify --deep --strict "${BUNDLE}" 2>/dev/null || echo " (signature verify skipped)"
ZIP_NAME="CodeBurnMenubar-${VERSION}.zip"
ZIP_NAME="CodeBurnMenubar-${ASSET_VERSION}.zip"
ZIP_PATH="${DIST_DIR}/${ZIP_NAME}"
echo "▸ Packaging ${ZIP_NAME}..."
(cd "${DIST_DIR}" && COPYFILE_DISABLE=1 /usr/bin/ditto -c -k --norsrc --keepParent "${BUNDLE_NAME}" "${ZIP_NAME}")

View file

@ -0,0 +1,43 @@
import Foundation
enum AppVersion {
static var bundleShortVersion: String {
Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? ""
}
static var bundleBuildVersion: String {
Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? ""
}
static var normalizedBundleShortVersion: String {
normalize(bundleShortVersion)
}
static var normalizedBundleBuildVersion: String {
normalize(bundleBuildVersion)
}
static var displayBundleShortVersion: String {
display(bundleShortVersion)
}
static func normalize(_ version: String) -> String {
let trimmed = version.trimmingCharacters(in: .whitespacesAndNewlines)
if trimmed.lowercased().hasPrefix("mac-v") {
return String(trimmed.dropFirst(5))
}
if trimmed.lowercased().hasPrefix("v") {
return String(trimmed.dropFirst())
}
return trimmed
}
static func display(_ version: String) -> String {
let normalized = normalize(version)
guard !normalized.isEmpty else { return "v?" }
if normalized == "?" || normalized == "dev" || normalized == "dev-preview" || normalized == "" {
return normalized
}
return "v\(normalized)"
}
}

View file

@ -705,10 +705,10 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
alert.icon = codeburnAlertIcon()
if updateChecker.updateAvailable, let latest = updateChecker.latestVersion {
alert.messageText = "Update Available"
alert.informativeText = "v\(latest) is available (you have v\(updateChecker.currentVersion)). Run:\n\ncodeburn menubar --force"
alert.informativeText = "\(AppVersion.display(latest)) is available (you have \(AppVersion.display(updateChecker.currentVersion))). Run:\n\ncodeburn menubar --force"
} else {
alert.messageText = "Up to Date"
alert.informativeText = "You're on the latest version (v\(updateChecker.currentVersion))."
alert.informativeText = "You're on the latest version (\(AppVersion.display(updateChecker.currentVersion)))."
}
alert.alertStyle = .informational
alert.addButton(withTitle: "OK")

View file

@ -16,14 +16,14 @@ final class UpdateChecker {
var updateAvailable: Bool {
guard let latest = latestVersion else { return false }
let current = currentVersion
let normalizedLatest = latest.hasPrefix("v") ? String(latest.dropFirst()) : latest
let normalizedCurrent = current.hasPrefix("v") ? String(current.dropFirst()) : current
let normalizedLatest = AppVersion.normalize(latest)
let normalizedCurrent = AppVersion.normalize(current)
guard !normalizedCurrent.isEmpty && normalizedCurrent != "dev" else { return false }
return normalizedLatest.compare(normalizedCurrent, options: .numeric) == .orderedDescending
}
var currentVersion: String {
Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? ""
AppVersion.normalizedBundleShortVersion
}
func checkIfNeeded() async {

View file

@ -567,7 +567,7 @@ struct FooterBar: View {
Spacer()
Text("v\(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "?")")
Text(AppVersion.displayBundleShortVersion)
.font(.system(size: 10, weight: .regular, design: .monospaced))
.foregroundStyle(.tertiary)

View file

@ -337,10 +337,8 @@ private struct CodexConnectionRow: View {
// MARK: - About
private struct AboutSettingsTab: View {
private let appVersion: String =
(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? ""
private let buildVersion: String =
(Bundle.main.infoDictionary?["CFBundleVersion"] as? String) ?? ""
private let appVersion: String = AppVersion.normalizedBundleShortVersion
private let buildVersion: String = AppVersion.normalizedBundleBuildVersion
var body: some View {
VStack(spacing: 14) {

View file

@ -0,0 +1,19 @@
import Testing
@testable import CodeBurnMenubar
@Suite("AppVersion")
struct AppVersionTests {
@Test("display avoids duplicate v prefix")
func displayAvoidsDuplicatePrefix() {
#expect(AppVersion.display("0.9.8") == "v0.9.8")
#expect(AppVersion.display("v0.9.8") == "v0.9.8")
#expect(AppVersion.display("mac-v0.9.8") == "v0.9.8")
}
@Test("bundle metadata stores unprefixed semver")
func normalizeBundleVersion() {
#expect(AppVersion.normalize("v0.9.8") == "0.9.8")
#expect(AppVersion.normalize("mac-v0.9.8") == "0.9.8")
#expect(AppVersion.normalize("dev") == "dev")
}
}