const got = require('got'); const url = require('url'); const is = require('@sindresorhus/is'); const { isVersion, sortVersions } = require('../versioning')('pep440'); module.exports = { getPkgReleases, }; function normalizeName(input) { return input.toLowerCase().replace(/(-|\.)/g, '_'); } // This is a manual list of changelog URLs for dependencies that don't publish to repositoryUrl // Make these lower case const changelogUrls = { 'pytest-django': 'https://pytest-django.readthedocs.io/en/latest/changelog.html#changelog', django: 'https://github.com/django/django/tree/master/docs/releases', djangorestframework: 'https://www.django-rest-framework.org/community/release-notes/', }; async function getPkgReleases(purl, config = {}) { const { fullname: depName } = purl; let hostUrl = 'https://pypi.org/pypi/'; if (is.nonEmptyArray(config.registryUrls)) { [hostUrl] = config.registryUrls; } if (process.env.PIP_INDEX_URL) { [hostUrl] = [process.env.PIP_INDEX_URL]; } const lookupUrl = url.resolve(hostUrl, `${depName}/json`); try { const dependency = {}; const rep = await got(lookupUrl, { json: true, }); const dep = rep && rep.body; if (!dep) { logger.debug({ dependency: depName }, 'pip package not found'); return null; } if ( !(dep.info && normalizeName(dep.info.name) === normalizeName(depName)) ) { logger.warn( { lookupUrl, lookupName: depName, returnedName: dep.info.name }, 'Returned name does not match with requested name' ); return null; } if (dep.info && dep.info.home_page) { if (dep.info.home_page.match(/^https?:\/\/github.com/)) { dependency.repositoryUrl = dep.info.home_page.replace( 'http://', 'https://' ); } else { dependency.homepage = dep.info.home_page; } } const manualRepositories = { mkdocs: 'https://github.com/mkdocs/mkdocs', pillow: 'https://github.com/python-pillow/Pillow', }; dependency.repositoryUrl = dependency.repositoryUrl || manualRepositories[depName.toLowerCase()]; dependency.releases = []; if (dep.releases) { const versions = Object.keys(dep.releases) .filter(isVersion) .sort(sortVersions); dependency.releases = versions.map(version => ({ version, releaseTimestamp: (dep.releases[version][0] || {}).upload_time, })); } // istanbul ignore if if (changelogUrls[purl.fullname.toLowerCase()]) { dependency.changelogUrl = changelogUrls[purl.fullname.toLowerCase()] || dependency.changelogUrl; if ( !dependency.repositoryUrl && dependency.changelogUrl.startsWith('https://github.com/') ) { dependency.repositoryUrl = dependency.changelogUrl .split('/') .slice(0, 5) .join('/'); } } return dependency; } catch (err) { logger.info('pypi dependency not found: ' + depName); return null; } }