Skip to content

Create Release

Create Release #100

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
permissions:
contents: write
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@v7
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@v7
with:
node-version: '24'
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 and package-lock.json. npm version
# writes both, so no install runs here and the registry is off the
# release path.
npm version "$NEW_VERSION" --no-git-tag-version
# 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: RoboFinSystems/robosystems/.github/workflows/tag-release.yml@main
with:
branch_ref: ${{ needs.create-branch.outputs.branch_name }}
product_name: 'RoboLedger App'
product_description: 'Financial reporting & accounting web application'
project_kind: 'Next.js frontend application'
live_url: 'https://roboledger.ai'
secrets: inherit
# ============================================================================
# DEPLOYMENTS
# Each deployment is dispatched as an independent workflow run for better
# monitoring. workflow_dispatch is exempt from GitHub's "GITHUB_TOKEN events
# don't trigger workflows" rule, so this works with the default token too;
# ACTIONS_TOKEN is preferred when available.
# ============================================================================
deploy-staging:
needs: [create-tag]
if: inputs.deploy_to == 'staging' || inputs.deploy_to == 'all'
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
actions: write
outputs:
run_id: ${{ steps.dispatch.outputs.run_id }}
steps:
- name: Dispatch staging deployment
id: dispatch
env:
GH_TOKEN: ${{ secrets.ACTIONS_TOKEN || github.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 (dispatched at the tag,
# so filtering by the tag ref avoids grabbing an unrelated run)
RUN_ID=$(gh run list \
--repo ${{ github.repository }} \
--workflow=staging.yml \
--branch ${{ needs.create-tag.outputs.tag_name }} \
--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:
needs: [create-tag, deploy-staging]
if: |
!cancelled() &&
needs.create-tag.result == 'success' &&
(inputs.deploy_to == 'prod' || inputs.deploy_to == 'all') &&
(needs.deploy-staging.result == 'success' || needs.deploy-staging.result == 'skipped')
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
actions: write
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 || github.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 (dispatched at the tag,
# so filtering by the tag ref avoids grabbing an unrelated run)
RUN_ID=$(gh run list \
--repo ${{ github.repository }} \
--workflow=prod.yml \
--branch ${{ needs.create-tag.outputs.tag_name }} \
--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"
create-summary:
needs: [create-branch, create-tag, deploy-staging, deploy-prod]
if: always()
runs-on: ubuntu-latest
timeout-minutes: 2
permissions: {}
steps:
- name: Create summary
env:
STAGING_RUN_ID: ${{ needs.deploy-staging.outputs.run_id }}
PROD_RUN_ID: ${{ needs.deploy-prod.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 links
if [ "${{ inputs.deploy_to }}" != "none" ]; then
echo "### Deployments" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Deployments run 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
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