docs: add a changelog and the release notes flow

This commit is contained in:
Felitendo committed 2026-09-24 02:00:37 +02:00
1 parent a8c59024b7
commit 9f07150643
3 files changed
+210

No files matched your search

+136
View File
@@ -0,0 +1,136 @@
#!/usr/bin/env bash
#
# Builds the GitHub release notes for a tag, the way big projects such as
# Immich lay them out: the hand-written entry from CHANGELOG.md (a welcome,
# the highlights), a support section, then every commit since the last
# release, sorted by kind, and a link to the full changelog.
#
# .github/release-notes.sh v1.2.0 the notes
# .github/release-notes.sh --title v1.2.0 the title (checks the entry exists)
#
# Commit authors come from the GitHub API through gh. Without it the list
# goes without them.
set -euo pipefail
cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.."
title=0
if [[ ${1:-} == --title ]]; then
title=1
shift
fi
tag="${1:?usage: release-notes.sh [--title] vX.Y.Z}"
if ! grep -qx "## $tag" CHANGELOG.md; then
echo "CHANGELOG.md has no entry for $tag. Add \"## $tag\" first." >&2
exit 1
fi
if (( title )); then
printf '%s\n' "$tag"
exit 0
fi
repo="${GITHUB_REPOSITORY:-$(git remote get-url origin | sed -E 's#^.*github\.com[:/]##; s#\.git$##')}"
name="${repo#*/}"
# The entry: up to the next one, without the date line (GitHub shows the
# date), one heading level up, outside code blocks.
entry="$(awk -v h="## $tag" '
$0 == h { on = 1; next }
on && /^## / { exit }
!on { next }
/^```/ { code = !code }
!code && /^_[0-9]{4}-[0-9]{2}-[0-9]{2}_$/ { next }
!code && /^###/ { sub(/^#/, "") }
{ print }
' CHANGELOG.md | sed -e '/./,$!d')"
# The commits: from the last release, or from the start. A tag that does not
# exist yet is a preview of what HEAD would become.
if git rev-parse -q --verify "refs/tags/$tag" > /dev/null; then
target="$tag"
prev="$(git describe --tags --abbrev=0 --match 'v[0-9]*' "$tag^" 2>/dev/null || true)"
else
target="$(git rev-parse HEAD)"
prev="$(git describe --tags --abbrev=0 --match 'v[0-9]*' HEAD 2>/dev/null || true)"
fi
declare -A login=()
if command -v gh > /dev/null; then
if [[ -n $prev ]]; then
api=(api "repos/$repo/compare/$prev...$target" --jq '.commits[] | [.sha, (.author.login // "")] | @tsv')
else
api=(api --paginate "repos/$repo/commits?sha=$target&per_page=100" --jq '.[] | [.sha, (.author.login // "")] | @tsv')
fi
while IFS=$'\t' read -r sha who; do
[[ -n $who ]] && login[$sha]="$who"
done < <(gh "${api[@]}" 2>/dev/null || true)
fi
declare -A list=()
count_maint=0
while IFS=$'\t' read -r sha subject body; do
# Version bumps say nothing a reader needs.
[[ $subject =~ ^(chore:\ )?($name\ )?v?[0-9]+\.[0-9]+\.[0-9]+$ ]] && continue
case "$subject" in
*!:*) kind=breaking ;;
feat:* | feat\(*) kind=feat ;;
fix:* | fix\(*) kind=fix ;;
perf:* | perf\(*) kind=enh ;;
docs:* | docs\(*) kind=docs ;;
chore* | ci:* | ci\(* | build* | refactor* | test* | style*) kind=maint ;;
# Older commits without a prefix, sorted by what they say.
*README* | *readme* | *Readme*) kind=docs ;;
Add\ * | Put\ * | Introduce\ *) kind=feat ;;
Fix\ * | Repair\ * | Recover\ * | Stop\ *) kind=fix ;;
*\ *) kind=enh ;;
*) kind=maint ;;
esac
[[ $body == *"BREAKING CHANGE"* ]] && kind=breaking
[[ $subject == "Initial commit" ]] && kind=maint
line="* $subject"
[[ -n ${login[$sha]:-} ]] && line+=" by @${login[$sha]}"
line+=" in https://github.com/$repo/commit/$sha"
list[$kind]+="$line"$'\n'
[[ $kind == maint ]] && count_maint=$((count_maint + 1))
done < <(git log --reverse --no-merges --format='%H%x09%s%x09%b%x1e' "${prev:+$prev..}$target" | tr '\n\036' ' \n' | sed 's/^ //')
changes=''
for pair in "breaking:🚨 Breaking Changes" "feat:🚀 Features" "enh:🌟 Enhancements" "fix:🐛 Bug fixes" "docs:📚 Documentation"; do
kind="${pair%%:*}"
[[ -n ${list[$kind]:-} ]] || continue
changes+="### ${pair#*:}"$'\n'"${list[$kind]}"
done
if [[ -n ${list[maint]:-} ]]; then
changes+=$'\n'"<details><summary>🧰 Maintenance ($count_maint)</summary>"$'\n\n'"${list[maint]}"$'\n'"</details>"$'\n'
fi
if [[ -n $prev ]]; then
full="**Full Changelog**: https://github.com/$repo/compare/$prev...$tag"
else
full="**Full Changelog**: https://github.com/$repo/commits/$tag"
fi
# A release with highlights is a big one: it gets a heading and the support
# section, as in Immich. A patch is a sentence or two and the list.
if grep -q '^## Highlights$' <<< "$entry"; then
cat <<EOF
# 🚀 $name $tag
$entry
## ☕ Support $name
If $name is useful to you, you can buy me a coffee. It keeps these tools going. Found a bug or have an idea? Tell me in the [issues](https://github.com/$repo/issues).
<a href="https://buymeacoffee.com/felitendo"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" height="48"></a>
----
EOF
else
printf '%s\n\n' "$entry"
fi
printf "## What's Changed\n%s\n\n%s\n" "$changes" "$full"
+21
View File
@@ -0,0 +1,21 @@
# Changelog
What each release brings, newest first. The [GitHub releases](https://github.com/LoonixTools/bt-volume-step/releases) add every commit that went into it.
## v1.0.0
_2026-08-16_
Welcome to the very first release of bt-volume-step! It gives your Bluetooth headphones and speakers clean volume steps.
AirPods Pro only know 16 volume steps, so every swipe moves the volume by 6.25 %, and your desktop walks through numbers like these:
```
6 · 13 · 19 · 25 · 31 · 38 · 44 · 50 · 56 · 63 · 69 · 75 · 81 · 88 · 94 · 100
```
### Highlights
- One swipe, one clean step
- Learns every device on its own
- Follows Plasma
+53
View File
@@ -5,3 +5,56 @@
- Use simple English: short sentences, common words.
- Avoid em dashes (—). Do not just swap them for "-" either. Rewrite the sentence instead, for
example with a comma, a colon, brackets or two sentences.
## Releases
Releases look like the ones of big projects such as Immich. `.github/release-notes.sh <tag>` builds
the notes: the entry from `CHANGELOG.md` (welcome and highlights), a support section, every commit
since the last release sorted by its prefix (`feat`, `fix`, `docs`, ...) with author and link, and
the full changelog link. So commit subjects end up in public: keep them clear.
1. Read `git log <last tag>..HEAD` and pick the version: only fixes → patch, something new → minor,
something that breaks or needs the user to act → major.
2. Bump the version and add the entry at the top of `CHANGELOG.md`, in one commit (`chore: 1.4.0`).
3. Push, tag and create the release:
```bash
git tag v1.4.0 && git push origin main v1.4.0
gh release create v1.4.0 --title v1.4.0 --notes "$(.github/release-notes.sh v1.4.0)"
```
4. PKGBUILDS picks up the new release for the AUR on its own.
A minor or major release (has `### Highlights`, gets a heading and the support section):
```markdown
## v1.4.0
_2026-09-24_
Welcome to bt-volume-step `v1.4.0`! One or two sentences on what this release is about.
<p align="center">
<img width="480" alt="What the picture shows" src="https://raw.githubusercontent.com/LoonixTools/bt-volume-step/v1.4.0/<path>">
</p>
### 🚨 Breaking changes
- Only if there are any: what changed, and what the user has to do.
### Highlights
- First highlight, a few words
- Second highlight
```
A picture under the welcome is optional. To show the menu or another screen of the program, use a
real screenshot of it running in Konsole, in English. Never a text copy of the screen. The same goes
for the README. The highlights stay a plain list: no heading or text per highlight, the list of
commits explains the rest.
A patch release is just a sentence or two, for example: "A small patch. The menu no longer closes
when you press Enter." The list of commits follows on its own.
- Write for users: what they notice, not how the code does it. Friendly and simple.
- Commands and settings they type go in backticks, buttons and labels in bold.
- To change an old release: edit its entry, commit, then
`gh release edit <tag> --title <tag> --notes "$(.github/release-notes.sh <tag>)"`.