diff --git a/.github/workflows/lint-pr-title.yml b/.github/workflows/lint-pr-title.yml index 5b2fdcaea2db..873343c8d799 100644 --- a/.github/workflows/lint-pr-title.yml +++ b/.github/workflows/lint-pr-title.yml @@ -20,7 +20,20 @@ jobs: with: # Configure which types are allowed (newline-delimited). # Default: https://github.com/commitizen/conventional-commit-types - #types: | + # Customized based on sections defined in scripts/generate_changelog.mjs + types: | + feat + fix + perf + refactor + revert + deps + build + ci + test + style + chore + docs # Configure which scopes are allowed (newline-delimited). # These are regex patterns auto-wrapped in `^ $`. diff --git a/.github/workflows/publish-rc.yml b/.github/workflows/publish-rc.yml index 8a0890eb899c..cbb8096e2f82 100644 --- a/.github/workflows/publish-rc.yml +++ b/.github/workflows/publish-rc.yml @@ -75,7 +75,7 @@ jobs: # - name: Generate changelog - run: node scripts/generate_changelog_simple.js ${{ needs.tag.outputs.prev_tag }} ${{ needs.tag.outputs.tag }} CHANGELOG.md + run: node scripts/generate_changelog.mjs ${{ needs.tag.outputs.prev_tag }} ${{ needs.tag.outputs.tag }} CHANGELOG.md - name: Create Release id: create_release diff --git a/.github/workflows/publish-stable.yml b/.github/workflows/publish-stable.yml index e2bc856f3172..930d65332f05 100644 --- a/.github/workflows/publish-stable.yml +++ b/.github/workflows/publish-stable.yml @@ -81,7 +81,7 @@ jobs: # - name: Generate changelog - run: node scripts/generate_changelog_simple.js ${{ needs.tag.outputs.prev_tag }} ${{ needs.tag.outputs.tag }} CHANGELOG.md + run: node scripts/generate_changelog.mjs ${{ needs.tag.outputs.prev_tag }} ${{ needs.tag.outputs.tag }} CHANGELOG.md - name: Create Release id: create_release diff --git a/scripts/generate_changelog_simple.js b/scripts/generate_changelog.mjs similarity index 51% rename from scripts/generate_changelog_simple.js rename to scripts/generate_changelog.mjs index db856303982b..cd809622c5ea 100644 --- a/scripts/generate_changelog_simple.js +++ b/scripts/generate_changelog.mjs @@ -6,15 +6,15 @@ no-console */ -const {execSync} = require("node:child_process"); -const fs = require("node:fs"); +import {execSync} from "node:child_process"; +import fs from "node:fs"; // Docs // [script] // -// Exmaple +// Example // ``` -// node scripts/changelog_simple.js v0.32.0 v0.33.0 +// node scripts/generate_changelog.mjs v0.32.0 v0.33.0 // ``` /** @@ -23,8 +23,8 @@ const fs = require("node:fs"); */ const knownAuthors = { "caymannava@gmail.com": "wemeetagain", - "76567250+g11tech@users.noreply.github.com": "g11tech", - "vutuyen2636@gmail.com": "tuyennhv", + "develop@g11tech.io": "g11tech", + "tuyen@chainsafe.io": "tuyennhv", "35266934+dapplion@users.noreply.github.com": "dapplion", "41898282+github-actions[bot]@users.noreply.github.com": "github-actions[bot]", "49699333+dependabot[bot]@users.noreply.github.com": "dependabot[bot]", @@ -39,6 +39,9 @@ const knownAuthors = { "ammar1lakho@gmail.com": "ammarlakho", "dadepo@gmail.com": "dadepo", "hi@enriqueortiz.dev": "Evalir", + "nflaig@protonmail.com": "nflaig", + "nazarhussain@gmail.com": "nazarhussain", + "me@matthewkeil.com": "matthewkeil", }; const fromTag = process.argv[2]; @@ -49,36 +52,96 @@ if (!fromTag) throw Error("No process.argv[2]"); if (!toTag) throw Error("No process.argv[3]"); if (!outpath) throw Error("No process.argv[4]"); +/** + * @type {Record}>} + */ +const sections = { + feat: {heading: "Features", commitsByScope: {"": []}}, + fix: {heading: "Bug Fixes", commitsByScope: {"": []}}, + perf: {heading: "Performance", commitsByScope: {"": []}}, + refactor: {heading: "Refactoring", commitsByScope: {"": []}}, + revert: {heading: "Reverts", commitsByScope: {"": []}}, + deps: {heading: "Dependencies", commitsByScope: {"": []}}, + build: {heading: "Build System", commitsByScope: {"": []}}, + ci: {heading: "Continuous Integration", commitsByScope: {"": []}}, + test: {heading: "Tests", commitsByScope: {"": []}}, + style: {heading: "Styles", commitsByScope: {"": []}}, + chore: {heading: "Maintenance", commitsByScope: {"": []}}, + docs: {heading: "Documentation", commitsByScope: {"": []}}, + _: {heading: "Miscellaneous", commitsByScope: {"": []}}, +}; + const isPrCommitRg = /\(#\d+\)/; +const conventionalCommitRg = /^([a-z]+)(?:\((.*)\))?(?:(!))?: (.*)$/; const commitHashes = shell(`git log --pretty=format:"%H" ${fromTag}...${toTag}`); -let commitListStr = ""; - for (const commitHash of commitHashes.trim().split("\n")) { - const subject = shell(`git log --format='%s' ${commitHash}^!`); - if (!isPrCommitRg.test(subject)) { + const rawCommit = shell(`git log --format='%s' ${commitHash}^!`); + + if (!isPrCommitRg.test(rawCommit)) { + console.log(`Ignored commit "${rawCommit}" (missing PR reference)`); continue; } + const conventionalCommit = rawCommit.match(conventionalCommitRg); + if (!conventionalCommit) { + console.log(`Ignored commit "${rawCommit}" (not conventional commit)`); + continue; + } + + const [, type, scope, _breaking, subject] = conventionalCommit; + const authorEmail = shell(`git log --format='%ae' ${commitHash}^!`); const authorName = shell(`git log --format='%an' ${commitHash}^!`); const login = getCommitAuthorLogin(commitHash, authorEmail, authorName); - commitListStr += `- ${subject} (@${login})\n`; + const formattedCommit = `- ${scope ? `**${scope}:** ` : ""}${subject} (@${login})\n`; + + // Sort commits by type and scope + // - assign each commit to section based on type + // - group commits by scope within each section + if (sections[type] != null) { + if (scope) { + if (sections[type].commitsByScope[scope] == null) { + sections[type].commitsByScope[scope] = []; + } + sections[type].commitsByScope[scope].push(formattedCommit); + } else { + sections[type].commitsByScope[""].push(formattedCommit); + } + } else { + // Commits with a type that is not defined in sections + sections._.commitsByScope[""].push(formattedCommit); + } } // Print knownAuthors to update if necessary console.log("knownAuthors", knownAuthors); -const changelog = `# Changelog +let changelog = `# Changelog [Full Changelog](https://github.com/ChainSafe/lodestar/compare/${fromTag}...${toTag}) +`; -**Merged pull requests:** +// Write sections to changelog +for (const type in sections) { + const section = sections[type]; + let hasCommits = false; + let sectionChangelog = `\n### ${section.heading}\n\n`; -${commitListStr} -`; + for (const commits of Object.values(section.commitsByScope)) { + if (commits.length > 0) { + hasCommits = true; + sectionChangelog += commits.join(""); + } + } + + if (hasCommits) { + // Only add section if it has at least one commit + changelog += sectionChangelog; + } +} // Print to console console.log(changelog);