17 Commits

Author SHA1 Message Date
gitea-actions[bot]
f82dace4b2 release: prepare v1.1.0 2026-03-21 20:16:35 +00:00
Micheal Wilkinson
81dced6ada fix(publish): avoid heredoc parsing in composite json helper
All checks were successful
Push Validation / coverage-badge (push) Successful in 1m53s
Push Validation / recommend-release (push) Successful in 28s
Replace the embedded python heredoc with python3 -c so the composite
action remains valid YAML and shell across teacup parsing.
2026-03-21 20:14:49 +00:00
Micheal Wilkinson
62693935d0 fix(release): parse release id robustly and validate upload endpoint
All checks were successful
Push Validation / coverage-badge (push) Successful in 1m36s
Push Validation / recommend-release (push) Successful in 47s
Use JSON parsing for release id extraction in publish action instead of
regex matching, preventing wrong id selection from nested fields.
Add a pre-upload release endpoint check to fail early with explicit
release URL diagnostics when the resolved id/path is invalid.
2026-03-21 20:07:45 +00:00
Micheal Wilkinson
c0b5ec385c fix(release): normalize wrapped release-id before asset upload
Some checks failed
Push Validation / recommend-release (push) Has been cancelled
Push Validation / coverage-badge (push) Has been cancelled
Teacup can wrap workflow outputs as %touch docker-compose.yml(string=...), which produced an
invalid releases/{id}/assets URL and a 404 in Upload release binaries.
Unwrap and validate release-id before building the API path.
2026-03-21 20:03:00 +00:00
Micheal Wilkinson
84f6fbcfc8 fix(release): unwrap teacup token inputs and correct failure summary
Some checks failed
Push Validation / recommend-release (push) Has been cancelled
Push Validation / coverage-badge (push) Has been cancelled
Normalize %touch docker-compose.yml(string=...) wrapped token values in publish composite before
API calls. This prevents malformed Authorization headers under teacup.
Also only print 'Release Published' summary when the publish step succeeds,
and print a failure summary otherwise.
2026-03-21 19:51:26 +00:00
Micheal Wilkinson
4a2d234ba3 fix(publish): stop sending target field in release payload
Some checks failed
Push Validation / recommend-release (push) Has been cancelled
Push Validation / coverage-badge (push) Has been cancelled
Prepare already creates and pushes the release tag, so publish should not
retarget it. Sending target can trigger 403 on Gitea when tag retargeting
is restricted. Build PATCH/POST payloads from tag_name + notes only.
2026-03-21 19:46:53 +00:00
Micheal Wilkinson
4841b04076 fix(publish): use tag's actual commit SHA for release target
Some checks failed
Push Validation / recommend-release (push) Has been cancelled
Push Validation / coverage-badge (push) Has been cancelled
GITHUB_SHA (github.sha) reflects the workflow trigger commit, which is
the main HEAD before the prepare job ran. The tag itself points to the
release commit created by prepare — a different SHA. Gitea rejects PATCH
and POST with 403 when target_commitish doesn't match the tag's commit.

