From 9f07150643e052f56e586f6789dd08e81c104182 Mon Sep 17 00:00:00 2001 From: Felitendo Date: Thu, 24 Sep 2026 02:00:37 +0200 Subject: [PATCH] docs: add a changelog and the release notes flow --- .github/release-notes.sh | 136 +++++++++++++++++++++++++++++++++++++++ CHANGELOG.md | 21 ++++++ CLAUDE.md | 53 +++++++++++++++ 3 files changed, 210 insertions(+) create mode 100755 .github/release-notes.sh create mode 100644 CHANGELOG.md diff --git a/.github/release-notes.sh b/.github/release-notes.sh new file mode 100755 index 0000000..f16f022 --- /dev/null +++ b/.github/release-notes.sh @@ -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'"
🧰 Maintenance ($count_maint)"$'\n\n'"${list[maint]}"$'\n'"
"$'\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 <Buy Me A Coffee + +---- + +EOF +else + printf '%s\n\n' "$entry" +fi +printf "## What's Changed\n%s\n\n%s\n" "$changes" "$full" diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..486db22 --- /dev/null +++ b/CHANGELOG.md @@ -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 diff --git a/CLAUDE.md b/CLAUDE.md index 0d672eb..dc53584 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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 ` 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 ..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. + +

+ What the picture shows +

+ +### 🚨 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 --title --notes "$(.github/release-notes.sh )"`.