إنشاء Multi-Monorepo عام / خاص لمشاريع PHP
نشرت: 2022-03-10لجعل تجربة التطوير الخاصة بي أسرع ، قمت بنقل جميع حزم PHP المطلوبة لمشاريعي إلى monorepo. عندما يتم استضافة كل حزمة في مستودع خاص بها (نهج multirepo) ، سأحتاج إلى تطويرها واختبارها بمفردها ثم نشرها على Packagist قبل أن أتمكن من تثبيتها في حزم أخرى عبر Composer. باستخدام monorepo ، نظرًا لاستضافة جميع الحزم معًا ، يمكن تطويرها واختبارها وإصدارها وإصدارها في نفس الوقت.
يعد monorepo الذي يستضيف حزم PHP الخاصة بي عامًا ، ويمكن لأي شخص الوصول إليه على GitHub. لا يمكن لمستودعات Git منح وصول مختلف إلى أصول مختلفة ؛ كلها إما عامة أو خاصة. نظرًا لأنني أخطط لإصدار مكون إضافي لبرنامج WordPress ، فأنا أريد أن تظل حزمه خاصة ، مما يعني أنه لا يمكن إضافتها إلى monorepo العام.
الحل الذي وجدته هو استخدام نهج "monorepo متعدد" ، يتألف من وحدتين monorepos: أحدهما عام والآخر خاص ، مع تضمين monorepo الخاص العام كنموذج Git الفرعي ، مما يسمح له بالوصول إلى ملفاته. يمكن اعتبار monorepo العام واحد المنبع ، و monorepo الخاص واحد المصب.
نظرًا لأنني واصلت التكرار على الكود الخاص بي ، كان إعداد المستودع الذي أحتاج إلى استخدامه في كل مرحلة من مراحل مشروعي بحاجة أيضًا إلى الترقية. ومن ثم ، لم أصل إلى نهج متعدد المونوريبو في اليوم الأول ؛ لقد كانت عملية امتدت لعدة سنوات واستغرقت قدرًا لا بأس به من الجهد ، من مستودع واحد ، إلى مستودعات متعددة ، إلى monorepo ، وأخيراً ، monorepo متعدد.
في هذه المقالة ، سوف أصف كيف أقوم بإعداد monorepo متعدد باستخدام Monorepo Builder ، والذي يعمل مع مشاريع PHP ويعتمد على Composer.
إعادة استخدام الكود في Multi-Monorepo
monorepo العام في leoloso/PoP
هو المكان الذي أحتفظ فيه بجميع مشاريع PHP الخاصة بي.
يحتوي هذا المونوريبو على ملف سير العمل generate_plugins.yml
، والذي يقوم بإنشاء العديد من مكونات WordPress الإضافية للتوزيع عندما أقوم بإنشاء إصدار على GitHub:
لم يتم ترميز تكوين سير العمل في ملف YAML ، بل يتم حقنه عبر كود PHP:
- id: output_data run: | echo "::set-output name=plugin_config_entries::$(vendor/bin/monorepo-builder plugin-config-entries-json)"
ويتم توفير التكوين عبر فئة PHP مخصصة:
class PluginDataSource { public function getPluginConfigEntries(): array { return [ // GraphQL API for WordPress [ 'path' => 'layers/GraphQLAPIForWP/plugins/graphql-api-for-wp', 'zip_file' => 'graphql-api.zip', 'main_file' => 'graphql-api.php', 'dist_repo_organization' => 'GraphQLAPI', 'dist_repo_name' => 'graphql-api-for-wp-dist', ], // GraphQL API - Extension Demo [ 'path' => 'layers/GraphQLAPIForWP/plugins/extension-demo', 'zip_file' => 'graphql-api-extension-demo.zip', 'main_file' => 'graphql-api-extension-demo.php', 'dist_repo_organization' => 'GraphQLAPI', 'dist_repo_name' => 'extension-demo-dist', ], ]; } }
أدى إنشاء العديد من مكونات WordPress الإضافية معًا ، وتكوين سير العمل عبر PHP ، إلى تقليل مقدار الوقت الذي أحتاجه لإدارة المشروع. يتعامل سير العمل حاليًا مع مكونين إضافيين (واجهة برمجة تطبيقات GraphQL وامتدادها التوضيحي) ، ولكن يمكنه التعامل مع 200 بدون مجهود إضافي مني.
هذا هو الإعداد الذي أريد إعادة استخدامه لمونوريبو الخاص في leoloso/GraphQLAPI-PRO
، بحيث يمكن أيضًا إنشاء المكونات الإضافية الاحترافية دون جهد.
سيتضمن الكود الذي سيتم إعادة استخدامه ما يلي:
- سير عمل إجراءات GitHub لإنشاء مكونات WordPress الإضافية (بما في ذلك تحديد النطاق ، والرجوع إلى إصدار سابق من PHP 8.0 إلى 7.1 ، والتحميل إلى صفحة الإصدارات).
- خدمات PHP المخصصة لتكوين سير العمل.
يمكن لـ monorepo الخاص بعد ذلك إنشاء إضافات WordPress الاحترافية ببساطة عن طريق تشغيل سير العمل من monorepo العام وتجاوز تكوينها في PHP.
ربط Monorepos عبر وحدات Git الفرعية
لتضمين المستودع العام في المستودع الخاص ، نستخدم وحدات Git الفرعية:
git submodule add <public repo URL>
لقد قمت بتضمين المستودع العام في الوحدات الفرعية للمجلد الفرعي من submodules
الخاص ، مما سمح لي بإضافة المزيد من monorepos المنبع في المستقبل إذا لزم الأمر. في GitHub ، يعرض المجلد الالتزام المحدد للوحدة الفرعية ، والنقر فوقه سينقلك إلى هذا الالتزام في leoloso/PoP
:
نظرًا لأن المستودع الخاص يحتوي على وحدات فرعية ، لاستنساخه ، يجب علينا توفير الخيار --recursive
:
git clone --recursive <private repo URL>
إعادة استخدام مهام سير عمل إجراءات GitHub
يقوم GitHub Actions بتحميل مهام سير العمل فقط من ضمن .github/workflows
. نظرًا لأن تدفقات العمل العامة في monorepo المتلقين للمعلومات تكون ضمن submodules/PoP/.github/workflows
، يجب تكرارها في الموقع المتوقع.
من أجل الحفاظ على سير العمل الأساسي كمصدر وحيد للحقيقة ، يمكننا أن نقصر أنفسنا على نسخ الملفات أسفل المسار تحت .github/workflows
، ولكن لا نقوم بتحريرها هناك. إذا كان يتعين إجراء أي تغيير ، فيجب إجراؤه في monorepo المنبع ثم نسخه.
كملاحظة جانبية ، لاحظ كيف أن هذا يعني أن monorepo متعدد التسريبات: إن monorepo المنبع ليس مستقلًا تمامًا ، وسيحتاج إلى تكييفه ليناسب monorepo المصب.
في أول تكرار لي لنسخ سير العمل ، قمت بإنشاء برنامج نصي بسيط من Composer:
{ "scripts": { "copy-workflows": [ "php -r \"copy('submodules/PoP/.github/workflows/generate_plugins.yml', '.github/workflows/generate_plugins.yml');\"", "php -r \"copy('submodules/PoP/.github/workflows/split_monorepo.yaml', '.github/workflows/split_monorepo.yaml');\"" ] } }
بعد ذلك ، بعد تحرير عمليات سير العمل في monorepo المنبع ، أود نسخها في اتجاه مجرى النهر عن طريق تنفيذ ما يلي:
composer copy-workflows
ولكن بعد ذلك أدركت أن مجرد نسخ مهام سير العمل لا يكفي: يجب أيضًا تعديلها في هذه العملية. هذا لأن فحص monorepo المصب يتطلب الخيار --recurse-submodules
أجل فحص الوحدات الفرعية أيضًا.
في إجراءات GitHub ، يتم إجراء الخروج في اتجاه مجرى النهر على النحو التالي:
- uses: actions/checkout@v2 with: submodules: recursive
لذا ، فإن التحقق من المستودع المتلقين للمعلومات يحتاج إلى وحدات إدخال submodules: recursive
، لكن المنبع لا يحتاج ، وكلاهما يستخدم نفس الملف المصدر.
الحل الذي وجدته هو توفير القيمة submodules
للإدخال عبر متغير البيئة CHECKOUT_SUBMODULES
، والذي يكون فارغًا افتراضيًا للمستودع الأولي:
env: CHECKOUT_SUBMODULES: "" jobs: provide_data: steps: - uses: actions/checkout@v2 with: submodules: ${{ env.CHECKOUT_SUBMODULES }}
بعد ذلك ، عند نسخ مهام سير العمل من المنبع إلى المصب ، يتم استبدال قيمة CHECKOUT_SUBMODULES
بالقيمة recursive
:
env: CHECKOUT_SUBMODULES: "recursive"
عند تعديل سير العمل ، من الأفضل استخدام تعبير عادي (regex) ، بحيث يعمل مع تنسيقات مختلفة في الملف المصدر (مثل CHECKOUT_SUBMODULES: ""
أو CHECKOUT_SUBMODULES:''
أو CHECKOUT_SUBMODULES:
. هذا سيمنع الخلل من التكون لهذه الأنواع من التغييرات غير المؤذية ظاهريًا.
وبالتالي ، فإن النص البرمجي لـ Copy copy-workflows
Composer الموضح أعلاه ليس جيدًا بما يكفي للتعامل مع هذا التعقيد.
في التكرار التالي ، قمت بإنشاء أمر PHP ، CopyUpstreamMonorepoFilesCommand
، ليتم تنفيذه عبر Monorepo Builder:
vendor/bin/monorepo-builder copy-upstream-monorepo-files
يستخدم هذا الأمر خدمة مخصصة ، FileCopierSystem
، لنسخ جميع الملفات من مجلد مصدر إلى الوجهة المحددة ، مع استبدال محتوياتها اختياريًا:
namespace PoP\GraphQLAPIPRO\Extensions\Symplify\MonorepoBuilder\SmartFile; use Nette\Utils\Strings; use Symplify\SmartFileSystem\Finder\SmartFinder; use Symplify\SmartFileSystem\SmartFileSystem; final class FileCopierSystem { public function __construct( private SmartFileSystem $smartFileSystem, private SmartFinder $smartFinder, ) { } /** * @param array $patternReplacements a regex pattern to search, and its replacement */ public function copyFilesFromFolder( string $fromFolder, string $toFolder, array $patternReplacements = [] ): void { $smartFileInfos = $this->smartFinder->find([$fromFolder], '*'); foreach ($smartFileInfos as $smartFileInfo) { $fromFile = $smartFileInfo->getRealPath(); $fileContent = $this->smartFileSystem->readFile($fromFile); foreach ($patternReplacements as $pattern => $replacement) { $fileContent = Strings::replace($fileContent, $pattern, $replacement); } $toFile = $toFolder . substr($fromFile, strlen($fromFolder)); $this->smartFileSystem->dumpFile($toFile, $fileContent); } } }
namespace PoP\GraphQLAPIPRO\Extensions\Symplify\MonorepoBuilder\SmartFile; use Nette\Utils\Strings; use Symplify\SmartFileSystem\Finder\SmartFinder; use Symplify\SmartFileSystem\SmartFileSystem; final class FileCopierSystem { public function __construct( private SmartFileSystem $smartFileSystem, private SmartFinder $smartFinder, ) { } /** * @param array $patternReplacements a regex pattern to search, and its replacement */ public function copyFilesFromFolder( string $fromFolder, string $toFolder, array $patternReplacements = [] ): void { $smartFileInfos = $this->smartFinder->find([$fromFolder], '*'); foreach ($smartFileInfos as $smartFileInfo) { $fromFile = $smartFileInfo->getRealPath(); $fileContent = $this->smartFileSystem->readFile($fromFile); foreach ($patternReplacements as $pattern => $replacement) { $fileContent = Strings::replace($fileContent, $pattern, $replacement); } $toFile = $toFolder . substr($fromFile, strlen($fromFolder)); $this->smartFileSystem->dumpFile($toFile, $fileContent); } } }
عند استدعاء هذه الطريقة لنسخ جميع مهام سير العمل في اتجاه المصب ، أستبدل أيضًا قيمة CHECKOUT_SUBMODULES
:
/** * Copy all workflows to `.github/`, and convert: * `CHECKOUT_SUBMODULES: ""` * into: * `CHECKOUT_SUBMODULES: "recursive"` */ $regexReplacements = [ '#CHECKOUT_SUBMODULES:(\s+".*")?#' => 'CHECKOUT_SUBMODULES: "recursive"', ]; (new FileCopierSystem())->copyFilesFromFolder( 'submodules/PoP/.github/workflows', '.github/workflows', $regexReplacements );
سير العمل في generate_plugins.yml
يحتاج إلى بديل إضافي. عندما يتم إنشاء المكون الإضافي WordPress ، يتم تخفيض رمزه من PHP 8.0 إلى 7.1 من خلال استدعاء البرنامج النصي ci/downgrade/downgrade_code.sh
:
- name: Downgrade code for production (to PHP 7.1) run: ci/downgrade/downgrade_code.sh "${{ matrix.pluginConfig.rector_downgrade_config }}" "" "${{ matrix.pluginConfig.path }}" "${{ matrix.pluginConfig.additional_rector_configs }}"
في monorepo المصب ، سيكون هذا الملف موجودًا ضمن submodules/PoP/ci/downgrade/downgrade_code.sh
. بعد ذلك ، نوجه سير العمل في المراحل النهائية إلى المسار الصحيح باستخدام هذا الاستبدال:
$regexReplacements = [ // ... '#(ci/downgrade/downgrade_code\.sh)#' => 'submodules/PoP/$1', ];
تكوين الحزم في Monorepo Builder
الملف monorepo-builder.php
- الموجود في جذر monorepo - يحمل تكوين Monorepo Builder. في ذلك ، يجب أن نشير إلى مكان وجود الحزم (والمكونات الإضافية والعملاء وأي شيء آخر):
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; use Symplify\MonorepoBuilder\ValueObject\Option; return static function (ContainerConfigurator $containerConfigurator): void { $parameters = $containerConfigurator->parameters(); $parameters->set(Option::PACKAGE_DIRECTORIES, [ __DIR__ . '/packages', __DIR__ . '/plugins', ]); };
يجب أن يكون لدى monorepo الخاص إمكانية الوصول إلى جميع الكود: حزمه الخاصة ، بالإضافة إلى تلك من monorepo العام. بعد ذلك ، يجب أن تحدد جميع الحزم من كل من monorepos في ملف التكوين. تقع الوحدات من monorepo العامة ضمن /submodules/PoP
:
return static function (ContainerConfigurator $containerConfigurator): void { $parameters = $containerConfigurator->parameters(); $parameters->set(Option::PACKAGE_DIRECTORIES, [ // public code __DIR__ . '/submodules/PoP/packages', __DIR__ . '/submodules/PoP/plugins', // private code __DIR__ . '/packages', __DIR__ . '/plugins', __DIR__ . '/clients', ]); };
كما هي ، فإن تكوينات المنبع والمصب هي نفسها إلى حد كبير ، والاختلافات هي أن التكوينات النهائية ستكون:
- تغيير المسار إلى الحزم العامة ،
- أضف الحزم الخاصة.
لذلك ، من المنطقي إعادة كتابة التكوين باستخدام البرمجة الشيئية (OOP). دعنا نتبع مبدأ DRY ("لا تكرر نفسك") من خلال توسيع فئة PHP في المستودع العام في المستودع الخاص.
إعادة التكوين عبر OOP
دعونا نعيد تشكيل التكوين. في المستودع العام ، يشير الملف monorepo-builder.php
ببساطة إلى فئة جديدة ، ContainerConfigurationService
، حيث ستحدث كل الإجراءات:
use PoP\PoP\Config\Symplify\MonorepoBuilder\Configurators\ContainerConfigurationService; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; return static function (ContainerConfigurator $containerConfigurator): void { $containerConfigurationService = new ContainerConfigurationService( $containerConfigurator, __DIR__ ); $containerConfigurationService->configureContainer(); };
تشير المعلمة __DIR__
إلى جذر monorepo. ستكون هناك حاجة للحصول على المسار الكامل لأدلة الحزمة.
أصبحت فئة ContainerConfigurationService
مسؤولة الآن عن إنتاج التكوين:
namespace PoP\PoP\Config\Symplify\MonorepoBuilder\Configurators; use PoP\PoP\Config\Symplify\MonorepoBuilder\DataSources\PackageOrganizationDataSource; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; use Symplify\MonorepoBuilder\ValueObject\Option; class ContainerConfigurationService { public function __construct( protected ContainerConfigurator $containerConfigurator, protected string $rootDirectory, ) { } public function configureContainer(): void { $parameters = $this->containerConfigurator->parameters(); if ($packageOrganizationConfig = $this->getPackageOrganizationDataSource($this->rootDirectory)) { $parameters->set( Option::PACKAGE_DIRECTORIES, $packageOrganizationConfig->getPackageDirectories() ); } } protected function getPackageOrganizationDataSource(): ?PackageOrganizationDataSource { return new PackageOrganizationDataSource($this->rootDirectory); } }
يمكن تقسيم التكوين عبر عدة فئات. في هذه الحالة ، تسترد ContainerConfigurationService
تكوين الحزمة من خلال الفئة PackageOrganizationDataSource
، والتي يمكنك رؤية تنفيذها:
namespace PoP\PoP\Config\Symplify\MonorepoBuilder\DataSources; class PackageOrganizationDataSource { public function __construct(protected string $rootDir) { } public function getPackageDirectories(): array { return array_map( fn (string $packagePath) => $this->rootDir . '/' . $packagePath, $this->getRelativePackagePaths() ); } public function getRelativePackagePaths(): array { return [ 'packages', 'plugins', ]; } }
تجاوز التكوين في Monorepo المصب
الآن بعد أن تم إعداد التكوين في monorepo العام باستخدام OOP ، يمكننا توسيعه ليناسب احتياجات monorepo الخاص.
من أجل السماح لـ monorepo الخاص بالتحميل التلقائي لشفرة PHP من monorepo العام ، يجب علينا أولاً تكوين ملف composer.json
المتلقين للمعلومات للإشارة إلى كود المصدر من المنبع ، والذي يقع تحت المسار submodules/PoP/src
:
{ "autoload": { "psr-4": { "PoP\\GraphQLAPIPRO\\": "src", "PoP\\PoP\\": "submodules/PoP/src" } } }
يوجد أدناه ملف monorepo-builder.php
الخاص. لاحظ أن فئة ContainerConfigurationService
المشار إليها في المستودع الرئيسي تنتمي إلى مساحة الاسم PoP\PoP
ولكن تم تحويلها الآن إلى مساحة الاسم PoP\GraphQLAPIPRO
. يجب أن يتلقى هذا الفصل الإدخال الإضافي $upstreamRelativeRootPath
(بقيمة submodules/PoP
) لإعادة إنشاء المسار الكامل للحزم العامة:
use PoP\GraphQLAPIPRO\Config\Symplify\MonorepoBuilder\Configurators\ContainerConfigurationService; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; return static function (ContainerConfigurator $containerConfigurator): void { $containerConfigurationService = new ContainerConfigurationService( $containerConfigurator, __DIR__, 'submodules/PoP' ); $containerConfigurationService->configureContainer(); };
تلغي فئة " ContainerConfigurationService
" المتلقية للمعلومات أي فئة PackageOrganizationDataSource
مستخدمة في التكوين:
namespace PoP\GraphQLAPIPRO\Config\Symplify\MonorepoBuilder\Configurators; use PoP\PoP\Config\Symplify\MonorepoBuilder\Configurators\ContainerConfigurationService as UpstreamContainerConfigurationService; use PoP\GraphQLAPIPRO\Config\Symplify\MonorepoBuilder\DataSources\PackageOrganizationDataSource; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; class ContainerConfigurationService extends UpstreamContainerConfigurationService { public function __construct( ContainerConfigurator $containerConfigurator, string $rootDirectory, protected string $upstreamRelativeRootPath ) { parent::__construct( $containerConfigurator, $rootDirectory ); } protected function getPackageOrganizationDataSource(): ?PackageOrganizationDataSource { return new PackageOrganizationDataSource( $this->rootDirectory, $this->upstreamRelativeRootPath ); } }
أخيرًا ، تحتوي فئة المصب PackageOrganizationDataSource
على المسار الكامل لكل من الحزم العامة والخاصة:
namespace PoP\GraphQLAPIPRO\Config\Symplify\MonorepoBuilder\DataSources; use PoP\PoP\Config\Symplify\MonorepoBuilder\DataSources\PackageOrganizationDataSource as UpstreamPackageOrganizationDataSource; class PackageOrganizationDataSource extends UpstreamPackageOrganizationDataSource { public function __construct( string $rootDir, protected string $upstreamRelativeRootPath ) { parent::__construct($rootDir); } public function getRelativePackagePaths(): array { return array_merge( // Public packages - Prepend them with "submodules/PoP/" array_map( fn ($upstreamPackagePath) => $this->upstreamRelativeRootPath . '/' . $upstreamPackagePath, parent::getRelativePackagePaths() ), // Private packages [ 'packages', 'plugins', 'clients', ] ); } }
حقن التكوين من PHP في إجراءات GitHub
يقدم Monorepo Builder packages-json
، والتي يمكننا استخدامها لحقن مسارات الحزمة في سير عمل GitHub Actions:
jobs: provide_data: steps: - id: output_data name: Calculate matrix for packages run: | echo "::set-output name=matrix::$(vendor/bin/monorepo-builder packages-json)" outputs: matrix: ${{ steps.output_data.outputs.matrix }}
ينتج عن هذا الأمر JSON سلسلة. في سير العمل ، يجب تحويله إلى كائن JSON عبر fromJson
:
jobs: split_monorepo: needs: provide_data strategy: matrix: package: ${{ fromJson(needs.provide_data.outputs.matrix) }}
لسوء الحظ ، packages-json
أسماء الحزم وليس مساراتها. سيعمل هذا إذا كانت جميع الحزم ضمن نفس المجلد (مثل packages/
) ، لكنه لا يعمل في حالتنا لأن الحزم العامة والخاصة موجودة في مجلدات مختلفة.
لحسن الحظ ، يمكن توسيع Monorepo Builder بخدمات PHP المخصصة. لذلك ، قمت بإنشاء أمر مخصص ، package-entries-json
(عبر الفئة PackageEntriesJsonCommand
) ، والذي يُخرج المسار إلى الحزمة.
ثم تم تحديث سير العمل بالأمر الجديد:
run: | echo "::set-output name=matrix::$(vendor/bin/monorepo-builder package-entries-json)"
يتم تنفيذه على monorepo العام ، ينتج هذا الحزم التالية (من بين العديد من الحزم الأخرى):
[ { "name": "graphql-api-for-wp", "path": "layers/GraphQLAPIForWP/plugins/graphql-api-for-wp" }, { "name": "extension-demo", "path": "layers/GraphQLAPIForWP/plugins/extension-demo" }, { "name": "access-control", "path": "layers/Engine/packages/access-control" }, { "name": "api", "path": "layers/API/packages/api" }, { "name": "api-clients", "path": "layers/API/packages/api-clients" } ]
يتم تنفيذه على monorepo الخاص ، فإنه ينتج الإدخالات التالية (من بين أشياء أخرى كثيرة):
[ { "name": "graphql-api-for-wp", "path": "submodules/PoP/layers/GraphQLAPIForWP/plugins/graphql-api-for-wp" }, { "name": "extension-demo", "path": "submodules/PoP/layers/GraphQLAPIForWP/plugins/extension-demo" }, { "name": "access-control", "path": "submodules/PoP/layers/Engine/packages/access-control" }, { "name": "api", "path": "submodules/PoP/layers/API/packages/api" }, { "name": "api-clients", "path": "submodules/PoP/layers/API/packages/api-clients" }, { "name": "graphql-api-pro", "path": "layers/GraphQLAPIForWP/plugins/graphql-api-pro" }, { "name": "convert-case-directives", "path": "layers/Schema/packages/convert-case-directives" }, { "name": "export-directive", "path": "layers/GraphQLByPoP/packages/export-directive" } ]
إنه يعمل بشكل جيد. يحتوي تكوين monorepo المتجه نحو المصب على كل من الحزم العامة والخاصة ، ويتم تقديم المسارات إلى الحزم العامة مسبقًا مع submodules/PoP
.
تخطي الحزم العامة في المصب Monorepo
حتى الآن ، يتضمن monorepo المصب كلاً من الحزم العامة والخاصة في تكوينه. ومع ذلك ، لا يلزم تنفيذ كل أمر على الحزم العامة.
خذ التحليل الساكن ، على سبيل المثال. يقوم monorepo العام بالفعل بتنفيذ PHPStan على جميع الحزم العامة عبر ملف مسار العمل phpstan.yml
، كما هو موضح في هذا التشغيل. إذا قامت monorepo المصب بتشغيل PHPStan مرة أخرى على الحزم العامة ، فسيكون ذلك مضيعة لوقت الحوسبة. يحتاج سير عمل phpstan.yml
إلى التشغيل على الحزم الخاصة فقط.
هذا يعني أنه بناءً على الأمر الذي سيتم تنفيذه في مستودع المصب ، قد نرغب في تضمين الحزم العامة والخاصة أو الحزم الخاصة فقط.
لتحديد ما إذا كان يجب إضافة حزم عامة في التكوين المتلقين للمعلومات ، نقوم بتكييف فئة المصب PackageOrganizationDataSource
للتحقق من هذا الشرط عبر الإدخال $includeUpstreamPackages
:
namespace PoP\GraphQLAPIPRO\Config\Symplify\MonorepoBuilder\DataSources; use PoP\PoP\Config\Symplify\MonorepoBuilder\DataSources\PackageOrganizationDataSource as UpstreamPackageOrganizationDataSource; class PackageOrganizationDataSource extends UpstreamPackageOrganizationDataSource { public function __construct( string $rootDir, protected string $upstreamRelativeRootPath, protected bool $includeUpstreamPackages ) { parent::__construct($rootDir); } public function getRelativePackagePaths(): array { return array_merge( // Add the public packages? $this->includeUpstreamPackages ? // Public packages - Prepend them with "submodules/PoP/" array_map( fn ($upstreamPackagePath) => $this->upstreamRelativeRootPath . '/' . $upstreamPackagePath, parent::getRelativePackagePaths() ) : [], // Private packages [ 'packages', 'plugins', 'clients', ] ); } }
بعد ذلك ، نحتاج إلى تقديم القيمة $includeUpstreamPackages
إما true
أو false
، اعتمادًا على الأمر المراد تنفيذه.
يمكننا القيام بذلك عن طريق استبدال ملف التكوين monorepo-builder.php
تكوين آخرين: monorepo-builder-with-upstream-packages.php
-pack.php (الذي يمر $includeUpstreamPackages
=> true
) و monorepo-builder-without-upstream-packages.php
(الذي $includeUpstreamPackages
=> false
):
// File monorepo-builder-without-upstream-packages.php use PoP\GraphQLAPIPRO\Config\Symplify\MonorepoBuilder\Configurators\ContainerConfigurationService; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; return static function (ContainerConfigurator $containerConfigurator): void { $containerConfigurationService = new ContainerConfigurationService( $containerConfigurator, __DIR__, 'submodules/PoP', false, // This is $includeUpstreamPackages ); $containerConfigurationService->configureContainer(); };
نقوم بعد ذلك بتحديث ContainerConfigurationService
لتلقي المعلمة $includeUpstreamPackages
إلى PackageOrganizationDataSource
:
namespace PoP\GraphQLAPIPRO\Config\Symplify\MonorepoBuilder\Configurators; use PoP\PoP\Config\Symplify\MonorepoBuilder\Configurators\ContainerConfigurationService as UpstreamContainerConfigurationService; use PoP\GraphQLAPIPRO\Config\Symplify\MonorepoBuilder\DataSources\PackageOrganizationDataSource; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; class ContainerConfigurationService extends UpstreamContainerConfigurationService { public function __construct( ContainerConfigurator $containerConfigurator, string $rootDirectory, protected string $upstreamRelativeRootPath, protected bool $includeUpstreamPackages, ) { parent::__construct( $containerConfigurator, $rootDirectory, ); } protected function getPackageOrganizationDataSource(): ?PackageOrganizationDataSource { return new PackageOrganizationDataSource( $this->rootDirectory, $this->upstreamRelativeRootPath, $this->includeUpstreamPackages, ); } }
بعد ذلك ، يتعين علينا استدعاء monorepo-builder
مع أي من ملفي التكوين ، من خلال توفير الخيار --config
:
jobs: provide_data: steps: - id: output_data name: Calculate matrix for packages run: | echo "::set-output name=matrix::$(vendor/bin/monorepo-builder package-entries-json --config=monorepo-builder-without-upstream-packages.php)"
ومع ذلك ، كما رأينا سابقًا ، نريد الاحتفاظ بسير عمل GitHub Actions في monorepo المنبع كمصدر وحيد للحقيقة ، ومن الواضح أنهم لا يحتاجون إلى هذه التغييرات.
الحل الذي وجدته لهذه المشكلة هو توفير خيار --config
دائمًا في المستودع الرئيسي ، مع حصول كل أمر على ملف التكوين الخاص به ، مثل أمر validate
الذي يستقبل ملف تكوين validate.php
:
- name: Run validation run: vendor/bin/monorepo-builder validate --config=config/monorepo-builder/validate.php
الآن ، لا توجد ملفات تكوين في monorepo المنبع ، لأنها لا تحتاج إليها. لكنه لن ينكسر ، لأن Monorepo Builder يتحقق مما إذا كان ملف التكوين موجودًا ، وإذا لم يكن كذلك ، فإنه يقوم بتحميل ملف التكوين الافتراضي بدلاً من ذلك. لذلك ، إما أننا سنتجاوز التكوين أو لن يحدث شيء.
يوفر مستودع المصب ملفات التكوين لكل أمر ، مع تحديد ما إذا كان سيتم إضافة الحزم الأولية:
كملاحظة جانبية ، هذا مثال آخر على كيفية تسرب المونوريبو المتعدد.
// File config/monorepo-builder/validate.php return require_once __DIR__ . '/monorepo-builder-with-upstream-packages.php';
تجاوز التكوين
نحن على وشك الإنتهاء. الآن ، يمكن monorepo المصب تجاوز التكوين من monorepo المنبع. لذا ، كل ما تبقى هو توفير التكوين الجديد.
في فئة PluginDataSource
، تجاوزت التكوين الذي يجب إنشاء مكونات WordPress الإضافية الخاصة به ، مع توفير الإضافات الاحترافية بدلاً من ذلك:
namespace PoP\GraphQLAPIPRO\Config\Symplify\MonorepoBuilder\DataSources; use PoP\PoP\Config\Symplify\MonorepoBuilder\DataSources\PluginDataSource as UpstreamPluginDataSource; class PluginDataSource extends UpstreamPluginDataSource { public function getPluginConfigEntries(): array { return [ // GraphQL API PRO [ 'path' => 'layers/GraphQLAPIForWP/plugins/graphql-api-pro', 'zip_file' => 'graphql-api-pro.zip', 'main_file' => 'graphql-api-pro.php', 'dist_repo_organization' => 'GraphQLAPI-PRO', 'dist_repo_name' => 'graphql-api-pro-dist', ], // GraphQL API Extensions // Google Translate [ 'path' => 'layers/GraphQLAPIForWP/plugins/google-translate', 'zip_file' => 'graphql-api-google-translate.zip', 'main_file' => 'graphql-api-google-translate.php', 'dist_repo_organization' => 'GraphQLAPI-PRO', 'dist_repo_name' => 'graphql-api-google-translate-dist', ], // Events Manager [ 'path' => 'layers/GraphQLAPIForWP/plugins/events-manager', 'zip_file' => 'graphql-api-events-manager.zip', 'main_file' => 'graphql-api-events-manager.php', 'dist_repo_organization' => 'GraphQLAPI-PRO', 'dist_repo_name' => 'graphql-api-events-manager-dist', ], ]; } }
سيؤدي generate_plugins.yml
إصدار جديد على GitHub إلى تشغيل سير عمل create_plugins.yml وسيُنشئ المكونات الإضافية الاحترافية في monorepo الخاص:
تا دا!
خاتمة
كالعادة ، لا يوجد حل "أفضل" ، فقط الحلول التي قد تعمل بشكل أفضل حسب السياق. نهج متعدد المونوريبو ليس مناسبًا لكل نوع من المشاريع أو الفريق. أعتقد أن أكبر المستفيدين سيكونون منشئو المكونات الإضافية الذين يطلقون المكونات الإضافية العامة التي ستتم ترقيتها إلى إصداراتهم الاحترافية ، بالإضافة إلى الوكالات التي تخصص المكونات الإضافية لعملائها.
في حالتي ، أنا سعيد جدًا بهذا النهج. يستغرق القيام بذلك بشكل صحيح بعض الوقت والجهد ، لكنه استثمار لمرة واحدة. بمجرد انتهاء الإعداد ، يمكنني التركيز على بناء المكونات الإضافية الاحترافية ، وقد يكون الوقت الموفر في إدارة المشروع ضخمًا.