// scrape-and-inject-ksp-releases-map.mjs // after: [ scrape-and-inject-gradle-kotlin-compatibility-map.mjs ] import { httpFetch } from './utils/fetch.mjs'; import { readPropertiesSync } from './utils/properties.mjs'; import { toUpdatedStamp } from './utils/date.mjs'; import { updateGradleLinesData } from './utils/update-helper.mjs'; const URL = 'https://api.github.com/repos/google/ksp/releases'; const GITHUB_TOKEN = process.env.GITHUB_TOKEN; /** * @returns {Promise} */ async function fetchKspReleases() { /** @type {import('http').OutgoingHttpHeaders} */ const headers = { 'accept': 'application/vnd.github+json', 'user-agent': 'node', ...(GITHUB_TOKEN ? { 'authorization': `Bearer ${GITHUB_TOKEN}` } : {}), }; const minToCheck = await getMinKotlinVersionToCheck(); const out = []; let page = 1; let reached = false; while (!reached) { const query = { per_page: 100, page }; /** @type {ReleasesData} */ const releases = await httpFetch(URL, { query, headers }); if (!Array.isArray(releases) || releases.length === 0) break; for (const release of releases) { const tag = String(release.tag_name || '').trim(); out.push({ version: tag, name: release.name, publishedAt: release.published_at, }); const parts = tag.split('-'); if (parts.length < 2) continue; /** * @example string * "2.2.20-2.0.3" -> "2.2.20" * "1.9.10-1.0.13" -> "1.9.10" * "2.2.20-RC2-2.0.2" -> "2.2.20-RC2" * "1.9.20-RC-1.0.13" -> "1.9.20-RC" * @type {string} */ const kotlinVer = parts.slice(0, -1).join('-'); if (kotlinVer === minToCheck) { reached = true; break; } } if (reached) break; page += 1; } return out; } async function getMinKotlinVersionToCheck() { const minGradle = readPropertiesSync('../version.properties').get('MIN_SUPPORTED_GRADLE_VERSION'); const mapElement = readPropertiesSync('../gradle/data/gradle-kotlin-compat.properties').get(minGradle); if (mapElement) return mapElement; throw new Error(`Failed to find the minimum Kotlin version to check in settings.gradle.kts`); } /** * @param {KspRelease[]} releases * @returns {string[]} */ function parseReleases(releases) { if (!Array.isArray(releases)) return []; // De-duplicate by Kotlin version, keep the latest one by release date. // zh-CN: 根据 Kotlin 版本去重, 保留发布时间最新的一条. /** @type {Map} */ const latestByKotlin = new Map(); for (const r of releases) { const rawVer = String(r.version || '').trim(); const parts = rawVer.split('-'); if (parts.length < 2) continue; const kspVer = parts.pop(); const KotlinVer = parts.join('-'); const d = new Date(r.publishedAt); if (Number.isNaN(d.getTime())) continue; const prev = latestByKotlin.get(KotlinVer); if (!prev || d > prev.date) { latestByKotlin.set(KotlinVer, { kspVer, date: d }); } } /** * Parse Kotlin version for sorting.
* zh-CN: 解析 Kotlin 版本用于排序. * * @param {string} kotlinVer * @returns {{ baseNums: number[], rank: number, qName: string, qNum: number }} */ function parseKotlinVer(kotlinVer) { const [ base, qualifierRaw = '' ] = kotlinVer.split('-', 2); const baseNums = base.split('.').map((n) => parseInt(String(n), 10) || 0); let qName = ''; let qNum = 0; if (qualifierRaw) { const m = /^([A-Za-z]+)(\d+)?$/i.exec(qualifierRaw.trim()); if (m) { qName = m[1].toUpperCase(); qNum = m[2] ? parseInt(m[2], 10) : 0; } else { qName = qualifierRaw.toUpperCase(); } } const rankMap = { '': 3, RC: 2, BETA: 1 }; const rank = Object.prototype.hasOwnProperty.call(rankMap, qName) ? rankMap[qName] : 0; return { baseNums, rank, qName, qNum }; } /** * @param {number[]} aNums * @param {number[]} bNums * @returns {number} */ function cmpBaseDesc(aNums, bNums) { const len = Math.max(aNums.length, bNums.length); for (let i = 0; i < len; i++) { const av = aNums[i] ?? 0; const bv = bNums[i] ?? 0; if (av !== bv) return bv - av; } return 0; } return Array.from(latestByKotlin.entries()) .map(([ kotlinVer, v ]) => { const parsed = parseKotlinVer(kotlinVer); return { kotlinVer, kspVer: v.kspVer, date: v.date, parsed }; }) .sort((a, b) => { const c = cmpBaseDesc(a.parsed.baseNums, b.parsed.baseNums); if (c !== 0) return c; if (a.parsed.rank !== b.parsed.rank) return b.parsed.rank - a.parsed.rank; if (a.parsed.qNum !== b.parsed.qNum) return b.parsed.qNum - a.parsed.qNum; if (a.parsed.qName !== b.parsed.qName) return a.parsed.qName < b.parsed.qName ? 1 : -1; return b.date.getTime() - a.date.getTime(); }) .map(({ kotlinVer, kspVer, date }) => { return [ `#${toUpdatedStamp(date)}`, `${kotlinVer}=${kspVer}` ]; }) .flat(1); } (async function main() { const releases = await fetchKspReleases(); const lines = parseReleases(releases); await updateGradleLinesData('ksp-releases', lines, { label: 'KSP releases version map', }); })().catch((err) => { console.error('Failed to fetch KSP releases:', err); process.exitCode = 1; });