-
Notifications
You must be signed in to change notification settings - Fork 14
/
release.js
154 lines (139 loc) · 5.01 KB
/
release.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
console.log('Welcome to TEAMMATES Release Helper!');
console.log('Make sure than you have a valid config.js file before running this script');
console.log('');
const fs = require('fs');
const shell = require('shelljs');
const moment = require('moment');
const { EOL: eol } = require('os');
const { githubUsername, teammatesDir } = require('./config');
const { getMilestoneInfo } = require('./milestone-api');
let version;
let prsInMilestone;
shell.config.silent = true;
shell.cd(teammatesDir);
console.log('Updating master branch...');
shell.exec('git stash');
shell.exec('git checkout master');
shell.exec('git pull');
console.log('');
const developersJsonDir = './src/web/data/developers.json';
const allDevs = JSON.parse(fs.readFileSync(developersJsonDir, 'UTF-8'));
const usernameToNameMap = {};
const prNumberToMetadataMap = {};
allDevs.contributors.forEach((contributor) => {
const { username } = contributor;
if (!username) {
return;
}
usernameToNameMap[username] = contributor.name || `@${username}`;
});
allDevs.committers.forEach((committer) => {
usernameToNameMap[committer.username] = committer.name;
});
allDevs.teammembers.forEach((teammember) => {
const { username } = teammember;
if (!username) {
return;
}
usernameToNameMap[username] = teammember.name;
});
const authors = [];
const reviewers = ['damithc'];
const releaseNotesContent = {
Bug: [],
Feature: [],
DevOps: [],
Task: [],
Unlisted: [],
};
const allowedPrTypes = Object.keys(releaseNotesContent);
function addReleaseNoteEntries(type, displayType) {
const entries = releaseNotesContent[type];
if (entries && entries.length) {
if (displayType) {
console.log(`## ${displayType}`);
console.log('');
}
entries.forEach((entry) => console.log(`- ${entry}`));
console.log('');
}
}
function createReleaseNotes() {
const timeToDisplay = moment(new Date()).format('MMMM Do, YYYY, hh.mma');
console.log('');
console.log('Release notes:');
console.log('==================================================');
console.log('# Release (fill this up)');
console.log('');
addReleaseNoteEntries('Breaking', 'Breaking Changes');
addReleaseNoteEntries('Bug', 'Bug Fixes');
addReleaseNoteEntries('Feature', 'New Features and Enhancements');
addReleaseNoteEntries('DevOps', 'DevOps/Build-related');
addReleaseNoteEntries('Task', 'Other Tasks');
console.log('==================================================');
console.log('');
console.log('Release announcement:');
console.log('==================================================');
console.log(`[${version}](https://github.com/TEAMMATES/teammates/releases/tag/${version}) has been released by @${githubUsername} at ${timeToDisplay} SGT.`);
console.log('');
console.log(`Code contributions from: ${authors.map((username) => `@${username}`).sort().join(', ')}`);
console.log(`Review contributions from: ${reviewers.map((username) => `@${username}`).sort().join(', ')}`);
console.log('');
console.log('Ready for post-release check and deployment by PM (@damithc).');
console.log('==================================================');
console.log('');
console.log('The following PRs are not listed in the release notes; make sure that this is a deliberate decision:');
addReleaseNoteEntries('Unlisted');
}
function releaseAndTag() {
console.log('Merging master to release...');
shell.exec('git checkout release');
shell.exec('git merge master');
console.log(`Tagging release as ${version}...`);
shell.exec(`git tag ${version}`);
console.log('');
}
function listPrsAndAuthors() {
prsInMilestone.forEach((pr) => {
const author = pr.user.login;
if (authors.indexOf(author) === -1) {
authors.push(author);
}
pr.assignees.map((assignee) => assignee.login).forEach((reviewer) => {
if (reviewers.indexOf(reviewer) === -1) {
reviewers.push(reviewer);
}
});
let prType = 'Unlisted';
const [cLabel] = pr.labels.filter((label) => label.name.startsWith('c.'));
if (cLabel) {
const prTypeFromLabel = cLabel.name.replace(/^c\./, '');
if (allowedPrTypes.indexOf(prTypeFromLabel) !== -1) {
prType = prTypeFromLabel;
}
}
prNumberToMetadataMap[pr.number] = {
prType,
authorName: `@${author}`,
};
});
const commitMsgRegex = /^[0-9a-f]{7,} \[#\d+] (.*) \(#(\d+)\)$/;
shell.exec('git log --oneline release..HEAD').stdout.split(eol)
.filter((commitMsg) => commitMsg && commitMsg.match(commitMsgRegex))
.forEach((commitMsg) => {
const parts = commitMsgRegex.exec(commitMsg);
const entry = parts[1];
const prNumber = Number.parseInt(parts[2], 10);
if (prNumberToMetadataMap[prNumber]) {
const { prType, authorName } = prNumberToMetadataMap[prNumber];
releaseNotesContent[prType].push(`[#${prNumber}] ${entry} (${authorName})`);
}
});
}
getMilestoneInfo((versionParam, prsInMilestoneParam) => {
version = versionParam;
prsInMilestone = prsInMilestoneParam;
listPrsAndAuthors();
releaseAndTag();
createReleaseNotes();
});