Skip to content

CI/CD

Introduction

Continuous Integration and Continuous Deployment (CI/CD) is essential for maintaining quality and velocity in Bond applications. This guide covers setting up automated testing, building, and deployment pipelines that work seamlessly with Bond's architecture and conventions.

Bond's CI/CD approach emphasizes: - Automated testing at multiple levels - Environment-specific builds with proper configuration - Security scanning and dependency management - Automated deployment to app stores and internal distribution

GitHub Actions Setup

Basic Workflow

Create .github/workflows/ci.yml:

name: CI/CD Pipeline

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

env:
  FLUTTER_VERSION: '3.16.0'

jobs:
  test:
    name: Test
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Flutter
        uses: subosito/flutter-action@v2
        with:
          flutter-version: ${{ env.FLUTTER_VERSION }}
          cache: true

      - name: Install Bond CLI
        run: dart pub global activate bond_cli

      - name: Get dependencies
        run: flutter pub get

      - name: Generate code
        run: flutter packages pub run build_runner build --delete-conflicting-outputs

      - name: Analyze code
        run: flutter analyze

      - name: Check formatting
        run: dart format --output=none --set-exit-if-changed .

      - name: Run unit tests
        run: flutter test --coverage

      - name: Run Bond analysis
        run: bond analyze --ci

      - name: Upload coverage
        uses: codecov/codecov-action@v3
        with:
          file: coverage/lcov.info

  build:
    name: Build
    runs-on: ubuntu-latest
    needs: test
    strategy:
      matrix:
        flavor: [staging, production]

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Flutter
        uses: subosito/flutter-action@v2
        with:
          flutter-version: ${{ env.FLUTTER_VERSION }}
          cache: true

      - name: Setup Java
        uses: actions/setup-java@v3
        with:
          distribution: 'zulu'
          java-version: '17'

      - name: Create environment file
        run: |
          echo '${{ secrets[format('ENV_{0}', matrix.flavor)] }}' > env.${{ matrix.flavor }}.json

      - name: Get dependencies
        run: flutter pub get

      - name: Generate code
        run: flutter packages pub run build_runner build --delete-conflicting-outputs

      - name: Build APK
        run: |
          flutter build apk \
            --flavor ${{ matrix.flavor }} \
            -t lib/main_${{ matrix.flavor }}.dart \
            --dart-define-from-file=env.${{ matrix.flavor }}.json

      - name: Build iOS (if on macOS)
        if: runner.os == 'macOS'
        run: |
          flutter build ios \
            --flavor ${{ matrix.flavor }} \
            -t lib/main_${{ matrix.flavor }}.dart \
            --dart-define-from-file=env.${{ matrix.flavor }}.json \
            --no-codesign

      - name: Upload artifacts
        uses: actions/upload-artifact@v3
        with:
          name: build-${{ matrix.flavor }}
          path: |
            build/app/outputs/flutter-apk/
            build/ios/iphoneos/

Advanced Workflow

Create .github/workflows/deploy.yml for automated deployment:

name: Deploy

on:
  push:
    tags:
      - 'v*'

