Andrew Hanna

Andrew Hanna

كيفية بناء Salesforce CI/CD Pipeline باستخدام GitHub Actions

كيفية بناء Salesforce CI/CD Pipeline باستخدام GitHub Actions

تطور Salesforce development كثيرا منذ أيام دفع التغييرات يدويا عبر Change Sets. ومع نمو الفرق وزيادة عدد releases، تتحول deployments اليدوية بسرعة إلى bottleneck.

هنا يأتي دور CI/CD (Continuous Integration and Continuous Delivery). فالـ pipeline الجيد يحقق validate وtest وpromote للتغييرات تلقائيا من development إلى production. وحتى مع أن فرقا كثيرة تستخدم منصات DevOps تجارية، يبقى مفيدا أن تفهم كيف تعمل الـ automation الأساسية تحتها.

في هذا الدليل نوضح كيف تعمل Salesforce CI/CD اليوم، وكيف تبني pipeline باستخدام GitHub Actions، وأي المناهج تناسبك أكثر حسب حجم فريقك ووتيرة الـ releases وتعقيد الـ org. كما نستعرض أين تبدأ الـ DIY pipelines تتعثر ولماذا تنتقل فرق كثيرة إلى منصة Salesforce DevOps مخصصة بمجرد أن تنضج عمليتها.

جديد على المجال؟ دليلنا لإتقان Salesforce DevOps يغطي الأساسيات التي يُبنى عليها هذا الـ pipeline.

لماذا CI/CD مهم في Salesforce

Salesforce development يختلف عن تطوير التطبيقات التقليدي. فرق Salesforce لا تنشر تطبيقا compiled، بل metadata وconfiguration مثل:

  • Apex classes وtriggers

  • Lightning Web Components

  • Flows وautomation

  • Profiles وpermission sets

  • Objects وfields وlayouts وschema

بدون automation، تصبح الـ deployments هشة بسرعة.

المشاكل الشائعة تشمل:

  • drift في الإعدادات بين البيئات

  • deployments معطوبة بسبب dependencies ناقصة

  • أخطاء نشر يدوية

  • رؤية محدودة على ما تغيّر فعليا

  • ضعف traceability بين work items ومحتوى release

تعالج CI/CD pipelines ذلك عبر:

  • validate للتغييرات تلقائيا

  • تشغيل tests قبل deployment

  • استخدام Git كمصدر للحقيقة

  • promote للتغييرات المعتمدة فقط

  • الحفاظ على اتساق البيئات مع الوقت

ما تحتاجه قبل أن تبدأ

هذا الدليل يفترض repo وorgs مهيّأة أصلا للأتمتة. إن نقص أي منها، اسدّ هذه الفجوة أولا، لأن أي pipeline فوقها تؤتمت الفجوة نفسها.

  • metadata تحت نظام إصدارات بالفعل، بصيغة source format. كل ما تنوي نشره موجود تحت force-app وله تاريخ commits. الـ pipeline تعمل promote للـ commits لا للـ orgs، فأي شيء موجود في sandbox فقط لا تراه.

  • Salesforce CLI v2 (sf) على الـ runner. كل أمر في هذا الدليل بصيغة v2، ويُثبَّت داخل الـ workflow بـ npm install @salesforce/cli --global. الـ sfdx CLI القديم deprecated.

  • org واحدة لكل مرحلة، ولها مالك واضح. على الأقل sandbox للتحقق والـ org التي تُصدر إليها. استراتيجية الـ branching لاحقا تربط الـ branches بهذه الـ orgs واحدة بواحدة.

  • Connected App وintegration user في كل org هدف، مع تفعيل digital signatures وتصريح مسبق للمستخدم. خطوات الإعداد كاملة في قسم Connected App وJWT authentication لاحقا.

نماذج Salesforce development

1. Org-based development

هذا ما زال النموذج الأكثر شيوعا. يعمل developers داخل sandboxes، ثم يتم retrieve للـ metadata إلى Git، وبعدها تقوم CI/CD pipeline بترقيتها عبر البيئات.

Developer Sandbox
      ↓
QA
      ↓
UAT / Staging
      ↓
Production

لا تزال فرق enterprise كثيرة تعمل بهذا الشكل، حتى عندما تبدأ إدخال unlocked packages لاحقا في مسار الـ release.

2. Package-based development

يستخدم package-based development حزم modular بدلا من نقل metadata كاملة ككتلة واحدة.

كل package يمثل feature set أو domain منطقي. هذا الأسلوب شائع لدى:

  • ISVs

  • AppExchange partners

  • enterprise teams كبيرة ذات workstreams متوازية متعددة

والواقع أن فرقا كثيرة تبدأ بنموذج org-based ثم تتبنى تدريجيا packages من أجل release versioning وpromotion أنظف.

أنماط مقترحة حسب حجم الفريق

فرق صغيرة

Developer Sandbox
      ↓
Retrieve to Git
      ↓
Pull Request validation
      ↓
Build unlocked package version
      ↓
Install in Staging / QA Sandbox
      ↓
Install same package version in Production

يعمل هذا عندما يكون الفريق صغيرا ومتناسقا، وrelease frequency عالية، وبيئة staging أو QA واحدة كافية للـ signoff.

يصلح هذا النموذج في الحالات التالية:

  • الفريق صغير ومتناسق

  • release frequency عالية

  • بيئة staging أو QA واحدة كافية للتحقق النهائي

  • الفريق يريد انضباطا بدون عبء process كبير

