PHP Projeleri İçin Genel/Özel Çoklu Monorepo Oluşturma

Yayınlanan: 2022-03-10
Kısa özet ↬ PHP paketlerinizi gizli tutarken geliştirme deneyimini daha hızlı hale getirmek için bir "çoklu monorepo" yaklaşımının nasıl kullanılacağını görelim. Bu çözüm, özellikle PRO eklenti yaratıcıları için faydalı olabilir.

Geliştirme deneyimimi daha hızlı hale getirmek için projelerimin gerektirdiği tüm PHP paketlerini bir monorepoya taşıdım. Her paket kendi deposunda barındırıldığında (çoklu repo yaklaşımı), bunu kendi başına geliştirmem ve test etmem ve ardından Composer aracılığıyla diğer paketlere kurmadan önce Packagist'te yayınlamam gerekir. Monorepo ile tüm paketler bir arada barındırıldığı için aynı anda geliştirilebilir, test edilebilir, versiyonlanabilir ve yayınlanabilir.

PHP paketlerimi barındıran monorepo herkese açıktır ve GitHub'daki herkes tarafından erişilebilir. Git depoları, farklı varlıklara farklı erişim izni veremez; hepsi ya genel ya da özel. Profesyonel bir WordPress eklentisi yayınlamayı planladığım için paketlerinin gizli tutulmasını istiyorum, yani genel monorepoya eklenemezler.

Bulduğum çözüm, iki monorepo içeren bir "çoklu monorepo" yaklaşımı kullanmaktır: bir genel ve bir özel, özel monorepo, genel olanı Git alt modülü olarak gömerek dosyalarına erişmesine izin verir. Kamu monoreposu yukarı yönlü, özel monorepo ise aşağı yönlü olarak kabul edilebilir.

Çoklu monorepo mimarisi
Çoklu monorepo mimarisi. (Büyük önizleme)

Kodumu yinelemeye devam ederken, projemin her aşamasında kullanmam gereken depo kurulumunun da yükseltilmesi gerekiyordu. Bu nedenle, ilk gün çoklu monorepo yaklaşımına ulaşmadım; bu, birkaç yıla yayılan ve tek bir depodan çok sayıda depoya, monorepoya ve nihayet çoklu monorepoya giden, oldukça fazla çaba gerektiren bir süreçti.

Bu yazımda PHP projeleri için çalışan ve Composer tabanlı Monorepo Builder kullanarak multi-monorepomu nasıl kurduğumu anlatacağım.

Atlamadan sonra daha fazlası! Aşağıdan okumaya devam edin ↓

Multi-Monorepo'da Kodu Yeniden Kullanma

leoloso/PoP PoP'daki genel monorepo, tüm PHP projelerimi sakladığım yerdir.

Bu monorepo, GitHub'da bir sürüm oluşturduğumda dağıtım için birden çok WordPress eklentisi generate_plugins.yml create_plugins.yml iş akışı dosyasını içeriyor:

Bir sürüm oluştururken eklentiler oluşturma
Bir sürüm oluştururken eklentiler oluşturma. (Büyük önizleme)

İş akışı yapılandırması, YAML dosyasında sabit kodlanmamıştır, bunun yerine PHP kodu aracılığıyla enjekte edilmiştir:

 - id: output_data run: | echo "::set-output name=plugin_config_entries::$(vendor/bin/monorepo-builder plugin-config-entries-json)"

Ve yapılandırma, özel bir PHP sınıfı aracılığıyla sağlanır:

 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', ], ]; } }

