mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 08:17:11 +00:00
fix(cliproxy): resolve plus release asset variants (#1523)
Use version-aware CLIProxy Plus asset names so current no-plugin releases install without breaking older fallback releases. Tag latest-version caches with their GitHub repo and ignore legacy or cross-repo cache entries. Closes #1522
This commit is contained in:
@@ -39,6 +39,7 @@ describe('version-checker stale cache fallback', () => {
|
||||
getVersionCachePath('plus'),
|
||||
JSON.stringify({
|
||||
latestVersion: '6.9.23-0',
|
||||
repo: 'kaitranntt/CLIProxyAPIPlus',
|
||||
checkedAt: Date.now() - VERSION_CACHE_DURATION_MS - 1_000,
|
||||
}),
|
||||
'utf8'
|
||||
@@ -56,6 +57,33 @@ describe('version-checker stale cache fallback', () => {
|
||||
expect(result.fromCache).toBe(true);
|
||||
});
|
||||
|
||||
it('ignores legacy or cross-repo latest-version caches', async () => {
|
||||
const { getVersionCachePath, writeInstalledVersion } = await import('../version-cache');
|
||||
const { checkForUpdates } = await import('../version-checker');
|
||||
const plusBinDir = path.join(tempHome, '.ccs', 'cliproxy', 'bin', 'plus');
|
||||
|
||||
fs.mkdirSync(plusBinDir, { recursive: true });
|
||||
writeInstalledVersion(plusBinDir, '6.6.80');
|
||||
for (const repo of [undefined, 'router-for-me/CLIProxyAPI']) {
|
||||
fs.writeFileSync(
|
||||
getVersionCachePath('plus'),
|
||||
JSON.stringify({
|
||||
latestVersion: '7.2.5',
|
||||
checkedAt: Date.now(),
|
||||
...(repo ? { repo } : {}),
|
||||
}),
|
||||
'utf8'
|
||||
);
|
||||
|
||||
const result = await checkForUpdates(plusBinDir, '6.6.80', false, 'plus', {
|
||||
fetchLatestVersionFn: async () => '7.1.68-2',
|
||||
});
|
||||
|
||||
expect(result.latestVersion).toBe('7.1.68-2');
|
||||
expect(result.fromCache).toBe(false);
|
||||
}
|
||||
});
|
||||
|
||||
it('uses a stale release-list cache when GitHub list lookup fails', async () => {
|
||||
const { getVersionListCachePath } = await import('../version-cache');
|
||||
const { VERSION_CACHE_DURATION_MS } = await import('../types');
|
||||
|
||||
@@ -83,11 +83,55 @@ const RELEASE_ARCH_MAP: Record<SupportedArch, SupportedArch> = {
|
||||
aarch64: 'aarch64',
|
||||
};
|
||||
|
||||
const PLUS_NO_PLUGIN_ASSET_MIN_VERSION = '7.1.68-0';
|
||||
const PLUS_AARCH64_ASSET_MIN_VERSION = '7.1.45-1';
|
||||
|
||||
export function mapNodeArchToReleaseArch(nodeArch: string): SupportedArch | undefined {
|
||||
const arch = ARCH_MAP[nodeArch];
|
||||
return arch ? RELEASE_ARCH_MAP[arch] : undefined;
|
||||
}
|
||||
|
||||
function parseVersionParts(version: string): [number, number, number, number] {
|
||||
const [coreVersion, forkRelease = '0'] = version.replace(/^v/, '').split('-', 2);
|
||||
const [major = 0, minor = 0, patch = 0] = coreVersion
|
||||
.split('.')
|
||||
.map((part) => parseInt(part, 10) || 0);
|
||||
return [major, minor, patch, parseInt(forkRelease, 10) || 0];
|
||||
}
|
||||
|
||||
function isAtLeastVersion(version: string, minimum: string): boolean {
|
||||
const left = parseVersionParts(version);
|
||||
const right = parseVersionParts(minimum);
|
||||
|
||||
for (let index = 0; index < left.length; index += 1) {
|
||||
if (left[index] > right[index]) return true;
|
||||
if (left[index] < right[index]) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function usesPlusNoPluginAsset(version: string, os: SupportedOS): boolean {
|
||||
return os !== 'windows' && isAtLeastVersion(version, PLUS_NO_PLUGIN_ASSET_MIN_VERSION);
|
||||
}
|
||||
|
||||
function getReleaseArchForBackend(
|
||||
backend: CLIProxyBackend,
|
||||
version: string,
|
||||
publicArch: SupportedArch,
|
||||
releaseArch: SupportedArch
|
||||
): SupportedArch {
|
||||
if (
|
||||
backend === 'plus' &&
|
||||
publicArch === 'arm64' &&
|
||||
!isAtLeastVersion(version, PLUS_AARCH64_ASSET_MIN_VERSION)
|
||||
) {
|
||||
return 'arm64';
|
||||
}
|
||||
|
||||
return releaseArch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect current platform and return binary info
|
||||
* @param version Optional version for binaryName (defaults to backend fallback)
|
||||
@@ -121,7 +165,9 @@ export function detectPlatform(
|
||||
const config = BACKEND_CONFIG[backend];
|
||||
const ver = version || config.fallbackVersion;
|
||||
const extension: ArchiveExtension = os === 'windows' ? 'zip' : 'tar.gz';
|
||||
const binaryName = `${config.binaryPrefix}_${ver}_${os}_${releaseArch}.${extension}`;
|
||||
const assetArch = getReleaseArchForBackend(backend, ver, arch, releaseArch);
|
||||
const assetVariant = backend === 'plus' && usesPlusNoPluginAsset(ver, os) ? '_no-plugin' : '';
|
||||
const binaryName = `${config.binaryPrefix}_${ver}_${os}_${assetArch}${assetVariant}.${extension}`;
|
||||
|
||||
return {
|
||||
os,
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
export interface VersionCache {
|
||||
latestVersion: string;
|
||||
checkedAt: number;
|
||||
repo: string;
|
||||
}
|
||||
|
||||
/** Update check result */
|
||||
@@ -39,9 +40,14 @@ export const GITHUB_REPOS = {
|
||||
plus: 'kaitranntt/CLIProxyAPIPlus',
|
||||
} as const;
|
||||
|
||||
/** Get GitHub repository for specific backend */
|
||||
export function getGitHubRepo(backend: 'original' | 'plus'): string {
|
||||
return GITHUB_REPOS[backend];
|
||||
}
|
||||
|
||||
/** Get GitHub API URLs for specific backend */
|
||||
export function getGitHubApiUrls(backend: 'original' | 'plus') {
|
||||
const repo = GITHUB_REPOS[backend];
|
||||
const repo = getGitHubRepo(backend);
|
||||
return {
|
||||
latestRelease: `https://api.github.com/repos/${repo}/releases/latest`,
|
||||
allReleases: `https://api.github.com/repos/${repo}/releases`,
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
VERSION_CACHE_DURATION_MS,
|
||||
VERSION_PIN_FILE,
|
||||
VersionListCache,
|
||||
getGitHubRepo,
|
||||
} from './types';
|
||||
import { DEFAULT_BACKEND } from '../binary/platform-detector';
|
||||
import type { CLIProxyBackend } from '../types';
|
||||
@@ -59,6 +60,10 @@ function readVersionCacheInternal(
|
||||
const content = fs.readFileSync(cachePath, 'utf8');
|
||||
const cache: VersionCache = JSON.parse(content);
|
||||
|
||||
if (cache.repo !== getGitHubRepo(backend)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if cache is still valid, unless caller explicitly allows stale fallback.
|
||||
if (allowExpired || Date.now() - cache.checkedAt < VERSION_CACHE_DURATION_MS) {
|
||||
return cache;
|
||||
@@ -82,6 +87,7 @@ export function writeVersionCache(
|
||||
const cache: VersionCache = {
|
||||
latestVersion: version,
|
||||
checkedAt: Date.now(),
|
||||
repo: getGitHubRepo(backend),
|
||||
};
|
||||
|
||||
try {
|
||||
|
||||
@@ -73,9 +73,50 @@ describe('Backend Selection', () => {
|
||||
assert(!info.binaryName.includes('CLIProxyAPIPlus'));
|
||||
});
|
||||
|
||||
it('generates correct binary name for plus backend', () => {
|
||||
const info = platformDetector.detectPlatform('6.6.51-0', 'plus');
|
||||
assert(info.binaryName.startsWith('CLIProxyAPIPlus_6.6.51-0_'));
|
||||
it('keeps old plus non-Windows archive names unsuffixed', () => {
|
||||
withMockedProcessPlatform('darwin', 'arm64', () => {
|
||||
assert.strictEqual(
|
||||
platformDetector.detectPlatform('6.9.45-0', 'plus').binaryName,
|
||||
'CLIProxyAPIPlus_6.9.45-0_darwin_arm64.tar.gz'
|
||||
);
|
||||
assert.strictEqual(
|
||||
platformDetector.detectPlatform('7.1.45-1', 'plus').binaryName,
|
||||
'CLIProxyAPIPlus_7.1.45-1_darwin_aarch64.tar.gz'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('generates no-plugin archive names for current plus backend on macOS and Linux', () => {
|
||||
withMockedProcessPlatform('darwin', 'arm64', () => {
|
||||
assert.strictEqual(
|
||||
platformDetector.detectPlatform('7.1.68-0', 'plus').binaryName,
|
||||
'CLIProxyAPIPlus_7.1.68-0_darwin_aarch64_no-plugin.tar.gz'
|
||||
);
|
||||
const info = platformDetector.detectPlatform('7.1.68-2', 'plus');
|
||||
assert.strictEqual(
|
||||
info.binaryName,
|
||||
'CLIProxyAPIPlus_7.1.68-2_darwin_aarch64_no-plugin.tar.gz'
|
||||
);
|
||||
assert.strictEqual(
|
||||
platformDetector.getDownloadUrl('7.1.68-2', 'plus'),
|
||||
'https://github.com/kaitranntt/CLIProxyAPIPlus/releases/download/v7.1.68-2/CLIProxyAPIPlus_7.1.68-2_darwin_aarch64_no-plugin.tar.gz'
|
||||
);
|
||||
});
|
||||
|
||||
withMockedProcessPlatform('linux', 'x64', () => {
|
||||
const info = platformDetector.detectPlatform('7.1.68-2', 'plus');
|
||||
assert.strictEqual(
|
||||
info.binaryName,
|
||||
'CLIProxyAPIPlus_7.1.68-2_linux_amd64_no-plugin.tar.gz'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('does not add no-plugin suffix to plus Windows archives', () => {
|
||||
withMockedProcessPlatform('win32', 'x64', () => {
|
||||
const info = platformDetector.detectPlatform('7.1.68-2', 'plus');
|
||||
assert.strictEqual(info.binaryName, 'CLIProxyAPIPlus_7.1.68-2_windows_amd64.zip');
|
||||
});
|
||||
});
|
||||
|
||||
it('uses original backend by default', () => {
|
||||
|
||||
Reference in New Issue
Block a user