jobs:
  deploy-android:
    name: Deploy Android
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Flutter
        uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.16.0'

      - name: Setup Java
        uses: actions/setup-java@v3
        with:
          distribution: 'zulu'
          java-version: '17'

      - name: Decode signing key
        run: |
          echo "${{ secrets.ANDROID_SIGNING_KEY }}" | base64 -d > android/app/key.jks

      - name: Create key.properties
        run: |
          echo "storePassword=${{ secrets.ANDROID_STORE_PASSWORD }}" > android/key.properties
          echo "keyPassword=${{ secrets.ANDROID_KEY_PASSWORD }}" >> android/key.properties
          echo "keyAlias=${{ secrets.ANDROID_KEY_ALIAS }}" >> android/key.properties
          echo "storeFile=key.jks" >> android/key.properties

      - name: Create environment file
        run: echo '${{ secrets.ENV_PRODUCTION }}' > env.production.json

      - name: Get dependencies
        run: flutter pub get

      - name: Generate code
        run: flutter packages pub run build_runner build --delete-conflicting-outputs

      - name: Build App Bundle
        run: |
          flutter build appbundle \
            --flavor production \
            -t lib/main_production.dart \
            --dart-define-from-file=env.production.json

      - name: Deploy to Play Store
        uses: r0adkll/upload-google-play@v1
        with:
          serviceAccountJsonPlainText: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT }}
          packageName: com.mycompany.myapp
          releaseFiles: build/app/outputs/bundle/productionRelease/app-production-release.aab
          track: production

  deploy-ios:
    name: Deploy iOS
    runs-on: macos-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Flutter
        uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.16.0'

      - name: Install CocoaPods
        run: sudo gem install cocoapods

      - name: Setup Xcode
        uses: maxim-lobanov/setup-xcode@v1
        with:
          xcode-version: latest-stable

      - name: Import certificates
        uses: apple-actions/import-codesign-certs@v1
        with:
          p12-file-base64: ${{ secrets.IOS_CERTIFICATES }}
          p12-password: ${{ secrets.IOS_CERTIFICATES_PASSWORD }}

      - name: Install provisioning profiles
        uses: apple-actions/download-provisioning-profiles@v1
        with:
          bundle-id: com.mycompany.myapp
          issuer-id: ${{ secrets.APPSTORE_ISSUER_ID }}
          api-key-id: ${{ secrets.APPSTORE_API_KEY_ID }}
          api-private-key: ${{ secrets.APPSTORE_API_PRIVATE_KEY }}

      - name: Create environment file
        run: echo '${{ secrets.ENV_PRODUCTION }}' > env.production.json

      - name: Get dependencies
        run: flutter pub get

      - name: Generate code
        run: flutter packages pub run build_runner build --delete-conflicting-outputs

      - name: Build iOS
        run: |
          flutter build ipa \
            --flavor production \
            -t lib/main_production.dart \
            --dart-define-from-file=env.production.json

      - name: Deploy to App Store
        uses: apple-actions/upload-testflight-build@v1
        with:
          app-path: build/ios/ipa/my_app.ipa
          issuer-id: ${{ secrets.APPSTORE_ISSUER_ID }}
          api-key-id: ${{ secrets.APPSTORE_API_KEY_ID }}
          api-private-key: ${{ secrets.APPSTORE_API_PRIVATE_KEY }}

Testing in CI

Comprehensive Test Suite

  test:
    name: Test Suite
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Flutter
        uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.16.0'

      - name: Create test environment
        run: |
          echo '${{ secrets.ENV_TEST }}' > env.test.json

      - name: Get dependencies
        run: flutter pub get

      - name: Generate code
        run: flutter packages pub run build_runner build --delete-conflicting-outputs

      - name: Run unit tests
        run: |
          flutter test \
            --coverage \
            --dart-define-from-file=env.test.json

      - name: Run integration tests
        run: |
          flutter test integration_test/ \
            --dart-define-from-file=env.test.json

      - name: Run golden tests
        run: flutter test --update-goldens

      - name: Check test coverage
        run: |
          dart pub global activate coverage
          genhtml coverage/lcov.info -o coverage/html

          # Fail if coverage is below threshold
          lcov --summary coverage/lcov.info | grep "lines......: " | awk '{print $2}' | sed 's/%//' | awk '{if($1<80) exit 1}'

Environment Management

Secrets Management

Store sensitive configuration in GitHub Secrets:

# Repository Secrets
ENV_STAGING: |
  {
    "API_BASE_URL": "https://api.staging.myapp.com",
    "FIREBASE_PROJECT_ID": "myapp-staging",
    "ANALYTICS_ENABLED": "true"
  }

ENV_PRODUCTION: |
  {
    "API_BASE_URL": "https://api.myapp.com",
    "FIREBASE_PROJECT_ID": "myapp-production",
    "ANALYTICS_ENABLED": "true"
  }

