"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MavenDatasource = exports.defaultRegistryUrls = void 0;
const tslib_1 = require("tslib");
const is_1 = tslib_1.__importDefault(require("@sindresorhus/is"));
const luxon_1 = require("luxon");
const global_1 = require("../../../config/global");
const logger_1 = require("../../../logger");
const packageCache = tslib_1.__importStar(require("../../../util/cache/package"));
const decorator_1 = require("../../../util/cache/package/decorator");
const regex_1 = require("../../../util/regex");
const url_1 = require("../../../util/url");
const maven_1 = tslib_1.__importDefault(require("../../versioning/maven"));
const mavenVersioning = tslib_1.__importStar(require("../../versioning/maven"));
const compare_1 = require("../../versioning/maven/compare");
const datasource_1 = require("../datasource");
const common_1 = require("./common");
const util_1 = require("./util");
function getLatestSuitableVersion(releases) {
    // istanbul ignore if
    if (!releases?.length) {
        return null;
    }
    const allVersions = releases.map(({ version }) => version);
    const stableVersions = allVersions.filter((x) => maven_1.default.isStable(x));
    const versions = stableVersions.length ? stableVersions : allVersions;
    return versions.reduce((latestVersion, version) => (0, compare_1.compare)(version, latestVersion) === 1
        ? version
        : /* istanbul ignore next: hard to test */ latestVersion);
}
function extractVersions(metadata) {
    const versions = metadata.descendantWithPath('versioning.versions');
    const elements = versions?.childrenNamed('version');
    if (!elements) {
        return [];
    }
    return elements.map((el) => el.val);
}
const mavenCentralHtmlVersionRegex = (0, regex_1.regEx)('^<a href="(?<version>[^"]+)/" title="(?:[^"]+)/">(?:[^"]+)/</a>\\s+(?<releaseTimestamp>\\d\\d\\d\\d-\\d\\d-\\d\\d \\d\\d:\\d\\d)\\s+-$', 'i');
exports.defaultRegistryUrls = [common_1.MAVEN_REPO];
class MavenDatasource extends datasource_1.Datasource {
    static id = 'maven';
    caching = true;
    defaultRegistryUrls = exports.defaultRegistryUrls;
    defaultVersioning = mavenVersioning.id;
    registryStrategy = 'merge';
    releaseTimestampSupport = true;
    releaseTimestampNote = 'The release timestamp is determined from the `Last-Modified` header or the `lastModified` field in the results.';
    sourceUrlSupport = 'package';
    sourceUrlNote = 'The source URL is determined from the `scm` tags in the results.';
    constructor(id = MavenDatasource.id) {
        super(id);
    }
    async fetchReleasesFromMetadata(dependency, repoUrl) {
        const metadataUrl = (0, util_1.getMavenUrl)(dependency, repoUrl, 'maven-metadata.xml');
        const cacheNamespace = 'datasource-maven:metadata-xml';
        const cacheKey = metadataUrl.toString();
        const cachedVersions = await packageCache.get(cacheNamespace, cacheKey);
        /* istanbul ignore if */
        if (cachedVersions) {
            return cachedVersions;
        }
        const { isCacheable, xml: mavenMetadata } = await (0, util_1.downloadMavenXml)(this.http, metadataUrl);
        if (!mavenMetadata) {
            return {};
        }
        const versions = extractVersions(mavenMetadata);
        const releaseMap = versions.reduce((acc, version) => ({ ...acc, [version]: null }), {});
        const cachePrivatePackages = global_1.GlobalConfig.get('cachePrivatePackages', false);
        if (cachePrivatePackages || isCacheable) {
            await packageCache.set(cacheNamespace, cacheKey, releaseMap, 30);
        }
        return releaseMap;
    }
    async addReleasesFromIndexPage(inputReleaseMap, dependency, repoUrl) {
        if (!repoUrl.startsWith(common_1.MAVEN_REPO)) {
            return inputReleaseMap;
        }
        const cacheNs = 'datasource-maven:index-html-releases';
        const cacheKey = `${repoUrl}${dependency.dependencyUrl}`;
        let workingReleaseMap = await packageCache.get(cacheNs, cacheKey);
        if (!workingReleaseMap) {
            workingReleaseMap = {};
            let retryEarlier = false;
            try {
                const indexUrl = (0, util_1.getMavenUrl)(dependency, repoUrl, '');
                const res = await (0, util_1.downloadHttpProtocol)(this.http, indexUrl);
                if (res) {
                    for (const line of res.body.split(regex_1.newlineRegex)) {
                        const match = line.trim().match(mavenCentralHtmlVersionRegex);
                        if (match) {
                            const { version, releaseTimestamp: timestamp } = match?.groups ?? /* istanbul ignore next: hard to test */ {};
                            if (version && timestamp) {
                                const date = luxon_1.DateTime.fromFormat(timestamp, 'yyyy-MM-dd HH:mm', {
                                    zone: 'UTC',
                                });
                                if (date.isValid) {
                                    const releaseTimestamp = date.toISO();
                                    workingReleaseMap[version] = { version, releaseTimestamp };
                                }
                            }
                        }
                    }
                }
            }
            catch (err) /* istanbul ignore next */ {
                retryEarlier = true;
                logger_1.logger.debug({ dependency, err }, 'Failed to get releases from package index page');
            }
            const cacheTTL = retryEarlier
                ? /* istanbul ignore next: hard to test */ 60
                : 24 * 60;
            await packageCache.set(cacheNs, cacheKey, workingReleaseMap, cacheTTL);
        }
        const releaseMap = { ...inputReleaseMap };
        for (const version of Object.keys(releaseMap)) {
            releaseMap[version] ||= workingReleaseMap[version] ?? null;
        }
        return releaseMap;
    }
    getReleasesFromMap(releaseMap) {
        const releases = Object.values(releaseMap).filter(is_1.default.truthy);
        if (releases.length) {
            return releases;
        }
        return Object.keys(releaseMap).map((version) => ({ version }));
    }
    async getReleases({ packageName, registryUrl, }) {
        // istanbul ignore if
        if (!registryUrl) {
            return null;
        }
        const dependency = (0, util_1.getDependencyParts)(packageName);
        const repoUrl = (0, url_1.ensureTrailingSlash)(registryUrl);
        logger_1.logger.debug(`Looking up ${dependency.display} in repository ${repoUrl}`);
        let releaseMap = await this.fetchReleasesFromMetadata(dependency, repoUrl);
        releaseMap = await this.addReleasesFromIndexPage(releaseMap, dependency, repoUrl);
        const releases = this.getReleasesFromMap(releaseMap);
        if (!releases?.length) {
            return null;
        }
        logger_1.logger.debug(`Found ${releases.length} new releases for ${dependency.display} in repository ${repoUrl}`);
        const latestSuitableVersion = getLatestSuitableVersion(releases);
        const dependencyInfo = latestSuitableVersion &&
            (await (0, util_1.getDependencyInfo)(this.http, dependency, repoUrl, latestSuitableVersion));
        const result = {
            ...dependency,
            ...dependencyInfo,
            releases,
        };
        if (!this.defaultRegistryUrls.includes(registryUrl)) {
            result.isPrivate = true;
        }
        return result;
    }
    async postprocessRelease({ packageName, registryUrl }, release) {
        if (!packageName || !registryUrl) {
            return release;
        }
        const dependency = (0, util_1.getDependencyParts)(packageName);
        const pomUrl = await (0, util_1.createUrlForDependencyPom)(this.http, release.versionOrig ?? release.version, dependency, registryUrl);
        const artifactUrl = (0, util_1.getMavenUrl)(dependency, registryUrl, pomUrl);
        const res = await (0, util_1.checkResource)(this.http, artifactUrl);
        if (res === 'not-found' || res === 'error') {
            return 'reject';
        }
        if (is_1.default.date(res)) {
            release.releaseTimestamp = res.toISOString();
        }
        return release;
    }
}
exports.MavenDatasource = MavenDatasource;
tslib_1.__decorate([
    (0, decorator_1.cache)({
        namespace: `datasource-maven`,
        key: ({ registryUrl, packageName }, { version, versionOrig }) => `postprocessRelease:${registryUrl}:${packageName}:${versionOrig ?? version}`,
        ttlMinutes: 24 * 60,
    })
], MavenDatasource.prototype, "postprocessRelease", null);
//# sourceMappingURL=index.js.map