"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PackagistDatasource = void 0;
const tslib_1 = require("tslib");
const is_1 = tslib_1.__importDefault(require("@sindresorhus/is"));
const zod_1 = require("zod");
const logger_1 = require("../../../logger");
const external_host_error_1 = require("../../../types/errors/external-host-error");
const decorator_1 = require("../../../util/cache/package/decorator");
const hostRules = tslib_1.__importStar(require("../../../util/host-rules"));
const p = tslib_1.__importStar(require("../../../util/promises"));
const url_1 = require("../../../util/url");
const composerVersioning = tslib_1.__importStar(require("../../versioning/composer"));
const datasource_1 = require("../datasource");
const schema_1 = require("./schema");
class PackagistDatasource extends datasource_1.Datasource {
    static id = 'packagist';
    constructor() {
        super(PackagistDatasource.id);
    }
    defaultRegistryUrls = ['https://packagist.org'];
    defaultVersioning = composerVersioning.id;
    registryStrategy = 'hunt';
    releaseTimestampSupport = true;
    releaseTimestampNote = 'The release timestamp is determined from the `time` field in the results.';
    // Note: this can be changed to 'release', as the source is present in each release but we remove it while processing
    sourceUrlSupport = 'package';
    sourceUrlNote = 'The source URL is determined from `source` field in the results.';
    // We calculate auth at this datasource layer so that we can know whether it's safe to cache or not
    static getHostOpts(url) {
        const { username, password } = hostRules.find({
            hostType: PackagistDatasource.id,
            url,
        });
        return username && password ? { username, password } : {};
    }
    async getJson(url, schema) {
        const opts = PackagistDatasource.getHostOpts(url);
        const { body } = await this.http.getJson(url, opts);
        return schema.parse(body);
    }
    async getRegistryMeta(regUrl) {
        const url = (0, url_1.resolveBaseUrl)(regUrl, 'packages.json');
        const result = await this.getJson(url, schema_1.RegistryMeta);
        return result;
    }
    static isPrivatePackage(regUrl) {
        const opts = PackagistDatasource.getHostOpts(regUrl);
        return !!opts.password;
    }
    static getPackagistFileUrl(regUrl, regFile) {
        const { key, hash } = regFile;
        const fileName = hash
            ? key.replace('%hash%', hash)
            : /* istanbul ignore next: hard to test */ key;
        const url = (0, url_1.resolveBaseUrl)(regUrl, fileName);
        return url;
    }
    async getPackagistFile(regUrl, regFile) {
        const url = PackagistDatasource.getPackagistFileUrl(regUrl, regFile);
        const packagistFile = await this.getJson(url, schema_1.PackagistFile);
        return packagistFile;
    }
    async fetchProviderPackages(regUrl, meta) {
        await p.map(meta.files, async (file) => {
            const res = await this.getPackagistFile(regUrl, file);
            Object.assign(meta.providerPackages, res.providers);
        });
    }
    async fetchIncludesPackages(regUrl, meta) {
        await p.map(meta.includesFiles, async (file) => {
            const res = await this.getPackagistFile(regUrl, file);
            for (const [key, val] of Object.entries(res.packages)) {
                meta.includesPackages[key] = (0, schema_1.extractDepReleases)(val);
            }
        });
    }
    async packagistV2Lookup(registryUrl, metadataUrl, packageName) {
        const pkgUrl = (0, url_1.replaceUrlPath)(registryUrl, metadataUrl.replace('%package%', packageName));
        const pkgPromise = this.getJson(pkgUrl, zod_1.z.unknown());
        const devUrl = (0, url_1.replaceUrlPath)(registryUrl, metadataUrl.replace('%package%', `${packageName}~dev`));
        const devPromise = this.getJson(devUrl, zod_1.z.unknown()).then((x) => x, () => null);
        const responses = await Promise.all([
            pkgPromise,
            devPromise,
        ]).then((responses) => responses.filter(is_1.default.object));
        return (0, schema_1.parsePackagesResponses)(packageName, responses);
    }
    getPkgUrl(packageName, registryUrl, registryMeta) {
        if (registryMeta.providersUrl &&
            packageName in registryMeta.providerPackages) {
            let url = registryMeta.providersUrl.replace('%package%', packageName);
            const hash = registryMeta.providerPackages[packageName];
            if (hash) {
                url = url.replace('%hash%', hash);
            }
            return (0, url_1.replaceUrlPath)(registryUrl, url);
        }
        if (registryMeta.providersLazyUrl) {
            return (0, url_1.replaceUrlPath)(registryUrl, registryMeta.providersLazyUrl.replace('%package%', packageName));
        }
        return null;
    }
    async getReleases({ packageName, registryUrl, }) {
        logger_1.logger.trace(`getReleases(${packageName})`);
        // istanbul ignore if
        if (!registryUrl) {
            return null;
        }
        try {
            const meta = await this.getRegistryMeta(registryUrl);
            if (meta.availablePackages &&
                !meta.availablePackages.includes(packageName)) {
                return null;
            }
            if (meta.metadataUrl) {
                const packagistResult = await this.packagistV2Lookup(registryUrl, meta.metadataUrl, packageName);
                return packagistResult;
            }
            if (meta.packages[packageName]) {
                const result = (0, schema_1.extractDepReleases)(meta.packages[packageName]);
                return result;
            }
            await this.fetchIncludesPackages(registryUrl, meta);
            if (meta.includesPackages[packageName]) {
                return meta.includesPackages[packageName];
            }
            await this.fetchProviderPackages(registryUrl, meta);
            const pkgUrl = this.getPkgUrl(packageName, registryUrl, meta);
            if (!pkgUrl) {
                return null;
            }
            const pkgRes = await this.getJson(pkgUrl, schema_1.PackagesResponse);
            const dep = (0, schema_1.extractDepReleases)(pkgRes.packages[packageName]);
            logger_1.logger.trace({ dep }, 'dep');
            return dep;
        }
        catch (err) /* istanbul ignore next */ {
            if (err.host === 'packagist.org') {
                if (err.code === 'ECONNRESET' || err.code === 'ETIMEDOUT') {
                    throw new external_host_error_1.ExternalHostError(err);
                }
                if (err.statusCode && err.statusCode >= 500 && err.statusCode < 600) {
                    throw new external_host_error_1.ExternalHostError(err);
                }
            }
            throw err;
        }
    }
}
exports.PackagistDatasource = PackagistDatasource;
tslib_1.__decorate([
    (0, decorator_1.cache)({
        namespace: `datasource-${PackagistDatasource.id}`,
        key: (regUrl) => `getRegistryMeta:${regUrl}`,
    })
], PackagistDatasource.prototype, "getRegistryMeta", null);
tslib_1.__decorate([
    (0, decorator_1.cache)({
        namespace: `datasource-${PackagistDatasource.id}`,
        key: (regUrl, regFile) => `getPackagistFile:${PackagistDatasource.getPackagistFileUrl(regUrl, regFile)}`,
        cacheable: (regUrl) => !PackagistDatasource.isPrivatePackage(regUrl),
        ttlMinutes: 1440,
    })
], PackagistDatasource.prototype, "getPackagistFile", null);
tslib_1.__decorate([
    (0, decorator_1.cache)({
        namespace: `datasource-${PackagistDatasource.id}`,
        key: (registryUrl, metadataUrl, packageName) => `packagistV2Lookup:${registryUrl}:${metadataUrl}:${packageName}`,
        ttlMinutes: 10,
    })
], PackagistDatasource.prototype, "packagistV2Lookup", null);
//# sourceMappingURL=index.js.map