From f22199e190e8a3b058cd3d3468e88ac6608855d9 Mon Sep 17 00:00:00 2001 From: Benni Mack Date: Tue, 1 Sep 2026 17:29:15 +0200 Subject: [PATCH] [BUGFIX] Keep excludes with escaped slashes working Quoting the configured exclude entries (#106) broke the workaround extensions used for nested directories: an entry written as `Resources\/Private\/Build` was passed to preg_quote as is, so the backslash itself got escaped and the resulting pattern only matched a directory whose name literally contains a backslash. Those extensions silently packaged the directory they excluded before. Strip the escaped slashes before quoting, so both notations describe the same directory and no extension configuration needs a change. --- README.md | 6 ++++ src/Service/VersionService.php | 21 +++++++++++-- .../config_nested_directory_escaped.php | 8 +++++ tests/Unit/Service/VersionServiceTest.php | 30 +++++++++++++++++-- 4 files changed, 60 insertions(+), 5 deletions(-) create mode 100644 tests/Unit/Fixtures/ExcludeFromPackaging/config_nested_directory_escaped.php diff --git a/README.md b/README.md index 03733ac..37d574e 100644 --- a/README.md +++ b/README.md @@ -656,6 +656,12 @@ path to your custom configuration file to the environment variable `TYPO3_EXCLUDE_FROM_PACKAGING`. This file must return an `array` with the keys `directories` and `files` on root level. +The entries are plain directory and file names, matched case-insensitively - +directories against the beginning of the path, files against the end of the +filename. Nested directories can be written as they appear on disk +(`Resources/Private/Build`); slashes escaped as `Resources\/Private\/Build` +are still accepted and describe the very same directory. + ## Overview of all available commands | Commands | Arguments | Options | Description | diff --git a/src/Service/VersionService.php b/src/Service/VersionService.php index 34dad5d..b0c6ce6 100644 --- a/src/Service/VersionService.php +++ b/src/Service/VersionService.php @@ -81,7 +81,7 @@ public function createZipArchiveFromPath(string $path): string if ($current->isDir()) { // if $current is a directory, check for excluded directories foreach ($this->excludeConfiguration['directories'] as $excludeDirectory) { - if (preg_match('/^' . preg_quote((string)$excludeDirectory, '/') . '/i', $path)) { + if (preg_match('/^' . $this->quoteExcludePattern((string)$excludeDirectory) . '/i', $path)) { return false; } } @@ -90,7 +90,7 @@ public function createZipArchiveFromPath(string $path): string if ($current->isFile()) { // if $current is a file, check for excluded files foreach ($this->excludeConfiguration['files'] as $excludeFile) { - if (preg_match('/' . preg_quote((string)$excludeFile, '/') . '$/i', $filename)) { + if (preg_match('/' . $this->quoteExcludePattern((string)$excludeFile) . '$/i', $filename)) { return false; } } @@ -127,6 +127,23 @@ public function createZipArchiveFromPath(string $path): string return $this->getVersionFilePath(); } + /** + * Quote a configured exclude entry for use within a slash delimited pattern. + * + * The entries are documented as plain directory and file names. Some extensions + * however escape the slashes of a nested directory name (`Resources\/Private\/Build`) + * to work around the unquoted interpolation used in earlier versions. Those escapes + * are removed first, so both notations describe the very same directory. + * + * @param string $excludeEntry The configured directory or file name + * + * @return string The quoted pattern part + */ + protected function quoteExcludePattern(string $excludeEntry): string + { + return preg_quote(str_replace('\\/', '/', $excludeEntry), '/'); + } + /** * Extract the given artefact (from either local or remote), * store it in a temporary transaction path and finally call diff --git a/tests/Unit/Fixtures/ExcludeFromPackaging/config_nested_directory_escaped.php b/tests/Unit/Fixtures/ExcludeFromPackaging/config_nested_directory_escaped.php new file mode 100644 index 0000000..5854681 --- /dev/null +++ b/tests/Unit/Fixtures/ExcludeFromPackaging/config_nested_directory_escaped.php @@ -0,0 +1,8 @@ + [ + 'Resources\/Private\/Build', + ], + 'files' => [], +]; diff --git a/tests/Unit/Service/VersionServiceTest.php b/tests/Unit/Service/VersionServiceTest.php index ff21ce5..019ff69 100644 --- a/tests/Unit/Service/VersionServiceTest.php +++ b/tests/Unit/Service/VersionServiceTest.php @@ -110,9 +110,34 @@ public function getVersionFilenameAsMd5Test(): void #[Test] public function excludedDirectoryContainingASlashIsNotPackaged(): void + { + $packagedFiles = $this->packageExtensionWithExcludeConfiguration('config_nested_directory.php'); + + self::assertContains('ext_emconf.php', $packagedFiles); + self::assertNotContains('Resources/Private/Build/gulpfile.js', $packagedFiles); + } + + #[Test] + public function excludedDirectoryContainingAnEscapedSlashIsNotPackaged(): void + { + $packagedFiles = $this->packageExtensionWithExcludeConfiguration('config_nested_directory_escaped.php'); + + self::assertContains('ext_emconf.php', $packagedFiles); + self::assertNotContains('Resources/Private/Build/gulpfile.js', $packagedFiles); + } + + /** + * Package an extension directory with the given exclude configuration + * and return the filenames the created archive contains. + * + * @param string $configurationFilename Filename of the exclude configuration fixture + * + * @return list The packaged filenames + */ + protected function packageExtensionWithExcludeConfiguration(string $configurationFilename): array { unset($_ENV); - putenv('TYPO3_EXCLUDE_FROM_PACKAGING=' . __DIR__ . '/../Fixtures/ExcludeFromPackaging/config_nested_directory.php'); + putenv('TYPO3_EXCLUDE_FROM_PACKAGING=' . __DIR__ . '/../Fixtures/ExcludeFromPackaging/' . $configurationFilename); $extensionPath = $this->createExtensionDirectory(); $transactionPath = $this->createTemporaryDirectory(); @@ -130,8 +155,7 @@ public function excludedDirectoryContainingASlashIsNotPackaged(): void $archive->close(); - self::assertContains('ext_emconf.php', $packagedFiles); - self::assertNotContains('Resources/Private/Build/gulpfile.js', $packagedFiles); + return $packagedFiles; } /**