From 63900bd0adeac277b88dac03f0df72555241731d Mon Sep 17 00:00:00 2001 From: zhom <2717306+zhom@users.noreply.github.com> Date: Fri, 30 May 2025 07:37:40 +0400 Subject: [PATCH] chore: get build version at build time --- .github/workflows/release.yml | 1 + .github/workflows/rolling-release.yml | 2 + src-tauri/build.rs | 17 +++ src-tauri/src/app_auto_updater.rs | 136 ++++++++++++++++++--- src-tauri/src/lib.rs | 12 +- src/app/page.tsx | 1 + src/hooks/use-app-update-notifications.tsx | 43 ++++++- 7 files changed, 189 insertions(+), 23 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4fbe198..d506721 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -126,6 +126,7 @@ jobs: uses: tauri-apps/tauri-action@v0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_REF_NAME: ${{ github.ref_name }} with: tagName: ${{ github.ref_name }} releaseName: "Donut Browser ${{ github.ref_name }}" diff --git a/.github/workflows/rolling-release.yml b/.github/workflows/rolling-release.yml index 2cc72be..5e1eb2b 100644 --- a/.github/workflows/rolling-release.yml +++ b/.github/workflows/rolling-release.yml @@ -94,6 +94,8 @@ jobs: uses: tauri-apps/tauri-action@v0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_REF_NAME: "nightly-${{ steps.commit.outputs.hash }}" + GITHUB_SHA: ${{ github.sha }} with: tagName: "nightly-${{ steps.commit.outputs.hash }}" releaseName: "Donut Browser Nightly (Build ${{ steps.commit.outputs.hash }})" diff --git a/src-tauri/build.rs b/src-tauri/build.rs index 37912d5..f568432 100644 --- a/src-tauri/build.rs +++ b/src-tauri/build.rs @@ -5,5 +5,22 @@ fn main() { println!("cargo:rustc-link-lib=framework=CoreServices"); } + // Inject build version based on environment variables set by CI + if let Ok(tag_name) = std::env::var("GITHUB_REF_NAME") { + // This is set by GitHub Actions to the tag name (e.g., "v1.0.0" or "nightly-abc123") + println!("cargo:rustc-env=BUILD_VERSION={}", tag_name); + } else if std::env::var("STABLE_RELEASE").is_ok() { + // Fallback for stable releases - use CARGO_PKG_VERSION with 'v' prefix + let version = std::env::var("CARGO_PKG_VERSION").unwrap_or_else(|_| "0.1.0".to_string()); + println!("cargo:rustc-env=BUILD_VERSION=v{}", version); + } else if let Ok(commit_hash) = std::env::var("GITHUB_SHA") { + // For nightly builds, use commit hash + let short_hash = &commit_hash[0..7.min(commit_hash.len())]; + println!("cargo:rustc-env=BUILD_VERSION=nightly-{}", short_hash); + } else { + // Development build fallback + println!("cargo:rustc-env=BUILD_VERSION=dev-{}", std::env::var("CARGO_PKG_VERSION").unwrap_or_else(|_| "0.1.0".to_string())); + } + tauri_build::build() } diff --git a/src-tauri/src/app_auto_updater.rs b/src-tauri/src/app_auto_updater.rs index a93098a..904acfc 100644 --- a/src-tauri/src/app_auto_updater.rs +++ b/src-tauri/src/app_auto_updater.rs @@ -51,9 +51,10 @@ impl AppAutoUpdater { option_env!("STABLE_RELEASE").is_none() } - /// Get current app version from Cargo.toml + /// Get current app version from build-time injection pub fn get_current_version() -> String { - env!("CARGO_PKG_VERSION").to_string() + // Use build-time injected version instead of CARGO_PKG_VERSION + env!("BUILD_VERSION").to_string() } /// Check for app updates @@ -63,37 +64,48 @@ impl AppAutoUpdater { let current_version = Self::get_current_version(); let is_nightly = Self::is_nightly_build(); - println!("Checking for updates - Current version: {current_version}, Is nightly: {is_nightly}"); + println!("=== App Update Check ==="); + println!("Current version: {}", current_version); + println!("Is nightly build: {}", is_nightly); + println!("STABLE_RELEASE env: {:?}", option_env!("STABLE_RELEASE")); let releases = self.fetch_app_releases().await?; + println!("Fetched {} releases from GitHub", releases.len()); // Filter releases based on build type let filtered_releases: Vec<&AppRelease> = if is_nightly { // For nightly builds, look for nightly releases - releases + let nightly_releases: Vec<&AppRelease> = releases .iter() .filter(|release| release.tag_name.starts_with("nightly-")) - .collect() + .collect(); + println!("Found {} nightly releases", nightly_releases.len()); + nightly_releases } else { // For stable builds, look for stable releases (semver format) - releases + let stable_releases: Vec<&AppRelease> = releases .iter() .filter(|release| { release.tag_name.starts_with('v') && !release.tag_name.starts_with("nightly-") }) - .collect() + .collect(); + println!("Found {} stable releases", stable_releases.len()); + stable_releases }; if filtered_releases.is_empty() { - println!("No releases found for build type"); + println!("No releases found for build type (nightly: {})", is_nightly); return Ok(None); } // Get the latest release let latest_release = filtered_releases[0]; + println!("Latest release: {} ({})", latest_release.tag_name, latest_release.name); // Check if we need to update if self.should_update(¤t_version, &latest_release.tag_name, is_nightly) { + println!("Update available!"); + // Find the appropriate asset for current platform if let Some(download_url) = self.get_download_url_for_platform(&latest_release.assets) { let update_info = AppUpdateInfo { @@ -105,8 +117,13 @@ impl AppAutoUpdater { published_at: latest_release.published_at.clone(), }; + println!("Update info prepared: {} -> {}", update_info.current_version, update_info.new_version); return Ok(Some(update_info)); + } else { + println!("No suitable download asset found for current platform"); } + } else { + println!("No update needed"); } Ok(None) @@ -134,21 +151,34 @@ impl AppAutoUpdater { /// Determine if an update should be performed fn should_update(&self, current_version: &str, new_version: &str, is_nightly: bool) -> bool { + println!("Comparing versions: current={}, new={}, is_nightly={}", current_version, new_version, is_nightly); + if is_nightly { // For nightly builds, always update if there's a newer nightly - // Compare the commit hashes (assuming format: nightly-) if let (Some(current_hash), Some(new_hash)) = ( current_version.strip_prefix("nightly-"), new_version.strip_prefix("nightly-"), ) { - return new_hash != current_hash; + // Different commit hashes mean we should update + let should_update = new_hash != current_hash; + println!("Nightly comparison: current_hash={}, new_hash={}, should_update={}", current_hash, new_hash, should_update); + return should_update; + } + + // If current version doesn't have nightly prefix but we're in nightly mode, + // this could be a dev build or stable build upgrading to nightly + if !current_version.starts_with("nightly-") { + println!("Upgrading from non-nightly to nightly: {}", new_version); + return true; } - // If current version doesn't have nightly prefix, it's an upgrade from stable to nightly - !current_version.starts_with("nightly-") } else { // For stable builds, use semantic versioning comparison - self.is_version_newer(new_version, current_version) + let should_update = self.is_version_newer(new_version, current_version); + println!("Stable comparison: {} > {} = {}", new_version, current_version, should_update); + return should_update; } + + false } /// Compare semantic versions (returns true if version1 > version2) @@ -174,24 +204,63 @@ impl AppAutoUpdater { fn get_download_url_for_platform(&self, assets: &[AppReleaseAsset]) -> Option { let arch = if cfg!(target_arch = "aarch64") { "aarch64" - } else { + } else if cfg!(target_arch = "x86_64") { "x64" + } else { + "unknown" }; - // Look for macOS DMG with the appropriate architecture + println!("Looking for assets with architecture: {}", arch); for asset in assets { - if asset.name.contains(".dmg") && asset.name.contains(arch) { + println!("Found asset: {}", asset.name); + } + + // Priority 1: Look for exact architecture match in DMG + for asset in assets { + if asset.name.contains(".dmg") && + (asset.name.contains(&format!("_{}.dmg", arch)) || + asset.name.contains(&format!("-{}.dmg", arch)) || + asset.name.contains(&format!("_{}_", arch)) || + asset.name.contains(&format!("-{}-", arch))) { + println!("Found exact architecture match: {}", asset.name); return Some(asset.browser_download_url.clone()); } } - // Fallback: look for any macOS DMG + // Priority 2: Look for x86_64 variations if we're looking for x64 + if arch == "x64" { + for asset in assets { + if asset.name.contains(".dmg") && + (asset.name.contains("x86_64") || asset.name.contains("x86-64")) { + println!("Found x86_64 variant: {}", asset.name); + return Some(asset.browser_download_url.clone()); + } + } + } + + // Priority 3: Look for arm64 variations if we're looking for aarch64 + if arch == "aarch64" { + for asset in assets { + if asset.name.contains(".dmg") && + (asset.name.contains("arm64") || asset.name.contains("aarch64")) { + println!("Found arm64 variant: {}", asset.name); + return Some(asset.browser_download_url.clone()); + } + } + } + + // Priority 4: Fallback to any macOS DMG for asset in assets { - if asset.name.contains(".dmg") { + if asset.name.contains(".dmg") && + (asset.name.to_lowercase().contains("macos") || + asset.name.to_lowercase().contains("darwin") || + asset.name.contains(".app.tar.gz") == false) { // Exclude app.tar.gz files + println!("Found fallback DMG: {}", asset.name); return Some(asset.browser_download_url.clone()); } } + println!("No suitable asset found for platform"); None } @@ -448,6 +517,16 @@ pub fn get_app_version_info() -> Result<(String, bool), String> { )) } +#[tauri::command] +pub async fn check_for_app_updates_manual() -> Result, String> { + println!("Manual app update check triggered"); + let updater = AppAutoUpdater::new(); + updater + .check_for_updates() + .await + .map_err(|e| format!("Failed to check for app updates: {e}")) +} + #[cfg(test)] mod tests { use super::*; @@ -502,6 +581,27 @@ mod tests { // Upgrade from stable to nightly assert!(updater.should_update("v1.0.0", "nightly-abc123", true)); + + // Upgrade from dev to nightly + assert!(updater.should_update("dev-0.1.0", "nightly-abc123", true)); + } + + #[test] + fn test_should_update_edge_cases() { + let updater = AppAutoUpdater::new(); + + // Test with different nightly formats + assert!(updater.should_update("nightly-abc123", "nightly-def456", true)); + assert!(!updater.should_update("nightly-abc123", "nightly-abc123", true)); + + // Test stable version edge cases + assert!(updater.should_update("v0.9.9", "v1.0.0", false)); + assert!(!updater.should_update("v1.0.0", "v0.9.9", false)); + assert!(!updater.should_update("v1.0.0", "v1.0.0", false)); + + // Test version without 'v' prefix + assert!(updater.should_update("0.9.9", "v1.0.0", false)); + assert!(updater.should_update("v0.9.9", "1.0.0", false)); } #[test] diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index a081bbf..929c52f 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -54,7 +54,7 @@ use auto_updater::{ }; use app_auto_updater::{ - check_for_app_updates, download_and_install_app_update, get_app_version_info, + check_for_app_updates, check_for_app_updates_manual, download_and_install_app_update, get_app_version_info, }; #[tauri::command] @@ -183,6 +183,7 @@ pub fn run() { // Add a small delay to ensure the app is fully loaded tokio::time::sleep(tokio::time::Duration::from_secs(3)).await; + println!("Starting app update check at startup..."); let updater = app_auto_updater::AppAutoUpdater::new(); match updater.check_for_updates().await { Ok(Some(update_info)) => { @@ -191,13 +192,17 @@ pub fn run() { update_info.current_version, update_info.new_version ); // Emit update available event to the frontend - let _ = app_handle_update.emit("app-update-available", &update_info); + if let Err(e) = app_handle_update.emit("app-update-available", &update_info) { + eprintln!("Failed to emit app update event: {}", e); + } else { + println!("App update event emitted successfully"); + } } Ok(None) => { println!("No app updates available"); } Err(e) => { - eprintln!("Failed to check for app updates: {e}"); + eprintln!("Failed to check for app updates: {}", e); } } }); @@ -255,6 +260,7 @@ pub fn run() { remove_auto_update_download, is_auto_update_download, check_for_app_updates, + check_for_app_updates_manual, download_and_install_app_update, get_app_version_info, ]) diff --git a/src/app/page.tsx b/src/app/page.tsx index 9827e45..7cb4f0c 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -56,6 +56,7 @@ export default function Home() { // App auto-update functionality const appUpdateNotifications = useAppUpdateNotifications(); + const { checkForAppUpdatesManual } = appUpdateNotifications; // Ensure we're on the client side to prevent hydration mismatches useEffect(() => { diff --git a/src/hooks/use-app-update-notifications.tsx b/src/hooks/use-app-update-notifications.tsx index 53587bd..0badb2d 100644 --- a/src/hooks/use-app-update-notifications.tsx +++ b/src/hooks/use-app-update-notifications.tsx @@ -13,6 +13,7 @@ export function useAppUpdateNotifications() { const [isUpdating, setIsUpdating] = useState(false); const [updateProgress, setUpdateProgress] = useState(""); const [isClient, setIsClient] = useState(false); + const [dismissedVersion, setDismissedVersion] = useState(null); // Ensure we're on the client side to prevent hydration mismatches useEffect(() => { @@ -26,10 +27,33 @@ export function useAppUpdateNotifications() { const update = await invoke( "check_for_app_updates", ); - setUpdateInfo(update); + + // Don't show update if this version was already dismissed + if (update && update.new_version !== dismissedVersion) { + setUpdateInfo(update); + } else if (update) { + console.log("Update available but dismissed:", update.new_version); + } } catch (error) { console.error("Failed to check for app updates:", error); } + }, [isClient, dismissedVersion]); + + const checkForAppUpdatesManual = useCallback(async () => { + if (!isClient) return; + + try { + console.log("Triggering manual app update check..."); + const update = await invoke( + "check_for_app_updates_manual", + ); + console.log("Manual check result:", update); + + // Always show manual check results, even if previously dismissed + setUpdateInfo(update); + } catch (error) { + console.error("Failed to manually check for app updates:", error); + } }, [isClient]); const handleAppUpdate = useCallback(async (appUpdateInfo: AppUpdateInfo) => { @@ -56,9 +80,15 @@ export function useAppUpdateNotifications() { const dismissAppUpdate = useCallback(() => { if (!isClient) return; + // Remember the dismissed version so we don't show it again + if (updateInfo) { + setDismissedVersion(updateInfo.new_version); + console.log("Dismissed app update version:", updateInfo.new_version); + } + setUpdateInfo(null); toast.dismiss("app-update"); - }, [isClient]); + }, [isClient, updateInfo]); // Listen for app update availability useEffect(() => { @@ -116,10 +146,19 @@ export function useAppUpdateNotifications() { isClient, ]); + // Check for app updates on startup + useEffect(() => { + if (!isClient) return; + + // Check for updates immediately on startup + void checkForAppUpdates(); + }, [isClient, checkForAppUpdates]); + return { updateInfo, isUpdating, checkForAppUpdates, + checkForAppUpdatesManual, dismissAppUpdate, }; }