Fermer

août 25, 2021

Création d'un multimonorepo public/privé pour les projets PHP —


Résumé rapide ↬

Voyons comment utiliser une approche « multi-monorepo » pour rendre l'expérience de développement plus rapide, tout en gardant vos packages PHP privés. Cette solution peut être particulièrement bénéfique pour les créateurs de plugins PRO.

Pour accélérer l'expérience de développement, j'ai déplacé tous les packages PHP requis par mes projets vers un monorepo. Lorsque chaque package est hébergé sur son propre référentiel (l'approche « multidépôt »), il doit être développé et testé seul, puis publié sur Packagist avant que je puisse l'installer sur d'autres packages via Composer. Avec le monorepo, parce que tous les packages sont hébergés ensemble, ceux-ci peuvent être développés, testés, versionnés et publiés en même temps.

Le monorepo hébergeant mes packages PHP est public, accessible à tous sur GitHub. Les dépôts Git ne peuvent pas accorder un accès différent à différents actifs, tout est public ou privé. Comme je prévois de publier un plugin PRO WordPress, je souhaite que ses packages restent privés, ce qui signifie qu'ils ne peuvent pas être ajoutés au monorepo public.

La solution que j'ai trouvée est d'utiliser une approche "multi-monorepo", comprenant deux monorepos : un public et un privé, le monorepo privé incorporant le public en tant que sous-module Git, lui permettant d'accéder à ses fichiers. Le monorepo public peut être considéré comme l'« amont », et le monorepo privé l'« aval ».

Architecture d'un multi-monorepo

Architecture d'un multi-monorepo. ( Grand aperçu)

Alors que je continuais d'itérer mon code, la configuration du référentiel que je devais utiliser à chaque étape de mon projet devait également être mise à niveau. Par conséquent, je ne suis pas arrivé à l'approche multi-monorepo le jour 1, mais c'était un processus qui a duré plusieurs années et a demandé beaucoup d'efforts, passant d'un seul repo, à plusieurs repos, au monorepo , pour enfin le multi-monorepo.

Dans cet article, je vais décrire comment j'ai configuré mon multi-monorepo à l'aide du Monorepo builderqui fonctionne pour les projets PHP basés sur Composer.[19659009]Plus après le saut ! Continuez à lire ci-dessous ↓

Réutilisation du code dans le Multi-Monorepo

Le monorepo public leoloso/PoP est l'endroit où je conserve tous mes projets PHP.

Ce monorepo contient le workflow generate_plugins.ymlqui génère plusieurs plugins WordPress pour la distribution lors de la création d'une nouvelle version sur GitHub :

Génération de plugins lors de la création d'une version

Génération de plugins lors de la création d'un Libération. ( Grand aperçu)

La configuration du workflow n'est pas codée en dur dans le YAML mais injectée via le code PHP :

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

Et la configuration est fournie via une classe PHP personnalisée :

 classe PluginDataSource
{
  fonction publique getPluginConfigEntries() : tableau
  {
    retour [
      // 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',
      ],
      // API GraphQL - Démo d'extension
      [
        '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',
      ],
    ] ;
  }
}

La génération simultanée de plusieurs plugins WordPress et la configuration du flux de travail via PHP ont réduit le temps nécessaire à la gestion du projet. Le workflow gère actuellement deux plugins (l'API GraphQL et sa démo d'extension), mais il pourrait en gérer 200 sans effort supplémentaire de ma part.

C'est cette configuration que je souhaite réutiliser pour mon monorepo privé leoloso/GraphQLAPI-PROafin que les plugins PRO puissent également être générés sans effort.

Le code à réutiliser comprendra :

Le monorepo privé pourra alors générer les plugins PRO WordPress, simplement en déclenchant les workflows à partir du monorepo public et en remplaçant leur configuration dans PHP. ]git submodule add

