From 6da262c7a96b56027d69c69cb88c0b67c4352b71 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <209825114+claude[bot]@users.noreply.github.com> Date: Mon, 2 Jun 2025 17:06:37 +0000 Subject: [PATCH 1/2] feat: use GitHub API for desktop download links instead of package.json version - Create githubRelease.ts utility to fetch latest release from GitHub API - Update downloads page to use dynamic GitHub release data - Add graceful fallback to package.json version if API fails - Include loading indicator while fetching release info - Fixes issue where NPM version bumps before desktop releases Fixes #89 Co-authored-by: AnthonyRonning --- frontend/src/routes/downloads.tsx | 63 ++++++++++++++++++++---- frontend/src/utils/githubRelease.ts | 74 +++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+), 10 deletions(-) create mode 100644 frontend/src/utils/githubRelease.ts diff --git a/frontend/src/routes/downloads.tsx b/frontend/src/routes/downloads.tsx index 90e14e19..32e78a6c 100644 --- a/frontend/src/routes/downloads.tsx +++ b/frontend/src/routes/downloads.tsx @@ -5,14 +5,56 @@ import { MarketingHeader } from "@/components/MarketingHeader"; import { Monitor, Terminal, Globe, Smartphone } from "lucide-react"; import { Apple } from "@/components/icons/Apple"; import { Android } from "@/components/icons/Android"; +import { useState, useEffect } from "react"; +import { getLatestDownloadInfo } from "@/utils/githubRelease"; import packageJson from "../../package.json"; -// Get version from package.json -const APP_VERSION = packageJson.version; -const CURRENT_VERSION = `v${APP_VERSION}`; -const BASE_DOWNLOAD_URL = `https://github.com/OpenSecretCloud/Maple/releases/download/${CURRENT_VERSION}`; +interface DownloadUrls { + macOS: string; + linuxAppImage: string; + linuxDeb: string; + linuxRpm: string; +} + +// Fallback to package.json version if GitHub API fails +const FALLBACK_VERSION = packageJson.version; +const FALLBACK_TAG = `v${FALLBACK_VERSION}`; +const FALLBACK_BASE_URL = `https://github.com/OpenSecretCloud/Maple/releases/download/${FALLBACK_TAG}`; +const FALLBACK_URLS: DownloadUrls = { + macOS: `${FALLBACK_BASE_URL}/Maple_${FALLBACK_VERSION}_universal.dmg`, + linuxAppImage: `${FALLBACK_BASE_URL}/Maple_${FALLBACK_VERSION}_amd64.AppImage`, + linuxDeb: `${FALLBACK_BASE_URL}/Maple_${FALLBACK_VERSION}_amd64.deb`, + linuxRpm: `${FALLBACK_BASE_URL}/Maple-${FALLBACK_VERSION}-1.x86_64.rpm`, +}; function DownloadPage() { + const [downloadUrls, setDownloadUrls] = useState(FALLBACK_URLS); + const [currentVersion, setCurrentVersion] = useState(FALLBACK_VERSION); + const [releaseUrl, setReleaseUrl] = useState( + `https://github.com/OpenSecretCloud/Maple/releases/tag/${FALLBACK_TAG}` + ); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + async function loadLatestRelease() { + try { + const downloadInfo = await getLatestDownloadInfo(); + if (downloadInfo) { + setDownloadUrls(downloadInfo.downloadUrls); + setCurrentVersion(downloadInfo.version); + setReleaseUrl(downloadInfo.releaseUrl); + } + } catch (error) { + console.error("Failed to load latest release info:", error); + // Keep fallback values + } finally { + setIsLoading(false); + } + } + + loadLatestRelease(); + }, []); + return ( <> @@ -51,7 +93,7 @@ function DownloadPage() {

- Current version: {APP_VERSION} •{" "} + Current version: {currentVersion} + {isLoading && (loading...)} •{" "} { + try { + const response = await fetch( + "https://api.github.com/repos/OpenSecretCloud/Maple/releases/latest" + ); + + if (!response.ok) { + console.error("Failed to fetch latest release:", response.status); + return null; + } + + const release: GitHubRelease = await response.json(); + return release; + } catch (error) { + console.error("Error fetching latest release:", error); + return null; + } +} + +/** + * Gets download information for the latest release + */ +export async function getLatestDownloadInfo(): Promise { + const release = await fetchLatestRelease(); + + if (!release) { + return null; + } + + // Extract version number from tag (remove 'v' prefix if present) + const version = release.tag_name.startsWith("v") + ? release.tag_name.slice(1) + : release.tag_name; + + const baseDownloadUrl = `https://github.com/OpenSecretCloud/Maple/releases/download/${release.tag_name}`; + + return { + version, + tagName: release.tag_name, + downloadUrls: { + macOS: `${baseDownloadUrl}/Maple_${version}_universal.dmg`, + linuxAppImage: `${baseDownloadUrl}/Maple_${version}_amd64.AppImage`, + linuxDeb: `${baseDownloadUrl}/Maple_${version}_amd64.deb`, + linuxRpm: `${baseDownloadUrl}/Maple-${version}-1.x86_64.rpm`, + }, + releaseUrl: release.html_url, + }; +} \ No newline at end of file From 1381e14b4c232ae28ba19d0ff53899d95721795f Mon Sep 17 00:00:00 2001 From: "claude[bot]" <209825114+claude[bot]@users.noreply.github.com> Date: Mon, 2 Jun 2025 17:26:18 +0000 Subject: [PATCH 2/2] feat: add type validation for GitHub API response Add runtime validation to ensure the GitHub releases API response contains the expected fields before casting to GitHubRelease interface. This prevents runtime errors from malformed API responses and provides better error messages for debugging. Co-authored-by: AnthonyRonning --- frontend/src/utils/githubRelease.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/frontend/src/utils/githubRelease.ts b/frontend/src/utils/githubRelease.ts index 58c3a89b..16ba2a7c 100644 --- a/frontend/src/utils/githubRelease.ts +++ b/frontend/src/utils/githubRelease.ts @@ -35,7 +35,12 @@ export async function fetchLatestRelease(): Promise { return null; } - const release: GitHubRelease = await response.json(); + const data = await response.json(); + if (!data?.tag_name || !data?.name || !data?.published_at || !data?.html_url) { + console.error("Invalid release data format from GitHub API"); + return null; + } + const release: GitHubRelease = data; return release; } catch (error) { console.error("Error fetching latest release:", error);