feat: add prepare and publish composite actions

Add two focused subdirectory composite actions:

- prepare/action.yml: downloads the vociferate binary, runs it to update
  changelog and release-version, then commits, tags, and pushes — replacing
  the boilerplate git steps consumers previously had to write inline.

- publish/action.yml: extracts the matching changelog section and creates or
  updates the Gitea/GitHub release. Outputs release-id, tag, and version so
  consumers can upload their own assets after it runs.

Simplify the vociferate workflows to use ./prepare and ./publish directly,
validating both actions in the self-release pipeline.

Update README to show the clean two-action usage pattern.
This commit is contained in:
Micheal Wilkinson
2026-03-20 20:27:22 +00:00
parent 4ae6f34931
commit 647d8cf76f
5 changed files with 429 additions and 256 deletions

View File

@@ -22,8 +22,8 @@ jobs:
runs-on: ubuntu-latest
container: docker.io/catthehacker/ubuntu:act-latest
outputs:
tag: ${{ steps.resolve-tag.outputs.tag }}
version: ${{ steps.resolve-tag.outputs.version }}
tag: ${{ steps.publish.outputs.tag }}
version: ${{ steps.publish.outputs.version }}
defaults:
run:
shell: bash
@@ -36,35 +36,12 @@ jobs:
fetch-depth: 0
ref: ${{ github.ref }}
- name: Resolve release tag
id: resolve-tag
env:
INPUT_TAG: ${{ inputs.tag }}
GITHUB_REF_VALUE: ${{ github.ref }}
run: |
set -euo pipefail
provided_tag="$(printf '%s' "${INPUT_TAG:-}" | sed 's/^[[:space:]]\+//; s/[[:space:]]\+$//')"
if [[ -n "$provided_tag" ]]; then
normalized_version="${provided_tag#v}"
tag="v${normalized_version}"
elif [[ "${GITHUB_REF_VALUE}" == refs/tags/* ]]; then
tag="${GITHUB_REF_VALUE#refs/tags/}"
normalized_version="${tag#v}"
else
echo "A tag input is required when the workflow is not running from a tag push" >&2
exit 1
fi
echo "tag=$tag" >> "$GITHUB_OUTPUT"
echo "version=$normalized_version" >> "$GITHUB_OUTPUT"
- name: Checkout requested tag
if: ${{ inputs.tag != '' }}
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: refs/tags/${{ steps.resolve-tag.outputs.tag }}
ref: ${{ startsWith(inputs.tag, 'v') && format('refs/tags/{0}', inputs.tag) || format('refs/tags/v{0}', inputs.tag) }}
- name: Setup Go
uses: actions/setup-go@v5
@@ -74,90 +51,16 @@ jobs:
cache: true
cache-dependency-path: go.sum
- name: Extract release notes from changelog
id: release-notes
env:
RELEASE_VERSION: ${{ steps.resolve-tag.outputs.version }}
run: |
set -euo pipefail
release_notes="$(awk -v version="$RELEASE_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 ${RELEASE_VERSION} was not found in changelog.md" >&2
exit 1
fi
notes_file="$RUNNER_TEMP/release-notes.md"
printf '%s\n' "$release_notes" > "$notes_file"
echo "notes_file=$notes_file" >> "$GITHUB_OUTPUT"
- name: Create or update release
id: release
env:
TAG_NAME: ${{ steps.resolve-tag.outputs.tag }}
RELEASE_NOTES_FILE: ${{ steps.release-notes.outputs.notes_file }}
run: |
set -euo pipefail
release_notes="$(cat "$RELEASE_NOTES_FILE")"
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_NAME}"
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_NAME}" >&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_NAME}\",\"target\":\"${GITHUB_SHA}\",\"name\":\"${TAG_NAME}\",\"body\":\"${escaped_release_notes}\",\"draft\":false,\"prerelease\":false}" \
--output release.json
elif [[ "$status_code" != "404" ]]; then
echo "Unexpected response while checking release ${TAG_NAME}: 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_NAME}\",\"target\":\"${GITHUB_SHA}\",\"name\":\"${TAG_NAME}\",\"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 "id=$release_id" >> "$GITHUB_OUTPUT"
id: publish
uses: ./publish
with:
token: ${{ secrets.GITHUB_TOKEN }}
version: ${{ inputs.tag }}
- name: Build release binaries
env:
RELEASE_VERSION: ${{ steps.resolve-tag.outputs.version }}
RELEASE_VERSION: ${{ steps.publish.outputs.version }}
run: |
set -euo pipefail
@@ -178,7 +81,8 @@ jobs:
- name: Upload release binaries
env:
RELEASE_ID: ${{ steps.release.outputs.id }}
RELEASE_ID: ${{ steps.publish.outputs.release-id }}
RELEASE_VERSION: ${{ steps.publish.outputs.version }}
run: |
set -euo pipefail
@@ -213,8 +117,8 @@ jobs:
- name: Summarize published release
env:
TAG_NAME: ${{ steps.resolve-tag.outputs.tag }}
RELEASE_VERSION: ${{ steps.resolve-tag.outputs.version }}
TAG_NAME: ${{ steps.publish.outputs.tag }}
RELEASE_VERSION: ${{ steps.publish.outputs.version }}
run: |
set -euo pipefail

View File

@@ -21,8 +21,6 @@ jobs:
defaults:
run:
shell: bash
env:
RELEASE_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -37,91 +35,20 @@ jobs:
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)" \
--changelog changelog.md
- name: Run tests
run: |
set -euo pipefail
go test ./...
run: 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 release-version
git commit -m "release: prepare ${tag}"
git tag "$tag"
git push origin HEAD
git push origin "$tag"
- name: Prepare and tag release
id: prepare
uses: ./prepare
with:
version: ${{ inputs.version }}
token: ${{ secrets.GITHUB_TOKEN }}
- name: Summarize prepared release
run: |
set -euo pipefail
normalized_version="${RELEASE_VERSION#v}"
tag="v${normalized_version}"
tag="${{ steps.prepare.outputs.version }}"
{
echo "## Release Prepared"
echo
echo "- Updated files were committed to main."
echo "- Tag pushed: ${tag}"
echo "- The tag-triggered Do Release workflow will create or update the release and publish binaries."
} >> "$GITHUB_STEP_SUMMARY"