Membuat Multi-Monorepo Publik/Swasta Untuk Proyek PHP
Diterbitkan: 2022-03-10Untuk membuat pengalaman pengembangan saya lebih cepat, saya memindahkan semua paket PHP yang diperlukan oleh proyek saya ke monorepo. Ketika setiap paket di-host di repositorinya sendiri (pendekatan multirepo), saya perlu mengembangkan dan mengujinya sendiri dan kemudian mempublikasikannya ke Packagist sebelum saya dapat menginstalnya di paket lain melalui Composer. Dengan monorepo, karena semua paket dihosting bersama, mereka dapat dikembangkan, diuji, diversi, dan dirilis pada saat yang bersamaan.
Monorepo yang menghosting paket PHP saya bersifat publik, dapat diakses oleh siapa saja di GitHub. Repositori Git tidak dapat memberikan akses yang berbeda ke aset yang berbeda; itu semua baik publik atau swasta. Karena saya berencana untuk merilis plugin WordPress pro, saya ingin paketnya dirahasiakan, artinya mereka tidak dapat ditambahkan ke monorepo publik.
Solusi yang saya temukan adalah dengan menggunakan pendekatan "multi-monorepo", yang terdiri dari dua monorepo: satu publik dan satu pribadi, dengan monorepo pribadi menyematkan yang publik sebagai submodul Git, memungkinkannya untuk mengakses file-nya. Monorepo publik dapat dianggap sebagai monorepo hulu, dan monorepo swasta dapat dianggap sebagai monorepo hilir.
Karena saya terus mengulangi kode saya, pengaturan repositori yang perlu saya gunakan pada setiap tahap proyek saya juga perlu ditingkatkan. Oleh karena itu, saya tidak sampai pada pendekatan multi-monorepo pada hari pertama; itu adalah proses yang berlangsung beberapa tahun dan membutuhkan cukup banyak usaha, mulai dari satu repositori, ke beberapa repositori, ke monorepo, ke, akhirnya, multi-monorepo.
Pada artikel ini, saya akan menjelaskan bagaimana saya mengatur multi-monorepo saya menggunakan Monorepo Builder, yang berfungsi untuk proyek PHP dan didasarkan pada Composer.
Menggunakan Kembali Kode di Multi-Monorepo
Monorepo publik di leoloso/PoP
adalah tempat saya menyimpan semua proyek PHP saya.
Monorepo ini berisi file alur kerja generate_plugins.yml
, yang menghasilkan beberapa plugin WordPress untuk distribusi saat saya membuat rilis di GitHub:
Konfigurasi alur kerja tidak di-hardcode dalam file YAML, melainkan disuntikkan melalui kode PHP:
- id: output_data run: | echo "::set-output name=plugin_config_entries::$(vendor/bin/monorepo-builder plugin-config-entries-json)"
Dan konfigurasi disediakan melalui kelas PHP khusus:
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', ], ]; } }
Membuat beberapa plugin WordPress secara bersamaan, dan mengonfigurasi alur kerja melalui PHP, telah mengurangi jumlah waktu yang saya perlukan untuk mengelola proyek. Alur kerja saat ini menangani dua plugin (API GraphQL dan demo ekstensinya), tetapi dapat menangani 200 tanpa usaha tambahan dari saya.
Pengaturan inilah yang ingin saya gunakan kembali untuk monorepo pribadi saya di leoloso/GraphQLAPI-PRO
, sehingga plugin pro juga dapat dibuat tanpa usaha.
Kode yang akan digunakan kembali akan terdiri dari:
- alur kerja GitHub Actions untuk menghasilkan plugin WordPress (termasuk pelingkupan, penurunan versi dari PHP 8.0 ke 7.1, dan mengunggah ke halaman rilis).
- layanan PHP kustom untuk mengonfigurasi alur kerja.
Monorepo pribadi kemudian dapat menghasilkan plugin WordPress pro hanya dengan memicu alur kerja dari monorepo publik dan mengesampingkan konfigurasinya di PHP.
Menautkan Monorepos Melalui Submodul Git
Untuk menyematkan repositori publik di repositori pribadi, kami menggunakan submodul Git:
git submodule add <public repo URL>
Saya menyematkan repositori publik di subfolder submodules
monorepo pribadi, memungkinkan saya untuk menambahkan lebih banyak monorepo hulu di masa mendatang jika diperlukan. Di GitHub, folder tersebut menampilkan komit khusus submodule, dan mengkliknya akan membawa saya ke komit itu di leoloso/PoP
:
Karena repositori pribadi berisi submodul, untuk mengkloningnya, kita harus menyediakan opsi --recursive
:
git clone --recursive <private repo URL>
Menggunakan kembali Alur Kerja Tindakan GitHub
GitHub Actions hanya memuat alur kerja dari bawah .github/workflows
. Karena alur kerja publik di monorepo hilir berada di bawah submodules/PoP/.github/workflows
, ini harus diduplikasi di lokasi yang diharapkan.
Untuk menjaga alur kerja upstream sebagai satu-satunya sumber kebenaran, kita dapat membatasi diri untuk menyalin file downstream di bawah .github/workflows
, tetapi tidak pernah mengeditnya di sana. Jika ada perubahan yang harus dilakukan, itu harus dilakukan di monorepo hulu dan kemudian disalin.
Sebagai catatan tambahan, perhatikan bagaimana ini berarti kebocoran multi-monorepo: Monorepo hulu tidak sepenuhnya otonom, dan perlu disesuaikan agar sesuai dengan monorepo hilir.
Dalam iterasi pertama saya untuk menyalin alur kerja, saya membuat skrip Komposer sederhana:
{ "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');\"" ] } }
Kemudian, setelah mengedit alur kerja di monorepo hulu, saya akan menyalinnya ke hilir dengan menjalankan yang berikut:
composer copy-workflows
Tetapi kemudian saya menyadari bahwa menyalin alur kerja saja tidak cukup: Mereka juga harus dimodifikasi dalam prosesnya. Ini karena memeriksa monorepo hilir memerlukan opsi --recurse-submodules
untuk juga memeriksa submodul.
Di GitHub Actions, checkout downstream dilakukan seperti ini:
- uses: actions/checkout@v2 with: submodules: recursive
Jadi, memeriksa repositori downstream memerlukan submodules: recursive
, tetapi yang upstream tidak, dan keduanya menggunakan file sumber yang sama.
Solusi yang saya temukan adalah memberikan nilai untuk submodules
input melalui variabel lingkungan CHECKOUT_SUBMODULES
, yang secara default kosong untuk repositori upstream:
env: CHECKOUT_SUBMODULES: "" jobs: provide_data: steps: - uses: actions/checkout@v2 with: submodules: ${{ env.CHECKOUT_SUBMODULES }}
Kemudian, saat menyalin alur kerja dari hulu ke hilir, nilai CHECKOUT_SUBMODULES
diganti dengan recursive
:
env: CHECKOUT_SUBMODULES: "recursive"
Saat memodifikasi alur kerja, sebaiknya gunakan ekspresi reguler (regex), sehingga berfungsi untuk format berbeda dalam file sumber (seperti CHECKOUT_SUBMODULES: ""
atau CHECKOUT_SUBMODULES:''
atau CHECKOUT_SUBMODULES:
). Ini akan mencegah bug dibuat untuk jenis perubahan yang seolah-olah tidak berbahaya ini.
Dengan demikian, skrip Komposer copy-workflows
ditunjukkan di atas tidak cukup baik untuk menangani kerumitan ini.
Dalam iterasi berikutnya, saya membuat perintah PHP, CopyUpstreamMonorepoFilesCommand
, untuk dieksekusi melalui Monorepo Builder:
vendor/bin/monorepo-builder copy-upstream-monorepo-files
Perintah ini menggunakan layanan khusus, FileCopierSystem
, untuk menyalin semua file dari folder sumber ke tujuan yang ditentukan, sambil secara opsional mengganti kontennya:
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); } } }
Saat menjalankan metode ini untuk menyalin semua alur kerja di hilir, saya juga mengganti nilai 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 );
Alur kerja di generate_plugins.yml
membutuhkan penggantian tambahan. Saat plugin WordPress dibuat, kodenya diturunkan dari PHP 8.0 ke 7.1 dengan menjalankan skrip 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 }}"
Di monorepo hilir, file ini akan berada di bawah submodules/PoP/ci/downgrade/downgrade_code.sh
. Kemudian, kami mengarahkan alur kerja hilir ke jalur yang benar dengan penggantian ini:
$regexReplacements = [ // ... '#(ci/downgrade/downgrade_code\.sh)#' => 'submodules/PoP/$1', ];
Mengonfigurasi Paket di Monorepo Builder
File monorepo-builder.php
— ditempatkan di root monorepo — menyimpan konfigurasi untuk Monorepo Builder. Di dalamnya, kita harus menunjukkan di mana paket (dan plugin, klien, dan lainnya) berada:
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 pribadi harus memiliki akses ke semua kode: paketnya sendiri, ditambah paket dari monorepo publik. Kemudian, itu harus mendefinisikan semua paket dari kedua monorepos di file konfigurasi. Yang dari monorepo publik terletak di bawah /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', ]); };
Karenanya, konfigurasi untuk hulu dan hilir hampir sama, perbedaannya adalah bahwa yang hilir akan:
- ubah jalur ke paket publik,
- tambahkan paket pribadi.
Jadi, masuk akal untuk menulis ulang konfigurasi menggunakan pemrograman berorientasi objek (OOP). Mari ikuti prinsip KERING (“jangan ulangi diri Anda sendiri”) dengan membuat kelas PHP di repositori publik diperluas di repositori pribadi.
Membuat Ulang Konfigurasi Melalui OOP
Mari kita refactor konfigurasi. Di repositori publik, file monorepo-builder.php
hanya akan mereferensikan kelas baru, ContainerConfigurationService
, di mana semua tindakan akan terjadi:
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(); };
Parameter __DIR__
menunjuk ke akar monorepo. Ini akan diperlukan untuk mendapatkan path lengkap ke direktori paket.
Kelas ContainerConfigurationService
sekarang bertanggung jawab untuk menghasilkan konfigurasi:
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); } }
Konfigurasi dapat dibagi menjadi beberapa kelas. Dalam hal ini, ContainerConfigurationService
mengambil konfigurasi paket melalui kelas PackageOrganizationDataSource
, yang implementasinya dapat Anda lihat:
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', ]; } }
Mengganti Konfigurasi di Monorepo Hilir
Sekarang konfigurasi di monorepo publik telah diatur menggunakan OOP, kami dapat memperluasnya agar sesuai dengan kebutuhan monorepo pribadi.
Untuk mengizinkan monorepo pribadi memuat otomatis kode PHP dari monorepo publik, pertama-tama kita harus mengonfigurasi file composer.json
hilir untuk mereferensikan kode sumber dari hulu, yang berada di bawah jalur submodules/PoP/src
:
{ "autoload": { "psr-4": { "PoP\\GraphQLAPIPRO\\": "src", "PoP\\PoP\\": "submodules/PoP/src" } } }
Di bawah ini adalah file monorepo-builder.php
untuk monorepo pribadi. Perhatikan bahwa kelas yang direferensikan ContainerConfigurationService
dalam repositori upstream adalah milik namespace PoP\PoP
tetapi sekarang telah dialihkan ke namespace PoP\GraphQLAPIPRO
. Kelas ini harus menerima input tambahan $upstreamRelativeRootPath
(dengan nilai submodules/PoP
) untuk membuat ulang path lengkap ke paket publik:
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(); };
Kelas hilir ContainerConfigurationService
menimpa kelas PackageOrganizationDataSource
mana yang digunakan dalam konfigurasi:
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 ); } }
Terakhir, kelas hilir PackageOrganizationDataSource
berisi path lengkap ke paket publik dan pribadi:
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', ] ); } }
Menyuntikkan Konfigurasi Dari PHP Ke Tindakan GitHub
Monorepo Builder menawarkan perintah packages-json
, yang dapat kita gunakan untuk menyuntikkan jalur paket ke dalam alur kerja 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 }}
Perintah ini menghasilkan JSON string. Dalam alur kerja, itu harus dikonversi ke objek JSON melalui fromJson
:
jobs: split_monorepo: needs: provide_data strategy: matrix: package: ${{ fromJson(needs.provide_data.outputs.matrix) }}
Sayangnya, perintah packages-json
menampilkan nama paket tetapi bukan jalurnya. Ini akan berfungsi jika semua paket berada di folder yang sama (seperti packages/
), tetapi tidak berfungsi dalam kasus kami karena paket publik dan pribadi terletak di folder yang berbeda.
Untungnya, Monorepo Builder dapat diperluas dengan layanan PHP khusus. Jadi, saya membuat perintah khusus, package-entries-json
(melalui kelas PackageEntriesJsonCommand
), yang menampilkan path ke package.json.
Alur kerja kemudian diperbarui dengan perintah baru:
run: | echo "::set-output name=matrix::$(vendor/bin/monorepo-builder package-entries-json)"
Dieksekusi pada monorepo publik, ini menghasilkan paket-paket berikut (di antara banyak lainnya):
[ { "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" } ]
Dieksekusi pada monorepo pribadi, ini menghasilkan entri berikut (di antara banyak lainnya):
[ { "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" } ]
Ini bekerja cukup baik. Konfigurasi untuk monorepo hilir berisi paket publik dan privat, dan jalur ke paket publik diawali dengan submodules/PoP
.
Melewatkan Paket Umum di Monorepo Hilir
Sejauh ini, monorepo hilir mencakup paket publik dan privat dalam konfigurasinya. Namun, tidak setiap perintah perlu dieksekusi pada paket publik.
Ambil analisis statis, misalnya. Monorepo publik sudah mengeksekusi PHPStan pada semua paket publik melalui file alur kerja phpstan.yml
, seperti yang ditunjukkan pada run ini. Jika monorepo hilir menjalankan PHPStan sekali lagi pada paket publik, itu akan membuang-buang waktu komputasi. Alur kerja phpstan.yml
hanya perlu dijalankan pada paket pribadi.
Ini berarti bahwa, bergantung pada perintah yang akan dieksekusi di repositori downstream, kita mungkin ingin menyertakan paket publik dan privat atau hanya paket privat.
Untuk menentukan apakah akan menambahkan paket publik dalam konfigurasi downstream, kami mengadaptasi kelas PackageOrganizationDataSource
untuk memeriksa kondisi ini melalui input $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', ] ); } }
Selanjutnya, kita perlu memberikan nilai $includeUpstreamPackages
sebagai true
atau false
, tergantung pada perintah yang akan dieksekusi.
Kita dapat melakukannya dengan mengganti file konfigurasi monorepo-builder.php
dengan dua file konfigurasi lainnya: monorepo-builder-with-upstream-packages.php
(yang melewati $includeUpstreamPackages
=> true
) dan monorepo-builder-without-upstream-packages.php
(yang melewati $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(); };
Kami kemudian memperbarui ContainerConfigurationService
untuk menerima parameter $includeUpstreamPackages
dan meneruskannya ke 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, ); } }
Selanjutnya, kita harus memanggil monorepo-builder
dengan salah satu file konfigurasi, dengan menyediakan opsi --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)"
Namun, seperti yang kita lihat sebelumnya, kami ingin mempertahankan alur kerja Tindakan GitHub di monorepo hulu sebagai satu-satunya sumber kebenaran, dan mereka jelas tidak memerlukan perubahan ini.
Solusi yang saya temukan untuk masalah ini adalah selalu menyediakan opsi --config
di repositori upstream, dengan setiap perintah mendapatkan file konfigurasinya sendiri, seperti perintah validate
yang menerima file konfigurasi validate.php
:
- name: Run validation run: vendor/bin/monorepo-builder validate --config=config/monorepo-builder/validate.php
Sekarang, tidak ada file konfigurasi di monorepo upstream, karena tidak membutuhkannya. Tapi itu tidak akan rusak, karena Monorepo Builder memeriksa apakah ada file konfigurasi, dan jika tidak, ia akan memuat file konfigurasi default. Jadi, apakah kita akan menimpa konfigurasi atau tidak akan terjadi apa-apa.
Repositori downstream menyediakan file konfigurasi untuk setiap perintah, menentukan apakah akan menambahkan paket upstream:
Sebagai catatan tambahan, ini adalah contoh lain bagaimana kebocoran multi-monorepo.
// File config/monorepo-builder/validate.php return require_once __DIR__ . '/monorepo-builder-with-upstream-packages.php';
Mengganti Konfigurasi
Kami hampir selesai. Saat ini, monorepo hilir dapat mengesampingkan konfigurasi dari monorepo hulu. Jadi, yang tersisa untuk dilakukan adalah menyediakan konfigurasi baru.
Di kelas PluginDataSource
, saya mengganti konfigurasi plugin WordPress mana yang harus dibuat, sebagai gantinya menyediakan yang pro:
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', ], ]; } }
Membuat rilis baru di GitHub akan memicu alur kerja generate_plugins.yml
dan akan menghasilkan plugin pro di monorepo pribadi saya:
Ta-da!
Kesimpulan
Seperti biasa, tidak ada solusi "terbaik", hanya solusi yang mungkin bekerja lebih baik tergantung pada konteksnya. Pendekatan multi-monorepo tidak cocok untuk setiap jenis proyek atau tim. Saya percaya penerima manfaat terbesar adalah pembuat plugin yang merilis plugin publik yang akan ditingkatkan ke versi pro mereka, serta agensi yang menyesuaikan plugin untuk klien mereka.
Dalam kasus saya, saya cukup senang dengan pendekatan ini. Melakukannya dengan benar membutuhkan sedikit waktu dan usaha, tetapi ini adalah investasi satu kali. Setelah penyiapan selesai, saya dapat fokus membangun plugin pro saya, dan waktu yang dihemat dengan manajemen proyek bisa sangat besar.