Skip to content

شرح مفصل على الإصدارت وآلية أتمتتها

License

Notifications You must be signed in to change notification settings

O2sa/semantic-release-demo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

20 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

الإصدارات وآلية أتمتتها

مقدمة

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

الفهرس

  1. ما الإصدارت(Versions) أو ماذا يُقصد بالإصدار في مجال تطوير البرمجيات؟
  2. ما أهمية الإصدارات ولمَ هي شيء أساسي في تطوير البرمجيات؟
  3. المنهجيات والقواعد المتبعة في تحديد الإصدارات
  4. ما الإصدارت الدلالية Semantic Versioning (SemVer) ؟ وما طريقتها في إدارة الإصدارات؟
  5. أتمتة الإصدارات بإستخدام semantic-release
  6. مثال تفصيلي مع الشرح النظري والتطبيق العملي

ما الإصدارت(Versions) أو ماذا يقصد بالإصدار في مجال تطوير البرمجيات؟

كل منتج برمجي سواءً كان برنامج، واجهة تطبيق برمجية(API)، مكتبة(library) أو حزمة(package) يمتلك رقم إصدار يعكس الحالة الحالية للبرمجية. فالإصدار يُخبر المطورين، الأنظمة والمستخدمين بالتالي:

  • ما الذي تغيّر.
  • هل الترقية لهذا الإصدار آمن.
  • هل الإصدار الحالي متوافق مع الإصدار السابق.
  • ما الميزات والإصلاحات المضافة.
  • إذا ما كان هناك تغييرات جذرية(Breaking Changes). لذلك فالإصدار ليس شيئا عشوائيا بل شيء واضح وقابل للتوقع.

ما أهمية الإصدارات ولمَ هي شيء أساسي في تطوير البرمجيات؟

  • الإصدارات تعكس صورة واضحة عن التغييرات والتعديلات لفريق التطوير ذاته وللمستخدمين.
  • التحديثات الغير مخطط لها والعشوائية تُسبب أخطاء وتوقفات غير محسوب حسابها.
  • الأنظمة الكبيرة التي لديها إعتماديات كثيرة تتطلب آلية إصدارات متوقعة وواضحة.
  • الإصدارات تضمن أن جميع البيئات(local, CI, production) تستخدم ذات الكود.
  • الإصدارات تسهل التشاركية بين المطورين وتزيل التشتت بين النسخ العشوائية من ذات المنتج.

المنهجيات والقواعد المتبعة في تحديد الإصدارات