J'ai intégré le dépôt public dans le sous-dossier submodules du monorepo privé, ce qui me permet d'ajouter plus de monorepos en amont à l'avenir si nécessaire. Dans GitHub, le dossier affiche le commit spécifique du sous-moduleet cliquer dessus me mènera à ce commit sur leoloso/PoP :

Intégration du monorepo public dans le monorepo privé[19659006]Intégration du monorepo public dans le monorepo privé. (<a href= Grand aperçu)

Comme il contient des sous-modules, pour cloner le dépôt privé, nous devons fournir l'option --recursive :

git clone --recursive 

Réutilisation des workflows d'actions GitHub

Actions GitHub uniquement charge les workflows sous .github/workflows. Étant donné que les workflows publics dans le monorepo en aval se trouvent sous submodules/PoP/.github/workflowsils doivent être dupliqués à l'emplacement prévu.

Afin de conserver les workflows en amont comme source unique de vérité, nous pouvons nous limiter à copier les fichiers en aval sous .github/workflowsmais ne jamais les éditer là-bas. S'il y a un changement à faire, il doit être fait dans le monorepo amont, puis copié. et devra être adapté pour s'adapter au monorepo en aval.

Dans ma première itération pour copier les workflows, j'ai créé un simple Script Composer :


{
  "scripts": {
    "copie-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');""
    ]
  }
}

Ensuite, après avoir modifié les workflows dans le monorepo amont, je les copie en aval en exécutant :

composer copy-workflows

Mais j'ai alors réalisé que copier les workflows ne suffisait pas : ils doivent aussi être modifié au cours du processus. C'est parce que la vérification du monorepo en aval nécessite l'option --recurse-submodulescomme pour extraire également les sous-modules.

Dans les actions GitHub, la vérification pour l'aval se fait comme ceci :

  - utilise : actions/checkout@v2
    avec:
        submodules: recursive

Donc, la vérification du repo en aval nécessite des sous-modules d'entrée : recursivemais pas en amont, et ils utilisent tous les deux le même fichier source.

La solution que j'ai trouvée est de fournir la valeur de l'entrée submodules via une variable d'environnement CHECKOUT_SUBMODULESqui est par défaut vide pour le repo amont :

env :
  CHECKOUT_SUBMODULES : ""
  
travaux:
  fournir_données :
    pas:
      - utilise : actions/checkout@v2
        avec:
          sous-modules : ${{ env.CHECKOUT_SUBMODULES }}

Ensuite, lors de la copie des workflows de l'amont vers l'aval, la valeur de CHECKOUT_SUBMODULES est remplacée par "recursive" :

 env :
  CHECKOUT_SUBMODULES : "récursif"

Lors de la modification du workflow, il est conseillé d'utiliser une expression régulière, afin qu'elle fonctionne pour différents formats dans le fichier source (comme CHECKOUT_SUBMODULES : "" ou CHECKOUT_SUBMODULES:'' ou CHECKOUT_SUBMODULES:) afin de ne pas créer de bogues à partir de ce type de modifications supposées être inoffensives.

Ensuite, les copies-workflows Le script Composer vu ci-dessus n'est pas assez bon pour gérer cette complexité.

Dans ma prochaine itération, j'ai créé une commande PHP CopyUpstreamMonorepoFilesCommandà exécuter via le générateur Monorepo :

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

Cette commande utilise un service personnalisé FileCopierSystem pour copier tous les fichiers d'un dossier source vers la destination indiquée, tout en remplaçant éventuellement leur contenu :

namespace PoP GraphQLAPIPROExtensionsSymplifyMonorepoBuilderSmartFile ;

utilisez NetteUtilsStrings ;
utilisez SymplifySmartFileSystemFinderSmartFinder ;
utilisez SymplifySmartFileSystemSmartFileSystem ;