النقطة الأساسية أن حتى الفريق الصغير يحتاج release process نظيفة، لكن يجب ألا تكون أثقل من الفريق نفسه.

فرق نامية وكبيرة

الفرق الأكبر تحتاج غالبا إلى QA وUAT منفصلتين. هذا النموذج أقوى للفرق الأكبر لأن QA وUAT تخدمان غرضين مختلفين:

  • QA تتحقق من integrated source changes من عدة مساهمين

  • UAT تتحقق من release artifact الفعلي

  • Production تستقبل نفس package version الذي اجتاز UAT

Qa Devops

Developer Sandbox
      ↓
Feature branch
      ↓
Pull Request validation
      ↓
Merge to develop
      ↓
Deploy source to QA
      ↓
Create release branch
      ↓
Build unlocked package version
      ↓
Install package in UAT
      ↓
Install same package version in Production

تستقبل production نفس نسخة الـ package التي اجتازت UAT، ما يمنح traceability أفضل للـ release، وخيارات rollback أنظف، ومفاجآت أقل في production.

ISVs

تتبع ISVs غالبا نفس المبادئ، لكن workflows عندها تكون package-led من البداية.

Scratch Org / Dev Org
      ↓
Source tracked to Git
      ↓
Pull Request validation
      ↓
Package version build
      ↓
Install in internal test org
      ↓
Install in staging / UAT
      ↓
Promote release artifact

نفس الأفكار تنطبق هنا: Git كمصدر للحقيقة، خطوات promotion واضحة، وrelease artifacts versioned.

فرق متقدمة وmodular packages

مع نمو org الخاصة بـ Salesforce وزيادة عدد المجالات فيها، قد يصبح unlocked package واحد واسعا جدا. عندها تصبح modular unlocked packages الخيار الأنسب غالبا.

من الأمثلة:

  • package لـ sales pipeline وopportunity management

  • package لـ service operations

  • package لـ customer onboarding

  • package لـ core automation

هذا مفيد بشكل خاص في الشركات الكبيرة حيث تعمل فرق متعددة على مجالات مختلفة بالتوازي. ومع نمو الفريق أو تعقّد الـ org، لازم DevOps process تكبر معه للحفاظ على السرعة والجودة والرؤية وtraceability.

Branching strategy

قبل بناء pipeline، اختر كيف يتحرك العمل داخل Git. الخياران الأكثر شيوعا هما GitFlow وtrunk-based development.

GitFlow

