6.7.0 - Alpha12 - Scrapers 工具 (run-scrapers.mjs) 支持 KSP2 版本解析
This commit is contained in:
@@ -10,7 +10,7 @@ const WIKI_URL = 'https://en.wikipedia.org/wiki/Android_version_history';
|
||||
const ANDROID_VC_URL = 'https://developer.android.com/reference/android/os/Build.VERSION_CODES';
|
||||
|
||||
const norm = (/** @type {string} */ s) => (s ?? '').trim().replace(/\s+/g, ' ');
|
||||
const trimAndNormDash = (/** @type {string} */ s) => s.replace(/\s*[\u002d\u2013\u2014]\s*/g, '-');
|
||||
const trimAndNormDash = (/** @type {string} */ s) => s.replace(/\s*[\u002d\u2013\u2014](\s*N\/A)?\s*/ig, '-');
|
||||
const stripSupportPrefix = (/** @type {string} */ s) => s.replace(/^(un)?supported:\s*/ig, '');
|
||||
const handleCsvEmptyString = (/** @type {string} */ s) => /^-?$/.test(s) ? '""' : s;
|
||||
|
||||
@@ -36,7 +36,7 @@ function quoteIfNeeded(o) {
|
||||
*/
|
||||
function cleanCell(cell) {
|
||||
const cloned = cell.clone();
|
||||
cloned.find('style,script,sup.reference').remove();
|
||||
cloned.find('style,script,sup.reference,span.sr-only').remove();
|
||||
return cloned.text().trim().replace(/\s+/g, ' ');
|
||||
}
|
||||
|
||||
|
||||
@@ -127,8 +127,8 @@ function getCodenameMapLinesInfo(releases) {
|
||||
const codenameFromName = (name) => {
|
||||
// Capture "Android Studio <Codename> [Feature Drop]".
|
||||
// zh-CN: 捕获 "Android Studio <Codename> [Feature Drop]".
|
||||
const m = /Android Studio\s+(.+?)\s*(?:(\s+\d+\s+)?Feature Drop)?(?=\s*\|)/i.exec(name);
|
||||
return m ? m[1].trim() : null;
|
||||
const m = /Android\s+Studio\s+(.+?)(\s+\d+)?(\s+Feature Drop)?(?=\s*\|)/i.exec(name);
|
||||
return m?.[1]?.trim();
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,6 +9,55 @@ import { updateGradleLinesData } from './utils/update-helper.mjs';
|
||||
const URL = 'https://api.github.com/repos/google/ksp/releases';
|
||||
const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
|
||||
|
||||
/**
|
||||
* Legacy tag: "<kotlin>-<ksp>"
|
||||
* - kotlinKey = full kotlin part
|
||||
* - kspVer = last part
|
||||
*
|
||||
* KSP2 tag: "<ksp>"
|
||||
* - kotlinKey = "<major>.<minor>.Z" (and keep qualifier if exists)
|
||||
* - kspVer = full part
|
||||
*
|
||||
* @param {string} rawTag
|
||||
* @returns {{ kotlinKey: string, kspVer: string } | null}
|
||||
*/
|
||||
function splitKspTag(rawTag) {
|
||||
const tag = String(rawTag || '').trim();
|
||||
if (!tag) return null;
|
||||
|
||||
/**
|
||||
* @param {string} s
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const looksLikePlainSemver = (s) => /^\d+\.\d+\.\d+(?:\.\d+)?$/.test(s);
|
||||
|
||||
// Legacy: "<kotlin>-<kspSemver>"
|
||||
const parts = tag.split('-');
|
||||
const last = parts[parts.length - 1];
|
||||
if (parts.length >= 2 && looksLikePlainSemver(last)) {
|
||||
return {
|
||||
kotlinKey: parts.slice(0, -1).join('-'),
|
||||
kspVer: last,
|
||||
};
|
||||
}
|
||||
|
||||
// KSP2: tag itself is kspVer (e.g. "2.3.4", or maybe "2.4.0-RC1") ----
|
||||
// Parse base numbers from the first segment before '-'
|
||||
const base = tag.split('-', 1)[0];
|
||||
const m = /^(\d+)\.(\d+)\.(\d+)(?:\.\d+)?$/.exec(base);
|
||||
if (!m) return null;
|
||||
|
||||
const major = m[1];
|
||||
const minor = m[2];
|
||||
|
||||
// Keep qualifier if you want separate buckets for RC/Beta (optional)
|
||||
const qualifier = tag.includes('-') ? tag.slice(tag.indexOf('-') + 1).trim() : '';
|
||||
const kotlinKeyPrefix = `${major}.${minor}.Z`;
|
||||
const kotlinKey = qualifier ? `${kotlinKeyPrefix}-${qualifier}` : kotlinKeyPrefix;
|
||||
|
||||
return { kotlinKey, kspVer: tag };
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<KspRelease[]>}
|
||||
*/
|
||||
@@ -40,18 +89,10 @@ async function fetchKspReleases() {
|
||||
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) {
|
||||
const parsed = splitKspTag(tag);
|
||||
if (!parsed) continue;
|
||||
|
||||
if (parsed.kotlinKey === minToCheck) {
|
||||
reached = true;
|
||||
break;
|
||||
}
|
||||
@@ -76,7 +117,10 @@ async function getMinKotlinVersionToCheck() {
|
||||
* @returns {string[]}
|
||||
*/
|
||||
function parseReleases(releases) {
|
||||
if (!Array.isArray(releases)) return [];
|
||||
if (!Array.isArray(releases)) {
|
||||
console.warn('Failed to parse KSP releases');
|
||||
return [];
|
||||
}
|
||||
|
||||
// De-duplicate by Kotlin version, keep the latest one by release date.
|
||||
// zh-CN: 根据 Kotlin 版本去重, 保留发布时间最新的一条.
|
||||
@@ -85,11 +129,12 @@ function parseReleases(releases) {
|
||||
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 parsed = splitKspTag(rawVer);
|
||||
if (!parsed) {
|
||||
console.warn(`Failed to parse KSP tag "${rawVer}"`);
|
||||
continue;
|
||||
}
|
||||
const { kotlinKey: KotlinVer, kspVer } = parsed;
|
||||
|
||||
const d = new Date(r.publishedAt);
|
||||
if (Number.isNaN(d.getTime())) continue;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// scrape-and-inject-latest-gradle-wrapper.mjs
|
||||
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import { compareVersionStrings } from './utils/versioning.mjs';
|
||||
import { fetchGradleReleases } from './fetch-and-parse-gradle-releases.mjs';
|
||||
import { readPropertiesSync, writePropertiesSyncWithMap } from './utils/properties.mjs';
|
||||
@@ -10,27 +12,24 @@ const URL_PREFIX = 'https://services.gradle.org/distributions';
|
||||
/** @type {GradleReleaseConfig} */
|
||||
const config = {
|
||||
// @Hint by SuperMonster003 on Sep 10, 2025.
|
||||
// ! Limit major version to 8.x.x, to:
|
||||
// ! Limit major version to 8.Y.Z, to:
|
||||
// ! - Legacy IDE/tooling compatibility
|
||||
// ! - JDK 17 constraint; Gradle 9+ requires JDK 21
|
||||
// ! - Align with AGP/KGP/plugins matrix
|
||||
// ! - Mitigate deprecation removals
|
||||
// ! Set to null/remove to track latest major.
|
||||
// ! zh-CN:
|
||||
// ! 将主版本限制在 8.x.x, 以便:
|
||||
// ! 将主版本限制在 8.Y.Z, 以便:
|
||||
// ! - 兼容旧版 IDE/Tooling API
|
||||
// ! - 满足 JDK 17 运行时约束, 避免 Gradle 9+ 需 JDK 21
|
||||
// ! - 与 AGP/KGP/第三方插件版本矩阵匹配
|
||||
// ! - 降低弃用 API 移除导致的构建中断风险
|
||||
// ! 如需跟随最新主版本, 将 majorVersionLimit 设为 null 或移除此项.
|
||||
majorVersionLimit: '8.x.x',
|
||||
// # majorVersionLimit: '8.Y.Z',
|
||||
majorVersionLimit: null,
|
||||
format: 'bin',
|
||||
};
|
||||
|
||||
function isVersionLimited() {
|
||||
return !config.majorVersionLimit || String(config.majorVersionLimit).match(/([?x])(\.\1)*/i);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {GradleRelease[]} releases
|
||||
* @param {string} majorVersionLimit
|
||||
@@ -57,14 +56,24 @@ function getLatestGradleUrl(latestGradleVersion) {
|
||||
|
||||
/**
|
||||
* @param {Object} data
|
||||
* @param {string} [data.fileName='gradle-wrapper.properties']
|
||||
* @param {string} [data.dirName='../gradle/wrapper']
|
||||
* @param {string} data.latestGradleVersion
|
||||
* @param {string} data.latestGradleUrl
|
||||
* @param {string} data.majorVersionLimit
|
||||
* @param {string|number} data.rawMajorVersionLimit
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function updateGradleWrapperFileContent({ latestGradleVersion, latestGradleUrl, majorVersionLimit }) {
|
||||
const fileName = 'gradle-wrapper.properties';
|
||||
const path = '../gradle/wrapper/' + fileName;
|
||||
async function updateGradleWrapperFileContent(
|
||||
{
|
||||
fileName = 'gradle-wrapper.properties',
|
||||
dirName = '../gradle/wrapper',
|
||||
latestGradleVersion,
|
||||
latestGradleUrl,
|
||||
rawMajorVersionLimit,
|
||||
},
|
||||
) {
|
||||
const path = `${dirName.replace(/\/?$/, '/')}${fileName}`;
|
||||
const majorVersionLimit = parseMajorVersionLimit(rawMajorVersionLimit);
|
||||
const messages = [];
|
||||
const props = readPropertiesSync(path);
|
||||
let propUrl = props.get(KEY);
|
||||
@@ -81,11 +90,11 @@ async function updateGradleWrapperFileContent({ latestGradleVersion, latestGradl
|
||||
}
|
||||
|
||||
if (compareVersionStrings(propVersion, majorVersionLimit) > 0) {
|
||||
const suffix = ` (downgrade, limited by "${config.majorVersionLimit}")`;
|
||||
const suffix = ` (downgrade, limited by "${rawMajorVersionLimit}")`;
|
||||
messages.push(`-- ${propUrl}\n-> ${latestGradleUrl}${suffix}`);
|
||||
props.set(KEY, propUrl.replace(re, `$1${latestGradleVersion}$3`));
|
||||
} else if (compareVersionStrings(propVersion, latestGradleVersion) < 0) {
|
||||
const suffix = isVersionLimited() ? ` (upgrade, but limited by "${config.majorVersionLimit}")` : ` (upgrade)`;
|
||||
const suffix = ` (upgrade)`;
|
||||
messages.push(`-- ${propUrl}\n-> ${latestGradleUrl}${suffix}`);
|
||||
props.set(KEY, propUrl.replace(re, `$1${latestGradleVersion}$3`));
|
||||
}
|
||||
@@ -109,12 +118,16 @@ async function updateGradleWrapperFileContent({ latestGradleVersion, latestGradl
|
||||
}
|
||||
}
|
||||
|
||||
function parseMajorVersionLimit() {
|
||||
const majorVersionLimit = config.majorVersionLimit ?? 'x.x.x';
|
||||
/**
|
||||
* @param {string|number} rawMajorVersionLimit
|
||||
* @returns {string}
|
||||
*/
|
||||
function parseMajorVersionLimit(rawMajorVersionLimit) {
|
||||
const majorVersionLimit = rawMajorVersionLimit ?? 'x.y.z';
|
||||
const limits = String(majorVersionLimit).split('.');
|
||||
for (let i = 0; i < limits.length; i++) {
|
||||
let limit = limits[i];
|
||||
if (limit.match(/([?x])(\.\1)*/i)) {
|
||||
if (limit.match(/([xyz*?])(\.\1)*/i)) {
|
||||
limits[i] = limit = '9'.repeat(9);
|
||||
}
|
||||
if (isNaN(parseInt(limit))) {
|
||||
@@ -124,18 +137,55 @@ function parseMajorVersionLimit() {
|
||||
return limits.join('.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} wrappersPath
|
||||
* @returns {Array<{dirName: string, rawMajorVersionLimit: string}>}
|
||||
*/
|
||||
function listMatchingSubdirectories(wrappersPath) {
|
||||
const pattern = /^g(\d+)$/;
|
||||
const fullWrappersPath = path.resolve(wrappersPath);
|
||||
|
||||
if (!fs.existsSync(fullWrappersPath)) {
|
||||
console.warn(`Wrappers directory not found: ${fullWrappersPath}`);
|
||||
return [];
|
||||
}
|
||||
|
||||
const results = [];
|
||||
const entries = fs.readdirSync(fullWrappersPath, { withFileTypes: true });
|
||||
entries.forEach(entry => {
|
||||
if (!entry.isDirectory()) return;
|
||||
const matched = pattern.exec(entry.name);
|
||||
const version = matched?.[1] ?? null;
|
||||
if (version === null) return;
|
||||
results.push({
|
||||
dirName: `${wrappersPath.replace(/\/?$/, '/')}${entry.name}/gradle/wrapper`,
|
||||
rawMajorVersionLimit: `${version}.y.z`,
|
||||
});
|
||||
});
|
||||
return results;
|
||||
}
|
||||
|
||||
(async function main() {
|
||||
const releases = await fetchGradleReleases();
|
||||
if (!releases || releases.length === 0) {
|
||||
throw new Error('No Gradle releases found');
|
||||
}
|
||||
const majorVersionLimit = parseMajorVersionLimit();
|
||||
const latestRelease = getLatestRelease(releases, majorVersionLimit);
|
||||
const latestGradleVersion = latestRelease.versionName;
|
||||
const latestGradleUrl = getLatestGradleUrl(latestGradleVersion);
|
||||
await updateGradleWrapperFileContent({
|
||||
latestGradleVersion, latestGradleUrl, majorVersionLimit,
|
||||
});
|
||||
const wrapperDir = '../gradle/wrapper';
|
||||
const modernWrappersDir = '../gradle/wrappers';
|
||||
|
||||
const candidates = [ {
|
||||
dirName: wrapperDir,
|
||||
rawMajorVersionLimit: config.majorVersionLimit,
|
||||
} ].concat(listMatchingSubdirectories(modernWrappersDir));
|
||||
|
||||
for (const { dirName, rawMajorVersionLimit } of candidates) {
|
||||
const latestRelease = getLatestRelease(releases, parseMajorVersionLimit(rawMajorVersionLimit));
|
||||
const latestGradleVersion = latestRelease.versionName;
|
||||
const latestGradleUrl = getLatestGradleUrl(latestGradleVersion);
|
||||
await updateGradleWrapperFileContent({
|
||||
dirName, latestGradleVersion, latestGradleUrl, rawMajorVersionLimit,
|
||||
});
|
||||
}
|
||||
})().catch(err => {
|
||||
console.error('Failed to scrape or inject latest Gradle wrapper:', err);
|
||||
process.exit(1);
|
||||
|
||||
@@ -48,7 +48,7 @@ function updateCsv(data) {
|
||||
* @param {object} options
|
||||
* @param {boolean} [options.showIndex=false]
|
||||
* @param {{ mode: 'all' | 'not-all-numbers' | 'none', style: 'single' | 'double' }} [options.quote]
|
||||
* @param {{ key: string, compare: (a: AndroidReleaseCsv, b: AndroidReleaseCsv) => number } | { key: string, direction: 'asc' | 'desc' }} [options.sortBy]
|
||||
* @param {{ key: string, compare?: (a: AndroidReleaseCsv, b: AndroidReleaseCsv) => number, direction?: 'asc' | 'desc' }} [options.sortBy]
|
||||
* @param {{ [key: string]: string }} [options.headerMap]
|
||||
* @param {'lower' | 'upper' | 'snake' | 'kebab' | 'title' | 'camel' | 'pascal' | null} [options.headerCase]
|
||||
* @param {string[]} [options.headers]
|
||||
|
||||
Reference in New Issue
Block a user