From 054852fcde7a4f8d607b31e74abd2b936f596762 Mon Sep 17 00:00:00 2001 From: Achintha Isuru Date: Thu, 5 Mar 2026 11:41:45 -0500 Subject: [PATCH] feat(ci): add GitHub Actions workflows for mobile releases and hotfixes - Add mobile-release.yml workflow: - Manual trigger with app (worker/client) and environment (dev/stage/prod) selection - Version validation (semantic versioning) - Tag creation with format: krow-withus--mobile/-vX.Y.Z - GitHub Release creation with CHANGELOG extraction - Release naming: 'Krow With Us - Worker Mobile - DEV - v0.1.0' - Pre-release support - Add mobile-hotfix.yml workflow: - Emergency production fix automation - Creates hotfix branch from production tag - Auto-increments PATCH version - Updates pubspec.yaml and CHANGELOG.md - Creates PR with hotfix instructions - Follows documented hotfix process Both workflows support staff (worker) and client mobile apps independently. Implements mobile release strategy from docs/release/ --- .github/workflows/mobile-hotfix.yml | 322 +++++++++++++++++++++++++++ .github/workflows/mobile-release.yml | 215 ++++++++++++++++++ 2 files changed, 537 insertions(+) create mode 100644 .github/workflows/mobile-hotfix.yml create mode 100644 .github/workflows/mobile-release.yml diff --git a/.github/workflows/mobile-hotfix.yml b/.github/workflows/mobile-hotfix.yml new file mode 100644 index 00000000..cd2df197 --- /dev/null +++ b/.github/workflows/mobile-hotfix.yml @@ -0,0 +1,322 @@ +name: Mobile Hotfix + +on: + workflow_dispatch: + inputs: + app: + description: 'Mobile App' + required: true + type: choice + options: + - worker + - client + production_tag: + description: 'Current Production Tag (e.g., krow-withus-worker-mobile/prod-v0.1.0)' + required: true + type: string + issue_description: + description: 'Brief issue description' + required: true + type: string + +jobs: + create-hotfix-branch: + name: 🚨 Create Hotfix Branch + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + + steps: + - name: 📥 Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: 🔍 Validate production tag exists + id: validate_tag + run: | + TAG="${{ github.event.inputs.production_tag }}" + + if ! git rev-parse "$TAG" >/dev/null 2>&1; then + echo "❌ Error: Production tag '$TAG' does not exist" + echo "Available tags:" + git tag -l "krow-withus-*-mobile/prod-*" | tail -10 + exit 1 + fi + + echo "✅ Production tag exists: $TAG" + + # Extract version from tag + VERSION=$(echo "$TAG" | grep -oP 'v\K[0-9]+\.[0-9]+\.[0-9]+' || echo "") + if [ -z "$VERSION" ]; then + echo "❌ Error: Could not extract version from tag" + exit 1 + fi + + echo "current_version=${VERSION}" >> $GITHUB_OUTPUT + echo "📌 Current production version: $VERSION" + + - name: 🔢 Calculate hotfix version + id: hotfix_version + run: | + CURRENT="${{ steps.validate_tag.outputs.current_version }}" + + # Split version into parts + IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT" + + # Increment PATCH version + NEW_PATCH=$((PATCH + 1)) + HOTFIX_VERSION="${MAJOR}.${MINOR}.${NEW_PATCH}" + + echo "hotfix_version=${HOTFIX_VERSION}" >> $GITHUB_OUTPUT + echo "🆕 Hotfix version: $HOTFIX_VERSION" + + - name: 🌿 Generate branch name + id: branch + run: | + APP="${{ github.event.inputs.app }}" + VERSION="${{ steps.hotfix_version.outputs.hotfix_version }}" + + BRANCH_NAME="hotfix/krow-withus-${APP}-mobile-v${VERSION}" + echo "branch_name=${BRANCH_NAME}" >> $GITHUB_OUTPUT + echo "🌿 Branch to create: $BRANCH_NAME" + + - name: 🔍 Check if hotfix branch already exists + run: | + BRANCH="${{ steps.branch.outputs.branch_name }}" + + if git ls-remote --heads origin "$BRANCH" | grep -q "$BRANCH"; then + echo "❌ Error: Branch $BRANCH already exists" + exit 1 + fi + + echo "✅ Branch does not exist, proceeding..." + + - name: 🌿 Create hotfix branch from production tag + run: | + TAG="${{ github.event.inputs.production_tag }}" + BRANCH="${{ steps.branch.outputs.branch_name }}" + + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + # Checkout the production tag + git checkout "$TAG" + + # Create new branch + git checkout -b "$BRANCH" + + echo "✅ Created branch $BRANCH from tag $TAG" + + - name: 📝 Update version files + id: update_versions + run: | + APP="${{ github.event.inputs.app }}" + HOTFIX_VERSION="${{ steps.hotfix_version.outputs.hotfix_version }}" + + if [ "$APP" = "worker" ]; then + PUBSPEC_PATH="apps/mobile/apps/staff/pubspec.yaml" + CHANGELOG_PATH="apps/mobile/apps/staff/CHANGELOG.md" + APP_NAME="Staff Mobile App" + else + PUBSPEC_PATH="apps/mobile/apps/client/pubspec.yaml" + CHANGELOG_PATH="apps/mobile/apps/client/CHANGELOG.md" + APP_NAME="Client Mobile App" + fi + + # Update pubspec.yaml version + if [ -f "$PUBSPEC_PATH" ]; then + # Extract current version and build number + CURRENT_VERSION_LINE=$(grep "^version:" "$PUBSPEC_PATH") + CURRENT_BUILD=$(echo "$CURRENT_VERSION_LINE" | grep -oP '\+\K[0-9]+' || echo "1") + NEW_BUILD=$((CURRENT_BUILD + 1)) + + # Update version line + sed -i "s/^version:.*/version: ${HOTFIX_VERSION}+${NEW_BUILD}/" "$PUBSPEC_PATH" + + echo "✅ Updated $PUBSPEC_PATH to ${HOTFIX_VERSION}+${NEW_BUILD}" + echo "updated_files=true" >> $GITHUB_OUTPUT + else + echo "⚠️ Warning: $PUBSPEC_PATH not found" + echo "updated_files=false" >> $GITHUB_OUTPUT + fi + + - name: 📋 Add CHANGELOG entry + run: | + APP="${{ github.event.inputs.app }}" + HOTFIX_VERSION="${{ steps.hotfix_version.outputs.hotfix_version }}" + ISSUE="${{ github.event.inputs.issue_description }}" + + if [ "$APP" = "worker" ]; then + CHANGELOG_PATH="apps/mobile/apps/staff/CHANGELOG.md" + APP_NAME="Staff Mobile App" + else + CHANGELOG_PATH="apps/mobile/apps/client/CHANGELOG.md" + APP_NAME="Client Mobile App" + fi + + if [ -f "$CHANGELOG_PATH" ]; then + DATE=$(date +%Y-%m-%d) + + # Create hotfix entry + HOTFIX_ENTRY="## [${HOTFIX_VERSION}] - ${DATE} - HOTFIX + +### Fixed +- ${ISSUE} + +--- + +" + + # Insert after the first line (title) + sed -i "1 a\\ +\\ +$HOTFIX_ENTRY" "$CHANGELOG_PATH" + + echo "✅ Added CHANGELOG entry for hotfix $HOTFIX_VERSION" + else + echo "⚠️ Warning: $CHANGELOG_PATH not found" + fi + + - name: 💾 Commit version changes + run: | + HOTFIX_VERSION="${{ steps.hotfix_version.outputs.hotfix_version }}" + ISSUE="${{ github.event.inputs.issue_description }}" + + git add -A + git commit -m "chore: prepare hotfix v${HOTFIX_VERSION} + +HOTFIX: ${ISSUE} + +- Bump version to ${HOTFIX_VERSION} +- Add CHANGELOG entry +- Ready for bug fix commits + +From production tag: ${{ github.event.inputs.production_tag }}" + + echo "✅ Committed version changes" + + - name: 🚀 Push hotfix branch + run: | + BRANCH="${{ steps.branch.outputs.branch_name }}" + + git push origin "$BRANCH" + + echo "✅ Pushed branch: $BRANCH" + + - name: 📄 Create Pull Request + id: create_pr + env: + GH_TOKEN: ${{ github.token }} + run: | + BRANCH="${{ steps.branch.outputs.branch_name }}" + HOTFIX_VERSION="${{ steps.hotfix_version.outputs.hotfix_version }}" + ISSUE="${{ github.event.inputs.issue_description }}" + APP="${{ github.event.inputs.app }}" + + if [ "$APP" = "worker" ]; then + APP_DISPLAY="Worker Mobile" + else + APP_DISPLAY="Client Mobile" + fi + + PR_TITLE="🚨 HOTFIX: ${APP_DISPLAY} v${HOTFIX_VERSION} - ${ISSUE}" + + PR_BODY="## 🚨 HOTFIX - URGENT PRODUCTION FIX + +**App:** ${APP_DISPLAY} +**Version:** ${HOTFIX_VERSION} +**From:** \`${{ github.event.inputs.production_tag }}\` + +### Issue +${ISSUE} + +### Impact + + +### Solution + + +### Testing + + +--- + +## ⚠️ Hotfix Process + +1. ✅ Hotfix branch created +2. ⏳ **NEXT:** Make your bug fix commits to this branch +3. ⏳ Test the fix locally +4. ⏳ Request expedited review (< 15 minutes) +5. ⏳ Merge to main and create production tag + +### To add your fix: +\`\`\`bash +git checkout $BRANCH +# Make your changes +git commit -m \"fix: [description]\" +git push origin $BRANCH +\`\`\` + +### After merging: +\`\`\`bash +# Tag and release +git checkout main +git pull origin main +git tag -a krow-withus-${APP}-mobile/prod-v${HOTFIX_VERSION} -m \"HOTFIX: ${ISSUE}\" +git push origin krow-withus-${APP}-mobile/prod-v${HOTFIX_VERSION} +\`\`\` + +--- + +**Ref:** [Hotfix Process Documentation](../docs/release/HOTFIX_PROCESS.md)" + + # Create PR + PR_URL=$(gh pr create \ + --base main \ + --head "$BRANCH" \ + --title "$PR_TITLE" \ + --body "$PR_BODY" \ + --label "hotfix,urgent,production") + + echo "pr_url=${PR_URL}" >> $GITHUB_OUTPUT + echo "✅ Pull Request created: $PR_URL" + + - name: 📊 Hotfix Summary + run: | + echo "## 🚨 Hotfix Branch Created" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**App:** ${{ github.event.inputs.app }}" >> $GITHUB_STEP_SUMMARY + echo "**Issue:** ${{ github.event.inputs.issue_description }}" >> $GITHUB_STEP_SUMMARY + echo "**From Tag:** \`${{ github.event.inputs.production_tag }}\`" >> $GITHUB_STEP_SUMMARY + echo "**Current Version:** ${{ steps.validate_tag.outputs.current_version }}" >> $GITHUB_STEP_SUMMARY + echo "**Hotfix Version:** ${{ steps.hotfix_version.outputs.hotfix_version }}" >> $GITHUB_STEP_SUMMARY + echo "**Branch:** \`${{ steps.branch.outputs.branch_name }}\`" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### 🔧 Next Steps" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "1. **Checkout the hotfix branch:**" >> $GITHUB_STEP_SUMMARY + echo " \`\`\`bash" >> $GITHUB_STEP_SUMMARY + echo " git fetch origin" >> $GITHUB_STEP_SUMMARY + echo " git checkout ${{ steps.branch.outputs.branch_name }}" >> $GITHUB_STEP_SUMMARY + echo " \`\`\`" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "2. **Make your bug fix(es)** - Keep changes minimal!" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "3. **Test locally** - Verify the fix works" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "4. **Request expedited review** - Target < 15 minutes" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "5. **Merge PR and create production tag:**" >> $GITHUB_STEP_SUMMARY + echo " \`\`\`bash" >> $GITHUB_STEP_SUMMARY + echo " git checkout main" >> $GITHUB_STEP_SUMMARY + echo " git pull origin main" >> $GITHUB_STEP_SUMMARY + echo " git tag -a krow-withus-${{ github.event.inputs.app }}-mobile/prod-v${{ steps.hotfix_version.outputs.hotfix_version }} -m \"HOTFIX: ${{ github.event.inputs.issue_description }}\"" >> $GITHUB_STEP_SUMMARY + echo " git push origin krow-withus-${{ github.event.inputs.app }}-mobile/prod-v${{ steps.hotfix_version.outputs.hotfix_version }}" >> $GITHUB_STEP_SUMMARY + echo " \`\`\`" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [ -n "${{ steps.create_pr.outputs.pr_url }}" ]; then + echo "**Pull Request:** ${{ steps.create_pr.outputs.pr_url }}" >> $GITHUB_STEP_SUMMARY + fi diff --git a/.github/workflows/mobile-release.yml b/.github/workflows/mobile-release.yml new file mode 100644 index 00000000..00dba7d3 --- /dev/null +++ b/.github/workflows/mobile-release.yml @@ -0,0 +1,215 @@ +name: Mobile Release + +on: + workflow_dispatch: + inputs: + app: + description: 'Mobile App' + required: true + type: choice + options: + - worker + - client + environment: + description: 'Environment' + required: true + type: choice + options: + - dev + - stage + - prod + version: + description: 'Version (e.g., 0.1.0)' + required: true + type: string + create_github_release: + description: 'Create GitHub Release' + required: true + type: boolean + default: true + prerelease: + description: 'Mark as Pre-release' + required: false + type: boolean + default: false + +jobs: + validate-and-create-release: + name: 🚀 Create Mobile Release + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - name: 📥 Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: 🔍 Validate version format + run: | + VERSION="${{ github.event.inputs.version }}" + if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "❌ Error: Version must be in format X.Y.Z (e.g., 0.1.0)" + exit 1 + fi + echo "✅ Version format valid: $VERSION" + + - name: 🏷️ Generate tag name + id: tag + run: | + APP="${{ github.event.inputs.app }}" + ENV="${{ github.event.inputs.environment }}" + VERSION="${{ github.event.inputs.version }}" + + TAG_NAME="krow-withus-${APP}-mobile/${ENV}-v${VERSION}" + echo "tag_name=${TAG_NAME}" >> $GITHUB_OUTPUT + echo "📌 Tag to create: ${TAG_NAME}" + + - name: 🔍 Check if tag already exists + run: | + TAG_NAME="${{ steps.tag.outputs.tag_name }}" + if git rev-parse "$TAG_NAME" >/dev/null 2>&1; then + echo "❌ Error: Tag $TAG_NAME already exists" + exit 1 + fi + echo "✅ Tag does not exist, proceeding..." + + - name: 📝 Verify CHANGELOG exists + run: | + APP="${{ github.event.inputs.app }}" + if [ "$APP" = "worker" ]; then + CHANGELOG_PATH="apps/mobile/apps/staff/CHANGELOG.md" + else + CHANGELOG_PATH="apps/mobile/apps/client/CHANGELOG.md" + fi + + if [ ! -f "$CHANGELOG_PATH" ]; then + echo "⚠️ Warning: CHANGELOG not found at $CHANGELOG_PATH" + else + echo "✅ CHANGELOG found at $CHANGELOG_PATH" + echo "changelog_path=${CHANGELOG_PATH}" >> $GITHUB_ENV + fi + + - name: 📋 Extract release notes from CHANGELOG + id: release_notes + run: | + APP="${{ github.event.inputs.app }}" + VERSION="${{ github.event.inputs.version }}" + + if [ "$APP" = "worker" ]; then + CHANGELOG_PATH="apps/mobile/apps/staff/CHANGELOG.md" + APP_NAME="Staff Mobile App (Worker)" + else + CHANGELOG_PATH="apps/mobile/apps/client/CHANGELOG.md" + APP_NAME="Client Mobile App" + fi + + # Try to extract release notes for this version + if [ -f "$CHANGELOG_PATH" ]; then + # Extract section for this version + NOTES=$(awk "/## \[${VERSION}\]/,/^## \[/" "$CHANGELOG_PATH" | sed '1d;$d' | sed '/^$/d') + + if [ -z "$NOTES" ]; then + NOTES="Release $VERSION for $APP_NAME + +No CHANGELOG entry found for this version. Please update the CHANGELOG manually. + +**Environment:** ${{ github.event.inputs.environment }} +**Tag:** ${{ steps.tag.outputs.tag_name }}" + else + NOTES="# $APP_NAME - Release $VERSION + +$NOTES + +--- + +**Environment:** ${{ github.event.inputs.environment }} +**Tag:** ${{ steps.tag.outputs.tag_name }}" + fi + else + NOTES="Release $VERSION for $APP_NAME + +**Environment:** ${{ github.event.inputs.environment }} +**Tag:** ${{ steps.tag.outputs.tag_name }}" + fi + + # Save to file to handle multiline + echo "$NOTES" > /tmp/release_notes.md + echo "notes_file=/tmp/release_notes.md" >> $GITHUB_OUTPUT + + - name: 🏷️ Create Git Tag + run: | + TAG_NAME="${{ steps.tag.outputs.tag_name }}" + APP="${{ github.event.inputs.app }}" + ENV="${{ github.event.inputs.environment }}" + VERSION="${{ github.event.inputs.version }}" + + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + git tag -a "$TAG_NAME" -m "Release ${APP} mobile app ${VERSION} to ${ENV}" + git push origin "$TAG_NAME" + + echo "✅ Tag created and pushed: $TAG_NAME" + + - name: 📦 Create GitHub Release + if: ${{ github.event.inputs.create_github_release == 'true' }} + env: + GH_TOKEN: ${{ github.token }} + run: | + TAG_NAME="${{ steps.tag.outputs.tag_name }}" + APP="${{ github.event.inputs.app }}" + ENV="${{ github.event.inputs.environment }}" + VERSION="${{ github.event.inputs.version }}" + + # Generate release title + if [ "$APP" = "worker" ]; then + APP_DISPLAY="Worker Mobile" + else + APP_DISPLAY="Client Mobile" + fi + + ENV_UPPER=$(echo "$ENV" | tr '[:lower:]' '[:upper:]') + RELEASE_NAME="Krow With Us - ${APP_DISPLAY} - ${ENV_UPPER} - v${VERSION}" + + # Create release + if [ "${{ github.event.inputs.prerelease }}" = "true" ]; then + gh release create "$TAG_NAME" \ + --title "$RELEASE_NAME" \ + --notes-file "${{ steps.release_notes.outputs.notes_file }}" \ + --prerelease + else + gh release create "$TAG_NAME" \ + --title "$RELEASE_NAME" \ + --notes-file "${{ steps.release_notes.outputs.notes_file }}" + fi + + echo "✅ GitHub Release created: $RELEASE_NAME" + + - name: 📊 Release Summary + run: | + echo "## 🚀 Release Created Successfully" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**App:** ${{ github.event.inputs.app }}" >> $GITHUB_STEP_SUMMARY + echo "**Environment:** ${{ github.event.inputs.environment }}" >> $GITHUB_STEP_SUMMARY + echo "**Version:** ${{ github.event.inputs.version }}" >> $GITHUB_STEP_SUMMARY + echo "**Tag:** \`${{ steps.tag.outputs.tag_name }}\`" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [ "${{ github.event.inputs.app }}" = "worker" ]; then + APP_DISPLAY="Worker Mobile" + else + APP_DISPLAY="Client Mobile" + fi + ENV_UPPER=$(echo "${{ github.event.inputs.environment }}" | tr '[:lower:]' '[:upper:]') + RELEASE_NAME="Krow With Us - ${APP_DISPLAY} - ${ENV_UPPER} - v${{ github.event.inputs.version }}" + + echo "**Release Name:** $RELEASE_NAME" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Next Steps" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "1. Verify the tag and release on GitHub" >> $GITHUB_STEP_SUMMARY + echo "2. Trigger CodeMagic build (if configured)" >> $GITHUB_STEP_SUMMARY + echo "3. Monitor app store deployment" >> $GITHUB_STEP_SUMMARY + echo "4. Update project documentation if needed" >> $GITHUB_STEP_SUMMARY