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

أتمتة إصدارات App Store باستخدام Fastlane Deliver

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

أتمتة إصدارات App Store باستخدام Fastlane Deliver

رفع تطبيق iOS يدوياً إلى App Store Connect هو سير عمل متكرر وعرضة للأخطاء: تصدير الأرشيف، وملء ملاحظات الإصدار، وضبط رقم البناء، وتفعيل مجموعات TestFlight، ثم أخيراً النقر على Submit for Review. Fastlane Deliver (إجراء deliver) يؤتمت كل هذه الخطوات من سطر الأوامر، مما يجعل من السهل ربط العملية بالكامل في خط أنابيب CI/CD مُشغَّل بعلامة Git أو دمج إلى فرع الإصدار.

ما الذي يفعله Fastlane Deliver

إجراء deliver هو أداة Fastlane الرسمية للتفاعل مع App Store Connect. يمكنه:

  • رفع ملف .ipa موقَّع إلى App Store Connect باستخدام بروتوكول Transporter
  • دفع البيانات الوصفية المحلية للتطبيق (الأوصاف والكلمات المفتاحية وملاحظات الإصدار وعناوين URL للدعم)
  • رفع لقطات الشاشة ومقاطع الفيديو التجريبية لكل حجم جهاز ولغة
  • توزيع البناء على مجموعات TestFlight الداخلية أو الخارجية تلقائياً
  • تقديم البناء لمراجعة التطبيق بعلامة واحدة
  • تخطي التقديم والرفع فقط، مع السماح للبشر بتشغيل المراجعة من واجهة الويب
ملاحظة: يتواصل Fastlane Deliver مع App Store Connect حصراً من خلال App Store Connect API (مصادقة مفتاح قائمة على JWT). تدفق Apple ID / كلمة المرور القديم مهمل. يجب إنشاء مفتاح API في App Store Connect ضمن Users and Access → Integrations → App Store Connect API وتنزيل ملف المفتاح الخاص .p8.

المصادقة باستخدام مفتاح App Store Connect API

قبل استدعاء deliver، يجب إخبار Fastlane بمفتاح API المراد استخدامه. النهج الأنظف هو إجراء app_store_connect_api_key، الذي يُعيد كائن مفتاح قابلاً لإعادة الاستخدام تمرره إلى الإجراءات اللاحقة.

تهيئة مفتاح API في مسار Fastlane

# fastlane/Fastfile

lane :upload_to_store do
  # تحميل مفتاح App Store Connect API من متغيرات بيئة CI
  api_key = app_store_connect_api_key(
    key_id:        ENV["ASC_KEY_ID"],        # مثال: "ABCD1234EF"
    issuer_id:     ENV["ASC_ISSUER_ID"],     # UUID من بوابة ASC
    key_content:   ENV["ASC_KEY_CONTENT"],   # محتوى ملف .p8 (base64 أو خام)
    is_key_content_base64: true,
    duration:      1200,                     # TTL للرمز بالثواني (الحد الأقصى 1200)
    in_house:      false,                    # true فقط لـ Apple Developer Enterprise
  )

  deliver(
    api_key:              api_key,
    ipa:                  "build/Runner.ipa",
    skip_metadata:        false,
    skip_screenshots:     true,
    submit_for_review:    false,
    automatic_release:    false,
    force:                true,              # تخطي معاينة تقرير HTML
    precheck_include_in_app_purchases: false,
  )
end
نصيحة: لا تُضمِّن أبداً محتوى مفتاح .p8 في Fastfile. خزِّنه كسر CI مُشفَّر بترميز base64 (مثل base64 AuthKey_ABCD1234EF.p8) واضبط is_key_content_base64: true. في GitHub Actions، أضفه كسر مستودع مُشفَّر وارجع إليه عبر ENV["ASC_KEY_CONTENT"].

التوزيع على مجموعات TestFlight الخارجية

بعد أن يجتاز الملف الثنائي قائمة انتظار معالجة Apple (عادةً 10-15 دقيقة)، يمكنك إضافته تلقائياً إلى مجموعة TestFlight خارجية. استخدم إجراء pilot (المعروف أيضاً بـ testflight) أو مرر معاملات التوزيع مباشرةً إلى deliver.

مسار CI كامل: البناء والتوقيع والرفع والتوزيع على TestFlight

# fastlane/Fastfile

lane :beta do
  api_key = app_store_connect_api_key(
    key_id:              ENV["ASC_KEY_ID"],
    issuer_id:           ENV["ASC_ISSUER_ID"],
    key_content:         ENV["ASC_KEY_CONTENT"],
    is_key_content_base64: true,
  )

  # زيادة رقم البناء من أحدث بناء TestFlight
  increment_build_number(
    build_number: app_store_build_number(
      api_key:    api_key,
      live:       false,
    ) + 1,
    xcodeproj: "ios/Runner.xcodeproj",
  )

  # بناء وتوقيع Flutter IPA
  sh "flutter build ipa --release " \
     "--export-options-plist=ios/ExportOptions.plist"

  # الرفع إلى TestFlight والتوزيع على المجموعة الخارجية
  pilot(
    api_key:                         api_key,
    ipa:                             "build/ios/ipa/Runner.ipa",
    distribute_external:             true,
    groups:                          ["External Beta Testers"],
    notify_external_testers:         true,
    changelog:                       ENV["RELEASE_NOTES"] || "Bug fixes and improvements.",
    beta_app_review_info:            {
      contact_email:        "qa@example.com",
      contact_first_name:   "QA",
      contact_last_name:    "Team",
      contact_phone:        "+1-555-0100",
      demo_account_name:    "demo@example.com",
      demo_account_password: "DemoPass123!",
      notes:                "Use demo account to test all flows.",
    },
    skip_waiting_for_build_processing: false,
  )
