From 16cd88a86930d23541f06cd60c9b69a0f534a7de Mon Sep 17 00:00:00 2001 From: Mickael Farina Date: Thu, 8 Oct 2026 16:33:42 +0700 Subject: [PATCH 1/2] macOS app: build number from VERSION so Sparkle can offer updates Sparkle compares CFBundleVersion (the appcast's sparkle:version), not the short version string. It stayed "1" on every build (3.1.0 through 3.6.0), so an installed copy was never offered a newer release. build_app.sh now sets CFBundleVersion from VERSION alongside CFBundleShortVersionString, and the static Info.plist matches VERSION (3.6.0). Co-Authored-By: Claude Opus 5.5 --- packaging/macos/Info.plist | 4 ++-- packaging/macos/build_app.sh | 5 +++++ tests/test_app_bundle.py | 15 +++++++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/packaging/macos/Info.plist b/packaging/macos/Info.plist index 7ceecbb..550a831 100644 --- a/packaging/macos/Info.plist +++ b/packaging/macos/Info.plist @@ -36,9 +36,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 3.2.0 + 3.6.0 CFBundleVersion - 1 + 3.6.0 LSMinimumSystemVersion 13.0 NSHumanReadableCopyright diff --git a/packaging/macos/build_app.sh b/packaging/macos/build_app.sh index a3e8231..66d47b3 100755 --- a/packaging/macos/build_app.sh +++ b/packaging/macos/build_app.sh @@ -78,6 +78,11 @@ APP_VERSION="$(tr -d '[:space:]' < "$REPO/VERSION" 2>/dev/null || true)" if [ -n "$APP_VERSION" ] && command -v plutil >/dev/null 2>&1; then plutil -replace CFBundleShortVersionString -string "$APP_VERSION" "$CONTENTS/Info.plist" echo "==> bundle CFBundleShortVersionString = $APP_VERSION (from VERSION)" + # Sparkle decides "is this newer?" from CFBundleVersion (the appcast's sparkle:version), + # not the short string. It stayed "1" on every build, so no installed copy was ever + # offered an update. Use the same version. + plutil -replace CFBundleVersion -string "$APP_VERSION" "$CONTENTS/Info.plist" + echo "==> bundle CFBundleVersion = $APP_VERSION (from VERSION)" fi printf 'APPL????' > "$CONTENTS/PkgInfo" diff --git a/tests/test_app_bundle.py b/tests/test_app_bundle.py index bc39b78..7171269 100644 --- a/tests/test_app_bundle.py +++ b/tests/test_app_bundle.py @@ -75,3 +75,18 @@ def test_build_assembles_bundle_and_selftest_passes(): capture_output=True, text=True, timeout=60, ) assert st.returncode == 0, f"--selftest failed: {st.stderr}\n{st.stdout}" + + +def test_the_bundle_carries_the_release_version_as_its_build_number(): + # Sparkle offers an update only when the appcast's sparkle:version (CFBundleVersion) is + # higher than the installed one. It stayed "1" on every build, so no installed copy was + # ever offered an update. Both version keys now come from VERSION. + import plistlib + version = (REPO / "VERSION").read_text(encoding="utf-8").strip() + static = plistlib.loads((PKG / "Info.plist").read_bytes()) + assert static["CFBundleVersion"] == static["CFBundleShortVersionString"] == version, "static plist matches VERSION" + with tempfile.TemporaryDirectory() as td: + r = subprocess.run(["bash", str(BUILD), "--out", td, "--clean"], capture_output=True, text=True, timeout=120) + assert r.returncode == 0, r.stderr + built = plistlib.loads((Path(td) / "Sovereign AI Workstation.app" / "Contents" / "Info.plist").read_bytes()) + assert built["CFBundleVersion"] == built["CFBundleShortVersionString"] == version From 0694e1a990bdbdffae7598d35478b891e716e014 Mon Sep 17 00:00:00 2001 From: Mickael Farina Date: Thu, 8 Oct 2026 19:01:28 +0700 Subject: [PATCH 2/2] test: run the bundle build part only on macOS (CI is Linux) Co-Authored-By: Claude Opus 5.5 --- tests/test_app_bundle.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_app_bundle.py b/tests/test_app_bundle.py index 7171269..90ec337 100644 --- a/tests/test_app_bundle.py +++ b/tests/test_app_bundle.py @@ -85,6 +85,8 @@ def test_the_bundle_carries_the_release_version_as_its_build_number(): version = (REPO / "VERSION").read_text(encoding="utf-8").strip() static = plistlib.loads((PKG / "Info.plist").read_bytes()) assert static["CFBundleVersion"] == static["CFBundleShortVersionString"] == version, "static plist matches VERSION" + if sys.platform != "darwin": + return # the build itself needs macOS (Swift/AppKit launcher, plutil) with tempfile.TemporaryDirectory() as td: r = subprocess.run(["bash", str(BUILD), "--out", td, "--clean"], capture_output=True, text=True, timeout=120) assert r.returncode == 0, r.stderr