6
StarHorizon_Public/Tools/_NF/changelog/validate_changelog.js
2026-01-24 12:49:55 +03:00

136 lines
3.7 KiB
JavaScript

// Dependencies
const fs = require("fs");
// Regexes
const HeaderRegex = /^\s*(?::cl:|๐Ÿ†‘) *([a-z0-9_\-, ]+)?/img; // :cl: or ๐Ÿ†‘ [0] followed by optional author name [1]
const EntryRegex = /^ *[*-]? *(\w+): *([^\n\r]+)\r?$/img; // * or - followed by change type [0] and change message [1]
const CommentRegex = /<!--.*?-->/gs; // HTML comments
// Main function
async function main() {
// Check changelog directory.
// TODO: restore this when we set up the environment variable
/*
if (!process.env.CHANGELOG_DIR) {
console.log("CHANGELOG_DIR not defined, exiting.");
return process.exit(1);
}
const ChangelogFilePath = `../../../${process.env.CHANGELOG_DIR}`
if (!fs.existsSync(ChangelogFilePath)) {
console.log(`Cannot find changelog at "${ChangelogFilePath}", exiting.`);
return process.exit(1);
}
*/
// Read GitHub event payload
const eventPath = process.env.GITHUB_EVENT_PATH;
if (!eventPath) {
console.error("GITHUB_EVENT_PATH not set.");
process.exit(1);
}
const event = JSON.parse(fs.readFileSync(eventPath, 'utf8'));
const body = event.pull_request && event.pull_request.body ? event.pull_request.body : '';
// Remove comments from the body
commentlessBody = body.replace(CommentRegex, '');
// Get author
const headerMatch = HeaderRegex.exec(commentlessBody);
if (!headerMatch) {
console.log("No changelog entry found.");
return;
}
let author = headerMatch[1];
if (author) {
author = author.trim();
}
// Offset results past the header
commentlessBody = commentlessBody.slice(HeaderRegex.lastIndex);
// Get all changes from the body
const results = getChanges(commentlessBody);
if (results.entries.length <= 0)
{
console.log("PR has a changelog header but no valid entries. Either remove the changelog completely, or use entries of the format '- add: text', '- remove: text', '- tweak: text', or '- fix: text'.");
return process.exit(1);
}
let success = true;
results.errors.forEach((entry) => {
console.log(`Invalid changelog entry: "${entry.type}" with message "${entry.message}"`);
success = false;
});
if (!success)
return process.exit(1);
console.log("Changelog is valid.")
if (author)
console.log(`Author: "${author}"`)
console.log("Entries:");
results.entries.forEach((entry) => {
console.log(`${entry.type}: ${entry.message}`);
});
}
// Get all changes from the PR body
function getChanges(body) {
const matches = [];
const entries = [];
const errors = [];
for (const match of body.matchAll(EntryRegex)) {
matches.push([match[1], match[2]]);
}
if (!matches)
{
console.log("No changelog entries found.");
return;
}
// Check change types and construct changelog entry
matches.forEach((entry) => {
let type;
switch (entry[0].toLowerCase()) {
case "add":
type = "Add";
break;
case "remove":
type = "Remove";
break;
case "tweak":
type = "Tweak";
break;
case "fix":
type = "Fix";
break;
default:
break;
}
if (type) {
entries.push({
type: type,
message: entry[1],
});
} else {
errors.push({
type: entry[0],
message: entry[1]
});
}
});
return {entries: entries, errors: errors};
}
// Run main
main();