classe finale FileCopierSystem
{
  fonction publique __construct(
    SmartFileSystem privé $smartFileSystem,
    SmartFinder privé $smartFinder,
  ) {
  }

  /**
   * @param array $patternReplacements un modèle regex à rechercher, et son remplacement
   */
  fonction publique copyFilesFromFolder(
    chaîne $fromFolder,
    chaîne $toFolder,
    tableau $patternReplacements = []
  ): annuler {
    $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);
    }
  }
}

Lors de l'appel de cette méthode pour copier tous les workflows en aval, je remplace également la valeur de CHECKOUT_SUBMODULES:

/**
 * Copiez tous les workflows dans `.github/` et convertissez :
 * `CHECKOUT_SUBMODULES : ""`
 * dans:
 * `CHECKOUT_SUBMODULES : "récursif"`
 */
$regexReplacements = [
  '#CHECKOUT_SUBMODULES:(s+".*")?#' => 'CHECKOUT_SUBMODULES: "recursive"',
];
(nouveau FileCopierSystem())->copyFilesFromFolder(
  'sous-modules/PoP/.github/workflows',
  '.github/workflows',
  $regexRemplacements
);

Workflow generate_plugins.yml nécessite un remplacement supplémentaire. Lorsque le plugin WordPress est généré, son code est rétrogradé de PHP 8.0 à 7.1 en invoquant le script ci/downgrade/downgrade_code.sh :

  - nom : rétrograder le code pour la production (vers PHP 7.1 )
    exécuter : ci/downgrade/downgrade_code.sh "${{ matrix.pluginConfig.rector_downgrade_config }}" "" "${{ matrix.pluginConfig.path }}" "${{ matrix.pluginConfig.additional_rector_configs }}"

Dans le monorepo aval, ce fichier sera situé sous submodules/PoP/ci/downgrade/downgrade_code.sh. Ensuite, nous avons le workflow en aval qui pointe vers le bon chemin avec ce remplacement :

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

Configuration des packages dans Monorepo Builder

Fichier monorepo-builder.php — placé à la racine du monorepo — contient la configuration pour le constructeur Monorepo . Dans celui-ci, nous devons indiquer où se trouvent les packages (et les plugins, les clients ou tout autre élément) :

use SymfonyComponentDependencyInjectionLoaderConfiguratorContainerConfigurator ;
utilisez SymplifyMonorepoBuilderValueObjectOption ;

return static function (ContainerConfigurator $containerConfigurator): void {
  $parameters = $containerConfigurator->parameters();
  $parameters->set(Option::PACKAGE_DIRECTORIES, [
    __DIR__ . '/packages',
    __DIR__ . '/plugins',
  ]);
};

Le monorepo privé doit avoir accès à tout le code : ses propres packages, plus ceux du monorepo public. Ensuite, il doit définir tous les packages des deux monorepos dans le fichier de configuration. Ceux du monorepo public se trouvent sous "/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',
  ]);
} ;

Comme on peut le voir, la configuration pour l'amont et l'aval est à peu près la même, à la différence que celle en aval :

  • Modifier le chemin vers les packages publics.
  • Ajouter les packages privés .

Ensuite, il est logique de réécrire la configuration à l'aide de la programmation orientée objet, de sorte que nous rendions le code DRY (ne vous répétez pas) en faisant étendre une classe PHP dans le référentiel public dans le référentiel privé.[19659078]Recréer la configuration via la POO

Refactorisons la configuration. Dans le référentiel public, le fichier monorepo-builder.php référencera simplement une nouvelle classe ContainerConfigurationService où toute action se déroulera :

use PoPPoPConfigSymplify MonorepoBuilderConfigurateursContainerConfigurationService;
utilisez SymfonyComponentDependencyInjectionLoaderConfiguratorContainerConfigurator ;

return static function (ContainerConfigurator $containerConfigurator): void {
  $containerConfigurationService = nouveau ContainerConfigurationService(
    $conteneurConfigurateur,
    __DIR__
  );
  $containerConfigurationService->configureContainer();
};

Le paramètre __DIR__ pointe vers la racine du monorepo. Il sera nécessaire pour obtenir le chemin complet vers les répertoires des packages.

