Move changelog-related definitions into their own module

This commit is contained in:
Michael B. Gale
2026-07-14 14:47:54 +01:00
parent 2c45c8158b
commit 4b861b89fc
2 changed files with 128 additions and 126 deletions

124
pr-checks/changelog.ts Normal file
View File

@@ -0,0 +1,124 @@
import * as fs from "node:fs";
import { CHANGELOG_FILE, DryRunOption } from "./config";
/** Placeholder changelog content for a new release. */
export const EMPTY_CHANGELOG = `# CodeQL Action Changelog
## [UNRELEASED]
No user facing changes.
`;
/** Returns today's date formatted as `DD Mon YYYY`. */
export function getTodayString(): string {
const today = new Date();
return today.toLocaleDateString("en-GB", {
day: "2-digit",
month: "short",
year: "numeric",
});
}
/**
* Updates the `[UNRELEASED]` marker in `CHANGELOG.md` with the given version
* and today's date.
*/
export function setVersionAndDate(
options: DryRunOption,
version: string,
): void {
let content: string;
if (fs.existsSync(CHANGELOG_FILE)) {
content = fs.readFileSync(CHANGELOG_FILE, "utf8");
} else {
content = EMPTY_CHANGELOG;
}
const versionAndDate = `${version} - ${getTodayString()}`;
const newContent = content.replace("[UNRELEASED]", versionAndDate);
if (!options.dryRun) {
fs.writeFileSync(CHANGELOG_FILE, newContent, "utf8");
} else {
console.info(
`[DRY RUN] Would have replaced '[UNRELEASED]' in '${CHANGELOG_FILE}' with '${versionAndDate}'.`,
);
}
}
/**
* Processes changelog entries for a backport, converting version references
* from the source major version to the target major version and filtering
* entries that only apply to newer versions.
*/
export function processChangelogForBackports(
sourceBranchMajorVersion: string,
targetBranchMajorVersion: string,
): void {
const content = fs.readFileSync(CHANGELOG_FILE, "utf8");
const lines = content.split("\n");
const someVersionsOnlyRegex = /\[v(\d+)\+ only\]/;
let output = "";
let i = 0;
// Copy lines until we find the first section heading.
let foundFirstSection = false;
while (!foundFirstSection && i < lines.length) {
let line = lines[i];
if (line.startsWith("## ")) {
line = line.replace(
`## ${sourceBranchMajorVersion}`,
`## ${targetBranchMajorVersion}`,
);
foundFirstSection = true;
}
output += `${line}\n`;
i++;
}
if (!foundFirstSection) {
throw new Error("Could not find any change sections in CHANGELOG.md");
}
// Process remaining lines.
let foundContent = false;
output += "\n";
while (i < lines.length) {
let line = lines[i];
i++;
// Filter out changelog entries that only apply to newer versions.
const match = someVersionsOnlyRegex.exec(line);
if (match) {
if (
Number.parseInt(targetBranchMajorVersion) < Number.parseInt(match[1])
) {
continue;
}
}
if (line.startsWith("## ")) {
line = line.replace(
`## ${sourceBranchMajorVersion}`,
`## ${targetBranchMajorVersion}`,
);
if (!foundContent) {
output += "No user facing changes.\n";
}
foundContent = false;
output += `\n${line}\n\n`;
} else {
if (line.trim() !== "") {
foundContent = true;
output += `${line}\n`;
}
}
}
fs.writeFileSync(CHANGELOG_FILE, output, "utf8");
}

View File