ENV_TEST: |
  {
    "API_BASE_URL": "https://test.api.com",
    "FIREBASE_PROJECT_ID": "myapp-test",
    "ANALYTICS_ENABLED": "false"
  }

# Signing secrets
ANDROID_SIGNING_KEY: <base64-encoded-keystore>
ANDROID_STORE_PASSWORD: <keystore-password>
ANDROID_KEY_PASSWORD: <key-password>
ANDROID_KEY_ALIAS: <key-alias>

IOS_CERTIFICATES: <base64-encoded-p12>
IOS_CERTIFICATES_PASSWORD: <p12-password>

# App Store Connect
APPSTORE_ISSUER_ID: <issuer-id>
APPSTORE_API_KEY_ID: <api-key-id>
APPSTORE_API_PRIVATE_KEY: <private-key>

# Google Play
GOOGLE_PLAY_SERVICE_ACCOUNT: <service-account-json>

Environment Validation

  validate-env:
    name: Validate Environment
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Flutter
        uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.16.0'

      - name: Install Bond CLI
        run: dart pub global activate bond_cli

      - name: Create environment files
        run: |
          echo '${{ secrets.ENV_STAGING }}' > env.staging.json
          echo '${{ secrets.ENV_PRODUCTION }}' > env.production.json

      - name: Validate environments
        run: |
          bond validate env --file=env.staging.json
          bond validate env --file=env.production.json

      - name: Check required secrets
        run: |
          bond validate secrets \
            --required=API_BASE_URL,FIREBASE_PROJECT_ID \
            --env=staging,production

Quality Gates

Code Quality Checks

  quality:
    name: Quality Gates
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Flutter
        uses: subosito/flutter-action@v4
        with:
          flutter-version: '3.16.0'

      - name: Install dependencies
        run: |
          dart pub global activate dart_code_metrics
          dart pub global activate pana
          flutter pub get

      - name: Run code metrics
        run: dart_code_metrics analyze lib --reporter=github

      - name: Check package health
        run: pana --json --no-warning > pana_report.json

      - name: Validate architecture
        run: bond analyze --strict --fail-on-warnings

      - name: Check dependencies
        run: flutter pub deps --style=compact

      - name: Security scan
        run: dart pub audit

Performance Testing

  performance:
    name: Performance Tests
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Flutter
        uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.16.0'

      - name: Create test environment
        run: echo '${{ secrets.ENV_TEST }}' > env.test.json

      - name: Get dependencies
        run: flutter pub get

      - name: Run performance tests
        run: |
          flutter test test/performance/ \
            --dart-define-from-file=env.test.json

      - name: Analyze bundle size
        run: |
          flutter build apk \
            --flavor staging \
            -t lib/main_staging.dart \
            --dart-define-from-file=env.test.json \
            --analyze-size

Deployment Strategies

Staged Deployment

  deploy-staging:
    name: Deploy Staging
    runs-on: ubuntu-latest
    needs: [test, quality]
    if: github.ref == 'refs/heads/develop'

    steps:
      - name: Deploy to staging
        run: |
          # Deploy to internal testing tracks
          # Firebase App Distribution, TestFlight, etc.

  deploy-production:
    name: Deploy Production
    runs-on: ubuntu-latest
    needs: [test, quality]
    if: startsWith(github.ref, 'refs/tags/v')

    steps:
      - name: Deploy to production
        run: |
          # Deploy to app stores

Feature Branch Builds

  feature-build:
    name: Feature Build
    runs-on: ubuntu-latest
    if: github.event_name == 'pull_request'

    steps:
      - name: Build feature branch
        run: |
          flutter build apk \
            --flavor staging \
            -t lib/main_staging.dart \
            --dart-define-from-file=env.staging.json

      - name: Comment PR with build info
        uses: actions/github-script@v6
        with:
          script: |
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: '🚀 Build completed! Download APK from artifacts.'
            })

