Files
Yuzu-GCA/packages/shared/src/updater/index.ts
LukeMackin 9f8659c293 feat(android): OTA双轨更新 — shared版本号+Android Expo/PackageInstaller
- packages/shared: 版本号解析 vX.Y.Z-dav-{client}-{build} + manifest远程检查
- packages/client-android: Expo52 + expo-updates JS bundle OTA
- PackageInstallerModule.kt: APK静默安装原生模块(~60行Kotlin)
- monorepo: pnpm workspace + 版本号体系
2026-07-19 01:41:36 +08:00

83 lines
2.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/** 版本号解析 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<string, ManifestBuild>;
}
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;
}