Skip to content

Commit 0cde1c4

Browse files
authored
Merge pull request #963 from primer/generate-changelog
Generate changelog with semantic-release
2 parents 2e687a8 + 828e3dc commit 0cde1c4

File tree

6 files changed

+13724
-7796
lines changed

6 files changed

+13724
-7796
lines changed

.github/workflows/changelog.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
name: changelog
2+
on:
3+
push:
4+
branches:
5+
- 'release-*'
6+
- '*changelog*'
7+
jobs:
8+
all:
9+
runs-on: ubuntu-latest
10+
steps:
11+
- uses: actions/checkout@master
12+
- uses: actions/setup-node@master
13+
with:
14+
node-version: 11
15+
- name: install
16+
run: npm install
17+
- name: changelog
18+
run: script/changelog.js

lib/semantic-release-plugin.js

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
const Octokit = require('@octokit/rest')
2+
3+
const commitMetadata = new Map()
4+
5+
const noteGroups = {
6+
breaking: {title: ':boom: Breaking changes'},
7+
enhancement: {title: ':rocket: Enhancements'},
8+
fix: {title: ':bug: Bug fixes'},
9+
docs: {title: ':memo: Documentation'},
10+
internal: {title: ':house: Internal'},
11+
polish: {title: ':nail_care: Polish'}
12+
}
13+
14+
const releaseLabels = {
15+
'Tag: Breaking Change': {releaseType: 'major', group: 'breaking'},
16+
'Tag: Enhancement': {releaseType: 'minor', group: 'enhancement'},
17+
'Tag: Bug Fix': {releaseType: 'patch', group: 'fix'},
18+
'Tag: Documentation': {releaseType: 'patch', group: 'docs'},
19+
'Tag: Internal': {releaseType: 'patch', group: 'internal'},
20+
'Tag: Polish': {releaseType: 'patch', group: 'polish'}
21+
}
22+
23+
const repoContext = {owner: 'primer', repo: 'css'}
24+
const githubOptions = {
25+
userAgent: '@primer/css changelog'
26+
}
27+
const {GITHUB_TOKEN} = process.env
28+
if (GITHUB_TOKEN) {
29+
githubOptions.auth = GITHUB_TOKEN
30+
}
31+
32+
const github = new Octokit(githubOptions)
33+
34+
module.exports = {analyzeCommits, generateNotes}
35+
36+
async function analyzeCommits(options, context) {
37+
const {commits, logger} = context
38+
39+
const releaseTypes = new Map()
40+
41+
for (const commit of commits) {
42+
const match = commit.message.match(/Merge pull request #(\d+)/)
43+
if (match) {
44+
const [, pullNumber] = match
45+
logger.log(`Fetching PR #${pullNumber}...`)
46+
// eslint-disable-next-line camelcase
47+
const {data: pull} = await github.pulls.get(Object.assign({pull_number: pullNumber}, repoContext))
48+
49+
const labels = pull.labels.map(label => label.name)
50+
if (labels.length) {
51+
logger.log(`Got labels: "${labels.join('", "')}"`)
52+
for (const label of labels) {
53+
const {releaseType, group} = releaseLabels[label] || {}
54+
if (releaseType) {
55+
logger.info(`Label "${label}" matches release type: ${releaseType}`)
56+
57+
if (!releaseTypes.has(releaseType)) {
58+
releaseTypes.set(releaseType, [])
59+
}
60+
releaseTypes.get(releaseType).push({pull, label})
61+
62+
commitMetadata.set(commit.hash, {pull, group})
63+
}
64+
}
65+
}
66+
} else {
67+
logger.log(`No PR number found in commit: "${commit.message}"`)
68+
}
69+
}
70+
71+
let returnReleaseType
72+
73+
for (const releaseType of ['major', 'minor', 'patch']) {
74+
if (releaseTypes.has(releaseType)) {
75+
const pulls = releaseTypes.get(releaseType)
76+
logger.info(`Found ${pulls.length} pulls for release type: ${releaseType}`)
77+
for (const {pull, label} of pulls) {
78+
logger.info(`- PR #${pull.number} labeled "${label}"`)
79+
if (!returnReleaseType) {
80+
returnReleaseType = releaseType
81+
}
82+
}
83+
}
84+
}
85+
86+
return returnReleaseType
87+
}
88+
89+
async function generateNotes(options, context) {
90+
const {
91+
commits,
92+
logger,
93+
nextRelease: {version}
94+
} = context
95+
96+
logger.info(`Finding metadata for ${commitMetadata.size} commits...`)
97+
const releaseCommits = commits.map(commit => commitMetadata.get(commit.hash)).filter(Boolean)
98+
99+
if (releaseCommits.length) {
100+
logger.info(`Got ${releaseCommits.length} release commits!`)
101+
102+
const groupItems = new Map()
103+
const committers = new Set()
104+
const {owner, repo} = repoContext
105+
const baseURL = `https://github.com/${owner}/${repo}`
106+
for (const {pull, group} of releaseCommits) {
107+
if (!groupItems.has(group)) {
108+
groupItems.set(group, [])
109+
}
110+
const {
111+
number,
112+
title,
113+
user: {login}
114+
} = pull
115+
groupItems.get(group).push(`- [#${number}](${baseURL}/${number}) ${title}`)
116+
committers.add(login)
117+
}
118+
119+
return [
120+
`## ${version}`,
121+
...Object.entries(noteGroups)
122+
.filter(([group]) => groupItems.has(group))
123+
.map(([group, {title}]) => `\n### ${title}\n${groupItems.get(group).join('\n')}`),
124+
'',
125+
`### Committers`,
126+
...Array.from(committers)
127+
.sort()
128+
.map(login => `- [@${login}](https://github.com/${login})`),
129+
''
130+
].join('\n')
131+
} else {
132+
logger.info(`No release commits. :(`)
133+
}
134+
}

0 commit comments

Comments
 (0)