From 48dd2f6f703182ff19402c521f562bed6144d12a Mon Sep 17 00:00:00 2001 From: Robert Jackson Date: Tue, 15 Aug 2017 10:18:09 -0400 Subject: [PATCH] smart merge package.json --- package.json | 2 + src/auto-merge-package-json.js | 82 +++++++++++++++++++ src/index.js | 19 ++++- .../adds-dependencies/from/package.json | 1 + .../adds-dependencies/my/package.json | 1 + .../adds-dependencies/result/package.json | 5 ++ .../adds-dependencies/to/package.json | 5 ++ .../preserves-ordering/from/package.json | 5 ++ .../preserves-ordering/my/package.json | 5 ++ .../preserves-ordering/result/package.json | 6 ++ .../preserves-ordering/to/package.json | 6 ++ .../removes-dependencies/from/package.json | 5 ++ .../removes-dependencies/my/package.json | 5 ++ .../removes-dependencies/result/package.json | 1 + .../removes-dependencies/to/package.json | 1 + .../updates-dependencies/from/package.json | 5 ++ .../updates-dependencies/my/package.json | 5 ++ .../updates-dependencies/result/package.json | 5 ++ .../updates-dependencies/to/package.json | 5 ++ test/unit/auto-merge-package-json-test.js | 42 ++++++++++ 20 files changed, 210 insertions(+), 1 deletion(-) create mode 100644 src/auto-merge-package-json.js create mode 100644 test/fixtures/package.json/adds-dependencies/from/package.json create mode 100644 test/fixtures/package.json/adds-dependencies/my/package.json create mode 100644 test/fixtures/package.json/adds-dependencies/result/package.json create mode 100644 test/fixtures/package.json/adds-dependencies/to/package.json create mode 100644 test/fixtures/package.json/preserves-ordering/from/package.json create mode 100644 test/fixtures/package.json/preserves-ordering/my/package.json create mode 100644 test/fixtures/package.json/preserves-ordering/result/package.json create mode 100644 test/fixtures/package.json/preserves-ordering/to/package.json create mode 100644 test/fixtures/package.json/removes-dependencies/from/package.json create mode 100644 test/fixtures/package.json/removes-dependencies/my/package.json create mode 100644 test/fixtures/package.json/removes-dependencies/result/package.json create mode 100644 test/fixtures/package.json/removes-dependencies/to/package.json create mode 100644 test/fixtures/package.json/updates-dependencies/from/package.json create mode 100644 test/fixtures/package.json/updates-dependencies/my/package.json create mode 100644 test/fixtures/package.json/updates-dependencies/result/package.json create mode 100644 test/fixtures/package.json/updates-dependencies/to/package.json create mode 100644 test/unit/auto-merge-package-json-test.js diff --git a/package.json b/package.json index 1d21ceae7..cd0fee974 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,9 @@ "dependencies": { "debug": "^3.0.0", "git-diff-apply": "^0.4.0", + "rfc6902-ordered": "^1.0.0", "semver": "^5.4.1", + "three-way-merger": "^0.3.0", "yargs": "^8.0.2" }, "devDependencies": { diff --git a/src/auto-merge-package-json.js b/src/auto-merge-package-json.js new file mode 100644 index 000000000..50daeb85b --- /dev/null +++ b/src/auto-merge-package-json.js @@ -0,0 +1,82 @@ +'use strict'; + +const EOL = require('os').EOL; +const ThreeWayMerger = require('three-way-merger'); +const rfc6902 = require('rfc6902-ordered'); + +const dependencyKeys = [ + 'dependencies', + 'devDependencies', + 'peerDependencies', + 'bundledDependencies', + 'optionalDependencies' +]; + +function clone(obj) { + return JSON.parse(JSON.stringify(obj)); +} + +function sortObjectKeys(obj) { + return Object.keys(obj).sort().reduce((sorted, key) => { + sorted[key] = obj[key]; + return sorted; + }, {}); +} + +function applyDependencyOperations(operations, deps) { + operations.add.forEach(dep => deps[dep.name] = dep.version); + operations.remove.forEach(dep => delete deps[dep.name]); + operations.change.forEach(dep => deps[dep.name] = dep.version); +} + +function mergeDependencyChanges(source, ours, theirs) { + let mergeOperations = ThreeWayMerger.merge({ source, ours, theirs }); + + // get a fresh copy so we don't mutate the passed in arg + let result = clone(ours); + + dependencyKeys.forEach(dependencyKey => { + // we could be missing the key and need to add to it + if (!result[dependencyKey]) { + result[dependencyKey] = {}; + } + + applyDependencyOperations(mergeOperations[dependencyKey], result[dependencyKey]); + + if (!Object.keys(result[dependencyKey]).length) { + delete result[dependencyKey]; + } else { + result[dependencyKey] = sortObjectKeys(result[dependencyKey]); + } + }); + + return result; +} + +function mergeNonDependencyChanges(source, ours, theirs) { + let fromSourceToTheirs = rfc6902.createPatch(source, theirs); + + rfc6902.applyPatch(ours, fromSourceToTheirs, theirs); + + return ours; +} + +function stringify(value) { + return JSON.stringify(value, null, 2).replace(/\n/g, EOL) + EOL; +} + +module.exports = function autoMergePackageJson(_currentPackageJson, _fromPackageJson, _toPackageJson) { + let currentPackageJson = JSON.parse(_currentPackageJson); + let fromPackageJson = JSON.parse(_fromPackageJson); + let toPackageJson = JSON.parse(_toPackageJson); + + let mergedDependenciesPackageJson = mergeDependencyChanges(fromPackageJson, currentPackageJson, toPackageJson); + let mergedOtherPackageJson = mergeNonDependencyChanges(fromPackageJson, currentPackageJson, toPackageJson); + + let finalMergedPackageJson = clone(mergedOtherPackageJson); + dependencyKeys.forEach(dependencyKey => { + finalMergedPackageJson[dependencyKey] = mergedDependenciesPackageJson[dependencyKey]; + }); + + return stringify(finalMergedPackageJson); +}; diff --git a/src/index.js b/src/index.js index 7ac775d8f..9b06e2b72 100644 --- a/src/index.js +++ b/src/index.js @@ -1,9 +1,11 @@ 'use strict'; +const fs = require('fs'); const getPackageVersion = require('./get-package-version'); const getProjectVersion = require('./get-project-version'); const getTagVersion = require('./get-tag-version'); const getProjectType = require('./get-project-type'); +const autoMergePackageJson = require('./auto-merge-package-json'); const gitDiffApply = require('git-diff-apply'); const run = require('./run'); @@ -40,6 +42,21 @@ module.exports = function emberCliUpdate(options) { remoteUrl, startTag, endTag, - ignoreConflicts + ignoreConflicts, + ignoredFiles: ['package.json'] + }).then(results => { + let myPackageJson = fs.readFileSync('package.json', 'utf8'); + let fromPackageJson = results.from['package.json']; + let toPackageJson = results.to['package.json']; + + let newPackageJson = autoMergePackageJson(myPackageJson, fromPackageJson, toPackageJson); + + fs.writeFileSync('package.json', newPackageJson); + + run('git add package.json'); + }).catch(err => { + require('debug')('ember-cli-update')(err); + + throw err; }); }; diff --git a/test/fixtures/package.json/adds-dependencies/from/package.json b/test/fixtures/package.json/adds-dependencies/from/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/test/fixtures/package.json/adds-dependencies/from/package.json @@ -0,0 +1 @@ +{} diff --git a/test/fixtures/package.json/adds-dependencies/my/package.json b/test/fixtures/package.json/adds-dependencies/my/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/test/fixtures/package.json/adds-dependencies/my/package.json @@ -0,0 +1 @@ +{} diff --git a/test/fixtures/package.json/adds-dependencies/result/package.json b/test/fixtures/package.json/adds-dependencies/result/package.json new file mode 100644 index 000000000..c33216fec --- /dev/null +++ b/test/fixtures/package.json/adds-dependencies/result/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "a": "2" + } +} diff --git a/test/fixtures/package.json/adds-dependencies/to/package.json b/test/fixtures/package.json/adds-dependencies/to/package.json new file mode 100644 index 000000000..c33216fec --- /dev/null +++ b/test/fixtures/package.json/adds-dependencies/to/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "a": "2" + } +} diff --git a/test/fixtures/package.json/preserves-ordering/from/package.json b/test/fixtures/package.json/preserves-ordering/from/package.json new file mode 100644 index 000000000..f639e109f --- /dev/null +++ b/test/fixtures/package.json/preserves-ordering/from/package.json @@ -0,0 +1,5 @@ +{ + "version": "1.0.0", + "name": "bar", + "baz": false +} diff --git a/test/fixtures/package.json/preserves-ordering/my/package.json b/test/fixtures/package.json/preserves-ordering/my/package.json new file mode 100644 index 000000000..f639e109f --- /dev/null +++ b/test/fixtures/package.json/preserves-ordering/my/package.json @@ -0,0 +1,5 @@ +{ + "version": "1.0.0", + "name": "bar", + "baz": false +} diff --git a/test/fixtures/package.json/preserves-ordering/result/package.json b/test/fixtures/package.json/preserves-ordering/result/package.json new file mode 100644 index 000000000..cf1328dcf --- /dev/null +++ b/test/fixtures/package.json/preserves-ordering/result/package.json @@ -0,0 +1,6 @@ +{ + "foo": true, + "version": "1.0.0", + "name": "bar", + "baz": false +} diff --git a/test/fixtures/package.json/preserves-ordering/to/package.json b/test/fixtures/package.json/preserves-ordering/to/package.json new file mode 100644 index 000000000..cf1328dcf --- /dev/null +++ b/test/fixtures/package.json/preserves-ordering/to/package.json @@ -0,0 +1,6 @@ +{ + "foo": true, + "version": "1.0.0", + "name": "bar", + "baz": false +} diff --git a/test/fixtures/package.json/removes-dependencies/from/package.json b/test/fixtures/package.json/removes-dependencies/from/package.json new file mode 100644 index 000000000..9422a5a38 --- /dev/null +++ b/test/fixtures/package.json/removes-dependencies/from/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "a": "1" + } +} diff --git a/test/fixtures/package.json/removes-dependencies/my/package.json b/test/fixtures/package.json/removes-dependencies/my/package.json new file mode 100644 index 000000000..9422a5a38 --- /dev/null +++ b/test/fixtures/package.json/removes-dependencies/my/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "a": "1" + } +} diff --git a/test/fixtures/package.json/removes-dependencies/result/package.json b/test/fixtures/package.json/removes-dependencies/result/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/test/fixtures/package.json/removes-dependencies/result/package.json @@ -0,0 +1 @@ +{} diff --git a/test/fixtures/package.json/removes-dependencies/to/package.json b/test/fixtures/package.json/removes-dependencies/to/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/test/fixtures/package.json/removes-dependencies/to/package.json @@ -0,0 +1 @@ +{} diff --git a/test/fixtures/package.json/updates-dependencies/from/package.json b/test/fixtures/package.json/updates-dependencies/from/package.json new file mode 100644 index 000000000..9422a5a38 --- /dev/null +++ b/test/fixtures/package.json/updates-dependencies/from/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "a": "1" + } +} diff --git a/test/fixtures/package.json/updates-dependencies/my/package.json b/test/fixtures/package.json/updates-dependencies/my/package.json new file mode 100644 index 000000000..9422a5a38 --- /dev/null +++ b/test/fixtures/package.json/updates-dependencies/my/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "a": "1" + } +} diff --git a/test/fixtures/package.json/updates-dependencies/result/package.json b/test/fixtures/package.json/updates-dependencies/result/package.json new file mode 100644 index 000000000..c33216fec --- /dev/null +++ b/test/fixtures/package.json/updates-dependencies/result/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "a": "2" + } +} diff --git a/test/fixtures/package.json/updates-dependencies/to/package.json b/test/fixtures/package.json/updates-dependencies/to/package.json new file mode 100644 index 000000000..c33216fec --- /dev/null +++ b/test/fixtures/package.json/updates-dependencies/to/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "a": "2" + } +} diff --git a/test/unit/auto-merge-package-json-test.js b/test/unit/auto-merge-package-json-test.js new file mode 100644 index 000000000..8f8e37927 --- /dev/null +++ b/test/unit/auto-merge-package-json-test.js @@ -0,0 +1,42 @@ +'use strict'; + +const expect = require('chai').expect; +const fs = require('fs'); +const path = require('path'); +const autoMergePackageJson = require('../../src/auto-merge-package-json'); + +const fixturesPath = 'test/fixtures/package.json'; + +function getFixtures(fixturesDir) { + let fixtures = path.join(fixturesPath, fixturesDir); + return fs.readdirSync(fixtures).reduce((result, name) => { + result[name] = fs.readFileSync(path.join(fixtures, name, 'package.json'), 'utf8'); + return result; + }, {}); +} + +describe('Unit - autoMergePackageJson', function() { + function forEachDir(callback) { + fs.readdirSync(fixturesPath).forEach(fixtureDir => { + let it = global.it; + if (fixtureDir.indexOf('_') === 0) { + it = it.only; + } + callback(it, fixtureDir); + }); + } + + forEachDir((it, fixturesDir) => { + it(fixturesDir, function() { + let fixtures = getFixtures(fixturesDir); + let from = fixtures.from; + let my = fixtures.my; + let result = fixtures.result; + let to = fixtures.to; + + let actual = autoMergePackageJson(my, from, to); + + expect(actual).to.equal(result); + }); + }); +});