
Andrew Hanna

Andrew Hanna

تطور 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.
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 لاحقا.
هذا ما زال النموذج الأكثر شيوعا. يعمل developers داخل sandboxes، ثم يتم retrieve للـ metadata إلى Git، وبعدها تقوم CI/CD pipeline بترقيتها عبر البيئات.
Developer Sandbox
↓
QA
↓
UAT / Staging
↓
Production
لا تزال فرق enterprise كثيرة تعمل بهذا الشكل، حتى عندما تبدأ إدخال unlocked packages لاحقا في مسار الـ release.
يستخدم 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
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 غالبا نفس المبادئ، لكن 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.
مع نمو 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.
قبل بناء pipeline، اختر كيف يتحرك العمل داخل Git. الخياران الأكثر شيوعا هما GitFlow وtrunk-based development.
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.
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 متسقة معه.
لأغلب فرق 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 + 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 هو 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 لـ 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 أثناء development وunlocked packages من أجل release promotion.
لماذا؟
source tracking تجربة development يومية أفضل من الـ manifests اليدوية
unlocked packages تمنحك release versioning حقيقي لاحقا في المسار
production traceability تصبح أوضح بكثير عبر package versions
الـ rollback والتعامل مع destructive changes أسهل أن تفهمه
مع ذلك، لا يزال package.xml مهما في حالات خاصة، فالهدف الحقيقي ليس النقاء. الهدف الحقيقي هو استخدام الأداة الصحيحة في الخطوة الصحيحة.
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 من أفضل الطرق لتحقيق ذلك.
تحتوي sandbox غالبا على تغييرات متعددة. النمط الآمن هو retrieve لكل شيء، ثم اختيار components الخاصة بميزة واحدة فقط وcommit لها في feature branch.
تبدو العملية اليدوية عادة هكذا:
retrieve لكل شيء من الـ sandbox
مراجعة الملفات المتغيرة في Git
اختيار الـ metadata الخاصة بالميزة فقط
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 تنشر فقط الـ 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 أوسع أكثر أمانا.
حتى في 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 أكثر قابلية للتوقع
لفريق 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
لا تحل unlocked packages كل مشكلة rollback بشكل سحري، خصوصا حين تكون تغييرات البيانات جزءا من الأمر. لكن مقارنة بـ source deployment الخام أو promotion المعتمد فقط على package.xml، تجعل الـ rollback أسهل أن تفهمه لأن:
الـ release عبارة عن artefact versioned
تعرف بالضبط أي package version أدخل التغيير
يمكنك مقارنة ما تغيّر بين الإصدارات بوضوح أكبر
destructive changes أسهل تتبعا في سياق تاريخ الـ release
هذا يهم أكثر في production، حيث لا يمنحك source tracking نفس الرؤية التي يمنحها في بيئات development.
GitHub Actions خيار مناسب للفرق التي تريد فهم mechanics وبناء CI/CD foundation داخل repository. يتم تعريف workflows كملفات YAML بجانب الكود.
feature/* → developer work item branches
develop → deployed to QA
release/* → package version build
main → production history
hotfix/* → urgent production fixes
يوضح المثال التالي إعدادا عمليا من طرف إلى طرف يستخدم:
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.
الـ CI/CD pipeline جزء واحد فقط من عملية Salesforce DevOps تعمل فعلا. ما يحدد النجاح أو الفشل غالبا هو كيف تتعامل الفرق مع التفاصيل التشغيلية حول development والاختبار والـ promotion وتنسيق الـ release.
من التحديات في 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 معا.
التحسينات الأخيرة في أدوات المطورين أدخلت مساعدة AI لتحليل الـ dependencies.
عمليا، هذا يعني:
يمكن تحليل وصف الميزة
يمكن اكتشاف components الـ metadata المرتبطة
يمكن تقييم dependency graphs
يمكن اقتراح نطاق الـ metadata الأرجح
يضم Serpent AI agents مدربة على أنماط metadata في Salesforce تساعد في تحديد المجموعة المنطقية من components المطلوبة لميزة ما، ما يقلل خطر الـ deployments الناقصة.
هذا لا يلغي تحكم المطور. هو فقط يجعل عملية الاختيار أسرع وأكثر أمانا.
مشكلة شائعة أخرى في بيئات 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 يشرح الطبقات بالترتيب.
من أكثر تحديات DevOps التي يُستهان بها هو ببساطة معرفة ما الموجود في release.
من الأسئلة التي تطرحها الفرق كثيرا:
ما الميزات المضمنة في الـ release القادم؟
ما الـ components التي نُشرت إلى QA أمس؟
ما package version المثبت حاليا في production؟
بدون workflow منظم، الإجابة عن هذه الأسئلة غالبا تتطلب البحث عبر تاريخ Git وسجلات الـ pipeline وملاحظات release يدوية.
يعالج Serpent هذا بربط الـ deployments بـ work items، ما يجعل فهم الـ releases عبر البيئات أسهل.
إنتاجية المطور مهمة أيضا.
يفضل كثير من المطورين العمل داخل VS Code مع إضافات Salesforce وأدوات CLI.
يجب أن تندمج منصات DevOps مع ذلك الـ workflow بدل إجبار المطورين على واجهات منفصلة تماما.
يضم Serpent VS Code extension يتيح للمطورين:
مقارنة metadata
التحقق من deployments
إدارة مهام الـ release
نشر التغييرات
هذا يبقي عمليات DevOps قريبة من بيئة development.
أمثلة الـ pipeline أعلاه تفترض أن الـ runner في GitHub Actions يقدر يصادق على Salesforce دون تدخل بشري. النمط المعتمد هو JWT auth مبني على Connected App. هذا هو الإعداد العملي من طرف إلى طرف.
أنشئ Connected App في Salesforce App Manager.
فعّل OAuth settings. اضبط callback URL على
http://localhost:1717/OauthRedirect.
اختر الـ scopes Manage user data via APIs (api) و Perform requests at any time (refresh_token, offline_access).
فعّل 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.
بدون التزام.