هناك الكثير من المنهجيات الطرق المتبعة لإدارة إصدارات البرمجيات وجميعها تدور حول تنظيم العمل على الإصدارت ووضع قواعد وتعليمات واضحة لأجل ذلك والحول دون وصول البرمجية إلى مرحلة من التعقيد يكون إخراج إصدار جديد بها صعب أو شبه مستحيل، وقد يقوم المطور أو فريق التطوير في وضع قواعدهم الخاصة بإدارة إصدارت البرمجية التي يتم العمل عليها، لكن هناك منهجية شهيرة جدا وذات قواعد واضحة وبسيطة وهي منهجية [الإصدارت الدلالية](# Semantic Versioning) Semantic Versioning وهي التي سيدور الشرح عنها في هذا المقال.

⚠️ عدم إستخدم آلية أو إستراتيجية واضحة في إدارة الإصدارات يؤدي إلى مشاكل ومعوقات يصعب حلها وتزيد بزيادة تعقيد المشروع.

ما الإصدارت الدلالية Semantic Versioning (SemVer) ؟ وما طريقتها في إدارة الإصدارات؟

تقوم هذه الطريقة على تمثيل الإصدار بثلاثة أرقام مفصول بينها بنقطة ولكل رقم معنى معين فشكلها العام كالتالي: MAJOR.MINOR.PATCH مثال: 3.0.2 فلكل قسم أو رقم من الأرقام الثلاثة معنى معين: إصدار كبير(MAJOR): يُزاد عندما تحتوي التعديلات على إضافة تغييرات جذرية قد يكون فيها تغيير على ميزات موجودة مسبقا أو إزالة ميزات سابقة أو تغييرات على واجهة التطبيقات البرمجيةAPI غير متوافقة مع الإصدارات السابقة، مما يعني أن التحديث إلى هذا الإصدار أمر غير آمن. إصدار صغير(MINOR): يُزاد عند تحتوي التعديلات على إضافة ميزات ووظائف جديدة دون الإخلال بطريقة العمل السابقة أو تغيير سلوكها، وغالبا يكون التحديث لهذا الإصدار آمن. إصدار إصلاح(PATCH): يُزاد عندما تحتوي التعديلات على إضافة إصلاحات على الميزات الموجودة دون التغيير على طريقة العمل السابقة مما يعني أن التحديث لهذا الإصدار آمن تماما. بعض من القواعد التي تتبعها الإصدارت الدلالية:

  • عند زيادة رقم الإصدار الكبير(MAJOR) يتم تغيير الإصدار الصغير والإصلإح إلى صفر 0، وإيضا عند تغيير رقم الإصدار الصغير(MINOR) يتم تغيير رقم إصدار الإصلاح(PATCH) إلى صفر 0.
  • قد يتم إضافة لواحق إضافية بعد الإصدار ذات معنى خاص(قبل نشر(pre-release)، بناء(build)...) مثل: 1.1.0.build, 2.3.1.pre-release, 1.0.0-alpha, 1.0.0-alpha.1
  • رقم الإصدار الكبير يجب أن لا يكون صفر وإذا كان صفرا فهذا يعني أن المشروع في مرحلة ما قبل النشر أي أن الكود أو محتويات المشروع أو واجهة التطبيق البرمجيةAPI غير مستقرة وأنها عرضة للتعديل والتغيير في أي وقت.

فمن خلال هذه المعايير يمكن لأي شخص توقع التغييرات الآتية مع الإصدار الجديد من خلال ملاحظة أماكن الأرقام المُتغيرة في الإصدار.

🟢 يمكنك الإنتقال إلى المثال التفصيلي لكي تتضح الصورة أكثر ثم تواصل القراءة لاحقا من هنا.

أتمتة الإصدارات بإستخدام semantic-release

يمكن القيام بأتمتة الإصدارات التي تتبع منهجية الإصدارات الدلالية بإستخدام المكتبة semantic-release

ما الذي يمكن أتمتته من خلال مكتبة semantic-release؟

يمكن القيام بأتمتة عملية إدارة الإصدارات بجميع مراحلها التي تتضمن:

  • تحديد رقم الإصدار القادم.
  • توليد سجل التغييرات المرافق للإصدار الجديد.
  • نشر التطبيق أو المكتبة أو الحزمة إلى مسجل الحزم مثل npm أو حتى إلى gitlab, github. ويمكن من خلال هذه المكتبة أتمتة كل ما يتعلق بالإصدارات ومراحلها مما يسهم في تسهيل عملية إدارة الإصدارات وإزالة هامش الأخطاء البشرية.

كيف تقوم semantic-release بتحديد رقم الإصدار؟

تعتمد هذه المكتبة على الإلتزامات(Commits) المكتوبة حسب قواعد الإلتزامات الإعتيادية Conventional Commits حيث تعمل على تحليل هذه الإلتزمات وتقرر رقم الإصدار بناء على المعلومات المستنتجة، فمثلا كل نوع من الإلتزامات يتسبب في تحديد جزء معين من الإصدار الدلالي:

  • النوع feat يتسبب في إصدار صغير MINOR
  • النوع fix يتسبب في إصدار إصلاحي PATCH
  • الجملة BREAKING CHANGE: في ذيل الإلتزام أو علامة التعجب بعد نوع الإلتزام، يتسبب في إصدار كبير MAJOR مثل:
feat!: تغيير طريقة التحكم بالشريط الجانبي في التطبيق
---- 
feat: إرسال بريد إلكتروني للمستخدم عند شراء المنتج
BREAKING CHANGE: إزالة الطريقة القديمة لإرسال الإيميلات

⚠️ لابد للمطورين من كتابة الإلتزامات حسب قواعد الإلتزامات الإعتيادية لكي تستطيع حزمة semantic-release فهمها وتحليلها، كما أنه لابد للمطور أن يكون على علم بأن كل إلتزام يكتبه قد يتسبب في إنشاء إصدار جديد ويجب أن يكون هذا الإصدار متوافق مع محددات الإصدارات الدلالية(SemVer).

تجهيز إعدادت semantic-release لتقوم بإدارة الإصدارات تلقائيا

  1. نقوم بإنشاء ملف من نوع json داخل المشروع في المستوى الأول يحتوي على إعدادت الـsemantic-release.
//.releaserc.json
{
// هذه الإعدادت تُخبر المكتبة أن تعتمد الفرع 
// main للإصدارات الرئيسية 
// والفروع: 
// 1.x, 2.x 
// فروع لإصدارات الصيانة
// والفروع: 
// beta, alpha, next
// كفروع لإصدارات ما قبل النشر  
  "branches": [
    "main",
    "1.x",
    "2.x",
    {
      "name": "beta",
      "prerelease": true
    },
    {
      "name": "alpha",
      "prerelease": true
    },
    {
      "name": "next",
      "prerelease": true
    }
  ],
  // هذه الإعدادت تحدد الإضافات التي يمكن تشغيلها مع تشغيل الـ`semantic-release`
  // مثل إضافة توليد سجل التغييرات ونشر الإصدار الجديد على الجيتهب أو الجيتلاب وغيره من الإضافات
  // مع إمكانية تخصيص إعدادت كل إضافة
  "plugins": [
    ["@semantic-release/commit-analyzer"],
    [
      "@semantic-release/release-notes-generator",
      {
        "preset": "conventionalcommits",
        "presetConfig": {
          "types": [
            {
              "type": "feat",
              "section": "الميزات الجديدة"
            },
            {
              "type": "fix",
              "section": "الإصلاحات"
            },
            {
              "type": "perf",
              "section": "تحسينات الأداء"
            },
            {
              "type": "revert",
              "section": "التراجعات"
            },
            {
              "type": "docs",
              "section": "التوثيق"
            },
            {
              "type": "style",
              "section": "Styles"
            },
            {
              "type": "refactor",
              "section": "أكواد أُعيد كتابتها"
            },
            {
              "type": "test",
              "section": "الإختبارات"
            },
            {
              "type": "build",
              "section": "تعديلات البناء"
            },
            {
              "type": "ci",
              "section": "الدمج المستمر"
            }
          ]
        }
      }
    ],
    [
      "@semantic-release/changelog",
      {
        "changelogFile": "CHANGELOG.md",
        "changelogTitle": "# سجل التغييرات\n\nجميع التغييرات على هذا المشروع يتم توثيق ملخصها في هذا الملف.\n\nشكل الملف يستند للمرجع [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),\nكما يتبع هذا المشروع آلية الإصدارت الدلالية [Semantic Versioning](https://semver.org/spec/v2.0.0.html)."
      }
    ],
    [
      "@semantic-release/npm",
      {
        "npmPublish": false
      }
    ],
    [
      "@semantic-release/exec",
      {
        "prepareCmd": "npm run build"
      }
    ],
    [
      "@semantic-release/git",
      {
        "assets": [
          "CHANGELOG.md",
          "dist/**/*"
        ],
        "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
      }
    ],
    [
      "@semantic-release/github",
      {
        "releasedLabels": ["released", "released-on-${nextRelease.channel}"],
        "addReleases": "bottom"
      }
    ]
  ],
  "tagFormat": "${version}",
  "preset": "conventionalcommits",
  "ci": true,
  "dryRun": false,
  "debug": false
}

📌 للإستزادة والتوسع في إعدادت semantic-release راجع هذه الصفحة 2. تشغيل semantic-release كجزء من عملية الدمج المستمر(Continuous Integration)

  • حيث نقوم في الدمج المستمر(gitlab ci/cd, github workflow...) بتثبيت إعتماديات الـsemantic-release ثم نقوم بتشغيل الأمر الخاص به، مثلا:
  • من خلال gitlab ci/cd:
stages:
  - release
# Only run semantic-release on these branches
.release_branches:
  only:
    - main
    - beta
    - alpha
    - next
    - /^([0-9]+)\.x$/        # matches 1.x, 2.x, 3.x
    - /^([0-9]+)\.x\.x$/    # matches 1.x.x
release:
  stage: release
  image: node:24-alpine
  extends: .release_branches
  before_script:
    - echo "📥 Installing dependencies..."
    - apk add --no-cache git jq curl
    - npm config set fund false
    - npm config set audit false
    # Initialize project if package.json doesn't exist
    - |
      # Create minimal package.json
      if [ ! -f "package.json" ]; then
        npm init -y
        # Update with minimal config
        jq '. + {
          "version": "0.0.0-development",
          "scripts": {
            "test": "echo \"Running tests...\" && exit 0",
            "build": "echo \"Building...\" && exit 0"
          }
        }' package.json > package.json.tmp && mv package.json.tmp package.json
      fi
    # Create package-lock.json if missing
    - npm install --package-lock-only --no-audit
  script:
    - echo "📦 Installing semantic-release..."
    - |
      npm install \
        semantic-release@^24.2.3 \
        @semantic-release/changelog@^6.0.3 \
        @semantic-release/commit-analyzer@^11.1.0 \
        @semantic-release/exec@^6.0.3 \
        @semantic-release/git@^10.0.1 \
        @semantic-release/gitlab@^13.2.4 \
        @semantic-release/npm@^11.0.2 \
        @semantic-release/release-notes-generator@^12.1.0 \
        conventional-changelog-conventionalcommits@^7.0.2 \
        --no-audit --progress=false
    - echo "🚀 Running semantic-release..."
    - npx semantic-release --debug
  • من خلال github workflow:
