Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign up// section -> label | |
const sectionLabelMap = { | |
'Core Changes': 'type: next', | |
'Documentation Changes': 'type: documentation', | |
'Example Changes': 'type: example', | |
} | |
const fallbackSection = 'Misc Changes' | |
// -------------------------------------------------- | |
const prNumberRegex = /\(#([-0-9]+)\)/ | |
const getCommitPullRequest = async (commit, github) => { | |
const match = prNumberRegex.exec(commit.title) | |
if (!match) { | |
return null | |
} | |
const number = parseInt(match[1], 10) | |
if (!number) { | |
return null | |
} | |
const { data } = await github.connection.pullRequests.get({ | |
owner: github.repoDetails.user, | |
repo: github.repoDetails.repo, | |
number, | |
}) | |
return data | |
} | |
const getSectionForPullRequest = (pullRequest) => { | |
const { labels } = pullRequest | |
// sections defined first will take priority | |
for (const [section, label] of Object.entries(sectionLabelMap)) { | |
if (labels.some((prLabel) => prLabel.name === label)) { | |
return section | |
} | |
} | |
return null | |
} | |
const groupByLabels = async (commits, github) => { | |
// Initialize the sections object with empty arrays | |
const sections = Object.keys(sectionLabelMap).reduce((sections, section) => { | |
sections[section] = [] | |
return sections | |
}, {}) | |
sections.__fallback = [] | |
for (const commit of commits) { | |
const pullRequest = await getCommitPullRequest(commit, github) | |
if (pullRequest) { | |
const section = getSectionForPullRequest(pullRequest) | |
if (section) { | |
// Add the change to the respective section | |
sections[section].push({ | |
title: pullRequest.title, | |
number: pullRequest.number, | |
}) | |
continue | |
} | |
// No section found, add it to the fallback section | |
sections.__fallback.push({ | |
title: pullRequest.title, | |
number: pullRequest.number, | |
}) | |
continue | |
} | |
// No Pull Request found, add it to the fallback section but without the number | |
sections.__fallback.push({ | |
title: commit.title, | |
}) | |
} | |
return sections | |
} | |
function cleanupPRTitle(title) { | |
if (title.startsWith('[Docs] ')) { | |
return title.replace('[Docs] ', '') | |
} | |
return title | |
} | |
const buildChangelog = (sections, authors) => { | |
let text = '' | |
for (const section in sections) { | |
const changes = sections[section] | |
// No changes in this section? Don't render it | |
if (changes.length === 0) { | |
continue | |
} | |
const title = section === '__fallback' ? fallbackSection : section | |
text += `### ${title}\n\n` | |
for (const change of changes) { | |
const numberText = change.number != null ? `: #${change.number}` : '' | |
text += `- ${cleanupPRTitle(change.title)}${numberText}\n` | |
} | |
text += '\n' | |
} | |
if (authors.size > 0) { | |
text += '### Credits \n\n' | |
text += 'Huge thanks to ' | |
let index = 1 | |
authors.forEach((author) => { | |
// GitHub links usernames if prefixed with @ | |
text += `@${author}` | |
const penultimate = index === authors.size - 1 | |
const notLast = index !== authors.size | |
if (penultimate) { | |
// Oxford comma is applied when list is bigger than 2 names | |
if (authors.size > 2) { | |
text += ',' | |
} | |
text += ' and ' | |
} else if (notLast) { | |
text += ', ' | |
} | |
index += 1 | |
}) | |
text += ' for helping!' | |
text += '\n' | |
} | |
return text | |
} | |
module.exports = async (markdown, metadata) => { | |
const { commits, authors, githubConnection, repoDetails } = metadata | |
const github = { connection: githubConnection, repoDetails } | |
const sections = await groupByLabels(commits.all, github) | |
const changelog = buildChangelog(sections, authors) | |
return changelog | |
} |