name: π¦ Release Management
on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
version_type:
description: 'Version bump type'
required: true
default: 'patch'
type: choice
options:
- patch
- minor
- major
release_notes:
description: 'Custom release notes (optional)'
required: false
type: textarea
jobs:
# π Prepare Release
prepare-release:
name: π Prepare Release
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
tag: ${{ steps.version.outputs.tag }}
release_notes: ${{ steps.release-notes.outputs.notes }}
steps:
- name: π₯ Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: π§ Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: π Determine version
id: version
run: |
if [[ $GITHUB_REF == refs/tags/* ]]; then
# Tagged release
TAG=${GITHUB_REF#refs/tags/}
VERSION=${TAG#v}
else
# Manual release
VERSION_TYPE="${{ github.event.inputs.version_type || 'patch' }}"
CURRENT_VERSION=$(node -p "require('./frontend/package.json').version")
# Bump version
if [ "$VERSION_TYPE" = "major" ]; then
NEW_VERSION=$(echo $CURRENT_VERSION | awk -F. '{print $1+1".0.0"}')
elif [ "$VERSION_TYPE" = "minor" ]; then
NEW_VERSION=$(echo $CURRENT_VERSION | awk -F. '{print $1"."$2+1".0"}')
else
NEW_VERSION=$(echo $CURRENT_VERSION | awk -F. '{print $1"."$2"."$3+1}')
fi
VERSION=$NEW_VERSION
TAG="v$NEW_VERSION"
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "tag=$TAG" >> $GITHUB_OUTPUT
- name: π Generate release notes
id: release-notes
run: |
if [[ $GITHUB_REF == refs/tags/* ]]; then
# Use existing tag message or generate from commits
TAG_MESSAGE=$(git tag -l --format='%(contents)' ${{ steps.version.outputs.tag }} 2>/dev/null || echo "")
if [ -n "$TAG_MESSAGE" ]; then
RELEASE_NOTES="$TAG_MESSAGE"
else
# Generate from commits since last tag
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
if [ -n "$LAST_TAG" ]; then
RELEASE_NOTES=$(git log --oneline --pretty=format:"- %s" $LAST_TAG..HEAD)
else
RELEASE_NOTES=$(git log --oneline --pretty=format:"- %s" -10)
fi
fi
else
# Use provided release notes or generate from recent commits
CUSTOM_NOTES="${{ github.event.inputs.release_notes }}"
if [ -n "$CUSTOM_NOTES" ]; then
RELEASE_NOTES="$CUSTOM_NOTES"
else
RELEASE_NOTES=$(git log --oneline --pretty=format:"- %s" -10)
fi
fi
# Format release notes
FORMATTED_NOTES="# Release ${{ steps.version.outputs.tag }}
## What's Changed
$RELEASE_NOTES
## Deployment
This release has been deployed to:
- **Frontend**: https://robotics-webapp.com
- **Backend API**: https://api.robotics-webapp.com
- **Documentation**: https://docs.robotics-webapp.com
## Installation
\`\`\`bash
# Docker deployment
docker-compose pull && docker-compose up -d
# Manual deployment
git checkout ${{ steps.version.outputs.tag }}
# Follow deployment guide
\`\`\`
## Support
For support or questions about this release, please:
- Check the [documentation](https://docs.robotics-webapp.com)
- Open an [issue](https://github.com/sandraschi/robotics-webapp/issues)
- Contact support at robotics@sandraschi.dev"
echo "notes<<EOF" >> $GITHUB_OUTPUT
echo "$FORMATTED_NOTES" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
# ποΈ Build Release Artifacts
build-artifacts:
name: ποΈ Build Release Artifacts
runs-on: ubuntu-latest
needs: prepare-release
steps:
- name: π₯ Checkout code
uses: actions/checkout@v4
- name: π§ Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- name: π§ Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: π¦ Build frontend production bundle
run: |
cd frontend
npm ci
npm run build
npm run export
- name: π¦ Build backend distribution
run: |
cd backend
python -m pip install --upgrade pip
pip install build
python -m build
- name: π¦ Create Docker images
run: |
# Build frontend image
docker build -t robotics-webapp/frontend:${{ needs.prepare-release.outputs.version }} ./frontend
# Build backend image
docker build -t robotics-webapp/backend:${{ needs.prepare-release.outputs.version }} ./backend
- name: π¦ Create release archives
run: |
# Frontend distribution
cd frontend
tar -czf ../../frontend-${{ needs.prepare-release.outputs.version }}.tar.gz out/
# Backend distribution
cd ../backend
tar -czf ../../backend-${{ needs.prepare-release.outputs.version }}.tar.gz dist/
# Source code
cd ..
tar -czf source-${{ needs.prepare-release.outputs.version }}.tar.gz \
--exclude='node_modules' \
--exclude='__pycache__' \
--exclude='.git' \
--exclude='*.log' \
.
- name: π€ Upload release artifacts
uses: actions/upload-artifact@v4
with:
name: release-artifacts
path: |
frontend-${{ needs.prepare-release.outputs.version }}.tar.gz
backend-${{ needs.prepare-release.outputs.version }}.tar.gz
source-${{ needs.prepare-release.outputs.version }}.tar.gz
# π Create GitHub Release
create-release:
name: π Create GitHub Release
runs-on: ubuntu-latest
needs: [prepare-release, build-artifacts]
permissions:
contents: write
steps:
- name: π₯ Download artifacts
uses: actions/download-artifact@v4
with:
name: release-artifacts
- name: π¦ Create GitHub release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ needs.prepare-release.outputs.tag }}
release_name: Release ${{ needs.prepare-release.outputs.tag }}
body: ${{ needs.prepare-release.outputs.release_notes }}
draft: false
prerelease: false
- name: π€ Upload release assets
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./frontend-${{ needs.prepare-release.outputs.version }}.tar.gz
asset_name: robotics-webapp-frontend-${{ needs.prepare-release.outputs.version }}.tar.gz
asset_content_type: application/gzip
- name: π€ Upload backend asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./backend-${{ needs.prepare-release.outputs.version }}.tar.gz
asset_name: robotics-webapp-backend-${{ needs.prepare-release.outputs.version }}.tar.gz
asset_content_type: application/gzip
- name: π€ Upload source asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./source-${{ needs.prepare-release.outputs.version }}.tar.gz
asset_name: robotics-webapp-source-${{ needs.prepare-release.outputs.version }}.tar.gz
asset_content_type: application/gzip
# π³ Publish Docker Images
publish-docker:
name: π³ Publish Docker Images
runs-on: ubuntu-latest
needs: prepare-release
permissions:
contents: read
packages: write
steps:
- name: π₯ Checkout code
uses: actions/checkout@v4
- name: π Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: π¦ Build and push frontend image
uses: docker/build-push-action@v5
with:
context: ./frontend
push: true
tags: |
ghcr.io/sandraschi/robotics-webapp/frontend:latest
ghcr.io/sandraschi/robotics-webapp/frontend:${{ needs.prepare-release.outputs.version }}
labels: |
org.opencontainers.image.title=Robotics MCP WebApp Frontend
org.opencontainers.image.version=${{ needs.prepare-release.outputs.version }}
- name: π¦ Build and push backend image
uses: docker/build-push-action@v5
with:
context: ./backend
push: true
tags: |
ghcr.io/sandraschi/robotics-webapp/backend:latest
ghcr.io/sandraschi/robotics-webapp/backend:${{ needs.prepare-release.outputs.version }}
labels: |
org.opencontainers.image.title=Robotics MCP WebApp Backend
org.opencontainers.image.version=${{ needs.prepare-release.outputs.version }}
# π’ Notifications
notify:
name: π’ Release Notifications
runs-on: ubuntu-latest
needs: [prepare-release, create-release, publish-docker]
if: always()
steps:
- name: π’ Slack notification
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
text: "Release ${{ needs.prepare-release.outputs.tag }} ${{ job.status == 'success' && 'published successfully β
' || 'failed β' }}"
fields: repo,message,commit,author,action,eventName,ref,workflow
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
- name: π§ Email notification
uses: dawidd6/action-send-mail@v3
with:
server_address: smtp.gmail.com
server_port: 465
username: ${{ secrets.EMAIL_USERNAME }}
password: ${{ secrets.EMAIL_PASSWORD }}
subject: 'π¦ Robotics WebApp Release ${{ needs.prepare-release.outputs.tag }}'
body: |
Robotics MCP WebApp ${{ needs.prepare-release.outputs.tag }} has been released!
π¦ Release: ${{ github.server_url }}/${{ github.repository }}/releases/tag/${{ needs.prepare-release.outputs.tag }}
π³ Docker Images: ghcr.io/sandraschi/robotics-webapp
π Documentation: https://docs.robotics-webapp.com
Release created by: ${{ github.actor }}
Build: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
to: robotics@sandraschi.dev
from: Release Bot <robotics@sandraschi.dev>
# π Update Version Files
update-versions:
name: π Update Version Files
runs-on: ubuntu-latest
needs: prepare-release
if: github.event_name == 'workflow_dispatch'
steps:
- name: π₯ Checkout code
uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: π§ Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: π Update frontend version
run: |
cd frontend
npm version ${{ needs.prepare-release.outputs.version }} --no-git-tag-version
- name: π Update backend version
run: |
cd backend
# Update version in main.py or pyproject.toml
sed -i "s/version = \".*\"/version = \"${{ needs.prepare-release.outputs.version }}\"/" main.py
- name: π Update root version files
run: |
# Update any root level version files
echo "${{ needs.prepare-release.outputs.version }}" > VERSION
- name: π Commit version updates
run: |
git config user.name "GitHub Actions Bot"
git config user.email "robotics@sandraschi.dev"
git add .
git commit -m "chore: bump version to ${{ needs.prepare-release.outputs.version }}"
git push