From 45ecd37ce5c89c2cfbee393bc7e323402081e5ed Mon Sep 17 00:00:00 2001 From: lzt404 <2596933790@qq.com> Date: Sat, 5 Sep 2026 21:58:13 +0800 Subject: [PATCH] fix(update): compare core versions using SemVer Only show update indicators when the remote core version is newer. Add coverage for prereleases, build metadata, invalid versions, and version boundaries. --- src/coreUpdate.tsx | 59 +++++++++++++++++++++++++++-- src/pages/VersionManagementPage.tsx | 2 +- tests/versionManagement.test.ts | 48 +++++++++++++++++++++++ 3 files changed, 105 insertions(+), 4 deletions(-) diff --git a/src/coreUpdate.tsx b/src/coreUpdate.tsx index 1962a31..5750544 100644 --- a/src/coreUpdate.tsx +++ b/src/coreUpdate.tsx @@ -35,13 +35,66 @@ function normalizeVersion(version: string | null | undefined) { return (version ?? '').trim().replace(/^v/i, ''); } +type SemanticVersion = { + core: [string, string, string]; + prerelease: string[] | null; +}; + +const semanticVersionPattern = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*))*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/; + +function parseSemanticVersion(version: string | null | undefined): SemanticVersion | null { + const match = semanticVersionPattern.exec(normalizeVersion(version)); + if (!match) return null; + + return { + core: [match[1], match[2], match[3]], + prerelease: match[4] ? match[4].split('.') : null, + }; +} + +function compareNumericIdentifier(left: string, right: string) { + if (left.length !== right.length) return left.length > right.length ? 1 : -1; + if (left === right) return 0; + return left > right ? 1 : -1; +} + +function compareSemanticVersions(left: SemanticVersion, right: SemanticVersion) { + for (let index = 0; index < left.core.length; index += 1) { + const difference = compareNumericIdentifier(left.core[index], right.core[index]); + if (difference !== 0) return difference; + } + + if (left.prerelease === null) return right.prerelease === null ? 0 : 1; + if (right.prerelease === null) return -1; + + const count = Math.max(left.prerelease.length, right.prerelease.length); + for (let index = 0; index < count; index += 1) { + const leftIdentifier = left.prerelease[index]; + const rightIdentifier = right.prerelease[index]; + if (leftIdentifier === undefined) return -1; + if (rightIdentifier === undefined) return 1; + if (leftIdentifier === rightIdentifier) continue; + + const leftIsNumeric = /^\d+$/.test(leftIdentifier); + const rightIsNumeric = /^\d+$/.test(rightIdentifier); + if (leftIsNumeric && rightIsNumeric) { + return compareNumericIdentifier(leftIdentifier, rightIdentifier); + } + if (leftIsNumeric) return -1; + if (rightIsNumeric) return 1; + return leftIdentifier > rightIdentifier ? 1 : -1; + } + + return 0; +} + export function coreUpdateAvailable( currentVersion: string | null | undefined, latestVersion: string | null | undefined, ) { - const current = normalizeVersion(currentVersion); - const latest = normalizeVersion(latestVersion); - return Boolean(current && latest && current !== latest); + const current = parseSemanticVersion(currentVersion); + const latest = parseSemanticVersion(latestVersion); + return Boolean(current && latest && compareSemanticVersions(latest, current) > 0); } export function requestLatestCore(force = false) { diff --git a/src/pages/VersionManagementPage.tsx b/src/pages/VersionManagementPage.tsx index 703588a..c0cc827 100644 --- a/src/pages/VersionManagementPage.tsx +++ b/src/pages/VersionManagementPage.tsx @@ -742,7 +742,7 @@ export function VersionManagementPage() {