513 قراءة٪ s
513 قراءة٪ s

إنشاء مستودع JS/TS قوي: أفضل الممارسات مع Yarn وNX وChangessets

بواسطة Teimur Gasanov22m2025/03/24
Read on Terminal Reader

طويل جدا؛ ليقرأ

تشرح هذه المقالة كيفية بناء مستودع أحادي قوي لـ JS/TS باستخدام Yarn v4 لمساحات العمل، وNX لتنفيذ النصوص البرمجية بكفاءة وإدارة التبعيات، وChegets لإدارة الإصدارات والإصدارات تلقائيًا. كما تتناول هيكلة الكود إلى تطبيقات وميزات ومكتبات، بالإضافة إلى دمج GitHub Actions for CI/CD لضمان جودة الكود وتبسيط عمليات النشر.
featured image - إنشاء مستودع JS/TS قوي: أفضل الممارسات مع Yarn وNX وChangessets
Teimur Gasanov HackerNoon profile picture
0-item
1-item

مقدمة

لماذا مونوريبو؟

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

إعداد مساحات عمل الغزل

ملاحظة: في جميع أنحاء هذه المقالة، عندما يتم ذكر "Yarn"، فإنه يشير على وجه التحديد إلى Yarn v4 - أحدث إصدار يوفر إمكانيات محسنة وأداءً محسنًا.

ما هي مساحات عمل الغزل؟

مساحات العمل هي حزم المستودع الأحادي، وتُسمى عادةً بالحزم. تساعدك هذه المساحات على إدارة حزم متعددة في مستودع واحد بسهولة. مع مساحات العمل، يمكنك:

  • مشاركة التبعيات بسهولة:

    قم بمشاركة التبعيات المشتركة عبر مشروعك بسلاسة.

  • تبسيط إدارة التبعيات:

    يقوم Yarn تلقائيًا بربط الحزم المحلية، مما يقلل من التكرار ويسهل التطوير.

  • تسريع التثبيتات:

    استفد من تحسينات الأداء وآليات التخزين المؤقت في Yarn (على سبيل المثال، plug'n'play المضمنة ).

  • تحسين التحكم في Monorepo:

    قم بتحديد القيود (القواعد) واستخدم العشرات من المكونات الإضافية المتاحة للحفاظ على الاتساق.


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

التكوين الأولي

إعداد Yarn عملية سهلة وبسيطة. اتبع الدليل الرسمي لتثبيت Yarn وتكوينه في مشروعك: دليل تثبيت Yarn .

بعد إتمام التثبيت، لننتقل إلى التهيئة. بما أننا نستخدم التوصيل والتشغيل، عليك التأكد من أن بيئة التطوير المتكاملة لديك تتعرف على التبعيات بشكل صحيح. إذا كنت تستخدم VSCode، فقم بتشغيل:

 # Typescript is required for VSCode SDK to set up correctly yarn add -D typescript@^5 yarn dlx @yarnpkg/sdks vscode

إذا كنت تستخدم محرر أكواد آخر، فتحقق من مجموعات أدوات التطوير البرمجية المتاحة هنا: مجموعات أدوات تطوير البرمجيات الخاصة بمحرر Yarn .

في هذه المرحلة، أنت جاهز تمامًا لبدء استخدام Yarn.

تنظيم هيكل المستودع الأحادي

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

  • التطبيقات :

    • العميل: يحتوي على منتجات العميل النهائية القابلة للنشر.
    • الخادم: يحتوي على منتجات الخادم النهائية القابلة للنشر.
  • سمات :

    • العميل: لعناصر واجهة المستخدم المستقلة.
    • الخادم: لأجزاء منطق الأعمال الخلفية المستقلة.
  • المكتبات :

    يضم أكوادًا مشتركة، مثل مكونات نظام التصميم، والثوابت، والأصول، والأدوات المساعدة. هذه هي المنطقة الخالية من السياق لتخزين المنطق القابل لإعادة الاستخدام.


لتوضيح فعالية بنية المجلدات هذه، لنبدأ بإضافة هذه المجلدات الرئيسية إلى قائمة مساحات عمل Yarn. في ملف package.json الجذر، أضف ما يلي:

 "workspaces": [ "apps/**", "features/**", "libs/**" ]

