Files
omarchy-key-visualizer/.github/workflows/release.yml
T
2026-08-28 10:18:46 -05:00

191 lines
6.7 KiB
YAML

name: release
on:
push:
tags:
- "v*"
workflow_dispatch:
permissions:
contents: write
pull-requests: read
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
- name: Generate changelog
id: changelog
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
TAG="${GITHUB_REF_NAME}"
REPO="${GITHUB_REPOSITORY}"
# previous tag (parent of this tag)
PREV_TAG="$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")"
if [ -z "$PREV_TAG" ]; then
RANGE="HEAD"
echo "No previous tag, using all history"
else
RANGE="${PREV_TAG}..HEAD"
echo "Range: $RANGE"
fi
FEATS=""
FIXES=""
CONTRIBUTORS=""
declare -A SEEN_PRS=()
declare -A SEEN_CONTRIBUTORS=()
add_contributor() {
local login="$1"
local avatar="$2"
local profile="$3"
[ -z "$login" ] && return
[ -n "${SEEN_CONTRIBUTORS[$login]+x}" ] && return
SEEN_CONTRIBUTORS["$login"]=1
avatar="${avatar:-https://github.com/${login}.png?size=96}"
profile="${profile:-https://github.com/${login}}"
CONTRIBUTORS="${CONTRIBUTORS}<a href=\"${profile}\"><img src=\"${avatar}\" width=\"48\" height=\"48\" alt=\"@${login}\"></a> "
}
parse_conventional() {
local text="$1"
ENTRY_TYPE=""
ENTRY_TITLE=""
local pattern='^(feat|fix)(\([^)]*\))?!?:[[:space:]](.+)$'
if [[ "$text" =~ $pattern ]]; then
ENTRY_TYPE="${BASH_REMATCH[1]}"
ENTRY_TITLE="${BASH_REMATCH[3]}"
return 0
fi
return 1
}
author_suffix() {
local login="$1"
local profile="$2"
local name="$3"
if [ -n "$login" ]; then
printf ' by [@%s](%s)' "$login" "${profile:-https://github.com/${login}}"
elif [ -n "$name" ]; then
printf ' by %s' "$name"
fi
}
# %H = hash, %s = subject (first line)
while IFS= read -r line; do
[ -z "$line" ] && continue
sha="${line%%$'\t'*}"
subject="${line#*$'\t'}"
commit_login="$(gh api "repos/${REPO}/commits/${sha}" --jq '.author.login // empty')"
commit_name="$(gh api "repos/${REPO}/commits/${sha}" --jq '.commit.author.name // empty')"
commit_avatar=""
commit_profile=""
if [ -n "$commit_login" ]; then
commit_avatar="$(gh api "repos/${REPO}/commits/${sha}" --jq '.author.avatar_url // empty')"
commit_profile="$(gh api "repos/${REPO}/commits/${sha}" --jq '.author.html_url // empty')"
add_contributor "$commit_login" "$commit_avatar" "$commit_profile"
fi
pr="$(gh api "repos/${REPO}/commits/${sha}/pulls" --jq '.[0].number // empty')"
if [ -n "$pr" ]; then
pr_title="$(gh api "repos/${REPO}/pulls/${pr}" --jq '.title // empty')"
pr_login="$(gh api "repos/${REPO}/pulls/${pr}" --jq '.user.login // empty')"
pr_avatar="$(gh api "repos/${REPO}/pulls/${pr}" --jq '.user.avatar_url // empty')"
pr_profile="$(gh api "repos/${REPO}/pulls/${pr}" --jq '.user.html_url // empty')"
pr_url="$(gh api "repos/${REPO}/pulls/${pr}" --jq '.html_url // empty')"
add_contributor "$pr_login" "$pr_avatar" "$pr_profile"
# A conflict-resolution commit and the original PR commit can
# both be reachable from a release. Render the PR only once and
# attribute it to the PR author, never to the merge author.
if [ -z "${SEEN_PRS[$pr]+x}" ]; then
candidate="$pr_title"
if ! parse_conventional "$candidate"; then
candidate="$subject"
parse_conventional "$candidate" || true
fi
if [ -n "$ENTRY_TYPE" ]; then
SEEN_PRS["$pr"]=1
entry="- ${ENTRY_TITLE} ([#${pr}](${pr_url}))"
entry="${entry}$(author_suffix "$pr_login" "$pr_profile" "$commit_name")"
if [ "$ENTRY_TYPE" = "feat" ]; then
FEATS="${FEATS}${entry}"$'\n'
elif [ "$ENTRY_TYPE" = "fix" ]; then
FIXES="${FIXES}${entry}"$'\n'
fi
fi
fi
elif parse_conventional "$subject"; then
short="${sha:0:7}"
url="https://github.com/${REPO}/commit/${sha}"
entry="- ${ENTRY_TITLE} ([\`${short}\`](${url}))"
entry="${entry}$(author_suffix "$commit_login" "$commit_profile" "$commit_name")"
if [ "$ENTRY_TYPE" = "feat" ]; then
FEATS="${FEATS}${entry}"$'\n'
elif [ "$ENTRY_TYPE" = "fix" ]; then
FIXES="${FIXES}${entry}"$'\n'
fi
fi
done < <(git log "$RANGE" --format="%H%x09%s" --no-merges)
{
echo "# ${TAG}"
echo ""
if [ -n "$FEATS" ]; then
echo "## Features"
echo ""
printf "%s" "$FEATS"
echo ""
fi
if [ -n "$FIXES" ]; then
echo "## Fixes"
echo ""
printf "%s" "$FIXES"
echo ""
fi
if [ -z "$FEATS" ] && [ -z "$FIXES" ]; then
echo "_No relevant conventional commits (feat/fix) in this release._"
echo ""
fi
if [ -n "$CONTRIBUTORS" ]; then
echo "## Contributors"
echo ""
printf "%s\n" "$CONTRIBUTORS"
echo ""
fi
echo "---"
echo ""
if [ -n "$PREV_TAG" ]; then
echo "**Full diff:** [\`${PREV_TAG}...${TAG}\`](https://github.com/${REPO}/compare/${PREV_TAG}...${TAG})"
else
echo "**Full history up to ${TAG}**"
fi
} > /tmp/notes.md
cat /tmp/notes.md
# multiline output for next step
{
echo "notes<<EOF"
cat /tmp/notes.md
echo "EOF"
} >> "$GITHUB_OUTPUT"
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
name: ${{ github.ref_name }}
body: ${{ steps.changelog.outputs.notes }}
generate_release_notes: false