Use git rev-list -n 1 TAG to resolve the exact SHA the tag points to,
ensuring the target field is always correct regardless of when or how
the release workflow is called.
2026-03-21 19:39:17 +00:00
Micheal Wilkinson
993768ae9b refactor(release): inline release and validate jobs into prepare-release
All checks were successful
Push Validation / coverage-badge (push) Successful in 1m24s
Push Validation / recommend-release (push) Successful in 42s
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.
2026-03-21 19:30:53 +00:00
Micheal Wilkinson
624b9d154c fix(release): re-enable workflow_call publish path in prepare-release
All checks were successful
Push Validation / coverage-badge (push) Successful in 1m18s
Push Validation / recommend-release (push) Successful in 23s
Tag push events never fire on this Gitea 1.25.x instance (confirmed
across 159 workflow run history). The workflow_call path is reliable and
has worked consistently. Remove the temporary if-false guard.
2026-03-21 19:23:31 +00:00
Micheal Wilkinson
53a097784e chore(workflows): use go-version-file for setup-go
All checks were successful
Push Validation / coverage-badge (push) Successful in 1m14s
Push Validation / recommend-release (push) Successful in 23s
2026-03-21 16:26:16 +00:00
Micheal Wilkinson
511110f466 chore(release): temporarily disable inline do-release call
All checks were successful
Push Validation / coverage-badge (push) Successful in 1m10s
Push Validation / recommend-release (push) Successful in 26s
Disable the publish job in prepare-release to validate that tag push triggers do-release automatically via workflow trigger path.
2026-03-21 16:19:22 +00:00
Micheal Wilkinson
d5170b6874 fix(release): require RELEASE_PAT for tag and release updates
All checks were successful
Push Validation / coverage-badge (push) Successful in 1m11s
Push Validation / recommend-release (push) Successful in 24s
Stop using GITHUB_TOKEN/GITEA_TOKEN fallbacks in prepare/do-release/publish mutation paths. Require explicit PAT wiring via secrets.RELEASE_PAT for commit/push/tag and release update operations so downstream workflows trigger reliably.
2026-03-21 16:17:17 +00:00
Micheal Wilkinson
925c99bb9e docs: note govulncheck api limit workflow fix
All checks were successful
Push Validation / coverage-badge (push) Successful in 1m20s
Push Validation / recommend-release (push) Successful in 22s
2026-03-21 16:08:51 +00:00
Micheal Wilkinson
d65af508a3 chore(go): avoid setup-go stable resolution in ci 2026-03-21 16:08:49 +00:00
Micheal Wilkinson
eae70bb20f fix(workflows): prefer caller tag in do-release resolution
Some checks failed
Push Validation / coverage-badge (push) Failing after 38s
Push Validation / recommend-release (push) Has been skipped
Handle Teacup workflow_call input forwarding gaps by using needs.prepare.outputs.tag as a fallback and normalizing %touch docker-compose.yml(string=...) wrappers before selecting the release tag.
2026-03-21 16:04:26 +00:00
Micheal Wilkinson
c96cab58ff docs: document HEAD-prioritized tag detection fix
Some checks failed
Push Validation / recommend-release (push) Has been cancelled
Push Validation / coverage-badge (push) Has been cancelled
2026-03-21 15:58:57 +00:00
Micheal Wilkinson
a6d57e4048 fix(workflows): prioritize HEAD tag detection over global latest tag
When prepare-release tags HEAD with a new release version, do-release should
immediately detect that tag rather than finding the latest tag chronologically.

Changes:
- Modified detect-tag step to check if HEAD is exactly at a tag first
- Falls back to latest tag only if HEAD is not tagged
- Fixes issue where v1.0.2 was detected instead of v1.1.0 at HEAD

This ensures correct version detection in prepare-release → do-release workflow chain.
2026-03-21 15:58:25 +00:00
9 changed files with 460 additions and 83 deletions

View File

