From 1d8110e4c07afb8848fea017b3e4ced8b4f1c0ef Mon Sep 17 00:00:00 2001 From: Kevin Renskers Date: Thu, 23 Nov 2023 13:25:10 +0100 Subject: [PATCH] feat: Add a config option `includeCommitBody` to render the body text of the commit (#15) The default value is false Closes #14 --- README.md | 43 ++++++++++++++-------- dist/index.js | 43 ++++++++++++---------- src/defaultConfig.js | 7 +++- src/generateChangelog.js | 2 +- test/generateChangelog.spec.js | 65 ++++++++++++++++++++++++++++++---- 5 files changed, 118 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 80562f8..53d57ad 100644 --- a/README.md +++ b/README.md @@ -3,18 +3,20 @@

# tag-changelog + A GitHub Action triggered by a new (valid semver) tag getting pushed. It then fetches all the commits since the previous tag and creates a changelog text using the [Conventional Commits](https://www.conventionalcommits.org) format. It will also turn PR numbers into clickable links, and mentions the author. This action returns the generated changelog text, but doesn't do anything more; you need to for example prepend it to a `CHANGELOG.md` file, create a GitHub Release with this text, etc. ## Example workflow -``` yml + +```yml name: Create Release on: push: tags: - - '*' + - "*" jobs: create-release: @@ -42,16 +44,19 @@ jobs: ``` ## Inputs -* `token`: Your GitHub token, `${{ secrets.GITHUB_TOKEN }}`. Required. -* `exclude_types`: A comma separated list of commit types you want to exclude from the changelog, for example: "other,chore". Optional (defaults to nothing). Can also be configured in the config file. -* `config_file`: Location of the config file. Optional. + +- `token`: Your GitHub token, `${{ secrets.GITHUB_TOKEN }}`. Required. +- `exclude_types`: A comma separated list of commit types you want to exclude from the changelog, for example: "other,chore". Optional (defaults to nothing). Can also be configured in the config file. +- `config_file`: Location of the config file. Optional. ## Outputs -* `changelog`: Generated changelog for the latest tag, including the version/date header (suitable for prepending to a CHANGELOG.md file). -* `changes`: Generated changelog for the latest tag, without the version/date header (suitable for GitHub Releases). + +- `changelog`: Generated changelog for the latest tag, including the version/date header (suitable for prepending to a CHANGELOG.md file). +- `changes`: Generated changelog for the latest tag, without the version/date header (suitable for GitHub Releases). ## Custom config -``` yml + +```yml - name: Create changelog text uses: loopwerk/tag-changelog@v1 with: @@ -59,11 +64,11 @@ jobs: config_file: .github/tag-changelog-config.js ``` -The config file can be used to map commit types to changelog labels, to override the rendering of changelog sections, and the rendering of the overall changelog. You only need to override the things you want to override. For example, you can leave out `renderTypeSection` and `renderChangelog` and only include the `types` config; the default config will be used for whatever is not overriden. +The config file can be used to map commit types to changelog labels, to override the rendering of changelog sections, and the rendering of the overall changelog. You only need to override the things you want to override. For example, you can leave out `renderTypeSection` and `renderChangelog` and only include the `types` config; the [default config](https://github.com/loopwerk/tag-changelog/blob/main/src/defaultConfig.js) will be used for whatever is not overriden. ### Example config file: -``` javascript +```javascript module.exports = { types: [ { types: ["feat", "feature"], label: "🎉 New Features" }, @@ -84,8 +89,11 @@ module.exports = { renderTypeSection: function (label, commits) { let text = `\n## ${label}\n`; - commits.forEach((commit) => { + commits.forEach(commit => { text += `- ${commit.subject}\n`; + if (commit.body) { + text += `${commit.body}\n`; + } }); return text; @@ -103,19 +111,23 @@ The order in which the `types` appear also determines the order of the generated ## Example output > # v0.14.0 - 2021-02-22 -> +> > ## New Features +> > - merge the default config with the user config so that the user config only has to override values it wants, and use the defaults for the others > - the custom config file is now JS instead of JSON, allow the override of the changelog text templates ([#2](https://github.com/loopwerk/tag-changelog/pull/2) by [kevinrenskers](https://github.com/kevinrenskers)) > - commit types to exclude can now also be configured via the config file -> +> > ## Documentation Changes +> > - simplified readme -> +> > ## Chores +> > - added project logo -> +> > ## BREAKING CHANGES +> > - due to [bcb876](https://github.com/loopwerk/tag-changelog/commit/bcb8767bc22bc7d4ab47a4fffd4ef435de581054): commit types to exclude can now also be configured via the config file > > The `exclude` input parameter has been renamed to `exclude_types`. @@ -123,4 +135,5 @@ The order in which the `types` appear also determines the order of the generated You can also check out the [Releases page](https://github.com/loopwerk/tag-changelog/releases) for tag-changelog. ## Thanks + Thanks to [Helmisek/conventional-changelog-generator](https://github.com/Helmisek/conventional-changelog-generator) and [ardalanamini/auto-changelog](https://github.com/ardalanamini/auto-changelog) for inspiration. Thanks to [nektos/act](https://github.com/nektos/act) for making it possible to run GitHub Actions locally, making development and testing a whole lot easier. diff --git a/dist/index.js b/dist/index.js index b4a8d65..bcca809 100644 --- a/dist/index.js +++ b/dist/index.js @@ -10680,12 +10680,17 @@ const DEFAULT_CONFIG = { excludeTypes: [], - renderTypeSection: function (label, commits) { + includeCommitBody: false, + + renderTypeSection: function (label, commits, includeCommitBody) { let text = `\n## ${label}\n`; - commits.forEach((commit) => { + commits.forEach(commit => { const scope = commit.scope ? `**${commit.scope}:** ` : ""; text += `- ${scope}${commit.subject}\n`; + if (commit.body && includeCommitBody) { + text += `${commit.body}\n`; + } }); return text; @@ -10694,7 +10699,7 @@ const DEFAULT_CONFIG = { renderNotes: function (notes) { let text = `\n## BREAKING CHANGES\n`; - notes.forEach((note) => { + notes.forEach(note => { text += `- due to [${note.commit.sha.substr(0, 6)}](${note.commit.url}): ${note.commit.subject}\n\n`; text += `${note.text}\n\n`; }); @@ -10724,30 +10729,30 @@ function generateChangelog(releaseName, commitObjects, config) { let changes = ""; commitsByType - .filter((obj) => { + .filter(obj => { return !config.excludeTypes.includes(obj.type); }) - .forEach((obj) => { + .forEach(obj => { const niceType = translateType(obj.type, config.types); - changes += config.renderTypeSection(niceType, obj.commits); + changes += config.renderTypeSection(niceType, obj.commits, config.includeCommitBody); }); // Find all the notes of all the commits of all the types const notes = commitsByType - .flatMap((obj) => { + .flatMap(obj => { return obj.commits - .map((commit) => { + .map(commit => { if (commit.notes && commit.notes.length) { - return commit.notes.map((note) => { + return commit.notes.map(note => { const noteObj = note; noteObj.commit = commit; return noteObj; }); } }) - .filter((o) => o); + .filter(o => o); }) - .flatMap((o) => o); + .flatMap(o => o); if (notes.length) { changes += config.renderNotes(notes); @@ -10775,7 +10780,7 @@ function groupByType(commits, typeConfig) { // First, group all the commits by their types. // We end up with a dictionary where the key is the type, and the values is an array of commits. const byType = {}; - commits.forEach((commit) => { + commits.forEach(commit => { if (!byType[commit.type]) { byType[commit.type] = []; } @@ -10785,7 +10790,7 @@ function groupByType(commits, typeConfig) { // Turn that dictionary into an array of objects, // where the key is the type, and the values is an array of commits. const byTypeArray = []; - Object.keys(byType).forEach((key) => { + Object.keys(byType).forEach(key => { byTypeArray.push({ type: key, commits: byType[key], @@ -10794,9 +10799,9 @@ function groupByType(commits, typeConfig) { // And now we sort that array using the TYPES object. byTypeArray.sort((a, b) => { - let aOrder = typeConfig.findIndex((t) => t.types.includes(a.type)); + let aOrder = typeConfig.findIndex(t => t.types.includes(a.type)); if (aOrder === -1) aOrder = 999; - let bOrder = typeConfig.findIndex((t) => t.types.includes(b.type)); + let bOrder = typeConfig.findIndex(t => t.types.includes(b.type)); if (bOrder === -1) bOrder = 999; return aOrder - bOrder; }); @@ -10854,7 +10859,7 @@ module.exports = parseCommitMessage; /***/ ((module) => { function translateType(type, typeConfig) { - const foundType = typeConfig.find((t) => t.types.includes(type)); + const foundType = typeConfig.find(t => t.types.includes(type)); if (foundType) { return foundType.label; } @@ -11089,7 +11094,7 @@ async function run() { const tags = await octokit.paginate("GET /repos/{owner}/{repo}/tags", { owner, repo }); const validSortedTags = tags - .filter((t) => validate(t.name)) + .filter(t => validate(t.name)) .sort((a, b) => { return compareVersions(a.name, b.name); }) @@ -11139,14 +11144,14 @@ async function run() { // Parse every commit, getting the type, turning PR numbers into links, etc const commitObjects = await Promise.all( result.data.commits - .map(async (commit) => { + .map(async commit => { const commitObj = await parseCommitMessage(commit.commit.message, `https://github.com/${owner}/${repo}`, fetchUserFunc); commitObj.sha = commit.sha; commitObj.url = commit.html_url; commitObj.author = commit.author; return commitObj; }) - .filter((m) => m !== false) + .filter(m => m !== false) ); // And generate the changelog diff --git a/src/defaultConfig.js b/src/defaultConfig.js index fadf862..ee58619 100644 --- a/src/defaultConfig.js +++ b/src/defaultConfig.js @@ -15,12 +15,17 @@ const DEFAULT_CONFIG = { excludeTypes: [], - renderTypeSection: function (label, commits) { + includeCommitBody: false, + + renderTypeSection: function (label, commits, includeCommitBody) { let text = `\n## ${label}\n`; commits.forEach(commit => { const scope = commit.scope ? `**${commit.scope}:** ` : ""; text += `- ${scope}${commit.subject}\n`; + if (commit.body && includeCommitBody) { + text += `${commit.body}\n`; + } }); return text; diff --git a/src/generateChangelog.js b/src/generateChangelog.js index 6da39dd..802591c 100644 --- a/src/generateChangelog.js +++ b/src/generateChangelog.js @@ -11,7 +11,7 @@ function generateChangelog(releaseName, commitObjects, config) { }) .forEach(obj => { const niceType = translateType(obj.type, config.types); - changes += config.renderTypeSection(niceType, obj.commits); + changes += config.renderTypeSection(niceType, obj.commits, config.includeCommitBody); }); // Find all the notes of all the commits of all the types diff --git a/test/generateChangelog.spec.js b/test/generateChangelog.spec.js index 5d385b2..80039a0 100644 --- a/test/generateChangelog.spec.js +++ b/test/generateChangelog.spec.js @@ -7,12 +7,12 @@ const DEFAULT_CONFIG = require("../src/defaultConfig"); describe("generateChangelog", () => { it("should create a changelog", () => { const commitObjects = [ - { subject: "Subject 1", type: "fix", notes: [] }, - { subject: "Subject 2", type: "feat", notes: [] }, - { subject: "Subject 3", type: "feat", notes: [] }, - { subject: "Subject 4", type: "fix", notes: [] }, - { subject: "Subject 5", type: "feat", notes: [] }, - { subject: "Subject 6", type: "other", notes: [] }, + { subject: "Subject 1", body: "Body 1", type: "fix", notes: [] }, + { subject: "Subject 2", body: "Body 2", type: "feat", notes: [] }, + { subject: "Subject 3", body: "Body 3", type: "feat", notes: [] }, + { subject: "Subject 4", body: "Body 4", type: "fix", notes: [] }, + { subject: "Subject 5", body: "Body 5", type: "feat", notes: [] }, + { subject: "Subject 6", body: "Body 6", type: "other", notes: [] }, ]; const dateString = new Date().toISOString().substr(0, 10); @@ -47,6 +47,59 @@ describe("generateChangelog", () => { assert.strictEqual(result.changelog, expectedChangelog); }); + it("should create a changelog with body text", () => { + const commitObjects = [ + { subject: "Subject 1", body: "Body 1", type: "fix", notes: [] }, + { subject: "Subject 2", body: "Body 2", type: "feat", notes: [] }, + { subject: "Subject 3", body: "Body 3", type: "feat", notes: [] }, + { subject: "Subject 4", body: "Body 4", type: "fix", notes: [] }, + { subject: "Subject 5", body: "Body 5", type: "feat", notes: [] }, + { subject: "Subject 6", body: "Body 6", type: "other", notes: [] }, + ]; + + const dateString = new Date().toISOString().substr(0, 10); + + const expectedChanges = `## New Features +- Subject 2 +Body 2 +- Subject 3 +Body 3 +- Subject 5 +Body 5 + +## Bugfixes +- Subject 1 +Body 1 +- Subject 4 +Body 4`; + + const expectedChangelog = `# 0.0.1 - ${dateString} + +## New Features +- Subject 2 +Body 2 +- Subject 3 +Body 3 +- Subject 5 +Body 5 + +## Bugfixes +- Subject 1 +Body 1 +- Subject 4 +Body 4 + +`; + + const config = DEFAULT_CONFIG; + config.excludeTypes = ["other"]; + config.includeCommitBody = true; + + const result = generateChangelog("0.0.1", commitObjects, config); + assert.strictEqual(result.changes, expectedChanges); + assert.strictEqual(result.changelog, expectedChangelog); + }); + it("should create a changelog with breaking changes", () => { const commitObjects = [ {