2019-04-13 17:14:41 +00:00
|
|
|
import consola from 'consola'
|
|
|
|
import execa from 'execa'
|
|
|
|
import groupBy from 'lodash/groupBy'
|
|
|
|
import sortBy from 'lodash/sortBy'
|
2019-04-15 12:20:54 +00:00
|
|
|
import uniq from 'lodash/uniq'
|
2019-04-13 17:14:41 +00:00
|
|
|
import { writeFile } from 'fs-extra'
|
|
|
|
|
|
|
|
const types = {
|
2019-05-21 19:10:15 +00:00
|
|
|
fix: { title: '🐛 Bug Fix' },
|
|
|
|
feat: { title: '🚀 Features' },
|
|
|
|
refactor: { title: '💅 Refactors' },
|
|
|
|
perf: { title: '🔥 Performance' },
|
|
|
|
examples: { title: '📝 Examples' },
|
|
|
|
chore: { title: '🏡 Chore' },
|
|
|
|
test: { title: '👓 Tests' }
|
2019-04-13 17:14:41 +00:00
|
|
|
}
|
|
|
|
|
2019-04-15 12:20:54 +00:00
|
|
|
const knownAuthors = [
|
|
|
|
'chopin',
|
|
|
|
'parsa',
|
|
|
|
'clark',
|
|
|
|
'galvez',
|
|
|
|
'lichter',
|
|
|
|
'molotkov',
|
|
|
|
'marrec',
|
|
|
|
'pim'
|
|
|
|
]
|
|
|
|
|
|
|
|
const isKnownAuthor = name => Boolean(knownAuthors.find(n => name.toLowerCase().includes(n)))
|
|
|
|
|
2019-04-13 17:14:41 +00:00
|
|
|
const allowedTypes = Object.keys(types)
|
|
|
|
|
|
|
|
async function main() {
|
|
|
|
// Get last git tag
|
|
|
|
const lastGitTag = await getLastGitTag()
|
|
|
|
|
2019-04-25 08:43:50 +00:00
|
|
|
// Get current branch
|
|
|
|
const currentGitBranch = await getCurrentGitBranch()
|
|
|
|
|
|
|
|
// Get all commits from last release to current branch
|
|
|
|
consola.log(`${currentGitBranch}...${lastGitTag}`)
|
|
|
|
let commits = await getGitDiff(currentGitBranch, lastGitTag)
|
2019-04-13 17:14:41 +00:00
|
|
|
|
|
|
|
// Parse commits as conventional commits
|
|
|
|
commits = parseCommits(commits)
|
|
|
|
|
|
|
|
// Filter commits
|
|
|
|
commits = commits.filter(c =>
|
|
|
|
allowedTypes.includes(c.type) &&
|
|
|
|
c.scope !== 'deps'
|
|
|
|
)
|
|
|
|
|
|
|
|
// Generate markdown
|
|
|
|
const markdown = generateMarkDown(commits)
|
|
|
|
|
|
|
|
process.stdout.write('\n\n' + markdown + '\n\n')
|
|
|
|
await writeFile('CHANGELOG.md', markdown, 'utf-8')
|
|
|
|
}
|
|
|
|
|
|
|
|
function execCommand(cmd, args) {
|
|
|
|
return execa(cmd, args).then(r => r.stdout)
|
|
|
|
}
|
|
|
|
|
|
|
|
async function getLastGitTag() {
|
2019-04-25 09:46:38 +00:00
|
|
|
const r = await execCommand('git', ['--no-pager', 'tag', '-l']).then(r => r.split('\n'))
|
|
|
|
return r[r.length - 1]
|
2019-04-13 17:14:41 +00:00
|
|
|
}
|
|
|
|
|
2019-04-25 08:43:50 +00:00
|
|
|
async function getCurrentGitBranch() {
|
|
|
|
const r = await execCommand('git', ['rev-parse', '--abbrev-ref', 'HEAD'])
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
2019-04-13 17:14:41 +00:00
|
|
|
async function getGitDiff(from, to) {
|
2019-04-15 12:20:54 +00:00
|
|
|
// # https://git-scm.com/docs/pretty-formats
|
|
|
|
const r = await execCommand('git', ['--no-pager', 'log', `${from}...${to}`, '--pretty=%s|%h|%an|%ae'])
|
2019-04-13 17:14:41 +00:00
|
|
|
return r.split('\n').map((line) => {
|
2019-04-15 12:20:54 +00:00
|
|
|
const [message, commit, authorName, authorEmail] = line.split('|')
|
2019-04-13 17:14:41 +00:00
|
|
|
|
2019-04-15 12:20:54 +00:00
|
|
|
return { message, commit, authorName, authorEmail }
|
2019-04-13 17:14:41 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
function parseCommits(commits) {
|
|
|
|
return commits.filter(c => c.message.includes(':')).map((commit) => {
|
2019-05-09 07:20:00 +00:00
|
|
|
let [type, ...message] = commit.message.split(':')
|
|
|
|
message = message.join(':')
|
2019-04-13 17:14:41 +00:00
|
|
|
|
|
|
|
// Extract references from message
|
2019-05-21 19:10:15 +00:00
|
|
|
message = message.replace(/\((fixes) #\d+\)/g, '')
|
2019-04-13 17:14:41 +00:00
|
|
|
const references = []
|
|
|
|
const referencesRegex = /#[0-9]+/g
|
|
|
|
let m
|
|
|
|
while (m = referencesRegex.exec(message)) { // eslint-disable-line no-cond-assign
|
|
|
|
references.push(m[0])
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove references and normalize
|
|
|
|
message = message.replace(referencesRegex, '').replace(/\(\)/g, '').trim()
|
|
|
|
|
|
|
|
// Extract scope from type
|
|
|
|
let scope = type.match(/\((.*)\)/)
|
|
|
|
if (scope) {
|
|
|
|
scope = scope[1]
|
|
|
|
}
|
2019-05-21 19:10:15 +00:00
|
|
|
if (!scope) {
|
|
|
|
scope = 'general'
|
|
|
|
}
|
2019-04-13 17:14:41 +00:00
|
|
|
type = type.split('(')[0]
|
|
|
|
|
|
|
|
return {
|
2019-04-15 12:20:54 +00:00
|
|
|
...commit,
|
2019-04-13 17:14:41 +00:00
|
|
|
message,
|
|
|
|
type,
|
|
|
|
scope,
|
2019-04-15 12:20:54 +00:00
|
|
|
references
|
2019-04-13 17:14:41 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
function generateMarkDown(commits) {
|
2019-05-21 19:10:15 +00:00
|
|
|
const typeGroups = groupBy(commits, 'type')
|
2019-04-13 17:14:41 +00:00
|
|
|
|
|
|
|
let markdown = ''
|
|
|
|
|
|
|
|
for (const type of allowedTypes) {
|
2019-05-21 19:10:15 +00:00
|
|
|
const group = typeGroups[type]
|
2019-04-13 17:14:41 +00:00
|
|
|
if (!group || !group.length) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
const { title } = types[type]
|
2019-05-21 19:10:15 +00:00
|
|
|
markdown += '\n\n' + '#### ' + title + '\n\n'
|
|
|
|
|
|
|
|
const scopeGroups = groupBy(group, 'scope')
|
|
|
|
for (const scopeName in scopeGroups) {
|
|
|
|
markdown += '- `' + scopeName + '`' + '\n'
|
|
|
|
for (const commit of scopeGroups[scopeName]) {
|
|
|
|
markdown += ' - ' + commit.references.join(',') + ' ' + commit.message + '\n'
|
|
|
|
}
|
|
|
|
}
|
2019-04-13 17:14:41 +00:00
|
|
|
}
|
|
|
|
|
2019-04-15 12:20:54 +00:00
|
|
|
const authors = sortBy(uniq(commits.map(commit => commit.authorName).filter(an => !isKnownAuthor(an))))
|
|
|
|
if (authors.length) {
|
2019-05-21 19:10:15 +00:00
|
|
|
markdown += '\n\n' + '#### ' + '💖 Thanks to' + '\n\n'
|
2019-04-15 12:20:54 +00:00
|
|
|
markdown += authors.map(name => '- ' + name).join('\n')
|
|
|
|
}
|
|
|
|
|
2019-04-13 17:14:41 +00:00
|
|
|
return markdown.trim()
|
|
|
|
}
|
|
|
|
|
|
|
|
main().catch(consola.error)
|