diff --git a/README.md b/README.md index 03e1493..e147396 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,9 @@ of your root `composer.json`. "includes": [ "sites/default/example.settings.my.php" ], + "includes-dev": [ + "my_settings_file_for_development.php" + ], "initial": { "sites/default/default.services.yml": "sites/default/services.yml", "sites/default/default.settings.php": "sites/default/settings.php" @@ -58,25 +61,29 @@ default. Default includes are provided by the plugin: ``` -.csslintrc -.editorconfig -.eslintignore -.eslintrc (Drupal <= 8.2.x) -.eslintrc.json (Drupal >= 8.3.x) -.gitattributes -.ht.router.php (Drupal >= 8.5.x) .htaccess index.php robots.txt sites/default/default.settings.php sites/default/default.services.yml -sites/development.services.yml sites/example.settings.local.php sites/example.sites.php update.php web.config ``` +Default includes dev are provided by the plugin: +``` +.csslintrc +.editorconfig +.eslintignore +.eslintrc (Drupal <= 8.2.x) +.eslintrc.json (Drupal >= 8.3.x) +.gitattributes +.ht.router.php (Drupal >= 8.5.x) +sites/development.services.yml +``` + When setting `omit-defaults` to `true`, neither the default excludes nor the default includes will be provided; in this instance, only those files explicitly listed in the `excludes` and `includes` options will be considered. If @@ -87,6 +94,9 @@ The `initial` hash lists files that should be copied over only if they do not exist in the destination. The key specifies the path to the source file, and the value indicates the path to the destination file. +The `includes-dev` hash lists files that should be copied over only if they do not +exist in the destination and if the dev packages are installed. + ## Limitation When using Composer to install or update the Drupal development branch, the diff --git a/src/DrupalScaffoldCommand.php b/src/DrupalScaffoldCommand.php index 485a2f5..970f269 100644 --- a/src/DrupalScaffoldCommand.php +++ b/src/DrupalScaffoldCommand.php @@ -4,6 +4,7 @@ use Composer\Command\BaseCommand; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; /** @@ -20,7 +21,10 @@ protected function configure() { parent::configure(); $this ->setName('drupal:scaffold') - ->setDescription('Update the Drupal scaffold files.'); + ->setDescription('Update the Drupal scaffold files.') + ->setDefinition(array( + new InputOption('no-dev', NULL, InputOption::VALUE_NONE, 'Disables download of include-dev files.'), + )); } /** @@ -28,7 +32,7 @@ protected function configure() { */ protected function execute(InputInterface $input, OutputInterface $output) { $handler = new Handler($this->getComposer(), $this->getIO()); - $handler->downloadScaffold(); + $handler->downloadScaffold(!$input->getOption('no-dev')); // Generate the autoload.php file after generating the scaffold files. $handler->generateAutoload(); } diff --git a/src/Handler.php b/src/Handler.php index 1bdefe0..db3ed4a 100644 --- a/src/Handler.php +++ b/src/Handler.php @@ -133,7 +133,7 @@ public function onPostCmdEvent(Event $event) { // Only install the scaffolding if drupal/core was installed, // AND there are no scaffolding files present. if (isset($this->drupalCorePackage)) { - $this->downloadScaffold(); + $this->downloadScaffold($event->isDevMode()); // Generate the autoload.php file after generating the scaffold files. $this->generateAutoload(); } @@ -141,14 +141,22 @@ public function onPostCmdEvent(Event $event) { /** * Downloads drupal scaffold files for the current process. + * + * @param bool $dev + * TRUE if dev packages are installed. FALSE otherwise. */ - public function downloadScaffold() { + public function downloadScaffold($dev = TRUE) { $drupalCorePackage = $this->getDrupalCorePackage(); $webroot = realpath($this->getWebRoot()); - // Collect options, excludes and settings files. + // Collect options, excludes, dev and settings files. $options = $this->getOptions(); - $files = array_diff($this->getIncludes(), $this->getExcludes()); + $includes = $this->getIncludes(); + // Check dev files if necessary. + if ($dev) { + $includes = array_merge($includes, $this->getIncludesDev()); + } + $files = array_diff($includes, $this->getExcludes()); // Call any pre-scaffold scripts that may be defined. $dispatcher = new EventDispatcher($this->composer, $this->io); @@ -304,6 +312,15 @@ protected function getIncludes() { return $this->getNamedOptionList('includes', 'getIncludesDefault'); } + /** + * Retrieve list of additional dev files from optional "extra" configuration. + * + * @return array + */ + protected function getIncludesDev() { + return $this->getNamedOptionList('includes-dev', 'getIncludesDevDefault'); + } + /** * Retrieve list of initial files from optional "extra" configuration. * @@ -342,6 +359,7 @@ protected function getOptions() { 'omit-defaults' => FALSE, 'excludes' => [], 'includes' => [], + 'includes-dev' => [], 'initial' => [], 'source' => 'https://cgit.drupalcode.org/drupal/plain/{path}?h={version}', // Github: https://raw.githubusercontent.com/drupal/drupal/{version}/{path} @@ -360,32 +378,47 @@ protected function getExcludesDefault() { * Holds default settings files list. */ protected function getIncludesDefault() { - $version = $this->getDrupalCoreVersion($this->getDrupalCorePackage()); - list($major, $minor) = explode('.', $version, 3); - $version = "$major.$minor"; - /** * Files from 8.3.x * * @see https://cgit.drupalcode.org/drupal/tree/?h=8.3.x */ $common = [ - '.csslintrc', - '.editorconfig', - '.eslintignore', - '.gitattributes', '.htaccess', 'index.php', 'robots.txt', 'sites/default/default.settings.php', 'sites/default/default.services.yml', - 'sites/development.services.yml', 'sites/example.settings.local.php', 'sites/example.sites.php', 'update.php', 'web.config', ]; + return $common; + } + + /** + * Holds default dev files list. + */ + protected function getIncludesDevDefault() { + $version = $this->getDrupalCoreVersion($this->getDrupalCorePackage()); + list($major, $minor) = explode('.', $version, 3); + $version = "$major.$minor"; + + /** + * Files from 8.3.x + * + * @see http://cgit.drupalcode.org/drupal/tree/?h=8.3.x + */ + $common = [ + '.csslintrc', + '.editorconfig', + '.eslintignore', + '.gitattributes', + 'sites/development.services.yml', + ]; + // Version specific variations. if (Semver::satisfies($version, '<8.3')) { $common[] = '.eslintrc'; diff --git a/src/Plugin.php b/src/Plugin.php index 7833903..318c3ad 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -95,7 +95,7 @@ public function postCmd(Event $event) { public static function scaffold(Event $event) { @trigger_error('\DrupalComposer\DrupalScaffold\Plugin::scaffold is deprecated since version 2.5.0 and will be removed in 3.0. Use "composer drupal:scaffold" instead.', E_USER_DEPRECATED); $handler = new Handler($event->getComposer(), $event->getIO()); - $handler->downloadScaffold(); + $handler->downloadScaffold($event->isDevMode()); // Generate the autoload.php file after generating the scaffold files. $handler->generateAutoload(); } diff --git a/tests/BaseTest.php b/tests/BaseTest.php new file mode 100644 index 0000000..492dc74 --- /dev/null +++ b/tests/BaseTest.php @@ -0,0 +1,141 @@ +rootDir = realpath(realpath(__DIR__ . '/..')); + + // Prepare temp directory. + $this->fs = new Filesystem(); + $this->tmpDir = realpath(sys_get_temp_dir()) . DIRECTORY_SEPARATOR . 'drupal-scaffold'; + $this->ensureDirectoryExistsAndClear($this->tmpDir); + + $this->writeTestReleaseTag(); + $this->writeComposerJSON(); + + chdir($this->tmpDir); + } + + /** + * tearDown + * + * @return void + */ + public function tearDown() { + $this->fs->removeDirectory($this->tmpDir); + $this->git(sprintf('tag -d "%s"', $this->tmpReleaseTag)); + } + + /** + * Writes the default composer json to the temp direcoty. + */ + protected function writeComposerJSON() { + $json = json_encode($this->composerJSONDefaults(), JSON_PRETTY_PRINT); + // Write composer.json. + file_put_contents($this->tmpDir . '/composer.json', $json); + } + + /** + * Writes a tag for the current commit, so we can reference it directly in the + * composer.json. + */ + protected function writeTestReleaseTag() { + // Tag the current state. + $this->tmpReleaseTag = '999.0.' . time(); + $this->git(sprintf('tag -a "%s" -m "%s"', $this->tmpReleaseTag, 'Tag for testing this exact commit')); + } + + /** + * Provides the default composer.json data. + * + * @return array + */ + protected function composerJSONDefaults() { + return array( + 'repositories' => array( + array( + 'type' => 'vcs', + 'url' => $this->rootDir, + ), + ), + 'require' => array( + 'drupal-composer/drupal-scaffold' => $this->tmpReleaseTag, + 'composer/installers' => '^1.0.20', + 'drupal/core' => '8.0.0', + ), + 'minimum-stability' => 'dev', + ); + } + + /** + * Wrapper for the composer command. + * + * @param string $command + * Composer command name, arguments and/or options + */ + protected function composer($command) { + chdir($this->tmpDir); + passthru(escapeshellcmd($this->rootDir . '/vendor/bin/composer ' . $command), $exit_code); + if ($exit_code !== 0) { + throw new \Exception('Composer returned a non-zero exit code'); + } + } + + /** + * Wrapper for git command in the root directory. + * + * @param $command + * Git command name, arguments and/or options. + */ + protected function git($command) { + chdir($this->rootDir); + passthru(escapeshellcmd('git ' . $command), $exit_code); + if ($exit_code !== 0) { + throw new \Exception('Git returned a non-zero exit code'); + } + } + + /** + * Makes sure the given directory exists and has no content. + * + * @param string $directory + */ + protected function ensureDirectoryExistsAndClear($directory) { + if (is_dir($directory)) { + $this->fs->removeDirectory($directory); + } + mkdir($directory, 0777, TRUE); + } + +} diff --git a/tests/HandlerTest.php b/tests/HandlerTest.php new file mode 100644 index 0000000..b74938c --- /dev/null +++ b/tests/HandlerTest.php @@ -0,0 +1,42 @@ +tmpDir . DIRECTORY_SEPARATOR . 'index.php'; + $developmentScaffoldFile = $this->tmpDir . DIRECTORY_SEPARATOR . 'sites' . DIRECTORY_SEPARATOR . 'development.services.yml'; + $this->assertFileNotExists($exampleScaffoldFile, 'Scaffold file should not exist.'); + $this->assertFileNotExists($developmentScaffoldFile, 'Development scaffold file should not exist.'); + $this->composer('install --no-dev'); + $this->assertFileExists($exampleScaffoldFile, 'Scaffold file should exist.'); + $this->assertFileNotExists($developmentScaffoldFile, 'Development scaffold file should not exist.'); + $this->composer('drupal:scaffold'); + $this->assertFileExists($exampleScaffoldFile, 'Scaffold file should exist.'); + $this->assertFileExists($developmentScaffoldFile, 'Development scaffold file should exist.'); + } + + /** + * Add prefer-stable true to speed up tests. + * + * @return array + */ + protected function composerJSONDefaults() { + $composerJsonDefault = parent::composerJSONDefaults(); + $composerJsonDefault['prefer-stable'] = TRUE; + return $composerJsonDefault; + } + +} diff --git a/tests/PluginTest.php b/tests/PluginTest.php index a392e06..6b2c6d5 100644 --- a/tests/PluginTest.php +++ b/tests/PluginTest.php @@ -2,60 +2,10 @@ namespace DrupalComposer\DrupalScaffold\Tests; -use Composer\Util\Filesystem; -use PHPUnit\Framework\TestCase; - /** * Tests composer plugin functionality. */ -class PluginTest extends TestCase { - - /** - * @var \Composer\Util\Filesystem - */ - protected $fs; - - /** - * @var string - */ - protected $tmpDir; - - /** - * @var string - */ - protected $rootDir; - - /** - * @var string - */ - protected $tmpReleaseTag; - - /** - * SetUp test. - */ - public function setUp() { - $this->rootDir = realpath(realpath(__DIR__ . '/..')); - - // Prepare temp directory. - $this->fs = new Filesystem(); - $this->tmpDir = realpath(sys_get_temp_dir()) . DIRECTORY_SEPARATOR . 'drupal-scaffold'; - $this->ensureDirectoryExistsAndClear($this->tmpDir); - - $this->writeTestReleaseTag(); - $this->writeComposerJSON(); - - chdir($this->tmpDir); - } - - /** - * TearDown. - * - * @return void - */ - public function tearDown() { - $this->fs->removeDirectory($this->tmpDir); - $this->git(sprintf('tag -d "%s"', $this->tmpReleaseTag)); - } +class PluginTest extends BaseTest { /** * Tests a simple composer install without core, but adding core later. @@ -63,7 +13,7 @@ public function tearDown() { public function testComposerInstallAndUpdate() { $exampleScaffoldFile = $this->tmpDir . DIRECTORY_SEPARATOR . 'index.php'; $this->assertFileNotExists($exampleScaffoldFile, 'Scaffold file should not be exist.'); - $this->composer('install --no-dev --prefer-dist'); + $this->composer('install --prefer-dist'); $this->assertFileExists($this->tmpDir . DIRECTORY_SEPARATOR . 'core', 'Drupal core is installed.'); $this->assertFileExists($exampleScaffoldFile, 'Scaffold file should be automatically installed.'); $this->fs->remove($exampleScaffoldFile); @@ -77,7 +27,7 @@ public function testComposerInstallAndUpdate() { touch($exampleScaffoldFile); $mtime_touched = filemtime($exampleScaffoldFile); // Requiring a newer version triggers "composer update". - $this->composer('require --update-with-dependencies --prefer-dist --update-no-dev drupal/core:"' . $version . '"'); + $this->composer('require --update-with-dependencies --prefer-dist drupal/core:"' . $version . '"'); clearstatcache(); $mtime_after = filemtime($exampleScaffoldFile); $this->assertNotEquals($mtime_after, $mtime_touched, 'Scaffold file was modified by composer update. (' . $version . ')'); @@ -111,85 +61,4 @@ public function testComposerInstallAndUpdate() { $this->assertNotEquals(file_get_contents($exampleScaffoldFile), 1, 'Scaffold file was modified by custom command.'); } - /** - * Writes the default composer json to the temp direcoty. - */ - protected function writeComposerJSON() { - $json = json_encode($this->composerJSONDefaults(), JSON_PRETTY_PRINT); - // Write composer.json. - file_put_contents($this->tmpDir . '/composer.json', $json); - } - - /** - * Writes a tag for the current commit, so we can reference it directly in the - * composer.json. - */ - protected function writeTestReleaseTag() { - // Tag the current state. - $this->tmpReleaseTag = '999.0.' . time(); - $this->git(sprintf('tag -a "%s" -m "%s"', $this->tmpReleaseTag, 'Tag for testing this exact commit')); - } - - /** - * Provides the default composer.json data. - * - * @return array - */ - protected function composerJSONDefaults() { - return array( - 'repositories' => array( - array( - 'type' => 'vcs', - 'url' => $this->rootDir, - ), - ), - 'require' => array( - 'drupal-composer/drupal-scaffold' => $this->tmpReleaseTag, - 'composer/installers' => '^1.0.20', - 'drupal/core' => '8.0.0', - ), - 'minimum-stability' => 'dev', - ); - } - - /** - * Wrapper for the composer command. - * - * @param string $command - * Composer command name, arguments and/or options. - */ - protected function composer($command) { - chdir($this->tmpDir); - passthru(escapeshellcmd($this->rootDir . '/vendor/bin/composer ' . $command), $exit_code); - if ($exit_code !== 0) { - throw new \Exception('Composer returned a non-zero exit code'); - } - } - - /** - * Wrapper for git command in the root directory. - * - * @param $command - * Git command name, arguments and/or options. - */ - protected function git($command) { - chdir($this->rootDir); - passthru(escapeshellcmd('git ' . $command), $exit_code); - if ($exit_code !== 0) { - throw new \Exception('Git returned a non-zero exit code'); - } - } - - /** - * Makes sure the given directory exists and has no content. - * - * @param string $directory - */ - protected function ensureDirectoryExistsAndClear($directory) { - if (is_dir($directory)) { - $this->fs->removeDirectory($directory); - } - mkdir($directory, 0777, TRUE); - } - }