From c1346b674f2ebe392f902d8ffb6f0f930e9baa0a Mon Sep 17 00:00:00 2001 From: Lee Rowlands <lee.rowlands@previousnext.com.au> Date: Wed, 9 Oct 2019 12:55:24 +1000 Subject: [PATCH] Issue #3085697 by greg.1.anderson, Mixologic, mbaynton, larowlan: Allow scaffold plugin to append to non-scaffolded files --- .../Scaffold/Operations/AbstractOperation.php | 26 ++++ .../Plugin/Scaffold/Operations/AppendOp.php | 118 ++++++++++++++++-- .../Operations/ConjoinableInterface.php | 13 -- .../Scaffold/Operations/ConjunctionOp.php | 2 +- .../Scaffold/Operations/OperationData.php | 66 +++++++++- .../Scaffold/Operations/OperationFactory.php | 28 ++++- .../Operations/OperationInterface.php | 27 ++++ .../Plugin/Scaffold/Operations/ReplaceOp.php | 2 +- .../Operations/ScaffoldFileCollection.php | 14 ++- .../Plugin/Scaffold/Operations/SkipOp.php | 2 +- composer/Plugin/Scaffold/README.md | 24 ++++ .../Plugin/Scaffold/AssertUtilsTrait.php | 17 +++ .../Functional/ManageGitIgnoreTest.php | 21 ++++ .../Scaffold/Functional/ScaffoldTest.php | 32 ++++- .../assets/append-to-settings.txt | 1 + .../assets/default-settings.txt | 3 + .../composer.json.tmpl | 64 ++++++++++ 17 files changed, 420 insertions(+), 40 deletions(-) create mode 100644 composer/Plugin/Scaffold/Operations/AbstractOperation.php delete mode 100644 composer/Plugin/Scaffold/Operations/ConjoinableInterface.php create mode 100644 core/tests/Drupal/Tests/Composer/Plugin/Scaffold/fixtures/drupal-drupal-append-settings/assets/append-to-settings.txt create mode 100644 core/tests/Drupal/Tests/Composer/Plugin/Scaffold/fixtures/drupal-drupal-append-settings/assets/default-settings.txt create mode 100644 core/tests/Drupal/Tests/Composer/Plugin/Scaffold/fixtures/drupal-drupal-append-settings/composer.json.tmpl diff --git a/composer/Plugin/Scaffold/Operations/AbstractOperation.php b/composer/Plugin/Scaffold/Operations/AbstractOperation.php new file mode 100644 index 000000000000..5f7261fd262c --- /dev/null +++ b/composer/Plugin/Scaffold/Operations/AbstractOperation.php @@ -0,0 +1,26 @@ +<?php + +namespace Drupal\Composer\Plugin\Scaffold\Operations; + +use Drupal\Composer\Plugin\Scaffold\ScaffoldFilePath; + +/** + * Provides default behaviors for operations. + */ +abstract class AbstractOperation implements OperationInterface { + + /** + * {@inheritdoc} + */ + public function combineWithConjunctionTarget(OperationInterface $conjunction_target) { + return $this; + } + + /** + * {@inheritdoc} + */ + public function missingConjunctionTarget(ScaffoldFilePath $destination) { + return $this; + } + +} diff --git a/composer/Plugin/Scaffold/Operations/AppendOp.php b/composer/Plugin/Scaffold/Operations/AppendOp.php index 1a3ecd2d86a9..7f5c7e8b0dbe 100644 --- a/composer/Plugin/Scaffold/Operations/AppendOp.php +++ b/composer/Plugin/Scaffold/Operations/AppendOp.php @@ -9,7 +9,7 @@ /** * Scaffold operation to add to the beginning and/or end of a scaffold file. */ -class AppendOp implements OperationInterface, ConjoinableInterface { +class AppendOp extends AbstractOperation { /** * Identifies Append operations. @@ -30,6 +30,23 @@ class AppendOp implements OperationInterface, ConjoinableInterface { */ protected $append; + /** + * Path to the default data to use when appending to an empty file. + * + * @var \Drupal\Composer\Plugin\Scaffold\ScaffoldFilePath + */ + protected $default; + + /** + * An indicator of whether the file we are appending to is managed or not. + */ + protected $managed; + + /** + * An indicator of whether we are allowed to append to a non-scaffolded file. + */ + protected $forceAppend; + /** * Constructs an AppendOp. * @@ -37,10 +54,17 @@ class AppendOp implements OperationInterface, ConjoinableInterface { * The relative path to the prepend file. * @param \Drupal\Composer\Plugin\Scaffold\ScaffoldFilePath $append_path * The relative path to the append file. + * @param bool $force_append + * TRUE if is okay to append to a file that was not scaffolded. + * @param \Drupal\Composer\Plugin\Scaffold\ScaffoldFilePath $default_path + * The relative path to the default data. */ - public function __construct(ScaffoldFilePath $prepend_path = NULL, ScaffoldFilePath $append_path = NULL) { + public function __construct(ScaffoldFilePath $prepend_path = NULL, ScaffoldFilePath $append_path = NULL, $force_append = FALSE, ScaffoldFilePath $default_path = NULL) { + $this->forceAppend = $force_append; $this->prepend = $prepend_path; $this->append = $append_path; + $this->default = $default_path; + $this->managed = TRUE; } /** @@ -48,11 +72,25 @@ public function __construct(ScaffoldFilePath $prepend_path = NULL, ScaffoldFileP */ public function process(ScaffoldFilePath $destination, IOInterface $io, ScaffoldOptions $options) { $destination_path = $destination->fullPath(); - if (!file_exists($destination_path)) { - throw new \RuntimeException($destination->getInterpolator()->interpolate("Cannot append/prepend because no prior package provided a scaffold file at that [dest-rel-path].")); + // This is just a sanity check; the OperationFactory has in theory already + // accounted for this, and will return a SkipOp with a warning message. + if (!file_exists($destination_path) && empty($this->default)) { + throw new \RuntimeException($destination->getInterpolator()->interpolate("Cannot append/prepend because no prior package provided a scaffold file at [dest-rel-path].")); } $interpolator = $destination->getInterpolator(); + // Be extra-noisy of creating a new file or appending to a non-scaffold + // file. Note that if the file already has the append contents, then the + // OperationFactory will make a SkipOp instead, and we will not get here. + if (!$this->managed) { + $message = ' - <info>NOTICE</info> Modifying existing file at <info>[dest-rel-path]</info>.'; + if (!file_exists($destination_path)) { + $message = ' - <info>NOTICE</info> Creating a new file at <info>[dest-rel-path]</info>.'; + } + $message .= ' Examine the contents and ensure that it came out correctly.'; + $io->write($interpolator->interpolate($message)); + } + // Fetch the prepend contents, if provided. $prepend_contents = ''; if (!empty($this->prepend)) { @@ -67,18 +105,82 @@ public function process(ScaffoldFilePath $destination, IOInterface $io, Scaffold $append_contents = "\n" . file_get_contents($this->append->fullPath()); $io->write($interpolator->interpolate(" - Append to <info>[dest-rel-path]</info> from <info>[append-rel-path]</info>")); } + // We typically should always have content if we get here; the + // OperationFactory should create a SkipOp instead of an AppendOp if there + // is no append / prepend content. The edge case is if there is content + // that is all 'trim'ed away. Then we get a message that we are appending, + // although nothing will in fact actually happen. if (!empty(trim($prepend_contents)) || !empty(trim($append_contents))) { // None of our asset files are very large, so we will load each one into // memory for processing. - $original_contents = file_get_contents($destination_path); + $original_contents = file_get_contents(file_exists($destination_path) ? $destination_path : $this->default->fullPath()); // Write the appended and prepended contents back to the file. $altered_contents = $prepend_contents . $original_contents . $append_contents; file_put_contents($destination_path, $altered_contents); } - else { - $io->write($interpolator->interpolate(" - Keep <info>[dest-rel-path]</info> unchanged: no content to prepend / append was provided.")); + + // Return a ScaffoldResult with knowledge of whether this file is managed. + return new ScaffoldResult($destination, $this->managed); + } + + /** + * {@inheritdoc} + */ + public function combineWithConjunctionTarget(OperationInterface $conjunction_target) { + return new ConjunctionOp($conjunction_target, $this); + } + + /** + * {@inheritdoc} + */ + public function missingConjunctionTarget(ScaffoldFilePath $destination) { + // If there is no conjunction target (the destination is not scaffolded), + // then any append we do will be to an unmanaged file. + $this->managed = FALSE; + + // Default: do not allow an append over a file that was not scaffolded. + if (!$this->forceAppend) { + $message = " - Skip <info>[dest-rel-path]</info>: cannot append to a path that was not scaffolded unless 'force-append' property is set."; + return new SkipOp($message); + } + + // If the target file does not exist, then we will allow the append to + // happen if we have default data to provide for it. + if (!file_exists($destination->fullPath())) { + if (!empty($this->default)) { + return $this; + } + $message = " - Skip <info>[dest-rel-path]</info>: no file exists at the target path, and no default data provided."; + return new SkipOp($message); + } + + // If the target file DOES exist, and it already contains the append/prepend + // data, then we will skip the operation. + $existingData = file_get_contents($destination->fullPath()); + if ($this->existingFileHasData($existingData, $this->append) || $this->existingFileHasData($existingData, $this->prepend)) { + $message = " - Skip <info>[dest-rel-path]</info>: the file already has the append/prepend data."; + return new SkipOp($message); } - return new ScaffoldResult($destination, TRUE); + + return $this; + } + + /** + * Check to see if the append/prepend data has already been applied. + * @param string $contents + * The contents of the target file. + * @param \Drupal\Composer\Plugin\Scaffold\ScaffoldFilePath $data_path + * The path to the data to append or prepend + * @return bool + * 'TRUE' if the append/prepend data already exists in contents. + */ + protected function existingFileHasData($contents, $data_path) { + if (empty($data_path)) { + return FALSE; + } + $data = file_get_contents($data_path->fullPath()); + + return strpos($contents, $data) !== FALSE; } } diff --git a/composer/Plugin/Scaffold/Operations/ConjoinableInterface.php b/composer/Plugin/Scaffold/Operations/ConjoinableInterface.php deleted file mode 100644 index ca8d433971ca..000000000000 --- a/composer/Plugin/Scaffold/Operations/ConjoinableInterface.php +++ /dev/null @@ -1,13 +0,0 @@ -<?php - -namespace Drupal\Composer\Plugin\Scaffold\Operations; - -/** - * Marker interface indicating that operation is conjoinable. - * - * A conjoinable operation is one that runs in addition to any previous - * operation defined at the same destination path. Operations that are - * not conjoinable simply replace anything at the same destination path. - */ -interface ConjoinableInterface { -} diff --git a/composer/Plugin/Scaffold/Operations/ConjunctionOp.php b/composer/Plugin/Scaffold/Operations/ConjunctionOp.php index f855e4e4dee8..83b2441271c7 100644 --- a/composer/Plugin/Scaffold/Operations/ConjunctionOp.php +++ b/composer/Plugin/Scaffold/Operations/ConjunctionOp.php @@ -9,7 +9,7 @@ /** * Joins two operations on the same file into a single operation. */ -class ConjunctionOp implements OperationInterface { +class ConjunctionOp extends AbstractOperation { /** * The first operation. diff --git a/composer/Plugin/Scaffold/Operations/OperationData.php b/composer/Plugin/Scaffold/Operations/OperationData.php index c98fe2c2ed84..66b85229aed4 100644 --- a/composer/Plugin/Scaffold/Operations/OperationData.php +++ b/composer/Plugin/Scaffold/Operations/OperationData.php @@ -12,6 +12,8 @@ class OperationData { const OVERWRITE = 'overwrite'; const PREPEND = 'prepend'; const APPEND = 'append'; + const DEFAULT = 'default'; + const FORCE_APPEND = 'force-append'; /** * The parameter data. @@ -85,7 +87,20 @@ public function path() { * Returns true if overwrite mode was selected. */ public function overwrite() { - return isset($this->data[self::OVERWRITE]) ? $this->data[self::OVERWRITE] : TRUE; + return !empty($this->data[self::OVERWRITE]); + } + + /** + * Determines whether 'force-append' has been set. + * + * @return bool + * Returns true if 'force-append' mode was selected. + */ + public function forceAppend() { + if ($this->hasDefault()) { + return TRUE; + } + return !empty($this->data[self::FORCE_APPEND]); } /** @@ -128,6 +143,26 @@ public function append() { return $this->data[self::APPEND]; } + /** + * Checks if default path exists. + * + * @return bool + * Returns true if there is default data available. + */ + public function hasDefault() { + return isset($this->data[self::DEFAULT]); + } + + /** + * Gets default path. + * + * @return string + * Path to default data + */ + public function default() { + return $this->data[self::DEFAULT]; + } + /** * Normalizes metadata by converting literal values into arrays. * @@ -141,9 +176,32 @@ public function append() { * The metadata for this operation object, which varies by operation type. * * @return array - * Normalized scaffold metadata. + * Normalized scaffold metadata with default values. */ protected function normalizeScaffoldMetadata($destination, $value) { + $defaultScaffoldMetadata = [ + self::MODE => ReplaceOp::ID, + self::PREPEND => NULL, + self::APPEND => NULL, + self::DEFAULT => NULL, + self::OVERWRITE => TRUE, + ]; + + return $this->convertScaffoldMetadata($destination, $value) + $defaultScaffoldMetadata; + } + + /** + * Performs the conversion-to-array step in normalizeScaffoldMetadata. + * + * @param string $destination + * The destination path for the scaffold file. + * @param mixed $value + * The metadata for this operation object, which varies by operation type. + * + * @return array + * Normalized scaffold metadata. + */ + protected function convertScaffoldMetadata($destination, $value) { if (is_bool($value)) { if (!$value) { return [self::MODE => SkipOp::ID]; @@ -161,10 +219,6 @@ protected function normalizeScaffoldMetadata($destination, $value) { if (!isset($value[self::MODE]) && (isset($value[self::APPEND]) || isset($value[self::PREPEND]))) { $value[self::MODE] = AppendOp::ID; } - // If there is no 'mode', then the default is 'replace'. - if (!isset($value[self::MODE])) { - $value[self::MODE] = ReplaceOp::ID; - } return $value; } diff --git a/composer/Plugin/Scaffold/Operations/OperationFactory.php b/composer/Plugin/Scaffold/Operations/OperationFactory.php index 4c0efa046291..7742e4bb9d7f 100644 --- a/composer/Plugin/Scaffold/Operations/OperationFactory.php +++ b/composer/Plugin/Scaffold/Operations/OperationFactory.php @@ -99,14 +99,38 @@ protected function createAppendOp(PackageInterface $package, OperationData $oper $package_path = $this->getPackagePath($package); $prepend_source_file = NULL; $append_source_file = NULL; + $default_data_file = NULL; if ($operation_data->hasPrepend()) { $prepend_source_file = ScaffoldFilePath::sourcePath($package_name, $package_path, $operation_data->destination(), $operation_data->prepend()); } if ($operation_data->hasAppend()) { $append_source_file = ScaffoldFilePath::sourcePath($package_name, $package_path, $operation_data->destination(), $operation_data->append()); } - $op = new AppendOp($prepend_source_file, $append_source_file); - return $op; + if ($operation_data->hasDefault()) { + $default_data_file = ScaffoldFilePath::sourcePath($package_name, $package_path, $operation_data->destination(), $operation_data->default()); + } + if (!$this->hasContent($prepend_source_file) && !$this->hasContent($append_source_file)) { + $message = ' - Keep <info>[dest-rel-path]</info> unchanged: no content to prepend / append was provided.'; + return new SkipOp($message); + } + + return new AppendOp($prepend_source_file, $append_source_file, $operation_data->forceAppend(), $default_data_file); + } + + /** + * Checks to see if the specified scaffold file exists and has content. + * + * @param Drupal\Composer\Plugin\Scaffold\ScaffoldFilePath $file + * Scaffold file to check. + * @return bool + * True if the file exists and has content. + */ + protected function hasContent(ScaffoldFilePath $file = NULL) { + if (!$file) { + return FALSE; + } + $path = $file->fullPath(); + return is_file($path) && (filesize($path) > 0); } /** diff --git a/composer/Plugin/Scaffold/Operations/OperationInterface.php b/composer/Plugin/Scaffold/Operations/OperationInterface.php index 5c1d4506e8de..409b4761a2a9 100644 --- a/composer/Plugin/Scaffold/Operations/OperationInterface.php +++ b/composer/Plugin/Scaffold/Operations/OperationInterface.php @@ -26,4 +26,31 @@ interface OperationInterface { */ public function process(ScaffoldFilePath $destination, IOInterface $io, ScaffoldOptions $options); + /** + * Determines what to do if operation is used with a previous operation. + * + * Default behavior is to scaffold this operation at the specified + * destination, ignoring whatever was there before. + * + * @param OperationInterface $conjunction_target + * Existing file at the destination path that we should combine with. + * + * @return OperationInterface + * The op to use at this destination. + */ + public function combineWithConjunctionTarget(OperationInterface $conjunction_target); + + /** + * Determines what to do if operation is used without a previous operation. + * + * Default behavior is to scaffold this operation at the specified + * destination. Most operations overwrite rather than modify existing files, + * and therefore do not need to do anything special when there is no existing + * file. + * + * @return OperationInterface + * The op to use at this destination. + */ + public function missingConjunctionTarget(ScaffoldFilePath $destination); + } diff --git a/composer/Plugin/Scaffold/Operations/ReplaceOp.php b/composer/Plugin/Scaffold/Operations/ReplaceOp.php index c2bf71c296a5..5b4c4b9b1d88 100644 --- a/composer/Plugin/Scaffold/Operations/ReplaceOp.php +++ b/composer/Plugin/Scaffold/Operations/ReplaceOp.php @@ -10,7 +10,7 @@ /** * Scaffold operation to copy or symlink from source to destination. */ -class ReplaceOp implements OperationInterface { +class ReplaceOp extends AbstractOperation { /** * Identifies Replace operations. diff --git a/composer/Plugin/Scaffold/Operations/ScaffoldFileCollection.php b/composer/Plugin/Scaffold/Operations/ScaffoldFileCollection.php index ea97cb873446..c000e64a478e 100644 --- a/composer/Plugin/Scaffold/Operations/ScaffoldFileCollection.php +++ b/composer/Plugin/Scaffold/Operations/ScaffoldFileCollection.php @@ -42,6 +42,7 @@ public function __construct(array $file_mappings, Interpolator $location_replace foreach ($file_mappings as $package_name => $package_file_mappings) { foreach ($package_file_mappings as $destination_rel_path => $op) { $destination = ScaffoldFilePath::destinationPath($package_name, $destination_rel_path, $location_replacements); + // If there was already a scaffolding operation happening at this path, // and the new operation is Conjoinable, then use a ConjunctionOp to // join together both operations. This will cause both operations to @@ -50,13 +51,20 @@ public function __construct(array $file_mappings, Interpolator $location_replace // path. if (isset($scaffoldFiles[$destination_rel_path])) { $previous_scaffold_file = $scaffoldFiles[$destination_rel_path]; - if ($op instanceof ConjoinableInterface) { - $op = new ConjunctionOp($previous_scaffold_file->op(), $op); - } + $op = $op->combineWithConjunctionTarget($previous_scaffold_file->op()); + // Remove the previous op so we only touch the destination once. $message = " - Skip <info>[dest-rel-path]</info>: overridden in <comment>{$package_name}</comment>"; $this->scaffoldFilesByProject[$previous_scaffold_file->packageName()][$destination_rel_path] = new ScaffoldFileInfo($destination, new SkipOp($message)); } + // If there is NOT already a scaffolding operation happening at this + // path, but the operation is a ConjunctionOp, then we need to check + // to see if there is a strategy for non-conjunction use. + else { + $op = $op->missingConjunctionTarget($destination); + } + + // Combine the scaffold operation with the destination and record it. $scaffold_file = new ScaffoldFileInfo($destination, $op); $scaffoldFiles[$destination_rel_path] = $scaffold_file; $this->scaffoldFilesByProject[$package_name][$destination_rel_path] = $scaffold_file; diff --git a/composer/Plugin/Scaffold/Operations/SkipOp.php b/composer/Plugin/Scaffold/Operations/SkipOp.php index 10b0ec9bbc35..3e6c111bdfcb 100644 --- a/composer/Plugin/Scaffold/Operations/SkipOp.php +++ b/composer/Plugin/Scaffold/Operations/SkipOp.php @@ -9,7 +9,7 @@ /** * Scaffold operation to skip a scaffold file (do nothing). */ -class SkipOp implements OperationInterface { +class SkipOp extends AbstractOperation { /** * Identifies Skip operations. diff --git a/composer/Plugin/Scaffold/README.md b/composer/Plugin/Scaffold/README.md index ae696cea0d67..659f0ed3f6d2 100644 --- a/composer/Plugin/Scaffold/README.md +++ b/composer/Plugin/Scaffold/README.md @@ -302,6 +302,30 @@ path, and it is not possible for multiple entries to have the same key. If "prepend" were a separate mode, then it would not be possible to both prepend and append to the same file. +By default, append operations may only be applied to files that were scaffolded +by a previously evaluated project. If the `force-append` attribute is added to +an `append` operation, though, then the append will be made to non-scaffolded +files if and only if the append text does not already appear in the file. When +using this mode, it is also possible to provide default contents to use in the +event that the destination file is entirely missing. + +The example below demonstrates scaffolding a settings-custom.php file, and +including it from the existing `settings.php` file. + +``` +"file-mapping": { + "[web-root]/sites/default/settings-custom.php": "assets/settings-custom.php", + "[web-root]/sites/default/settings.php": { + "append": "assets/include-settings-custom.txt", + "force-append": true, + "default": "assets/initial-default-settings.txt" + } +} +``` + +Note that the example above still works if used with a project that scaffolds +the settings.php file. + ### gitignore The `gitignore` configuration setting controls whether or not this plugin will diff --git a/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/AssertUtilsTrait.php b/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/AssertUtilsTrait.php index 5634f492dd88..2f39d832ec80 100644 --- a/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/AssertUtilsTrait.php +++ b/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/AssertUtilsTrait.php @@ -24,4 +24,21 @@ protected function assertScaffoldedFile($path, $is_link, $contents_contains) { $this->assertSame($is_link, is_link($path)); } + /** + * Asserts that a file does not exist or exists and does not contain a value. + * + * @param string $path + * The path to check exists. + * @param string $contents_not_contains + * A string that is expected should NOT occur in the file contents. + */ + protected function assertScaffoldedFileDoesNotContain($path, $contents_not_contains) { + // If the file does not exist at all, we'll count that as a pass. + if (!file_exists($path)) { + return; + } + $contents = file_get_contents($path); + $this->assertNotContains($contents_not_contains, $contents, basename($path) . ' contains unexpected contents:'); + } + } diff --git a/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/Functional/ManageGitIgnoreTest.php b/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/Functional/ManageGitIgnoreTest.php index b7ea314c6159..c73faf74bdf2 100644 --- a/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/Functional/ManageGitIgnoreTest.php +++ b/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/Functional/ManageGitIgnoreTest.php @@ -159,6 +159,27 @@ public function testUnmanagedGitIgnoreWhenDisabled() { $this->assertFileNotExists($sut . '/docroot/sites/default/.gitignore'); } + /** + * Test appending to an unmanaged file, and confirm it is not .gitignored. + * + * If we append to an unmanaged (not scaffolded) file, and we are managing + * .gitignore files, then we expect that the unmanaged file should not be + * added to the .gitignore file, because unmanaged files should be committed. + */ + public function testAppendToEmptySettingsIsUnmanaged() { + $sut = $this->createSutWithGit('drupal-drupal-append-settings'); + $this->assertFileNotExists($sut . '/autoload.php'); + $this->assertFileNotExists($sut . '/index.php'); + $this->assertFileNotExists($sut . '/sites/.gitignore'); + // Run the scaffold command. + $this->fixtures->runScaffold($sut); + $this->assertFileExists($sut . '/autoload.php'); + $this->assertFileExists($sut . '/index.php'); + + $this->assertScaffoldedFile($sut . '/sites/.gitignore', FALSE, 'example.sites.php'); + $this->assertScaffoldedFileDoesNotContain($sut . '/sites/.gitignore', 'settings.php'); + } + /** * Tests scaffold command disables .gitignore management when git not present. * diff --git a/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/Functional/ScaffoldTest.php b/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/Functional/ScaffoldTest.php index 357c6bd1d1ae..948dd8aa12fd 100644 --- a/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/Functional/ScaffoldTest.php +++ b/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/Functional/ScaffoldTest.php @@ -260,7 +260,20 @@ public function testDrupalDrupalFileWasReplaced() { public function scaffoldAppendTestValues() { return array_merge( $this->scaffoldAppendTestValuesToPermute(FALSE), - $this->scaffoldAppendTestValuesToPermute(TRUE) + $this->scaffoldAppendTestValuesToPermute(TRUE), + [ + [ + 'drupal-drupal-append-settings', + FALSE, + 'sites/default/settings.php', + '<?php + +// Default settings.php contents + +include __DIR__ . "/settings-custom-additions.php";', + 'NOTICE Creating a new file at [web-root]/sites/default/settings.php. Examine the contents and ensure that it came out correctly.', + ], + ] ); } @@ -275,6 +288,7 @@ protected function scaffoldAppendTestValuesToPermute($is_link) { [ 'drupal-drupal-test-append', $is_link, + 'robots.txt', '# robots.txt fixture scaffolded from "file-mappings" in drupal-drupal-test-append composer.json fixture. # This content is prepended to the top of the existing robots.txt fixture. # :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: @@ -285,11 +299,13 @@ protected function scaffoldAppendTestValuesToPermute($is_link) { # This content is appended to the bottom of the existing robots.txt fixture. # robots.txt fixture scaffolded from "file-mappings" in drupal-drupal-test-append composer.json fixture. ', + 'Prepend to [web-root]/robots.txt from assets/prepend-to-robots.txt', ], [ 'drupal-drupal-append-to-append', $is_link, + 'robots.txt', '# robots.txt fixture scaffolded from "file-mappings" in drupal-drupal-append-to-append composer.json fixture. # This content is prepended to the top of the existing robots.txt fixture. # :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: @@ -303,6 +319,7 @@ protected function scaffoldAppendTestValuesToPermute($is_link) { # :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: # This content is appended to the bottom of the existing robots.txt fixture. # robots.txt fixture scaffolded from "file-mappings" in drupal-drupal-append-to-append composer.json fixture.', + 'Append to [web-root]/robots.txt from assets/append-to-robots.txt', ], ]; } @@ -315,15 +332,20 @@ protected function scaffoldAppendTestValuesToPermute($is_link) { * core/tests/Drupal/Tests/Component/Scaffold/fixtures. * @param bool $is_link * Whether or not symlinking should be used. - * @param string $robots_txt_contents - * Regular expression matching expectations for robots.txt. + * @param string $scaffold_file_path + * Relative path to the scaffold file target we are testing. + * @param string $scaffold_file_contents + * A string expected to be contained inside the scaffold file we are testing. + * @param string $scaffoldOutputContains + * A string expected to be contained in the scaffold command output. * * @dataProvider scaffoldAppendTestValues */ - public function testDrupalDrupalFileWasAppended($fixture_name, $is_link, $robots_txt_contents) { + public function testDrupalDrupalFileWasAppended($fixture_name, $is_link, $scaffold_file_path, $scaffold_file_contents, $scaffoldOutputContains) { $result = $this->scaffoldSut($fixture_name, $is_link, FALSE); + $this->assertContains($scaffoldOutputContains, $result->scaffoldOutput()); - $this->assertScaffoldedFile($result->docroot() . '/robots.txt', FALSE, $robots_txt_contents); + $this->assertScaffoldedFile($result->docroot() . '/' . $scaffold_file_path, FALSE, $scaffold_file_contents); $this->assertCommonDrupalAssetsWereScaffolded($result->docroot(), $is_link); $this->assertAutoloadFileCorrect($result->docroot()); } diff --git a/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/fixtures/drupal-drupal-append-settings/assets/append-to-settings.txt b/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/fixtures/drupal-drupal-append-settings/assets/append-to-settings.txt new file mode 100644 index 000000000000..c6ed5c310161 --- /dev/null +++ b/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/fixtures/drupal-drupal-append-settings/assets/append-to-settings.txt @@ -0,0 +1 @@ +include __DIR__ . "/settings-custom-additions.php"; diff --git a/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/fixtures/drupal-drupal-append-settings/assets/default-settings.txt b/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/fixtures/drupal-drupal-append-settings/assets/default-settings.txt new file mode 100644 index 000000000000..b3621e07098e --- /dev/null +++ b/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/fixtures/drupal-drupal-append-settings/assets/default-settings.txt @@ -0,0 +1,3 @@ +<?php + +// Default settings.php contents diff --git a/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/fixtures/drupal-drupal-append-settings/composer.json.tmpl b/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/fixtures/drupal-drupal-append-settings/composer.json.tmpl new file mode 100644 index 000000000000..613e34dfffd3 --- /dev/null +++ b/core/tests/Drupal/Tests/Composer/Plugin/Scaffold/fixtures/drupal-drupal-append-settings/composer.json.tmpl @@ -0,0 +1,64 @@ +{ + "name": "fixtures/drupal-drupal-append-settings", + "type": "project", + "minimum-stability": "dev", + "prefer-stable": true, + "repositories": { + "packagist.org": false, + "composer-scaffold": { + "type": "path", + "url": "__PROJECT_ROOT__", + "options": { + "symlink": true + } + }, + "drupal-core-fixture": { + "type": "path", + "url": "../drupal-core-fixture", + "options": { + "symlink": true + } + }, + "drupal-assets-fixture": { + "type": "path", + "url": "../drupal-assets-fixture", + "options": { + "symlink": true + } + } + }, + "require": { + "drupal/core-composer-scaffold": "*", + "fixtures/drupal-core-fixture": "*" + }, + "extra": { + "drupal-scaffold": { + "allowed-packages": [ + "fixtures/drupal-core-fixture" + ], + "gitignore": true, + "locations": { + "web-root": "./" + }, + "symlink": __SYMLINK__, + "file-mapping": { + "[web-root]/.htaccess": false, + "[web-root]/sites/default/settings.php": { + "default": "assets/default-settings.txt", + "append": "assets/append-to-settings.txt" + } + } + }, + "installer-paths": { + "core": ["type:drupal-core"], + "modules/contrib/{$name}": ["type:drupal-module"], + "modules/custom/{$name}": ["type:drupal-custom-module"], + "profiles/contrib/{$name}": ["type:drupal-profile"], + "profiles/custom/{$name}": ["type:drupal-custom-profile"], + "themes/contrib/{$name}": ["type:drupal-theme"], + "themes/custom/{$name}": ["type:drupal-custom-theme"], + "libraries/{$name}": ["type:drupal-library"], + "drush/Commands/contrib/{$name}": ["type:drupal-drush"] + } + } +} -- GitLab