main      → production history
develop   → integration branch
feature/* → work item branches
release/* → release candidate branches
hotfix/*  → urgent production fixes

يناسب GitFlow الحالات التالية:

  • عدة أشخاص يعملون بالتوازي

  • QA وUAT تحتاجان branches ثابتة

  • releases أسبوعية أو كل أسبوعين أو على جدول محدد

  • الفريق يريد release candidate branches واضحة

العيب هو مزيد من إدارة الـ branches وتنسيق الـ merge.

Trunk-based development

main       → always releasable
feature/*  → short-lived branches merged quickly

يناسب هذا النموذج الحالات التالية:

  • الفريق صغير

  • deployments متكررة

  • validation وtesting قويان

  • الفريق يريد branches أقل طول عمرا

العيب أن ضعف انضباط الاختبارات يمكن أن يزعزع الـ branch المشترك بسرعة.

أي النموذجين نرشح؟

بالنسبة لأغلب فرق Salesforce ذات النموذج org-based، GitFlow أسهل في التبني لأنه يعكس نموذج الـ release المألوف Dev → QA → UAT → Prod. أما trunk-based development فيناسب الفرق الصغيرة الناضجة ذات automation قوية وdeployments متكررة.

المهم ليس أن نموذجا واحدا أفضل دائما. المهم أن تختار نموذجا واحدا بوعي وتجعل بيئاتك وموافقاتك ومنطق الـ pipeline متسقة معه.

معمارية pipeline المقترحة

لأغلب فرق Salesforce النامية، النموذج العملي هو:

Developer Sandbox
      ↓
Retrieve metadata to Git
      ↓
Feature branch
      ↓
Pull Request validation
      ↓
Merge to develop
      ↓
Deploy source to QA
      ↓
Create release branch
      ↓
Build unlocked package version
      ↓
Install package in UAT
      ↓
Regression testing and signoff
      ↓
Install same package version in Production

لماذا نرشح هذا النموذج؟

  • source deploy إلى QA مرن وسريع لاختبارات integration

  • package version يصبح release artifact ثابت

  • UAT تتحقق من نفس الـ artifact المخصص لـ production

  • production تستقبل نفس package version الذي اختُبر

source deploy إلى QA سريع لاختبارات integration، بينما تتحول package version إلى release artifact ثابتة للـ UAT وproduction.

هذا يخلق عملية عملية ومضبوطة في آن واحد. كما يعمل جيدا مع source tracking أثناء development بينما يمنحك traceability أفضل للـ release لاحقا في المسار.

package.xml مقابل source tracking مقابل Serpent workflow

الفئة Package.xml Source tracking + unlocked packages Serpent workflow
النموذج Manifest-driven deployment Git-driven development مع package promotion Task-driven workflow حول Git والبيئات وrelease visibility
أفضل استخدام Hotfixes والفرق legacy فرق حديثة تريد versioned releases فرق تريد سرعة ورؤية وتشغيل أسهل
اختيار الـ metadata يدوي source tracking مع اختيار عبر Git اختيار موجّه حسب المهمة ويراعي dependencies
Dependencies يدوية أفضل، لكنها تحتاج discipline مدعومة بـ metadata analysis وworkflow logic
Production traceability محدودة جيدة عند استخدام package versions رؤية قوية عبر work items وreleases وorgs
وضوح rollback أصعب، خصوصا مع destructive changes أفضل مع versioned packages أوضح عبر release artefacts وworkflow history وpackage promotion
Hotfix / back-promotion مفيد وغالبا ضروري لا يزال يحتاج أحيانا package.xml لاسترجاع production مدعوم كجزء من workflow الـ release ونموذج الرؤية
العبء التشغيلي منخفض بداية، مرتفع لاحقا متوسط عبء أقل مع الوقت لأن جزءا أكبر من الـ workflow أصلي ومرئي
التعاون يدوي غالبا Git-centric ومناسب للمطورين مناسب للمطورين وadmins وtesters وrelease managers
متى نرشحه حالات خاصة وprocesses قديمة الخيار الافتراضي المرشح لأغلب الفرق الحديثة مرشح عندما تريد نفس مبادئ DevOps بأقل احتكاك

استخدام package.xml وsource tracking

استخدام package.xml

package.xml هو manifest التقليدي الذي يحدد metadata المطلوب retrieve أو deploy لها.

مثال:

<?xml version="1.0" encoding="UTF-8"?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
  <types>
    <members>AccountService</members>
    <name>ApexClass</name>
  </types>
  <types>
    <members>AccountTrigger</members>
    <name>ApexTrigger</name>
  </types>
  <version>61.0</version>
</Package>

إيجابيات:

  • تحكم صريح فيما يُنشر

  • يعمل في كل أنواع الـ orgs بما فيها production

  • مفيد لـ hotfixes والـ deploys المستهدفة

سلبيات:

  • صيانته يدوية

  • سهل أن يفوّت dependencies

  • لا يعكس تلقائيا ما تغيّر في الـ org

  • يصعب إدارته مع اتساع نطاق الـ release

يمنحك تحكما صريحا، لكنه يدوي وسهل أن يفوّت dependencies ويصعب مع releases الكبيرة.

فرق كثيرة لا تزال تستخدم package.xml، وهذا منطقي تاريخيا. لفترة طويلة كان الخيار الأعملي، خصوصا حين كان source tracking لا يزال غير ناضج.

استخدام source tracking

يتيح source tracking لـ Salesforce CLI مقارنة ما تغيّر في org بالنسبة لـ local source. هذا مفيد بشكل خاص في scratch orgs، وأصبح عمليا أكثر بكثير في workflows الحديثة القائمة على sandboxes مع نضج المنصة.

تحسّن هذا المجال كثيرا بعد الانتقال من الـ SFDX CLI القديم إلى sf CLI الحديث، واستمر في التحسن خلال العامين الأخيرين.

من الأوامر الشائعة:

sf project retrieve start
sf project deploy start

إيجابيات:

  • تغذية راجعة أسرع أثناء development

  • يقلل صيانة الـ manifest اليدوية

  • تجربة أفضل للمطورين

  • يعمل جيدا مع promotion قائم على Git

سلبيات:

  • production لا يدعم source tracking بنفس الطريقة

  • لا تزال الفرق تحتاج استراتيجية لـ release versioning

  • اختيار جزء من ميزة قد يبقى صعبا بدون الأدوات المناسبة

في أغلب الفرق الحديثة، استخدم source tracking أثناء development وunlocked packages أثناء release promotion.

لماذا نرشح source tracking وunlocked packages

لأغلب الفرق الحديثة، نرشح استخدام source tracking أثناء development وunlocked packages من أجل release promotion.

لماذا؟

  • source tracking تجربة development يومية أفضل من الـ manifests اليدوية

  • unlocked packages تمنحك release versioning حقيقي لاحقا في المسار

  • production traceability تصبح أوضح بكثير عبر package versions

  • الـ rollback والتعامل مع destructive changes أسهل أن تفهمه

مع ذلك، لا يزال package.xml مهما في حالات خاصة، فالهدف الحقيقي ليس النقاء. الهدف الحقيقي هو استخدام الأداة الصحيحة في الخطوة الصحيحة.

ماذا عن production؟

production هو حيث تصبح قيود source tracking مهمة. لا يمكنك التعامل مع production كأنها scratch org وتطلب من Salesforce أن تخبرك بوضوح بما تغيّر فيها مع الوقت.

هذا يعني أن production deployments تعتمد غالبا على:

  • package.xml manifests

  • مجلدات source مبنية من Git

  • unlocked package versions

هذا أحد الأسباب الرئيسية التي تجعل versioned package promotion مفيدا جدا. فهو يمنح production هوية release أوضح مما يستطيع source tracking وحده أن يوفره هناك.

يجب ألا نبالغ في القصد هنا. النقطة العملية بسيطة: production لا يتصرف كـ dev org source-tracked، لذا تحتاج الفرق طريقة أخرى لجعل حالة الـ release واضحة. unlocked packages من أفضل الطرق لتحقيق ذلك.

اختيار feature-level changes بأمان

تحتوي sandbox غالبا على تغييرات متعددة. النمط الآمن هو retrieve لكل شيء، ثم اختيار components الخاصة بميزة واحدة فقط وcommit لها في feature branch.

تبدو العملية اليدوية عادة هكذا:

  1. retrieve لكل شيء من الـ sandbox

  2. مراجعة الملفات المتغيرة في Git

  3. اختيار الـ metadata الخاصة بالميزة فقط

  4. commit لنطاق تلك الميزة فقط في الـ feature branch

مثال:

sf project retrieve start
git status
git add force-app/main/default/classes/PricingService.cls
git add force-app/main/default/triggers/OpportunityTrigger.trigger
git add force-app/main/default/flows/Discount_Approval.flow-meta.xml
git commit -m "Feature: pricing approval flow"

التحدي أن الـ dependencies ليست واضحة دائما. قد يشير Flow إلى field، وقد يعتمد field على permission set، وقد تحتاج تغيير permission set لتصبح الميزة قابلة للاستخدام.

هنا غالبا تفوّت الفرق metadata أو تنشر metadata زائدة فقط لتحتاط.

مع Serpent يصبح ذلك أسهل لأن الـ workflow مبني حول work items وmetadata selection. يساعد Serpent في تحديد أي components تخص الميزة، وAI المدرّب على Salesforce فيه يساعد في اختيار الـ metadata المنطقي وتحليل الـ dependencies بناء على وصف الميزة نفسه.

Delta deployments مقابل full branch deployments

Delta deployments تنشر فقط الـ metadata التي تغيّرت. مفيدة عند:

  • التحقق السريع من الـ pull requests

  • ترقية ميزات صغيرة ومعزولة

  • تقليل وقت الـ validation في orgs كبيرة جدا

مثال باستخدام sfdx-git-delta:

sf sgd source delta \
  --to HEAD \
  --from origin/develop \
  --output-dir delta \
  --generate-delta

ثم تحقق من الـ delta أو انشرها:

sf project deploy validate --source-dir delta --test-level RunLocalTests
sf project deploy start --source-dir delta

أدوات CLI مفتوحة المصدر تغلّف نفس الأوامر خلف menu موجّه، وهو حل وسط معقول بين scripts خام ومنصة كاملة. راجع كيف يقارَن Serpent بـ sfdx-hardis إذا كان هذا المسار الذي تقيّمه.

Full branch deployments تنشر شجرة الـ metadata كاملة أو حالة branch كاملة. هي أفضل عند:

  • التعامل مع deletions أو destructive changes

  • عمل revert لـ commits

  • التأكد أن البيئة تطابق حالة branch معروفة

  • إعادة ضبط drift في بيئة مشتركة

  • نشر profiles أو permission sets، حيث يجب أن يصل الملف كاملا كوحدة واحدة لا كـ diff محسوب

Full deployments أفضل مع destructive changes وrevert commits وإعادة ضبط drift في البيئات المشتركة. ونفس الكلام على الأنواع اللي لسه عايشة في ملفات مشتركة كبيرة، وأهمها profiles وpermission sets، لأنها مابتعملش diff نضيف والـ delta ممكن يفوّتها أو يكتب فوق جزء منها.

مثال:

sf project deploy start --source-dir force-app

في الممارسة، العملية القوية تستخدم الاثنين معا:

  • delta deploys من أجل السرعة

  • full deploys من أجل اليقين حين يتطلبه نوع التغيير

كلا الأسلوبين يدفع الكود إلى أعلى. والاتجاه المعاكس لا يقل أهمية: بمجرد أن يذهب hotfix إلى الـ production مباشرة تصبح كل البيئات الأدنى متأخرة عنه، إلى أن تُعِدّ الـ back-promotion داخل الـ pipeline، وهي خطوة تتخطاها أغلب الـ workflows المبنية يدويا.

يساعد Serpent هنا أيضا بجعل مسار الـ deployment أقل هشاشة. فبدل أن يعيد كل فريق بناء هذا المنطق يدويا في GitHub Actions، يستطيع الـ workflow أن يوجّه متى تكون الحركة المستهدفة مناسبة ومتى يكون promotion أوسع أكثر أمانا.

Unlocked packages من أجل release versioning

حتى في org-based development، يمكن لـ unlocked packages أن تحسّن إدارة الـ release بشكل ملموس.

بدل نشر مجموعة متفرقة من الـ metadata مباشرة في كل بيئة أعلى، يمكن للفرق تجميع مجموعة منطقية من التغييرات في release artefact versioned.

تجعل unlocked packages releases أكثر وضوحا: يتم promote لـ versioned artifact بدلا من metadata متفرقة. هذا يحسن traceability وUAT-to-production promotion وخطة rollback.

هذا يعني:

  • أرقام إصدار واضحة للـ releases

  • promotion أسهل من UAT إلى Production

  • رؤية أفضل لما هو منشور وأين

  • تعامل أنظف مع dependencies

  • تخطيط rollback أكثر قابلية للتوقع

تدفق unlocked package المقترح

لفريق org-based أكبر، يبدو مسار الـ release عادة هكذا:

Developer Sandbox
      ↓
Retrieve to Git
      ↓
Pull Request validation
      ↓
Deploy source to QA
      ↓
Create release branch
      ↓
Build unlocked package version
      ↓
Install in UAT
      ↓
Regression and approval
      ↓
Install same package version in Production

وبالنسبة لفريق صغير جدا، يمكن أن يكون أبسط:

Developer Sandbox
      ↓
Retrieve to Git
      ↓
Pull Request validation
      ↓
Build unlocked package version
      ↓
Install in Staging
      ↓
Promote same package version to Production

أمثلة أوامر

sf package create --name SalesApp --package-type Unlocked --path force-app
sf package version create --package SalesApp --wait 20 --installation-key-bypass
sf package install --package 04tXXXXXXXXXXXX --target-org myUATOrg --wait 20
sf package install --package 04tXXXXXXXXXXXX --target-org myProdOrg --wait 20

لماذا يصبح rollback أوضح

لا تحل unlocked packages كل مشكلة rollback بشكل سحري، خصوصا حين تكون تغييرات البيانات جزءا من الأمر. لكن مقارنة بـ source deployment الخام أو promotion المعتمد فقط على package.xml، تجعل الـ rollback أسهل أن تفهمه لأن:

  • الـ release عبارة عن artefact versioned

  • تعرف بالضبط أي package version أدخل التغيير

  • يمكنك مقارنة ما تغيّر بين الإصدارات بوضوح أكبر

  • destructive changes أسهل تتبعا في سياق تاريخ الـ release

هذا يهم أكثر في production، حيث لا يمنحك source tracking نفس الرؤية التي يمنحها في بيئات development.

بناء GitHub Actions pipeline

GitHub Actions خيار مناسب للفرق التي تريد فهم mechanics وبناء CI/CD foundation داخل repository. يتم تعريف workflows كملفات YAML بجانب الكود.

خريطة branch-to-environment المقترحة

feature/*   → developer work item branches
develop     → deployed to QA
release/*   → package version build
main        → production history
hotfix/*    → urgent production fixes

مثال pipeline كامل من طرف إلى طرف

يوضح المثال التالي إعدادا عمليا من طرف إلى طرف يستخدم:

  • PR validation قبل الدمج في develop

  • source deploy تلقائي إلى QA عند الدمج في develop

  • package version build على release/*

  • package install إلى UAT

  • promotion يدوي لنفس package version إلى Production

name: Salesforce CI/CD

on:
  pull_request:
    branches: [develop]
  push:
    branches:
      - develop
      - 'release/*'
  workflow_dispatch:
    inputs:
      package_version_id:
        required: false

env:
  PROJECT_DIR: force-app
  DELTA_DIR: delta
  QA_ALIAS: qa
  UAT_ALIAS: uat
  PROD_ALIAS: prod
  PACKAGE_NAME: SalesApp

jobs:
  pr-validate:
    if: github.event_name == 'pull_request'
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Install Salesforce CLI
        run: npm install @salesforce/cli --global

      - name: Install plugins
        run: |
          sf plugins install sfdx-git-delta
          sf plugins install @salesforce/plugin-packaging

      - name: Create JWT key file
        run: echo "${{ secrets.SF_JWT_KEY_QA }}" > server.key

      - name: Authenticate to QA validation org
        run: |
          sf org login jwt \
            --client-id ${{ secrets.SF_CLIENT_ID_QA }} \
            --jwt-key-file server.key \
            --username ${{ secrets.SF_USERNAME_QA }} \
            --instance-url ${{ secrets.SF_INSTANCE_URL_QA }} \
            --alias $QA_ALIAS

      - name: Generate delta package
        run: |
          sf sgd source delta \
            --to HEAD \
            --from origin/develop \
            --output-dir $DELTA_DIR \
            --generate-delta

      - name: Show generated delta
        run: |
          echo "Delta contents:"
          find $DELTA_DIR || true

      - name: Run PMD scan
        run: |
          pmd -d $PROJECT_DIR -R category/apex/design.xml -f text || true

      - name: Run ESLint
        run: |
          if [ -f package.json ]; then
            npm ci
            npm run lint || true
          fi

      - name: Validate delta deployment against QA
        run: |
          if [ -d "$DELTA_DIR" ]; then
            sf project deploy validate \
              --source-dir $DELTA_DIR \
              --target-org $QA_ALIAS \
              --test-level RunLocalTests
          else
            echo "No delta generated, validating full source instead."
            sf project deploy validate \
              --source-dir $PROJECT_DIR \
              --target-org $QA_ALIAS \
              --test-level RunLocalTests
          fi

  deploy-to-qa:
    if: github.event_name == 'push' && github.ref == 'refs/heads/develop'
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Install Salesforce CLI
        run: npm install @salesforce/cli --global

      - name: Install plugins
        run: |
          sf plugins install sfdx-git-delta

      - name: Create JWT key file
        run: echo "${{ secrets.SF_JWT_KEY_QA }}" > server.key

      - name: Authenticate to QA
        run: |
          sf org login jwt \
            --client-id ${{ secrets.SF_CLIENT_ID_QA }} \
            --jwt-key-file server.key \
            --username ${{ secrets.SF_USERNAME_QA }} \
            --instance-url ${{ secrets.SF_INSTANCE_URL_QA }} \
            --alias $QA_ALIAS

      - name: Generate delta for QA deploy
        run: |
          sf sgd source delta \
            --to HEAD \
            --from HEAD^ \
            --output-dir $DELTA_DIR \
            --generate-delta || true

      - name: Deploy to QA
        run: |
          if [ -d "$DELTA_DIR" ] && [ "$(find $DELTA_DIR -type f | wc -l)" -gt 0 ]; then
            sf project deploy start \
              --source-dir $DELTA_DIR \
              --target-org $QA_ALIAS \
              --test-level RunLocalTests
          else
            echo "Falling back to full branch deploy"
            sf project deploy start \
              --source-dir $PROJECT_DIR \
              --target-org $QA_ALIAS \
              --test-level RunLocalTests
          fi

  build-package-and-install-uat:
    if: github.event_name == 'push' && startsWith(github.ref, 'refs/heads/release/')
    runs-on: ubuntu-latest
    outputs:
      package_version_id: ${{ steps.capture_version.outputs.package_version_id }}

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Install Salesforce CLI
        run: npm install @salesforce/cli --global

      - name: Install packaging plugin
        run: sf plugins install @salesforce/plugin-packaging

      - name: Create Dev Hub JWT key file
        run: echo "${{ secrets.SF_JWT_KEY_DEVHUB }}" > server.key

      - name: Authenticate to Dev Hub
        run: |
          sf org login jwt \
            --client-id ${{ secrets.SF_CLIENT_ID_DEVHUB }} \
            --jwt-key-file server.key \
            --username ${{ secrets.SF_USERNAME_DEVHUB }} \
            --instance-url ${{ secrets.SF_INSTANCE_URL_DEVHUB }} \
            --alias devhub

      - name: Create package version
        run: |
          sf package version create \
            --package $PACKAGE_NAME \
            --target-dev-hub devhub \
            --wait 30 \
            --installation-key-bypass \
            --code-coverage

      - name: Capture latest package version id
        id: capture_version
        run: |
          VERSION_ID=$(sf package version list --packages $PACKAGE_NAME --target-dev-hub devhub --json | jq -r '.result[0].SubscriberPackageVersionId')
          echo "package_version_id=$VERSION_ID" >> $GITHUB_OUTPUT
          echo "Latest package version id: $VERSION_ID"

      - name: Create UAT JWT key file
        run: echo "${{ secrets.SF_JWT_KEY_UAT }}" > server.key

      - name: Authenticate to UAT
        run: |
          sf org login jwt \
            --client-id ${{ secrets.SF_CLIENT_ID_UAT }} \
            --jwt-key-file server.key \
            --username ${{ secrets.SF_USERNAME_UAT }} \
            --instance-url ${{ secrets.SF_INSTANCE_URL_UAT }} \
            --alias $UAT_ALIAS

      - name: Install package in UAT
        run: |
          sf package install \
            --package ${{ steps.capture_version.outputs.package_version_id }} \
            --target-org $UAT_ALIAS \
            --wait 30 \
            --publish-wait 10 \
            --no-prompt

  promote-to-production:
    if: github.event_name == 'workflow_dispatch' && github.event.inputs.package_version_id != ''
    runs-on: ubuntu-latest
    environment: production

    steps:
      - name: Install Salesforce CLI
        run: npm install @salesforce/cli --global

      - name: Create Production JWT key file
        run: echo "${{ secrets.SF_JWT_KEY_PROD }}" > server.key

      - name: Authenticate to Production
        run: |
          sf org login jwt \
            --client-id ${{ secrets.SF_CLIENT_ID_PROD }} \
            --jwt-key-file server.key \
            --username ${{ secrets.SF_USERNAME_PROD }} \
            --instance-url ${{ secrets.SF_INSTANCE_URL_PROD }} \
            --alias $PROD_ALIAS

      - name: Install approved package in Production
        run: |
          sf package install \
            --package ${{ github.event.inputs.package_version_id }} \
            --target-org $PROD_ALIAS \
            --wait 30 \
            --publish-wait 10 \
            --no-prompt

هذا ليس التصميم الوحيد الصحيح لـ GitHub Actions، لكنه يعكس التقسيم المرشح بين validation قائم على source في البيئات الأدنى وpromotion قائم على package إلى UAT وProduction.

ممارسات إضافية وإرشادات تشغيلية للـ pipeline

الـ CI/CD pipeline جزء واحد فقط من عملية Salesforce DevOps تعمل فعلا. ما يحدد النجاح أو الفشل غالبا هو كيف تتعامل الفرق مع التفاصيل التشغيلية حول development والاختبار والـ promotion وتنسيق الـ release.

التعامل الآمن مع تغييرات feature واحدة

من التحديات في Salesforce development أن الـ sandboxes غالبا ما تتراكم فيها عدة تغييرات في آن واحد. قد يعمل المطور على ميزتين أو ثلاث، لكن واحدة فقط جاهزة لـ promotion.

إذا اكتفيت بعمل retrieve لكل الـ metadata ونشرها كما هي، فأنت تخاطر بترقية عمل غير مكتمل.

النمط الأكثر أمانا هو:

Developer Sandbox
      ↓
Retrieve all metadata locally
      ↓
Select only the components belonging to the feature
      ↓
Commit those components to a feature branch
      ↓
Submit a pull request for validation

مثال workflow:

sf project retrieve start

git checkout -b feature/pricing-update

git add force-app/main/default/classes/PricingService.cls
git add force-app/main/default/triggers/PricingTrigger.trigger
git add force-app/main/default/lwc/pricingWidget

git commit -m "Feature: pricing update"

هذا يضمن أن الـ repository يعكس ميزة منطقية واحدة، لا خليطا من تغييرات غير مترابطة.

الصعوبة أن metadata في Salesforce غالبا مترابطة. قد يعتمد Flow على:

  • custom fields

  • Apex classes

  • permission sets

  • record types

فقدان واحدة من هذه الـ dependencies قد يكسر validation النشر.

فرق كثيرة تحل هذا عبر الخبرة والفحص اليدوي. منصات مثل Serpent تساعد بتحليل علاقات الـ metadata وتحديد الـ dependencies تلقائيا حتى تتحرك المجموعة الصحيحة من components معا.

اختيار metadata بمساعدة AI

التحسينات الأخيرة في أدوات المطورين أدخلت مساعدة AI لتحليل الـ dependencies.

عمليا، هذا يعني:

  • يمكن تحليل وصف الميزة

  • يمكن اكتشاف components الـ metadata المرتبطة

  • يمكن تقييم dependency graphs

  • يمكن اقتراح نطاق الـ metadata الأرجح

يضم Serpent AI agents مدربة على أنماط metadata في Salesforce تساعد في تحديد المجموعة المنطقية من components المطلوبة لميزة ما، ما يقلل خطر الـ deployments الناقصة.

هذا لا يلغي تحكم المطور. هو فقط يجعل عملية الاختيار أسرع وأكثر أمانا.

التعامل مع sandbox drift

مشكلة شائعة أخرى في بيئات Salesforce هي sandbox drift.

يحدث هذا حين يقوم أحدهم بتغيير مباشر في بيئة مشتركة مثل QA أو UAT لا يصل أبدا إلى Git repository.

مع الوقت تنحرف البيئة عن source control.

لاكتشاف الـ drift، تقوم الفرق غالبا بمقارنات retrieve دورية:

sf project retrieve start
git diff

إذا ظهرت فروق غير متوقعة، يجب تسويتها فورا إما بـ commit لها في Git أو إزالتها.

ترك الـ drift يتراكم يجعل الـ deployments اللاحقة غير قابلة للتوقع.

استراتيجية البيئات

يجب أن يكون لكل بيئة غرض واضح.

البيئة الغرض
Developer Sandbox تطوير الميزات والاختبار المبكر
QA اختبار متكامل عبر عدة ميزات
UAT تحقق العمل من release candidates
Production النظام الحي

تظهر المشاكل غالبا حين تفقد البيئات غرضها. إذا تحولت QA إلى sandbox development أخرى، تتوقف عن العمل كنقطة تحقق تكامل موثوقة.

استراتيجية الاختبار

عملية CI/CD قوية تتضمن اختبارات تلقائية على مستويات متعددة.

  • unit tests لـ Apex

  • component tests لـ LWC

  • integration tests لـ workflows عبر عدة ميزات

غالبا ما تشغّل pipelines التحقق من الـ PR اختبارات مستهدفة من أجل السرعة، بينما تشغّل release pipelines مجموعات اختبار أوسع.

مثال PR validation:

sf project deploy validate \
  --source-dir delta \
  --test-level RunSpecifiedTests

مثال release validation:

sf project deploy validate \
  --source-dir force-app \
  --test-level RunLocalTests

الهدف هو اكتشاف المشاكل مبكرا دون إبطاء دورات التغذية الراجعة للمطورين بلا داعٍ. تحديد أي مجموعة اختبارات تعمل عند أي بوابة، وأين يقع تغطية الـ UI، موضوع بحد ذاته: دليل بناء test automation pipeline لـ Salesforce في CI يشرح الطبقات بالترتيب.

وضوح الـ release

من أكثر تحديات DevOps التي يُستهان بها هو ببساطة معرفة ما الموجود في release.

من الأسئلة التي تطرحها الفرق كثيرا:

  • ما الميزات المضمنة في الـ release القادم؟

  • ما الـ components التي نُشرت إلى QA أمس؟

  • ما package version المثبت حاليا في production؟

بدون workflow منظم، الإجابة عن هذه الأسئلة غالبا تتطلب البحث عبر تاريخ Git وسجلات الـ pipeline وملاحظات release يدوية.

يعالج Serpent هذا بربط الـ deployments بـ work items، ما يجعل فهم الـ releases عبر البيئات أسهل.

تجربة الـ developers

إنتاجية المطور مهمة أيضا.

يفضل كثير من المطورين العمل داخل VS Code مع إضافات Salesforce وأدوات CLI.

يجب أن تندمج منصات DevOps مع ذلك الـ workflow بدل إجبار المطورين على واجهات منفصلة تماما.

يضم Serpent VS Code extension يتيح للمطورين:

  • مقارنة metadata

  • التحقق من deployments

  • إدارة مهام الـ release

  • نشر التغييرات

هذا يبقي عمليات DevOps قريبة من بيئة development.

Connected App وJWT authentication

أمثلة الـ pipeline أعلاه تفترض أن الـ runner في GitHub Actions يقدر يصادق على Salesforce دون تدخل بشري. النمط المعتمد هو JWT auth مبني على Connected App. هذا هو الإعداد العملي من طرف إلى طرف.

  1. أنشئ Connected App في Salesforce App Manager.

  2. فعّل OAuth settings. اضبط callback URL على http://localhost:1717/OauthRedirect.

  3. اختر الـ scopes Manage user data via APIs (api) و Perform requests at any time (refresh_token, offline_access).

  4. فعّل Use digital signatures وارفع الـ certificate (المولّد بالخطوة التالية)، واحفظ private key في GitHub Secrets.

openssl genrsa -out server.key 2048
openssl req -new -x509 -days 1825 -key server.key -out server.crt

ارفع server.crt إلى الـ Connected App. احفظ server.key بأمان، فهو يذهب إلى GitHub Secrets ولا يدخل الـ repo أبدا.

في مستودع GitHub اذهب إلى Settings → Secrets and variables → Actions وأضف:

الـ workflow أعلاه يصادق على أربع orgs منفصلة - QA وDev Hub وUAT وProduction - لذلك يحتاج كل secret نسخة لكل بيئة، بلاحقة _QA و_DEVHUB و_UAT و_PROD (مثلا SF_CLIENT_ID_QA وSF_CLIENT_ID_DEVHUB). كرر الأسرار الأربعة التالية لكل لاحقة، بحيث تشير إلى الـ Connected App والـ org الخاصين بتلك البيئة:

  • SF_CLIENT_ID_<ENV> - الـ Consumer Key من الـ Connected App الخاص بتلك البيئة.

  • SF_USERNAME_<ENV> - اسم المستخدم في Salesforce لتلك البيئة، مثل [email protected].

  • SF_JWT_KEY_<ENV> - محتوى ملف server.key الخاص بتلك البيئة.

  • SF_INSTANCE_URL_<ENV> - https://test.salesforce.com للـ sandboxes (QA وUAT وDev Hub) أو https://login.salesforce.com للـ production.

أخطاء شائعة وحلول

أغلب أعطال الـ CI/CD تقع ضمن مجموعة صغيرة من المشاكل المتكررة. الجدول التالي يغطي الأكثر شيوعا.

Error السبب المحتمل الحل
INVALID_LOGIN JWT key أو client ID غير صحيح راجع secrets وتأكد من عدم وجود trailing newlines.
INSUFFICIENT_ACCESS Connected App permissions راجع scopes وتفعيل التطبيق للـ integration user.
Tests failing on deploy Code coverage أقل من 75% شغّل sf apex run test محليا وحسّن coverage.
Metadata not deploying ترتيب SFDX project غير صحيح تأكد أن sfdx-project.json يحتوي على packageDirectories صحيحة.
Job times out mid-deploy validation طويلة أو اختبارات على كامل الـ org استخدم --test-level RunLocalTests عند التحقق من الـ pull request، واحتفظ بـ RunAllTestsInOrg لمهمة الإنتاج.

الأسئلة الشائعة

كيف يصادق GitHub Actions على مؤسسة Salesforce؟

عبر Connected App بتدفق الـ JWT. تنشئ التطبيق في App Manager، وترفع شهادة، وتحفظ الـ private key والـ consumer key في GitHub Secrets، ثم يسجل الـ workflow الدخول بها قبل أي خطوة نشر.

لماذا تفشل الـ pipeline بخطأ INVALID_LOGIN؟

لأن الـ JWT key أو الـ client ID لا يطابق ما يتوقعه الـ Connected App. راجع الـ secrets حرفا حرفا، وانتبه لوجود سطر جديد زائد في نهاية الـ private key.

لماذا تفشل الاختبارات أثناء النشر؟

غالبا لأن تغطية الكود أقل من 75 بالمئة. شغّل sf apex run test محليا لتعرف أي الكلاسات ناقصة التغطية، واستخدم RunLocalTests عند التحقق من الـ pull requests حتى تبقى المهمة سريعة. وأي مجموعة اختبارات تعمل عند أي بوابة، يشرحه دليل test automation في الـ CI.

تختار delta deployments أم full deployments؟

الـ delta deployments تدفع ما تغيّر فقط، وهي الأنسب للتحقق من الـ pull requests وترقية الميزات الصغيرة في المؤسسات الكبيرة. أما الـ full deployments فأأمن مع الحذف والـ reverts وحين يجب أن تطابق البيئة حالة branch محددة.

هل أحتاج إلى sfdx-git-delta؟

لا، لكنه يستحق العناء بمجرد أن يصير النشر بطيئا. اقرنه بـ full deploy عند الحذف حتى تُعالَج الـ destructive changes بدل أن تُتخطّى بهدوء.

أستخدم sfdx أم sf لخط Salesforce على GitHub Actions؟

استخدم sf، أداة Salesforce CLI الحديثة، فالأداة القديمة sfdx التي حلّت محلها deprecated ولا تصلح أن تكون أساس خط جديد. ونقل خط قائم مسألة أسماء أوامر في الأغلب: sfdx force:source:deploy تصبح sf project deploy start، وsfdx force:apex:test:run تصبح sf apex run test، والـ plugins تُعاد تثبيتها عبر sf plugins install.

لماذا نتحقق عند الـ pull request ثم ننشر مرة أخرى عند الدمج؟

مهمة الـ pull request تثبت أن الـ branch يُترجم ويجتاز اختباراته مقابل الـ org الهدف دون أن تكتب فيها شيئا. أما مهمة الدمج فهي التي تغيّر الـ org، وتعمل على commit الدمج، وهو ليس نفس الشجرة التي تحقق منها الـ pull request.

الخلاصة

Salesforce DevOps لا يتعلق بأداة واحدة أو pipeline مثالية واحدة. المهم هو عملية موثوقة تجعل Git source of truth، وتحقق validate تلقائي، وتجعل releases versioned وtraceable.

قد تبدأ الفرق الصغيرة بـ pipelines بسيطة وبيئة staging واحدة، بينما تحتاج المؤسسات الأكبر بيئات منظمة وreleases versioned وpackages modular.

المبادئ الأساسية تبقى واحدة:

  • Git يجب أن يكون source of truth

  • التغييرات يجب أن تُتحقق تلقائيا

  • الـ releases يجب أن تكون versioned وtraceable

  • البيئات يجب أن تبقى متسقة

فهم كيف تعمل CI/CD pipelines تحت الغطاء أمر قيّم. لكن مع تعقّد بيئات Salesforce أكثر، يصبح تنسيق الـ releases عبر الأشخاص والبيئات والـ packages هو التحدي الحقيقي.

GitHub Actions يساعد الفرق على فهم automation من الداخل. ومع تعقّد Salesforce environments، تساعد أدوات مثل Serpent على الحفاظ على السرعة مع control وvisibility وrelease confidence.

مقالات ذات صلة

هل تريد معرفة كيف يمكنكم الشحن أسرع قبل البدء؟ لنتحدث

بدون التزام.