-
Notifications
You must be signed in to change notification settings - Fork 416
/
Copy pathgenerate-release-body.ts
361 lines (320 loc) · 11.2 KB
/
generate-release-body.ts
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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
import { Octokit } from "octokit";
import yargs from "yargs";
import { execSync } from "child_process";
import { readFileSync } from "fs";
import path from "path";
// Typescript 4 will support it natively
type Await<T> = T extends PromiseLike<infer U> ? U : T;
type Commits = Await<ReturnType<Octokit["rest"]["repos"]["compareCommits"]>>["data"]["commits"];
function getCompareLink(packageName: string, previousTag: string, newTag: string) {
// Previous
let previousPackage: string;
try {
previousPackage = execSync(
`git show ${previousTag}:../../Cargo.lock | grep ${packageName}? | head -1 | grep -o '".*"'`
).toString();
} catch (error) {
console.error('An error occurred while executing the shell command:', error);
return ""
}
const previousCommitTmp = /#([0-9a-f]*)/g.exec(previousPackage);
if (previousCommitTmp == null) { // regexp didn't match
return ""
};
const previousCommit = previousCommitTmp[1].slice(0, 8);
const previousRepoTmp = /(https:\/\/.*)\?/g.exec(previousPackage);
if (previousRepoTmp == null) {
return ""
};
const previousRepo = previousRepoTmp[1];
// New
let newPackage: string;
try {
newPackage = execSync(
`git show ${newTag}:../../Cargo.lock | grep ${packageName}? | head -1 | grep -o '".*"'`
).toString();
} catch (error) {
console.error('An error occurred while executing the shell command:', error);
return ""
}
const newCommitTmp = /#([0-9a-f]*)/g.exec(newPackage)
if (newCommitTmp == null) {
return ""
};
const newCommit = newCommitTmp[1].slice(0, 8);
const newRepoTmp = /(https:\/\/.*)\?/g.exec(newPackage);
if (newRepoTmp == null) {
return ""
}
const newRepo = newRepoTmp[1];
const newRepoOrganization = /github.com\/([^\/]*)/g.exec(newRepo)[1];
const diffLink =
previousRepo !== newRepo
? `${previousRepo}/compare/${previousCommit}...${newRepoOrganization}:${newCommit}`
: `${previousRepo}/compare/${previousCommit}...${newCommit}`;
return diffLink;
}
async function getCommitAndLabels(
octokit: Octokit,
owner: string,
repo: string,
previousTag: string,
newTag: string
): Promise<{ prByLabels: any; commits: any[] }> {
let commits: Commits = [];
let more = true;
let page = 0;
while (more) {
const compare = await octokit.rest.repos.compareCommitsWithBasehead({
owner,
repo,
basehead: previousTag + "..." + newTag,
per_page: 200,
page,
});
commits = commits.concat(compare.data.commits);
more = compare.data.commits.length == 200;
page++;
}
// Determine commits to exclude
// - commits reverted in the same range
const excludedCommits: number[] = [];
const revertedCommits: number[] = [];
for (let i = commits.length - 1; i >= 0; i--) {
const commitMessageFirstLine = commits[i].commit.message.split("\n")[0].trim();
if (revertedCommits[commitMessageFirstLine] != null) {
excludedCommits.push(i);
excludedCommits.push(revertedCommits[commitMessageFirstLine]);
} else {
const foundRevertedCommitName = commitMessageFirstLine.match(/Revert \"(.*)\"/);
if (foundRevertedCommitName?.length > 0) {
revertedCommits[foundRevertedCommitName[1]] = i;
}
}
}
const prByLabels = {};
for (let i = 0; i < commits.length; i++) {
const commitMessageFirstLine = commits[i].commit.message.split("\n")[0].trim();
if (!excludedCommits.includes(i)) {
const foundPrsNumbers = commitMessageFirstLine.match(/\(#([0-9]+)\)$/);
if (foundPrsNumbers && foundPrsNumbers.length > 1) {
// This will check current repo and if the PR is not found, will try the official repo
const repos = [
{ owner, repo },
{ owner: "AstarNetwork", repo: "Astar" },
];
for (const { owner, repo } of repos) {
try {
const pr = await octokit.rest.pulls.get({
owner,
repo,
pull_number: parseInt(foundPrsNumbers[1]),
});
if (pr.data.labels && pr.data.labels.length > 0) {
for (const label of pr.data.labels) {
prByLabels[label.name] = prByLabels[label.name] || [];
prByLabels[label.name].push(pr.data);
}
} else {
prByLabels[""] = prByLabels[""] || [];
prByLabels[""].push(pr);
}
break;
} catch (e) {
// PR not found... let's try the other repo
}
}
}
}
}
return {
prByLabels,
commits,
};
}
function getRuntimeInfo(srtoolReportFolder: string, runtimeName: string) {
const specVersion = execSync(
`cat ../../runtime/${runtimeName}/src/lib.rs | grep 'spec_version: [0-9]*' | tail -1`
).toString();
return {
name: runtimeName,
version: /:\s?([0-9A-z\-]*)/.exec(specVersion)[1],
srtool: JSON.parse(
readFileSync(path.join(srtoolReportFolder, `./${runtimeName}-srtool-digest.json`)).toString()
),
};
}
function capitalize(s) {
return s[0].toUpperCase() + s.slice(1);
}
// filters out the PR that has a tag `client` or `runtime` and returns all the remaining PRs
function PRfilter(prLabels: any) {
// resulting array that contains all the filtered PRs
let otherPrs = [];
// storage item to make sure duplicate PRs don't get included
let included_pr_number = [];
// to make sure that PR that has already been included in `runtime` and `client`
// don't get included because of different labels
let client_pr_numbers = [];
let runtime_pr_numbers = [];
// make sure that there are some PRs for 'client' otherwise results in undefined
if (prLabels['client']) {
prLabels["client"].forEach(element => {
client_pr_numbers.push(element.number);
});
}
if (prLabels['runtime']) {
prLabels["runtime"].forEach(element => {
runtime_pr_numbers.push(element.number);
});
}
// empty label has a different api resposnse, so have to handle it differently
if (prLabels[""]) {
prLabels[""].forEach(element => {
if (included_pr_number.includes(element.data.number)) {
// do nothing
}
else {
included_pr_number.push(element.data.number);
otherPrs.push(element);
}
}
);
}
for (let label in prLabels) {
// already handled all these cases
if (label == 'runtime' || label == 'client' || label == "") {
continue;
}
else {
prLabels[label].forEach(element => {
if (included_pr_number.includes(element.number) || client_pr_numbers.includes(element.number) || runtime_pr_numbers.includes(element.number)) {
// do nothing, PR already sent to appropriate place.
}
else {
included_pr_number.push(element.number);
otherPrs.push(element);
}
}
)
}
}
return otherPrs;
}
const CLIENT_CHANGES_LABEL = "client";
const RUNTIME_CHANGES_LABEL = "runtime"
const BREAKING_CHANGES_LABEL = "breaksapi";
async function main() {
const argv = yargs(process.argv.slice(2))
.usage("npm run ts-node generate-release-body.ts [args]")
.version("1.0.0")
.options({
from: {
type: "string",
describe: "previous tag to retrieve commits from",
required: true,
},
to: {
type: "string",
describe: "current tag being drafted",
required: true,
},
owner: {
type: "string",
describe: "Repository owner (Ex: AstarNetwork)",
required: true,
},
repo: {
type: "string",
describe: "Repository name (Ex: Astar)",
required: true,
},
})
.demandOption(["from", "to"])
.help().argv;
const octokit = new Octokit({
auth: process.env.GITHUB_TOKEN || undefined,
});
const previousTag = argv.from;
const newTag = argv.to;
const runtimes = ["shibuya", "shiden", "astar"].map((runtimeName) =>
getRuntimeInfo(argv["srtool-report-folder"], runtimeName)
);
const moduleLinks = ["polkadot-sdk", "frontier"].map((repoName) => ({
name: repoName,
link: getCompareLink(repoName, previousTag, newTag),
}));
const { prByLabels } = await getCommitAndLabels(
octokit,
argv.owner,
argv.repo,
previousTag,
newTag
);
const clientPRs = prByLabels[CLIENT_CHANGES_LABEL] || [];
const runtimePRs = prByLabels[RUNTIME_CHANGES_LABEL] || [];
let remainingPRs = PRfilter(prByLabels);
const printPr = (pr) => {
if (pr.labels) {
if (pr.labels.includes(BREAKING_CHANGES_LABEL)) {
return "⚠️ " + pr.title + " (#" + pr.number + ")";
}
return pr.title + " (#" + pr.number + ")";
}
else {
return pr.data.title + " (#" + pr.data.number + ")";
}
};
const template = `
## Description
(Placeholder for release descriptions, please freely write explanations for this release here.)
\*\*Upgrade priority: LOW/MID/HIGH/CRITICAL\*\*
> DELETE THIS
> CRITICAL - contains critical update for the client which should be rolled out ASAP
> HIGH - significant changes to client
> MEDIUM - some minor changes to the client
> LOW - no client changes
${runtimes.length > 0 ? `## Runtimes
${runtimes
.map(
(runtime) => `### ${capitalize(runtime.name)}
\`\`\`
✨ spec_version: ${runtime.version}
🏋 Runtime Size: ${runtime.srtool.runtimes.compressed.size}
🗜 Compressed: ${runtime.srtool.runtimes.compressed.subwasm.compression.compressed ? "Yes" : "No"}
🎁 Metadata version: ${runtime.srtool.runtimes.compressed.subwasm.metadata_version}
🗳️ sha256: ${runtime.srtool.runtimes.compressed.sha256}
🗳️ blake2-256: ${runtime.srtool.runtimes.compressed.blake2_256}
🗳️ proposal (authorizeUpgrade): ${runtime.srtool.runtimes.compressed.subwasm.parachain_authorize_upgrade_hash}
📦 IPFS: ${runtime.srtool.runtimes.compressed.subwasm.ipfs_hash}
\`\`\`
`).join(`\n`)}` : ""}
## Build Info
WASM runtime built using \`${runtimes[0]?.srtool.info.rustc}\`
## Changes
### Client
${clientPRs.length > 0 ? `
${clientPRs.map((pr) => `* ${printPr(pr)}`).join("\n")}
` : "None"}
### Runtime
${runtimePRs.length > 0 ? `
${runtimePRs.map((pr) => `* ${printPr(pr)}`).join("\n")}
` : "None"}
### Others
${remainingPRs.length > 0 ? `
${remainingPRs.map((pr) => `* ${printPr(pr)}`).join("\n")}
` : "None"}
## Dependency Changes
Astar: https://github.com/${argv.owner}/${argv.repo}/compare/${previousTag}...${newTag}
${moduleLinks.map((modules) => `${capitalize(modules.name)}: ${modules.link}`).join("\n")}
## Download Links
| Arch | Link |
| ----------- | ------- |
| \`MacOS x86_64\` | [Download](https://github.com/AstarNetwork/Astar/releases/download/${newTag}/astar-collator-${newTag}-macOS-x86_64.tar.gz) |
| \`Ubuntu x86_64\` | [Download](https://github.com/AstarNetwork/Astar/releases/download/${newTag}/astar-collator-${newTag}-ubuntu-x86_64.tar.gz) |
| \`Ubuntu aarch64\` | [Download](https://github.com/AstarNetwork/Astar/releases/download/${newTag}/astar-collator-${newTag}-ubuntu-aarch64.tar.gz) |
[<img src="https://github.com/AstarNetwork/Astar/blob/master/.github/images/docker.webp" height="200px">](https://hub.docker.com/r/staketechnologies/astar-collator/tags)
`
console.log(template);
}
main();