feat(updater): nightly update channel (Android/Windows/macOS/Linux) (#4577)

This commit is contained in:
Huang Xin
2026-06-14 16:33:53 +08:00
committed by GitHub
parent bfb85c2f68
commit 57501cc520
23 changed files with 3120 additions and 26 deletions
+29 -1
View File
@@ -1,4 +1,4 @@
import { invoke } from '@tauri-apps/api/core';
import { invoke, Channel } from '@tauri-apps/api/core';
export interface CopyURIRequest {
uri: string;
@@ -263,3 +263,31 @@ export async function clearSyncPassphrase(): Promise<SyncPassphraseResponse> {
export async function isSyncKeychainAvailable(): Promise<SyncKeychainAvailableResponse> {
return invoke<SyncKeychainAvailableResponse>('plugin:native-bridge|is_sync_keychain_available');
}
// ── Nightly updater (main-app commands, no native-bridge prefix) ─────────
// `verify_update_signature` gates the custom install flows (portable /
// AppImage / Android); `install_nightly_update` drives the Tauri updater for
// the platform keys it natively installs (macOS / Windows-NSIS).
export async function verifyUpdateSignature(
path: string,
signature: string,
pubKey: string,
): Promise<boolean> {
return invoke<boolean>('verify_update_signature', { path, signature, pubKey });
}
export interface NightlyProgress {
event: 'progress' | 'finished';
downloaded: number;
contentLength: number;
}
export async function installNightlyUpdate(
endpoint: string,
onProgress?: (p: NightlyProgress) => void,
): Promise<void> {
const channel = new Channel<NightlyProgress>();
if (onProgress) channel.onmessage = onProgress;
await invoke<void>('install_nightly_update', { endpoint, channel });
}
+42
View File
@@ -1,5 +1,47 @@
import semver from 'semver';
import packageJson from '../../package.json';
export const getAppVersion = () => {
return packageJson.version;
};
export interface ParsedUpdateVersion {
base: string; // "X.Y.Z"
stamp: number | null; // YYYYMMDDHH, or null when not a nightly
isNightly: boolean;
}
// A nightly version is `<base>-<YYYYMMDDHH>`: a single, pure-10-digit
// prerelease identifier. Anything else (e.g. `-rc.1`, `-2026`) is treated as a
// non-nightly base version.
export const parseUpdateVersion = (version: string): ParsedUpdateVersion | null => {
const parsed = semver.parse(version);
if (!parsed) return null;
const base = `${parsed.major}.${parsed.minor}.${parsed.patch}`;
let stamp: number | null = null;
if (parsed.prerelease.length === 1) {
const id = String(parsed.prerelease[0]);
if (/^\d{10}$/.test(id)) {
stamp = Number(id);
}
}
return { base, stamp, isNightly: stamp !== null };
};
// Base-aware "is candidate newer than current?" used by both the nightly channel
// check and (mirrored in Rust) the Tauri updater version_comparator.
// Rule: higher X.Y.Z core wins; on equal core a nightly outranks the matching
// stable (it was built after it) but never the reverse; two nightlies compare by
// stamp.
export const isUpdateNewer = (candidate: string, current: string): boolean => {
const c = parseUpdateVersion(candidate);
const cur = parseUpdateVersion(current);
if (!c || !cur) return false;
if (c.base !== cur.base) {
return semver.compare(c.base, cur.base) > 0;
}
if (c.isNightly && !cur.isNightly) return true;
if (!c.isNightly && cur.isNightly) return false;
if (c.isNightly && cur.isNightly) return (c.stamp as number) > (cur.stamp as number);
return false;
};