/** 版本号解析 vX.Y.Z-dav-{client}-{build} */ export interface GcaVersion { major: number; minor: number; patch: number; client: 'desktop' | 'android' | 'cli'; build: number; raw: string; } export interface Manifest { version: string; builds: Record; } export interface ManifestBuild { tag: string; url: string; sha256: string; size: number; minVersion?: string; } const MANIFEST_URL = 'https://git.childish-ghost.com/LukeMackin/Yuzu-GCA/releases/download/manifest/manifest.json'; const VERSION_RE = /^v(\d+)\.(\d+)\.(\d+)-dav-(desktop|android|cli)-(\d+)$/; export function parseVersion(raw: string): GcaVersion | null { const m = raw.trim().match(VERSION_RE); if (!m) return null; return { major: parseInt(m[1], 10), minor: parseInt(m[2], 10), patch: parseInt(m[3], 10), client: m[4] as GcaVersion['client'], build: parseInt(m[5], 10), raw, }; } export function compareVersion(a: GcaVersion, b: GcaVersion): number { if (a.major !== b.major) return a.major - b.major; if (a.minor !== b.minor) return a.minor - b.minor; if (a.patch !== b.patch) return a.patch - b.patch; return a.build - b.build; } /** 调用方传入本地版本号,返回是否需要更新以及目标 manifest entry */ export async function checkUpdate( client: GcaVersion['client'], localVersion: GcaVersion, manifestUrl = MANIFEST_URL, ): Promise<{ shouldUpdate: boolean; build: ManifestBuild | null; remoteVersion: GcaVersion | null }> { try { const resp = await fetch(manifestUrl); if (!resp.ok) return { shouldUpdate: false, build: null, remoteVersion: null }; const manifest: Manifest = await resp.json(); const build = manifest.builds[client]; if (!build) return { shouldUpdate: false, build: null, remoteVersion: null }; const remoteVersion = parseVersion(build.tag); if (!remoteVersion) return { shouldUpdate: false, build: null, remoteVersion: null }; return { shouldUpdate: compareVersion(remoteVersion, localVersion) > 0, build, remoteVersion, }; } catch { return { shouldUpdate: false, build: null, remoteVersion: null }; } } /** SemVer 比较(用于 expo-updates manifest) */ export function semverGt(a: string, b: string): boolean { const [aM, am, aP] = a.split('.').map(Number); const [bM, bm, bP] = b.split('.').map(Number); if (aM !== bM) return aM > bM; if (am !== bm) return am > bm; return aP > bP; }