From b43bcd320a39e00351f72e806576d91b7cadf585 Mon Sep 17 00:00:00 2001
From: "yash.rode" <yash.rode@3685174.no-reply.drupal.org>
Date: Wed, 19 Oct 2022 13:23:03 +0000
Subject: [PATCH] Issue #3257134 by phenaproxima, yash.rode, tedbow: Don't
 stage node_modules directories

---
 package_manager/package_manager.services.yml  |  6 ++
 .../src/PathExcluder/NodeModulesExcluder.php  | 66 +++++++++++++++++++
 .../fake_site/core/node_modules/ignore.txt    |  1 +
 .../modules/example/node_modules/ignore.txt   |  1 +
 .../PathExcluder/NodeModulesExcluderTest.php  | 58 ++++++++++++++++
 5 files changed, 132 insertions(+)
 create mode 100644 package_manager/src/PathExcluder/NodeModulesExcluder.php
 create mode 100644 package_manager/tests/fixtures/fake_site/core/node_modules/ignore.txt
 create mode 100644 package_manager/tests/fixtures/fake_site/modules/example/node_modules/ignore.txt
 create mode 100644 package_manager/tests/src/Kernel/PathExcluder/NodeModulesExcluderTest.php

diff --git a/package_manager/package_manager.services.yml b/package_manager/package_manager.services.yml
index 46f0a71f30..410e3ccce8 100644
--- a/package_manager/package_manager.services.yml
+++ b/package_manager/package_manager.services.yml
@@ -181,6 +181,12 @@ services:
       - '@package_manager.path_locator'
     tags:
       - { name: event_subscriber }
+  package_manager.node_modules_excluder:
+    class: Drupal\package_manager\PathExcluder\NodeModulesExcluder
+    arguments:
+      - '@package_manager.path_locator'
+    tags:
+      - { name: event_subscriber }
   package_manager.uninstall_validator:
     class: Drupal\package_manager\PackageManagerUninstallValidator
     tags:
diff --git a/package_manager/src/PathExcluder/NodeModulesExcluder.php b/package_manager/src/PathExcluder/NodeModulesExcluder.php
new file mode 100644
index 0000000000..a6e0a675c8
--- /dev/null
+++ b/package_manager/src/PathExcluder/NodeModulesExcluder.php
@@ -0,0 +1,66 @@
+<?php
+
+namespace Drupal\package_manager\PathExcluder;
+
+use Drupal\package_manager\Event\PreApplyEvent;
+use Drupal\package_manager\Event\PreCreateEvent;
+use Drupal\package_manager\Event\StageEvent;
+use Drupal\package_manager\PathLocator;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\Finder\Finder;
+
+/**
+ * Excludes node_modules files from staging areas.
+ *
+ * @internal
+ *   This is an internal part of Package Manager and may be changed or removed
+ *   at any time without warning. External code should not interact with this
+ *   class.
+ */
+class NodeModulesExcluder implements EventSubscriberInterface {
+
+  use PathExclusionsTrait;
+
+  /**
+   * Constructs a NodeModulesExcluder object.
+   *
+   * @param \Drupal\package_manager\PathLocator $path_locator
+   *   The path locator service.
+   */
+  public function __construct(PathLocator $path_locator) {
+    $this->pathLocator = $path_locator;
+  }
+
+  /**
+   * Excludes node_modules directories from staging operations.
+   *
+   * @param \Drupal\package_manager\Event\PreApplyEvent|\Drupal\package_manager\Event\PreCreateEvent $event
+   *   The event object.
+   *
+   * @see \Drupal\package_manager\Event\ExcludedPathsTrait::excludePath()
+   */
+  public function excludeNodeModulesFiles(StageEvent $event): void {
+    $finder = Finder::create()
+      ->in($this->pathLocator->getProjectRoot())
+      ->directories()
+      ->name('node_modules')
+      ->ignoreVCS(FALSE)
+      ->ignoreDotFiles(FALSE);
+    $paths = [];
+    foreach ($finder as $directory) {
+      $paths[] = $directory->getPathname();
+    }
+    $this->excludeInProjectRoot($event, $paths);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents(): array {
+    return [
+      PreCreateEvent::class => 'excludeNodeModulesFiles',
+      PreApplyEvent::class => 'excludeNodeModulesFiles',
+    ];
+  }
+
+}
diff --git a/package_manager/tests/fixtures/fake_site/core/node_modules/ignore.txt b/package_manager/tests/fixtures/fake_site/core/node_modules/ignore.txt
new file mode 100644
index 0000000000..08874eba8b
--- /dev/null
+++ b/package_manager/tests/fixtures/fake_site/core/node_modules/ignore.txt
@@ -0,0 +1 @@
+This file should never be staged.
diff --git a/package_manager/tests/fixtures/fake_site/modules/example/node_modules/ignore.txt b/package_manager/tests/fixtures/fake_site/modules/example/node_modules/ignore.txt
new file mode 100644
index 0000000000..08874eba8b
--- /dev/null
+++ b/package_manager/tests/fixtures/fake_site/modules/example/node_modules/ignore.txt
@@ -0,0 +1 @@
+This file should never be staged.
diff --git a/package_manager/tests/src/Kernel/PathExcluder/NodeModulesExcluderTest.php b/package_manager/tests/src/Kernel/PathExcluder/NodeModulesExcluderTest.php
new file mode 100644
index 0000000000..006e207126
--- /dev/null
+++ b/package_manager/tests/src/Kernel/PathExcluder/NodeModulesExcluderTest.php
@@ -0,0 +1,58 @@
+<?php
+
+namespace Drupal\Tests\package_manager\Kernel\PathExcluder;
+
+use Drupal\Tests\package_manager\Kernel\PackageManagerKernelTestBase;
+
+/**
+ * @covers \Drupal\package_manager\PathExcluder\NodeModulesExcluder
+ *
+ * @group package_manager
+ */
+class NodeModulesExcluderTest extends PackageManagerKernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    // In this test, we want to disable the lock file validator because, even
+    // though both the active and stage directories will have a valid lock file,
+    // this validator will complain because they don't differ at all.
+    $this->disableValidators[] = 'package_manager.validator.lock_file';
+    parent::setUp();
+  }
+
+  /**
+   * Tests that node_modules directories are excluded from staging operations.
+   */
+  public function testExcludedPaths(): void {
+    // In this test, we want to perform the actual staging operations so that we
+    // can be sure that files are staged as expected.
+    $this->setSetting('package_manager_bypass_composer_stager', FALSE);
+    // Ensure we have an up-to-date container.
+    $this->container = $this->container->get('kernel')->rebuildContainer();
+
+    $active_dir = $this->container->get('package_manager.path_locator')
+      ->getProjectRoot();
+
+    $stage = $this->createStage();
+    $stage->create();
+    $stage_dir = $stage->getStageDirectory();
+
+    $ignored = [
+      "core/node_modules/ignore.txt",
+      'modules/example/node_modules/ignore.txt',
+    ];
+    foreach ($ignored as $path) {
+      $this->assertFileExists("$active_dir/$path");
+      $this->assertFileDoesNotExist("$stage_dir/$path");
+    }
+
+    $stage->apply();
+    // The ignored files should still be in the active directory.
+    foreach ($ignored as $path) {
+      $this->assertFileExists("$active_dir/$path");
+    }
+  }
+
+}
-- 
GitLab