Flutter: أتمتة CI/CD

الإصدارات اليدوية حاضنة للخطأ البشري — الأتمتة تجعل كل خطوة قابلة للتكرار والتتبع والثقة.

📋 المتطلبات السابقة: يجب أن تكون قد أتقنت ما يلي أولًا

1. ما ستتعلمه


2. قصة حقيقية عن كارثة إصدار يدوي

(1) المشكلة: إصدارات يدوية لمدة 4 ساعات + 3 دورات إعادة عمل

في كل مرة يُصدر بوب نسخة جديدة من ShopApp، يجب عليه: تشغيل flutter build يدويًا ← التوقيع ← الرفع لـ Play Console ← أخذ لقطات شاشة ← ملء ملاحظات الإصدار ← التقديم للمراجعة. عملية iOS أكثر تعقيدًا: Archive ← تصدير IPA ← الرفع لـ App Store Connect ← انتظار المراجعة. العملية بأكملها تستغرق 4 ساعات، مع دورتي إعادة عمل على الأقل (نسي تحديث النسخة / إعدادات توقيع خاطئة / لقطات غير متوافقة). كل يوم إصدار، يعمل بوب لوقت متأخر من الليل.

(2) حل خط أنابيب CI/CD

أتمتة GitHub Actions: دفع علامة ← اختبار تلقائي ← بناء تلقائي ← توقيع تلقائي ← رفع تلقائي ← إشعار Slack. صفر تدخل يدوي طوال العملية.

YAML
# Push tag v1.2.3 → automatic release pipeline
on: push: tags: ['v*']
# → flutter test → flutter build → sign → upload → notify

(3) النتيجة: إصدارات في ساعة واحدة + صفر إعادة عمل

بعد إعداد CI/CD، انخفض وقت إصدار بوب من 4 ساعات إلى ساعة واحدة (معظمها انتظار المراجعة)، وانخفض معدل إعادة العمل من 60% إلى 0%.


3. نظرة عامة على خط أنابيب CI/CD

100%
graph LR
    subgraph GitHub Actions Pipeline
        P[Push / PR] --> LINT[Lint & Analyze]
        LINT --> TEST[flutter test]
        TEST --> BUILD[Build APK/IPA/Web]
        BUILD --> SIGN[Code Signing]
        SIGN --> DIST[Distribute]
        DIST --> |Slack| NOTIFY[Team Notification]
    end
TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

(1) مقارنة حلول CI/CD

الحل السعر دعم المنصات الإعدادات الأنسب لـ
GitHub Actions مجاني (2000 دقيقة/شهر) جميع المنصات YAML الخيار الأول
Codemagic مجاني (500 دقيقة/شهر) جميع المنصات YAML بناء macOS أفضل
Bitrise مجاني (200 دقيقة/شهر) جميع المنصات UI + YAML مخصص iOS
Fastlane مجاني (محلي) iOS/Android Ruby أتمتة محلية
Cirrus CI مجاني (مفتوح المصدر) جميع المنصات YAML مشاريع المجتمع

4. سير عمل GitHub Actions

▶ مثال

TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

: سير عمل فحص PR

YAML
# .github/workflows/pr-check.yml
name: PR Check

on:
  pull_request:
    branches: [main]

jobs:
  analyze-and-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Flutter
        uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.x'
          channel: 'stable'

      - name: Install dependencies
        run: flutter pub get

      - name: Analyze code
        run: flutter analyze --no-fatal-infos

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

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

▶ مثال

TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

: سير عمل بناء متعدد المصفوفات

YAML
# .github/workflows/build.yml
name: Build All Platforms

on:
  push:
    tags: ['v*']

