Files
ccs/src/cliproxy/binary/version-cache.ts
T
kaitranntt 0a1cbcc612 fix(cliproxy): use backend-specific GitHub repos for version fetching
Root cause: fetchLatestVersion and fetchAllVersions were hardcoded to
CLIProxyAPIPlus repo, ignoring backend selection.

Changes:
- Add getGitHubApiUrls(backend) helper to types.ts
- Make fetchLatestVersion and fetchAllVersions backend-aware
- Make version list cache backend-specific (bin/{backend}/.version-list-cache.json)
- Update /api/cliproxy/versions route to use configured backend
- Allow version management even when proxy not running (ProxyStatusWidget)
- Show settings button in all states for easier access
2026-01-23 14:55:51 -05:00

232 lines
5.8 KiB
TypeScript

/**
* Version Cache Manager
* Handles reading/writing version cache to avoid excessive GitHub API calls.
*/
import * as fs from 'fs';
import * as path from 'path';
import { getBinDir } from '../config-generator';
import {
VersionCache,
VERSION_CACHE_DURATION_MS,
VERSION_PIN_FILE,
VersionListCache,
} from './types';
import { DEFAULT_BACKEND } from '../platform-detector';
import type { CLIProxyBackend } from '../types';
/**
* Get path to version cache file (backend-specific)
*/
export function getVersionCachePath(backend: CLIProxyBackend = DEFAULT_BACKEND): string {
return path.join(getBinDir(), backend, '.version-cache.json');
}
/**
* Get path to version pin file (backend-specific)
*/
export function getVersionPinPath(backend: CLIProxyBackend = DEFAULT_BACKEND): string {
return path.join(getBinDir(), backend, VERSION_PIN_FILE);
}
/**
* Read version cache if still valid (backend-specific)
*/
export function readVersionCache(backend: CLIProxyBackend = DEFAULT_BACKEND): VersionCache | null {
const cachePath = getVersionCachePath(backend);
if (!fs.existsSync(cachePath)) {
return null;
}
try {
const content = fs.readFileSync(cachePath, 'utf8');
const cache: VersionCache = JSON.parse(content);
// Check if cache is still valid
if (Date.now() - cache.checkedAt < VERSION_CACHE_DURATION_MS) {
return cache;
}
// Cache expired
return null;
} catch {
return null;
}
}
/**
* Write version to cache (backend-specific)
*/
export function writeVersionCache(
version: string,
backend: CLIProxyBackend = DEFAULT_BACKEND
): void {
const cachePath = getVersionCachePath(backend);
const cache: VersionCache = {
latestVersion: version,
checkedAt: Date.now(),
};
try {
fs.mkdirSync(path.dirname(cachePath), { recursive: true });
fs.writeFileSync(cachePath, JSON.stringify(cache), 'utf8');
} catch {
// Silent fail - caching is optional
}
}
/**
* Read installed version from .version file
*/
export function readInstalledVersion(binPath: string, fallbackVersion: string): string {
const versionFile = path.join(binPath, '.version');
if (fs.existsSync(versionFile)) {
try {
return fs.readFileSync(versionFile, 'utf8').trim();
} catch {
return fallbackVersion;
}
}
return fallbackVersion;
}
/**
* Write installed version to .version file
*/
export function writeInstalledVersion(binPath: string, version: string): void {
const versionFile = path.join(binPath, '.version');
try {
fs.writeFileSync(versionFile, version, 'utf8');
} catch {
// Silent fail - not critical
}
}
/**
* Get pinned version if one exists (backend-specific)
*/
export function getPinnedVersion(backend: CLIProxyBackend = DEFAULT_BACKEND): string | null {
const pinPath = getVersionPinPath(backend);
if (!fs.existsSync(pinPath)) {
return null;
}
try {
return fs.readFileSync(pinPath, 'utf8').trim();
} catch {
return null;
}
}
/**
* Save pinned version to persist user's explicit choice (backend-specific)
*/
export function savePinnedVersion(
version: string,
backend: CLIProxyBackend = DEFAULT_BACKEND
): void {
const pinPath = getVersionPinPath(backend);
try {
fs.mkdirSync(path.dirname(pinPath), { recursive: true });
fs.writeFileSync(pinPath, version, 'utf8');
} catch {
// Silent fail - not critical
}
}
/**
* Clear pinned version (unpin) - backend-specific
*/
export function clearPinnedVersion(backend: CLIProxyBackend = DEFAULT_BACKEND): void {
const pinPath = getVersionPinPath(backend);
if (fs.existsSync(pinPath)) {
try {
fs.unlinkSync(pinPath);
} catch {
// Silent fail
}
}
}
/**
* Check if a version is currently pinned (backend-specific)
*/
export function isVersionPinned(backend: CLIProxyBackend = DEFAULT_BACKEND): boolean {
return getPinnedVersion(backend) !== null;
}
/**
* Migrate old shared version pin to backend-specific location.
* Called once on first run after update.
*/
export function migrateVersionPin(backend: CLIProxyBackend): void {
const oldPinPath = path.join(getBinDir(), VERSION_PIN_FILE);
if (!fs.existsSync(oldPinPath)) return;
try {
const oldVersion = fs.readFileSync(oldPinPath, 'utf8').trim();
if (!oldVersion) return;
// Save to new backend-specific location
savePinnedVersion(oldVersion, backend);
// Delete old shared file
fs.unlinkSync(oldPinPath);
} catch {
// Silent fail - not critical
}
}
// ==================== Version List Cache ====================
const VERSION_LIST_CACHE_FILE = '.version-list-cache.json';
/**
* Get path to version list cache file (backend-specific)
*/
export function getVersionListCachePath(backend: CLIProxyBackend = DEFAULT_BACKEND): string {
return path.join(getBinDir(), backend, VERSION_LIST_CACHE_FILE);
}
/**
* Read version list cache if still valid (backend-specific)
*/
export function readVersionListCache(
backend: CLIProxyBackend = DEFAULT_BACKEND
): VersionListCache | null {
const cachePath = getVersionListCachePath(backend);
if (!fs.existsSync(cachePath)) {
return null;
}
try {
const content = fs.readFileSync(cachePath, 'utf8');
const cache: VersionListCache = JSON.parse(content);
// Check if cache is still valid (1 hour)
if (Date.now() - cache.checkedAt < VERSION_CACHE_DURATION_MS) {
return cache;
}
return null;
} catch {
return null;
}
}
/**
* Write version list to cache (backend-specific)
*/
export function writeVersionListCache(
cache: VersionListCache,
backend: CLIProxyBackend = DEFAULT_BACKEND
): void {
const cachePath = getVersionListCachePath(backend);
try {
fs.mkdirSync(path.dirname(cachePath), { recursive: true });
fs.writeFileSync(cachePath, JSON.stringify(cache), 'utf8');
} catch {
// Silent fail - caching is optional
}
}