Files
sencho/backend/src/utils/version-check.ts
T
SaelixCode 3da0aa6036 chore: migrate repository URLs from AnsoCode/Sencho to studio-saelix/sencho
Updates all hardcoded GitHub repository references across 21 files:
- package.json: repository URL, bugs URL, homepage, description, author
- CONTRIBUTING.md: bug report template URL
- SECURITY.md: advisory URL, cosign cert-identity regexp
- .github/CODEOWNERS: @AnsoCode -> @studio-saelix/maintainers
- .github/workflows/ci.yml: repositories scope (Sencho -> sencho), docs-sync git URL
- .github/workflows/cla.yml: path-to-document URL
- .github/workflows/docker-publish.yml: cosign verify comment
- frontend/**/*.tsx: issues and changelog links (3 components)
- frontend/public/.well-known/security.txt: Contact and Policy URLs
- security/vex/sencho.openvex.json: @id field
- docs/openapi.yaml: license URL
- docs/docs.json: navbar and footer GitHub links (5 instances)
- docs/security.mdx: advisory and SECURITY.md links
- docs/reference/verifying-images.mdx: repo link + cosign regexp + legacy identity note
- docs/reference/contact.mdx: issues, LICENSE, advisory, policy, CoC links
- docs/reference/security-advisories.mdx: releases link
- docs/operations/verifying-images.mdx: cosign regexps and VEX download URL (6 instances)
- docs/operations/upgrade.mdx: releases links (2 instances)
- backend/src/utils/version-check.ts: GitHub Releases API endpoint

CHANGELOG.md intentionally excluded (release-please managed).
Legacy cosign identity note added for pre-migration image verification.
2026-04-29 09:24:20 -04:00

77 lines
2.7 KiB
TypeScript

import semver from 'semver';
import { CacheService } from '../services/CacheService';
/**
* Fetches the latest Sencho release version from GitHub or Docker Hub.
* Extracted from index.ts so both the fleet endpoint and MonitorService
* can share the same lookup logic.
*/
async function fetchFromGitHub(): Promise<string | null> {
const res = await fetch('https://api.github.com/repos/studio-saelix/sencho/releases/latest', {
headers: { Accept: 'application/vnd.github+json', 'User-Agent': 'Sencho' },
signal: AbortSignal.timeout(10000),
});
if (!res.ok) return null;
const data = await res.json() as { tag_name?: string };
const tag = data.tag_name?.replace(/^v/, '') ?? null;
return tag && semver.valid(tag) ? tag : null;
}
async function fetchFromDockerHub(): Promise<string | null> {
const res = await fetch(
'https://hub.docker.com/v2/repositories/saelix/sencho/tags/?page_size=50&ordering=last_updated',
{ headers: { 'User-Agent': 'Sencho' }, signal: AbortSignal.timeout(10000) },
);
if (!res.ok) return null;
const data = await res.json() as { results?: { name: string }[] };
const tags = (data.results ?? [])
.map(t => t.name)
.filter(n => semver.valid(n));
if (tags.length === 0) return null;
tags.sort(semver.rcompare);
return tags[0];
}
export async function fetchLatestSenchoVersion(): Promise<string> {
try {
const gh = await fetchFromGitHub();
if (gh) return gh;
} catch (err) {
// GitHub API fails for private repos or rate limits; try Docker Hub
console.warn('[VersionCheck] GitHub fetch failed:', (err as Error).message);
}
try {
const hub = await fetchFromDockerHub();
if (hub) return hub;
} catch (err) {
console.warn('[VersionCheck] Docker Hub fetch failed:', (err as Error).message);
}
// Throw so CacheService falls back to a stale value if one exists,
// and so we do not poison the cache with null.
throw new Error('Both GitHub and Docker Hub version lookups failed');
}
/**
* Cached wrapper shared by the Fleet endpoint and MonitorService.
* CacheService provides TTL, inflight deduplication, and stale-on-error
* fallback so transient network blips do not cause user-visible gaps.
*/
const LATEST_VERSION_CACHE_KEY = 'latest-version';
const LATEST_VERSION_CACHE_TTL = 30 * 60 * 1000; // 30 minutes
export async function getLatestVersion(forceRefresh = false): Promise<string | null> {
if (forceRefresh) {
CacheService.getInstance().invalidate(LATEST_VERSION_CACHE_KEY);
}
try {
return await CacheService.getInstance().getOrFetch<string>(
LATEST_VERSION_CACHE_KEY,
LATEST_VERSION_CACHE_TTL,
fetchLatestSenchoVersion,
);
} catch {
return null;
}
}