import type { WandConfig } from "./types.js"; export type ApkUpdateChannel = "stable" | "beta"; export interface LocalDistributionAsset { fileName: string; filePath: string; size: number; updatedAt: string; version: string | null; downloadUrl: string; source: "local"; } export interface ResolvedDistributionAsset { version: string; downloadUrl: string; fileName: string; size: number; source: "local" | "github"; releaseNotes?: string; /** 本地文件哈希或 GitHub Release asset.digest;客户端下载后校验。 */ sha256?: string; } export interface GitHubApkDownload { remoteUrl: string; fileName: string; size: number; sha256?: string; } export interface DistributionSettings { androidApk: Record; macosDmg: Record; iosIpa: Record; } export interface DistributionManagerOptions { configDir: string; configPath: string; config: WandConfig; repositoryUrl: string; fetch?: typeof fetch; now?: () => number; } /** * GitHub Release 正文还包含 Android/macOS/iOS 的安装指引;它们属于发布页, * 不该出现在 Android 的更新弹窗。保留分隔线前的变更摘要,并兼容旧版正文。 */ export declare function extractUpdateSummary(releaseBody: string): string; /** * GitHub Release 文件名带 semver build metadata(`wand-v4.48.0+202608232136.apk`)。 * `+` 在 content URI / 部分 OEM 安装器里会被当成空格,下载成功后无法拉起安装。 * 落盘和对外 fileName 都改成 `-`,版本比较仍用原始 GitHub 文件名提取的 version。 */ export declare function sanitizeApkFileName(fileName: string): string; /** 解析 GitHub Release asset.digest(`sha256:`);格式不对返回 undefined。 */ export declare function parseGithubDigest(digest: string | undefined): string | undefined; export declare function githubApkProxyPath(channel?: ApkUpdateChannel): string; export declare class DistributionManager { private readonly options; private readonly fetchImpl; private readonly now; private readonly githubCache; constructor(options: DistributionManagerOptions); resolveLatestApk(channel: ApkUpdateChannel): Promise; /** * 给 `/android/download?source=github` 用:原始 GitHub URL 只留在服务端, * 客户端只拿到同源代理地址。fileName 已去掉 `+`。 */ resolveGitHubApkDownload(): Promise; resolveAndroidDownload(channel?: ApkUpdateChannel): Promise; /** * 清理 Android 分发目录,只保留最新的一个 APK。在设备开始下载 Beta 包时 * 触发(见 /android/download 路由):debug 构建每次都带独立时间戳版本号, * 不清理会按构建次数无限堆积。 * * 永不删除两类文件: * - keepFileName:本次正在下发的文件(理论上就是最新的,防御性保留); * - config.android.currentApkFile 固定指向的文件(stable 通道可能仍在用)。 * * 「最新」的判定与 resolveLocalAsset 的选择规则一致(见 * compareLocalAssetCandidates)。返回删除的文件名与释放字节数;目录不存在、 * 只剩一个包等情形都是空操作。单个文件删除失败不阻断其余清理。 */ pruneAndroidApkDirectory(keepFileName?: string): Promise<{ deleted: string[]; freedBytes: number; }>; resolveLatestDmg(): Promise; resolveMacosDownload(): Promise; resolveLatestIpa(): Promise; resolveIosDownload(): Promise; getSettings(): Promise; private readonly sha256Cache; /** * 计算本地分发资产的 SHA-256(hex),供下载路由写入 X-APK-Sha256 响应头。 * 计算失败返回 null,调用方按无哈希处理。 */ computeAssetSha256(asset: LocalDistributionAsset): Promise; /** * 计算本地分发文件的 SHA-256(hex)。按 path + size + mtime 缓存:更新检查 * 可能被多个客户端频繁触发,避免每次都全量读盘。 */ private computeLocalFileSha256; private refreshConfig; private resolveLocalAsset; private readLocalAsset; private fetchGitHubAsset; private fetchGitHubReleaseAsset; private buildSettings; private publicAsset; }