"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.NugetV3Api = void 0;
const tslib_1 = require("tslib");
const is_1 = tslib_1.__importDefault(require("@sindresorhus/is"));
const extract_zip_1 = tslib_1.__importDefault(require("extract-zip"));
const semver_1 = tslib_1.__importDefault(require("semver"));
const upath_1 = tslib_1.__importDefault(require("upath"));
const xmldoc_1 = require("xmldoc");
const logger_1 = require("../../../logger");
const external_host_error_1 = require("../../../types/errors/external-host-error");
const packageCache = tslib_1.__importStar(require("../../../util/cache/package"));
const decorator_1 = require("../../../util/cache/package/decorator");
const fs = tslib_1.__importStar(require("../../../util/fs"));
const fs_1 = require("../../../util/fs");
const http_1 = require("../../../util/http");
const p = tslib_1.__importStar(require("../../../util/promises"));
const regex_1 = require("../../../util/regex");
const url_1 = require("../../../util/url");
const nuget_1 = require("../../versioning/nuget");
const common_1 = require("./common");
class NugetV3Api {
    static cacheNamespace = 'datasource-nuget-v3';
    async getResourceUrl(http, url, resourceType = 'RegistrationsBaseUrl') {
        // https://docs.microsoft.com/en-us/nuget/api/service-index
        const resultCacheKey = `${url}:${resourceType}`;
        const cachedResult = await packageCache.get(NugetV3Api.cacheNamespace, resultCacheKey);
        // istanbul ignore if
        if (cachedResult) {
            return cachedResult;
        }
        let servicesIndexRaw;
        try {
            const responseCacheKey = url;
            servicesIndexRaw = await packageCache.get(NugetV3Api.cacheNamespace, responseCacheKey);
            // istanbul ignore else: currently not testable
            if (!servicesIndexRaw) {
                servicesIndexRaw = (await http.getJson(url)).body;
                await packageCache.set(NugetV3Api.cacheNamespace, responseCacheKey, servicesIndexRaw, 3 * 24 * 60);
            }
            const services = servicesIndexRaw.resources
                .map(({ '@id': serviceId, '@type': t }) => ({
                serviceId,
                type: t?.split('/')?.shift(),
                version: t?.split('/')?.pop(),
            }))
                .filter(({ type, version }) => type === resourceType && semver_1.default.valid(version))
                .sort((x, y) => x.version && y.version
                ? semver_1.default.compare(x.version, y.version)
                : /* istanbul ignore next: hard to test */ 0);
            if (services.length === 0) {
                await packageCache.set(NugetV3Api.cacheNamespace, resultCacheKey, null, 60);
                logger_1.logger.debug({ url, servicesIndexRaw }, `no ${resourceType} services found`);
                return null;
            }
            const { serviceId, version } = services.pop();
            // istanbul ignore if
            if (resourceType === 'RegistrationsBaseUrl' &&
                version &&
                !version.startsWith('3.0.0-') &&
                !semver_1.default.satisfies(version, '^3.0.0')) {
                logger_1.logger.warn({ url, version }, `Nuget: Unknown version returned. Only v3 is supported`);
            }
            await packageCache.set(NugetV3Api.cacheNamespace, resultCacheKey, serviceId, 60);
            return serviceId;
        }
        catch (err) {
            // istanbul ignore if: not easy testable with nock
            if (err instanceof external_host_error_1.ExternalHostError) {
                throw err;
            }
            logger_1.logger.debug({ err, url, servicesIndexRaw }, `nuget registry failure: can't get ${resourceType}`);
            return null;
        }
    }
    async getCatalogEntry(http, catalogPage) {
        let items = catalogPage.items;
        if (!items) {
            const url = catalogPage['@id'];
            const catalogPageFull = await http.getJson(url);
            items = catalogPageFull.body.items;
        }
        return items.map(({ catalogEntry }) => catalogEntry);
    }
    async getReleases(http, registryUrl, feedUrl, pkgName) {
        const baseUrl = feedUrl.replace((0, regex_1.regEx)(/\/*$/), '');
        const url = `${baseUrl}/${pkgName.toLowerCase()}/index.json`;
        const packageRegistration = await http.getJson(url);
        const catalogPages = packageRegistration.body.items || [];
        const catalogPagesQueue = catalogPages.map((page) => () => this.getCatalogEntry(http, page));
        const catalogEntries = (await p.all(catalogPagesQueue))
            .flat()
            .sort((a, b) => (0, common_1.sortNugetVersions)(a.version, b.version));
        let homepage = null;
        let latestStable = null;
        let nupkgUrl = null;
        const releases = catalogEntries.map(({ version, published: releaseTimestamp, projectUrl, listed, packageContent, }) => {
            const release = { version: (0, common_1.removeBuildMeta)(version) };
            if (releaseTimestamp) {
                release.releaseTimestamp = releaseTimestamp;
            }
            if (nuget_1.api.isValid(version) && nuget_1.api.isStable(version)) {
                latestStable = (0, common_1.removeBuildMeta)(version);
                homepage = projectUrl ? (0, common_1.massageUrl)(projectUrl) : homepage;
                nupkgUrl = (0, common_1.massageUrl)(packageContent);
            }
            if (listed === false) {
                release.isDeprecated = true;
            }
            return release;
        });
        if (!releases.length) {
            return null;
        }
        // istanbul ignore next: only happens when no stable version exists
        if (latestStable === null && catalogPages.length) {
            const last = catalogEntries.pop();
            latestStable = (0, common_1.removeBuildMeta)(last.version);
            homepage ??= last.projectUrl ?? null;
            nupkgUrl ??= (0, common_1.massageUrl)(last.packageContent);
        }
        const dep = {
            releases,
        };
        try {
            const packageBaseAddress = await this.getResourceUrl(http, registryUrl, 'PackageBaseAddress');
            if (is_1.default.nonEmptyString(packageBaseAddress)) {
                const nuspecUrl = `${(0, url_1.ensureTrailingSlash)(packageBaseAddress)}${pkgName.toLowerCase()}/${
                // TODO: types (#22198)
                latestStable}/${pkgName.toLowerCase()}.nuspec`;
                const metaresult = await http.get(nuspecUrl);
                const nuspec = new xmldoc_1.XmlDocument(metaresult.body);
                const sourceUrl = nuspec.valueWithPath('metadata.repository@url');
                if (sourceUrl) {
                    dep.sourceUrl = (0, common_1.massageUrl)(sourceUrl);
                }
            }
            else if (nupkgUrl) {
                const sourceUrl = await this.getSourceUrlFromNupkg(http, registryUrl, pkgName, latestStable, nupkgUrl);
                if (sourceUrl) {
                    dep.sourceUrl = (0, common_1.massageUrl)(sourceUrl);
                    logger_1.logger.debug(`Determined sourceUrl ${sourceUrl} from ${nupkgUrl}`);
                }
            }
        }
        catch (err) {
            // istanbul ignore if: not easy testable with nock
            if (err instanceof external_host_error_1.ExternalHostError) {
                throw err;
            }
            // ignore / silence 404. Seen on proget, if remote connector is used and package is not yet cached
            if (err instanceof http_1.HttpError && err.response?.statusCode === 404) {
                logger_1.logger.debug({ registryUrl, pkgName, pkgVersion: latestStable }, `package manifest (.nuspec) not found`);
            }
            else {
                logger_1.logger.debug({ err, registryUrl, pkgName, pkgVersion: latestStable }, `Cannot obtain sourceUrl`);
            }
        }
        // istanbul ignore else: not easy testable
        if (homepage) {
            // only assign if not assigned
            dep.sourceUrl ??= homepage;
            dep.homepage ??= homepage;
        }
        return dep;
    }
    async getSourceUrlFromNupkg(http, _registryUrl, packageName, packageVersion, nupkgUrl) {
        // istanbul ignore if: experimental feature
        if (!process.env.RENOVATE_X_NUGET_DOWNLOAD_NUPKGS) {
            logger_1.logger.once.debug('RENOVATE_X_NUGET_DOWNLOAD_NUPKGS is not set');
            return null;
        }
        const cacheDir = await (0, fs_1.ensureCacheDir)('nuget');
        const nupkgFile = upath_1.default.join(cacheDir, `${packageName}.${packageVersion}.nupkg`);
        const nupkgContentsDir = upath_1.default.join(cacheDir, `${packageName}.${packageVersion}`);
        const readStream = http.stream(nupkgUrl);
        try {
            const writeStream = fs.createCacheWriteStream(nupkgFile);
            await fs.pipeline(readStream, writeStream);
            await (0, extract_zip_1.default)(nupkgFile, { dir: nupkgContentsDir });
            const nuspecFile = upath_1.default.join(nupkgContentsDir, `${packageName}.nuspec`);
            const nuspec = new xmldoc_1.XmlDocument(await fs.readCacheFile(nuspecFile, 'utf8'));
            return nuspec.valueWithPath('metadata.repository@url') ?? null;
        }
        finally {
            await fs.rmCache(nupkgFile);
            await fs.rmCache(nupkgContentsDir);
        }
    }
}
exports.NugetV3Api = NugetV3Api;
tslib_1.__decorate([
    (0, decorator_1.cache)({
        namespace: NugetV3Api.cacheNamespace,
        key: (_http, registryUrl, packageName, _packageVersion, _nupkgUrl) => `source-url:${registryUrl}:${packageName}`,
        ttlMinutes: 10080, // 1 week
    })
], NugetV3Api.prototype, "getSourceUrlFromNupkg", null);
//# sourceMappingURL=v3.js.map