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¶
- Learn about Flavors - Multi-environment deployment
- Explore Advanced Topics - Complex deployment scenarios
- Set up Monitoring - Production monitoring
- Join the Community - Share CI/CD experiences