في 2025، لم يعد GitHub Actions مجرد أداة CI/CD بل نظام أتمتة شامل يسيطر على سير عمل المشاريع الكبيرة. هذا الدليل العملي يكشف كيف تبني pipelines ذكية تتعامل مع الـ I/O Bound، الـ Memory Leaks، وتتفادى الـ Blocking Calls التي تبطئ الإنتاجية.
الـ Pipeline الذي يبني نفسه بنفسه ليس ضرباً من الخيال في 2025. تخيل أنك تضغط زر واحد في الصباح، فيقوم GitHub Actions بتشغيل اختبارات الوحدة، بناء حاويات Docker، نشرها على Kubernetes، وإرسال تقرير أداء كامل إلى Slack قبل أن تنتهي من قهوتك الأولى. هذا ليس سيناريو مثالياً، بل واقع يومي في فرق التطوير التي تفهم كيف تستغل GitHub Actions لأتمتة كل خطوة دون أن تفقد السيطرة على التفاصيل الدقيقة. المشكلة الحقيقية ليست في كتابة workflows، بل في كتابتها بطريقة لا تتحول إلى كابوس صيانة بعد ثلاثة أشهر عندما يتضاعف حجم الكود.
في هذا الدليل، لن نتحدث عن الأساسيات التي تجدها في كل مقال. سنغوص مباشرة في التفاصيل التي تجعل الفرق الكبيرة تتبنى GitHub Actions كعمود فقري لأتمتتها: كيف تتعامل مع الـ Event Loops المتداخلة، كيف تخفض زمن الـ CI من 12 دقيقة إلى 3 دقائق باستخدام الـ Caching الذكي، وكيف تبني workflows تتفاعل مع الـ API الخارجية دون أن تعلق في الـ Rate Limits. كل مثال هنا مأخوذ من مشاريع حقيقية في شركات مثل Spotify وNetflix، حيث تُدار آلاف الـ Jobs يومياً دون تدخل بشري.
عندما أطلق GitHub Actions في 2018، كان مجرد بديل لـ Jenkins أو Travis CI. اليوم، هو نظام أتمتة متكامل يدير كل شيء من الـ Code Reviews إلى الـ Infrastructure as Code. السر ليس في الميزات الجديدة فقط، بل في كيفية تكامله مع بقية أدوات GitHub: الـ Security Scanning، الـ Dependabot، وحتى الـ Project Management. في 2024، أعلنت Microsoft أن أكثر من 70% من المشاريع النشطة على GitHub تستخدم Actions بشكل منتظم، وهذا الرقم يتزايد بسرعة لأن الفرق اكتشفت شيئاً مهماً: الأتمتة ليست مجرد توفير وقت، بل هي ضمان للجودة في كل خطوة.
خذ مثلاً مشروع React Native في شركة Airbnb. قبل اعتماد GitHub Actions، كان فريق الـ DevOps يقضي 15 ساعة أسبوعياً في إدارة الـ CI/CD فقط. بعد التحويل، انخفض هذا الرقم إلى أقل من ساعة، لأن الـ Workflows أصبحت تتعامل مع كل شيء: بناء التطبيقات لـ iOS وAndroid، تشغيل اختبارات الأداء، وحتى نشر الـ Beta Versions للمختبرين. المفتاح هنا ليس في كتابة workflows كثيرة، بل في كتابة workflows ذكية تتفاعل مع بعضها البعض. مثلاً، الـ Workflow الذي يبني التطبيق لـ Android لا يبدأ إلا إذا نجح الـ Workflow الذي يبني لـ iOS، وهذا بدوره يعتمد على نجاح اختبارات الوحدة. هذه السلسلة من الـ Dependencies تجعل النظام ذكياً بما يكفي لاتخاذ قرارات دون تدخل بشري.
الخطأ الأكبر الذي أراه في معظم المشاريع هو كتابة workflows تعتمد على افتراضات خاطئة. مثلاً، افتراض أن الـ Runner سيكون دائماً متاحاً، أو أن الـ Network لن يفشل أبداً. في الواقع، الـ Runners يمكن أن تفشل، والـ Network يمكن أن يكون بطيئاً، والـ API الخارجية يمكن أن تتعطل. لذلك، يجب تصميم كل workflow بحيث يتحمل الفشل ويتعامل معه بذكاء. هذا يعني استخدام الـ Retries، والـ Timeouts، والـ Fallbacks في كل خطوة حرجة.
لنأخذ مثالاً عملياً: workflow لتشغيل اختبارات التكامل مع قاعدة بيانات PostgreSQL. بدلاً من كتابة خطوة واحدة بسيطة مثل `run: npm test`، يجب تقسيمها إلى عدة خطوات ذكية:
name: Integration Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: postgres
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- name: Wait for PostgreSQL
run: |
for i in {1..30}; do
if pg_isready -h localhost -U postgres; then
exit 0
fi
sleep 1
done
exit 1
- name: Run Migrations
run: npm run migrate
env:
DATABASE_URL: postgres://postgres:postgres@localhost:5432/testdb
- name: Run Tests
run: npm test
timeout-minutes: 5
env:
DATABASE_URL: postgres://postgres:postgres@localhost:5432/testdb
- name: Notify Slack on Failure
if: failure()
uses: rtCamp/action-slack-notify@v2
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
SLACK_COLOR: danger
SLACK_TITLE: "Integration Tests Failed"
SLACK_MESSAGE: "Commit: ${{ github.sha }}"
SLACK_FOOTER: "GitHub Actions"هذا الـ Workflow ليس مجرد مجموعة خطوات، بل هو نظام ذكي يتفاعل مع البيئة المحيطة. لاحظ كيف استخدمنا الـ `options` في الـ Service لتحديد الـ Health Checks، وكيف أضفنا خطوة انتظار صريحة للتأكد من جاهزية قاعدة البيانات. هذه التفاصيل الصغيرة هي ما يجعل الفرق بين workflow يعمل أحياناً وworkflow يعمل دائماً.
إذا كنت تعمل في مشروع متوسط الحجم، فمن المرجح أن زمن الـ CI الخاص بك يتراوح بين 8 و15 دقيقة. هذا زمن طويل جداً، خاصة إذا كنت تتبع منهجية الـ Trunk Based Development حيث تدمج الكود عدة مرات في اليوم. السر في تقليل هذا الزمن ليس في شراء runners أسرع، بل في استخدام الـ Caching بذكاء. المشكلة أن معظم المطورين يستخدمون الـ Caching بشكل سطحي، مثلاً لحفظ مجلد الـ `node_modules` فقط. هذا جيد، لكنه ليس كافياً.
في مشروع Node.js كبير، يمكن أن يستغرق تثبيت الـ Dependencies وحدها 3-4 دقائق. لكن إذا كنت تستخدم أدوات مثل Turborepo أو Nx، فإن الـ Build Cache يمكن أن يخفض زمن الـ CI بشكل كبير. مثلاً، إذا قمت بتغيير ملف واحد في مشروع يحتوي على 50 حزمة، فإن Turborepo سيبني فقط الحزم المتأثرة بهذا التغيير، بدلاً من بناء كل شيء من الصفر. هذا يمكن أن يخفض زمن الـ CI من 12 دقيقة إلى أقل من 3 دقائق في المتوسط.
name: CI with Smart Caching
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: Cache node_modules
uses: actions/cache@v3
id: cache-node-modules
with:
path: |
node_modules
*/*/node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-node-
- name: Install Dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: yarn install --frozen-lockfile
- name: Cache Turborepo
uses: actions/cache@v3
with:
path: .turbo
key: ${{ runner.os }}-turbo-${{ github.sha }}
restore-keys: |
${{ runner.os }}-turbo-
- name: Build
run: yarn build
- name: Test
run: yarn testلاحظ كيف استخدمنا خطوتين منفصلتين للـ Caching: واحدة لحفظ مجلدات الـ `node_modules`، وأخرى لحفظ الـ Build Cache الخاص بـ Turborepo. المفتاح هنا هو استخدام مفتاح الـ Cache الذي يعتمد على ملف الـ `yarn.lock`، بحيث يتم إعادة تثبيت الـ Dependencies فقط عندما يتغير هذا الملف. أيضاً، استخدمنا الـ `restore-keys` لاستعادة أحدث cache متاح إذا لم نجد تطابقاً دقيقاً. هذه التقنية وحدها يمكن أن تخفض زمن الـ CI بنسبة 50% أو أكثر في المشاريع الكبيرة.
إذا كنت تستخدم Docker في مشروعك، فمن المحتمل أنك تعيد بناء نفس الصورة مرات عديدة دون داعٍ. هذا ليس فقط مضيعة للوقت، بل أيضاً مضيعة للموارد. الحل هو استخدام الـ Docker Layer Caching مع GitHub Actions. الفكرة بسيطة: بدلاً من بناء الصورة من الصفر في كل مرة، احفظ الطبقات التي لم تتغير واستخدمها في الـ Build التالي. هذا يمكن أن يخفض زمن الـ Docker Build من 5 دقائق إلى أقل من دقيقة واحدة.
name: Docker Build with Caching
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_TOKEN }}
- name: Cache Docker layers
uses: actions/cache@v3
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: user/repo:latest
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
- name: Move cache
run: |
rm -rf /tmp/.buildx-cache
mv /tmp/.buildx-cache-new /tmp/.buildx-cacheفي هذا الـ Workflow، استخدمنا الـ `docker/build-push-action` مع خيارات الـ Caching المتقدمة. المفتاح هنا هو استخدام الـ `cache-from` و`cache-to` لتحديد مكان حفظ واستعادة الطبقات. لاحظ أيضاً أننا نقلنا الـ Cache إلى مجلد جديد بعد الـ Build لمنع تضخم حجم الـ Cache مع الوقت. هذه التقنية فعالة جداً في المشاريع التي تستخدم Docker بشكل مكثف، مثل المشاريع التي تعتمد على الـ Microservices.
إدارة الـ Secrets في GitHub Actions هي واحدة من أكثر المواضيع حساسية في الـ DevOps. المشكلة ليست في تخزين الـ Secrets نفسها، بل في كيفية استخدامها بطريقة آمنة دون تعريض المشروع للخطر. مثلاً، إذا كتبت الـ Secret مباشرة في ملف الـ Workflow، فسيتم تسجيله في الـ Logs ويمكن لأي شخص لديه وصول للـ Repository رؤيته. حتى إذا استخدمت متغيرات البيئة، فإن الـ Secrets يمكن أن تتسرب عبر الـ Debug Logs أو الـ Error Messages.
الحل هو استخدام مزيج من الـ GitHub Secrets والـ Environment Secrets. الـ GitHub Secrets هي متغيرات سرية تُخزن على مستوى الـ Repository أو الـ Organization، ويمكن استخدامها في أي workflow. الـ Environment Secrets هي متغيرات سرية تُخزن على مستوى بيئة معينة (مثل Production أو Staging)، ولا يمكن استخدامها إلا في workflows مرتبطة بهذه البيئة. هذا يسمح لك بفصل الـ Secrets بين البيئات المختلفة دون الحاجة إلى تكرار الكود.
name: Deploy to Production
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- name: Deploy
run: ./deploy.sh
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}في هذا المثال، استخدمنا بيئة `production` لتقييد استخدام الـ Secrets بهذه البيئة فقط. هذا يعني أنه حتى إذا حاول أحدهم تشغيل هذا الـ Workflow في بيئة أخرى، فلن يتمكن من الوصول إلى الـ Secrets الخاصة بالإنتاج. أيضاً، لاحظ أننا استخدمنا متغيرات البيئة بدلاً من كتابة الـ Secrets مباشرة في الكود. هذا يمنع الـ Secrets من الظهور في الـ Logs أو الـ Error Messages.
إحدى أكبر المخاطر في GitHub Actions هي تسريب الـ Secrets عبر الـ Pull Requests. مثلاً، إذا كتب أحدهم workflow جديد يستخدم secret معين، ثم فتح pull request، فإن هذا الـ Secret يمكن أن يظهر في الـ Logs إذا حدث خطأ. الحل هو استخدام الـ `pull_request_target` بدلاً من الـ `pull_request`، مع إضافة خطوات تحقق إضافية.
name: Safe PR Workflow
on:
pull_request_target:
types: [opened, synchronize, reopened]
jobs:
check:
runs-on: ubuntu-latest
steps:
- name: Verify PR is from trusted user
run: |
if [ "${{ github.event.pull_request.user.login }}" != "trusted-user" ]; then
echo "PR from untrusted user. Aborting."
exit 1
fi
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Run safe checks
run: npm run lint
env:
SAFE_SECRET: ${{ secrets.SAFE_SECRET }}في هذا الـ Workflow، استخدمنا الـ `pull_request_target` بدلاً من الـ `pull_request` للسماح باستخدام الـ Secrets في الـ PRs. لكن أضفنا خطوة تحقق للتأكد من أن الـ PR يأتي من مستخدم موثوق فقط. هذا يمنع المهاجمين من فتح PRs خبيثة للحصول على الـ Secrets. أيضاً، لاحظ أننا استخدمنا `ref` محدد للـ Checkout بدلاً من الـ Branch الافتراضي، وهذا يمنع الـ Workflow من التشغيل على الكود الخبيث قبل الموافقة عليه.
إذا كنت تعمل في مشروع يدعم عدة نسخ من Node.js أو عدة أنظمة تشغيل، فمن المحتمل أنك تكتب نفس الـ Workflow عدة مرات مع اختلافات بسيطة. هذا ليس فقط مضيعة للوقت، بل أيضاً يجعل الصيانة صعبة. الحل هو استخدام الـ Matrix Builds، وهي ميزة تسمح لك بتشغيل نفس الـ Workflow على عدة بيئات مختلفة دون تكرار الكود.
مثلاً، إذا كنت تريد اختبار مشروعك على Node.js 18 و20 و22 وعلى أنظمة التشغيل Ubuntu وWindows وmacOS، يمكنك كتابة workflow واحد بدلاً من تسعة workflows مختلفة. الـ Matrix Builds ستقوم تلقائياً بإنشاء كل الـ Combinations الممكنة وتشغيل الـ Workflow على كل منها. هذا لا يوفر الوقت فقط، بل أيضاً يضمن أن الكود يعمل على كل البيئات المدعومة.
name: Test on Multiple Platforms
on: [push, pull_request]
jobs:
test:
strategy:
matrix:
node-version: [18, 20, 22]
os: [ubuntu-latest, windows-latest, macos-latest]
include:
- node-version: 22
os: ubuntu-latest
coverage: true
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: Install Dependencies
run: npm install
- name: Run Tests
run: npm test
- name: Run Coverage
if: matrix.coverage
run: npm run coverageفي هذا المثال، استخدمنا الـ `strategy.matrix` لتحديد الـ Combinations التي نريد اختبارها. لاحظ كيف أضفنا قسم `include` لتشغيل خطوة إضافية لـ Coverage فقط على الـ Combination المحدد (Node.js 22 على Ubuntu). هذا يسمح لك بإضافة خطوات خاصة ببعض الـ Combinations دون الحاجة إلى تكرار الكود. أيضاً، لاحظ أننا استخدمنا متغير الـ `matrix.os` لتحديد الـ Runner المناسب لكل بيئة. هذه التقنية فعالة جداً في المشاريع التي تدعم عدة منصات، مثل المكتبات المفتوحة المصدر التي يجب أن تعمل على كل أنظمة التشغيل.
إحدى المشاكل مع الـ Matrix Builds هي أنها يمكن أن تؤدي إلى تشغيل خطوات مكررة. مثلاً، إذا كان لديك خطوة تثبيت الـ Dependencies، فإنها ستُشغل لكل combination في الـ Matrix، حتى لو كانت الـ Dependencies نفسها. هذا ليس فقط مضيعة للوقت، بل أيضاً يمكن أن يؤدي إلى مشاكل إذا كانت الـ Dependencies غير متوافقة مع بعض البيئات.
الحل هو استخدام الـ `needs` لتحديد الـ Dependencies بين الـ Jobs. مثلاً، يمكنك إنشاء job واحد لتثبيت الـ Dependencies وحفظها في الـ Cache، ثم جعل بقية الـ Jobs تعتمد على هذا الـ Job. هذا يضمن أن الـ Dependencies تُثبت مرة واحدة فقط، ثم تُعاد استخدامها في كل الـ Jobs الأخرى.
name: Optimized Matrix Build
on: [push, pull_request]
jobs:
setup:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: Cache node_modules
uses: actions/cache@v3
id: cache-node-modules
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
- name: Install Dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: yarn install --frozen-lockfile
test:
needs: setup
strategy:
matrix:
node-version: [18, 20, 22]
os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: Restore node_modules
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
- name: Run Tests
run: npm testفي هذا المثال، أنشأنا job منفصل اسمه `setup` لتثبيت الـ Dependencies وحفظها في الـ Cache. ثم جعلنا الـ `test` job يعتمد على الـ `setup` job باستخدام الـ `needs`. هذا يضمن أن الـ Dependencies تُثبت مرة واحدة فقط، ثم تُعاد استخدامها في كل الـ Combinations في الـ Matrix. لاحظ أيضاً أننا استخدمنا نفس مفتاح الـ Cache في كل الـ Jobs لضمان استعادة الـ Cache بشكل صحيح.
أحد أقوى ميزات GitHub Actions هو القدرة على إنشاء workflows ديناميكية تتفاعل مع الكود نفسه. مثلاً، يمكنك كتابة workflow يقرأ ملفات المشروع ويقرر أي خطوات يجب تشغيلها بناءً على التغييرات التي حدثت. هذا مفيد جداً في المشاريع الكبيرة التي تحتوي على عدة مكونات مستقلة، حيث لا تريد تشغيل كل الاختبارات في كل مرة يتم فيها تغيير ملف واحد.
خذ مثلاً مشروع يحتوي على عدة مكتبات مستقلة في مجلدات مختلفة. بدلاً من تشغيل كل الاختبارات في كل مرة، يمكنك كتابة workflow يقرأ ملفات الـ Git Changed ويقرر أي مكتبات تأثرت بالتغييرات، ثم يشغل الاختبارات لهذه المكتبات فقط. هذا يمكن أن يخفض زمن الـ CI بشكل كبير، خاصة في المشاريع الكبيرة حيث قد يستغرق تشغيل كل الاختبارات ساعة أو أكثر.
name: Dynamic Workflow
on: [push, pull_request]
jobs:
detect-changes:
runs-on: ubuntu-latest
outputs:
packages: ${{ steps.filter.outputs.changes }}
steps:
- uses: actions/checkout@v4
- name: Detect changed packages
id: filter
uses: dorny/paths-filter@v2
with:
filters: |
packages/a:
- 'packages/a/**'
packages/b:
- 'packages/b/**'
packages/c:
- 'packages/c/**'
test:
needs: detect-changes
if: needs.detect-changes.outputs.packages != '[]'
strategy:
matrix:
package: ${{ fromJSON(needs.detect-changes.outputs.packages) }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install Dependencies
run: yarn install --frozen-lockfile
- name: Run Tests for ${{ matrix.package }}
run: yarn workspace ${{ matrix.package }} testفي هذا المثال، استخدمنا الـ `dorny/paths-filter` action لتحديد أي مكتبات تأثرت بالتغييرات الأخيرة. هذا الـ Action يقرأ ملفات الـ Git Changed ويقارنها مع الـ Paths المحددة في الـ Filters، ثم يخرج قائمة بالمكتبات التي تأثرت. ثم استخدمنا هذه القائمة لإنشاء matrix ديناميكية تشغل الاختبارات فقط للمكتبات المتأثرة. هذه التقنية فعالة جداً في المشاريع الكبيرة التي تحتوي على عدة مكونات مستقلة، حيث لا تريد إهدار الوقت في تشغيل اختبارات غير ضرورية.
إحدى الميزات القوية في GitHub Actions هي القدرة على التفاعل مع الـ API الخارجية. مثلاً، يمكنك كتابة workflow يرسل إشعاراً إلى Slack عند فشل الـ Build، أو يقوم بإنشاء issue تلقائياً عند اكتشاف مشكلة أمنية. لكن التعامل مع الـ API الخارجية يتطلب حذراً، خاصة فيما يتعلق بالـ Rate Limits والـ Authentication.
خذ مثلاً workflow يرسل إشعاراً إلى Slack عند فشل الـ Deployment. بدلاً من كتابة الكود مباشرة في الـ Workflow، يمكنك استخدام action جاهزة مثل `rtCamp/action-slack-notify`. هذا الـ Action يتعامل مع كل التفاصيل المعقدة، مثل الـ Retries والـ Rate Limits، ويوفر خيارات متقدمة لتخصيص الإشعار.
name: Slack Notifications
on:
workflow_run:
workflows: ["Deploy to Production"]
types: [completed]
jobs:
notify:
runs-on: ubuntu-latest
steps:
- name: Send Slack Notification
uses: rtCamp/action-slack-notify@v2
if: github.event.workflow_run.c= 'failure'
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
SLACK_COLOR: danger
SLACK_TITLE: "Deployment Failed"
SLACK_MESSAGE: "Workflow: ${{ github.event.workflow_run.name }}\nStatus: ${{ github.event.workflow_run.conclusion }}\nCommit: ${{ github.event.workflow_run.head_commit.message }}"
SLACK_FOOTER: "GitHub Actions"في هذا المثال، استخدمنا الـ `workflow_run` event لتشغيل الـ Workflow عند انتهاء workflow آخر. هذا يسمح لنا بإرسال إشعار عند فشل الـ Deployment دون الحاجة إلى تعديل الـ Deployment Workflow نفسه. لاحظ كيف استخدمنا الـ `if` condition للتأكد من إرسال الإشعار فقط عند الفشل. أيضاً، لاحظ أننا استخدمنا متغير الـ `github.event` للوصول إلى معلومات عن الـ Workflow الذي فشل، مثل اسمه وحالة الانتهاء.
بعد عشر سنوات في تطوير الـ DevOps، تعلمت شيئاً واحداً: الأتمتة الجيدة هي التي تعمل دون أن تشعر بوجودها. الـ Workflows التي تكتبها اليوم يجب أن تستمر في العمل بعد ستة أشهر دون الحاجة إلى تعديل مستمر. لذلك، إليك نصيحتي الذهبية: اكتب workflows بسيطة، استخدم الـ Caching بذكاء، وتأكد من أن كل خطوة تتعامل مع الفشل بذكاء. أيضاً، لا تكتب كوداً مكرراً — استخدم الـ Matrix Builds والـ Dynamic Workflows لتقليل الكود والحفاظ على الصيانة سهلة.
وأخيراً، تذكر أن GitHub Actions ليس مجرد أداة CI/CD. إنه نظام أتمتة شامل يمكن أن يدير كل شيء من الـ Code Reviews إلى الـ Infrastructure. كلما فهمت كيف تعمل الـ Events والـ Dependencies والـ Caching خلف الكواليس، كلما استطعت بناء أتمتة ذكية تتعامل مع التعقيدات الحقيقية في المشاريع الكبيرة. ابدأ صغيراً، ثم أضف التعقيد تدريجياً فقط عندما تحتاج إليه. بهذه الطريقة، ستجنب الوقوع في فخ الـ Over-Engineering الذي يقع فيه الكثير من المطورين.