diff --git a/lib/config/definitions.js b/lib/config/definitions.js index aee81a704ccfd173d867988bedf98a2a6991b978..d13666d615a68c5b9475c5ef1c534c2d86a46dcf 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 0000000000000000000000000000000000000000..6ae10e628ae0e7555da42032bc3731dcb89f25be --- /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 5255a5c095f9a91a23afc75a61f23af9243eeeb4..c0a653635c629eb34f838fd3be99c021204a4373 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 caf91e31761ee59e034f42dafa4a8e0fccc22c2a..76dd9a8ef129c7289a81eea28a98ccaaa90252d9 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 cccab2c7ce6b8de5b8f174194f5596b0a44b7152..c0b4d1153fdf219f6772e10a0e16cd5954017e6d 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 0000000000000000000000000000000000000000..2c65d9c9e74a0941af1df43881af4b89a72fccb2 --- /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 eb77e142bbf2f262d5d838978bd46435cf5ba176..ac1a4b0c8c08b8ca0c9879a5ea4c3a0077b8ba69 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 70191f4e31bb5268511b510b64bdd584433a7c71..b26149b3d3d1f78052340826bc72f9da458a7aac 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.