diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8575a229..75919777 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -89,3 +89,43 @@ jobs: args: ${{ matrix.args }} updaterJsonPath: latest.json updaterJsonKeepUniversal: true + + # The source repo is private, so its release assets 404 for the public. + # Mirror the built artifacts to a PUBLIC releases repo that the updater + # endpoint and the website download button point at. + - name: Mirror release to public repo + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # read the private release + RELEASES_TOKEN: ${{ secrets.RELEASES_TOKEN }} # write to the public repo + PUBLIC_REPO: mydevtools-tech/mydevtools-releases + run: | + set -euo pipefail + VERSION="$(node -p "require('./apps/desktop/src-tauri/tauri.conf.json').version")" + TAG="v$VERSION" + BUNDLE="apps/desktop/src-tauri/target/universal-apple-darwin/release/bundle" + DMG="$(ls "$BUNDLE"/dmg/*.dmg)" + TARGZ="$(ls "$BUNDLE"/macos/*.app.tar.gz)" + SIG="$(ls "$BUNDLE"/macos/*.app.tar.gz.sig)" + + # Pull tauri's generated latest.json from the private release. + GH_TOKEN="$GITHUB_TOKEN" gh release download "$TAG" \ + --repo "$GITHUB_REPOSITORY" --pattern latest.json --dir . --clobber + + # Repoint its asset URLs at the public repo. The minisign signature + # signs the file bytes, not the URL, so it stays valid. + node -e ' + const fs = require("fs"); + const src = process.env.GITHUB_REPOSITORY, dst = process.env.PUBLIC_REPO; + const j = JSON.parse(fs.readFileSync("latest.json", "utf8")); + for (const k of Object.keys(j.platforms || {})) + j.platforms[k].url = j.platforms[k].url.split(src).join(dst); + fs.writeFileSync("latest.json", JSON.stringify(j, null, 2)); + ' + + # Create the public release (once) and upload all four assets. + if ! GH_TOKEN="$RELEASES_TOKEN" gh release view "$TAG" --repo "$PUBLIC_REPO" >/dev/null 2>&1; then + GH_TOKEN="$RELEASES_TOKEN" gh release create "$TAG" --repo "$PUBLIC_REPO" \ + --title "MyDevTools $TAG" --notes "MyDevTools desktop $TAG" + fi + GH_TOKEN="$RELEASES_TOKEN" gh release upload "$TAG" --repo "$PUBLIC_REPO" \ + --clobber "$DMG" "$TARGZ" "$SIG" latest.json diff --git a/apps/desktop-ui/src/components/desktop/app-version-label.tsx b/apps/desktop-ui/src/components/desktop/app-version-label.tsx new file mode 100644 index 00000000..12174ffa --- /dev/null +++ b/apps/desktop-ui/src/components/desktop/app-version-label.tsx @@ -0,0 +1,33 @@ +"use client"; + +import { useEffect, useState } from "react"; + +import { isDesktop } from "@/lib/desktop/is-desktop"; + +/** + * Subtle running-app-version line for the sidebar footer. Reads the version + * baked into the bundle (tauri.conf.json) via the updater lib. Desktop-only — + * renders nothing on web. + */ +export function AppVersionLabel() { + const [version, setVersion] = useState(""); + + useEffect(() => { + if (!isDesktop()) return; + void import("@/lib/desktop/updater").then(async (m) => { + try { + setVersion(await m.currentAppVersion()); + } catch { + // version API unavailable — just don't show it + } + }); + }, []); + + if (!isDesktop() || !version) return null; + + return ( +
+ MyDevTools v{version} +
+ ); +} diff --git a/apps/desktop-ui/src/components/sidebar/app-sidebar.tsx b/apps/desktop-ui/src/components/sidebar/app-sidebar.tsx index b1fc12fa..6d90a059 100644 --- a/apps/desktop-ui/src/components/sidebar/app-sidebar.tsx +++ b/apps/desktop-ui/src/components/sidebar/app-sidebar.tsx @@ -15,6 +15,7 @@ import { import { NavGroup } from './nav-group' import { NavUser } from './nav-user' import { FeedbackDialog } from '@/components/feedback-dialog' +import { AppVersionLabel } from '@/components/desktop/app-version-label' import { Logo } from '../logo' import { SidebarMenu, SidebarMenuButton, SidebarMenuItem } from '@/components/ui/sidebar' import { LayoutDashboard, Sparkles } from 'lucide-react' @@ -191,6 +192,7 @@ export function AppSidebar({ ...props }: React.ComponentProps