merge-config.js 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. 'use strict'
  2. const dateFormat = require('dateformat')
  3. const getPkgRepo = require('get-pkg-repo')
  4. const gitSemverTags = require('git-semver-tags')
  5. const normalizePackageData = require('normalize-package-data')
  6. const Q = require('q')
  7. let gitRemoteOriginUrl
  8. try {
  9. gitRemoteOriginUrl = require('git-remote-origin-url')
  10. } catch (err) {
  11. gitRemoteOriginUrl = function () {
  12. return Q.reject(err)
  13. }
  14. }
  15. const readPkg = require('read-pkg')
  16. const readPkgUp = require('read-pkg-up')
  17. const URL = require('url').URL
  18. const _ = require('lodash')
  19. const rhosts = /github|bitbucket|gitlab/i
  20. function semverTagsPromise (options) {
  21. return Q.Promise(function (resolve, reject) {
  22. gitSemverTags({ lernaTags: !!options.lernaPackage, package: options.lernaPackage, tagPrefix: options.tagPrefix, skipUnstable: options.skipUnstable }, function (err, result) {
  23. if (err) {
  24. reject(err)
  25. } else {
  26. resolve(result)
  27. }
  28. })
  29. })
  30. }
  31. function guessNextTag (previousTag, version) {
  32. if (previousTag) {
  33. if (previousTag[0] === 'v' && version[0] !== 'v') {
  34. return 'v' + version
  35. }
  36. if (previousTag[0] !== 'v' && version[0] === 'v') {
  37. return version.replace(/^v/, '')
  38. }
  39. return version
  40. }
  41. if (version[0] !== 'v') {
  42. return 'v' + version
  43. }
  44. return version
  45. }
  46. function mergeConfig (options, context, gitRawCommitsOpts, parserOpts, writerOpts, gitRawExecOpts) {
  47. let configPromise
  48. let pkgPromise
  49. context = context || {}
  50. gitRawCommitsOpts = gitRawCommitsOpts || {}
  51. gitRawExecOpts = gitRawExecOpts || {}
  52. const rtag = options && options.tagPrefix ? new RegExp(`tag:\\s*[=]?${options.tagPrefix}(.+?)[,)]`, 'gi') : /tag:\s*[v=]?(.+?)[,)]/gi
  53. options = _.merge({
  54. pkg: {
  55. transform: function (pkg) {
  56. return pkg
  57. }
  58. },
  59. append: false,
  60. releaseCount: 1,
  61. skipUnstable: false,
  62. debug: function () {},
  63. transform: function (commit, cb) {
  64. if (_.isString(commit.gitTags)) {
  65. const match = rtag.exec(commit.gitTags)
  66. rtag.lastIndex = 0
  67. if (match) {
  68. commit.version = match[1]
  69. }
  70. }
  71. if (commit.committerDate) {
  72. commit.committerDate = dateFormat(commit.committerDate, 'yyyy-mm-dd', true)
  73. }
  74. cb(null, commit)
  75. },
  76. lernaPackage: null
  77. }, options)
  78. options.warn = options.warn || options.debug
  79. if (options.config) {
  80. if (_.isFunction(options.config)) {
  81. configPromise = Q.nfcall(options.config)
  82. } else {
  83. configPromise = Q(options.config)
  84. }
  85. }
  86. if (options.pkg) {
  87. if (options.pkg.path) {
  88. pkgPromise = Q(readPkg(options.pkg.path))
  89. } else {
  90. pkgPromise = Q(readPkgUp())
  91. }
  92. }
  93. const gitRemoteOriginUrlPromise = Q(gitRemoteOriginUrl())
  94. return Q.allSettled([configPromise, pkgPromise, semverTagsPromise(options), gitRemoteOriginUrlPromise])
  95. .spread(function (configObj, pkgObj, tagsObj, gitRemoteOriginUrlObj) {
  96. let config
  97. let pkg
  98. let fromTag
  99. let repo
  100. let hostOpts
  101. let gitSemverTags = []
  102. if (configPromise) {
  103. if (configObj.state === 'fulfilled') {
  104. config = configObj.value
  105. } else {
  106. options.warn('Error in config' + configObj.reason.toString())
  107. config = {}
  108. }
  109. } else {
  110. config = {}
  111. }
  112. context = _.assign(context, config.context)
  113. if (options.pkg) {
  114. if (pkgObj.state === 'fulfilled') {
  115. if (options.pkg.path) {
  116. pkg = pkgObj.value
  117. } else {
  118. pkg = pkgObj.value.pkg || {}
  119. }
  120. pkg = options.pkg.transform(pkg)
  121. } else if (options.pkg.path) {
  122. options.warn(pkgObj.reason.toString())
  123. }
  124. }
  125. if ((!pkg || !pkg.repository || !pkg.repository.url) && gitRemoteOriginUrlObj.state === 'fulfilled') {
  126. pkg = pkg || {}
  127. pkg.repository = pkg.repository || {}
  128. pkg.repository.url = gitRemoteOriginUrlObj.value
  129. normalizePackageData(pkg)
  130. }
  131. if (pkg) {
  132. context.version = context.version || pkg.version
  133. try {
  134. repo = getPkgRepo(pkg)
  135. } catch (err) {
  136. repo = {}
  137. }
  138. if (repo.browse) {
  139. const browse = repo.browse()
  140. if (!context.host) {
  141. if (repo.domain) {
  142. const parsedBrowse = new URL(browse)
  143. if (parsedBrowse.origin.indexOf('//') !== -1) {
  144. context.host = parsedBrowse.protocol + '//' + repo.domain
  145. } else {
  146. context.host = parsedBrowse.protocol + repo.domain
  147. }
  148. } else {
  149. context.host = null
  150. }
  151. }
  152. context.owner = context.owner || repo.user || ''
  153. context.repository = context.repository || repo.project
  154. if (repo.host && repo.project && repo.user) {
  155. context.repoUrl = browse
  156. } else {
  157. context.repoUrl = context.host
  158. }
  159. }
  160. context.packageData = pkg
  161. }
  162. context.version = context.version || ''
  163. if (tagsObj.state === 'fulfilled') {
  164. gitSemverTags = context.gitSemverTags = tagsObj.value
  165. fromTag = gitSemverTags[options.releaseCount - 1]
  166. const lastTag = gitSemverTags[0]
  167. if (lastTag === context.version || lastTag === 'v' + context.version) {
  168. if (options.outputUnreleased) {
  169. context.version = 'Unreleased'
  170. } else {
  171. options.outputUnreleased = false
  172. }
  173. }
  174. }
  175. if (!_.isBoolean(options.outputUnreleased)) {
  176. options.outputUnreleased = true
  177. }
  178. if (context.host && (!context.issue || !context.commit || !parserOpts || !parserOpts.referenceActions)) {
  179. let type
  180. if (context.host) {
  181. const match = context.host.match(rhosts)
  182. if (match) {
  183. type = match[0]
  184. }
  185. } else if (repo && repo.type) {
  186. type = repo.type
  187. }
  188. if (type) {
  189. hostOpts = require('../hosts/' + type)
  190. context = _.assign({
  191. issue: hostOpts.issue,
  192. commit: hostOpts.commit
  193. }, context)
  194. } else {
  195. options.warn('Host: "' + context.host + '" does not exist')
  196. hostOpts = {}
  197. }
  198. } else {
  199. hostOpts = {}
  200. }
  201. if (context.resetChangelog) {
  202. fromTag = null
  203. }
  204. gitRawCommitsOpts = _.assign({
  205. format: '%B%n-hash-%n%H%n-gitTags-%n%d%n-committerDate-%n%ci',
  206. from: fromTag,
  207. merges: false,
  208. debug: options.debug
  209. },
  210. config.gitRawCommitsOpts,
  211. gitRawCommitsOpts
  212. )
  213. if (options.append) {
  214. gitRawCommitsOpts.reverse = gitRawCommitsOpts.reverse || true
  215. }
  216. parserOpts = _.assign(
  217. {}, config.parserOpts, {
  218. warn: options.warn
  219. },
  220. parserOpts)
  221. if (hostOpts.referenceActions && parserOpts) {
  222. parserOpts.referenceActions = hostOpts.referenceActions
  223. }
  224. if (_.isEmpty(parserOpts.issuePrefixes) && hostOpts.issuePrefixes) {
  225. parserOpts.issuePrefixes = hostOpts.issuePrefixes
  226. }
  227. writerOpts = _.assign({
  228. finalizeContext: function (context, writerOpts, filteredCommits, keyCommit, originalCommits) {
  229. const firstCommit = originalCommits[0]
  230. const lastCommit = originalCommits[originalCommits.length - 1]
  231. const firstCommitHash = firstCommit ? firstCommit.hash : null
  232. const lastCommitHash = lastCommit ? lastCommit.hash : null
  233. if ((!context.currentTag || !context.previousTag) && keyCommit) {
  234. const match = /tag:\s*(.+?)[,)]/gi.exec(keyCommit.gitTags)
  235. const currentTag = context.currentTag
  236. context.currentTag = currentTag || match ? match[1] : null
  237. const index = gitSemverTags.indexOf(context.currentTag)
  238. // if `keyCommit.gitTags` is not a semver
  239. if (index === -1) {
  240. context.currentTag = currentTag || null
  241. } else {
  242. const previousTag = context.previousTag = gitSemverTags[index + 1]
  243. if (!previousTag) {
  244. if (options.append) {
  245. context.previousTag = context.previousTag || firstCommitHash
  246. } else {
  247. context.previousTag = context.previousTag || lastCommitHash
  248. }
  249. }
  250. }
  251. } else {
  252. context.previousTag = context.previousTag || gitSemverTags[0]
  253. if (context.version === 'Unreleased') {
  254. if (options.append) {
  255. context.currentTag = context.currentTag || lastCommitHash
  256. } else {
  257. context.currentTag = context.currentTag || firstCommitHash
  258. }
  259. } else if (!context.currentTag) {
  260. if (options.lernaPackage) {
  261. context.currentTag = options.lernaPackage + '@' + context.version
  262. } else if (options.tagPrefix) {
  263. context.currentTag = options.tagPrefix + context.version
  264. } else {
  265. context.currentTag = guessNextTag(gitSemverTags[0], context.version)
  266. }
  267. }
  268. }
  269. if (!_.isBoolean(context.linkCompare) && context.previousTag && context.currentTag) {
  270. context.linkCompare = true
  271. }
  272. return context
  273. },
  274. debug: options.debug
  275. },
  276. config.writerOpts, {
  277. reverse: options.append,
  278. doFlush: options.outputUnreleased
  279. },
  280. writerOpts
  281. )
  282. return {
  283. options: options,
  284. context: context,
  285. gitRawCommitsOpts: gitRawCommitsOpts,
  286. parserOpts: parserOpts,
  287. writerOpts: writerOpts,
  288. gitRawExecOpts: gitRawExecOpts
  289. }
  290. })
  291. }
  292. module.exports = mergeConfig