سجلات التغيير والالتزامات التقليدية
سجلات التغيير والالتزامات التقليدية
سجل التغيير عقدٌ مع مستخدميك. يُخبرهم بما تغيّر، وما انكسر، وما يجب عليهم فعله قبل الترقية. لكن على نطاق واسع — عشرات المهندسين ومئات الالتزامات أسبوعيًا — لا يستطيع أحد الحفاظ على سجل تغيير دقيق يدويًا. الحل الصناعي هو الالتزامات التقليدية (Conventional Commits): مواصفة خفيفة تمنح رسائل الالتزام هيكلًا قابلًا للتحليل آليًا، مما يُمكّن الأدوات من توليد سجلات التغييرات، وتحديث الإصدارات، وتشغيل عمليات الإطلاق دون أي تدخل بشري. هذه هي الطريقة التي تدير بها Google وMicrosoft ومعظم مشاريع المصدر المفتوح الحديثة التواصل مع المستخدمين حول الإصدارات.
مواصفة الالتزامات التقليدية
تُحدِّد المواصفة (conventionalcommits.org) تنسيقًا بسيطًا لرسائل الالتزام:
النوع هو الحقل الأساسي. تُحدِّد المواصفة نوعين إلزاميين وتحجز أخرى بالاتفاق:
fix— إصلاح خطأ. يُقابل رفع إصدار PATCH في SemVer.feat— ميزة جديدة. يُقابل رفع إصدار MINOR.feat!أو تذييلBREAKING CHANGE: ...— تغيير جوهري يكسر التوافق. يُقابل رفع إصدار MAJOR.- أنواع مجتمعية (من اتفاقية Angular، معتمدة على نطاق واسع):
build،chore،ci،docs،perf،refactor،revert،style،test. لا تُؤدي هذه الأنواع إلى رفع رقم الإصدار بمفردها.
يُضيّق النطاق الاختياري السياق: feat(auth): add OIDC provider support. تظهر النطاقات في سجل التغيير مُجمَّعةً تحت نوعها، ويمكن استخدامها لتحديد قواعد الإصدار لكل مكوّن في المستودعات المتعددة.
fix(auth): resolve session cookie SameSite mismatch. حقل النوع هو الإشارة؛ والوصف نثر بشري. كلاهما مهم — أحدهما للأتمتة والآخر للمهندس الذي يقرأ سجل التغيير في الساعة الثانية صباحًا خلال حادثة طارئة.
أمثلة حقيقية على الالتزامات
الأدوات: commitlint و Husky
المواصفة لا قيمة لها إذا تجاهلها المهندسون. commitlint يُطبِّق التنسيق عند الالتزام عبر خطاف Git، مما يُعطي ردود فعل فورية قبل وصول الرسالة إلى CI. husky يُوصِّل الخطاف تلقائيًا بعد npm install.
git commit --no-verify أو الدفع المباشر. أضف commitlint كمهمة CI تعمل على طلبات السحب: npx commitlint --from origin/main --to HEAD. الخطاف هو تجربة المطوّر؛ CI هو البوابة الحقيقية.
توليد سجل التغيير تلقائيًا: standard-version و semantic-release
مع وجود الالتزامات المنظَّمة، تهيمن أداتان على مساحة توليد سجل التغيير:
- standard-version — واجهة سطر أوامر محلية تُعدِّل
package.jsonوتولِّدCHANGELOG.mdوتُنشئ وسمًا في git. مناسبة للفرق التي تريد أن يُشغِّل الإنسان عمليات الإطلاق. مُهمَلة رسميًا لكنها لا تزال مستخدمة على نطاق واسع. - semantic-release — مؤتمتة بالكامل ومُشغَّلة بواسطة CI. لا يُشغِّلها إنسان؛ يُحدِّد مسار CI الإصدار التالي، ويُنشر القطعة الأثرية، ويُنشئ إصدارًا على GitHub، ويُسجِّل سجل التغيير. هذا هو المعيار الافتراضي للمستوى الكبير للمكتبات والخدمات ذات الإصدارات المتكررة.
semantic-release في CI (GitHub Actions)
fetch-depth: 1، الافتراضي في GitHub Actions) يُعطي semantic-release الالتزام الأخير فقط. لا يستطيع تحديد الوسم السابق، فإما أن يتعطل أو يُحدِّد نوع الرفع بشكل خاطئ. هذا هو السبب الأكثر شيوعًا لإنتاج semantic-release إصدارات خاطئة في CI — دائمًا اجلب التاريخ الكامل.
تنسيق سجل التغيير واحتفظ بسجل تغيير
يتبع ملف CHANGELOG.md المُولَّد اتفاقية Keep a Changelog (keepachangelog.com): أقسام لكل إصدار، كل منها مُقسَّم إلى ### Added و### Fixed و### Changed و### Removed و### Breaking Changes. يتصفح القراء البشريون سجل التغيير من الأعلى؛ تُحلِّله الأدوات الآلية من الأسفل. كلا الجمهورين بالغا الأهمية.
لبيئات بيئات التطوير غير Node.js، تتوفر أدوات مكافئة:
- Python:
python-semantic-release(يقرأ pyproject.toml، ينشر إلى PyPI) - Go:
goreleaserمع دعم الالتزامات التقليدية - Rust:
cargo-release+git-cliffلتوليد سجل التغيير - عام / مستودعات متعددة:
release-please(من Google)،changesets(npm)
أنماط الفشل في بيئة الإنتاج
أبرز أنماط الفشل الشائعة على نطاق واسع:
- تجاوز الدمج بالسحق (Squash): يولِّد زر "Squash and merge" في GitHub رسالة الالتزام الافتراضية مثل
feat: my PR title (#123)، وهي غالبًا صحيحة — لكن فقط إذا كان عنوان طلب السحب مكتوبًا بالتنسيق التقليدي. طبِّق فحص عنوان طلب السحب معamannn/action-semantic-pull-requestفي CI حتى تكون رسالة السحق دائمًا صحيحة. - تغيير جوهري مدفون في النص: يكتب المهندسون
feat: update SDKمعBREAKING CHANGE: ...في النص لكن قاعدة commitlint في CI تتحقق فقط من الترويسة. يلتقطه المُحلِّل ما زال، لكن المراجعين يُفوِّتون الإشارة. اشترط بناءfeat!في حقل النوع للوضوح. - التزامات الروبوت تُشغِّل حلقات: عندما يُسجِّل
semantic-releaseملفCHANGELOG.mdالمُحدَّث إلىmain، يُعيد تشغيل سير عملpush. احمِ بـif: "!contains(github.event.head_commit.message, 'chore(release)')"أو استخدم الرمزskip_ciفي رسالة الالتزام. - غياب
NPM_TOKEN: تنجح مهمة الإصدار، يُنشأ الوسم، يُنشر إصدار GitHub — لكن خطوة نشر npm تفشل بصمت إذا لم يكن السر مُعيَّنًا. تحقق دائمًا من مخرجات المهمة الكاملة، وليس فقط العلامة الخضراء.