Birden çok WordPress eklentisini bir arada oluşturmak ve iş akışını PHP aracılığıyla yapılandırmak, projeyi yönetmek için ihtiyaç duyduğum süreyi azalttı. İş akışı şu anda iki eklentiyi (GraphQL API'si ve onun uzantı demosu) idare ediyor, ancak benden ek bir çaba harcamadan 200'ü işleyebilir.

Bu kurulum, leoloso/GraphQLAPI-PRO özel monorepo'm için yeniden kullanmak istiyorum, böylece profesyonel eklentiler de çaba harcamadan oluşturulabilir.

Yeniden kullanılacak kod şunları içerecektir:

  • WordPress eklentilerini oluşturmak için GitHub Eylemleri iş akışları (kapsam belirleme, PHP 8.0'dan 7.1'e düşürme ve sürümler sayfasına yükleme dahil).
  • iş akışlarını yapılandırmak için özel PHP hizmetleri.

Özel monorepo, daha sonra genel monorepodan iş akışlarını tetikleyerek ve PHP'deki yapılandırmalarını geçersiz kılarak profesyonel WordPress eklentilerini oluşturabilir.

Monorepoları Git Alt Modülleri Üzerinden Bağlama

Genel depoyu özel depoya gömmek için Git alt modüllerini kullanırız:

 git submodule add <public repo URL>

Genel depoyu, özel monorepo'nun alt klasör alt submodules yerleştirdim ve gerekirse gelecekte daha fazla yukarı akış monorepo eklememe izin verdim. GitHub'da, klasör alt modülün belirli taahhüdünü görüntüler ve üzerine tıklamak beni leoloso/PoP o taahhüde götürecektir:

Genel monorepoyu özel monorepoya gömmek
Genel monorepoyu özel monorepoya gömmek. (Büyük önizleme)

Özel depo alt modüller içerdiğinden, onu klonlamak için --recursive seçeneğini sağlamalıyız:

 git clone --recursive <private repo URL>

GitHub Eylemleri İş Akışlarını Yeniden Kullanma

GitHub Eylemleri, yalnızca .github/workflows altındaki iş akışlarını yükler. Downstream monorepo'daki genel iş akışları submodules/PoP/.github/workflows altında olduğundan, bunların beklenen konumda çoğaltılması gerekir.

Yukarı akış iş akışlarını tek gerçek kaynak olarak tutmak için, kendimizi .github/workflows altındaki dosyaları aşağı akışa kopyalamakla sınırlayabiliriz, ancak onları asla orada düzenlemeyebiliriz. Herhangi bir değişiklik yapılacaksa, yukarı akış monoreposunda yapılmalı ve ardından kopyalanmalıdır.

Bir yan not olarak, bunun nasıl çoklu monorepo sızıntısı anlamına geldiğine dikkat edin: Yukarı akış monorepo tamamen özerk değildir ve aşağı akış monorepo'ya uyacak şekilde uyarlanması gerekecektir.

İş akışlarını kopyalamak için ilk yinelememde basit bir Besteci komut dosyası oluşturdum:

 { "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');\"" ] } }

Ardından, yukarı akış monoreposunda iş akışlarını düzenledikten sonra, aşağıdakileri yürüterek bunları aşağı akışa kopyalardım:

 composer copy-workflows

Ama sonra sadece iş akışlarını kopyalamanın yeterli olmadığını fark ettim: Süreç içinde onların da değiştirilmesi gerekiyor. Bunun nedeni, aşağı akış monoreposunun kontrol edilmesinin, alt modülleri de kontrol etmek için --recurse-submodules seçeneğini gerektirmesidir.

GitHub Eylemlerinde, aşağı akış şu şekilde yapılır:

 - uses: actions/checkout@v2 with: submodules: recursive

Bu nedenle, aşağı akış deposunu kontrol etmek için giriş submodules: recursive , ancak yukarı akış havuzuna ihtiyaç duymaz ve ikisi de aynı kaynak dosyayı kullanır.

Bulduğum çözüm, giriş submodules değerini, yukarı akış deposu için varsayılan olarak boş olan CHECKOUT_SUBMODULES ortam değişkeni aracılığıyla sağlamaktır:

 env: CHECKOUT_SUBMODULES: "" jobs: provide_data: steps: - uses: actions/checkout@v2 with: submodules: ${{ env.CHECKOUT_SUBMODULES }}

Ardından, iş akışlarını yukarı akıştan aşağı akışa kopyalarken, CHECKOUT_SUBMODULES değeri recursive ile değiştirilir:

 env: CHECKOUT_SUBMODULES: "recursive"

