add SemVer release process with automated publishing

- Split workflows: CI (lint/validate) and Docker (dev builds)
- Add release.yml for versioned releases with manual trigger
- Release workflow creates release/<version> branches, updates version.py, builds Docker images with SemVer tags, and creates GitHub releases
- Docker images tagged as major.minor.patch, major.minor, major, latest for stable releases
- Pre-release versions tagged with exact version only

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Fengqing Liu
2025-10-19 20:58:59 +11:00
parent 603d07f3d3
commit ecd39a1c8e
3 changed files with 286 additions and 64 deletions

View File

@@ -1,16 +1,12 @@
name: Build
name: CI
on:
# Disabled automatic builds - run manually only
# push:
# branches:
# - "master"
# pull_request:
push:
pull_request:
workflow_dispatch:
env:
PYTHON_VERSION: '>=3.10'
USE_UPX: false
jobs:
lint:
@@ -70,60 +66,3 @@ jobs:
echo -e "\nFailed to validate the following language file(s): ${failed[@]}"
exit 1
fi
docker:
name: Docker Build
runs-on: ubuntu-latest
needs:
- lint
- validate
permissions:
contents: read
packages: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up variables
id: vars
run: |
echo "sha_short=$(git rev-parse --short HEAD)" >> "${GITHUB_OUTPUT}"
echo "date=$(date +%Y-%m-%d)" >> "${GITHUB_OUTPUT}"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{github.actor}}
password: ${{secrets.GITHUB_TOKEN}}
- name: Extract metadata for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{github.repository}}
tags: |
type=raw,value=dev
type=raw,value=dev-${{steps.vars.outputs.sha_short}}
type=raw,value=latest,enable=${{github.ref == 'refs/heads/master'}}
type=sha,prefix=
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64
push: ${{github.event_name != 'pull_request'}}
tags: ${{steps.meta.outputs.tags}}
labels: ${{steps.meta.outputs.labels}}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
BUILD_DATE=${{steps.vars.outputs.date}}
VCS_REF=${{github.sha}}
VERSION=dev-${{steps.vars.outputs.sha_short}}

62
.github/workflows/docker.yml vendored Normal file
View File

@@ -0,0 +1,62 @@
name: Docker Dev Build
on:
push:
branches:
- main
- master
- develop
workflow_dispatch:
jobs:
docker:
name: Docker Dev Build & Push
runs-on: ubuntu-latest
environment: prod
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Set up variables
id: vars
run: |
echo "sha_short=$(git rev-parse --short HEAD)" >> "${GITHUB_OUTPUT}"
echo "date=$(date +%Y-%m-%d)" >> "${GITHUB_OUTPUT}"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract metadata for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: rangermix/twitch-drops-miner
tags: |
type=raw,value=dev
type=raw,value=dev-${{steps.vars.outputs.sha_short}}
type=raw,value=latest,enable=${{github.ref == 'refs/heads/master'}}
type=sha,prefix=
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{steps.meta.outputs.tags}}
labels: ${{steps.meta.outputs.labels}}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
BUILD_DATE=${{steps.vars.outputs.date}}
VCS_REF=${{github.sha}}
VERSION=dev-${{steps.vars.outputs.sha_short}}

