Support Maven mirrors in generated settings.xml

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 59df028a-0d1b-49cb-8126-ab26408663e4
This commit is contained in:
Bruno Borges
2026-10-16 09:39:48 -04:00
parent b843ebf9b9
commit df4305dee7
10 changed files with 445 additions and 9 deletions

View File

@@ -30,11 +30,20 @@ export interface MavenRepositorySettings {
prioritizeCentral: boolean;
}
export interface MavenMirror {
id: string;
url: string;
mirrorOf: string;
}
const MAVEN_REPOSITORY_ORIGIN = /^[A-Za-z][A-Za-z0-9+.-]*:\/\/[^/?#@]+\/?$/;
export async function configureAuthentication() {
const servers = getMavenServerSettings();
const repositorySettings = getMavenRepositorySettings();
const mirrors = parseMavenMirrors(
core.getMultilineInput(constants.INPUT_MVN_MIRRORS)
);
const settingsDirectory =
core.getInput(constants.INPUT_SETTINGS_PATH) ||
path.join(os.homedir(), constants.M2_DIR);
@@ -60,7 +69,8 @@ export async function configureAuthentication() {
settingsDirectory,
overwriteSettings,
gpgPassphraseEnvVar,
repositorySettings
repositorySettings,
mirrors
);
if (gpgPrivateKey) {
@@ -333,6 +343,52 @@ export function parseMavenRepositories(
return repositories;
}
// only exported for testing purposes
export function parseMavenMirrors(entries: string[]): MavenMirror[] {
const mirrors: MavenMirror[] = [];
const mirrorIds = new Set<string>();
entries.forEach((entry, index) => {
if (!entry.trim()) {
return;
}
const separator = entry.indexOf(':');
const remainder = entry.slice(separator + 1).trim();
// Consume the URL scheme before looking for the selector delimiter. Selector
// tokens may themselves contain colons (external:* and external:http:*).
const scheme = /^[A-Za-z][A-Za-z0-9+.-]*:/.exec(remainder)?.[0] ?? '';
const fields =
/^(.*?):((?:\s*!?external:(?:http:)?\*\s*|[^:,]*)(?:,(?:\s*!?external:(?:http:)?\*\s*|[^:,]*))*)$/.exec(
remainder.slice(scheme.length)
);
if (separator < 0 || !fields) {
throw new Error(
`Invalid mvn-mirrors entry at line ${index + 1}. Expected format: mirror-id:mirror-url:mirror-of`
);
}
const id = entry.slice(0, separator).trim();
const url = `${scheme}${fields[1]}`.trim();
const mirrorOf = fields[2].trim();
if (!id || !url || !mirrorOf) {
throw new Error(
`Invalid mvn-mirrors entry at line ${index + 1}. mirror-id, mirror URL, and mirror-of are required`
);
}
if (mirrorIds.has(id)) {
throw new Error(
`Duplicate mirror-id '${id}' in mvn-mirrors input at line ${index + 1}`
);
}
mirrorIds.add(id);
mirrors.push({id, url, mirrorOf});
});
return mirrors;
}
function generateRepositorySection(
customRepositories: MavenRepository[],
settings: MavenRepositorySettings,
@@ -388,7 +444,8 @@ export async function createAuthenticationSettings(
settingsDirectory: string,
overwriteSettings: boolean,
gpgPassphraseEnvVar: string | undefined = undefined,
repositorySettings: MavenRepositorySettings | undefined = undefined
repositorySettings: MavenRepositorySettings | undefined = undefined,
mirrors: MavenMirror[] = []
) {
core.info(
`Creating ${constants.MVN_SETTINGS_FILE} with server-id: ${servers.map(server => server.id).join(', ')}`
@@ -398,7 +455,7 @@ export async function createAuthenticationSettings(
await io.mkdirP(settingsDirectory);
await write(
settingsDirectory,
generate(servers, gpgPassphraseEnvVar, repositorySettings),
generate(servers, gpgPassphraseEnvVar, repositorySettings, mirrors),
overwriteSettings
);
}
@@ -407,7 +464,8 @@ export async function createAuthenticationSettings(
export function generate(
servers: MavenServerCredentials[],
gpgPassphraseEnvVar?: string | undefined,
repositorySettings?: MavenRepositorySettings | undefined
repositorySettings?: MavenRepositorySettings | undefined,
mirrors: MavenMirror[] = []
) {
// The maven-gpg-plugin reads the passphrase from the environment variable
// named by the `gpg.passphraseEnvName` property (default MAVEN_GPG_PASSPHRASE).
@@ -447,6 +505,20 @@ export function generate(
}
lines.push(' </servers>');
if (mirrors.length) {
lines.push(' <mirrors>');
for (const mirror of mirrors) {
lines.push(
' <mirror>',
` <id>${escapeXmlText(mirror.id)}</id>`,
` <url>${escapeXmlText(mirror.url)}</url>`,
` <mirrorOf>${escapeXmlText(mirror.mirrorOf)}</mirrorOf>`,
' </mirror>'
);
}
lines.push(' </mirrors>');
}
if (repositorySettings || includeGpgPassphraseProfile) {
lines.push(' <profiles>');
if (repositorySettings) {