Files
FileHost/server/utils/app-version.ts
2026-10-17 18:15:42 +08:00

101 lines
3.0 KiB
TypeScript

import { readFileSync, statSync } from 'node:fs'
import { join } from 'node:path'
const GITHUB_REPO = 'O96u/PicHost'
const RELEASES_LATEST_URL = `https://api.github.com/repos/${GITHUB_REPO}/releases/latest`
const CACHE_TTL_MS = 60 * 60 * 1000
export interface LatestReleaseInfo {
latestVersion: string
releaseUrl: string
}
let cachedRelease: { fetchedAt: number, value: LatestReleaseInfo | null } | null = null
let cachedPackageVersion: { mtimeMs: number, version: string } | null = null
/** Read package.json at request time so a hot-reloaded version update is visible without restart. */
export function resolveRuntimeAppVersion(fallback: string): string {
const path = join(process.cwd(), 'package.json')
try {
const mtimeMs = statSync(path).mtimeMs
if (cachedPackageVersion?.mtimeMs === mtimeMs) {
return cachedPackageVersion.version
}
const pkg = JSON.parse(readFileSync(path, 'utf8')) as { version?: unknown }
const version = typeof pkg.version === 'string' && pkg.version
? pkg.version
: fallback
cachedPackageVersion = { mtimeMs, version }
return version
} catch {
return fallback
}
}
export function normalizeVersionTag(tag: string): string {
return tag.trim().replace(/^v/i, '')
}
export function parseVersionParts(version: string): number[] {
return normalizeVersionTag(version)
.split('.')
.map(part => Number.parseInt(part, 10))
.map(part => (Number.isFinite(part) ? part : 0))
}
export function isVersionNewer(candidate: string, current: string): boolean {
const a = parseVersionParts(candidate)
const b = parseVersionParts(current)
const length = Math.max(a.length, b.length)
for (let i = 0; i < length; i++) {
const diff = (a[i] ?? 0) - (b[i] ?? 0)
if (diff !== 0) return diff > 0
}
return false
}
export async function fetchLatestGitHubRelease(force = false): Promise<LatestReleaseInfo | null> {
const now = Date.now()
if (!force && cachedRelease && now - cachedRelease.fetchedAt < CACHE_TTL_MS) {
return cachedRelease.value
}
try {
const response = await fetch(RELEASES_LATEST_URL, {
headers: {
'Accept': 'application/vnd.github+json',
'User-Agent': 'PicHost'
},
signal: AbortSignal.timeout(8000)
})
if (!response.ok) {
cachedRelease = { fetchedAt: now, value: null }
return null
}
const data = await response.json() as {
tag_name?: string
html_url?: string
}
const tag = typeof data.tag_name === 'string' ? data.tag_name : ''
const releaseUrl = typeof data.html_url === 'string' ? data.html_url : ''
if (!tag) {
cachedRelease = { fetchedAt: now, value: null }
return null
}
const value: LatestReleaseInfo = {
latestVersion: normalizeVersionTag(tag),
releaseUrl: releaseUrl || `https://github.com/${GITHUB_REPO}/releases/latest`
}
cachedRelease = { fetchedAt: now, value }
return value
} catch {
cachedRelease = { fetchedAt: now, value: null }
return null
}
}