From 116939ffb51d460beffcb206d7e45057adffa0ca Mon Sep 17 00:00:00 2001 From: Rhys Arkins <rhys@arkins.net> Date: Mon, 19 Feb 2018 14:21:45 +0100 Subject: [PATCH] feat: renovate node engine in package.json (#1519) Adds support for upgrading `node` version in `package.json` > `engines` if the current version is pinned. - Does not convert from range to pin - Ignores ranges - Does not upgrade major versions --- lib/config/definitions.js | 9 ++++ lib/manager/npm/engines.js | 43 +++++++++++++++++++ lib/manager/npm/package.js | 4 ++ lib/workers/package-file/index.js | 1 + lib/workers/pr/index.js | 13 +++--- test/manager/npm/engines.spec.js | 37 ++++++++++++++++ test/manager/npm/package.spec.js | 5 +++ .../2017-10-05-configuration-options.md | 11 +++++ 8 files changed, 118 insertions(+), 5 deletions(-) create mode 100644 lib/manager/npm/engines.js create mode 100644 test/manager/npm/engines.spec.js diff --git a/lib/config/definitions.js b/lib/config/definitions.js index aee81a704c..d13666d615 100644 --- a/lib/config/definitions.js +++ b/lib/config/definitions.js @@ -298,6 +298,15 @@ const options = [ mergeable: true, cli: false, }, + { + name: 'engines', + description: 'Configuration specifically for `package.json`>`engines`', + stage: 'packageFile', + type: 'json', + default: {}, + mergeable: true, + cli: false, + }, // depType { name: 'ignoreDeps', diff --git a/lib/manager/npm/engines.js b/lib/manager/npm/engines.js new file mode 100644 index 0000000000..6ae10e628a --- /dev/null +++ b/lib/manager/npm/engines.js @@ -0,0 +1,43 @@ +const semver = require('semver'); +const { getRepoReleases, semverSort } = require('../../datasource/github'); + +async function renovateEngines(config) { + const { currentVersion, depName: dependency } = config; + logger.debug({ dependency, currentVersion }, 'Found engines'); + if (config.depName !== 'node') { + logger.debug('Skipping non-node engine'); + return []; + } + logger.info('Checking node engine'); + if (!semver.valid(currentVersion)) { + logger.info({ currentVersion }, 'Skipping non-pinned node version'); + return []; + } + const newReleases = (await getRepoReleases('nodejs/node')) + .map(release => release.replace(/^v/, '')) + .filter(release => semver.major(currentVersion) === semver.major(release)) + .filter(release => semver.gt(release, currentVersion)) + .sort(semverSort); + if (newReleases.length) { + logger.info({ newReleases }, 'Found newer Node releases'); + } else { + return []; + } + const newVersion = newReleases.pop(); + return [ + { + type: + semver.major(currentVersion) !== semver.major(newVersion) + ? 'major' + : 'minor', + newVersion, + newVersionMajor: semver.major(newVersion), + newVersionMinor: semver.minor(newVersion), + changeLogFromVersion: currentVersion, + changeLogToVersion: newVersion, + repositoryUrl: 'https://github.com/nodejs/node', + }, + ]; +} + +module.exports = { renovateEngines }; diff --git a/lib/manager/npm/package.js b/lib/manager/npm/package.js index 5255a5c095..c0a653635c 100644 --- a/lib/manager/npm/package.js +++ b/lib/manager/npm/package.js @@ -1,11 +1,15 @@ const npmApi = require('./registry'); const versions = require('../../workers/package/versions'); +const { renovateEngines } = require('./engines'); module.exports = { getPackageUpdates, }; async function getPackageUpdates(config) { + if (config.depType === 'engines') { + return renovateEngines(config); + } let results = []; if (config.currentVersion.startsWith('file:')) { logger.debug( diff --git a/lib/workers/package-file/index.js b/lib/workers/package-file/index.js index caf91e3176..76dd9a8ef1 100644 --- a/lib/workers/package-file/index.js +++ b/lib/workers/package-file/index.js @@ -96,6 +96,7 @@ async function renovatePackageFile(packageFileConfig) { 'devDependencies', 'optionalDependencies', 'peerDependencies', + 'engines', ]; const depTypeConfigs = depTypes.map(depType => { const depTypeConfig = configParser.mergeChildConfig(config, { diff --git a/lib/workers/pr/index.js b/lib/workers/pr/index.js index cccab2c7ce..c0b4d1153f 100644 --- a/lib/workers/pr/index.js +++ b/lib/workers/pr/index.js @@ -110,11 +110,14 @@ async function ensurePr(prConfig) { } processedUpgrades.push(upgradeKey); - const logJSON = await changelogHelper.getChangeLogJSON( - upgrade.depName, - upgrade.changeLogFromVersion, - upgrade.changeLogToVersion - ); + let logJSON; + if (upgrade.depType !== 'engines') { + logJSON = await changelogHelper.getChangeLogJSON( + upgrade.depName, + upgrade.changeLogFromVersion, + upgrade.changeLogToVersion + ); + } if (logJSON) { upgrade.githubName = logJSON.project.github; upgrade.hasReleaseNotes = logJSON.hasReleaseNotes; diff --git a/test/manager/npm/engines.spec.js b/test/manager/npm/engines.spec.js new file mode 100644 index 0000000000..2c65d9c9e7 --- /dev/null +++ b/test/manager/npm/engines.spec.js @@ -0,0 +1,37 @@ +const engines = require('../../../lib/manager/npm/engines'); +const { getRepoReleases } = require('../../../lib/datasource/github'); + +jest.mock('../../../lib/datasource/github'); + +describe('manager/npm/engines', () => { + let config; + beforeEach(() => { + config = { + depName: 'node', + }; + }); + it('skips non-pinned versions', async () => { + config.currentVersion = '8'; + const res = await engines.renovateEngines(config); + expect(res).toEqual([]); + }); + it('returns empty', async () => { + config.currentVersion = '8.9.0'; + getRepoReleases.mockReturnValueOnce([]); + const res = await engines.renovateEngines(config); + expect(res).toEqual([]); + }); + it('filters v', async () => { + config.currentVersion = '8.9.0'; + getRepoReleases.mockReturnValueOnce(['v8.0.0', 'v8.9.1']); + const res = await engines.renovateEngines(config); + expect(res).toHaveLength(1); + expect(res[0].newVersion).toEqual('8.9.1'); + }); + it('skips major versions', async () => { + config.currentVersion = '8.9.0'; + getRepoReleases.mockReturnValueOnce(['v9.4.0']); + const res = await engines.renovateEngines(config); + expect(res).toHaveLength(0); + }); +}); diff --git a/test/manager/npm/package.spec.js b/test/manager/npm/package.spec.js index eb77e142bb..ac1a4b0c8c 100644 --- a/test/manager/npm/package.spec.js +++ b/test/manager/npm/package.spec.js @@ -17,6 +17,11 @@ describe('lib/workers/package/npm', () => { currentVersion: '1.0.0', }; }); + it('calls engines function', async () => { + config.depType = 'engines'; + const res = await npm.getPackageUpdates(config); + expect(res).toHaveLength(0); + }); it('returns if using a file reference', async () => { config.currentVersion = 'file:../sibling/package.json'; const res = await npm.getPackageUpdates(config); diff --git a/website/docs/_posts/2017-10-05-configuration-options.md b/website/docs/_posts/2017-10-05-configuration-options.md index 70191f4e31..b26149b3d3 100644 --- a/website/docs/_posts/2017-10-05-configuration-options.md +++ b/website/docs/_posts/2017-10-05-configuration-options.md @@ -262,6 +262,17 @@ A configuration object containing strings encrypted with Renovate's public key. See https://renovateapp.com/docs/deep-dives/private-modules for details on how this is used to encrypt npm tokens. +## engines + +Configuration specific for `package.json > engines`. + +| name | value | +| ------- | ------ | +| type | object | +| default | {} | + +Extend this if you wish to configure rules specifically for `engines` definitions. Currently only `node` is supported. + ## excludePackageNames A list of package names inside a package rule which are to be excluded/ignored. -- GitLab