Documentation Deployment

Auto-deploy Docs

Create .github/workflows/docs.yml:

name: Deploy Documentation

on:
  push:
    branches: [ main ]
    paths: [ 'bond-docs/**' ]

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.11'

      - name: Install MkDocs
        run: pip install mkdocs-material mike

      - name: Deploy docs
        run: |
          cd bond-docs
          mike deploy --push --update-aliases ${{ github.ref_name }} latest
          mike set-default --push latest

Security and Compliance

Security Scanning

  security:
    name: Security Scan
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Run Trivy vulnerability scanner
        uses: aquasecurity/trivy-action@master
        with:
          scan-type: 'fs'
          scan-ref: '.'
          format: 'sarif'
          output: 'trivy-results.sarif'

      - name: Upload Trivy scan results
        uses: github/codeql-action/upload-sarif@v2
        with:
          sarif_file: 'trivy-results.sarif'

      - name: Dependency audit
        run: |
          dart pub audit --json > audit_report.json

          # Fail if high severity vulnerabilities found
          if jq -e '.vulnerabilities[] | select(.severity == "high")' audit_report.json; then
            echo "High severity vulnerabilities found!"
            exit 1
          fi

Compliance Checks

  compliance:
    name: Compliance
    runs-on: ubuntu-latest

    steps:
      - name: License compliance
        run: |
          flutter pub deps --json > dependencies.json
          dart run license_checker dependencies.json

      - name: Privacy compliance
        run: |
          # Check for sensitive permissions
          grep -r "android.permission" android/

          # Validate privacy policy links
          bond validate privacy --check-links

      - name: Accessibility audit
        run: flutter test test/accessibility/

Monitoring and Alerting

Build Notifications

      - name: Notify on failure
        if: failure()
        uses: 8398a7/action-slack@v3
        with:
          status: failure
          channel: '#dev-alerts'
          text: |
            Build failed for ${{ github.repository }}
            Branch: ${{ github.ref }}
            Commit: ${{ github.sha }}
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

      - name: Notify on success
        if: success() && github.ref == 'refs/heads/main'
        uses: 8398a7/action-slack@v3
        with:
          status: success
          channel: '#releases'
          text: |
            🚀 New release deployed!
            Version: ${{ github.ref_name }}
            Changes: ${{ github.event.head_commit.message }}

Performance Monitoring

      - name: Performance regression check
        run: |
          # Compare bundle sizes
          flutter build apk --analyze-size > current_size.txt

          # Compare with baseline (stored in cache or artifacts)
          if [ -f baseline_size.txt ]; then
            python scripts/compare_sizes.py baseline_size.txt current_size.txt
          fi

Best Practices

Do's

✅ Run tests before building to catch issues early

✅ Use matrix builds for different flavors and platforms

✅ Cache dependencies to speed up builds

✅ Validate environments before deployment

✅ Monitor build performance and optimize when needed

✅ Use semantic versioning for releases

Don'ts

❌ Don't store secrets in code - use GitHub Secrets or environment variables

❌ Don't skip quality gates - they prevent production issues

❌ Don't deploy without testing - always test before production

❌ Don't ignore security scans - address vulnerabilities promptly

Troubleshooting

Common CI Issues

Build timeouts:

# Increase timeout and add caching
timeout-minutes: 60
- uses: actions/cache@v3
  with:
    path: ~/.pub-cache
    key: ${{ runner.os }}-pub-cache-${{ hashFiles('**/pubspec.lock') }}

Environment file issues:

# Validate environment files before use
- name: Validate environment
  run: |
    if ! jq empty env.production.json; then
      echo "Invalid JSON in environment file"
      exit 1
    fi

Flaky tests:

# Retry flaky tests
- name: Run tests with retry
  uses: nick-invision/retry@v2
  with:
    timeout_minutes: 10
    max_attempts: 3
    command: flutter test

Next Steps