Skip to content

Commit 5634945

Browse files
Refactor: Automate changelog generation with custom script
This commit introduces a new Node.js script `generate-changelog.js` to automate the creation of the `CHANGELOG.md` file. This replaces the previous `git-cliff` based GitHub Action workflow. - **`generate-changelog.js`:** - Fetches the last `TAGS_TO_INCLUDE` (currently 5) Git tags. - Parses commit messages based on defined `commitParsers` rules to group changes (e.g., Features, Bug Fixes, Refactoring). - Skips certain commit types (e.g., `chore(release)`, `chore(deps)`). - Includes a "Coming Soon" section for unreleased changes since the latest tag. - Formats tag dates and links to the repository tag. - Adds a custom header and footer to the `CHANGELOG.md`. - Writes the generated changelog to `OUTPUT_FILE` (CHANGELOG.md). - **`.github/workflows/changelog.yml`:** - Updated the "Generate Changelog" workflow. - Replaced steps for installing Rust and `git-cliff` with setting up Node.js. - The changelog generation step now executes the new `generate-changelog.js` script. - Removed environment variables and commands related to `git-cliff`. - Changed the script path from `scripts/generate-changelog.js` to `generate-changelog.js` (assuming the script is at the root or the path in the workflow will be adjusted accordingly). Signed-off-by: CreativeCodeCat <wayne6324@gmail.com>
1 parent db54890 commit 5634945

File tree

2 files changed

+147
-10
lines changed

2 files changed

+147
-10
lines changed

.github/workflows/changelog.yml

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,14 @@ jobs:
1919
ref: main
2020
fetch-depth: 0
2121

22-
- name: Install Rust (required for git-cliff)
23-
uses: actions-rs/toolchain@v1
22+
- name: Set up Node.js
23+
uses: actions/setup-node@v3
2424
with:
25-
toolchain: stable
26-
override: true
25+
node-version: '20' # or whatever version you need
2726

28-
- name: Install git-cliff
29-
run: cargo install git-cliff
30-
31-
- name: Generate changelog for last 5 tags
27+
- name: Run changelog script
3228
run: |
33-
LAST_TAG=$(git tag --sort=-creatordate | head -n 10 | tail -n 1)
34-
git-cliff "$LAST_TAG..HEAD" -o CHANGELOG.md --verbose
29+
node scripts/generate-changelog.js
3530
env:
3631
OUTPUT: CHANGELOG.md
3732