@@ -23,21 +23,8 @@ import * as fs from "node:fs";
import { parseArgs } from "node:util";
import { type ApiClient, getApiClient } from "./api-client";
import {
CHANGELOG_FILE,
DryRunOption,
PACKAGE_JSON,
REPO_ROOT,
} from "./config";
/** Placeholder changelog content for a new release. */
const EMPTY_CHANGELOG = `# CodeQL Action Changelog
## [UNRELEASED]
No user facing changes.
`;
import * as changelog from "./changelog";
import { DryRunOption, PACKAGE_JSON, REPO_ROOT } from "./config";
/**
* NB: This exact commit message is used to find commits for reverting during backports.
@@ -190,115 +177,6 @@ export function replaceVersionInPackageJson(
fs.writeFileSync(PACKAGE_JSON, `${output.join("\n")}\n`, "utf8");
}
/** Returns today's date formatted as `DD Mon YYYY`. */
export function getTodayString(): string {
const today = new Date();
return today.toLocaleDateString("en-GB", {
day: "2-digit",
month: "short",
year: "numeric",
});
}
/**
* Updates the `[UNRELEASED]` marker in `CHANGELOG.md` with the given version
* and today's date.
*/
export function updateChangelog(options: MainOptions, version: string): void {
let content: string;
if (fs.existsSync(CHANGELOG_FILE)) {
content = fs.readFileSync(CHANGELOG_FILE, "utf8");
} else {
content = EMPTY_CHANGELOG;
}
const versionAndDate = `${version} - ${getTodayString()}`;
const newContent = content.replace("[UNRELEASED]", versionAndDate);
if (!options.dryRun) {
fs.writeFileSync(CHANGELOG_FILE, newContent, "utf8");
} else {
console.info(
`[DRY RUN] Would have replaced '[UNRELEASED]' in '${CHANGELOG_FILE}' with '${versionAndDate}'.`,
);
}
}
/**
* Processes changelog entries for a backport, converting version references
* from the source major version to the target major version and filtering
* entries that only apply to newer versions.
*/
export function processChangelogForBackports(
sourceBranchMajorVersion: string,
targetBranchMajorVersion: string,
): void {
const content = fs.readFileSync(CHANGELOG_FILE, "utf8");
const lines = content.split("\n");
const someVersionsOnlyRegex = /\[v(\d+)\+ only\]/;
let output = "";
let i = 0;
// Copy lines until we find the first section heading.
let foundFirstSection = false;
while (!foundFirstSection && i < lines.length) {
let line = lines[i];
if (line.startsWith("## ")) {
line = line.replace(
`## ${sourceBranchMajorVersion}`,
`## ${targetBranchMajorVersion}`,
);
foundFirstSection = true;
}
output += `${line}\n`;
i++;
}
if (!foundFirstSection) {
throw new Error("Could not find any change sections in CHANGELOG.md");
}
// Process remaining lines.
let foundContent = false;
output += "\n";
while (i < lines.length) {
let line = lines[i];
i++;
// Filter out changelog entries that only apply to newer versions.
const match = someVersionsOnlyRegex.exec(line);
if (match) {
if (
Number.parseInt(targetBranchMajorVersion) < Number.parseInt(match[1])
) {
continue;
}
}
if (line.startsWith("## ")) {
line = line.replace(
`## ${sourceBranchMajorVersion}`,
`## ${targetBranchMajorVersion}`,
);
if (!foundContent) {
output += "No user facing changes.\n";
}
foundContent = false;
output += `\n${line}\n\n`;
} else {
if (line.trim() !== "") {
foundContent = true;
output += `${line}\n`;
}
}
}
fs.writeFileSync(CHANGELOG_FILE, output, "utf8");
}
/** Represents commits returned by the GitHub API (relevant fields only). */
export interface GitHubCommit {
sha: string;
@@ -795,7 +673,7 @@ export async function prepareNewBranch(
console.log(
`Migrating changelog notes from v${sourceBranchMajorVersion} to v${targetBranchMajorVersion}`,
);
processChangelogForBackports(
changelog.processChangelogForBackports(
sourceBranchMajorVersion,
targetBranchMajorVersion,
);
@@ -818,7 +696,7 @@ export async function prepareNewBranch(
);
console.log("Updating changelog");
updateChangelog(options, version);
changelog.setVersionAndDate(options, version);
runGit(["add", "CHANGELOG.md"], {
dryRun: options.dryRun,