Skip to content

Now implements changelog edits in a new PR #66

Now implements changelog edits in a new PR

Now implements changelog edits in a new PR #66

name: "Auto Changelog"
on:
pull_request:
types: [labeled, unlabeled]
branches:
- '**'
jobs:
auto-changelog:
if: ${{ github.event.label.name == 'Changelog' }}
env:
LABEL_ADDED: ${{ github.event.action == 'labeled' }}
PR_TITLE: ${{ github.event.pull_request.title }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.event.repository.default_branch }}
- id: remote-data
run: |
git branch -a -l origin/added/changelog-entry
echo "REMOTE_EXISTS=$(git branch -a -l origin/added/changelog-entry)" >> $GITHUB_OUTPUT
- name: Create Remote Branch
if: ${{ !contains(steps.remote-data.outputs.REMOTE_EXISTS, 'remotes/origin/added/changelog-entry') }}
run: |
git branch added/changelog-entry
git push -u origin added/changelog-entry
- continue-on-error: true
run: |
git checkout added/changelog-entry
- name: Import GPG key
uses: crazy-max/ghaction-import-gpg@v6
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
passphrase: ${{ secrets.GPG_SIGNING_PASSPHRASE }}
git_user_signingkey: true
git_commit_gpgsign: true
- name: CHANGELOG Setup
uses: actions/github-script@v7
env:
HEAD_REF: ${{ github.head_ref }}
ISSUE_TITLE: ${{ env.PR_TITLE }}
ISSUE_NUMBER: ${{ github.event.pull_request.number }}
ISSUE_URL: ${{ github.event.pull_request.html_url }}
with:
script: |
const fs = require('fs');
const filePath = 'CHANGELOG.md';
const headRef = process.env.HEAD_REF;
const labelAdded = process.env.LABEL_ADDED == 'true';
Array.prototype.insert = function ( index, ...items ) {
this.splice( index, 0, ...items );
};
class MarkdownHead {
constructor(level = 0, parent = null) {
this.level = level;
this.children = [];
if (parent != null && parent.children != null)
parent.children.push(this);
}
findOrCreateChildHead(firstLineContains, template) {
for (var idx = 0; idx < this.children.length; idx++) {
var cur = this.children[idx];
if (typeof(cur) == typeof('') || cur.children == null) continue;
if (cur.children.length > 0 && cur.children[0].toLowerCase().includes(firstLineContains.toLowerCase())) {
return cur;
}
}
var newHead = new MarkdownHead(this.level + 1, this);
if (template != null)
newHead.children = template.split('\n');
else
newHead.children = [ '#'.repeat(newHead.level) + ' ' + firstLineContains ];
return newHead;
}
cleanup() {
while (this.children.length > 0 && typeof(this.children[this.children.length - 1]) == typeof('') && this.children[this.children.length - 1].replace(' ', '') == '')
this.children.pop();
while (this.children.length > 0 && typeof(this.children[0]) == typeof('') && this.children[0].replace(' ', '') == '')
this.children.remove(0);
}
parse(markdown) {
var inChild = false;
const markdownSplit = markdown.split('\n');
const headStack = [ this ];
for (var idx = 0; idx < markdownSplit.length; idx++) {
const currentLine = markdownSplit[idx];
const match = currentLine.match(/^(#+).*$/)
const matchLevel = match != null ? match[1].length : -1;
var newHead;
// Handle all head changes
if (match && matchLevel > headStack[headStack.length - 1].level) {
newHead = new MarkdownHead(matchLevel, headStack[headStack.length - 1]);
headStack.push(newHead);
} else if (match && matchLevel == headStack[headStack.length - 1].level) {
headStack.pop().cleanup();
newHead = new MarkdownHead(matchLevel, headStack[headStack.length - 1]);
headStack.push(newHead);
} else if (match && matchLevel < headStack[headStack.length - 1].level) {
while (matchLevel <= headStack[headStack.length - 1].level) {
headStack.pop().cleanup();
}
newHead = new MarkdownHead(matchLevel, headStack[headStack.length - 1]);
headStack.push(newHead);
}
const currentHead = headStack[headStack.length - 1];
currentHead.children.push(currentLine);
}
this.cleanup();
}
toString() {
var curString = "";
for (var idx = 0; idx < this.children.length; idx++) {
const curElem = this.children[idx];
if (typeof(curElem) == typeof(""))
curString += `${curElem}\n`;
else {
if (curElem.children.length <= 1)
continue; // Skip empty sections
if (curElem.children.length > 1 && curElem.children[1] != "")
curElem.children.insert(1, "");
if (curElem.children.length > 0 && curElem.children[0] != "" &&
curString.length > 0 && !curString.endsWith('\n\n'))
curElem.children.insert(0, "");
curString += curElem.toString();
}
}
return curString;
}
}
const DefaultChangelog = `# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).`;
// Check if the file exists and read its content if it does
let fileContent = '';
if (fs.existsSync(filePath)) {
fileContent = fs.readFileSync(filePath, 'utf8');
}
const masterHead = new MarkdownHead();
masterHead.parse(fileContent);
(() => { //
masterHead.changelog = masterHead.findOrCreateChildHead("Changelog", DefaultChangelog);
masterHead.changelog.unreleased = masterHead.changelog.findOrCreateChildHead("[Unreleased]");
masterHead.changelog.unreleased.added =
masterHead.changelog.unreleased.findOrCreateChildHead("Added");
masterHead.changelog.unreleased.changed =
masterHead.changelog.unreleased.findOrCreateChildHead("Changed");
masterHead.changelog.unreleased.deprecated =
masterHead.changelog.unreleased.findOrCreateChildHead("Deprecated");
masterHead.changelog.unreleased.removed =
masterHead.changelog.unreleased.findOrCreateChildHead("Removed");
masterHead.changelog.unreleased.fixed =
masterHead.changelog.unreleased.findOrCreateChildHead("Fixed");
masterHead.changelog.unreleased.secured =
masterHead.changelog.unreleased.findOrCreateChildHead("Secured");
})();
var category = headRef.split('/')[0];
var potentialHead = masterHead.changelog.unreleased[category];
var entryPRPrefix = '- [#' + process.env.ISSUE_NUMBER + '](' + process.env.ISSUE_URL + ')';
var potentialEntry = entryPRPrefix + ' ' + process.env.ISSUE_TITLE;
if (labelAdded) {
if (potentialHead != null) {
potentialHead.children.push(potentialEntry);
}
} else {
const newChildren = [];
for (var curIdx = 0; curIdx < potentialHead.children.length; curIdx++) {
const curItem = potentialHead.children[curIdx];
if (typeof(curItem) != typeof('') || !curItem.startsWith(entryPRPrefix)) {
newChildren.push(curItem);
}
}
potentialHead.children = newChildren;
}
fs.writeFileSync(filePath, masterHead.toString());
console.log("-=-=-=-=-\nWhat we have exported...");
console.log(masterHead.toString());
- continue-on-error: true
env:
GH_TOKEN: ${{ github.token }}
run: |
git add .
git commit -am "Updated CHANGELOG.md"
git push
gh pr create --base ${{ github.event.repository.default_branch }} --head added/changelog-entry --title "Updated CHANGELOG.md" --body "" || echo "PR already exists or another error occurred"