generate-changelog.js

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
#!/usr/bin/env node
2+
3+
const { execSync } = require("child_process");
4+
const fs = require("fs");
5+
6+
const OUTPUT_FILE = "CHANGELOG.md";
7+
const TAGS_TO_INCLUDE = 5;
8+
const REPO_URL = "https://github.com/DroidWorksStudio/mLauncher";
9+
const HEADER = `# Changelog
10+
11+
All notable changes to this project will be documented in this file. See [conventional commits](https://www.conventionalcommits.org/) for commit guidelines.
12+
13+
`; // Your custom header
14+
15+
const FOOTER = `
16+
---
17+
> Generated by DroidWorksStudio
18+
`;
19+
20+
// Commit parsing rules
21+
const commitParsers = [
22+
{ message: /^chore\(release\): prepare for/i, skip: true },
23+
{ message: /^chore\(deps.*\)/i, skip: true },
24+
{ message: /^chore\(change.*\)/i, skip: true },
25+
{ message: /^chore\(pr\)/i, skip: true },
26+
{ message: /^chore\(pull\)/i, skip: true },
27+
{ message: /^fixes/i, skip: true },
28+
{ message: /^feat/i, group: "### Implemented Enhancements:" },
29+
{ message: /^fix|^bug/i, group: "### Bug Fixes:" },
30+
{ message: /^lang/i, group: "### Language Support:" },
31+
{ message: /^doc/i, group: "### Documentation:" },
32+
{ message: /^perf/i, group: "### Performance Improvements:" },
33+
{ message: /^refactor/i, group: "### Refactoring:" },
34+
{ message: /^style/i, group: "### Styling Changes:" },
35+
{ message: /^security/i, group: "### Security Updates:" },
36+
{ message: /^revert/i, group: "### Reverts:" },
37+
{ message: /^release/i, group: "### Releases:" },
38+
{ message: /^dependency|^deps/i, group: "### Dependency Updates:" },
39+
{ message: /^ci|^pipeline/i, group: "### Continuous Integration (CI):" },
40+
{ message: /^chore|^housekeeping/i, group: "### Chore:" },
41+
{ message: /^version|^versioning/i, group: "### Versioning:" },
42+
{ message: /^config|^configuration/i, group: "### Configuration Changes:" },
43+
{ message: /^cleanup|^clean\(up\)/i, group: "### Code Cleanup:" },
44+
{ message: /^drop|^remove/i, group: "### Feature Removal:" },
45+
{ message: /^hotfix|^emergency/i, group: "### Hotfixes:" },
46+
];
47+
48+
// Helper functions
49+
function run(cmd) {
50+
return execSync(cmd, { encoding: "utf8" }).trim();
51+
}
52+
53+
function formatDate(dateString) {
54+
const date = new Date(dateString);
55+
const day = String(date.getDate()).padStart(2, "0");
56+
const month = date.toLocaleString("en-US", { month: "long" });
57+
const year = date.getFullYear();
58+
return `- (${day}, ${month} ${year})`;
59+
}
60+
61+
function classifyCommit(msg) {
62+
for (const parser of commitParsers) {
63+
if (parser.message.test(msg)) {
64+
if (parser.skip) return null;
65+
return { group: parser.group, message: msg };
66+
}
67+
}
68+
return null; // Skip commits that don't match
69+
}
70+
71+
// Get last N tags
72+
const tags = run(`git tag --sort=-creatordate`).split("\n").slice(0, TAGS_TO_INCLUDE);
73+
let changelog = HEADER;
74+
75+
// "Coming Soon" section for commits after latest tag
76+
const latestTag = tags[0] || "";
77+
if (latestTag) {
78+
const unreleasedCommits = run(`git log ${latestTag}..HEAD --pretty=format:"%s"`)
79+
.split("\n")
80+
.map((c) => classifyCommit(c))
81+
.filter(Boolean);
82+
83+
if (unreleasedCommits.length > 0) {
84+
changelog += "## [Coming Soon](https://github.com/DroidWorksStudio/mLauncher/tree/main) - TBD\n";
85+
const groups = {};
86+
for (const c of unreleasedCommits) {
87+
groups[c.group] = groups[c.group] || [];
88+
groups[c.group].push(`* ${c.message}`);
89+
}
90+
for (const group of Object.keys(groups)) {
91+
changelog += `${group}\n${groups[group].join("\n")}\n\n`;
92+
}
93+
}
94+
}
95+
96+
// Generate changelog for each tag
97+
for (let i = 0; i < tags.length; i++) {
98+
const currentTag = tags[i];
99+
// Determine the range
100+
let range;
101+
if (i === tags.length - 1) {
102+
// Oldest tag in the slice, only show commits between it and the previous tag in git history
103+
const allTags = run("git tag --sort=creatordate").split("\n");
104+
const oldestTagIndex = allTags.indexOf(currentTag);
105+
const parentTag = allTags[oldestTagIndex - 1];
106+
range = parentTag ? `${parentTag}..${currentTag}` : currentTag;
107+
} else {
108+
const previousTagInSlice = tags[i + 1];
109+
range = `${previousTagInSlice}..${currentTag}`;
110+
}
111+
112+
// Tag date
113+
const tagDateRaw = run(`git log -1 --format=%ad --date=short ${currentTag}`);
114+
const tagDateFormatted = formatDate(tagDateRaw);
115+
116+
// Commits
117+
const commits = run(`git log ${range} --pretty=format:"%s"`)
118+
.split("\n")
119+
.map((c) => classifyCommit(c))
120+
.filter(Boolean);
121+
122+
if (commits.length === 0) continue;
123+
124+
changelog += `## [${currentTag}](${REPO_URL}/tree/${currentTag}) ${tagDateFormatted}\n`;
125+
126+
const groups = {};
127+
for (const c of commits) {
128+
groups[c.group] = groups[c.group] || [];
129+
groups[c.group].push(`* ${c.message}`);
130+
}
131+
132+
for (const group of Object.keys(groups)) {
133+
changelog += `${group}\n${groups[group].join("\n")}\n\n`;
134+
}
135+
}
136+
137+
// Later, after generating the changelog content, append the footer:
138+
changelog += FOOTER;
139+
140+
// Write file
141+
fs.writeFileSync(OUTPUT_FILE, changelog, "utf8");
142+
console.log(`✅ Generated ${OUTPUT_FILE}`);

0 commit comments

Comments
 (0)