Skip to content

Commit

Permalink
chore: add release notes tooling (#665)
Browse files Browse the repository at this point in the history
  • Loading branch information
piksel authored Sep 19, 2021
1 parent 1b1ab01 commit c086bc2
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 0 deletions.
10 changes: 10 additions & 0 deletions tools/release-notes/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
## Requirements
```
npm install -g git-release-notes
```

## Usage
```
git release-notes -f release-notes.json PREVIOUS..CURRENT release-notes-md.ejs
```
Where PREVIOUS is the previous release tag, and CURRENT is the current release tag
31 changes: 31 additions & 0 deletions tools/release-notes/release-notes-md.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<%
const typeGroups = {
feats: { title: 'Features:', types: ['feat'] },
fixes: { title: 'Fixes:', types: ['fix'] },
etc: {
title: 'Other changes (not related to library code):',
types: ['docs','style','refactor','perf','test','build','ci','chore']
},
unknown: { title: 'Unknown:', types: ['?'] },
}
const commitTypes = {
feat: '', fix: '🐛', docs: '📚', style: '💎',
refactor: '🔨', perf: '🚀', test: '🚨', build: '📦',
ci: '⚙️', chore: '🔧', ['?']: '',
}
for(const group of Object.values(typeGroups)){
const groupCommits = commits.filter(c => group.types.includes(c.type));
if (groupCommits.length < 1) continue;
%>
## <%=group.title%>
<% for (const {issue, title, authorName, authorUser, scope, type} of groupCommits) { %>
* <%=commitTypes[type]%>
<%=issue ? ` [[#${issue}](https://github.com/icsharpcode/SharpZipLib/pull/${issue})]\n` : ''-%>
<%=scope ? ` \`${scope}\`\n` : ''-%>
__<%=title-%>__
by <%=authorUser ? `[_${authorName}_](https://github.com/${authorUser})` : `_${authorName}_`%>
<% } %>
<% } %>
76 changes: 76 additions & 0 deletions tools/release-notes/release-notes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
const https = require('https')

const authorUsers = {}

/**
* @param {string} email
* @param {string} prId
* @returns {Promise<string | null>} User login if found */
const getAuthorUser = async (email, prId) => {
const lookupUser = authorUsers[email];
if (lookupUser) return lookupUser;

const match = /[0-9]+\+([^@]+)@users\.noreply\.github\.com/.exec(email);
if (match) {
return match[1];
}

const pr = await new Promise((resolve, reject) => {
console.warn(`Looking up GitHub user for PR #${prId} (${email})...`)
https.get(`https://api.github.com/repos/icsharpcode/sharpziplib/pulls/${prId}`, {
headers: {Accept: 'application/vnd.github.v3+json', 'User-Agent': 'release-notes-script/0.3.1'}
}, (res) => {
res.setEncoding('utf8');
let chunks = '';
res.on('data', (chunk) => chunks += chunk);
res.on('end', () => resolve(JSON.parse(chunks)));
res.on('error', reject);
}).on('error', reject);
}).catch(e => {
console.error(`Could not get GitHub user (${email}): ${e}}`)
return null;
});

if (!pr) {
console.error(`Could not get GitHub user (${email})}`)
return null;
} else {
const user = pr.user.login;
console.warn(`Resolved email ${email} to user ${user}`)
authorUsers[email] = user;
return user;
}
}

/**
* @typedef {{issue?: string, sha1: string, authorEmail: string, title: string, type: string}} Commit
* @param {{commits: Commit[], range: string, dateFnsFormat: ()=>any, debug: (...p[]) => void}} data
* @param {(data: {commits: Commit[], extra: {[key: string]: any}}) => void} callback
* */
module.exports = (data, callback) => {
// Migrates commits in the old format to conventional commit style, omitting any commits in neither format
const normalizedCommits = data.commits.flatMap(c => {
if (c.type) return [c]
const match = /^(?:Merge )?(?:PR ?)?#(\d+):? (.*)/.exec(c.title)
if (match != null) {
const [, issue, title] = match
return [{...c, title, issue, type: '?'}]
} else {
console.warn(`Skipping commit [${c.sha1.substr(0, 7)}] "${c.title}"!`);
return [];
}
});

const commitAuthoredBy = email => commit => commit.authorEmail === email && commit.issue ? [commit.issue] : []
const authorEmails = new Set(normalizedCommits.map(c => c.authorEmail));
Promise.all(
Array
.from(authorEmails.values(), e => [e, normalizedCommits.flatMap(commitAuthoredBy(e))])
.map(async ([email, prs]) => [email, await getAuthorUser(email, ...prs)])
)
.then(Object.fromEntries)
.then(authorUsers => callback({
commits: normalizedCommits.map(c => ({...c, authorUser: authorUsers[c.authorEmail]})),
extra: {}
}))
};
5 changes: 5 additions & 0 deletions tools/release-notes/release-notes.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"title" : "^([a-z]+)(?:\\(([\\w\\$\\.]*)\\))?\\: (.*?)(?: \\(#(\\d+)\\))?$",
"meaning": ["type", "scope", "title", "issue"],
"script": "release-notes.js"
}

0 comments on commit c086bc2

Please sign in to comment.