"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PackagistDatasource = void 0;
const tslib_1 = require("tslib");
const url_1 = tslib_1.__importDefault(require("url"));
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_2 = require("../../../util/url");
const composerVersioning = tslib_1.__importStar(require("../../versioning/composer"));
const datasource_1 = require("../datasource");
const schema = tslib_1.__importStar(require("./schema"));
const schema_1 = require("./schema");
class PackagistDatasource extends datasource_1.Datasource {
    constructor() {
        super(PackagistDatasource.id);
        this.defaultRegistryUrls = ['https://packagist.org'];
        this.defaultVersioning = composerVersioning.id;
        this.registryStrategy = 'hunt';
    }
    // 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 getRegistryMeta(regUrl) {
        const url = url_1.default.resolve((0, url_2.ensureTrailingSlash)(regUrl), 'packages.json');
        const opts = PackagistDatasource.getHostOpts(url);
        const res = (await this.http.getJson(url, opts)).body;
        const meta = {
            providerPackages: {},
            packages: res.packages,
        };
        if (res.includes) {
            meta.includesFiles = [];
            for (const [name, val] of Object.entries(res.includes)) {
                const file = {
                    key: name.replace(val.sha256, '%hash%'),
                    sha256: val.sha256,
                };
                meta.includesFiles.push(file);
            }
        }
        if (res['providers-url']) {
            meta.providersUrl = res['providers-url'];
        }
        if (res['providers-lazy-url']) {
            meta.providersLazyUrl = res['providers-lazy-url'];
        }
        if (res['provider-includes']) {
            meta.files = [];
            for (const [key, val] of Object.entries(res['provider-includes'])) {
                const file = {
                    key,
                    sha256: val.sha256,
                };
                meta.files.push(file);
            }
        }
        if (res.providers) {
            for (const [key, val] of Object.entries(res.providers)) {
                meta.providerPackages[key] = val.sha256;
            }
        }
        return meta;
    }
    static isPrivatePackage(regUrl) {
        const opts = PackagistDatasource.getHostOpts(regUrl);
        return !!opts.password;
    }
    static getPackagistFileUrl(regUrl, regFile) {
        const { key, sha256 } = regFile;
        const fileName = key.replace('%hash%', sha256);
        const url = `${regUrl}/${fileName}`;
        return url;
    }
    async getPackagistFile(regUrl, regFile) {
        const url = PackagistDatasource.getPackagistFileUrl(regUrl, regFile);
        const opts = PackagistDatasource.getHostOpts(regUrl);
        const { body: packagistFile } = await this.http.getJson(url, opts);
        return packagistFile;
    }
    async getAllPackages(regUrl) {
        const registryMeta = await this.getRegistryMeta(regUrl);
        // istanbul ignore if: needs test
        if (!registryMeta) {
            return null;
        }
        const { packages, providersUrl, providersLazyUrl, files = [], includesFiles = [], providerPackages, } = registryMeta;
        const includesPackages = {};
        const tasks = [];
        for (const file of files) {
            tasks.push(async () => {
                const res = await this.getPackagistFile(regUrl, file);
                for (const [name, val] of Object.entries(res.providers)) {
                    providerPackages[name] = val.sha256;
                }
            });
        }
        for (const file of includesFiles) {
            tasks.push(async () => {
                const res = await this.getPackagistFile(regUrl, file);
                for (const [key, val] of Object.entries(res.packages ?? {})) {
                    includesPackages[key] = (0, schema_1.extractDepReleases)(val);
                }
            });
        }
        await p.all(tasks);
        const allPackages = {
            packages,
            providersUrl,
            providersLazyUrl,
            providerPackages,
            includesPackages,
        };
        return allPackages;
    }
    async packagistOrgLookup(name) {
        const regUrl = 'https://packagist.org';
        const pkgUrl = (0, url_2.joinUrlParts)(regUrl, `/p2/${name}.json`);
        const devUrl = (0, url_2.joinUrlParts)(regUrl, `/p2/${name}~dev.json`);
        const results = await p.map([pkgUrl, devUrl], (url) => this.http.getJson(url).then(({ body }) => body));
        return schema.parsePackagesResponses(name, results);
    }
    async getReleases({ packageName, registryUrl, }) {
        logger_1.logger.trace(`getReleases(${packageName})`);
        // istanbul ignore if
        if (!registryUrl) {
            return null;
        }
        try {
            if (registryUrl === 'https://packagist.org') {
                const packagistResult = await this.packagistOrgLookup(packageName);
                return packagistResult;
            }
            const allPackages = await this.getAllPackages(registryUrl);
            // istanbul ignore if: needs test
            if (!allPackages) {
                return null;
            }
            const { packages, providersUrl, providersLazyUrl, providerPackages, includesPackages, } = allPackages;
            if (packages?.[packageName]) {
                const dep = (0, schema_1.extractDepReleases)(packages[packageName]);
                return dep;
            }
            if (includesPackages?.[packageName]) {
                return includesPackages[packageName];
            }
            let pkgUrl;
            if (packageName in providerPackages) {
                pkgUrl = url_1.default.resolve(registryUrl, providersUrl
                    .replace('%package%', packageName)
                    .replace('%hash%', providerPackages[packageName]));
            }
            else if (providersLazyUrl) {
                pkgUrl = url_1.default.resolve(registryUrl, providersLazyUrl.replace('%package%', packageName));
            }
            else {
                return null;
            }
            const opts = PackagistDatasource.getHostOpts(registryUrl);
            // TODO: fix types (#9610)
            const versions = (await this.http.getJson(pkgUrl, opts)).body
                .packages[packageName];
            const dep = (0, schema_1.extractDepReleases)(versions);
            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;
        }
    }
}
PackagistDatasource.id = 'packagist';
tslib_1.__decorate([
    (0, decorator_1.cache)({
        namespace: `datasource-${PackagistDatasource.id}-public-files`,
        key: (regUrl, regFile) => 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: (regUrl) => regUrl,
    })
], PackagistDatasource.prototype, "getAllPackages", null);
tslib_1.__decorate([
    (0, decorator_1.cache)({
        namespace: `datasource-${PackagistDatasource.id}-org`,
        key: (regUrl) => regUrl,
        ttlMinutes: 10,
    })
], PackagistDatasource.prototype, "packagistOrgLookup", null);
exports.PackagistDatasource = PackagistDatasource;
//# sourceMappingURL=index.js.map