end

التقديم لمراجعة التطبيق

للانتقال مباشرةً من CI إلى قائمة انتظار مراجعة التطبيق، اضبط submit_for_review: true داخل استدعاء deliver. ادمج هذا مع automatic_release: false للإبقاء على إنسان في حلقة القرار النهائي للنشر، أو اضبطه على true للإصدار الآلي الكامل بمجرد موافقة Apple.

تحذير: ضبط submit_for_review: true في CI يعني أن كل بناء ناجح في ذلك المسار سيدخل قائمة انتظار مراجعة التطبيق. بوِّب هذا المسار خلف نمط علامة محمية (مثل v*.*.*-rc) أو مشغِّل CI يدوي لتجنب تقديم بنيات التطوير للمراجعة عن طريق الخطأ.

ربط المسار في خط أنابيب GitHub Actions CI

يُشغِّل سير عمل GitHub Actions النموذجي مسار الإصدار عند دفع علامة إصدار. محتوى .p8 والأسرار الأخرى مخزَّنة كأسرار مستودع وتُحقَن في وقت التشغيل.

سير عمل GitHub Actions (.github/workflows/release.yml)

# .github/workflows/release.yml
name: App Store Release

on:
  push:
    tags:
      - "v[0-9]+.[0-9]+.[0-9]+"   # يُشغَّل عند مثل v1.4.0

jobs:
  release:
    runs-on: macos-14              # صورة Xcode 15
    steps:
      - uses: actions/checkout@v4

      - uses: subosito/flutter-action@v2
        with:
          flutter-version: "3.22.0"
          channel: stable

      - name: Install Ruby & Fastlane
        run: |
          gem install bundler --no-document
          bundle install

      - name: Install Flutter dependencies
        run: flutter pub get

      - name: Install CocoaPods
        run: cd ios && pod install --repo-update

      - name: Run Fastlane beta lane
        env:
          ASC_KEY_ID:       ${{ secrets.ASC_KEY_ID }}
          ASC_ISSUER_ID:    ${{ secrets.ASC_ISSUER_ID }}
          ASC_KEY_CONTENT:  ${{ secrets.ASC_KEY_CONTENT }}
          MATCH_PASSWORD:   ${{ secrets.MATCH_PASSWORD }}
          RELEASE_NOTES:    "Version ${{ github.ref_name }} released."
        run: bundle exec fastlane beta

هيكل البيانات الوصفية لـ Fastlane Deliver

عند ضبط skip_metadata: false، يتوقع Fastlane وجود مجلد fastlane/metadata/ بتخطيط محدد. يتيح ذلك التحكم في إصدارات كل محتوى App Store جنباً إلى جنب مع الكود:

  • metadata/en-US/name.txt — اسم التطبيق للغة المحددة
  • metadata/en-US/description.txt — الوصف الكامل لـ App Store
  • metadata/en-US/keywords.txt — كلمات مفتاحية مفصولة بفواصل
  • metadata/en-US/release_notes.txt — الجديد في هذا الإصدار
  • metadata/en-US/support_url.txt — رابط URL للدعم
  • metadata/review_information/ — معلومات تواصل مراجعة التطبيق وبيانات اعتماد العرض التجريبي
نصيحة: نفِّذ fastlane deliver init مرةً واحدة لتنزيل بيانات App Store الوصفية الحالية إلى هيكل المجلدات الصحيح. أودِع مجلد fastlane/metadata/ بالكامل في نظام التحكم بالإصدارات حتى تتم مراجعة ملاحظات الإصدار والأوصاف في طلبات الدمج تماماً مثل تغييرات الكود.

ملخص

يحوِّل Fastlane Deliver عملية تقديم App Store من سير عمل يدوي متعدد الخطوات عبر واجهة رسومية إلى أمر طرفي واحد. النقاط الرئيسية التي يجب تذكرها هي: استخدام مفتاح App Store Connect API (ليس اسم المستخدم/كلمة المرور أبداً) مخزَّناً كأسرار CI؛ استخدام deliver لرفع البيانات الوصفية والملف الثنائي؛ استخدام pilot لتوزيع TestFlight مع مجموعات المختبرين الخارجيين؛ وبوابة علامة submit_for_review خلف علامات محمية أو مشغِّلات يدوية لمنع التقديمات العرضية. مع هذا الإعداد، تُنتج كل علامة Git تلقائياً بناء TestFlight بدون أي تدخل يدوي.