يُلزم هذا التكوين Yarn بمعاملة الحزم في هذه المجلدات كحزم محلية. ستضمن عمليات التثبيت اللاحقة إعداد تبعيات كل حزمة وربطها بشكل صحيح.

تمهيد قاعدة الكود

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

تطبيق خادم التمهيد

نبدأ بواجهة برمجة تطبيقات Express بسيطة لمصادقة المستخدم. يعرض تطبيق الخادم هذا نقطة نهاية واحدة ( /auth/signIn ) تستخدم معالجًا من حزمة أخرى.

 import express from "express"; import cors from "cors"; import { signInHandler } from "@robust-monorepo-yarn-nx-changesets/sign-in-handler"; const app = express(); const port = process.env.PORT || 1234; app.use(express.json()); app.use( cors({ origin: process.env.CORS_ORIGIN || "http://localhost:3000", }) ); app.post("/auth/signIn", signInHandler); app.listen(port, () => { console.log(`Server is running at http://localhost:${port}`); });

رابط الحزمة


كما ترى، تستخدم نقطة النهاية /auth/signIn مُعالِجًا مُستوردًا من حزمة أخرى. هذا يقودنا إلى المُكوّن التالي: ميزة الخادم.

ميزة خادم التمهيد

تُغلِّف ميزة الخادم منطق المصادقة. في هذه الحزمة، نُعرِّف مُعالِج تسجيل الدخول، الذي يستفيد من أداة تحقق مُشتركة من المكتبات.

 import type { RequestHandler } from "express"; import { passwordValidator, usernameValidator, } from "@robust-monorepo-yarn-nx-changesets/validator"; const signInHandler: RequestHandler = (req, res) => { if (!req.body) { res.status(422).send("Request body is missing"); return; } if (typeof req.body !== "object") { res.status(422).send("Request body expected to be an object"); return; } const { username, password } = req.body; const usernameValidationResult = usernameValidator(username); if (typeof usernameValidationResult === "string") { res .status(422) .send("Invalid username format: " + usernameValidationResult); return; } const passwordValidationResult = passwordValidator(password); if (typeof passwordValidationResult === "string") { res .status(422) .send("Invalid password format: " + passwordValidationResult); return; } // Emulate a successful sign-in if (username === "test" && password === "test1234") { res.status(200).send("Sign in successful"); return; } return res.status(422).send("Username or password is incorrect"); }; export default signInHandler;

رابط الحزمة


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

تمهيد تطبيق العميل

الآن، لنلقِ نظرة على جانب العميل. في تطبيق العميل، نبني موقعًا إلكترونيًا بسيطًا يُمكّن مصادقة المستخدم عبر استدعاء واجهة برمجة تطبيقات الخادم.

 "use client"; import { SignInForm } from "@robust-monorepo-yarn-nx-changesets/sign-in-form"; const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:1234"; export default function Home() { const handleSubmit = async (username: string, password: string) => { const response = await fetch(`${API_URL}/auth/signIn`, { method: "POST", body: JSON.stringify({ username, password }), headers: { "Content-Type": "application/json", }, }); if (response.status === 200) { alert("Sign in successful"); return; } if (response.status === 422) { alert("Sign in failed: " + (await response.text())); return; } alert("Sign in failed"); }; return ( <div className="w-full h-screen overflow-hidden flex items-center justify-center"> <SignInForm onSubmit={handleSubmit} /> </div> ); }

رابط الحزمة

في هذا المثال، يتم استيراد مكون SignInForm من حزمة ميزات العميل، مما يقودنا إلى مكوننا النهائي.

ميزة عميل التمهيد

توفر حزمة ميزات العميل نموذج المصادقة مع منطق التحقق المشترك. هذا يمنع تكرار الكود ويضمن الاتساق.

 import { passwordValidator, usernameValidator, } from "@robust-monorepo-yarn-nx-changesets/validator"; interface SignInFormProps { onSubmit: (username: string, password: string) => void; } const SignInForm = ({ onSubmit }: SignInFormProps) => { const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => { event.preventDefault(); const username = (event.currentTarget[0] as HTMLInputElement).value; const usernameValidationResult = usernameValidator(username); if (typeof usernameValidationResult === "string") { alert(usernameValidationResult); return; } const password = (event.currentTarget[1] as HTMLInputElement).value; const passwordValidationResult = passwordValidator(password); if (typeof passwordValidationResult === "string") { alert(passwordValidationResult); return; } onSubmit(username!, password!); }; return ( <form onSubmit={handleSubmit}> <input type="text" placeholder="Username" /> <input type="password" placeholder="Password" /> <button type="submit">Submit</button> </form> ); }; export default SignInForm;

رابط الحزمة


هنا، نرى مرة أخرى استخدام المحقق من مكتباتنا المشتركة، مما يضمن أن يكون منطق التحقق مركزيًا وسهل الصيانة.


هذا كل ما لدينا من مثال لقاعدة الكود البسيطة. تذكر أن هذا الكود هو توضيح مبسط يهدف إلى توضيح البنية الأساسية والترابط بين التطبيقات والميزات والمكتبات في مستودع واحد. يمكنك التوسع في هذه الأمثلة بما يتناسب مع متطلبات مشروعك.

تشغيل البرامج النصية باستخدام NX

قد تكون إدارة النصوص البرمجية في مستودع واحد أمرًا صعبًا. بينما يسمح لك Yarn بتشغيل النصوص البرمجية عبر حزم متعددة باستخدام شروط مختلفة، إلا أنه قد يتطلب برمجة نصية مخصصة لتحكم أكثر دقة. وهنا يأتي دور NX: فهو يوفر حلاً جاهزًا لتنفيذ النصوص البرمجية بكفاءة ودقة.

مقدمة إلى NX

NX هو نظام بناء مُحسّن للمستودعات الأحادية مع إمكانيات تكامل مستمر متقدمة. مع NX، يمكنك:

  • قم بتشغيل المهام بكفاءة بالتوازي : استفد من التزامن لتسريع عمليات البناء الخاصة بك.
  • تحديد علاقات التبعية : فهم الاتصالات بين الحزم والبرامج النصية.
  • نتائج تنفيذ البرنامج النصي في ذاكرة التخزين المؤقت : تجنب العمل المكرر عن طريق تخزين المخرجات مؤقتًا.
  • تخصيص السلوك باستخدام المكونات الإضافية: قم بتوسيع الوظائف من خلال نظام بيئي غني بالمكونات الإضافية .

تنفيذ البرنامج النصي المستهدف

للاستفادة من إمكانيات NX، نحتاج أولًا إلى إنشاء ملف nx.json لتحديد مجموعة قواعد لنصوصنا البرمجية. فيما يلي مثال على التكوين:

 { "targetDefaults": { "build": { "dependsOn": [ "^build" ], "outputs": [ "{projectRoot}/dist" ], "cache": true }, "typecheck": { "dependsOn": [ "^build", "^typecheck" ] }, "lint": { "dependsOn": [ "^build", "^lint" ] } }, "defaultBase": "main" }

ببساطة، هذا التكوين يعني:

  • يبني

    يعتمد البرنامج النصي build الحزمة على البناء الناجح لتبعياتها، ويتم تخزين مخرجاتها مؤقتًا.

  • التحقق من النوع

    يعتمد البرنامج typecheck للحزمة على كل من البرامج النصية للبناء والتحقق من النوع الخاصة بتبعياتها.

  • الوبر

    يعتمد البرنامج النصي lint الخاص بالحزمة على كل من نصوص البناء وlint الخاصة بتبعياتها.


الآن، دعنا نضيف البرامج النصية إلى package.json :

 "scripts": { "build:all": "yarn nx run-many -t build", "build:affected": "yarn nx affected -t build --base=${BASE:-origin/main} --head=${HEAD:-HEAD}", "typecheck:all": "yarn nx run-many -t typecheck", "typecheck:affected": "yarn nx affected -t typecheck --base=${BASE:-origin/main} --head=${HEAD:-HEAD}", "lint:all": "yarn nx run-many -t lint", "lint:affected": "yarn nx affected -t lint --base=${BASE:-origin/main} --head=${HEAD:-HEAD}", "quality:all": "yarn nx run-many --targets=typecheck,lint", "quality:affected": "yarn nx affected --targets=typecheck,lint --base=${BASE:-origin/main} --head=${HEAD:-HEAD}" }

هنا، نقوم بتعريف أربعة أنواع من نصوص التنفيذ:

  • بناء: بناء الحزمة.

  • typecheck: التحقق من أنواع الحزمة.

  • lint: ينثر الوبر على الحزمة.

  • الجودة: يتم تشغيل كل من فحص النوع والتحقق من الوبر.


يحتوي كل نص على نسختين:

  • الكل: تشغيل البرنامج النصي على جميع الحزم.
  • متأثر: يُشغّل البرنامج النصي فقط على الحزم المتأثرة بالتغييرات الأخيرة. تتيح لك متغيرات البيئة BASE و HEAD تحديد نطاق (افتراضيًا: origin/main و HEAD الحالي)، مما يُمكّن من تنفيذ طلبات السحب بدقة. هذا يُوفّر الوقت والموارد بشكل كبير.

إدارة التبعيات الدائرية

يوفر NX أيضًا أمرًا مدمجًا لإنشاء رسم بياني للتبعيات، مما يُساعد في اكتشاف دورات التبعيات. يستخدم البرنامج النصي التالي مُخرجات الرسم البياني NX للتحقق من التبعيات الدائرية، ويفشل في حال العثور على أيٍّ منها.

قم بإنشاء ملف في scripts/check-circulardeps.mjs بالمحتوى التالي:

 import { execSync } from "child_process"; import path from "path"; import fs from "fs"; const hasCycle = (node, graph, visited, stack, path) => { if (!visited.has(node)) { visited.add(node); stack.add(node); path.push(node); const dependencies = graph.dependencies[node] || []; for (const dep of dependencies) { const depNode = dep.target; if ( !visited.has(depNode) && hasCycle(depNode, graph, visited, stack, path) ) { return true; } if (stack.has(depNode)) { path.push(depNode); return true; } } } stack.delete(node); path.pop(); return false; }; const getGraph = () => { const cwd = process.cwd(); const tempOutputFilePath = path.join(cwd, "nx-graph.json"); execSync(`nx graph --file=${tempOutputFilePath}`, { encoding: "utf-8", }); const output = fs.readFileSync(tempOutputFilePath, "utf-8"); fs.rmSync(tempOutputFilePath); return JSON.parse(output).graph; }; const checkCircularDeps = () => { const graph = getGraph(); const visited = new Set(); const stack = new Set(); for (const node of Object.keys(graph.dependencies)) { const path = []; if (hasCycle(node, graph, visited, stack, path)) { console.error("🔴 Circular dependency detected:", path.join(" → ")); process.exit(1); } } console.log("✅ No circular dependencies detected."); }; checkCircularDeps();

هذا النص:

  • تنفيذ أمر NX لإنشاء رسم بياني للتبعية.
  • يقوم بقراءة الرسم البياني من ملف JSON مؤقت.
  • التحقق بشكل متكرر من الدورات.
  • يسجل خطأ ويخرج إذا تم اكتشاف اعتماد دائري.

التحقق من صحة التبعيات باستخدام قيود الغزل

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

فهم قيود الغزل

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

على الرغم من أن ترحيل مستودع أحادي كبير إلى إصدار رئيسي جديد من التبعيات قد يكون معقدًا، فإن استخدام القيود يؤدي في النهاية إلى تحقيق الاتساق والاستقرار للمشروع بأكمله.

تعزيز الاتساق

في مستودع المثال الخاص بنا، نستخدم ملفyarn.config.cjs لفرض الاتساق على:

  • إصدار العقدة

  • نسخة الغزل

  • إصدارات التبعيات


لتوفير المرونة أثناء عمليات الانتقال، يمكنك تحديد استثناءات لتجاوز بعض عمليات التحقق مؤقتًا. على سبيل المثال:

 const workspaceCheckExclusions = []; const dependencyCheckExclusions = [];

تتيح لك هذه الثوابت استبعاد مساحات عمل أو تبعيات محددة من عملية التحقق، مما يضمن عمليات ترحيل سلسة عند الضرورة.

إدارة الإصدارات باستخدام مجموعات التغييرات

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

مقدمة إلى مجموعات التغييرات

Changesets هي أداة مفتوحة المصدر مصممة لإدارة إصدارات مستودعات monorepo. تُبسّط هذه الأداة عملية تتبع التغييرات من خلال توزيعها في مستندات صغيرة سهلة القراءة، تُسجّل هدف التغيير. تُسمى هذه المستندات مجموعات التغييرات. من أهم مزاياها:

  • توثيق واضح

    تلخص كل مجموعة تغييرات التغييرات التي تم إجراؤها، مما يساعد كل من المطورين والمستهلكين على فهم ما يمكن توقعه في الإصدار الجديد.

  • التحكم في الإصدار الحبيبي

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

  • صديق للتعاون

    نظرًا لأن كل تغيير يتم تسجيله من خلال مجموعة التغييرات، يمكن للفرق مراجعة التحديثات والموافقة عليها قبل الإصدار الفعلي.

أتمتة الإصدارات

من أقوى ميزات Changesets إمكانية أتمتة العملية. يمكنك دمج Changesets في خط أنابيب CI/CD الخاص بك، وتجنّب تغييرات الإصدارات اليدوية ونشر NPM.

ألقِ نظرة على سير عمل release.yaml في مستودع الأمثلة. يتضمن خطوة create-release-pull-request-or-publish . هذه الخطوة، المدعومة بإجراء GitHub changesets/action ، تُحدث كل هذه العملية الرائعة. ما عليك سوى إعداد NPM_TOKEN لنشر حزمك. بعد ذلك، ستؤدي كل عملية دفع إلى الفرع main إلى:

  • تحقق مما إذا كان هناك أي مستندات Changeset .

    في حال وجود مستندات مجموعة التغييرات، يُنشئ الإجراء طلب سحب يتضمن تعديلات الإصدار وتحديثات سجل التغييرات اللازمة. في حال عدم اكتشاف أي تغييرات، لن يحدث شيء.

  • تحقق مما إذا كانت هناك أي حزم جاهزة للنشر .

    إذا كانت الحزم جاهزة للنشر، ينشر الإجراء الإصدارات الجديدة في NPM باستخدام NPM_TOKEN المُقدّم. إذا لم تكن هناك حزم جاهزة للنشر، ينتهي الإجراء دون إجراء أي تغييرات.


من خلال أتمتة هذه المهام، تضمن Changesets أن تكون إصداراتك متسقة وموثوقة، مما يقلل من احتمالية الخطأ البشري ويبسط سير عمل التطوير لديك.

تكامل سير العمل مع GitHub Actions

يتناول هذا القسم كيفية الاستفادة القصوى من البنية التي بنيناها. باستخدام إجراءات GitHub، سنؤتمت عمليات فحص جودة طلبات العلاقات العامة، وإصدارات المكتبات والميزات، ونشر التطبيقات. ينصب التركيز على تعظيم الأتمتة مع الحفاظ على جودة الكود ودقة العمل.

التحقق من جودة العلاقات العامة

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

 - id: check_version name: Check version changes run: | BASE_BRANCH=${{ github.event.pull_request.base.ref }} git fetch origin $BASE_BRANCH CHANGED_FILES=$(git diff --name-only origin/$BASE_BRANCH HEAD) VERSION_CHANGED=false for FILE in $CHANGED_FILES; do if [[ $FILE == */package.json ]]; then if [ -f "$FILE" ]; then HEAD_VERSION=$(grep '"version":' "$FILE" | awk -F '"' '{print $4}') else continue fi HEAD_VERSION=$(cat $FILE | grep '"version":' | awk -F '"' '{print $4}') if git cat-file -e origin/$BASE_BRANCH:$FILE 2>/dev/null; then BASE_VERSION=$(git show origin/$BASE_BRANCH:$FILE | grep '"version":' | awk -F '"' '{print $4}') else BASE_VERSION=$HEAD_VERSION fi if [ "$BASE_VERSION" != "$HEAD_VERSION" ]; then VERSION_CHANGED=true echo "Version change detected in $FILE" fi fi done if [ "$VERSION_CHANGED" = true ]; then echo "Manual version changes are prohibited. Use changesets instead." exit 1 fi env: GITHUB_REF: ${{ github.ref }}

إلى جانب هذا الفحص، تقوم مهمة check-quality بتثبيت التبعيات، والتحقق من صحة القيود، والتحقق من التبعيات الدائرية والتحقق من جودة الكود الشاملة باستخدام البرنامج النصي الذي حددناه سابقًا باستخدام NX:

 - id: install-dependencies name: Install dependencies run: yarn --immutable - id: check-constraints name: Check constraints run: yarn constraints - id: check-circulardeps name: Check circular dependencies run: yarn check-circulardeps:all - id: check-quality name: Check quality run: BASE=origin/${{ github.event.pull_request.base.ref }} yarn quality:affected

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

إذا كانت هناك حاجة إلى فحوصات إضافية لمشروعك، فيمكنك تحديث ملف nx.json ونص الجودة مع الحفاظ على سير العمل دون تغيير.

نشر المكتبات والميزات

بعد دمج طلب السحب، يتم تشغيل سير عمل الإصدار (كما هو موضح في فصل مجموعات التغييرات). يُنشئ سير العمل هذا الحزم المتأثرة ويُنشئ طلب سحب مع تحديثات الإصدار. بعد الموافقة على طلب السحب هذا ودمجه، يُعاد تشغيل ملف release.yaml - هذه المرة، بدلاً من إنشاء طلب سحب، يكتشف تغييرات الإصدار ويُصدر الحزم المُحدثة إلى NPM:

 - id: build-packages name: Build packages run: yarn build:affected - id: create-release-pull-request-or-publish name: Create Release Pull Request or Publish to NPM uses: changesets/action@v1 with: version: yarn changeset version publish: yarn release commit: "chore: publish new release" title: "chore: publish new release" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} release-apps: needs: release-libs-features uses: ./.github/workflows/release-apps.yaml with: publishedPackages: ${{ needs.release-libs-features.outputs.publishedPackages }}

