- 하드코딩 [8,11,17,21,25] 대신 Adoptium(Temurin) API 에서 실제 배포 버전을 가져와 편집기 드롭다운을 채운다. 브라우저 CORS 차단이라 서버가 프록시: GET /op/jdk-versions (메이저 목록), GET /op/jdk-versions/:major (GA/EA 릴리스명). 6h 캐시 + 네트워크 실패 시 정적 폴백. - 편집기: 기본은 LTS(25/21/17/11/8)만 표시, "자세히 보기" 체크 시 비-LTS 메이저까지 펼치고 선택 메이저의 GA/EA 스냅샷 릴리스명을 함께 보여준다. - normalizeRecommendedJdk 를 고정 목록 대신 8~99 범위로 완화(동적 메이저 허용). - 버전 0.4.7→0.4.8. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
119 lines
4.2 KiB
TypeScript
119 lines
4.2 KiB
TypeScript
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<number, { data: JdkReleaseNames; at: number }>()
|
|
|
|
function fetchJson<T>(url: string): Promise<T> {
|
|
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<JdkAvailability> {
|
|
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<string[]> {
|
|
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<Array<{ release_name?: string }>>(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<JdkReleaseNames> {
|
|
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
|
|
}
|