أتمتة إصدارات Google Play باستخدام Fastlane Supply
أتمتة إصدارات 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).
تثبيت 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— يرفع إلى مسار betabundle 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 بأمر واحد.