Skip to content

Create Release

Create Release #24

name: Create Release
on:
workflow_dispatch:
inputs:
version_type:
description: 'Version type'
required: true
default: 'patch'
type: choice
options:
- major
- minor
- patch
deploy_to:
description: 'Initial deployment target'
required: true
default: 'all'
type: choice
options:
- all
- staging
- prod
- none
jobs:
create-branch:
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
new_version: ${{ steps.new-version.outputs.new_version }}
branch_name: ${{ steps.new-version.outputs.branch_name }}
steps:
- name: Checkout main branch
uses: actions/checkout@v4
with:
ref: main
fetch-depth: 0
# ACTIONS_TOKEN required to push to protected main branch
token: ${{ secrets.ACTIONS_TOKEN || github.token }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
- name: Get current version
id: current-version
run: |
CURRENT_VERSION=$(node -p "require('./package.json').version")
echo "current_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
- name: Calculate new version
id: new-version
run: |
CURRENT_VERSION="${{ steps.current-version.outputs.current_version }}"
VERSION_TYPE="${{ inputs.version_type }}"
IFS='.' read -r major minor patch <<< "$CURRENT_VERSION"
case "$VERSION_TYPE" in
major)
major=$((major + 1))
minor=0
patch=0
;;
minor)
minor=$((minor + 1))
patch=0
;;
patch)
patch=$((patch + 1))
;;
esac
NEW_VERSION="${major}.${minor}.${patch}"
BRANCH_NAME="release/${NEW_VERSION}"
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT
echo "New version: $NEW_VERSION"
echo "Branch name: $BRANCH_NAME"
- name: Check if branch already exists
run: |
BRANCH_NAME="${{ steps.new-version.outputs.branch_name }}"
# Check if branch exists locally or remotely
if git show-ref --verify --quiet refs/heads/$BRANCH_NAME || git show-ref --verify --quiet refs/remotes/origin/$BRANCH_NAME; then
echo "❌ Branch $BRANCH_NAME already exists"
exit 1
else
echo "✅ Branch $BRANCH_NAME does not exist, proceeding..."
fi
- name: Configure Git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Update main branch version
run: |
CURRENT_VERSION="${{ steps.current-version.outputs.current_version }}"
NEW_VERSION="${{ steps.new-version.outputs.new_version }}"
echo "Updating main branch version from $CURRENT_VERSION to $NEW_VERSION"
# Update version in package.json
npm version "$NEW_VERSION" --no-git-tag-version
# Install dependencies to update lock file
npm install
# Commit and push to main
git add package.json package-lock.json
git commit -m "Release v$NEW_VERSION"
git push origin main
- name: Create release branch
run: |
BRANCH_NAME="${{ steps.new-version.outputs.branch_name }}"
echo "Creating release branch: $BRANCH_NAME"
# Create new branch from updated main
git checkout -b "$BRANCH_NAME"
# Push the release branch
git push origin "$BRANCH_NAME"
echo "✅ Release branch $BRANCH_NAME created and pushed"
create-tag:
needs: create-branch
permissions:
contents: write
uses: ./.github/workflows/tag-release.yml
with:
branch_ref: ${{ needs.create-branch.outputs.branch_name }}
secrets: inherit
# Check if ACTIONS_TOKEN is available for dispatch-based deployments
# Dispatch gives independent workflow runs for better monitoring
# Reusable workflows are fallback when PAT not available
check-deploy-method:
needs: create-tag
runs-on: ubuntu-latest
outputs:
use_dispatch: ${{ steps.check.outputs.use_dispatch }}
steps:
- name: Check for ACTIONS_TOKEN
id: check
run: |
if [ "${{ secrets.ACTIONS_TOKEN != '' }}" = "true" ]; then
echo "use_dispatch=true" >> $GITHUB_OUTPUT
echo "✅ ACTIONS_TOKEN available - will dispatch independent workflow runs"
else
echo "use_dispatch=false" >> $GITHUB_OUTPUT
echo "ℹ️ ACTIONS_TOKEN not set - using reusable workflows (bundled in this run)"
fi
# ============================================================================
# DISPATCH-BASED DEPLOYMENTS (when ACTIONS_TOKEN is available)
# Each deployment runs as an independent workflow for better monitoring
# ============================================================================
deploy-staging-dispatch:
needs: [create-branch, create-tag, check-deploy-method]
if: |
needs.check-deploy-method.outputs.use_dispatch == 'true' &&
(inputs.deploy_to == 'staging' || inputs.deploy_to == 'all')
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
run_id: ${{ steps.dispatch.outputs.run_id }}
steps:
- name: Dispatch staging deployment
id: dispatch
env:
GH_TOKEN: ${{ secrets.ACTIONS_TOKEN }}
run: |
echo "🚀 Dispatching staging deployment from tag ${{ needs.create-tag.outputs.tag_name }}"
# Trigger the workflow
gh workflow run staging.yml \
--repo ${{ github.repository }} \
--ref ${{ needs.create-tag.outputs.tag_name }}
# Wait briefly for the run to be created
sleep 5
# Get the run ID of the triggered workflow
RUN_ID=$(gh run list \
--repo ${{ github.repository }} \
--workflow=staging.yml \
--limit 1 \
--json databaseId \
--jq '.[0].databaseId')
echo "run_id=$RUN_ID" >> $GITHUB_OUTPUT
echo "✅ Staging deployment dispatched: https://github.com/${{ github.repository }}/actions/runs/$RUN_ID"
deploy-prod-dispatch:
needs:
[create-branch, create-tag, check-deploy-method, deploy-staging-dispatch]
if: |
always() &&
needs.check-deploy-method.outputs.use_dispatch == 'true' &&
(inputs.deploy_to == 'prod' || inputs.deploy_to == 'all') &&
(needs.deploy-staging-dispatch.result == 'success' || needs.deploy-staging-dispatch.result == 'skipped')
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
run_id: ${{ steps.dispatch.outputs.run_id }}
steps:
- name: Wait before prod deployment
if: inputs.deploy_to == 'all'
run: |
echo "⏳ Waiting 30 seconds before dispatching prod deployment..."
echo " (prod.yml has concurrency group - will queue if staging still running)"
sleep 30
- name: Dispatch prod deployment
id: dispatch
env:
GH_TOKEN: ${{ secrets.ACTIONS_TOKEN }}
run: |
echo "🚀 Dispatching prod deployment from tag ${{ needs.create-tag.outputs.tag_name }}"
# Trigger the workflow
gh workflow run prod.yml \
--repo ${{ github.repository }} \
--ref ${{ needs.create-tag.outputs.tag_name }}
# Wait briefly for the run to be created
sleep 5
# Get the run ID of the triggered workflow
RUN_ID=$(gh run list \
--repo ${{ github.repository }} \
--workflow=prod.yml \
--limit 1 \
--json databaseId \
--jq '.[0].databaseId')
echo "run_id=$RUN_ID" >> $GITHUB_OUTPUT
echo "✅ Prod deployment dispatched: https://github.com/${{ github.repository }}/actions/runs/$RUN_ID"
# ============================================================================
# REUSABLE WORKFLOW DEPLOYMENTS (fallback when ACTIONS_TOKEN not available)
# Deployments run as part of this workflow - less visibility but no PAT needed
# ============================================================================
deploy-staging-reusable:
needs: [create-branch, create-tag, check-deploy-method]
if: |
needs.check-deploy-method.outputs.use_dispatch == 'false' &&
(inputs.deploy_to == 'staging' || inputs.deploy_to == 'all')
permissions:
contents: read
deployments: write
id-token: write
security-events: write
uses: ./.github/workflows/staging.yml
secrets: inherit
deploy-prod-reusable:
needs:
[create-branch, create-tag, check-deploy-method, deploy-staging-reusable]
if: |
always() &&
needs.check-deploy-method.outputs.use_dispatch == 'false' &&
(inputs.deploy_to == 'prod' || inputs.deploy_to == 'all') &&
(needs.deploy-staging-reusable.result == 'success' || needs.deploy-staging-reusable.result == 'skipped')
permissions:
contents: read
deployments: write
id-token: write
security-events: write
uses: ./.github/workflows/prod.yml
secrets: inherit
create-summary:
needs:
[
create-branch,
create-tag,
check-deploy-method,
deploy-staging-dispatch,
deploy-prod-dispatch,
deploy-staging-reusable,
deploy-prod-reusable,
]
if: always()
runs-on: ubuntu-latest
timeout-minutes: 2
steps:
- name: Create summary
env:
USE_DISPATCH: ${{ needs.check-deploy-method.outputs.use_dispatch }}
STAGING_RUN_ID: ${{ needs.deploy-staging-dispatch.outputs.run_id }}
PROD_RUN_ID: ${{ needs.deploy-prod-dispatch.outputs.run_id }}
run: |
echo "## RoboLedger App Release Created" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Property | Value |" >> $GITHUB_STEP_SUMMARY
echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| Version | ${{ needs.create-tag.outputs.version }} |" >> $GITHUB_STEP_SUMMARY
echo "| Tag | \`${{ needs.create-tag.outputs.tag_name }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Branch | \`${{ needs.create-branch.outputs.branch_name }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Type | ${{ inputs.version_type }} |" >> $GITHUB_STEP_SUMMARY
echo "| Deploy Target | ${{ inputs.deploy_to }} |" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Release URL:** ${{ needs.create-tag.outputs.release_url }}" >> $GITHUB_STEP_SUMMARY
echo "**Live App:** [roboledger.ai](https://roboledger.ai)" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Show deployment method and links
if [ "$USE_DISPATCH" = "true" ]; then
echo "### Deployments (Independent Workflows)" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Deployments are running as **separate workflow runs** for independent monitoring." >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ -n "$STAGING_RUN_ID" ]; then
echo "- **Staging**: [View Run](https://github.com/${{ github.repository }}/actions/runs/$STAGING_RUN_ID)" >> $GITHUB_STEP_SUMMARY
fi
if [ -n "$PROD_RUN_ID" ]; then
echo "- **Production**: [View Run](https://github.com/${{ github.repository }}/actions/runs/$PROD_RUN_ID)" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "Monitor all deployments: \`gh run list --workflow=staging.yml\` / \`gh run list --workflow=prod.yml\`" >> $GITHUB_STEP_SUMMARY
else
echo "### Deployments (Reusable Workflows)" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Deployments are running as **part of this workflow** (ACTIONS_TOKEN not configured)." >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "To enable independent deployment tracking, set the \`ACTIONS_TOKEN\` secret." >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${{ inputs.deploy_to }}" = "staging" ]; then
echo "### Next Steps" >> $GITHUB_STEP_SUMMARY
echo "1. Staging deployment has been triggered from tag ${{ needs.create-tag.outputs.tag_name }}" >> $GITHUB_STEP_SUMMARY
echo "2. Test the release in staging environment" >> $GITHUB_STEP_SUMMARY
echo "3. When ready for production: \`gh workflow run prod.yml --ref ${{ needs.create-tag.outputs.tag_name }}\`" >> $GITHUB_STEP_SUMMARY
elif [ "${{ inputs.deploy_to }}" = "prod" ]; then
echo "### Next Steps" >> $GITHUB_STEP_SUMMARY
echo "1. Production deployment has been triggered from tag ${{ needs.create-tag.outputs.tag_name }}" >> $GITHUB_STEP_SUMMARY
echo "2. The git tag is available for future deployments" >> $GITHUB_STEP_SUMMARY
elif [ "${{ inputs.deploy_to }}" = "all" ]; then
echo "### Next Steps" >> $GITHUB_STEP_SUMMARY
echo "1. Staging deployment triggered from tag ${{ needs.create-tag.outputs.tag_name }}" >> $GITHUB_STEP_SUMMARY
echo "2. Production deployment will follow (queued via concurrency group)" >> $GITHUB_STEP_SUMMARY
else
echo "### Next Steps" >> $GITHUB_STEP_SUMMARY
echo "1. Release tag ${{ needs.create-tag.outputs.tag_name }} created (no deployment)" >> $GITHUB_STEP_SUMMARY
echo "2. To deploy to staging: \`gh workflow run staging.yml --ref ${{ needs.create-tag.outputs.tag_name }}\`" >> $GITHUB_STEP_SUMMARY
echo "3. To deploy to production: \`gh workflow run prod.yml --ref ${{ needs.create-tag.outputs.tag_name }}\`" >> $GITHUB_STEP_SUMMARY
fi