bitburner-src/tools/fetch-changelog/index.js
Martin Fournier c367de24a8 Add tool & workflow to fetch changelog
Rewrote the changelog script to use primarily merged pull requests
between "commit A" and "commit B".
It uses the GitHub rest api. The resulting data is pushed to a draft gist.

Manual usage is in the README.

Also adds a GitHub workflow action to run the tool on demand, through
the GitHub action's interface.
2022-01-25 10:20:36 -05:00

241 lines
7.0 KiB
JavaScript

import { Octokit } from "@octokit/rest";
import commandLineArgs from "command-line-args";
const owner = "danielyxie";
const repo = "bitburner"
const basePath = `https://github.com/${owner}/${repo}`;
const cliArgs = commandLineArgs([
{ name: 'from', alias: 'f', type: String },
{ name: 'to', alias: 't', type: String },
{ name: 'detailed', alias: 'd', type: Boolean }
]);
class MergeChangelog {
constructor(options) {
this.octokit = new Octokit(options);
}
async getCommitsSearchResults(query) {
const iterator = this.octokit.paginate.iterator(
this.octokit.rest.search.commits,
{
owner, repo,
q: query,
sort: 'updated',
direction: 'desc',
},
);
const searchResults = [];
for await (const response of iterator) {
const entries = response.data.map((entry) => ({
sha: entry.sha,
url: entry.html_url,
user: {
id: entry.author.id,
login: entry.author.login,
avatar: entry.author.avatar_url,
url: entry.author.html_url,
},
commit_date: entry.commit.committer.date,
message: entry.commit.message,
}));
searchResults.push(...entries);
}
return searchResults;
}
async getPullsSearchResults(query) {
const iterator = this.octokit.paginate.iterator(
this.octokit.rest.search.issuesAndPullRequests,
{
owner, repo,
q: query,
sort: 'committer-date',
direction: 'desc',
},
);
const searchResults = [];
for await (const response of iterator) {
const entries = response.data.map((entry) => ({
id: entry.id,
number: entry.number,
created_at: entry.updated_at,
merged_at: entry.pull_request.merged_at,
url: entry.pull_request.html_url,
title: entry.title,
body: entry.body,
diff: entry.diff_url,
patch: entry.patch_url,
user: {
id: entry.user.id,
login: entry.user.login,
avatar: entry.user.avatar_url,
url: entry.user.html_url,
},
}));
searchResults.push(...entries);
}
const pullRequestPromises = [];
for (const entry of searchResults) {
pullRequestPromises.push(
this.octokit.rest.pulls.get({
owner, repo,
pull_number: entry.number,
}).then((response) => ({
...entry,
merge_commit_sha: response.data.merge_commit_sha,
head_commit_sha: response.data.head.sha,
})));
}
const pulls = await Promise.all(pullRequestPromises);
return pulls;
}
async getCommit(sha) {
const response = await this.octokit.rest.git.getCommit({
owner, repo,
commit_sha: sha,
});
const commit = {
date: response.data.committer.date,
message: response.data.message,
sha: response.data.sha,
url: response.data.html_url,
}
return commit;
}
async getPullsMergedBetween(sha_from, sha_to) {
const from = {};
const to = {};
from.commit = await this.getCommit(sha_from);
from.date = new Date(from.commit.date);
if (!sha_to) {
const newest = await this.getLastCommitByBranch('dev');
to.commit = await this.getCommit(newest)
} else {
to.commit = await this.getCommit(sha_to);
}
to.date = new Date(to.commit.date);
const commitQuery = `user:${owner} repo:${repo} merge:false committer-date:"${from.date.toISOString()}..${to.date.toISOString()}"`;
const pullQuery = `user:${owner} repo:${repo} is:pr is:merged merged:"${from.date.toISOString()}..${to.date.toISOString()}"`;
const commits = await this.getCommitsSearchResults(commitQuery);
const pulls = await this.getPullsSearchResults(pullQuery);
// We only have the merge commit sha & the HEAD sha in this data, but it can exclude some entries
const pullsCommitSha = pulls.
map((p) => [p.merge_commit_sha, p.head_commit_sha]).
reduce((all, current) => [...all, ...current]);
let danglingCommits = commits.filter((c) => !pullsCommitSha.includes(c.sha));
const listPullsPromises = [];
for (const commit of danglingCommits) {
const promise = this.octokit.rest.repos.listPullRequestsAssociatedWithCommit({
owner, repo, commit_sha: commit.sha
}).then((response) => ({
...commit,
nbPulls: response.data.length,
}));
listPullsPromises.push(promise);
}
const commitsThatAreIncludedInPulls = (await Promise.all(listPullsPromises)).
filter((c) => c.nbPulls > 0).
map((c) => c.sha);
danglingCommits = danglingCommits.
filter((c) => !commitsThatAreIncludedInPulls.includes(c.sha));
return {
from,
to,
pulls,
danglingCommits,
pullQuery,
commitQuery,
}
}
async getLastCommitByBranch(branch) {
const response = await this.octokit.rest.repos.getBranch({
owner,
repo,
branch,
});
return response.data.commit.sha;
}
async getChangelog(from, to, detailedOutput) {
const changes = await this.getPullsMergedBetween(from, to);
const pullLines = changes.pulls.map((line) => this.getPullMarkdown(line, detailedOutput));
const commitLines = changes.danglingCommits.map((line) => this.getCommitMarkdown(line, detailedOutput));
commitLines.push(`* Nerf noodle bar.`)
const shortFrom = changes.from.date.toISOString().split('T')[0];
const shortTo = changes.to.date.toISOString().split('T')[0]
const shortFromSha = changes.from.commit.sha.slice(0, 7);
const shortToSha = changes.to.commit.sha.slice(0, 7);
const title = `## [draft] v1.x.x - ${shortFrom} to ${shortTo}`;
let log = `
${title}
#### Information
Modifications included between **${shortFrom}** and **${shortTo}** (\`${shortFromSha}\` to \`${shortToSha}\`).
*[See Pull Requests on GitHub](https://github.com/search?q=${encodeURIComponent(changes.pullQuery)})*
#### Merged Pull Requests
${pullLines.join('\n')}
`;
if (commitLines.length > 0) {
log += `
#### Other Changes
${commitLines.join('\n')}
`;
}
return {
log: log.trim(),
changes: changes,
};
}
getPullMarkdown(pr, detailedOutput) {
if (!detailedOutput) {
return `* ` +
`${pr.title} (by @${pr.user.login})` +
` #[${pr.number}](${pr.url})`;
} else {
return `* [${pr.merge_commit_sha.slice(0, 7)}](${basePath}/commit/${pr.merge_commit_sha}) | ` +
`${pr.title} ([@${pr.user.login}](${pr.user.url}))` +
` PR #[${pr.number}](${pr.url})`;
}
}
getCommitMarkdown(commit, detailedOutput) {
if (!detailedOutput) {
return `* ` +
`${commit.message} (by @${commit.user.login})` +
` - [${commit.sha.slice(0, 7)}](${commit.url})`;
} else {
return `* [${commit.sha.slice(0, 7)}](${commit.url}) | ` +
`${commit.message} ([@${commit.user.login}](${commit.user.url}))`;
}
}
}
const api = new MergeChangelog({ auth: process.env.GITHUB_API_TOKEN });
api.getChangelog(cliArgs.from, cliArgs.to, cliArgs.detailed).then((data) => {
console.log(data.log);
});