بعد ذلك، تُنفَّذ مهمة release-apps ، المسؤولة عن نشر التطبيقات. تتلقى هذه المهمة قائمة بالحزم المنشورة من الخطوة السابقة، مما ينقلنا إلى الفصل التالي.

نشر التطبيقات

يتضمن الجزء الأخير من عملية الإصدار نشر تطبيقاتك (لا تُنشر التطبيقات على NPM، لأنها مُعَيَّنة على أنها private في package.json ). يتم تشغيل سير عمل release-apps.yaml تلقائيًا من خلال release.yaml ، أو يُمكن تنفيذه مباشرةً من علامة التبويب "الإجراءات" على GitHub:

 name: Release Apps on: workflow_call: inputs: publishedPackages: description: "List of published packages" required: false type: string default: "[]" workflow_dispatch: inputs: publishedPackages: description: "List of published packages (optional)" required: false type: string default: "[]"

يقبل سير العمل هذا مدخلات الحزم publishedPackages لتحديد الحزم المنشورة. باستخدام استراتيجية المصفوفة، يتحقق من وجود تبعيات منشورة في كل تطبيق من تطبيقات المصفوفة:

 - id: check-dependency-published name: Check if any app dependency is published run: | PUBLISHED_PACKAGES="${{ inputs.publishedPackages }}" PACKAGE_NAME="${{ matrix.package }}" APP="${{ matrix.app }}" DEPENDENCIES=$(jq -r '.dependencies // {} | keys[]' "apps/$APP/package.json") for DEP in $DEPENDENCIES; do if echo "$PUBLISHED_PACKAGES" | grep -w "$DEP"; then echo "published=true" >> $GITHUB_OUTPUT exit 0 fi done echo "published=false" >> $GITHUB_OUTPUT

