fix(upgrade): respect prerelease channel & guard downgrades#1698
Conversation
- 默认 updateChannel 不再硬编码为 stable,首次按当前安装包版本号 推断(含 -beta/-alpha/-rc/-canary 等后缀的视为 beta 渠道) - update-available 加 semver 兜底,远端版本 <= 当前版本时拒绝, 避免 channel 错配把 Canary 用户"升级"到旧的正式版 - checkPendingUpdate 加渠道一致性校验:marker 中目标版本与当前 安装包不属于同一渠道时直接丢弃,避免被钉死为 previousUpdateFailed - 补充对应单元测试
📝 WalkthroughWalkthroughThe PR refactors how the Electron app determines its update channel and validates remote versions. Channel selection now infers ChangesUpdate Channel and Version Validation
🎯 3 (Moderate) | ⏱️ ~25 minutes
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/main/presenter/upgradePresenter/index.ts`:
- Around line 291-302: The current marker check only discards cross-channel
markers; extend it to also discard stale same-channel markers by comparing
semantic versions: when updateInfo.version (markerVersion) exists and is on the
same channel as currentVersion (isPrereleaseVersion(markerVersion) ===
isPrereleaseVersion(currentVersion)) and markerVersion is less than
currentVersion (use semver.lt / semver.compare), unlink this._updateMarkerPath
and return so the marker does not fall through to setting _previousUpdateFailed;
change the logic in the block that references updateInfo.version,
isPrereleaseVersion, currentVersion and this._updateMarkerPath accordingly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: cf122de4-047c-497b-809e-cb8f6dbe7ed5
📒 Files selected for processing (3)
src/main/presenter/configPresenter/index.tssrc/main/presenter/upgradePresenter/index.tstest/main/presenter/upgradePresenter.test.ts
| // 渠道一致性校验:marker 中的目标版本若与当前安装包不属于同一渠道(beta vs stable), | ||
| // 说明上次的"待完成更新"来自渠道错配,应直接丢弃而不是钉死为 previousUpdateFailed | ||
| const markerVersion = typeof updateInfo.version === 'string' ? updateInfo.version : '' | ||
| if (markerVersion) { | ||
| const markerIsPre = isPrereleaseVersion(markerVersion) | ||
| const currentIsPre = isPrereleaseVersion(currentVersion) | ||
| if (markerIsPre !== currentIsPre) { | ||
| console.log('忽略跨渠道的旧 update marker', { marker: markerVersion, currentVersion }) | ||
| fs.unlinkSync(this._updateMarkerPath) | ||
| return | ||
| } | ||
| } |
There was a problem hiding this comment.
Discard same-channel stale markers as well.
A marker for an older version on the same channel still falls through to _previousUpdateFailed. For example, 1.0.4-beta.4 left behind after a manual install of 1.0.5-beta.5 will now block later auto-updates even though the marker is stale.
Suggested fix
const markerVersion = typeof updateInfo.version === 'string' ? updateInfo.version : ''
if (markerVersion) {
+ try {
+ if (compare(markerVersion, currentVersion, '<=')) {
+ console.log('忽略过期的 update marker', { marker: markerVersion, currentVersion })
+ fs.unlinkSync(this._updateMarkerPath)
+ return
+ }
+ } catch (e) {
+ console.warn('marker 版本号无效,删除 update marker', markerVersion, e)
+ fs.unlinkSync(this._updateMarkerPath)
+ return
+ }
+
const markerIsPre = isPrereleaseVersion(markerVersion)
const currentIsPre = isPrereleaseVersion(currentVersion)
if (markerIsPre !== currentIsPre) {
console.log('忽略跨渠道的旧 update marker', { marker: markerVersion, currentVersion })
fs.unlinkSync(this._updateMarkerPath)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // 渠道一致性校验:marker 中的目标版本若与当前安装包不属于同一渠道(beta vs stable), | |
| // 说明上次的"待完成更新"来自渠道错配,应直接丢弃而不是钉死为 previousUpdateFailed | |
| const markerVersion = typeof updateInfo.version === 'string' ? updateInfo.version : '' | |
| if (markerVersion) { | |
| const markerIsPre = isPrereleaseVersion(markerVersion) | |
| const currentIsPre = isPrereleaseVersion(currentVersion) | |
| if (markerIsPre !== currentIsPre) { | |
| console.log('忽略跨渠道的旧 update marker', { marker: markerVersion, currentVersion }) | |
| fs.unlinkSync(this._updateMarkerPath) | |
| return | |
| } | |
| } | |
| // 渠道一致性校验:marker 中的目标版本若与当前安装包不属于同一渠道(beta vs stable), | |
| // 说明上次的"待完成更新"来自渠道错配,应直接丢弃而不是钉死为 previousUpdateFailed | |
| const markerVersion = typeof updateInfo.version === 'string' ? updateInfo.version : '' | |
| if (markerVersion) { | |
| try { | |
| if (compare(markerVersion, currentVersion, '<=')) { | |
| console.log('忽略过期的 update marker', { marker: markerVersion, currentVersion }) | |
| fs.unlinkSync(this._updateMarkerPath) | |
| return | |
| } | |
| } catch (e) { | |
| console.warn('marker 版本号无效,删除 update marker', markerVersion, e) | |
| fs.unlinkSync(this._updateMarkerPath) | |
| return | |
| } | |
| const markerIsPre = isPrereleaseVersion(markerVersion) | |
| const currentIsPre = isPrereleaseVersion(currentVersion) | |
| if (markerIsPre !== currentIsPre) { | |
| console.log('忽略跨渠道的旧 update marker', { marker: markerVersion, currentVersion }) | |
| fs.unlinkSync(this._updateMarkerPath) | |
| return | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/main/presenter/upgradePresenter/index.ts` around lines 291 - 302, The
current marker check only discards cross-channel markers; extend it to also
discard stale same-channel markers by comparing semantic versions: when
updateInfo.version (markerVersion) exists and is on the same channel as
currentVersion (isPrereleaseVersion(markerVersion) ===
isPrereleaseVersion(currentVersion)) and markerVersion is less than
currentVersion (use semver.lt / semver.compare), unlink this._updateMarkerPath
and return so the marker does not fall through to setting _previousUpdateFailed;
change the logic in the block that references updateInfo.version,
isPrereleaseVersion, currentVersion and this._updateMarkerPath accordingly.
Summary
close #1691。Canary 安装包启动后会被自动"更新"到旧的正式版本,且后续切回内测渠道会被误判为"自动更新失败"。
根因
configPresenter默认updateChannel = 'stable',导致 Canary 安装包首启时也按 stable 拉latest.yml。electron-updater在 channel 错配时拿到的可能是版本号低于当前 beta 的旧正式版,但update-available没有做版本号兜底,直接进入自动下载安装。auto_update_marker.json,下次启动checkPendingUpdate会把渠道错配的 stale marker 当作"上次更新失败",把_previousUpdateFailed钉死,后续切到 beta 拿到正确版本也只能提示手动下载。改动
configPresenter.getUpdateChannel():移除硬编码默认stable,首次按当前安装包版本号是否含-alpha/-beta/-rc/-canary等后缀推断默认渠道。upgradePresenterupdate-available:加 semver 兜底,远端版本<=当前版本直接拒绝(覆盖 issue 描述的 Canary 被推送旧正式版场景)。不以"channel 是否同源"作为独立拒绝条件,避免误伤 "beta → 同版本号 stable 正式发布" 这类合法的渠道收敛升级。upgradePresentercheckPendingUpdate:marker 中目标版本若与当前安装包不属于同一渠道(beta vs stable),直接丢弃 marker 而不是钉死_previousUpdateFailed。Test plan
pnpm exec vitest run test/main/presenter/upgradePresenter.test.ts(7/7 passed)pnpm run typecheckpnpm run lintpnpm run formatSummary by CodeRabbit
Release Notes