name: Release
on:
  push:      
    branches:
      - main 
      - beta 
      - alpha
      - next 
      - 1.x  
      - 2.x  
      - 3.x  
      - 4.x  
      - 1.x.x
  pull_request:
    branches:
      - main
jobs:
  release:
    permissions:
      contents: write
      issues: write
      pull-requests: write
    name: Semantic Release
    runs-on: ubuntu-latest
    if: github.event_name == 'push'
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
          token: ${{ secrets.GITHUB_TOKEN }}
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '24'
      - name: Initialize project
        run: |
          # Create minimal package.json
          if [ ! -f "package.json" ]; then
            npm init -y
            # Update with minimal config
            jq '. + {
              "version": "0.0.0-development",
              "scripts": {
                "test": "echo \"Running tests...\" && exit 0",
                "build": "echo \"Building...\" && exit 0"
              }
            }' package.json > package.json.tmp && mv package.json.tmp package.json
          fi
          # Generate lock file
          npm install --package-lock-only --no-audit
      - name: Install semantic-release
        run: |
          echo "Installing semantic-release packages..."
          npm install \
             semantic-release@^22.0.5 \
            @semantic-release/changelog@^6.0.3 \
            @semantic-release/commit-analyzer@^11.1.0 \
            @semantic-release/exec@^6.0.3 \
            @semantic-release/git@^10.0.1 \
            @semantic-release/github@^9.2.5 \
            @semantic-release/npm@^11.0.2 \
            @semantic-release/release-notes-generator@^12.1.0 \
            conventional-changelog-conventionalcommits@^7.0.2 \
            --no-audit --progress=false
      - name: Release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          echo "Running semantic-release..."
          npx semantic-release --debug

