/
release-helpers.js
126 lines (119 loc) · 3.36 KB
/
release-helpers.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
import { readFile } from 'node:fs/promises';
import { exit } from 'node:process';
import GitHub from 'github-api';
import { runAndGetStdout } from './helpers.js';
/**
* @param {string} changelog
* @returns {{ currentVersion: string, index: number, previousVersion: string, text: string }}
*/
export function getFirstChangelogEntry(changelog) {
const match = changelog.match(
/(?<text>## (?<currentVersion>\d+\.\d+\.\d+(-\d+)?)[\S\s]*?)\n+## (?<previousVersion>\d+\.\d+\.\d+)/
);
if (!match || !match.groups || typeof match.index !== 'number') {
throw new Error('Could not detect any changelog entry.');
}
const {
groups: { text, currentVersion, previousVersion },
index
} = match;
return { currentVersion, index, previousVersion, text };
}
/**
* @typedef {object} IncludedPR
* @property {string} author
* @property {number[]} closed - which PRs are closed by this
* @property {number} pr
* @property {string} text
*/
/**
* @param {string} fromVersion
* @param {string} toVersion
* @param {import('github-api').Repo} repo
* @param {string|null} currentBranch We only have a branch when locally prepare a release, otherwise we use the sha to find the PR
* @param {boolean} isPreRelease
* @returns {Promise<IncludedPR[]>}
*/
export async function getIncludedPRs(fromVersion, toVersion, repo, currentBranch, isPreRelease) {
const [commits, commitSha] = await Promise.all([
runAndGetStdout('git', [
'--no-pager',
'log',
`${fromVersion}..${toVersion}`,
'--pretty=tformat:%s'
]),
runAndGetStdout('git', ['rev-parse', toVersion])
]);
const getPrRegExp = /^(.+)\s\(#(\d+)\)$/gm;
const prs = [];
let match;
while ((match = getPrRegExp.exec(commits))) {
prs.push({ pr: Number(match[2]), text: match[1].split('\n')[0] });
}
if (isPreRelease) {
const { data: basePrs } = await repo.listPullRequests({
state: 'open',
...(currentBranch ? { head: `rollup:${currentBranch}` } : {})
});
for (const {
number,
title,
head: { sha }
} of basePrs) {
if (currentBranch || sha === commitSha) {
prs.push({ pr: number, text: title });
}
}
}
prs.sort((a, b) => (a.pr > b.pr ? 1 : -1));
return Promise.all(
prs.map(async ({ pr, text }) => {
const { data } = await repo.getPullRequest(pr);
const bodyWithoutComments = data.body.replace(/<!--[\S\s]*?-->/g, '');
const closedIssuesRegexp = /([Ff]ix(es|ed)?|([Cc]lose|[Rr]esolve)[ds]?) #(\d+)/g;
const closed = [];
while ((match = closedIssuesRegexp.exec(bodyWithoutComments))) {
closed.push(Number(match[4]));
}
return {
author: data.user.login,
closed,
pr,
text
};
})
);
}
/**
* @return {Promise<GitHub>}
*/
export async function getGithubApi() {
const GITHUB_TOKEN = '.github_token';
try {
const token = (await readFile(GITHUB_TOKEN, 'utf8')).trim();
return new GitHub({ token });
} catch (error) {
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
console.error(
`Could not find GitHub token file. Please create "${GITHUB_TOKEN}" containing a token with the following permissions:
- public_repo`
);
exit(1);
} else {
throw error;
}
}
}
/**
* @param {string} version
* @return {string}
*/
export function getGitTag(version) {
return `v${version}`;
}
/**
* @return {Promise<string>}
*/
export function getCurrentCommitMessage() {
return runAndGetStdout('git', ['--no-pager', 'log', '-1', '--pretty=%B']);
}