Skip to content

🚀 Release Builder #23

🚀 Release Builder

🚀 Release Builder #23

# This workflow will build the project and create a release on demand
# Uses:
# OS: ubuntu-latest
# Go: go 1.x
name: 🚀 Release Builder
on:
schedule:
# Runs every Friday at 12:00 PM IST (6:30 AM UTC)
- cron: '30 6 * * 5'
workflow_dispatch:
inputs:
bump_type:
description: 'Version bump type to apply to version.txt (major, minor, or patch)'
required: false
type: choice
options:
- minor
- patch
- major
default: minor
use_existing_version:
description: 'Use version as-is (no bump, no auto-commit)'
required: false
type: boolean
default: false
prerelease:
description: 'Set as a pre-release'
required: false
type: boolean
default: false
run_performance_test:
description: 'Run performance tests after release'
required: false
type: boolean
default: true
# Add permissions to allow pushing tags and packages
permissions:
contents: write
packages: write
env:
GOFLAGS: "-mod=readonly"
PRODUCT_NAME: "Thunder"
PRODUCT_NAME_LOWER: "thunder"
RELEASE_BRANCH: "release"
RELEASE_GIT_USER_NAME: "thunder-automation-bot"
RELEASE_GIT_USER_EMAIL: "thunder-bot@wso2.com"
jobs:
build:
name: ⚡ Build
runs-on: ubuntu-latest
outputs:
version: ${{ steps.get_version.outputs.version }}
prerelease: ${{ steps.get_version.outputs.prerelease }}
run_performance_test: ${{ steps.get_version.outputs.run_performance_test }}
use_existing_version: ${{ steps.get_version.outputs.use_existing_version }}
steps:
- name: 📥 Checkout Code
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
token: ${{ secrets.THUNDER_AUTOMATION_BOT }}
ref: ${{ env.RELEASE_BRANCH }}
- name: 📝 Determine Release Version
id: get_version
run: |
# Read current version from version.txt
if [ ! -f version.txt ] || [ -z "$(cat version.txt | tr -d '[:space:]')" ]; then
echo "❌ Error: version.txt not found or empty"
exit 1
fi
CURRENT_VERSION=$(tr -d '[:space:]' < version.txt)
# Strip leading 'v' for semver parsing
SEMVER="${CURRENT_VERSION#v}"
if [[ $SEMVER =~ ^([0-9]+)\.([0-9]+)\.([0-9]+) ]]; then
CUR_MAJOR="${BASH_REMATCH[1]}"
CUR_MINOR="${BASH_REMATCH[2]}"
CUR_PATCH="${BASH_REMATCH[3]}"
else
echo "❌ Error: Could not parse version '$CURRENT_VERSION' from version.txt"
exit 1
fi
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
PRERELEASE="${{ github.event.inputs.prerelease }}"
RUN_PERF_TEST="${{ github.event.inputs.run_performance_test }}"
BUMP_TYPE="${{ github.event.inputs.bump_type }}"
USE_EXISTING_VERSION="${{ github.event.inputs.use_existing_version }}"
else
# Scheduled run — default to minor bump
PRERELEASE="false"
RUN_PERF_TEST="true"
BUMP_TYPE="minor"
USE_EXISTING_VERSION="false"
fi
if [ "$USE_EXISTING_VERSION" == "true" ]; then
VERSION="$CURRENT_VERSION"
echo "✅ Using existing version from version.txt: $VERSION"
else
case "$BUMP_TYPE" in
major)
VERSION="v$((CUR_MAJOR + 1)).0.0"
;;
minor)
VERSION="v${CUR_MAJOR}.$((CUR_MINOR + 1)).0"
;;
patch)
VERSION="v${CUR_MAJOR}.${CUR_MINOR}.$((CUR_PATCH + 1))"
;;
*)
echo "❌ Error: Invalid bump_type '$BUMP_TYPE'. Must be major, minor, or patch."
exit 1
;;
esac
fi
echo "✅ Selected release version: $VERSION (use_existing_version: $USE_EXISTING_VERSION, bump: $BUMP_TYPE, Prerelease: $PRERELEASE, Perf Test: $RUN_PERF_TEST)"
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "prerelease=$PRERELEASE" >> $GITHUB_OUTPUT
echo "run_performance_test=$RUN_PERF_TEST" >> $GITHUB_OUTPUT
echo "use_existing_version=$USE_EXISTING_VERSION" >> $GITHUB_OUTPUT
- name: ✅ Validate Release Version
run: |
VERSION="${{ steps.get_version.outputs.version }}"
# Check version format
if ! [[ $VERSION =~ ^v[0-9]+\.[0-9]+(\.[0-9]+)?(-[0-9a-zA-Z.-]+)?(\+[0-9a-zA-Z.-]+)?$ ]]; then
echo "❌ Error: Version '$VERSION' does not follow format vX.Y[.Z][-PRERELEASE][+BUILD]"
echo "❌ Version must start with 'v'"
exit 1
fi
echo "✅ Version '$VERSION' format is valid"
# Check if tag already exists
if git rev-parse "$VERSION" >/dev/null 2>&1; then
echo "❌ Error: Version '$VERSION' already exists as a git tag"
echo "❌ Please update version.txt with a new version before running the release"
exit 1
fi
echo "✅ Version '$VERSION' does not exist yet - proceeding with release"
- name: 🏷️ Update Product Version
if: ${{ steps.get_version.outputs.use_existing_version != 'true' }}
run: |
VERSION="${{ steps.get_version.outputs.version }}"
echo "$VERSION" > version.txt
echo "✅ Updated version.txt to $VERSION"
- name: 📝 Update README Version References
if: ${{ steps.get_version.outputs.use_existing_version != 'true' }}
run: |
VERSION="${{ steps.get_version.outputs.version }}"
echo "📝 Updating README.md docker-compose curl URL to $VERSION"
# Update the docker-compose.yml curl URL in README.md
sed -i -E "s|(https://raw\.githubusercontent\.com/asgardeo/thunder/)v[0-9]+\.[0-9]+\.[0-9]+(/.+docker-compose\.yml)|\1${VERSION}\2|g" README.md
echo "✅ Updated README.md curl URL to $VERSION"
grep 'docker-compose.yml' README.md
- name: 📝 Update Helm and Sample App Versions
if: ${{ steps.get_version.outputs.use_existing_version != 'true' }}
run: |
VERSION="${{ steps.get_version.outputs.version }}"
SEMVER_VERSION="${VERSION#v}"
echo "📝 Updating install/helm/Chart.yaml to $SEMVER_VERSION"
sed -i -E "s/^version: .*/version: ${SEMVER_VERSION}/" install/helm/Chart.yaml
sed -i -E "s/^appVersion: .*/appVersion: \"${SEMVER_VERSION}\"/" install/helm/Chart.yaml
echo "📝 Updating OpenChoreo Helm chart versions to $SEMVER_VERSION"
sed -i -E "s/^version: .*/version: ${SEMVER_VERSION}/" install/openchoreo/helm/Chart.yaml
sed -i -E "s/^appVersion: .*/appVersion: \"${SEMVER_VERSION}\"/" install/openchoreo/helm/Chart.yaml
sed -i -E "s/^version: .*/version: ${SEMVER_VERSION}/" install/openchoreo/helm/charts/${PRODUCT_NAME_LOWER}-oc-componenttype/Chart.yaml
sed -i -E "s/^version: .*/version: ${SEMVER_VERSION}/" install/openchoreo/helm/charts/${PRODUCT_NAME_LOWER}-component/Chart.yaml
# Update sub-chart dependency versions in the umbrella chart (indented version: entries)
sed -i -E "s/(^[[:space:]]+version: \")[^\"]*/\1${SEMVER_VERSION}/" install/openchoreo/helm/Chart.yaml
echo "📝 Updating sample app package versions to $SEMVER_VERSION"
cd samples/apps/react-api-based-sample
npm version "$SEMVER_VERSION" --no-git-tag-version --allow-same-version
cd "$GITHUB_WORKSPACE/samples/apps/react-sdk-sample"
npm version "$SEMVER_VERSION" --no-git-tag-version --allow-same-version
cd "$GITHUB_WORKSPACE/samples/apps/react-vanilla-sample"
npm version "$SEMVER_VERSION" --no-git-tag-version --allow-same-version
cd server
npm version "$SEMVER_VERSION" --no-git-tag-version --allow-same-version
cd "$GITHUB_WORKSPACE"
echo "✅ Updated Helm chart and sample app versions to $SEMVER_VERSION"
- name: 📤 Commit and Push Version Bump
if: ${{ steps.get_version.outputs.use_existing_version != 'true' }}
run: |
VERSION="${{ steps.get_version.outputs.version }}"
git config --local user.name "${{ env.RELEASE_GIT_USER_NAME }}"
git config --local user.email "${{ env.RELEASE_GIT_USER_EMAIL }}"
git add \
version.txt \
README.md \
install/helm/Chart.yaml \
install/openchoreo/helm/Chart.yaml \
install/openchoreo/helm/charts/${PRODUCT_NAME_LOWER}-oc-componenttype/Chart.yaml \
install/openchoreo/helm/charts/${PRODUCT_NAME_LOWER}-component/Chart.yaml \
samples/apps/react-api-based-sample/package-lock.json \
samples/apps/react-api-based-sample/package.json \
samples/apps/react-sdk-sample/package-lock.json \
samples/apps/react-sdk-sample/package.json \
samples/apps/react-vanilla-sample/package-lock.json \
samples/apps/react-vanilla-sample/package.json \
samples/apps/react-vanilla-sample/server/package-lock.json \
samples/apps/react-vanilla-sample/server/package.json
if git diff --cached --quiet; then
echo "✅ No changes to commit (files already at $VERSION)"
else
git commit -m "[Release] Bump version to ${VERSION}"
git push origin HEAD:release
echo "✅ Pushed version bump commit to release branch"
fi
- name: ⚙️ Set up Go Environment
uses: ./.github/actions/setup-go
- name: 🗄️ Cache Go Modules
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
id: cache-go-modules
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-modules-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-modules-
- name: 📦 Install Dependencies
run: |
cd backend
go mod download
cd ../tests/integration
go mod download
- name: 🧹 Clean Previous Builds
run: make clean
- name: 🔨 Build Product with Coverage
run: |
set -e
make build_with_coverage_only OS=$(go env GOOS) ARCH=$(go env GOARCH)
# Find the built distribution
DIST_PATH=$(find target/dist -name "$PRODUCT_NAME_LOWER-*.zip" | head -1)
echo "Built distribution: $DIST_PATH"
- name: 📦 Upload Built Distribution
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: product-distribution
path: target/dist/*.zip
if-no-files-found: error
test-integration:
name: 🧪 Integration Tests (${{ matrix.database }})
needs: build
runs-on: ubuntu-latest
strategy:
matrix:
database: [sqlite, postgres]
fail-fast: false
services:
postgres:
image: postgres:latest
env:
POSTGRES_USER: dbuser
POSTGRES_PASSWORD: dbpassword
POSTGRES_DB: thunderdb
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- name: 📥 Checkout Code
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
ref: ${{ env.RELEASE_BRANCH }}
- name: ⚙️ Set up Go Environment
uses: ./.github/actions/setup-go
- name: 🗄️ Cache Go Modules
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
id: cache-go-modules
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-modules-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-modules-
- name: 📦 Install Dependencies
run: |
cd backend
go mod download
cd ../tests/integration
go mod download
- name: 📝 Configure Test Database
run: |
chmod +x tests/integration/resources/scripts/setup-test-config.sh
./tests/integration/resources/scripts/setup-test-config.sh
env:
DB_TYPE: ${{ matrix.database }}
- name: 🧪 Run Integration Tests (${{ matrix.database }})
uses: ./.github/actions/run-integration-tests
with:
database-type: ${{ matrix.database }}
coverage-enabled: true
validate-windows:
name: 🪟 Validate Windows PowerShell
needs: build
uses: ./.github/workflows/windows-powershell-validation.yml
build-and-package:
name: 📦 Build and Package Release
runs-on: ubuntu-latest
needs: [build, test-integration, validate-windows]
steps:
- name: 📥 Checkout Code
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
ref: ${{ env.RELEASE_BRANCH }}
- name: 🏷️ Update Product Version
run: |
VERSION="${{ needs.build.outputs.version }}"
echo "$VERSION" > version.txt
- name: ⚙️ Set up Go Environment
uses: ./.github/actions/setup-go
- name: 🗄️ Cache Go Modules
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
id: cache-go-modules
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-modules-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-modules-
- name: 📦 Install Dependencies
run: |
cd backend
go mod download
cd ../tests/integration
go mod download
- name: 🔐 Generate and Upload Certificates
uses: ./.github/actions/generate-certificates
with:
show-cert-info: 'true'
product-name: ${{ env.PRODUCT_NAME }}
- name: 🔨 Build Release Artifacts
uses: ./.github/actions/build-multiplatform
- name: 📝 Read Updated Version
id: version
run: echo "version=$(cat version.txt)" >> $GITHUB_OUTPUT
- name: 📦 Upload Backend Distribution Artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: product-distribution
path: target/dist/*.zip
if-no-files-found: error
overwrite: true
- name: 🏷️ Create Git Tag
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
TAG_VERSION="${{ needs.build.outputs.version }}"
if [[ ! $TAG_VERSION == v* ]]; then
TAG_VERSION="v$TAG_VERSION"
fi
git tag -a "$TAG_VERSION" -m "Release $TAG_VERSION"
git push origin "$TAG_VERSION"
- name: 🐳 Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
- name: 🔐 Log in to GitHub Container Registry
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: 📥 Download Shared Certificates for Docker
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: ssl-certificates
path: target/out/.cert/
- name: 📋 Prepare Docker Build Context
run: |
# Copy certificates into the Docker build context
mkdir -p docker-certs
cp target/out/.cert/server.cert docker-certs/
cp target/out/.cert/server.key docker-certs/
echo "✅ Certificates prepared for Docker build context"
- name: 🐳 Build and Push Multi-Arch Docker Image
run: |
# Convert repository name to lowercase for GHCR
REPO_NAME=$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]')
IMAGE_NAME="ghcr.io/${REPO_NAME}"
# Get version without 'v' prefix for Docker tags
DOCKER_VERSION="${{ needs.build.outputs.version }}"
if [[ $DOCKER_VERSION == v* ]]; then
DOCKER_VERSION="${DOCKER_VERSION#v}"
fi
echo "🐳 Building and pushing Docker image: ${IMAGE_NAME}:${DOCKER_VERSION}"
# Build and push multi-arch image with version and latest tags
# Pass shared certificates as build context with paths relative to build context
docker buildx build \
--platform linux/amd64,linux/arm64 \
--tag "${IMAGE_NAME}:${DOCKER_VERSION}" \
--tag "${IMAGE_NAME}:latest" \
--build-arg CERT_FILE=docker-certs/server.cert \
--build-arg KEY_FILE=docker-certs/server.key \
--push \
.
echo "✅ Docker image pushed successfully!"
echo "📦 Image available at: ${IMAGE_NAME}:${DOCKER_VERSION}"
echo "📦 Image available at: ${IMAGE_NAME}:latest"
- name: 🏷️ Update Helm Values Image Tag
run: |
# Get version without 'v' prefix
CHART_VERSION="${{ needs.build.outputs.version }}"
if [[ $CHART_VERSION == v* ]]; then
CHART_VERSION="${CHART_VERSION#v}"
fi
echo "📝 Updating Helm values.yaml with image tag ${CHART_VERSION}"
# Check if the file exists and has the expected pattern
if ! grep -q "tag: \"" install/helm/values.yaml; then
echo "❌ Error: Could not find 'tag: \"' pattern in install/helm/values.yaml"
echo "File may have been modified or corrupted"
exit 1
fi
# Update values.yaml with the new image tag
sed -i "s/tag: \"[^\"]*\"/tag: \"${CHART_VERSION}\"/" install/helm/values.yaml
echo "📝 Updating OpenChoreo Helm values with image tag ${CHART_VERSION}"
sed -i "s/tag: \"[^\"]*\"/tag: \"${CHART_VERSION}\"/" install/openchoreo/helm/values.yaml
sed -i "s/tag: \"[^\"]*\"/tag: \"${CHART_VERSION}\"/" install/openchoreo/helm/charts/${PRODUCT_NAME_LOWER}-component/values.yaml
# Verify the replacement was successful
if ! grep -q "tag: \"${CHART_VERSION}\"" install/helm/values.yaml; then
echo "❌ Error: Failed to update image tag in values.yaml"
echo "Expected to find: tag: \"${CHART_VERSION}\""
echo "Current content:"
grep "tag:" install/helm/values.yaml
exit 1
fi
echo "✅ Successfully updated values.yaml with image tag: ${CHART_VERSION}"
echo "✅ Verification passed - new tag is present in file"
grep "tag:" install/helm/values.yaml | head -1
- name: ⚙️ Set up Helm
uses: azure/setup-helm@5119fcb9089d432beecbf79bb2c7915207344b78 # v3
with:
version: 'v3.12.3'
- name: 📦 Package and Push Helm Chart to GHCR
run: |
CHART_NAME="$PRODUCT_NAME_LOWER"
OWNER_NAME=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
HELM_REGISTRY="ghcr.io/${OWNER_NAME}"
# Get version without 'v' prefix for Helm chart version
CHART_VERSION="${{ needs.build.outputs.version }}"
if [[ $CHART_VERSION == v* ]]; then
CHART_VERSION="${CHART_VERSION#v}"
fi
echo "📦 Packaging Helm chart: ${CHART_NAME} version ${CHART_VERSION}"
# Ensure target directory exists
mkdir -p target/dist/
# Package the Helm chart
helm package install/helm --version "${CHART_VERSION}" --app-version "${CHART_VERSION}" --destination target/dist/
# Validate package was created
if [ ! -f "target/dist/${CHART_NAME}-${CHART_VERSION}.tgz" ]; then
echo "❌ Error: Helm chart package not found"
exit 1
fi
# Authenticate Helm to GHCR
echo "${{ secrets.GITHUB_TOKEN }}" | helm registry login ghcr.io -u ${{ github.actor }} --password-stdin
# Push the Helm chart to GHCR
helm push "target/dist/${CHART_NAME}-${CHART_VERSION}.tgz" "oci://${HELM_REGISTRY}/helm-charts"
echo "✅ Helm chart pushed successfully!"
echo "📦 Helm chart available at: oci://${HELM_REGISTRY}/helm-charts"
- name: 📦 Package and Push OpenChoreo Helm Charts to GHCR
run: |
OWNER_NAME=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
HELM_REGISTRY="ghcr.io/${OWNER_NAME}/helm-charts"
CHART_VERSION="${{ needs.build.outputs.version }}"
if [[ $CHART_VERSION == v* ]]; then
CHART_VERSION="${CHART_VERSION#v}"
fi
mkdir -p target/dist/openchoreo
# Authenticate Helm to GHCR
echo "${{ secrets.GITHUB_TOKEN }}" | helm registry login ghcr.io -u ${{ github.actor }} --password-stdin
# Package and push oc-componenttype
echo "📦 Packaging ${PRODUCT_NAME_LOWER}-oc-componenttype ${CHART_VERSION}"
helm package install/openchoreo/helm/charts/${PRODUCT_NAME_LOWER}-oc-componenttype \
--version "${CHART_VERSION}" --app-version "${CHART_VERSION}" \
--destination target/dist/openchoreo/
helm push "target/dist/openchoreo/${PRODUCT_NAME_LOWER}-oc-componenttype-${CHART_VERSION}.tgz" "oci://${HELM_REGISTRY}"
echo "✅ ${PRODUCT_NAME_LOWER}-oc-componenttype pushed to oci://${HELM_REGISTRY}"
- name: 📝 Calculate Next Version
id: next_version
run: |
# Get the current release version and remove 'v' prefix
RELEASE_VERSION="${{ needs.build.outputs.version }}"
if [[ $RELEASE_VERSION == v* ]]; then
RELEASE_VERSION="${RELEASE_VERSION#v}"
fi
# Parse version components (handle both X.Y and X.Y.Z formats)
if [[ $RELEASE_VERSION =~ ^([0-9]+)\.([0-9]+)\.([0-9]+) ]]; then
# X.Y.Z format
MAJOR="${BASH_REMATCH[1]}"
MINOR="${BASH_REMATCH[2]}"
PATCH="${BASH_REMATCH[3]}"
elif [[ $RELEASE_VERSION =~ ^([0-9]+)\.([0-9]+) ]]; then
# X.Y format
MAJOR="${BASH_REMATCH[1]}"
MINOR="${BASH_REMATCH[2]}"
PATCH="0"
else
echo "❌ Error: Unable to parse version format: $RELEASE_VERSION"
exit 1
fi
# Bump minor version for next development cycle
NEXT_MINOR=$((MINOR + 1))
NEXT_VERSION="${MAJOR}.${NEXT_MINOR}.0"
echo "✅ Next development version: $NEXT_VERSION"
echo "next_version=$NEXT_VERSION" >> $GITHUB_OUTPUT
- name: 🏷️ Update Product Version
run: |
NEXT_VERSION="v${{ steps.next_version.outputs.next_version }}"
echo "$NEXT_VERSION" > version.txt
echo "✅ Updated version.txt to $NEXT_VERSION"
- name: ⚙️ Set up Node.js for Version Update
uses: ./.github/actions/setup-node
with:
node-version: 'lts/*'
package-manager: 'npm'
dependency-path: 'samples/apps/react-vanilla-sample'
- name: 📦 Install pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
with:
version: 9
- name: 🏷️ Update Sample Apps Version
run: |
NEXT_VERSION="${{ steps.next_version.outputs.next_version }}"
echo "📝 Setting sample apps version to $NEXT_VERSION"
# Update the react-vanilla sample app package.json
cd samples/apps/react-vanilla-sample
npm version $NEXT_VERSION --no-git-tag-version --allow-same-version
# Update the server package.json
cd server
npm version $NEXT_VERSION --no-git-tag-version --allow-same-version
cd "$GITHUB_WORKSPACE"
# Update the react-sdk sample app package.json
cd samples/apps/react-sdk-sample
pnpm version $NEXT_VERSION --no-git-tag-version --allow-same-version
cd "$GITHUB_WORKSPACE"
# Update the react-api-based sample app package.json
cd samples/apps/react-api-based-sample
pnpm version $NEXT_VERSION --no-git-tag-version --allow-same-version
cd "$GITHUB_WORKSPACE"
echo "✅ Updated sample apps version to $NEXT_VERSION"
package-samples-linux:
name: 📦 Package Linux & Windows Samples
runs-on: ubuntu-latest
needs: [build, build-and-package]
steps:
- name: 📥 Checkout Code
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
ref: ${{ env.RELEASE_BRANCH }}
- name: ⚙️ Setup Samples Environment
uses: ./.github/actions/setup-samples-environment
- name: 📝 Update Sample Apps Version
uses: ./.github/actions/update-sample-versions
with:
version: ${{ needs.build.outputs.version }}
- name: 📦 Build and Package Linux Samples
run: |
./scripts/package-samples.sh linux x64
./scripts/package-samples.sh linux arm64
- name: 📦 Build and Package Windows Samples
run: ./scripts/package-samples.sh win x64
- name: 📦 Upload Linux & Windows Sample Artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: ${{ env.PRODUCT_NAME_LOWER }}-samples-linux-windows
path: target/dist/*.zip
if-no-files-found: error
package-samples-macos:
name: 📦 Package macOS Samples
runs-on: macos-latest
needs: [build, build-and-package]
steps:
- name: 📥 Checkout Code
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
ref: ${{ env.RELEASE_BRANCH }}
- name: ⚙️ Setup Samples Environment
uses: ./.github/actions/setup-samples-environment
- name: 📝 Update Sample Apps Version
uses: ./.github/actions/update-sample-versions
with:
version: ${{ needs.build.outputs.version }}
- name: 📦 Build and Package macOS Samples
run: |
./scripts/package-samples.sh macos x64
./scripts/package-samples.sh macos arm64
- name: 📦 Upload macOS Sample Artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: ${{ env.PRODUCT_NAME_LOWER }}-samples-macos
path: target/dist/*.zip
if-no-files-found: error
release:
name: 🚀 Release
runs-on: ubuntu-latest
needs: [build, build-and-package, package-samples-linux, package-samples-macos]
outputs:
release_tag: ${{ steps.release_info.outputs.release_tag }}
steps:
- name: 📥 Checkout Code
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
ref: ${{ env.RELEASE_BRANCH }}
- name: 📥 Download Backend Artifacts
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: product-distribution
path: ./backend-artifacts
- name: 📥 Download Linux & Windows Sample Artifacts
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: ${{ env.PRODUCT_NAME_LOWER }}-samples-linux-windows
path: ./linux-windows-artifacts
- name: 📥 Download macOS Sample Artifacts
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: ${{ env.PRODUCT_NAME_LOWER }}-samples-macos
path: ./macos-artifacts
- name: 📦 Combine All Distribution Artifacts
run: |
mkdir -p target/dist
# Copy backend artifacts
cp ./backend-artifacts/*.zip target/dist/ || true
# Copy sample artifacts from each platform
cp ./linux-windows-artifacts/*.zip target/dist/ || true
cp ./macos-artifacts/*.zip target/dist/ || true
echo "✅ Combined all artifacts:"
ls -la target/dist/
- name: 📦 Upload Final Combined Artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: final-distribution
path: target/dist/*.zip
if-no-files-found: error
- name: 📝 Read Version
id: version
run: |
VERSION="${{ needs.build.outputs.version }}"
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: 📝 Generate Automatic Release Notes
id: generate_notes
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
with:
script: |
const currentTag = '${{ needs.build.outputs.version }}';
try {
// Get only the 2 most recent releases (current + previous)
const { data: releases } = await github.rest.repos.listReleases({
owner: context.repo.owner,
repo: context.repo.repo,
per_page: 2
});
console.log(`Found ${releases.length} recent release(s)`);
// Find the previous release (skip the current one if it exists)
let previousTag = null;
if (releases.length === 0) {
console.log('No previous releases found - this is the first release');
} else if (releases.length === 1) {
// Only one release exists
if (releases[0].tag_name === currentTag) {
console.log('Current release found, but no previous release exists');
} else {
previousTag = releases[0].tag_name;
console.log(`Previous release tag: ${previousTag}`);
}
} else {
// Two or more releases exist
if (releases[0].tag_name === currentTag) {
// Current release is the most recent, use the second one
previousTag = releases[1].tag_name;
console.log(`Current release found at position 0, using position 1: ${previousTag}`);
} else {
// Current release doesn't exist yet or is not the most recent
previousTag = releases[0].tag_name;
console.log(`Using most recent release: ${previousTag}`);
}
}
if (previousTag !== null && previousTag !== undefined) {
console.log(`Will compare ${previousTag}...${currentTag}`);
}
// Generate release notes
// Build the request parameters conditionally
const releaseNotesParams = {
owner: context.repo.owner,
repo: context.repo.repo,
tag_name: currentTag,
};
// Only include previous_tag_name if it exists
if (previousTag !== null && previousTag !== undefined) {
releaseNotesParams.previous_tag_name = previousTag;
}
const { data } = await github.rest.repos.generateReleaseNotes(releaseNotesParams);
// Only save to file if we have actual content
const releaseNotesBody = data.body || '';
if (releaseNotesBody.trim().length > 0) {
const fs = require('fs');
fs.writeFileSync('release-notes.md', releaseNotesBody);
console.log('✅ Generated release notes successfully');
console.log(`Release notes length: ${releaseNotesBody.length} characters`);
} else {
console.log('⚠️ Generated release notes are empty, skipping file creation');
}
return releaseNotesBody;
} catch (error) {
console.log('⚠️ Could not generate release notes:', error.message);
// Return empty string if generation fails (e.g., for first release)
return '';
}
- name: 📝 Extract README Content for Release
id: readme_extract
run: |
# Get the release version without v prefix for replacement
RELEASE_VERSION="${{ needs.build.outputs.version }}"
if [[ $RELEASE_VERSION == v* ]]; then
RELEASE_VERSION="${RELEASE_VERSION#v}"
fi
# Extract introduction (everything before first --- divider)
INTRO=$(awk 'BEGIN{flag=1} /^---$/{flag=0; exit} flag{print}' README.md)
# Extract Quick Start section
QUICKSTART=$(sed -n '/^## ⚡ Quickstart/,/^---$/p' README.md | head -n -1)
# Extract license section including header
LICENSE=$(grep -A 5 "^## License" README.md)
# Replace <version> placeholders with actual version
INTRO=$(echo "$INTRO" | sed "s/<version>/$RELEASE_VERSION/g")
QUICKSTART=$(echo "$QUICKSTART" | sed "s/<version>/$RELEASE_VERSION/g")
# Replace 'latest' placeholders with actual version for release notes
INTRO=$(echo "$INTRO" | sed "s/:latest/:$RELEASE_VERSION/g")
QUICKSTART=$(echo "$QUICKSTART" | sed "s/:latest/:$RELEASE_VERSION/g")
INTRO=$(echo "$INTRO" | sed "s/latest release/$RELEASE_VERSION release/g")
QUICKSTART=$(echo "$QUICKSTART" | sed "s/latest release/$RELEASE_VERSION release/g")
# Handle other 'latest' references
INTRO=$(echo "$INTRO" | sed "s/latest release of $PRODUCT_NAME/$RELEASE_VERSION release of $PRODUCT_NAME/g")
QUICKSTART=$(echo "$QUICKSTART" | sed "s/latest release of $PRODUCT_NAME/$RELEASE_VERSION release of $PRODUCT_NAME/g")
# Add direct download links for $PRODUCT_NAME server - replace download instruction and example paragraph
# Create the $PRODUCT_NAME downloads table with proper newlines using printf
PRODUCT_TABLE=$(printf "%s\n%s\n%s\n%s\n%s\n%s\n%s" \
" | OS | Architecture | Download Link |" \
" |-------|-------------|-------------|" \
" | macOS | ARM64 (Apple Silicon) | [${PRODUCT_NAME_LOWER}-${RELEASE_VERSION}-macos-arm64.zip](https://github.com/${{ github.repository }}/releases/download/v${RELEASE_VERSION}/${PRODUCT_NAME_LOWER}-${RELEASE_VERSION}-macos-arm64.zip) |" \
" | macOS | x64 (Intel) | [${PRODUCT_NAME_LOWER}-${RELEASE_VERSION}-macos-x64.zip](https://github.com/${{ github.repository }}/releases/download/v${RELEASE_VERSION}/${PRODUCT_NAME_LOWER}-${RELEASE_VERSION}-macos-x64.zip) |" \
" | Linux | x64 | [${PRODUCT_NAME_LOWER}-${RELEASE_VERSION}-linux-x64.zip](https://github.com/${{ github.repository }}/releases/download/v${RELEASE_VERSION}/${PRODUCT_NAME_LOWER}-${RELEASE_VERSION}-linux-x64.zip) |" \
" | Linux | ARM64 | [${PRODUCT_NAME_LOWER}-${RELEASE_VERSION}-linux-arm64.zip](https://github.com/${{ github.repository }}/releases/download/v${RELEASE_VERSION}/${PRODUCT_NAME_LOWER}-${RELEASE_VERSION}-linux-arm64.zip) |" \
" | Windows | x64 | [${PRODUCT_NAME_LOWER}-${RELEASE_VERSION}-win-x64.zip](https://github.com/${{ github.repository }}/releases/download/v${RELEASE_VERSION}/${PRODUCT_NAME_LOWER}-${RELEASE_VERSION}-win-x64.zip) |")
# Replace the download instruction with the table using awk for proper handling
QUICKSTART=$(echo "$QUICKSTART" | awk -v table="$PRODUCT_TABLE" '
/Download `'"${PRODUCT_NAME_LOWER}"'-.*-<os>-<arch>\.zip` from the \[.*release\]/ {
print table
next
}
{ print }
')
# Remove the example paragraph that follows (it's now redundant with the table)
QUICKSTART=$(echo "$QUICKSTART" | sed '/For example, if you are using a MacOS machine with a Apple Silicon (ARM64) processor, you would download `'"${PRODUCT_NAME_LOWER}-${RELEASE_VERSION}"'-macos-arm64\.zip`\./d')
# Create the React Vanilla Sample App downloads table with proper newlines using printf
VANILLA_SAMPLE_TABLE=$(printf "%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s" \
" | OS | Architecture | Download Link |" \
" |-------|-------------|-------------|" \
" | macOS | ARM64 (Apple Silicon) | [sample-app-react-vanilla-${RELEASE_VERSION}-macos-arm64.zip](https://github.com/${{ github.repository }}/releases/download/v${RELEASE_VERSION}/sample-app-react-vanilla-${RELEASE_VERSION}-macos-arm64.zip) |" \
" | macOS | x64 (Intel) | [sample-app-react-vanilla-${RELEASE_VERSION}-macos-x64.zip](https://github.com/${{ github.repository }}/releases/download/v${RELEASE_VERSION}/sample-app-react-vanilla-${RELEASE_VERSION}-macos-x64.zip) |" \
" | Linux | x64 | [sample-app-react-vanilla-${RELEASE_VERSION}-linux-x64.zip](https://github.com/${{ github.repository }}/releases/download/v${RELEASE_VERSION}/sample-app-react-vanilla-${RELEASE_VERSION}-linux-x64.zip) |" \
" | Linux | ARM64 | [sample-app-react-vanilla-${RELEASE_VERSION}-linux-arm64.zip](https://github.com/${{ github.repository }}/releases/download/v${RELEASE_VERSION}/sample-app-react-vanilla-${RELEASE_VERSION}-linux-arm64.zip) |" \
" | Windows | x64 | [sample-app-react-vanilla-${RELEASE_VERSION}-win-x64.zip](https://github.com/${{ github.repository }}/releases/download/v${RELEASE_VERSION}/sample-app-react-vanilla-${RELEASE_VERSION}-win-x64.zip) |")
# Create the React SDK Sample App downloads table with proper newlines using printf
SDK_SAMPLE_TABLE=$(printf "%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s" \
" | OS | Architecture | Download Link |" \
" |-------|-------------|-------------|" \
" | macOS | ARM64 (Apple Silicon) | [sample-app-react-sdk-${RELEASE_VERSION}-macos-arm64.zip](https://github.com/${{ github.repository }}/releases/download/v${RELEASE_VERSION}/sample-app-react-sdk-${RELEASE_VERSION}-macos-arm64.zip) |" \
" | macOS | x64 (Intel) | [sample-app-react-sdk-${RELEASE_VERSION}-macos-x64.zip](https://github.com/${{ github.repository }}/releases/download/v${RELEASE_VERSION}/sample-app-react-sdk-${RELEASE_VERSION}-macos-x64.zip) |" \
" | Linux | x64 | [sample-app-react-sdk-${RELEASE_VERSION}-linux-x64.zip](https://github.com/${{ github.repository }}/releases/download/v${RELEASE_VERSION}/sample-app-react-sdk-${RELEASE_VERSION}-linux-x64.zip) |" \
" | Linux | ARM64 | [sample-app-react-sdk-${RELEASE_VERSION}-linux-arm64.zip](https://github.com/${{ github.repository }}/releases/download/v${RELEASE_VERSION}/sample-app-react-sdk-${RELEASE_VERSION}-linux-arm64.zip) |" \
" | Windows | x64 | [sample-app-react-sdk-${RELEASE_VERSION}-win-x64.zip](https://github.com/${{ github.repository }}/releases/download/v${RELEASE_VERSION}/sample-app-react-sdk-${RELEASE_VERSION}-win-x64.zip) |")
# Create the React API-based Sample App downloads table with proper newlines using printf
API_SAMPLE_TABLE=$(printf "%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s" \
" | OS | Architecture | Download Link |" \
" |-------|-------------|-------------|" \
" | macOS | ARM64 (Apple Silicon) | [sample-app-react-api-based-${RELEASE_VERSION}-macos-arm64.zip](https://github.com/${{ github.repository }}/releases/download/v${RELEASE_VERSION}/sample-app-react-api-based-${RELEASE_VERSION}-macos-arm64.zip) |" \
" | macOS | x64 (Intel) | [sample-app-react-api-based-${RELEASE_VERSION}-macos-x64.zip](https://github.com/${{ github.repository }}/releases/download/v${RELEASE_VERSION}/sample-app-react-api-based-${RELEASE_VERSION}-macos-x64.zip) |" \
" | Linux | x64 | [sample-app-react-api-based-${RELEASE_VERSION}-linux-x64.zip](https://github.com/${{ github.repository }}/releases/download/v${RELEASE_VERSION}/sample-app-react-api-based-${RELEASE_VERSION}-linux-x64.zip) |" \
" | Linux | ARM64 | [sample-app-react-api-based-${RELEASE_VERSION}-linux-arm64.zip](https://github.com/${{ github.repository }}/releases/download/v${RELEASE_VERSION}/sample-app-react-api-based-${RELEASE_VERSION}-linux-arm64.zip) |" \
" | Windows | x64 | [sample-app-react-api-based-${RELEASE_VERSION}-win-x64.zip](https://github.com/${{ github.repository }}/releases/download/v${RELEASE_VERSION}/sample-app-react-api-based-${RELEASE_VERSION}-win-x64.zip) |")
# Replace the vanilla sample app download instruction with its table
QUICKSTART=$(echo "$QUICKSTART" | awk -v table="$VANILLA_SAMPLE_TABLE" '
/Download `sample-app-react-vanilla-.*-<os>-<arch>\.zip` from the \[.*release\]/ {
print table
next
}
{ print }
')
# Replace the SDK sample app download instruction with its table
QUICKSTART=$(echo "$QUICKSTART" | awk -v table="$SDK_SAMPLE_TABLE" '
/Download `sample-app-react-sdk-.*-<os>-<arch>\.zip` from the \[.*release\]/ {
print table
next
}
{ print }
')
# Replace the API-based sample app download instruction with its table
QUICKSTART=$(echo "$QUICKSTART" | awk -v table="$API_SAMPLE_TABLE" '
/Download `sample-app-react-api-based-.*-<os>-<arch>\.zip` from the \[.*release\]/ {
print table
next
}
{ print }
')
# Read the generated release notes if they exist
CHANGELOG=""
if [ -f "release-notes.md" ]; then
CHANGELOG=$(cat release-notes.md)
echo "✅ Found generated release notes"
else
echo "⚠️ No release notes file found, skipping changelog"
fi
# Combine for release description with changelog
echo "RELEASE_BODY<<EOF" >> $GITHUB_ENV
echo "$INTRO" >> $GITHUB_ENV
echo "" >> $GITHUB_ENV
# Insert changelog
if [ -n "$CHANGELOG" ]; then
echo "$CHANGELOG" >> $GITHUB_ENV
echo "" >> $GITHUB_ENV
fi
echo "$QUICKSTART" >> $GITHUB_ENV
echo "" >> $GITHUB_ENV
echo "$LICENSE" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
- name: 📦 Create GitHub Release
id: create_release
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2
with:
tag_name: ${{ steps.version.outputs.version }}
name: ${{ env.PRODUCT_NAME }} ${{ steps.version.outputs.version }}
draft: false
prerelease: ${{ needs.build.outputs.prerelease }}
files: target/dist/*.zip
body: ${{ env.RELEASE_BODY }}
generate_release_notes: false
- name: 🔔 Set Release Info
id: release_info
run: |
RELEASE_VERSION="${{ steps.version.outputs.version }}"
echo "release_name=${PRODUCT_NAME} ${RELEASE_VERSION}" >> $GITHUB_OUTPUT
echo "release_tag=${RELEASE_VERSION}" >> $GITHUB_OUTPUT
echo "release_url=https://github.com/${{ github.repository }}/releases/tag/${RELEASE_VERSION}" >> $GITHUB_OUTPUT
- name: 🔔 Send Release Notification
uses: ./.github/actions/release-notification
with:
webhook: ${{ secrets.GOOGLE_CHAT_WEBHOOK }}
product-name: ${{ env.PRODUCT_NAME }}
release-name: ${{ steps.release_info.outputs.release_name }}
release-tag: ${{ steps.release_info.outputs.release_tag }}
release-url: ${{ steps.release_info.outputs.release_url }}
trigger-performance-test:
name: 🧪 Trigger Performance Test
runs-on: ubuntu-latest
needs: [build, release]
if: ${{ needs.build.outputs.run_performance_test == 'true' }}
steps:
- name: 📥 Checkout Code
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
ref: ${{ env.RELEASE_BRANCH }}
- name: ⚡ Trigger Performance Test Workflows
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
with:
github-token: ${{ secrets.THUNDER_GITHUB_BOT_TOKEN }}
script: |
// Construct the pack URL using the release version
const releaseTag = '${{ needs.release.outputs.release_tag }}';
const productNameLower = process.env.PRODUCT_NAME_LOWER;
const serverDistributionUrl = `https://github.com/${{ github.repository }}/releases/download/${releaseTag}/${productNameLower}-${releaseTag.replace('v', '')}-linux-x64.zip`;
const pushBenchmarkToGitHub = ${{ github.repository == 'asgardeo/thunder' }};
try {
console.log(`🔄 Triggering performance test workflow`);
await github.rest.actions.createWorkflowDispatch({
owner: 'asgardeo',
repo: 'thunder-performance',
workflow_id: 'vm-perf-workflow.yml',
ref: 'main',
inputs: {
THUNDER_PACK_URL: serverDistributionUrl,
CONCURRENCY: '50,200,500',
PUSH_BENCHMARKS_TO_GITHUB: pushBenchmarkToGitHub
}
});
console.log(`✅ Performance test workflow triggered successfully`);
} catch (error) {
console.error(`❌ Failed to trigger performance test workflow:`, error);
}
// Return success
return {success: true};
trigger-deploy-docs:
name: 📚 Trigger Deploy Documentation
runs-on: ubuntu-latest
needs: [build, release]
steps:
- name: 📚 Trigger Deploy Documentation Workflow
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
with:
github-token: ${{ secrets.THUNDER_AUTOMATION_BOT }}
script: |
try {
console.log('🔄 Triggering deploy-docs workflow...');
await github.rest.actions.createWorkflowDispatch({
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: 'deploy-docs.yml',
ref: 'main',
inputs: {
'use-artifact': ''
}
});
console.log('✅ deploy-docs workflow triggered successfully');
} catch (error) {
console.error('❌ Failed to trigger deploy-docs workflow:', error);
core.setFailed(error.message);
}