From 8c980d2b37f7e72a59505eb00283692ceec9dcfd Mon Sep 17 00:00:00 2001 From: Blizzard Date: Tue, 23 Jun 2026 16:26:02 +0800 Subject: [PATCH] =?UTF-8?q?feat(desktop,ci):=20GitHub=20Releases=20?= =?UTF-8?q?=E5=88=86=E5=8F=91=20+=20=E6=A1=8C=E9=9D=A2=E7=AB=AF=E6=A3=80?= =?UTF-8?q?=E6=9F=A5=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 让桌面端像开源项目一样用 GitHub Releases 分发,旧版用户自动获知更新: - CD:.github/workflows/release.yml —— 打 vX.Y.Z 标签触发,macOS(universal)/ Windows(amd64) 用 wails build 出安装包,softprops/action-gh-release 发布到 GitHub Release(GOWORK=off,因 desktop 不在工作区)。 - 桌面端检查更新:lib/version.ts(APP_VERSION + 查 GitHub /releases/latest + 语义版本比较 + openExternal 走 Wails BrowserOpenURL);UpdateBanner 启动时 检查,有新版顶部横幅「前往下载」。404/限流/离线均静默不打扰。 注:未签名/公证(macOS Gatekeeper / Win SmartScreen 需后续证书);release workflow 需真实 tag push 才能验证(GitHub 侧运行)。 Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/release.yml | 60 +++++++++++++++++++ sundynix-desktop/frontend/src/App.tsx | 2 + .../frontend/src/components/UpdateBanner.tsx | 38 ++++++++++++ sundynix-desktop/frontend/src/lib/version.ts | 50 ++++++++++++++++ 4 files changed, 150 insertions(+) create mode 100644 .github/workflows/release.yml create mode 100644 sundynix-desktop/frontend/src/components/UpdateBanner.tsx create mode 100644 sundynix-desktop/frontend/src/lib/version.ts diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..b280160 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,60 @@ +name: Release + +# 打 vX.Y.Z 标签即触发:各平台用 wails 构建桌面端安装包 → 发布到 GitHub Release。 +# 用户旧版 App 启动时查 /releases/latest,发现新版即提示下载(见 frontend/src/lib/version.ts)。 +on: + push: + tags: ["v*"] + +permissions: + contents: write # 创建 release + 上传产物需要 + +jobs: + build: + name: 构建 ${{ matrix.label }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - os: macos-latest + label: macOS (universal) + platform: darwin/universal + - os: windows-latest + label: Windows (amd64) + platform: windows/amd64 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: "1.25" + - uses: actions/setup-node@v4 + with: + node-version: "20" + - name: 安装 wails CLI + run: go install github.com/wailsapp/wails/v2/cmd/wails@v2.12.0 + + - name: 构建桌面端(wails build;GOWORK=off 因 desktop 不在工作区) + working-directory: sundynix-desktop + env: + GOWORK: off + run: wails build -clean -platform ${{ matrix.platform }} + + # macOS:.app 是目录,打包成 zip 才能作为单文件资产分发。 + - name: 打包 macOS 产物 + if: runner.os == 'macOS' + working-directory: sundynix-desktop/build/bin + run: zip -r -y sundynix_desktop_macos_universal.zip sundynix_desktop.app + + - name: 重命名 Windows 产物 + if: runner.os == 'Windows' + working-directory: sundynix-desktop/build/bin + run: ren sundynix_desktop.exe sundynix_desktop_windows_amd64.exe + + - name: 发布到 GitHub Release(按 tag 创建/追加资产) + uses: softprops/action-gh-release@v2 + with: + fail_on_unmatched_files: false + files: | + sundynix-desktop/build/bin/sundynix_desktop_macos_universal.zip + sundynix-desktop/build/bin/sundynix_desktop_windows_amd64.exe diff --git a/sundynix-desktop/frontend/src/App.tsx b/sundynix-desktop/frontend/src/App.tsx index 4009511..7171ff2 100644 --- a/sundynix-desktop/frontend/src/App.tsx +++ b/sundynix-desktop/frontend/src/App.tsx @@ -12,6 +12,7 @@ import { RunsView } from "./views/RunsView"; import { Home } from "./views/Home"; import { Placeholder } from "./views/Placeholder"; import { CommandPalette, type Command } from "./components/CommandPalette"; +import { UpdateBanner } from "./components/UpdateBanner"; import { Login } from "./views/Login"; import { submitTask, streamTokens, streamExec, authMe, logout, type Identity, type AuthUser } from "./lib/api"; import type { TaskDsl } from "./lib/dsl"; @@ -158,6 +159,7 @@ export default function App() { className="pointer-events-none absolute inset-x-0 top-0 h-64 opacity-60" style={{ background: "radial-gradient(60% 100% at 50% 0%, rgba(124,92,246,0.10), transparent 70%)" }} /> + setCmdOpen(true)} />
diff --git a/sundynix-desktop/frontend/src/components/UpdateBanner.tsx b/sundynix-desktop/frontend/src/components/UpdateBanner.tsx new file mode 100644 index 0000000..633c00b --- /dev/null +++ b/sundynix-desktop/frontend/src/components/UpdateBanner.tsx @@ -0,0 +1,38 @@ +import { useEffect, useState } from "react"; +import { Download, X } from "lucide-react"; +import { checkUpdate, openExternal, type ReleaseInfo } from "../lib/version"; + +// 启动时查 GitHub Releases,有新版则在顶部显示一条可关闭的横幅,点击去下载页。 +export function UpdateBanner() { + const [rel, setRel] = useState(null); + const [dismissed, setDismissed] = useState(false); + + useEffect(() => { + let alive = true; + void checkUpdate().then((r) => { + if (alive) setRel(r); + }); + return () => { + alive = false; + }; + }, []); + + if (!rel || dismissed) return null; + return ( +
+ + + 新版本 v{rel.version} 可用 + + + +
+ ); +} diff --git a/sundynix-desktop/frontend/src/lib/version.ts b/sundynix-desktop/frontend/src/lib/version.ts new file mode 100644 index 0000000..34d4348 --- /dev/null +++ b/sundynix-desktop/frontend/src/lib/version.ts @@ -0,0 +1,50 @@ +// 桌面端版本与"检查更新"——查 GitHub Releases 最新版,比当前版本新则提示下载。 +// 发版流程:bump 此处 APP_VERSION(= package.json version)→ 打 tag vX.Y.Z → release workflow 自动构建+发布。 + +export const APP_VERSION = "0.1.0"; // 当前桌面端版本(与 git tag 去掉 v 前缀对齐) + +const REPO = "blizzardzhang/sundynix-agentix"; + +export interface ReleaseInfo { + version: string; // 最新版本号(不含 v) + url: string; // release 页面(用户在此选 mac/win 安装包) + notes: string; // 更新日志 +} + +// checkUpdate 查 GitHub 最新 release;有比当前更新的版本则返回信息,否则 null(含出错/限流时静默)。 +export async function checkUpdate(): Promise { + try { + const res = await fetch(`https://api.github.com/repos/${REPO}/releases/latest`, { + headers: { Accept: "application/vnd.github+json" }, + }); + if (!res.ok) return null; // 404=还没发过 release / 403=限流 → 静默 + const d = (await res.json()) as { tag_name?: string; html_url?: string; body?: string }; + const latest = String(d.tag_name ?? "").replace(/^v/, ""); + if (!latest || !isNewer(latest, APP_VERSION)) return null; + return { version: latest, url: d.html_url ?? `https://github.com/${REPO}/releases/latest`, notes: d.body ?? "" }; + } catch { + return null; // 离线等 → 不打扰 + } +} + +// openExternal 在系统浏览器打开链接(Wails 用 runtime.BrowserOpenURL,浏览器模式回退 window.open)。 +export function openExternal(url: string): void { + const w = window as unknown as { runtime?: { BrowserOpenURL?: (u: string) => void } }; + if (w.runtime?.BrowserOpenURL) { + w.runtime.BrowserOpenURL(url); + } else { + window.open(url, "_blank"); + } +} + +// isNewer 语义化版本比较:a 是否比 b 新(按 major.minor.patch)。 +function isNewer(a: string, b: string): boolean { + const pa = a.split(".").map((n) => parseInt(n, 10) || 0); + const pb = b.split(".").map((n) => parseInt(n, 10) || 0); + for (let i = 0; i < 3; i++) { + const x = pa[i] ?? 0; + const y = pb[i] ?? 0; + if (x !== y) return x > y; + } + return false; +}