Skip to content

Commit

Permalink
Add release notes generation (#239)
Browse files Browse the repository at this point in the history
## Description

- Added a function to generate release notes based on merged PRs between
tags.
  • Loading branch information
forntoh authored Oct 7, 2024
2 parents 5fc8d64 + b950d97 commit 3569f5b
Show file tree
Hide file tree
Showing 2 changed files with 149 additions and 5 deletions.
31 changes: 26 additions & 5 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,12 @@ jobs:
- name: Commit changes
run: |
git config --global user.name "${{ github.actor }}"
git config --global user.email "${{ github.actor }}@users.noreply.github.com"
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git remote set-url origin https://x-access-token:${{ secrets.GH_PAT }}@github.com/forntoh/LcdMenu.git
git add .
git commit -m "Update version to $UPDATED_VERSION"
git push origin master
git push origin master --force
- name: Create Tag
if: ${{ env.UPDATED_VERSION != '' }}
Expand All @@ -66,6 +67,25 @@ jobs:
git tag -d ${{ env.UPDATED_VERSION }}
git push origin :refs/tags/${{ env.UPDATED_VERSION }}
- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: "14"

- name: Install @actions/github
run: npm install @actions/github

- name: Generate Release Notes
id: generate_release_notes
uses: actions/github-script@v6
env:
CURRENT_TAG: ${{ env.UPDATED_VERSION }}
with:
script: |
const generateReleaseNotes = require('.scripts/release_notes.js');
const releaseNotes = await generateReleaseNotes(github, context);
core.setOutput('release_notes', releaseNotes);
- name: Create GitHub Release
if: success()
uses: actions/create-release@v1
Expand All @@ -74,5 +94,6 @@ jobs:
with:
tag_name: ${{ env.UPDATED_VERSION }}
release_name: LcdMenu v${{ env.UPDATED_VERSION }}
draft: true
prerelease: false
draft: false
prerelease: false
body: ${{ steps.generate_release_notes.outputs.result }}
123 changes: 123 additions & 0 deletions .scripts/release_notes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
function escapeSpecialChars(str) {
const specialChars = /[\\`*_{}\[\]()#+\-!:.]/g;
return str.replace(specialChars, "\\$&");
}

async function generateReleaseNotes(github, context) {
const { owner, repo } = context.repo;
const currentTag = process.env.CURRENT_TAG;

// Fetch all tags
const { data: tags } = await github.rest.repos.listTags({
owner,
repo,
per_page: 100,
});

// Find the previous tag
const currentTagIndex = tags.findIndex((tag) => tag.name === currentTag);
const previousTag =
currentTagIndex < tags.length - 1 ? tags[currentTagIndex + 1].name : null;

console.log(`Current Tag: ${currentTag}`);
console.log(`Previous Tag: ${previousTag}`);

const getCommitDate = async (github, owner, repo, sha) => {
const { data: commit } = await github.rest.repos.getCommit({
owner,
repo,
ref: sha,
});
return new Date(commit.commit.committer.date);
};

const previousTagDate = previousTag
? await getCommitDate(
github,
owner,
repo,
tags.find((tag) => tag.name === previousTag).commit.sha
)
: new Date(0);

const currentTagDate = await getCommitDate(
github,
owner,
repo,
tags.find((tag) => tag.name === currentTag).commit.sha
);

// Fetch PRs merged between previousTag and currentTag
const { data: pulls } = await github.rest.pulls.list({
owner,
repo,
state: "closed",
sort: "updated",
direction: "desc",
per_page: 100,
});

const categoryNames = {
feature: "New Features",
enhancement: "Enhancements",
bugfix: "Bug Fixes",
chore: "Chore Updates",
documentation: "Documentation Updates",
};

const categories = {
feature: [],
enhancement: [],
bugfix: [],
chore: [],
documentation: [],
};

let hasBreakingChanges = false;

pulls
.filter((pr) => pr.merged_at)
.filter((pr) => {
const mergedAt = new Date(pr.merged_at);
return mergedAt > previousTagDate && mergedAt <= currentTagDate;
})
.forEach((pr) => {
const prEntry = `* ${escapeSpecialChars(pr.title)} by @${
pr.user.login
} in ${pr.html_url}`;

pr.labels.forEach((label) => {
if (label.name === "breaking-change") {
hasBreakingChanges = true;
}
if (categories[label.name]) {
categories[label.name].push(prEntry);
} else if (!categories[label.name] && label.name === pr.labels[pr.labels.length - 1].name) {
categories["chore"].push(prEntry);
}
});
});

console.log(`Categories: ${JSON.stringify(categories, null, 2)}`);

const releaseNotes = Object.entries(categories)
.filter(([_, notes]) => notes.length > 0)
.map(
([category, notes]) =>
`### ${
categoryNames[category] ||
category.charAt(0).toUpperCase() + category.slice(1)
}\n${notes.join("\n")}`
)
.join("\n\n");

const breakingChangesSection = hasBreakingChanges
? `### Breaking Changes\n\n- This release introduces breaking changes. Please review [the migration guide](https://lcdmenu.forntoh.dev/reference/migration/index.html) for details on how to update your code.\n\n`
: "";

const repoUrl = pulls.length > 0 ? pulls[0].base.repo.html_url : "";
const fullChangelog = `**Full Changelog**: ${repoUrl}/compare/${previousTag}...${currentTag}`;
return `${releaseNotes}\n\n${breakingChangesSection}\n\n\n${fullChangelog}`;
}

module.exports = generateReleaseNotes;

0 comments on commit 3569f5b

Please sign in to comment.