mirror of
https://github.com/Airsonic-Pulse/airsonic-pulse.git
synced 2026-07-09 21:18:27 +00:00
chore: update release workflow (#159)
Co-authored-by: litebito <litebito@users.noreply.github.com>
This commit is contained in:
parent
5f8a2e849f
commit
a2687aaa2e
3 changed files with 248 additions and 67 deletions
|
|
@ -1,23 +1,39 @@
|
|||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# format-release-notes.sh — Generate formatted release notes from merged PRs
|
||||
# release-notes.sh — Generate formatted release notes and write to file
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/format-release-notes.sh <release-tag> [since-tag]
|
||||
# ./scripts/release-notes.sh <release-tag> [since-tag]
|
||||
#
|
||||
# Behavior:
|
||||
# - Generates the auto-categorized "What's Changed" section from merged PRs
|
||||
# - Writes to docs/release-notes/<tag>.md
|
||||
# - Also emits the auto section to stdout for piping/preview
|
||||
# - On first run for a tag, creates the file with a title header and marker:
|
||||
# # Airsonic-Pulse <tag>
|
||||
#
|
||||
# <!-- AUTO-GENERATED-BELOW -->
|
||||
# ## What's Changed
|
||||
# [... auto content ...]
|
||||
# - On subsequent runs, finds the <!-- AUTO-GENERATED-BELOW --> marker and
|
||||
# replaces everything from that line down with freshly-generated content.
|
||||
# Content above the marker (Highlights, Upgrade notes, etc.) is preserved.
|
||||
# - If the file exists but has no marker line, appends marker + auto content
|
||||
# to the end (safety fallback).
|
||||
#
|
||||
# Examples:
|
||||
# ./scripts/format-release-notes.sh v13.1.0 v13.0.0
|
||||
# ./scripts/format-release-notes.sh v13.1.0 # auto-detects previous tag
|
||||
# ./scripts/format-release-notes.sh v13.1.0 v13.0.0 | gh release edit v13.1.0 --notes-file -
|
||||
# ./scripts/release-notes.sh v13.1.0 v13.0.0
|
||||
# ./scripts/release-notes.sh v13.1.0 # auto-detects previous tag
|
||||
# ./scripts/release-notes.sh v13.1.0 | less # preview while writing
|
||||
#
|
||||
# Categorization priority:
|
||||
# 1. Issue labels (if PR references an issue via "fixes #N" or "closes #N")
|
||||
# 2. PR title pattern (fallback for PRs without issue references)
|
||||
#
|
||||
# Label-to-category mapping (in priority order — highest wins):
|
||||
# Label-to-category mapping (highest priority first):
|
||||
# bug → Bug Fixes
|
||||
# hardening, security → Hardening
|
||||
# hardening, security → Hardening & Security
|
||||
# infrastructure → Infrastructure & CI
|
||||
# documentation → Documentation
|
||||
# enhancement → Features & Enhancements
|
||||
|
|
@ -27,6 +43,9 @@ set -euo pipefail
|
|||
|
||||
RELEASE_TAG="${1:-}"
|
||||
SINCE_TAG="${2:-}"
|
||||
MARKER="<!-- AUTO-GENERATED-BELOW -->"
|
||||
NOTES_DIR="docs/release-notes"
|
||||
NOTES_FILE="${NOTES_DIR}/${RELEASE_TAG}.md"
|
||||
|
||||
if [[ -z "${RELEASE_TAG}" ]]; then
|
||||
echo "Usage: $0 <release-tag> [since-tag]" >&2
|
||||
|
|
@ -89,8 +108,6 @@ fi
|
|||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Label priority order (highest priority first).
|
||||
# When an issue has multiple labels, the highest-priority match wins.
|
||||
# E.g. an issue labeled "bug" + "hardening" → Bug Fixes (bug is higher priority).
|
||||
LABEL_PRIORITY=(
|
||||
bug
|
||||
hardening
|
||||
|
|
@ -102,7 +119,6 @@ LABEL_PRIORITY=(
|
|||
chore
|
||||
)
|
||||
|
||||
# Map a label name to a category key
|
||||
label_to_category() {
|
||||
local label="$1"
|
||||
case "${label}" in
|
||||
|
|
@ -118,8 +134,6 @@ label_to_category() {
|
|||
esac
|
||||
}
|
||||
|
||||
# Given a newline-separated list of labels, return the category for the
|
||||
# highest-priority label that maps to a category.
|
||||
labels_to_category() {
|
||||
local labels="$1"
|
||||
local priority_label
|
||||
|
|
@ -132,7 +146,6 @@ labels_to_category() {
|
|||
echo ""
|
||||
}
|
||||
|
||||
# Fallback: guess category from PR title (for PRs without issue references)
|
||||
title_to_category() {
|
||||
local title_lower
|
||||
title_lower="$(echo "$1" | tr '[:upper:]' '[:lower:]')"
|
||||
|
|
@ -157,7 +170,6 @@ title_to_category() {
|
|||
fi
|
||||
}
|
||||
|
||||
# Arrays for categorized output
|
||||
declare -a CAT_FEATURES=()
|
||||
declare -a CAT_DOCUMENTATION=()
|
||||
declare -a CAT_BUGFIXES=()
|
||||
|
|
@ -165,14 +177,12 @@ declare -a CAT_HARDENING=()
|
|||
declare -a CAT_INFRASTRUCTURE=()
|
||||
declare -a CAT_MAINTENANCE=()
|
||||
|
||||
# Process each PR
|
||||
while IFS= read -r pr_line; do
|
||||
PR_NUM="$(echo "${pr_line}" | jq -r '.number')"
|
||||
PR_TITLE="$(echo "${pr_line}" | jq -r '.title')"
|
||||
PR_AUTHOR="$(echo "${pr_line}" | jq -r '.author.login')"
|
||||
PR_BODY="$(echo "${pr_line}" | jq -r '.body // ""')"
|
||||
|
||||
# Extract issue number from PR title or body ("fixes #N", "closes #N")
|
||||
ISSUE_NUM=""
|
||||
if [[ "${PR_TITLE}" =~ [Ff]ixes[[:space:]]+#([0-9]+) ]]; then
|
||||
ISSUE_NUM="${BASH_REMATCH[1]}"
|
||||
|
|
@ -184,38 +194,30 @@ while IFS= read -r pr_line; do
|
|||
ISSUE_NUM="${BASH_REMATCH[1]}"
|
||||
fi
|
||||
|
||||
# Build the formatted line
|
||||
CATEGORY=""
|
||||
if [[ -n "${ISSUE_NUM}" ]]; then
|
||||
# Fetch issue title and labels
|
||||
ISSUE_DATA="$(gh issue view "${ISSUE_NUM}" --json title,labels 2>/dev/null || echo "")"
|
||||
if [[ -n "${ISSUE_DATA}" ]]; then
|
||||
ISSUE_TITLE="$(echo "${ISSUE_DATA}" | jq -r '.title')"
|
||||
# Strip common prefixes from issue titles
|
||||
ISSUE_TITLE="${ISSUE_TITLE#\[Feature Request\]: }"
|
||||
ISSUE_TITLE="${ISSUE_TITLE#\[Bug Report\]: }"
|
||||
|
||||
LINE="* #${ISSUE_NUM} ${ISSUE_TITLE} by @${PR_AUTHOR} in #${PR_NUM}"
|
||||
|
||||
# Categorize from labels (highest-priority label wins)
|
||||
LABELS="$(echo "${ISSUE_DATA}" | jq -r '[.labels[].name] | join("\n")')"
|
||||
CATEGORY="$(labels_to_category "${LABELS}")"
|
||||
else
|
||||
# Issue not found — use PR title
|
||||
CLEAN_TITLE="$(echo "${PR_TITLE}" | sed -E 's/[[:space:]]*[-—]+[[:space:]]*[Ff]ixes[[:space:]]+#[0-9]+//')"
|
||||
LINE="* #${ISSUE_NUM} ${CLEAN_TITLE} by @${PR_AUTHOR} in #${PR_NUM}"
|
||||
fi
|
||||
else
|
||||
# No issue reference — use PR title as-is
|
||||
LINE="* ${PR_TITLE} by @${PR_AUTHOR} in #${PR_NUM}"
|
||||
fi
|
||||
|
||||
# Fallback to title-based categorization if labels didn't match
|
||||
if [[ -z "${CATEGORY}" ]]; then
|
||||
CATEGORY="$(title_to_category "${PR_TITLE}")"
|
||||
fi
|
||||
|
||||
# Add to appropriate category array
|
||||
case "${CATEGORY}" in
|
||||
features) CAT_FEATURES+=("${LINE}") ;;
|
||||
documentation) CAT_DOCUMENTATION+=("${LINE}") ;;
|
||||
|
|
@ -230,46 +232,101 @@ while IFS= read -r pr_line; do
|
|||
done < <(echo "${PRS_FILTERED}" | jq -c '.[]')
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Output
|
||||
# Build the auto section
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
echo "## What's Changed"
|
||||
echo ""
|
||||
AUTO_CONTENT="$(mktemp --suffix=.md)"
|
||||
trap 'rm -f "${AUTO_CONTENT}"' EXIT
|
||||
|
||||
if [[ ${#CAT_FEATURES[@]} -gt 0 ]]; then
|
||||
echo "### Features & Enhancements"
|
||||
printf '%s\n' "${CAT_FEATURES[@]}"
|
||||
{
|
||||
echo "## What's Changed"
|
||||
echo ""
|
||||
|
||||
if [[ ${#CAT_FEATURES[@]} -gt 0 ]]; then
|
||||
echo "### Features & Enhancements"
|
||||
printf '%s\n' "${CAT_FEATURES[@]}"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
if [[ ${#CAT_DOCUMENTATION[@]} -gt 0 ]]; then
|
||||
echo "### Documentation"
|
||||
printf '%s\n' "${CAT_DOCUMENTATION[@]}"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
if [[ ${#CAT_BUGFIXES[@]} -gt 0 ]]; then
|
||||
echo "### Bug Fixes"
|
||||
printf '%s\n' "${CAT_BUGFIXES[@]}"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
if [[ ${#CAT_HARDENING[@]} -gt 0 ]]; then
|
||||
echo "### Hardening & Security"
|
||||
printf '%s\n' "${CAT_HARDENING[@]}"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
if [[ ${#CAT_INFRASTRUCTURE[@]} -gt 0 ]]; then
|
||||
echo "### Infrastructure & CI"
|
||||
printf '%s\n' "${CAT_INFRASTRUCTURE[@]}"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
if [[ ${#CAT_MAINTENANCE[@]} -gt 0 ]]; then
|
||||
echo "### Maintenance"
|
||||
printf '%s\n' "${CAT_MAINTENANCE[@]}"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
echo "**Full Changelog**: https://github.com/litebito/airsonic-pulse/compare/${SINCE_TAG}...${RELEASE_TAG}"
|
||||
} > "${AUTO_CONTENT}"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Write to file (creating, updating, or appending as appropriate)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
mkdir -p "${NOTES_DIR}"
|
||||
|
||||
if [[ ! -f "${NOTES_FILE}" ]]; then
|
||||
# First run for this tag — create the file with title, marker, and auto content
|
||||
{
|
||||
echo "# Airsonic-Pulse ${RELEASE_TAG}"
|
||||
echo ""
|
||||
echo "${MARKER}"
|
||||
cat "${AUTO_CONTENT}"
|
||||
} > "${NOTES_FILE}"
|
||||
echo "Created ${NOTES_FILE}" >&2
|
||||
echo "Add Highlights / Upgrade notes between the title and the ${MARKER} marker," >&2
|
||||
echo "then commit and push before tagging." >&2
|
||||
|
||||
elif grep -qF "${MARKER}" "${NOTES_FILE}"; then
|
||||
# File exists with marker — preserve everything up to and including the marker,
|
||||
# replace everything below with fresh auto content
|
||||
PRESERVED="$(mktemp --suffix=.md)"
|
||||
trap 'rm -f "${AUTO_CONTENT}" "${PRESERVED}"' EXIT
|
||||
|
||||
# Extract content up to and including the marker line
|
||||
awk -v marker="${MARKER}" '
|
||||
{ print }
|
||||
index($0, marker) { exit }
|
||||
' "${NOTES_FILE}" > "${PRESERVED}"
|
||||
|
||||
{
|
||||
cat "${PRESERVED}"
|
||||
cat "${AUTO_CONTENT}"
|
||||
} > "${NOTES_FILE}"
|
||||
|
||||
echo "Updated ${NOTES_FILE} (regenerated auto section below ${MARKER})" >&2
|
||||
|
||||
else
|
||||
# File exists but no marker — append marker + auto content as safety fallback
|
||||
{
|
||||
echo ""
|
||||
echo "${MARKER}"
|
||||
cat "${AUTO_CONTENT}"
|
||||
} >> "${NOTES_FILE}"
|
||||
echo "Appended marker + auto content to ${NOTES_FILE} (no existing marker found)" >&2
|
||||
fi
|
||||
|
||||
if [[ ${#CAT_DOCUMENTATION[@]} -gt 0 ]]; then
|
||||
echo "### Documentation"
|
||||
printf '%s\n' "${CAT_DOCUMENTATION[@]}"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
if [[ ${#CAT_BUGFIXES[@]} -gt 0 ]]; then
|
||||
echo "### Bug Fixes"
|
||||
printf '%s\n' "${CAT_BUGFIXES[@]}"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
if [[ ${#CAT_HARDENING[@]} -gt 0 ]]; then
|
||||
echo "### Hardening & Security"
|
||||
printf '%s\n' "${CAT_HARDENING[@]}"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
if [[ ${#CAT_INFRASTRUCTURE[@]} -gt 0 ]]; then
|
||||
echo "### Infrastructure & CI"
|
||||
printf '%s\n' "${CAT_INFRASTRUCTURE[@]}"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
if [[ ${#CAT_MAINTENANCE[@]} -gt 0 ]]; then
|
||||
echo "### Maintenance"
|
||||
printf '%s\n' "${CAT_MAINTENANCE[@]}"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
echo "**Full Changelog**: https://github.com/litebito/airsonic-pulse/compare/${SINCE_TAG}...${RELEASE_TAG}"
|
||||
# Always emit to stdout for piping/preview
|
||||
cat "${AUTO_CONTENT}"
|
||||
47
.github/workflows/release.yml
vendored
47
.github/workflows/release.yml
vendored
|
|
@ -21,6 +21,22 @@ jobs:
|
|||
mvn --version
|
||||
ffmpeg -version
|
||||
|
||||
- name: Locate release notes
|
||||
id: notes
|
||||
env:
|
||||
REF_NAME: ${{ github.ref_name }}
|
||||
run: |
|
||||
NOTES_FILE="docs/release-notes/${REF_NAME}.md"
|
||||
if [[ ! -f "${NOTES_FILE}" ]]; then
|
||||
echo "::error::Release notes file missing at ${NOTES_FILE}"
|
||||
echo "::error::Generate it locally with:"
|
||||
echo "::error:: bash .github/scripts/release-notes.sh ${REF_NAME}"
|
||||
echo "::error::Edit the file to add Highlights / Upgrade notes above the marker,"
|
||||
echo "::error::then commit, push, and re-tag."
|
||||
exit 1
|
||||
fi
|
||||
echo "notes_file=${NOTES_FILE}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Create music directory
|
||||
run: mkdir -p /tmp/music
|
||||
|
||||
|
|
@ -29,20 +45,38 @@ jobs:
|
|||
|
||||
- name: Extract version from tag
|
||||
id: version
|
||||
run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
|
||||
env:
|
||||
REF_NAME: ${{ github.ref_name }}
|
||||
run: echo "version=${REF_NAME#v}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Generate checksums
|
||||
run: |
|
||||
cd airsonic-main/target
|
||||
sha256sum airsonic.war > artifacts-checksums.sha256
|
||||
|
||||
- name: Determine prerelease flag
|
||||
id: prerelease
|
||||
env:
|
||||
REF_NAME: ${{ github.ref_name }}
|
||||
run: |
|
||||
# Tag contains a hyphen → prerelease (e.g. v13.1.0-rc.1, v13.1.0-beta.2)
|
||||
if [[ "${REF_NAME}" == *-* ]]; then
|
||||
echo "flag=--prerelease" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "flag=" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Create GitHub Release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
REF_NAME: ${{ github.ref_name }}
|
||||
NOTES_FILE: ${{ steps.notes.outputs.notes_file }}
|
||||
PRERELEASE_FLAG: ${{ steps.prerelease.outputs.flag }}
|
||||
run: |
|
||||
gh release create "${{ github.ref_name }}" \
|
||||
--title "${{ github.ref_name }}" \
|
||||
--generate-notes \
|
||||
gh release create "${REF_NAME}" \
|
||||
--title "${REF_NAME}" \
|
||||
--notes-file "${NOTES_FILE}" \
|
||||
${PRERELEASE_FLAG} \
|
||||
airsonic-main/target/airsonic.war#airsonic.war \
|
||||
airsonic-main/target/artifacts-checksums.sha256#artifacts-checksums.sha256
|
||||
|
||||
|
|
@ -66,8 +100,9 @@ jobs:
|
|||
|
||||
- name: Determine Docker tags
|
||||
id: docker-tags
|
||||
env:
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
run: |
|
||||
VERSION=${{ steps.version.outputs.version }}
|
||||
TAGS="ghcr.io/litebito/airsonic-pulse:${VERSION}"
|
||||
# Add 'latest' tag only for non-prerelease versions (no hyphen in version)
|
||||
if [[ "${VERSION}" != *-* ]]; then
|
||||
|
|
@ -83,4 +118,4 @@ jobs:
|
|||
push: true
|
||||
tags: ${{ steps.docker-tags.outputs.tags }}
|
||||
build-args: |
|
||||
VERSION=${{ steps.version.outputs.version }}
|
||||
VERSION=${{ steps.version.outputs.version }}
|
||||
Loading…
Add table
Add a link
Reference in a new issue