İş akışını değiştirirken, kaynak dosyadaki farklı biçimlerde çalışması için normal bir ifade (regex) kullanmak iyi bir fikirdir (örneğin CHECKOUT_SUBMODULES: "" veya CHECKOUT_SUBMODULES:'' veya CHECKOUT_SUBMODULES: ). Bu, görünüşte zararsız bu tür değişiklikler için hataların oluşmasını önleyecektir.

Bu nedenle, yukarıda gösterilen copy-workflows Besteci betiği bu karmaşıklığın üstesinden gelmek için yeterince iyi değil.

Bir sonraki yinelememde, Monorepo Builder aracılığıyla yürütülmek üzere CopyUpstreamMonorepoFilesCommand adlı bir PHP komutu oluşturdum:

 vendor/bin/monorepo-builder copy-upstream-monorepo-files

Bu komut, isteğe bağlı olarak içeriklerini değiştirirken tüm dosyaları bir kaynak klasörden belirtilen hedefe kopyalamak için özel bir hizmet olan FileCopierSystem kullanır:

 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); } } }

Tüm iş akışlarını aşağı yönde kopyalamak için bu yöntemi çağırırken, CHECKOUT_SUBMODULES değerini de değiştiririm:

 /** * 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 );

create_plugins.yml içindeki generate_plugins.yml akışının ek olarak değiştirilmesi gerekiyor. WordPress eklentisi oluşturulduğunda, kodu ci/downgrade/downgrade_code.sh komut dosyası çağrılarak PHP 8.0'dan 7.1'e düşürülür:

 - 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 }}"

Downstream monorepoda, bu dosya submodules/PoP/ci/downgrade/downgrade_code.sh altında yer alacaktır. Ardından, bu değiştirme ile aşağı akış iş akışını doğru yola yönlendiririz:

 $regexReplacements = [ // ... '#(ci/downgrade/downgrade_code\.sh)#' => 'submodules/PoP/$1', ];

Monorepo Builder'da Paketleri Yapılandırma

Monorepo'nun köküne yerleştirilen monorepo-builder.php dosyası, Monorepo Builder yapılandırmasını tutar. İçinde paketlerin (ve eklentilerin, istemcilerin ve diğer her şeyin) nerede olduğunu belirtmeliyiz:

 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', ]); };

Özel monorepo'nun tüm kodlara erişimi olmalıdır: kendi paketleri artı genel monorepodan olanlar. Ardından, yapılandırma dosyasındaki her iki monorepodan tüm paketleri tanımlamalıdır. Genel monorepodan olanlar /submodules/PoP altında bulunur:

 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', ]); };

Olduğu gibi, yukarı akış ve aşağı akış için konfigürasyonlar hemen hemen aynıdır, farklar aşağı yöndekinin yapacağıdır:

  • genel paketlerin yolunu değiştirin,
  • özel paketleri ekleyin.

Bu nedenle, nesne yönelimli programlama (OOP) kullanarak yapılandırmayı yeniden yazmak mantıklıdır. Genel depoda bir PHP sınıfının özel depoda genişletilmesini sağlayarak DRY ilkesini (“kendini tekrar etme”) izleyelim.

OOP Üzerinden Yapılandırmayı Yeniden Oluşturma

Yapılandırmayı yeniden düzenleyelim. Genel depoda, monorepo-builder.php dosyası, tüm eylemin gerçekleşeceği ContainerConfigurationService adlı yeni bir sınıfa başvuracaktır:

 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__ parametresi, monorepo'nun kökünü gösterir. Paket dizinlerinin tam yolunu elde etmek gerekecektir.

ContainerConfigurationService sınıfı artık yapılandırmayı üretmekten sorumludur:

 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); } }

Yapılandırma birkaç sınıfa bölünebilir. Bu durumda, ContainerConfigurationService , uygulamasını görebileceğiniz PackageOrganizationDataSource sınıfı aracılığıyla paket yapılandırmasını alır:

 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', ]; } }

Aşağı Yöndeki Monorepo'daki Yapılandırmayı Geçersiz Kılma

Artık genel monorepodaki konfigürasyon OOP kullanılarak kurulduğuna göre, onu özel monorepo'nun ihtiyaçlarına uyacak şekilde genişletebiliriz.

Özel monorepo'nun genel monorepo'dan PHP kodunu otomatik olarak yüklemesine izin vermek için, önce downstream composer.json dosyasını, submodules/PoP/src yolu altındaki upstream'den kaynak kodu referans alacak şekilde yapılandırmamız gerekir:

 { "autoload": { "psr-4": { "PoP\\GraphQLAPIPRO\\": "src", "PoP\\PoP\\": "submodules/PoP/src" } } }

Aşağıda özel monorepo için monorepo-builder.php dosyası bulunmaktadır. Yukarı akış deposunda başvurulan sınıf ContainerConfigurationService PoP\PoP ad alanına ait olduğuna, ancak şimdi PoP\GraphQLAPIPRO ad alanına değiştirildiğine dikkat edin. Bu sınıf, genel paketlerin tam yolunu yeniden oluşturmak için $upstreamRelativeRootPath ( submodules/PoP değeriyle) ek girdisini almalıdır:

 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(); };

Aşağı akış sınıfı ContainerConfigurationService , yapılandırmada hangi PackageOrganizationDataSource sınıfının kullanıldığını geçersiz kılar:

 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 ); } }

Son olarak, aşağı akış sınıfı PackageOrganizationDataSource hem genel hem de özel paketlerin tam yolunu içerir:

 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', ] ); } }

Yapılandırmayı PHP'den GitHub Eylemlerine Enjekte Etme

Monorepo Builder, paket yollarını GitHub Eylemleri iş akışına eklemek için kullanabileceğimiz package packages-json komutunu sunar:

 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 }}

Bu komut, dizilenmiş bir JSON üretir. İş akışında, fromJson aracılığıyla bir JSON nesnesine dönüştürülmesi gerekir:

 jobs: split_monorepo: needs: provide_data strategy: matrix: package: ${{ fromJson(needs.provide_data.outputs.matrix) }}

Ne yazık ki, package packages-json komutu paket adlarını verir ancak yollarını vermez. Bu, tüm paketler aynı klasör altında olsaydı ( packages/ gibi) işe yarar, ancak bizim durumumuzda işe yaramaz çünkü genel ve özel paketler farklı klasörlerde bulunur.

Neyse ki, Monorepo Builder özel PHP hizmetleriyle genişletilebilir. Bu yüzden, paketin yolunu veren package-entries-json ( PackageEntriesJsonCommand sınıfı aracılığıyla) özel bir komut oluşturdum.

İş akışı daha sonra yeni komutla güncellendi:

 run: | echo "::set-output name=matrix::$(vendor/bin/monorepo-builder package-entries-json)"

Genel monorepoda yürütüldüğünde, bu, aşağıdaki paketleri üretir (diğerlerinin yanı sıra):

 [ { "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" } ]

Özel monorepo üzerinde yürütülür, aşağıdaki girdileri üretir (diğerleri arasında):

 [ { "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" } ]

Oldukça iyi çalışıyor. Downstream monorepo yapılandırması hem genel hem de özel paketleri içerir ve genel paketlere giden yollar submodules/PoP ile hazırlanır.

Downstream Monorepo'da Genel Paketleri Atlamak

Şimdiye kadar, aşağı akış monorepo, yapılandırmasında hem genel hem de özel paketleri içeriyor. Ancak, genel paketlerde her komutun yürütülmesi gerekmez.

Örneğin, statik analiz yapın. Genel monorepo, bu çalıştırmada gösterildiği gibi, phpstan.yml iş akışı dosyası aracılığıyla tüm genel paketlerde PHPStan'ı zaten yürütür. Aşağı akış monorepo, PHPStan'i bir kez daha genel paketlerde çalıştırırsa, bu bir hesaplama zamanı kaybı olur. phpstan.yml iş akışının yalnızca özel paketler üzerinde çalışması gerekir.

Bu, aşağı akış deposunda yürütülecek komuta bağlı olarak, hem genel hem de özel paketleri veya yalnızca özel paketleri dahil etmek isteyebileceğimiz anlamına gelir.

Aşağı akış yapılandırmasına genel paketler eklenip eklenmeyeceğini belirlemek için, bu koşulu $includeUpstreamPackages girişi aracılığıyla kontrol etmek için PackageOrganizationDataSource aşağı akış sınıfını uyarlarız:

 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', ] ); } }

Ardından, yürütülecek komuta bağlı olarak $includeUpstreamPackages değerini true veya false olarak sağlamamız gerekiyor.

Bunu, monorepo-builder.php yapılandırma dosyasını diğer iki yapılandırma dosyasıyla değiştirerek yapabiliriz: monorepo-builder-with-upstream-packages.php ( $includeUpstreamPackages => true geçer) ve monorepo-builder-without-upstream-packages.php ( $includeUpstreamPackages => false iletir):

 // 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(); };

Ardından ContainerConfigurationService $includeUpstreamPackages parametresini alacak ve onu PackageOrganizationDataSource şekilde güncelleriz:

 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, ); } }

Ardından, --config seçeneğini sağlayarak her iki yapılandırma dosyasıyla monorepo-builder gerekir:

 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)"

Ancak, daha önce gördüğümüz gibi, yukarı akış monoreposunda GitHub Eylemleri iş akışlarını tek gerçek kaynağı olarak tutmak istiyoruz ve açıkça bu değişikliklere ihtiyaçları yok.

Bu soruna bulduğum çözüm, her komutun validate.php yapılandırma dosyasını alan validate komutu gibi kendi yapılandırma dosyasını aldığı, yukarı akış deposunda her zaman bir --config seçeneği sağlamaktır:

 - name: Run validation run: vendor/bin/monorepo-builder validate --config=config/monorepo-builder/validate.php

Şimdi, yukarı akış monoreposunda yapılandırma dosyaları yok çünkü bunlara ihtiyacı yok. Ancak bozulmayacaktır, çünkü Monorepo Builder yapılandırma dosyasının var olup olmadığını kontrol eder ve yoksa, bunun yerine varsayılan yapılandırma dosyasını yükler. Yani ya konfigürasyonu geçersiz kılacağız ya da hiçbir şey olmayacak.

Aşağı akış deposu, yukarı akış paketlerinin eklenip eklenmeyeceğini belirterek her komut için yapılandırma dosyalarını sağlar:

Bir yan not olarak, bu, multi-monorepo'nun nasıl sızdığının başka bir örneğidir.

 // File config/monorepo-builder/validate.php return require_once __DIR__ . '/monorepo-builder-with-upstream-packages.php';

Yapılandırmayı Geçersiz Kılma

Neredeyse bitirdik. Şimdiye kadar, aşağı akış monorepo, yukarı akış monorepodan yapılandırmayı geçersiz kılabilir. Bu nedenle, yapılması gereken tek şey yeni yapılandırmayı sağlamaktır.

PluginDataSource sınıfında, WordPress eklentilerinin oluşturulması gereken yapılandırmayı geçersiz kıldım ve bunun yerine profesyonel olanları sağladım:

 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', ], ]; } }

GitHub'da yeni bir sürüm generate_plugins.yml , create_plugins.yml iş akışını tetikleyecek ve özel monorepomda profesyonel eklentiler oluşturacak:

Profesyonel eklentiler oluşturma
Profesyonel eklentiler oluşturma. (Büyük önizleme)

Ta-da!

Çözüm

Her zaman olduğu gibi, "en iyi" çözüm yoktur, yalnızca bağlama bağlı olarak daha iyi sonuç verebilecek çözümler vardır. Çoklu monorepo yaklaşımı her tür proje veya takıma uygun değildir. En büyük yararlanıcıların, profesyonel sürümlerine yükseltilecek genel eklentileri yayınlayan eklenti yaratıcılarının yanı sıra müşterileri için eklentileri özelleştiren ajanslar olacağına inanıyorum.

Benim durumumda, bu yaklaşımdan oldukça memnunum. Doğru yapmak biraz zaman ve çaba gerektirir, ancak bu tek seferlik bir yatırımdır. Kurulum bittiğinde, profesyonel eklentilerimi oluşturmaya odaklanabilirim ve proje yönetimi ile kazanılan zaman çok büyük olabilir.