From 438b05b8a3ba257801e1fb4987d3a31b201874f0 Mon Sep 17 00:00:00 2001 From: vikingowl Date: Fri, 17 Oct 2025 01:08:57 +0200 Subject: [PATCH] ci: derive release notes from CHANGELOG.md --- .woodpecker.yml | 7 ++++- scripts/release-notes.sh | 57 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100755 scripts/release-notes.sh diff --git a/.woodpecker.yml b/.woodpecker.yml index ff32250..76071cf 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -146,6 +146,11 @@ steps: sha256sum ${ARTIFACT}.tar.gz > ${ARTIFACT}.tar.gz.sha256 fi + - name: release-notes + image: *rust_image + commands: + - scripts/release-notes.sh "${CI_COMMIT_TAG}" release-notes.md + - name: release image: plugins/gitea-release settings: @@ -158,4 +163,4 @@ steps: - ${ARTIFACT}.zip - ${ARTIFACT}.zip.sha256 title: Release ${CI_COMMIT_TAG} - note: "Release ${CI_COMMIT_TAG}" + note_file: release-notes.md diff --git a/scripts/release-notes.sh b/scripts/release-notes.sh new file mode 100755 index 0000000..6f9094c --- /dev/null +++ b/scripts/release-notes.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" +CHANGELOG="${REPO_ROOT}/CHANGELOG.md" + +TAG="${1:-}" +OUTPUT="${2:-}" + +if [[ -z "${TAG}" ]]; then + echo "usage: $0 [output-file]" >&2 + exit 1 +fi + +TAG="${TAG#v}" +TAG="${TAG#V}" + +if [[ ! -f "${CHANGELOG}" ]]; then + echo "error: CHANGELOG.md not found at ${CHANGELOG}" >&2 + exit 1 +fi + +NOTES=$(TAG="${TAG}" CHANGELOG_PATH="${CHANGELOG}" python - <<'PY' +import os +import re +import sys +from pathlib import Path + +changelog_path = Path(os.environ['CHANGELOG_PATH']) +tag = os.environ['TAG'] +text = changelog_path.read_text(encoding='utf-8') +pattern = re.compile(rf'^## \[{re.escape(tag)}\]\s*(?:-.*)?$', re.MULTILINE) +match = pattern.search(text) +if not match: + sys.stderr.write(f"No changelog section found for tag {tag}.\n") + sys.exit(1) +start = match.end() +rest = text[start:] +next_heading = re.search(r'^## \[', rest, re.MULTILINE) +section = rest[:next_heading.start()] if next_heading else rest +lines = [line.rstrip() for line in section.strip().splitlines()] +print('\n'.join(lines)) +PY +) + +if [[ -z "${NOTES}" ]]; then + echo "error: no content generated for tag ${TAG}" >&2 + exit 1 +fi + +if [[ -n "${OUTPUT}" ]]; then + printf '%s\n' "${NOTES}" > "${OUTPUT}" +else + printf '%s\n' "${NOTES}" +fi