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 + 版本号体系
This commit is contained in:
82
packages/shared/src/updater/index.ts
Normal file
82
packages/shared/src/updater/index.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
/** 版本号解析 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;
|
||||
}
|
||||
Reference in New Issue
Block a user