diff --git a/RoboFile.php b/RoboFile.php new file mode 100644 index 0000000..d499361 --- /dev/null +++ b/RoboFile.php @@ -0,0 +1,37 @@ +=5.4.5", "composer-plugin-api": "^1.0.0", - "drupal-composer/drupal-scaffold": "^2.2" + "drupal-composer/drupal-scaffold": "^2.2", + "symfony/filesystem": "~2.8|~3.0" + }, + "config": { + "bin-dir": "bin/" }, "autoload": { "psr-4": { diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..9d3864d --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,40 @@ +version: '2' +services: + web: + image: uofa/apache2-php7-dev:shepherd + hostname: ${PROJECT} + environment: + SITE_TITLE: Drupal site + SITE_MAIL: site@example.com + SITE_ADMIN_EMAIL: admin@example.com + SITE_ADMIN_USERNAME: admin + SITE_ADMIN_PASSWORD: password + VIRTUAL_HOST: ${PROJECT}.${DOMAIN} + SSH_AUTH_SOCK: ${CONTAINER_SSH_AUTH_SOCK} + DATABASE_HOST: db + DATABASE_PORT: 3306 + DATABASE_NAME: drupal + DATABASE_USER: user + DATABASE_PASSWORD: password + PRIVATE_DIR: /shared/private + PUBLIC_DIR: web/sites/default/files + HASH_SALT: random-hash + CONFIG_SYNC_DIRECTORY: /shared/private/random-hash/sync + volumes: + - .:/code + - shared:/shared + - ${HOST_SSH_AUTH_SOCK_DIR}:/ssh + db: + image: mariadb + environment: + MYSQL_DATABASE: drupal + MYSQL_USER: user + MYSQL_PASSWORD: password + MYSQL_ROOT_PASSWORD: super-secret-password + mail: + image: helder/mailcatcher + environment: + - VIRTUAL_HOST=mail.${PROJECT}.${DOMAIN} +volumes: + shared: + ssh: diff --git a/src/Handler.php b/src/Handler.php index 63b3bc0..36bb45d 100644 --- a/src/Handler.php +++ b/src/Handler.php @@ -4,10 +4,8 @@ use Composer\Composer; use Composer\IO\IOInterface; -use Composer\Util\Filesystem; -use Composer\Util\RemoteFilesystem; -use DrupalComposer\DrupalScaffold\FileFetcher; -use Symfony\Component\Filesystem\Filesystem as SymfonyFilesystem; +use Composer\Util\Filesystem as ComposerFilesystem; +use Symfony\Component\Filesystem\Filesystem; class Handler { @@ -22,6 +20,11 @@ class Handler */ protected $io; + /** + * @var \Symfony\Component\Filesystem\Filesystem + */ + protected $filesystem; + /** * Handler constructor. * @@ -32,6 +35,7 @@ public function __construct(Composer $composer, IOInterface $io) { $this->composer = $composer; $this->io = $io; + $this->filesystem = new Filesystem(); } /** @@ -41,28 +45,207 @@ public function __construct(Composer $composer, IOInterface $io) */ public function onPostCmdEvent(\Composer\Script\Event $event) { - $this->downloadScaffold(); + $event->getIO()->write("Updating Shepherd scaffold files."); + $this->updateShepherdScaffoldFiles(); + $event->getIO()->write("Creating necessary directories."); + $this->createDirectories(); + $event->getIO()->write("Creating settings.php file if not present."); + $this->createSettingsFile(); + $event->getIO()->write("Creating services.yml file if not present."); + $this->createServicesFile(); } /** - * Downloads Shepherd Drupal scaffold files. + * Update the Shepherd scaffold files. */ - public function downloadScaffold() + public function updateShepherdScaffoldFiles() { - $source = 'https://raw.githubusercontent.com/universityofadelaide/shepherd-drupal-scaffold/{version}/{path}'; - $filenames = [ - 'dsh', - 'RoboFileBase.php', - ]; - $version = 'master'; - $destination = dirname($this->composer->getConfig() - ->get('vendor-dir')); - - $fetcher = new FileFetcher( - new RemoteFilesystem($this->io), - $source, - $filenames + $packagePath = $this->getPackagePath(); + $projectPath = $this->getProjectPath(); + + // Always copy and replace these files. + $this->copyFiles( + $packagePath, + $projectPath, + [ + 'dsh', + 'RoboFileBase.php', + ], + true ); - $fetcher->fetch($version, $destination); + + // Only copy these files if they do not exist at the destination. + $this->copyFiles( + $packagePath, + $projectPath, + [ + 'docker-compose.yml', + 'RoboFile.php', + ] + ); + } + + /** + * Ensure necessary directories exist. + */ + public function createDirectories() + { + $root = $this->getDrupalRootPath(); + $dirs = [ + 'modules', + 'profiles', + 'themes', + ]; + + // Required for unit testing. + foreach ($dirs as $dir) { + if (!$this->filesystem->exists($root . '/'. $dir)) { + $this->filesystem->mkdir($root . '/'. $dir); + $this->filesystem->touch($root . '/'. $dir . '/.gitkeep'); + } + } + } + + /** + * Create settings.php file and inject Shepherd-specific settings. + * + * Note: does nothing if the file already exists. + */ + public function createSettingsFile() + { + $root = $this->getDrupalRootPath(); + + // If the settings.php is not present, and the default version is... + if (!$this->filesystem->exists($root . '/sites/default/settings.php') && $this->filesystem->exists($root . '/sites/default/default.settings.php')) { + $this->filesystem->copy($root . '/sites/default/default.settings.php', $root . '/sites/default/settings.php'); + $this->filesystem->chmod($root . '/sites/default/settings.php', 0666); + + $shepherdSettings = "\n/**\n * START SHEPHERD CONFIG\n */\n" . + "\$databases['default']['default'] = array (\n" . + " 'database' => getenv('DATABASE_NAME'),\n" . + " 'username' => getenv('DATABASE_USER'),\n" . + " 'password' => getenv('DATABASE_PASSWORD_FILE') ? file_get_contents(getenv('DATABASE_PASSWORD_FILE')) : getenv('DATABASE_PASSWORD'),\n" . + " 'host' => getenv('DATABASE_HOST'),\n" . + " 'port' => getenv('DATABASE_PORT') ?: '3306',\n" . + " 'driver' => getenv('DATABASE_DRIVER') ?: 'mysql',\n" . + " 'prefix' => getenv('DATABASE_PREFIX') ?: '',\n" . + " 'collation' => getenv('DATABASE_COLLATION') ?: 'utf8mb4_general_ci',\n" . + " 'namespace' => getenv('DATABASE_NAMESPACE') ?: 'Drupal\\\\Core\\\\Database\\\\Driver\\\\mysql',\n" . + ");\n" . + "\$settings['file_private_path'] = getenv('PRIVATE_DIR');\n" . + "\$settings['hash_salt'] = getenv('HASH_SALT') ?: '" . str_replace(['+', '/', '='], ['-', '_', ''], base64_encode(random_bytes(55))) . "';\n" . + "\$config_directories['sync'] = getenv('CONFIG_SYNC_DIRECTORY') ?: 'sites/default/files/config_" . str_replace(['+', '/', '='], ['-', '_', ''], base64_encode(random_bytes(55))) . "/sync';\n" . + "if (! is_dir(\$app_root . '/' . \$config_directories['sync'])) mkdir(\$app_root . '/' . \$config_directories['sync'], 0777, true);\n" . + "\$settings['shepherd_site_id'] = getenv('SHEPHERD_SITE_ID');\n" . + "\$settings['shepherd_url'] = getenv('SHEPHERD_URL');\n" . + "\$settings['shepherd_token'] = getenv('SHEPHERD_TOKEN_FILE') ? file_get_contents(getenv('SHEPHERD_TOKEN_FILE')) : getenv('SHEPHERD_TOKEN');\n" . + "/**\n * END SHEPHERD CONFIG\n */\n" . + "\n" . + "/**\n * START LOCAL CONFIG\n */\n" . + "if (file_exists(__DIR__ . '/settings.local.php')) {\n" . + " include __DIR__ . '/settings.local.php';\n" . + "}\n" . + "/**\n * END LOCAL CONFIG\n */\n" + ; + + // Append Shepherd-specific environment variable settings to settings.php. + file_put_contents( + $root . '/sites/default/settings.php', + $shepherdSettings, + FILE_APPEND + ); + } + } + + /** + * Create services.yml file if not present. + */ + public function createServicesFile() + { + $root = $this->getDrupalRootPath(); + + if (!$this->filesystem->exists($root . '/sites/default/services.yml') && $this->filesystem->exists($root . '/sites/default/default.services.yml')) { + $this->filesystem->copy($root . '/sites/default/default.services.yml', $root . '/sites/default/services.yml'); + $this->filesystem->chmod($root . '/sites/default/services.yml', 0666); + } + } + + /** + * Copy files from origin to destination, optionally overwritting existing. + * + * @param bool $overwriteExisting + * If true, replace existing files. Defaults to false. + */ + public function copyFiles($origin, $destination, $filenames, $overwriteExisting = false) + { + foreach ($filenames as $filename) { + // Skip copying files that already exist at the destination. + if (! $overwriteExisting && $this->filesystem->exists($destination . '/' . $filename)) { + continue; + } + $this->filesystem->copy( + $origin . '/' . $filename, + $destination . '/' . $filename, + true + ); + } + } + + /** + * Get the path to the vendor directory. + * + * E.g. /home/user/code/project/vendor + * + * @return string + */ + public function getVendorPath() + { + // Load ComposerFilesystem to get access to path normalisation. + $composerFilesystem = new ComposerFilesystem(); + + $config = $this->composer->getConfig(); + $composerFilesystem->ensureDirectoryExists($config->get('vendor-dir')); + $vendorPath = $composerFilesystem->normalizePath(realpath($config->get('vendor-dir'))); + + return $vendorPath; + } + + /** + * Get the path to the project directory. + * + * E.g. /home/user/code/project + * + * @return string + */ + public function getProjectPath() + { + $projectPath = dirname($this->getVendorPath()); + return $projectPath; + } + + /** + * Get the path to the package directory. + * + * E.g. /home/user/code/project/vendor/universityofadelaide/shepherd-drupal-scaffold + * + * @return string + */ + public function getPackagePath() + { + $packagePath = $this->getVendorPath() . '/universityofadelaide/shepherd-drupal-scaffold'; + return $packagePath; + } + + /** + * Get the path to the Drupal root directory. + * + * E.g. /home/user/code/project/web + * + * @return string + */ + public function getDrupalRootPath() + { + $drupalRootPath = $this->getProjectPath() . '/web'; + return $drupalRootPath; } } diff --git a/src/Plugin.php b/src/Plugin.php index 668996f..9416043 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -38,6 +38,7 @@ public function activate(Composer $composer, IOInterface $io) public static function getSubscribedEvents() { return array( + ScriptEvents::POST_INSTALL_CMD => 'postCmd', ScriptEvents::POST_UPDATE_CMD => 'postCmd', ); } @@ -58,10 +59,10 @@ public function postCmd(\Composer\Script\Event $event) * scaffold files. * * @param \Composer\Script\Event $event - */ + */ public static function scaffold(\Composer\Script\Event $event) { $handler = new Handler($event->getComposer(), $event->getIO()); - $handler->downloadScaffold(); + $handler->onPostCmdEvent($event); } }