From 993768ae9b95f806b4f03117b88b8fdf41a0891e Mon Sep 17 00:00:00 2001 From: Micheal Wilkinson Date: Sat, 21 Mar 2026 19:30:53 +0000 Subject: [PATCH] refactor(release): inline release and validate jobs into prepare-release Replaces the workflow_call to do-release with directly inlined release and validate jobs. All steps now appear flat in the Actions UI with full individual step visibility instead of being collapsed under a setup job. Tag resolution in the release job is simplified: the tag always comes from needs.prepare.outputs.tag, removing the detect-tag guessing needed for standalone dispatch. do-release.yml is unchanged and remains available for manual dispatch. --- .gitea/workflows/prepare-release.yml | 276 ++++++++++++++++++++++++++- 1 file changed, 267 insertions(+), 9 deletions(-) diff --git a/.gitea/workflows/prepare-release.yml b/.gitea/workflows/prepare-release.yml index 6c5f4a4..31e4d0d 100644 --- a/.gitea/workflows/prepare-release.yml +++ b/.gitea/workflows/prepare-release.yml @@ -127,15 +127,280 @@ jobs: token: ${{ secrets.RELEASE_PAT }} git-add-files: CHANGELOG.md release-version README.md AGENTS.md - - name: Summarize prepared release + - name: Summary + if: ${{ always() }} run: | set -euo pipefail + tag="${{ steps.prepare.outputs.version }}" { echo "## Release Prepared" echo echo "- Tag pushed: ${tag}" - echo "- Do Release should trigger automatically from tag push for ${tag}." + } >> "$SUMMARY_FILE" + + echo 'Summary' + echo + + if [[ -s "$SUMMARY_FILE" ]]; then + cat "$SUMMARY_FILE" + else + echo 'No summary generated.' + fi + + release: + runs-on: ubuntu-latest + container: docker.io/catthehacker/ubuntu:act-latest + needs: prepare + outputs: + tag: ${{ steps.publish.outputs.tag }} + version: ${{ steps.publish.outputs.version }} + defaults: + run: + shell: bash + env: + RELEASE_TOKEN: ${{ secrets.RELEASE_PAT }} + SUMMARY_FILE: ${{ runner.temp }}/release-summary.md + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Resolve release version + id: resolve-version + env: + PREPARE_TAG: ${{ needs.prepare.outputs.tag }} + run: | + set -euo pipefail + + tag="$(printf '%s' "${PREPARE_TAG}" | sed 's/^[[:space:]]\+//; s/[[:space:]]\+$//')" + + # Unwrap Teacup expression strings if present. + if [[ "${tag}" =~ ^%\!t\(string=(.*)\)$ ]]; then + tag="${BASH_REMATCH[1]}" + fi + + normalized="${tag#v}" + tag="v${normalized}" + echo "tag=${tag}" >> "$GITHUB_OUTPUT" + echo "version=${normalized}" >> "$GITHUB_OUTPUT" + + - name: Checkout release tag + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: refs/tags/${{ steps.resolve-version.outputs.tag }} + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + check-latest: false + cache: true + cache-dependency-path: go.sum + + - name: Preflight release API access + env: + TAG_NAME: ${{ steps.resolve-version.outputs.tag }} + run: | + set -euo pipefail + + if [[ -z "${RELEASE_TOKEN:-}" ]]; then + echo "No release token available. Set secrets.RELEASE_PAT." >&2 + exit 1 + fi + + api_base="${GITHUB_API_URL:-${GITHUB_SERVER_URL%/}/api/v1}" + repo_api="${api_base}/repos/${GITHUB_REPOSITORY}" + + curl --fail-with-body -sS \ + -H "Authorization: token ${RELEASE_TOKEN}" \ + -H "Content-Type: application/json" \ + "${repo_api}" >/dev/null + + curl --fail-with-body -sS \ + -H "Authorization: token ${RELEASE_TOKEN}" \ + -H "Content-Type: application/json" \ + "${repo_api}/releases?limit=1" >/dev/null + + if ! git rev-parse --verify --quiet "refs/tags/${TAG_NAME}" >/dev/null; then + echo "Tag ${TAG_NAME} was not found in the checked out repository." >&2 + exit 1 + fi + + - name: Create or update release + id: publish + uses: ./publish + with: + token: ${{ secrets.RELEASE_PAT }} + version: ${{ steps.resolve-version.outputs.version }} + + - name: Build release binaries + env: + RELEASE_VERSION: ${{ steps.publish.outputs.version }} + run: | + set -euo pipefail + + mkdir -p dist + + for target in linux/amd64 linux/arm64; do + os="${target%/*}" + arch="${target#*/}" + + bin="vociferate_${RELEASE_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 + env: + RELEASE_ID: ${{ steps.publish.outputs.release-id }} + RELEASE_VERSION: ${{ steps.publish.outputs.version }} + run: | + set -euo pipefail + + 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 + + - name: Summary + if: ${{ always() }} + env: + TAG_NAME: ${{ steps.publish.outputs.tag }} + RELEASE_VERSION: ${{ steps.publish.outputs.version }} + run: | + set -euo pipefail + + { + echo "## Release Published" + echo + echo "- Tag: ${TAG_NAME}" + echo "- Release notes sourced from changelog entry ${RELEASE_VERSION}." + echo "- Published assets: vociferate_${RELEASE_VERSION}_linux_amd64, vociferate_${RELEASE_VERSION}_linux_arm64, checksums.txt" + } >> "$SUMMARY_FILE" + + echo 'Summary' + echo + + if [[ -s "$SUMMARY_FILE" ]]; then + cat "$SUMMARY_FILE" + else + echo 'No summary generated.' + fi + + validate: + runs-on: ubuntu-latest + container: docker.io/catthehacker/ubuntu:act-latest + needs: release + strategy: + fail-fast: false + matrix: + include: + - asset_arch: amd64 + run_command: ./vociferate + - asset_arch: arm64 + run_command: qemu-aarch64-static ./vociferate + defaults: + run: + shell: bash + env: + SUMMARY_FILE: ${{ runner.temp }}/validate-summary.md + steps: + - name: Checkout tagged revision + uses: actions/checkout@v4 + with: + ref: refs/tags/${{ needs.release.outputs.tag }} + + - name: Install arm64 emulator + if: ${{ matrix.asset_arch == 'arm64' }} + run: | + set -euo pipefail + apt-get update + apt-get install -y qemu-user-static + + - name: Download released binary + env: + TOKEN: ${{ secrets.RELEASE_PAT }} + TAG_NAME: ${{ needs.release.outputs.tag }} + RELEASE_VERSION: ${{ needs.release.outputs.version }} + ASSET_ARCH: ${{ matrix.asset_arch }} + SERVER_URL: ${{ github.server_url }} + run: | + set -euo pipefail + + asset_name="vociferate_${RELEASE_VERSION}_linux_${ASSET_ARCH}" + asset_url="${SERVER_URL}/aether/vociferate/releases/download/${TAG_NAME}/${asset_name}" + + curl --fail --location \ + -H "Authorization: token ${TOKEN}" \ + -o vociferate \ + "$asset_url" + chmod +x vociferate + + echo "asset_name=${asset_name}" >> "$GITHUB_ENV" + + - name: Smoke test released binary + env: + RUN_COMMAND: ${{ matrix.run_command }} + TAG_NAME: ${{ needs.release.outputs.tag }} + run: | + set -euo pipefail + + ${RUN_COMMAND} --help >/dev/null + + recommend_stderr="$(mktemp)" + if ${RUN_COMMAND} --recommend --root . >/dev/null 2>"${recommend_stderr}"; then + echo "Expected --recommend to fail on the tagged release checkout" >&2 + exit 1 + fi + + recommend_error="$(cat "${recommend_stderr}")" + case "${recommend_error}" in + *"unreleased section is empty"*) + ;; + *) + echo "Unexpected recommend failure output: ${recommend_error}" >&2 + exit 1 + ;; + esac + + { + echo "## Released Binary Validation (${{ matrix.asset_arch }})" + echo + echo "- Release tag: ${TAG_NAME}" + echo "- Asset: ${asset_name}" + echo "- Binary executed successfully via --help." + echo "- --recommend failed as expected on the tagged checkout because Unreleased is empty." } >> "$SUMMARY_FILE" - name: Summary @@ -151,10 +416,3 @@ jobs: else echo 'No summary generated.' fi - - publish: - needs: prepare - uses: ./.gitea/workflows/do-release.yml - with: - tag: ${{ needs.prepare.outputs.tag }} - secrets: inherit