La classe ContainerConfigurationService est désormais en charge de produire la configuration :

namespace PoPPoPConfig SymplifyMonorepoBuilderConfigurateurs ;

utilisez PoPPoPConfigSymplifyMonorepoBuilderDataSourcesPackageOrganizationDataSource ;
utilisez SymfonyComponentDependencyInjectionLoaderConfiguratorContainerConfigurator ;
utilisez SymplifyMonorepoBuilderValueObjectOption ;

classe ContainerConfigurationService
{
  fonction publique __construct(
    protégé ContainerConfigurator $containerConfigurator,
    chaîne protégée $rootDirectory,
  ) {
  }

  fonction publique configureContainer() : void
  {
    $parameters = $this->containerConfigurator->parameters();
    if ($packageOrganizationConfig = $this->getPackageOrganizationDataSource($this->rootDirectory)) {
      $parameters->set(
        Option ::PACKAGE_DIRECTORIES,
        $packageOrganizationConfig->getPackageDirectories()
      );
    }
  }

  fonction protégée getPackageOrganizationDataSource() : ?PackageOrganizationDataSource
  {
    return new PackageOrganizationDataSource($this->rootDirectory);
  }
}

La configuration peut être répartie sur plusieurs classes. Dans ce cas, ContainerConfigurationService récupère la configuration du package via la classe PackageOrganizationDataSourcequi a cette implémentation:

namespace PoPPoPConfigSymplifyMonorepoBuilder Source d'information;

classe PackageOrganizationDataSource
{
  fonction publique __construct (chaîne protégée $rootDir)
  {
  }

  fonction publique getPackageDirectories() : tableau
  {
    return array_map(
      fn (string $packagePath) => $this->rootDir . '/' . $PathPath,
      $this->getRelativePackagePaths()
    );
  }

  fonction publique getRelativePackagePaths() : tableau
  {
    retour [
      'packages',
      'plugins',
    ];
  }
}

Remplacement de la configuration dans le monorepo en aval

Maintenant que la configuration dans le monorepo public est configurée via la POO, nous pouvons l'étendre pour répondre aux besoins du monorepo privé.

Afin d'autoriser le monorepo privé. pour charger automatiquement le code PHP depuis le monorepo public, nous devons d'abord configurer l'aval composer.json pour référencer le code source depuis l'amont, qui se trouve sous le chemin submodules/PoP/src :

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

Vous trouverez ci-dessous le fichier monorepo-builder.php pour le monorepo privé. Notez que la classe référencée ContainerConfigurationService dans le référentiel en amont appartient à l'espace de noms PoPPoPmais elle est maintenant passée à l'espace de noms PoPGraphQLAPIPRO. Cette classe doit recevoir l'entrée supplémentaire $upstreamRelativeRootPath (avec la valeur "submodules/PoP") pour recréer le chemin complet vers les packages publics :

use PoPGraphQLAPIPROConfig SymplifyMonorepoBuilderConfiguratorsContainerConfigurationService;
utilisez SymfonyComponentDependencyInjectionLoaderConfiguratorContainerConfigurator ;

return static function (ContainerConfigurator $containerConfigurator): void {
  $containerConfigurationService = nouveau ContainerConfigurationService(
    $conteneurConfigurateur,
    __DIR__,
    'sous-modules/PoP'
  );
  $containerConfigurationService->configureContainer();
};

La classe en aval ContainerConfigurationService remplace la classe PackageOrganizationDataSource utilisée dans la configuration :

namespace PoPGraphQLAPIPROConfigSymplifyMonorepoBuilderConfigurators ;

utilisez PoPPoPConfigSymplifyMonorepoBuilderConfiguratorsContainerConfigurationService comme UpstreamContainerConfigurationService ;
utilisez PoPGraphQLAPIPROConfigSymplifyMonorepoBuilderDataSourcesPackageOrganizationDataSource ;
utilisez SymfonyComponentDependencyInjectionLoaderConfiguratorContainerConfigurator ;

