mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-19 14:41:24 +08:00
* ♻️ 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>
431 lines
18 KiB
Markdown
431 lines
18 KiB
Markdown
# Update Configuration System Design Document
|
||
|
||
## Background
|
||
|
||
Currently, AppUpdater directly queries the GitHub API to retrieve beta and rc update information. To support users in China, we need to fetch a static JSON configuration file from GitHub/GitCode based on IP geolocation, which contains update URLs for all channels.
|
||
|
||
## Design Goals
|
||
|
||
1. Support different configuration sources based on IP geolocation (GitHub/GitCode)
|
||
2. Support version compatibility control (e.g., users below v1.x must upgrade to v1.7.0 before upgrading to v2.0)
|
||
3. Easy to extend, supporting future multi-major-version upgrade paths (v1.6 → v1.7 → v2.0 → v2.8 → v3.0)
|
||
4. Maintain compatibility with existing electron-updater mechanism
|
||
|
||
## Current Version Strategy
|
||
|
||
- **v1.7.x** is the last version of the 1.x series
|
||
- Users **below v1.7.0** must first upgrade to v1.7.0 (or higher 1.7.x version)
|
||
- Users **v1.7.0 and above** can directly upgrade to v2.x.x
|
||
|
||
## Automation Workflow
|
||
|
||
The `cs-releases/app-upgrade-config.json` file is synchronized by the [`Update App Upgrade Config`](../../.github/workflows/update-app-upgrade-config.yml) workflow. The workflow runs the [`scripts/update-app-upgrade-config.ts`](../../scripts/update-app-upgrade-config.ts) helper so that every release tag automatically updates the JSON in `cs-releases`.
|
||
|
||
### Trigger Conditions
|
||
|
||
- **Release events (`release: released/prereleased`)**
|
||
- Draft releases are ignored.
|
||
- When GitHub marks the release as _prerelease_, the tag must include `-beta`/`-rc` (with optional numeric suffix). Otherwise the workflow exits early.
|
||
- When GitHub marks the release as stable, the tag must match the latest release returned by the GitHub API. This prevents out-of-order updates when publishing historical tags.
|
||
- If the guard clauses pass, the version is tagged as `latest` or `beta/rc` based on its semantic suffix and propagated to the script through the `IS_PRERELEASE` flag.
|
||
- **Manual dispatch (`workflow_dispatch`)**
|
||
- Required input: `tag` (e.g., `v2.0.1`). Optional input: `is_prerelease` (defaults to `false`).
|
||
- When `is_prerelease=true`, the tag must carry a beta/rc suffix, mirroring the automatic validation.
|
||
- Manual runs still download the latest release metadata so that the workflow knows whether the tag represents the newest stable version (for documentation inside the PR body).
|
||
|
||
### Workflow Steps
|
||
|
||
1. **Guard + metadata preparation** – the `Check if should proceed` and `Prepare metadata` steps compute the target tag, prerelease flag, whether the tag is the newest release, and a `safe_tag` slug used for branch names. When any rule fails, the workflow stops without touching the config.
|
||
2. **Checkout source branches** – the default branch is checked out into `main/`, while the long-lived `cs-releases` branch lives in `cs/`. All modifications happen in the latter directory.
|
||
3. **Install toolchain** – Node.js 22, Corepack, and frozen Yarn dependencies are installed inside `main/`.
|
||
4. **Run the update script** – `yarn tsx scripts/update-app-upgrade-config.ts --tag <tag> --config ../cs/app-upgrade-config.json --is-prerelease <flag>` updates the JSON in-place.
|
||
- The script normalizes the tag (e.g., strips `v` prefix), detects the release channel (`latest`, `rc`, `beta`), and loads segment rules from `config/app-upgrade-segments.json`.
|
||
- It validates that prerelease flags and semantic suffixes agree, enforces locked segments, builds mirror feed URLs, and performs release-availability checks (GitHub HEAD request for every channel; GitCode GET for latest channels, falling back to `https://releases.cherry-ai.com` when gitcode is delayed).
|
||
- After updating the relevant channel entry, the script rewrites the config with semver-sort order and a new `lastUpdated` timestamp.
|
||
5. **Detect changes + create PR** – if `cs/app-upgrade-config.json` changed, the workflow opens a PR `chore/update-app-upgrade-config/<safe_tag>` against `cs-releases` with a commit message `🤖 chore: sync app-upgrade-config for <tag>`. Otherwise it logs that no update is required.
|
||
|
||
### Manual Trigger Guide
|
||
|
||
1. Open the Cherry Studio repository on GitHub → **Actions** tab → select **Update App Upgrade Config**.
|
||
2. Click **Run workflow**, choose the default branch (usually `main`), and fill in the `tag` input (e.g., `v2.1.0`).
|
||
3. Toggle `is_prerelease` only when the tag carries a prerelease suffix (`-beta`, `-rc`). Leave it unchecked for stable releases.
|
||
4. Start the run and wait for it to finish. Check the generated PR in the `cs-releases` branch, verify the diff in `app-upgrade-config.json`, and merge once validated.
|
||
|
||
## JSON Configuration File Format
|
||
|
||
### File Location
|
||
|
||
- **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`
|
||
|
||
**Note**: Both mirrors provide the same configuration file hosted on the `cs-releases` branch. The client automatically selects the optimal mirror based on IP geolocation.
|
||
|
||
### Configuration Structure (Current Implementation)
|
||
|
||
```json
|
||
{
|
||
"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
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### Future Extension Example
|
||
|
||
When releasing v3.0, if users need to first upgrade to v2.8, you can add:
|
||
|
||
```json
|
||
{
|
||
"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
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### Field Descriptions
|
||
|
||
- `lastUpdated`: Last update time of the configuration file (ISO 8601 format)
|
||
- `versions`: Version configuration object, key is the version number, sorted by semantic versioning
|
||
- `minCompatibleVersion`: Minimum compatible version that can upgrade to this version
|
||
- `description`: Version description
|
||
- `channels`: Update channel configuration
|
||
- `latest`: Stable release channel
|
||
- `rc`: Release Candidate channel
|
||
- `beta`: Beta testing channel
|
||
- Each channel contains:
|
||
- `version`: Version number for this channel
|
||
- `feedUrls`: Multi-mirror URL configuration
|
||
- `github`: electron-updater feed URL for GitHub mirror
|
||
- `gitcode`: electron-updater feed URL for GitCode mirror
|
||
- `metadata`: Stable mapping info for automation
|
||
- `segmentId`: ID from `config/app-upgrade-segments.json`
|
||
- `segmentType`: Optional flag (`legacy` | `breaking` | `latest`) for documentation/debugging
|
||
|
||
## TypeScript Type Definitions
|
||
|
||
```typescript
|
||
// Mirror enum
|
||
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>
|
||
// Equivalent to:
|
||
// feedUrls: {
|
||
// github: string
|
||
// gitcode: string
|
||
// }
|
||
}
|
||
```
|
||
|
||
## Segment Metadata & Breaking Markers
|
||
|
||
- **Segment definitions** now live in `config/app-upgrade-segments.json`. Each segment describes a semantic-version range (or exact matches) plus metadata such as `segmentId`, `segmentType`, `minCompatibleVersion`, and per-channel feed URL templates.
|
||
- Each entry under `versions` carries a `metadata.segmentId`. This acts as the stable key that scripts use to decide which slot to update, even if the actual semantic version string changes.
|
||
- Mark major upgrade gateways (e.g., `2.0.0`) by giving the related segment a `segmentType: "breaking"` and (optionally) `lockedVersion`. This prevents automation from accidentally moving that entry when other 2.x builds ship.
|
||
- Adding another breaking hop (e.g., `3.0.0`) only requires defining a new segment in the JSON file; the automation will pick it up on the next run.
|
||
|
||
## Automation Workflow
|
||
|
||
Starting from this change, `.github/workflows/update-app-upgrade-config.yml` listens to GitHub release events (published + prerelease). The workflow:
|
||
|
||
1. Checks out the default branch (for scripts) and the `cs-releases` branch (where the config is hosted).
|
||
2. Runs `yarn tsx scripts/update-app-upgrade-config.ts --tag <tag> --config ../cs/app-upgrade-config.json` to regenerate the config directly inside the `cs-releases` working tree.
|
||
3. If the file changed, it opens a PR against `cs-releases` via `peter-evans/create-pull-request`, with the generated diff limited to `app-upgrade-config.json`.
|
||
|
||
You can run the same script locally via `yarn update:upgrade-config --tag v2.1.6 --config ../cs/app-upgrade-config.json` (add `--dry-run` to preview) to reproduce or debug whatever the workflow does. Passing `--skip-release-checks` along with `--dry-run` lets you bypass the release-page existence check (useful when the GitHub/GitCode pages aren’t published yet). Running without `--config` continues to update the copy in your current working directory (main branch) for documentation purposes.
|
||
|
||
## Version Matching Logic
|
||
|
||
### Algorithm Flow
|
||
|
||
1. Get user's current version (`currentVersion`) and requested channel (`requestedChannel`)
|
||
2. Get all version numbers from configuration file, sort in descending order by semantic versioning
|
||
3. Iterate through the sorted version list:
|
||
- Check if `currentVersion >= minCompatibleVersion`
|
||
- Check if the requested `channel` exists and is not `null`
|
||
- If conditions are met, return the channel configuration
|
||
4. If no matching version is found, return `null`
|
||
|
||
### Pseudocode Implementation
|
||
|
||
```typescript
|
||
function findCompatibleVersion(
|
||
currentVersion: string,
|
||
requestedChannel: UpgradeChannel,
|
||
config: UpdateConfig
|
||
): ChannelConfig | null {
|
||
// Get all version numbers and sort in descending order
|
||
const versions = Object.keys(config.versions).sort(semver.rcompare)
|
||
|
||
for (const versionKey of versions) {
|
||
const versionConfig = config.versions[versionKey]
|
||
const channelConfig = versionConfig.channels[requestedChannel]
|
||
|
||
// Check version compatibility and channel availability
|
||
if (
|
||
semver.gte(currentVersion, versionConfig.minCompatibleVersion) &&
|
||
channelConfig !== null
|
||
) {
|
||
return channelConfig
|
||
}
|
||
}
|
||
|
||
return null // No compatible version found
|
||
}
|
||
```
|
||
|
||
## Upgrade Path Examples
|
||
|
||
### Scenario 1: v1.6.5 User Upgrade (Below 1.7)
|
||
|
||
- **Current Version**: 1.6.5
|
||
- **Requested Channel**: latest
|
||
- **Match Result**: 1.7.0
|
||
- **Reason**: 1.6.5 >= 0.0.0 (satisfies 1.7.0's minCompatibleVersion), but doesn't satisfy 2.0.0's minCompatibleVersion (1.7.0)
|
||
- **Action**: Prompt user to upgrade to 1.7.0, which is the required intermediate version for v2.x upgrade
|
||
|
||
### Scenario 2: v1.6.5 User Requests rc/beta
|
||
|
||
- **Current Version**: 1.6.5
|
||
- **Requested Channel**: rc or beta
|
||
- **Match Result**: 1.7.0 (latest)
|
||
- **Reason**: 1.7.0 version doesn't provide rc/beta channels (values are null)
|
||
- **Action**: Upgrade to 1.7.0 stable version
|
||
|
||
### Scenario 3: v1.7.0 User Upgrades to Latest
|
||
|
||
- **Current Version**: 1.7.0
|
||
- **Requested Channel**: latest
|
||
- **Match Result**: 2.0.0
|
||
- **Reason**: 1.7.0 >= 1.7.0 (satisfies 2.0.0's minCompatibleVersion)
|
||
- **Action**: Directly upgrade to 2.0.0 (current latest stable version)
|
||
|
||
### Scenario 4: v1.7.2 User Upgrades to RC Version
|
||
|
||
- **Current Version**: 1.7.2
|
||
- **Requested Channel**: rc
|
||
- **Match Result**: 2.0.0-rc.1
|
||
- **Reason**: 1.7.2 >= 1.7.0 (satisfies 2.0.0's minCompatibleVersion), and rc channel exists
|
||
- **Action**: Upgrade to 2.0.0-rc.1
|
||
|
||
### Scenario 5: v1.7.0 User Upgrades to Beta Version
|
||
|
||
- **Current Version**: 1.7.0
|
||
- **Requested Channel**: beta
|
||
- **Match Result**: 2.0.0-beta.1
|
||
- **Reason**: 1.7.0 >= 1.7.0, and beta channel exists
|
||
- **Action**: Upgrade to 2.0.0-beta.1
|
||
|
||
### Scenario 6: v2.5.0 User Upgrade (Future)
|
||
|
||
Assuming v2.8.0 and v3.0.0 configurations have been added:
|
||
- **Current Version**: 2.5.0
|
||
- **Requested Channel**: latest
|
||
- **Match Result**: 2.8.0
|
||
- **Reason**: 2.5.0 >= 2.0.0 (satisfies 2.8.0's minCompatibleVersion), but doesn't satisfy 3.0.0's requirement
|
||
- **Action**: Prompt user to upgrade to 2.8.0, which is the required intermediate version for v3.x upgrade
|
||
|
||
## Code Changes
|
||
|
||
### Main Modifications
|
||
|
||
1. **New Methods**
|
||
- `_fetchUpdateConfig(ipCountry: string): Promise<UpdateConfig | null>` - Fetch configuration file based on IP
|
||
- `_findCompatibleChannel(currentVersion: string, channel: UpgradeChannel, config: UpdateConfig): ChannelConfig | null` - Find compatible channel configuration
|
||
|
||
2. **Modified Methods**
|
||
- `_getReleaseVersionFromGithub()` → Remove or refactor to `_getChannelFeedUrl()`
|
||
- `_setFeedUrl()` - Use new configuration system to replace existing logic
|
||
|
||
3. **New Type Definitions**
|
||
- `UpdateConfig`
|
||
- `VersionConfig`
|
||
- `ChannelConfig`
|
||
|
||
### Mirror Selection Logic
|
||
|
||
The client automatically selects the optimal mirror based on IP geolocation:
|
||
|
||
```typescript
|
||
private async _setFeedUrl() {
|
||
const currentVersion = app.getVersion()
|
||
const testPlan = configManager.getTestPlan()
|
||
const requestedChannel = testPlan ? this._getTestChannel() : UpgradeChannel.LATEST
|
||
|
||
// Determine mirror based on IP country
|
||
const ipCountry = await getIpCountry()
|
||
const mirror = ipCountry.toLowerCase() === 'cn' ? 'gitcode' : 'github'
|
||
|
||
// Fetch update config
|
||
const config = await this._fetchUpdateConfig(mirror)
|
||
|
||
if (config) {
|
||
const channelConfig = this._findCompatibleChannel(currentVersion, requestedChannel, config)
|
||
if (channelConfig) {
|
||
// Select feed URL from the corresponding mirror
|
||
const feedUrl = channelConfig.feedUrls[mirror]
|
||
this._setChannel(requestedChannel, feedUrl)
|
||
return
|
||
}
|
||
}
|
||
|
||
// Fallback logic
|
||
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
|
||
}
|
||
}
|
||
```
|
||
|
||
## Fallback and Error Handling Strategy
|
||
|
||
1. **Configuration file fetch failure**: Log error, return current version, don't offer updates
|
||
2. **No matching version**: Notify user that current version doesn't support automatic upgrade
|
||
3. **Network exception**: Cache last successfully fetched configuration (optional)
|
||
|
||
## GitHub Release Requirements
|
||
|
||
To support intermediate version upgrades, the following files need to be retained:
|
||
|
||
- **v1.7.0 release** and its latest*.yml files (as upgrade target for users below v1.7)
|
||
- Future intermediate versions (e.g., v2.8.0) need to retain corresponding release and latest*.yml files
|
||
- Complete installation packages for each version
|
||
|
||
### Currently Required Releases
|
||
|
||
| Version | Purpose | Must Retain |
|
||
|---------|---------|-------------|
|
||
| v1.7.0 | Upgrade target for users below 1.7 | ✅ Yes |
|
||
| v2.0.0-rc.1 | RC testing channel | ❌ Optional |
|
||
| v2.0.0-beta.1 | Beta testing channel | ❌ Optional |
|
||
| latest | Latest stable version (automatic) | ✅ Yes |
|
||
|
||
## Advantages
|
||
|
||
1. **Flexibility**: Supports arbitrarily complex upgrade paths
|
||
2. **Extensibility**: Adding new versions only requires adding new entries to the configuration file
|
||
3. **Maintainability**: Configuration is separated from code, allowing upgrade strategy adjustments without releasing new versions
|
||
4. **Multi-source support**: Automatically selects optimal configuration source based on geolocation
|
||
5. **Version control**: Enforces intermediate version upgrades, ensuring data migration and compatibility
|
||
|
||
## Future Extensions
|
||
|
||
- Support more granular version range control (e.g., `>=1.5.0 <1.8.0`)
|
||
- Support multi-step upgrade path hints (e.g., notify user needs 1.5 → 1.8 → 2.0)
|
||
- Support A/B testing and gradual rollout
|
||
- Support local caching and expiration strategy for configuration files
|