Create Release #24
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |