UNPKG

chg

Version:

simple changelog/release history manager

237 lines (189 loc) 6.74 kB
/* * chg * https://github.com/heff/chg * * Copyright (c) 2014 heff * Licensed under the Apache license. */ 'use strict'; var chg = module.exports = {}; var fs = require('fs'); var shell = require('shelljs'); var moment = require('moment'); var changeLogFile = 'CHANGELOG.md'; var unreleasedTitle = '## HEAD (Unreleased)\n'; var noItems = '_(none)_'; var divider = '---\n\n'; var dividerRe = /\n\-{3,}\n\n/; var ERR_NO_CHANGELOG = changeLogFile+' does not exist. Change to the directory where it does exist, or run `init` to create one in the current directory.'; function noopCallback(){} function getChangeLog(){ if (!fs.existsSync(changeLogFile)) { return null; } return fs.readFileSync(changeLogFile, 'utf8'); } /** * Callbacks are standard "node style" * @callback chgCallback * @param {object} Error * @param {string} responseMessage */ /** * Creates CHANGELOG.md if it does not exist. * @param {object} options * @param {chgCallback} callback - successfull returns the new changelog file. * @returns {string} changelog filename */ chg.init = function(options, callback){ options = options || {}; callback = callback || noopCallback; if (fs.existsSync(changeLogFile)){ var err = new Error(changeLogFile + ' already exists'); callback(err); return err; } var contents = 'CHANGELOG\n=========\n\n'; contents += unreleasedTitle + noItems + '\n\n' + divider; fs.writeFileSync(changeLogFile, contents, 'utf8'); callback(null, changeLogFile); return changeLogFile; }; /** * Deletes CHANGELOG.md * @param {object} options * @param {chgCallback} callback - success returns the deleted changelog file * @returns {string} changelog filename */ chg.delete = function(options, callback){ options = options || {}; callback = callback || noopCallback; if (!fs.existsSync(changeLogFile)) { var err = new Error(changeLogFile+' does not exist'); callback(err); return err; } shell.rm(changeLogFile); callback(null, changeLogFile); return changeLogFile; }; /** * Add a new line to CHANGELOG.md * @param {string} line - the new line to be added * @param {object} options * @param {chgCallback} callback - success returns the newly added line * @returns {string} the new line added */ chg.add = function(line, options, callback){ var contents, sections, top, divider; options = options || {}; callback = callback || noopCallback; // get existing contents contents = getChangeLog(); if (!contents) { var err = new Error(ERR_NO_CHANGELOG); callback(err); return err; } divider = contents.match(dividerRe); // if 'noItems' is there, remove it contents = contents.replace(unreleasedTitle + noItems + '\n', unreleasedTitle); // split on the divider including preceding newline sections = contents.split(dividerRe); // add new line to the unreleased section top = sections[0] + '* ' + line + '\n'; // combine new contents and write file contents = top + divider + sections[1]; fs.writeFileSync(changeLogFile, contents, 'utf8'); callback(null, line); return line; }; /** * Creates a new release by bumping the version, moving everything in the unreleased head to a new section headed by the new version (and creating a new, empty head) * @param {string} version - the new release (this will be the title for the changelog) * @param {object} options * @param {string} options.date - date of the new release (defaults to the current date) * @param {string} options.version - release type to create (if version param is null) * @param {chgCallback} callback - success returns an object containing the new changes * @returns {object} changeData * @returns {object} changeData.title - new title to be created * @returns {object} changeData.changes - list of changes added under the new title * @returns {object} changeData.changelog - entire changelog contents */ chg.release = function(version, options, callback){ var date, contents, changes, title, err; callback = callback || noopCallback; options = options || {}; date = options.date || moment().format('YYYY-MM-DD'); version = version || options.version || null; if (!version) { err = new Error('Version required'); callback(err); return err; } // get existing contents contents = getChangeLog(); if (!contents) { err = new Error(ERR_NO_CHANGELOG); callback(err); return err; } // get everything after the unreleased title changes = contents.split(unreleasedTitle)[1]; // get only the unreleased changes changes = changes.split('\n\n')[0]; // replace unreleased changes with noItems contents = contents.replace(changes, noItems); // build new release title title = '##' + ' ' + version + ' ('+ date +')\n'; // add title at the top of the release section contents = contents.replace(dividerRe, function (divider) { return divider + title + changes + '\n\n'; }); fs.writeFileSync(changeLogFile, contents, 'utf8'); var changeData = { title: title, changes: changes, changelog: contents }; callback(null, changeData); return changeData; }; /** * Finds a section by title in the changelog, and if it exists, returns the information. If no title is found, an empty object is returned. * @param {string} title - the title of the desired section * @param {object} options * @param {chgCallback} callback - success returns an object containing the requested section * @returns {object} section * @returns {string} section.title - title of the section found * @returns {array} section.changes - changes in the section * @returns {string} section.changesRaw - raw text of the changes in the section */ chg.find = function(title, options, callback) { options = options || {}; callback = callback || noopCallback; var err; if (!title) { err = new Error('Title required'); callback(err); return err; } var contents = getChangeLog(); if (!contents) { err = new Error(ERR_NO_CHANGELOG); callback(err); return err; } // Get everything from the title to the first empty line var regex = new RegExp('(## '+ title +'.*)\n((.+\n)+)'); var result = regex.exec(contents); if (!result) { callback(null, {}); return {}; } // Turn the changes into an array before handing them back to the callback var changeArray = result[2].split('\n'); var resObj = { title: result[1], changesRaw: result[2], changes: changeArray }; callback(null, resObj); return resObj; };