jobs:
  build-android:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: subosito/flutter-action@v2
        with: { flutter-version: '3.x' }
      - run: flutter pub get
      - run: flutter test
      - name: Build APK
        run: flutter build apk --release
      - name: Build AAB
        run: flutter build appbundle --release
        env:
          KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
          KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
      - name: Upload to Play Console
        uses: r0adkll/upload-google-play@v1
        with:
          serviceAccountJsonPlainText: ${{ secrets.PLAY_SERVICE_ACCOUNT }}
          packageName: com.shopapp.app
          releaseFiles: build/app/outputs/bundle/release/app-release.aab
          track: production

  build-ios:
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v4
      - uses: subosito/flutter-action@v2
        with: { flutter-version: '3.x' }
      - run: flutter pub get
      - name: Install CocoaPods
        run: cd ios && pod install
      - name: Build IPA
        run: flutter build ipa --release --export-options-plist=ios/ExportOptions.plist
      - name: Upload to App Store Connect
        uses: apple/upload-testflight@v1
        with:
          app-path: build/ios/ipa/shopapp.ipa
          issuer-id: ${{ secrets.APPSTORE_ISSUER_ID }}
          api-key-id: ${{ secrets.APPSTORE_KEY_ID }}
          api-private-key: ${{ secrets.APPSTORE_PRIVATE_KEY }}

  build-web:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: subosito/flutter-action@v2
        with: { flutter-version: '3.x' }
      - run: flutter pub get
      - run: flutter build web --wasm
      - name: Deploy to Firebase Hosting
        uses: FirebaseExtended/action-hosting-deploy@v0
        with:
          repoToken: ${{ secrets.GITHUB_TOKEN }}
          firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT }}
          channelId: live

  notify:
    needs: [build-android, build-ios, build-web]
    runs-on: ubuntu-latest
    steps:
      - name: Slack Notification
        uses: slackapi/slack-github-action@v1
        with:
          payload: |
            {"text": "ShopApp ${{ github.ref_name }} released successfully!"}
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

5. أتمتة توقيع الكود

▶ مثال

TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

: إعدادات توقيع أندرويد

YAML
# GitHub Secrets needed:
# KEYSTORE_BASE64: base64 encoded .jks file
# KEY_PASSWORD: keystore password
# KEY_ALIAS: key alias

- name: Decode keystore
  run: echo "$KEYSTORE_BASE64" | base64 --decode > android/app/keystore.jks
  env:
    KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
