Skip to content

Instantly share code, notes, and snippets.

@estliberitas
Created September 17, 2015 11:31
Show Gist options
  • Save estliberitas/5ba8f04432d7e2671391 to your computer and use it in GitHub Desktop.
Save estliberitas/5ba8f04432d7e2671391 to your computer and use it in GitHub Desktop.
Update Changelog in README.md when dependencies are updated in package.json
#!/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