Initial Drupal 11 with DDEV setup
This commit is contained in:
195
vendor/drupal/core-composer-scaffold/AllowedPackages.php
vendored
Normal file
195
vendor/drupal/core-composer-scaffold/AllowedPackages.php
vendored
Normal file
@ -0,0 +1,195 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Composer\Plugin\Scaffold;
|
||||
|
||||
use Composer\Composer;
|
||||
use Composer\Installer\PackageEvent;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Package\PackageInterface;
|
||||
|
||||
/**
|
||||
* Determine recursively which packages have been allowed to scaffold files.
|
||||
*
|
||||
* If the root-level composer.json allows drupal/core, and drupal/core allows
|
||||
* drupal/assets, then the later package will also implicitly be allowed.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class AllowedPackages implements PostPackageEventListenerInterface {
|
||||
|
||||
/**
|
||||
* The Composer service.
|
||||
*
|
||||
* @var \Composer\Composer
|
||||
*/
|
||||
protected $composer;
|
||||
|
||||
/**
|
||||
* Composer's I/O service.
|
||||
*
|
||||
* @var \Composer\IO\IOInterface
|
||||
*/
|
||||
protected $io;
|
||||
|
||||
/**
|
||||
* Manager of the options in the top-level composer.json's 'extra' section.
|
||||
*
|
||||
* @var \Drupal\Composer\Plugin\Scaffold\ManageOptions
|
||||
*/
|
||||
protected $manageOptions;
|
||||
|
||||
/**
|
||||
* The list of new packages added by this Composer command.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $newPackages = [];
|
||||
|
||||
/**
|
||||
* AllowedPackages constructor.
|
||||
*
|
||||
* @param \Composer\Composer $composer
|
||||
* The composer object.
|
||||
* @param \Composer\IO\IOInterface $io
|
||||
* IOInterface to write to.
|
||||
* @param \Drupal\Composer\Plugin\Scaffold\ManageOptions $manage_options
|
||||
* Manager of the options in the top-level composer.json's 'extra' section.
|
||||
*/
|
||||
public function __construct(Composer $composer, IOInterface $io, ManageOptions $manage_options) {
|
||||
$this->composer = $composer;
|
||||
$this->io = $io;
|
||||
$this->manageOptions = $manage_options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of all packages that are allowed to copy scaffold files.
|
||||
*
|
||||
* We will implicitly allow the projects 'drupal/legacy-scaffold-assets'
|
||||
* and 'drupal/core' to scaffold files, if they are present. Any other
|
||||
* project must be explicitly allowed in the top-level composer.json
|
||||
* file in order to be allowed to override scaffold files.
|
||||
* Configuration for packages specified later will override configuration
|
||||
* specified by packages listed earlier. In other words, the last listed
|
||||
* package has the highest priority. The root package will always be returned
|
||||
* at the end of the list.
|
||||
*
|
||||
* @return \Composer\Package\PackageInterface[]
|
||||
* An array of allowed Composer packages.
|
||||
*/
|
||||
public function getAllowedPackages() {
|
||||
$top_level_packages = $this->getTopLevelAllowedPackages();
|
||||
$allowed_packages = $this->recursiveGetAllowedPackages($top_level_packages);
|
||||
// If the root package defines any file mappings, then implicitly add it
|
||||
// to the list of allowed packages. Add it at the end so that it overrides
|
||||
// all the preceding packages.
|
||||
if ($this->manageOptions->getOptions()->hasFileMapping()) {
|
||||
$root_package = $this->composer->getPackage();
|
||||
unset($allowed_packages[$root_package->getName()]);
|
||||
$allowed_packages[$root_package->getName()] = $root_package;
|
||||
}
|
||||
// Handle any newly-added packages that are not already allowed.
|
||||
return $this->evaluateNewPackages($allowed_packages);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function event(PackageEvent $event) {
|
||||
$operation = $event->getOperation();
|
||||
// Determine the package. Later, in evaluateNewPackages(), we will report
|
||||
// which of the newly-installed packages have scaffold operations, and
|
||||
// whether or not they are allowed to scaffold by the allowed-packages
|
||||
// option in the root-level composer.json file.
|
||||
$package = $operation->getOperationType() === 'update' ? $operation->getTargetPackage() : $operation->getPackage();
|
||||
if (ScaffoldOptions::hasOptions($package->getExtra())) {
|
||||
$this->newPackages[$package->getName()] = $package;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all packages that are allowed in the top-level composer.json.
|
||||
*
|
||||
* We will implicitly allow the projects 'drupal/legacy-scaffold-assets'
|
||||
* and 'drupal/core' to scaffold files, if they are present. Any other
|
||||
* project must be explicitly allowed in the top-level composer.json
|
||||
* file in order to be allowed to override scaffold files.
|
||||
*
|
||||
* @return array
|
||||
* An array of allowed Composer package names.
|
||||
*/
|
||||
protected function getTopLevelAllowedPackages() {
|
||||
$implicit_packages = [
|
||||
'drupal/legacy-scaffold-assets',
|
||||
'drupal/core',
|
||||
];
|
||||
$top_level_packages = $this->manageOptions->getOptions()->allowedPackages();
|
||||
return array_merge($implicit_packages, $top_level_packages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a name-to-package mapping from a list of package names.
|
||||
*
|
||||
* @param string[] $packages_to_allow
|
||||
* List of package names to allow.
|
||||
* @param array $allowed_packages
|
||||
* Mapping of package names to PackageInterface of packages already
|
||||
* accumulated.
|
||||
*
|
||||
* @return \Composer\Package\PackageInterface[]
|
||||
* Mapping of package names to PackageInterface in priority order.
|
||||
*/
|
||||
protected function recursiveGetAllowedPackages(array $packages_to_allow, array $allowed_packages = []) {
|
||||
foreach ($packages_to_allow as $name) {
|
||||
$package = $this->getPackage($name);
|
||||
if ($package instanceof PackageInterface && !isset($allowed_packages[$name])) {
|
||||
$allowed_packages[$name] = $package;
|
||||
$package_options = $this->manageOptions->packageOptions($package);
|
||||
$allowed_packages = $this->recursiveGetAllowedPackages($package_options->allowedPackages(), $allowed_packages);
|
||||
}
|
||||
}
|
||||
return $allowed_packages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates newly-added packages and see if they are already allowed.
|
||||
*
|
||||
* For now we will only emit warnings if they are not.
|
||||
*
|
||||
* @param array $allowed_packages
|
||||
* Mapping of package names to PackageInterface of packages already
|
||||
* accumulated.
|
||||
*
|
||||
* @return \Composer\Package\PackageInterface[]
|
||||
* Mapping of package names to PackageInterface in priority order.
|
||||
*/
|
||||
protected function evaluateNewPackages(array $allowed_packages) {
|
||||
foreach ($this->newPackages as $name => $newPackage) {
|
||||
if (!array_key_exists($name, $allowed_packages)) {
|
||||
$this->io->write("Not scaffolding files for <comment>{$name}</comment>, because it is not listed in the element 'extra.drupal-scaffold.allowed-packages' in the root-level composer.json file.");
|
||||
}
|
||||
else {
|
||||
$this->io->write("Package <comment>{$name}</comment> has scaffold operations, and is already allowed in the root-level composer.json file.");
|
||||
}
|
||||
}
|
||||
// @todo We could prompt the user and ask if they wish to allow a
|
||||
// newly-added package. This might be useful if, for example, the user
|
||||
// might wish to require an installation profile that contains scaffolded
|
||||
// assets. For more information, see:
|
||||
// https://www.drupal.org/project/drupal/issues/3064990
|
||||
return $allowed_packages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves 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|null
|
||||
* The Composer package.
|
||||
*/
|
||||
protected function getPackage($name) {
|
||||
return $this->composer->getRepositoryManager()->getLocalRepository()->findPackage($name, '*');
|
||||
}
|
||||
|
||||
}
|
||||
21
vendor/drupal/core-composer-scaffold/CommandProvider.php
vendored
Normal file
21
vendor/drupal/core-composer-scaffold/CommandProvider.php
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Composer\Plugin\Scaffold;
|
||||
|
||||
use Composer\Plugin\Capability\CommandProvider as CommandProviderCapability;
|
||||
|
||||
/**
|
||||
* List of all commands provided by this package.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class CommandProvider implements CommandProviderCapability {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCommands() {
|
||||
return [new ComposerScaffoldCommand()];
|
||||
}
|
||||
|
||||
}
|
||||
56
vendor/drupal/core-composer-scaffold/ComposerScaffoldCommand.php
vendored
Normal file
56
vendor/drupal/core-composer-scaffold/ComposerScaffoldCommand.php
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Composer\Plugin\Scaffold;
|
||||
|
||||
use Composer\Command\BaseCommand;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* The "drupal:scaffold" command class.
|
||||
*
|
||||
* Manually run the scaffold operation that normally happens after
|
||||
* 'composer install'.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ComposerScaffoldCommand extends BaseCommand {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configure() {
|
||||
$this
|
||||
->setName('drupal:scaffold')
|
||||
->setAliases(['scaffold'])
|
||||
->setDescription('Update the Drupal scaffold files.')
|
||||
->setHelp(
|
||||
<<<EOT
|
||||
The <info>drupal:scaffold</info> command places the scaffold files in their
|
||||
respective locations according to the layout stipulated in the composer.json
|
||||
file.
|
||||
|
||||
<info>php composer.phar drupal:scaffold</info>
|
||||
|
||||
It is usually not necessary to call <info>drupal:scaffold</info> manually,
|
||||
because it is called automatically as needed, e.g. after an <info>install</info>
|
||||
or <info>update</info> command. Note, though, that only packages explicitly
|
||||
allowed to scaffold in the top-level composer.json will be processed by this
|
||||
command.
|
||||
|
||||
For more information, see https://www.drupal.org/docs/develop/using-composer/using-drupals-composer-scaffold.
|
||||
EOT
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int {
|
||||
$handler = new Handler($this->requireComposer(), $this->getIO());
|
||||
$handler->scaffold();
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
123
vendor/drupal/core-composer-scaffold/GenerateAutoloadReferenceFile.php
vendored
Normal file
123
vendor/drupal/core-composer-scaffold/GenerateAutoloadReferenceFile.php
vendored
Normal file
@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Composer\Plugin\Scaffold;
|
||||
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Util\Filesystem;
|
||||
use Drupal\Composer\Plugin\Scaffold\Operations\ScaffoldResult;
|
||||
|
||||
/**
|
||||
* Generates an 'autoload.php' that includes the autoloader created by Composer.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class GenerateAutoloadReferenceFile {
|
||||
|
||||
/**
|
||||
* This class provides only static methods.
|
||||
*/
|
||||
private function __construct() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the autoload file at the specified location.
|
||||
*
|
||||
* This only writes a bit of PHP that includes the autoload file that
|
||||
* Composer generated. Drupal does this so that it can guarantee that there
|
||||
* will always be an `autoload.php` file in a well-known location.
|
||||
*
|
||||
* @param \Composer\IO\IOInterface $io
|
||||
* IOInterface to write to.
|
||||
* @param string $package_name
|
||||
* The name of the package defining the autoload file (the root package).
|
||||
* @param string $web_root
|
||||
* The path to the web root.
|
||||
* @param string $vendor
|
||||
* The path to the vendor directory.
|
||||
*
|
||||
* @return \Drupal\Composer\Plugin\Scaffold\Operations\ScaffoldResult
|
||||
* The result of the autoload file generation.
|
||||
*/
|
||||
public static function generateAutoload(IOInterface $io, $package_name, $web_root, $vendor) {
|
||||
$autoload_path = static::autoloadPath($package_name, $web_root);
|
||||
// Calculate the relative path from the webroot (location of the project
|
||||
// autoload.php) to the vendor directory.
|
||||
$fs = new Filesystem();
|
||||
$relative_autoload_path = $fs->findShortestPath($autoload_path->fullPath(), "$vendor/autoload.php");
|
||||
file_put_contents($autoload_path->fullPath(), static::autoLoadContents($relative_autoload_path));
|
||||
return new ScaffoldResult($autoload_path, TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether or not the autoload file has been committed.
|
||||
*
|
||||
* @param \Composer\IO\IOInterface $io
|
||||
* IOInterface to write to.
|
||||
* @param string $package_name
|
||||
* The name of the package defining the autoload file (the root package).
|
||||
* @param string $web_root
|
||||
* The path to the web root.
|
||||
*
|
||||
* @return bool
|
||||
* True if autoload.php file exists and has been committed to the repository
|
||||
*/
|
||||
public static function autoloadFileCommitted(IOInterface $io, $package_name, $web_root) {
|
||||
$autoload_path = static::autoloadPath($package_name, $web_root);
|
||||
$autoload_file = $autoload_path->fullPath();
|
||||
$location = dirname($autoload_file);
|
||||
if (!file_exists($autoload_file)) {
|
||||
return FALSE;
|
||||
}
|
||||
return Git::checkTracked($io, $autoload_file, $location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a scaffold file path object for the autoload file.
|
||||
*
|
||||
* @param string $package_name
|
||||
* The name of the package defining the autoload file (the root package).
|
||||
* @param string $web_root
|
||||
* The path to the web root.
|
||||
*
|
||||
* @return \Drupal\Composer\Plugin\Scaffold\ScaffoldFilePath
|
||||
* Object wrapping the relative and absolute path to the destination file.
|
||||
*/
|
||||
protected static function autoloadPath($package_name, $web_root) {
|
||||
$rel_path = 'autoload.php';
|
||||
$dest_rel_path = '[web-root]/' . $rel_path;
|
||||
$dest_full_path = $web_root . '/' . $rel_path;
|
||||
return new ScaffoldFilePath('autoload', $package_name, $dest_rel_path, $dest_full_path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the contents of the autoload file.
|
||||
*
|
||||
* @param string $relative_autoload_path
|
||||
* The relative path to the autoloader in vendor.
|
||||
*
|
||||
* @return string
|
||||
* Return the contents for the autoload.php.
|
||||
*/
|
||||
protected static function autoLoadContents($relative_autoload_path) {
|
||||
$relative_autoload_path = preg_replace('#^\./#', '', $relative_autoload_path);
|
||||
return <<<EOF
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Includes the autoloader created by Composer.
|
||||
*
|
||||
* This file was generated by drupal-scaffold.
|
||||
*
|
||||
* @see composer.json
|
||||
* @see index.php
|
||||
* @see core/install.php
|
||||
* @see core/rebuild.php
|
||||
*/
|
||||
|
||||
return require __DIR__ . '/{$relative_autoload_path}';
|
||||
|
||||
EOF;
|
||||
}
|
||||
|
||||
}
|
||||
81
vendor/drupal/core-composer-scaffold/Git.php
vendored
Normal file
81
vendor/drupal/core-composer-scaffold/Git.php
vendored
Normal file
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Composer\Plugin\Scaffold;
|
||||
|
||||
// cspell:ignore unmatch
|
||||
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
|
||||
/**
|
||||
* Provide some Git utility operations.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class Git {
|
||||
|
||||
/**
|
||||
* This class provides only static methods.
|
||||
*/
|
||||
private function __construct() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the specified scaffold file is already ignored.
|
||||
*
|
||||
* @param \Composer\IO\IOInterface $io
|
||||
* The Composer IO interface.
|
||||
* @param string $path
|
||||
* Path to scaffold file to check.
|
||||
* @param string $dir
|
||||
* Base directory for git process.
|
||||
*
|
||||
* @return bool
|
||||
* Whether the specified file is already ignored or not (TRUE if ignored).
|
||||
*/
|
||||
public static function checkIgnore(IOInterface $io, $path, $dir = NULL) {
|
||||
$process = new ProcessExecutor($io);
|
||||
$output = '';
|
||||
$exitCode = $process->execute('git check-ignore ' . $process->escape($path), $output, $dir);
|
||||
return $exitCode == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the specified scaffold file is tracked by git.
|
||||
*
|
||||
* @param \Composer\IO\IOInterface $io
|
||||
* The Composer IO interface.
|
||||
* @param string $path
|
||||
* Path to scaffold file to check.
|
||||
* @param string $dir
|
||||
* Base directory for git process.
|
||||
*
|
||||
* @return bool
|
||||
* Whether the specified file is already tracked or not (TRUE if tracked).
|
||||
*/
|
||||
public static function checkTracked(IOInterface $io, $path, $dir = NULL) {
|
||||
$process = new ProcessExecutor($io);
|
||||
$output = '';
|
||||
$exitCode = $process->execute('git ls-files --error-unmatch ' . $process->escape($path), $output, $dir);
|
||||
return $exitCode == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if the project root dir is in a git repository.
|
||||
*
|
||||
* @param \Composer\IO\IOInterface $io
|
||||
* The Composer IO interface.
|
||||
* @param string $dir
|
||||
* Base directory for git process.
|
||||
*
|
||||
* @return bool
|
||||
* True if this is a repository.
|
||||
*/
|
||||
public static function isRepository(IOInterface $io, $dir = NULL) {
|
||||
$process = new ProcessExecutor($io);
|
||||
$output = '';
|
||||
$exitCode = $process->execute('git rev-parse --show-toplevel', $output, $dir);
|
||||
return $exitCode == 0;
|
||||
}
|
||||
|
||||
}
|
||||
248
vendor/drupal/core-composer-scaffold/Handler.php
vendored
Normal file
248
vendor/drupal/core-composer-scaffold/Handler.php
vendored
Normal file
@ -0,0 +1,248 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Composer\Plugin\Scaffold;
|
||||
|
||||
use Composer\Composer;
|
||||
use Composer\Installer\PackageEvent;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Package\PackageInterface;
|
||||
use Composer\Util\Filesystem;
|
||||
use Drupal\Composer\Plugin\Scaffold\Operations\OperationData;
|
||||
use Drupal\Composer\Plugin\Scaffold\Operations\OperationFactory;
|
||||
use Drupal\Composer\Plugin\Scaffold\Operations\ScaffoldFileCollection;
|
||||
|
||||
/**
|
||||
* Core class of the plugin.
|
||||
*
|
||||
* Contains the primary logic which determines the files to be fetched and
|
||||
* processed.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class Handler {
|
||||
|
||||
/**
|
||||
* Composer hook called before scaffolding begins.
|
||||
*/
|
||||
const PRE_DRUPAL_SCAFFOLD_CMD = 'pre-drupal-scaffold-cmd';
|
||||
|
||||
/**
|
||||
* Composer hook called after scaffolding completes.
|
||||
*/
|
||||
const POST_DRUPAL_SCAFFOLD_CMD = 'post-drupal-scaffold-cmd';
|
||||
|
||||
/**
|
||||
* The Composer service.
|
||||
*
|
||||
* @var \Composer\Composer
|
||||
*/
|
||||
protected $composer;
|
||||
|
||||
/**
|
||||
* Composer's I/O service.
|
||||
*
|
||||
* @var \Composer\IO\IOInterface
|
||||
*/
|
||||
protected $io;
|
||||
|
||||
/**
|
||||
* The scaffold options in the top-level composer.json's 'extra' section.
|
||||
*
|
||||
* @var \Drupal\Composer\Plugin\Scaffold\ManageOptions
|
||||
*/
|
||||
protected $manageOptions;
|
||||
|
||||
/**
|
||||
* The manager that keeps track of which packages are allowed to scaffold.
|
||||
*
|
||||
* @var \Drupal\Composer\Plugin\Scaffold\AllowedPackages
|
||||
*/
|
||||
protected $manageAllowedPackages;
|
||||
|
||||
/**
|
||||
* The list of listeners that are notified after a package event.
|
||||
*
|
||||
* @var \Drupal\Composer\Plugin\Scaffold\PostPackageEventListenerInterface[]
|
||||
*/
|
||||
protected $postPackageListeners = [];
|
||||
|
||||
/**
|
||||
* Handler constructor.
|
||||
*
|
||||
* @param \Composer\Composer $composer
|
||||
* The Composer service.
|
||||
* @param \Composer\IO\IOInterface $io
|
||||
* The Composer I/O service.
|
||||
*/
|
||||
public function __construct(Composer $composer, IOInterface $io) {
|
||||
$this->composer = $composer;
|
||||
$this->io = $io;
|
||||
$this->manageOptions = new ManageOptions($composer);
|
||||
$this->manageAllowedPackages = new AllowedPackages($composer, $io, $this->manageOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers post-package events if the 'require' command was called.
|
||||
*/
|
||||
public function requireWasCalled() {
|
||||
// In order to differentiate between post-package events called after
|
||||
// 'composer require' vs. the same events called at other times, we will
|
||||
// only install our handler when a 'require' event is detected.
|
||||
$this->postPackageListeners[] = $this->manageAllowedPackages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Posts package command event.
|
||||
*
|
||||
* We want to detect packages 'require'd that have scaffold files, but are not
|
||||
* yet allowed in the top-level composer.json file.
|
||||
*
|
||||
* @param \Composer\Installer\PackageEvent $event
|
||||
* Composer package event sent on install/update/remove.
|
||||
*/
|
||||
public function onPostPackageEvent(PackageEvent $event) {
|
||||
foreach ($this->postPackageListeners as $listener) {
|
||||
$listener->event($event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates scaffold operation objects for all items in the file mappings.
|
||||
*
|
||||
* @param \Composer\Package\PackageInterface $package
|
||||
* The package that relative paths will be relative from.
|
||||
* @param array $package_file_mappings
|
||||
* The package file mappings array keyed by destination path and the values
|
||||
* are operation metadata arrays.
|
||||
*
|
||||
* @return \Drupal\Composer\Plugin\Scaffold\Operations\OperationInterface[]
|
||||
* A list of scaffolding operation objects
|
||||
*/
|
||||
protected function createScaffoldOperations(PackageInterface $package, array $package_file_mappings) {
|
||||
$scaffold_op_factory = new OperationFactory($this->composer);
|
||||
$scaffold_ops = [];
|
||||
foreach ($package_file_mappings as $dest_rel_path => $data) {
|
||||
$operation_data = new OperationData($dest_rel_path, $data);
|
||||
$scaffold_ops[$dest_rel_path] = $scaffold_op_factory->create($package, $operation_data);
|
||||
}
|
||||
return $scaffold_ops;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies all scaffold files from source to destination.
|
||||
*/
|
||||
public function scaffold() {
|
||||
// Recursively get the list of allowed packages. Only allowed packages
|
||||
// may declare scaffold files. Note that the top-level composer.json file
|
||||
// is implicitly allowed.
|
||||
$allowed_packages = $this->manageAllowedPackages->getAllowedPackages();
|
||||
if (empty($allowed_packages)) {
|
||||
$this->io->write("Nothing scaffolded because no packages are allowed in the top-level composer.json file.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Call any pre-scaffold scripts that may be defined.
|
||||
$dispatcher = $this->composer->getEventDispatcher();
|
||||
$dispatcher->dispatchScript(self::PRE_DRUPAL_SCAFFOLD_CMD);
|
||||
|
||||
// Fetch the list of file mappings from each allowed package and normalize
|
||||
// them.
|
||||
$file_mappings = $this->getFileMappingsFromPackages($allowed_packages);
|
||||
|
||||
$location_replacements = $this->manageOptions->getLocationReplacements();
|
||||
$scaffold_options = $this->manageOptions->getOptions();
|
||||
|
||||
// Create a collection of scaffolded files to process. This determines which
|
||||
// take priority and which are combined.
|
||||
$scaffold_files = new ScaffoldFileCollection($file_mappings, $location_replacements);
|
||||
|
||||
// Get the scaffold files whose contents on disk match what we are about to
|
||||
// write. We can remove these from consideration, as rewriting would be a
|
||||
// no-op.
|
||||
$unchanged = $scaffold_files->checkUnchanged();
|
||||
$scaffold_files->filterFiles($unchanged);
|
||||
|
||||
// Process the list of scaffolded files.
|
||||
$scaffold_results = $scaffold_files->processScaffoldFiles($this->io, $scaffold_options);
|
||||
|
||||
// Generate an autoload file in the document root that includes the
|
||||
// autoload.php file in the vendor directory, wherever that is. Drupal
|
||||
// requires this in order to easily locate relocated vendor dirs.
|
||||
$web_root = $this->manageOptions->getOptions()->getLocation('web-root');
|
||||
if (!GenerateAutoloadReferenceFile::autoloadFileCommitted($this->io, $this->rootPackageName(), $web_root)) {
|
||||
$scaffold_results[] = GenerateAutoloadReferenceFile::generateAutoload($this->io, $this->rootPackageName(), $web_root, $this->getVendorPath());
|
||||
}
|
||||
|
||||
// Add the managed scaffold files to .gitignore if applicable.
|
||||
$gitIgnoreManager = new ManageGitIgnore($this->io, getcwd());
|
||||
$gitIgnoreManager->manageIgnored($scaffold_results, $scaffold_options);
|
||||
|
||||
// Call post-scaffold scripts.
|
||||
$dispatcher->dispatchScript(self::POST_DRUPAL_SCAFFOLD_CMD);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the path to the 'vendor' directory.
|
||||
*
|
||||
* @return string
|
||||
* The file path of the vendor directory.
|
||||
*/
|
||||
protected function getVendorPath() {
|
||||
$vendor_dir = $this->composer->getConfig()->get('vendor-dir');
|
||||
$filesystem = new Filesystem();
|
||||
return $filesystem->normalizePath(realpath($vendor_dir));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a consolidated list of file mappings from all allowed packages.
|
||||
*
|
||||
* @param \Composer\Package\PackageInterface[] $allowed_packages
|
||||
* A multidimensional array of file mappings, as returned by
|
||||
* self::getAllowedPackages().
|
||||
*
|
||||
* @return \Drupal\Composer\Plugin\Scaffold\Operations\OperationInterface[][]
|
||||
* An array of destination paths => scaffold operation objects.
|
||||
*/
|
||||
protected function getFileMappingsFromPackages(array $allowed_packages) {
|
||||
$file_mappings = [];
|
||||
foreach ($allowed_packages as $package_name => $package) {
|
||||
$file_mappings[$package_name] = $this->getPackageFileMappings($package);
|
||||
}
|
||||
return $file_mappings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the array of file mappings provided by a given package.
|
||||
*
|
||||
* @param \Composer\Package\PackageInterface $package
|
||||
* The Composer package from which to get the file mappings.
|
||||
*
|
||||
* @return \Drupal\Composer\Plugin\Scaffold\Operations\OperationInterface[]
|
||||
* An array of destination paths => scaffold operation objects.
|
||||
*/
|
||||
protected function getPackageFileMappings(PackageInterface $package) {
|
||||
$options = $this->manageOptions->packageOptions($package);
|
||||
if ($options->hasFileMapping()) {
|
||||
return $this->createScaffoldOperations($package, $options->fileMapping());
|
||||
}
|
||||
// Warn the user if they allow a package that does not have any scaffold
|
||||
// files. We will ignore drupal/core, though, as it is implicitly allowed,
|
||||
// but might not have scaffold files (version 8.7.x and earlier).
|
||||
if (!$options->hasAllowedPackages() && ($package->getName() != 'drupal/core')) {
|
||||
$this->io->writeError("The allowed package {$package->getName()} does not provide a file mapping for Composer Scaffold.");
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the root package name.
|
||||
*
|
||||
* @return string
|
||||
* The package name of the root project
|
||||
*/
|
||||
protected function rootPackageName() {
|
||||
$root_package = $this->composer->getPackage();
|
||||
return $root_package->getName();
|
||||
}
|
||||
|
||||
}
|
||||
157
vendor/drupal/core-composer-scaffold/Interpolator.php
vendored
Normal file
157
vendor/drupal/core-composer-scaffold/Interpolator.php
vendored
Normal file
@ -0,0 +1,157 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Composer\Plugin\Scaffold;
|
||||
|
||||
/**
|
||||
* Injects config values from an associative array into a string.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class Interpolator {
|
||||
|
||||
/**
|
||||
* The character sequence that identifies the start of a token.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $startToken;
|
||||
|
||||
/**
|
||||
* The character sequence that identifies the end of a token.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $endToken;
|
||||
|
||||
/**
|
||||
* The associative array of replacements.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $data = [];
|
||||
|
||||
/**
|
||||
* Interpolator constructor.
|
||||
*
|
||||
* @param string $start_token
|
||||
* The start marker for a token, e.g. '['.
|
||||
* @param string $end_token
|
||||
* The end marker for a token, e.g. ']'.
|
||||
*/
|
||||
public function __construct($start_token = '\\[', $end_token = '\\]') {
|
||||
$this->startToken = $start_token;
|
||||
$this->endToken = $end_token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the data set to use when interpolating.
|
||||
*
|
||||
* @param array $data
|
||||
* The key:value pairs to use when interpolating.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setData(array $data) {
|
||||
$this->data = $data;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds to the data set to use when interpolating.
|
||||
*
|
||||
* @param array $data
|
||||
* The key:value pairs to use when interpolating.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addData(array $data) {
|
||||
$this->data = array_merge($this->data, $data);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces tokens in a string with values from an associative array.
|
||||
*
|
||||
* Tokens are surrounded by delimiters, e.g. square brackets "[key]". The
|
||||
* characters that surround the key may be defined when the Interpolator is
|
||||
* constructed.
|
||||
*
|
||||
* Example:
|
||||
* If the message is 'Hello, [user.name]', then the value of the user.name
|
||||
* item is fetched from the array, and the token [user.name] is replaced with
|
||||
* the result.
|
||||
*
|
||||
* @param string $message
|
||||
* Message containing tokens to be replaced.
|
||||
* @param array $extra
|
||||
* Data to use for interpolation in addition to whatever was provided to
|
||||
* self::setData().
|
||||
* @param string|bool $default
|
||||
* (optional) The value to substitute for tokens that are not found in the
|
||||
* data. If FALSE, then missing tokens are not replaced. Defaults to an
|
||||
* empty string.
|
||||
*
|
||||
* @return string
|
||||
* The message after replacements have been made.
|
||||
*/
|
||||
public function interpolate($message, array $extra = [], $default = '') {
|
||||
$data = $extra + $this->data;
|
||||
$replacements = $this->replacements($message, $data, $default);
|
||||
return strtr($message, $replacements);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the tokens that exist in a message and builds a replacement array.
|
||||
*
|
||||
* All of the replacements in the data array are looked up given the token
|
||||
* keys from the provided message. Keys that do not exist in the configuration
|
||||
* are replaced with the default value.
|
||||
*
|
||||
* @param string $message
|
||||
* String with tokens.
|
||||
* @param array $data
|
||||
* Data to use for interpolation.
|
||||
* @param string $default
|
||||
* (optional) The value to substitute for tokens that are not found in the
|
||||
* data. If FALSE, then missing tokens are not replaced. Defaults to an
|
||||
* empty string.
|
||||
*
|
||||
* @return string[]
|
||||
* An array of replacements to make. Keyed by tokens and the replacements
|
||||
* are the values.
|
||||
*/
|
||||
protected function replacements($message, array $data, $default = '') {
|
||||
$tokens = $this->findTokens($message);
|
||||
$replacements = [];
|
||||
foreach ($tokens as $sourceText => $key) {
|
||||
$replacement_text = array_key_exists($key, $data) ? $data[$key] : $default;
|
||||
if ($replacement_text !== FALSE) {
|
||||
$replacements[$sourceText] = $replacement_text;
|
||||
}
|
||||
}
|
||||
return $replacements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all of the tokens in the provided message.
|
||||
*
|
||||
* @param string $message
|
||||
* String with tokens.
|
||||
*
|
||||
* @return string[]
|
||||
* map of token to key, e.g. {{key}} => key
|
||||
*/
|
||||
protected function findTokens($message) {
|
||||
$reg_ex = '#' . $this->startToken . '([a-zA-Z0-9._-]+)' . $this->endToken . '#';
|
||||
if (!preg_match_all($reg_ex, $message, $matches, PREG_SET_ORDER)) {
|
||||
return [];
|
||||
}
|
||||
$tokens = [];
|
||||
foreach ($matches as $matchSet) {
|
||||
[$sourceText, $key] = $matchSet;
|
||||
$tokens[$sourceText] = $key;
|
||||
}
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
}
|
||||
339
vendor/drupal/core-composer-scaffold/LICENSE.txt
vendored
Normal file
339
vendor/drupal/core-composer-scaffold/LICENSE.txt
vendored
Normal file
@ -0,0 +1,339 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
||||
127
vendor/drupal/core-composer-scaffold/ManageGitIgnore.php
vendored
Normal file
127
vendor/drupal/core-composer-scaffold/ManageGitIgnore.php
vendored
Normal file
@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Composer\Plugin\Scaffold;
|
||||
|
||||
use Composer\IO\IOInterface;
|
||||
|
||||
/**
|
||||
* Manage the .gitignore file.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ManageGitIgnore {
|
||||
|
||||
/**
|
||||
* Composer's I/O service.
|
||||
*
|
||||
* @var \Composer\IO\IOInterface
|
||||
*/
|
||||
protected $io;
|
||||
|
||||
/**
|
||||
* The directory where the project is located.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $dir;
|
||||
|
||||
/**
|
||||
* ManageGitIgnore constructor.
|
||||
*
|
||||
* @param \Composer\IO\IOInterface $io
|
||||
* The Composer IO interface.
|
||||
* @param string $dir
|
||||
* The directory where the project is located.
|
||||
*/
|
||||
public function __construct(IOInterface $io, $dir) {
|
||||
$this->io = $io;
|
||||
$this->dir = $dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages gitignore files.
|
||||
*
|
||||
* @param \Drupal\Composer\Plugin\Scaffold\Operations\ScaffoldResult[] $files
|
||||
* A list of scaffold results, each of which holds a path and whether
|
||||
* or not that file is managed.
|
||||
* @param \Drupal\Composer\Plugin\Scaffold\ScaffoldOptions $options
|
||||
* Configuration options from the composer.json extras section.
|
||||
*/
|
||||
public function manageIgnored(array $files, ScaffoldOptions $options) {
|
||||
if (!$this->managementOfGitIgnoreEnabled($options)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Accumulate entries to add to .gitignore, sorted into buckets based on the
|
||||
// location of the .gitignore file the entry should be added to.
|
||||
$add_to_git_ignore = [];
|
||||
foreach ($files as $scaffoldResult) {
|
||||
$path = $scaffoldResult->destination()->fullPath();
|
||||
$is_ignored = Git::checkIgnore($this->io, $path, $this->dir);
|
||||
if (!$is_ignored) {
|
||||
$is_tracked = Git::checkTracked($this->io, $path, $this->dir);
|
||||
if (!$is_tracked && $scaffoldResult->isManaged()) {
|
||||
$dir = realpath(dirname($path));
|
||||
$name = basename($path);
|
||||
$add_to_git_ignore[$dir][] = '/' . $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Write out the .gitignore files one at a time.
|
||||
foreach ($add_to_git_ignore as $dir => $entries) {
|
||||
$this->addToGitIgnore($dir, $entries);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether we should manage gitignore files.
|
||||
*
|
||||
* @param \Drupal\Composer\Plugin\Scaffold\ScaffoldOptions $options
|
||||
* Configuration options from the composer.json extras section.
|
||||
*
|
||||
* @return bool
|
||||
* Whether or not gitignore files should be managed.
|
||||
*/
|
||||
protected function managementOfGitIgnoreEnabled(ScaffoldOptions $options) {
|
||||
// If the composer.json stipulates whether gitignore is managed or not, then
|
||||
// follow its recommendation.
|
||||
if ($options->hasGitIgnore()) {
|
||||
return $options->gitIgnore();
|
||||
}
|
||||
|
||||
// Do not manage .gitignore if there is no repository here.
|
||||
if (!Git::isRepository($this->io, $this->dir)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// If the composer.json did not specify whether or not .gitignore files
|
||||
// should be managed, then manage them if the vendor directory is ignored.
|
||||
return Git::checkIgnore($this->io, 'vendor', $this->dir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a set of entries to the specified .gitignore file.
|
||||
*
|
||||
* @param string $dir
|
||||
* Path to directory where gitignore should be written.
|
||||
* @param string[] $entries
|
||||
* Entries to write to .gitignore file.
|
||||
*/
|
||||
protected function addToGitIgnore($dir, array $entries) {
|
||||
sort($entries);
|
||||
$git_ignore_path = $dir . '/.gitignore';
|
||||
$contents = '';
|
||||
|
||||
// Appending to existing .gitignore files.
|
||||
if (file_exists($git_ignore_path)) {
|
||||
$contents = file_get_contents($git_ignore_path);
|
||||
if (!empty($contents) && !str_ends_with($contents, "\n")) {
|
||||
$contents .= "\n";
|
||||
}
|
||||
}
|
||||
|
||||
$contents .= implode("\n", $entries);
|
||||
file_put_contents($git_ignore_path, $contents);
|
||||
}
|
||||
|
||||
}
|
||||
92
vendor/drupal/core-composer-scaffold/ManageOptions.php
vendored
Normal file
92
vendor/drupal/core-composer-scaffold/ManageOptions.php
vendored
Normal file
@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Composer\Plugin\Scaffold;
|
||||
|
||||
use Composer\Composer;
|
||||
use Composer\Package\PackageInterface;
|
||||
use Composer\Util\Filesystem;
|
||||
|
||||
/**
|
||||
* Per-project options from the 'extras' section of the composer.json file.
|
||||
*
|
||||
* Projects that describe scaffold files do so via their scaffold options.
|
||||
* This data is pulled from the 'drupal-scaffold' portion of the extras
|
||||
* section of the project data.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ManageOptions {
|
||||
|
||||
/**
|
||||
* The Composer service.
|
||||
*
|
||||
* @var \Composer\Composer
|
||||
*/
|
||||
protected $composer;
|
||||
|
||||
/**
|
||||
* ManageOptions constructor.
|
||||
*
|
||||
* @param \Composer\Composer $composer
|
||||
* The Composer service.
|
||||
*/
|
||||
public function __construct(Composer $composer) {
|
||||
$this->composer = $composer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the root-level scaffold options for this project.
|
||||
*
|
||||
* @return \Drupal\Composer\Plugin\Scaffold\ScaffoldOptions
|
||||
* The scaffold options object.
|
||||
*/
|
||||
public function getOptions() {
|
||||
return $this->packageOptions($this->composer->getPackage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the scaffold options for the stipulated project.
|
||||
*
|
||||
* @param \Composer\Package\PackageInterface $package
|
||||
* The package to fetch the scaffold options from.
|
||||
*
|
||||
* @return \Drupal\Composer\Plugin\Scaffold\ScaffoldOptions
|
||||
* The scaffold options object.
|
||||
*/
|
||||
public function packageOptions(PackageInterface $package) {
|
||||
return ScaffoldOptions::create($package->getExtra());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an interpolator for the 'locations' element.
|
||||
*
|
||||
* The interpolator returned will replace a path string with the tokens
|
||||
* defined in the 'locations' element.
|
||||
*
|
||||
* Note that only the root package may define locations.
|
||||
*
|
||||
* @return \Drupal\Composer\Plugin\Scaffold\Interpolator
|
||||
* Interpolator that will do replacements in a string using tokens in
|
||||
* 'locations' element.
|
||||
*/
|
||||
public function getLocationReplacements() {
|
||||
return (new Interpolator())->setData($this->ensureLocations());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that all of the locations defined in the scaffold files exist.
|
||||
*
|
||||
* Create them on the filesystem if they do not.
|
||||
*/
|
||||
protected function ensureLocations() {
|
||||
$fs = new Filesystem();
|
||||
$locations = $this->getOptions()->locations() + ['web_root' => './'];
|
||||
$locations = array_map(function ($location) use ($fs) {
|
||||
$fs->ensureDirectoryExists($location);
|
||||
$location = realpath($location);
|
||||
return $location;
|
||||
}, $locations);
|
||||
return $locations;
|
||||
}
|
||||
|
||||
}
|
||||
53
vendor/drupal/core-composer-scaffold/Operations/AbstractOperation.php
vendored
Normal file
53
vendor/drupal/core-composer-scaffold/Operations/AbstractOperation.php
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Composer\Plugin\Scaffold\Operations;
|
||||
|
||||
use Drupal\Composer\Plugin\Scaffold\ScaffoldFilePath;
|
||||
|
||||
/**
|
||||
* Provides default behaviors for operations.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
abstract class AbstractOperation implements OperationInterface {
|
||||
|
||||
/**
|
||||
* Cached contents of scaffold file to be written to disk.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $contents;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
final public function contents() {
|
||||
if (!isset($this->contents)) {
|
||||
$this->contents = $this->generateContents();
|
||||
}
|
||||
return $this->contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the scaffold contents or otherwise generate what is needed.
|
||||
*
|
||||
* @return string
|
||||
* The contents of the scaffold file.
|
||||
*/
|
||||
abstract protected function generateContents();
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function scaffoldOverExistingTarget(OperationInterface $existing_target) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function scaffoldAtNewLocation(ScaffoldFilePath $destination) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
216
vendor/drupal/core-composer-scaffold/Operations/AppendOp.php
vendored
Normal file
216
vendor/drupal/core-composer-scaffold/Operations/AppendOp.php
vendored
Normal file
@ -0,0 +1,216 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Composer\Plugin\Scaffold\Operations;
|
||||
|
||||
use Composer\IO\IOInterface;
|
||||
use Drupal\Composer\Plugin\Scaffold\ScaffoldFilePath;
|
||||
use Drupal\Composer\Plugin\Scaffold\ScaffoldOptions;
|
||||
|
||||
/**
|
||||
* Scaffold operation to add to the beginning and/or end of a scaffold file.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class AppendOp extends AbstractOperation {
|
||||
|
||||
/**
|
||||
* Identifies Append operations.
|
||||
*/
|
||||
const ID = 'append';
|
||||
|
||||
/**
|
||||
* Path to the source file to prepend, if any.
|
||||
*
|
||||
* @var \Drupal\Composer\Plugin\Scaffold\ScaffoldFilePath
|
||||
*/
|
||||
protected $prepend;
|
||||
|
||||
/**
|
||||
* Path to the source file to append, if any.
|
||||
*
|
||||
* @var \Drupal\Composer\Plugin\Scaffold\ScaffoldFilePath
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $managed;
|
||||
|
||||
/**
|
||||
* An indicator of whether we are allowed to append to a non-scaffolded file.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $forceAppend;
|
||||
|
||||
/**
|
||||
* The contents from the file that we are prepending / appending to.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $originalContents;
|
||||
|
||||
/**
|
||||
* Constructs an AppendOp.
|
||||
*
|
||||
* @param \Drupal\Composer\Plugin\Scaffold\ScaffoldFilePath|null $prepend_path
|
||||
* (optional) The relative path to the prepend file.
|
||||
* @param \Drupal\Composer\Plugin\Scaffold\ScaffoldFilePath|null $append_path
|
||||
* (optional) The relative path to the append file.
|
||||
* @param bool $force_append
|
||||
* (optional) TRUE if is okay to append to a file that was not scaffolded.
|
||||
* @param \Drupal\Composer\Plugin\Scaffold\ScaffoldFilePath|null $default_path
|
||||
* (optional) The relative path to the default data.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function generateContents() {
|
||||
// Fetch the prepend contents, if provided.
|
||||
$prepend_contents = '';
|
||||
if (!empty($this->prepend)) {
|
||||
$prepend_contents = file_get_contents($this->prepend->fullPath()) . "\n";
|
||||
}
|
||||
// Fetch the append contents, if provided.
|
||||
$append_contents = '';
|
||||
if (!empty($this->append)) {
|
||||
$append_contents = "\n" . file_get_contents($this->append->fullPath());
|
||||
}
|
||||
|
||||
// Get the original contents, or the default data if the original is empty.
|
||||
$original_contents = $this->originalContents;
|
||||
if (empty($original_contents) && !empty($this->default)) {
|
||||
$original_contents = file_get_contents($this->default->fullPath());
|
||||
}
|
||||
|
||||
// Attach it all together.
|
||||
return $prepend_contents . $original_contents . $append_contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function process(ScaffoldFilePath $destination, IOInterface $io, ScaffoldOptions $options) {
|
||||
$destination_path = $destination->fullPath();
|
||||
$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));
|
||||
}
|
||||
|
||||
// Notify that we are prepending, if there is prepend data.
|
||||
if (!empty($this->prepend)) {
|
||||
$this->prepend->addInterpolationData($interpolator, 'prepend');
|
||||
$io->write($interpolator->interpolate(" - Prepend to <info>[dest-rel-path]</info> from <info>[prepend-rel-path]</info>"));
|
||||
}
|
||||
// Notify that we are appending, if there is append data.
|
||||
if (!empty($this->append)) {
|
||||
$this->append->addInterpolationData($interpolator, 'append');
|
||||
$io->write($interpolator->interpolate(" - Append to <info>[dest-rel-path]</info> from <info>[append-rel-path]</info>"));
|
||||
}
|
||||
|
||||
// Write the resulting data
|
||||
file_put_contents($destination_path, $this->contents());
|
||||
|
||||
// Return a ScaffoldResult with knowledge of whether this file is managed.
|
||||
return new ScaffoldResult($destination, $this->managed);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function scaffoldOverExistingTarget(OperationInterface $existing_target) {
|
||||
$this->originalContents = $existing_target->contents();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function scaffoldAtNewLocation(ScaffoldFilePath $destination) {
|
||||
// If there is no existing scaffold file at the target location, 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);
|
||||
}
|
||||
|
||||
// Cache the original data to use during append.
|
||||
$this->originalContents = $existingData;
|
||||
|
||||
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 str_contains($contents, $data);
|
||||
}
|
||||
|
||||
}
|
||||
229
vendor/drupal/core-composer-scaffold/Operations/OperationData.php
vendored
Normal file
229
vendor/drupal/core-composer-scaffold/Operations/OperationData.php
vendored
Normal file
@ -0,0 +1,229 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Composer\Plugin\Scaffold\Operations;
|
||||
|
||||
/**
|
||||
* Holds parameter data for operation objects during operation creation only.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class OperationData {
|
||||
|
||||
const MODE = 'mode';
|
||||
const PATH = 'path';
|
||||
const OVERWRITE = 'overwrite';
|
||||
const PREPEND = 'prepend';
|
||||
const APPEND = 'append';
|
||||
const DEFAULT = 'default';
|
||||
const FORCE_APPEND = 'force-append';
|
||||
|
||||
/**
|
||||
* The parameter data.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $data;
|
||||
|
||||
/**
|
||||
* The destination path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $destination;
|
||||
|
||||
/**
|
||||
* OperationData constructor.
|
||||
*
|
||||
* @param string $destination
|
||||
* The destination path.
|
||||
* @param mixed $data
|
||||
* The raw data array to wrap.
|
||||
*/
|
||||
public function __construct($destination, $data) {
|
||||
$this->destination = $destination;
|
||||
$this->data = $this->normalizeScaffoldMetadata($destination, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the destination path that this operation data is associated with.
|
||||
*
|
||||
* @return string
|
||||
* The destination path for the scaffold result.
|
||||
*/
|
||||
public function destination() {
|
||||
return $this->destination;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets operation mode.
|
||||
*
|
||||
* @return string
|
||||
* Operation mode.
|
||||
*/
|
||||
public function mode() {
|
||||
return $this->data[self::MODE];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if path exists.
|
||||
*
|
||||
* @return bool
|
||||
* Returns true if path exists
|
||||
*/
|
||||
public function hasPath() {
|
||||
return isset($this->data[self::PATH]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets path.
|
||||
*
|
||||
* @return string
|
||||
* The path.
|
||||
*/
|
||||
public function path() {
|
||||
return $this->data[self::PATH];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines overwrite.
|
||||
*
|
||||
* @return bool
|
||||
* Returns true if overwrite mode was selected.
|
||||
*/
|
||||
public function overwrite() {
|
||||
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]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if prepend path exists.
|
||||
*
|
||||
* @return bool
|
||||
* Returns true if prepend exists.
|
||||
*/
|
||||
public function hasPrepend() {
|
||||
return isset($this->data[self::PREPEND]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets prepend path.
|
||||
*
|
||||
* @return string
|
||||
* Path to prepend data
|
||||
*/
|
||||
public function prepend() {
|
||||
return $this->data[self::PREPEND];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if append path exists.
|
||||
*
|
||||
* @return bool
|
||||
* Returns true if prepend exists.
|
||||
*/
|
||||
public function hasAppend() {
|
||||
return isset($this->data[self::APPEND]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets append path.
|
||||
*
|
||||
* @return string
|
||||
* Path to append data
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* Conversions performed include:
|
||||
* - Boolean 'false' means "skip".
|
||||
* - A string means "replace", with the string value becoming the path.
|
||||
*
|
||||
* @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 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];
|
||||
}
|
||||
throw new \RuntimeException("File mapping {$destination} cannot be given the value 'true'.");
|
||||
}
|
||||
if (empty($value)) {
|
||||
throw new \RuntimeException("File mapping {$destination} cannot be empty.");
|
||||
}
|
||||
if (is_string($value)) {
|
||||
$value = [self::PATH => $value];
|
||||
}
|
||||
// If there is no 'mode', but there is an 'append' or a 'prepend' path,
|
||||
// then the mode is 'append' (append + prepend).
|
||||
if (!isset($value[self::MODE]) && (isset($value[self::APPEND]) || isset($value[self::PREPEND]))) {
|
||||
$value[self::MODE] = AppendOp::ID;
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
}
|
||||
162
vendor/drupal/core-composer-scaffold/Operations/OperationFactory.php
vendored
Normal file
162
vendor/drupal/core-composer-scaffold/Operations/OperationFactory.php
vendored
Normal file
@ -0,0 +1,162 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Composer\Plugin\Scaffold\Operations;
|
||||
|
||||
use Composer\Composer;
|
||||
use Composer\Package\PackageInterface;
|
||||
use Drupal\Composer\Plugin\Scaffold\ScaffoldFilePath;
|
||||
|
||||
/**
|
||||
* Create Scaffold operation objects based on provided metadata.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class OperationFactory {
|
||||
|
||||
/**
|
||||
* The Composer service.
|
||||
*
|
||||
* @var \Composer\Composer
|
||||
*/
|
||||
protected $composer;
|
||||
|
||||
/**
|
||||
* OperationFactory constructor.
|
||||
*
|
||||
* @param \Composer\Composer $composer
|
||||
* Reference to the 'Composer' object, since the Scaffold Operation Factory
|
||||
* is also responsible for evaluating relative package paths as it creates
|
||||
* scaffold operations.
|
||||
*/
|
||||
public function __construct(Composer $composer) {
|
||||
$this->composer = $composer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a scaffolding operation object as determined by the metadata.
|
||||
*
|
||||
* @param \Composer\Package\PackageInterface $package
|
||||
* The package that relative paths will be relative from.
|
||||
* @param OperationData $operation_data
|
||||
* The parameter data for this operation object; varies by operation type.
|
||||
*
|
||||
* @return \Drupal\Composer\Plugin\Scaffold\Operations\OperationInterface
|
||||
* The scaffolding operation object (skip, replace, etc.)
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
* Exception thrown when parameter data does not identify a known scaffold
|
||||
* operation.
|
||||
*/
|
||||
public function create(PackageInterface $package, OperationData $operation_data) {
|
||||
switch ($operation_data->mode()) {
|
||||
case SkipOp::ID:
|
||||
return new SkipOp();
|
||||
|
||||
case ReplaceOp::ID:
|
||||
return $this->createReplaceOp($package, $operation_data);
|
||||
|
||||
case AppendOp::ID:
|
||||
return $this->createAppendOp($package, $operation_data);
|
||||
}
|
||||
throw new \RuntimeException("Unknown scaffold operation mode <comment>{$operation_data->mode()}</comment>.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a 'replace' scaffold op.
|
||||
*
|
||||
* Replace ops may copy or symlink, depending on settings.
|
||||
*
|
||||
* @param \Composer\Package\PackageInterface $package
|
||||
* The package that relative paths will be relative from.
|
||||
* @param OperationData $operation_data
|
||||
* The parameter data for this operation object, i.e. the relative 'path'.
|
||||
*
|
||||
* @return \Drupal\Composer\Plugin\Scaffold\Operations\OperationInterface
|
||||
* A scaffold replace operation object.
|
||||
*/
|
||||
protected function createReplaceOp(PackageInterface $package, OperationData $operation_data) {
|
||||
if (!$operation_data->hasPath()) {
|
||||
throw new \RuntimeException("'path' component required for 'replace' operations.");
|
||||
}
|
||||
$package_name = $package->getName();
|
||||
$package_path = $this->getPackagePath($package);
|
||||
$source = ScaffoldFilePath::sourcePath($package_name, $package_path, $operation_data->destination(), $operation_data->path());
|
||||
$op = new ReplaceOp($source, $operation_data->overwrite());
|
||||
return $op;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an 'append' (or 'prepend') scaffold op.
|
||||
*
|
||||
* @param \Composer\Package\PackageInterface $package
|
||||
* The package that relative paths will be relative from.
|
||||
* @param OperationData $operation_data
|
||||
* The parameter data for this operation object, i.e. the relative 'path'.
|
||||
*
|
||||
* @return \Drupal\Composer\Plugin\Scaffold\Operations\OperationInterface
|
||||
* A scaffold replace operation object.
|
||||
*/
|
||||
protected function createAppendOp(PackageInterface $package, OperationData $operation_data) {
|
||||
$package_name = $package->getName();
|
||||
$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());
|
||||
}
|
||||
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|null $file
|
||||
* (optional) 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file path of a package.
|
||||
*
|
||||
* Note that if we call getInstallPath on the root package, we get the
|
||||
* wrong answer (the installation manager thinks our package is in
|
||||
* vendor). We therefore add special checking for this case.
|
||||
*
|
||||
* @param \Composer\Package\PackageInterface $package
|
||||
* The package.
|
||||
*
|
||||
* @return string
|
||||
* The file path.
|
||||
*/
|
||||
protected function getPackagePath(PackageInterface $package) {
|
||||
if ($package->getName() == $this->composer->getPackage()->getName()) {
|
||||
// This will respect the --working-dir option if Composer is invoked with
|
||||
// it. There is no API or method to determine the filesystem path of
|
||||
// a package's composer.json file.
|
||||
return getcwd();
|
||||
}
|
||||
return $this->composer->getInstallationManager()->getInstallPath($package);
|
||||
}
|
||||
|
||||
}
|
||||
69
vendor/drupal/core-composer-scaffold/Operations/OperationInterface.php
vendored
Normal file
69
vendor/drupal/core-composer-scaffold/Operations/OperationInterface.php
vendored
Normal file
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Composer\Plugin\Scaffold\Operations;
|
||||
|
||||
use Composer\IO\IOInterface;
|
||||
use Drupal\Composer\Plugin\Scaffold\ScaffoldFilePath;
|
||||
use Drupal\Composer\Plugin\Scaffold\ScaffoldOptions;
|
||||
|
||||
/**
|
||||
* Interface for scaffold operation objects.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
interface OperationInterface {
|
||||
|
||||
/**
|
||||
* Returns the exact data that will be written to the scaffold files.
|
||||
*
|
||||
* @return string
|
||||
* Data to be written to the scaffold location.
|
||||
*/
|
||||
public function contents();
|
||||
|
||||
/**
|
||||
* Process this scaffold operation.
|
||||
*
|
||||
* @param \Drupal\Composer\Plugin\Scaffold\ScaffoldFilePath $destination
|
||||
* Scaffold file's destination path.
|
||||
* @param \Composer\IO\IOInterface $io
|
||||
* IOInterface to write to.
|
||||
* @param \Drupal\Composer\Plugin\Scaffold\ScaffoldOptions $options
|
||||
* Various options that may alter the behavior of the operation.
|
||||
*
|
||||
* @return \Drupal\Composer\Plugin\Scaffold\Operations\ScaffoldResult
|
||||
* Result of the scaffolding operation.
|
||||
*/
|
||||
public function process(ScaffoldFilePath $destination, IOInterface $io, ScaffoldOptions $options);
|
||||
|
||||
/**
|
||||
* Determines what to do if operation is used at same path as a previous op.
|
||||
*
|
||||
* Default behavior is to scaffold this operation at the specified
|
||||
* destination, ignoring whatever was there before.
|
||||
*
|
||||
* @param OperationInterface $existing_target
|
||||
* Existing file at the destination path that we should combine with.
|
||||
*
|
||||
* @return OperationInterface
|
||||
* The op to use at this destination.
|
||||
*/
|
||||
public function scaffoldOverExistingTarget(OperationInterface $existing_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.
|
||||
*
|
||||
* @param \Drupal\Composer\Plugin\Scaffold\ScaffoldFilePath $destination
|
||||
* Scaffold file's destination path.
|
||||
*
|
||||
* @return OperationInterface
|
||||
* The op to use at this destination.
|
||||
*/
|
||||
public function scaffoldAtNewLocation(ScaffoldFilePath $destination);
|
||||
|
||||
}
|
||||
126
vendor/drupal/core-composer-scaffold/Operations/ReplaceOp.php
vendored
Normal file
126
vendor/drupal/core-composer-scaffold/Operations/ReplaceOp.php
vendored
Normal file
@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Composer\Plugin\Scaffold\Operations;
|
||||
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Util\Filesystem;
|
||||
use Drupal\Composer\Plugin\Scaffold\ScaffoldFilePath;
|
||||
use Drupal\Composer\Plugin\Scaffold\ScaffoldOptions;
|
||||
|
||||
/**
|
||||
* Scaffold operation to copy or symlink from source to destination.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ReplaceOp extends AbstractOperation {
|
||||
|
||||
/**
|
||||
* Identifies Replace operations.
|
||||
*/
|
||||
const ID = 'replace';
|
||||
|
||||
/**
|
||||
* The relative path to the source file.
|
||||
*
|
||||
* @var \Drupal\Composer\Plugin\Scaffold\ScaffoldFilePath
|
||||
*/
|
||||
protected $source;
|
||||
|
||||
/**
|
||||
* Whether to overwrite existing files.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $overwrite;
|
||||
|
||||
/**
|
||||
* Constructs a ReplaceOp.
|
||||
*
|
||||
* @param \Drupal\Composer\Plugin\Scaffold\ScaffoldFilePath $sourcePath
|
||||
* The relative path to the source file.
|
||||
* @param bool $overwrite
|
||||
* Whether to allow this scaffold file to overwrite files already at
|
||||
* the destination. Defaults to TRUE.
|
||||
*/
|
||||
public function __construct(ScaffoldFilePath $sourcePath, $overwrite = TRUE) {
|
||||
$this->source = $sourcePath;
|
||||
$this->overwrite = $overwrite;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function generateContents() {
|
||||
return file_get_contents($this->source->fullPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function process(ScaffoldFilePath $destination, IOInterface $io, ScaffoldOptions $options) {
|
||||
$fs = new Filesystem();
|
||||
$destination_path = $destination->fullPath();
|
||||
// Do nothing if overwrite is 'false' and a file already exists at the
|
||||
// destination.
|
||||
if ($this->overwrite === FALSE && file_exists($destination_path)) {
|
||||
$interpolator = $destination->getInterpolator();
|
||||
$io->write($interpolator->interpolate(" - Skip <info>[dest-rel-path]</info> because it already exists and overwrite is <comment>false</comment>."));
|
||||
return new ScaffoldResult($destination, FALSE);
|
||||
}
|
||||
|
||||
// Get rid of the destination if it exists, and make sure that
|
||||
// the directory where it's going to be placed exists.
|
||||
$fs->remove($destination_path);
|
||||
$fs->ensureDirectoryExists(dirname($destination_path));
|
||||
if ($options->symlink()) {
|
||||
return $this->symlinkScaffold($destination, $io);
|
||||
}
|
||||
return $this->copyScaffold($destination, $io);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the scaffold file.
|
||||
*
|
||||
* @param \Drupal\Composer\Plugin\Scaffold\ScaffoldFilePath $destination
|
||||
* Scaffold file to process.
|
||||
* @param \Composer\IO\IOInterface $io
|
||||
* IOInterface to writing to.
|
||||
*
|
||||
* @return \Drupal\Composer\Plugin\Scaffold\Operations\ScaffoldResult
|
||||
* The scaffold result.
|
||||
*/
|
||||
protected function copyScaffold(ScaffoldFilePath $destination, IOInterface $io) {
|
||||
$interpolator = $destination->getInterpolator();
|
||||
$this->source->addInterpolationData($interpolator);
|
||||
if (file_put_contents($destination->fullPath(), $this->contents()) === FALSE) {
|
||||
throw new \RuntimeException($interpolator->interpolate("Could not copy source file <info>[src-rel-path]</info> to <info>[dest-rel-path]</info>!"));
|
||||
}
|
||||
$io->write($interpolator->interpolate(" - Copy <info>[dest-rel-path]</info> from <info>[src-rel-path]</info>"));
|
||||
return new ScaffoldResult($destination, $this->overwrite);
|
||||
}
|
||||
|
||||
/**
|
||||
* Symlinks the scaffold file.
|
||||
*
|
||||
* @param \Drupal\Composer\Plugin\Scaffold\ScaffoldFilePath $destination
|
||||
* Scaffold file to process.
|
||||
* @param \Composer\IO\IOInterface $io
|
||||
* IOInterface to writing to.
|
||||
*
|
||||
* @return \Drupal\Composer\Plugin\Scaffold\Operations\ScaffoldResult
|
||||
* The scaffold result.
|
||||
*/
|
||||
protected function symlinkScaffold(ScaffoldFilePath $destination, IOInterface $io) {
|
||||
$interpolator = $destination->getInterpolator();
|
||||
try {
|
||||
$fs = new Filesystem();
|
||||
$fs->relativeSymlink($this->source->fullPath(), $destination->fullPath());
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
throw new \RuntimeException($interpolator->interpolate("Could not symlink source file <info>[src-rel-path]</info> to <info>[dest-rel-path]</info>!"), [], $e);
|
||||
}
|
||||
$io->write($interpolator->interpolate(" - Link <info>[dest-rel-path]</info> from <info>[src-rel-path]</info>"));
|
||||
return new ScaffoldResult($destination, $this->overwrite);
|
||||
}
|
||||
|
||||
}
|
||||
198
vendor/drupal/core-composer-scaffold/Operations/ScaffoldFileCollection.php
vendored
Normal file
198
vendor/drupal/core-composer-scaffold/Operations/ScaffoldFileCollection.php
vendored
Normal file
@ -0,0 +1,198 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Composer\Plugin\Scaffold\Operations;
|
||||
|
||||
use Composer\IO\IOInterface;
|
||||
use Drupal\Composer\Plugin\Scaffold\Interpolator;
|
||||
use Drupal\Composer\Plugin\Scaffold\ScaffoldFileInfo;
|
||||
use Drupal\Composer\Plugin\Scaffold\ScaffoldFilePath;
|
||||
use Drupal\Composer\Plugin\Scaffold\ScaffoldOptions;
|
||||
|
||||
/**
|
||||
* Collection of scaffold files.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ScaffoldFileCollection implements \IteratorAggregate {
|
||||
|
||||
/**
|
||||
* Nested list of all scaffold files.
|
||||
*
|
||||
* The top level array maps from the package name to the collection of
|
||||
* scaffold files provided by that package. Each collection of scaffold files
|
||||
* is keyed by destination path.
|
||||
*
|
||||
* @var \Drupal\Composer\Plugin\Scaffold\ScaffoldFileInfo[][]
|
||||
*/
|
||||
protected $scaffoldFilesByProject = [];
|
||||
|
||||
/**
|
||||
* ScaffoldFileCollection constructor.
|
||||
*
|
||||
* @param \Drupal\Composer\Plugin\Scaffold\Operations\OperationInterface[][] $file_mappings
|
||||
* A multidimensional array of file mappings.
|
||||
* @param \Drupal\Composer\Plugin\Scaffold\Interpolator $location_replacements
|
||||
* An object with the location mappings (e.g. [web-root]).
|
||||
*/
|
||||
public function __construct(array $file_mappings, Interpolator $location_replacements) {
|
||||
// Collection of all destination paths to be scaffolded. Used to determine
|
||||
// when two projects scaffold the same file and we have to either replace or
|
||||
// combine them together.
|
||||
// @see OperationInterface::scaffoldOverExistingTarget().
|
||||
$scaffoldFiles = [];
|
||||
|
||||
// Build the list of ScaffoldFileInfo objects by project.
|
||||
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,
|
||||
// allow the new operation to decide how to handle the override.
|
||||
// Usually, the new operation will replace whatever was there before.
|
||||
if (isset($scaffoldFiles[$destination_rel_path])) {
|
||||
$previous_scaffold_file = $scaffoldFiles[$destination_rel_path];
|
||||
$op = $op->scaffoldOverExistingTarget($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, notify the scaffold operation of this fact.
|
||||
else {
|
||||
$op = $op->scaffoldAtNewLocation($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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes any item that has a path matching any path in the provided list.
|
||||
*
|
||||
* Matching is done via destination path.
|
||||
*
|
||||
* @param string[] $files_to_filter
|
||||
* List of destination paths.
|
||||
*/
|
||||
public function filterFiles(array $files_to_filter) {
|
||||
foreach ($this->scaffoldFilesByProject as $project_name => $scaffold_files) {
|
||||
foreach ($scaffold_files as $destination_rel_path => $scaffold_file) {
|
||||
if (in_array($destination_rel_path, $files_to_filter, TRUE)) {
|
||||
unset($scaffold_files[$destination_rel_path]);
|
||||
}
|
||||
}
|
||||
$this->scaffoldFilesByProject[$project_name] = $scaffold_files;
|
||||
if (!$this->checkListHasItemWithContent($scaffold_files)) {
|
||||
unset($this->scaffoldFilesByProject[$project_name]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans through a list of scaffold files and determines if any has contents.
|
||||
*
|
||||
* @param \Drupal\Composer\Plugin\Scaffold\ScaffoldFileInfo[] $scaffold_files
|
||||
* List of scaffold files, path: ScaffoldFileInfo.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if at least one item in the list has content
|
||||
*/
|
||||
protected function checkListHasItemWithContent(array $scaffold_files) {
|
||||
foreach ($scaffold_files as $scaffold_file) {
|
||||
$contents = $scaffold_file->op()->contents();
|
||||
if (!empty($contents)) {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIterator(): \ArrayIterator {
|
||||
return new \ArrayIterator($this->scaffoldFilesByProject);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the files in our collection.
|
||||
*
|
||||
* @param \Composer\IO\IOInterface $io
|
||||
* The Composer IO object.
|
||||
* @param \Drupal\Composer\Plugin\Scaffold\ScaffoldOptions $scaffold_options
|
||||
* The scaffold options.
|
||||
*
|
||||
* @return \Drupal\Composer\Plugin\Scaffold\Operations\ScaffoldResult[]
|
||||
* The results array.
|
||||
*/
|
||||
public function processScaffoldFiles(IOInterface $io, ScaffoldOptions $scaffold_options) {
|
||||
$results = [];
|
||||
foreach ($this as $project_name => $scaffold_files) {
|
||||
$io->write("Scaffolding files for <comment>{$project_name}</comment>:");
|
||||
foreach ($scaffold_files as $scaffold_file) {
|
||||
$results[$scaffold_file->destination()->relativePath()] = $scaffold_file->process($io, $scaffold_options);
|
||||
}
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the iterator created by ScaffoldFileCollection::create().
|
||||
*
|
||||
* @param \Drupal\Composer\Plugin\Scaffold\Operations\ScaffoldFileCollection $collection
|
||||
* The iterator to process.
|
||||
* @param \Composer\IO\IOInterface $io
|
||||
* The Composer IO object.
|
||||
* @param \Drupal\Composer\Plugin\Scaffold\ScaffoldOptions $scaffold_options
|
||||
* The scaffold options.
|
||||
*
|
||||
* @return \Drupal\Composer\Plugin\Scaffold\Operations\ScaffoldResult[]
|
||||
* The results array.
|
||||
*
|
||||
* @deprecated. Called when upgrading from the Core Composer Scaffold plugin
|
||||
* version 8.8.x due to a bug in the plugin and handler classes. Do not use
|
||||
* in 8.9.x or 9.x, and remove in Drupal 10.x.
|
||||
*/
|
||||
public static function process(ScaffoldFileCollection $collection, IOInterface $io, ScaffoldOptions $scaffold_options) {
|
||||
$results = [];
|
||||
foreach ($collection as $project_name => $scaffold_files) {
|
||||
$io->write("Scaffolding files for <comment>{$project_name}</comment>:");
|
||||
foreach ($scaffold_files as $scaffold_file) {
|
||||
$results[$scaffold_file->destination()->relativePath()] = $scaffold_file->process($io, $scaffold_options);
|
||||
}
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of files that have not changed since they were scaffolded.
|
||||
*
|
||||
* Note that there are two reasons a file may have changed:
|
||||
* - The user modified it after it was scaffolded.
|
||||
* - The package the file came to was updated, and the file is different in
|
||||
* the new version.
|
||||
*
|
||||
* With the current scaffold code, we cannot tell the difference between the
|
||||
* two. @see https://www.drupal.org/project/drupal/issues/3092563
|
||||
*
|
||||
* @return string[]
|
||||
* List of relative paths to unchanged files on disk.
|
||||
*/
|
||||
public function checkUnchanged() {
|
||||
$results = [];
|
||||
foreach ($this as $scaffold_files) {
|
||||
foreach ($scaffold_files as $scaffold_file) {
|
||||
if (!$scaffold_file->hasChanged()) {
|
||||
$results[] = $scaffold_file->destination()->relativePath();
|
||||
}
|
||||
}
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
}
|
||||
61
vendor/drupal/core-composer-scaffold/Operations/ScaffoldResult.php
vendored
Normal file
61
vendor/drupal/core-composer-scaffold/Operations/ScaffoldResult.php
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Composer\Plugin\Scaffold\Operations;
|
||||
|
||||
use Drupal\Composer\Plugin\Scaffold\ScaffoldFilePath;
|
||||
|
||||
/**
|
||||
* Record the result of a scaffold operation.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ScaffoldResult {
|
||||
|
||||
/**
|
||||
* The path to the scaffold file that was processed.
|
||||
*
|
||||
* @var \Drupal\Composer\Plugin\Scaffold\ScaffoldFilePath
|
||||
*/
|
||||
protected $destination;
|
||||
|
||||
/**
|
||||
* Indicates if this scaffold file is managed by the scaffold command.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $managed;
|
||||
|
||||
/**
|
||||
* ScaffoldResult constructor.
|
||||
*
|
||||
* @param \Drupal\Composer\Plugin\Scaffold\ScaffoldFilePath $destination
|
||||
* The path to the scaffold file that was processed.
|
||||
* @param bool $isManaged
|
||||
* (optional) Whether this result is managed. Defaults to FALSE.
|
||||
*/
|
||||
public function __construct(ScaffoldFilePath $destination, $isManaged = FALSE) {
|
||||
$this->destination = $destination;
|
||||
$this->managed = $isManaged;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether this scaffold file is managed.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if this scaffold file is managed, FALSE if not.
|
||||
*/
|
||||
public function isManaged() {
|
||||
return $this->managed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the destination scaffold file that this result refers to.
|
||||
*
|
||||
* @return \Drupal\Composer\Plugin\Scaffold\ScaffoldFilePath
|
||||
* The destination path for the scaffold result.
|
||||
*/
|
||||
public function destination() {
|
||||
return $this->destination;
|
||||
}
|
||||
|
||||
}
|
||||
54
vendor/drupal/core-composer-scaffold/Operations/SkipOp.php
vendored
Normal file
54
vendor/drupal/core-composer-scaffold/Operations/SkipOp.php
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Composer\Plugin\Scaffold\Operations;
|
||||
|
||||
use Composer\IO\IOInterface;
|
||||
use Drupal\Composer\Plugin\Scaffold\ScaffoldFilePath;
|
||||
use Drupal\Composer\Plugin\Scaffold\ScaffoldOptions;
|
||||
|
||||
/**
|
||||
* Scaffold operation to skip a scaffold file (do nothing).
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class SkipOp extends AbstractOperation {
|
||||
|
||||
/**
|
||||
* Identifies Skip operations.
|
||||
*/
|
||||
const ID = 'skip';
|
||||
|
||||
/**
|
||||
* The message to output while processing.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $message;
|
||||
|
||||
/**
|
||||
* SkipOp constructor.
|
||||
*
|
||||
* @param string $message
|
||||
* (optional) A custom message to output while skipping.
|
||||
*/
|
||||
public function __construct($message = " - Skip <info>[dest-rel-path]</info>: disabled") {
|
||||
$this->message = $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function generateContents() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function process(ScaffoldFilePath $destination, IOInterface $io, ScaffoldOptions $options) {
|
||||
$interpolator = $destination->getInterpolator();
|
||||
$io->write($interpolator->interpolate($this->message));
|
||||
return new ScaffoldResult($destination, FALSE);
|
||||
}
|
||||
|
||||
}
|
||||
149
vendor/drupal/core-composer-scaffold/Plugin.php
vendored
Normal file
149
vendor/drupal/core-composer-scaffold/Plugin.php
vendored
Normal file
@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Composer\Plugin\Scaffold;
|
||||
|
||||
use Composer\Composer;
|
||||
use Composer\EventDispatcher\EventSubscriberInterface;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Installer\PackageEvent;
|
||||
use Composer\Installer\PackageEvents;
|
||||
use Composer\Plugin\Capability\CommandProvider;
|
||||
use Composer\Plugin\Capable;
|
||||
use Composer\Plugin\CommandEvent;
|
||||
use Composer\Plugin\PluginEvents;
|
||||
use Composer\Plugin\PluginInterface;
|
||||
use Composer\Script\Event;
|
||||
use Composer\Script\ScriptEvents;
|
||||
use Drupal\Composer\Plugin\Scaffold\CommandProvider as ScaffoldCommandProvider;
|
||||
|
||||
/**
|
||||
* Composer plugin for handling drupal scaffold.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class Plugin implements PluginInterface, EventSubscriberInterface, Capable {
|
||||
|
||||
/**
|
||||
* The Composer service.
|
||||
*
|
||||
* @var \Composer\Composer
|
||||
*/
|
||||
protected $composer;
|
||||
|
||||
/**
|
||||
* Composer's I/O service.
|
||||
*
|
||||
* @var \Composer\IO\IOInterface
|
||||
*/
|
||||
protected $io;
|
||||
|
||||
/**
|
||||
* The Composer Scaffold handler.
|
||||
*
|
||||
* @var \Drupal\Composer\Plugin\Scaffold\Handler
|
||||
*/
|
||||
protected $handler;
|
||||
|
||||
/**
|
||||
* Record whether the 'require' command was called.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $requireWasCalled;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function activate(Composer $composer, IOInterface $io) {
|
||||
$this->composer = $composer;
|
||||
$this->io = $io;
|
||||
$this->requireWasCalled = FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deactivate(Composer $composer, IOInterface $io) {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function uninstall(Composer $composer, IOInterface $io) {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCapabilities() {
|
||||
return [CommandProvider::class => ScaffoldCommandProvider::class];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents() {
|
||||
// Important note: We only instantiate our handler on "post" events.
|
||||
return [
|
||||
ScriptEvents::POST_UPDATE_CMD => 'postCmd',
|
||||
ScriptEvents::POST_INSTALL_CMD => 'postCmd',
|
||||
PackageEvents::POST_PACKAGE_INSTALL => 'postPackage',
|
||||
PluginEvents::COMMAND => 'onCommand',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Post command event callback.
|
||||
*
|
||||
* @param \Composer\Script\Event $event
|
||||
* The Composer event.
|
||||
*/
|
||||
public function postCmd(Event $event) {
|
||||
$this->handler()->scaffold();
|
||||
}
|
||||
|
||||
/**
|
||||
* Post package event behavior.
|
||||
*
|
||||
* @param \Composer\Installer\PackageEvent $event
|
||||
* Composer package event sent on install/update/remove.
|
||||
*/
|
||||
public function postPackage(PackageEvent $event) {
|
||||
$this->handler()->onPostPackageEvent($event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre command event callback.
|
||||
*
|
||||
* @param \Composer\Plugin\CommandEvent $event
|
||||
* The Composer command event.
|
||||
*/
|
||||
public function onCommand(CommandEvent $event) {
|
||||
if ($event->getCommandName() == 'require') {
|
||||
if ($this->handler) {
|
||||
throw new \Error('Core Scaffold Plugin handler instantiated too early. See https://www.drupal.org/project/drupal/issues/3104922');
|
||||
}
|
||||
$this->requireWasCalled = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates the handler object upon demand.
|
||||
*
|
||||
* It is dangerous to update a Composer plugin if it loads any classes prior
|
||||
* to the `composer update` operation, and later tries to use them in a
|
||||
* post-update hook.
|
||||
*/
|
||||
protected function handler() {
|
||||
if (!$this->handler) {
|
||||
$this->handler = new Handler($this->composer, $this->io);
|
||||
// On instantiation of our handler, notify it if the 'require' command
|
||||
// was executed.
|
||||
if ($this->requireWasCalled) {
|
||||
$this->handler->requireWasCalled();
|
||||
}
|
||||
}
|
||||
return $this->handler;
|
||||
}
|
||||
|
||||
}
|
||||
24
vendor/drupal/core-composer-scaffold/PostPackageEventListenerInterface.php
vendored
Normal file
24
vendor/drupal/core-composer-scaffold/PostPackageEventListenerInterface.php
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Composer\Plugin\Scaffold;
|
||||
|
||||
use Composer\Installer\PackageEvent;
|
||||
|
||||
/**
|
||||
* Interface for post package event listeners.
|
||||
*
|
||||
* @see \Drupal\Composer\Plugin\Scaffold\Handler::onPostPackageEvent
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
interface PostPackageEventListenerInterface {
|
||||
|
||||
/**
|
||||
* Handles package events during a 'composer require' operation.
|
||||
*
|
||||
* @param \Composer\Installer\PackageEvent $event
|
||||
* Composer package event sent on install/update/remove.
|
||||
*/
|
||||
public function event(PackageEvent $event);
|
||||
|
||||
}
|
||||
522
vendor/drupal/core-composer-scaffold/README.md
vendored
Normal file
522
vendor/drupal/core-composer-scaffold/README.md
vendored
Normal file
@ -0,0 +1,522 @@
|
||||
# Drupal Composer Scaffold
|
||||
|
||||
This project provides a composer plugin making the Drupal core Composer package
|
||||
work correctly in a Composer project.
|
||||
|
||||
This takes care of:
|
||||
- Placing scaffold files (like `index.php`, `update.php`, …) from the
|
||||
`drupal/core` project into their desired location inside the web root. Only
|
||||
individual files may be scaffolded with this plugin.
|
||||
- Writing an autoload.php file to the web root, which includes the Composer
|
||||
autoload.php file.
|
||||
|
||||
The purpose of scaffolding files is to allow Drupal sites to be fully managed by
|
||||
Composer, and still allow individual asset files to be placed in arbitrary
|
||||
locations. The goal of doing this is to enable a properly configured composer
|
||||
template to produce a file layout that exactly matches the file layout of a
|
||||
Drupal 8.7.x and earlier tarball distribution. Other file layouts will also be
|
||||
possible; for example, a project layout very similar to the current
|
||||
[drupal-composer/drupal-project](https://github.com/drupal-composer/drupal-scaffold)
|
||||
template will also be provided. When one of these projects is used, the user
|
||||
should be able to use `composer require` and `composer update` on a Drupal site
|
||||
immediately after extracting the downloaded archive.
|
||||
|
||||
Note that the dependencies of a Drupal site are only able to scaffold files if
|
||||
explicitly granted that right in the top-level composer.json file. See
|
||||
[allowed packages](#allowed-packages), below.
|
||||
|
||||
## Usage
|
||||
|
||||
Drupal Composer Scaffold is used by requiring `drupal/core-composer-scaffold` in your
|
||||
project, and providing configuration settings in the `extra` section of your
|
||||
project's composer.json file. Additional configuration from the composer.json
|
||||
file of your project's dependencies is also consulted in order to scaffold the
|
||||
files a project needs. Additional information may be added to the beginning or
|
||||
end of scaffold files, as is commonly done to `.htaccess` and `robots.txt`
|
||||
files. See [altering scaffold files](#altering-scaffold-files) for more
|
||||
information.
|
||||
|
||||
Typically, the scaffold operations run automatically as needed, e.g. after
|
||||
`composer install`, so it is usually not necessary to do anything different
|
||||
to scaffold a project once the configuration is set up in the project
|
||||
composer.json file, as described below. To scaffold files directly, run:
|
||||
```
|
||||
composer drupal:scaffold
|
||||
```
|
||||
|
||||
### Allowed Packages
|
||||
|
||||
Scaffold files are stored inside of projects that are required from the main
|
||||
project's composer.json file as usual. The scaffolding operation happens after
|
||||
`composer install`, and involves copying or symlinking the desired assets to
|
||||
their destination location. In order to prevent arbitrary dependencies from
|
||||
copying files via the scaffold mechanism, only those projects that are
|
||||
specifically permitted by the top-level project will be used to scaffold files.
|
||||
|
||||
Example: Permit scaffolding from the project `upstream/project`
|
||||
```
|
||||
"name": "my/project",
|
||||
...
|
||||
"extra": {
|
||||
"drupal-scaffold": {
|
||||
"allowed-packages": [
|
||||
"upstream/project"
|
||||
],
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
Allowing a package to scaffold files also permits it to delegate permission to
|
||||
scaffold to any project that it requires itself. This allows a package to
|
||||
organize its scaffold assets as it sees fit. For example, if `upstream/project`
|
||||
stores its assets in a subproject `upstream/assets`, `upstream/assets` would
|
||||
implicitly be allowed to scaffold files.
|
||||
|
||||
It is possible for a project to obtain scaffold files from multiple projects.
|
||||
For example, a Drupal project using a distribution, and installing on a specific
|
||||
web hosting service provider might take its scaffold files from:
|
||||
|
||||
- Drupal core
|
||||
- Its distribution
|
||||
- A project provided by the hosting provider
|
||||
- The project itself
|
||||
|
||||
Each project allowed to scaffold by the top-level project will be used in turn,
|
||||
with projects declared later in the `allowed-packages` list taking precedence
|
||||
over the projects named before. `drupal/core` is implicitly allowed and will be
|
||||
placed at the top of the list. The top-level composer.json itself is also
|
||||
implicitly allowed to scaffold files, and its scaffold files have highest
|
||||
priority.
|
||||
|
||||
### Defining Project Locations
|
||||
|
||||
The top-level project in turn must define where the web root is located. It does
|
||||
so via the `locations` mapping, as shown below:
|
||||
```
|
||||
"name": "my/project",
|
||||
...
|
||||
"extra": {
|
||||
"drupal-scaffold": {
|
||||
"locations": {
|
||||
"web-root": "./docroot"
|
||||
},
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
This makes it possible to configure a project with different file layouts; for
|
||||
example, either the `drupal/drupal` file layout or the
|
||||
`drupal-composer/drupal-project` file layout could be used to set up a project.
|
||||
|
||||
If a web-root is not explicitly defined, then it will default to `.`, the same
|
||||
directory as the composer.json file.
|
||||
|
||||
### Altering Scaffold Files
|
||||
|
||||
Sometimes, a project might wish to use a scaffold file provided by a dependency,
|
||||
but alter it in some way. Two forms of alteration are supported: appending and
|
||||
patching.
|
||||
|
||||
The example below shows a project that appends additional entries onto the end
|
||||
of the `robots.txt` file provided by `drupal/core`:
|
||||
```
|
||||
"name": "my/project",
|
||||
...
|
||||
"extra": {
|
||||
"drupal-scaffold": {
|
||||
"file-mapping": {
|
||||
"[web-root]/robots.txt": {
|
||||
"append": "assets/my-robots-additions.txt",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
It is also possible to prepend to a scaffold file instead of, or in addition to
|
||||
appending by including a "prepend" entry that provides the relative path to the
|
||||
file to prepend to the scaffold file.
|
||||
|
||||
The example below demonstrates the use of the `post-drupal-scaffold-cmd` hook
|
||||
to patch the `.htaccess` file using a patch.
|
||||
```
|
||||
"name": "my/project",
|
||||
...
|
||||
"scripts": {
|
||||
"post-drupal-scaffold-cmd": [
|
||||
"cd docroot && patch -p1 <../patches/htaccess-ssl.patch"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Defining Scaffold Files
|
||||
|
||||
The placement of scaffold assets is under the control of the project that
|
||||
provides them, but the location is always relative to some directory defined by
|
||||
the root project -- usually the web root. For example, the scaffold file
|
||||
`robots.txt` is copied from its source location, `assets/robots.txt` into the
|
||||
web root in the snippet below.
|
||||
```
|
||||
{
|
||||
"name": "drupal/assets",
|
||||
...
|
||||
"extra": {
|
||||
"drupal-scaffold": {
|
||||
"file-mapping": {
|
||||
"[web-root]/robots.txt": "assets/robots.txt",
|
||||
...
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Excluding Scaffold Files
|
||||
|
||||
Sometimes, a project might prefer to entirely replace a scaffold file provided
|
||||
by a dependency, and receive no further updates for it. This can be done by
|
||||
setting the value for the scaffold file to exclude to `false`:
|
||||
```
|
||||
"name": "my/project",
|
||||
...
|
||||
"extra": {
|
||||
"drupal-scaffold": {
|
||||
"file-mapping": {
|
||||
"[web-root]/robots.txt": false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
If possible, use the `append` and `prepend` directives as explained in [altering
|
||||
scaffold files](#altering-scaffold-files), above. Excluding a file means that
|
||||
your project will not get any bug fixes or other updates to files that are
|
||||
modified locally.
|
||||
|
||||
### Overwrite
|
||||
|
||||
By default, scaffold files overwrite whatever content exists at the target
|
||||
location. Sometimes a project may wish to provide the initial contents for a
|
||||
file that will not be changed in subsequent updates. This can be done by setting
|
||||
the `overwrite` flag to `false`, as shown in the example below:
|
||||
```
|
||||
{
|
||||
"name": "service-provider/d8-scaffold-files",
|
||||
"extra": {
|
||||
"drupal-scaffold": {
|
||||
"file-mapping": {
|
||||
"[web-root]/sites/default/settings.php": {
|
||||
"mode": "replace",
|
||||
"path": "assets/sites/default/settings.php",
|
||||
"overwrite": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
Note that the `overwrite` directive is intended to be used by starter kits,
|
||||
service providers, and so on. Individual Drupal sites should exclude the file
|
||||
by setting its value to false instead.
|
||||
|
||||
### Autoload File
|
||||
|
||||
The scaffold tool automatically creates the required `autoload.php` file at the
|
||||
Drupal root as part of the scaffolding operation. This file should not be
|
||||
modified or customized in any way. If it is committed to the repository, though,
|
||||
then the scaffold tool will stop managing it. If the location of the `vendor`
|
||||
directory is changed for any reason, and the `autoload.php` file has been
|
||||
committed to the repository, manually delete it and then run `composer install`
|
||||
to update it.
|
||||
|
||||
## Specifications
|
||||
|
||||
Reference section for the configuration directives for the "drupal-scaffold"
|
||||
section of the "extra" section of a `composer.json` file appear below.
|
||||
|
||||
### allowed-packages
|
||||
|
||||
The `allowed-packages` configuration setting contains an ordered list of package
|
||||
names that will be used during the scaffolding phase.
|
||||
```
|
||||
"allowed-packages": [
|
||||
"example/assets",
|
||||
],
|
||||
```
|
||||
### file-mapping
|
||||
|
||||
The `file-mapping` configuration setting consists of a map from the destination
|
||||
path of the file to scaffold to a set of properties that control how the file
|
||||
should be scaffolded.
|
||||
|
||||
The available properties are as follows:
|
||||
|
||||
- mode: One of "replace", "append" or "skip".
|
||||
- path: The path to the source file to write over the destination file.
|
||||
- prepend: The path to the source file to prepend to the destination file, which
|
||||
must always be a scaffold file provided by some other project.
|
||||
- append: Like `prepend`, but appends content rather than prepends.
|
||||
- overwrite: If `false`, prevents a `replace` from happening if the destination
|
||||
already exists.
|
||||
|
||||
The mode may be inferred from the other properties. If the mode is not
|
||||
specified, then the following defaults will be supplied:
|
||||
|
||||
- replace: Selected if a `path` property is present, or if the entry's value is
|
||||
a string rather than a property set.
|
||||
- append: Selected if a `prepend` or `append` property is present.
|
||||
- skip: Selected if the entry's value is a boolean `false`.
|
||||
|
||||
Examples:
|
||||
```
|
||||
"file-mapping": {
|
||||
"[web-root]/sites/default/default.settings.php": {
|
||||
"mode": "replace",
|
||||
"path": "assets/sites/default/default.settings.php",
|
||||
"overwrite": true
|
||||
},
|
||||
"[web-root]/sites/default/settings.php": {
|
||||
"mode": "replace",
|
||||
"path": "assets/sites/default/settings.php",
|
||||
"overwrite": false
|
||||
},
|
||||
"[web-root]/robots.txt": {
|
||||
"mode": "append",
|
||||
"prepend": "assets/robots-prequel.txt",
|
||||
"append": "assets/robots-append.txt"
|
||||
},
|
||||
"[web-root]/.htaccess": {
|
||||
"mode": "skip",
|
||||
}
|
||||
}
|
||||
```
|
||||
The short-form of the above example would be:
|
||||
```
|
||||
"file-mapping": {
|
||||
"[web-root]/sites/default/default.settings.php": "assets/sites/default/default.settings.php",
|
||||
"[web-root]/sites/default/settings.php": {
|
||||
"path": "assets/sites/default/settings.php",
|
||||
"overwrite": false
|
||||
},
|
||||
"[web-root]/robots.txt": {
|
||||
"prepend": "assets/robots-prequel.txt",
|
||||
"append": "assets/robots-append.txt"
|
||||
},
|
||||
"[web-root]/.htaccess": false
|
||||
}
|
||||
```
|
||||
Note that there is no distinct "prepend" mode; "append" mode is used to both
|
||||
append and prepend to scaffold files. The reason for this is that scaffold file
|
||||
entries are identified in the file-mapping section keyed by their destination
|
||||
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
|
||||
manage `.gitignore` files for files written during the scaffold operation.
|
||||
|
||||
- true: `.gitignore` files will be updated when scaffold files are written.
|
||||
- false: `.gitignore` files will never be modified.
|
||||
- Not set: `.gitignore` files will be updated if the target directory is a local
|
||||
working copy of a git repository, and the `vendor` directory is ignored
|
||||
in that repository.
|
||||
|
||||
### locations
|
||||
|
||||
The `locations` configuration setting contains a list of named locations that
|
||||
may be used in placing scaffold files. The only required location is `web-root`.
|
||||
Other locations may also be defined if desired.
|
||||
```
|
||||
"locations": {
|
||||
"web-root": "./docroot"
|
||||
},
|
||||
```
|
||||
### symlink
|
||||
|
||||
The `symlink` property causes `replace` operations to make a symlink to the
|
||||
source file rather than copying it. This is useful when doing core development,
|
||||
as the symlink files themselves should not be edited. Note that `append`
|
||||
operations override the `symlink` option, to prevent the original scaffold
|
||||
assets from being altered.
|
||||
```
|
||||
"symlink": true,
|
||||
```
|
||||
## Managing Scaffold Files
|
||||
|
||||
Scaffold files should be treated the same way that the `vendor` directory is
|
||||
handled. If you need to commit `vendor` (e.g. in order to deploy your site),
|
||||
then you should also commit your scaffold files. You should not commit your
|
||||
`vendor` directory or scaffold files unless it is necessary.
|
||||
|
||||
If a dependency provides a scaffold file with `overwrite` set to `false`, that
|
||||
file should be committed to your repository.
|
||||
|
||||
By default, `.gitignore` files will be automatically updated if needed when
|
||||
scaffold files are written. See the `gitignore` setting in the Specifications
|
||||
section above.
|
||||
|
||||
## Examples
|
||||
|
||||
Some full-length examples appear below.
|
||||
|
||||
Sample composer.json for a project that relies on packages that use composer-scaffold:
|
||||
```
|
||||
{
|
||||
"name": "my/project",
|
||||
"require": {
|
||||
"drupal/core-composer-scaffold": "*",
|
||||
"composer/installers": "^2.3",
|
||||
"cweagans/composer-patches": "^1.6.5",
|
||||
"drupal/core": "^8.8.x-dev",
|
||||
"service-provider/d8-scaffold-files": "^1"
|
||||
},
|
||||
"config": {
|
||||
"optimize-autoloader": true,
|
||||
"sort-packages": true
|
||||
},
|
||||
"extra": {
|
||||
"drupal-scaffold": {
|
||||
"locations": {
|
||||
"web-root": "./docroot"
|
||||
},
|
||||
"symlink": true,
|
||||
"file-mapping": {
|
||||
"[web-root]/.htaccess": false,
|
||||
"[web-root]/robots.txt": "assets/robots-default.txt"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Sample composer.json for drupal/core, with assets placed in a different project:
|
||||
|
||||
```
|
||||
{
|
||||
"name": "drupal/core",
|
||||
"extra": {
|
||||
"drupal-scaffold": {
|
||||
"allowed-packages": [
|
||||
"drupal/assets",
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Sample composer.json for composer-scaffold files in drupal/assets:
|
||||
|
||||
```
|
||||
{
|
||||
"name": "drupal/assets",
|
||||
"extra": {
|
||||
"drupal-scaffold": {
|
||||
"file-mapping": {
|
||||
"[web-root]/.csslintrc": "assets/.csslintrc",
|
||||
"[web-root]/.editorconfig": "assets/.editorconfig",
|
||||
"[web-root]/.eslintignore": "assets/.eslintignore",
|
||||
"[web-root]/.eslintrc.json": "assets/.eslintrc.json",
|
||||
"[web-root]/.gitattributes": "assets/.gitattributes",
|
||||
"[web-root]/.ht.router.php": "assets/.ht.router.php",
|
||||
"[web-root]/.htaccess": "assets/.htaccess",
|
||||
"[web-root]/sites/default/default.services.yml": "assets/default.services.yml",
|
||||
"[web-root]/sites/default/default.settings.php": "assets/default.settings.php",
|
||||
"[web-root]/sites/example.settings.local.php": "assets/example.settings.local.php",
|
||||
"[web-root]/sites/example.sites.php": "assets/example.sites.php",
|
||||
"[web-root]/index.php": "assets/index.php",
|
||||
"[web-root]/robots.txt": "assets/robots.txt",
|
||||
"[web-root]/update.php": "assets/update.php",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Sample composer.json for a library that implements composer-scaffold:
|
||||
|
||||
```
|
||||
{
|
||||
"name": "service-provider/d8-scaffold-files",
|
||||
"extra": {
|
||||
"drupal-scaffold": {
|
||||
"file-mapping": {
|
||||
"[web-root]/sites/default/settings.php": "assets/sites/default/settings.php"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Append to robots.txt:
|
||||
|
||||
```
|
||||
{
|
||||
"name": "service-provider/d8-scaffold-files",
|
||||
"extra": {
|
||||
"drupal-scaffold": {
|
||||
"file-mapping": {
|
||||
"[web-root]/robots.txt": {
|
||||
"append": "assets/my-robots-additions.txt",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Patch a file after it's copied:
|
||||
|
||||
```
|
||||
"post-drupal-scaffold-cmd": [
|
||||
"cd docroot && patch -p1 <../patches/htaccess-ssl.patch"
|
||||
]
|
||||
```
|
||||
|
||||
## Related Plugins
|
||||
|
||||
### drupal-composer/drupal-scaffold
|
||||
|
||||
Previous versions of Drupal Composer Scaffold (see community project,
|
||||
[drupal-composer/drupal-scaffold](https://github.com/drupal-composer/drupal-project))
|
||||
downloaded each scaffold file directly from its distribution server (e.g.
|
||||
`https://git.drupalcode.org`) to the desired destination directory. This was
|
||||
necessary, because there was no subtree split of the scaffold files available.
|
||||
Copying the scaffold assets from projects already downloaded by Composer is more
|
||||
effective, as downloading and unpacking archive files is more efficient than
|
||||
downloading each scaffold file individually.
|
||||
|
||||
### composer/installers
|
||||
|
||||
The [composer/installers](https://github.com/composer/installers) plugin is
|
||||
similar to this plugin in that it allows dependencies to be installed in
|
||||
locations other than the `vendor` directory. However, Composer and the
|
||||
`composer/installers` plugin have a limitation that one project cannot be moved
|
||||
inside of another project. Therefore, if you use `composer/installers` to place
|
||||
Drupal modules inside the directory `web/modules/contrib`, then you cannot also
|
||||
use `composer/installers` to place files such as `index.php` and `robots.txt`
|
||||
into the `web` directory. The drupal-scaffold plugin was created to work around
|
||||
this limitation.
|
||||
140
vendor/drupal/core-composer-scaffold/ScaffoldFileInfo.php
vendored
Normal file
140
vendor/drupal/core-composer-scaffold/ScaffoldFileInfo.php
vendored
Normal file
@ -0,0 +1,140 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Composer\Plugin\Scaffold;
|
||||
|
||||
use Composer\IO\IOInterface;
|
||||
use Drupal\Composer\Plugin\Scaffold\Operations\OperationInterface;
|
||||
|
||||
/**
|
||||
* Data object that keeps track of one scaffold file.
|
||||
*
|
||||
* Scaffold files are identified primarily by their destination path. Each
|
||||
* scaffold file also has an 'operation' object that controls how the scaffold
|
||||
* file will be placed (e.g. via copy or symlink, or maybe by appending multiple
|
||||
* files together). The operation may have one or more source files.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ScaffoldFileInfo {
|
||||
|
||||
/**
|
||||
* The path to the destination.
|
||||
*
|
||||
* @var \Drupal\Composer\Plugin\Scaffold\ScaffoldFilePath
|
||||
*/
|
||||
protected $destination;
|
||||
|
||||
/**
|
||||
* The operation used to create the destination.
|
||||
*
|
||||
* @var \Drupal\Composer\Plugin\Scaffold\Operations\OperationInterface
|
||||
*/
|
||||
protected $op;
|
||||
|
||||
/**
|
||||
* Constructs a ScaffoldFileInfo object.
|
||||
*
|
||||
* @param \Drupal\Composer\Plugin\Scaffold\ScaffoldFilePath $destination
|
||||
* The full and relative paths to the destination file and the package
|
||||
* defining it.
|
||||
* @param \Drupal\Composer\Plugin\Scaffold\Operations\OperationInterface $op
|
||||
* Operations object that will handle scaffolding operations.
|
||||
*/
|
||||
public function __construct(ScaffoldFilePath $destination, OperationInterface $op) {
|
||||
$this->destination = $destination;
|
||||
$this->op = $op;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Scaffold operation.
|
||||
*
|
||||
* @return \Drupal\Composer\Plugin\Scaffold\Operations\OperationInterface
|
||||
* Operations object that handles scaffolding (copy, make symlink, etc).
|
||||
*/
|
||||
public function op() {
|
||||
return $this->op;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the package name.
|
||||
*
|
||||
* @return string
|
||||
* The name of the package this scaffold file info was collected from.
|
||||
*/
|
||||
public function packageName() {
|
||||
return $this->destination->packageName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the destination.
|
||||
*
|
||||
* @return \Drupal\Composer\Plugin\Scaffold\ScaffoldFilePath
|
||||
* The scaffold path to the destination file.
|
||||
*/
|
||||
public function destination() {
|
||||
return $this->destination;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if this scaffold file has been overridden by another package.
|
||||
*
|
||||
* @param string $providing_package
|
||||
* The name of the package that provides the scaffold file at this location,
|
||||
* as returned by self::findProvidingPackage()
|
||||
*
|
||||
* @return bool
|
||||
* Whether this scaffold file if overridden or removed.
|
||||
*/
|
||||
public function overridden($providing_package) {
|
||||
return $this->packageName() !== $providing_package;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces placeholders in a message.
|
||||
*
|
||||
* @param string $message
|
||||
* Message with placeholders to fill in.
|
||||
* @param array $extra
|
||||
* Additional data to merge with the interpolator.
|
||||
* @param mixed $default
|
||||
* Default value to use for missing placeholders, or FALSE to keep them.
|
||||
*
|
||||
* @return string
|
||||
* Interpolated string with placeholders replaced.
|
||||
*/
|
||||
public function interpolate($message, array $extra = [], $default = FALSE) {
|
||||
$interpolator = $this->destination->getInterpolator();
|
||||
return $interpolator->interpolate($message, $extra, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves a single scaffold file from source to destination.
|
||||
*
|
||||
* @param \Composer\IO\IOInterface $io
|
||||
* The scaffold file to be processed.
|
||||
* @param \Drupal\Composer\Plugin\Scaffold\ScaffoldOptions $options
|
||||
* Assorted operational options, e.g. whether the destination should be a
|
||||
* symlink.
|
||||
*
|
||||
* @return \Drupal\Composer\Plugin\Scaffold\Operations\ScaffoldResult
|
||||
* The scaffold result.
|
||||
*/
|
||||
public function process(IOInterface $io, ScaffoldOptions $options) {
|
||||
return $this->op()->process($this->destination, $io, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns TRUE if the target does not exist or has changed.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the target does not exist or has changed, FALSE otherwise.
|
||||
*/
|
||||
final public function hasChanged() {
|
||||
$path = $this->destination()->fullPath();
|
||||
if (!file_exists($path)) {
|
||||
return TRUE;
|
||||
}
|
||||
return $this->op()->contents() !== file_get_contents($path);
|
||||
}
|
||||
|
||||
}
|
||||
202
vendor/drupal/core-composer-scaffold/ScaffoldFilePath.php
vendored
Normal file
202
vendor/drupal/core-composer-scaffold/ScaffoldFilePath.php
vendored
Normal file
@ -0,0 +1,202 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Composer\Plugin\Scaffold;
|
||||
|
||||
use Composer\Util\Filesystem;
|
||||
|
||||
/**
|
||||
* Manage the path to a file to scaffold.
|
||||
*
|
||||
* Both the relative and full path to the file is maintained so that the shorter
|
||||
* name may be used in progress and error messages, as needed. The name of the
|
||||
* package that provided the file path is also recorded for the same reason.
|
||||
*
|
||||
* ScaffoldFilePaths may be used to represent destination scaffold files, or the
|
||||
* source files used to create them. Static factory methods named
|
||||
* destinationPath and sourcePath, respectively, are provided to create
|
||||
* ScaffoldFilePath objects.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ScaffoldFilePath {
|
||||
|
||||
/**
|
||||
* The type of scaffold file this is,'autoload', 'dest' or 'src'.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type;
|
||||
|
||||
/**
|
||||
* The name of the package containing the file.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $packageName;
|
||||
|
||||
/**
|
||||
* The relative path to the file.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $relativePath;
|
||||
|
||||
/**
|
||||
* The full path to the file.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fullPath;
|
||||
|
||||
/**
|
||||
* ScaffoldFilePath constructor.
|
||||
*
|
||||
* @param string $path_type
|
||||
* The type of scaffold file this is,'autoload', 'dest' or 'src'.
|
||||
* @param string $package_name
|
||||
* The name of the package containing the file.
|
||||
* @param string $rel_path
|
||||
* The relative path to the file.
|
||||
* @param string $full_path
|
||||
* The full path to the file.
|
||||
*/
|
||||
public function __construct($path_type, $package_name, $rel_path, $full_path) {
|
||||
$this->type = $path_type;
|
||||
$this->packageName = $package_name;
|
||||
$this->relativePath = $rel_path;
|
||||
$this->fullPath = $full_path;
|
||||
|
||||
// Ensure that the full path really is a full path. We do not use
|
||||
// 'realpath' here because the file specified by the full path might
|
||||
// not exist yet.
|
||||
$fs = new Filesystem();
|
||||
if (!$fs->isAbsolutePath($this->fullPath)) {
|
||||
$this->fullPath = getcwd() . '/' . $this->fullPath;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the package this source file was pulled from.
|
||||
*
|
||||
* @return string
|
||||
* Name of package.
|
||||
*/
|
||||
public function packageName() {
|
||||
return $this->packageName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the relative path to the source file (best to use in messages).
|
||||
*
|
||||
* @return string
|
||||
* Relative path to file.
|
||||
*/
|
||||
public function relativePath() {
|
||||
return $this->relativePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the full path to the source file.
|
||||
*
|
||||
* @return string
|
||||
* Full path to file.
|
||||
*/
|
||||
public function fullPath() {
|
||||
return $this->fullPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the relative source path into an absolute path.
|
||||
*
|
||||
* The path returned will be relative to the package installation location.
|
||||
*
|
||||
* @param string $package_name
|
||||
* The name of the package containing the source file. Only used for error
|
||||
* messages.
|
||||
* @param string $package_path
|
||||
* The installation path of the package containing the source file.
|
||||
* @param string $destination
|
||||
* Destination location provided as a relative path. Only used for error
|
||||
* messages.
|
||||
* @param string $source
|
||||
* Source location provided as a relative path.
|
||||
*
|
||||
* @return self
|
||||
* Object wrapping the relative and absolute path to the source file.
|
||||
*/
|
||||
public static function sourcePath($package_name, $package_path, $destination, $source) {
|
||||
// Complain if there is no source path.
|
||||
if (empty($source)) {
|
||||
throw new \RuntimeException("No scaffold file path given for {$destination} in package {$package_name}.");
|
||||
}
|
||||
// Calculate the full path to the source scaffold file.
|
||||
$source_full_path = $package_path . '/' . $source;
|
||||
if (!file_exists($source_full_path)) {
|
||||
throw new \RuntimeException("Scaffold file {$source} not found in package {$package_name}.");
|
||||
}
|
||||
if (is_dir($source_full_path)) {
|
||||
throw new \RuntimeException("Scaffold file {$source} in package {$package_name} is a directory; only files may be scaffolded.");
|
||||
}
|
||||
return new self('src', $package_name, $source, $source_full_path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the relative destination path into an absolute path.
|
||||
*
|
||||
* Any placeholders in the destination path, e.g. '[web-root]', will be
|
||||
* replaced using the provided location replacements interpolator.
|
||||
*
|
||||
* @param string $package_name
|
||||
* The name of the package defining the destination path.
|
||||
* @param string $destination
|
||||
* The relative path to the destination file being scaffolded.
|
||||
* @param \Drupal\Composer\Plugin\Scaffold\Interpolator $location_replacements
|
||||
* Interpolator that includes the [web-root] and any other available
|
||||
* placeholder replacements.
|
||||
*
|
||||
* @return self
|
||||
* Object wrapping the relative and absolute path to the destination file.
|
||||
*/
|
||||
public static function destinationPath($package_name, $destination, Interpolator $location_replacements) {
|
||||
$dest_full_path = $location_replacements->interpolate($destination);
|
||||
return new self('dest', $package_name, $destination, $dest_full_path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds data about the relative and full path to the provided interpolator.
|
||||
*
|
||||
* @param \Drupal\Composer\Plugin\Scaffold\Interpolator $interpolator
|
||||
* Interpolator to add data to.
|
||||
* @param string $name_prefix
|
||||
* (optional) Prefix to add before -rel-path and -full-path item names.
|
||||
* Defaults to path type provided when constructing this object.
|
||||
*/
|
||||
public function addInterpolationData(Interpolator $interpolator, $name_prefix = '') {
|
||||
if (empty($name_prefix)) {
|
||||
$name_prefix = $this->type;
|
||||
}
|
||||
$data = [
|
||||
'package-name' => $this->packageName(),
|
||||
"{$name_prefix}-rel-path" => $this->relativePath(),
|
||||
"{$name_prefix}-full-path" => $this->fullPath(),
|
||||
];
|
||||
$interpolator->addData($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpolate a string using the data from this scaffold file info.
|
||||
*
|
||||
* @param string $name_prefix
|
||||
* (optional) Prefix to add before -rel-path and -full-path item names.
|
||||
* Defaults to path type provided when constructing this object.
|
||||
*
|
||||
* @return \Drupal\Composer\Plugin\Scaffold\Interpolator
|
||||
* An interpolator for making string replacements.
|
||||
*/
|
||||
public function getInterpolator($name_prefix = '') {
|
||||
$interpolator = new Interpolator();
|
||||
$this->addInterpolationData($interpolator, $name_prefix);
|
||||
return $interpolator;
|
||||
}
|
||||
|
||||
}
|
||||
203
vendor/drupal/core-composer-scaffold/ScaffoldOptions.php
vendored
Normal file
203
vendor/drupal/core-composer-scaffold/ScaffoldOptions.php
vendored
Normal file
@ -0,0 +1,203 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Composer\Plugin\Scaffold;
|
||||
|
||||
/**
|
||||
* Per-project options from the 'extras' section of the composer.json file.
|
||||
*
|
||||
* Projects that describe scaffold files do so via their scaffold options. This
|
||||
* data is pulled from the 'drupal-scaffold' portion of the extras section of
|
||||
* the project data.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ScaffoldOptions {
|
||||
|
||||
/**
|
||||
* The raw data from the 'extras' section of the top-level composer.json file.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $options = [];
|
||||
|
||||
/**
|
||||
* ScaffoldOptions constructor.
|
||||
*
|
||||
* @param array $options
|
||||
* The scaffold options taken from the 'drupal-scaffold' section.
|
||||
*/
|
||||
protected function __construct(array $options) {
|
||||
$this->options = $options + [
|
||||
"allowed-packages" => [],
|
||||
"locations" => [],
|
||||
"symlink" => FALSE,
|
||||
"file-mapping" => [],
|
||||
];
|
||||
|
||||
// Define any default locations.
|
||||
$this->options['locations'] += [
|
||||
'project-root' => '.',
|
||||
'web-root' => '.',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the provided 'extras' section has scaffold options.
|
||||
*
|
||||
* @param array $extras
|
||||
* The contents of the 'extras' section.
|
||||
*
|
||||
* @return bool
|
||||
* True if scaffold options have been declared
|
||||
*/
|
||||
public static function hasOptions(array $extras) {
|
||||
return array_key_exists('drupal-scaffold', $extras);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a scaffold options object.
|
||||
*
|
||||
* @param array $extras
|
||||
* The contents of the 'extras' section.
|
||||
*
|
||||
* @return self
|
||||
* The scaffold options object representing the provided scaffold options
|
||||
*/
|
||||
public static function create(array $extras) {
|
||||
$options = static::hasOptions($extras) ? $extras['drupal-scaffold'] : [];
|
||||
return new self($options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new scaffold options object with some values overridden.
|
||||
*
|
||||
* @param array $options
|
||||
* Override values.
|
||||
*
|
||||
* @return self
|
||||
* The scaffold options object representing the provided scaffold options
|
||||
*/
|
||||
protected function override(array $options) {
|
||||
return new self($options + $this->options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new scaffold options object with an overridden 'symlink' value.
|
||||
*
|
||||
* @param bool $symlink
|
||||
* Whether symlinking should be enabled or not.
|
||||
*
|
||||
* @return self
|
||||
* The scaffold options object representing the provided scaffold options
|
||||
*/
|
||||
public function overrideSymlink($symlink) {
|
||||
return $this->override(['symlink' => $symlink]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether any allowed packages were defined.
|
||||
*
|
||||
* @return bool
|
||||
* Whether there are allowed packages
|
||||
*/
|
||||
public function hasAllowedPackages() {
|
||||
return !empty($this->allowedPackages());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets allowed packages from these options.
|
||||
*
|
||||
* @return array
|
||||
* The list of allowed packages
|
||||
*/
|
||||
public function allowedPackages() {
|
||||
return $this->options['allowed-packages'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the location mapping table, e.g. 'webroot' => './'.
|
||||
*
|
||||
* @return array
|
||||
* A map of name : location values
|
||||
*/
|
||||
public function locations() {
|
||||
return $this->options['locations'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a given named location is defined.
|
||||
*
|
||||
* @param string $name
|
||||
* The location name to search for.
|
||||
*
|
||||
* @return bool
|
||||
* True if the specified named location exist.
|
||||
*/
|
||||
protected function hasLocation($name) {
|
||||
return array_key_exists($name, $this->locations());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a specific named location.
|
||||
*
|
||||
* @param string $name
|
||||
* The name of the location to fetch.
|
||||
*
|
||||
* @return string
|
||||
* The value of the provided named location
|
||||
*/
|
||||
public function getLocation($name) {
|
||||
return $this->hasLocation($name) ? $this->locations()[$name] : FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if symlink mode is set.
|
||||
*
|
||||
* @return bool
|
||||
* Whether or not 'symlink' mode
|
||||
*/
|
||||
public function symlink() {
|
||||
return $this->options['symlink'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if there are file mappings.
|
||||
*
|
||||
* @return bool
|
||||
* Whether or not the scaffold options contain any file mappings
|
||||
*/
|
||||
public function hasFileMapping() {
|
||||
return !empty($this->fileMapping());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the actual file mappings.
|
||||
*
|
||||
* @return array
|
||||
* File mappings for just this config type.
|
||||
*/
|
||||
public function fileMapping() {
|
||||
return $this->options['file-mapping'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if there is defined a value for the 'gitignore' option.
|
||||
*
|
||||
* @return bool
|
||||
* Whether or not there is a 'gitignore' option setting
|
||||
*/
|
||||
public function hasGitIgnore() {
|
||||
return isset($this->options['gitignore']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the 'gitignore' option.
|
||||
*
|
||||
* @return bool
|
||||
* The 'gitignore' option, or TRUE if undefined.
|
||||
*/
|
||||
public function gitIgnore() {
|
||||
return $this->hasGitIgnore() ? $this->options['gitignore'] : TRUE;
|
||||
}
|
||||
|
||||
}
|
||||
18
vendor/drupal/core-composer-scaffold/TESTING.txt
vendored
Normal file
18
vendor/drupal/core-composer-scaffold/TESTING.txt
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
HOW-TO: Test this Drupal composer plugin
|
||||
|
||||
In order to test this plugin, you'll need to get the entire Drupal repo and
|
||||
run the tests there.
|
||||
|
||||
You'll find the tests under core/tests/Drupal/Tests/Composer/Plugin.
|
||||
|
||||
You can get the full Drupal repo here:
|
||||
https://www.drupal.org/project/drupal/git-instructions
|
||||
|
||||
You can find more information about running PHPUnit tests with Drupal here:
|
||||
https://www.drupal.org/node/2116263
|
||||
|
||||
Each component in the Drupal\Composer\Plugin namespace has its own annotated test
|
||||
group. You can use this group to run only the tests for this component. Like
|
||||
this:
|
||||
|
||||
$ ./vendor/bin/phpunit -c core --group Scaffold
|
||||
32
vendor/drupal/core-composer-scaffold/composer.json
vendored
Normal file
32
vendor/drupal/core-composer-scaffold/composer.json
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "drupal/core-composer-scaffold",
|
||||
"description": "A flexible Composer project scaffold builder.",
|
||||
"type": "composer-plugin",
|
||||
"keywords": ["drupal"],
|
||||
"homepage": "https://www.drupal.org/project/drupal",
|
||||
"license": "GPL-2.0-or-later",
|
||||
"require": {
|
||||
"composer-plugin-api": "^2",
|
||||
"php": ">=7.3.0"
|
||||
},
|
||||
"conflict": {
|
||||
"drupal-composer/drupal-scaffold": "*"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Drupal\\Composer\\Plugin\\Scaffold\\": ""
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"class": "Drupal\\Composer\\Plugin\\Scaffold\\Plugin",
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
},
|
||||
"require-dev": {
|
||||
"composer/composer": "^1.8@stable"
|
||||
}
|
||||
}
|
||||
339
vendor/drupal/core-project-message/LICENSE.txt
vendored
Normal file
339
vendor/drupal/core-project-message/LICENSE.txt
vendored
Normal file
@ -0,0 +1,339 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
||||
117
vendor/drupal/core-project-message/Message.php
vendored
Normal file
117
vendor/drupal/core-project-message/Message.php
vendored
Normal file
@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Composer\Plugin\ProjectMessage;
|
||||
|
||||
use Composer\Package\RootPackageInterface;
|
||||
|
||||
/**
|
||||
* Determine configuration.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class Message {
|
||||
|
||||
/**
|
||||
* The root package.
|
||||
*
|
||||
* @var \Composer\Package\RootPackageInterface
|
||||
*/
|
||||
protected $rootPackage;
|
||||
|
||||
/**
|
||||
* The name of the event.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $eventName;
|
||||
|
||||
/**
|
||||
* The message to display.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $messageText = [];
|
||||
|
||||
/**
|
||||
* Construct a Config object.
|
||||
*
|
||||
* @param \Composer\Package\RootPackageInterface $root_package
|
||||
* Composer package object for the root package.
|
||||
* @param string $event_name
|
||||
* The event name.
|
||||
*/
|
||||
public function __construct(RootPackageInterface $root_package, $event_name) {
|
||||
$this->rootPackage = $root_package;
|
||||
$this->eventName = $event_name;
|
||||
}
|
||||
|
||||
public function getText() {
|
||||
if ($this->messageText) {
|
||||
return $this->messageText;
|
||||
}
|
||||
$package_config = $this->rootPackage->getExtra();
|
||||
$file = $this->eventName . '-message.txt';
|
||||
if ($config_file = $package_config['drupal-core-project-message'][$this->eventName . '-file'] ?? FALSE) {
|
||||
$file = $config_file;
|
||||
}
|
||||
|
||||
$message = $package_config['drupal-core-project-message'][$this->eventName . '-message'] ?? [];
|
||||
|
||||
if ($message) {
|
||||
$this->messageText = $message;
|
||||
}
|
||||
else {
|
||||
$this->messageText = $this->getMessageFromFile($file);
|
||||
}
|
||||
|
||||
// Include structured support info from composer.json.
|
||||
if ($config_keys = $package_config['drupal-core-project-message']['include-keys'] ?? FALSE) {
|
||||
foreach ($config_keys as $config_key) {
|
||||
switch ($config_key) {
|
||||
case 'name':
|
||||
if ($homepage = $this->rootPackage->getName()) {
|
||||
$this->messageText[] = ' * Name: ' . $homepage;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'description':
|
||||
if ($homepage = $this->rootPackage->getDescription()) {
|
||||
$this->messageText[] = ' * Description: ' . $homepage;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'homepage':
|
||||
if ($homepage = $this->rootPackage->getHomepage()) {
|
||||
$this->messageText[] = ' * Homepage: ' . $homepage;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'support':
|
||||
if ($support = $this->rootPackage->getSupport()) {
|
||||
$this->messageText[] = ' * Support:';
|
||||
foreach ($support as $support_key => $support_value) {
|
||||
$this->messageText[] = ' * ' . $support_key . ': ' . $support_value;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->messageText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the message file as an array.
|
||||
*
|
||||
* @param string $file
|
||||
* The file to read. Relative paths are relative to the project directory.
|
||||
*
|
||||
* @return string[]
|
||||
* The message text.
|
||||
*/
|
||||
protected function getMessageFromFile($file) {
|
||||
return file_exists($file) ? file($file, FILE_IGNORE_NEW_LINES) : [];
|
||||
}
|
||||
|
||||
}
|
||||
77
vendor/drupal/core-project-message/MessagePlugin.php
vendored
Normal file
77
vendor/drupal/core-project-message/MessagePlugin.php
vendored
Normal file
@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Composer\Plugin\ProjectMessage;
|
||||
|
||||
use Composer\Composer;
|
||||
use Composer\EventDispatcher\EventSubscriberInterface;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Plugin\PluginInterface;
|
||||
use Composer\Script\Event;
|
||||
use Composer\Script\ScriptEvents;
|
||||
|
||||
/**
|
||||
* A Composer plugin to display a message after creating a project.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class MessagePlugin implements PluginInterface, EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* Composer object.
|
||||
*
|
||||
* @var \Composer\Composer
|
||||
*/
|
||||
protected $composer;
|
||||
|
||||
/**
|
||||
* IO object.
|
||||
*
|
||||
* @var \Composer\IO\IOInterface
|
||||
*/
|
||||
protected $io;
|
||||
|
||||
/**
|
||||
* Configuration.
|
||||
*
|
||||
* @var \Drupal\Composer\Plugin\VendorHardening\Config
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function activate(Composer $composer, IOInterface $io) {
|
||||
$this->composer = $composer;
|
||||
$this->io = $io;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deactivate(Composer $composer, IOInterface $io) {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function uninstall(Composer $composer, IOInterface $io) {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents() {
|
||||
return [
|
||||
ScriptEvents::POST_CREATE_PROJECT_CMD => 'displayPostCreateMessage',
|
||||
ScriptEvents::POST_INSTALL_CMD => 'displayPostCreateMessage',
|
||||
];
|
||||
}
|
||||
|
||||
public function displayPostCreateMessage(Event $event) {
|
||||
$message = new Message($this->composer->getPackage(), $event->getName());
|
||||
if ($message = $message->getText()) {
|
||||
$this->io->write($message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
101
vendor/drupal/core-project-message/README.md
vendored
Normal file
101
vendor/drupal/core-project-message/README.md
vendored
Normal file
@ -0,0 +1,101 @@
|
||||
The Drupal Project Message Plugin
|
||||
=================================
|
||||
|
||||
Thanks for using this Drupal component.
|
||||
|
||||
You can participate in its development on Drupal.org, through our issue system:
|
||||
https://www.drupal.org/project/issues/drupal
|
||||
|
||||
You can get the full Drupal repo here:
|
||||
https://www.drupal.org/project/drupal/git-instructions
|
||||
|
||||
You can browse the full Drupal repo here:
|
||||
https://git.drupalcode.org/project/drupal
|
||||
|
||||
What does it do?
|
||||
----------------
|
||||
|
||||
This Composer plugin displays a configurable message after Composer installation
|
||||
processes have finished.
|
||||
|
||||
This is ideal for a 'next steps' type prompt to help get the user oriented.
|
||||
|
||||
Currently only two Composer events are supported:
|
||||
- post-create-project-cmd, when a `composer create-project` command has
|
||||
finished.
|
||||
- post-install-cmd, when a `composer install` command has finished.
|
||||
|
||||
How do I set it up?
|
||||
-------------------
|
||||
|
||||
Require this Composer plugin in your project template composer.json file:
|
||||
|
||||
"require": {
|
||||
"drupal/core-project-message": "^8.8"
|
||||
}
|
||||
|
||||
### Configuration
|
||||
|
||||
There are three ways to configure this plugin to output information:
|
||||
- Using a text file.
|
||||
- Using composer.json schema keys.
|
||||
- Embedding the information in the extra section of the composer.json file.
|
||||
|
||||
### Using a text file
|
||||
|
||||
By default, the plugin will respond to `post-install-cmd` or
|
||||
`post-create-project-cmd` Composer events by looking for a similarly-named file
|
||||
in the root of the project. For instance, if the user issues a `composer
|
||||
create-project` command, when that command is finished, the plugin will look for
|
||||
a file named `post-create-project-cmd-message.txt` and then display it on the
|
||||
command line.
|
||||
|
||||
The file should be plain text, with markup suitable for Symfony's
|
||||
`OutputInterface::writeln()` method. See documentation here:
|
||||
https://symfony.com/doc/3.4/console/coloring.html
|
||||
|
||||
You can also configure your own file(s), using the `extra` section of your
|
||||
composer.json file:
|
||||
|
||||
"extra": {
|
||||
"drupal-core-project-message": {
|
||||
"post-create-project-cmd-file": "bespoke/special_file.txt"
|
||||
}
|
||||
}
|
||||
|
||||
### Using composer.json schema keys
|
||||
|
||||
You can tell the plugin to output the structured support information from the
|
||||
composer.json file by telling it the keys you wish to display.
|
||||
|
||||
Currently, only `name`, `description`, `homepage` and `support` are supported.
|
||||
|
||||
"extra": {
|
||||
"drupal-core-project-message": {
|
||||
"include-keys": ["homepage", "support"],
|
||||
}
|
||||
}
|
||||
|
||||
Then you can include this information in your composer.json file, which you
|
||||
should probably be doing anyway.
|
||||
|
||||
### Embedding the information in the extra section
|
||||
|
||||
You can specify text directly within the `extra` section by using the
|
||||
`[event-name]-message` key. This message should be an array, with one string for
|
||||
each line:
|
||||
|
||||
"extra": {
|
||||
"drupal-core-project-message": {
|
||||
"post-create-project-cmd-message": [
|
||||
"Thanks for installing this project.",
|
||||
"Read our documentation here: http://example.com/docs"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
These strings should be plain text, with markup suitable for Symfony's
|
||||
`OutputInterface::writeln()` method. See documentation here:
|
||||
https://symfony.com/doc/3.4/console/coloring.html
|
||||
|
||||
The `-message` section will always override `-file` for a given event.
|
||||
18
vendor/drupal/core-project-message/TESTING.txt
vendored
Normal file
18
vendor/drupal/core-project-message/TESTING.txt
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
HOW-TO: Test this Drupal component
|
||||
|
||||
In order to test this component, you'll need to get the entire Drupal repo and
|
||||
run the tests there.
|
||||
|
||||
You'll find the tests under core/tests/Drupal/Tests/Plugin.
|
||||
|
||||
You can get the full Drupal repo here:
|
||||
https://www.drupal.org/project/drupal/git-instructions
|
||||
|
||||
You can find more information about running PHPUnit tests with Drupal here:
|
||||
https://www.drupal.org/node/2116263
|
||||
|
||||
Each component in the Drupal\Composer namespace has its own annotated test
|
||||
group. You can use this group to run only the tests for this component. Like
|
||||
this:
|
||||
|
||||
$ ./vendor/bin/phpunit -c core --group ProjectMessage
|
||||
20
vendor/drupal/core-project-message/composer.json
vendored
Normal file
20
vendor/drupal/core-project-message/composer.json
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "drupal/core-project-message",
|
||||
"description": "Adds a message after Composer installation.",
|
||||
"keywords": ["drupal"],
|
||||
"homepage": "https://www.drupal.org/project/drupal",
|
||||
"license": "GPL-2.0-or-later",
|
||||
"type": "composer-plugin",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Drupal\\Composer\\Plugin\\ProjectMessage\\": "."
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"class": "Drupal\\Composer\\Plugin\\ProjectMessage\\MessagePlugin"
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.3.0",
|
||||
"composer-plugin-api": "^2"
|
||||
}
|
||||
}
|
||||
21
vendor/drupal/core-recipe-unpack/CommandProvider.php
vendored
Normal file
21
vendor/drupal/core-recipe-unpack/CommandProvider.php
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Composer\Plugin\RecipeUnpack;
|
||||
|
||||
use Composer\Plugin\Capability\CommandProvider as CommandProviderCapability;
|
||||
|
||||
/**
|
||||
* List of all commands provided by this package.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class CommandProvider implements CommandProviderCapability {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCommands(): array {
|
||||
return [new UnpackCommand()];
|
||||
}
|
||||
|
||||
}
|
||||
170
vendor/drupal/core-recipe-unpack/Plugin.php
vendored
Normal file
170
vendor/drupal/core-recipe-unpack/Plugin.php
vendored
Normal file
@ -0,0 +1,170 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Composer\Plugin\RecipeUnpack;
|
||||
|
||||
use Composer\Command\RequireCommand;
|
||||
use Composer\Composer;
|
||||
use Composer\EventDispatcher\EventSubscriberInterface;
|
||||
use Composer\Installer;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Package\PackageInterface;
|
||||
use Composer\Plugin\Capability\CommandProvider;
|
||||
use Composer\Plugin\Capable;
|
||||
use Composer\Plugin\PluginInterface;
|
||||
use Composer\Script\Event;
|
||||
use Composer\Script\ScriptEvents;
|
||||
use Drupal\Composer\Plugin\RecipeUnpack\CommandProvider as UnpackCommandProvider;
|
||||
|
||||
/**
|
||||
* Composer plugin for handling dependency unpacking.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class Plugin implements PluginInterface, EventSubscriberInterface, Capable {
|
||||
|
||||
/**
|
||||
* The composer package type of Drupal recipes.
|
||||
*/
|
||||
public const string RECIPE_PACKAGE_TYPE = 'drupal-recipe';
|
||||
|
||||
/**
|
||||
* The handler for dependency unpacking.
|
||||
*/
|
||||
private UnpackManager $manager;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCapabilities(): array {
|
||||
return [CommandProvider::class => UnpackCommandProvider::class];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function activate(Composer $composer, IOInterface $io): void {
|
||||
$this->manager = new UnpackManager($composer, $io);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deactivate(Composer $composer, IOInterface $io): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function uninstall(Composer $composer, IOInterface $io): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents(): array {
|
||||
return [
|
||||
ScriptEvents::POST_UPDATE_CMD => 'unpackOnRequire',
|
||||
ScriptEvents::POST_CREATE_PROJECT_CMD => 'unpackOnCreateProject',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Post update command event callback.
|
||||
*/
|
||||
public function unpackOnRequire(Event $event): void {
|
||||
if (!$this->manager->unpackOptions->options['on-require']) {
|
||||
return;
|
||||
}
|
||||
|
||||
// @todo https://www.drupal.org/project/drupal/issues/3523269 Use Composer
|
||||
// API once it exists.
|
||||
$backtrace = debug_backtrace();
|
||||
$composer = $event->getComposer();
|
||||
foreach ($backtrace as $trace) {
|
||||
if (isset($trace['object']) && $trace['object'] instanceof Installer) {
|
||||
$installer = $trace['object'];
|
||||
|
||||
// Get the list of packages being required. This code is largely copied
|
||||
// from https://github.com/symfony/flex/blob/2.x/src/Flex.php#L218.
|
||||
$updateAllowList = \Closure::bind(function () {
|
||||
return $this->updateAllowList ?? [];
|
||||
}, $installer, $installer)();
|
||||
|
||||
// Determine if the --no-install flag has been passed to require.
|
||||
$isInstalling = \Closure::bind(function () {
|
||||
return $this->install;
|
||||
}, $installer, $installer)();
|
||||
}
|
||||
|
||||
// If the command is a require command, populate the list of recipes to
|
||||
// unpack.
|
||||
if (isset($trace['object']) && $trace['object'] instanceof RequireCommand && isset($installer, $updateAllowList, $isInstalling)) {
|
||||
// Determines if a message has been sent about require-dev and recipes.
|
||||
$devRecipeWarningEmitted = FALSE;
|
||||
$unpackCollection = new UnpackCollection();
|
||||
|
||||
foreach ($updateAllowList as $package_name) {
|
||||
$packages = $composer->getRepositoryManager()->getLocalRepository()->findPackages($package_name);
|
||||
$package = reset($packages);
|
||||
|
||||
if (!$package instanceof PackageInterface) {
|
||||
if (!$isInstalling) {
|
||||
$event->getIO()->write('Recipes are not unpacked when the --no-install option is used.', verbosity: IOInterface::VERBOSE);
|
||||
return;
|
||||
}
|
||||
$event->getIO()->error(sprintf('%s does not resolve to a package.', $package_name));
|
||||
return;
|
||||
}
|
||||
|
||||
// Only recipes are supported.
|
||||
if ($package->getType() === self::RECIPE_PACKAGE_TYPE) {
|
||||
if ($this->manager->unpackOptions->isIgnored($package)) {
|
||||
$event->getIO()->write(sprintf('<info>%s</info> not unpacked because it is ignored.', $package_name), verbosity: IOInterface::VERBOSE);
|
||||
}
|
||||
elseif (UnpackManager::isDevRequirement($package)) {
|
||||
if (!$devRecipeWarningEmitted) {
|
||||
$event->getIO()->write('<info>Recipes required as a development dependency are not automatically unpacked.</info>');
|
||||
$devRecipeWarningEmitted = TRUE;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$unpackCollection->add($package);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Unpack any recipes that have been added to the collection.
|
||||
$this->manager->unpack($unpackCollection);
|
||||
// The trace has been processed far enough and the $updateAllowList has
|
||||
// been used.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Post create-project command event callback.
|
||||
*/
|
||||
public function unpackOnCreateProject(Event $event): void {
|
||||
$composer = $event->getComposer();
|
||||
$unpackCollection = new UnpackCollection();
|
||||
foreach ($composer->getRepositoryManager()->getLocalRepository()->getPackages() as $package) {
|
||||
// Only recipes are supported.
|
||||
if ($package->getType() === self::RECIPE_PACKAGE_TYPE) {
|
||||
if ($this->manager->unpackOptions->isIgnored($package)) {
|
||||
$event->getIO()->write(sprintf('<info>%s</info> not unpacked because it is ignored.', $package->getName()), verbosity: IOInterface::VERBOSE);
|
||||
}
|
||||
elseif (UnpackManager::isDevRequirement($package)) {
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
$unpackCollection->add($package);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Unpack any recipes that have been registered.
|
||||
$this->manager->unpack($unpackCollection);
|
||||
}
|
||||
|
||||
}
|
||||
256
vendor/drupal/core-recipe-unpack/README.md
vendored
Normal file
256
vendor/drupal/core-recipe-unpack/README.md
vendored
Normal file
@ -0,0 +1,256 @@
|
||||
# Drupal Recipe Unpack Plugin
|
||||
|
||||
Thanks for using this Drupal component.
|
||||
|
||||
You can participate in its development on Drupal.org, through our issue system:
|
||||
https://www.drupal.org/project/issues/drupal
|
||||
|
||||
You can get the full Drupal repo here:
|
||||
https://www.drupal.org/project/drupal/git-instructions
|
||||
|
||||
You can browse the full Drupal repo here:
|
||||
https://git.drupalcode.org/project/drupal
|
||||
|
||||
## Overview
|
||||
|
||||
The Recipe Unpacking system is a Composer plugin that manages "drupal-recipe"
|
||||
packages. Recipes are special Composer packages designed to bootstrap Drupal
|
||||
projects with necessary dependencies. When a recipe is installed, this plugin
|
||||
"unpacks" it by moving the recipe's dependencies directly into your project's
|
||||
root `composer.json`, and removes the recipe as a project dependency.
|
||||
|
||||
## Key Concepts
|
||||
|
||||
### What is a Recipe?
|
||||
|
||||
A recipe is a Composer package with type `drupal-recipe` that contains a curated
|
||||
set of dependencies, configuration and content but no code of its own. Recipes
|
||||
are meant to be "unpacked" and "applied" rather than remain as runtime
|
||||
dependencies.
|
||||
|
||||
### What is Unpacking?
|
||||
|
||||
Unpacking is the process where:
|
||||
|
||||
1. A recipe's dependencies are added to your project's root `composer.json`
|
||||
2. The recipe itself is removed from your dependencies
|
||||
3. The `composer.lock` and vendor installation files are updated accordingly
|
||||
4. The recipe will remain in the project's recipes folder so it can be applied
|
||||
|
||||
## Commands
|
||||
|
||||
### `drupal:recipe-unpack`
|
||||
|
||||
Unpack a recipe package that's already required in your project.
|
||||
|
||||
```bash
|
||||
composer drupal:recipe-unpack drupal/example_recipe
|
||||
```
|
||||
|
||||
Unpack all recipes that are required in your project.
|
||||
|
||||
```bash
|
||||
composer drupal:recipe-unpack
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
This command doesn't take additional options.
|
||||
|
||||
## Automatic Unpacking
|
||||
|
||||
### After `composer require`
|
||||
|
||||
By default, recipes are automatically unpacked after running `composer require`
|
||||
for a recipe package:
|
||||
|
||||
```bash
|
||||
composer require drupal/example_recipe
|
||||
```
|
||||
|
||||
This will:
|
||||
1. Download the recipe and its dependencies
|
||||
2. Add the recipe's dependencies to your project's root `composer.json`
|
||||
3. Remove the recipe itself from your dependencies
|
||||
4. Update your `composer.lock` file
|
||||
|
||||
### After `composer create-project`
|
||||
|
||||
Recipes are always automatically unpacked when creating a new project from a
|
||||
template that requires this plugin:
|
||||
|
||||
```bash
|
||||
composer create-project drupal/recommended-project my-project
|
||||
```
|
||||
|
||||
Any recipes included in the project template will be unpacked during
|
||||
installation, as long as the plugin is enabled.
|
||||
|
||||
## Configuration
|
||||
|
||||
Configuration options are set in the `extra` section of your `composer.json`
|
||||
file:
|
||||
|
||||
```json
|
||||
{
|
||||
"extra": {
|
||||
"drupal-recipe-unpack": {
|
||||
"ignore": ["drupal/recipe_to_ignore"],
|
||||
"on-require": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Available Options
|
||||
|
||||
| Option | Type | Default | Description |
|
||||
|--------|------|---------|-------------|
|
||||
| `ignore` | array | `[]` | List of recipe packages to exclude from unpacking |
|
||||
| `on-require` | boolean | `true` | Automatically unpack recipes when required by `composer require` |
|
||||
|
||||
## How Recipe Unpacking Works
|
||||
|
||||
1. The system identifies packages of type `drupal-recipe` during installation
|
||||
2. For each recipe not in the ignore list, it:
|
||||
- Extracts its dependencies
|
||||
- Adds them to the root `composer.json`
|
||||
- Recursively processes any dependencies that are also recipes
|
||||
- Removes the recipe and any dependencies that are also recipes from the root
|
||||
`composer.json`
|
||||
3. Updates all necessary Composer files:
|
||||
- `composer.json`
|
||||
- `composer.lock`
|
||||
- `vendor/composer/installed.json`
|
||||
- `vendor/composer/installed.php`
|
||||
|
||||
## Cases Where Recipes Will Not Be Unpacked
|
||||
|
||||
Recipes will **not** be unpacked in the following scenarios:
|
||||
|
||||
1. **Explicit Ignore List**: If the recipe is listed in the `ignore` array in
|
||||
your `extra.drupal-recipe-unpack` configuration
|
||||
```json
|
||||
{
|
||||
"extra": {
|
||||
"drupal-recipe-unpack": {
|
||||
"ignore": ["drupal/recipe_name"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. **Disabled Automatic Unpacking**: If `on-require` is set to `false` in your
|
||||
`extra.drupal-recipe-unpack` configuration
|
||||
```json
|
||||
{
|
||||
"extra": {
|
||||
"drupal-recipe-unpack": {
|
||||
"on-require": false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. **Development Dependencies**: Recipes in the `require-dev` section are not
|
||||
automatically unpacked
|
||||
```json
|
||||
{
|
||||
"require-dev": {
|
||||
"drupal/dev_recipe": "^1.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
You will need to manually unpack these using the `drupal:recipe-unpack`
|
||||
command if desired.
|
||||
|
||||
4. **With `--no-install` Option**: When using `composer require` with the
|
||||
`--no-install` flag
|
||||
```bash
|
||||
composer require drupal/example_recipe --no-install
|
||||
```
|
||||
In this case, you'll need to run `composer install` afterward and then
|
||||
manually unpack using the `drupal:recipe-unpack` command.
|
||||
|
||||
## Example Usage Scenarios
|
||||
|
||||
### Basic Recipe Installation
|
||||
|
||||
```bash
|
||||
# This will automatically install and unpack the recipe
|
||||
composer require drupal/example_recipe
|
||||
```
|
||||
|
||||
The result:
|
||||
- Dependencies from `drupal/example_recipe` are added to your root
|
||||
`composer.json`
|
||||
- `drupal/example_recipe` itself is removed from your dependencies
|
||||
- You'll see a message: "drupal/example_recipe unpacked successfully."
|
||||
- The recipe files will be present in the drupal-recipe installer path
|
||||
|
||||
### Manual Recipe Unpacking
|
||||
|
||||
```bash
|
||||
# First require the recipe without unpacking
|
||||
composer require drupal/example_recipe --no-install
|
||||
composer install
|
||||
|
||||
# Then manually unpack it
|
||||
composer drupal:recipe-unpack drupal/example_recipe
|
||||
```
|
||||
|
||||
### Working with Dev Recipes
|
||||
|
||||
```bash
|
||||
# This won't automatically unpack (dev dependencies aren't auto-unpacked)
|
||||
composer require --dev drupal/dev_recipe
|
||||
|
||||
# You'll need to manually unpack if desired (with confirmation prompt)
|
||||
composer drupal:recipe-unpack drupal/dev_recipe
|
||||
```
|
||||
|
||||
### Creating a New Project with Recipes
|
||||
|
||||
```bash
|
||||
composer create-project drupal/recipe-based-project my-project
|
||||
```
|
||||
|
||||
Any recipes included in the project template will be automatically unpacked
|
||||
during installation.
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Review Recipe Contents**: Before requiring a recipe, review its
|
||||
dependencies to understand what will be added to your project.
|
||||
|
||||
2. **Consider Versioning**: When a recipe is unpacked, its version constraints
|
||||
for dependencies are merged with your existing constraints, which may result
|
||||
in complex version requirements.
|
||||
|
||||
3. **Dev Dependencies**: Be cautious when unpacking development recipes, as
|
||||
their dependencies will be moved to the main `require` section, not
|
||||
`require-dev`.
|
||||
|
||||
4. **Custom Recipes**: When creating custom recipes, ensure they have the
|
||||
correct package type `drupal-recipe` and include appropriate dependencies.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Recipe Not Unpacking
|
||||
|
||||
- Check if the package type is `drupal-recipe`
|
||||
- Verify it's not in your ignore list
|
||||
- Confirm it's not in `require-dev` (which requires manual unpacking)
|
||||
- Ensure you haven't used the `--no-install` flag without following up with
|
||||
installation and manual unpacking
|
||||
|
||||
### Unpacking Errors
|
||||
|
||||
If you encounter issues during unpacking:
|
||||
|
||||
1. Check Composer's error output for specific issues and run commands with the
|
||||
`--verbose` flag
|
||||
2. Verify that version constraints between your existing dependencies and the
|
||||
recipe's dependencies are compatible
|
||||
3. For manual troubleshooting, consider temporarily setting `on-require` to
|
||||
`false` and unpacking recipes one by one
|
||||
161
vendor/drupal/core-recipe-unpack/RootComposer.php
vendored
Normal file
161
vendor/drupal/core-recipe-unpack/RootComposer.php
vendored
Normal file
@ -0,0 +1,161 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Composer\Plugin\RecipeUnpack;
|
||||
|
||||
use Composer\Composer;
|
||||
use Composer\Factory;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Json\JsonFile;
|
||||
use Composer\Json\JsonManipulator;
|
||||
use Composer\Package\Locker;
|
||||
use Composer\Package\RootPackageInterface;
|
||||
|
||||
/**
|
||||
* Provides access to and manipulation of the root composer files.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class RootComposer {
|
||||
|
||||
/**
|
||||
* The JSON manipulator for the contents of the root composer.json.
|
||||
*/
|
||||
private JsonManipulator $composerManipulator;
|
||||
|
||||
/**
|
||||
* The locked root composer.json content.
|
||||
*
|
||||
* @var array<string, mixed>|null
|
||||
*/
|
||||
private ?array $composerLockedContent = NULL;
|
||||
|
||||
public function __construct(
|
||||
private readonly Composer $composer,
|
||||
private readonly IOInterface $io,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieves the JSON manipulator for the contents of the root composer.json.
|
||||
*
|
||||
* @return \Composer\Json\JsonManipulator
|
||||
* The JSON manipulator.
|
||||
*/
|
||||
public function getComposerManipulator(): JsonManipulator {
|
||||
$this->composerManipulator ??= new JsonManipulator(file_get_contents(Factory::getComposerFile()));
|
||||
return $this->composerManipulator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the locked root composer.json content.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
* The locked root composer.json content.
|
||||
*/
|
||||
public function getComposerLockedContent(): array {
|
||||
$this->composerLockedContent ??= $this->composer->getLocker()->getLockData();
|
||||
return $this->composerLockedContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an element from the composer lock.
|
||||
*
|
||||
* @param string $key
|
||||
* The key of the element to remove.
|
||||
* @param string $index
|
||||
* The index of the element to remove.
|
||||
*/
|
||||
public function removeFromComposerLock(string $key, string $index): void {
|
||||
unset($this->composerLockedContent[$key][$index]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an element to the composer lock.
|
||||
*
|
||||
* @param string $key
|
||||
* The key of the element to add.
|
||||
* @param array $data
|
||||
* The data to add.
|
||||
*/
|
||||
public function addToComposerLock(string $key, array $data): void {
|
||||
$this->composerLockedContent[$key][] = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the root composer files.
|
||||
*
|
||||
* The files written are:
|
||||
* - composer.json
|
||||
* - composer.lock
|
||||
* - vendor/composer/installed.json
|
||||
* - vendor/composer/installed.php
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
* If the root composer could not be updated.
|
||||
*/
|
||||
public function writeFiles(): void {
|
||||
// Write composer.json.
|
||||
$composer_json = Factory::getComposerFile();
|
||||
$composer_content = $this->getComposerManipulator()->getContents();
|
||||
if (!file_put_contents($composer_json, $composer_content)) {
|
||||
throw new \RuntimeException(sprintf('Could not update %s', $composer_json));
|
||||
}
|
||||
|
||||
// Create package lists for lock file update.
|
||||
$local_repo = $this->composer->getRepositoryManager()->getLocalRepository();
|
||||
$packages = $dev_packages = [];
|
||||
$dev_package_names = $local_repo->getDevPackageNames();
|
||||
foreach ($local_repo->getPackages() as $package) {
|
||||
if (in_array($package->getName(), $dev_package_names, TRUE)) {
|
||||
$dev_packages[] = $package;
|
||||
}
|
||||
else {
|
||||
$packages[] = $package;
|
||||
}
|
||||
}
|
||||
|
||||
$lock_file_path = Factory::getLockFile(Factory::getComposerFile());
|
||||
$lock_file = new JsonFile($lock_file_path, io: $this->io);
|
||||
$old_locker = $this->composer->getLocker();
|
||||
$locker = new Locker($this->io, $lock_file, $this->composer->getInstallationManager(), $composer_content);
|
||||
$composer_locker_content = $this->getComposerLockedContent();
|
||||
|
||||
// Write the lock file.
|
||||
$locker->setLockData(
|
||||
$packages,
|
||||
$dev_packages,
|
||||
$composer_locker_content['platform'],
|
||||
$composer_locker_content['platform-dev'],
|
||||
$composer_locker_content['aliases'],
|
||||
$old_locker->getMinimumStability(),
|
||||
$old_locker->getStabilityFlags(),
|
||||
$old_locker->getPreferStable(),
|
||||
$old_locker->getPreferLowest(),
|
||||
$old_locker->getPlatformOverrides(),
|
||||
);
|
||||
$this->composer->setLocker($locker);
|
||||
|
||||
// Update installed.json and installed.php.
|
||||
$local_repo->write($local_repo->getDevMode() ?? TRUE, $this->composer->getInstallationManager());
|
||||
|
||||
$this->io->write("Unpacking has updated the root composer files.", verbosity: IOInterface::VERBOSE);
|
||||
|
||||
assert(self::checkRootPackage($composer_content, $this->composer->getPackage()), 'Composer root package and composer.json match');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the composer content and root package match.
|
||||
*
|
||||
* @param string $composer_content
|
||||
* The root composer content.
|
||||
* @param \Composer\Package\RootPackageInterface $root_package
|
||||
* The root package.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the composer content and root package match, FALSE if not.
|
||||
*/
|
||||
private static function checkRootPackage(string $composer_content, RootPackageInterface $root_package): bool {
|
||||
$composer = JsonFile::parseJson($composer_content);
|
||||
return empty(array_diff_key($root_package->getRequires(), $composer['require'] ?? [])) && empty(array_diff_key($root_package->getDevRequires(), $composer['require-dev'] ?? []));
|
||||
}
|
||||
|
||||
}
|
||||
64
vendor/drupal/core-recipe-unpack/SemVer.php
vendored
Normal file
64
vendor/drupal/core-recipe-unpack/SemVer.php
vendored
Normal file
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Composer\Plugin\RecipeUnpack;
|
||||
|
||||
use Composer\Semver\Constraint\MatchNoneConstraint;
|
||||
use Composer\Semver\Constraint\MultiConstraint;
|
||||
use Composer\Semver\Intervals;
|
||||
use Composer\Semver\VersionParser;
|
||||
|
||||
/**
|
||||
* Helper class to manipulate semantic versioning constraints.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class SemVer {
|
||||
|
||||
private function __construct() {}
|
||||
|
||||
/**
|
||||
* Minimizes two constraints.
|
||||
*
|
||||
* Compares two constraints and determines if one is a subset of the other. If
|
||||
* this is the case, the constraint that is a subset is returned. For example,
|
||||
* if called with '^6.2' and '^6.3' the function will return '^6.3'. If
|
||||
* neither constraint is a subset then the constraints are compacted and the
|
||||
* intersection is returned. For example, if called with ">=10.3" and
|
||||
* "^10.4 || ^11" the function will return ">=10.4.0.0-dev, <12.0.0.0-dev".
|
||||
*
|
||||
* @param \Composer\Semver\VersionParser $version_parser
|
||||
* A version parser.
|
||||
* @param string $constraint_a
|
||||
* A constraint to compact.
|
||||
* @param string $constraint_b
|
||||
* A constraint to compact.
|
||||
*
|
||||
* @return string
|
||||
* The compacted constraint.
|
||||
*
|
||||
* @throws \LogicException
|
||||
* Thrown when the provided constraints have no intersection.
|
||||
*/
|
||||
public static function minimizeConstraints(VersionParser $version_parser, string $constraint_a, string $constraint_b): string {
|
||||
$constraint_object_a = $version_parser->parseConstraints($constraint_a);
|
||||
$constraint_object_b = $version_parser->parseConstraints($constraint_b);
|
||||
if (Intervals::isSubsetOf($constraint_object_a, $constraint_object_b)) {
|
||||
return $constraint_a;
|
||||
}
|
||||
if (Intervals::isSubsetOf($constraint_object_b, $constraint_object_a)) {
|
||||
return $constraint_b;
|
||||
}
|
||||
$constraint = Intervals::compactConstraint(new MultiConstraint([$constraint_object_a, $constraint_object_b]));
|
||||
if ($constraint instanceof MatchNoneConstraint) {
|
||||
throw new \LogicException(sprintf('The constraints "%s" and "%s" do not intersect and cannot be minimized.', $constraint_a, $constraint_b));
|
||||
}
|
||||
return sprintf(
|
||||
'%s%s, %s%s',
|
||||
$constraint->getLowerBound()->isInclusive() ? '>=' : '>',
|
||||
$constraint->getLowerBound()->getVersion(),
|
||||
$constraint->getUpperBound()->isInclusive() ? '<=' : '<',
|
||||
$constraint->getUpperBound()->getVersion()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
104
vendor/drupal/core-recipe-unpack/UnpackCollection.php
vendored
Normal file
104
vendor/drupal/core-recipe-unpack/UnpackCollection.php
vendored
Normal file
@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Composer\Plugin\RecipeUnpack;
|
||||
|
||||
use Composer\Package\PackageInterface;
|
||||
|
||||
/**
|
||||
* A collection with packages to unpack.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class UnpackCollection implements \Iterator, \Countable {
|
||||
|
||||
/**
|
||||
* The queue of packages to unpack.
|
||||
*
|
||||
* @var \Composer\Package\PackageInterface[]
|
||||
*/
|
||||
private array $packagesToUnpack = [];
|
||||
|
||||
/**
|
||||
* The list of packages that have been unpacked.
|
||||
*
|
||||
* @var array<string, \Composer\Package\PackageInterface>
|
||||
*/
|
||||
private array $unpackedPackages = [];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rewind(): void {
|
||||
reset($this->packagesToUnpack);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function current(): PackageInterface|false {
|
||||
return current($this->packagesToUnpack);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function key(): ?string {
|
||||
return key($this->packagesToUnpack);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function next(): void {
|
||||
next($this->packagesToUnpack);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function valid(): bool {
|
||||
return current($this->packagesToUnpack) !== FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function count(): int {
|
||||
return count($this->packagesToUnpack);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a package to the queue of packages to unpack.
|
||||
*
|
||||
* @param \Composer\Package\PackageInterface $package
|
||||
* The package to add to the queue.
|
||||
*/
|
||||
public function add(PackageInterface $package): self {
|
||||
$this->packagesToUnpack[$package->getUniqueName()] = $package;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks a package as unpacked.
|
||||
*
|
||||
* @param \Composer\Package\PackageInterface $package
|
||||
* The package that has been unpacked.
|
||||
*/
|
||||
public function markPackageUnpacked(PackageInterface $package): void {
|
||||
$this->unpackedPackages[$package->getUniqueName()] = $package;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a package has been unpacked, or it's queued for unpacking.
|
||||
*
|
||||
* @param \Composer\Package\PackageInterface $package
|
||||
* The package to check.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the package has been unpacked.
|
||||
*/
|
||||
public function isUnpacked(PackageInterface $package): bool {
|
||||
return isset($this->unpackedPackages[$package->getUniqueName()]);
|
||||
}
|
||||
|
||||
}
|
||||
113
vendor/drupal/core-recipe-unpack/UnpackCommand.php
vendored
Normal file
113
vendor/drupal/core-recipe-unpack/UnpackCommand.php
vendored
Normal file
@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Composer\Plugin\RecipeUnpack;
|
||||
|
||||
use Composer\Command\BaseCommand;
|
||||
use Composer\Package\PackageInterface;
|
||||
use Composer\Repository\PlatformRepository;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* The "drupal:recipe-unpack" command class.
|
||||
*
|
||||
* Manually run the unpack operation that normally happens after
|
||||
* 'composer require'.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class UnpackCommand extends BaseCommand {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configure(): void {
|
||||
$name = 'drupal:recipe-unpack';
|
||||
$this
|
||||
->setName($name)
|
||||
->setDescription('Unpack Drupal recipes.')
|
||||
->addArgument('recipes', InputArgument::IS_ARRAY, "A list of recipe package names separated by a space, e.g. drupal/recipe_one drupal/recipe_two. If not provided, all recipes listed in the require section of the root composer are unpacked.")
|
||||
->setHelp(
|
||||
<<<EOT
|
||||
The <info>$name</info> command unpacks dependencies from the specified recipe
|
||||
packages into the composer.json file.
|
||||
|
||||
<info>php composer.phar $name drupal/my-recipe [...]</info>
|
||||
|
||||
It is usually not necessary to call <info>$name</info> manually,
|
||||
because by default it is called automatically as needed, after a
|
||||
<info>require</info> command.
|
||||
EOT
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int {
|
||||
$composer = $this->requireComposer();
|
||||
$io = $this->getIO();
|
||||
$local_repo = $composer->getRepositoryManager()->getLocalRepository();
|
||||
$package_names = $input->getArgument('recipes') ?? [];
|
||||
|
||||
// If no recipes are provided unpack all recipes that are required by the
|
||||
// root package.
|
||||
if (empty($package_names)) {
|
||||
foreach ($composer->getPackage()->getRequires() as $link) {
|
||||
$target = $link->getTarget();
|
||||
// Skip platform requirements, since those don't resolve to a real
|
||||
// package.
|
||||
// @see https://getcomposer.org/doc/articles/composer-platform-dependencies.md
|
||||
if (PlatformRepository::isPlatformPackage($target)) {
|
||||
continue;
|
||||
}
|
||||
$package = $local_repo->findPackage($target, $link->getConstraint());
|
||||
if ($package->getType() === Plugin::RECIPE_PACKAGE_TYPE) {
|
||||
$package_names[] = $package->getName();
|
||||
}
|
||||
}
|
||||
if (empty($package_names)) {
|
||||
$io->write('<info>No recipes to unpack.</info>');
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
$manager = new UnpackManager($composer, $io);
|
||||
$unpack_collection = new UnpackCollection();
|
||||
foreach ($package_names as $package_name) {
|
||||
if (!$manager->isRootDependency($package_name)) {
|
||||
$io->error(sprintf('<info>%s</info> not found in the root composer.json.', $package_name));
|
||||
return 1;
|
||||
}
|
||||
$packages = $local_repo->findPackages($package_name);
|
||||
$package = reset($packages);
|
||||
|
||||
if (!$package instanceof PackageInterface) {
|
||||
$io->error(sprintf('<info>%s</info> does not resolve to a package.', $package_name));
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ($package->getType() !== Plugin::RECIPE_PACKAGE_TYPE) {
|
||||
$io->error(sprintf('<info>%s</info> is not a recipe.', $package->getPrettyName()));
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ($manager->unpackOptions->isIgnored($package)) {
|
||||
$io->error(sprintf('<info>%s</info> is in the extra.drupal-recipe-unpack.ignore list.', $package->getName()));
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (UnpackManager::isDevRequirement($package)) {
|
||||
$io->warning(sprintf('<info>%s</info> is present in the require-dev key. Unpacking will move the recipe\'s dependencies to the require key.', $package->getName()));
|
||||
if ($io->isInteractive() && !$io->askConfirmation('<info>Do you want to continue</info> [<comment>yes</comment>]?')) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
$unpack_collection->add($package);
|
||||
}
|
||||
$manager->unpack($unpack_collection);
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
99
vendor/drupal/core-recipe-unpack/UnpackManager.php
vendored
Normal file
99
vendor/drupal/core-recipe-unpack/UnpackManager.php
vendored
Normal file
@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Composer\Plugin\RecipeUnpack;
|
||||
|
||||
use Composer\Composer;
|
||||
use Composer\InstalledVersions;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Package\PackageInterface;
|
||||
use Composer\Semver\Intervals;
|
||||
|
||||
/**
|
||||
* Manages the recipe unpacking process.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final readonly class UnpackManager {
|
||||
|
||||
/**
|
||||
* The root composer with the root dependencies to be manipulated.
|
||||
*/
|
||||
private RootComposer $rootComposer;
|
||||
|
||||
/**
|
||||
* The unpack options.
|
||||
*/
|
||||
public UnpackOptions $unpackOptions;
|
||||
|
||||
public function __construct(
|
||||
private Composer $composer,
|
||||
private IOInterface $io,
|
||||
) {
|
||||
$this->rootComposer = new RootComposer($composer, $io);
|
||||
$this->unpackOptions = UnpackOptions::create($composer->getPackage()->getExtra());
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpacks the packages in the provided collection.
|
||||
*
|
||||
* @param \Drupal\Composer\Plugin\RecipeUnpack\UnpackCollection $unpackCollection
|
||||
* The collection of recipe packages to unpack.
|
||||
*/
|
||||
public function unpack(UnpackCollection $unpackCollection): void {
|
||||
if (count($unpackCollection) === 0) {
|
||||
// Early return to avoid unnecessary work.
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($unpackCollection as $package) {
|
||||
$unpacker = new Unpacker(
|
||||
$package,
|
||||
$this->composer,
|
||||
$this->rootComposer,
|
||||
$unpackCollection,
|
||||
$this->unpackOptions,
|
||||
$this->io,
|
||||
);
|
||||
$unpacker->unpackDependencies();
|
||||
$this->io->write("<info>{$package->getName()}</info> unpacked.");
|
||||
}
|
||||
|
||||
// Unpacking uses \Composer\Semver\Intervals::isSubsetOf() to choose between
|
||||
// constraints.
|
||||
Intervals::clear();
|
||||
|
||||
$this->rootComposer->writeFiles();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the provided package is present in the root composer.json.
|
||||
*
|
||||
* @param string $package_name
|
||||
* The package name to check.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the package is present in the root composer.json, FALSE if not.
|
||||
*/
|
||||
public function isRootDependency(string $package_name): bool {
|
||||
$root_package = $this->composer->getPackage();
|
||||
return isset($root_package->getRequires()[$package_name]) || isset($root_package->getDevRequires()[$package_name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a package is a dev requirement.
|
||||
*
|
||||
* @param \Composer\Package\PackageInterface $package
|
||||
* The package to check.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the package is present in require-dev or due to a package in
|
||||
* require-dev, FALSE if not.
|
||||
*/
|
||||
public static function isDevRequirement(PackageInterface $package): bool {
|
||||
// Check if package is either a regular or dev requirement.
|
||||
return InstalledVersions::isInstalled($package->getName()) &&
|
||||
// Check if package is a regular requirement.
|
||||
!InstalledVersions::isInstalled($package->getName(), FALSE);
|
||||
}
|
||||
|
||||
}
|
||||
79
vendor/drupal/core-recipe-unpack/UnpackOptions.php
vendored
Normal file
79
vendor/drupal/core-recipe-unpack/UnpackOptions.php
vendored
Normal file
@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Composer\Plugin\RecipeUnpack;
|
||||
|
||||
use Composer\Package\PackageInterface;
|
||||
|
||||
/**
|
||||
* Per-project options from the 'extras' section of the composer.json file.
|
||||
*
|
||||
* Projects that implement dependency unpacking plugin can further configure it.
|
||||
* This data is pulled from the 'drupal-recipe-unpack' portion of the extras
|
||||
* section.
|
||||
*
|
||||
* @code
|
||||
* "extras": {
|
||||
* "drupal-recipe-unpack": {
|
||||
* "ignore": ["drupal/recipe_name"],
|
||||
* "on-require": true
|
||||
* }
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* Supported options:
|
||||
* - `ignore` (array):
|
||||
* Specifies packages to exclude from unpacking into the root composer.json.
|
||||
* - `on-require` (boolean):
|
||||
* Whether to unpack recipes automatically on require.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final readonly class UnpackOptions {
|
||||
|
||||
/**
|
||||
* The ID of the extra section in the top-level composer.json file.
|
||||
*/
|
||||
const string ID = 'drupal-recipe-unpack';
|
||||
|
||||
/**
|
||||
* The raw data from the 'extras' section of the top-level composer.json file.
|
||||
*
|
||||
* @var array{ignore: string[], on-require: boolean}
|
||||
*/
|
||||
public array $options;
|
||||
|
||||
private function __construct(array $options) {
|
||||
$this->options = $options + [
|
||||
'ignore' => [],
|
||||
'on-require' => TRUE,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a package should be ignored.
|
||||
*
|
||||
* @param \Composer\Package\PackageInterface $package
|
||||
* The package.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the package should be ignored, FALSE if not.
|
||||
*/
|
||||
public function isIgnored(PackageInterface $package): bool {
|
||||
return in_array($package->getName(), $this->options['ignore'], TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an unpack options object.
|
||||
*
|
||||
* @param array $extras
|
||||
* The contents of the 'extras' section.
|
||||
*
|
||||
* @return self
|
||||
* The unpack options object representing the provided unpack options
|
||||
*/
|
||||
public static function create(array $extras): self {
|
||||
$options = $extras[self::ID] ?? [];
|
||||
return new self($options);
|
||||
}
|
||||
|
||||
}
|
||||
217
vendor/drupal/core-recipe-unpack/Unpacker.php
vendored
Normal file
217
vendor/drupal/core-recipe-unpack/Unpacker.php
vendored
Normal file
@ -0,0 +1,217 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Composer\Plugin\RecipeUnpack;
|
||||
|
||||
use Composer\Composer;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Package\Link;
|
||||
use Composer\Package\PackageInterface;
|
||||
use Composer\Semver\VersionParser;
|
||||
|
||||
/**
|
||||
* Handles the details of unpacking a specific recipe.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final readonly class Unpacker {
|
||||
|
||||
/**
|
||||
* The version parser.
|
||||
*/
|
||||
private VersionParser $versionParser;
|
||||
|
||||
public function __construct(
|
||||
private PackageInterface $package,
|
||||
private Composer $composer,
|
||||
private RootComposer $rootComposer,
|
||||
private UnpackCollection $unpackCollection,
|
||||
private UnpackOptions $unpackOptions,
|
||||
private IOInterface $io,
|
||||
) {
|
||||
$this->versionParser = new VersionParser();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpacks the package's dependencies to the root composer.json and lock file.
|
||||
*/
|
||||
public function unpackDependencies(): void {
|
||||
$this->updateComposerJsonPackages();
|
||||
$this->updateComposerLockContent();
|
||||
$this->unpackCollection->markPackageUnpacked($this->package);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes dependencies of the package that is being unpacked.
|
||||
*
|
||||
* If the dependency is a recipe and should be unpacked, we add it into the
|
||||
* package queue so that it will be unpacked as well. If the dependency is not
|
||||
* a recipe, or an ignored recipe, the package link will be yielded.
|
||||
*
|
||||
* @param array<string, \Composer\Package\Link> $package_dependency_links
|
||||
* The package dependencies to process.
|
||||
*
|
||||
* @return iterable<\Composer\Package\Link>
|
||||
* The package dependencies to add to composer.json.
|
||||
*/
|
||||
private function processPackageDependencies(array $package_dependency_links): iterable {
|
||||
foreach ($package_dependency_links as $link) {
|
||||
if ($link->getTarget() === $this->package->getName()) {
|
||||
// This dependency is the same as the current package, so let's skip it.
|
||||
continue;
|
||||
}
|
||||
|
||||
$package = $this->getPackageFromLinkTarget($link);
|
||||
|
||||
// If we can't find the package in the local repository that's because it
|
||||
// has already been removed therefore skip it.
|
||||
if ($package === NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($package->getType() === Plugin::RECIPE_PACKAGE_TYPE) {
|
||||
if ($this->unpackCollection->isUnpacked($package)) {
|
||||
// This dependency is already unpacked.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$this->unpackOptions->isIgnored($package)) {
|
||||
// This recipe should be unpacked as well.
|
||||
$this->unpackCollection->add($package);
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
// This recipe should not be unpacked. But it might need to be added
|
||||
// to the root composer.json
|
||||
$this->io->write(sprintf('<info>%s</info> not unpacked because it is ignored.', $package->getName()), verbosity: IOInterface::VERBOSE);
|
||||
}
|
||||
}
|
||||
|
||||
yield $link;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the composer.json content with the package being unpacked.
|
||||
*
|
||||
* This method will add all the package dependencies to the root composer.json
|
||||
* content and also remove the package itself from the root composer.json.
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
* If the composer.json could not be updated.
|
||||
*/
|
||||
private function updateComposerJsonPackages(): void {
|
||||
$composer_manipulator = $this->rootComposer->getComposerManipulator();
|
||||
$composer_config = $this->composer->getConfig();
|
||||
$sort_packages = $composer_config->get('sort-packages');
|
||||
$root_package = $this->composer->getPackage();
|
||||
$root_requires = $root_package->getRequires();
|
||||
$root_dev_requires = $root_package->getDevRequires();
|
||||
|
||||
foreach ($this->processPackageDependencies($this->package->getRequires()) as $package_dependency) {
|
||||
$dependency_name = $package_dependency->getTarget();
|
||||
$recipe_constraint_string = $package_dependency->getPrettyConstraint();
|
||||
if (isset($root_requires[$dependency_name])) {
|
||||
$recipe_constraint_string = SemVer::minimizeConstraints($this->versionParser, $recipe_constraint_string, $root_requires[$dependency_name]->getPrettyConstraint());
|
||||
if ($recipe_constraint_string === $root_requires[$dependency_name]) {
|
||||
// This dependency is already in the required section with the
|
||||
// correct constraint.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
elseif (isset($root_dev_requires[$dependency_name])) {
|
||||
$recipe_constraint_string = SemVer::minimizeConstraints($this->versionParser, $recipe_constraint_string, $root_dev_requires[$dependency_name]->getPrettyConstraint());
|
||||
// This dependency is already in the require-dev section. We will
|
||||
// move it to the require section.
|
||||
$composer_manipulator->removeSubNode('require-dev', $dependency_name);
|
||||
}
|
||||
|
||||
// Add the dependency to the required section. If it cannot be added, then
|
||||
// throw an exception.
|
||||
if (!$composer_manipulator->addLink(
|
||||
'require',
|
||||
$dependency_name,
|
||||
$recipe_constraint_string,
|
||||
$sort_packages,
|
||||
)) {
|
||||
throw new \RuntimeException(sprintf('Unable to manipulate composer.json during the unpack of %s',
|
||||
$dependency_name,
|
||||
));
|
||||
}
|
||||
$link = new Link($root_package->getName(), $dependency_name, $this->versionParser->parseConstraints($recipe_constraint_string), Link::TYPE_REQUIRE, $recipe_constraint_string);
|
||||
$root_requires[$dependency_name] = $link;
|
||||
unset($root_dev_requires[$dependency_name]);
|
||||
$this->io->write(sprintf('Adding <info>%s</info> (<comment>%s</comment>) to composer.json during the unpack of <info>%s</info>', $dependency_name, $recipe_constraint_string, $this->package->getName()), verbosity: IOInterface::VERBOSE);
|
||||
}
|
||||
|
||||
// Ensure the written packages are no longer in the dev package names.
|
||||
$local_repo = $this->composer->getRepositoryManager()->getLocalRepository();
|
||||
$local_repo->setDevPackageNames(array_diff($local_repo->getDevPackageNames(), array_keys($root_requires)));
|
||||
|
||||
// Update the root package to reflect the changes.
|
||||
$root_package->setDevRequires($root_dev_requires);
|
||||
$root_package->setRequires($root_requires);
|
||||
|
||||
$composer_manipulator->removeSubNode(UnpackManager::isDevRequirement($this->package) ? 'require-dev' : 'require', $this->package->getName());
|
||||
$this->io->write(sprintf('Removing <info>%s</info> from composer.json', $this->package->getName()), verbosity: IOInterface::VERBOSE);
|
||||
|
||||
$composer_manipulator->removeMainKeyIfEmpty('require-dev');
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the composer.lock content and keeps the local repo in sync.
|
||||
*
|
||||
* This method will remove the package itself from the composer.lock content
|
||||
* in the root composer.
|
||||
*/
|
||||
private function updateComposerLockContent(): void {
|
||||
$composer_locker_content = $this->rootComposer->getComposerLockedContent();
|
||||
$root_package = $this->composer->getPackage();
|
||||
$root_requires = $root_package->getRequires();
|
||||
$root_dev_requires = $root_package->getDevRequires();
|
||||
$local_repo = $this->composer->getRepositoryManager()->getLocalRepository();
|
||||
|
||||
if (isset($root_requires[$this->package->getName()])) {
|
||||
unset($root_requires[$this->package->getName()]);
|
||||
$root_package->setRequires($root_requires);
|
||||
}
|
||||
|
||||
foreach ($composer_locker_content['packages'] as $key => $lock_data) {
|
||||
// Find the package being unpacked in the composer.lock content and
|
||||
// remove it.
|
||||
if ($lock_data['name'] === $this->package->getName()) {
|
||||
$this->rootComposer->removeFromComposerLock('packages', $key);
|
||||
// If the package is in require-dev we need to move the lock data.
|
||||
if (isset($root_dev_requires[$lock_data['name']])) {
|
||||
$this->rootComposer->addToComposerLock('packages-dev', $lock_data);
|
||||
$dev_package_names = $local_repo->getDevPackageNames();
|
||||
$dev_package_names[] = $lock_data['name'];
|
||||
$local_repo->setDevPackageNames($dev_package_names);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
$local_repo->setDevPackageNames(array_diff($local_repo->getDevPackageNames(), [$this->package->getName()]));
|
||||
$local_repo->removePackage($this->package);
|
||||
if (isset($root_dev_requires[$this->package->getName()])) {
|
||||
unset($root_dev_requires[$this->package->getName()]);
|
||||
$root_package->setDevRequires($root_dev_requires);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the package object from a link's target.
|
||||
*
|
||||
* @param \Composer\Package\Link $dependency
|
||||
* The link dependency.
|
||||
*
|
||||
* @return \Composer\Package\PackageInterface|null
|
||||
* The package object.
|
||||
*/
|
||||
private function getPackageFromLinkTarget(Link $dependency): ?PackageInterface {
|
||||
return $this->composer->getRepositoryManager()
|
||||
->getLocalRepository()
|
||||
->findPackage($dependency->getTarget(), $dependency->getConstraint());
|
||||
}
|
||||
|
||||
}
|
||||
26
vendor/drupal/core-recipe-unpack/composer.json
vendored
Normal file
26
vendor/drupal/core-recipe-unpack/composer.json
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "drupal/core-recipe-unpack",
|
||||
"description": "A Composer project unpacker for Drupal recipes.",
|
||||
"type": "composer-plugin",
|
||||
"keywords": ["drupal"],
|
||||
"homepage": "https://www.drupal.org/project/drupal",
|
||||
"license": "GPL-2.0-or-later",
|
||||
"require": {
|
||||
"composer-plugin-api": "^2",
|
||||
"php": ">=8.3"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Drupal\\Composer\\Plugin\\RecipeUnpack\\": ""
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"class": "Drupal\\Composer\\Plugin\\RecipeUnpack\\Plugin"
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
},
|
||||
"require-dev": {
|
||||
"composer/composer": "^2.7"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user