mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-06-21 15:34:18 +08:00
fix(kernel): align isLatest with suffix-aware recommended version
buildSnapshot used versionGte against KERNEL_TARGET, which only compares the base date triplet and ignores -zh.N. Users on e.g. 2026.5.12-zh.1 were marked latest when the panel target was 2026.5.12-zh.2, hiding the sidebar upgrade hint and showing a false "latest" badge. Add recommendedIsNewer mirroring Rust config.rs logic; isLatest is now !recommendedIsNewer(target, current). Extend kernel tests accordingly. Co-authored-by: 晴天 <1186258278@users.noreply.github.com>
This commit is contained in:
@@ -26,7 +26,7 @@ const _listeners = []
|
||||
* @property {string|null} target 当前推荐目标版本
|
||||
* @property {string} floor 硬地板版本
|
||||
* @property {boolean} aboveFloor 是否 >= floor
|
||||
* @property {boolean} isLatest 是否 >= target
|
||||
* @property {boolean} isLatest 是否已达推荐目标(含 -zh.N 等后缀小版本,与后端 recommended 语义一致)
|
||||
* @property {Set<string>} features 当前启用的特性 id 集合
|
||||
* @property {string} versionLabel 人类可读的版本显示,例如 "2026.5.6 汉化"
|
||||
* @property {number|null} protocol 握手协商出的 Gateway WS 协议版本 (3 或 4),未握手为 null
|
||||
@@ -60,6 +60,54 @@ export function versionGte(a, b) {
|
||||
return true
|
||||
}
|
||||
|
||||
/** 与 Rust `base_version` 一致:在首个 `-` 处截断 */
|
||||
function baseVersionStr(v) {
|
||||
const s = String(v || '')
|
||||
const i = s.indexOf('-')
|
||||
return i === -1 ? s : s.slice(0, i)
|
||||
}
|
||||
|
||||
function hasVersionSuffix(v) {
|
||||
return String(v || '').includes('-')
|
||||
}
|
||||
|
||||
/** 提取字符串中所有数字段,对齐 Rust `parse_version` 对整串的拆分 */
|
||||
function allNumericParts(ver) {
|
||||
return String(ver || '')
|
||||
.split(/[^\d]+/)
|
||||
.filter(Boolean)
|
||||
.map(n => parseInt(n, 10))
|
||||
.filter(n => !Number.isNaN(n))
|
||||
}
|
||||
|
||||
function compareLex(a, b) {
|
||||
if (!a?.length || !b?.length) return 0
|
||||
const len = Math.max(a.length, b.length)
|
||||
for (let i = 0; i < len; i++) {
|
||||
const ai = a[i] || 0
|
||||
const bi = b[i] || 0
|
||||
if (ai < bi) return -1
|
||||
if (ai > bi) return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
/**
|
||||
* 与 `src-tauri/src/commands/config.rs` 中 `recommended_is_newer` 对齐:
|
||||
* 在基础 x.y.z 相同的情况下,继续比较 `-zh.N` / `-nightly.N` 等后缀中的数字序。
|
||||
*/
|
||||
export function recommendedIsNewer(recommended, current) {
|
||||
const r = parseVersion(baseVersionStr(recommended))
|
||||
const c = parseVersion(baseVersionStr(current))
|
||||
if (!r || !c) return false
|
||||
const baseCmp = compareLex(r, c)
|
||||
if (baseCmp !== 0) return baseCmp > 0
|
||||
if (hasVersionSuffix(recommended) && hasVersionSuffix(current)) {
|
||||
return compareLex(allNumericParts(recommended), allNumericParts(current)) > 0
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测版本是否属于汉化版
|
||||
*/
|
||||
@@ -100,7 +148,7 @@ export function buildSnapshot(engineId, version) {
|
||||
target,
|
||||
floor,
|
||||
aboveFloor: !!version && versionGte(version, floor),
|
||||
isLatest: !!version && !!target && versionGte(version, target),
|
||||
isLatest: !!version && !!target && !recommendedIsNewer(target, version),
|
||||
features,
|
||||
versionLabel: version
|
||||
? `${versionBase}${variant === 'chinese' ? ' 汉化' : ''}`
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
|
||||
import { parseVersion, versionGte, buildSnapshot } from '../src/lib/kernel.js'
|
||||
import { FEATURE_CATALOG, KERNEL_FLOOR } from '../src/lib/feature-catalog.js'
|
||||
import { parseVersion, versionGte, buildSnapshot, recommendedIsNewer } from '../src/lib/kernel.js'
|
||||
import { FEATURE_CATALOG, KERNEL_FLOOR, KERNEL_TARGET } from '../src/lib/feature-catalog.js'
|
||||
|
||||
// ============================================================================
|
||||
// parseVersion
|
||||
@@ -160,12 +160,30 @@ test('buildSnapshot edge case: version slightly below 5.6 feature requirement',
|
||||
})
|
||||
|
||||
test('buildSnapshot.isLatest works against KERNEL_TARGET', () => {
|
||||
const at_target = buildSnapshot('openclaw', '2026.5.6')
|
||||
const above_target = buildSnapshot('openclaw', '2026.6.0')
|
||||
const below_target = buildSnapshot('openclaw', '2026.5.5')
|
||||
assert.equal(at_target.isLatest, true)
|
||||
assert.equal(above_target.isLatest, true)
|
||||
assert.equal(below_target.isLatest, false)
|
||||
const officialT = KERNEL_TARGET.openclaw.official
|
||||
const atOfficial = buildSnapshot('openclaw', officialT)
|
||||
const aboveOfficial = buildSnapshot('openclaw', '2026.6.0')
|
||||
const belowOfficial = buildSnapshot('openclaw', '2026.5.5')
|
||||
assert.equal(atOfficial.isLatest, true)
|
||||
assert.equal(aboveOfficial.isLatest, true)
|
||||
assert.equal(belowOfficial.isLatest, false)
|
||||
|
||||
const chin = KERNEL_TARGET.openclaw.chinese
|
||||
if (chin && /-zh\.\d+/.test(chin)) {
|
||||
assert.equal(buildSnapshot('openclaw', chin).isLatest, true)
|
||||
const m = chin.match(/-zh\.(\d+)$/)
|
||||
if (m && Number(m[1]) > 1) {
|
||||
const prevZh = chin.replace(/-zh\.\d+$/, `-zh.${Number(m[1]) - 1}`)
|
||||
assert.equal(buildSnapshot('openclaw', prevZh).isLatest, false)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
test('recommendedIsNewer matches suffix patch ordering', () => {
|
||||
assert.equal(recommendedIsNewer('2026.5.12-zh.2', '2026.5.12-zh.1'), true)
|
||||
assert.equal(recommendedIsNewer('2026.5.12-zh.1', '2026.5.12-zh.2'), false)
|
||||
assert.equal(recommendedIsNewer('2026.5.12-zh.2', '2026.5.12-zh.2'), false)
|
||||
assert.equal(recommendedIsNewer('2026.5.13', '2026.5.12-zh.9'), true)
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
|
||||
Reference in New Issue
Block a user