From 3e0084ae1d6530f6deb33ef43c7e5a21b1dbfbfb Mon Sep 17 00:00:00 2001 From: Felitendo Date: Thu, 24 Sep 2026 01:16:02 +0200 Subject: [PATCH] docs: add a changelog and the release notes flow --- .github/release-notes.sh | 135 +++++++++++++++++++++++++++++++++++++++ CHANGELOG.md | 21 ++++++ CLAUDE.md | 48 ++++++++++++++ 3 files changed, 204 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..473be62 --- /dev/null +++ b/.github/release-notes.sh @@ -0,0 +1,135 @@ +#!/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 their first word. + 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..b78acbc --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,21 @@ +# Changelog + +What each release brings, newest first. The [GitHub releases](https://github.com/LoonixTools/plasma-face-unlock/releases) add every commit that went into it. + +## v1.0.0 + +_2026-09-23_ + +Welcome to the very first release of plasma-face-unlock! It brings Face ID to KDE Plasma: look at the screen and it unlocks. The lock screen, sudo and admin prompts can all take your face instead of a password. + +

+ The bubble drops down over the lock screen, finds the face and shows a green tick +

+ +### Highlights + +- Unlock the lock screen with your face +- sudo and admin prompts +- A bubble like Face ID +- Photos do not get in +- Packages for Arch, Fedora, Debian and Kubuntu diff --git a/CLAUDE.md b/CLAUDE.md index e1bf6a4..e19f86c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -24,6 +24,54 @@ - `src/pam`: the PAM module for sudo and admin prompts. - `src/ctl`: the client the bash code talks to the daemon with. +## 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, then push the tag: `git tag v1.4.0 && git push origin main v1.4.0`. The `release` workflow + builds the packages and creates the release. It stops before building when the entry is missing. +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 plasma-face-unlock `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 or a snippet of the menu under the welcome is optional. 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 )"`. + ## Testing Never test against the real setup: enrolling and the PAM files belong to the user's machine.