240 lines
8.5 KiB
YAML
240 lines
8.5 KiB
YAML
name: Prepare Release
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
version:
|
|
description: Optional semantic version override, with or without leading v. When omitted, the recommended version is used.
|
|
required: false
|
|
workflow_call:
|
|
inputs:
|
|
version:
|
|
description: Optional semantic version override, with or without leading v. When omitted, the recommended version is used.
|
|
required: false
|
|
default: ''
|
|
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: Resolve release version
|
|
id: resolve-version
|
|
env:
|
|
INPUT_VERSION: ${{ inputs.version }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
provided_version="$(printf '%s' "${INPUT_VERSION:-}" | sed 's/^[[:space:]]\+//; s/[[:space:]]\+$//')"
|
|
|
|
if [[ -n "$provided_version" ]]; then
|
|
release_version="$provided_version"
|
|
else
|
|
if ! release_version="$(go run ./cmd/vociferate --recommend --root . 2>release-recommendation.err)"; then
|
|
recommendation_error="$(tr '\n' ' ' < release-recommendation.err | sed 's/[[:space:]]\+/ /g' | sed 's/^ //; s/ $//')"
|
|
echo "Resolve release version: ${recommendation_error}" >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
echo "RELEASE_VERSION=$release_version" >> "$GITHUB_ENV"
|
|
echo "version=$release_version" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Prepare release files
|
|
run: |
|
|
set -euo pipefail
|
|
go run ./cmd/vociferate \
|
|
--root . \
|
|
--version "$RELEASE_VERSION" \
|
|
--date "$(date -u +%F)" \
|
|
--version-file internal/vociferate/version/version.go \
|
|
--version-pattern 'const String = "([^"]+)"' \
|
|
--changelog changelog.md
|
|
|
|
- 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
|
|
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/vociferate/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
|
|
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
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
normalized_version="${RELEASE_VERSION#v}"
|
|
mkdir -p dist
|
|
|
|
for target in linux/amd64 linux/arm64; do
|
|
os="${target%/*}"
|
|
arch="${target#*/}"
|
|
|
|
bin="vociferate_${normalized_version}_${os}_${arch}"
|
|
GOOS="$os" GOARCH="$arch" go build -trimpath -ldflags="-s -w" -o "dist/${bin}" ./cmd/vociferate
|
|
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
|