الإصدار الدلالي وأنظمة الإصدار
الإصدار الدلالي وأنظمة الإصدار
أرقام الإصدار ليست مجرد زينة. على نطاق الشركات التقنية الكبرى، هي عقد بين منتجي البرمجيات ومستهلكيها: وعد بما تغيّر، ومدى أمان الترقية، وإمكانية ترقية الأتمتة للبنية دون مراجعة بشرية. اختيار النظام الخاطئ — أو تطبيقه بشكل غير متسق — يتسبب في حوادث إنتاجية حقيقية: رفع MINOR يُشحن معه تغيير كسري صامت للـ API، أو تاريخ CalVer يُجمّد تبعية قديمة إلى الأبد لأن أحداً لا يعرف إن كان الرفع آمناً. يتناول هذا الدرس كل مخطط إصدار مستخدم في الإنتاج، وسلّم ما قبل الإصدار، والأدوات التي تؤتمت عمليات الرفع حتى لا يلمس أي إنسان رقم إصدار بيده.
SemVer: العقد الثلاثي
الإصدار الدلالي (semver)، المحدد في semver.org، يستخدم ثلاثة أرقام: MAJOR.MINOR.PATCH. القواعد دقيقة وقابلة للإنفاذ آلياً.
- PATCH — إصلاح خطأ متوافق للخلف. لا تغييرات على الـ API العام. يمكن للمستهلكين الترقية التلقائية بأمان.
- MINOR — وظيفة جديدة متوافقة للخلف. الاستدعاءات الحالية تستمر في العمل. يمكن للمستهلكين الترقية التلقائية بأمان.
- MAJOR — تغيير غير متوافق في الـ API. يجب على المستهلكين قراءة ملاحظات الإصدار والموافقة صراحةً. رفع MAJOR يُشير إلى ضرورة مسار هجرة.
هذه القواعد تجعل مديري التبعيات موثوقين: مستهلك يُعلن ^2.3.0 (نطاق caret في npm) سيثبت تلقائياً أي 2.x.y حيث x >= 3 بدون تدخل بشري، ثقةً في أن رفعَي MINOR وPATCH آمنان. يكسر العقد — ويكسر الإنتاج — بمجرد انتهاكه. إذا حذف إصدار MINOR دالة، تنكسر كل الخدمات المنبثقة التي تستخدمها عند npm install أو pip install --upgrade التالي، بصمت، بعد أيام من شحن الإصدار.
معرّفات ما قبل الإصدار وسلّم الترقية
يدعم SemVer لاحقتين اختياريتين بدلالات صارمة. فهمهما ضروري لأن خط أنابيب CI/CD للترقية يتطابق مباشرةً مع سلّم ما قبل الإصدار.
- معرّف ما قبل الإصدار — يُلحق بشرطة:
2.0.0-alpha.1،2.0.0-beta.3،2.0.0-rc.1. تُصنَّف ما قبل الإصدارات أدنى من الإصدار ذاته:2.0.0-rc.1 < 2.0.0. لن يُرقّي مدير الحزم بشكل افتراضي مستهلكاً مستقراً إلى ما قبل إصدار — يجب على المستهلك الموافقة صراحةً (مثلnpm install mylib@nextأوpip install --pre mylib). - بيانات ميتا للبناء — تُلحق بعلامة زائد:
2.0.0+20260611.sha.a3f9d12. تُتجاهل بيانات ميتا للبناء تماماً عند مقارنة الإصدارات؛ نسختان ببيانات ميتا مختلفة متساويتان من حيث semver. استخدمها لتضمين SHA الـ git، ومعرّف تشغيل CI، أو طابع زمني للبناء لإمكانية التتبع البشري — دون التأثير على ترتيب الإصدارات أو حل التبعيات.
في خط أنابيب إصدار ناضج، يُطابق سلّم ما قبل الإصدار بوابات الترقية. الحزمة تُبنى مرة واحدة وتُعاد وسمها عند كل بوابة — الملف التنفيذي لا يتغير أبداً:
CalVer: الإصدار بالتاريخ
الإصدار التقويمي (CalVer) يُضمّن تاريخ الإصدار في رقم الإصدار. هو الاختيار الصحيح عندما تكون "متى صدر هذا" أكثر فائدة للمستهلكين من "ما الذي تغيّر دلالياً." صيغ شائعة في الأنظمة الإنتاجية:
YYYY.MM— Ubuntu:24.04(إبريل 2024 LTS)،24.10. التاريخ يُشير مباشرةً إلى تاريخ انتهاء نافذة الدعم.YYYY.MM.MICRO— Django استخدم هذا تاريخياً:2024.6.0،2024.6.1. جزء micro هو عداد رقع ضمن الشهر.YYYY.MINOR— pip:24.1،24.2. تزايدات طفيفة ضمن السنة، بدون دقة الشهر.YYYY.0M.DD— بعض أدوات Google الداخلية تستخدم تواريخ كاملة بتنسيق ISO لقطارات الإصدار اليومية.
CalVer ملائم بشكل سيء للمكتبات التي لها كثير من المستهلكين المنبثقين وتغييرات كسرية متكررة: لا توجد إشارة مدمجة تدل على أن 2024.07 → 2024.08 آمن للترقية التلقائية أم يتطلب هجرة. للمكتبات ذات الـ API المستقرة لكن مع تحولات كبرى متفرقة، يعمل الهجين جيداً: 24.6.0 حيث 24.6 هو CalVer (سنة.شهر) والجزء الأخير عداد رقع بأسلوب semver. Kubernetes يستخدم semver خالصاً (1.30.2) لكنه ينشر إصدارات minor بوتيرة ربع سنوية تقريباً — يتصرف كقطار إصدار بتسميات semver.
أتمتة رفع الإصدار: semantic-release
semantic-release هو الأداة المعيارية لأتمتة semver دون تدخل بشري. تقرأ تاريخ commits الـ git — تحديداً commits مُنسَّقة كـ Conventional Commits (feat:، fix:، feat!:) — تحدد الإصدار الصحيح التالي، تُنشئ سجل تغييرات، تنشر الحزمة، تُنشئ إصداراً على GitHub، وتُعلّم الـ commit. لا يُعدّل أي مهندس رقم إصدار بيده.
إعداد الفروع أعلاه يُنفّذ استراتيجية متعددة القنوات كاملة: main يُنتج إصدارات GA مستقرة، فرع next يُنتج ما قبل إصدارات x.y.z-next.N للمستهلكين المتقدمين، beta يُنتج بنيات x.y.z-beta.N، وفروع الصيانة كـ release/2.x تستقبل رقع الاستعادة لخط 2.x دون الإخلال بـ main. هذا هو النموذج المستخدم بالضبط في المشاريع مفتوحة المصدر الكبيرة كـ Babel وJest وsemantic-release ذاتها.
خصائص بيئتَي Python والحاويات
ليس كل مشروع يستخدم أدوات Node.js. نفس انضباط semver-from-commits ينطبق عبر البيئات، لكن الأدوات تختلف:
- Python:
python-semantic-releaseيقرأ Conventional Commits ويرفعpyproject.toml. ينشر على PyPI ويُنشئ إصدارات GitHub. صياغة ما قبل الإصدار في PEP 440 مختلفة قليلاً:2.0.0a1(alpha)،2.0.0b2(beta)،2.0.0rc1— بدون شرطة. أدوات كـpipتفهم كلا صيغتَي PEP 440 وsemver. - وحدات Go: semver مُفرَض من أدوات Go. الوحدة بإصدار v2+ يجب أن يكون لها لاحقة مسار استيراد
/v2(مثلgithub.com/myorg/mylib/v2). وكيل Go يُخزّن إصدارات الوحدة غير القابلة للتغيير — لا يمكن إعادة وسمv2.0.0بعد تخزينه مؤقتاً. - صور الحاويات: لا توجد أداة معيارية لإصدار-من-commits لصور OCI، لكن الاتفاقية هي وسم الصورة بنص semver جنباً إلى جنب مع SHA الـ git. وظيفة CI نموذجية تُنتج
myimage:2.3.1وmyimage:sha-a7f3d91يُشيران إلى نفس الـ digest. علامة semver للمستهلكين البشريين؛ علامة SHA تُستخدم في مانيفستات النشر لضمان عدم القابلية للتغيير.
مخطط الإصدار على نطاق واسع: ما الذي ينكسر فعلاً
النظرية نظيفة؛ الإنتاج أكثر فوضى. هذه حالات الفشل التي تحدث فعلاً على نطاق واسع عند انهيار انضباط الإصدار:
- الإصدارات ذات الأساس الصفري: semver ينص على أن
0.y.zمخصص للتطوير الأولي — الـ API غير مستقر وأي شيء قد يتغير. كثير من الفرق تبقي المكتبات عند0.xلسنوات، مما يمنح المستهلكين انطباعاً زائفاً بأن رفعَي minor آمنان. إذا استُخدمت مكتبتك في الإنتاج، أطلق1.0.0. - إعادة وسم الإصدارات المنشورة: على npm وPyPI ووكيل Go، الإصدارات غير قابلة للتغيير عموماً بمجرد نشرها. إعادة الوسم (حذف وإعادة نشر نفس الإصدار) يكسر البنيات الحتمية لكل مستهلك حلّ ذلك الإصدار. على GitHub Container Registry، إعادة دفع علامة قابلة للتغيير يكسر هذا الضمان لمستهلكي الصور. لا تُعد وسم إصدار منشور أبداً.
- انجراف الإصدار في الـ monorepos: في monorepo به 50 حزمة، يُغري تحديد إصدار كل الحزم بشكل متزامن (جميعها عند
2.3.0بغض النظر عما تغير). هذا ينتهك عقد semver: الحزم التي لم يطرأ عليها تغيير لا ينبغي أن ترفع. أدوات كـ Lerna وNx وChangesets تدعم الإصدار المستقل — كل حزمة تُصدر بشكل منفصل بناءً على تاريخ commits الخاص بها. - غياب قناة ما قبل الإصدار: الفرق التي تتخطى سلّم ما قبل الإصدار وتشحن مباشرةً من alpha إلى GA لا تمنح المستهلكين أي فرصة للتحقق من الترقية في بيئات غير إنتاجية. قناة ما قبل الإصدار لا تقل أهمية عن القناة المستقرة — هي صمام الأمان الذي يمسك التغييرات الكسرية قبل وصولها لقاعدة التثبيت الكاملة.
v* بحيث يستطيع إنشاء علامات الإصدار أو حذفها فقط بوت CI (حساب الخدمة الذي يشغّل semantic-release). هذا يمنع مهندساً من دفع v2.0.0 يدوياً فوق العلامة التي يديرها CI، مما يُفسد تاريخ الإصدار ويتسبب في حصول المستهلكين على الحزمة الخاطئة.
الدرس التالي يتناول مستودعات الحزم — أين تعيش الحزم المُصدَرة بين البناء والنشر، وكيف تُنفّذ السجلات عدم القابلية للتغيير، وكيفية ضبط سياسات الاحتفاظ التي توازن بين تكاليف التخزين والحاجة الامتثالية لإعادة إنتاج أي إصدار من السنوات السبع الماضية.