repo_description: :trophy: Automate versioning and CHANGELOG generation, with semver.org and conventionalcommits.org
repo_topics: changelog cli git hacktoberfest history release semantic standard tag tags version versioning
code: 'use strict'
  
  const chalk = require('chalk')
  const checkpoint = require('../checkpoint')
  const conventionalRecommendedBump = require('conventional-recommended-bump')
  const figures = require('figures')
  const fs = require('fs')
  const DotGitignore = require('dotgitignore')
  const path = require('path')
  const presetLoader = require('../preset-loader')
  const runLifecycleScript = require('../run-lifecycle-script')
  const semver = require('semver')
  const writeFile = require('../write-file')
  const { resolveUpdaterObjectFromArgument } = require('../updaters')
  let configsToUpdate = {}
  
  async function Bump (args, version) {
    // reset the cache of updated config files each
    // time we perform the version bump step.
    configsToUpdate = {}
  
    if (args.skip.bump) return version
    let newVersion = version
    await runLifecycleScript(args, 'prerelease')
    const stdout = await runLifecycleScript(args, 'prebump')
    if (stdout && stdout.trim().length) args.releaseAs = stdout.trim()
    const release = await bumpVersion(args.releaseAs, version, args)
    if (!args.firstRelease) {
      const releaseType = getReleaseType(args.prerelease, release.releaseType, version)
      newVersion = semver.valid(releaseType) || semver.inc(version, releaseType, args.prerelease)
      updateConfigs(args, newVersion)
    } else {
      checkpoint(args, 'skip version bump on first release', [], chalk.red(figures.cross))
    }
    await runLifecycleScript(args, 'postbump')
    return newVersion
  }
  
  Bump.getUpdatedConfigs = function () {
    return configsToUpdate
  }
  
  function getReleaseType (prerelease, expectedReleaseType, currentVersion) {
    if (isString(prerelease)) {
      if (isInPrerelease(currentVersion)) {
        if (shouldContinuePrerelease(currentVersion, expectedReleaseType) ||
          getTypePriority(getCurrentActiveType(currentVersion)) > getTypePriority(expectedReleaseType)
        ) {
          return 'prerelease'
        }
      }
  
      return 'pre' + expectedReleaseType
    } else {
      return expectedReleaseType
    }
  }
  
  function isString (val) {
    return typeof val === 'string'
  }
  
  /**
   * if a version is currently in pre-release state,
   * and if it current in-pre-release type is same as expect type,
   * it should continue the pre-release with the same type
   *
   * @param version
   * @param expectType
   * @return {boolean}
   */
  function shouldContinuePrerelease (version, expectType) {
    return getCurrentActiveType(version) === expectType
  }
  
  function isInPrerelease (version) {
    return Array.isArray(semver.prerelease(version))
  }
  
  const TypeList = ['major', 'minor', 'patch'].reverse()
  
  /**
   * extract the in-pre-release type in target version
   *
   * @param version
   * @return {string}
   */
  function getCurrentActiveType (version) {
    const typelist = TypeList
    for (let i = 0; i < typelist.length; i++) {
      if (semver[typelist[i]](version)) {
        return typelist[i]
      }
    }
  }
  
  /**
   * calculate the priority of release type,
   * major - 2, minor - 1, patch - 0
   *
   * @param type
   * @return {number}
   */
  function getTypePriority (type) {
    return TypeList.indexOf(type)
  }
  
  function bumpVersion (releaseAs, currentVersion, args) {
    return new Promise((resolve, reject) => {
      if (releaseAs) {
        return resolve({
          releaseType: releaseAs
        })
      } else {
        const presetOptions = presetLoader(args)
        if (typeof presetOptions === 'object') {
          if (semver.lt(currentVersion, '1.0.0')) presetOptions.preMajor = true
        }
        conventionalRecommendedBump({
          debug: args.verbose && console.info.bind(console, 'conventional-recommended-bump'),
          preset: presetOptions,
          path: args.path,
          tagPrefix: args.tagPrefix,
          lernaPackage: args.lernaPackage
        }, function (err, release) {
          if (err) return reject(err)
          else return resolve(release)
        })
      }
    })
  }
  
  /**
   * attempt to update the version number in provided `bumpFiles`
   * @param args config object
   * @param newVersion version number to update to.
   * @return void
   */
  function updateConfigs (args, newVersion) {
    const dotgit = DotGitignore()
    args.bumpFiles.forEach(function (bumpFile) {
      const updater = resolveUpdaterObjectFromArgument(bumpFile)
      if (!updater) {
        return
      }
      const configPath = path.resolve(process.cwd(), updater.filename)
      try {
        if (dotgit.ignore(configPath)) return
        const stat = fs.lstatSync(configPath)
  
        if (!stat.isFile()) return
        const contents = fs.readFileSync(configPath, 'utf8')
        checkpoint(
          args,
          'bumping version in ' + updater.filename + ' from %s to %s',
          [updater.updater.readVersion(contents), newVersion]
        )
        writeFile(
          args,
          configPath,
          updater.updater.writeVersion(contents, newVersion)
        )
        // flag any config files that we modify the version # for
        // as having been updated.
        configsToUpdate[updater.filename] = true
      } catch (err) {
        if (err.code !== 'ENOENT') console.warn(err.message)
      }
    })
  }
  
  module.exports = Bump
  