la classe ContainerConfigurationService étend UpstreamContainerConfigurationService
{
  fonction publique __construct(
    ContainerConfigurator $containerConfigurator,
    chaîne $rootDirectory,
    chaîne protégée $upstreamRelativeRootPath
  ) {
    parent::__construire(
      $conteneurConfigurateur,
      $rootDirectory
    );
  }

  fonction protégée getPackageOrganizationDataSource() : ?PackageOrganizationDataSource
  {
    renvoyer un nouveau PackageOrganizationDataSource(
      $this->rootDirectory,
      $this->upstreamRelativeRootPath
    );
  }
}

Enfin, la classe en aval PackageOrganizationDataSource contient le chemin complet vers les packages publics et privés :

espace de noms PoPGraphQLAPIPROConfigSymplifyMonorepoBuilderDataSources ;

utilisez PoPPoPConfigSymplifyMonorepoBuilderDataSourcesPackageOrganizationDataSource comme UpstreamPackageOrganizationDataSource ;

la classe PackageOrganizationDataSource étend UpstreamPackageOrganizationDataSource
{
  fonction publique __construct(
    chaîne $rootDir,
    chaîne protégée $upstreamRelativeRootPath
  ) {
    parent::__construct($rootDir);
  }

  fonction publique getRelativePackagePaths() : tableau
  {
    return array_merge(
      // Packages publics - Les ajouter avec "submodules/PoP/"
      array_map(
        fn ($upstreamPackagePath) => $this->upstreamRelativeRootPath . '/' . $CheminPaquetamont,
        parent::getRelativePackagePaths()
      ),
      // Forfaits privés
      [
        'packages',
        'plugins',
        'clients',
      ]
    );
  }
}

Injection de la configuration de PHP dans les actions GitHub

Le constructeur Monorepo propose la commande packages-jsonque nous pouvons utiliser pour injecter les chemins des packages dans le workflow GitHub Actions :

jobs :
  fournir_données :
    pas:
      - identifiant : output_data
        name : Calculer la matrice pour les packages
        exécuter : |
          echo "::set-output name=matrix::$(vendor/bin/monorepo-builder packages-json)"

    les sorties:
      matrice : ${{ steps.output_data.outputs.matrix }}

Cette commande produit un JSON sous forme de chaîne. Dans le workflow, il doit être converti en un objet JSON via fromJson:

jobs :
  split_monorepo :
    besoins : fournir_données
    stratégie:
      matrice:
        package : ${{ fromJson(needs.provide_data.outputs.matrix) }}

Malheureusement, la commande packages-json affiche les noms des packages mais pas leurs chemins, ce qui fonctionne lorsque tous les packages sont sous le même dossier (comme packages/). Cela ne fonctionne pas dans notre cas, car les packages publics et privés se trouvent dans des dossiers différents.

Heureusement, le Monorepo builder peut être étendu avec des services PHP personnalisés. J'ai donc créé une commande personnalisée package-entries-json (via la classe PackageEntriesJsonCommand) qui affiche le chemin d'accès au package.

Le workflow a ensuite été mis à jour avec le nouvelle commande :

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

