* refactor: switch workflows from yarn to pnpm
Replace Yarn usage with pnpm in CI workflows to standardize package
management and leverage pnpm's store/cache behavior.
- Use pnpm/action-setup to install pnpm (v) instead of enabling corepack
and preparing Yarn.
- Retrieve pnpm store path and update cache actions to cache the pnpm
store and use pnpm-lock.yaml for cache keys and restores.
- Replace yarn commands with pnpm equivalents across workflows:
install, i18n:sync/translate, format, build:* and tsx invocation.
- Avoid committing lockfile changes by resetting pnpm-lock.yaml instead
of yarn.lock when checking for changes.
- Update install flags: use pnpm install --frozen-lockfile / --install
semantics where appropriate.
These changes unify dependency tooling, improve caching correctness,
and ensure CI uses pnpm-specific lockfile and cache paths.
* build: switch pre-commit hook to pnpm lint-staged
Update .husky/pre-commit to run pnpm lint-staged instead of yarn.
This aligns the pre-commit hook with the project's package manager
and ensures lint-staged runs using pnpm's environment and caching.
* chore(ci): remove pinned pnpm version from GH Action steps
Remove the explicit `with: version: 9` lines from multiple GitHub Actions workflows
(auto-i18n.yml, nightly-build.yml, pr-ci.yml, update-app-upgrade-config.yml,
sync-to-gitcode.yml, release.yml). The workflows still call `pnpm/action-setup@v4`
but no longer hardcode a pnpm version.
This simplifies maintenance and allows the action to resolve an appropriate pnpm
version (or use its default) without needing updates whenever the pinned
version becomes outdated. It reduces churn when bumping pnpm across CI configs
and prevents accidental pin drift between workflow files.
* build: Update pnpm to 10.27.0 and add onlyBuiltDependencies config
* Update @cherrystudio/openai to 6.15.0 and consolidate overrides
* Add @langchain/core to overrides
* Add override for openai-compatible 1.0.27
* build: optimize pnpm config and add missing dependencies
- Comment out shamefully-hoist in .npmrc for better pnpm compatibility
- Add React-related packages to optimizeDeps in electron.vite.config.ts
- Add missing peer dependencies and packages that were previously hoisted
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* build: refine pnpm configuration and dependency management
- Simplify .npmrc to only essential electron mirror config
- Move platform-specific dependencies to devDependencies
- Pin sharp version to 0.34.3 for consistency
- Update sharp-libvips versions to 1.2.4
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* reduce app size
* format
* build: remove unnecessary disableOxcRecommendation option from react plugin configuration
* docs: Replace yarn commands with pnpm in documentation and scripts
* Revert "build: optimize pnpm config and add missing dependencies"
This reverts commit acffad31f8.
* build: import dependencies from yarn.lock
* build: Add some phantom dependencies and reorganize dependencies
* build: Keep consistent by removing types of semver
It's not in the previous package.json
* build: Add some phantom dependencies
Keep same version with yarn.lock
* build: Add form-data dependency version 4.0.4
* Add chalk dependency
* build: downgrade some dependencies
Reference: .yarn-state-copy.yml. These phantom dependencies should use top-level package of that version in node_modules
* build: Add phantom dependencies
* build: pin tiptap dependencies to exact versions
Ensure consistent dependency resolution by removing caret ranges and pinning all @tiptap packages to exact version 3.2.0
* chore: pin embedjs dependencies to exact versions
* build: pin @modelcontextprotocol/sdk to exact version 1.23.0
Remove caret from version specifier to prevent automatic upgrades and ensure consistent dependencies
* chore: update @types/node dependency to 22.17.2
Update package.json and pnpm-lock.yaml to use @types/node version 22.17.2 instead of 22.19.3 to maintain consistency across dependencies
* build: move some dependencies to dev deps and pin dependency versions to exact numbers
Remove caret (^) from version ranges to ensure consistent dependency resolution across environments
* chore: move dependencies from prod to dev and update lockfile
Move @ant-design/icons, chalk, form-data, and open from dependencies to devDependencies
Update pnpm-lock.yaml to reflect dependency changes
* build: update package dependencies
- Add new dependencies: md5, @libsql/win32-x64-msvc, @strongtz/win32-arm64-msvc, bonjour-service, emoji-picker-element-data, gray-matter, js-yaml
- Remove redundant dependencies from devDependencies
* build: add cors, katex and pako dependencies
add new dependencies to support cross-origin requests, mathematical notation rendering and data compression
* move some js deps to dev deps
* test: update snapshot tests for Spinner and InputEmbeddingDimension
* chore: exclude .zed directory from biome formatting
* Update @ai-sdk/openai-compatible patch hash
* chore: update @kangfenmao/keyv-storage to version 0.1.3 in package.json and pnpm-lock.yaml
---------
Co-authored-by: icarus <eurfelux@gmail.com>
Co-authored-by: beyondkmp <beyondkmp@gmail.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-authored-by: kangfenmao <kangfenmao@qq.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
自动化工作流
x-files/app-upgrade-config/app-upgrade-config.json 由 Update App Upgrade Config workflow 自动同步。工作流会调用 scripts/update-app-upgrade-config.ts 脚本,根据指定 tag 更新 x-files/app-upgrade-config 分支上的配置文件。
触发条件
- 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/,长期维护的x-files/app-upgrade-config分支则在cs/中,所有改动都发生在cs/。 - 安装工具链:安装 Node.js 22、启用 Corepack,并在
main/目录执行pnpm install --frozen-lockfile。 - 运行更新脚本:执行
pnpm 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>,并向x-files/app-upgrade-config提 PR;无变更则输出提示。
手动触发指南
- 进入 Cherry Studio 仓库的 GitHub Actions 页面,选择 Update App Upgrade Config 工作流。
- 点击 Run workflow,保持默认分支(通常为
main),填写tag(如v2.1.0)。 - 只有在 tag 带
-beta/-rc后缀时才勾选is_prerelease,稳定版保持默认。 - 启动运行并等待完成,随后到
x-files/app-upgrade-config分支的 PR 查看app-upgrade-config.json的变更并在验证后合并。
JSON 配置文件格式
文件位置
- GitHub:
https://raw.githubusercontent.com/CherryHQ/cherry-studio/refs/heads/x-files/app-upgrade-config/app-upgrade-config.json - GitCode:
https://gitcode.com/CherryHQ/cherry-studio/raw/x-files/app-upgrade-config/app-upgrade-config.json
说明:两个镜像源提供相同的配置文件,统一托管在 x-files/app-upgrade-config 分支上。客户端根据 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 仓库默认分支(用于脚本)和
x-files/app-upgrade-config分支(真实托管配置的分支)。 - 在默认分支目录执行
pnpm tsx scripts/update-app-upgrade-config.ts --tag <tag> --config ../cs/app-upgrade-config.json,直接重写x-files/app-upgrade-config分支里的配置文件。 - 如果
app-upgrade-config.json有变化,则通过peter-evans/create-pull-request自动创建一个指向x-files/app-upgrade-config的 PR,Diff 仅包含该文件。
如需本地调试,可执行 pnpm 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 测试和灰度发布
- 支持配置文件的本地缓存和过期策略