From f893df14d66c1dd30227bbadd27dc4bec14931bb Mon Sep 17 00:00:00 2001
From: Adam G-H <32250-phenaproxima@users.noreply.drupalcode.org>
Date: Fri, 30 Aug 2024 15:53:23 +0000
Subject: [PATCH] Issue #3471232: The recipes source plugin cannot see recipes
 that are symlinked into the project

---
 src/Plugin/ProjectBrowserSource/Recipes.php |  3 +++
 tests/src/Kernel/RecipesSourceTest.php      | 28 ++++++++++++++++++++-
 2 files changed, 30 insertions(+), 1 deletion(-)

diff --git a/src/Plugin/ProjectBrowserSource/Recipes.php b/src/Plugin/ProjectBrowserSource/Recipes.php
index ce3764562..d31c93d0f 100644
--- a/src/Plugin/ProjectBrowserSource/Recipes.php
+++ b/src/Plugin/ProjectBrowserSource/Recipes.php
@@ -182,6 +182,9 @@ class Recipes extends ProjectBrowserSourceBase {
       ->files()
       ->in($search_in)
       ->depth(1)
+      // Without this, recipes that are symlinked into the project (e.g.,
+      // path repositories) will be missed.
+      ->followLinks()
       // The example recipe exists for documentation purposes only.
       ->notPath('example/')
       ->name('recipe.yml');
diff --git a/tests/src/Kernel/RecipesSourceTest.php b/tests/src/Kernel/RecipesSourceTest.php
index bd4b026f7..2ce6db96f 100644
--- a/tests/src/Kernel/RecipesSourceTest.php
+++ b/tests/src/Kernel/RecipesSourceTest.php
@@ -4,12 +4,15 @@ declare(strict_types=1);
 
 namespace Drupal\Tests\project_browser\Kernel;
 
+use Composer\InstalledVersions;
+use Drupal\Component\FileSystem\FileSystem;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Recipe\Recipe;
 use Drupal\KernelTests\KernelTestBase;
 use Drupal\project_browser\EnabledSourceHandler;
 use Drupal\project_browser\Plugin\ProjectBrowserSourceManager;
 use Drupal\project_browser\ProjectType;
+use Symfony\Component\Filesystem\Filesystem as SymfonyFilesystem;
 use Symfony\Component\Finder\Finder;
 use Symfony\Component\Finder\SplFileInfo;
 
@@ -64,14 +67,31 @@ class RecipesSourceTest extends KernelTestBase {
    * Tests that recipes are discovered by the plugin.
    */
   public function testRecipesAreDiscovered(): void {
+    // Generate a fake recipe in the temporary directory.
+    $generated_recipe_name = uniqid();
+    $generated_recipe_dir = FileSystem::getOsTemporaryDirectory() . '/' . $generated_recipe_name;
+    mkdir($generated_recipe_dir);
+    file_put_contents($generated_recipe_dir . '/composer.json', '{"name": "drupal/bogus_recipe"}');
+    file_put_contents($generated_recipe_dir . '/recipe.yml', 'name: Bogus');
+
+    // Find the place where recipes are installed; this contributed recipe is
+    // one of our dev dependencies.
+    $installed_recipes_dir = InstalledVersions::getInstallPath('kanopi/imagemagick-configuration');
+    $installed_recipes_dir = realpath($installed_recipes_dir);
+    $installed_recipes_dir = dirname($installed_recipes_dir);
+    $file_system = new SymfonyFilesystem();
+    // Symlink the fake recipe into the place where the source plugin will
+    // search, to prove that the plugin follows symlinks.
+    $file_system->symlink($generated_recipe_dir, $installed_recipes_dir . '/' . $generated_recipe_name);
+
     $finder = Finder::create()
       ->in($this->getDrupalRoot() . '/core/recipes')
       ->directories()
       ->notName('example')
       ->depth(0);
     $expected_recipe_names = array_map(fn (SplFileInfo $dir) => $dir->getBasename(), iterator_to_array($finder));
-    // This contributed recipe is one of our dev dependencies.
     $expected_recipe_names[] = 'imagemagick-configuration';
+    $expected_recipe_names[] = $generated_recipe_name;
 
     /** @var \Drupal\project_browser\ProjectBrowser\ProjectsResultsPage $projects */
     $projects = $this->container->get(ProjectBrowserSourceManager::class)
@@ -104,6 +124,12 @@ class RecipesSourceTest extends KernelTestBase {
     $body = (new \ReflectionProperty($found_recipes['standard'], 'body'))
       ->getValue($found_recipes['standard']);
     $this->assertNotEmpty($body);
+
+    // Clean up.
+    $file_system->remove([
+      $installed_recipes_dir . '/' . $generated_recipe_name,
+      $generated_recipe_dir,
+    ]);
   }
 
   /**
-- 
GitLab