مثال تفصيلي مع الشرح النظري والتطبيق العملي

  • هذا مثال مبسط يشرح تطبيق الإصدارات على مثال عملي، في هذا القسم تجد الشرح النظري وفي ذات المخزن(Repository) تجد التطبيق العملي مع جميع الإلتزامات(Commits) والإصدارت(Releases) وجميع الملفات المتعلقة.
  • لنقل أننا نريد عمل مكتبة بسيطة بإستخدام الجافا سكربت للقيام بالعمليات الحسابية الأساسية(الضرب، الطرح...) ولتكن هذه المكتبة مكونة من ملف واحد فقط math-lite.js.
  • عند كل إصدار سنقوم بإنشاء Release و Tag ونربطهما مع بعض، أيضا سنقوم بإنشاء ملف سجل التغييرات Changelog بحيث نكتب بداخله التغييرات التي تمت في كل إصدار يتم نشره، لكن بعد الإصدار 1.0.0 سنقوم بأتممتة للعملية بأكملها بواسطة semantic-release
  • لكي تتضح لك الصورة جيدا قم إنشاء مخزن(Repository) خاص بك وتطبيق جميع الخطوات الواردة عليه

📘 0. قبل الإصدار — مرحلة التطوير

الكود

//math-lite.js
export function add(a, b) {
  return a + b;
}

الإصدار: 0.1.0

نجعل الإصدار 0.1.0 لأن المكتبة:

  • غير مستقرة بعد
  • قد يتغير الـ API الخاص بها في أي وقت

الإلتزامات:


🚀 1. الإصدار المستقر الأول MAJOR — 1.0.0

الآن يمكن للمستخدمين الوثوق في الـ API واعتبار المكتبة أصبحت مستقرة وأن أي تغييرات جذرية لا يمكن أن تتم إلا في إصدار كبير قادم الذي هو 2.0.0.

الكود (بدون تغييرات):

//math-lite.js
export function add(a, b) {
  return a + b;
}

الإصدار: 1.0.0

الإلتزامات:


🐞 2. أول إصدار إصلاحي PATCH — إصلاح خطأ

افترض أن المستخدم استخدم الدالة كالتالي:
add(1, undefined) ستقوم الدالة بإعادة NaN بينا نريدها أن تُرجع خطأ(Exception)

الكود بعد التعديل:

//math-lite.js
export function add(a, b) {
  if (typeof a !== 'number' || typeof b !== 'number') {
    throw new Error("add: يجب أن يكون كلا المعاملين رقمين");
  }
  return a + b;
}

🔹 ضمن التعديلات الحالية تم إضافة إعدادت أتمتة الإصدارات بواسطةsemantic-release من خلال الملفين:

الإصدار: 1.0.1

لماذا PATCH؟

  • لا تغييرات في الـ API
  • لا ميزات جديدة
  • فقط إصلاح آمن ومتوافق مع الإصدارات السابقة

الإلتزامات:


✨ 3. تحديث صغير MINOR — إضافة دالة جديدة

أضفنا دالة الطرح:

//math-lite.js
export function add(a, b) {
  if (typeof a !== 'number' || typeof b !== 'number') {
    throw new Error("add: يجب أن يكون كلا المعاملين رقمين");
  }
  return a + b;
}
export function subtract(a, b) {
  return a - b;
}

هذا التعديل لا يُغير شيء في الميزات السابقة لكنه يضيف ميزة جديدة.

الإصدار: 1.1.0

الإلتزامات:


🌟 4. تحديث صغير MINOR — إضافة معامل اختياري

إضافة مدخل اختياري للدالة add():

//math-lite.js
export function add(a, b, options = { absolute: false }) {
  if (typeof a !== 'number' || typeof b !== 'number') {
    throw new Error("add: يجب أن يكون كلا المعاملين رقمين");
  }
  const result = a + b;
  return options.absolute ? Math.abs(result) : result;
}
export function subtract(a, b) {
  return a - b;
}

الاستخدام القديم لا يزال يعمل → متوافق مع الإصدارات السابقة.

الإصدار: 1.2.0

الإلتزامات:


❗ 5. تحديث مع تغييرات جذرية — إصدار كبير MAJOR

قررنا إعادة تسمية الدالة subtract إلى minus مع تصحيح بعض الأخطاء.
سيؤدي هذا إلى أخطاء لدى المستخدمين الذين يستخدمون الدالة subtract لذلك ستؤدي هذه التعديلات إلى إنشاء إصدار كبير MAJOR.

الكود الجديد:

//math-lite.js
export function add(a, b, options = { absolute: false }) {
  if (typeof a !== 'number' || typeof b !== 'number') {
    throw new Error("add: يجب أن يكون كلا المعاملين رقمين");
  }
  const result = a + b;
  return options.absolute ? Math.abs(result) : result;
}
export function minus(a, b) {
  if (typeof a !== 'number' || typeof b !== 'number') {
    throw new Error("minus: يجب أن يكون كلا المعاملين رقمين");
  }
  return a - b;
}

الإصدار: 2.0.0

الإلتزامات:


🛠 6. المحافظة على الإصدارات الرئيسية السابقة

بعض المستخدمين لا يزالون يعتمدون على الإصدار 1.x.x، ولنقل أن جميع التعديلات التي تتم على آخر إصدار يتم إضافتها في الفرع(branch) المسمي main فسنقوم بالمحافظة على فرع منفصل يحتوي على الميزات الموجودة في الإصدار 1 فقط ولتكن تسمية الفرع 1.x

  • 1.x → المحافظة عليه لتصحيحات الأخطاء الحرجة
  • main → إضافة الميزات الجديدة

إصلاح في الفرع القديم 1.x:

لنفترض وجود خطأ في الدالة subtract بالإصدار `1.x.

الإصلاح في الفرع 1.x:

//math-lite.js
export function add(a, b, options = { absolute: false }) {
  if (typeof a !== 'number' || typeof b !== 'number') {
    throw new Error("add: يجب أن يكون كلا المعاملين رقمين");
  }
  const result = a + b;
  return options.absolute ? Math.abs(result) : result;
}
export function subtract(a, b) {
  if (typeof a !== 'number' || typeof b !== 'number') {
    throw new Error("subtract: يجب أن يكون كلا المعاملين رقمين");
  }
  return a - b;
}

الإصدار: 1.2.1

نستمر في دعم الإصدارات 1.x بالتصحيحات فقط، دون إضافة ميزات جديدة.

الإلتزامات:


📦 ملخص جدول الإصدارات

الإصدار النوع السبب
0.1.0 تطوير إصدار غير مستقر مبكر
1.0.0 كبير MAJOR أول إصدار مستقر
1.0.1 إصلاحي PATCH إصلاح خطأ
1.1.0 صغير MINOR ميزة جديدة متوافقة مع الإصدارات السابقة (subtract)
1.2.0 صغير MINOR إضافة معامل اختياري إلى add()
2.0.0 كبير MAJOR تغيير جذري: إعادة تسمية subtract إلى minus
1.2.1 إصلاحي PATCH إصلاح في الإصدار القديم (1.x)

سجل التغييرات

جميع التغييرات التي تمت تجدها هنا.

About

شرح مفصل على الإصدارت وآلية أتمتتها

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •