Skip to content

Commit

Permalink
chore: Refactor generate_changelog script.
Browse files Browse the repository at this point in the history
  • Loading branch information
Gerrit0 committed Jun 27, 2021
1 parent fc920bd commit 4f82c83
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 85 deletions.
2 changes: 1 addition & 1 deletion scripts/create_release.js
Expand Up @@ -81,7 +81,7 @@ async function main() {

console.log("Creating release...");
const changelog = await exec(
`node ${join(__dirname, "generate_changelog.js")} ${lastTag} - github`
`node ${join(__dirname, "generate_changelog.js")} ${lastTag} -`
);
await createGitHubRelease({
tag_name: currentVersion,
Expand Down
162 changes: 78 additions & 84 deletions scripts/generate_changelog.js
Expand Up @@ -15,7 +15,7 @@ const assert = require("assert");

// Commits older than this didn't follow conventional commits, so there's no
// easy way to guess how to categorize them.
const OLDEST_CHANGELOG_COMMIT = "f388b42d1a16656681bedfc45f33f1d856441c58";
const OLDEST_VERSION = "v0.16.5";

const categories = [
{ prefix: "BREAKING CHANGE", category: "Breaking Changes" },
Expand All @@ -25,11 +25,49 @@ const categories = [

const maintainers = new Set(["Gerrit Birkeland", "dependabot[bot]"]);

/** @param {string} since */
async function getLogs(since) {
const { stdout: log } = await promisify(exec)(
`git log --pretty="format:%H%x00%at%x00%an%x00%B%x00" --no-merges ${since}..master`,
{ encoding: "utf-8" }
/** @param {string} command */
async function run(command) {
const result = await promisify(exec)(command, {
encoding: "utf-8",
});
return result.stdout;
}

async function getVersions() {
const VERSION_REGEX = /^v(\d+)\.(\d+)\.(\d+)$/;

const tags = await run("git tag -l");
const versionTags = tags.split("\n").filter((t) => VERSION_REGEX.test(t));

versionTags.sort((a, b) => {
const [a1, a2, a3] = a
.match(VERSION_REGEX)
.slice(1)
.map((v) => parseInt(v));
const [b1, b2, b3] = b
.match(VERSION_REGEX)
.slice(1)
.map((v) => parseInt(v));

if (a1 == b1) {
if (a2 == b2) {
return b3 - a3;
}
return b2 - a2;
}
return b1 - a1;
});

return versionTags.slice(0, versionTags.indexOf(OLDEST_VERSION));
}

/**
* @param {string} previous
* @param {string} version
*/
async function getLogs(previous, version) {
const log = await run(
`git log --pretty="format:%H%x00%at%x00%an%x00%B%x00" --no-merges ${previous}..${version}`
);

/** @type {LogEntry[]} */
Expand Down Expand Up @@ -59,18 +97,13 @@ async function getLogs(since) {
/**
* @param {string} version
* @param {Date} date
* @param {string} target
*/
function getHeader(version, date, target) {
// In github mode, we're only generating the changelog for a single release.
if (target === "github") return;

function getHeader(version, date) {
version = version.trim();

const dateString = `(${date.getFullYear()}-${date
.getUTCMonth()
.toString()
.padStart(2, "0")}-${date.getUTCDate().toString().padStart(2, "0")})`;
const month = date.getUTCMonth().toString().padStart(2, "0");
const day = date.getUTCDate().toString().padStart(2, "0");
const dateString = ` (${date.getFullYear()}-${month}-${day})`;

if (/^\d+\.\d+\.\d+$/.test(version)) {
const [_major, minor, patch] = version.split(".");
Expand All @@ -85,44 +118,6 @@ function getHeader(version, date, target) {
return `# ${version}${dateString}`;
}

/**
* @param {readonly LogEntry[]} entries
* @param {string} target
*/
function groupByVersions(entries, target) {
/** @type {[string, LogEntry[]][]} */
const result = [];

let header = "# Unreleased";
/** @type {LogEntry[]} */
let current = [];

for (const entry of entries) {
const match = entry.message.match(/^chore: Bump version to (.*)/m);
if (match) {
// Beta version bump
if (match[1].includes("-")) {
continue;
}

if (current.length > 0) {
result.push([header, current]);
}
header = getHeader(match[1], entry.date, target);
current = [];
continue;
}

current.push(entry);
}

if (current.length > 0) {
result.push([header, current]);
}

return result;
}

/**
* @param {RegExp} regex
* @param {string} str
Expand Down Expand Up @@ -161,13 +156,8 @@ function getThanks(commits) {

/**
* @param {string} commit
* @param {string} target
*/
function commitLink(commit, target) {
if (target === "github") {
return commit;
}

function commitLink(commit) {
return `[${commit.substr(
0,
7
Expand All @@ -176,22 +166,17 @@ function commitLink(commit, target) {

/**
* @param {string} message
* @param {string} target
*/
function issueLinks(message, target) {
function issueLinks(message) {
/** @type {Set<string>} */
const issues = new Set();

for (const match of matchAll(
/(close|resolve|fixe?)[sd]? #(\d+)/gi,
message
)) {
if (target == "github") {
issues.add(`#${match[2]}`);
} else {
const url = `https://github.com/TypeStrong/typedoc/issues/${match[2]}`;
issues.add(`[#${match[2]}](${url})`);
}
const url = `https://github.com/TypeStrong/typedoc/issues/${match[2]}`;
issues.add(`[#${match[2]}](${url})`);
}

if (issues.size === 0) {
Expand All @@ -202,9 +187,8 @@ function issueLinks(message, target) {

/**
* @param {readonly LogEntry[]} commits
* @param {string} target
*/
function getCategories(commits, target) {
function getCategories(commits) {
/** @type {Map<string, string>} */
const result = new Map();

Expand All @@ -219,8 +203,8 @@ function getCategories(commits, target) {
[
" * ",
line.replace(/^[^:]+:\s*/, ""),
` (${commitLink(commit.commit, target)})`,
issueLinks(commit.message, target),
` (${commitLink(commit.commit)})`,
issueLinks(commit.message),
].join("")
);
}
Expand All @@ -237,13 +221,12 @@ function getCategories(commits, target) {

/**
* @param {readonly LogEntry[]} commits
* @param {string} target
*/
function getBody(commits, target) {
function getBody(commits) {
/** @type {string[]} */
const lines = [];

for (const [name, body] of getCategories(commits, target)) {
for (const [name, body] of getCategories(commits)) {
lines.push(`### ${name}`, "", body, "");
}

Expand All @@ -256,22 +239,33 @@ function getBody(commits, target) {
return lines.join("\n");
}

async function main(
since = OLDEST_CHANGELOG_COMMIT,
where = "CHANGELOG.md",
target = "standalone" // standalone | github
) {
const entries = await getLogs(since);
async function main(fromVersion = OLDEST_VERSION, where = "CHANGELOG.md") {
const versions = await getVersions();
let end = versions.indexOf(fromVersion) + 1;
if (end === 0) {
if (fromVersion !== OLDEST_VERSION) {
throw new Error("Invalid version");
}
end = versions.length;
}

/** @type {string[]} */
const out = [];

for (const [header, commits] of groupByVersions(entries, target)) {
if (header) {
out.push(header, "");
for (let i = 0; i < end; i++) {
const logs = await getLogs(
i + 1 === versions.length ? OLDEST_VERSION : versions[i + 1],
versions[i]
);
assert(logs.length > 0, `${versions[i]} has no commit logs`);

if (end !== 1) {
out.push(getHeader(versions[i], logs[0].date), "");
}
out.push(getBody(commits, target), "", "");

out.push(getBody(logs), "");
}

out.pop();

if (where == "-") {
Expand Down

0 comments on commit 4f82c83

Please sign in to comment.