为 PHP 项目创建一个公共/私有的多单体存储库
已发表: 2022-03-10为了让我的开发体验更快,我将项目所需的所有 PHP 包都移到了 monorepo。 当每个包都托管在自己的存储库中时(多存储库方法),我需要自己开发和测试它,然后将它发布到 Packagist,然后我才能通过 Composer 将它安装到其他包中。 使用 monorepo,因为所有包都托管在一起,所以它们可以同时开发、测试、版本控制和发布。
托管我的 PHP 包的 monorepo 是公开的,GitHub 上的任何人都可以访问。 Git 存储库不能授予对不同资产的不同访问权限; 这都是公共的或私人的。 因为我计划发布一个专业的 WordPress 插件,我希望它的包是私有的,这意味着它们不能被添加到公共 monorepo。
我找到的解决方案是使用“multi-monorepo”方法,包括两个monorepo:一个公共的和一个私有的,私有的monorepo 将公共的monorepo 嵌入为Git 子模块,允许它访问其文件。 公共的monorepo可以被认为是上游的,私有的monorepo可以被认为是下游的。
当我不断迭代我的代码时,我需要在项目的每个阶段使用的存储库设置也需要升级。 因此,我并没有在第一天就采用多单体方案。 这是一个跨越数年的过程,花费了相当多的努力,从单个存储库到多个存储库,再到 monorepo,最后到 multi-monorepo。
在本文中,我将描述如何使用 Monorepo Builder 设置我的 multi-monorepo,它适用于 PHP 项目并且基于 Composer。
在 Multi-Monorepo 中重用代码
leoloso/PoP
的公共 monorepo 是我保存所有 PHP 项目的地方。
这个 monorepo 包含工作流文件generate_plugins.yml
,当我在 GitHub 上创建发布时,它会生成多个 WordPress 插件以供分发:
工作流配置不是硬编码在 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 API 及其扩展演示),但它可以处理 200 个插件,而无需我付出额外的努力。
我想在leoloso/GraphQLAPI-PRO
为我的私人 monorepo 重用这个设置,这样也可以毫不费力地生成专业插件。
要重用的代码将包括:
- 用于生成 WordPress 插件的 GitHub Actions 工作流程(包括范围界定、从 PHP 8.0 降级到 7.1 以及上传到发布页面)。
- 自定义 PHP 服务来配置工作流。
然后,私有 monorepo 可以简单地通过从公共 monorepo 触发工作流并覆盖它们在 PHP 中的配置来生成 pro WordPress 插件。
通过 Git 子模块链接 Monorepos
要将公共存储库嵌入私有存储库,我们使用 Git 子模块:
git submodule add <public repo URL>
我将公共存储库嵌入到私有 monorepo 的子文件夹子submodules
中,如果需要,我可以在将来添加更多上游 monorepos。 在 GitHub 中,该文件夹显示子模块的特定提交,单击它将带我到leoloso/PoP
的提交:
因为私有存储库包含子模块,所以要克隆它,我们必须提供--recursive
选项:
git clone --recursive <private repo URL>
重用 GitHub Actions 工作流
GitHub Actions 仅从.github/workflows
下加载工作流。 由于下游 monorepo 中的公共工作流位于submodules/PoP/.github/workflows
下,因此必须在预期位置复制这些工作流。
为了保持上游工作流作为唯一的事实来源,我们可以限制自己在下游复制.github/workflows
下的文件,但永远不要在那里编辑它们。 如果要进行任何更改,必须在上游 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 Actions 中,下游的结帐是这样完成的:
- uses: actions/checkout@v2 with: submodules: recursive
因此,检查下游存储库需要输入submodules: recursive
,但上游不需要,它们都使用相同的源文件。
我找到的解决方案是通过环境变量CHECKOUT_SUBMODULES
为输入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-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 插件时,通过调用脚本ci/downgrade/downgrade_code.sh
其代码从 PHP 8.0 降级到 7.1:
- 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) 重写配置是有意义的。 让我们通过在私有存储库中扩展公共存储库中的 PHP 类来遵循 DRY 原则(“不要重复自己”)。
通过 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 中的配置
既然已经使用 OOP 设置了公共 monorepo 中的配置,我们可以对其进行扩展以满足私有 monorepo 的需求。
为了让私有 monorepo 自动加载公共 monorepo 中的 PHP 代码,我们必须首先配置下游的composer.json
文件以引用来自上游的源代码,该文件位于路径submodules/PoP/src
下:
{ "autoload": { "psr-4": { "PoP\\GraphQLAPIPRO\\": "src", "PoP\\PoP\\": "submodules/PoP/src" } } }
下面是私有 monorepo 的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 Actions
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。 在工作流程中,必须通过fromJson
将其转换为 JSON 对象:
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.yml
在所有公共包上执行 PHPStan,如本次运行所示。 如果下游的 monorepo 再次在公共包上运行 PHPStan,那将是浪费计算时间。 phpstan.yml
工作流只需要在私有包上运行。
这意味着,根据要在下游存储库中执行的命令,我们可能希望同时包含公共包和私有包,或者只包含私有包。
为了确定是否在下游配置中添加公共包,我们通过输入$includeUpstreamPackages
调整下游类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, 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
(通过$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, ); } }
接下来,我们必须通过提供--config
选项,使用任一配置文件调用monorepo-builder
:
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)"
但是,正如我们之前看到的,我们希望将上游 monorepo 中的 GitHub Actions 工作流作为唯一的事实来源,它们显然不需要这些更改。
我发现这个问题的解决方案是始终在上游存储库中提供一个--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 插件的配置,而是提供了 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', ], ]; } }
在 GitHub 上创建新版本将触发generate_plugins.yml
工作流程,并将在我的私有 monorepo 中生成专业插件:
达达!
结论
与往常一样,没有“最佳”解决方案,只有根据具体情况可能效果更好的解决方案。 multi-monorepo 方法并不适合所有类型的项目或团队。 我相信最大的受益者将是发布公共插件并将升级到专业版的插件创建者,以及为其客户定制插件的代理机构。
就我而言,我对这种方法非常满意。 把它做好需要一点时间和精力,但这是一次性的投资。 设置完成后,我可以专注于构建我的专业插件,项目管理节省的时间可能是巨大的。