Files
vociferate/.gitea/workflows/prepare-release.yml
Micheal Wilkinson 8fadb8299c
All checks were successful
Push Validation / validate (push) Successful in 52s
ci(release): upsert release and replace matching assets
2026-03-20 18:43:49 +00:00

223 lines
7.6 KiB
YAML

name: Prepare Release
on:
workflow_dispatch:
inputs:
version:
description: Semantic version to release, with or without leading v.
required: true
workflow_call:
inputs:
version:
description: Semantic version to release, with or without leading v.
required: true
type: string
jobs:
prepare:
runs-on: ubuntu-latest
container: docker.io/catthehacker/ubuntu:act-latest
defaults:
run:
shell: bash
env:
RELEASE_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.26.1'
check-latest: true
cache: true
cache-dependency-path: go.sum
- name: Prepare release files
env:
RELEASE_VERSION: ${{ inputs.version }}
run: |
set -euo pipefail
./script/prepare-release.sh "$RELEASE_VERSION"
- name: Run tests
run: |
set -euo pipefail
go test ./...
- name: Configure git author
run: |
set -euo pipefail
git config user.name "gitea-actions[bot]"
git config user.email "gitea-actions[bot]@users.noreply.local"
- name: Commit release changes and push tag
env:
RELEASE_VERSION: ${{ inputs.version }}
run: |
set -euo pipefail
normalized_version="${RELEASE_VERSION#v}"
tag="v${normalized_version}"
if git rev-parse "$tag" >/dev/null 2>&1; then
echo "Tag ${tag} already exists" >&2
exit 1
fi
case "$GITHUB_SERVER_URL" in
https://*)
authed_remote="https://oauth2:${RELEASE_TOKEN}@${GITHUB_SERVER_URL#https://}/${GITHUB_REPOSITORY}.git"
;;
http://*)
authed_remote="http://oauth2:${RELEASE_TOKEN}@${GITHUB_SERVER_URL#http://}/${GITHUB_REPOSITORY}.git"
;;
*)
echo "Unsupported GITHUB_SERVER_URL: ${GITHUB_SERVER_URL}" >&2
exit 1
;;
esac
git remote set-url origin "$authed_remote"
git add changelog.md internal/releaseprep/version/version.go
git commit -m "release: prepare ${tag}"
git tag "$tag"
git push origin HEAD
git push origin "$tag"
- name: Create release with changelog notes
env:
RELEASE_VERSION: ${{ inputs.version }}
run: |
set -euo pipefail
normalized_version="${RELEASE_VERSION#v}"
tag="v${normalized_version}"
release_notes="$(awk -v version="$normalized_version" '
$0 ~ "^## \\\\[" version "\\\\] - " {capture=1}
capture {
if ($0 ~ "^## \\\\[" && $0 !~ "^## \\\\[" version "\\\\] - ") exit
print
}
' changelog.md)"
if [[ -z "${release_notes//[[:space:]]/}" ]]; then
echo "Release notes section for ${normalized_version} was not found in changelog.md" >&2
exit 1
fi
escaped_release_notes="$(printf '%s' "$release_notes" | sed 's/\\/\\\\/g; s/"/\\"/g; :a;N;$!ba;s/\n/\\n/g')"
release_api="${GITHUB_API_URL:-${GITHUB_SERVER_URL%/}/api/v1}/repos/${GITHUB_REPOSITORY}/releases"
release_by_tag_api="${release_api}/tags/${tag}"
status_code="$(curl -sS -o release-existing.json -w '%{http_code}' \
-H "Authorization: token ${RELEASE_TOKEN}" \
-H "Content-Type: application/json" \
"${release_by_tag_api}")"
if [[ "$status_code" == "200" ]]; then
existing_release_id="$(sed -n 's/.*"id"[[:space:]]*:[[:space:]]*\([0-9][0-9]*\).*/\1/p' release-existing.json | head -n 1)"
if [[ -z "$existing_release_id" ]]; then
echo "Failed to parse existing release id for ${tag}" >&2
cat release-existing.json >&2
exit 1
fi
curl --fail-with-body \
-X PATCH \
-H "Authorization: token ${RELEASE_TOKEN}" \
-H "Content-Type: application/json" \
"${release_api}/${existing_release_id}" \
--data "{\"tag_name\":\"${tag}\",\"target\":\"${GITHUB_SHA}\",\"name\":\"${tag}\",\"body\":\"${escaped_release_notes}\",\"draft\":false,\"prerelease\":false}" \
--output release.json
elif [[ "$status_code" != "404" ]]; then
echo "Unexpected response while checking release ${tag}: HTTP ${status_code}" >&2
cat release-existing.json >&2
exit 1
else
curl --fail-with-body \
-X POST \
-H "Authorization: token ${RELEASE_TOKEN}" \
-H "Content-Type: application/json" \
"${release_api}" \
--data "{\"tag_name\":\"${tag}\",\"target\":\"${GITHUB_SHA}\",\"name\":\"${tag}\",\"body\":\"${escaped_release_notes}\",\"draft\":false,\"prerelease\":false}" \
--output release.json
fi
release_id="$(sed -n 's/.*"id"[[:space:]]*:[[:space:]]*\([0-9][0-9]*\).*/\1/p' release.json | head -n 1)"
if [[ -z "$release_id" ]]; then
echo "Failed to parse release id from API response" >&2
cat release.json >&2
exit 1
fi
echo "RELEASE_ID=$release_id" >> "$GITHUB_ENV"
- name: Build release binaries
env:
RELEASE_VERSION: ${{ inputs.version }}
run: |
set -euo pipefail
normalized_version="${RELEASE_VERSION#v}"
mkdir -p dist
for target in darwin/amd64 darwin/arm64 linux/amd64 linux/arm64 windows/amd64 windows/arm64; do
os="${target%/*}"
arch="${target#*/}"
ext=""
if [[ "$os" == "windows" ]]; then
ext=".exe"
fi
bin="vociferate_${normalized_version}_${os}_${arch}${ext}"
GOOS="$os" GOARCH="$arch" go build -trimpath -ldflags="-s -w" -o "dist/${bin}" ./cmd/releaseprep
done
(
cd dist
shasum -a 256 * > checksums.txt
)
- name: Upload release binaries
run: |
set -euo pipefail
if [[ -z "${RELEASE_ID:-}" ]]; then
echo "RELEASE_ID is not available for asset upload" >&2
exit 1
fi
release_api="${GITHUB_API_URL:-${GITHUB_SERVER_URL%/}/api/v1}/repos/${GITHUB_REPOSITORY}/releases/${RELEASE_ID}/assets"
for asset in dist/*; do
name="$(basename "$asset")"
assets_json="$(curl -sS --fail-with-body \
-H "Authorization: token ${RELEASE_TOKEN}" \
-H "Content-Type: application/json" \
"${release_api}")"
escaped_name="$(printf '%s' "$name" | sed 's/[][(){}.^$*+?|\\/]/\\&/g')"
existing_asset_id="$(printf '%s' "$assets_json" | tr -d '\n' | sed -n "s/.*{\"id\":\([0-9][0-9]*\)[^}]*\"name\":\"${escaped_name}\".*/\1/p")"
if [[ -n "$existing_asset_id" ]]; then
curl --fail-with-body \
-X DELETE \
-H "Authorization: token ${RELEASE_TOKEN}" \
-H "Content-Type: application/json" \
"${release_api}/${existing_asset_id}"
fi
curl --fail-with-body \
-X POST \
-H "Authorization: token ${RELEASE_TOKEN}" \
-H "Content-Type: application/octet-stream" \
"${release_api}?name=${name}" \
--data-binary "@${asset}"
done