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.
162 lines
6.1 KiB
YAML
162 lines
6.1 KiB
YAML
name: vociferate/publish
|
|
description: >
|
|
Extract release notes from the changelog and create or update a
|
|
Gitea/GitHub release. The repository must be checked out at the release
|
|
tag before this action runs.
|
|
|
|
inputs:
|
|
token:
|
|
description: >
|
|
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,
|
|
derived from the current git tag ref.
|
|
required: false
|
|
default: ''
|
|
changelog:
|
|
description: Path to changelog file relative to repository root.
|
|
required: false
|
|
default: CHANGELOG.md
|
|
|
|
outputs:
|
|
release-id:
|
|
description: Numeric ID of the created or updated release.
|
|
value: ${{ steps.create-release.outputs.id }}
|
|
tag:
|
|
description: The tag used for the release (e.g. v1.2.3).
|
|
value: ${{ steps.resolve-version.outputs.tag }}
|
|
version:
|
|
description: The bare version string without leading v (e.g. 1.2.3).
|
|
value: ${{ steps.resolve-version.outputs.version }}
|
|
|
|
runs:
|
|
using: composite
|
|
steps:
|
|
- name: Resolve release version
|
|
id: resolve-version
|
|
shell: bash
|
|
env:
|
|
INPUT_VERSION: ${{ inputs.version }}
|
|
GITHUB_REF_VALUE: ${{ github.ref }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
provided="$(printf '%s' "${INPUT_VERSION:-}" | sed 's/^[[:space:]]\+//; s/[[:space:]]\+$//')"
|
|
if [[ -n "$provided" ]]; then
|
|
normalized="${provided#v}"
|
|
tag="v${normalized}"
|
|
elif [[ "${GITHUB_REF_VALUE}" == refs/tags/* ]]; then
|
|
tag="${GITHUB_REF_VALUE#refs/tags/}"
|
|
normalized="${tag#v}"
|
|
elif head_tag="$(git describe --exact-match --tags HEAD 2>/dev/null)" && [[ -n "$head_tag" ]]; then
|
|
tag="$head_tag"
|
|
normalized="${tag#v}"
|
|
else
|
|
echo "A version input is required when the workflow is not running from a tag push" >&2
|
|
echo "Provide version via input or ensure HEAD is at a tagged commit." >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "tag=$tag" >> "$GITHUB_OUTPUT"
|
|
echo "version=$normalized" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Extract release notes
|
|
id: extract-notes
|
|
uses: ./run-vociferate
|
|
with:
|
|
root: ${{ github.workspace }}
|
|
changelog: ${{ inputs.changelog }}
|
|
version: ${{ steps.resolve-version.outputs.version }}
|
|
print-release-notes: 'true'
|
|
|
|
- name: Write release notes file
|
|
id: write-notes
|
|
shell: bash
|
|
env:
|
|
RELEASE_NOTES: ${{ steps.extract-notes.outputs.stdout }}
|
|
RUNNER_TEMP: ${{ runner.temp }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
notes_file="${RUNNER_TEMP}/release-notes.md"
|
|
printf '%s\n' "$RELEASE_NOTES" > "$notes_file"
|
|
|
|
printf 'notes_file=%s\n' "$notes_file" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Create or update release
|
|
id: create-release
|
|
shell: bash
|
|
env:
|
|
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_REPOSITORY: ${{ github.repository }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
if [[ -z "${TOKEN:-}" ]]; then
|
|
echo "inputs.token is required (set to secrets.RELEASE_PAT)." >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Resolve the exact commit SHA the tag points to. Using git rev-list
|
|
# rather than github.sha avoids the common mismatch where the workflow
|
|
# was triggered from a pre-release commit but the tag was created on
|
|
# the subsequent release commit by the prepare job.
|
|
tag_sha="$(git rev-list -n 1 "${TAG_NAME}")"
|
|
|
|
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 "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 ${TOKEN}" \
|
|
-H "Content-Type: application/json" \
|
|
"${release_api}/${existing_release_id}" \
|
|
--data "{\"tag_name\":\"${TAG_NAME}\",\"target\":\"${tag_sha}\",\"name\":\"${TAG_NAME}\",\"body\":\"${escaped_release_notes}\",\"draft\":false,\"prerelease\":false}" \
|
|
--output release.json
|
|
|
|
echo "id=$existing_release_id" >> "$GITHUB_OUTPUT"
|
|
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 ${TOKEN}" \
|
|
-H "Content-Type: application/json" \
|
|
"${release_api}" \
|
|
--data "{\"tag_name\":\"${TAG_NAME}\",\"target\":\"${tag_sha}\",\"name\":\"${TAG_NAME}\",\"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 "id=$release_id" >> "$GITHUB_OUTPUT"
|
|
fi
|