Skip to content

Commit 94c687c

Browse files
authored
[CICD-73] Automate population of GitHub release notes (#41)
* Create action to retrieve release notes * Add output to report whether new tag was published * Split versioning and releasing to separate jobs * Add steps to create GitHub release * Update release documentation
1 parent 90b82f4 commit 94c687c

File tree

6 files changed

+153
-16
lines changed

6 files changed

+153
-16
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
name: Get release notes
2+
description: Retrieves the release notes for a given version from the changelog.
3+
inputs:
4+
version:
5+
description: "Which version's release notes to retrieve."
6+
required: true
7+
changelog:
8+
description: "Path to the changelog."
9+
required: true
10+
outputs:
11+
release_notes:
12+
description: "Release notes parsed from the changelog."
13+
value: ${{ steps.notes.outputs.RELEASE_NOTES }}
14+
runs:
15+
using: 'composite'
16+
steps:
17+
- id: notes
18+
run: |
19+
notes=$(node ${{ github.action_path }}/getReleaseNotes ${{ inputs.version }} ${{ inputs.changelog }})
20+
notes="${notes//'%'/'%25'}"
21+
notes="${notes//$'\n'/'%0A'}"
22+
notes="${notes//$'\r'/'%0D'}"
23+
echo "::set-output name=RELEASE_NOTES::$notes"
24+
shell: bash
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#! /usr/bin/env node
2+
3+
const [,, ...args] = process.argv;
4+
5+
const fs = require('fs/promises');
6+
const path = require("path");
7+
8+
const readFile = (filename) => fs.readFile(filename, { encoding: "utf8" });
9+
10+
(async ()=>{
11+
if (! args[0] || ! args[1] ) {
12+
printUsageInstructions();
13+
process.exit(1);
14+
}
15+
16+
const notes = await getReleaseNotes(args[0], args[1]);
17+
18+
process.stdout.write(notes);
19+
})();
20+
21+
/**
22+
* Retrieves the changelog entry for a particular version.
23+
*
24+
* Expects the changelog to be a markdown file as generated by Changesets.
25+
*
26+
* @param {String} version The version number to get notes for.
27+
* @param {String} changelogPath Path to the CHANGELOG.md file.
28+
*/
29+
async function getReleaseNotes(version, changelogPath) {
30+
changelogPath = path.resolve(changelogPath);
31+
32+
let changelog = await readFile(changelogPath);
33+
34+
const versionStart = changelog.indexOf(`## ${version}`);
35+
36+
if ( versionStart < 0 ) {
37+
throw new Error(`Version ${version} does not exist in ${changelogPath}`);
38+
}
39+
40+
changelog = changelog.substring( versionStart );
41+
42+
// split the contents by new line
43+
const changelogLines = changelog.split(/\r?\n/);
44+
// we don't need the version heading in release notes, so drop the first line
45+
changelogLines.shift();
46+
const processedLines = [];
47+
48+
// print all lines in current version
49+
changelogLines.every((line) => {
50+
// Version numbers in CHANGELOG.md are h2, so we've hit the next version
51+
if (line.startsWith("## ")) {
52+
return false;
53+
}
54+
55+
if (line.startsWith("### ")) {
56+
// Make h3 into h2
57+
line = line.replace("### ", "## ");
58+
}
59+
60+
processedLines.push(line);
61+
62+
return true;
63+
});
64+
65+
return processedLines.join("\n");
66+
}
67+
68+
function printUsageInstructions() {
69+
usage = "Usage: node getReleaseNotes <version> <changelogPath>\n";
70+
usage += "\n";
71+
usage += "Example use:\n";
72+
usage += " node getReleaseNotes 3.0.1 ../CHANGELOG.md\n";
73+
console.info(usage);
74+
}

.github/actions/publish/action.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,13 @@ inputs:
44
version:
55
description: 'Patch version to publish.'
66
required: true
7+
outputs:
8+
published:
9+
description: 'Whether a new version was published.'
10+
value: ${{ steps.publish.outputs.PUBLISHED }}
711
runs:
812
using: 'composite'
913
steps:
10-
- run: "${{ github.action_path }}/publish.sh ${{ inputs.version }}"
14+
- id: publish
15+
run: "${{ github.action_path }}/publish.sh ${{ inputs.version }}"
1116
shell: bash

.github/actions/publish/publish.sh

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ version="$1"
2323
# capture 4: patch number with dot
2424
# capture 5: patch number without dot
2525
VERSION_REGEX="^([1-9][0-9]*)(\.(0|[1-9][0-9]*)){0,1}(\.(0|[1-9][0-9]*)){0,1}$"
26+
PUBLISHED='false'
2627

2728
if [[ "$version" =~ $VERSION_REGEX ]] ; then
2829
majorTag="v${BASH_REMATCH[1]}"
@@ -41,6 +42,7 @@ else
4142
echo "Creating tag: $patchTag"
4243
git tag -a "$patchTag" -m " Release $patchTag"
4344
git push origin "$patchTag"
45+
PUBLISHED='true'
4446
fi
4547

4648
for tag in "${tagsToUpdate[@]}"; do
@@ -55,4 +57,6 @@ for tag in "${tagsToUpdate[@]}"; do
5557

5658
git tag -fa "$tag" -m "$message"
5759
git push origin "$tag" --force
58-
done
60+
done
61+
62+
echo "::set-output name=PUBLISHED::$PUBLISHED"

.github/workflows/release.yml

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@ on:
88
concurrency: ${{ github.workflow }}-${{ github.ref }}
99

1010
jobs:
11-
release:
12-
name: Version and Release
11+
versioning:
12+
name: Versioning
1313
runs-on: ubuntu-latest
14+
outputs:
15+
hasChangesets: ${{ steps.changesets.outputs.hasChangesets }}
16+
version: ${{ steps.version.outputs.CURRENT_VERSION }}
1417
steps:
1518
- name: Checkout Repo
1619
uses: actions/checkout@v3
17-
with:
18-
fetch-depth: 0
1920

2021
- name: Setup Node.js 16.x
2122
uses: actions/setup-node@v2
@@ -38,8 +39,44 @@ jobs:
3839
id: version
3940
run: echo "::set-output name=CURRENT_VERSION::$(node -p "require('./package.json').version")"
4041

42+
release:
43+
name: Release
44+
runs-on: ubuntu-latest
45+
needs: versioning
46+
if: needs.versioning.outputs.hasChangesets == 'false'
47+
steps:
48+
- name: Checkout Repo
49+
uses: actions/checkout@v3
50+
with:
51+
fetch-depth: 0
52+
53+
- name: Configure git
54+
run: |
55+
git config user.name github-actions[bot]
56+
git config user.email github-actions[bot]@users.noreply.github.com
57+
58+
- name: Setup Node.js 16.x
59+
uses: actions/setup-node@v2
60+
with:
61+
node-version: 16.x
62+
4163
- name: Publish tags
42-
if: steps.changesets.outputs.hasChangesets == 'false'
64+
id: publish
4365
uses: ./.github/actions/publish
4466
with:
45-
version: ${{ steps.version.outputs.CURRENT_VERSION }}
67+
version: ${{ needs.versioning.outputs.version }}
68+
69+
- name: Get release notes
70+
id: notes
71+
if: steps.publish.outputs.published == 'true'
72+
uses: ./.github/actions/get-release-notes
73+
with:
74+
version: ${{ needs.versioning.outputs.version }}
75+
changelog: ./CHANGELOG.md
76+
77+
- name: Create release
78+
if: steps.notes.outputs.release_notes
79+
uses: softprops/action-gh-release@1e07f4398721186383de40550babbdf2b84acfc5
80+
with:
81+
body: ${{ steps.notes.outputs.release_notes }}
82+
tag_name: v${{ needs.versioning.outputs.version }}

DEVELOPMENT.md

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,4 @@ We use [Changesets](https://github.com/changesets/changesets) to automate versio
2626
- [ ] All `.changeset/*.md` files were removed.
2727
3. Approve, then "Squash and merge" the PR into `main`.
2828

29-
Merging the versioning PR will run a workflow that creates or updates all necessary tags. The next step is to create a release in GitHub. This may be automated in the future, but for now the process is:
30-
31-
1. In GitHub, visit "Releases -> Draft new release"
32-
2. In the "Choose a tag" dropdown, choose the tag you want to release. This is usually the most recently created patch tag (`v{MAJOR}.{MINOR}.{PATCH}`). Major/minor tags (i.e `v3` and `v3.0`) are only present to allow users to opt into automatic patch or minor version updates and should not be associated with a GitHub release.
33-
3. Leave the target branch set to `main`.
34-
4. Give the release a title. Typically, this will be identical to the release's tag name.
35-
5. Copy the [CHANGLELOG.md](./CHANGELOG.md) entry for this release into the release body.
36-
6. Click "Publish Release".
29+
Merging the versioning PR will run a workflow that creates or updates all necessary tags. It will also create a new release in GitHub.

0 commit comments

Comments
 (0)