Created
September 17, 2015 11:31
-
-
Save estliberitas/5ba8f04432d7e2671391 to your computer and use it in GitHub Desktop.
Update Changelog in README.md when dependencies are updated in package.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env node | |
/** | |
* In private projects I usually keep changelog in README.md. | |
* | |
* It looks like: | |
* | |
* ## Changelog | |
* | |
* ### Version | |
* | |
* DESCRIPTION | |
* | |
* * feature1 | |
* * feature2 | |
* * Updated dependencies: | |
* * Updated `async` to `1.4.2` | |
* * Added `redis@1.0.0` | |
* * Removed `socket.io` | |
* | |
* The idea is dependency updates are reflected | |
* in changelog too in the end of each version | |
* description. | |
* | |
* This script assumes only dependencies | |
* are changed in package.json file and are | |
* not committed yet. | |
* | |
* What it does: | |
* - Updates changelog record for given version | |
* with dependencies update info | |
* - Commits package.json using Angular commit style: | |
* chore(npm): update dependencies | |
* - Updates package.json with new version (does not | |
* commit though) | |
* | |
* Run: | |
* changelog-add-dependencies.js [major|minor|patch|d.d.d-s] | |
*/ | |
var execSync = require('child_process').execSync; | |
var fs = require('fs'); | |
var path = require('path'); | |
var util = require('util'); | |
var PACKAGE_JSON_PATH = path.resolve(process.cwd(), 'package.json'); | |
var PACKAGE_JSON = JSON.parse(fs.readFileSync(PACKAGE_JSON_PATH).toString('utf8')); | |
var VERSION = detectVersion(process.argv[2] || 'patch'); | |
var CHANGELOG_LINE = '## Changelog\n'; | |
var VERSION_LINE = '### ' + VERSION + '\n'; | |
var stdout = execSync('git diff package.json | grep -E \'^[-+] \'').toString('utf8').split('\n').filter(Boolean); | |
var mapped = stdout.map(extract).reduce(group, []); | |
var markdown = mapped.filter(equalVersion).sort(byPackage).map(toMarkdown); | |
markdown.unshift('* Updated dependencies:'); | |
markdown.push(''); | |
insertInfo(markdown); | |
function extract(line) { | |
var match = line.match(/^([-+])\s+"([^"]+)": "\^([^"]+)",?$/); | |
return { | |
isNew: match[1] === '+', | |
packageName: match[2], | |
version: match[3] | |
}; | |
} | |
function group(out, dep) { | |
var rec; | |
if (!(rec = getByPackage(out, dep.packageName))) { | |
rec = { | |
packageName: dep.packageName | |
}; | |
out.push(rec); | |
} | |
if (dep.isNew) { | |
rec.new = dep.version; | |
} | |
else { | |
rec.old = dep.version; | |
} | |
return out; | |
} | |
function equalVersion(rec) { | |
return rec.new !== rec.old; | |
} | |
function getByPackage(arr, packageName) { | |
var i = 0; | |
var rec; | |
while ((rec = arr[i++])) { | |
if (rec.packageName === packageName) { | |
return rec; | |
} | |
} | |
} | |
function byPackage(a, b) { | |
if (a.packageName < b.packageName) { | |
return -1; | |
} | |
else if (a.packageName === b.packageName) { | |
return 0; | |
} | |
return 1; | |
} | |
function toMarkdown(rec) { | |
var out = []; | |
if (rec.new && rec.old) { | |
out.push(util.format(' * Updated `%s` to `%s`', rec.packageName, rec.new)); | |
} | |
else if (rec.new) { | |
out.push(util.format(' * Added `%s@%s`', rec.packageName, rec.new)); | |
} | |
else if (rec.old) { | |
out.push(util.format(' * Removed `%s`', rec.packageName)); | |
} | |
return out.join('\n'); | |
} | |
function getIndex(str) { | |
return function reduce(out, line, idx) { | |
if (line.indexOf(str) === 0) { | |
return idx; | |
} | |
else { | |
return out; | |
} | |
}; | |
} | |
function insertInfo(lines) { | |
var readme = fs.readFileSync('./README.md').toString('utf8'); | |
var sections = readme.split(/\n## /); | |
var changelogIdx = sections.reduce(getIndex('Changelog'), -1); | |
var changelog; | |
var versions; | |
var versionIdx = -1; | |
var version; | |
var depIdx = -1; | |
if (~changelogIdx) { | |
changelog = sections[changelogIdx]; | |
versions = changelog.split('### '); | |
versionIdx = versions.reduce(getIndex(VERSION), -1); | |
} | |
if (~versionIdx) { | |
version = versions[versionIdx]; | |
depIdx = version.indexOf('* Updated dependencies:'); | |
} | |
if (~depIdx) { | |
version = version.substring(0, depIdx); | |
} | |
if (~versionIdx) { | |
versions[versionIdx] = version.replace(/\n*$/, '\n') + lines.join('\n') + '\n'; | |
sections[changelogIdx] = versions.join('### '); | |
readme = sections.join('\n## '); | |
} | |
else if (~changelogIdx) { | |
lines.unshift(VERSION_LINE); | |
var parts = changelog.split('\n'); | |
parts.splice(2, 0, lines.join('\n')); | |
sections[changelogIdx] = parts.join('\n'); | |
readme = sections.join('\n## '); | |
} | |
else { | |
lines.unshift(CHANGELOG_LINE, VERSION_LINE); | |
readme = sections.join('\n## ').replace(/\n$/, '') + '\n\n' + lines.join('\n'); | |
} | |
fs.unlinkSync('./README.md'); | |
fs.writeFileSync('./README.md', readme); | |
execSync('git reset ./*'); | |
execSync('git add package.json'); | |
execSync('git commit -m "chore(npm): update dependencies"'); | |
PACKAGE_JSON.version = VERSION; | |
fs.writeFileSync(PACKAGE_JSON_PATH, JSON.stringify(PACKAGE_JSON, null, ' ') + '\n'); | |
} | |
function detectVersion(version) { | |
if (/^\d+\.\d+\.\d+$/.test(version)) { | |
return version; | |
} | |
var idx = ['major', 'minor', 'patch'].indexOf(version); | |
if (!~idx) { | |
throw new Error('Wrong version identifier: ' + version); | |
} | |
var vvv = PACKAGE_JSON.version.replace(/-.*$/g, '').split('.').map(toInt); | |
function toInt(str) { | |
return parseInt(str, 10); | |
} | |
vvv[idx]++; | |
while (++idx < 3) { | |
vvv[idx] = 0; | |
} | |
return vvv.join('.'); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment