diff --git a/lib/workers/pr/changelog/index.js b/lib/workers/pr/changelog/index.js index bfea732692250ba7c4b2289bec25699fb5d97a2f..422f4aa94326df5de9c03588410f5da9ccf767c5 100644 --- a/lib/workers/pr/changelog/index.js +++ b/lib/workers/pr/changelog/index.js @@ -3,26 +3,30 @@ const { addReleaseNotes } = require('../release-notes'); const sourceCache = require('./source-cache'); const sourceGithub = require('./source-github'); +const managerNpm = require('./manager-npm'); + module.exports = { getChangeLogJSON, }; -async function getChangeLogJSON(depName, fromVersion, newVersion) { - logger.debug(`getChangeLogJSON(${depName}, ${fromVersion}, ${newVersion})`); +async function getChangeLogJSON(args) { + const { manager, fromVersion, newVersion } = args; + logger.debug({ args }, `getChangeLogJSON(args)`); if (!fromVersion || fromVersion === newVersion) { return null; } - - const args = [depName, fromVersion, newVersion]; - // Return from cache if present - let res = await sourceCache.getChangeLogJSON(...args); + let res = await sourceCache.getChangeLogJSON(args); if (res) { return addReleaseNotes(res); } + let pkg = null; + if (['npm', 'meteor'].includes(manager)) { + pkg = await managerNpm.getPackage(args); + } - res = await sourceGithub.getChangeLogJSON(...args); + res = await sourceGithub.getChangeLogJSON({ ...args, ...pkg }); - await sourceCache.setChangeLogJSON(...args, res); + await sourceCache.setChangeLogJSON(args, res); return addReleaseNotes(res); } diff --git a/lib/workers/pr/changelog/manager-npm.js b/lib/workers/pr/changelog/manager-npm.js new file mode 100644 index 0000000000000000000000000000000000000000..6fe3da21701068b6ad66ddd366412c84b65142f8 --- /dev/null +++ b/lib/workers/pr/changelog/manager-npm.js @@ -0,0 +1,27 @@ +const npmRegistry = require('../../../datasource/npm'); +const { semverSort } = require('../../../util/semver'); + +module.exports = { + getPackage, +}; + +async function getPackage({ depName, depType }) { + if (depType === 'engines') { + return null; + } + const dep = await npmRegistry.getDependency(depName); + if (!dep) { + return null; + } + const releases = Object.keys(dep.versions); + releases.sort(semverSort); + const versions = releases.map(release => ({ + version: release, + date: dep.versions[release].time, + gitHead: dep.versions[release].gitHead, + })); + return { + repositoryUrl: dep.repositoryUrl, + versions, + }; +} diff --git a/lib/workers/pr/changelog/source-cache.js b/lib/workers/pr/changelog/source-cache.js index b8b2fb195c927585080e7ed584cb0f5f6d46b17c..bcfee3d78bd60a046cfe6b4ccb22b043b11e6561 100644 --- a/lib/workers/pr/changelog/source-cache.js +++ b/lib/workers/pr/changelog/source-cache.js @@ -7,15 +7,16 @@ module.exports = { rmAllCache, }; -function getCache(depName, fromVersion, newVersion) { +function getCache({ depName, fromVersion, newVersion }) { const tmpdir = process.env.RENOVATE_TMPDIR || os.tmpdir(); const cachePath = tmpdir + '/renovate-commits-cache'; const cacheKey = `${depName}-${fromVersion}-${newVersion}`; return [cachePath, cacheKey]; } -async function getChangeLogJSON(depName, fromVersion, newVersion) { - const cache = getCache(depName, fromVersion, newVersion); +async function getChangeLogJSON(args) { + const cache = getCache(args); + const { depName } = args; try { const cacheVal = await cacache.get(...cache); logger.trace(`Returning cached version of ${depName}`); @@ -27,12 +28,12 @@ async function getChangeLogJSON(depName, fromVersion, newVersion) { } } -async function setChangeLogJSON(depName, fromVersion, newVersion, res) { - const cache = getCache(depName, fromVersion, newVersion); +async function setChangeLogJSON(args, res) { + const cache = getCache(args); await cacache.put(...cache, JSON.stringify(res)); } async function rmAllCache() { - const cache = getCache(); + const cache = getCache({}); await cacache.rm.all(cache[0]); } diff --git a/lib/workers/pr/changelog/source-github.js b/lib/workers/pr/changelog/source-github.js index 740adb23a94d71850caa965f4228a556600fa953..de39aecdea5200b47a6623a78c3a2a74b6e09f9f 100644 --- a/lib/workers/pr/changelog/source-github.js +++ b/lib/workers/pr/changelog/source-github.js @@ -1,9 +1,4 @@ -const npmRegistry = require('../../../datasource/npm'); -const { - matchesSemver, - isPinnedVersion, - semverSort, -} = require('../../../util/semver'); +const { matchesSemver, isPinnedVersion } = require('../../../util/semver'); const ghGot = require('../../../platform/github/gh-got-wrapper'); module.exports = { @@ -47,39 +42,37 @@ async function getRepositoryHead(repository, version) { if (version.gitHead) { return version.gitHead; } - if (!version.time) { + if (!version.date) { return null; } - logger.info({ repository, version }, 'Looking for commit SHA by time'); + logger.trace({ repository, version }, 'Looking for commit SHA by date'); try { const res = await ghGot( - `https://api.github.com/repos/${repository}/commits/@{${version.time}}` + `https://api.github.com/repos/${repository}/commits/@{${version.date}}` ); const commit = res && res.body; return commit && commit.sha; } catch (err) { - logger.debug({ err, repository }, 'Failed to fetch Github commit by time'); + logger.debug({ err, repository }, 'Failed to fetch Github commit by date'); return null; } } -async function getChangeLogJSON(depName, fromVersion, newVersion) { +async function getChangeLogJSON({ + repositoryUrl, + fromVersion, + newVersion, + versions, +}) { logger.debug('Checking for github source URL manually'); const semverString = `>${fromVersion} <=${newVersion}`; logger.trace(`semverString: ${semverString}`); - const dep = await npmRegistry.getDependency(depName); - if ( - !( - dep && - dep.repositoryUrl && - dep.repositoryUrl.startsWith('https://github.com/') - ) - ) { + if (!(repositoryUrl && repositoryUrl.startsWith('https://github.com/'))) { logger.debug('No repo found manually'); return null; } - logger.info({ url: dep.repositoryUrl }, 'Found github URL manually'); - const repository = dep.repositoryUrl + logger.debug({ url: repositoryUrl }, 'Found github URL manually'); + const repository = repositoryUrl .replace('https://github.com/', '') .replace(/#.*/, ''); if (repository.split('/').length !== 2) { @@ -88,25 +81,24 @@ async function getChangeLogJSON(depName, fromVersion, newVersion) { } const tags = await getTags(repository); - const releases = Object.keys(dep.versions); - releases.sort(semverSort); - function getHead(name) { + function getHead(version) { return getRepositoryHead(repository, { - ...dep.versions[name], - ...tags[name], + ...version, + ...tags[version.version], }); } - const versions = []; + const releases = []; + // compare versions - for (let i = 1; i < releases.length; i += 1) { - const prev = releases[i - 1]; - const next = releases[i]; - if (matchesSemver(next, semverString)) { - const version = { - version: next, - date: dep.versions[next].time, + for (let i = 1; i < versions.length; i += 1) { + const prev = versions[i - 1]; + const next = versions[i]; + if (matchesSemver(next.version, semverString)) { + const release = { + version: next.version, + date: next.date, // put empty changes so that existing templates won't break changes: [], compare: {}, @@ -114,18 +106,18 @@ async function getChangeLogJSON(depName, fromVersion, newVersion) { const prevHead = await getHead(prev); const nextHead = await getHead(next); if (prevHead && nextHead) { - version.compare.url = `https://github.com/${repository}/compare/${prevHead}...${nextHead}`; + release.compare.url = `https://github.com/${repository}/compare/${prevHead}...${nextHead}`; } - versions.unshift(version); + releases.unshift(release); } } const res = { project: { github: repository, - repository: dep.repositoryUrl, + repository: repositoryUrl, }, - versions, + versions: releases, }; logger.debug({ res }, 'Manual res'); diff --git a/lib/workers/pr/index.js b/lib/workers/pr/index.js index 964fa43811e0c68b2fdf06fca7a16e206202b419..fa8fc11ad7114b9bb7d8a9238ab2ffd577bbbd24 100644 --- a/lib/workers/pr/index.js +++ b/lib/workers/pr/index.js @@ -123,19 +123,14 @@ async function ensurePr(prConfig) { } processedUpgrades.push(upgradeKey); - let logJSON; - if ( - upgrade.manager !== 'travis' && - upgrade.manager !== 'nvm' && - upgrade.manager !== 'circleci' && - upgrade.depType !== 'engines' - ) { - logJSON = await changelogHelper.getChangeLogJSON( - upgrade.depName, - upgrade.changeLogFromVersion, - upgrade.changeLogToVersion - ); - } + const logJSON = await changelogHelper.getChangeLogJSON({ + manager: upgrade.manager, + depType: upgrade.depType, + depName: upgrade.depName, + fromVersion: upgrade.changeLogFromVersion, + newVersion: upgrade.changeLogToVersion, + }); + if (logJSON) { upgrade.githubName = logJSON.project.github; upgrade.hasReleaseNotes = logJSON.hasReleaseNotes; diff --git a/test/workers/pr/changelog.spec.js b/test/workers/pr/changelog.spec.js index d2f62e88307c7500fe19c9216d298f1c139b5a66..5db1ba5bb64eb482ff969737cc3f88b04dae017c 100644 --- a/test/workers/pr/changelog.spec.js +++ b/test/workers/pr/changelog.spec.js @@ -9,6 +9,13 @@ const { rmAllCache, } = require('../../../lib/workers/pr/changelog/source-cache'); +const upgrade = { + manager: 'npm', + depName: 'renovate', + fromVersion: '1.0.0', + newVersion: '3.0.0', +}; + function npmResponse() { return { repositoryUrl: 'https://github.com/chalk/chalk', @@ -36,18 +43,29 @@ describe('workers/pr/changelog', () => { await rmAllCache(); }); it('returns null if no fromVersion', async () => { - expect(await getChangeLogJSON('renovate', null, '1.0.0')).toBe(null); + expect( + await getChangeLogJSON({ + ...upgrade, + fromVersion: null, + }) + ).toBe(null); expect(npmRegistry.getDependency.mock.calls).toHaveLength(0); expect(ghGot.mock.calls).toHaveLength(0); }); it('returns null if fromVersion equals newVersion', async () => { - expect(await getChangeLogJSON('renovate', '1.0.0', '1.0.0')).toBe(null); + expect( + await getChangeLogJSON({ + ...upgrade, + fromVersion: '1.0.0', + newVersion: '1.0.0', + }) + ).toBe(null); expect(ghGot.mock.calls).toHaveLength(0); }); it('logs when no JSON', async () => { // clear the mock npmRegistry.getDependency.mockReset(); - expect(await getChangeLogJSON('renovate', '1.0.0', '3.0.0')).toBe(null); + expect(await getChangeLogJSON({ ...upgrade })).toBe(null); }); it('skips invalid repos', async () => { // clear the mock @@ -55,13 +73,10 @@ describe('workers/pr/changelog', () => { const res = npmResponse(); res.repositoryUrl = 'https://github.com/about'; npmRegistry.getDependency.mockReturnValueOnce(Promise.resolve(res)); - - expect(await getChangeLogJSON('renovate', '1.0.0', '3.0.0')).toBe(null); + expect(await getChangeLogJSON({ ...upgrade })).toBe(null); }); it('works without Github', async () => { - expect( - await getChangeLogJSON('renovate', '1.0.0', '3.0.0') - ).toMatchSnapshot(); + expect(await getChangeLogJSON({ ...upgrade })).toMatchSnapshot(); }); it('uses GitHub tags', async () => { ghGot.mockReturnValueOnce( @@ -76,9 +91,7 @@ describe('workers/pr/changelog', () => { ], }) ); - expect( - await getChangeLogJSON('renovate', '1.0.0', '3.0.0') - ).toMatchSnapshot(); + expect(await getChangeLogJSON({ ...upgrade })).toMatchSnapshot(); }); it('falls back to commit from release time', async () => { // mock tags response @@ -90,13 +103,16 @@ describe('workers/pr/changelog', () => { }) ); expect( - await getChangeLogJSON('@renovate/no', '1.0.0', '3.0.0') + await getChangeLogJSON({ + ...upgrade, + depName: '@renovate/no', + }) ).toMatchSnapshot(); }); it('returns cached JSON', async () => { - const first = await getChangeLogJSON('renovate', '1.0.0', '3.0.0'); + const first = await getChangeLogJSON({ ...upgrade }); npmRegistry.getDependency.mockClear(); - const second = await getChangeLogJSON('renovate', '1.0.0', '3.0.0'); + const second = await getChangeLogJSON({ ...upgrade }); expect(first).toEqual(second); expect(npmRegistry.getDependency.mock.calls).toHaveLength(0); }); @@ -105,8 +121,16 @@ describe('workers/pr/changelog', () => { throw new Error('Unknown Github Repo'); }); expect( - await getChangeLogJSON('@renovate/no', '1.0.0', '3.0.0') + await getChangeLogJSON({ + ...upgrade, + depName: '@renovate/no', + }) ).toMatchSnapshot(); }); + it('skips node engines', async () => { + expect(await getChangeLogJSON({ ...upgrade, depType: 'engines' })).toBe( + null + ); + }); }); });