All files / lib/resource-types keep-a-changelog.js

95.41% Statements 125/131
100% Branches 16/16
75% Functions 6/8
95.41% Lines 125/131

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 1321x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 2x 2x 2x       2x 2x 2x 2x 2x 1x 1x 1x 1x 2x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 2x 2x 2x       2x 2x 2x 2x 1x 1x 1x 1x 2x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 5x 5x 3x 3x 3x 3x 3x 1x 3x 1x 1x 1x 3x 3x 3x 3x 5x 5x 1x 5x 4x 4x 5x 5x 5x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 4x 4x 4x 4x 4x 4x 1x 1x 1x 4x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x  
"use strict";
import { parser, Release } from 'keep-a-changelog';
import fs from 'fs';
import stringFormat from 'string-format';
 
/**
 * Set a release version number to the latest release within the changelog, with date set to current date.
 * The changelog follows keep a changelog format https://keepachangelog.com .
 *
 * @param {String} version: version value to set
 * @param {Object} resource: resource configuration which contains type, path, and params
 * @param {Object} opts: optional settings
 *   - dryRun: when true, changelog file won't be modified
 * @param {Function} cb: standard cb(err, result) callback
 */
function setReleaseVersion(version, resource, opts, cb) {
  const changelog = parser(fs.readFileSync(resource.path, 'UTF-8'));
  changelog.tagNameBuilder = function (release) {
    // Use tagFormat option to format the version to be used as SCM tag
    const formattedVersion = stringFormat(opts.tagFormat, { version: release.version });
    return formattedVersion;
  };
  const latestRelease = changelog.releases[0];
  latestRelease.setVersion(version);
  latestRelease.setDate(new Date());
  if (!opts.dryRun) {
    fs.writeFile(resource.path, changelog.toString(), cb);
  } else {
    cb();
  }
}
 
/**
 * Add a new release to the changelog having the post-release version and an unreleased date.
 * The changelog follows keep a changelog format https://keepachangelog.com .
 *
 * @param {String} version: version value to set
 * @param {Object} resource: resource configuration which contains type, path, and params
 * @param {Object} opts: optional settings
 *   - dryRun: when true, changelog file won't be modified
 * @param {Function} cb: standard cb(err, result) callback
 */
function setPostReleaseVersion(version, resource, opts, cb) {
  const changelog = parser(fs.readFileSync(resource.path, 'UTF-8'));
  changelog.tagNameBuilder = function (release) {
    // Use tagFormat option to format the version to be used as SCM tag
    const formattedVersion = stringFormat(opts.tagFormat, { version: release.version });
    return formattedVersion;
  };
  const postRelease = new Release();
  changelog.addRelease(postRelease);
  if (!opts.dryRun) {
    fs.writeFile(resource.path, changelog.toString(), cb);
  } else {
    cb();
  }
}
 
/**
 * Get version value from the changelog's latest release.
 * The changelog follows keep a changelog format https://keepachangelog.com .
 * It's common for the latest positioned version value for keep a changelog to be set to 'Unreleased'
 * in order to indicate that it's the one that's currently in progress.
 * Hence we need to handle two scenarios here:
 * - when latest release has a version number, then use that as the current version
 * - when latest release is 'Unreleased' (version is undefined after parsing), then use the previous release
 * - otherwise defaults to '0.0.0'
 *
 * @param {Object} resource: resource configuration which contains type, path, and params
 * @param {Function} cb: standard cb(err, result) callback
 */
function getVersion(resource, cb) {
  function parseCb(err, changelog) {
    let version;
    if (!err) {
      const latestRelease = changelog.releases[0];
      const latestReleaseMinusOne = changelog.releases[1];
      if (latestRelease && latestRelease.version) {
        version = latestRelease.version;
      } else if (latestReleaseMinusOne && latestReleaseMinusOne.version) {
        version = latestReleaseMinusOne.version;
      } else {
        version = '0.0.0';
      }          
    }
    cb(err, version);
  }
  function readCb(err, result) {
    if (err) {
      cb(err);
    } else {
      _parseWithValidation(result, parseCb);
    }
  }
  fs.readFile(resource.path, 'UTF-8', readCb);
}
 
/**
 * Parse changelog content using keep-a-changelog parser.
 * This function also serves as a validator, where an error will be thrown.
 * 
 * Due to the way keep-a-changelog parser works, it throws error directly
 * instead of passing it to callback, hence we rethrow the error with a more
 * descriptive message.
 * 
 * Even though the current implementation wouldn't pass an error to callback,
 * we still keep the cb parameter for consistency and future-proofing.
 * 
 * @param {String} content: changelog content
 * @param {Function} cb: standard cb(err, result) callback
 */
function _parseWithValidation(content, cb) {
  try {
    const changelog = parser(content);
    console.dir(changelog);
    cb(null, changelog);
  } catch (err) {
    const message = `Failed to parse changelog. Please fix the changelog following the keep a changelog format https://keepachangelog.com . Error: ${err.message}`;
    throw (new Error(message));
  }
}
 
const exports = {
  setReleaseVersion: setReleaseVersion,
  setPostReleaseVersion: setPostReleaseVersion,
  getVersion: getVersion
};
 
export {
  exports as default
};