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 DELETE \ -H "Authorization: token ${RELEASE_TOKEN}" \ -H "Content-Type: application/json" \ "${release_api}/${existing_release_id}" elif [[ "$status_code" != "404" ]]; then echo "Unexpected response while checking release ${tag}: HTTP ${status_code}" >&2 cat release-existing.json >&2 exit 1 fi 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 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")" 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