UNPKG

renovate

Version:

Automated dependency updates. Flexible so you don't need to be.

166 lines • 7.87 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.RepologyDatasource = void 0; const tslib_1 = require("tslib"); const is_1 = tslib_1.__importDefault(require("@sindresorhus/is")); const error_messages_1 = require("../../../constants/error-messages"); 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 url_1 = require("../../../util/url"); const datasource_1 = require("../datasource"); const packageTypes = ['binname', 'srcname']; function findPackageInResponse(response, repoName, pkgName, types) { const repoPackages = response.filter((pkg) => pkg.repo === repoName); if (repoPackages.length === 0) { // no packages associated with repoName return null; } if (repoPackages.length === 1) { // repo contains exactly one package, so we can return them safely return repoPackages; } // In some cases Repology bundles multiple packages into a single project, which might result in ambiguous results. // We need to do additional filtering by matching allowed package types passed as params with package description. // Remaining packages are the one we are looking for const packagesWithType = repoPackages.filter((pkg) => { for (const pkgType of types) { if (pkg[pkgType] && pkg[pkgType] === pkgName) { return true; } } return false; }); return packagesWithType.length > 0 ? packagesWithType : null; } class RepologyDatasource extends datasource_1.Datasource { static id = 'repology'; defaultRegistryUrls = ['https://repology.org/']; registryStrategy = 'hunt'; constructor() { super(RepologyDatasource.id); } async queryPackages(url) { try { const res = await this.http.getJsonUnchecked(url); return res.body; } catch (err) { if (err.statusCode === 404) { // Return an array here because the api does not return proper http codes // and instead of an 404 error an empty array with code 200 is returned // When querying the resolver 404 is thrown if package could not be resolved // and 403 if the repo is not supported // 403 is handled later because in this case we are trying the API return []; } throw err; } } async queryPackagesViaResolver(registryUrl, repoName, packageName, packageType) { const query = (0, url_1.getQueryString)({ repo: repoName, name_type: packageType, target_page: 'api_v1_project', noautoresolve: 'on', name: packageName, }); // Retrieve list of packages by looking up Repology project const packages = await this.queryPackages((0, url_1.joinUrlParts)(registryUrl, `tools/project-by?${query}`)); return packages; } async queryPackagesViaAPI(registryUrl, packageName) { // Directly query the package via the API. This will only work if `packageName` has the // same name as the repology project const packages = await this.queryPackages((0, url_1.joinUrlParts)(registryUrl, `api/v1/project`, packageName)); return packages; } async queryPackage(registryUrl, repoName, pkgName) { let response; // Try getting the packages from tools/project-by first for type binname and // afterwards for srcname. This needs to be done first, because some packages // resolve to repology projects which have a different name than the package // e.g. `pulseaudio-utils` resolves to project `pulseaudio`, BUT there is also // a project called `pulseaudio-utils` but it does not contain the package we // are looking for. try { for (const pkgType of packageTypes) { response = await this.queryPackagesViaResolver(registryUrl, repoName, pkgName, pkgType); if (response) { const pkg = findPackageInResponse(response, repoName, pkgName, [ pkgType, ]); if (is_1.default.nonEmptyArray(pkg)) { // exit immediately if package found return pkg; } } } } catch (err) { if (err.statusCode === 403) { logger_1.logger.debug({ repoName, pkgName }, 'Repology does not support tools/project-by lookups for repository. Will try direct API access now'); // If the repository is not supported in tools/project-by we try directly accessing the // API. This will support all repositories but requires that the project name is equal to the // package name. This won't be always the case but for a good portion we might be able to resolve // the package this way. response = await this.queryPackagesViaAPI(registryUrl, pkgName); const pkg = findPackageInResponse(response, repoName, pkgName, packageTypes); if (is_1.default.nonEmptyArray(pkg)) { // exit immediately if package found return pkg; } } else if (err.statusCode === 300) { logger_1.logger.warn({ repoName, pkgName }, 'Ambiguous redirection from package name to project name in Repology. Skipping this package'); return undefined; } throw err; } logger_1.logger.debug({ repoName, pkgName }, 'Repository or package not found on Repology'); return undefined; } async getReleases({ packageName, registryUrl, }) { /* v8 ignore next 3 -- should never happen */ if (!registryUrl) { return null; } // Ensure lookup name contains both repository and package const [repoName, pkgName] = packageName.split('/', 2); if (!repoName || !pkgName) { throw new external_host_error_1.ExternalHostError(new Error('Repology lookup name must contain repository and package separated by slash (<repo>/<pkg>)')); } logger_1.logger.trace(`repology.getReleases(${repoName}, ${pkgName})`); try { // Try to retrieve (cached) package information from Repology const pkg = await this.queryPackage(registryUrl, repoName, pkgName); if (!pkg) { return null; } // Always prefer origversion if available, otherwise default to version // This is required as source packages usually have no origversion const releases = pkg.map((item) => ({ version: item.origversion ?? item.version, })); return { releases }; } catch (err) { if (err.message === error_messages_1.HOST_DISABLED) { logger_1.logger.trace({ packageName, err }, 'Host disabled'); } else { logger_1.logger.warn({ packageName, err }, 'Repology lookup failed with unexpected error'); } throw new external_host_error_1.ExternalHostError(err); } } } exports.RepologyDatasource = RepologyDatasource; tslib_1.__decorate([ (0, decorator_1.cache)({ ttlMinutes: 60, namespace: `datasource-${RepologyDatasource.id}`, key: (registryUrl, repoName, pkgName) => (0, url_1.joinUrlParts)(registryUrl, repoName, pkgName), }) ], RepologyDatasource.prototype, "queryPackage", null); //# sourceMappingURL=index.js.map