221
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,221 @@
name: Release
on:
workflow_dispatch:
inputs:
version:
description: 'Release version (SemVer format, e.g., 1.2.3 or 2.0.0-rc.1)'
required: true
type: string
permissions:
contents: write
packages: write
jobs:
validate:
name: Validate Version
runs-on: ubuntu-latest
outputs:
version: ${{ steps.validate.outputs.version }}
is_prerelease: ${{ steps.validate.outputs.is_prerelease }}
steps:
- name: Validate SemVer format
id: validate
run: |
VERSION="${{ github.event.inputs.version }}"
# SemVer regex: https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
SEMVER_REGEX='^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-((0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*))*))?(\+([0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*))?$'
if ! echo "$VERSION" | grep -Pq "$SEMVER_REGEX"; then
echo "Error: Version '$VERSION' is not valid SemVer format"
echo "Examples: 1.2.3, 2.0.0-rc.1, 1.0.0-beta.2+build.123"
exit 1
fi
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
# Check if pre-release (contains hyphen)
if echo "$VERSION" | grep -q '-'; then
echo "is_prerelease=true" >> "$GITHUB_OUTPUT"
echo "Detected pre-release version: $VERSION"
else
echo "is_prerelease=false" >> "$GITHUB_OUTPUT"
echo "Detected stable release version: $VERSION"
fi
create-release-branch:
name: Create Release Branch
runs-on: ubuntu-latest
needs: validate
outputs:
branch_name: ${{ steps.create-branch.outputs.branch_name }}
steps:
- name: Checkout code
uses: actions/checkout@v5
with:
ref: master
fetch-depth: 0
- name: Configure Git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Check if branch exists
id: check-branch
run: |
BRANCH_NAME="release/${{ needs.validate.outputs.version }}"
if git ls-remote --heads origin "$BRANCH_NAME" | grep -q "$BRANCH_NAME"; then
echo "Error: Branch '$BRANCH_NAME' already exists"
exit 1
fi
echo "Branch '$BRANCH_NAME' does not exist, proceeding..."
- name: Create release branch
id: create-branch
run: |
BRANCH_NAME="release/${{ needs.validate.outputs.version }}"
VERSION="${{ needs.validate.outputs.version }}"
# Create and checkout new branch
git checkout -b "$BRANCH_NAME"
# Update version.py
echo "__version__ = \"$VERSION\"" > src/version.py
# Commit changes
git add src/version.py
git commit -m "chore: bump version to $VERSION"
# Push branch
git push origin "$BRANCH_NAME"
# Create and push tag
git tag "v$VERSION"
git push origin "v$VERSION"
echo "branch_name=$BRANCH_NAME" >> "$GITHUB_OUTPUT"
echo "Created branch '$BRANCH_NAME' and tag 'v$VERSION'"
docker-build:
name: Build & Push Docker Images
runs-on: ubuntu-latest
needs: [validate, create-release-branch]
environment: prod
steps:
- name: Checkout release branch
uses: actions/checkout@v5
with:
ref: ${{ needs.create-release-branch.outputs.branch_name }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Generate Docker tags
id: docker-tags
run: |
VERSION="${{ needs.validate.outputs.version }}"
IMAGE="rangermix/twitch-drops-miner"
IS_PRERELEASE="${{ needs.validate.outputs.is_prerelease }}"
# Always include the full version tag
TAGS="$IMAGE:$VERSION"
# For stable releases, add major.minor, major, and latest tags
if [ "$IS_PRERELEASE" = "false" ]; then
# Extract major.minor.patch
MAJOR=$(echo "$VERSION" | cut -d. -f1)
MINOR=$(echo "$VERSION" | cut -d. -f2)
TAGS="$TAGS,$IMAGE:$MAJOR.$MINOR"
TAGS="$TAGS,$IMAGE:$MAJOR"
TAGS="$TAGS,$IMAGE:latest"
fi
echo "tags=$TAGS" >> "$GITHUB_OUTPUT"
echo "Generated tags: $TAGS"
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.docker-tags.outputs.tags }}
labels: |
org.opencontainers.image.title=Twitch Drops Miner
org.opencontainers.image.description=Automatically mine Twitch drops
org.opencontainers.image.version=${{ needs.validate.outputs.version }}
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.created=${{ github.event.repository.updated_at }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
BUILD_DATE=${{ github.event.repository.updated_at }}
VCS_REF=${{ github.sha }}
VERSION=${{ needs.validate.outputs.version }}
github-release:
name: Create GitHub Release
runs-on: ubuntu-latest
needs: [validate, create-release-branch, docker-build]
steps:
- name: Checkout release branch
uses: actions/checkout@v5
with:
ref: ${{ needs.create-release-branch.outputs.branch_name }}
fetch-depth: 0
- name: Generate release notes
id: release-notes
run: |
VERSION="${{ needs.validate.outputs.version }}"
# Get the previous tag
PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
if [ -n "$PREV_TAG" ]; then
echo "## Changes since $PREV_TAG" > release_notes.md
echo "" >> release_notes.md
git log $PREV_TAG..HEAD --pretty=format:"- %s (%h)" >> release_notes.md
else
echo "## Initial Release" > release_notes.md
echo "" >> release_notes.md
echo "First release of Twitch Drops Miner $VERSION" >> release_notes.md
fi
echo "" >> release_notes.md
echo "" >> release_notes.md
echo "## Docker Images" >> release_notes.md
echo "" >> release_notes.md
echo '```bash' >> release_notes.md
echo "docker pull rangermix/twitch-drops-miner:$VERSION" >> release_notes.md
echo '```' >> release_notes.md
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ needs.validate.outputs.version }}
name: Release ${{ needs.validate.outputs.version }}
body_path: release_notes.md
prerelease: ${{ needs.validate.outputs.is_prerelease }}
draft: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}