Skip to content

Commit 60ae3f1

Browse files
authored
Add automatic changelog PR github workflow (#299)
1 parent bf75d82 commit 60ae3f1

File tree

3 files changed

+169
-0
lines changed

3 files changed

+169
-0
lines changed

.github/changelog-pr-config.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
[
2+
{
3+
"title": "💥 Breaking Changes",
4+
"labels": ["breaking change"]
5+
},
6+
{
7+
"title": "✨ New Scripts",
8+
"labels": ["new script"]
9+
},
10+
{
11+
"title": "🚀 Updated Scripts",
12+
"labels": ["update script"]
13+
},
14+
{
15+
"title": "🌐 Website",
16+
"labels": ["website"]
17+
},
18+
{
19+
"title": "🐞 Bug Fixes",
20+
"labels": ["bug fix"]
21+
},
22+
{
23+
"title": "🧰 Maintenance",
24+
"labels": ["maintenance"]
25+
},
26+
{
27+
"title": "❔ Unlabelled",
28+
"labels": []
29+
}
30+
]

.github/workflows/changelog-pr.yml

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
name: Create Changelog Pull Request
2+
3+
on:
4+
push:
5+
branches: ["main"]
6+
7+
workflow_dispatch:
8+
9+
jobs:
10+
update-changelog-pull-request:
11+
runs-on: ubuntu-latest
12+
env:
13+
CONFIG_PATH: .github/changelog-pr-config.json
14+
BRANCH_NAME: github-action-update-changelog
15+
AUTOMATED_PR_LABEL: "automated pr"
16+
permissions:
17+
contents: write
18+
pull-requests: write
19+
steps:
20+
- name: Checkout code
21+
uses: actions/checkout@v4
22+
with:
23+
fetch-depth: 0
24+
25+
- name: Get latest dates in changelog
26+
run: |
27+
# Extract the latest and second latest dates from changelog
28+
DATES=$(grep '^## [0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}' CHANGELOG.md | head -n 2 | awk '{print $2}')
29+
30+
LATEST_DATE=$(echo "$DATES" | sed -n '1p')
31+
SECOND_LATEST_DATE=$(echo "$DATES" | sed -n '2p')
32+
TODAY=$(date +%Y-%m-%d)
33+
34+
echo "TODAY=$TODAY" >> $GITHUB_ENV
35+
if [ "$LATEST_DATE" == "$TODAY" ]; then
36+
echo "LATEST_DATE=$SECOND_LATEST_DATE" >> $GITHUB_ENV
37+
else
38+
echo "LATEST_DATE=$LATEST_DATE" >> $GITHUB_ENV
39+
fi
40+
41+
- name: Get categorized pull requests
42+
id: get-categorized-prs
43+
uses: actions/github-script@v7
44+
with:
45+
script: |
46+
const fs = require('fs').promises;
47+
const path = require('path');
48+
49+
const configPath = path.resolve(process.env.CONFIG_PATH);
50+
const fileContent = await fs.readFile(configPath, 'utf-8');
51+
const changelogConfig = JSON.parse(fileContent);
52+
const categorizedPRs = changelogConfig.map((obj) => ({ ...obj, notes: [] }));
53+
54+
const latestDateInChangelog = new Date(process.env.LATEST_DATE);
55+
latestDateInChangelog.setUTCHours(23,59,59,999);
56+
57+
const { data: pulls } = await github.rest.pulls.list({
58+
owner: context.repo.owner,
59+
repo: context.repo.repo,
60+
state: "closed",
61+
sort: "updated",
62+
direction: "desc",
63+
per_page: 100,
64+
});
65+
66+
pulls.filter((pr) =>
67+
pr.merged_at && new Date(pr.merged_at) > latestDateInChangelog
68+
).forEach((pr) => {
69+
const prLabels = pr.labels.map((label) => label.name.toLowerCase());
70+
const prNote = `- ${pr.title} [@${pr.user.login}](https://github.com/${pr.user.login}) ([#${pr.number}](${pr.html_url}))`;
71+
72+
for (const { labels, notes } of categorizedPRs) {
73+
const prHasCategoryLabel = labels.some((label) => prLabels.includes(label));
74+
const isUnlabelledCategory = labels.length === 0;
75+
const prShouldBeExcluded = prLabels.includes(process.env.AUTOMATED_PR_LABEL);
76+
if ((prHasCategoryLabel || isUnlabelledCategory) && !prShouldBeExcluded) {
77+
notes.push(prNote);
78+
break;
79+
}
80+
};
81+
});
82+
83+
return categorizedPRs;
84+
85+
- name: Update CHANGELOG.md
86+
uses: actions/github-script@v7
87+
with:
88+
script: |
89+
const fs = require('fs').promises;
90+
const path = require('path');
91+
92+
const today = process.env.TODAY;
93+
const latestDateInChangelog = process.env.LATEST_DATE;
94+
const changelogPath = path.resolve('CHANGELOG.md');
95+
const categorizedPRs = ${{ steps.get-categorized-prs.outputs.result }};
96+
97+
let newReleaseNotes = `\n## ${today}\n\n### Changed\n\n`;
98+
for (const { title, notes } of categorizedPRs) {
99+
if (notes.length > 0) {
100+
newReleaseNotes += `### ${title}\n\n${notes.join("\n")}\n\n`;
101+
}
102+
}
103+
104+
const changelogContent = await fs.readFile(changelogPath, 'utf-8');
105+
const changelogIncludesTodaysReleaseNotes = changelogContent.includes(`\n## ${today}`);
106+
107+
// Replace todays release notes or insert release notes above previous release notes
108+
const regex = changelogIncludesTodaysReleaseNotes ?
109+
new RegExp(`\n## ${today}.*(?=## ${latestDateInChangelog})`, "gs") :
110+
new RegExp(`(?=## ${latestDateInChangelog})`, "gs");
111+
112+
const newChangelogContent = changelogContent.replace(regex, newReleaseNotes)
113+
await fs.writeFile(changelogPath, newChangelogContent);
114+
115+
116+
- name: Commit and push changes to separate branch
117+
run: |
118+
git config --global user.name "github-actions[bot]"
119+
git config --global user.email "github-actions[bot]@users.noreply.github.com"
120+
git add CHANGELOG.md
121+
git commit -m "Update CHANGELOG.md"
122+
git checkout -b $BRANCH_NAME || git checkout $BRANCH_NAME
123+
git push origin $BRANCH_NAME --force
124+
125+
- name: Create pull request if not exists
126+
env:
127+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
128+
run: |
129+
PR_EXISTS=$(gh pr list --head "${BRANCH_NAME}" --json number --jq '.[].number')
130+
if [ -z "$PR_EXISTS" ]; then
131+
gh pr create --title "[Github Action] Update CHANGELOG.md" \
132+
--body "This PR is auto-generated by a Github Action to update the CHANGELOG.md file." \
133+
--head $BRANCH_NAME \
134+
--base main \
135+
--label "$AUTOMATED_PR_LABEL"
136+
fi

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
1313
> [!NOTE]
1414
All LXC instances created using this repository come pre-installed with Midnight Commander, which is a command-line tool (`mc`) that offers a user-friendly file and directory management interface for the terminal environment.
1515

16+
> [!IMPORTANT]
17+
Do not break established syntax in this file, as it is automatically updated by a Github Workflow
18+
1619
## 2024-11-16
1720

1821
### Changed

0 commit comments

Comments
 (0)