mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-26 12:29:06 +01:00
312f2afc41
The `auto-changelog.js` script has been refactoring into various different modules. This was done in preparation for migrating this to a separate repository, where it can be used in our libraries as well. Functionally this should act _mostly_ the same way, but there have been some changes. It was difficult to make this a pure refactor because of the strategy used to validate the changelog and ensure each addition remained valid. Instead of being updated in-place, the changelog is now parsed upfront and stored as a "Changelog" instance, which is a new class that was written to allow only valid changes. The new changelog is then stringified and completely overwrites the old one. The parsing had to be much more strict, as any unanticipated content would otherwise be erased unintentionally. This script now also normalizes the formatting of the changelog (though the individual change descriptions are still unformatted). The changelog stringification now accommodates non-linear releases as well. For example, you can now release v1.0.1 *after* v2.0.0, and it will be listed in chronological order while also correctly constructing the `compare` URLs for each release.
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 };
|