@@ -28,7 +28,7 @@ jobs:
run:
shell: bash
env:
RELEASE_TOKEN: ${{ secrets.GITHUB_TOKEN || secrets.GITEA_TOKEN }}
RELEASE_TOKEN: ${{ secrets.RELEASE_PAT }}
SUMMARY_FILE: ${{ runner.temp }}/do-release-summary.md
steps:
- name: Checkout
@@ -41,13 +41,17 @@ jobs:
run: |
set -euo pipefail
# Fetch all tags from origin to ensure we have the latest
# Fetch all tags from origin first
git fetch origin --tags --force --quiet 2>/dev/null || true
# Try to find the most recent tag (in case it was just created by prepare-release)
latest_tag="$(git describe --tags --abbrev=0 2>/dev/null || echo '')"
# Check if HEAD is at a tag (prepare-release may have just tagged it)
if head_tag="$(git describe --exact-match --tags HEAD 2>/dev/null)" && [[ -n "$head_tag" ]]; then
echo "detected_tag=$head_tag" >> "$GITHUB_OUTPUT"
exit 0
fi
if [[ -n "$latest_tag" ]]; then
# Fall back to finding the most recent tag
if latest_tag="$(git describe --tags --abbrev=0 2>/dev/null)" && [[ -n "$latest_tag" ]]; then
echo "detected_tag=$latest_tag" >> "$GITHUB_OUTPUT"
fi
@@ -55,16 +59,40 @@ jobs:
id: resolve-version
env:
INPUT_TAG: ${{ inputs.tag }}
CALLER_TAG: ${{ needs.prepare.outputs.tag }}
DETECTED_TAG: ${{ steps.detect-tag.outputs.detected_tag }}
run: |
set -euo pipefail
# Try to use explicit input first
requested_tag="$(printf '%s' "${INPUT_TAG}" | sed 's/^[[:space:]]\+//; s/[[:space:]]\+$//')"
normalize_candidate() {
local raw="$1"
raw="$(printf '%s' "$raw" | sed 's/^[[:space:]]\+//; s/[[:space:]]\+$//')"
# Teacup can surface expression strings as %!t(string=value); unwrap it.
if [[ "$raw" =~ ^%\!t\(string=(.*)\)$ ]]; then
raw="${BASH_REMATCH[1]}"
fi
raw="$(printf '%s' "$raw" | sed 's/^[[:space:]]\+//; s/[[:space:]]\+$//')"
printf '%s' "$raw"
}
input_tag="$(normalize_candidate "${INPUT_TAG}")"
caller_tag="$(normalize_candidate "${CALLER_TAG}")"
detected_tag="$(normalize_candidate "${DETECTED_TAG}")"
# Try explicit input first.
requested_tag="$input_tag"
# Fall back to caller workflow output when workflow_call input forwarding is unreliable.
if [[ -z "$requested_tag" && -n "$caller_tag" ]]; then
requested_tag="$caller_tag"
fi
# Fall back to detected tag if input is empty
if [[ -z "$requested_tag" && -n "${DETECTED_TAG}" ]]; then
requested_tag="$DETECTED_TAG"
# Fall back to detected tag if neither input nor caller tag is available.
if [[ -z "$requested_tag" && -n "$detected_tag" ]]; then
requested_tag="$detected_tag"
fi
# Try GITHUB_REF if still empty
@@ -78,8 +106,9 @@ jobs:
tag="v${normalized}"
else
echo "Error: Could not resolve release version" >&2
echo " - inputs.tag: '$INPUT_TAG'" >&2
echo " - detected_tag: '${DETECTED_TAG}'" >&2
echo " - inputs.tag(raw): '$INPUT_TAG'" >&2
echo " - needs.prepare.outputs.tag(raw): '$CALLER_TAG'" >&2
echo " - detected_tag(raw): '${DETECTED_TAG}'" >&2
echo " - GITHUB_REF: '$GITHUB_REF'" >&2
exit 1
fi
@@ -96,8 +125,8 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.26.1'
check-latest: true
go-version-file: go.mod
check-latest: false
cache: true
cache-dependency-path: go.sum
@@ -108,7 +137,7 @@ jobs:
set -euo pipefail
if [[ -z "${RELEASE_TOKEN:-}" ]]; then
echo "No release token available. Set GITEA_TOKEN (or GITHUB_TOKEN on GitHub)." >&2
echo "No release token available. Set secrets.RELEASE_PAT." >&2
exit 1
fi
@@ -134,7 +163,7 @@ jobs:
id: publish
uses: ./publish
with:
token: ${{ secrets.GITHUB_TOKEN || secrets.GITEA_TOKEN }}
token: ${{ secrets.RELEASE_PAT }}
version: ${{ steps.resolve-version.outputs.version }}
- name: Build release binaries
@@ -255,7 +284,7 @@ jobs:
- name: Download released binary
env:
TOKEN: ${{ secrets.GITHUB_TOKEN || secrets.GITEA_TOKEN }}
TOKEN: ${{ secrets.RELEASE_PAT }}
TAG_NAME: ${{ needs.release.outputs.tag }}
RELEASE_VERSION: ${{ needs.release.outputs.version }}
ASSET_ARCH: ${{ matrix.asset_arch }}

View File

