Now implements changelog edits in a new PR #61
Workflow file for this run
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)\n" >> $GITHUB_OUTPUT | |
- name: Create Remote Branch | |
if: ${{ steps.remote-data.outputs.REMOTE_EXISTS != 'remotes/origin/added/changelog-entry'}} | |
run: | | |
echo '${{ steps.remote-data.outputs.REMOTE_EXISTS }}' | |
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 }} | |
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 potentialEntry = '- ' + process.env.ISSUE_TITLE; | |
if (labelAdded) { | |
if (potentialHead != null) { | |
potentialHead.children.push(potentialEntry); | |
} | |
} else { | |
const potentialEntryIdx = potentialHead.children.findIndex(item => typeof(item) == typeof('') && item == potentialEntry); | |
if (potentialEntryIdx >= 0) { | |
potentialHead.children.splice(potentialEntryidx, 1) | |
} | |
} | |
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" |