هذا التحقق شرطٌ لبدء نشر التطبيق. أما الشرط الآخر فيضمن تغيير إصدار التطبيق (مما يشير إلى ضرورة إعادة النشر حتى في حال عدم تحديث أي تبعيات):

 - id: check-version-change name: Check if app version has changed run: | APP="${{ matrix.app }}" PACKAGE_JSON_PATH="apps/$APP/package.json" CURRENT_VERSION=$(jq -r '.version' "$PACKAGE_JSON_PATH") PREVIOUS_VERSION=$(git show HEAD~1:"$PACKAGE_JSON_PATH" | jq -r '.version' || echo "") if [[ "$CURRENT_VERSION" == "$PREVIOUS_VERSION" ]]; then echo "changed=false" >> $GITHUB_OUTPUT else echo "changed=true" >> $GITHUB_OUTPUT fi

أخيرًا، بعد التأكد من أن التطبيق لديه تبعيات محدثة أو أن إصداره قد تغير، يسترد سير العمل الإصدار الجديد ويستمر في بناء التطبيق ونشره:

 - id: set-up-docker name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - id: get-app-version name: Get the app version from package.json run: echo "app-version=$(cat ./apps/${{ matrix.app }}/package.json | jq -r '.version')" >> $GITHUB_OUTPUT - id: build-image name: Build image if: steps.check-dependency-published.outputs.published == 'true' || steps.check-version-change.outputs.changed == 'true' uses: docker/build-push-action@v4 with: build-contexts: | workspace=./ context: "./apps/${{ matrix.app }}" load: true push: false tags: | ${{ matrix.app }}:v${{ steps.get-app-version.outputs.app-version }}

