* ♻️ refactor: implement config-based update system with version compatibility control Replace GitHub API-based update discovery with JSON config file system. Support version gating (users below v1.7 must upgrade to v1.7.0 before v2.0). Auto-select GitHub/GitCode config source based on IP location. Simplify fallback logic. Changes: - Add update-config.json with version compatibility rules - Implement _fetchUpdateConfig() and _findCompatibleChannel() - Remove legacy _getReleaseVersionFromGithub() and GitHub API dependency - Refactor _setFeedUrl() with simplified fallback to default feed URLs - Add design documentation in docs/UPDATE_CONFIG_DESIGN.md 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix(i18n): Auto update translations for PR #11147 * format code * 🔧 chore: update config for v1.7.5 → v2.0.0 → v2.1.6 upgrade path Update version configuration to support multi-step upgrade path: - v1.6.x users → v1.7.5 (last v1.x release) - v1.7.x users → v2.0.0 (v2.x intermediate version) - v2.0.0+ users → v2.1.6 (current latest) Changes: - Update 1.7.0 → 1.7.5 with fixed feedUrl - Set 2.0.0 as intermediate version with fixed feedUrl - Add 2.1.6 as current latest pointing to releases/latest This ensures users upgrade through required intermediate versions before jumping to major releases. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * 🔧 chore: refactor update config with constants and adjust versions Refactor update configuration system and adjust to actual versions: - Add UpdateConfigUrl enum in constant.ts for centralized config URLs - Point to test server (birdcat.top) for development testing - Update AppUpdater.ts to use UpdateConfigUrl constants - Adjust update-config.json to actual v1.6.7 with rc/beta channels - Remove v2.1.6 entry (not yet released) - Set package version to 1.6.5 for testing upgrade path - Add update-config.example.json for reference 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * update version * ✅ test: add comprehensive unit tests for AppUpdater config system Add extensive test coverage for new config-based update system including: - Config fetching with IP-based source selection (GitHub/GitCode) - Channel compatibility matching with version constraints - Smart fallback from rc/beta to latest when appropriate - Multi-step upgrade path validation (1.6.3 → 1.6.7 → 2.0.0) - Error handling for network and HTTP failures Test Coverage: - _fetchUpdateConfig: 4 tests (GitHub/GitCode selection, error handling) - _findCompatibleChannel: 9 tests (channel matching, version comparison) - Upgrade Path: 3 tests (version gating scenarios) - Total: 30 tests, 100% passing Also optimize _findCompatibleChannel logic with better variable naming and log messages. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * ✅ test: add complete multi-step upgrade path tests (1.6.3 → 1.7.5 → 2.0.0 → 2.1.6) Add comprehensive test suite for complete upgrade journey including: - Individual step validation (1.6.3→1.7.5, 1.7.5→2.0.0, 2.0.0→2.1.6) - Full multi-step upgrade simulation with version progression - Version gating enforcement (block skipping intermediate versions) - Verification that 1.6.3 cannot directly upgrade to 2.0.0 or 2.1.6 - Verification that 1.7.5 cannot skip 2.0.0 to reach 2.1.6 Test Coverage: - 6 new tests for complete upgrade path scenarios - Total: 36 tests, 100% passing This ensures the version compatibility system correctly enforces intermediate version upgrades for major releases. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * 📝 docs: reorganize update config documentation with English translation Move update configuration design document to docs/technical/ directory and add English translation for international contributors. Changes: - Move docs/UPDATE_CONFIG_DESIGN.md → docs/technical/app-update-config-zh.md - Add docs/technical/app-update-config-en.md (English translation) - Organize technical documentation in dedicated directory Documentation covers: - Config-based update system design and rationale - JSON schema with version compatibility control - Multi-step upgrade path examples (1.6.3 → 1.7.5 → 2.0.0 → 2.1.6) - TypeScript type definitions and matching algorithms - GitHub/GitCode source selection for different regions 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * format code * ✅ test: add tests for latest channel self-comparison prevention Add tests to verify the optimization that prevents comparing latest channel with itself when latest is requested, and ensures rc/beta channels are returned when they are newer than latest. New tests: - should not compare latest with itself when requesting latest channel - should return rc when rc version > latest version - should return beta when beta version > latest version These tests ensure the requestedChannel !== UpgradeChannel.LATEST check works correctly and users get the right channel based on version comparisons. Test Coverage: 39 tests, 100% passing 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * update github/gitcode * format code * update rc version * ♻️ refactor: merge update configs into single multi-mirror file - Merge app-upgrade-config-github.json and app-upgrade-config-gitcode.json into single app-upgrade-config.json - Add UpdateMirror enum for type-safe mirror selection - Optimize _fetchUpdateConfig to receive mirror parameter, eliminating duplicate IP country checks - Update ChannelConfig interface to use Record<UpdateMirror, string> for feedUrls - Rename documentation files from app-update-config-* to app-upgrade-config-* - Update docs with new multi-mirror configuration structure 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * ✅ test: update AppUpdater tests for multi-mirror configuration - Add UpdateMirror enum import - Update _fetchUpdateConfig tests to accept mirror parameter - Convert all feedUrl to feedUrls structure in test mocks - Update test expectations to match new ChannelConfig interface - All 39 tests passing 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * format code * delete files * 📝 docs: add UpdateMirror enum to type definitions - Add UpdateMirror enum definition in both EN and ZH docs - Update ChannelConfig to use Record<UpdateMirror, string> - Add comments showing equivalent structure for clarity 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * 🐛 fix: return actual channel from _findCompatibleChannel Fix channel mismatch issue where requesting rc/beta but getting latest: - Change _findCompatibleChannel return type to include actual channel - Return { config, channel } instead of just config - Update _setFeedUrl to use actualChannel instead of requestedChannel - Update all test expectations to match new return structure - Add channel assertions to key tests This ensures autoUpdater.channel matches the actual feed URL being used. Fixes issue where: - User requests 'rc' channel - latest >= rc, so latest config is returned - But channel was set to 'rc' with latest URL ❌ - Now channel is correctly set to 'latest' ✅ All 39 tests passing ✅ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * update version * udpate version * update config * add no cache header * update files * 🤖 chore: automate app upgrade config updates * format code * update workflow * update get method * docs: document upgrade workflow automation --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: GitHub Action <action@github.com>
17 KiB
更新配置系统设计文档
背景
当前 AppUpdater 直接请求 GitHub API 获取 beta 和 rc 的更新信息。为了支持国内用户,需要根据 IP 地理位置,分别从 GitHub/GitCode 获取一个固定的 JSON 配置文件,该文件包含所有渠道的更新地址。
设计目标
- 支持根据 IP 地理位置选择不同的配置源(GitHub/GitCode)
- 支持版本兼容性控制(如 v1.x 以下必须先升级到 v1.7.0 才能升级到 v2.0)
- 易于扩展,支持未来多个主版本的升级路径(v1.6 → v1.7 → v2.0 → v2.8 → v3.0)
- 保持与现有 electron-updater 机制的兼容性
当前版本策略
- v1.7.x 是 1.x 系列的最后版本
- v1.7.0 以下的用户必须先升级到 v1.7.0(或更高的 1.7.x 版本)
- v1.7.0 及以上的用户可以直接升级到 v2.x.x
自动化工作流
cs-releases/app-upgrade-config.json 由 Update App Upgrade Config workflow 自动同步。工作流会调用 scripts/update-app-upgrade-config.ts 脚本,根据指定 tag 更新 cs-releases 分支上的配置文件。
触发条件
- Release 事件(
release: released/prereleased)- Draft release 会被忽略。
- 当 GitHub 将 release 标记为 prerelease 时,tag 必须包含
-beta/-rc(可带序号),否则直接跳过。 - 当 release 标记为稳定版时,tag 必须与 GitHub API 返回的最新稳定版本一致,防止发布历史 tag 时意外挂起工作流。
- 满足上述条件后,工作流会根据语义化版本判断渠道(
latest/beta/rc),并通过IS_PRERELEASE传递给脚本。
- 手动触发(
workflow_dispatch)- 必填:
tag(例:v2.0.1);选填:is_prerelease(默认false)。 - 当
is_prerelease=true时,同样要求 tag 带有 beta/rc 后缀。 - 手动运行仍会请求 GitHub 最新 release 信息,用于在 PR 说明中标注该 tag 是否是最新稳定版。
- 必填:
工作流步骤
- 检查与元数据准备:
Check if should proceed和Prepare metadata步骤会计算 tag、prerelease 标志、是否最新版本以及用于分支名的safe_tag。若任意校验失败,工作流立即退出。 - 检出分支:默认分支被检出到
main/,长期维护的cs-releases分支则在cs/中,所有改动都发生在cs/。 - 安装工具链:安装 Node.js 22、启用 Corepack,并在
main/目录执行yarn install --immutable。 - 运行更新脚本:执行
yarn tsx scripts/update-app-upgrade-config.ts --tag <tag> --config ../cs/app-upgrade-config.json --is-prerelease <flag>。- 脚本会标准化 tag(去掉
v前缀等)、识别渠道、加载config/app-upgrade-segments.json中的分段规则。 - 校验 prerelease 标志与语义后缀是否匹配、强制锁定的 segment 是否满足、生成镜像的下载地址,并检查 release 是否已经在 GitHub/GitCode 可用(latest 渠道在 GitCode 不可用时会回退到
https://releases.cherry-ai.com)。 - 更新对应的渠道配置后,脚本会按 semver 排序写回 JSON,并刷新
lastUpdated。
- 脚本会标准化 tag(去掉
- 检测变更并创建 PR:若
cs/app-upgrade-config.json有变更,则创建chore/update-app-upgrade-config/<safe_tag>分支,提交信息为🤖 chore: sync app-upgrade-config for <tag>,并向cs-releases提 PR;无变更则输出提示。
手动触发指南
- 进入 Cherry Studio 仓库的 GitHub Actions 页面,选择 Update App Upgrade Config 工作流。
- 点击 Run workflow,保持默认分支(通常为
main),填写tag(如v2.1.0)。 - 只有在 tag 带
-beta/-rc后缀时才勾选is_prerelease,稳定版保持默认。 - 启动运行并等待完成,随后到
cs-releases分支的 PR 查看app-upgrade-config.json的变更并在验证后合并。
JSON 配置文件格式
文件位置
- GitHub:
https://raw.githubusercontent.com/CherryHQ/cherry-studio/refs/heads/cs-releases/app-upgrade-config.json - GitCode:
https://gitcode.com/CherryHQ/cherry-studio/raw/cs-releases/app-upgrade-config.json
说明:两个镜像源提供相同的配置文件,统一托管在 cs-releases 分支上。客户端根据 IP 地理位置自动选择最优镜像源。
配置结构(当前实际配置)
{
"lastUpdated": "2025-01-05T00:00:00Z",
"versions": {
"1.6.7": {
"minCompatibleVersion": "1.0.0",
"description": "Last stable v1.7.x release - required intermediate version for users below v1.7",
"channels": {
"latest": {
"version": "1.6.7",
"feedUrls": {
"github": "https://github.com/CherryHQ/cherry-studio/releases/download/v1.6.7",
"gitcode": "https://gitcode.com/CherryHQ/cherry-studio/releases/download/v1.6.7"
}
},
"rc": {
"version": "1.6.0-rc.5",
"feedUrls": {
"github": "https://github.com/CherryHQ/cherry-studio/releases/download/v1.6.0-rc.5",
"gitcode": "https://github.com/CherryHQ/cherry-studio/releases/download/v1.6.0-rc.5"
}
},
"beta": {
"version": "1.6.7-beta.3",
"feedUrls": {
"github": "https://github.com/CherryHQ/cherry-studio/releases/download/v1.7.0-beta.3",
"gitcode": "https://github.com/CherryHQ/cherry-studio/releases/download/v1.7.0-beta.3"
}
}
}
},
"2.0.0": {
"minCompatibleVersion": "1.7.0",
"description": "Major release v2.0 - required intermediate version for v2.x upgrades",
"channels": {
"latest": null,
"rc": null,
"beta": null
}
}
}
}
未来扩展示例
当需要发布 v3.0 时,如果需要强制用户先升级到 v2.8,可以添加:
{
"2.8.0": {
"minCompatibleVersion": "2.0.0",
"description": "Stable v2.8 - required for v3 upgrade",
"channels": {
"latest": {
"version": "2.8.0",
"feedUrls": {
"github": "https://github.com/CherryHQ/cherry-studio/releases/download/v2.8.0",
"gitcode": "https://gitcode.com/CherryHQ/cherry-studio/releases/download/v2.8.0"
}
},
"rc": null,
"beta": null
}
},
"3.0.0": {
"minCompatibleVersion": "2.8.0",
"description": "Major release v3.0",
"channels": {
"latest": {
"version": "3.0.0",
"feedUrls": {
"github": "https://github.com/CherryHQ/cherry-studio/releases/latest",
"gitcode": "https://gitcode.com/CherryHQ/cherry-studio/releases/latest"
}
},
"rc": {
"version": "3.0.0-rc.1",
"feedUrls": {
"github": "https://github.com/CherryHQ/cherry-studio/releases/download/v3.0.0-rc.1",
"gitcode": "https://gitcode.com/CherryHQ/cherry-studio/releases/download/v3.0.0-rc.1"
}
},
"beta": null
}
}
}
字段说明
lastUpdated: 配置文件最后更新时间(ISO 8601 格式)versions: 版本配置对象,key 为版本号,按语义化版本排序minCompatibleVersion: 可以升级到此版本的最低兼容版本description: 版本描述channels: 更新渠道配置latest: 稳定版渠道rc: Release Candidate 渠道beta: Beta 测试渠道- 每个渠道包含:
version: 该渠道的版本号feedUrls: 多镜像源 URL 配置github: GitHub 镜像源的 electron-updater feed URLgitcode: GitCode 镜像源的 electron-updater feed URL
metadata: 自动化匹配所需的稳定标识segmentId: 来自config/app-upgrade-segments.json的段位 IDsegmentType: 可选字段(legacy|breaking|latest),便于文档/调试
TypeScript 类型定义
// 镜像源枚举
enum UpdateMirror {
GITHUB = 'github',
GITCODE = 'gitcode'
}
interface UpdateConfig {
lastUpdated: string
versions: {
[versionKey: string]: VersionConfig
}
}
interface VersionConfig {
minCompatibleVersion: string
description: string
channels: {
latest: ChannelConfig | null
rc: ChannelConfig | null
beta: ChannelConfig | null
}
metadata?: {
segmentId: string
segmentType?: 'legacy' | 'breaking' | 'latest'
}
}
interface ChannelConfig {
version: string
feedUrls: Record<UpdateMirror, string>
// 等同于:
// feedUrls: {
// github: string
// gitcode: string
// }
}
段位元数据(Break Change 标记)
- 所有段位定义(如
legacy-v1、gateway-v2等)集中在config/app-upgrade-segments.json,用于描述匹配范围、segmentId、segmentType、默认minCompatibleVersion/description以及各渠道的 URL 模板。 versions下的每个节点都会带上metadata.segmentId。自动脚本始终依据该 ID 来定位并更新条目,即便 key 从2.1.5切换到2.1.6也不会错位。- 如果某段需要锁死在特定版本(例如
2.0.0的 break change),可在段定义中设置segmentType: "breaking"并提供lockedVersion,脚本在遇到不匹配的 tag 时会短路报错,保证升级路径安全。 - 面对未来新的断层(例如
3.0.0),只需要在段定义里新增一段,自动化即可识别并更新。
自动化工作流
.github/workflows/update-app-upgrade-config.yml 会在 GitHub Release(包含正常发布与 Pre Release)触发:
- 同时 Checkout 仓库默认分支(用于脚本)和
cs-releases分支(真实托管配置的分支)。 - 在默认分支目录执行
yarn tsx scripts/update-app-upgrade-config.ts --tag <tag> --config ../cs/app-upgrade-config.json,直接重写cs-releases分支里的配置文件。 - 如果
app-upgrade-config.json有变化,则通过peter-evans/create-pull-request自动创建一个指向cs-releases的 PR,Diff 仅包含该文件。
如需本地调试,可执行 yarn update:upgrade-config --tag v2.1.6 --config ../cs/app-upgrade-config.json(加 --dry-run 仅打印结果)来复现 CI 行为。若需要暂时跳过 GitHub/GitCode Release 页面是否就绪的校验,可在 --dry-run 的同时附加 --skip-release-checks。不加 --config 时默认更新当前工作目录(通常是 main 分支)下的副本,方便文档/审查。
版本匹配逻辑
算法流程
- 获取用户当前版本(
currentVersion)和请求的渠道(requestedChannel) - 获取配置文件中所有版本号,按语义化版本从大到小排序
- 遍历排序后的版本列表:
- 检查
currentVersion >= minCompatibleVersion - 检查请求的
channel是否存在且不为null - 如果满足条件,返回该渠道配置
- 检查
- 如果没有找到匹配版本,返回
null
伪代码实现
function findCompatibleVersion(
currentVersion: string,
requestedChannel: UpgradeChannel,
config: UpdateConfig
): ChannelConfig | null {
// 获取所有版本号并从大到小排序
const versions = Object.keys(config.versions).sort(semver.rcompare)
for (const versionKey of versions) {
const versionConfig = config.versions[versionKey]
const channelConfig = versionConfig.channels[requestedChannel]
// 检查版本兼容性和渠道可用性
if (
semver.gte(currentVersion, versionConfig.minCompatibleVersion) &&
channelConfig !== null
) {
return channelConfig
}
}
return null // 没有找到兼容版本
}
升级路径示例
场景 1: v1.6.5 用户升级(低于 1.7)
- 当前版本: 1.6.5
- 请求渠道: latest
- 匹配结果: 1.7.0
- 原因: 1.6.5 >= 0.0.0(满足 1.7.0 的 minCompatibleVersion),但不满足 2.0.0 的 minCompatibleVersion (1.7.0)
- 操作: 提示用户升级到 1.7.0,这是升级到 v2.x 的必要中间版本
场景 2: v1.6.5 用户请求 rc/beta
- 当前版本: 1.6.5
- 请求渠道: rc 或 beta
- 匹配结果: 1.7.0 (latest)
- 原因: 1.7.0 版本不提供 rc/beta 渠道(值为 null)
- 操作: 升级到 1.7.0 稳定版
场景 3: v1.7.0 用户升级到最新版
- 当前版本: 1.7.0
- 请求渠道: latest
- 匹配结果: 2.0.0
- 原因: 1.7.0 >= 1.7.0(满足 2.0.0 的 minCompatibleVersion)
- 操作: 直接升级到 2.0.0(当前最新稳定版)
场景 4: v1.7.2 用户升级到 RC 版本
- 当前版本: 1.7.2
- 请求渠道: rc
- 匹配结果: 2.0.0-rc.1
- 原因: 1.7.2 >= 1.7.0(满足 2.0.0 的 minCompatibleVersion),且 rc 渠道存在
- 操作: 升级到 2.0.0-rc.1
场景 5: v1.7.0 用户升级到 Beta 版本
- 当前版本: 1.7.0
- 请求渠道: beta
- 匹配结果: 2.0.0-beta.1
- 原因: 1.7.0 >= 1.7.0,且 beta 渠道存在
- 操作: 升级到 2.0.0-beta.1
场景 6: v2.5.0 用户升级(未来)
假设已添加 v2.8.0 和 v3.0.0 配置:
- 当前版本: 2.5.0
- 请求渠道: latest
- 匹配结果: 2.8.0
- 原因: 2.5.0 >= 2.0.0(满足 2.8.0 的 minCompatibleVersion),但不满足 3.0.0 的要求
- 操作: 提示用户升级到 2.8.0,这是升级到 v3.x 的必要中间版本
代码改动计划
主要修改
-
新增方法
_fetchUpdateConfig(ipCountry: string): Promise<UpdateConfig | null>- 根据 IP 获取配置文件_findCompatibleChannel(currentVersion: string, channel: UpgradeChannel, config: UpdateConfig): ChannelConfig | null- 查找兼容的渠道配置
-
修改方法
_getReleaseVersionFromGithub()→ 移除或重构为_getChannelFeedUrl()_setFeedUrl()- 使用新的配置系统替代现有逻辑
-
新增类型定义
UpdateConfigVersionConfigChannelConfig
镜像源选择逻辑
客户端根据 IP 地理位置自动选择最优镜像源:
private async _setFeedUrl() {
const currentVersion = app.getVersion()
const testPlan = configManager.getTestPlan()
const requestedChannel = testPlan ? this._getTestChannel() : UpgradeChannel.LATEST
// 根据 IP 国家确定镜像源
const ipCountry = await getIpCountry()
const mirror = ipCountry.toLowerCase() === 'cn' ? 'gitcode' : 'github'
// 获取更新配置
const config = await this._fetchUpdateConfig(mirror)
if (config) {
const channelConfig = this._findCompatibleChannel(currentVersion, requestedChannel, config)
if (channelConfig) {
// 从配置中选择对应镜像源的 URL
const feedUrl = channelConfig.feedUrls[mirror]
this._setChannel(requestedChannel, feedUrl)
return
}
}
// Fallback 逻辑
const defaultFeedUrl = mirror === 'gitcode'
? FeedUrl.PRODUCTION
: FeedUrl.GITHUB_LATEST
this._setChannel(UpgradeChannel.LATEST, defaultFeedUrl)
}
private async _fetchUpdateConfig(mirror: 'github' | 'gitcode'): Promise<UpdateConfig | null> {
const configUrl = mirror === 'gitcode'
? UpdateConfigUrl.GITCODE
: UpdateConfigUrl.GITHUB
try {
const response = await net.fetch(configUrl, {
headers: {
'User-Agent': generateUserAgent(),
'Accept': 'application/json',
'X-Client-Id': configManager.getClientId()
}
})
return await response.json() as UpdateConfig
} catch (error) {
logger.error('Failed to fetch update config:', error)
return null
}
}
降级和容错策略
- 配置文件获取失败: 记录错误日志,返回当前版本,不提供更新
- 没有匹配的版本: 提示用户当前版本不支持自动升级
- 网络异常: 缓存上次成功获取的配置(可选)
GitHub Release 要求
为支持中间版本升级,需要保留以下文件:
- v1.7.0 release 及其 latest*.yml 文件(作为 v1.7 以下用户的升级目标)
- 未来如需强制中间版本(如 v2.8.0),需要保留对应的 release 和 latest*.yml 文件
- 各版本的完整安装包
当前需要的 Release
| 版本 | 用途 | 必须保留 |
|---|---|---|
| v1.7.0 | 1.7 以下用户的升级目标 | ✅ 是 |
| v2.0.0-rc.1 | RC 测试渠道 | ❌ 可选 |
| v2.0.0-beta.1 | Beta 测试渠道 | ❌ 可选 |
| latest | 最新稳定版(自动) | ✅ 是 |
优势
- 灵活性: 支持任意复杂的升级路径
- 可扩展性: 新增版本只需在配置文件中添加新条目
- 可维护性: 配置与代码分离,无需发版即可调整升级策略
- 多源支持: 自动根据地理位置选择最优配置源
- 版本控制: 强制中间版本升级,确保数据迁移和兼容性
未来扩展
- 支持更细粒度的版本范围控制(如
>=1.5.0 <1.8.0) - 支持多步升级路径提示(如提示用户需要 1.5 → 1.8 → 2.0)
- 支持 A/B 测试和灰度发布
- 支持配置文件的本地缓存和过期策略