doc: Add festival ID to deep linking structure #950
Workflow file for this run
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: Flutter App CI/CD | |
| on: | |
| push: | |
| branches: [main, copilot/**] | |
| pull_request: | |
| branches: [main] | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| id-token: write | |
| pull-requests: write | |
| deployments: write | |
| concurrency: | |
| group: ci-${{ github.ref }} | |
| cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} | |
| jobs: | |
| # Detect which files have changed | |
| changes: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| app: ${{ steps.filter.outputs.app }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Check for file changes | |
| uses: dorny/paths-filter@v3 | |
| id: filter | |
| with: | |
| filters: | | |
| app: | |
| - 'lib/**' | |
| - 'web/**' | |
| - 'pubspec.yaml' | |
| - 'test/**' | |
| - 'test-e2e/**' # Includes test-e2e/screenshots.ts | |
| - 'package.json' | |
| - 'playwright.config.ts' | |
| - 'android/**' | |
| - '.github/workflows/build-deploy.yml' | |
| - 'mise.toml' | |
| # Run tests once before building | |
| test: | |
| needs: changes | |
| runs-on: ubuntu-latest | |
| if: | | |
| github.event_name == 'workflow_dispatch' || | |
| needs.changes.outputs.app == 'true' | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Flutter | |
| uses: subosito/flutter-action@v2 | |
| with: | |
| flutter-version: '3.38.3' | |
| channel: 'stable' | |
| cache: true | |
| - name: Create Firebase google-services.json | |
| run: echo '${{ secrets.GOOGLE_SERVICES_JSON }}' > android/app/google-services.json | |
| - name: Get dependencies | |
| run: flutter pub get | |
| - name: Generate mocks | |
| run: dart run build_runner build --delete-conflicting-outputs | |
| - name: Analyze code | |
| run: flutter analyze --no-fatal-infos | |
| - name: Run tests with coverage | |
| run: flutter test --coverage | |
| - name: Install lcov | |
| run: sudo apt-get update && sudo apt-get install -y lcov | |
| - name: Report code coverage | |
| uses: zgosalvez/github-actions-report-lcov@v4 | |
| with: | |
| coverage-files: coverage/lcov.info | |
| # TODO: Increase coverage target back to 70% as test coverage improves | |
| minimum-coverage: 25 | |
| artifact-name: code-coverage-report | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| update-comment: true | |
| - name: Upload coverage to Codecov | |
| uses: codecov/codecov-action@v5 | |
| with: | |
| files: coverage/lcov.info | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| fail_ci_if_error: false | |
| # Build web after tests pass | |
| build-web: | |
| needs: test | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Flutter | |
| uses: subosito/flutter-action@v2 | |
| with: | |
| flutter-version: '3.38.3' | |
| channel: 'stable' | |
| cache: true | |
| - name: Create Firebase google-services.json | |
| run: echo '${{ secrets.GOOGLE_SERVICES_JSON }}' > android/app/google-services.json | |
| - name: Get dependencies | |
| run: flutter pub get | |
| - name: Get git version info | |
| id: git_version | |
| run: | | |
| chmod +x scripts/get_version_info.sh | |
| scripts/get_version_info.sh github >> $GITHUB_OUTPUT | |
| - name: Build web | |
| run: | | |
| flutter build web --release --base-href "/" \ | |
| --dart-define=GIT_TAG=${{ steps.git_version.outputs.git_tag }} \ | |
| --dart-define=GIT_COMMIT=${{ steps.git_version.outputs.git_commit }} \ | |
| --dart-define=GIT_BRANCH=${{ steps.git_version.outputs.git_branch }} \ | |
| --dart-define=BUILD_VERSION=${{ steps.git_version.outputs.version }} \ | |
| --dart-define=BUILD_TIME=${{ steps.git_version.outputs.build_time }} | |
| - name: Upload build artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: web-build | |
| path: build/web | |
| # Run E2E tests on web build | |
| test-e2e-web: | |
| needs: build-web | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '21' | |
| - name: Download web build artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: web-build | |
| path: build/web | |
| - name: Install npm dependencies | |
| run: npm install | |
| - name: Install Playwright browsers | |
| run: npx playwright install --with-deps chromium | |
| - name: Start http-server in background | |
| run: | | |
| npx http-server build/web -p 8080 -c-1 -a 127.0.0.1 > /dev/null 2>&1 & | |
| echo $! > .http-server.pid | |
| - name: Wait for server to be ready | |
| run: | | |
| timeout 30 bash -c 'until curl -s http://127.0.0.1:8080 > /dev/null; do sleep 1; done' || exit 1 | |
| - name: Run Playwright tests | |
| run: npx playwright test | |
| - name: Upload Playwright report | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: playwright-report | |
| path: playwright-report/ | |
| retention-days: 7 | |
| - name: Stop http-server | |
| if: always() | |
| run: | | |
| if [ -f .http-server.pid ]; then | |
| kill $(cat .http-server.pid) || true | |
| rm .http-server.pid | |
| fi | |
| # Capture screenshots for PR visual review | |
| capture-screenshots: | |
| needs: [build-web, changes] | |
| runs-on: ubuntu-latest | |
| # Only run on PRs to main when app files change | |
| if: | | |
| github.event_name == 'pull_request' && | |
| github.base_ref == 'main' && | |
| needs.changes.outputs.app == 'true' | |
| permissions: | |
| contents: write # For pushing to pr-screenshots branch | |
| pull-requests: write # For posting PR comments | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '21' | |
| - name: Download web build artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: web-build | |
| path: build/web | |
| - name: Install npm dependencies | |
| run: npm install | |
| - name: Install Playwright browsers | |
| run: npx playwright install --with-deps chromium | |
| - name: Start http-server in background | |
| run: | | |
| npx http-server build/web -p 8080 -c-1 -a 127.0.0.1 > /dev/null 2>&1 & | |
| echo $! > .http-server.pid | |
| - name: Wait for server to be ready | |
| run: | | |
| timeout 30 bash -c 'until curl -s http://127.0.0.1:8080 > /dev/null; do sleep 1; done' || exit 1 | |
| - name: Capture screenshots | |
| run: | | |
| mkdir -p screenshots | |
| npm run screenshots screenshots | |
| - name: Stop http-server | |
| if: always() | |
| run: | | |
| if [ -f .http-server.pid ]; then | |
| kill $(cat .http-server.pid) || true | |
| rm .http-server.pid | |
| fi | |
| - name: Upload screenshots to pr-screenshots branch | |
| run: | | |
| # Verify screenshots exist before proceeding | |
| if [ ! -d screenshots ] || [ -z "$(ls -A screenshots/*.png 2>/dev/null)" ]; then | |
| echo "Error: No screenshots found to upload" | |
| exit 1 | |
| fi | |
| # Move screenshots to temporary location to avoid git working tree conflicts | |
| mkdir -p /tmp/pr-screenshots-upload | |
| cp screenshots/*.png /tmp/pr-screenshots-upload/ | |
| # Configure git | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| # Try to fetch the pr-screenshots branch if it exists | |
| if git fetch origin pr-screenshots:pr-screenshots 2>/dev/null; then | |
| # Branch exists remotely, check it out | |
| git checkout pr-screenshots | |
| else | |
| # Branch doesn't exist, create orphan branch | |
| git checkout --orphan pr-screenshots | |
| # Remove all files from the working tree (orphan branch is empty) | |
| git rm -rf . 2>/dev/null || true | |
| fi | |
| # Create directory for this PR's screenshots | |
| PR_DIR="pr-${{ github.event.pull_request.number }}" | |
| mkdir -p "$PR_DIR" | |
| # Copy screenshots from temp location | |
| cp /tmp/pr-screenshots-upload/*.png "$PR_DIR/" | |
| # Add and commit | |
| git add "$PR_DIR" | |
| # Only commit if there are changes | |
| if ! git diff --staged --quiet; then | |
| git commit -m "Add screenshots for PR #${{ github.event.pull_request.number }}" | |
| # Push with retry logic for concurrent PR handling | |
| MAX_RETRIES=3 | |
| RETRY_COUNT=0 | |
| until git push origin pr-screenshots; do | |
| RETRY_COUNT=$((RETRY_COUNT + 1)) | |
| if [ $RETRY_COUNT -ge $MAX_RETRIES ]; then | |
| echo "Error: Failed to push screenshots after $MAX_RETRIES attempts" | |
| exit 1 | |
| fi | |
| echo "Push failed, retrying ($RETRY_COUNT/$MAX_RETRIES)..." | |
| sleep 2 | |
| # Pull with rebase to handle concurrent updates | |
| git pull --rebase origin pr-screenshots | |
| done | |
| echo "Successfully pushed screenshots" | |
| else | |
| echo "No changes to commit (screenshots unchanged)" | |
| fi | |
| # Clean up temp directory | |
| rm -rf /tmp/pr-screenshots-upload | |
| - name: Post PR comment with screenshots | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const prNumber = context.issue.number; | |
| const screenshotDir = 'screenshots'; | |
| // Check if screenshot directory exists | |
| if (!fs.existsSync(screenshotDir)) { | |
| console.log('Screenshot directory does not exist, skipping comment'); | |
| return; | |
| } | |
| // Get list of screenshot files | |
| const screenshots = fs.readdirSync(screenshotDir) | |
| .filter(f => f.endsWith('.png')) | |
| .sort(); | |
| if (screenshots.length === 0) { | |
| console.log('No screenshots found, skipping comment'); | |
| return; | |
| } | |
| // Build markdown table with screenshots | |
| const baseUrl = `https://raw.githubusercontent.com/${context.repo.owner}/${context.repo.repo}/pr-screenshots/pr-${prNumber}`; | |
| let tableRows = ''; | |
| for (const screenshot of screenshots) { | |
| const name = screenshot.replace(/^\d+-/, '').replace(/-/g, ' ').replace('.png', ''); | |
| const capitalizedName = name.charAt(0).toUpperCase() + name.slice(1); | |
| const imageUrl = `${baseUrl}/${screenshot}`; | |
| tableRows += `| ${capitalizedName} |  |\n`; | |
| } | |
| const comment = [ | |
| '## 📸 App Screenshots', | |
| '', | |
| 'Visual preview of the app after your changes:', | |
| '', | |
| '| Screen | Preview (Mobile 390x844) |', | |
| '|--------|--------------------------|', | |
| tableRows.trimEnd(), | |
| '', | |
| '---', | |
| '<sub>Screenshots are automatically generated for PRs targeting main branch when app files change.</sub>' | |
| ].join('\n'); | |
| // Find existing screenshot comment | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: prNumber, | |
| }); | |
| const botComment = comments.find(comment => | |
| comment.user.type === 'Bot' && | |
| comment.body.includes('📸 App Screenshots') | |
| ); | |
| if (botComment) { | |
| // Update existing comment | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: botComment.id, | |
| body: comment, | |
| }); | |
| } else { | |
| // Create new comment | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: prNumber, | |
| body: comment, | |
| }); | |
| } | |
| # Build Android after tests pass | |
| build-android: | |
| needs: test | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up JDK 17 | |
| uses: actions/setup-java@v4 | |
| with: | |
| java-version: '17' | |
| distribution: 'temurin' | |
| - name: Cache Gradle dependencies | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.gradle/caches | |
| ~/.gradle/wrapper | |
| key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} | |
| restore-keys: | | |
| ${{ runner.os }}-gradle- | |
| - name: Setup Flutter | |
| uses: subosito/flutter-action@v2 | |
| with: | |
| flutter-version: '3.38.3' | |
| channel: 'stable' | |
| cache: true | |
| - name: Create Firebase google-services.json | |
| run: echo '${{ secrets.GOOGLE_SERVICES_JSON }}' > android/app/google-services.json | |
| - name: Get dependencies | |
| run: flutter pub get | |
| - name: Get git version info | |
| id: git_version | |
| run: | | |
| chmod +x scripts/get_version_info.sh | |
| scripts/get_version_info.sh github >> $GITHUB_OUTPUT | |
| - name: Build debug APK | |
| run: | | |
| flutter build apk --debug \ | |
| --dart-define=GIT_TAG=${{ steps.git_version.outputs.git_tag }} \ | |
| --dart-define=GIT_COMMIT=${{ steps.git_version.outputs.git_commit }} \ | |
| --dart-define=GIT_BRANCH=${{ steps.git_version.outputs.git_branch }} \ | |
| --dart-define=BUILD_VERSION=${{ steps.git_version.outputs.version }} \ | |
| --dart-define=BUILD_TIME=${{ steps.git_version.outputs.build_time }} | |
| - name: Upload debug APK | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: app-debug-apk | |
| path: build/app/outputs/flutter-apk/app-debug.apk | |
| if-no-files-found: error | |
| deploy-web-preview: | |
| needs: [changes, build-web, test-e2e-web] | |
| runs-on: ubuntu-latest | |
| if: | | |
| github.event_name == 'workflow_dispatch' || | |
| needs.changes.outputs.app == 'true' | |
| steps: | |
| - name: Download build artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: web-build | |
| path: build/web | |
| - name: Deploy to Cloudflare Pages (Staging/PR Previews) | |
| id: deploy | |
| uses: cloudflare/wrangler-action@v3 | |
| with: | |
| apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
| accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | |
| command: pages deploy build/web --project-name=staging-cambeerfestival --branch=${{ github.head_ref || github.ref_name }} | |
| - name: Comment PR with Preview URL | |
| if: github.event_name == 'pull_request' | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const aliasUrl = '${{ steps.deploy.outputs.pages-deployment-alias-url }}'; | |
| const deployUrl = '${{ steps.deploy.outputs.deployment-url }}'; | |
| const previewUrl = aliasUrl || deployUrl; | |
| const comment = `## 🚀 Cloudflare Pages Preview | |
| Your preview deployment is ready! | |
| **Preview URL**: ${previewUrl} | |
| This preview will be automatically updated when you push new commits to this PR.`; | |
| github.rest.issues.createComment({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: comment | |
| }); |