From 1e3c858b4e21a968b7bbb1f53902994e9ba98fba Mon Sep 17 00:00:00 2001 From: BaaRai <130491490+BaaRai@users.noreply.github.com> Date: Thu, 12 Nov 2026 12:43:10 +0100 Subject: [PATCH] release: build release body from CHANGELOG section The release job now checks out the repo and extracts the CHANGELOG.md section matching the pushed tag (leading v stripped) with scripts/extract-changelog-section.sh, and passes it as body_path. If the section is missing it logs a warning and falls back to a generic body naming the tag and pointing at CHANGELOG.md. scripts/test-extract-changelog-section.sh covers the found and missing cases against a fake CHANGELOG in a temp dir; the CI shell job runs it and shellchecks both scripts. --- .github/workflows/ci.yml | 3 +- .github/workflows/release.yml | 14 +++++- scripts/extract-changelog-section.sh | 33 ++++++++++++++ scripts/test-extract-changelog-section.sh | 54 +++++++++++++++++++++++ 4 files changed, 102 insertions(+), 2 deletions(-) create mode 100755 scripts/extract-changelog-section.sh create mode 100755 scripts/test-extract-changelog-section.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b9dac1f..ac6e007 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -63,7 +63,8 @@ jobs: # shellcheck is preinstalled on the ubuntu runners. -S warning: the scripts # currently have only info-level notes (intentional A && B || C chains in # update.sh, an unquoted glob in backup.sh's prune), so fail on warnings. - - run: shellcheck -S warning install.sh update.sh start.sh scripts/backup.sh reset-password.sh + - run: shellcheck -S warning install.sh update.sh start.sh scripts/backup.sh reset-password.sh scripts/extract-changelog-section.sh scripts/test-extract-changelog-section.sh + - run: bash scripts/test-extract-changelog-section.sh web: runs-on: ubuntu-latest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 79b07ee..3a47d9e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -82,6 +82,7 @@ jobs: needs: binaries runs-on: ubuntu-latest steps: + - uses: actions/checkout@v4 # CHANGELOG.md and the extractor script - uses: actions/download-artifact@v4 with: pattern: pkg-* @@ -90,13 +91,24 @@ jobs: - name: Generate checksums working-directory: dist run: sha256sum *.zip > SHA256SUMS + - name: Release body from CHANGELOG.md + env: + TAG: ${{ github.ref_name }} + run: | + version="${TAG#v}" + if bash scripts/extract-changelog-section.sh "$version" CHANGELOG.md > "$RUNNER_TEMP/release-body.md"; then + echo "Release body: CHANGELOG.md section [$version]" + else + echo "::warning::CHANGELOG.md has no section [$version]; using the generic release body" + echo "Release $TAG. See CHANGELOG.md for the list of changes in this release." > "$RUNNER_TEMP/release-body.md" + fi - uses: softprops/action-gh-release@v2 with: files: | dist/*.zip dist/SHA256SUMS generate_release_notes: false - body: "See CHANGELOG.md for the list of changes in this release." + body_path: ${{ runner.temp }}/release-body.md # ④ Multi-arch image → push to Docker Hub (reuses the Linux binaries built by the # binaries job; the image only installs tools + ships the binary; arm64 only needs diff --git a/scripts/extract-changelog-section.sh b/scripts/extract-changelog-section.sh new file mode 100755 index 0000000..7c11a5f --- /dev/null +++ b/scripts/extract-changelog-section.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# Print the body of the CHANGELOG section "## []": the lines after that +# heading up to (not including) the next "## [" heading, with leading and +# trailing blank lines removed. The version is matched literally (no leading v). +# Exit 1 with nothing on stdout when the section is missing or empty. +# +# Usage: scripts/extract-changelog-section.sh [changelog] (default CHANGELOG.md) +set -euo pipefail + +if [ $# -lt 1 ] || [ $# -gt 2 ] || [ -z "$1" ]; then + echo "usage: $0 [changelog]" >&2 + exit 2 +fi +version=$1 +changelog=${2:-CHANGELOG.md} + +body=$(awk -v head="## [$version]" ' + { sub(/\r$/, "") } # tolerate a CRLF checkout + index($0, "## [") == 1 { + if (found) exit + rest = substr($0, length(head) + 1) + if (substr($0, 1, length(head)) == head && (rest == "" || rest ~ /^[ \t]/)) { found = 1; next } + } + found { print } +' "$changelog") + +# Trim leading blank lines ($(...) already drops the trailing ones). +body=$(printf '%s\n' "$body" | sed '/./,$!d') + +if [ -z "$body" ]; then + exit 1 +fi +printf '%s\n' "$body" diff --git a/scripts/test-extract-changelog-section.sh b/scripts/test-extract-changelog-section.sh new file mode 100755 index 0000000..17a7e16 --- /dev/null +++ b/scripts/test-extract-changelog-section.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +# Tests scripts/extract-changelog-section.sh against a fake CHANGELOG in a temp dir. +set -euo pipefail + +extract="$(cd "$(dirname "$0")" && pwd)/extract-changelog-section.sh" +tmp=$(mktemp -d) +trap 'rm -rf "$tmp"' EXIT + +cat > "$tmp/CHANGELOG.md" <<'EOF' +# Changelog + +## [1.2.30] - 2030-01-03 + +Not this one. + +## [1.2.3] - 2030-01-02 + +Summary line. + +### Added + +- **Thing**: did a thing (#1). + +## [1.2.2] - 2030-01-01 + +- Older entry. +EOF + +expected='Summary line. + +### Added + +- **Thing**: did a thing (#1).' + +fail=0 + +if out=$(bash "$extract" 1.2.3 "$tmp/CHANGELOG.md") && [ "$out" = "$expected" ]; then + echo "PASS: section [1.2.3] found, body returned exactly" +else + echo "FAIL: section [1.2.3]; got:"; printf '%s\n' "${out-}" + fail=1 +fi + +if out=$(bash "$extract" 9.9.9 "$tmp/CHANGELOG.md"); then + echo "FAIL: missing section [9.9.9] exited 0; got:"; printf '%s\n' "$out" + fail=1 +elif [ -n "$out" ]; then + echo "FAIL: missing section [9.9.9] printed output:"; printf '%s\n' "$out" + fail=1 +else + echo "PASS: missing section [9.9.9] exits non-zero with no output" +fi + +exit "$fail"