feat: add route for maven central (#20820)

* feat: add route for maven central

* fix: change sort api to Array#toSorted

* Update lib/routes/maven/central.ts

* fix: update url for maven namespace

* fix: use category to tag items

- remove custom query param
- add categories for route

* fix: ensure pubDate contained in data item

* fix: extract date regex into a constant for better readability

---------
This commit is contained in:
chris zheng 2026-01-08 01:13:27 +08:00 committed by GitHub
parent b043018a4b
commit 3f7f5848ba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 132 additions and 0 deletions

123
lib/routes/maven/central.ts Normal file
View File

@ -0,0 +1,123 @@
import { load } from 'cheerio';
import type { Route } from '@/types';
import ofetch from '@/utils/ofetch';
import { parseDate } from '@/utils/parse-date';
export const route: Route = {
path: '/central/:group/:artifact',
name: 'Maven Central Feed',
maintainers: ['chrisis58'],
description: 'Get the latest versions of a Maven artifact from Maven Central Repository.',
url: 'central.sonatype.com/',
categories: ['programming'],
parameters: {
group: {
description: 'The group ID of the Maven artifact (e.g., org.springframework)',
},
artifact: {
description: 'The artifact ID of the Maven artifact (e.g., spring-core)',
},
},
example: '/maven/central/org.springframework/spring-core',
radar: [
{
source: ['central.sonatype.com/artifact/:group/:artifact/:suffix', 'central.sonatype.com/artifact/:group/:artifact'],
target: '/maven/central/:group/:artifact',
},
],
features: {
requireConfig: false,
requirePuppeteer: false,
antiCrawler: false,
supportRadar: true,
supportBT: false,
supportPodcast: false,
supportScihub: false,
nsfw: false,
},
handler,
};
/**
* Regex to identify unstable versions
* Handles cases with delimiters: 1.0.0-rc1, 2.0.0.M2, 6.0.0_preview
* Handles cases without delimiters: 5.0.0beta2, 7.0.0canary
* Handles secondary versions: 1.0.0-M6.1
*/
const UNSTABLE_VERSION_REGEX = /[-_.]?(rc|m|snapshot|alpha|beta|preview|canary)[.\d]*$/i;
/**
* Regex to extract date in the format YYYY-MM-DD HH:mm (e.g., 2024-09-22 04:19)
*/
const DATE_REGEX = /(\d{4}-\d{2}-\d{2} \d{2}:\d{2})/;
async function handler(ctx) {
const group = ctx.req.param('group');
const artifact = ctx.req.param('artifact');
const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 15;
// (org.springframework, spring-core) -> org/springframework/spring-core
const identifier = `${group.replaceAll('.', '/')}/${artifact}`;
try {
const metadataUrl = `https://repo1.maven.org/maven2/${identifier}/maven-metadata.xml`;
const metaDataResponse = await ofetch(metadataUrl);
const $meta = load(metaDataResponse, { xmlMode: true });
const latestVersion = $meta('metadata > versioning > latest').text();
if (!latestVersion) {
throw new Error(`Not a valid component for ${group}:${artifact}: versions not found`);
}
} catch (error: any) {
if (error?.response?.status === 404) {
throw new Error(`Could not find component for ${group}:${artifact}: metadata not found`);
}
throw error;
}
const response = await ofetch(`https://repo1.maven.org/maven2/${identifier}/`);
const $ = load(response);
const items = $('pre#contents a')
.toArray()
.filter((element) => {
const href = $(element).attr('href') ?? '';
return href.endsWith('/') && href !== '../';
})
.map((element) => {
const href = $(element).attr('href') ?? '';
const version = href.replace('/', '');
const versionUrl = `https://central.sonatype.com/artifact/${group}/${artifact}/${version}`;
const rawText = element.nextSibling ? (element.nextSibling as any).nodeValue : '';
// Date format: 2024-09-22 04:19
const match = rawText.match(DATE_REGEX);
let pubDate: Date | undefined;
if (match) {
pubDate = parseDate(match[1], 'YYYY-MM-DD HH:mm');
}
const category = UNSTABLE_VERSION_REGEX.test(version) ? 'Unstable' : 'Stable';
return {
title: `Version ${version} of ${group}:${artifact}`,
link: versionUrl,
pubDate,
description: `Released version ${version} of ${group}:${artifact}.`,
category: [category],
};
})
.filter((item) => !!item && item.pubDate !== undefined)
.toSorted((a, b) => (b.pubDate?.getTime() ?? 0) - (a.pubDate?.getTime() ?? 0))
.slice(0, limit);
return {
title: `Maven Central - ${group}:${artifact}`,
link: `https://central.sonatype.com/artifact/${group}/${artifact}`,
description: `Versions of ${group}:${artifact} available on Maven Central.`,
item: items,
};
}

View File

@ -0,0 +1,9 @@
import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: 'Maven',
url: 'repo1.maven.org/maven2/',
categories: ['programming'],
description: "Apache Maven is a build tool for Java projects. Using a project object model (POM), Maven manages a project's compilation, testing, and documentation.",
lang: 'en',
};