From e8e6e51a093b1ab6a6d7dacc90afa27557812230 Mon Sep 17 00:00:00 2001
From: Esther Kim <44627152+estherkim@users.noreply.github.com>
Date: Tue, 3 Aug 2021 18:44:09 -0400
Subject: [PATCH] =?UTF-8?q?=F0=9F=8F=97=20=20Generate=20release=20body=20f?=
=?UTF-8?q?or=20release=20tagger=20(#35507)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
build-system/release-tagger/create-release.js | 90 +++++++++++-
.../test/create-release.test.js | 138 ++++++++++++++++--
build-system/release-tagger/utils.js | 21 ++-
3 files changed, 231 insertions(+), 18 deletions(-)
diff --git a/build-system/release-tagger/create-release.js b/build-system/release-tagger/create-release.js
index 4c86fc61b295..091d8c395194 100644
--- a/build-system/release-tagger/create-release.js
+++ b/build-system/release-tagger/create-release.js
@@ -23,6 +23,7 @@
*/
const argv = require('minimist')(process.argv.slice(2));
+const dedent = require('dedent');
const {
createRelease,
getPullRequestsBetweenCommits,
@@ -30,13 +31,96 @@ const {
} = require('./utils');
const {GraphQlQueryResponseData} = require('@octokit/graphql'); //eslint-disable-line no-unused-vars
+/**
+ * Format pull request line
+ * @param {GraphQlQueryResponseData} pr
+ * @return {string}
+ */
+function _formatPullRequestLine(pr) {
+ const {mergeCommit, number, title, url} = pr;
+ const {abbreviatedOid, commitUrl} = mergeCommit;
+ return dedent`\
+ ${abbreviatedOid}
${title} \
+ (#${number})`;
+}
+
+/**
+ * Organize pull requests into sections
+ * @param {Array} prs
+ * @return {Array}
+ */
+function _createSections(prs) {
+ const sections = {
+ 'ads': [],
+ 'build-system': [],
+ 'src': [],
+ 'third_party': [],
+ 'validator': [],
+ 'package updates': new Array(), // [] defaults to never[], which tsc flags
+ };
+
+ for (const pr of prs) {
+ // renovate bot
+ if (pr.author.login === 'renovate-bot') {
+ sections['package updates'].push(_formatPullRequestLine(pr));
+ continue;
+ }
+
+ // directories
+ for (const key of Object.keys(sections)) {
+ if (pr.files.nodes.some((node) => node.path.startsWith(`${key}/`))) {
+ sections[key].push(_formatPullRequestLine(pr));
+ }
+ }
+
+ // components
+ for (const node of pr.files.nodes) {
+ if (node.path.startsWith('extensions/')) {
+ const component = node.path.split('/')[1];
+ if (!Object.keys(sections).includes(component)) {
+ sections[component] = [];
+ }
+ sections[component].push(_formatPullRequestLine(pr));
+ }
+ }
+ }
+
+ const sectionsMarkdown = [];
+ for (const key of Object.keys(sections).sort()) {
+ const orderedPrs = sections[key].sort();
+ const template = dedent`
+ \
+ \
+ ${key} (${orderedPrs.length})\
+
\
+ ${orderedPrs.join('
')}\
+ `;
+ sectionsMarkdown.push(template);
+ }
+
+ return sectionsMarkdown;
+}
+
/**
* Create body for GitHub release
+ * @param {string} base
* @param {Array} prs
* @return {string}
*/
-function _createBody(prs) {
- return prs.map((pr) => pr.number).join('\n'); // TODO: build this out please
+function _createBody(base, prs) {
+ const rawNotes = prs.map(_formatPullRequestLine);
+ const sections = _createSections(prs);
+ const template = dedent`\
+ #### *Baseline release: [${base}]\
+ (https://github.com/ampproject/amphtml/releases/${base})*
+
+ #### Raw notes
+ ${rawNotes.join('\n')}
+
+ #### Breakdown by component
+ ${sections.join('')}\
+ `;
+ return template;
}
/**
@@ -49,7 +133,7 @@ async function main(tag, previousTag) {
const {'target_commitish': commit} = await getRelease(tag);
const {'target_commitish': previousCommit} = await getRelease(previousTag);
const prs = await getPullRequestsBetweenCommits(commit, previousCommit);
- const body = _createBody(prs);
+ const body = _createBody(previousTag, prs);
return await createRelease(tag, commit, body);
}
diff --git a/build-system/release-tagger/test/create-release.test.js b/build-system/release-tagger/test/create-release.test.js
index c7452ac410d0..33089277826b 100644
--- a/build-system/release-tagger/test/create-release.test.js
+++ b/build-system/release-tagger/test/create-release.test.js
@@ -25,31 +25,56 @@ test.after(() => {
});
test('create', async (t) => {
+ const pr1 =
+ '' +
+ '1abc
Bunch of changes ' +
+ '(#1)';
+ const pr2 =
+ '' +
+ '2abc
`README` updates ' +
+ '(#2)';
+ const pr3 =
+ '' +
+ '3abc
Update packages ' +
+ '(#3)';
+
const rest = nock('https://api.github.com')
// https://docs.github.com/en/rest/reference/repos#get-a-release-by-tag-name
.get('/repos/ampproject/amphtml/releases/tags/2107280123000')
.reply(200, {
id: 2,
- 'target_commitish': 'abcde3',
+ 'target_commitish': '3abcdef',
})
// https://docs.github.com/en/rest/reference/repos#get-a-release-by-tag-name
.get('/repos/ampproject/amphtml/releases/tags/2107210123000')
.reply(200, {
id: 1,
- 'target_commitish': 'abcde1',
+ 'target_commitish': '1abcdef',
})
// https://docs.github.com/en/rest/reference/repos#compare-two-commits
- .get('/repos/ampproject/amphtml/compare/abcde1...abcde3')
+ .get('/repos/ampproject/amphtml/compare/1abcdef...3abcdef')
.reply(200, {
- commits: [{sha: 'abcde1'}, {sha: 'abcde2'}, {sha: 'abcde3'}],
+ commits: [{sha: '1abcdef'}, {sha: '2abcdef'}, {sha: '3abcdef'}],
})
// https://docs.github.com/en/rest/reference/repos#create-a-release
.post('/repos/ampproject/amphtml/releases', {
name: '2107280123000',
'tag_name': '2107280123000',
- 'target_commitish': 'abcde3',
- body: '',
+ 'target_commitish': '3abcdef',
prerelease: true,
+ body:
+ '#### *Baseline release: [2107210123000]' +
+ '(https://github.com/ampproject/amphtml/releases/2107210123000)*\n\n' +
+ '#### Raw notes\n' +
+ `${pr1}\n${pr2}\n${pr3}\n\n` +
+ '#### Breakdown by component\n' +
+ `ads (1)
${pr1} ` +
+ `amp-bind (1)
${pr1} ` +
+ `build-system (1)
${pr2} ` +
+ `package updates (1)
${pr3} ` +
+ `src (1)
${pr1} ` +
+ `third_party (2)
${pr1}
${pr2} ` +
+ `validator (1)
${pr1} `,
})
.reply(200, {});
@@ -57,20 +82,113 @@ test('create', async (t) => {
.post(
'',
'{"query":"query {' +
- 'pr0: search(query:\\"repo:ampproject/amphtml sha:abcde1\\", ' +
+ 'pr0: search(query:\\"repo:ampproject/amphtml sha:1abcdef\\", ' +
'type:ISSUE first:100){nodes { ... on PullRequest { id title number ' +
'url author { login } files(first:100) { nodes { path }} mergeCommit ' +
'{ commitUrl oid abbreviatedOid }}}} ' +
- 'pr1: search(query:\\"repo:ampproject/amphtml sha:abcde2\\", ' +
+ 'pr1: search(query:\\"repo:ampproject/amphtml sha:2abcdef\\", ' +
'type:ISSUE first:100){nodes { ... on PullRequest { id title number ' +
'url author { login } files(first:100) { nodes { path }} mergeCommit ' +
'{ commitUrl oid abbreviatedOid }}}} ' +
- 'pr2: search(query:\\"repo:ampproject/amphtml sha:abcde3\\", ' +
+ 'pr2: search(query:\\"repo:ampproject/amphtml sha:3abcdef\\", ' +
'type:ISSUE first:100){nodes { ... on PullRequest { id title number ' +
'url author { login } files(first:100) { nodes { path }} mergeCommit ' +
'{ commitUrl oid abbreviatedOid }}}}}"}'
)
- .reply(200, {data: {pr0: 'todo', pr1: 'todo', pr2: 'todo'}});
+ .reply(200, {
+ data: {
+ pr0: {
+ nodes: [
+ {
+ id: 'MDExOlB1bGxSZXF1ZXN0Mjk4OTg2MDI4',
+ title: 'Bunch of changes',
+ number: 1,
+ url: 'https://github.com/ampproject/amphtml/pull/1',
+ author: {login: 'testauthor'},
+ files: {
+ nodes: [
+ {
+ 'path': 'ads/readme.md',
+ },
+ {
+ 'path': 'extensions/amp-bind/readme.md',
+ },
+ {
+ 'path': 'src/readme.md',
+ },
+ {
+ 'path': 'third_party/tasks/e2e/readme.md',
+ },
+ {
+ 'path': 'validator/readme.md',
+ },
+ ],
+ },
+ mergeCommit: {
+ commitUrl:
+ 'https://github.com/ampproject/amphtml/commit/1abcdef',
+ oid: '1abcdef',
+ abbreviatedOid: '1abc',
+ },
+ },
+ ],
+ },
+ pr1: {
+ nodes: [
+ {
+ id: 'MDExOlB1bGxSZXF1ZXN0MzAyMTU2MjIy',
+ title: '`README` updates',
+ number: 2,
+ url: 'https://github.com/ampproject/amphtml/pull/2',
+ author: {login: 'testauthor'},
+ files: {
+ nodes: [
+ {
+ 'path': 'build-system/tasks/e2e/readme.md',
+ },
+ {
+ 'path': 'third_party/tasks/e2e/readme.md',
+ },
+ ],
+ },
+ mergeCommit: {
+ commitUrl:
+ 'https://github.com/ampproject/amphtml/commit/2abcdef',
+ oid: '2abcdef',
+ abbreviatedOid: '2abc',
+ },
+ },
+ ],
+ },
+ pr2: {
+ nodes: [
+ {
+ id: 'MDExOlB1bGxSZXF1ZXN0MzAyMTU4NDIw',
+ title: 'Update packages',
+ number: 3,
+ url: 'https://github.com/ampproject/amphtml/pull/3',
+ author: {login: 'renovate-bot'},
+ files: {
+ nodes: [
+ {
+ 'path': 'extensions/amp-bind/readme.md',
+ },
+ {
+ 'path': 'extensions/amp-story/readme.md',
+ },
+ ],
+ },
+ mergeCommit: {
+ commitUrl:
+ 'https://github.com/ampproject/amphtml/commit/3abcdef',
+ oid: '3abcdef',
+ abbreviatedOid: '3abc',
+ },
+ },
+ ],
+ },
+ },
+ });
await createRelease('2107280123000', '2107210123000');
t.true(rest.isDone());
diff --git a/build-system/release-tagger/utils.js b/build-system/release-tagger/utils.js
index af927b669809..9b81106e987f 100644
--- a/build-system/release-tagger/utils.js
+++ b/build-system/release-tagger/utils.js
@@ -27,15 +27,14 @@ const {Octokit} = require('@octokit/rest');
// setup
const octokit = new Octokit({
- auth: '',
+ auth: process.env.GITHUB_TOKEN,
userAgent: 'amp release tagger',
- previews: ['groot-preview'], // to list pull requests by commit
timeZone: 'America/New_York',
});
const graphqlWithAuth = graphql.defaults({
headers: {
- authorization: `token todo`,
+ authorization: `token ${process.env.GITHUB_TOKEN}`,
},
});
const owner = 'ampproject';
@@ -58,7 +57,8 @@ async function _runQueryInBatches(queryType, queries) {
for (let i = 0; i < queries.length; i += config.batchSize) {
const join = queries.slice(i, config.batchSize).join(' ');
const query = `${queryType} {${join}}`;
- responses.push(await graphqlWithAuth(query));
+ const data = await graphqlWithAuth(query);
+ responses.push(...Object.values(data));
}
return responses;
}
@@ -140,7 +140,18 @@ async function getPullRequests(shas) {
mergeCommit { commitUrl oid abbreviatedOid }}}}`
);
}
- return await _runQueryInBatches('query', queries);
+ const nodesLists = await _runQueryInBatches('query', queries);
+
+ // Only return pull requests with the merge commit shas
+ const prs = [];
+ for (const nodesList of nodesLists) {
+ for (const node of nodesList.nodes) {
+ if (node.mergeCommit && shas.includes(node.mergeCommit.oid)) {
+ prs.push(node);
+ }
+ }
+ }
+ return prs;
}
/**