import https from 'node:https' // Adoptium(Temurin) API 로부터 실제 배포되는 JDK 버전을 가져온다. 설치기가 이 벤더를 // 쓰므로 사이트의 "권장 JDK" 목록도 같은 소스에서 채운다(하드코딩 금지). 네트워크 // 실패 시에는 정적 폴백을 돌려줘 편집기가 항상 동작하도록 한다. const AVAILABLE_URL = 'https://api.adoptium.net/v3/info/available_releases' const CACHE_TTL_MS = 6 * 60 * 60 * 1000 // 6시간 const FALLBACK_MAJORS = [8, 11, 17, 21, 25] const FALLBACK_LTS = [8, 11, 17, 21, 25] export interface JdkAvailability { /** 배포되는 모든 메이저(예: 8,11,17,21,25,26). 내림차순. */ available: number[] /** LTS 메이저만(예: 8,11,17,21,25). 내림차순. */ lts: number[] mostRecentLts: number mostRecentFeature: number } export interface JdkReleaseNames { major: number /** 정식(GA) 릴리스 이름들(예: jdk-25.0.3+9). */ ga: string[] /** 개발 스냅샷(EA) 릴리스 이름들(예: jdk-25.0.4+6-ea-beta). */ ea: string[] } let availCache: { data: JdkAvailability; at: number } | null = null const releaseNamesCache = new Map() function fetchJson(url: string): Promise { return new Promise((resolve, reject) => { const request = https.get(url, { timeout: 8000, headers: { accept: 'application/json' } }, (response) => { const code = response.statusCode ?? 0 if (code !== 200) { response.resume() reject(new Error(`Adoptium HTTP ${code}`)) return } const chunks: Buffer[] = [] response.on('data', (c: Buffer) => chunks.push(c)) response.on('end', () => { try { resolve(JSON.parse(Buffer.concat(chunks).toString('utf8')) as T) } catch (error) { reject(error as Error) } }) }) request.on('error', reject) request.on('timeout', () => request.destroy(new Error('Adoptium timeout'))) }) } const sortDesc = (nums: number[]): number[] => [...new Set(nums)].sort((a, b) => b - a) export async function fetchJdkAvailability(): Promise { if (availCache && Date.now() - availCache.at < CACHE_TTL_MS) return availCache.data try { const raw = await fetchJson<{ available_releases?: number[] available_lts_releases?: number[] most_recent_lts?: number most_recent_feature_release?: number }>(AVAILABLE_URL) const available = sortDesc( (Array.isArray(raw.available_releases) ? raw.available_releases : FALLBACK_MAJORS).filter((n) => Number.isInteger(n)) ) const lts = sortDesc( (Array.isArray(raw.available_lts_releases) ? raw.available_lts_releases : FALLBACK_LTS).filter((n) => Number.isInteger(n)) ) const data: JdkAvailability = { available, lts, mostRecentLts: Number(raw.most_recent_lts) || (lts[0] ?? 21), mostRecentFeature: Number(raw.most_recent_feature_release) || (available[0] ?? 25) } availCache = { data, at: Date.now() } return data } catch { return ( availCache?.data ?? { available: sortDesc(FALLBACK_MAJORS), lts: sortDesc(FALLBACK_LTS), mostRecentLts: 25, mostRecentFeature: 25 } ) } } async function fetchReleaseNames(major: number, type: 'ga' | 'ea'): Promise { const url = `https://api.adoptium.net/v3/assets/feature_releases/${major}/${type}` + '?architecture=x64&image_type=jdk&os=windows&vendor=eclipse&page_size=10' try { const arr = await fetchJson>(url) if (!Array.isArray(arr)) return [] const names: string[] = [] for (const r of arr) { if (typeof r?.release_name === 'string' && !names.includes(r.release_name)) names.push(r.release_name) } return names } catch { return [] } } export async function fetchJdkReleaseNames(major: number): Promise { const cached = releaseNamesCache.get(major) if (cached && Date.now() - cached.at < CACHE_TTL_MS) return cached.data const [ga, ea] = await Promise.all([fetchReleaseNames(major, 'ga'), fetchReleaseNames(major, 'ea')]) const data: JdkReleaseNames = { major, ga, ea } releaseNamesCache.set(major, { data, at: Date.now() }) return data }