feat: Miru 2.0 - Modern UI upgrade with Geist font and Ferrum PDF generation #10
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: Deploy Review App | |
| on: | |
| pull_request: | |
| types: [opened, synchronize, reopened] | |
| jobs: | |
| deploy: | |
| runs-on: ubuntu-latest | |
| if: github.event.pull_request.draft == false | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup Fly CLI | |
| uses: superfly/flyctl-actions/setup-flyctl@master | |
| - name: Generate App Name | |
| id: app_name | |
| run: | | |
| BRANCH_SLUG=$(echo "${{ github.head_ref }}" | sed 's/[^a-zA-Z0-9-]/-/g' | tr '[:upper:]' '[:lower:]' | cut -c1-30) | |
| APP_NAME="miru-pr-${{ github.event.pull_request.number }}-${BRANCH_SLUG}" | |
| echo "name=${APP_NAME}" >> $GITHUB_OUTPUT | |
| echo "url=https://${APP_NAME}.fly.dev" >> $GITHUB_OUTPUT | |
| - name: Deploy to Fly.io | |
| env: | |
| FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} | |
| run: | | |
| # Create app if it doesn't exist | |
| if ! fly apps list | grep -q "${{ steps.app_name.outputs.name }}"; then | |
| fly apps create "${{ steps.app_name.outputs.name }}" --org personal | |
| # Create PostgreSQL | |
| fly postgres create --name "${{ steps.app_name.outputs.name }}-db" \ | |
| --region iad \ | |
| --initial-cluster-size 1 \ | |
| --vm-size shared-cpu-1x \ | |
| --volume-size 1 \ | |
| --no-replicas | |
| # Attach database | |
| fly postgres attach "${{ steps.app_name.outputs.name }}-db" \ | |
| --app "${{ steps.app_name.outputs.name }}" | |
| fi | |
| # Set secrets | |
| fly secrets set \ | |
| SECRET_KEY_BASE=${{ secrets.RAILS_SECRET_KEY_BASE }} \ | |
| RAILS_MASTER_KEY=${{ secrets.RAILS_MASTER_KEY }} \ | |
| --app "${{ steps.app_name.outputs.name }}" | |
| # Deploy | |
| fly deploy --app "${{ steps.app_name.outputs.name }}" \ | |
| --config fly.review.toml \ | |
| --build-arg REVIEW_APP_BRANCH="${{ github.head_ref }}" | |
| - name: Run Playwright Tests | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| - name: Install Playwright | |
| run: | | |
| npm install -g playwright | |
| npx playwright install chromium | |
| - name: Test Review App | |
| id: test_app | |
| run: | | |
| # Wait for app to be ready | |
| for i in {1..30}; do | |
| if curl -s "${{ steps.app_name.outputs.url }}/health" | grep -q "healthy"; then | |
| echo "App is healthy!" | |
| break | |
| fi | |
| echo "Waiting for app to be ready... (attempt $i/30)" | |
| sleep 10 | |
| done | |
| # Run Playwright login test | |
| npx playwright test --project=chromium tests/review-app-login.spec.ts | |
| env: | |
| REVIEW_APP_URL: ${{ steps.app_name.outputs.url }} | |
| - name: Comment on PR | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const appName = '${{ steps.app_name.outputs.name }}'; | |
| const appUrl = '${{ steps.app_name.outputs.url }}'; | |
| const branch = '${{ github.head_ref }}'; | |
| const body = `## 🚀 Review App Deployed! | |
| **App Name:** \`${appName}\` | |
| **URL:** ${appUrl} | |
| **Branch:** \`${branch}\` | |
| ### 📊 Deployment Details | |
| - **Region:** US East (iad) | |
| - **Database:** PostgreSQL (1GB) | |
| - **Machine:** shared-cpu-1x (512MB RAM) | |
| ### 🧪 Test Results | |
| - Health Check: ✅ Passed | |
| - Login Test: ${{ steps.test_app.outcome == 'success' && '✅ Passed' || '❌ Failed' }} | |
| ### 🔧 Useful Commands | |
| \`\`\`bash | |
| # View logs | |
| fly logs --app ${appName} | |
| # SSH into app | |
| fly ssh console --app ${appName} | |
| # Open database console | |
| fly postgres connect --app ${appName}-db | |
| # Destroy app (when PR is closed) | |
| fly destroy ${appName} --yes | |
| fly destroy ${appName}-db --yes | |
| \`\`\` | |
| ### 📱 Mobile App Testing | |
| For mobile app testing, update your mobile app's base URL to: | |
| \`\`\` | |
| ${appUrl} | |
| \`\`\` | |
| --- | |
| <sub>🤖 Deployed with GitHub Actions | [View Workflow](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})</sub>`; | |
| // Find existing comment | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| }); | |
| const botComment = comments.find(comment => | |
| comment.user.type === 'Bot' && | |
| comment.body.includes('Review App Deployed') | |
| ); | |
| if (botComment) { | |
| // Update existing comment | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: botComment.id, | |
| body: body | |
| }); | |
| } else { | |
| // Create new comment | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body: body | |
| }); | |
| } | |
| cleanup: | |
| runs-on: ubuntu-latest | |
| if: github.event.action == 'closed' | |
| steps: | |
| - name: Setup Fly CLI | |
| uses: superfly/flyctl-actions/setup-flyctl@master | |
| - name: Destroy Review App | |
| env: | |
| FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} | |
| run: | | |
| BRANCH_SLUG=$(echo "${{ github.head_ref }}" | sed 's/[^a-zA-Z0-9-]/-/g' | tr '[:upper:]' '[:lower:]' | cut -c1-30) | |
| APP_NAME="miru-pr-${{ github.event.pull_request.number }}-${BRANCH_SLUG}" | |
| # Destroy app and database | |
| fly destroy "${APP_NAME}" --yes || true | |
| fly destroy "${APP_NAME}-db" --yes || true | |
| - name: Comment on PR | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const body = '## 🧹 Review App Destroyed\n\nThe review app has been automatically destroyed since the PR was closed.'; | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body: body | |
| }); |