يا جماعة الخير، السلام عليكم ورحمة الله وبركاته. معكم أخوكم أبو عمر.
خليني أحكيلكم قصة صارت معي قبل كم سنة، قصة بتلخص المعاناة اللي كتير من المبرمجين بعيشوها. كنا فريق شغالين على مشروع مهم، والساعة صارت 2 بعد نص الليل، عيوننا حمر من التعب والقهوة صارت زي المي. إحنا على وشك نطلق إصدار جديد للعميل، والكل متوتر. مدير المشروع بسألني: “أبو عمر، جاهزين؟ شو رقم الإصدار الجديد؟ وشو نكتب في إعلان التحديثات؟”
هون بلشت الكركبة. واحد من الشباب بحكي الإصدار لازم يكون 1.7.0، والثاني بحكي لا، التغييرات بسيطة، خليه 1.6.2. وأنا فتحت سجل الـ commits في Git عشان أشوف شو عملنا بالضبط، وإذا هو عبارة عن فوضى عارمة: “fix bug”, “update”, “more stuff”, “final changes”… رسائل ما حدا بفهم منها إشي. قعدنا ساعة كاملة نحاول نلملم التغييرات ونكتب “سجل التغييرات” (Changelog) بإيدنا، وبالآخر طلعنا بإصدار جديد وإحنا مش متأكدين 100% إذا نسينا إشي أو لأ. يومها قلت لحالي: “يا زلمة، لازم يكون في طريقة أحسن من هيك! هاي مش شغلة تنعمل يدويًا في 2024”.
ومن يومها، بلشت رحلة البحث عن حل، ولقيت الكنز اللي اسمه ‘Semantic Release’. واليوم، بدي أشاركم كيف هاي الأداة السحرية أنقذتنا من الفوضى وحوّلت عملية الإطلاق من كابوس إلى متعة.
قبل الأتمتة: ما هو ترقيم الإصدارات الدلالي (Semantic Versioning)؟
قبل ما نغوص في الأداة نفسها، لازم نفهم المبدأ اللي بتعتمد عليه. ترقيم الإصدارات الدلالي، أو SemVer اختصارًا، هو مش مجرد أرقام عشوائية بنحطها، هو عبارة عن اتفاقية ومعيار عالمي لوصف التغييرات في كل إصدار. الصيغة بسيطة جدًا:
MAJOR.MINOR.PATCH (رئيسي.فرعي.تصحيح)
- MAJOR (رئيسي): بنزيد هذا الرقم (مثلاً من 1.7.5 إلى 2.0.0) لما نعمل تغييرات جذرية غير متوافقة مع الإصدارات السابقة (Breaking Changes). يعني الكود القديم اللي كان يستخدم مكتبتك ممكن يتكسّر ويحتاج تعديل.
- MINOR (فرعي): بنزيده (مثلاً من 1.7.5 إلى 1.8.0) لما نضيف ميزات جديدة، لكنها متوافقة مع الإصدارات السابقة. الكود القديم بضل شغال بدون مشاكل.
- PATCH (تصحيح): بنزيده (مثلاً من 1.7.5 إلى 1.7.6) لما نعمل إصلاحات لأخطاء (bug fixes)، وهاي الإصلاحات طبعًا بتكون متوافقة مع الإصدارات السابقة.
هذا المعيار بخلي التواصل بين المطورين والمشاريع أوضح. لما تشوف مكتبة تحدّثت من إصدار 2.1.0 إلى 2.2.0، بتعرف فورًا إنه في ميزات جديدة بس كل إشي آمن للتحديث. أما لو التحديث كان لـ 3.0.0، هون لازم تنتبه وتقرأ دليل الترقية لأنه في تغييرات جذرية.
الفوضى اليدوية: لماذا كانت إدارة الإصدارات كابوسًا؟
القصة اللي حكيتها في البداية هي مثال بسيط على المشاكل اللي بتصير لما تكون العملية يدوية:
- الخطأ البشري: نسيان تحديث رقم الإصدار في ملف
package.json، أو كتابة رقم غلط، أو نسخ ولصق تغييرات بشكل خاطئ في ملفCHANGELOG.md. - سجل تغييرات غير متناسق: كل مطور بكتب ملاحظاته بطريقته. واحد بكتب بالتفصيل، والثاني بكتب كلمتين. النتيجة ملف فوضوي صعب فهمه.
- انقطاع التواصل: فريق التسويق أو إدارة المنتج ما بعرفوا شو الميزات الجديدة اللي لازم يعلنوا عنها، والمستخدمين النهائيين ما بعرفوا شو تصلّح أو شو الجديد.
- وقت ضائع: بدل ما المطور يركز على كتابة الكود وحل المشاكل، بضيع ساعات في كل عملية إطلاق وهو بعمل مهام روتينية مملة: يقرأ الـ commits، يكتب الـ changelog، يعمل tag جديد في Git، ويرفع الحزمة على npm أو أي منصة ثانية.
المنقذ: ‘Semantic Release’ وكيف يعمل السحر؟
هنا يأتي دور semantic-release. هي أداة تقوم بأتمتة عملية الإصدار بالكامل. الفكرة عبقرية وبسيطة: بدل ما الإنسان يقرر رقم الإصدار الجديد ويكتب سجل التغييرات، الأداة بتعمل كل هاد بناءً على رسائل الـ commits اللي أنت بتكتبها في Git.
كيف يفهم ‘Semantic Release’ رسائل الـ Commit؟
السر كله يكمن في اتباع معيار محدد لكتابة رسائل الـ commit اسمه “Conventional Commits”. هذا المعيار بفرض عليك صيغة معينة للرسالة، وهي الصيغة اللي بتخلي الآلة تفهم طبيعة التغيير اللي عملته.
الصيغة العامة هي: <type>(<scope>): <subject>
- type: هو أهم جزء، وبحدد نوع التغيير. أشهر الأنواع:
feat: لإضافة ميزة جديدة (سيؤدي إلى إصدار MINOR).fix: لإصلاح خطأ (سيؤدي إلى إصدار PATCH).docs: لتعديل التوثيق فقط (لن يؤدي إلى إصدار جديد).style,refactor,test,chore: أنواع أخرى لتنظيم العمل، لا تؤدي لإصدار جديد.
- scope (اختياري): لتحديد الجزء من المشروع اللي صار عليه التغيير (مثال:
auth,api,ui). - subject: وصف مختصر وواضح للتغيير.
وللإشارة إلى تغيير جذري (Breaking Change)، يمكنك إضافة ! بعد الـ type، أو إضافة فقرة BREAKING CHANGE: في جسم الرسالة.
أمثلة عملية:
# هذا الـ commit سيؤدي إلى إصدار MINOR (مثلاً من 1.2.3 إلى 1.3.0)
feat(auth): add user login with email and password
# هذا الـ commit سيؤدي إلى إصدار PATCH (مثلاً من 1.3.0 إلى 1.3.1)
fix(api): correct calculation for order total
# هذا الـ commit لن يؤدي إلى أي إصدار
docs(readme): update installation instructions
# هذا الـ commit سيؤدي إلى إصدار MAJOR (مثلاً من 1.3.1 إلى 2.0.0)
feat(api)!: change user data structure in API response
BREAKING CHANGE: The `user.fullName` field has been replaced with `user.firstName` and `user.lastName` to provide more granular data.
دورة حياة الإصدار المؤتمتة
عندما تدمج تغييراتك في الفرع الرئيسي (مثل main أو master)، يقوم سير عمل الـ CI/CD (مثل GitHub Actions) بتشغيل semantic-release، والذي يقوم بالخطوات التالية تلقائيًا:
- تحليل الـ Commits: يقرأ كل رسائل الـ commit منذ آخر إصدار تم.
- تحديد الإصدار القادم: بناءً على أنواع الـ commits (
feat,fix,!)، يقرر ما إذا كان الإصدار القادم هو MAJOR أو MINOR أو PATCH. إذا لم يجد أيًا منها، فلن يتم إنشاء إصدار جديد. - إنشاء سجل التغييرات: يقوم تلقائيًا بإنشاء أو تحديث ملف
CHANGELOG.md، مع تقسيم التغييرات إلى أقسام واضحة مثل “✨ Features” و “🐛 Bug Fixes”. - تحديث الملفات وعمل Tag: يقوم بتحديث رقم الإصدار في ملف
package.json(أو ملفات أخرى)، ثم يقوم بإنشاء Git tag جديد (مثلv2.0.0). - النشر: يقوم بنشر الحزمة البرمجية إلى مستودع الحزم (مثل npm)، وإنشاء “Release” على GitHub مع إرفاق سجل التغييرات الخاص بهذا الإصدار.
كل هذا يتم في دقائق، بدون أي تدخل بشري!
تطبيق عملي: لنقم بإعداد ‘Semantic Release’ في مشروع Node.js
الكلام النظري جميل، لكن خلينا نشوف كيف بنطبق هالحكي بشكل عملي. سأستخدم كمثال مشروع Node.js مع GitHub Actions.
المتطلبات الأساسية
- مشروع Node.js يحتوي على ملف
package.json. - مستودع Git على منصة GitHub.
- حساب على npm (إذا كنت تريد النشر عليه).
الخطوة 1: التثبيت والإعداد
أولاً، نقوم بتثبيت الحزم اللازمة كـ dev dependencies:
npm install --save-dev semantic-release @semantic-release/git @semantic-release/changelog @semantic-release/npm @semantic-release/github
هذه الحزم هي “ملحقات” (plugins) تسمح لـ semantic-release بالتفاعل مع Git، وتحديث سجل التغييرات، والنشر على npm و GitHub.
الخطوة 2: تهيئة ملف الإعدادات
يمكنك إنشاء ملف .releaserc.json في جذر المشروع، أو إضافة قسم "release" داخل ملف package.json. أنا أفضل ملف منفصل لتنظيم أفضل.
محتوى ملف .releaserc.json:
{
"branches": ["main"],
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
["@semantic-release/changelog", {
"changelogFile": "CHANGELOG.md"
}],
["@semantic-release/npm", {
"npmPublish": true
}],
["@semantic-release/git", {
"assets": ["package.json", "CHANGELOG.md"],
"message": "chore(release): ${nextRelease.version} [skip ci]nn${nextRelease.notes}"
}],
"@semantic-release/github"
]
}
branches: نحدد الفروع التي سيتم إطلاق الإصدارات منها (عادةmain).plugins: نحدد الملحقات التي سيتم استخدامها وبالترتيب. كل ملحق له وظيفة محددة في دورة حياة الإصدار.
الخطوة 3: إعداد الـ CI/CD (مثال GitHub Actions)
الآن، نحتاج إلى إخبار GitHub بتشغيل هذه العملية تلقائيًا. ننشئ ملفًا جديدًا هنا: .github/workflows/release.yml
name: Release
on:
push:
branches:
- main
jobs:
release:
name: Create Release
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run semantic-release
run: npx semantic-release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
شرح سريع:
on: push: branches: [main]: هذا يعني أن سير العمل سيعمل فقط عند حدوثpushعلى فرعmain.fetch-depth: 0: مهم جدًا، لأنه يسمح لـ semantic-release بالوصول إلى كل تاريخ الـ commits.env: هنا نمرر التوكينات اللازمة.GITHUB_TOKENيتوفر تلقائيًا في GitHub Actions. أماNPM_TOKEN، فيجب عليك إنشاؤه من حسابك في npm وإضافته كـ “Secret” في إعدادات المستودع على GitHub.
نصيحة من أبو عمر: يا جماعة، أهم إشي الالتزام! لازم كل الفريق يلتزم بصيغة الـ Conventional Commits، وإلا كل الشغل هاد بروح عالفاضي. استخدموا أدوات مثل commitlint مع husky (Git hooks) عشان تجبروا الكل يلتزم بالصيغة الصحيحة عند عمل commit. هيك بتضمنوا إنه ما في مجال للغلط.
ما بعد الأتمتة: الفوائد التي لمسناها
بعد تطبيق هذا النظام، تغيرت حياتنا كفريق تطوير بشكل جذري:
- زيادة الثقة وراحة البال: لم نعد نقلق بشأن عملية الإطلاق. كل شيء مؤتمت وموثوق. لا يوجد قلق “هل نسينا شيئًا؟”.
- توفير هائل للوقت: المطورون يركزون على ما يبرعون فيه: كتابة الكود. الأداة تتكفل بالباقي.
- تواصل أوضح 100%: سجل التغييرات
CHANGELOG.mdأصبح المصدر الوحيد والموثوق للحقيقة. فريق المنتج، التسويق، وحتى المستخدمون، يمكنهم قراءته بسهولة لمعرفة كل جديد. - إصدارات أسرع وأكثر تكرارًا: بما أن العملية سهلة جدًا، أصبحنا لا نتردد في إطلاق إصدار جديد حتى لو كان لإصلاح خطأ بسيط. هذا يعني أن التحسينات تصل للمستخدمين بشكل أسرع.
الخلاصة: من الفوضى إلى النظام بضغطة زر 🚀
الانتقال من إدارة الإصدارات اليدوية إلى نظام مؤتمت باستخدام semantic-release كان واحدًا من أفضل القرارات التقنية التي اتخذناها. لقد حررنا من مهام روتينية مملة، وقلل الأخطاء البشرية إلى الصفر، وجعل عملية تطوير البرمجيات أكثر احترافية ومتعة.
إذا كنت لا تزال تعاني من فوضى ما قبل الإطلاق، وتضيع ساعات في مهام كان يجب أن تكون مؤتمتة، فأنصحك بشدة أن تعطي هذا النهج فرصة. قد يتطلب الأمر بعض الجهد في الإعداد الأولي وتعويد الفريق على كتابة رسائل commit منظمة، لكن العائد على المدى الطويل يستحق كل دقيقة.
جربوها في مشروع جانبي، وشوفوا الفرق بأنفسكم. صدقوني، ما راح ترجعوا للطريقة القديمة أبدًا. والله ولي التوفيق.