GitHub Actions لـ .NET MAUI: دليل CI/CD الكامل للبناء والتوقيع والنشر لعام 2026
دليل عملي لبناء خط أنابيب CI/CD لتطبيقات .NET MAUI باستخدام GitHub Actions: توقيع Android و iOS، النشر التلقائي إلى TestFlight و Google Play، مع أمثلة YAML جاهزة للاستخدام.
إعداد CI/CD لـ .NET MAUI باستخدام GitHub Actions يعني بناء تطبيقك تلقائيًا لأنظمة Android و iOS و Windows عند كل push، ثم توقيعه بالشهادات الرسمية، ورفعه إلى TestFlight أو Google Play Internal Track دون تدخل يدوي. بصراحة، أغلب الفرق تتجاهل هذا الجزء في البداية، ثم تكتشف بعد شهرين أن البناء المحلي على جهاز مطوّر واحد أصبح عنق الزجاجة (وهو ما حدث معي حرفيًا في مشروعين متتاليين). في هذا الدليل نبني خط أنابيب متكامل من الصفر، ونعالج كل الفخاخ التي رأيتها في مشاريع حقيقية.
لبناء iOS في GitHub Actions تحتاج runner من نوع macos-14 على الأقل مع Xcode 15.4+ وworkload maui-ios مثبّتة صراحة.
يتم تخزين شهادات Apple (.p12) وملفات التزويد (.mobileprovision) وkeystore الخاص بـ Android كأسرار مُشفَّرة بـ Base64 داخل GitHub Secrets، ولا يجب رفعها للمستودع أبدًا.
استخدم مصفوفة (matrix) واحدة لبناء Android على ubuntu-latest و iOS على macos-14 بالتوازي لتقليل زمن التنفيذ إلى النصف تقريبًا.
ضبط تخزين مؤقت (cache) لحزم NuGet وworkloads الخاصة بـ .NET يوفّر 4–6 دقائق في كل عملية بناء.
Fastlane يبقى الأداة الأنسب للنشر إلى TestFlight وGoogle Play من داخل GitHub Actions في 2026، مع دعم كامل لـ App Store Connect API.
رقم البناء (ApplicationDisplayVersion وApplicationVersion) يجب أن يُحقن من متغيّرات البيئة أثناء البناء، لا أن يُكتب يدويًا في ملف المشروع.
لماذا GitHub Actions لـ .NET MAUI تحديدًا؟
عندما بدأتُ العمل على أول خط أنابيب لتطبيق .NET MAUI ضخم في 2024، جرّبنا ثلاث منصات مختلفة قبل الاستقرار على GitHub Actions. السبب لم يكن السعر فقط، بل التكامل الأصلي مع المستودع، ومكتبة الإجراءات الجاهزة، وسهولة إدارة الأسرار عبر البيئات (Environments).
في 2026، أصبح دعم GitHub Actions لـ .NET MAUI أكثر نضجًا مع توفّر runners من نوع macos-14 وmacos-15 التي تأتي مع Xcode 15.4 و16 مثبّتَين مسبقًا، بالإضافة إلى .NET 9 SDK. هذا يعني أن مرحلة الإعداد (setup) تختصر من 8 دقائق إلى أقل من دقيقتين في أغلب المشاريع.
الميزة الأهم من وجهة نظر DevOps: القدرة على تعريف environments منفصلة لـ staging وproduction مع قواعد حماية (approval reviewers, wait timers) تمنع النشر العرضي إلى المتاجر. هذه الطبقة الإدارية غير متوفرة بنفس البساطة في أدوات مثل Bitrise أو App Center.
إذا لم تكن على دراية بإصدار .NET MAUI 10 وميزاته الجديدة، أنصح بمراجعة دليل .NET MAUI 10 الشامل قبل بدء إعداد خط الأنابيب، لأن بعض أوامر workload تغيّرت في هذا الإصدار.
مقارنة بين GitHub Actions و Azure DevOps و Bitrise
قبل أن نلتزم بأداة، من المفيد وضعها جنبًا إلى جنب. الجدول التالي يلخّص التجربة الفعلية بعد بناء خطوط أنابيب حقيقية على المنصات الثلاث لتطبيقات .NET MAUI:
المعيار
GitHub Actions
Azure DevOps
Bitrise
الدقائق المجانية شهريًا (للمشاريع الخاصة)
2000 دقيقة Linux / 500 دقيقة macOS (خطة Pro)
1800 دقيقة Microsoft-hosted
300 build ≤10 دقائق (الخطة المجانية)
runners لـ macOS جاهزة
نعم، macos-14/macos-15
نعم، macOS-latest
نعم، مُخصّصة لـ mobile
سهولة إعداد أول pipeline
عالية (YAML واحد)
متوسطة (Classic أو YAML)
عالية جدًا (workflows جاهزة)
إدارة توقيع iOS تلقائيًا
يدوي عبر fastlane match
Apple App Store extension
مُدمج (Code Signing tab)
مكتبة الإجراءات الجاهزة
ضخمة (Marketplace)
متوسطة
متخصصة في mobile
التكامل مع GitHub Issues/PRs
أصلي
عبر webhooks
عبر webhooks
الأنسب لـ
فرق تستخدم GitHub بالفعل
فرق Microsoft/Azure
شركات mobile-only
الخلاصة العملية: إذا كان مستودعك على GitHub، فإن GitHub Actions هو الخيار الافتراضي المنطقي. Bitrise يستحق النظر إذا كنت مبتدئًا تمامًا في CI/CD ولا تريد كتابة YAML، لكن الفاتورة ترتفع بسرعة مع نمو الفريق.
تحضير المستودع وهيكل خط الأنابيب
قبل كتابة أي workflow، لا بد من ترتيب المستودع بطريقة تُسهّل الأتمتة. القاعدة التي أطبّقها في كل مشروع:
مجلد .github/workflows/ يحتوي على ملفات منفصلة: build.yml (لكل push)، release.yml (للنشر عند إطلاق tag)، وpr-check.yml (لفحص pull requests).
ملف Directory.Build.props في جذر المشروع يحدّد ApplicationDisplayVersion وApplicationVersion من متغيّرات البيئة.
مجلد build-scripts/ يحتوي على سكربتات bash المشتركة (تركيب الشهادات، فك تشفير الأسرار).
لاحظ ثلاث نقاط مهمة: تحديد إصدار Java 17 صراحة (المطلوب لـ Android SDK 34)، تفعيل التخزين المؤقت لـ NuGet، وتثبيت workload maui-android فقط بدلًا من الحزمة الكاملة، وهذا يوفّر حوالي 3 دقائق لأننا لا نحتاج iOS على runner Linux.
بناء وتوقيع تطبيق Android
توقيع Android في CI/CD هو أبسط جزء لأنه لا يعتمد على منصة macOS ولا على شهادات معقّدة. كل ما تحتاجه هو ملف .keystore واحد وأربعة أسرار: مسار الملف، كلمة السر، اسم الـ alias، وكلمة سر الـ alias.
الخطوة الأولى: تحويل ملف keystore إلى Base64 لتخزينه في GitHub Secrets. نفّذ هذا الأمر محليًا مرة واحدة:
base64 -i myapp.keystore -o myapp.keystore.base64
# على macOS: pbcopy < myapp.keystore.base64
# على Linux: xclip -sel clip < myapp.keystore.base64
ثم أضف السر ANDROID_KEYSTORE_BASE64 في Settings → Secrets and variables → Actions. أضف أيضًا ANDROID_KEYSTORE_PASSWORD، ANDROID_KEY_ALIAS، وANDROID_KEY_ALIAS_PASSWORD.
الآن نُوسّع الوظيفة build-android لتُنتج AAB مُوقّع جاهز للنشر إلى Google Play:
هنا نُنتج AAB (Android App Bundle) بدل APK لأنه المتطلَّب الرسمي لـ Google Play منذ 2021. الملف يُرفع كـ artifact ليكون متاحًا للتحميل من واجهة GitHub Actions أو لاستخدامه في مرحلة النشر التالية.
بناء وتوقيع تطبيق iOS في GitHub Actions
هذا هو الجزء الذي يُسبب معظم الصداع. توقيع iOS يتطلّب: شهادة توزيع (.p12)، ملف تزويد (.mobileprovision)، وسلسلة مفاتيح (keychain) مؤقتة على runner. الحل الأنيق هو استخدام fastlane match، لكن للسيطرة الكاملة سأشرح الطريقة اليدوية أولًا.
حوّل شهادتك وملف التزويد إلى Base64 وضعهما في الأسرار:
القيمة CodesignKey يجب أن تطابق الاسم الظاهر في Keychain Access تمامًا (بما في ذلك اسم الفريق بين قوسين). CodesignProvision هو الاسم الداخلي لملف التزويد (Name في developer portal، وليس اسم الملف).
بعد الانتهاء من إعداد التوقيع، تأكّد من أن الاختبارات تعمل ضمن نفس خط الأنابيب. راجع دليل اختبار تطبيقات .NET MAUI لإضافة مرحلة dotnet test قبل مرحلة النشر لتفادي نشر إصدارات معطوبة.
النشر التلقائي إلى TestFlight و Google Play
بعد إنتاج AAB و IPA موقّعَين، الخطوة الأخيرة هي دفعهما إلى المتاجر. Fastlane لا يزال الحل الأفضل في 2026 لأنه يدير التفاصيل المُملّة (rate limits، محاولات إعادة الرفع، تحديث metadata) بدلًا منك.
أنشئ ملف fastlane/Fastfile في جذر المشروع:
default_platform(:ios)
platform :ios do
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
)
upload_to_testflight(
api_key: api_key,
ipa: ENV["IPA_PATH"],
skip_waiting_for_build_processing: true,
changelog: ENV["RELEASE_NOTES"] || "Nightly build"
)
end
end
platform :android do
lane :internal do
upload_to_play_store(
track: "internal",
aab: ENV["AAB_PATH"],
json_key_data: ENV["PLAY_STORE_JSON_KEY"],
skip_upload_apk: true,
skip_upload_metadata: true,
skip_upload_images: true,
skip_upload_screenshots: true
)
end
end
لاستخدام App Store Connect API (الطريقة الحديثة والمُفضّلة من Apple)، عليك إنشاء مفتاح API من App Store Connect → Users and Access → Keys. حمّل ملف .p8، ثم حوّله إلى Base64 وضعه في السر ASC_KEY_CONTENT مع ASC_KEY_ID وASC_ISSUER_ID.
لـ Google Play، أنشئ Service Account من Google Cloud Console، امنحه صلاحية Release Manager في Play Console، حمّل ملف JSON، وضع محتواه الكامل في السر PLAY_STORE_JSON_KEY. بعدها، أضف مرحلة النشر إلى workflow:
استخدام environment: production هنا مهم جدًا: يمكنك في إعدادات المستودع تعريف هذه البيئة وربطها بقاعدة مراجعة (required reviewers) بحيث لا يبدأ النشر إلا بعد موافقة يدوية من مسؤول. هذه طبقة أمان لا تُقدَّر بثمن.
إدارة أرقام الإصدارات ورقم البناء
الخطأ الأكثر شيوعًا الذي أراه: فرق تحدّث رقم البناء يدويًا في csproj قبل كل نشر، ثم ينسون، فيرفض المتجر الإصدار الجديد لأن رقم البناء لم يزد. الحل هو حقن الرقم من CI مباشرة.
هذا يضمن أن كل build يحصل على رقم فريد، ولا حاجة لأي commit يدوي لتحديث الإصدار. عند إصدار نسخة رئيسية جديدة، غيّر فقط الجزء الثابت من SEMVER في workflow.
الأخطاء الشائعة وكيفية تجنّبها
بعد سنوات من تشخيص فشل خطوط الأنابيب، هذه هي القائمة القصيرة للفخاخ التي أراها بشكل متكرر:
1. نفاد الحصة الشهرية لـ macOS minutes
runners macOS تُحسب بمعدّل 10× مقارنة بـ Linux. مشروع نشط قد يستنفد الـ 500 دقيقة الشهرية في أسبوع. الحلول: ابنِ iOS فقط عند push إلى main أو release/*، واستخدم paths filter لتجاهل التغييرات على ملفات .md أو مجلد docs/.
2. keychain مقفلة أثناء codesign
Runner macOS يقفل الـ keychain بعد فترة قصيرة. أضف دائمًا security set-keychain-settings -t 3600 -u بعد إنشائها لتمديد الوقت إلى ساعة كاملة، وإلا ستفشل عملية التوقيع في منتصف البناء الطويل.
3. workload versions غير متطابقة
إذا كان مشروعك مربوطًا بإصدار .NET SDK محدد عبر global.json، تأكّد أن نسخة workload المُثبّتة على runner تطابقها. استخدم dotnet workload restore بدل install لضمان التوافق التلقائي.
4. البناء يستهلك ذاكرة أكثر من المتاح
runners GitHub القياسية توفّر 7 جيجابايت RAM فقط. تطبيقات MAUI الكبيرة قد تصطدم بحدود الذاكرة أثناء AOT compilation. الحل: أضف -p:AndroidEnableProfiledAot=false مؤقتًا، أو ارقِ إلى larger runner. إذا كنت تعاني من بطء البناء بشكل عام، راجع دليل تحسين أداء .NET MAUI — بعض التحسينات على مستوى المشروع تقلّل زمن البناء أيضًا.
5. أسرار تُطبع في السجلّات
GitHub يحجب الأسرار تلقائيًا في السجلّات، لكن هذا لا يعمل إذا مررت السر كـ argument. استخدم دائمًا env: block. للتحقّق، أضف run: env | grep -v "SECRET\|PASSWORD\|KEY" لتأكيد أن أسماء الأسرار لا تظهر في المتغيّرات المصدَّرة.
هل يمكن بناء تطبيق .NET MAUI iOS على Windows في GitHub Actions؟
لا. بناء iOS يتطلّب أدوات Xcode التي تعمل فقط على macOS. عليك استخدام runner من نوع macos-14 أو أحدث. البناء عن بُعد عبر Pair to Mac غير عملي في CI لأنه يتطلّب جهاز Mac دائم الاتصال.
كم يستغرق بناء تطبيق .NET MAUI في GitHub Actions؟
مع تفعيل التخزين المؤقت لـ NuGet وworkloads، بناء Android يستغرق 4–7 دقائق، وبناء iOS الموقّع يستغرق 8–12 دقيقة. البناء الأول دون تخزين مؤقت قد يستغرق 15–25 دقيقة بسبب تنزيل workloads.
ما الفرق بين fastlane match و import شهادة يدويًا؟
fastlane match يخزّن شهادات فريقك في مستودع Git خاص مشفّر، ويزامنها تلقائيًا بين المطوّرين وbuild agents. الطريقة اليدوية أبسط لخط أنابيب واحد ومطوّر منفرد، لكن match لا يُعوَّض في فرق تضم 5+ مطوّرين.
هل GitHub Actions مجاني للمشاريع الخاصة؟
الحساب المجاني يمنحك 2000 دقيقة Linux شهريًا للمستودعات الخاصة، لكن دقائق macOS تُحسب بمعدّل 10×، أي أن كل دقيقة iOS build تستهلك 10 دقائق من الحصة. المشاريع النشطة تحتاج غالبًا خطة GitHub Team ($4/مستخدم/شهر) للحصول على دقائق كافية.
هل يجب استخدام self-hosted runners بدلًا من GitHub-hosted؟
Self-hosted runners منطقية فقط إذا كان لديك جهاز Mac مخصّص متصل دائمًا، وتريد تجاوز حدود الدقائق أو تسريع البناء عبر تخزين مؤقت محلي كبير. لكنها تضيف عبء صيانة (تحديثات Xcode، أمان الشبكة). لأغلب الفرق حتى 20 مطوّرًا، GitHub-hosted أرخص وأبسط.
كيف أختبر workflow قبل دمجه إلى main؟
افتح pull request من فرع منفصل — سيعمل workflow تلقائيًا على PR. لاختبار محلي دون push، استخدم أداة act (nektos/act) التي تُشغّل workflows على Docker محليًا. لا تعمل على وظائف macOS، لكنها ممتازة لتصحيح خطوات Linux.
تجربة عملية لتقليل حجم تطبيق .NET MAUI Android من 84 إلى 31 ميجابايت باستخدام التقليم الكامل و R8 و Android App Bundle وتحسين الصور والخطوط دون خسارة أي ميزة.