ci: split prepare and publish into separate release pipelines
All checks were successful
Push Validation / validate (push) Successful in 54s
All checks were successful
Push Validation / validate (push) Successful in 54s
- Remove publish steps (release creation, binary build/upload) from the Prepare Release workflow; it now stops after committing and pushing the tag. - Add Do Release workflow triggered on v*.*.* tag pushes; reads release notes from the tagged changelog section, creates or updates the release, builds linux/amd64 and linux/arm64 binaries, uploads assets, then smoke-tests both binaries in a follow-on validate job. - Remove the standalone Action Validation workflow; binary validation now runs as a second job in Do Release after the release job succeeds, using the exact tag and version just published. - Update README to document the two-workflow release model and add split prepare/publish usage examples for both the composite action and the reusable workflows. - Update changelog unreleased section to reflect the new pipeline split and corrected artifact scope (linux/amd64 and linux/arm64 only).
This commit is contained in:
@@ -1,102 +0,0 @@
|
|||||||
name: Action Validation
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- "**"
|
|
||||||
tags-ignore:
|
|
||||||
- "*"
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
validate-released-binary:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
container: docker.io/catthehacker/ubuntu:act-latest
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- runner_arch: X64
|
|
||||||
asset_arch: amd64
|
|
||||||
run_command: ./vociferate
|
|
||||||
- runner_arch: ARM64
|
|
||||||
asset_arch: arm64
|
|
||||||
run_command: qemu-aarch64-static ./vociferate
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
shell: bash
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Install arm64 emulator
|
|
||||||
if: ${{ matrix.runner_arch == 'ARM64' }}
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
apt-get update
|
|
||||||
apt-get install -y qemu-user-static
|
|
||||||
|
|
||||||
- name: Resolve latest released binary
|
|
||||||
id: resolve-binary
|
|
||||||
env:
|
|
||||||
API_URL: ${{ github.api_url }}
|
|
||||||
SERVER_URL: ${{ github.server_url }}
|
|
||||||
TOKEN: ${{ github.token }}
|
|
||||||
ASSET_ARCH: ${{ matrix.asset_arch }}
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
release_tag="$(curl -fsSL \
|
|
||||||
-H "Authorization: token ${TOKEN}" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"${API_URL}/repos/aether/vociferate/releases/latest" | sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -n 1)"
|
|
||||||
|
|
||||||
if [[ -z "$release_tag" ]]; then
|
|
||||||
echo "Unable to resolve latest vociferate release" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
normalized_version="${release_tag#v}"
|
|
||||||
asset_name="vociferate_${normalized_version}_linux_${ASSET_ARCH}"
|
|
||||||
asset_url="${SERVER_URL}/aether/vociferate/releases/download/${release_tag}/${asset_name}"
|
|
||||||
|
|
||||||
echo "release_tag=$release_tag" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "asset_name=$asset_name" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "asset_url=$asset_url" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
- name: Download released binary
|
|
||||||
env:
|
|
||||||
TOKEN: ${{ github.token }}
|
|
||||||
ASSET_URL: ${{ steps.resolve-binary.outputs.asset_url }}
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
curl --fail --location \
|
|
||||||
-H "Authorization: token ${TOKEN}" \
|
|
||||||
-o vociferate \
|
|
||||||
"$ASSET_URL"
|
|
||||||
chmod +x vociferate
|
|
||||||
|
|
||||||
- name: Smoke test released binary
|
|
||||||
env:
|
|
||||||
RUN_COMMAND: ${{ matrix.run_command }}
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
recommended_tag="$($RUN_COMMAND --recommend --root .)"
|
|
||||||
case "$recommended_tag" in
|
|
||||||
v*.*.*)
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Unexpected recommended tag: $recommended_tag" >&2
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
{
|
|
||||||
echo "## Released Binary Validation"
|
|
||||||
echo
|
|
||||||
echo "- Release tag: ${{ steps.resolve-binary.outputs.release_tag }}"
|
|
||||||
echo "- Asset: ${{ steps.resolve-binary.outputs.asset_name }}"
|
|
||||||
echo "- Recommended tag: ${recommended_tag}"
|
|
||||||
} >> "$GITHUB_STEP_SUMMARY"
|
|
||||||
301
.gitea/workflows/do-release.yml
Normal file
301
.gitea/workflows/do-release.yml
Normal file
@@ -0,0 +1,301 @@
|
|||||||
|
name: Do Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*.*.*'
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
tag:
|
||||||
|
description: Semantic version tag to publish, with or without leading v. Defaults to the current tag ref when dispatched from a tag.
|
||||||
|
required: false
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
tag:
|
||||||
|
description: Semantic version tag to publish, with or without leading v. When omitted, the current tag ref is used.
|
||||||
|
required: false
|
||||||
|
default: ''
|
||||||
|
type: string
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container: docker.io/catthehacker/ubuntu:act-latest
|
||||||
|
outputs:
|
||||||
|
tag: ${{ steps.resolve-tag.outputs.tag }}
|
||||||
|
version: ${{ steps.resolve-tag.outputs.version }}
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
RELEASE_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout tagged revision
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
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 }}
|
||||||
|
|
||||||
|
- 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: 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"
|
||||||
|
|
||||||
|
- name: Build release binaries
|
||||||
|
env:
|
||||||
|
RELEASE_VERSION: ${{ steps.resolve-tag.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.release.outputs.id }}
|
||||||
|
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: Summarize published release
|
||||||
|
env:
|
||||||
|
TAG_NAME: ${{ steps.resolve-tag.outputs.tag }}
|
||||||
|
RELEASE_VERSION: ${{ steps.resolve-tag.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"
|
||||||
|
} >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
|
||||||
|
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
|
||||||
|
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: ${{ github.token }}
|
||||||
|
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
|
||||||
|
|
||||||
|
recommended_tag="$(${RUN_COMMAND} --recommend --root .)"
|
||||||
|
case "$recommended_tag" in
|
||||||
|
v*.*.*)
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unexpected recommended tag: $recommended_tag" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "## Released Binary Validation (${{ matrix.asset_arch }})"
|
||||||
|
echo
|
||||||
|
echo "- Release tag: ${TAG_NAME}"
|
||||||
|
echo "- Asset: ${asset_name}"
|
||||||
|
echo "- Recommended tag: ${recommended_tag}"
|
||||||
|
} >> "$GITHUB_STEP_SUMMARY"
|
||||||
@@ -111,127 +111,17 @@ jobs:
|
|||||||
git push origin HEAD
|
git push origin HEAD
|
||||||
git push origin "$tag"
|
git push origin "$tag"
|
||||||
|
|
||||||
- name: Create release with changelog notes
|
- name: Summarize prepared release
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
normalized_version="${RELEASE_VERSION#v}"
|
normalized_version="${RELEASE_VERSION#v}"
|
||||||
tag="v${normalized_version}"
|
tag="v${normalized_version}"
|
||||||
|
|
||||||
release_notes="$(awk -v version="$normalized_version" '
|
{
|
||||||
$0 ~ "^## \\\\[" version "\\\\] - " {capture=1}
|
echo "## Release Prepared"
|
||||||
capture {
|
echo
|
||||||
if ($0 ~ "^## \\\\[" && $0 !~ "^## \\\\[" version "\\\\] - ") exit
|
echo "- Updated files were committed to main."
|
||||||
print
|
echo "- Tag pushed: ${tag}"
|
||||||
}
|
echo "- The tag-triggered Do Release workflow will create or update the release and publish binaries."
|
||||||
' changelog.md)"
|
} >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
|
||||||
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
|
|
||||||
|
|||||||
88
README.md
88
README.md
@@ -56,9 +56,18 @@ By default, `vociferate` reads and writes the release version as the sole conten
|
|||||||
just go-test
|
just go-test
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Release Flow
|
||||||
|
|
||||||
|
Releases use two workflows:
|
||||||
|
|
||||||
|
- `Prepare Release` runs on demand, updates `release-version` and `changelog.md`, commits those changes back to `main`, and pushes the release tag.
|
||||||
|
- `Do Release` runs from the pushed tag, reads the matching changelog section from that tagged revision, creates or updates the release, and uploads prebuilt binaries.
|
||||||
|
|
||||||
|
This split matters because release notes must be generated from the tagged commit that already contains the promoted changelog section.
|
||||||
|
|
||||||
## Release Artifacts
|
## Release Artifacts
|
||||||
|
|
||||||
Releases are prepared through the `Prepare Release` workflow. The workflow creates a release and uploads prebuilt `vociferate` binaries for:
|
The tag-driven `Do Release` workflow publishes prebuilt `vociferate` binaries for:
|
||||||
|
|
||||||
- `linux/amd64`
|
- `linux/amd64`
|
||||||
- `linux/arm64`
|
- `linux/arm64`
|
||||||
@@ -70,7 +79,7 @@ If a release already exists for the same tag, the workflow updates its release n
|
|||||||
|
|
||||||
You can reuse vociferate in two ways.
|
You can reuse vociferate in two ways.
|
||||||
|
|
||||||
Use the composite action directly:
|
Use the composite action directly in your prepare workflow:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- name: Prepare release files
|
- name: Prepare release files
|
||||||
@@ -85,10 +94,65 @@ Set `version` only when you want to override the recommended version.
|
|||||||
Pin the composite action to a released tag. It downloads a prebuilt Linux binary from vociferate releases and caches it on the runner, so it does not require installing Go.
|
Pin the composite action to a released tag. It downloads a prebuilt Linux binary from vociferate releases and caches it on the runner, so it does not require installing Go.
|
||||||
If your repository uses the default plain-text `release-version` file, you can omit `version-file` and `version-pattern` entirely.
|
If your repository uses the default plain-text `release-version` file, you can omit `version-file` and `version-pattern` entirely.
|
||||||
|
|
||||||
Call the reusable release workflow:
|
A complete release setup should also split preparation from publication. For example:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
name: Release
|
name: Prepare Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: Optional semantic version override.
|
||||||
|
required: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
prepare:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Prepare release files
|
||||||
|
id: prepare
|
||||||
|
uses: git.hrafn.xyz/aether/vociferate@v1.0.0
|
||||||
|
with:
|
||||||
|
version: ${{ inputs.version }}
|
||||||
|
|
||||||
|
- name: Commit and push prepared release
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
tag="${{ steps.prepare.outputs.version }}"
|
||||||
|
git config user.name "gitea-actions[bot]"
|
||||||
|
git config user.email "gitea-actions[bot]@users.noreply.local"
|
||||||
|
git add changelog.md release-version
|
||||||
|
git commit -m "release: prepare ${tag}"
|
||||||
|
git tag "$tag"
|
||||||
|
git push origin HEAD
|
||||||
|
git push origin "$tag"
|
||||||
|
```
|
||||||
|
|
||||||
|
Then use a separate tag workflow to publish the release from the tagged revision:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: Do Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v*.*.*"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
uses: aether/vociferate/.gitea/workflows/do-release.yml@main
|
||||||
|
secrets: inherit
|
||||||
|
```
|
||||||
|
|
||||||
|
Call the reusable prepare workflow:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: Prepare Release
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
@@ -104,3 +168,19 @@ jobs:
|
|||||||
version: ${{ inputs.version }}
|
version: ${{ inputs.version }}
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
```
|
```
|
||||||
|
|
||||||
|
And publish from tags with:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: Do Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v*.*.*"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
uses: aether/vociferate/.gitea/workflows/do-release.yml@main
|
||||||
|
secrets: inherit
|
||||||
|
```
|
||||||
|
|||||||
@@ -11,20 +11,21 @@ A `### Breaking` section is used in addition to Keep a Changelog's standard sect
|
|||||||
|
|
||||||
### Breaking
|
### Breaking
|
||||||
|
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
- Release automation is now split into a prepare workflow that updates and tags `main`, and a tag-driven publish workflow that creates the release from the tagged revision.
|
||||||
- The CLI entrypoint, internal package paths, build outputs, and automation references now use the `vociferate` name instead of the earlier `releaseprep` naming.
|
- The CLI entrypoint, internal package paths, build outputs, and automation references now use the `vociferate` name instead of the earlier `releaseprep` naming.
|
||||||
- Configurable version source and parser via `--version-file` and `--version-pattern`.
|
- Configurable version source and parser via `--version-file` and `--version-pattern`.
|
||||||
- Configurable changelog path via `--changelog`.
|
- Configurable changelog path via `--changelog`.
|
||||||
- The release workflow and composite action now treat a provided `version` as an override and otherwise fall back to the recommended next version automatically.
|
- The release workflow and composite action now treat a provided `version` as an override and otherwise fall back to the recommended next version automatically.
|
||||||
- Release preparation now runs directly in the release workflow; the repository-local helper script and just recipe were removed.
|
- Release preparation now runs directly in the prepare workflow; the repository-local helper script and just recipe were removed.
|
||||||
- Release creation is now idempotent: existing releases for the same tag are updated in place instead of recreated.
|
- Release creation is now idempotent: existing releases for the same tag are updated in place instead of recreated.
|
||||||
- Release asset uploads now replace existing assets with matching filenames so reruns stay synchronized.
|
- Release asset uploads now replace existing assets with matching filenames so reruns stay synchronized.
|
||||||
- Automated release artifact publishing in the release workflow for `darwin`, `linux`, and `windows` binaries plus `checksums.txt`.
|
- Automated release artifact publishing in the tag-driven release workflow for `linux/amd64`, `linux/arm64`, and `checksums.txt`.
|
||||||
- Release recommendation now forces a major version bump whenever a `### Breaking` heading is present in `## [Unreleased]`, even if the section has no bullet entries yet.
|
- Release recommendation now forces a major version bump whenever a `### Breaking` heading is present in `## [Unreleased]`, even if the section has no bullet entries yet.
|
||||||
- The composite action now downloads and caches released `vociferate` binaries on both `amd64` and `arm64` platforms instead of installing Go and running the module source directly.
|
- The composite action now downloads and caches released `vociferate` binaries on both `amd64` and `arm64` platforms instead of installing Go and running the module source directly.
|
||||||
- Reusable `workflow_call` support for the `Prepare Release` workflow, enabling other repositories to invoke it directly.
|
- Reusable `workflow_call` support for the `Prepare Release` workflow, enabling other repositories to invoke it directly.
|
||||||
|
- Reusable `workflow_call` support for the tag-driven `Do Release` workflow, enabling other repositories to publish from pushed tags without reimplementing release note or asset logic.
|
||||||
- Composite action (`action.yml`) for release preparation and recommendation flows.
|
- Composite action (`action.yml`) for release preparation and recommendation flows.
|
||||||
- Gitea workflows for push validation and manual release preparation.
|
- Gitea workflows for push validation, manual release preparation, and tag-driven release publishing.
|
||||||
- README guidance for release artifacts and examples for reusing vociferate as a composite action or reusable workflow.
|
- README guidance for release artifacts and examples for reusing vociferate as a composite action or reusable workflow.
|
||||||
|
|||||||
Reference in New Issue
Block a user