mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-27 15:13:37 +08:00
fix: update check误判equal version为available update + about设置页满宽
- ElectronUpdaterService加currentVersion选项,checkForUpdates时比较feed版本 - 自包含的isNewerVersion():忽略prerelease/build后缀,等版本->非更新 - AboutSettings去掉max-w-lg/mx-auto约束,面板左右撑满 - 新增4个版本比较回归测试(等版本/旧版本/新版本场景) Tested: updater.test 14 pass, generalSettings/updateStore 67 pass, lint通过
This commit is contained in:
parent
78ca7070ad
commit
f65ae5b2b9
@ -109,6 +109,7 @@ function getUpdaterService() {
|
||||
},
|
||||
}, {
|
||||
updateConfigPath: !smokeUpdater && app.isPackaged ? path.join(process.resourcesPath, 'app-update.yml') : undefined,
|
||||
currentVersion: app.getVersion(),
|
||||
})
|
||||
return updaterService
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { ElectronUpdaterService, normalizeUpdateInfo, type ElectronUpdaterLike } from './updater'
|
||||
import { ElectronUpdaterService, normalizeUpdateInfo, isNewerVersion, type ElectronUpdaterLike } from './updater'
|
||||
import { mkdtempSync, rmSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
@ -51,6 +51,42 @@ describe('Electron updater service', () => {
|
||||
expect(normalizeUpdateInfo(undefined)).toBeNull()
|
||||
})
|
||||
|
||||
it('compares release versions, ignoring prerelease and build metadata', () => {
|
||||
expect(isNewerVersion('0.5.6', '0.5.5')).toBe(true)
|
||||
expect(isNewerVersion('1.0.0', '0.9.9')).toBe(true)
|
||||
expect(isNewerVersion('0.6.0', '0.5.9')).toBe(true)
|
||||
// Equal or older must not count as newer (the safe up-to-date direction).
|
||||
expect(isNewerVersion('0.5.5', '0.5.5')).toBe(false)
|
||||
expect(isNewerVersion('0.5.4', '0.5.5')).toBe(false)
|
||||
expect(isNewerVersion('v0.5.5', '0.5.5')).toBe(false)
|
||||
expect(isNewerVersion('0.5.5+abc123', '0.5.5')).toBe(false)
|
||||
expect(isNewerVersion('0.5.5-beta.1', '0.5.5')).toBe(false)
|
||||
})
|
||||
|
||||
it('treats a feed version equal to the running version as no update', async () => {
|
||||
updater.checkForUpdates.mockResolvedValue({ updateInfo: { version: '0.5.5', body: 'Same release' } })
|
||||
const service = new ElectronUpdaterService(updater, undefined, { currentVersion: '0.5.5' })
|
||||
|
||||
await expect(service.checkForUpdates()).resolves.toBeNull()
|
||||
await expect(service.downloadUpdate(() => {})).rejects.toThrow(
|
||||
'No Electron update is available to download',
|
||||
)
|
||||
})
|
||||
|
||||
it('treats a feed version older than the running version as no update', async () => {
|
||||
updater.checkForUpdates.mockResolvedValue({ updateInfo: { version: '0.5.4' } })
|
||||
const service = new ElectronUpdaterService(updater, undefined, { currentVersion: '0.5.5' })
|
||||
|
||||
await expect(service.checkForUpdates()).resolves.toBeNull()
|
||||
})
|
||||
|
||||
it('surfaces a feed version newer than the running version as an update', async () => {
|
||||
updater.checkForUpdates.mockResolvedValue({ updateInfo: { version: '0.5.6', body: 'New' } })
|
||||
const service = new ElectronUpdaterService(updater, undefined, { currentVersion: '0.5.5' })
|
||||
|
||||
await expect(service.checkForUpdates()).resolves.toEqual({ version: '0.5.6', body: 'New' })
|
||||
})
|
||||
|
||||
it('keeps electron-updater autoDownload disabled and emits store-compatible progress', async () => {
|
||||
updater.checkForUpdates.mockResolvedValue({ updateInfo: { version: '1.2.3', body: 'Fixes' } })
|
||||
updater.downloadUpdate.mockImplementation(async () => {
|
||||
|
||||
@ -36,6 +36,12 @@ export type ElectronUpdaterProxyController = {
|
||||
|
||||
export type ElectronUpdaterRuntimeOptions = {
|
||||
updateConfigPath?: string
|
||||
/**
|
||||
* Currently running app version. When provided, a feed entry whose version
|
||||
* is not strictly newer is treated as "no update", so an already-latest
|
||||
* release is not surfaced as an available update. Omit to skip comparison.
|
||||
*/
|
||||
currentVersion?: string
|
||||
}
|
||||
|
||||
export function normalizeUpdateInfo(info: ElectronUpdateInfo | undefined): ElectronUpdateMetadata | null {
|
||||
@ -49,6 +55,36 @@ export function normalizeUpdateInfo(info: ElectronUpdateInfo | undefined): Elect
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the numeric X.Y.Z core of a version string, ignoring any prerelease
|
||||
* (`-beta`) or build metadata (`+sha`) suffix. Missing segments default to 0.
|
||||
* Electron release versions are clean semver, so a self-contained comparator
|
||||
* avoids pulling a semver dependency into the electron main-process bundle.
|
||||
*/
|
||||
function parseVersionCore(version: string): [number, number, number] {
|
||||
const core = version.trim().replace(/^v/i, '').split(/[-+]/, 1)[0] ?? ''
|
||||
const parts = core.split('.')
|
||||
const toInt = (value: string | undefined) => {
|
||||
const parsed = Number.parseInt(value ?? '', 10)
|
||||
return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0
|
||||
}
|
||||
return [toInt(parts[0]), toInt(parts[1]), toInt(parts[2])]
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether `candidate` is a strictly newer release than `current`. Equal or
|
||||
* older versions return false so an "already latest" feed entry is not
|
||||
* mislabeled as an available update. Prerelease/build suffixes are ignored:
|
||||
* an equal numeric core counts as not newer (the safe up-to-date direction).
|
||||
*/
|
||||
export function isNewerVersion(candidate: string, current: string): boolean {
|
||||
const [aMajor, aMinor, aPatch] = parseVersionCore(candidate)
|
||||
const [bMajor, bMinor, bPatch] = parseVersionCore(current)
|
||||
if (aMajor !== bMajor) return aMajor > bMajor
|
||||
if (aMinor !== bMinor) return aMinor > bMinor
|
||||
return aPatch > bPatch
|
||||
}
|
||||
|
||||
function isMissingUpdateMetadataError(error: unknown): boolean {
|
||||
if (!error) return false
|
||||
const maybeError = typeof error === 'object'
|
||||
@ -73,6 +109,7 @@ export class ElectronUpdaterService {
|
||||
private readonly updater: ElectronUpdaterLike
|
||||
private readonly proxyController?: ElectronUpdaterProxyController
|
||||
private readonly updateConfigPath?: string
|
||||
private readonly currentVersion?: string
|
||||
private pendingUpdate: ElectronUpdateMetadata | null = null
|
||||
private downloaded = false
|
||||
private proxyKey: string | null = null
|
||||
@ -85,6 +122,7 @@ export class ElectronUpdaterService {
|
||||
this.updater = updater
|
||||
this.proxyController = proxyController
|
||||
this.updateConfigPath = runtimeOptions.updateConfigPath
|
||||
this.currentVersion = runtimeOptions.currentVersion
|
||||
this.updater.autoDownload = false
|
||||
this.updater.logger = null
|
||||
}
|
||||
@ -113,7 +151,11 @@ export class ElectronUpdaterService {
|
||||
if (!isMissingUpdateMetadataError(error)) throw error
|
||||
result = null
|
||||
}
|
||||
this.pendingUpdate = normalizeUpdateInfo(result?.updateInfo)
|
||||
const normalized = normalizeUpdateInfo(result?.updateInfo)
|
||||
this.pendingUpdate =
|
||||
normalized && this.currentVersion && !isNewerVersion(normalized.version, this.currentVersion)
|
||||
? null
|
||||
: normalized
|
||||
this.downloaded = false
|
||||
return this.pendingUpdate
|
||||
}
|
||||
|
||||
@ -3934,7 +3934,7 @@ function AboutSettings() {
|
||||
})()
|
||||
|
||||
return (
|
||||
<div className="w-full min-w-0 max-w-lg mx-auto flex flex-col items-center py-6">
|
||||
<div className="w-full min-w-0 flex flex-col items-center py-6">
|
||||
{/* Logo + App Name + Version */}
|
||||
<img src={publicAssetPath('app-icon.png')} alt="Claude Code Haha" className="w-20 h-20 mb-4" />
|
||||
<h1 className="text-xl font-bold text-[var(--color-text-primary)]">Claude Code Haha</h1>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user