ci(release): make release notes idempotent and publish binaries
This commit is contained in:
@@ -6,6 +6,12 @@ on:
|
|||||||
version:
|
version:
|
||||||
description: Semantic version to release, with or without leading v.
|
description: Semantic version to release, with or without leading v.
|
||||||
required: true
|
required: true
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: Semantic version to release, with or without leading v.
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
prepare:
|
prepare:
|
||||||
@@ -81,3 +87,117 @@ jobs:
|
|||||||
git tag "$tag"
|
git tag "$tag"
|
||||||
git push origin HEAD
|
git push origin HEAD
|
||||||
git push origin "$tag"
|
git push origin "$tag"
|
||||||
|
|
||||||
|
- name: Create release with changelog notes
|
||||||
|
env:
|
||||||
|
RELEASE_VERSION: ${{ inputs.version }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
normalized_version="${RELEASE_VERSION#v}"
|
||||||
|
tag="v${normalized_version}"
|
||||||
|
|
||||||
|
release_notes="$(awk -v version="$normalized_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 ${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 DELETE \
|
||||||
|
-H "Authorization: token ${RELEASE_TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
"${release_api}/${existing_release_id}"
|
||||||
|
elif [[ "$status_code" != "404" ]]; then
|
||||||
|
echo "Unexpected response while checking release ${tag}: HTTP ${status_code}" >&2
|
||||||
|
cat release-existing.json >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
env:
|
||||||
|
RELEASE_VERSION: ${{ inputs.version }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
normalized_version="${RELEASE_VERSION#v}"
|
||||||
|
mkdir -p dist
|
||||||
|
|
||||||
|
for target in darwin/amd64 darwin/arm64 linux/amd64 linux/arm64 windows/amd64 windows/arm64; do
|
||||||
|
os="${target%/*}"
|
||||||
|
arch="${target#*/}"
|
||||||
|
ext=""
|
||||||
|
if [[ "$os" == "windows" ]]; then
|
||||||
|
ext=".exe"
|
||||||
|
fi
|
||||||
|
|
||||||
|
bin="vociferate_${normalized_version}_${os}_${arch}${ext}"
|
||||||
|
GOOS="$os" GOARCH="$arch" go build -trimpath -ldflags="-s -w" -o "dist/${bin}" ./cmd/releaseprep
|
||||||
|
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")"
|
||||||
|
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
|
||||||
|
|||||||
50
README.md
50
README.md
@@ -51,3 +51,53 @@ Defaults:
|
|||||||
```bash
|
```bash
|
||||||
just go-test
|
just go-test
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Release Artifacts
|
||||||
|
|
||||||
|
The `Prepare Release` workflow creates a release and uploads prebuilt `vociferate` binaries for:
|
||||||
|
|
||||||
|
- `darwin/amd64`
|
||||||
|
- `darwin/arm64`
|
||||||
|
- `linux/amd64`
|
||||||
|
- `linux/arm64`
|
||||||
|
- `windows/amd64`
|
||||||
|
- `windows/arm64`
|
||||||
|
|
||||||
|
It also uploads `checksums.txt` for integrity verification.
|
||||||
|
If a release already exists for the same tag, the workflow replaces it so release notes and attached binaries stay in sync.
|
||||||
|
|
||||||
|
## Reuse In Other Repositories
|
||||||
|
|
||||||
|
You can reuse vociferate in two ways.
|
||||||
|
|
||||||
|
Use the composite action directly:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Prepare release files
|
||||||
|
uses: git.hrafn.xyz/aether/vociferate@main
|
||||||
|
with:
|
||||||
|
version: v1.2.3
|
||||||
|
version-file: internal/myapp/version/version.go
|
||||||
|
version-pattern: 'const Version = "([^"]+)"'
|
||||||
|
changelog: changelog.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Call the reusable release workflow:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: Semantic version to release.
|
||||||
|
required: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
uses: aether/vociferate/.gitea/workflows/prepare-release.yml@main
|
||||||
|
with:
|
||||||
|
version: ${{ inputs.version }}
|
||||||
|
secrets: inherit
|
||||||
|
```
|
||||||
|
|||||||
Reference in New Issue
Block a user