From b72bfdf4ba46c8235c636db13b10d3f1802f6fda Mon Sep 17 00:00:00 2001
From: Rhys Arkins <rhys@arkins.net>
Date: Sat, 20 Jan 2018 09:27:05 +0100
Subject: [PATCH] feat: bumpVersion (#1413)

Adds a new configuration option that allows you to bump the version number in the package.json being updated. e.g. you might configure Renovate to bump a patch every time, or maybe for dependencies only and not devDependencies. e.g. if you configure `"bumpVersion": "patch"` then a Renovate PR updating a `package.json` that used to be version `1.2.1` will now see it updated to `1.2.2` (in addition to the dependency version(s) being updated too). Thanks to @gunar for the feature suggestion.

Closes #861
---
 lib/config/definitions.js                     |  5 +++
 lib/manager/index.js                          |  5 +++
 lib/manager/npm/update.js                     | 36 +++++++++++++++++++
 lib/manager/resolve.js                        |  1 +
 .../__snapshots__/resolve.spec.js.snap        |  8 ++++-
 .../npm/__snapshots__/update.spec.js.snap     |  5 +++
 test/manager/npm/update.spec.js               | 25 +++++++++++++
 test/manager/resolve.spec.js                  |  6 +++-
 .../2017-10-05-configuration-options.md       | 10 ++++++
 9 files changed, 99 insertions(+), 2 deletions(-)
 create mode 100644 test/manager/npm/__snapshots__/update.spec.js.snap

diff --git a/lib/config/definitions.js b/lib/config/definitions.js
index eb416efdfb..f26e0bca18 100644
--- a/lib/config/definitions.js
+++ b/lib/config/definitions.js
@@ -412,6 +412,11 @@ const options = [
     type: 'string',
     default: 'renovate/',
   },
+  {
+    name: 'bumpVersion',
+    description: 'Bump the version in the package.json being updated',
+    type: 'string',
+  },
   // Major/Minor/Patch
   {
     name: 'major',
diff --git a/lib/manager/index.js b/lib/manager/index.js
index e9672c8d79..e2e8726cec 100644
--- a/lib/manager/index.js
+++ b/lib/manager/index.js
@@ -93,6 +93,11 @@ async function getUpdatedPackageFiles(config) {
           upgrade.depName,
           upgrade.newVersion
         );
+        newContent = npmUpdater.bumpPackageVersion(
+          newContent,
+          upgrade.currentPackageJsonVersion,
+          upgrade.bumpVersion
+        );
       } else if (upgrade.packageFile.endsWith('package.js')) {
         newContent = meteorUpdater.setNewValue(
           existingContent,
diff --git a/lib/manager/npm/update.js b/lib/manager/npm/update.js
index 61e6212e2e..1c2c3a3f7c 100644
--- a/lib/manager/npm/update.js
+++ b/lib/manager/npm/update.js
@@ -1,7 +1,9 @@
 const _ = require('lodash');
+const semver = require('semver');
 
 module.exports = {
   setNewValue,
+  bumpPackageVersion,
 };
 
 function setNewValue(currentFileContent, depType, depName, newVersion) {
@@ -113,3 +115,37 @@ function replaceAt(content, index, oldString, newString) {
     content.substr(index + oldString.length)
   );
 }
+
+function bumpPackageVersion(content, currentVersion, bumpVersion) {
+  logger.debug('bumpVersion()');
+  if (!bumpVersion) {
+    return content;
+  }
+  logger.debug(
+    { bumpVersion },
+    'Checking if we should bump package.json version'
+  );
+  try {
+    const newPjVersion = semver.inc(currentVersion, bumpVersion);
+    const bumpedContent = content.replace(
+      /("version":\s*")[^"]*/,
+      `$1${newPjVersion}`
+    );
+    if (bumpedContent === content) {
+      logger.debug('Version was already bumped');
+    } else {
+      logger.info('Bumped package.json version');
+    }
+    return bumpedContent;
+  } catch (err) {
+    logger.warn(
+      {
+        content,
+        currentVersion,
+        bumpVersion,
+      },
+      'Failed to bumpVersion'
+    );
+    return content;
+  }
+}
diff --git a/lib/manager/resolve.js b/lib/manager/resolve.js
index 22be5b7038..ea87f08950 100644
--- a/lib/manager/resolve.js
+++ b/lib/manager/resolve.js
@@ -157,6 +157,7 @@ async function resolvePackageFiles(config) {
         );
         packageFile.shrinkwrapYaml = shrinkwrapFileName;
       }
+      packageFile.currentPackageJsonVersion = packageFile.content.version;
       return packageFile;
     } else if (packageFile.packageFile.endsWith('package.js')) {
       // meteor
diff --git a/test/manager/__snapshots__/resolve.spec.js.snap b/test/manager/__snapshots__/resolve.spec.js.snap
index 5e0d3a2296..e53651306b 100644
--- a/test/manager/__snapshots__/resolve.spec.js.snap
+++ b/test/manager/__snapshots__/resolve.spec.js.snap
@@ -9,7 +9,9 @@ Array [
   Object {
     "content": Object {
       "name": "package.json",
+      "version": "0.0.1",
     },
+    "currentPackageJsonVersion": "0.0.1",
     "enabled": true,
     "npmrc": "npmrc",
     "packageFile": "package.json",
@@ -299,7 +301,11 @@ exports[`manager/resolve resolvePackageFiles() detects package.json and parses j
 Array [
   Object {
     "automerge": true,
-    "content": Object {},
+    "content": Object {
+      "name": "something",
+      "version": "1.0.0",
+    },
+    "currentPackageJsonVersion": "1.0.0",
     "enabled": true,
     "errors": Array [],
     "packageFile": "package.json",
diff --git a/test/manager/npm/__snapshots__/update.spec.js.snap b/test/manager/npm/__snapshots__/update.spec.js.snap
new file mode 100644
index 0000000000..2384ab0f65
--- /dev/null
+++ b/test/manager/npm/__snapshots__/update.spec.js.snap
@@ -0,0 +1,5 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`workers/branch/package-json .bumpPackageVersion() increments 1`] = `"{\\"name\\":\\"some-package\\",\\"version\\":\\"0.0.3\\"}"`;
+
+exports[`workers/branch/package-json .bumpPackageVersion() updates 1`] = `"{\\"name\\":\\"some-package\\",\\"version\\":\\"0.1.0\\"}"`;
diff --git a/test/manager/npm/update.spec.js b/test/manager/npm/update.spec.js
index 775248d0dc..46d6516c9e 100644
--- a/test/manager/npm/update.spec.js
+++ b/test/manager/npm/update.spec.js
@@ -1,6 +1,7 @@
 const fs = require('fs');
 const path = require('path');
 const npmUpdater = require('../../../lib/manager/npm/update');
+const semver = require('semver');
 
 function readFixture(fixture) {
   return fs.readFileSync(
@@ -72,4 +73,28 @@ describe('workers/branch/package-json', () => {
       expect(testContent).toBe(null);
     });
   });
+  describe('.bumpPackageVersion()', () => {
+    const content = JSON.stringify({ name: 'some-package', version: '0.0.2' });
+    it('increments', () => {
+      const res = npmUpdater.bumpPackageVersion(content, '0.0.2', 'patch');
+      expect(res).toMatchSnapshot();
+      expect(res).not.toEqual(content);
+    });
+    it('no ops', () => {
+      const res = npmUpdater.bumpPackageVersion(content, '0.0.1', 'patch');
+      expect(res).toEqual(content);
+    });
+    it('updates', () => {
+      const res = npmUpdater.bumpPackageVersion(content, '0.0.1', 'minor');
+      expect(res).toMatchSnapshot();
+      expect(res).not.toEqual(content);
+    });
+    it('returns content if bumping errors', () => {
+      semver.inc = jest.fn(() => {
+        throw new Error('semver inc');
+      });
+      const res = npmUpdater.bumpPackageVersion(content, '0.0.2', true);
+      expect(res).toEqual(content);
+    });
+  });
 });
diff --git a/test/manager/resolve.spec.js b/test/manager/resolve.spec.js
index ba1cb40c40..e39131cc72 100644
--- a/test/manager/resolve.spec.js
+++ b/test/manager/resolve.spec.js
@@ -54,6 +54,8 @@ describe('manager/resolve', () => {
         { packageFile: 'package.json' },
       ]);
       const pJson = {
+        name: 'something',
+        version: '1.0.0',
         renovate: {
           automerge: true,
         },
@@ -73,7 +75,9 @@ describe('manager/resolve', () => {
         'package-lock.json',
         'shrinkwrap.yaml',
       ]);
-      platform.getFile.mockReturnValueOnce('{"name": "package.json"}');
+      platform.getFile.mockReturnValueOnce(
+        '{"name": "package.json", "version": "0.0.1"}'
+      );
       platform.getFile.mockReturnValueOnce('npmrc');
       platform.getFile.mockReturnValueOnce('yarnrc');
       const res = await resolvePackageFiles(config);
diff --git a/website/docs/_posts/2017-10-05-configuration-options.md b/website/docs/_posts/2017-10-05-configuration-options.md
index 19f2364ea8..3abd6bf4ce 100644
--- a/website/docs/_posts/2017-10-05-configuration-options.md
+++ b/website/docs/_posts/2017-10-05-configuration-options.md
@@ -121,6 +121,16 @@ Prefix to be used for all branch names
 
 You can modify this field if you want to change the prefix used. For example if you want branches to be like `deps/eslint-4.x` instead of `renovate/eslint-4.x` then you set `branchPrefix` = `deps/`. Or if you wish to avoid forward slashes in branch names then you could use `renovate_` instead, for example.
 
+## bumpVersion
+
+Bump the version in the package.json being updated
+
+| name | value  |
+| ---- | ------ |
+| type | string |
+
+Set this value to 'patch', 'minor' or 'major' to have Renovate update the version in your edited `package.json`. e.g. if you wish Renovate to always increase the target `package.json` version with a patch update, set this to `patch`.
+
 ## commitBody
 
 Commit body template
-- 
GitLab