mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
164 lines
4.9 KiB
JavaScript
164 lines
4.9 KiB
JavaScript
|
const assert = require('assert').strict;
|
||
|
const runCommand = require('../runCommand');
|
||
|
const { parseChangelog } = require('./parseChangelog');
|
||
|
const { changeCategories } = require('./constants');
|
||
|
|
||
|
async function getMostRecentTag() {
|
||
|
const [mostRecentTagCommitHash] = await runCommand('git', [
|
||
|
'rev-list',
|
||
|
'--tags',
|
||
|
'--max-count=1',
|
||
|
]);
|
||
|
const [mostRecentTag] = await runCommand('git', [
|
||
|
'describe',
|
||
|
'--tags',
|
||
|
mostRecentTagCommitHash,
|
||
|
]);
|
||
|
assert.equal(mostRecentTag[0], 'v', 'Most recent tag should start with v');
|
||
|
return mostRecentTag;
|
||
|
}
|
||
|
|
||
|
async function getCommits(commitHashes) {
|
||
|
const commits = [];
|
||
|
for (const commitHash of commitHashes) {
|
||
|
const [subject] = await runCommand('git', [
|
||
|
'show',
|
||
|
'-s',
|
||
|
'--format=%s',
|
||
|
commitHash,
|
||
|
]);
|
||
|
|
||
|
let prNumber;
|
||
|
let description = subject;
|
||
|
|
||
|
// Squash & Merge: the commit subject is parsed as `<description> (#<PR ID>)`
|
||
|
if (subject.match(/\(#\d+\)/u)) {
|
||
|
const matchResults = subject.match(/\(#(\d+)\)/u);
|
||
|
prNumber = matchResults[1];
|
||
|
description = subject.match(/^(.+)\s\(#\d+\)/u)[1];
|
||
|
// Merge: the PR ID is parsed from the git subject (which is of the form `Merge pull request
|
||
|
// #<PR ID> from <branch>`, and the description is assumed to be the first line of the body.
|
||
|
// If no body is found, the description is set to the commit subject
|
||
|
} else if (subject.match(/#\d+\sfrom/u)) {
|
||
|
const matchResults = subject.match(/#(\d+)\sfrom/u);
|
||
|
prNumber = matchResults[1];
|
||
|
const [firstLineOfBody] = await runCommand('git', [
|
||
|
'show',
|
||
|
'-s',
|
||
|
'--format=%b',
|
||
|
commitHash,
|
||
|
]);
|
||
|
description = firstLineOfBody || subject;
|
||
|
}
|
||
|
// Otherwise:
|
||
|
// Normal commits: The commit subject is the description, and the PR ID is omitted.
|
||
|
|
||
|
commits.push({ prNumber, description });
|
||
|
}
|
||
|
return commits;
|
||
|
}
|
||
|
|
||
|
function getAllChangeDescriptions(changelog) {
|
||
|
const releases = changelog.getReleases();
|
||
|
const changeDescriptions = Object.values(
|
||
|
changelog.getUnreleasedChanges(),
|
||
|
).flat();
|
||
|
for (const release of releases) {
|
||
|
changeDescriptions.push(
|
||
|
...Object.values(changelog.getReleaseChanges(release.version)).flat(),
|
||
|
);
|
||
|
}
|
||
|
return changeDescriptions;
|
||
|
}
|
||
|
|
||
|
function getAllLoggedPrNumbers(changelog) {
|
||
|
const changeDescriptions = getAllChangeDescriptions(changelog);
|
||
|
|
||
|
const prNumbersWithChangelogEntries = [];
|
||
|
for (const description of changeDescriptions) {
|
||
|
const matchResults = description.match(/^\[#(\d+)\]/u);
|
||
|
if (matchResults === null) {
|
||
|
continue;
|
||
|
}
|
||
|
const prNumber = matchResults[1];
|
||
|
prNumbersWithChangelogEntries.push(prNumber);
|
||
|
}
|
||
|
|
||
|
return prNumbersWithChangelogEntries;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @typedef {import('./constants.js').Version} Version
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* Update a changelog with any commits made since the last release. Commits for
|
||
|
* PRs that are already included in the changelog are omitted.
|
||
|
* @param {Object} options
|
||
|
* @param {string} options.changelogContent - The current changelog
|
||
|
* @param {Version} options.currentVersion - The current version
|
||
|
* @param {string} options.repoUrl - The GitHub repository URL for the current
|
||
|
* project.
|
||
|
* @param {boolean} options.isReleaseCandidate - Denotes whether the current
|
||
|
* project is in the midst of release preparation or not. If this is set, any
|
||
|
* new changes are listed under the current release header. Otherwise, they
|
||
|
* are listed under the 'Unreleased' section.
|
||
|
* @returns
|
||
|
*/
|
||
|
async function updateChangelog({
|
||
|
changelogContent,
|
||
|
currentVersion,
|
||
|
repoUrl,
|
||
|
isReleaseCandidate,
|
||
|
}) {
|
||
|
const changelog = parseChangelog({ changelogContent, repoUrl });
|
||
|
|
||
|
// Ensure we have all tags on remote
|
||
|
await runCommand('git', ['fetch', '--tags']);
|
||
|
const mostRecentTag = await getMostRecentTag();
|
||
|
const commitsHashesSinceLastRelease = await runCommand('git', [
|
||
|
'rev-list',
|
||
|
`${mostRecentTag}..HEAD`,
|
||
|
]);
|
||
|
const commits = await getCommits(commitsHashesSinceLastRelease);
|
||
|
|
||
|
const loggedPrNumbers = getAllLoggedPrNumbers(changelog);
|
||
|
const newCommits = commits.filter(
|
||
|
({ prNumber }) => !loggedPrNumbers.includes(prNumber),
|
||
|
);
|
||
|
|
||
|
if (newCommits.length === 0) {
|
||
|
return undefined;
|
||
|
}
|
||
|
|
||
|
// Ensure release header exists, if necessary
|
||
|
if (
|
||
|
isReleaseCandidate &&
|
||
|
!changelog
|
||
|
.getReleases()
|
||
|
.find((release) => release.version === currentVersion)
|
||
|
) {
|
||
|
changelog.addRelease({ currentVersion });
|
||
|
}
|
||
|
|
||
|
const newChangeEntries = newCommits.map(({ prNumber, description }) => {
|
||
|
if (prNumber) {
|
||
|
const prefix = `[#${prNumber}](${repoUrl}/pull/${prNumber})`;
|
||
|
return `${prefix}: ${description}`;
|
||
|
}
|
||
|
return description;
|
||
|
});
|
||
|
|
||
|
for (const description of newChangeEntries.reverse()) {
|
||
|
changelog.addChange({
|
||
|
version: isReleaseCandidate ? currentVersion : undefined,
|
||
|
category: changeCategories.Uncategorized,
|
||
|
description,
|
||
|
});
|
||
|
}
|
||
|
|
||
|
return changelog.toString();
|
||
|
}
|
||
|
|
||
|
module.exports = { updateChangelog };
|