cherry-studio/docs/technical/app-upgrade-config-en.md
beyondkmp 038d30831c
♻️ refactor: implement config-based update system with version compatibility control (#11147)
* ♻️ 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>
2025-11-14 17:49:40 +08:00

18 KiB
Raw Blame History

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 workflow. The workflow runs the 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)

{
  "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:

{
  "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

// 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 arent 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

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:

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