Update Composer, update everything

This commit is contained in:
Oliver Davies 2018-11-23 12:29:20 +00:00
parent ea3e94409f
commit dda5c284b6
19527 changed files with 1135420 additions and 351004 deletions

View file

@ -0,0 +1,17 @@
# Drupal editor configuration normalization
# @see http://editorconfig.org/
# This is the top-most .editorconfig file; do not search in parent directories.
root = true
# All files.
[*]
end_of_line = LF
indent_style = space
indent_size = 2
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[{composer.json,composer.lock}]
indent_size = 4

View file

@ -0,0 +1 @@
vendor

View file

@ -0,0 +1,52 @@
#!/bin/bash
SCENARIO=$1
DEPENDENCIES=${2-install}
# Convert the aliases 'highest', 'lowest' and 'lock' to
# the corresponding composer command to run.
case $DEPENDENCIES in
highest)
DEPENDENCIES=update
;;
lowest)
DEPENDENCIES='update --prefer-lowest'
;;
lock|default|"")
DEPENDENCIES=install
;;
esac
original_name=scenarios
recommended_name=".scenarios.lock"
base="$original_name"
if [ -d "$recommended_name" ] ; then
base="$recommended_name"
fi
# If scenario is not specified, install the lockfile at
# the root of the project.
dir="$base/${SCENARIO}"
if [ -z "$SCENARIO" ] || [ "$SCENARIO" == "default" ] ; then
SCENARIO=default
dir=.
fi
# Test to make sure that the selected scenario exists.
if [ ! -d "$dir" ] ; then
echo "Requested scenario '${SCENARIO}' does not exist."
exit 1
fi
echo
echo "::"
echo ":: Switch to ${SCENARIO} scenario"
echo "::"
echo
set -ex
composer -n validate --working-dir=$dir --no-check-all --ansi
composer -n --working-dir=$dir ${DEPENDENCIES} --prefer-dist --no-scripts
composer -n --working-dir=$dir info

View file

@ -0,0 +1 @@
vendor

View file

