CI/CD والنشر على متاجر التطبيقات

أتمتة إصدارات Google Play باستخدام Fastlane Supply

16 دقيقة الدرس 9 من 12

أتمتة إصدارات Google Play باستخدام Fastlane Supply

إن رفع حزمة تطبيق Android (AAB) يدوياً إلى Google Play Console، وكتابة ملاحظات الإصدار، وإرفاق لقطات الشاشة، وترقية الإصدارات بين المسارات، كل ذلك مضيعة للوقت وعرضة للأخطاء. تقوم Fastlane Supply بأتمتة كل هذه الخطوات من سطر الأوامر — أو داخل خط أنابيب CI — بحيث يُوصِّل أمر واحد إصداراً جاهزاً للإنتاج إلى أي مسار في متجر Play.

ما هو Fastlane و Supply؟

Fastlane أداة أتمتة مفتوحة المصدر لنظامي iOS و Android. تنظّم الأتمتة في مسارات (lanes) — متتاليات مسمّاة من الإجراءات (actions) — معرَّفة في ملف Ruby واحد يُسمّى Fastfile. Supply هو إجراء Fastlane (وأداة مستقلة) الذي يتخاطب مع Google Play Developer API: يمكنه رفع APKs/AABs، وإدارة البيانات الوصفية، وملاحظات الإصدار المحلّية، ولقطات الشاشة، وترقية الإصدارات بين المسارات (internal → alpha → beta → production).

ملاحظة: تتواصل Supply مع Google Play عبر ملف JSON لحساب خدمة (service account). يجب إنشاء حساب خدمة في Google Play Console، ومنحه الأذونات الصحيحة (Release Manager أو أعلى)، وتنزيل المفتاح قبل تشغيل أي أوامر Supply.

تثبيت Fastlane

Fastlane حزمة Ruby. ثبِّتها باستخدام Bundler (موصى به) حتى يستخدم كل مطور في الفريق الإصدار ذاته:

تثبيت Fastlane عبر Bundler

# في جذر مشروع Flutter
gem install bundler

# إنشاء Gemfile
bundle init

# أضف Fastlane إلى Gemfile:
#   gem "fastlane"
bundle add fastlane

# تهيئة Fastlane (ينشئ مجلد android/fastlane/)
cd android
bundle exec fastlane init

بعد التهيئة ستحصل على:

  • android/fastlane/Fastfile — تعريفات المسارات
  • android/fastlane/Appfile — معرّف التطبيق ومسار مفتاح حساب الخدمة
  • android/fastlane/supply/ (يُنشأ عند أول تشغيل لـ supply init) — البيانات الوصفية وسجلات التغييرات ولقطات الشاشة مرتّبة حسب اللغة

إعداد Appfile وحساب الخدمة

يُخبر Appfile الـ Supply بالتطبيق الذي يجب إدارته وأين توجد بيانات اعتماد Google:

android/fastlane/Appfile

json_key_file("fastlane/google-play-key.json")   # مسار ملف JSON لحساب الخدمة
package_name("com.example.myapp")                 # اسم حزمة تطبيقك
تحذير: لا تُضِف google-play-key.json أبداً إلى نظام التحكم في الإصدارات. أضفه إلى .gitignore وأدخِله كسر CI (متغير بيئة أو ملف مشفَّر) أثناء وقت البناء.

كتابة مسار النشر في Fastfile

مسار Fastlane هو كتلة دالة Ruby. يبني مسار النشر أدناه حزمة AAB للإصدار باستخدام Gradle، ثم يستدعي supply لرفعها إلى مسار internal مع ملاحظات الإصدار المحلّية:

android/fastlane/Fastfile — مسار النشر

default_platform(:android)

platform :android do

  desc "بناء AAB للإصدار ورفعه إلى مسار internal في Play Store"
  lane :deploy do |options|
    track = options[:track] || "internal"   # تجاوز باستخدام: fastlane deploy track:beta

    # 1. بناء AAB الموقَّع عبر Gradle
    gradle(
      task:          "bundle",
      build_type:    "Release",
      project_dir:   "../android",           # المسار من fastlane/ إلى android/
      print_command: false                   # إخفاء أمر Gradle الكامل في السجلات
    )

    # 2. رفع AAB + ملاحظات الإصدار إلى المسار المختار
    supply(
      track:           track,
      aab:             lane_context[SharedValues::GRADLE_AAB_OUTPUT_PATH],
      release_status:  "draft",              # "draft" | "completed" | "halted"
      skip_upload_apk: true,                 # نستخدم AAB وليس APK
      skip_upload_images:       false,
      skip_upload_screenshots:  false,
      metadata_path:   "fastlane/supply"     # سجلات التغييرات + البيانات الوصفية
    )

    UI.success("تم رفع البناء إلى مسار Play Store: #{track}")
  end

