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.
This commit is contained in:
1 parent
355d8057e6
commit
45ecd37ce5
3 files changed
+105
-4
No files matched your search
+56
-3
@@ -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) {
|
||||
|
||||
@@ -742,7 +742,7 @@ export function VersionManagementPage() {
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className={latestVersion && (!coreInstalled || currentVersion !== latestVersion) ? 'primary-button' : 'secondary-button'}
|
||||
className={latestVersion && (!coreInstalled || coreHasUpdate) ? 'primary-button' : 'secondary-button'}
|
||||
title={latestVersion ? t('kernel.versions.stopAndUpdateVersion', { version: latestVersion }) : t('kernel.versions.installLatest')}
|
||||
disabled={!latestVersion || busy}
|
||||
onClick={() => setConfirmUpdateOpen(true)}
|
||||
|
||||
Reference in new issue
Block a user