@ -0,0 +1,53 @@
{
"name": "drupal-composer/drupal-scaffold",
"description": "Composer Plugin for updating the Drupal scaffold files when using drupal/core",
"type": "composer-plugin",
"license": "GPL-2.0-or-later",
"require": {
"php": ">=5.4.5",
"composer-plugin-api": "^1.0.0",
"composer/semver": "^1.4"
},
"autoload": {
"psr-4": {
"DrupalComposer\\DrupalScaffold\\": "src/"
}
},
"extra": {
"class": "DrupalComposer\\DrupalScaffold\\Plugin",
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"scripts": {
"cs": "phpcs --standard=PSR2 -n src",
"cbf": "phpcbf --standard=PSR2 -n src",
"unit": "phpunit --colors=always",
"lint": [
"find src -name '*.php' -print0 | xargs -0 -n1 php -l"
],
"test": [
"@lint",
"@unit"
],
"scenario": ".scenarios.lock/install",
"post-update-cmd": [
"create-scenario phpunit4 'phpunit/phpunit:^4.8.36' --platform-php '5.5.27'"
]
},
"config": {
"optimize-autoloader": true,
"sort-packages": true,
"platform": {
"php": "5.5.27"
},
"vendor-dir": "../../vendor"
},
"require-dev": {
"composer/composer": "dev-master",
"g1a/composer-test-scenarios": "^2.1.0",
"phpunit/phpunit": "^4.8.36",
"squizlabs/php_codesniffer": "^2.8"
},
"minimum-stability": "stable"
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1 @@
../../src

View file

@ -0,0 +1 @@
../../tests

View file

@ -0,0 +1,42 @@
language: php
branches:
# Only test the master branch and SemVer tags.
only:
- master
- /^[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+.*$/
matrix:
fast_finish: true
include:
- php: 7.2
env: 'DEPENCENCIES=highest'
- php: 7.2
- php: 7.1
- php: 7.0.11
- php: 5.6
env: 'SCENARIO=phpunit4'
- php: 5.5
env: 'SCENARIO=phpunit4'
- php: 5.5
env: 'SCENARIO=phpunit4 DEPENDENCIES=lowest'
sudo: false
git:
depth: 10000
before_install:
- phpenv config-rm xdebug.ini
- composer --verbose self-update
- composer --version
install:
- 'composer scenario "${SCENARIO}" "${DEPENDENCIES}"'
before_script:
- git config --global user.email "travisci@example.com"
- git config --global user.name "Travis CI Test"
script:
- composer test

View file

@ -0,0 +1,121 @@
# drupal-scaffold
[![Build Status](https://travis-ci.org/drupal-composer/drupal-scaffold.svg?branch=master)](https://travis-ci.org/drupal-composer/drupal-scaffold)
Composer plugin for automatically downloading Drupal scaffold files (like
`index.php`, `update.php`, …) when using `drupal/core` via Composer.
It is recommended that the vendor directory be placed in its standard location
at the project root, outside of the Drupal root; however, the location of the
vendor directory and the name of the Drupal root may be placed in whatever
location suits the project. Drupal-scaffold will generate the autoload.php
file at the Drupal root to require the Composer-generated autoload file in the
vendor directory.
## Usage
Run `composer require drupal-composer/drupal-scaffold:dev-master` in your composer
project before installing or updating `drupal/core`.
Once drupal-scaffold is required by your project, it will automatically update
your scaffold files whenever `composer update` changes the version of
`drupal/core` installed.
## Configuration
You can configure the plugin with providing some settings in the `extra` section
of your root `composer.json`.
```json
{
"extra": {
"drupal-scaffold": {
"source": "https://cgit.drupalcode.org/drupal/plain/{path}?h={version}",
"excludes": [
"google123.html",
"robots.txt"
],
"includes": [
"sites/default/example.settings.my.php"
],
"initial": {
"sites/default/default.services.yml": "sites/default/services.yml",
"sites/default/default.settings.php": "sites/default/settings.php"
},
"omit-defaults": false
}
}
}
```
The `source` option may be used to specify the URL to download the
scaffold files from; the default source is drupal.org. The literal string
`{version}` in the `source` option is replaced with the current version of
Drupal core being updated prior to download.
With the `drupal-scaffold` option `excludes`, you can provide additional paths
that should not be copied or overwritten. The plugin provides no excludes by
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
```
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
`omit-defaults` is `false` (the default), then any items listed in `excludes`
or `includes` will be in addition to the usual defaults.
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.
## Limitation
When using Composer to install or update the Drupal development branch, the
scaffold files are always taken from the HEAD of the branch (or, more
specifically, from the most recent development .tar.gz archive). This might
not be what you want when using an old development version (e.g. when the
version is fixed via composer.lock). To avoid problems, always commit your
scaffold files to the repository any time that composer.lock is committed.
Note that the correct scaffold files are retrieved when using a tagged release
of `drupal/core` (recommended).
## Custom command
The plugin by default is only downloading the scaffold files when installing or
updating `drupal/core`. If you want to call it manually, you have to add the
command callback to the `scripts`-section of your root `composer.json`, like this:
```json
{
"scripts": {
"drupal-scaffold": "DrupalComposer\\DrupalScaffold\\Plugin::scaffold"
}
}
```
After that you can manually download the scaffold files according to your
configuration by using `composer drupal-scaffold`.
It is assumed that the scaffold files will be committed to the repository, to
ensure that the correct files are used on the CI server (see **Limitation**,
above). After running `composer install` for the first time commit the scaffold
files to your repository.

View file

@ -0,0 +1,51 @@
{
"name": "drupal-composer/drupal-scaffold",
"description": "Composer Plugin for updating the Drupal scaffold files when using drupal/core",
"type": "composer-plugin",
"license": "GPL-2.0-or-later",
"require": {
"php": ">=5.4.5",
"composer-plugin-api": "^1.0.0",
"composer/semver": "^1.4"
},
"autoload": {
"psr-4": {
"DrupalComposer\\DrupalScaffold\\": "src/"
}
},
"extra": {
"class": "DrupalComposer\\DrupalScaffold\\Plugin",
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"scripts": {
"cs": "phpcs --standard=PSR2 -n src",
"cbf": "phpcbf --standard=PSR2 -n src",
"unit": "phpunit --colors=always",
"lint": [
"find src -name '*.php' -print0 | xargs -0 -n1 php -l"
],
"test": [
"@lint",
"@unit"
],
"scenario": ".scenarios.lock/install",
"post-update-cmd": [
"create-scenario phpunit4 'phpunit/phpunit:^4.8.36' --platform-php '5.5.27'"
]
},
"config": {
"optimize-autoloader": true,
"sort-packages": true,
"platform": {
"php": "7.0.8"
}
},
"require-dev": {
"composer/composer": "dev-master",
"g1a/composer-test-scenarios": "^2.1.0",
"phpunit/phpunit": "^6",
"squizlabs/php_codesniffer": "^2.8"
}
}

2490
vendor/drupal-composer/drupal-scaffold/composer.lock generated vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
backupGlobals="false"
colors="true"
bootstrap="vendor/autoload.php"
verbose="true"
>
<testsuites>
<testsuite name="drupal-scaffold">
<directory>./tests/</directory>
</testsuite>
</testsuites>
</phpunit>

View file

@ -0,0 +1,21 @@
<?php
namespace DrupalComposer\DrupalScaffold;
use Composer\Plugin\Capability\CommandProvider as CommandProviderCapability;
/**
* List of all commands provided by this package.
*/
class CommandProvider implements CommandProviderCapability {
/**
* {@inheritdoc}
*/
public function getCommands() {
return [
new DrupalScaffoldCommand(),
];
}
}

View file

@ -0,0 +1,36 @@
<?php
namespace DrupalComposer\DrupalScaffold;
use Composer\Command\BaseCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* The "drupal:scaffold" command class.
*
* Downloads scaffold files and generates the autoload.php file.
*/
class DrupalScaffoldCommand extends BaseCommand {
/**
* {@inheritdoc}
*/
protected function configure() {
parent::configure();
$this
->setName('drupal:scaffold')
->setDescription('Update the Drupal scaffold files.');
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output) {
$handler = new Handler($this->getComposer(), $this->getIO());
$handler->downloadScaffold();
// Generate the autoload.php file after generating the scaffold files.
$handler->generateAutoload();
}
}

View file

@ -0,0 +1,87 @@
<?php
namespace DrupalComposer\DrupalScaffold;
use Composer\IO\IOInterface;
use Composer\Util\Filesystem;
use Composer\Util\RemoteFilesystem;
/**
* Downloads all required files and writes it to the file system.
*/
class FileFetcher {
/**
* @var \Composer\Util\RemoteFilesystem
*/
protected $remoteFilesystem;
/**
* @var \Composer\IO\IOInterface
*/
protected $io;
/**
* @var bool
*
* A boolean indicating if progress should be displayed.
*/
protected $progress;
protected $source;
protected $filenames;
protected $fs;
/**
* Constructs this FileFetcher object.
*/
public function __construct(RemoteFilesystem $remoteFilesystem, $source, IOInterface $io, $progress = TRUE) {
$this->remoteFilesystem = $remoteFilesystem;
$this->io = $io;
$this->source = $source;
$this->fs = new Filesystem();
$this->progress = $progress;
}
/**
* Downloads all required files and writes it to the file system.
*/
public function fetch($version, $destination, $override) {
foreach ($this->filenames as $sourceFilename => $filename) {
$target = "$destination/$filename";
if ($override || !file_exists($target)) {
$url = $this->getUri($sourceFilename, $version);
$this->fs->ensureDirectoryExists($destination . '/' . dirname($filename));
if ($this->progress) {
$this->io->writeError(" - <info>$filename</info> (<comment>$url</comment>): ", FALSE);
$this->remoteFilesystem->copy($url, $url, $target, $this->progress);
// Used to put a new line because the remote file system does not put
// one.
$this->io->writeError('');
}
else {
$this->remoteFilesystem->copy($url, $url, $target, $this->progress);
}
}
}
}
/**
* Set filenames.
*/
public function setFilenames(array $filenames) {
$this->filenames = $filenames;
}
/**
* Replace filename and version in the source pattern with their values.
*/
protected function getUri($filename, $version) {
$map = [
'{path}' => $filename,
'{version}' => $version,
];
return str_replace(array_keys($map), array_values($map), $this->source);
}
}

View file

@ -0,0 +1,411 @@
<?php
namespace DrupalComposer\DrupalScaffold;
use Composer\Script\Event;
use Composer\Installer\PackageEvent;
use Composer\Plugin\CommandEvent;
use Composer\Composer;
use Composer\DependencyResolver\Operation\InstallOperation;
use Composer\DependencyResolver\Operation\UpdateOperation;
use Composer\EventDispatcher\EventDispatcher;
use Composer\IO\IOInterface;
use Composer\Package\PackageInterface;
use Composer\Semver\Semver;
use Composer\Util\Filesystem;
use Composer\Util\RemoteFilesystem;
use Symfony\Component\Filesystem\Filesystem as SymfonyFilesystem;
/**
* Core class of the plugin, contains all logic which files should be fetched.
*/
class Handler {
const PRE_DRUPAL_SCAFFOLD_CMD = 'pre-drupal-scaffold-cmd';
const POST_DRUPAL_SCAFFOLD_CMD = 'post-drupal-scaffold-cmd';
/**
* @var \Composer\Composer
*/
protected $composer;
/**
* @var \Composer\IO\IOInterface
*/
protected $io;
/**
* @var bool
*
* A boolean indicating if progress should be displayed.
*/
protected $progress;
/**
* @var \Composer\Package\PackageInterface
*/
protected $drupalCorePackage;
/**
* Handler constructor.
*
* @param \Composer\Composer $composer
* @param \Composer\IO\IOInterface $io
*/
public function __construct(Composer $composer, IOInterface $io) {
$this->composer = $composer;
$this->io = $io;
$this->progress = TRUE;
// Pre-load all of our sources so that we do not run up
// against problems in `composer update` operations.
$this->manualLoad();
}
protected function manualLoad() {
$src_dir = __DIR__;
$classes = [
'CommandProvider',
'DrupalScaffoldCommand',
'FileFetcher',
'PrestissimoFileFetcher',
];
foreach ($classes as $src) {
if (!class_exists('\\DrupalComposer\\DrupalScaffold\\' . $src)) {
include "{$src_dir}/{$src}.php";
}
}
}
/**
* @param $operation
* @return mixed
*/
protected function getCorePackage($operation) {
if ($operation instanceof InstallOperation) {
$package = $operation->getPackage();
}
elseif ($operation instanceof UpdateOperation) {
$package = $operation->getTargetPackage();
}
if (isset($package) && $package instanceof PackageInterface && $package->getName() == 'drupal/core') {
return $package;
}
return NULL;
}
/**
* Get the command options.
*
* @param \Composer\Plugin\CommandEvent $event
*/
public function onCmdBeginsEvent(CommandEvent $event) {
if ($event->getInput()->hasOption('no-progress')) {
$this->progress = !($event->getInput()->getOption('no-progress'));
}
else {
$this->progress = TRUE;
}
}
/**
* Marks scaffolding to be processed after an install or update command.
*
* @param \Composer\Installer\PackageEvent $event
*/
public function onPostPackageEvent(PackageEvent $event) {
$package = $this->getCorePackage($event->getOperation());
if ($package) {
// By explicitly setting the core package, the onPostCmdEvent() will
// process the scaffolding automatically.
$this->drupalCorePackage = $package;
}
}
/**
* Post install command event to execute the scaffolding.
*
* @param \Composer\Script\Event $event
*/
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();
// Generate the autoload.php file after generating the scaffold files.
$this->generateAutoload();
}
}
/**
* Downloads drupal scaffold files for the current process.
*/
public function downloadScaffold() {
$drupalCorePackage = $this->getDrupalCorePackage();
$webroot = realpath($this->getWebRoot());
// Collect options, excludes and settings files.
$options = $this->getOptions();
$files = array_diff($this->getIncludes(), $this->getExcludes());
// Call any pre-scaffold scripts that may be defined.
$dispatcher = new EventDispatcher($this->composer, $this->io);
$dispatcher->dispatch(self::PRE_DRUPAL_SCAFFOLD_CMD);
$version = $this->getDrupalCoreVersion($drupalCorePackage);
$remoteFs = new RemoteFilesystem($this->io);
$fetcher = new PrestissimoFileFetcher($remoteFs, $options['source'], $this->io, $this->progress, $this->composer->getConfig());
$fetcher->setFilenames(array_combine($files, $files));
$fetcher->fetch($version, $webroot, TRUE);
$fetcher->setFilenames($this->getInitial());
$fetcher->fetch($version, $webroot, FALSE);
// Call post-scaffold scripts.
$dispatcher->dispatch(self::POST_DRUPAL_SCAFFOLD_CMD);
}
/**
* Generate the autoload file at the project root. Include the
* autoload file that Composer generated.
*/
public function generateAutoload() {
$vendorPath = $this->getVendorPath();
$webroot = $this->getWebRoot();
// Calculate the relative path from the webroot (location of the
// project autoload.php) to the vendor directory.
$fs = new SymfonyFilesystem();
$relativeVendorPath = $fs->makePathRelative($vendorPath, realpath($webroot));
$fs->dumpFile($webroot . "/autoload.php", $this->autoLoadContents($relativeVendorPath));
}
/**
* Build the contents of the autoload file.
*
* @return string
*/
protected function autoLoadContents($relativeVendorPath) {
$relativeVendorPath = rtrim($relativeVendorPath, '/');
$autoloadContents = <<<EOF
<?php
/**
* @file
* Includes the autoloader created by Composer.
*
* This file was generated by drupal-composer/drupal-scaffold.
* https://github.com/drupal-composer/drupal-scaffold
*
* @see composer.json
* @see index.php
* @see core/install.php
* @see core/rebuild.php
* @see core/modules/statistics/statistics.php
*/
return require __DIR__ . '/$relativeVendorPath/autoload.php';
EOF;
return $autoloadContents;
}
/**
* Get the path to the 'vendor' directory.
*
* @return string
*/
public function getVendorPath() {
$config = $this->composer->getConfig();
$filesystem = new Filesystem();
$filesystem->ensureDirectoryExists($config->get('vendor-dir'));
$vendorPath = $filesystem->normalizePath(realpath($config->get('vendor-dir')));
return $vendorPath;
}
/**
* Look up the Drupal core package object, or return it from where we cached
* it in the $drupalCorePackage field.
*
* @return \Composer\Package\PackageInterface
*/
public function getDrupalCorePackage() {
if (!isset($this->drupalCorePackage)) {
$this->drupalCorePackage = $this->getPackage('drupal/core');
}
return $this->drupalCorePackage;
}
/**
* Returns the Drupal core version for the given package.
*
* @param \Composer\Package\PackageInterface $drupalCorePackage
*
* @return string
*/
protected function getDrupalCoreVersion(PackageInterface $drupalCorePackage) {
$version = $drupalCorePackage->getPrettyVersion();
if ($drupalCorePackage->getStability() == 'dev' && substr($version, -4) == '-dev') {
$version = substr($version, 0, -4);
return $version;
}
return $version;
}
/**
* Retrieve the path to the web root.
*
* @return string
*/
public function getWebRoot() {
$drupalCorePackage = $this->getDrupalCorePackage();
$installationManager = $this->composer->getInstallationManager();
$corePath = $installationManager->getInstallPath($drupalCorePackage);
// Webroot is the parent path of the drupal core installation path.
$webroot = dirname($corePath);
return $webroot;
}
/**
* Retrieve a package from the current composer process.
*
* @param string $name
* Name of the package to get from the current composer installation.
*
* @return \Composer\Package\PackageInterface
*/
protected function getPackage($name) {
return $this->composer->getRepositoryManager()->getLocalRepository()->findPackage($name, '*');
}
/**
* Retrieve excludes from optional "extra" configuration.
*
* @return array
*/
protected function getExcludes() {
return $this->getNamedOptionList('excludes', 'getExcludesDefault');
}
/**
* Retrieve list of additional settings files from optional "extra" configuration.
*
* @return array
*/
protected function getIncludes() {
return $this->getNamedOptionList('includes', 'getIncludesDefault');
}
/**
* Retrieve list of initial files from optional "extra" configuration.
*
* @return array
*/
protected function getInitial() {
return $this->getNamedOptionList('initial', 'getInitialDefault');
}
/**
* Retrieve a named list of options from optional "extra" configuration.
* Respects 'omit-defaults', and either includes or does not include the
* default values, as requested.
*
* @return array
*/
protected function getNamedOptionList($optionName, $defaultFn) {
$options = $this->getOptions($this->composer);
$result = array();
if (empty($options['omit-defaults'])) {
$result = $this->$defaultFn();
}
$result = array_merge($result, (array) $options[$optionName]);
return $result;
}
/**
* Retrieve excludes from optional "extra" configuration.
*
* @return array
*/
protected function getOptions() {
$extra = $this->composer->getPackage()->getExtra() + ['drupal-scaffold' => []];
$options = $extra['drupal-scaffold'] + [
'omit-defaults' => FALSE,
'excludes' => [],
'includes' => [],
'initial' => [],
'source' => 'https://cgit.drupalcode.org/drupal/plain/{path}?h={version}',
// Github: https://raw.githubusercontent.com/drupal/drupal/{version}/{path}
];
return $options;
}
/**
* Holds default excludes.
*/
protected function getExcludesDefault() {
return [];
}
/**
* 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',
];
// Version specific variations.
if (Semver::satisfies($version, '<8.3')) {
$common[] = '.eslintrc';
}
if (Semver::satisfies($version, '>=8.3')) {
$common[] = '.eslintrc.json';
}
if (Semver::satisfies($version, '>=8.5')) {
$common[] = '.ht.router.php';
}
sort($common);
return $common;
}
/**
* Holds default initial files.
*/
protected function getInitialDefault() {
return [];
}
}

View file

@ -0,0 +1,103 @@
<?php
namespace DrupalComposer\DrupalScaffold;
use Composer\Script\Event;
use Composer\Plugin\CommandEvent;
use Composer\Composer;
use Composer\EventDispatcher\EventSubscriberInterface;
use Composer\Installer\PackageEvent;
use Composer\Installer\PackageEvents;
use Composer\IO\IOInterface;
use Composer\Plugin\Capable;
use Composer\Plugin\PluginEvents;
use Composer\Plugin\PluginInterface;
use Composer\Script\ScriptEvents;
/**
* Composer plugin for handling drupal scaffold.
*/
class Plugin implements PluginInterface, EventSubscriberInterface, Capable {
/**
* @var \DrupalComposer\DrupalScaffold\Handler
*/
protected $handler;
/**
* {@inheritdoc}
*/
public function activate(Composer $composer, IOInterface $io) {
// We use a separate PluginScripts object. This way we separate
// functionality and also avoid some debug issues with the plugin being
// copied on initialisation.
// @see \Composer\Plugin\PluginManager::registerPackage()
$this->handler = new Handler($composer, $io);
}
/**
* {@inheritdoc}
*/
public function getCapabilities() {
return array(
'Composer\Plugin\Capability\CommandProvider' => 'DrupalComposer\DrupalScaffold\CommandProvider',
);
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
return array(
PackageEvents::POST_PACKAGE_INSTALL => 'postPackage',
PackageEvents::POST_PACKAGE_UPDATE => 'postPackage',
ScriptEvents::POST_UPDATE_CMD => 'postCmd',
PluginEvents::COMMAND => 'cmdBegins',
);
}
/**
* Command begins event callback.
*
* @param \Composer\Plugin\CommandEvent $event
*/
public function cmdBegins(CommandEvent $event) {
$this->handler->onCmdBeginsEvent($event);
}
/**
* Post package event behaviour.
*
* @param \Composer\Installer\PackageEvent $event
*/
public function postPackage(PackageEvent $event) {
$this->handler->onPostPackageEvent($event);
}
/**
* Post command event callback.
*
* @param \Composer\Script\Event $event
*/
public function postCmd(Event $event) {
$this->handler->onPostCmdEvent($event);
}
/**
* Script callback for putting in composer scripts to download the
* scaffold files.
*
* @param \Composer\Script\Event $event
*
* @deprecated since version 2.5.0, to be removed in 3.0. Use the command
* "composer drupal:scaffold" instead.
*/
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();
// Generate the autoload.php file after generating the scaffold files.
$handler->generateAutoload();
}
}

View file

@ -0,0 +1,87 @@
<?php
namespace DrupalComposer\DrupalScaffold;
use Composer\Util\RemoteFilesystem;
use Composer\Config;
use Composer\IO\IOInterface;
use Hirak\Prestissimo\CopyRequest;
use Hirak\Prestissimo\CurlMulti;
/**
* Extends the default FileFetcher and uses hirak/prestissimo for parallel
* downloads.
*/
class PrestissimoFileFetcher extends FileFetcher {
/**
* @var \Composer\Config
*/
protected $config;
/**
* Constructs this PrestissimoFileFetcher object.
*/
public function __construct(RemoteFilesystem $remoteFilesystem, $source, IOInterface $io, $progress = TRUE, Config $config) {
parent::__construct($remoteFilesystem, $source, $io, $progress);
$this->config = $config;
}
/**
* {@inheritdoc}
*/
public function fetch($version, $destination, $override) {
if (class_exists(CurlMulti::class)) {
$this->fetchWithPrestissimo($version, $destination, $override);
return;
}
parent::fetch($version, $destination, $override);
}
/**
* Fetch files in parallel.
*/
protected function fetchWithPrestissimo($version, $destination, $override) {
$requests = [];
foreach ($this->filenames as $sourceFilename => $filename) {
$target = "$destination/$filename";
if ($override || !file_exists($target)) {
$url = $this->getUri($sourceFilename, $version);
$this->fs->ensureDirectoryExists($destination . '/' . dirname($filename));
$requests[] = new CopyRequest($url, $target, FALSE, $this->io, $this->config);
}
}
$successCnt = $failureCnt = 0;
$errors = [];
$totalCnt = count($requests);
if ($totalCnt == 0) {
return;
}
$multi = new CurlMulti();
$multi->setRequests($requests);
do {
$multi->setupEventLoop();
$multi->wait();
$result = $multi->getFinishedResults();
$successCnt += $result['successCnt'];
$failureCnt += $result['failureCnt'];
if (isset($result['errors'])) {
$errors += $result['errors'];
}
if ($this->progress) {
foreach ($result['urls'] as $url) {
$this->io->writeError(" - Downloading <comment>$successCnt</comment>/<comment>$totalCnt</comment>: <info>$url</info>", TRUE);
}
}
} while ($multi->remain());
$urls = array_keys($errors);
if ($urls) {
throw new \Exception('Failed to download ' . implode(", ", $urls));
}
}
}

View file

@ -0,0 +1,79 @@
<?php
namespace DrupalComposer\DrupalScaffold\Tests;
use Composer\IO\NullIO;
use Composer\Util\Filesystem;
use Composer\Util\RemoteFilesystem;
use DrupalComposer\DrupalScaffold\FileFetcher;
use PHPUnit\Framework\TestCase;
class FetcherTest 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);
chdir($this->tmpDir);
}
/**
* 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);
}
public function testFetch() {
$fetcher = new FileFetcher(new RemoteFilesystem(new NullIO()), 'https://cgit.drupalcode.org/drupal/plain/{path}?h={version}', new NullIO());
$fetcher->setFilenames([
'.htaccess' => '.htaccess',
'sites/default/default.settings.php' => 'sites/default/default.settings.php',
]);
$fetcher->fetch('8.1.1', $this->tmpDir, TRUE);
$this->assertFileExists($this->tmpDir . '/.htaccess');
$this->assertFileExists($this->tmpDir . '/sites/default/default.settings.php');
}
public function testInitialFetch() {
$fetcher = new FileFetcher(new RemoteFilesystem(new NullIO()), 'https://cgit.drupalcode.org/drupal/plain/{path}?h={version}', new NullIO());
$fetcher->setFilenames([
'sites/default/default.settings.php' => 'sites/default/settings.php',
]);
$fetcher->fetch('8.1.1', $this->tmpDir, FALSE);
$this->assertFileExists($this->tmpDir . '/sites/default/settings.php');
}
}

View file

@ -0,0 +1,195 @@
<?php
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));
}
/**
* Tests a simple composer install without core, but adding core later.
*/
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->assertFileExists($this->tmpDir . DIRECTORY_SEPARATOR . 'core', 'Drupal core is installed.');
$this->assertFileExists($exampleScaffoldFile, 'Scaffold file should be automatically installed.');
$this->fs->remove($exampleScaffoldFile);
$this->assertFileNotExists($exampleScaffoldFile, 'Scaffold file should not be exist.');
$this->composer('drupal:scaffold');
$this->assertFileExists($exampleScaffoldFile, 'Scaffold file should be installed by "drupal:scaffold" command.');
foreach (['8.0.1', '8.1.x-dev', '8.3.0', '8.5.x-dev'] as $version) {
// We touch a scaffold file, so we can check the file was modified after
// the scaffold update.
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 . '"');
clearstatcache();
$mtime_after = filemtime($exampleScaffoldFile);
$this->assertNotEquals($mtime_after, $mtime_touched, 'Scaffold file was modified by composer update. (' . $version . ')');
switch ($version) {
case '8.0.1':
case '8.1.x-dev':
$this->assertFileExists($this->tmpDir . DIRECTORY_SEPARATOR . '.eslintrc');
$this->assertFileNotExists($this->tmpDir . DIRECTORY_SEPARATOR . '.eslintrc.json');
$this->assertFileNotExists($this->tmpDir . DIRECTORY_SEPARATOR . '.ht.router.php');
break;
case '8.3.0':
// Note we don't clean up .eslintrc file.
$this->assertFileExists($this->tmpDir . DIRECTORY_SEPARATOR . '.eslintrc');
$this->assertFileExists($this->tmpDir . DIRECTORY_SEPARATOR . '.eslintrc.json');
$this->assertFileNotExists($this->tmpDir . DIRECTORY_SEPARATOR . '.ht.router.php');
break;
case '8.5.x-dev':
$this->assertFileExists($this->tmpDir . DIRECTORY_SEPARATOR . '.eslintrc');
$this->assertFileExists($this->tmpDir . DIRECTORY_SEPARATOR . '.eslintrc.json');
$this->assertFileExists($this->tmpDir . DIRECTORY_SEPARATOR . '.ht.router.php');
break;
}
}
// We touch a scaffold file, so we can check the file was modified by the
// custom command.
file_put_contents($exampleScaffoldFile, 1);
$this->composer('drupal:scaffold');
$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);
}
}