From 6c21b29c50bc6f99d66ac3fe0da80f2dcbc70244 Mon Sep 17 00:00:00 2001 From: Debug <49997488+DebugOk@users.noreply.github.com> Date: Mon, 11 Sep 2023 15:58:45 +0200 Subject: [PATCH] Add automatic changelogs (#11) --- .github/workflows/changelog.yml | 56 +++++++++ Resources/Changelog/DeltaVChangelog.yml | 1 + Tools/changelog/changelog.js | 153 ++++++++++++++++++++++++ Tools/changelog/package.json | 7 ++ 4 files changed, 217 insertions(+) create mode 100644 .github/workflows/changelog.yml create mode 100644 Resources/Changelog/DeltaVChangelog.yml create mode 100644 Tools/changelog/changelog.js create mode 100644 Tools/changelog/package.json diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml new file mode 100644 index 00000000000..a44cac2cd1a --- /dev/null +++ b/.github/workflows/changelog.yml @@ -0,0 +1,56 @@ +name: PR Changelogs +concurrency: commit_action +on: + pull_request_target: + types: [closed] + +env: + GITHUB_TOKEN: ${{ secrets.BOT_TOKEN }} + CHANGELOG_DIR: ${{ vars.CHANGELOG_DIR }} + PR_NUMBER: ${{ github.event.number }} + +jobs: + changelog: + runs-on: ubuntu-latest + if: github.event.pull_request.merged == true + permissions: + contents: write + steps: + - name: Checkout Master + uses: actions/checkout@v3 + with: + token: ${{ secrets.BOT_TOKEN }} + ref: "${{ vars.CHANGELOG_BRANCH }}" + + - name: Setup Git + run: | + git config --global user.name "${{ vars.CHANGELOG_USER }}" + git config --global user.email "${{ vars.CHANGELOG_EMAIL }}" + shell: bash + + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: 18.x + + - name: Install Dependencies + run: | + cd "Tools/changelog" + npm install + shell: bash + continue-on-error: true + + - name: Generate Changelog + run: | + cd "Tools/changelog" + node changelog.js + shell: bash + continue-on-error: true + + - name: Commit Changelog + run: | + git add *.yml + git commit -m "${{ vars.CHANGELOG_MESSAGE }} (#${{ env.PR_NUMBER }})" + git push + shell: bash + continue-on-error: true diff --git a/Resources/Changelog/DeltaVChangelog.yml b/Resources/Changelog/DeltaVChangelog.yml new file mode 100644 index 00000000000..7c115857f04 --- /dev/null +++ b/Resources/Changelog/DeltaVChangelog.yml @@ -0,0 +1 @@ +Entries: \ No newline at end of file diff --git a/Tools/changelog/changelog.js b/Tools/changelog/changelog.js new file mode 100644 index 00000000000..07a70f30fb2 --- /dev/null +++ b/Tools/changelog/changelog.js @@ -0,0 +1,153 @@ +// Dependencies +const fs = require("fs"); +const yaml = require("js-yaml"); +const axios = require("axios"); + +// Use GitHub token if available +if (process.env.GITHUB_TOKEN) axios.defaults.headers.common["Authorization"] = `Bearer ${process.env.GITHUB_TOKEN}`; + +// Regexes +const HeaderRegex = /^\s*(?::cl:|🆑) *([a-z0-9_\- ]+)?\s+/im; // :cl: or 🆑 [0] followed by optional author name [1] +const EntryRegex = /^ *[*-]? *(add|remove|tweak|fix): *([^\n\r]+)\r?$/img; // * or - followed by change type [0] and change message [1] +const CommentRegex = //gs; // HTML comments + +// Main function +async function main() { + // Get PR details + const pr = await axios.get(`https://api.github.com/repos/${process.env.GITHUB_REPOSITORY}/pulls/${process.env.PR_NUMBER}`); + const { merged_at, body, user } = pr.data; + + // Remove comments from the body + commentlessBody = body.replace(CommentRegex, ''); + + // Get author + const headerMatch = HeaderRegex.exec(commentlessBody); + if (!headerMatch) { + console.log("No changelog entry found, skipping"); + return; + } + + let author = headerMatch[1]; + if (!author) { + console.log("No author found, setting it to author of the PR\n"); + author = user.login; + } + + // Get all changes from the body + const entries = getChanges(commentlessBody); + + + // Time is something like 2021-08-29T20:00:00Z + // Time should be something like 2023-02-18T00:00:00.0000000+00:00 + let time = merged_at; + if (time) + { + time = time.replace("z", ".0000000+00:00").replace("Z", ".0000000+00:00"); + } + else + { + console.log("Pull request was not merged, skipping"); + return; + } + + + // Construct changelog yml entry + const entry = { + author: author, + changes: entries, + id: getHighestCLNumber() + 1, + time: time, + }; + + // Write changelogs + writeChangelog(entry); + + console.log(`Changelog updated with changes from PR #${process.env.PR_NUMBER}`); +} + + +// Code chunking + +// Get all changes from the PR body +function getChanges(body) { + const matches = []; + const entries = []; + + for (const match of body.matchAll(EntryRegex)) { + matches.push([match[1], match[2]]); + } + + if (!matches) + { + console.log("No changes found, skipping"); + 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], + }); + } + }); + + return entries; +} + +// Get the highest changelog number from the changelogs file +function getHighestCLNumber() { + // Read changelogs file + const file = fs.readFileSync(`../../${process.env.CHANGELOG_DIR}`, "utf8"); + + // Get list of CL numbers + const data = yaml.load(file); + const entries = data && data.Entries ? Array.from(data.Entries) : []; + const clNumbers = entries.map((entry) => entry.id); + + // Return highest changelog number + return Math.max(...clNumbers, 0); +} + +function writeChangelog(entry) { + let data = { Entries: [] }; + + // Create a new changelogs file if it does not exist + if (fs.existsSync(`../../${process.env.CHANGELOG_DIR}`)) { + const file = fs.readFileSync(`../../${process.env.CHANGELOG_DIR}`, "utf8"); + data = yaml.load(file); + } + + data.Entries.push(entry); + + // Write updated changelogs file + fs.writeFileSync( + `../../${process.env.CHANGELOG_DIR}`, + "Entries:\n" + + yaml.dump(data.Entries, { indent: 2 }).replace(/^---/, "") + ); +} + +// Run main +main(); diff --git a/Tools/changelog/package.json b/Tools/changelog/package.json new file mode 100644 index 00000000000..512a3624c87 --- /dev/null +++ b/Tools/changelog/package.json @@ -0,0 +1,7 @@ +{ + "name": "changelogs", + "dependencies": { + "axios": "^1.3.4", + "js-yaml": "^4.1.0" + } +}