في هذا المثال، نُنشئ نسخة Docker دون نقلها إلى سجلّ. في سير عمل الإنتاج، استبدل هذه الخطوة بعملية النشر الفعلية.

خاتمة

ملخص لأفضل الممارسات

في هذه المقالة، استكشفنا كيفية إعداد مستودع أحادي قوي والأدوات التي تُساعد على إدارته بكفاءة. بتركيز قاعدة الكود، لا تُبسط إدارة التبعيات فحسب، بل تُسهّل أيضًا التعاون بين الفرق. أوضحنا كيف يُمكن الاستفادة من Yarn لمشاركة التبعيات، وتسريع التثبيت باستخدام PnP، وتحسين الاتساق العام للمشروع. بالإضافة إلى ذلك، يضمن دمج NX لتنفيذ النصوص البرمجية المُستهدفة سرعة وكفاءة التكامل المستمر (CI). ساعدت مجموعات التغييرات (Checksets) في أتمتة إدارة الإصدارات، وتقليل الأخطاء اليدوية، وتبسيط عمليات الإصدار. وأخيرًا، أنشأنا خط أنابيب تكامل مستمر/تجديد مستمر (CI/CD) جاهزًا للإنتاج مع إجراءات GitHub التي تُنفّذ المهام الضرورية فقط.