Exécuté sur le monorepo public, il produit les packages suivants (parmi beaucoup d'autres):

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

Exécuté sur le monorepo privé, il produit les entrées suivantes (parmi beaucoup d'autres) :

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

Comme on peut l'apprécier, cela fonctionne bien : le La configuration du monorepo en aval contient à la fois des packages publics et privés, et les chemins vers les packages publics ont été précédés de "submodules/PoP".

Ignorer les packages publics dans le monorepo en aval

Jusqu'à présent, le monorepo en aval a inclus des packages publics et privés dans sa configuration. Cependant, toutes les commandes n'ont pas besoin d'être exécutées sur les packages publics.

Prenez l'analyse statique, par exemple. Le monorepo public exécute déjà PHPStan sur tous les packages publics via le workflow phpstan.ymlcomme indiqué dans cette exécution. Si le monorepo aval exécute à nouveau PHPStan sur les packages publics, c'est une perte de temps de calcul. Ensuite, le workflow phpstan.yml doit s'exécuter uniquement sur les packages privés.

Cela signifie que, selon la commande à exécuter dans le référentiel en aval, nous pouvons souhaiter inclure à la fois les packages publics et privés. , ou uniquement privés.

Pour ajouter des packages publics ou non sur la configuration aval, nous adaptons la classe aval PackageOrganizationDataSource pour vérifier cette condition via l'entrée $includeUpstreamPackages:

namespace PoPGraphQLAPIPROConfigSymplifyMonorepoBuilderDataSources ;

utilisez PoPPoPConfigSymplifyMonorepoBuilderDataSourcesPackageOrganizationDataSource comme UpstreamPackageOrganizationDataSource ;

la classe PackageOrganizationDataSource étend UpstreamPackageOrganizationDataSource
{
  fonction publique __construct(
    chaîne $rootDir,
    chaîne protégée $upstreamRelativeRootPath,
    bool protégé $includeUpstreamPackages
  ) {
    parent::__construct($rootDir);
  }

  fonction publique getRelativePackagePaths() : tableau
  {
    return array_merge(
      // Ajouter les packages publics ?
      $this->includeUpstreamPackages ?
        // Packages publics - Les ajouter avec "submodules/PoP/"
        array_map(
          fn ($upstreamPackagePath) => $this->upstreamRelativeRootPath . '/' . $CheminPaquetamont,
          parent::getRelativePackagePaths()
        ) : [],
      // Forfaits privés
      [
        'packages',
        'plugins',
        'clients',
      ]
    );
  }
}

Ensuite, nous devons fournir la valeur $includeUpstreamPackages comme true ou false selon la commande à exécuter.

Nous pouvons le faire en remplaçant le fichier de configuration monorepo-builder.php par deux autres fichiers de configuration : monorepo-builder-with-upstream-packages.php (qui passe $includeUpstreamPackages = > true) et monorepo-builder-without-upstream-packages.php (qui passe $includeUpstreamPackages => false):[19659026]// Fichier monorepo-builder-without-upstream-packages.php
utilisez PoPGraphQLAPIPROConfigSymplifyMonorepoBuilderConfiguratorsContainerConfigurationService ;
utilisez SymfonyComponentDependencyInjectionLoaderConfiguratorContainerConfigurator ;

return static function (ContainerConfigurator $containerConfigurator): void {
$containerConfigurationService = nouveau ContainerConfigurationService(
$conteneurConfigurateur,
__DIR__,
'sous-modules/PoP',
false, // Ceci est $includeUpstreamPackages
);
$containerConfigurationService->configureContainer();
};

Nous mettons ensuite à jour ContainerConfigurationService pour recevoir le paramètre $includeUpstreamPackages et le transmettons à PackageOrganizationDataSource :

namespace PoPGraphQLAPIPROConfigConfig MonorepoBuilderConfigurateurs;

utilisez PoPPoPConfigSymplifyMonorepoBuilderConfiguratorsContainerConfigurationService comme UpstreamContainerConfigurationService ;
utilisez PoPGraphQLAPIPROConfigSymplifyMonorepoBuilderDataSourcesPackageOrganizationDataSource ;
utilisez SymfonyComponentDependencyInjectionLoaderConfiguratorContainerConfigurator ;

la classe ContainerConfigurationService étend UpstreamContainerConfigurationService
{
  fonction publique __construct(
    ContainerConfigurator $containerConfigurator,
    chaîne $rootDirectory,
    chaîne protégée $upstreamRelativeRootPath,
    bool protégé $includeUpstreamPackages,
  ) {
    parent::__construire(
      $conteneurConfigurateur,
      $rootDirectory,
    );
  }

  fonction protégée getPackageOrganizationDataSource() : ?PackageOrganizationDataSource
  {
    renvoyer un nouveau PackageOrganizationDataSource(
      $this->rootDirectory,
      $this->upstreamRelativeRootPath,
      $this->includeUpstreamPackages,
    );
  }
}

Ensuite, nous devons appeler le monorepo-builder avec l'un ou l'autre des fichiers de configuration, en fournissant l'option --config :

jobs :
  fournir_données :
    pas:
      - identifiant : output_data
        name : Calculer la matrice pour les packages
        exécuter : |
          echo "::set-output name=matrix::$(vendor/bin/monorepo-builder package-entries-json --config=monorepo-builder-without-upstream-packages.php)"

Cependant, comme nous vu plus tôt, nous voulons conserver les workflows GitHub Actions dans le monorepo en amont comme source unique de vérité, et ils n'ont clairement pas besoin de ces modifications.

La solution que j'ai trouvée à ce problème est de fournir un - Option -config dans le référentiel en amont toujours, chaque commande obtenant son propre fichier de configuration, telle que la commande validate recevant le fichier de configuration validate.php :[19659024]- nom : Exécuter la validation
run: vendor/bin/monorepo-builder validate –config=config/monorepo-builder/validate.php

Maintenant, il n'y a pas de fichiers de configuration dans le monorepo amont, car il n'en a pas besoin. Mais il ne se brisera pas, car le constructeur Monorepo vérifie si le fichier de configuration existe et, si ce n'est pas le cas, il charge le fichier de configuration par défaut à la place. Nous allons donc soit écraser la configuration, soit rien ne se passe.

Le référentiel en aval fournit les fichiers de configuration pour chaque commande, en spécifiant s'il faut ou non ajouter les packages en amont : exemple de la façon dont le multi-monorepo fuit.

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

Remplacement de la configuration

Nous avons presque terminé. A présent, le monorepo en aval peut remplacer la configuration du monorepo en amont. Il ne reste donc plus qu'à fournir la nouvelle configuration.

Dans la classe PluginDataSource je remplace la configuration dont les plugins WordPress doivent être générés, en fournissant à la place les PRO :

namespace PoPGraphQLAPIPRO ConfigSymplifyMonorepoBuilderDataSources;

utilisez PoPPoPConfigSymplifyMonorepoBuilderDataSourcesPluginDataSource comme UpstreamPluginDataSource ;

la classe PluginDataSource étend UpstreamPluginDataSource
{
  fonction publique getPluginConfigEntries() : tableau
  {
    retour [
      // 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',
      ],
      // Extensions d'API GraphQL
      // Google Traduction
      [
        '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',
      ],
      // Gestionnaire d'évènements
      [
        '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',
      ],
    ] ;
  }
}

Créer une nouvelle version sur GitHub déclenchera le workflow generate_plugins.yml et générera les plugins PRO sur mon monorepo privé :

Génération de plugins PRO

Génération de plugins PRO. ( Grand aperçu)

Tadaaaaaaaa ! 🎉

Conclusion

Comme toujours, il n'y a pas de "meilleure" solution, seulement des solutions qui peuvent mieux fonctionner selon le contexte. L'approche multi-monorepo n'est pas adaptée à tout type de projet ou d'équipe. Je pense que les plus grands bénéficiaires sont les créateurs de plugins qui publient des plugins publics à mettre à niveau vers leurs versions PRO, et les agences personnalisant les plugins pour leurs clients.

Dans mon cas, je suis assez satisfait de cette approche. Il faut un peu de temps et d'efforts pour réussir, mais c'est un investissement ponctuel. Une fois la configuration terminée, je peux me concentrer uniquement sur la création de mes plugins PRO, et le gain de temps concernant la gestion de projet peut être énorme.

Smashing Editorial" width="35" height="46" loading="lazy" decoding="async(yk)




Source link

Revenir vers le haut