From 49be79fb9a9c90e31e82044a9ab4b9b505ace984 Mon Sep 17 00:00:00 2001 From: chenchen Date: Tue, 12 May 2026 16:18:51 +0800 Subject: [PATCH] feat(desktop,website): wire updater endpoint + release tooling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - tauri.conf.json: endpoints now points at the website-hosted manifest (https:///updater/latest.json) so the already-built UpdateChecker actually has somewhere to check. - website/public/updater/latest.json: placeholder manifest with empty platforms map (clients will see "up to date" until a real release ships). - scripts/build-updater-manifest.mjs: helper that ingests signed Tauri bundle artifacts and emits the manifest, so future releases are one node command instead of hand-edited JSON. - scripts/UPDATER.md: step-by-step for every release — what to set, what to upload, where to commit. Co-Authored-By: Claude Opus 4.7 (1M context) --- cc-haha/desktop/scripts/UPDATER.md | 62 +++++++++++ .../scripts/build-updater-manifest.mjs | 101 ++++++++++++++++++ cc-haha/desktop/src-tauri/tauri.conf.json | 4 +- website/public/updater/latest.json | 6 ++ 4 files changed, 172 insertions(+), 1 deletion(-) create mode 100644 cc-haha/desktop/scripts/UPDATER.md create mode 100644 cc-haha/desktop/scripts/build-updater-manifest.mjs create mode 100644 website/public/updater/latest.json diff --git a/cc-haha/desktop/scripts/UPDATER.md b/cc-haha/desktop/scripts/UPDATER.md new file mode 100644 index 0000000..19de145 --- /dev/null +++ b/cc-haha/desktop/scripts/UPDATER.md @@ -0,0 +1,62 @@ +# Desktop 自动更新发版手册 + +> 客户端 UI(`UpdateChecker.tsx` + `updateStore.ts`)已经做好。本文只讲**每次发版**怎么把 manifest 喂进去。 + +## 一次性准备 + +1. **拿到 minisign 私钥**。`tauri.conf.json` 里的 `pubkey` 对应一把私钥,发版机必须有。如果丢了,重新生成一对并同步更新 `pubkey`,所有已经装在用户机上的旧版本将无法验证新版本(必须手工换包)。 +2. **设置签名环境变量**(每次 build 都要): + ```pwsh + $env:TAURI_SIGNING_PRIVATE_KEY = "" + $env:TAURI_SIGNING_PRIVATE_KEY_PASSWORD = "" + ``` +3. **决定 artifact 托管位置**。当前 `endpoints` 指向: + ``` + https://ashy-dune-0e22d7b00.7.azurestaticapps.net/updater/latest.json + ``` + manifest 自己住在 website 仓库的 `website/public/updater/latest.json`。 + **MSI/DMG 本体不能塞进静态站**(太大 + Azure SWA 有大小限制)—— 需要单独的 CDN:Azure Blob、阿里 OSS、GitHub Release 都行。本文档以 `https://heicode-release.blob.core.windows.net/desktop//` 占位。 + +## 每次发版 + +```pwsh +# 1. 在 cc-haha/desktop 目录打包,签名私钥已经在环境变量里 +cd cc-haha/desktop +bun run tauri build +# 产出: +# src-tauri/target/release/bundle/nsis/heicode_X.Y.Z_x64-setup.exe +# src-tauri/target/release/bundle/nsis/heicode_X.Y.Z_x64-setup.exe.sig +# (mac 上).../bundle/macos/Heicode.app.tar.gz + .sig + +# 2. 把 .exe/.app.tar.gz 传到 CDN +# 例如 azcopy copy + +# 3. 生成 manifest,写到 website 仓库 +node scripts/build-updater-manifest.mjs ` + --version 0.1.1 ` + --notes "本次更新内容..." ` + --base-url https://heicode-release.blob.core.windows.net/desktop/0.1.1 ` + --windows-x86_64 src-tauri/target/release/bundle/nsis/heicode_0.1.1_x64-setup.exe ` + --darwin-aarch64 src-tauri/target/release/bundle/macos/Heicode_aarch64.app.tar.gz ` + --out ../../website/public/updater/latest.json + +# 4. 提交 website 仓库(manifest 改动)并触发部署 +cd ../../website +git add public/updater/latest.json +git commit -m "release: desktop 0.1.1" +git push # Azure SWA 自动重新部署 +``` + +部署完后:旧版本客户端在启动 5 秒后会自动 `silent` 检查;命中新版本会弹出右上角更新框。 + +## 兜底 / 调试 + +- 手动触发检查:Settings 页面里有"检查更新"按钮(`UpdateChecker` + `updateStore.checkForUpdates`)。 +- 用户主动忽略某个版本:右上框点"稍后",写 localStorage `cc-haha-dismissed-update-version`。下一个版本号会再次弹。 +- 验证 manifest:浏览器直接访问 `https:///updater/latest.json` 应能下载 JSON。 + +## 当前状态 + +- ✅ 客户端 UI:完整 +- ✅ `endpoints`:已指向 Azure SWA +- ⏳ `latest.json` 当前只有占位 `platforms: {}`,意味着任何客户端 check 都会返回"已是最新"。第一次走完上面 4 步后才开始真实推送更新。 diff --git a/cc-haha/desktop/scripts/build-updater-manifest.mjs b/cc-haha/desktop/scripts/build-updater-manifest.mjs new file mode 100644 index 0000000..78f02dc --- /dev/null +++ b/cc-haha/desktop/scripts/build-updater-manifest.mjs @@ -0,0 +1,101 @@ +#!/usr/bin/env node +/** + * Build the updater manifest (latest.json) consumed by tauri-plugin-updater. + * + * Usage: + * node scripts/build-updater-manifest.mjs \ + * --version 0.1.1 \ + * --notes "Bugfix release" \ + * --base-url https://your-cdn.example.com/releases/0.1.1 \ + * [--windows-x86_64 ./bundle/heicode_0.1.1_x64-setup.exe] \ + * [--darwin-aarch64 ./bundle/heicode_0.1.1_aarch64.app.tar.gz] \ + * [--darwin-x86_64 ./bundle/heicode_0.1.1_x64.app.tar.gz] \ + * [--linux-x86_64 ./bundle/heicode_0.1.1_amd64.AppImage] \ + * --out ../../../website/public/updater/latest.json + * + * For each platform path supplied, the script reads the sibling `.sig` file + * that `tauri build` produced (because `createUpdaterArtifacts: true` is + * set in tauri.conf.json) and inlines its contents into the manifest. The + * `url` field is built from `--base-url` + basename of the artifact, so the + * uploader is free to put artifacts wherever — release CDN, Azure Blob, + * GitHub Release assets — as long as `--base-url` matches. + */ +import { readFileSync, writeFileSync, mkdirSync, statSync } from 'node:fs' +import { dirname, basename, resolve } from 'node:path' +import { argv } from 'node:process' + +function parseArgs(rawArgs) { + const out = {} + for (let i = 0; i < rawArgs.length; i++) { + const key = rawArgs[i] + if (!key?.startsWith('--')) continue + const k = key.slice(2) + const next = rawArgs[i + 1] + if (!next || next.startsWith('--')) { + out[k] = true + } else { + out[k] = next + i++ + } + } + return out +} + +const PLATFORM_KEYS = [ + 'windows-x86_64', + 'darwin-aarch64', + 'darwin-x86_64', + 'linux-x86_64', +] + +const args = parseArgs(argv.slice(2)) +if (!args.version || !args['base-url'] || !args.out) { + console.error('Required: --version, --base-url, --out') + process.exit(2) +} + +const platforms = {} +for (const key of PLATFORM_KEYS) { + const path = args[key] + if (!path || path === true) continue + const resolvedPath = resolve(path) + try { + statSync(resolvedPath) + } catch { + console.error(`Artifact not found for ${key}: ${resolvedPath}`) + process.exit(3) + } + const sigPath = `${resolvedPath}.sig` + let signature + try { + signature = readFileSync(sigPath, 'utf8').trim() + } catch { + console.error(`Signature file missing: ${sigPath}`) + console.error('Run `bun run tauri build` with TAURI_SIGNING_PRIVATE_KEY set.') + process.exit(4) + } + platforms[key] = { + signature, + url: `${args['base-url'].replace(/\/$/, '')}/${basename(resolvedPath)}`, + } +} + +if (Object.keys(platforms).length === 0) { + console.error('No platform artifacts supplied — manifest would be empty.') + process.exit(5) +} + +const manifest = { + version: args.version, + notes: args.notes || `Heicode ${args.version}`, + pub_date: new Date().toISOString(), + platforms, +} + +const outPath = resolve(args.out) +mkdirSync(dirname(outPath), { recursive: true }) +writeFileSync(outPath, `${JSON.stringify(manifest, null, 2)}\n`) +console.log(`Wrote ${outPath}`) +console.log(` version = ${manifest.version}`) +console.log(` pub_date = ${manifest.pub_date}`) +console.log(` platforms = ${Object.keys(platforms).join(', ')}`) diff --git a/cc-haha/desktop/src-tauri/tauri.conf.json b/cc-haha/desktop/src-tauri/tauri.conf.json index 628e190..2b53e08 100644 --- a/cc-haha/desktop/src-tauri/tauri.conf.json +++ b/cc-haha/desktop/src-tauri/tauri.conf.json @@ -32,7 +32,9 @@ "plugins": { "updater": { "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDlCOUIwRDExQTc5RTFGMzYKUldRMkg1Nm5FUTJibTJ2cGlHY0pkL0dGemxXMUlzc01pVTVMM1U3WGpmWUtrUC8wK2ErSXhLKzEK", - "endpoints": [], + "endpoints": [ + "https://ashy-dune-0e22d7b00.7.azurestaticapps.net/updater/latest.json" + ], "windows": { "installMode": "passive" } diff --git a/website/public/updater/latest.json b/website/public/updater/latest.json new file mode 100644 index 0000000..7587375 --- /dev/null +++ b/website/public/updater/latest.json @@ -0,0 +1,6 @@ +{ + "version": "0.1.0", + "notes": "Initial Heicode desktop release. Future versions will populate this manifest automatically.", + "pub_date": "2026-05-12T00:00:00Z", + "platforms": {} +}