الخطوات التالية

  1. جرّب وتكيّفَ : ابدأ بإنشاء مستودع أحادي صغير لاختبار أفضل الممارسات. جرّب هياكل مجلدات مختلفة، ثم وسّعها تدريجيًا لتشمل المزيد من الحزم مع ازدياد ثقتك بنفسك.
  2. دمج أدوات إضافية : فكر في دمج أدوات تكميلية مثل PNPM أو Turborepo استنادًا إلى متطلبات مشروعك الفريدة وتفضيلات الفريق.
  3. تحسين خطوط أنابيب CI/CD : قم بضبط سير عمل GitHub Actions لتشمل عمليات فحص الجودة الإضافية وتغطية التعليمات البرمجية وعمليات مسح الأمان المصممة خصيصًا لمشروعك.
  4. المجتمع والتحديثات : ابقَ على اطلاع بأحدث إصدارات Yarn وNX وChangesets. تواصل مع المجتمع لمشاركة الأفكار والتعرف على أحدث التوجهات في إدارة المستودعات الأحادية.

موارد

  • مثال على المستودع :

    اطلع على مستودع الأمثلة الكامل المُصمم خصيصًا لهذا الدليل. استكشف هيكل المشروع، ونماذج التعليمات البرمجية، والبرامج النصية التي تُظهر إعداد مستودع monorepo عمليًا.

  • حزم NPM المنشورة :

    اطلع على حزمة NPM الفعلية المنشورة كجزء من هذا المشروع. توضح هذه الحزم الاستخدام العملي وتطبيق المفاهيم المذكورة في المقالة.


Trending Topics

blockchaincryptocurrencyhackernoon-top-storyprogrammingsoftware-developmenttechnologystartuphackernoon-booksBitcoinbooks