تعمل ثلاث فرق أمامية حاليًا على تطوير ثلاثة مشاريع رئيسية في
ISPsystem : ISPmanager لإدارة خوادم الويب ، و VMmanager للعمل مع المحاكاة الافتراضية ، و BILLmanager لأتمتة أعمال المضيفين. تعمل الفرق في نفس الوقت ، في وضع المواعيد النهائية الضيقة ، لذلك لا يمكنك الاستغناء عن التحسين. لتوفير الوقت ، نستخدم الحلول المشتركة ونأخذ المكونات المشتركة في مشاريع منفصلة. هذه المشاريع لها مستودعاتها الخاصة ، والتي يدعمها أعضاء جميع الفرق. ستتمحور هذه المقالة حول بناء هذه المستودعات ، وكذلك العمل معها.

كيف هي مستودعات المشاريع المشتركة
نستخدم خادمنا الخاص مع
GitLab لتخزين المستودعات البعيدة. كان من المهم بالنسبة لنا الحفاظ على بيئة العمل المألوفة والقدرة على العمل مع الوحدات المشتركة في عملية تطويرها. لذلك ، رفضنا النشر في مستودعات
npmjs.com الخاصة. لحسن الحظ ، يمكن تثبيت وحدات Node.js ليس فقط مع NPM ، ولكن أيضًا من
مصادر أخرى ، بما في ذلك مستودعات git.
نكتب في TypeScript ، والذي يتم تجميعه فيما بعد في JavaScript لاستخدامه لاحقًا. ولكن في عصرنا ، ربما لا تقوم الواجهة الأمامية الكسولة بتجميع JavaScript. لذلك ، نحن بحاجة إلى مستودعات مختلفة لشفرة المصدر والمشروع المترجمة.
بعد المرور بأشواك المناقشات الطويلة ، طورنا المفهوم التالي. يجب أن يكون هناك مستودعين منفصلين للمصادر وللنسخة المترجمة من الوحدة. علاوة على ذلك ، يجب أن يكون المستودع الثاني مرآة للأول.
هذا يعني أنه أثناء التطوير يجب نشر أي ميزة قبل الإصدار في الفرع بنفس اسم الفرع الذي يجري فيه التطوير. وبالتالي ، لدينا الفرصة لاستخدام النسخة التجريبية من الوحدة ، وتثبيتها من فرع معين. إن الشيء الذي نقوم بتطويره مناسب للغاية للتحقق منه في العمل.
بالإضافة إلى ذلك ، لكل منشور ، نقوم بإنشاء تسمية تحفظ حالة المشروع. يتوافق اسم الملصق مع الإصدار المحدد في package.json. عند التثبيت من مستودع git ، تتم الإشارة إلى الملصق بعد الشبكة ، على سبيل المثال:
npm install git+ssh://[url ]
وبالتالي ، يمكننا إصلاح الإصدار المستخدم من الوحدة وعدم القلق من أن يقوم شخص ما بتغيير شيء ما.
يتم أيضًا إنشاء التسميات للإصدارات غير المستقرة ، ومع ذلك ، تتم إضافة تجزئة مختصرة للارتباط إليها في مستودع المصدر ، الذي تم منه النشر. فيما يلي مثال لمثل هذه التسمية:
1.0.0_e5541dc1
يتيح لك هذا النهج تحقيق تفرد الملصقات ، بالإضافة إلى ربطها بمستودع المصدر.
نظرًا لأننا نتحدث عن إصدارات مستقرة وغير مستقرة من الوحدة ، فإليك كيفية تمييزها: إذا تم تنفيذ المنشور من الفرع الرئيسي أو الفرع المطور ، فإن الإصدار مستقر ، وإلا فلا.
كيف يتم تنظيم المشاريع المشتركة؟
جميع اتفاقاتنا لن تكون منطقية إذا لم نتمكن من أتمتتها. على وجه الخصوص ، أتمتة عملية النشر. أدناه سوف أعرض كيف يتم تنظيم العمل مع واحدة من الوحدات الشائعة - أداة لاختبار البرامج النصية المخصصة.
تعمل هذه الأداة ، باستخدام مكتبة
العرائس ، على إعداد متصفح Chromium للاستخدام في حاويات عامل الميناء وتشغيل الاختبارات باستخدام
Mocha . يمكن للمشاركين من جميع الفرق تعديل الأداة دون الخوف من كسر شيء عن بعضهم البعض.
يتم كتابة الأمر التالي في ملف package.json الخاص بأداة الاختبار المساعدة:
"publish:git": "ts-node ./scripts/publish.ts"
تدير نصًا قريبًا:
كود البرنامج النصي للنشر الكامل import { spawnSync } from 'child_process'; import { mkdirSync, existsSync } from 'fs'; import { join } from 'path'; import chalk from 'chalk'; /** * */ /** * * @param cwd - * @param stdio - / */ const getSpawnOptions = (cwd = process.cwd(), stdio = 'inherit') => ({ cwd, shell: true, stdio, }); /* */ const rootDir = join(__dirname, '../'); /* */ const isDiff = !!spawnSync('git', ['diff'], getSpawnOptions(rootDir, 'pipe')).stdout.toString().trim(); if (isDiff) { console.log(chalk.red('There are uncommitted changes')); } else { /* */ const build = spawnSync('npm', ['run', 'build'], getSpawnOptions(rootDir)); /* */ if (build.status === 0) { /* */ const tempDir = join(rootDir, 'temp'); if (existsSync(tempDir)) { spawnSync('rm', ['-rf', 'temp'], getSpawnOptions(rootDir)); } mkdirSync(tempDir); /* package.json */ const { name, version, repository } = require(join(rootDir, 'package.json')); const originUrl = repository.url.replace(`${name}-source`, name); spawnSync('git', ['init'], getSpawnOptions(tempDir)); spawnSync('git', ['remote', 'add', 'origin', originUrl], getSpawnOptions(tempDir)); /* */ const branch = spawnSync( 'git', ['symbolic-ref', '--short', 'HEAD'], getSpawnOptions(rootDir, 'pipe') ).stdout.toString().trim(); /* */ const buildBranch = branch === 'develop' ? 'master' : branch; /* , */ const shortSHA = spawnSync( 'git', ['rev-parse', '--short', 'HEAD'], getSpawnOptions(rootDir, 'pipe') ).stdout.toString().trim(); /* */ const tag = buildBranch === 'master' ? version : `${version}_${shortSHA}`; /* */ const isTagExists = !!spawnSync( 'git', ['ls-remote', 'origin', `refs/tags/${tag}`], getSpawnOptions(tempDir, 'pipe') ).stdout.toString().trim(); if (isTagExists) { console.log(chalk.red(`Tag ${tag} already exists`)); } else { /* */ const isBranchExits = !!spawnSync( 'git', ['ls-remote', '--exit-code', 'origin', buildBranch], getSpawnOptions(tempDir, 'pipe') ).stdout.toString().trim(); if (isBranchExits) { /* */ spawnSync('git', ['fetch', 'origin', buildBranch], getSpawnOptions(tempDir)); spawnSync('git', ['checkout', buildBranch], getSpawnOptions(tempDir)); } else { /* master */ spawnSync('git', ['fetch', 'origin', 'master'], getSpawnOptions(tempDir)); spawnSync('git', ['checkout', 'master'], getSpawnOptions(tempDir)); /* */ spawnSync('git', ['checkout', '-b', buildBranch], getSpawnOptions(tempDir)); /* */ spawnSync('git', ['commit', '--allow-empty', '-m', '"Initial commit"'], getSpawnOptions(tempDir)); } /* */ spawnSync( 'rm', ['-rf', 'lib', 'package.json', 'package-lock.json', 'README.md'], getSpawnOptions(tempDir) ); /* */ spawnSync('cp', ['-r', 'lib', 'temp/lib'], getSpawnOptions(rootDir)); spawnSync('cp', ['package.json', 'temp/package.json'], getSpawnOptions(rootDir)); spawnSync('cp', ['package-lock.json', 'temp/package-lock.json'], getSpawnOptions(rootDir)); spawnSync('cp', ['README.md', 'temp/README.md'], getSpawnOptions(rootDir)); /* */ spawnSync('git', ['add', '--all'], getSpawnOptions(tempDir)); /* */ const lastCommitMessage = spawnSync( 'git', ['log', '--oneline', '-1'], getSpawnOptions(rootDir, 'pipe') ).stdout.toString().trim(); /* */ const message = buildBranch === 'master' ? version : lastCommitMessage; /* */ spawnSync('git', ['commit', '-m', `"${message}"`], getSpawnOptions(tempDir)); /* */ spawnSync('git', ['tag', tag], getSpawnOptions(tempDir)); /* */ spawnSync('git', ['push', 'origin', buildBranch], getSpawnOptions(tempDir)); spawnSync('git', ['push', '--tags'], getSpawnOptions(tempDir)); console.log(chalk.green('Published successfully!')); } /* */ spawnSync('rm', ['-rf', 'temp'], getSpawnOptions(rootDir)); } else { console.log(chalk.red(`Build was exited exited with code ${build.status}`)); } } console.log(''); // space
في المقابل ، ينفّذ هذا الرمز من خلال وحدة Node.js
child_process جميع الأوامر اللازمة.
فيما يلي المراحل الرئيسية لعمله:
1. تحقق من وجود تغييرات غير مصرح بها const isDiff = !!spawnSync('git', ['diff'], getSpawnOptions(rootDir, 'pipe')).stdout.toString().trim();
هنا نتحقق من نتيجة
الأمر git diff . ليس جيدًا إذا كان المنشور يحتوي على تغييرات غير موجودة في المصدر. بالإضافة إلى ذلك ، سيؤدي هذا إلى قطع اتصال الإصدارات غير المستقرة مع عمليات التثبيت.
2. تجميع المنفعة const build = spawnSync('npm', ['run', 'build'], getSpawnOptions(rootDir))
ثابت البناء يحصل على نتيجة البناء. إذا سار كل شيء على ما يرام ، فستكون معلمة الحالة 0. وإلا فلن يتم نشر أي شيء.
3. نشر مستودع النسخة المترجمةعملية النشر بأكملها ليست أكثر من تقديم التغييرات إلى مستودع معين. لذلك ، يقوم البرنامج النصي بإنشاء دليل مؤقت في مشروعنا يقوم فيه بتهيئة مستودع git وربطه بمخزن التجميع عن بعد.
const tempDir = join(rootDir, 'temp'); if (existsSync(tempDir)) { spawnSync('rm', ['-rf', 'temp'], getSpawnOptions(rootDir)); } mkdirSync(tempDir); const { name, version, repository } = require(join(rootDir, 'package.json')); const originUrl = repository.url.replace(`${name}-source`, name); spawnSync('git', ['init'], getSpawnOptions(tempDir)); spawnSync('git', ['remote', 'add', 'origin', originUrl], getSpawnOptions(tempDir));
هذه عملية قياسية باستخدام
git init و
git remote .
4. إنشاء اسم الملصقأولاً ، اكتشفنا اسم الفرع الذي ننشر منه باستخدام الأمر
git symbolic-ref . وقم بتعيين اسم الفرع الذي سيتم تحميل التغييرات فيه (لا يوجد فرع تطوير في مستودع التجميع).
const branch = spawnSync( 'git', ['symbolic-ref', '--short', 'HEAD'], getSpawnOptions(rootDir, 'pipe') ).stdout.toString().trim(); const buildBranch = branch === 'develop' ? 'master' : branch;
باستخدام الأمر
git rev-parse ، نحصل على تجزئة مختصرة لآخر التزام في الفرع الذي نحن فيه. قد تكون هناك حاجة لإنشاء اسم تسمية الإصدار غير المستقر.
<source lang="typescript"> const shortSHA = spawnSync( 'git', ['rev-parse', '--short', 'HEAD'], getSpawnOptions(rootDir, 'pipe') ).stdout.toString().trim();
حسنًا ، اصنع اسم الملصق بالفعل.
const tag = buildBranch === 'master' ? version : `${version}_${shortSHA}`;
5. التحقق من عدم وجود نفس البطاقة بالضبط في المستودع البعيد /* */ const isTagExists = !!spawnSync( 'git', ['ls-remote', 'origin', `refs/tags/${tag}`], getSpawnOptions(tempDir, 'pipe') ).stdout.toString().trim();
إذا تم إنشاء ملصق مشابه سابقًا ، فلن تكون نتيجة الأمر
git ls-remote فارغة. يجب نشر نفس الإصدار مرة واحدة فقط.
6. إنشاء الفرع المناسب في مستودع التجميعكما قلت سابقًا ، فإن مستودع النسخ المجمعة من الأداة المساعدة هو مرآة للمستودع مع رموز المصدر. لذلك ، إذا لم يكن المنشور من الفرع الرئيسي أو الفرع المطور ، فيجب إنشاء الفرع المقابل في مستودع التجميع. حسنًا ، أو على الأقل تأكد من وجودها
/* */ const isBranchExits = !!spawnSync( 'git', ['ls-remote', '--exit-code', 'origin', buildBranch], getSpawnOptions(tempDir, 'pipe') ).stdout.toString().trim(); if (isBranchExits) { /* */ spawnSync('git', ['fetch', 'origin', buildBranch], getSpawnOptions(tempDir)); spawnSync('git', ['checkout', buildBranch], getSpawnOptions(tempDir)); } else { /* master */ spawnSync('git', ['fetch', 'origin', 'master'], getSpawnOptions(tempDir)); spawnSync('git', ['checkout', 'master'], getSpawnOptions(tempDir)); /* */ spawnSync('git', ['checkout', '-b', buildBranch], getSpawnOptions(tempDir)); /* */ spawnSync('git', ['commit', '--allow-empty', '-m', '"Initial commit"'], getSpawnOptions(tempDir)); }
إذا كان الفرع غائبًا من قبل ، فنحن نبدأ بالتعهد الفارغ باستخدام علامة
-allow-blank .
7. إعداد الملفتحتاج أولاً إلى حذف كل ما يمكن أن يكون في المستودع المنشور. بعد كل شيء ، إذا استخدمنا فرعًا موجودًا مسبقًا ، فإنه يحتوي على الإصدار السابق من الأداة.
spawnSync( 'rm', ['-rf', 'lib', 'package.json', 'package-lock.json', 'README.md'], getSpawnOptions(tempDir) );
بعد ذلك ، ننقل الملفات المحدثة اللازمة للنشر ، ونضيفها إلى فهرس المستودع.
/* */ spawnSync('cp', ['-r', 'lib', 'temp/lib'], getSpawnOptions(rootDir)); spawnSync('cp', ['package.json', 'temp/package.json'], getSpawnOptions(rootDir)); spawnSync('cp', ['package-lock.json', 'temp/package-lock.json'], getSpawnOptions(rootDir)); spawnSync('cp', ['README.md', 'temp/README.md'], getSpawnOptions(rootDir)); /* */ spawnSync('git', ['add', '--all'], getSpawnOptions(tempDir));
بعد هذا التلاعب ، سوف يتعرف git على التغييرات التي أجرتها خطوط الملفات. بهذه الطريقة نحصل على سجل تغيير ثابت حتى في مستودع النسخة المترجمة.
8. الالتزام وتقديم التغييراتكرسالة التزام في مستودع التجميع ، نستخدم اسم التصنيف للإصدارات الثابتة. وللاستقرار - رسالة التزام من مستودع المصدر. بهذه الطريقة ، دعم فكرتنا عن مستودع مرآة.
const lastCommitMessage = spawnSync( 'git', ['log', '--oneline', '-1'], getSpawnOptions(rootDir, 'pipe') ).stdout.toString().trim(); const message = buildBranch === 'master' ? version : lastCommitMessage; spawnSync('git', ['commit', '-m', `"${message}"`], getSpawnOptions(tempDir)); spawnSync('git', ['tag', tag], getSpawnOptions(tempDir)); spawnSync('git', ['push', 'origin', buildBranch], getSpawnOptions(tempDir)); spawnSync('git', ['push', '--tags'], getSpawnOptions(tempDir));
9. حذف دليل مؤقت spawnSync('rm', ['-rf', 'temp'], getSpawnOptions(rootDir))
مراجعة التحديثات في المشاريع المشتركة
تصبح واحدة من أهم العمليات بعد إجراء تغييرات على المشاريع المشتركة مراجعة. على الرغم من حقيقة أن التكنولوجيا المطورة تسمح لك بإنشاء إصدارات معزولة تمامًا من الوحدات ، لا أحد يريد أن يكون لديك عشرات الإصدارات المختلفة من نفس الأداة. لذلك ، يجب أن يتبع كل مشروع مشترك مسار تنمية واحد. هذا يجب أن يتفق عليه بين الفرق.
تتم مراجعة التحديثات في المشاريع المشتركة من قبل أعضاء جميع الفرق بقدر الإمكان. هذه عملية معقدة ، حيث يعيش كل فريق على ركضه الخاص ولديه عبء عمل مختلف. في بعض الأحيان قد يتأخر الانتقال إلى إصدار جديد.
هنا يمكنك فقط التوصية بعدم الإهمال وعدم التأخير في هذه العملية.