تفسر كل من npm وCargo وComposer وpip نفس الاختصارات الخاصة بـ semver بشكل مختلف، وفقط تعبيران من أصل ثلاثة عشر تعبيرًا للنطاقات (range expressions) قمت باختبارها تعمل بشكل متطابق عبر الأدوات الأربع جميعها.

لقد كتبت أدوات تحليل (parsers) منفصلة لكل مدير حزم، واتبعت مواصفاتها الرسمية، ثم قمت بتشغيل كل نطاق مقابل مصفوفة مكونة من تسعة عشر رقم إصدار. رسمت الشبكة المكونة من 13 × 19 (247 خلية) صورة مجزأة: أعطت 17 خلية فقط نفس الإجابة بـ "نعم" أو "لا" من كل أداة، ومعظم هذه الـ 17 كانت حالات رفض بالإجماع. باختصار، تتفق الأدوات على "لا" بشكل أكبر بكثير من اتفاقها على "نعم".

لماذا تختلف الأدوات

تدعي جميع المديرين الأربعة اتباع Semantic Versioning، ومع ذلك يضيف كل منها قواعد الاختصار الخاصة به.

  • Caret (^) و dot-x – تدعم npm الـ caret؛ بينما يتجاهل pip كلاً من caret و dot-x. هذا وحده يخلق اختلافًا في 49 خلية بين npm وpip.
  • الإصدارات المجردة (Bare versions) – يعامل Cargo الإصدار 1.2.3 البسيط كنطاق caret، مما يعني "متوافق مع 1.x". بينما تقرأ npm وComposer نفس السلسلة كطابق دقيق، حيث تقبلان 1.2.3 فقط.
  • Tilde (~) – يقوم tilde في Cargo بتثبيت الإصدار الثانوي (minor) (حيث يطابق ~1.2 الإصدار 1.2.* ولكن ليس 1.3.0). أما tilde في Composer فيقوم بتثبيت الإصدار الرئيسي (major) (حيث يطابق ~1.2 الإصدار 1.*). ويختلف التفسيران عند أي إصدار يغير المكون الثانوي.

وتضيف معالجة الإصدارات ما قبل الإصدار (Pre-release) مفاجأة أخرى. فنطاق مثل ^1.2.3 لا يتضمن 2.0.0-rc.1 رغم أن الجزء الرقمي أقل من الحد الأعلى. القاعدة هي: يتم استبعاد الإصدارات ما قبل الإصدار ما لم يذكر النطاق صراحةً معرفًا للإصدار ما قبل الإصدار.

الصيغة الوحيدة الآمنة عبر الأدوات

تظهر التجربة أن نطاقات عدم المساواة الصريحة — مثل >=1.2.0 <2.0.0 — تعمل بنفس الطريقة في npm وCargo وComposer وpip. حيث يعامل كل مدير الحدين كحدود رقمية حرفية، دون أي دلالات خفية لـ caret أو tilde.

إذا كنت بحاجة للتعبير عن "أي إصدار 1.x يكون 1.2 على الأقل"، فقم بكتابته بالكامل. قد يكلفك ذلك بضعة أحرف إضافية، لكنه يضمن دقة التحديد (resolution) المتوقعة أينما تم تشغيل الكود.

متى يظل الاختصار منطقيًا

يظل الاختصار جذابًا لمشاريع اللغة الواحدة. فداخل نظام npm البيئي الصرف، يلخص ^1.2.3 بإيجاز عبارة "متوافق مع الإصدارات الثانوية والترقيعية المستقبلية". وينطبق الأمر نفسه على caret الخاص بـ Cargo أو tilde الخاص بـ Composer عندما تعلم أن الأداة المستهلكة لن تتغير. ويظهر الخطر عندما يُعاد استخدام نفس ملف البيان (manifest) عبر حدود اللغات المختلفة، أو عندما تقوم وظيفة CI بسحب التبعيات من أنظمة بيئية متعددة.

الخلاصة

إن الاعتماد على اختصارات semver من أجل التوافق عبر اللغات هو مقامرة. الطريقة الوحيدة الموثوقة لضمان أن قيد الإصدار يعني الشيء نفسه في كل مكان هي كتابة عدم مساواة صريحة. عندما تحتاج إلى الإيجاز، ابقَ ضمن نظام بيئي واحد؛ وعندما تحتاج إلى الاتساق، استبدل الاختصار بالوضوح.