end

شغِّل هذا المسار محلياً بـ:

  • bundle exec fastlane deploy — يرفع إلى مسار internal الافتراضي
  • bundle exec fastlane deploy track:beta — يرفع إلى مسار beta
  • bundle exec fastlane deploy track:production — يرفع مباشرة إلى الإنتاج

ملاحظات الإصدار والبيانات الوصفية المحلّية

تقرأ Supply سجلات التغييرات من مجلد fastlane/supply/. هيكل المجلد يعكس لغات Play Console:

هيكل مجلد ملاحظات الإصدار

android/fastlane/supply/
  metadata/
    android/
      en-US/
        changelogs/
          default.txt       # نص "الجديد" بالإنجليزية
        title.txt
        short_description.txt
        full_description.txt
      ar/
        changelogs/
          default.txt       # ملاحظات الإصدار بالعربية
        title.txt
        short_description.txt
        full_description.txt
      images/
        phoneScreenshots/   # 2-8 لقطات شاشة (PNG/JPEG، حد أقصى 8 ميغابايت)
        sevenInchScreenshots/
نصيحة: شغِّل bundle exec fastlane supply init مرة واحدة لسحب البيانات الوصفية الموجودة في Play Store إلى هيكل المجلد الصحيح. من ذلك الحين، عدِّل ملفات النصوص مباشرة ودع Supply ترفعها في النشر التالي.

دمج المسار في خط أنابيب CI

يتكامل مسار النشر بسلاسة مع أي مزود CI (GitHub Actions، Bitbucket Pipelines، GitLab CI، Codemagic). فيما يلي سير عمل GitHub Actions بسيط يُشغَّل عند كل دفع إلى فرع main:

.github/workflows/deploy-android.yml

name: نشر Android إلى Play Store

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: إعداد Java 17
        uses: actions/setup-java@v4
        with:
          java-version: "17"
          distribution: "temurin"

      - name: إعداد Flutter
        uses: subosito/flutter-action@v2
        with:
          flutter-version: "3.22.0"

      - name: تثبيت التبعيات
        run: flutter pub get

      - name: إعداد Ruby و Bundler
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: "3.3"
          bundler-cache: true
          working-directory: android

      - name: فكّ ترميز مفتاح حساب الخدمة
        run: |
          echo "${{ secrets.GOOGLE_PLAY_KEY_JSON }}" \
            > android/fastlane/google-play-key.json

      - name: فكّ ترميز مخزن المفاتيح
        run: |
          echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 --decode \
            > android/app/release.keystore

      - name: تشغيل مسار Fastlane للنشر
        env:
          STORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
          KEY_ALIAS:      ${{ secrets.KEY_ALIAS }}
          KEY_PASSWORD:   ${{ secrets.KEY_PASSWORD }}
        run: bundle exec fastlane deploy
        working-directory: android
ملاحظة: خزِّن جميع الأسرار (GOOGLE_PLAY_KEY_JSON، KEYSTORE_BASE64، كلمات المرور) في مخزن الأسرار المشفَّرة لدى مزود CI — لا تُضمِّنها أبداً في ملف سير العمل أو Fastfile.

ترقية الإصدارات بين المسارات

بمجرد اجتياز إصدار الاختبار على المسار الداخلي، يمكن لـ Supply ترقيته دون إعادة رفع AAB:

مسار الترقية في Fastfile

lane :promote_to_beta do
  supply(
    track:             "internal",
    track_promote_to:  "beta",
    skip_upload_aab:   true,    # لا حاجة لـ AAB جديد للترقية
    skip_upload_apk:   true,
    skip_upload_images:      true,
    skip_upload_screenshots: true,
    version_code: google_play_track_version_codes(track: "internal").first
  )
end

الملخص

تُزيل Fastlane Supply كل خطوة يدوية من إصدار Google Play. تثبّت Fastlane باستخدام Bundler، وتضبط Appfile بمفتاح حساب خدمة، وتعرّف مسار deploy الذي يستدعي gradle ثم supply، وتضع ملاحظات الإصدار المحلّية في fastlane/supply/metadata/، وتستدعي المسار من CI بحقن الأسرار أثناء التشغيل. النتيجة خط أنابيب نشر كامل قابل للتكرار والتدقيق يستهدف أي مسار في Play Store بأمر واحد.