renovate
Version:
Automated dependency updates. Flexible so you don't need to be.
174 lines • 7.17 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.PodDatasource = void 0;
const tslib_1 = require("tslib");
const node_crypto_1 = tslib_1.__importDefault(require("node:crypto"));
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 github_1 = require("../../../util/http/github");
const regex_1 = require("../../../util/regex");
const datasource_1 = require("../datasource");
const metadata_1 = require("../metadata");
function shardParts(packageName) {
return node_crypto_1.default
.createHash('md5')
.update(packageName)
.digest('hex')
.slice(0, 3)
.split('');
}
const githubRegex = (0, regex_1.regEx)(/(?<hostURL>^https:\/\/[a-zA-Z0-9-.]+)\/(?<account>[^/]+)\/(?<repo>[^/]+?)(?:\.git|\/.*)?$/);
function releasesGithubUrl(packageName, opts) {
const { hostURL, account, repo, useShard, useSpecs } = opts;
const prefix = hostURL && hostURL !== 'https://github.com'
? `${hostURL}/api/v3/repos`
: 'https://api.github.com/repos';
const shard = shardParts(packageName).join('/');
// `Specs` in the pods repo URL is a new requirement for legacy support also allow pod repo URL without `Specs`
const packageNamePath = useSpecs ? `Specs/${packageName}` : packageName;
const shardPath = useSpecs
? `Specs/${shard}/${packageName}`
: `${shard}/${packageName}`;
const suffix = useShard ? shardPath : packageNamePath;
return `${prefix}/${account}/${repo}/contents/${suffix}`;
}
function handleError(packageName, err) {
const errorData = { packageName, err };
const statusCode = err.response?.statusCode ?? 0;
if (statusCode === 429 || (statusCode >= 500 && statusCode < 600)) {
logger_1.logger.warn({ packageName, err }, `CocoaPods registry failure`);
throw new external_host_error_1.ExternalHostError(err);
}
if (statusCode === 401) {
logger_1.logger.debug(errorData, 'Authorization error');
}
else if (statusCode === 404) {
logger_1.logger.debug(errorData, 'Package lookup error');
}
else if (err.message === error_messages_1.HOST_DISABLED) {
logger_1.logger.trace(errorData, 'Host disabled');
}
else {
logger_1.logger.warn(errorData, 'CocoaPods lookup failure: Unknown error');
}
}
function isDefaultRepo(url) {
const match = githubRegex.exec(url);
if (match?.groups) {
const { account, repo } = match.groups;
return (account.toLowerCase() === 'cocoapods' && repo.toLowerCase() === 'specs'); // https://github.com/CocoaPods/Specs.git
}
return false;
}
function releasesCDNUrl(packageName, registryUrl) {
const shard = shardParts(packageName).join('_');
return `${registryUrl}/all_pods_versions_${shard}.txt`;
}
class PodDatasource extends datasource_1.Datasource {
static id = 'pod';
defaultRegistryUrls = ['https://cdn.cocoapods.org'];
registryStrategy = 'hunt';
githubHttp;
constructor() {
super(PodDatasource.id);
this.githubHttp = new github_1.GithubHttp(PodDatasource.id);
}
async requestCDN(url, packageName) {
try {
const resp = await this.http.getText(url);
if (resp?.body) {
return resp.body;
}
}
catch (err) {
handleError(packageName, err);
}
return null;
}
async requestGithub(url, packageName) {
try {
const resp = await this.githubHttp.getJsonUnchecked(url);
if (resp?.body) {
return resp.body;
}
}
catch (err) {
handleError(packageName, err);
}
return null;
}
async getReleasesFromGithub(packageName, opts, useShard = true, useSpecs = true, urlFormatOptions = 'withShardWithSpec') {
const url = releasesGithubUrl(packageName, { ...opts, useShard, useSpecs });
const resp = await this.requestGithub(url, packageName);
if (resp) {
const releases = resp.map(({ name }) => ({ version: name }));
return { releases };
}
// support different url formats
switch (urlFormatOptions) {
case 'withShardWithSpec':
return this.getReleasesFromGithub(packageName, opts, true, false, 'withShardWithoutSpec');
case 'withShardWithoutSpec':
return this.getReleasesFromGithub(packageName, opts, false, true, 'withSpecsWithoutShard');
case 'withSpecsWithoutShard':
return this.getReleasesFromGithub(packageName, opts, false, false, 'withoutSpecsWithoutShard');
case 'withoutSpecsWithoutShard':
default:
return null;
}
}
async getReleasesFromCDN(packageName, registryUrl) {
const url = releasesCDNUrl(packageName, registryUrl);
const resp = await this.requestCDN(url, packageName);
if (resp) {
const lines = resp.split(regex_1.newlineRegex);
for (const line of lines) {
const [name, ...versions] = line.split('/');
if (name === packageName.replace((0, regex_1.regEx)(/\/.*$/), '')) {
const releases = versions.map((version) => ({ version }));
return { releases };
}
}
}
return null;
}
async getReleases({ packageName, registryUrl, }) {
/* v8 ignore next 3 -- should never happen */
if (!registryUrl) {
return null;
}
const podName = packageName.replace((0, regex_1.regEx)(/\/.*$/), '');
let baseUrl = registryUrl.replace((0, regex_1.regEx)(/\/+$/), '');
// In order to not abuse github API limits, query CDN instead
if (isDefaultRepo(baseUrl)) {
[baseUrl] = this.defaultRegistryUrls;
}
let result = null;
const match = githubRegex.exec(baseUrl);
// We would ideally have a reliable way to differentiate between
// a CDN URL and a Github URL, but we'll start with detecting Artifactory
if (match?.groups && !baseUrl.includes('/api/pods/')) {
baseUrl = (0, metadata_1.massageGithubUrl)(baseUrl);
const { hostURL, account, repo } = match.groups;
const opts = { hostURL, account, repo };
result = await this.getReleasesFromGithub(podName, opts);
}
else {
result = await this.getReleasesFromCDN(podName, baseUrl);
}
return result;
}
}
exports.PodDatasource = PodDatasource;
tslib_1.__decorate([
(0, decorator_1.cache)({
ttlMinutes: 30,
namespace: `datasource-${PodDatasource.id}`,
key: ({ packageName, registryUrl }) =>
// TODO: types (#22198)
`${registryUrl}:${packageName}`,
})
], PodDatasource.prototype, "getReleases", null);
//# sourceMappingURL=index.js.map