应用更新与自动发布集成总结
背景
本次改造的目标是为 Keep Notes 增加应用内更新能力,并通过 GitHub Actions 自动完成打包、发布和安装包上传,避免每次发布都依赖手动打包操作。
最终产出包括:
- 设置弹窗新增
关于tab。 关于tab 展示当前版本号、检查更新入口、仓库地址和作者信息。- 支持通过 GitHub Release 检查更新。
- 支持更新包下载进度展示。
- 支持下载过程中通过图标按钮取消更新。
- GitHub Actions 在
master更新时自动构建 macOS、Windows、Linux 安装包。 - 自动创建版本 tag 和 GitHub Release,并上传安装包及更新元数据。
应用更新集成
应用更新能力基于 electron-updater 实现,核心代码位于:
src/main/updater.tssrc/main/ipc/updater.ipc.tssrc/preload/api/app.api.tssrc/shared/constants.tssrc/shared/types.tssrc/renderer/src/features/settings/components/settings-modal.tsx
主进程更新控制器
AppUpdateController 负责封装更新相关能力,包括:
- 获取应用信息:版本号、仓库地址、作者。
- 检查 GitHub Release 是否存在新版本。
- 控制更新下载。
- 监听下载进度。
- 取消更新下载。
- 更新下载完成后触发安装。
- 通过 IPC 将更新状态同步给渲染进程。
更新状态统一抽象为 AppUpdateState,渲染层只消费状态,不直接接触 Electron 或 Node 能力,符合当前项目中主进程负责系统能力、preload 暴露安全 API 的架构原则。
更新流程
当前更新流程如下:
- 用户打开设置弹窗,进入
关于tab。 - 页面通过 preload API 获取应用信息和更新状态。
- 用户点击
检查更新。 - 渲染进程通过 IPC 调用主进程的
checkForUpdates。 - 主进程通过
electron-updater查询 GitHub Release。 - 如果发现新版本,自动开始下载。
- 下载期间持续接收
download-progress事件并同步给页面。 - 页面显示下载进度。
- 下载中用户可以点击关闭图标取消下载。
- 下载完成后,页面显示安装入口。
- 用户确认安装后,主进程调用
quitAndInstall重启并安装更新。
开发环境限制
开发环境下不直接执行自动更新。原因是 electron-updater 的真实更新流程依赖打包后的应用信息、发布配置和安装包元数据。开发模式运行的 Electron 应用没有完整安装包上下文,不能代表真实更新环境。
因此当前实现中:
app.isPackaged === false时,不进行真实更新检查。- 页面提示用户使用打包后的安装版检查 GitHub 更新。
- 真实更新能力以 GitHub Actions 生成的 Release 安装包为准。
技术方案
方案总览
本次更新能力采用 Electron 主进程集中控制、渲染进程状态展示、GitHub Release 作为发布源、GitHub Actions 自动构建发布的方案。
整体链路可以拆成四层:
- 应用层:设置弹窗提供
关于tab,负责展示版本信息、更新状态、下载进度和操作按钮。 - 通信层:preload 暴露受控 API,renderer 通过 IPC 调用主进程能力。
- 更新层:主进程封装
electron-updater,负责检查、下载、取消和安装更新。 - 发布层:GitHub Actions 调用
electron-builder生成安装包,再上传到 GitHub Release,供客户端自动更新使用。
这个方案的核心取舍是:更新相关能力全部留在主进程,页面只做状态驱动的 UI 渲染。这样既能满足 Electron 自动更新的权限要求,也避免把 Node/Electron 原生能力直接暴露到渲染进程。
相关库与作用
本次改造主要涉及以下技术和库:
| 技术/库 | 使用位置 | 作用 |
|---|---|---|
| Electron | 主进程、preload、渲染进程 | 桌面应用运行环境,提供应用生命周期、窗口、IPC、外链打开等能力 |
| electron-updater | src/main/updater.ts | 检查 GitHub Release、下载更新包、监听下载进度、安装更新 |
| builder-util-runtime | src/main/updater.ts | 提供 CancellationToken,用于取消更新下载 |
| electron-builder | electron-builder.yml、GitHub Actions | 打包 Windows/macOS/Linux 安装包,生成自动更新需要的 yml 和 blockmap 元数据 |
| electron-vite | pnpm build | 构建 Electron 主进程、preload 和 renderer 产物 |
| React | 设置弹窗 UI | 渲染 关于 tab、更新按钮、进度展示和取消按钮 |
| TypeScript | 全项目 | 定义更新状态、应用信息、IPC API 类型,减少主进程与渲染进程之间的数据结构歧义 |
| lucide-react | 设置弹窗 UI | 提供检查更新、外链跳转、取消下载等图标 |
| Radix UI | 设置弹窗基础组件 | 承载设置弹窗、tab、按钮等交互基础能力 |
| GitHub Actions | .github/workflows/release.yml | 在推送到 master 后自动构建、创建 Release、上传安装包 |
| softprops/action-gh-release | .github/workflows/release.yml | 将各平台安装包和更新元数据上传到指定 GitHub Release |
| actions/upload-artifact | .github/workflows/release.yml | 保存 CI 构建产物,便于排查和下载 |
| pnpm | 本地和 CI | 依赖安装、脚本执行、锁定依赖版本 |
| Vitest | 单元测试 | 验证更新控制器和设置弹窗更新交互 |
关键技术选择
1. 使用 electron-updater 而不是手写更新逻辑
自动更新涉及平台差异、安装包校验、元数据解析、增量下载和安装触发。手写这部分逻辑成本高,也容易遗漏平台细节。
选择 electron-updater 的原因:
- 与
electron-builder的发布产物天然配套。 - 支持 GitHub Release provider。
- 自动识别
latest.yml、latest-mac.yml、latest-linux.yml。 - 内置下载进度事件。
- 支持更新下载完成后的
quitAndInstall。
2. 使用 electron-builder 统一打包和生成更新元数据
自动更新不只是上传安装包,还需要上传平台对应的更新元数据。electron-builder 可以在打包时同步生成这些文件,避免手动维护。
关键产物包括:
- Windows:
.exe、.exe.blockmap、latest.yml - macOS:
.dmg、.zip、.blockmap、latest-mac.yml - Linux:
.AppImage、.deb、latest-linux.yml
3. 使用 GitHub Release 作为更新源
当前项目已经托管在 GitHub,使用 GitHub Release 可以减少额外服务依赖。
优点:
- 不需要自建更新服务器。
- Release 资产可以直接作为更新包分发源。
GITHUB_TOKEN可以在 Actions 内直接创建 Release 和上传资产。- 版本、tag、安装包可以统一管理。
限制:
- 国内网络访问 GitHub 可能不稳定。
- 大文件下载速度取决于用户网络环境。
- 私有仓库场景需要额外处理访问权限。
4. 使用 GitHub Actions matrix 做跨平台构建
Electron 应用的安装包通常需要在对应平台构建。workflow 使用 matrix 分别运行在 macOS、Windows、Linux runner 上,减少跨平台构建问题。
这样可以保证:
- macOS 产物由 macOS runner 构建。
- Windows NSIS 安装包由 Windows runner 构建。
- Linux AppImage/deb 由 Ubuntu runner 构建。
5. 使用版本号驱动发布
Actions 通过 package.json 的 version 生成 Git tag 和 Release 名称,例如 2.0.3 对应 v2.0.3。
这个策略让版本来源保持单一:
- 应用内显示版本来自
app.getVersion()。 - 打包产物版本来自
package.json。 - GitHub Release tag 来自
package.json。 - 自动更新比较版本时也基于发布元数据中的版本。
发布源
发布源选择 GitHub Release,配置在 electron-builder.yml:
publish:
provider: github
owner: qiuweikangdev
repo: keep-notes
electron-updater 会基于该配置查询 Release 中的更新元数据,例如:
latest.ymllatest-mac.ymllatest-linux.yml.blockmap- 平台安装包
打包配置
electron-builder.yml 负责定义各平台产物:
- Windows:NSIS 安装包,产物形如
keep-notes-2.0.3-setup.exe。 - macOS:DMG 和 zip,产物形如
keep-notes-2.0.3.dmg、keep-notes-2.0.3-arm64-mac.zip。 - Linux:AppImage 和 deb,产物形如
keep-notes-2.0.3.AppImage、keep-notes_2.0.3_amd64.deb。
其中 zip 和 yml 文件对 macOS 自动更新尤其重要,Windows 自动更新主要依赖 latest.yml 和 NSIS 安装包。
UI 设计
关于 tab 采用较轻量的设置项布局:
- 版本号显示在检查更新按钮旁边。
- 检查更新按钮去掉默认蓝色背景,仅保留悬浮态反馈。
- 下载中展示进度。
- 取消更新使用图标按钮表达,避免占用过多空间。
- 仓库地址通过外链图标跳转。
- 作者信息直接展示。
这样可以让 关于 页面更接近设置页的信息密度,而不是营销式卡片布局。
GitHub Actions 集成
工作流文件位于:
.github/workflows/release.yml
触发方式
当前支持两种触发方式:
- 推送到
master分支。 - 手动触发
workflow_dispatch。
on:
push:
branches:
- master
workflow_dispatch:
发布准备阶段
prepare-release job 负责:
- 读取
package.json中的版本号。 - 生成 tag 名称,例如
v2.0.3。 - 检查远端是否已有同名 tag。
- 如果 tag 不存在,则创建并推送 tag。
- 如果 tag 已存在且指向当前 commit,则复用。
- 如果 tag 已存在但指向其他 commit,则终止发布,要求先升级版本号。
- 创建或更新 GitHub Release。
这个设计避免了同一个版本号被不同 commit 重复发布,减少自动更新版本混乱的风险。
构建发布阶段
build-and-publish job 使用 matrix 分别在三个系统构建:
macos-latest构建 macOS 安装包。windows-latest构建 Windows 安装包。ubuntu-latest构建 Linux 安装包。
每个平台执行:
- Checkout 代码。
- 安装 pnpm。
- 安装 Node.js 20。
- 安装依赖。
- 执行
pnpm build。 - 执行
electron-builder install-app-deps。 - 执行
electron-builder打包。 - 通过
softprops/action-gh-release上传安装包和更新元数据。 - 通过
actions/upload-artifact保存 workflow artifacts。
依赖安装策略
Actions 中使用:
pnpm install --frozen-lockfile --ignore-scripts --registry=https://registry.npmjs.org/
原因:
--frozen-lockfile保证 CI 使用锁文件中的确定依赖版本。--ignore-scripts避免安装阶段提前执行postinstall,减少平台依赖安装时机的不确定性。- 后续显式执行
pnpm exec electron-builder install-app-deps,让 Electron 原生依赖安装步骤更清晰。 --registry=https://registry.npmjs.org/避免历史 lockfile 中的镜像源证书问题影响 CI。
相关技术难点
1. Electron 主进程与渲染进程边界
自动更新涉及文件下载、应用重启、外链跳转等系统能力,这些能力不应该直接暴露给渲染进程。
解决方式:
- 主进程封装
AppUpdateController。 - preload 只暴露有限的安全 API。
- 渲染进程只通过 IPC 调用更新动作和订阅状态。
这样既满足功能需求,也保持了项目已有的安全边界。
2. 更新状态同步
更新过程不是一次性请求,而是一个持续状态流,包括:
- 空闲
- 检查中
- 有可用更新
- 下载中
- 已下载
- 已取消
- 无更新
- 失败
解决方式:
- 主进程维护统一状态。
- 通过事件监听
electron-updater。 - 状态变化后广播到所有窗口。
- 渲染进程根据状态渲染按钮、进度和提示文案。
3. 下载取消
electron-updater 的下载取消需要使用 CancellationToken。本次实现中,每次下载开始时创建新的 token,并在用户点击取消图标时调用 cancel()。
需要注意:
- 取消后要清理当前 token。
- 取消状态不能被后续 error 事件覆盖成失败状态。
- 取消后页面需要恢复到可再次检查更新的状态。
4. 自动发布版本一致性
自动更新非常依赖版本号、tag 和 Release 的一致性。如果同一个版本号指向多个 commit,客户端可能拿到错误的更新包。
解决方式:
- Actions 读取
package.json版本号生成 tag。 - tag 已存在但不指向当前 commit 时直接失败。
- 每次发布新功能前需要先升级
package.json版本号。
5. 跨平台构建产物
不同系统的自动更新依赖不同产物:
- Windows 需要 NSIS 安装包和
latest.yml。 - macOS 通常需要 zip、DMG、
latest-mac.yml和 blockmap。 - Linux 需要 AppImage/deb 以及
latest-linux.yml。
因此 workflow 不能只上传安装包,还必须上传 yml 和 blockmap 元数据。
遇到的问题与处理
问题 1:主进程启动时报 autoUpdater named export 错误
现象:
SyntaxError: Named export 'autoUpdater' not found.
The requested module 'electron-updater' is a CommonJS module
原因:
electron-updater 是 CommonJS 模块,不能在当前 ESM 构建方式下直接使用 named import。
处理方式:
改为默认导入后解构:
import electronUpdater from "electron-updater";
const { autoUpdater } = electronUpdater;
问题 2:开发环境无法真实检查更新
现象:
开发模式中点击检查更新时无法执行真实自动更新。
原因:
自动更新依赖打包后的安装应用、发布配置和 Release 元数据。开发模式缺少真实安装包上下文。
处理方式:
在 app.isPackaged === false 时返回明确提示:
开发模式仅预览更新界面;请使用安装版检查 GitHub 更新。
问题 3:Release 创建成功但没有安装包
现象:
早期 workflow 创建了 tag 和 release,但安装包没有成功上传。
原因:
发布流程中需要明确区分:
- 创建 GitHub Release。
- 各平台构建安装包。
- 将安装包和更新元数据上传到对应 Release。
处理方式:
将工作流拆成 prepare-release 和 build-and-publish 两个阶段,并使用 softprops/action-gh-release 上传 dist 下的安装包和更新元数据。
问题 4:GitHub Actions 安装依赖失败
现象:
Actions 在 pnpm install --frozen-lockfile 阶段失败,日志中出现:
CERT_HAS_EXPIRED
失败地址来自:
https://r2.cnpmjs.org/...
原因:
pnpm-lock.yaml 中存在历史镜像源 tarball 地址,GitHub Actions 下载依赖时遇到镜像源证书过期。
处理方式:
- 将 lockfile 中的 tarball 地址切换为
https://registry.npmjs.org/。 - 在 workflow 中显式指定 npm 官方 registry。
最终 Actions 成功完成,v2.0.3 Release 中已上传安装包和自动更新元数据。
当前验证结果
本次最终验证结果:
pnpm typecheck通过。pnpm lint通过,0 error,仍存在项目既有 warning。pnpm build通过。pnpm test通过,38 个测试文件、152 个测试全部通过。- GitHub Actions run 成功:
- GitHub Release 成功:
v2.0.3 Release 中包含关键产物:
keep-notes-2.0.3-setup.exekeep-notes-2.0.3-setup.exe.blockmapkeep-notes-2.0.3.dmgkeep-notes-2.0.3.dmg.blockmapkeep-notes-2.0.3-arm64-mac.zipkeep-notes-2.0.3-arm64-mac.zip.blockmapkeep-notes-2.0.3.AppImagekeep-notes_2.0.3_amd64.deblatest.ymllatest-mac.ymllatest-linux.yml
后续维护建议
- 每次需要发布新版本时,先升级
package.json中的版本号。 - 合并或推送到
master后,GitHub Actions 会自动创建对应 tag 和 Release。 - 如果 Actions 提示 tag 已存在且指向其他 commit,说明当前版本号已经发布过,需要继续升级版本号。
- 可以后续清理早期失败发布产生的
v2.0.0、v2.0.1、v2.0.2tag 和 Release。 - 如果需要正式面向 macOS 用户分发,后续应补充 Apple Developer 签名和 notarization。
- 如果需要减少 Release 中的辅助文件,可以进一步收窄 workflow 的上传 glob。