@@ -34,8 +34,8 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.26.1'
check-latest: true
go-version-file: go.mod
check-latest: false
cache: true
cache-dependency-path: go.sum
@@ -77,6 +77,8 @@ jobs:
- name: Run govulncheck
uses: golang/govulncheck-action@v1.0.4
with:
go-version-file: go.mod
check-latest: false
go-package: ./...
cache: true
cache-dependency-path: go.sum
@@ -122,17 +124,313 @@ jobs:
VOCIFERATE_CACHE_TOKEN: ${{ steps.cache-token.outputs.value }}
with:
version: ${{ steps.resolve-version.outputs.tag }}
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 "- Calling Do Release workflow 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
raw_release_id="$(printf '%s' "${RELEASE_ID:-}" | sed 's/^[[:space:]]\+//; s/[[:space:]]\+$//')"
if [[ "$raw_release_id" =~ ^%\!t\(string=(.*)\)$ ]]; then
raw_release_id="${BASH_REMATCH[1]}"
fi
release_id="$(printf '%s' "$raw_release_id" | sed 's/^[[:space:]]\+//; s/[[:space:]]\+$//')"
if ! [[ "$release_id" =~ ^[0-9]+$ ]]; then
echo "Invalid release id from publish step: '${RELEASE_ID}'" >&2
exit 1
fi
release_detail_api="${GITHUB_API_URL:-${GITHUB_SERVER_URL%/}/api/v1}/repos/${GITHUB_REPOSITORY}/releases/${release_id}"
if ! curl --fail-with-body -sS \
-H "Authorization: token ${RELEASE_TOKEN}" \
-H "Content-Type: application/json" \
"$release_detail_api" >/dev/null; then
echo "Resolved release endpoint is not accessible: ${release_detail_api}" >&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
- name: Summary
if: ${{ always() }}
env:
TAG_NAME: ${{ steps.publish.outputs.tag }}
RELEASE_VERSION: ${{ steps.publish.outputs.version }}
PUBLISH_OUTCOME: ${{ steps.publish.outcome }}
run: |
set -euo pipefail
if [[ "${PUBLISH_OUTCOME}" == "success" ]]; then
{
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"
else
{
echo "## Release Failed"
echo
echo "- Tag: ${TAG_NAME:-unknown}"
echo "- Create or update release step did not complete successfully."
} >> "$SUMMARY_FILE"
fi
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
@@ -148,10 +446,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

View File

@@ -30,8 +30,8 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.26.1'
check-latest: true
go-version-file: go.mod
check-latest: false
cache: true
cache-dependency-path: go.sum
@@ -73,6 +73,8 @@ jobs:
- name: Run govulncheck
uses: golang/govulncheck-action@v1.0.4
with:
go-version-file: go.mod
check-latest: false
go-package: ./...
cache: true
cache-dependency-path: go.sum
@@ -122,8 +124,8 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.26.1'
check-latest: true
go-version-file: go.mod
check-latest: false
cache: true
cache-dependency-path: go.sum

View File

@@ -4,15 +4,15 @@ This guide is for agentic coding partners that need to integrate the composite a
## Source Of Truth
Pin all action references to a released tag (for example `@v1.0.2`) and keep all vociferate references on the same tag in a workflow.
Pin all action references to a released tag (for example `@v1.1.0`) and keep all vociferate references on the same tag in a workflow.
Published composite actions:
- `https://git.hrafn.xyz/aether/vociferate@v1.0.2` (root action)
- `https://git.hrafn.xyz/aether/vociferate/prepare@v1.0.2`
- `https://git.hrafn.xyz/aether/vociferate/publish@v1.0.2`
- `https://git.hrafn.xyz/aether/vociferate/coverage-badge@v1.0.2`
- `https://git.hrafn.xyz/aether/vociferate/decorate-pr@v1.0.2`
- `https://git.hrafn.xyz/aether/vociferate@v1.1.0` (root action)
- `https://git.hrafn.xyz/aether/vociferate/prepare@v1.1.0`
- `https://git.hrafn.xyz/aether/vociferate/publish@v1.1.0`
- `https://git.hrafn.xyz/aether/vociferate/coverage-badge@v1.1.0`
- `https://git.hrafn.xyz/aether/vociferate/decorate-pr@v1.1.0`
## Action Selection Matrix
@@ -30,7 +30,7 @@ Apply these checks before invoking actions:
- Checkout repository first.
- For prepare/publish flows that depend on tags/history, use full history checkout (`fetch-depth: 0`).
- Use valid credentials for release/comment API calls. On GitHub, `secrets.GITHUB_TOKEN` is used; on self-hosted Gitea, set `secrets.GITEA_TOKEN`.
- Use `secrets.RELEASE_PAT` for release/tag/update operations (prepare/publish/do-release) so tag pushes trigger downstream workflows reliably.
- `do-release` and `decorate-pr` now run preflight API checks and fail fast when token credentials are missing or insufficient.
- Set required vars/secrets for coverage uploads:
- `vars.ARTEFACT_BUCKET_NAME`
@@ -94,11 +94,11 @@ jobs:
with:
fetch-depth: 0
- id: prepare
uses: https://git.hrafn.xyz/aether/vociferate/prepare@v1.0.2
uses: https://git.hrafn.xyz/aether/vociferate/prepare@v1.1.0
publish:
needs: prepare
uses: https://git.hrafn.xyz/aether/vociferate/.gitea/workflows/do-release.yml@v1.0.2
uses: https://git.hrafn.xyz/aether/vociferate/.gitea/workflows/do-release.yml@v1.1.0
with:
tag: ${{ needs.prepare.outputs.version }}
secrets: inherit
@@ -115,7 +115,7 @@ jobs:
with:
fetch-depth: 0
- id: publish
uses: https://git.hrafn.xyz/aether/vociferate/publish@v1.0.2
uses: https://git.hrafn.xyz/aether/vociferate/publish@v1.1.0
with:
version: v1.2.3
```
@@ -136,7 +136,7 @@ jobs:
- name: Run tests with coverage
run: go test -covermode=atomic -coverprofile=coverage.out ./...
- id: badge
uses: https://git.hrafn.xyz/aether/vociferate/coverage-badge@v1.0.2
uses: https://git.hrafn.xyz/aether/vociferate/coverage-badge@v1.1.0
with:
artefact-bucket-name: ${{ vars.ARTEFACT_BUCKET_NAME }}
artefact-bucket-endpoint: ${{ vars.ARTEFACT_BUCKET_ENDPONT }}
@@ -159,12 +159,12 @@ jobs:
- name: Run tests with coverage
run: go test -covermode=atomic -coverprofile=coverage.out ./...
- id: badge
uses: https://git.hrafn.xyz/aether/vociferate/coverage-badge@v1.0.2
uses: https://git.hrafn.xyz/aether/vociferate/coverage-badge@v1.1.0
with:
artefact-bucket-name: ${{ vars.ARTEFACT_BUCKET_NAME }}
artefact-bucket-endpoint: ${{ vars.ARTEFACT_BUCKET_ENDPONT }}
- name: Decorate PR
uses: https://git.hrafn.xyz/aether/vociferate/decorate-pr@v1.0.2
uses: https://git.hrafn.xyz/aether/vociferate/decorate-pr@v1.1.0
with:
coverage-percentage: ${{ steps.badge.outputs.total }}
badge-url: ${{ steps.badge.outputs.badge-url }}

View File

@@ -13,6 +13,18 @@ A `### Breaking` section is used in addition to Keep a Changelog's standard sect
### Added
### Changed
### Removed
### Fixed
## [1.1.0] - 2026-03-21
### Breaking
### Added
- Added changelog gate validation to `decorate-pr` action for enforcing changelog updates on qualifying code changes.
- Changelog gate modes: `strict` (fails job on violation) and `soft` (warns via PR comment).
- Docs-only PR exemption with customizable glob patterns for documentation files.
@@ -30,13 +42,18 @@ A `### Breaking` section is used in addition to Keep a Changelog's standard sect
- `publish` now extracts tagged release notes through the `vociferate` Go CLI instead of duplicating changelog section parsing in shell.
- Composite actions now share a centralized `run-vociferate` orchestration flow, with binary-versus-source execution delegated through shared composite actions and single-use runtime/download logic folded back into `run-vociferate.binary`.
- `run-vociferate` now contains both binary and source execution flows directly in a single action implementation, removing nested local action wrappers for better runner compatibility.
- Release automation now requires `secrets.RELEASE_PAT` for prepare/publish/do-release operations instead of defaulting to `GITHUB_TOKEN`/`GITEA_TOKEN`.
### Removed
### Fixed
- Prevented `govulncheck-action` from defaulting to `setup-go` version `stable` by explicitly setting `go-version-file` and disabling `check-latest`, avoiding unauthenticated GitHub API rate-limit failures on self-hosted/act-style runners.
- Made `do-release` version resolution resilient to `workflow_call` input passing issues by adding a separate tag detection step that fetches and discovers the latest tag from origin as a fallback when `inputs.tag` is empty, enabling proper operation even when Gitea's workflow_call doesn't pass inputs through correctly.
- Fixed version resolution in `do-release` workflow by moving version calculation before checkout, resolving from inputs/git tags, and always passing explicit version to `publish` action.
- Fixed tag detection in `do-release` to prioritize the tag at current HEAD (created by `prepare-release`) over the globally latest tag, ensuring correct version is detected when called from `prepare-release` workflow.
- Fixed `do-release` workflow_call resolution on Teacup runners by explicitly falling back to `needs.prepare.outputs.tag` and normalizing `%!t(string=...)` wrapped values before choosing a release tag.
- Fixed release-chain triggering by using a PAT for release commit/tag pushes so downstream release workflows are triggered reliably.
- Made `publish` action version resolution more robust with clearer error messages when version input is missing and workflow is not running from a tag push.
- Fixed `do-release` workflow to always checkout the resolved release tag, eliminating conditional checkout logic that could skip the checkout when called from `prepare-release` workflow.
- Pinned `securego/gosec` and `golang/govulncheck-action` to concrete version tags (`v2.22.4` and `v1.0.4`) so self-hosted Gitea runners can resolve them via direct git clone without relying on the GitHub Actions floating-tag API.
@@ -151,9 +168,10 @@ A `### Breaking` section is used in addition to Keep a Changelog's standard sect
- Project/automation rename from `releaseprep` to `vociferate` (entrypoint, package paths, outputs).
- README guidance focused on primary cross-repository reuse workflows.
[Unreleased]: https://git.hrafn.xyz/aether/vociferate/compare/v1.0.2...main
[Unreleased]: https://git.hrafn.xyz/aether/vociferate/compare/v1.1.0...main
[1.1.0]: https://git.hrafn.xyz/aether/vociferate/compare/v1.0.2...v1.1.0
[1.0.2]: https://git.hrafn.xyz/aether/vociferate/compare/v1.0.1...v1.0.2
[1.0.1]: https://git.hrafn.xyz/aether/vociferate/compare/v1.0.0...v1.0.1
[1.0.0]: https://git.hrafn.xyz/aether/vociferate/compare/v0.2.0...v1.0.0
[0.2.0]: https://git.hrafn.xyz/aether/vociferate/compare/v0.1.0...v0.2.0
[0.1.0]: https://git.hrafn.xyz/aether/vociferate/compare/2060af6...v0.1.0
[0.1.0]: https://git.hrafn.xyz/aether/vociferate/compare/81dced6...v0.1.0

View File

@@ -17,7 +17,7 @@ revision.
## Use In Other Repositories
Vociferate ships composite actions covering release preparation, release publication, coverage badge publishing, and pull request decoration.
Release tags now exist; pin all action and reusable-workflow references to the same released tag (for example, `@v1.0.2`) instead of `@main`.
Release tags now exist; pin all action and reusable-workflow references to the same released tag (for example, `@v1.1.0`) instead of `@main`.
For agentic coding partners, see [`AGENTS.md`](AGENTS.md) for a direct integration playbook, selection matrix, and copy-paste workflow patterns.
@@ -41,13 +41,13 @@ jobs:
with:
fetch-depth: 0
- uses: https://git.hrafn.xyz/aether/vociferate/prepare@v1.0.2
- uses: https://git.hrafn.xyz/aether/vociferate/prepare@v1.1.0
with:
version: ${{ inputs.version }}
publish:
needs: prepare
uses: https://git.hrafn.xyz/aether/vociferate/.gitea/workflows/do-release.yml@v1.0.2
uses: https://git.hrafn.xyz/aether/vociferate/.gitea/workflows/do-release.yml@v1.1.0
with:
tag: ${{ needs.prepare.outputs.version }}
secrets: inherit
@@ -61,15 +61,16 @@ For repositories that embed the version inside source code, pass `version-file`
and `version-pattern`:
```yaml
- uses: https://git.hrafn.xyz/aether/vociferate/prepare@v1.0.2
- uses: https://git.hrafn.xyz/aether/vociferate/prepare@v1.1.0
with:
token: ${{ secrets.RELEASE_PAT }}
version-file: internal/myapp/version/version.go
version-pattern: 'const Version = "([^"]+)"'
git-add-files: CHANGELOG.md internal/myapp/version/version.go
```
`prepare` uses `github.token` internally for authenticated fetch/push operations,
so no token input is required.
`prepare` requires a PAT input for authenticated commit/push/tag operations.
Pass `token: ${{ secrets.RELEASE_PAT }}` when invoking the action.
### `publish` — create release with changelog notes
@@ -85,7 +86,7 @@ on:
jobs:
release:
uses: https://git.hrafn.xyz/aether/vociferate/.gitea/workflows/do-release.yml@v1.0.2
uses: https://git.hrafn.xyz/aether/vociferate/.gitea/workflows/do-release.yml@v1.1.0
with:
tag: ${{ inputs.tag }}
secrets: inherit
@@ -96,21 +97,20 @@ Gitea/GitHub release with those notes. The `version` input is optional — when
omitted it is derived from the current tag ref automatically.
The reusable `Do Release` workflow now runs preflight checks before publish to
fail fast when the release token is missing or lacks API access. On
self-hosted Gitea, set `secrets.GITEA_TOKEN`; on GitHub, `secrets.GITHUB_TOKEN`
is used automatically.
fail fast when the release token is missing or lacks API access. Set
`secrets.RELEASE_PAT` and use it for prepare/publish release operations.
The `publish` action outputs `release-id` so you can upload additional release
assets after it runs:
```yaml
- id: publish
uses: https://git.hrafn.xyz/aether/vociferate/publish@v1.0.2
uses: https://git.hrafn.xyz/aether/vociferate/publish@v1.1.0
- name: Upload my binary
run: |
curl --fail-with-body -X POST \
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
-H "Authorization: token ${{ secrets.RELEASE_PAT }}" \
-H "Content-Type: application/octet-stream" \
"${{ github.api_url }}/repos/${{ github.repository }}/releases/${{ steps.publish.outputs.release-id }}/assets?name=myapp" \
--data-binary "@dist/myapp"
@@ -125,7 +125,7 @@ Run your coverage tests first, then call the action to generate `coverage.html`,
run: go test -covermode=atomic -coverprofile=coverage.out ./...
- id: coverage
uses: https://git.hrafn.xyz/aether/vociferate/coverage-badge@v1.0.2
uses: https://git.hrafn.xyz/aether/vociferate/coverage-badge@v1.1.0
with:
artefact-bucket-name: ${{ vars.ARTEFACT_BUCKET_NAME }}
artefact-bucket-endpoint: ${{ vars.ARTEFACT_BUCKET_ENDPONT }}
@@ -150,14 +150,14 @@ with a clear message when token permissions are insufficient.
run: go test -covermode=atomic -coverprofile=coverage.out ./...
- id: coverage
uses: https://git.hrafn.xyz/aether/vociferate/coverage-badge@v1.0.2
uses: https://git.hrafn.xyz/aether/vociferate/coverage-badge@v1.1.0
with:
artefact-bucket-name: ${{ vars.ARTEFACT_BUCKET_NAME }}
artefact-bucket-endpoint: ${{ vars.ARTEFACT_BUCKET_ENDPONT }}
- name: Decorate pull request
if: github.event_name == 'pull_request'
uses: https://git.hrafn.xyz/aether/vociferate/decorate-pr@v1.0.2
uses: https://git.hrafn.xyz/aether/vociferate/decorate-pr@v1.1.0
with:
coverage-percentage: ${{ steps.coverage.outputs.total }}
badge-url: ${{ steps.coverage.outputs.badge-url }}
@@ -170,7 +170,7 @@ Enable changelog validation to enforce that code changes include `Unreleased` ch
```yaml
- name: Decorate pull request with changelog gate
if: github.event_name == 'pull_request'
uses: https://git.hrafn.xyz/aether/vociferate/decorate-pr@v1.0.2
uses: https://git.hrafn.xyz/aether/vociferate/decorate-pr@v1.1.0
with:
coverage-percentage: ${{ steps.coverage.outputs.total }}
badge-url: ${{ steps.coverage.outputs.badge-url }}
@@ -196,7 +196,7 @@ Decision outputs enable downstream workflow logic:
- name: Decorate PR and check gate
id: decorate
if: github.event_name == 'pull_request'
uses: https://git.hrafn.xyz/aether/vociferate/decorate-pr@v1.0.2
uses: https://git.hrafn.xyz/aether/vociferate/decorate-pr@v1.1.0
with:
coverage-percentage: ${{ steps.coverage.outputs.total }}
badge-url: ${{ steps.coverage.outputs.badge-url }}

View File

@@ -42,6 +42,11 @@ inputs:
custom version-file.
required: false
default: 'CHANGELOG.md release-version'
token:
description: >
Personal access token used to authenticate commit, push, and tag
operations. Required to ensure downstream workflows trigger on tag push.
required: true
outputs:
version:
@@ -114,7 +119,7 @@ runs:
- name: Commit and push release
shell: bash
env:
TOKEN: ${{ github.token }}
TOKEN: ${{ inputs.token }}
GIT_USER_NAME: ${{ inputs.git-user-name }}
GIT_USER_EMAIL: ${{ inputs.git-user-email }}
GIT_ADD_FILES: ${{ inputs.git-add-files }}
@@ -124,6 +129,11 @@ runs:
run: |
set -euo pipefail
if [[ -z "${TOKEN:-}" ]]; then
echo "A release PAT is required. Provide inputs.token (for example secrets.RELEASE_PAT)." >&2
exit 1
fi
case "$GITHUB_SERVER_URL" in
https://*)
authed_remote="https://oauth2:${TOKEN}@${GITHUB_SERVER_URL#https://}/${GITHUB_REPOSITORY}.git"

View File

@@ -7,10 +7,9 @@ description: >
inputs:
token:
description: >
Token used to authenticate release API calls. Defaults to the
workflow token.
required: false
default: ''
Personal access token used to authenticate release API calls.
Required to support release updates across workflow boundaries.
required: true
version:
description: >
Semantic version to publish (with or without leading v). When omitted,
@@ -91,41 +90,66 @@ runs:
id: create-release
shell: bash
env:
TOKEN: ${{ inputs.token != '' && inputs.token || github.token }}
TOKEN: ${{ inputs.token }}
TAG_NAME: ${{ steps.resolve-version.outputs.tag }}
RELEASE_NOTES_FILE: ${{ steps.write-notes.outputs.notes_file }}
GITHUB_API_URL: ${{ github.api_url }}
GITHUB_SERVER_URL: ${{ github.server_url }}
GITHUB_SHA: ${{ github.sha }}
GITHUB_REPOSITORY: ${{ github.repository }}
run: |
set -euo pipefail
parse_release_id() {
local json_file="$1"
if command -v python3 >/dev/null 2>&1; then
python3 -c 'import json, sys; payload = json.load(open(sys.argv[1], encoding="utf-8")); value = payload.get("id"); print(value if isinstance(value, int) else "")' "$json_file"
return
fi
# Fallback for environments without python3.
sed -n 's/.*"id"[[:space:]]*:[[:space:]]*\([0-9][0-9]*\).*/\1/p' "$json_file" | head -n 1
}
raw_token="$(printf '%s' "${TOKEN:-}" | sed 's/^[[:space:]]\+//; s/[[:space:]]\+$//')"
if [[ "$raw_token" =~ ^%\!t\(string=(.*)\)$ ]]; then
raw_token="${BASH_REMATCH[1]}"
fi
api_token="$(printf '%s' "$raw_token" | sed 's/^[[:space:]]\+//; s/[[:space:]]\+$//')"
if [[ -z "$api_token" ]]; then
echo "inputs.token is required (set to secrets.RELEASE_PAT)." >&2
exit 1
fi
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 ${TOKEN}" \
-H "Authorization: token ${api_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)"
existing_release_id="$(parse_release_id release-existing.json)"
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 \
if ! curl --fail-with-body \
-X PATCH \
-H "Authorization: token ${TOKEN}" \
-H "Authorization: token ${api_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
--data "{\"tag_name\":\"${TAG_NAME}\",\"name\":\"${TAG_NAME}\",\"body\":\"${escaped_release_notes}\",\"draft\":false,\"prerelease\":false}" \
--output release.json; then
cat release.json >&2 || true
exit 1
fi
echo "id=$existing_release_id" >> "$GITHUB_OUTPUT"
elif [[ "$status_code" != "404" ]]; then
@@ -133,15 +157,18 @@ runs:
cat release-existing.json >&2
exit 1
else
curl --fail-with-body \
if ! curl --fail-with-body \
-X POST \
-H "Authorization: token ${TOKEN}" \
-H "Authorization: token ${api_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
--data "{\"tag_name\":\"${TAG_NAME}\",\"name\":\"${TAG_NAME}\",\"body\":\"${escaped_release_notes}\",\"draft\":false,\"prerelease\":false}" \
--output release.json; then
cat release.json >&2 || true
exit 1
fi
release_id="$(sed -n 's/.*"id"[[:space:]]*:[[:space:]]*\([0-9][0-9]*\).*/\1/p' release.json | head -n 1)"
release_id="$(parse_release_id release.json)"
if [[ -z "$release_id" ]]; then
echo "Failed to parse release id from API response" >&2
cat release.json >&2

View File

@@ -1 +1 @@
1.0.2
1.1.0