GROOVY
// android/app/build.gradle
android {
    signingConfigs {
        release {
            storeFile file('keystore.jks')
            storePassword System.getenv("KEY_PASSWORD")
            keyAlias System.getenv("KEY_ALIAS")
            keyPassword System.getenv("KEY_PASSWORD")
        }
    }
    buildTypes {
        release {
            signingConfig signingConfigs.release
            minifyEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}
TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

6. تكامل Fastlane

▶ مثال

TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

: إعدادات Fastlane

RUBY
# ios/fastlane/Fastfile
default_platform(:ios)

platform :ios do
  desc "Build and upload to TestFlight"
  lane :beta do
    build_app(
      workspace: "Runner.xcworkspace",
      scheme: "Runner",
      export_method: "app-store",
    )
    upload_to_testflight(
      skip_waiting_for_build_processing: true,
    )
  end

  desc "Take screenshots"
  lane :screenshots do
    capture_screenshots(
      scheme: "Runner",
      devices: ["iPhone 15 Pro", "iPad Pro (12.9-inch)"],
    )
  end
end
TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
RUBY
# android/fastlane/Fastfile
default_platform(:android)

platform :android do
  desc "Build and upload to Play Console"
  lane :beta do
    gradle(task: "bundleRelease")
    upload_to_play_store(
      track: "internal",
      aab: "../build/app/outputs/bundle/release/app-release.aab",
    )
  end
end
TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

7. مثال كامل: إعدادات CI/CD لـ ShopApp

YAML
# .github/workflows/release.yml
name: Release ShopApp

on:
  push:
    tags: ['v*.*.*']

concurrency:
  group: release-${{ github.ref }}
  cancel-in-progress: true

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: subosito/flutter-action@v2
        with: { flutter-version: '3.x', channel: 'stable' }
      - run: flutter pub get
      - run: flutter analyze --no-fatal-infos
      - run: flutter test --coverage
      - uses: codecov/codecov-action@v3

  build-android:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: subosito/flutter-action@v2
        with: { flutter-version: '3.x' }
      - run: flutter pub get
      - name: Decode keystore
        run: echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 --decode > android/app/keystore.jks
      - name: Build AAB
        run: flutter build appbundle --release
        env:
          KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
          KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
      - name: Upload AAB artifact
        uses: actions/upload-artifact@v4
        with: { name: app-release-aab, path: build/app/outputs/bundle/release/app-release.aab }

  build-ios:
    needs: test
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v4
      - uses: subosito/flutter-action@v2
        with: { flutter-version: '3.x' }
      - run: flutter pub get
      - run: cd ios && pod install
      - name: Build IPA
        run: flutter build ipa --release --export-options-plist=ios/ExportOptions.plist
      - uses: actions/upload-artifact@v4
        with: { name: app-release-ipa, path: build/ios/ipa/*.ipa }

  build-web:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: subosito/flutter-action@v2
        with: { flutter-version: '3.x' }
      - run: flutter pub get
      - run: flutter build web --wasm --base-href "/app/"
      - uses: actions/upload-artifact@v4
        with: { name: web-release, path: build/web/ }

  release:
    needs: [build-android, build-ios, build-web]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Create GitHub Release
        uses: softprops/action-gh-release@v1
        with:
          generate_release_notes: true
          draft: false
      - name: Slack notify
        uses: slackapi/slack-github-action@v1
        with:
          payload: '{"text": "ShopApp ${{ github.ref_name }} released to all platforms!"}'
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

❓ أسئلة شائعة

س هل الطبقة المجانية من GitHub Actions كافية؟
ج المشاريع مفتوحة المصدر لديها دقائق غير محدودة. المشاريع الخاصة تحصل على 2000 دقيقة/شهر — بناء كامل عبر المنصات يستغرق حوالي 30-40 دقيقة، أي حوالي 50 بناء شهريًا.
س هل يجب استخدام مشغل macOS لبناء iOS؟
ج نعم، Xcode يعمل فقط على macOS. GitHub يوفر مشغلات macOS، لكن الفوترة بمعدل 10 أضعاف Linux. استخدم Codemagic لتوفير دقائق macOS.
س كيف أخزن مفاتيح التوقيع بأمان؟
ج استخدم GitHub Secrets (رمّز keystore/الشهادات بـ Base64)، لا تُرسلها أبدًا للمستودع. فعّل Secret scanning.
س كيف أسرّع بناء Flutter بالتخزين المؤقت؟
ج استخدم actions/cache لتخزين pub-cache وgradle-cache مؤقتًا — ينخفض وقت البناء من 5 دقائق إلى دقيقتين.
س كيف يعمل Fastlane مع GitHub Actions؟
ج GitHub Actions يُطلق البناء، Fastlane يتعامل مع التوقيع والرفع. Fastlane يعمل بسهولة أكبر على مشغل macOS.
س كيف أتراجع عن نسخة حية؟
ج Google Play Console يدعم التراجع لنسخة سابقة؛ App Store يتطلب تقديم نسخة جديدة. يجب أن يحتفظ CI/CD بجميع منتجات البناء التاريخية.

📖 ملخص


📝 تمارين

  1. أساسي (الصعوبة ⭐): أعد سير عمل فحص PR بـ GitHub Actions: flutter analyze + flutter test.
  2. متوسط (الصعوبة ⭐⭐): أضف خطوة بناء AAB أندرويد، أدر مفاتيح التوقيع بـ GitHub Secrets، وارفع المنتج بعد البناء.
  3. متقدم (الصعوبة ⭐⭐⭐): نفذ خط أنابيب CI/CD كامل: فحوصات PR (lint + test) + بناء عبر المنصات مُطلق بالعلامات (Android AAB + iOS IPA + Web) + توقيع تلقائي + GitHub Release + إشعار Slack.

← الدرس السابق | الدرس التالي →

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%