From 7f9d73a318c3f41a8098dc442870124e701498ea Mon Sep 17 00:00:00 2001
From: Rhys Arkins <rhys@arkins.net>
Date: Fri, 2 Mar 2018 23:10:42 +0100
Subject: [PATCH] feat: allowedVersions

A semver range defining allowed versions for dependencies

| name | value  |
| ---- | ------ |
| type | string |

Use this - usually within a packageRule - to limit how far to upgrade a dependency. For example, if you wish to upgrade to angular v1.5 but not to `angular` v1.6 or higher, you could defined this to be `<= 1.5` or `< 1.6.0`:

```
  "packageRules": [{
    "packageNames": ["angular"],
    "allowedVersions": "<=1.5"
  }]
```
---
 lib/config/definitions.js                       |  8 ++++++++
 lib/config/validation.js                        |  8 ++++++++
 lib/workers/package/versions.js                 | 11 ++++++++++-
 .../__snapshots__/validation.spec.js.snap       |  4 ++++
 test/config/validation.spec.js                  |  3 ++-
 .../_posts/2017-10-05-configuration-options.md  | 17 +++++++++++++++++
 6 files changed, 49 insertions(+), 2 deletions(-)

diff --git a/lib/config/definitions.js b/lib/config/definitions.js
index bdded628db..bab0d6f0ae 100644
--- a/lib/config/definitions.js
+++ b/lib/config/definitions.js
@@ -378,6 +378,14 @@ const options = [
     env: false,
   },
   // Version behaviour
+  {
+    name: 'allowedVersions',
+    description: 'A semver range defining allowed versions for dependencies',
+    type: 'string',
+    stage: 'package',
+    cli: false,
+    env: false,
+  },
   {
     name: 'pinDigests',
     description: 'Whether to add digests to Dockerfile source images',
diff --git a/lib/config/validation.js b/lib/config/validation.js
index 836fbd0ef4..e0fe44d25e 100644
--- a/lib/config/validation.js
+++ b/lib/config/validation.js
@@ -1,3 +1,4 @@
+const semver = require('semver');
 const options = require('./definitions').getOptions();
 const { hasValidSchedule } = require('../workers/branch/schedule');
 
@@ -60,6 +61,13 @@ function validateConfig(config) {
             message: `Invalid schedule: \`${errorMessage}\``,
           });
         }
+      } else if (key === 'allowedVersions' && val !== null) {
+        if (!semver.validRange(val)) {
+          errors.push({
+            depName: 'Configuration Error',
+            message: `Invalid semver range for allowedVersions: \`${val}\``,
+          });
+        }
       } else if (val != null) {
         const type = optionTypes[key];
         if (type === 'boolean') {
diff --git a/lib/workers/package/versions.js b/lib/workers/package/versions.js
index 4ee2386388..83b5d32b48 100644
--- a/lib/workers/package/versions.js
+++ b/lib/workers/package/versions.js
@@ -18,7 +18,12 @@ function determineUpgrades(npmDep, config) {
   const result = {
     type: 'warning',
   };
-  const { currentVersion, lockedVersion, pinVersions } = config;
+  const {
+    currentVersion,
+    lockedVersion,
+    pinVersions,
+    allowedVersions,
+  } = config;
   const { versions } = npmDep;
   if (!versions || Object.keys(versions).length === 0) {
     result.message = `No versions returned from registry for this package`;
@@ -82,6 +87,10 @@ function determineUpgrades(npmDep, config) {
   _(versionList)
     // Filter out older versions as we can't upgrade to those
     .filter(version => semver.gt(version, changeLogFromVersion))
+    // fillter out non-allowed versions if preference is set
+    .reject(
+      version => allowedVersions && !semver.satisfies(version, allowedVersions)
+    )
     // Ignore unstable versions, unless the current version is unstable
     .reject(
       version =>
diff --git a/test/config/__snapshots__/validation.spec.js.snap b/test/config/__snapshots__/validation.spec.js.snap
index 63af4b2400..fcc172e25b 100644
--- a/test/config/__snapshots__/validation.spec.js.snap
+++ b/test/config/__snapshots__/validation.spec.js.snap
@@ -2,6 +2,10 @@
 
 exports[`config/validation validateConfig(config) errors for all types 1`] = `
 Array [
+  Object {
+    "depName": "Configuration Error",
+    "message": "Invalid semver range for allowedVersions: \`foo\`",
+  },
   Object {
     "depName": "Configuration Error",
     "message": "Configuration option \`enabled\` should be boolean. Found: 1 (number)",
diff --git a/test/config/validation.spec.js b/test/config/validation.spec.js
index 213386278a..3c2f725da2 100644
--- a/test/config/validation.spec.js
+++ b/test/config/validation.spec.js
@@ -18,6 +18,7 @@ describe('config/validation', () => {
     });
     it('errors for all types', () => {
       const config = {
+        allowedVersions: 'foo',
         enabled: 1,
         schedule: ['every 15 mins every weekday'],
         labels: 5,
@@ -31,7 +32,7 @@ describe('config/validation', () => {
       };
       const { warnings, errors } = configValidation.validateConfig(config);
       expect(warnings).toHaveLength(0);
-      expect(errors).toHaveLength(6);
+      expect(errors).toHaveLength(7);
       expect(errors).toMatchSnapshot();
     });
   });
diff --git a/website/docs/_posts/2017-10-05-configuration-options.md b/website/docs/_posts/2017-10-05-configuration-options.md
index a7b7e63aa4..834e5d68c6 100644
--- a/website/docs/_posts/2017-10-05-configuration-options.md
+++ b/website/docs/_posts/2017-10-05-configuration-options.md
@@ -14,6 +14,23 @@ Also, be sure to check out Renovate's [shareable config presets](/docs/configura
 
 If you have any questions about the below config options, or would like to get help/feedback about a config, please post it as an issue in [renovateapp/config-help](https://github.com/renovateapp/config-help) where it will be promptly answered.
 
+## allowedVersions
+
+A semver range defining allowed versions for dependencies
+
+| name | value  |
+| ---- | ------ |
+| type | string |
+
+Use this - usually within a packageRule - to limit how far to upgrade a dependency. For example, if you wish to upgrade to angular v1.5 but not to `angular` v1.6 or higher, you could defined this to be `<= 1.5` or `< 1.6.0`:
+
+```
+  "packageRules": [{
+    "packageNames": ["angular"],
+    "allowedVersions": "<=1.5"
+  }]
+```
+
 ## assignees
 
 Assignees for Pull Requests
-- 
GitLab