توقف عن نشر الكود يدوياً وانتظر الكوارث. تعلم كيف تبني خط إنتاج CI/CD كامل بخطوات عملية، حتى لو كنت تعمل بمفردك، وتخلص من الـ manual deployments للأبد.
آخر مرة قمت فيها بنشر تحديث على تطبيقك، هل تذكر اللحظة التي ضغطت فيها زر Deploy وانتظرت ١٠ دقائق لترى ما إذا كان السيرفر سينهار؟ أو الأسوأ، عندما اكتشفت بعد ساعة أن آخر commit كان ينقصه ملف config ولم تلاحظ لأنك كنت مستعجلاً؟ هذه ليست مجرد حوادث فردية، بل هي القاعدة في عالم الـ manual deployments. دراسة من Google في ٢٠٢٣ أظهرت أن ٦٨٪ من حوادث الإنتاج سببها أخطاء بشرية أثناء النشر اليدوي، وأن الفرق التي تستخدم CI/CD الكامل تقلل وقت الاستجابة للحوادث بنسبة ٧٠٪. المشكلة ليست في الكود نفسه، بل في الطريقة التي يصل بها إلى المستخدمين. إذا كنت مطوراً فردياً أو تعمل في فريق صغير، فأنت معرض لهذه الأخطاء أكثر من غيرك، لأنك لا تملك فريق DevOps مخصصاً لمراقبة كل خطوة.
الحقيقة هي أن CI/CD ليس ترفاً تقنياً، بل هو أداة بقاء. عندما تبني خط إنتاج تلقائي، أنت لا توفر الوقت فقط، بل تقلل من الضغط العصبي الذي يأتي مع كل نشر. تخيل أنك تستطيع أن تضغط commit في المساء وتنام وأنت مطمئن أن الكود سينتقل تلقائياً من GitHub إلى الإنتاج بعد أن يمر بكل الاختبارات والتحقق الآلي. هذا ليس حلماً، بل هو واقع ممكن حتى للمشاريع الصغيرة إذا بنيت النظام بشكل صحيح. في هذا الدليل، سنبني معاً خط إنتاج CI/CD كاملاً من الصفر، خطوة بخطوة، بدون أدوات معقدة أو تكاليف باهظة، وسنتجنب الفخاخ التي يقع فيها معظم المطورين عند البدء.
قبل أن نكتب أي سطر كود، دعنا نفهم ماذا يحدث بالضبط عندما نقول CI/CD. الكثير من المقالات تتحدث عن الفوائد العامة مثل "التسليم المستمر" و"الاختبار الآلي"، لكنها لا تشرح ماذا يجري في الذاكرة والمعالج عندما يضغط المطور commit. عندما تقوم بـ push إلى فرع main في GitHub، يحدث تسلسل من الأحداث يشبه خط تجميع المصنع، لكن بدلاً من السيارات، نحن نجمع ونختبر وننشر كود برمجي. أولاً، GitHub يرسل webhook إلى سيرفر CI/CD (مثل GitHub Actions أو GitLab CI) يحتوي على معلومات الـ commit مثل الـ SHA والرسالة والتغييرات. هذا الـ webhook يتم استقباله بواسطة worker process على السيرفر، الذي يبدأ في تحميل الـ repository بالكامل إلى ذاكرة مؤقتة (temp directory).
بعد ذلك، الـ worker يبدأ في تنفيذ الـ pipeline الذي قمت بتعريفه في ملف YAML. كل خطوة في الـ pipeline هي عملية جديدة (process) منفصلة، لها مساحة ذاكرة خاصة بها، وتتواصل مع العمليات الأخرى عبر نظام الملفات أو الـ environment variables. مثلاً، عندما تقول "run tests"، الـ worker ينفذ أمراً مثل `npm test`، وهذا الأمر يفتح Node.js process جديد يقوم بتحميل كل ملفات الجافاسكريبت إلى الذاكرة، ويبدأ في تنفيذ الـ test cases واحداً تلو الآخر. إذا كان أحد الاختبارات fail، الـ process يرجع exit code غير صفري، والـ worker يتوقف فوراً ويبلغك بالفشل عبر إشعار. هذه الآلية تعتمد على مفهوم الـ process isolation في أنظمة التشغيل، وهو ما يجعل CI/CD آمناً حتى لو كان الكود يحتوي على أخطاء فادحة مثل infinite loops أو memory leaks.
# مثال مبسط لملف GitHub Actions يوضح الـ process isolation
name: CI Pipeline
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test # هذا الأمر ينفذ في process منفصل
# إذا فشل، الـ process يرجع exit code 1 والـ job يتوقف
- name: Build project
run: npm run build
# هذا أيضاً process منفصل، لا يشارك الذاكرة مع سابقه
deploy:
needs: test # لا يبدأ إلا إذا نجح job الـ test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy to production
run: ./deploy.sh
env:
API_KEY: ${{ secrets.API_KEY }} # متغيرات البيئة تُمرر للـ processالخطأ الأول الذي يقع فيه المطورون هو أن CI/CD هو مجرد كتابة ملف YAML ووضعه في مجلد .github. الحقيقة أن معظم المشاكل تظهر في مرحلة التنفيذ، عندما تبدأ الـ processes في الفشل بسبب مشاكل في الـ environment أو الـ dependencies. مثلاً، قد يعمل الكود على جهازك لأن لديك Node.js v18 مثبتاً، لكن الـ CI worker يستخدم v16، وهذا يؤدي إلى فشل في الـ build. أو قد يكون لديك ملف config محلي لا يوجد في الـ repository، فتنسى إضافته، والـ pipeline يفشل في خطوة الـ deploy. هذه المشاكل لا تظهر إلا عندما تبدأ في تشغيل الـ pipeline على سيرفر حقيقي، وليس على جهازك المحلي.
الخطأ الثاني هو تجاهل الـ caching. عندما تقوم بتشغيل `npm install` في كل مرة تقوم فيها بـ push، أنت تهدر وقتاً وموارد ثمينة. الـ CI worker يضطر إلى تحميل كل الـ dependencies من جديد، وهذا قد يستغرق دقائق في المشاريع الكبيرة. الحل هو استخدام الـ caching المدمج في GitHub Actions، الذي يحفظ مجلد node_modules بين الـ runs، ويقلل وقت الـ build من ٥ دقائق إلى ٣٠ ثانية. لكن حتى الـ caching له مشاكله: إذا لم تحدد مفاتيح الـ cache بشكل صحيح، قد ينتهي بك الأمر باستخدام نسخة قديمة من الـ dependencies، أو أسوأ، قد تتداخل الـ caches بين الـ branches المختلفة.
# مثال صحيح للـ caching في GitHub Actions
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Cache node modules
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Install dependencies
run: npm ciأول شيء يجب أن تبنيه في خط الإنتاج هو مرحلة الاختبار. هذه المرحلة هي خط الدفاع الأول ضد الكود السيئ، ويجب أن تكون صارمة وغير قابلة للتفاوض. عندما أقول "غير قابلة للتفاوض"، أعني أنه إذا فشل أي اختبار، الـ pipeline يجب أن يتوقف فوراً ولا ينتقل إلى مرحلة الـ build أو الـ deploy. المشكلة أن الكثير من المطورين يضعون اختبارات سطحية لا تغطي الحالات الحرجة، أو أسوأ، يستخدمون اختبارات وهمية (mock tests) لا تختبر السلوك الحقيقي للتطبيق.
في تجربتي، أفضل طريقة لكتابة اختبارات فعالة هي استخدام مبدأ "الاختبار السلوكي" بدلاً من "الاختبار الوظيفي". بدلاً من أن تختبر ما إذا كانت دالة معينة ترجع قيمة صحيحة، اختبر ما إذا كان المستخدم يستطيع فعل شيء معين في التطبيق. مثلاً، بدلاً من اختبار ما إذا كانت دالة `calculateTotal()` ترجع ١٠٠ عندما تعطى مدخلات معينة، اختبر ما إذا كان زر "احسب الإجمالي" في الواجهة يظهر الرقم ١٠٠ عندما يضغط المستخدم عليه. هذا النوع من الاختبارات يتطلب استخدام أدوات مثل Playwright أو Cypress، التي تسمح لك بكتابة اختبارات end-to-end تحاكي سلوك المستخدم الحقيقي.
// مثال لاختبار سلوكي باستخدام Playwright
const { test, expect } = require('@playwright/test');
test('User can add item to cart and see correct total', async ({ page }) => {
await page.goto('https://example.com');
await page.click('text=Add to Cart'); // المستخدم يضغط زر
await page.click('text=View Cart');
// نتوقع أن يظهر الإجمالي الصحيح في الصفحة
const total = await page.textContent('.cart-total');
expect(total).toBe('$100.00');
// هذا الاختبار يختبر السلوك الكامل للتطبيق، وليس مجرد دالة
});الفخ الأول في مرحلة الاختبار هو الاعتماد على البيانات الثابتة (hardcoded data). مثلاً، قد تكتب اختباراً يتوقع أن قاعدة البيانات تحتوي على مستخدم اسمه "أحمد"، لكن في الواقع، قاعدة البيانات قد تكون فارغة أو تحتوي على بيانات مختلفة. الحل هو استخدام قاعدة بيانات اختبار مؤقتة (test database) يتم ملؤها ببيانات عشوائية قبل كل اختبار، ثم يتم إفراغها بعده. أدوات مثل Prisma وTypeORM تسمح لك بإنشاء قاعدة بيانات مؤقتة بسهولة، ويمكنك استخدام مكتبات مثل Faker.js لإنشاء بيانات واقعية عشوائية.
الفخ الثاني هو تجاهل الـ flaky tests، وهي الاختبارات التي تفشل أحياناً بدون سبب واضح. مثلاً، قد يكون لديك اختبار يعتمد على API خارجي، وإذا كان هذا الـ API بطيئاً أو غير متاح، فالاختبار سيفشل. الحل هو استخدام الـ retries مع timeout مناسب، وإضافة منطق للتعامل مع الأخطاء المتوقعة. في GitHub Actions، يمكنك استخدام خاصية `retry` لإعادة تشغيل الـ job إذا فشل، لكن يجب أن تكون حذراً لأن هذا قد يخفي مشاكل حقيقية في الكود.
# مثال لاستخدام retries في GitHub Actions
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false # لا تتوقف إذا فشل أحد الـ jobs
matrix:
node-version: [16, 18]
steps:
- uses: actions/checkout@v4
- name: Run tests with retry
uses: nick-fields/retry@v2
with:
timeout_minutes: 5
max_attempts: 3
command: npm testبعد أن تتأكد أن الكود سليم من خلال الاختبارات، تأتي مرحلة البناء (build). هذه المرحلة هي المسؤولة عن تحويل الكود المصدر إلى ملفات قابلة للنشر، سواء كانت ملفات جافاسكريبت مضغوطة أو حاويات Docker جاهزة للنشر. المشكلة الأكبر في هذه المرحلة هي إدارة الـ dependencies، خاصة في المشاريع الكبيرة التي تحتوي على عشرات المكتبات الخارجية. إذا لم تكن حذراً، قد ينتهي بك الأمر ببناء تطبيق بحجم ٥٠ ميجابايت بدلاً من ٥ ميجابايت، أو أسوأ، قد يحتوي البناء على مكتبات قديمة تحتوي على ثغرات أمنية.
الحل هو استخدام أدوات تحليل الـ dependencies مثل `npm audit` أو `yarn audit`، التي تفحص الـ dependencies وتخبرك إذا كانت تحتوي على ثغرات معروفة. لكن هذه الأدوات ليست كافية وحدها، لأن بعضها قد يعطي إنذارات كاذبة أو لا يغطي كل الثغرات. الحل الأفضل هو استخدام أدوات مثل Dependabot، التي تراقب الـ dependencies بشكل مستمر وتفتح pull requests تلقائياً عندما يتم إصدار تحديث أمني. في GitHub، يمكنك تفعيل Dependabot بسهولة من إعدادات الـ repository، وسيبدأ في إرسال تحديثات تلقائية لك.
# ملف إعدادات Dependabot في GitHub
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "daily"
open-pull-requests-limit: 10
reviewers:
- "your-github-username"
assignees:
- "your-github-username"
labels:
- "dependencies"
- "automated"
# يفتح PR تلقائي لكل تحديث أمنيإذا كنت تبني تطبيق ويب، فإن حجم ملفات الجافاسكريبت هو عامل حاسم في أداء التطبيق. دراسة من Google أظهرت أن كل ١٠٠ كيلوبايت إضافية في حجم الصفحة تزيد من معدل الارتداد بنسبة ١٪. الحل هو استخدام أدوات مثل Webpack أو Vite لضغط وتحسين ملفات الجافاسكريبت. لكن حتى هذه الأدوات قد لا تكون كافية إذا لم تضبطها بشكل صحيح. مثلاً، قد تستخدم Webpack بدون تفعيل خاصية tree-shaking، مما يعني أن الكود سيحتوي على دوال ومكتبات لم تستخدمها أبداً.
الحل هو استخدام أدوات تحليل البناء مثل `webpack-bundle-analyzer`، التي تظهر لك بالضبط ما يحتوي عليه البناء النهائي. بهذه الطريقة، يمكنك اكتشاف المكتبات الكبيرة التي لا تحتاجها، أو الملفات التي تم تكرارها بدون داع. مثلاً، قد تكتشف أن مكتبة Lodash كاملة تم تضمينها في البناء، بينما كنت تستخدم فقط دالة واحدة منها. في هذه الحالة، يمكنك استبدال Lodash بمكتبة أصغر مثل `lodash-es` واستيراد الدوال التي تحتاجها فقط.
// مثال لاستخدام tree-shaking في Webpack
// في ملف webpack.config.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
plugins: [
new BundleAnalyzerPlugin(), // يظهر تقرير تحليل البناء
],
optimization: {
usedExports: true, // يفعّل tree-shaking
},
};
// في الكود، استورد الدوال التي تحتاجها فقط
import { debounce } from 'lodash-es';
// بدلاً من: import _ from 'lodash';المرحلة الأخيرة في خط الإنتاج هي النشر (deploy). هذه هي المرحلة الأكثر خطورة، لأن أي خطأ هنا يعني أن المستخدمين سيرون موقعاً معطلاً أو تطبيقاً لا يعمل. المشكلة أن الكثير من المطورين يستخدمون طريقة النشر التقليدية، حيث يتم استبدال النسخة القديمة بالجديدة مباشرة، وهذا يعني أنه إذا كان هناك خطأ في النسخة الجديدة، فسيتأثر جميع المستخدمين فوراً. الحل هو استخدام تقنية الـ blue-green deployment، حيث تحتفظ بنسختين من التطبيق تعملان في نفس الوقت، وتنتقل من النسخة القديمة إلى الجديدة تدريجياً.
كيف يعمل الـ blue-green؟ ببساطة، لديك بيئتين متطابقتين: البيئة الزرقاء (blue) وهي النسخة الحالية التي يستخدمها المستخدمون، والبيئة الخضراء (green) وهي النسخة الجديدة التي تريد نشرها. عندما تنتهي من بناء النسخة الجديدة، تقوم بتوجيه جزء من المستخدمين إلى البيئة الخضراء، وتراقب الأداء والأخطاء. إذا كان كل شيء على ما يرام، تقوم بتوجيه جميع المستخدمين إلى البيئة الخضراء، وتجعلها البيئة الزرقاء الجديدة. إذا حدث خطأ، يمكنك العودة إلى البيئة الزرقاء القديمة بضغطة زر. هذه الطريقة تقلل من وقت التوقف إلى الصفر، وتجعل عملية النشر آمنة حتى للمشاريع الكبيرة.
# مثال لسكربت نشر blue-green باستخدام Docker وNginx
#!/bin/bash
# المتغيرات
BLUE_C"app-blue"
GREEN_CONTAINER="app-green"
CURRENT_COLOR=$(docker ps --format '{{.Names}}' | grep -q $BLUE_CONTAINER && echo "blue" || echo "green")
NEW_COLOR=$([ "$CURRENT_COLOR" == "blue" ] && echo "green" || echo "blue")
NEW_PORT=$([ "$NEW_COLOR" == "green" ] && echo "8081" || echo "8080")
# بناء الصورة الجديدة
docker build -t app:$NEW_COLOR .
# تشغيل الحاوية الجديدة
if [ "$NEW_COLOR" == "green" ]; then
docker run -d --name $GREEN_CONTAINER -p 8081:3000 app:$NEW_COLOR
else
docker run -d --name $BLUE_CONTAINER -p 8080:3000 app:$NEW_COLOR
fi
# انتظار الحاوية الجديدة حتى تصبح جاهزة
while ! curl -s http://localhost:$NEW_PORT/health >/dev/null; do
sleep 1
echo "Waiting for $NEW_COLOR to be ready..."
done
# تحديث Nginx لتوجيه المستخدمين إلى البيئة الجديدة
cat > /etc/nginx/conf.d/app.conf <<EOF
upstream app {
server localhost:$NEW_PORT;
}
server {
listen 80;
location / {
proxy_pass http://app;
}
}
EOF
# إعادة تحميل Nginx
nginx -s reload
# حذف الحاوية القديمة بعد التأكد من أن الجديدة تعمل
if [ "$NEW_COLOR" == "green" ]; then
docker stop $BLUE_CONTAINER && docker rm $BLUE_CONTAINER
else
docker stop $GREEN_CONTAINER && docker rm $GREEN_CONTAINER
fi
# إرسال إشعار بنجاح النشر
curl -X POST -H 'Content-type: application/json' --data '{"text":"✅ تم نشر النسخة '$NEW_COLOR' بنجاح!"}' $SLACK_WEBHOOK_URLحتى مع الـ blue-green deployments، قد تحدث أخطاء أثناء النشر. مثلاً، قد يكون هناك خطأ في قاعدة البيانات الجديدة، أو قد يفشل الـ health check بسبب مشكلة في الـ environment variables. الحل هو استخدام استراتيجية الـ rollback التلقائي، حيث إذا فشل النشر بعد عدد معين من المحاولات، يعود النظام تلقائياً إلى النسخة السابقة. في GitHub Actions، يمكنك استخدام خاصية `continue-on-error` مع منطق مخصص لإعادة المحاولة أو التراجع.
أفضل طريقة للتعامل مع الأخطاء هي استخدام الـ feature flags. هذه التقنية تسمح لك بتفعيل أو تعطيل ميزات معينة في التطبيق دون الحاجة إلى نشر كود جديد. مثلاً، إذا كنت تريد تجربة ميزة جديدة ولكن لست متأكداً من استقرارها، يمكنك تفعيلها فقط لمجموعة صغيرة من المستخدمين باستخدام feature flag. إذا حدث خطأ، يمكنك تعطيل الميزة بضغطة زر دون الحاجة إلى rollback كامل. أدوات مثل LaunchDarkly أو Flagsmith توفر هذه الخدمة، لكن يمكنك أيضاً بناء نظام بسيط بنفسك باستخدام قاعدة بيانات أو ملف config.
// مثال بسيط لاستخدام feature flags في Node.js
const FEATURE_FLAGS = {
newCheckoutFlow: process.env.NEW_CHECKOUT_FLOW === 'true',
darkMode: process.env.DARK_MODE === 'true',
};
// في الكود، استخدم الـ flag للتحكم في السلوك
app.get('/checkout', (req, res) => {
if (FEATURE_FLAGS.newCheckoutFlow) {
res.sendFile('new-checkout.html');
} else {
res.sendFile('old-checkout.html');
}
});
// في الـ CI/CD، يمكنك تفعيل الـ flag تلقائياً بعد النشر الناجح
// باستخدام GitHub Secrets أو متغيرات البيئةبناء خط إنتاج CI/CD ليس مهمة تتم مرة واحدة وتنتهي، بل هي عملية مستمرة تتطلب مراقبة وتحسين. المشكلة أن الكثير من المطورين يبنون الـ pipeline وينسون عنه، ثم يفاجؤون بعد أشهر بأن وقت البناء أصبح يستغرق ٢٠ دقيقة بدلاً من ٥ دقائق، أو أن هناك اختبارات تفشل بشكل عشوائي. الحل هو استخدام أدوات مراقبة مثل GitHub Insights أو Datadog، التي تراقب أداء الـ pipeline وتخبرك إذا كان هناك تباطؤ أو أخطاء متكررة.
أول شيء يجب مراقبته هو وقت التنفيذ الكلي للـ pipeline. إذا كان الوقت يزيد بشكل مستمر، فهذا يعني أن هناك مشكلة في الكود أو في الـ dependencies. مثلاً، قد يكون لديك مكتبة جديدة أضفتها تزيد من وقت البناء، أو قد يكون لديك اختبار بطيء يحتاج إلى تحسين. في تجربتي، أفضل طريقة لتحسين وقت البناء هي استخدام خاصية الـ parallel jobs في GitHub Actions، التي تسمح لك بتشغيل خطوات متعددة في نفس الوقت بدلاً من التوالي. مثلاً، يمكنك تشغيل اختبارات الواجهة واختبارات الـ API في نفس الوقت، مما يقلل الوقت الكلي إلى النصف.
# مثال لاستخدام parallel jobs في GitHub Actions
jobs:
test-ui:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm run test:ui # اختبارات الواجهة
test-api:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm run test:api # اختبارات API
build:
needs: [test-ui, test-api] # لا يبدأ إلا إذا نجحت الاختبارات
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm run build
# هذه الـ jobs الثلاثة تعمل بالتوازي، مما يقلل الوقت الكليالـ technical debt لا يظهر فقط في الكود، بل يظهر أيضاً في خط الإنتاج. مثلاً، قد يكون لديك ملف YAML معقد يحتوي على عشرات الخطوات، أو قد يكون لديك سكربتات نشر قديمة لم تعد تعمل ولكن لم يتم إزالتها. هذا النوع من الـ debt يجعل خط الإنتاج صعب الصيانة ويزيد من احتمالية الأخطاء. الحل هو مراجعة خط الإنتاج بشكل دوري، وإزالة أي خطوات غير ضرورية، وتوثيق كل جزء بشكل واضح.
أفضل ممارسة هي استخدام مبدأ "الخطوات الصغيرة المتكررة" بدلاً من "الخطوات الكبيرة المعقدة". مثلاً، بدلاً من أن يكون لديك سكربت نشر واحد كبير يقوم بكل شيء، قسمه إلى سكربتات صغيرة كل منها مسؤول عن مهمة واحدة. هذا يجعل خط الإنتاج أسهل في الصيانة وأكثر مرونة. أيضاً، استخدم التعليقات في ملفات YAML لشرح الغرض من كل خطوة، حتى لو كنت أنت من كتب الكود، لأنك قد تنسى التفاصيل بعد أشهر.
# مثال لملف YAML منظم جيداً مع تعليقات واضحة
name: CI/CD Pipeline
on: [push]
jobs:
# ===== مرحلة الاختبار =====
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# تحميل الـ cache لتسريع البناء
- name: Restore node modules cache
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
# تثبيت الـ dependencies
- name: Install dependencies
run: npm ci
# تشغيل الاختبارات
- name: Run unit tests
run: npm run test:unit
# تشغيل اختبارات التكامل
- name: Run integration tests
run: npm run test:integration
# ===== مرحلة البناء =====
build:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# بناء مشروع الإنتاج
- name: Build production bundle
run: npm run build
# تحليل حجم البناء
- name: Analyze bundle size
run: npm run analyze
# رفع البناء إلى التخزين المؤقت
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: production-build
path: dist/
# ===== مرحلة النشر =====
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# تحميل البناء من التخزين المؤقت
- name: Download build artifacts
uses: actions/download-artifact@v3
with:
name: production-build
path: dist/
# نشر إلى بيئة الإنتاج
- name: Deploy to production
run: ./deploy.sh
env:
API_KEY: ${{ secrets.API_KEY }}بعد أكثر من عشر سنوات في بناء خطوط إنتاج CI/CD لمشاريع تتراوح بين الـ side projects الصغيرة والتطبيقات المستخدمة من قبل ملايين المستخدمين، هذه هي النصائح الذهبية التي أتمنى أن يعرفها كل مطور قبل أن يبدأ:
أخيراً، تذكر أن CI/CD ليس مجرد أداة تقنية، بل هو طريقة تفكير. عندما تبني خط إنتاج تلقائي، أنت لا تكتب كوداً فقط، بل تبني نظاماً كاملاً يدعم عملك اليومي. هذا النظام سيوفر عليك ساعات من العمل اليدوي، ويقلل من الأخطاء، ويجعلك مطمئناً عند النشر. ابدأ اليوم بخطوات صغيرة، وسترى الفرق في غضون أسابيع قليلة. إذا كنت تريد خطوة عملية الآن، افتح مشروعك الحالي وأنشئ ملف `.github/workflows/ci.yml` بسيط يحتوي على خطوة اختبار واحدة فقط. هذا هو أول خطوة في رحلتك نحو CI/CD الكامل.