initial commit
This commit is contained in:
65
composer.json
Normal file
65
composer.json
Normal file
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"type": "project",
|
||||
"license": "proprietary",
|
||||
"minimum-stability": "stable",
|
||||
"prefer-stable": true,
|
||||
"require": {
|
||||
"php": ">=8.1",
|
||||
"ext-ctype": "*",
|
||||
"ext-iconv": "*",
|
||||
"symfony/console": "6.4.*",
|
||||
"symfony/dotenv": "6.4.*",
|
||||
"symfony/flex": "^2",
|
||||
"symfony/framework-bundle": "6.4.*",
|
||||
"symfony/runtime": "6.4.*",
|
||||
"symfony/yaml": "6.4.*"
|
||||
},
|
||||
"require-dev": {
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"php-http/discovery": true,
|
||||
"symfony/flex": true,
|
||||
"symfony/runtime": true
|
||||
},
|
||||
"sort-packages": true
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"App\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"App\\Tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
"replace": {
|
||||
"symfony/polyfill-ctype": "*",
|
||||
"symfony/polyfill-iconv": "*",
|
||||
"symfony/polyfill-php72": "*",
|
||||
"symfony/polyfill-php73": "*",
|
||||
"symfony/polyfill-php74": "*",
|
||||
"symfony/polyfill-php80": "*",
|
||||
"symfony/polyfill-php81": "*"
|
||||
},
|
||||
"scripts": {
|
||||
"auto-scripts": [
|
||||
],
|
||||
"post-install-cmd": [
|
||||
"@auto-scripts"
|
||||
],
|
||||
"post-update-cmd": [
|
||||
"@auto-scripts"
|
||||
]
|
||||
},
|
||||
"conflict": {
|
||||
"symfony/symfony": "*"
|
||||
},
|
||||
"extra": {
|
||||
"symfony": {
|
||||
"allow-contrib": false,
|
||||
"require": "6.4.*"
|
||||
}
|
||||
}
|
||||
}
|
||||
91
composer.lock
generated
Normal file
91
composer.lock
generated
Normal file
@@ -0,0 +1,91 @@
|
||||
{
|
||||
"_readme": [
|
||||
"This file locks the dependencies of your project to a known state",
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "6c7e3dbdfc97c282707609d966719896",
|
||||
"packages": [
|
||||
{
|
||||
"name": "symfony/flex",
|
||||
"version": "v2.4.7",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/flex.git",
|
||||
"reference": "92f4fba342161ff36072bd3b8e0b3c6c23160402"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/flex/zipball/92f4fba342161ff36072bd3b8e0b3c6c23160402",
|
||||
"reference": "92f4fba342161ff36072bd3b8e0b3c6c23160402",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"composer-plugin-api": "^2.1",
|
||||
"php": ">=8.0"
|
||||
},
|
||||
"conflict": {
|
||||
"composer/semver": "<1.7.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"composer/composer": "^2.1",
|
||||
"symfony/dotenv": "^5.4|^6.0",
|
||||
"symfony/filesystem": "^5.4|^6.0",
|
||||
"symfony/phpunit-bridge": "^5.4|^6.0",
|
||||
"symfony/process": "^5.4|^6.0"
|
||||
},
|
||||
"type": "composer-plugin",
|
||||
"extra": {
|
||||
"class": "Symfony\\Flex\\Flex"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Flex\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien.potencier@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "Composer plugin for Symfony",
|
||||
"support": {
|
||||
"issues": "https://github.com/symfony/flex/issues",
|
||||
"source": "https://github.com/symfony/flex/tree/v2.4.7"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-10-07T08:51:54+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": [],
|
||||
"prefer-stable": true,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
"php": ">=8.1",
|
||||
"ext-ctype": "*",
|
||||
"ext-iconv": "*"
|
||||
},
|
||||
"platform-dev": [],
|
||||
"plugin-api-version": "2.6.0"
|
||||
}
|
||||
25
vendor/autoload.php
vendored
Normal file
25
vendor/autoload.php
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
// autoload.php @generated by Composer
|
||||
|
||||
if (PHP_VERSION_ID < 50600) {
|
||||
if (!headers_sent()) {
|
||||
header('HTTP/1.1 500 Internal Server Error');
|
||||
}
|
||||
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
|
||||
if (!ini_get('display_errors')) {
|
||||
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
|
||||
fwrite(STDERR, $err);
|
||||
} elseif (!headers_sent()) {
|
||||
echo $err;
|
||||
}
|
||||
}
|
||||
trigger_error(
|
||||
$err,
|
||||
E_USER_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/composer/autoload_real.php';
|
||||
|
||||
return ComposerAutoloaderInit54c05e64f01f49eb136e9af7b3075bdd::getLoader();
|
||||
579
vendor/composer/ClassLoader.php
vendored
Normal file
579
vendor/composer/ClassLoader.php
vendored
Normal file
@@ -0,0 +1,579 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Composer.
|
||||
*
|
||||
* (c) Nils Adermann <naderman@naderman.de>
|
||||
* Jordi Boggiano <j.boggiano@seld.be>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
/**
|
||||
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
|
||||
*
|
||||
* $loader = new \Composer\Autoload\ClassLoader();
|
||||
*
|
||||
* // register classes with namespaces
|
||||
* $loader->add('Symfony\Component', __DIR__.'/component');
|
||||
* $loader->add('Symfony', __DIR__.'/framework');
|
||||
*
|
||||
* // activate the autoloader
|
||||
* $loader->register();
|
||||
*
|
||||
* // to enable searching the include path (eg. for PEAR packages)
|
||||
* $loader->setUseIncludePath(true);
|
||||
*
|
||||
* In this example, if you try to use a class in the Symfony\Component
|
||||
* namespace or one of its children (Symfony\Component\Console for instance),
|
||||
* the autoloader will first look for the class under the component/
|
||||
* directory, and it will then fallback to the framework/ directory if not
|
||||
* found before giving up.
|
||||
*
|
||||
* This class is loosely based on the Symfony UniversalClassLoader.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
* @see https://www.php-fig.org/psr/psr-0/
|
||||
* @see https://www.php-fig.org/psr/psr-4/
|
||||
*/
|
||||
class ClassLoader
|
||||
{
|
||||
/** @var \Closure(string):void */
|
||||
private static $includeFile;
|
||||
|
||||
/** @var string|null */
|
||||
private $vendorDir;
|
||||
|
||||
// PSR-4
|
||||
/**
|
||||
* @var array<string, array<string, int>>
|
||||
*/
|
||||
private $prefixLengthsPsr4 = array();
|
||||
/**
|
||||
* @var array<string, list<string>>
|
||||
*/
|
||||
private $prefixDirsPsr4 = array();
|
||||
/**
|
||||
* @var list<string>
|
||||
*/
|
||||
private $fallbackDirsPsr4 = array();
|
||||
|
||||
// PSR-0
|
||||
/**
|
||||
* List of PSR-0 prefixes
|
||||
*
|
||||
* Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
|
||||
*
|
||||
* @var array<string, array<string, list<string>>>
|
||||
*/
|
||||
private $prefixesPsr0 = array();
|
||||
/**
|
||||
* @var list<string>
|
||||
*/
|
||||
private $fallbackDirsPsr0 = array();
|
||||
|
||||
/** @var bool */
|
||||
private $useIncludePath = false;
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
private $classMap = array();
|
||||
|
||||
/** @var bool */
|
||||
private $classMapAuthoritative = false;
|
||||
|
||||
/**
|
||||
* @var array<string, bool>
|
||||
*/
|
||||
private $missingClasses = array();
|
||||
|
||||
/** @var string|null */
|
||||
private $apcuPrefix;
|
||||
|
||||
/**
|
||||
* @var array<string, self>
|
||||
*/
|
||||
private static $registeredLoaders = array();
|
||||
|
||||
/**
|
||||
* @param string|null $vendorDir
|
||||
*/
|
||||
public function __construct($vendorDir = null)
|
||||
{
|
||||
$this->vendorDir = $vendorDir;
|
||||
self::initializeIncludeClosure();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, list<string>>
|
||||
*/
|
||||
public function getPrefixes()
|
||||
{
|
||||
if (!empty($this->prefixesPsr0)) {
|
||||
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, list<string>>
|
||||
*/
|
||||
public function getPrefixesPsr4()
|
||||
{
|
||||
return $this->prefixDirsPsr4;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public function getFallbackDirs()
|
||||
{
|
||||
return $this->fallbackDirsPsr0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public function getFallbackDirsPsr4()
|
||||
{
|
||||
return $this->fallbackDirsPsr4;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string> Array of classname => path
|
||||
*/
|
||||
public function getClassMap()
|
||||
{
|
||||
return $this->classMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $classMap Class to filename map
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addClassMap(array $classMap)
|
||||
{
|
||||
if ($this->classMap) {
|
||||
$this->classMap = array_merge($this->classMap, $classMap);
|
||||
} else {
|
||||
$this->classMap = $classMap;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-0 directories for a given prefix, either
|
||||
* appending or prepending to the ones previously set for this prefix.
|
||||
*
|
||||
* @param string $prefix The prefix
|
||||
* @param list<string>|string $paths The PSR-0 root directories
|
||||
* @param bool $prepend Whether to prepend the directories
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add($prefix, $paths, $prepend = false)
|
||||
{
|
||||
$paths = (array) $paths;
|
||||
if (!$prefix) {
|
||||
if ($prepend) {
|
||||
$this->fallbackDirsPsr0 = array_merge(
|
||||
$paths,
|
||||
$this->fallbackDirsPsr0
|
||||
);
|
||||
} else {
|
||||
$this->fallbackDirsPsr0 = array_merge(
|
||||
$this->fallbackDirsPsr0,
|
||||
$paths
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$first = $prefix[0];
|
||||
if (!isset($this->prefixesPsr0[$first][$prefix])) {
|
||||
$this->prefixesPsr0[$first][$prefix] = $paths;
|
||||
|
||||
return;
|
||||
}
|
||||
if ($prepend) {
|
||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||
$paths,
|
||||
$this->prefixesPsr0[$first][$prefix]
|
||||
);
|
||||
} else {
|
||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||
$this->prefixesPsr0[$first][$prefix],
|
||||
$paths
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-4 directories for a given namespace, either
|
||||
* appending or prepending to the ones previously set for this namespace.
|
||||
*
|
||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||
* @param list<string>|string $paths The PSR-4 base directories
|
||||
* @param bool $prepend Whether to prepend the directories
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addPsr4($prefix, $paths, $prepend = false)
|
||||
{
|
||||
$paths = (array) $paths;
|
||||
if (!$prefix) {
|
||||
// Register directories for the root namespace.
|
||||
if ($prepend) {
|
||||
$this->fallbackDirsPsr4 = array_merge(
|
||||
$paths,
|
||||
$this->fallbackDirsPsr4
|
||||
);
|
||||
} else {
|
||||
$this->fallbackDirsPsr4 = array_merge(
|
||||
$this->fallbackDirsPsr4,
|
||||
$paths
|
||||
);
|
||||
}
|
||||
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
|
||||
// Register directories for a new namespace.
|
||||
$length = strlen($prefix);
|
||||
if ('\\' !== $prefix[$length - 1]) {
|
||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||
}
|
||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||
$this->prefixDirsPsr4[$prefix] = $paths;
|
||||
} elseif ($prepend) {
|
||||
// Prepend directories for an already registered namespace.
|
||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||
$paths,
|
||||
$this->prefixDirsPsr4[$prefix]
|
||||
);
|
||||
} else {
|
||||
// Append directories for an already registered namespace.
|
||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||
$this->prefixDirsPsr4[$prefix],
|
||||
$paths
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-0 directories for a given prefix,
|
||||
* replacing any others previously set for this prefix.
|
||||
*
|
||||
* @param string $prefix The prefix
|
||||
* @param list<string>|string $paths The PSR-0 base directories
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set($prefix, $paths)
|
||||
{
|
||||
if (!$prefix) {
|
||||
$this->fallbackDirsPsr0 = (array) $paths;
|
||||
} else {
|
||||
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-4 directories for a given namespace,
|
||||
* replacing any others previously set for this namespace.
|
||||
*
|
||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||
* @param list<string>|string $paths The PSR-4 base directories
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setPsr4($prefix, $paths)
|
||||
{
|
||||
if (!$prefix) {
|
||||
$this->fallbackDirsPsr4 = (array) $paths;
|
||||
} else {
|
||||
$length = strlen($prefix);
|
||||
if ('\\' !== $prefix[$length - 1]) {
|
||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||
}
|
||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||
$this->prefixDirsPsr4[$prefix] = (array) $paths;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns on searching the include path for class files.
|
||||
*
|
||||
* @param bool $useIncludePath
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setUseIncludePath($useIncludePath)
|
||||
{
|
||||
$this->useIncludePath = $useIncludePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be used to check if the autoloader uses the include path to check
|
||||
* for classes.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getUseIncludePath()
|
||||
{
|
||||
return $this->useIncludePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns off searching the prefix and fallback directories for classes
|
||||
* that have not been registered with the class map.
|
||||
*
|
||||
* @param bool $classMapAuthoritative
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setClassMapAuthoritative($classMapAuthoritative)
|
||||
{
|
||||
$this->classMapAuthoritative = $classMapAuthoritative;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should class lookup fail if not found in the current class map?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isClassMapAuthoritative()
|
||||
{
|
||||
return $this->classMapAuthoritative;
|
||||
}
|
||||
|
||||
/**
|
||||
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
|
||||
*
|
||||
* @param string|null $apcuPrefix
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setApcuPrefix($apcuPrefix)
|
||||
{
|
||||
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The APCu prefix in use, or null if APCu caching is not enabled.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getApcuPrefix()
|
||||
{
|
||||
return $this->apcuPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers this instance as an autoloader.
|
||||
*
|
||||
* @param bool $prepend Whether to prepend the autoloader or not
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register($prepend = false)
|
||||
{
|
||||
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
|
||||
|
||||
if (null === $this->vendorDir) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($prepend) {
|
||||
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
|
||||
} else {
|
||||
unset(self::$registeredLoaders[$this->vendorDir]);
|
||||
self::$registeredLoaders[$this->vendorDir] = $this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters this instance as an autoloader.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function unregister()
|
||||
{
|
||||
spl_autoload_unregister(array($this, 'loadClass'));
|
||||
|
||||
if (null !== $this->vendorDir) {
|
||||
unset(self::$registeredLoaders[$this->vendorDir]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the given class or interface.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
* @return true|null True if loaded, null otherwise
|
||||
*/
|
||||
public function loadClass($class)
|
||||
{
|
||||
if ($file = $this->findFile($class)) {
|
||||
$includeFile = self::$includeFile;
|
||||
$includeFile($file);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the path to the file where the class is defined.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
*
|
||||
* @return string|false The path if found, false otherwise
|
||||
*/
|
||||
public function findFile($class)
|
||||
{
|
||||
// class map lookup
|
||||
if (isset($this->classMap[$class])) {
|
||||
return $this->classMap[$class];
|
||||
}
|
||||
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
|
||||
return false;
|
||||
}
|
||||
if (null !== $this->apcuPrefix) {
|
||||
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
|
||||
if ($hit) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
$file = $this->findFileWithExtension($class, '.php');
|
||||
|
||||
// Search for Hack files if we are running on HHVM
|
||||
if (false === $file && defined('HHVM_VERSION')) {
|
||||
$file = $this->findFileWithExtension($class, '.hh');
|
||||
}
|
||||
|
||||
if (null !== $this->apcuPrefix) {
|
||||
apcu_add($this->apcuPrefix.$class, $file);
|
||||
}
|
||||
|
||||
if (false === $file) {
|
||||
// Remember that this class does not exist.
|
||||
$this->missingClasses[$class] = true;
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently registered loaders keyed by their corresponding vendor directories.
|
||||
*
|
||||
* @return array<string, self>
|
||||
*/
|
||||
public static function getRegisteredLoaders()
|
||||
{
|
||||
return self::$registeredLoaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
* @param string $ext
|
||||
* @return string|false
|
||||
*/
|
||||
private function findFileWithExtension($class, $ext)
|
||||
{
|
||||
// PSR-4 lookup
|
||||
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
|
||||
|
||||
$first = $class[0];
|
||||
if (isset($this->prefixLengthsPsr4[$first])) {
|
||||
$subPath = $class;
|
||||
while (false !== $lastPos = strrpos($subPath, '\\')) {
|
||||
$subPath = substr($subPath, 0, $lastPos);
|
||||
$search = $subPath . '\\';
|
||||
if (isset($this->prefixDirsPsr4[$search])) {
|
||||
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
|
||||
foreach ($this->prefixDirsPsr4[$search] as $dir) {
|
||||
if (file_exists($file = $dir . $pathEnd)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-4 fallback dirs
|
||||
foreach ($this->fallbackDirsPsr4 as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 lookup
|
||||
if (false !== $pos = strrpos($class, '\\')) {
|
||||
// namespaced class name
|
||||
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
|
||||
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
|
||||
} else {
|
||||
// PEAR-like class name
|
||||
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
|
||||
}
|
||||
|
||||
if (isset($this->prefixesPsr0[$first])) {
|
||||
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
|
||||
if (0 === strpos($class, $prefix)) {
|
||||
foreach ($dirs as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 fallback dirs
|
||||
foreach ($this->fallbackDirsPsr0 as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 include paths.
|
||||
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
private static function initializeIncludeClosure()
|
||||
{
|
||||
if (self::$includeFile !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope isolated include.
|
||||
*
|
||||
* Prevents access to $this/self from included files.
|
||||
*
|
||||
* @param string $file
|
||||
* @return void
|
||||
*/
|
||||
self::$includeFile = \Closure::bind(static function($file) {
|
||||
include $file;
|
||||
}, null, null);
|
||||
}
|
||||
}
|
||||
359
vendor/composer/InstalledVersions.php
vendored
Normal file
359
vendor/composer/InstalledVersions.php
vendored
Normal file
@@ -0,0 +1,359 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Composer.
|
||||
*
|
||||
* (c) Nils Adermann <naderman@naderman.de>
|
||||
* Jordi Boggiano <j.boggiano@seld.be>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Composer;
|
||||
|
||||
use Composer\Autoload\ClassLoader;
|
||||
use Composer\Semver\VersionParser;
|
||||
|
||||
/**
|
||||
* This class is copied in every Composer installed project and available to all
|
||||
*
|
||||
* See also https://getcomposer.org/doc/07-runtime.md#installed-versions
|
||||
*
|
||||
* To require its presence, you can require `composer-runtime-api ^2.0`
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class InstalledVersions
|
||||
{
|
||||
/**
|
||||
* @var mixed[]|null
|
||||
* @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
|
||||
*/
|
||||
private static $installed;
|
||||
|
||||
/**
|
||||
* @var bool|null
|
||||
*/
|
||||
private static $canGetVendors;
|
||||
|
||||
/**
|
||||
* @var array[]
|
||||
* @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
|
||||
*/
|
||||
private static $installedByVendor = array();
|
||||
|
||||
/**
|
||||
* Returns a list of all package names which are present, either by being installed, replaced or provided
|
||||
*
|
||||
* @return string[]
|
||||
* @psalm-return list<string>
|
||||
*/
|
||||
public static function getInstalledPackages()
|
||||
{
|
||||
$packages = array();
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
$packages[] = array_keys($installed['versions']);
|
||||
}
|
||||
|
||||
if (1 === \count($packages)) {
|
||||
return $packages[0];
|
||||
}
|
||||
|
||||
return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all package names with a specific type e.g. 'library'
|
||||
*
|
||||
* @param string $type
|
||||
* @return string[]
|
||||
* @psalm-return list<string>
|
||||
*/
|
||||
public static function getInstalledPackagesByType($type)
|
||||
{
|
||||
$packagesByType = array();
|
||||
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
foreach ($installed['versions'] as $name => $package) {
|
||||
if (isset($package['type']) && $package['type'] === $type) {
|
||||
$packagesByType[] = $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $packagesByType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given package is installed
|
||||
*
|
||||
* This also returns true if the package name is provided or replaced by another package
|
||||
*
|
||||
* @param string $packageName
|
||||
* @param bool $includeDevRequirements
|
||||
* @return bool
|
||||
*/
|
||||
public static function isInstalled($packageName, $includeDevRequirements = true)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (isset($installed['versions'][$packageName])) {
|
||||
return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given package satisfies a version constraint
|
||||
*
|
||||
* e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
|
||||
*
|
||||
* Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
|
||||
*
|
||||
* @param VersionParser $parser Install composer/semver to have access to this class and functionality
|
||||
* @param string $packageName
|
||||
* @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
|
||||
* @return bool
|
||||
*/
|
||||
public static function satisfies(VersionParser $parser, $packageName, $constraint)
|
||||
{
|
||||
$constraint = $parser->parseConstraints((string) $constraint);
|
||||
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
|
||||
|
||||
return $provided->matches($constraint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a version constraint representing all the range(s) which are installed for a given package
|
||||
*
|
||||
* It is easier to use this via isInstalled() with the $constraint argument if you need to check
|
||||
* whether a given version of a package is installed, and not just whether it exists
|
||||
*
|
||||
* @param string $packageName
|
||||
* @return string Version constraint usable with composer/semver
|
||||
*/
|
||||
public static function getVersionRanges($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$ranges = array();
|
||||
if (isset($installed['versions'][$packageName]['pretty_version'])) {
|
||||
$ranges[] = $installed['versions'][$packageName]['pretty_version'];
|
||||
}
|
||||
if (array_key_exists('aliases', $installed['versions'][$packageName])) {
|
||||
$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
|
||||
}
|
||||
if (array_key_exists('replaced', $installed['versions'][$packageName])) {
|
||||
$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
|
||||
}
|
||||
if (array_key_exists('provided', $installed['versions'][$packageName])) {
|
||||
$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
|
||||
}
|
||||
|
||||
return implode(' || ', $ranges);
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $packageName
|
||||
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
|
||||
*/
|
||||
public static function getVersion($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($installed['versions'][$packageName]['version'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $installed['versions'][$packageName]['version'];
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $packageName
|
||||
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
|
||||
*/
|
||||
public static function getPrettyVersion($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($installed['versions'][$packageName]['pretty_version'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $installed['versions'][$packageName]['pretty_version'];
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $packageName
|
||||
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
|
||||
*/
|
||||
public static function getReference($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($installed['versions'][$packageName]['reference'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $installed['versions'][$packageName]['reference'];
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $packageName
|
||||
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
|
||||
*/
|
||||
public static function getInstallPath($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
|
||||
*/
|
||||
public static function getRootPackage()
|
||||
{
|
||||
$installed = self::getInstalled();
|
||||
|
||||
return $installed[0]['root'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the raw installed.php data for custom implementations
|
||||
*
|
||||
* @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
|
||||
* @return array[]
|
||||
* @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
|
||||
*/
|
||||
public static function getRawData()
|
||||
{
|
||||
@trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
|
||||
|
||||
if (null === self::$installed) {
|
||||
// only require the installed.php file if this file is loaded from its dumped location,
|
||||
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
|
||||
if (substr(__DIR__, -8, 1) !== 'C') {
|
||||
self::$installed = include __DIR__ . '/installed.php';
|
||||
} else {
|
||||
self::$installed = array();
|
||||
}
|
||||
}
|
||||
|
||||
return self::$installed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the raw data of all installed.php which are currently loaded for custom implementations
|
||||
*
|
||||
* @return array[]
|
||||
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
|
||||
*/
|
||||
public static function getAllRawData()
|
||||
{
|
||||
return self::getInstalled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Lets you reload the static array from another file
|
||||
*
|
||||
* This is only useful for complex integrations in which a project needs to use
|
||||
* this class but then also needs to execute another project's autoloader in process,
|
||||
* and wants to ensure both projects have access to their version of installed.php.
|
||||
*
|
||||
* A typical case would be PHPUnit, where it would need to make sure it reads all
|
||||
* the data it needs from this class, then call reload() with
|
||||
* `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
|
||||
* the project in which it runs can then also use this class safely, without
|
||||
* interference between PHPUnit's dependencies and the project's dependencies.
|
||||
*
|
||||
* @param array[] $data A vendor/composer/installed.php data set
|
||||
* @return void
|
||||
*
|
||||
* @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
|
||||
*/
|
||||
public static function reload($data)
|
||||
{
|
||||
self::$installed = $data;
|
||||
self::$installedByVendor = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array[]
|
||||
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
|
||||
*/
|
||||
private static function getInstalled()
|
||||
{
|
||||
if (null === self::$canGetVendors) {
|
||||
self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
|
||||
}
|
||||
|
||||
$installed = array();
|
||||
|
||||
if (self::$canGetVendors) {
|
||||
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
|
||||
if (isset(self::$installedByVendor[$vendorDir])) {
|
||||
$installed[] = self::$installedByVendor[$vendorDir];
|
||||
} elseif (is_file($vendorDir.'/composer/installed.php')) {
|
||||
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
|
||||
$required = require $vendorDir.'/composer/installed.php';
|
||||
$installed[] = self::$installedByVendor[$vendorDir] = $required;
|
||||
if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
|
||||
self::$installed = $installed[count($installed) - 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (null === self::$installed) {
|
||||
// only require the installed.php file if this file is loaded from its dumped location,
|
||||
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
|
||||
if (substr(__DIR__, -8, 1) !== 'C') {
|
||||
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
|
||||
$required = require __DIR__ . '/installed.php';
|
||||
self::$installed = $required;
|
||||
} else {
|
||||
self::$installed = array();
|
||||
}
|
||||
}
|
||||
|
||||
if (self::$installed !== array()) {
|
||||
$installed[] = self::$installed;
|
||||
}
|
||||
|
||||
return $installed;
|
||||
}
|
||||
}
|
||||
21
vendor/composer/LICENSE
vendored
Normal file
21
vendor/composer/LICENSE
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
|
||||
Copyright (c) Nils Adermann, Jordi Boggiano
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
10
vendor/composer/autoload_classmap.php
vendored
Normal file
10
vendor/composer/autoload_classmap.php
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
// autoload_classmap.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
|
||||
);
|
||||
9
vendor/composer/autoload_namespaces.php
vendored
Normal file
9
vendor/composer/autoload_namespaces.php
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
// autoload_namespaces.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
);
|
||||
12
vendor/composer/autoload_psr4.php
vendored
Normal file
12
vendor/composer/autoload_psr4.php
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
// autoload_psr4.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'Symfony\\Flex\\' => array($vendorDir . '/symfony/flex/src'),
|
||||
'App\\Tests\\' => array($baseDir . '/tests'),
|
||||
'App\\' => array($baseDir . '/src'),
|
||||
);
|
||||
38
vendor/composer/autoload_real.php
vendored
Normal file
38
vendor/composer/autoload_real.php
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
// autoload_real.php @generated by Composer
|
||||
|
||||
class ComposerAutoloaderInit54c05e64f01f49eb136e9af7b3075bdd
|
||||
{
|
||||
private static $loader;
|
||||
|
||||
public static function loadClassLoader($class)
|
||||
{
|
||||
if ('Composer\Autoload\ClassLoader' === $class) {
|
||||
require __DIR__ . '/ClassLoader.php';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Composer\Autoload\ClassLoader
|
||||
*/
|
||||
public static function getLoader()
|
||||
{
|
||||
if (null !== self::$loader) {
|
||||
return self::$loader;
|
||||
}
|
||||
|
||||
require __DIR__ . '/platform_check.php';
|
||||
|
||||
spl_autoload_register(array('ComposerAutoloaderInit54c05e64f01f49eb136e9af7b3075bdd', 'loadClassLoader'), true, true);
|
||||
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
|
||||
spl_autoload_unregister(array('ComposerAutoloaderInit54c05e64f01f49eb136e9af7b3075bdd', 'loadClassLoader'));
|
||||
|
||||
require __DIR__ . '/autoload_static.php';
|
||||
call_user_func(\Composer\Autoload\ComposerStaticInit54c05e64f01f49eb136e9af7b3075bdd::getInitializer($loader));
|
||||
|
||||
$loader->register(true);
|
||||
|
||||
return $loader;
|
||||
}
|
||||
}
|
||||
49
vendor/composer/autoload_static.php
vendored
Normal file
49
vendor/composer/autoload_static.php
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
// autoload_static.php @generated by Composer
|
||||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
class ComposerStaticInit54c05e64f01f49eb136e9af7b3075bdd
|
||||
{
|
||||
public static $prefixLengthsPsr4 = array (
|
||||
'S' =>
|
||||
array (
|
||||
'Symfony\\Flex\\' => 13,
|
||||
),
|
||||
'A' =>
|
||||
array (
|
||||
'App\\Tests\\' => 10,
|
||||
'App\\' => 4,
|
||||
),
|
||||
);
|
||||
|
||||
public static $prefixDirsPsr4 = array (
|
||||
'Symfony\\Flex\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/symfony/flex/src',
|
||||
),
|
||||
'App\\Tests\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/../..' . '/tests',
|
||||
),
|
||||
'App\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/../..' . '/src',
|
||||
),
|
||||
);
|
||||
|
||||
public static $classMap = array (
|
||||
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
|
||||
);
|
||||
|
||||
public static function getInitializer(ClassLoader $loader)
|
||||
{
|
||||
return \Closure::bind(function () use ($loader) {
|
||||
$loader->prefixLengthsPsr4 = ComposerStaticInit54c05e64f01f49eb136e9af7b3075bdd::$prefixLengthsPsr4;
|
||||
$loader->prefixDirsPsr4 = ComposerStaticInit54c05e64f01f49eb136e9af7b3075bdd::$prefixDirsPsr4;
|
||||
$loader->classMap = ComposerStaticInit54c05e64f01f49eb136e9af7b3075bdd::$classMap;
|
||||
|
||||
}, null, ClassLoader::class);
|
||||
}
|
||||
}
|
||||
77
vendor/composer/installed.json
vendored
Normal file
77
vendor/composer/installed.json
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
{
|
||||
"packages": [
|
||||
{
|
||||
"name": "symfony/flex",
|
||||
"version": "v2.4.7",
|
||||
"version_normalized": "2.4.7.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/flex.git",
|
||||
"reference": "92f4fba342161ff36072bd3b8e0b3c6c23160402"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/flex/zipball/92f4fba342161ff36072bd3b8e0b3c6c23160402",
|
||||
"reference": "92f4fba342161ff36072bd3b8e0b3c6c23160402",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"composer-plugin-api": "^2.1",
|
||||
"php": ">=8.0"
|
||||
},
|
||||
"conflict": {
|
||||
"composer/semver": "<1.7.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"composer/composer": "^2.1",
|
||||
"symfony/dotenv": "^5.4|^6.0",
|
||||
"symfony/filesystem": "^5.4|^6.0",
|
||||
"symfony/phpunit-bridge": "^5.4|^6.0",
|
||||
"symfony/process": "^5.4|^6.0"
|
||||
},
|
||||
"time": "2024-10-07T08:51:54+00:00",
|
||||
"type": "composer-plugin",
|
||||
"extra": {
|
||||
"class": "Symfony\\Flex\\Flex"
|
||||
},
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Flex\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien.potencier@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "Composer plugin for Symfony",
|
||||
"support": {
|
||||
"issues": "https://github.com/symfony/flex/issues",
|
||||
"source": "https://github.com/symfony/flex/tree/v2.4.7"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"install-path": "../symfony/flex"
|
||||
}
|
||||
],
|
||||
"dev": true,
|
||||
"dev-package-names": []
|
||||
}
|
||||
74
vendor/composer/installed.php
vendored
Normal file
74
vendor/composer/installed.php
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php return array(
|
||||
'root' => array(
|
||||
'name' => 'symfony/skeleton',
|
||||
'pretty_version' => 'v6.4.99',
|
||||
'version' => '6.4.99.0',
|
||||
'reference' => null,
|
||||
'type' => 'project',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
'dev' => true,
|
||||
),
|
||||
'versions' => array(
|
||||
'symfony/flex' => array(
|
||||
'pretty_version' => 'v2.4.7',
|
||||
'version' => '2.4.7.0',
|
||||
'reference' => '92f4fba342161ff36072bd3b8e0b3c6c23160402',
|
||||
'type' => 'composer-plugin',
|
||||
'install_path' => __DIR__ . '/../symfony/flex',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'symfony/polyfill-ctype' => array(
|
||||
'dev_requirement' => false,
|
||||
'replaced' => array(
|
||||
0 => '*',
|
||||
),
|
||||
),
|
||||
'symfony/polyfill-iconv' => array(
|
||||
'dev_requirement' => false,
|
||||
'replaced' => array(
|
||||
0 => '*',
|
||||
),
|
||||
),
|
||||
'symfony/polyfill-php72' => array(
|
||||
'dev_requirement' => false,
|
||||
'replaced' => array(
|
||||
0 => '*',
|
||||
),
|
||||
),
|
||||
'symfony/polyfill-php73' => array(
|
||||
'dev_requirement' => false,
|
||||
'replaced' => array(
|
||||
0 => '*',
|
||||
),
|
||||
),
|
||||
'symfony/polyfill-php74' => array(
|
||||
'dev_requirement' => false,
|
||||
'replaced' => array(
|
||||
0 => '*',
|
||||
),
|
||||
),
|
||||
'symfony/polyfill-php80' => array(
|
||||
'dev_requirement' => false,
|
||||
'replaced' => array(
|
||||
0 => '*',
|
||||
),
|
||||
),
|
||||
'symfony/polyfill-php81' => array(
|
||||
'dev_requirement' => false,
|
||||
'replaced' => array(
|
||||
0 => '*',
|
||||
),
|
||||
),
|
||||
'symfony/skeleton' => array(
|
||||
'pretty_version' => 'v6.4.99',
|
||||
'version' => '6.4.99.0',
|
||||
'reference' => null,
|
||||
'type' => 'project',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
),
|
||||
);
|
||||
26
vendor/composer/platform_check.php
vendored
Normal file
26
vendor/composer/platform_check.php
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
// platform_check.php @generated by Composer
|
||||
|
||||
$issues = array();
|
||||
|
||||
if (!(PHP_VERSION_ID >= 80100)) {
|
||||
$issues[] = 'Your Composer dependencies require a PHP version ">= 8.1.0". You are running ' . PHP_VERSION . '.';
|
||||
}
|
||||
|
||||
if ($issues) {
|
||||
if (!headers_sent()) {
|
||||
header('HTTP/1.1 500 Internal Server Error');
|
||||
}
|
||||
if (!ini_get('display_errors')) {
|
||||
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
|
||||
fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
|
||||
} elseif (!headers_sent()) {
|
||||
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
|
||||
}
|
||||
}
|
||||
trigger_error(
|
||||
'Composer detected issues in your platform: ' . implode(' ', $issues),
|
||||
E_USER_ERROR
|
||||
);
|
||||
}
|
||||
19
vendor/symfony/flex/LICENSE
vendored
Normal file
19
vendor/symfony/flex/LICENSE
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2016-2019 Fabien Potencier
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
10
vendor/symfony/flex/README.md
vendored
Normal file
10
vendor/symfony/flex/README.md
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
<p align="center"><a href="https://symfony.com" target="_blank">
|
||||
<img src="https://symfony.com/logos/symfony_black_02.svg">
|
||||
</a></p>
|
||||
|
||||
[Symfony Flex][1] helps developers create [Symfony][2] applications, from the most
|
||||
simple micro-style projects to the more complex ones with dozens of
|
||||
dependencies.
|
||||
|
||||
[1]: https://symfony.com/doc/current/setup/flex.html
|
||||
[2]: https://symfony.com
|
||||
35
vendor/symfony/flex/composer.json
vendored
Normal file
35
vendor/symfony/flex/composer.json
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "symfony/flex",
|
||||
"type": "composer-plugin",
|
||||
"description": "Composer plugin for Symfony",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien.potencier@gmail.com"
|
||||
}
|
||||
],
|
||||
"minimum-stability": "dev",
|
||||
"require": {
|
||||
"php": ">=8.0",
|
||||
"composer-plugin-api": "^2.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"composer/composer": "^2.1",
|
||||
"symfony/dotenv": "^5.4|^6.0",
|
||||
"symfony/filesystem": "^5.4|^6.0",
|
||||
"symfony/phpunit-bridge": "^5.4|^6.0",
|
||||
"symfony/process": "^5.4|^6.0"
|
||||
},
|
||||
"conflict": {
|
||||
"composer/semver": "<1.7.2"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Flex\\": "src"
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"class": "Symfony\\Flex\\Flex"
|
||||
}
|
||||
}
|
||||
147
vendor/symfony/flex/src/Command/DumpEnvCommand.php
vendored
Normal file
147
vendor/symfony/flex/src/Command/DumpEnvCommand.php
vendored
Normal file
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex\Command;
|
||||
|
||||
use Composer\Command\BaseCommand;
|
||||
use Composer\Config;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Dotenv\Dotenv;
|
||||
use Symfony\Flex\Options;
|
||||
|
||||
class DumpEnvCommand extends BaseCommand
|
||||
{
|
||||
private $config;
|
||||
private $options;
|
||||
|
||||
public function __construct(Config $config, Options $options)
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->options = $options;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('symfony:dump-env')
|
||||
->setAliases(['dump-env'])
|
||||
->setDescription('Compiles .env files to .env.local.php.')
|
||||
->setDefinition([
|
||||
new InputArgument('env', InputArgument::OPTIONAL, 'The application environment to dump .env files for - e.g. "prod".'),
|
||||
])
|
||||
->addOption('empty', null, InputOption::VALUE_NONE, 'Ignore the content of .env files')
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$runtime = $this->options->get('runtime') ?? [];
|
||||
$envKey = $runtime['env_var_name'] ?? 'APP_ENV';
|
||||
|
||||
if ($env = $input->getArgument('env') ?? $runtime['env'] ?? null) {
|
||||
$_SERVER[$envKey] = $env;
|
||||
}
|
||||
|
||||
$path = $this->options->get('root-dir').'/'.($runtime['dotenv_path'] ?? '.env');
|
||||
|
||||
if (!$env || !$input->getOption('empty')) {
|
||||
$vars = $this->loadEnv($path, $env, $runtime);
|
||||
$env = $vars[$envKey];
|
||||
}
|
||||
|
||||
if ($input->getOption('empty')) {
|
||||
$vars = [$envKey => $env];
|
||||
}
|
||||
|
||||
$vars = var_export($vars, true);
|
||||
$vars = <<<EOF
|
||||
<?php
|
||||
|
||||
// This file was generated by running "composer dump-env $env"
|
||||
|
||||
return $vars;
|
||||
|
||||
EOF;
|
||||
file_put_contents($path.'.local.php', $vars, \LOCK_EX);
|
||||
|
||||
$this->getIO()->writeError('Successfully dumped .env files in <info>.env.local.php</>');
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function loadEnv(string $path, ?string $env, array $runtime): array
|
||||
{
|
||||
if (!file_exists($autoloadFile = $this->config->get('vendor-dir').'/autoload.php')) {
|
||||
throw new \RuntimeException(sprintf('Please run "composer install" before running this command: "%s" not found.', $autoloadFile));
|
||||
}
|
||||
|
||||
require $autoloadFile;
|
||||
|
||||
if (!class_exists(Dotenv::class)) {
|
||||
throw new \RuntimeException('Please run "composer require symfony/dotenv" to load the ".env" files configuring the application.');
|
||||
}
|
||||
|
||||
$envKey = $runtime['env_var_name'] ?? 'APP_ENV';
|
||||
$globalsBackup = [$_SERVER, $_ENV];
|
||||
unset($_SERVER[$envKey]);
|
||||
$_ENV = [$envKey => $env];
|
||||
$_SERVER['SYMFONY_DOTENV_VARS'] = implode(',', array_keys($_SERVER));
|
||||
putenv('SYMFONY_DOTENV_VARS='.$_SERVER['SYMFONY_DOTENV_VARS']);
|
||||
|
||||
try {
|
||||
if (method_exists(Dotenv::class, 'usePutenv')) {
|
||||
$dotenv = new Dotenv();
|
||||
} else {
|
||||
$dotenv = new Dotenv(false);
|
||||
}
|
||||
|
||||
if (!$env && file_exists($p = "$path.local")) {
|
||||
$env = $_ENV[$envKey] = $dotenv->parse(file_get_contents($p), $p)[$envKey] ?? null;
|
||||
}
|
||||
|
||||
if (!$env) {
|
||||
throw new \RuntimeException(sprintf('Please provide the name of the environment either by passing it as command line argument or by defining the "%s" variable in the ".env.local" file.', $envKey));
|
||||
}
|
||||
|
||||
$testEnvs = $runtime['test_envs'] ?? ['test'];
|
||||
|
||||
if (method_exists($dotenv, 'loadEnv')) {
|
||||
$dotenv->loadEnv($path, $envKey, 'dev', $testEnvs);
|
||||
} else {
|
||||
// fallback code in case your Dotenv component is not 4.2 or higher (when loadEnv() was added)
|
||||
$dotenv->load(file_exists($path) || !file_exists($p = "$path.dist") ? $path : $p);
|
||||
|
||||
if (!\in_array($env, $testEnvs, true) && file_exists($p = "$path.local")) {
|
||||
$dotenv->load($p);
|
||||
}
|
||||
|
||||
if (file_exists($p = "$path.$env")) {
|
||||
$dotenv->load($p);
|
||||
}
|
||||
|
||||
if (file_exists($p = "$path.$env.local")) {
|
||||
$dotenv->load($p);
|
||||
}
|
||||
}
|
||||
|
||||
unset($_ENV['SYMFONY_DOTENV_VARS']);
|
||||
$env = $_ENV;
|
||||
} finally {
|
||||
list($_SERVER, $_ENV) = $globalsBackup;
|
||||
}
|
||||
|
||||
return $env;
|
||||
}
|
||||
}
|
||||
181
vendor/symfony/flex/src/Command/InstallRecipesCommand.php
vendored
Normal file
181
vendor/symfony/flex/src/Command/InstallRecipesCommand.php
vendored
Normal file
@@ -0,0 +1,181 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex\Command;
|
||||
|
||||
use Composer\Command\BaseCommand;
|
||||
use Composer\DependencyResolver\Operation\InstallOperation;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
use Symfony\Component\Console\Exception\RuntimeException;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Flex\Event\UpdateEvent;
|
||||
use Symfony\Flex\Flex;
|
||||
|
||||
class InstallRecipesCommand extends BaseCommand
|
||||
{
|
||||
/** @var Flex */
|
||||
private $flex;
|
||||
private $rootDir;
|
||||
private $dotenvPath;
|
||||
|
||||
public function __construct(/* cannot be type-hinted */ $flex, string $rootDir, string $dotenvPath = '.env')
|
||||
{
|
||||
$this->flex = $flex;
|
||||
$this->rootDir = $rootDir;
|
||||
$this->dotenvPath = $dotenvPath;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('symfony:recipes:install')
|
||||
->setAliases(['recipes:install', 'symfony:sync-recipes', 'sync-recipes', 'fix-recipes'])
|
||||
->setDescription('Installs or reinstalls recipes for already installed packages.')
|
||||
->addArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Recipes that should be installed.')
|
||||
->addOption('force', null, InputOption::VALUE_NONE, 'Overwrite existing files when a new version of a recipe is available')
|
||||
->addOption('reset', null, InputOption::VALUE_NONE, 'Reset all recipes back to their initial state (should be combined with --force)')
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$win = '\\' === \DIRECTORY_SEPARATOR;
|
||||
$force = (bool) $input->getOption('force');
|
||||
|
||||
if ($force && !@is_executable(strtok(exec($win ? 'where git' : 'command -v git'), \PHP_EOL))) {
|
||||
throw new RuntimeException('Cannot run "sync-recipes --force": git not found.');
|
||||
}
|
||||
|
||||
$symfonyLock = $this->flex->getLock();
|
||||
$composer = $this->getComposer();
|
||||
$locker = $composer->getLocker();
|
||||
$lockData = $locker->getLockData();
|
||||
|
||||
$packages = [];
|
||||
$totalPackages = [];
|
||||
foreach ($lockData['packages'] as $pkg) {
|
||||
$totalPackages[] = $pkg['name'];
|
||||
if ($force || !$symfonyLock->has($pkg['name'])) {
|
||||
$packages[] = $pkg['name'];
|
||||
}
|
||||
}
|
||||
foreach ($lockData['packages-dev'] as $pkg) {
|
||||
$totalPackages[] = $pkg['name'];
|
||||
if ($force || !$symfonyLock->has($pkg['name'])) {
|
||||
$packages[] = $pkg['name'];
|
||||
}
|
||||
}
|
||||
|
||||
$io = $this->getIO();
|
||||
|
||||
if (!$io->isVerbose()) {
|
||||
$io->writeError([
|
||||
'Run command with <info>-v</info> to see more details',
|
||||
'',
|
||||
]);
|
||||
}
|
||||
|
||||
if ($targetPackages = $input->getArgument('packages')) {
|
||||
if ($invalidPackages = array_diff($targetPackages, $totalPackages)) {
|
||||
$io->writeError(sprintf('<warning>Cannot update: some packages are not installed:</warning> %s', implode(', ', $invalidPackages)));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ($packagesRequiringForce = array_diff($targetPackages, $packages)) {
|
||||
$io->writeError(sprintf('Recipe(s) already installed for: <info>%s</info>', implode(', ', $packagesRequiringForce)));
|
||||
$io->writeError('Re-run the command with <info>--force</info> to re-install the recipes.');
|
||||
$io->writeError('');
|
||||
}
|
||||
|
||||
$packages = array_diff($targetPackages, $packagesRequiringForce);
|
||||
}
|
||||
|
||||
if (!$packages) {
|
||||
$io->writeError('No recipes to install.');
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
$composer = $this->getComposer();
|
||||
$installedRepo = $composer->getRepositoryManager()->getLocalRepository();
|
||||
|
||||
$operations = [];
|
||||
foreach ($packages as $package) {
|
||||
if (null === $pkg = $installedRepo->findPackage($package, '*')) {
|
||||
$io->writeError(sprintf('<error>Package %s is not installed</>', $package));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
$operations[] = new InstallOperation($pkg);
|
||||
}
|
||||
|
||||
$dotenvFile = $this->dotenvPath;
|
||||
$dotenvPath = $this->rootDir.'/'.$dotenvFile;
|
||||
|
||||
if ($createEnvLocal = $force && file_exists($dotenvPath) && file_exists($dotenvPath.'.dist') && !file_exists($dotenvPath.'.local')) {
|
||||
rename($dotenvPath, $dotenvPath.'.local');
|
||||
$pipes = [];
|
||||
proc_close(proc_open(sprintf('git mv %s %s > %s 2>&1 || %s %1$s %2$s', ProcessExecutor::escape($dotenvFile.'.dist'), ProcessExecutor::escape($dotenvFile), $win ? 'NUL' : '/dev/null', $win ? 'rename' : 'mv'), $pipes, $pipes, $this->rootDir));
|
||||
if (file_exists($this->rootDir.'/phpunit.xml.dist')) {
|
||||
touch($dotenvPath.'.test');
|
||||
}
|
||||
}
|
||||
|
||||
$this->flex->update(new UpdateEvent($force, (bool) $input->getOption('reset')), $operations);
|
||||
|
||||
if ($force) {
|
||||
$output = [
|
||||
'',
|
||||
'<bg=blue;fg=white> </>',
|
||||
'<bg=blue;fg=white> Files have been reset to the latest version of the recipe. </>',
|
||||
'<bg=blue;fg=white> </>',
|
||||
'',
|
||||
' * Use <comment>git diff</> to inspect the changes.',
|
||||
'',
|
||||
' Not all of the changes will be relevant to your app: you now',
|
||||
' need to selectively add or revert them using e.g. a combination',
|
||||
' of <comment>git add -p</> and <comment>git checkout -p</>',
|
||||
'',
|
||||
];
|
||||
|
||||
if ($createEnvLocal) {
|
||||
$output[] = ' Dotenv files have been renamed: .env -> .env.local and .env.dist -> .env';
|
||||
$output[] = ' See https://symfony.com/doc/current/configuration/dot-env-changes.html';
|
||||
$output[] = '';
|
||||
}
|
||||
|
||||
$output[] = ' * Use <comment>git checkout .</> to revert the changes.';
|
||||
$output[] = '';
|
||||
|
||||
if ($createEnvLocal) {
|
||||
$root = '.' !== $this->rootDir ? $this->rootDir.'/' : '';
|
||||
$output[] = ' To revert the changes made to .env files, run';
|
||||
$output[] = sprintf(' <comment>git mv %s %s</> && <comment>%s %s %1$s</>', ProcessExecutor::escape($root.$dotenvFile), ProcessExecutor::escape($root.$dotenvFile.'.dist'), $win ? 'rename' : 'mv', ProcessExecutor::escape($root.$dotenvFile.'.local'));
|
||||
$output[] = '';
|
||||
}
|
||||
|
||||
$output[] = ' New (untracked) files can be inspected using <comment>git clean --dry-run</>';
|
||||
$output[] = ' Add the new files you want to keep using <comment>git add</>';
|
||||
$output[] = ' then delete the rest using <comment>git clean --force</>';
|
||||
$output[] = '';
|
||||
|
||||
$io->write($output);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
344
vendor/symfony/flex/src/Command/RecipesCommand.php
vendored
Normal file
344
vendor/symfony/flex/src/Command/RecipesCommand.php
vendored
Normal file
@@ -0,0 +1,344 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex\Command;
|
||||
|
||||
use Composer\Command\BaseCommand;
|
||||
use Composer\Downloader\TransportException;
|
||||
use Composer\Package\Package;
|
||||
use Composer\Util\HttpDownloader;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Flex\GithubApi;
|
||||
use Symfony\Flex\InformationOperation;
|
||||
use Symfony\Flex\Lock;
|
||||
use Symfony\Flex\Recipe;
|
||||
|
||||
/**
|
||||
* @author Maxime Hélias <maximehelias16@gmail.com>
|
||||
*/
|
||||
class RecipesCommand extends BaseCommand
|
||||
{
|
||||
/** @var \Symfony\Flex\Flex */
|
||||
private $flex;
|
||||
|
||||
private Lock $symfonyLock;
|
||||
private GithubApi $githubApi;
|
||||
|
||||
public function __construct(/* cannot be type-hinted */ $flex, Lock $symfonyLock, HttpDownloader $downloader)
|
||||
{
|
||||
$this->flex = $flex;
|
||||
$this->symfonyLock = $symfonyLock;
|
||||
$this->githubApi = new GithubApi($downloader);
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('symfony:recipes')
|
||||
->setAliases(['recipes'])
|
||||
->setDescription('Shows information about all available recipes.')
|
||||
->setDefinition([
|
||||
new InputArgument('package', InputArgument::OPTIONAL, 'Package to inspect, if not provided all packages are.'),
|
||||
])
|
||||
->addOption('outdated', 'o', InputOption::VALUE_NONE, 'Show only recipes that are outdated')
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$installedRepo = $this->getComposer()->getRepositoryManager()->getLocalRepository();
|
||||
|
||||
// Inspect one or all packages
|
||||
$package = $input->getArgument('package');
|
||||
if (null !== $package) {
|
||||
$packages = [strtolower($package)];
|
||||
} else {
|
||||
$locker = $this->getComposer()->getLocker();
|
||||
$lockData = $locker->getLockData();
|
||||
|
||||
// Merge all packages installed
|
||||
$packages = array_column(array_merge($lockData['packages'], $lockData['packages-dev']), 'name');
|
||||
$packages = array_unique(array_merge($packages, array_keys($this->symfonyLock->all())));
|
||||
}
|
||||
|
||||
$operations = [];
|
||||
foreach ($packages as $name) {
|
||||
$pkg = $installedRepo->findPackage($name, '*');
|
||||
|
||||
if (!$pkg && $this->symfonyLock->has($name)) {
|
||||
$pkgVersion = $this->symfonyLock->get($name)['version'];
|
||||
$pkg = new Package($name, $pkgVersion, $pkgVersion);
|
||||
} elseif (!$pkg) {
|
||||
$this->getIO()->writeError(sprintf('<error>Package %s is not installed</error>', $name));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$operations[] = new InformationOperation($pkg);
|
||||
}
|
||||
|
||||
$recipes = $this->flex->fetchRecipes($operations, false);
|
||||
ksort($recipes);
|
||||
|
||||
$nbRecipe = \count($recipes);
|
||||
if ($nbRecipe <= 0) {
|
||||
$this->getIO()->writeError('<error>No recipe found</error>');
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Display the information about a specific recipe
|
||||
if (1 === $nbRecipe) {
|
||||
$this->displayPackageInformation(current($recipes));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
$outdated = $input->getOption('outdated');
|
||||
|
||||
$write = [];
|
||||
$hasOutdatedRecipes = false;
|
||||
foreach ($recipes as $name => $recipe) {
|
||||
$lockRef = $this->symfonyLock->get($name)['recipe']['ref'] ?? null;
|
||||
|
||||
$additional = null;
|
||||
if (null === $lockRef && null !== $recipe->getRef()) {
|
||||
$additional = '<comment>(recipe not installed)</comment>';
|
||||
} elseif ($recipe->getRef() !== $lockRef && !$recipe->isAuto()) {
|
||||
$additional = '<comment>(update available)</comment>';
|
||||
}
|
||||
|
||||
if ($outdated && null === $additional) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$hasOutdatedRecipes = true;
|
||||
$write[] = sprintf(' * %s %s', $name, $additional);
|
||||
}
|
||||
|
||||
// Nothing to display
|
||||
if (!$hasOutdatedRecipes) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$this->getIO()->write(array_merge([
|
||||
'',
|
||||
'<bg=blue;fg=white> </>',
|
||||
sprintf('<bg=blue;fg=white> %s recipes. </>', $outdated ? ' Outdated' : 'Available'),
|
||||
'<bg=blue;fg=white> </>',
|
||||
'',
|
||||
], $write, [
|
||||
'',
|
||||
'Run:',
|
||||
' * <info>composer recipes vendor/package</info> to see details about a recipe.',
|
||||
' * <info>composer recipes:update vendor/package</info> to update that recipe.',
|
||||
'',
|
||||
]));
|
||||
|
||||
if ($outdated) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function displayPackageInformation(Recipe $recipe)
|
||||
{
|
||||
$io = $this->getIO();
|
||||
$recipeLock = $this->symfonyLock->get($recipe->getName());
|
||||
|
||||
$lockRef = $recipeLock['recipe']['ref'] ?? null;
|
||||
$lockRepo = $recipeLock['recipe']['repo'] ?? null;
|
||||
$lockFiles = $recipeLock['files'] ?? null;
|
||||
$lockBranch = $recipeLock['recipe']['branch'] ?? null;
|
||||
$lockVersion = $recipeLock['recipe']['version'] ?? $recipeLock['version'] ?? null;
|
||||
|
||||
if ('master' === $lockBranch && \in_array($lockRepo, ['github.com/symfony/recipes', 'github.com/symfony/recipes-contrib'])) {
|
||||
$lockBranch = 'main';
|
||||
}
|
||||
|
||||
$status = '<comment>up to date</comment>';
|
||||
if ($recipe->isAuto()) {
|
||||
$status = '<comment>auto-generated recipe</comment>';
|
||||
} elseif (null === $lockRef && null !== $recipe->getRef()) {
|
||||
$status = '<comment>recipe not installed</comment>';
|
||||
} elseif ($recipe->getRef() !== $lockRef) {
|
||||
$status = '<comment>update available</comment>';
|
||||
}
|
||||
|
||||
$gitSha = null;
|
||||
$commitDate = null;
|
||||
if (null !== $lockRef && null !== $lockRepo) {
|
||||
try {
|
||||
$recipeCommitData = $this->githubApi->findRecipeCommitDataFromTreeRef(
|
||||
$recipe->getName(),
|
||||
$lockRepo,
|
||||
$lockBranch ?? '',
|
||||
$lockVersion,
|
||||
$lockRef
|
||||
);
|
||||
$gitSha = $recipeCommitData ? $recipeCommitData['commit'] : null;
|
||||
$commitDate = $recipeCommitData ? $recipeCommitData['date'] : null;
|
||||
} catch (TransportException $exception) {
|
||||
$io->writeError('Error downloading exact git sha for installed recipe.');
|
||||
}
|
||||
}
|
||||
|
||||
$io->write('<info>name</info> : '.$recipe->getName());
|
||||
$io->write('<info>version</info> : '.($lockVersion ?? 'n/a'));
|
||||
$io->write('<info>status</info> : '.$status);
|
||||
if (!$recipe->isAuto() && null !== $lockVersion) {
|
||||
$recipeUrl = sprintf(
|
||||
'https://%s/tree/%s/%s/%s',
|
||||
$lockRepo,
|
||||
// if something fails, default to the branch as the closest "sha"
|
||||
$gitSha ?? $lockBranch,
|
||||
$recipe->getName(),
|
||||
$lockVersion
|
||||
);
|
||||
|
||||
$io->write('<info>installed recipe</info> : '.$recipeUrl);
|
||||
}
|
||||
|
||||
if ($lockRef !== $recipe->getRef()) {
|
||||
$io->write('<info>latest recipe</info> : '.$recipe->getURL());
|
||||
}
|
||||
|
||||
if ($lockRef !== $recipe->getRef() && null !== $lockVersion) {
|
||||
$historyUrl = sprintf(
|
||||
'https://%s/commits/%s/%s',
|
||||
$lockRepo,
|
||||
$lockBranch,
|
||||
$recipe->getName()
|
||||
);
|
||||
|
||||
// show commits since one second after the currently-installed recipe
|
||||
if (null !== $commitDate) {
|
||||
$historyUrl .= '?since=';
|
||||
$historyUrl .= (new \DateTime($commitDate))
|
||||
->setTimezone(new \DateTimeZone('UTC'))
|
||||
->modify('+1 seconds')
|
||||
->format('Y-m-d\TH:i:s\Z');
|
||||
}
|
||||
|
||||
$io->write('<info>recipe history</info> : '.$historyUrl);
|
||||
}
|
||||
|
||||
if (null !== $lockFiles) {
|
||||
$io->write('<info>files</info> : ');
|
||||
$io->write('');
|
||||
|
||||
$tree = $this->generateFilesTree($lockFiles);
|
||||
|
||||
$this->displayFilesTree($tree);
|
||||
}
|
||||
|
||||
if ($lockRef !== $recipe->getRef()) {
|
||||
$io->write([
|
||||
'',
|
||||
'Update this recipe by running:',
|
||||
sprintf('<info>composer recipes:update %s</info>', $recipe->getName()),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
private function generateFilesTree(array $files): array
|
||||
{
|
||||
$tree = [];
|
||||
foreach ($files as $file) {
|
||||
$path = explode('/', $file);
|
||||
|
||||
$tree = array_merge_recursive($tree, $this->addNode($path));
|
||||
}
|
||||
|
||||
return $tree;
|
||||
}
|
||||
|
||||
private function addNode(array $node): array
|
||||
{
|
||||
$current = array_shift($node);
|
||||
|
||||
$subTree = [];
|
||||
if (null !== $current) {
|
||||
$subTree[$current] = $this->addNode($node);
|
||||
}
|
||||
|
||||
return $subTree;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note : We do not display file modification information with Configurator like ComposerScripts, Container, DockerComposer, Dockerfile, Env, Gitignore and Makefile.
|
||||
*/
|
||||
private function displayFilesTree(array $tree)
|
||||
{
|
||||
end($tree);
|
||||
$endKey = key($tree);
|
||||
foreach ($tree as $dir => $files) {
|
||||
$treeBar = '├';
|
||||
$total = \count($files);
|
||||
if (0 === $total || $endKey === $dir) {
|
||||
$treeBar = '└';
|
||||
}
|
||||
|
||||
$info = sprintf(
|
||||
'%s──%s',
|
||||
$treeBar,
|
||||
$dir
|
||||
);
|
||||
$this->writeTreeLine($info);
|
||||
|
||||
$treeBar = str_replace('└', ' ', $treeBar);
|
||||
|
||||
$this->displayTree($files, $treeBar);
|
||||
}
|
||||
}
|
||||
|
||||
private function displayTree(array $tree, $previousTreeBar = '├', $level = 1)
|
||||
{
|
||||
$previousTreeBar = str_replace('├', '│', $previousTreeBar);
|
||||
$treeBar = $previousTreeBar.' ├';
|
||||
|
||||
$i = 0;
|
||||
$total = \count($tree);
|
||||
|
||||
foreach ($tree as $dir => $files) {
|
||||
++$i;
|
||||
if ($i === $total) {
|
||||
$treeBar = $previousTreeBar.' └';
|
||||
}
|
||||
|
||||
$info = sprintf(
|
||||
'%s──%s',
|
||||
$treeBar,
|
||||
$dir
|
||||
);
|
||||
$this->writeTreeLine($info);
|
||||
|
||||
$treeBar = str_replace('└', ' ', $treeBar);
|
||||
|
||||
$this->displayTree($files, $treeBar, $level + 1);
|
||||
}
|
||||
}
|
||||
|
||||
private function writeTreeLine($line)
|
||||
{
|
||||
$io = $this->getIO();
|
||||
if (!$io->isDecorated()) {
|
||||
$line = str_replace(['└', '├', '──', '│'], ['`-', '|-', '-', '|'], $line);
|
||||
}
|
||||
|
||||
$io->write($line);
|
||||
}
|
||||
}
|
||||
415
vendor/symfony/flex/src/Command/UpdateRecipesCommand.php
vendored
Normal file
415
vendor/symfony/flex/src/Command/UpdateRecipesCommand.php
vendored
Normal file
@@ -0,0 +1,415 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex\Command;
|
||||
|
||||
use Composer\Command\BaseCommand;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Package\Package;
|
||||
use Composer\Package\PackageInterface;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
use Symfony\Component\Console\Exception\RuntimeException;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Flex\Configurator;
|
||||
use Symfony\Flex\Downloader;
|
||||
use Symfony\Flex\Flex;
|
||||
use Symfony\Flex\GithubApi;
|
||||
use Symfony\Flex\InformationOperation;
|
||||
use Symfony\Flex\Lock;
|
||||
use Symfony\Flex\Recipe;
|
||||
use Symfony\Flex\Update\RecipePatcher;
|
||||
use Symfony\Flex\Update\RecipeUpdate;
|
||||
|
||||
class UpdateRecipesCommand extends BaseCommand
|
||||
{
|
||||
/** @var Flex */
|
||||
private $flex;
|
||||
private $downloader;
|
||||
private $configurator;
|
||||
private $rootDir;
|
||||
private $githubApi;
|
||||
private $processExecutor;
|
||||
|
||||
public function __construct(/* cannot be type-hinted */ $flex, Downloader $downloader, $httpDownloader, Configurator $configurator, string $rootDir)
|
||||
{
|
||||
$this->flex = $flex;
|
||||
$this->downloader = $downloader;
|
||||
$this->configurator = $configurator;
|
||||
$this->rootDir = $rootDir;
|
||||
$this->githubApi = new GithubApi($httpDownloader);
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('symfony:recipes:update')
|
||||
->setAliases(['recipes:update'])
|
||||
->setDescription('Updates an already-installed recipe to the latest version.')
|
||||
->addArgument('package', InputArgument::OPTIONAL, 'Recipe that should be updated.')
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$win = '\\' === \DIRECTORY_SEPARATOR;
|
||||
$runtimeExceptionClass = class_exists(RuntimeException::class) ? RuntimeException::class : \RuntimeException::class;
|
||||
if (!@is_executable(strtok(exec($win ? 'where git' : 'command -v git'), \PHP_EOL))) {
|
||||
throw new $runtimeExceptionClass('Cannot run "recipes:update": git not found.');
|
||||
}
|
||||
|
||||
$io = $this->getIO();
|
||||
if (!$this->isIndexClean($io)) {
|
||||
$io->write([
|
||||
' Cannot run <comment>recipes:update</comment>: Your git index contains uncommitted changes.',
|
||||
' Please commit or stash them and try again!',
|
||||
]);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
$packageName = $input->getArgument('package');
|
||||
$symfonyLock = $this->flex->getLock();
|
||||
if (!$packageName) {
|
||||
$packageName = $this->askForPackage($io, $symfonyLock);
|
||||
|
||||
if (null === $packageName) {
|
||||
$io->writeError('All packages appear to be up-to-date!');
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$symfonyLock->has($packageName)) {
|
||||
$io->writeError([
|
||||
'Package not found inside symfony.lock. It looks like it\'s not installed?',
|
||||
sprintf('Try running <info>composer recipes:install %s --force -v</info> to re-install the recipe.', $packageName),
|
||||
]);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
$packageLockData = $symfonyLock->get($packageName);
|
||||
if (!isset($packageLockData['recipe'])) {
|
||||
$io->writeError([
|
||||
'It doesn\'t look like this package had a recipe when it was originally installed.',
|
||||
'To install the latest version of the recipe, if there is one, run:',
|
||||
sprintf(' <info>composer recipes:install %s --force -v</info>', $packageName),
|
||||
]);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
$recipeRef = $packageLockData['recipe']['ref'] ?? null;
|
||||
$recipeVersion = $packageLockData['recipe']['version'] ?? null;
|
||||
if (!$recipeRef || !$recipeVersion) {
|
||||
$io->writeError([
|
||||
'The version of the installed recipe was not saved into symfony.lock.',
|
||||
'This is possible if it was installed by an old version of Symfony Flex.',
|
||||
'Update the recipe by re-installing the latest version with:',
|
||||
sprintf(' <info>composer recipes:install %s --force -v</info>', $packageName),
|
||||
]);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
$installedRepo = $this->getComposer()->getRepositoryManager()->getLocalRepository();
|
||||
$package = $installedRepo->findPackage($packageName, '*') ?? new Package($packageName, $packageLockData['version'], $packageLockData['version']);
|
||||
$originalRecipe = $this->getRecipe($package, $recipeRef, $recipeVersion);
|
||||
|
||||
if (null === $originalRecipe) {
|
||||
$io->writeError([
|
||||
'The original recipe version you have installed could not be found, it may be too old.',
|
||||
'Update the recipe by re-installing the latest version with:',
|
||||
sprintf(' <info>composer recipes:install %s --force -v</info>', $packageName),
|
||||
]);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
$newRecipe = $this->getRecipe($package);
|
||||
|
||||
if ($newRecipe->getRef() === $originalRecipe->getRef()) {
|
||||
$io->write(sprintf('This recipe for <info>%s</info> is already at the latest version.', $packageName));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
$io->write([
|
||||
sprintf(' Updating recipe for <info>%s</info>...', $packageName),
|
||||
'',
|
||||
]);
|
||||
|
||||
$recipeUpdate = new RecipeUpdate($originalRecipe, $newRecipe, $symfonyLock, $this->rootDir);
|
||||
$this->configurator->populateUpdate($recipeUpdate);
|
||||
$originalComposerJsonHash = $this->flex->getComposerJsonHash();
|
||||
$patcher = new RecipePatcher($this->rootDir, $io);
|
||||
|
||||
try {
|
||||
$patch = $patcher->generatePatch($recipeUpdate->getOriginalFiles(), $recipeUpdate->getNewFiles());
|
||||
$hasConflicts = !$patcher->applyPatch($patch);
|
||||
} catch (\Throwable $throwable) {
|
||||
$io->writeError([
|
||||
'<bg=red;fg=white>There was an error applying the recipe update patch</>',
|
||||
$throwable->getMessage(),
|
||||
'',
|
||||
'Update the recipe by re-installing the latest version with:',
|
||||
sprintf(' <info>composer recipes:install %s --force -v</info>', $packageName),
|
||||
]);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
$symfonyLock->add($packageName, $newRecipe->getLock());
|
||||
$this->flex->finish($this->rootDir, $originalComposerJsonHash);
|
||||
|
||||
// stage symfony.lock, as all patched files with already be staged
|
||||
$cmdOutput = '';
|
||||
$this->getProcessExecutor()->execute('git add symfony.lock', $cmdOutput, $this->rootDir);
|
||||
|
||||
$io->write([
|
||||
' <bg=blue;fg=white> </>',
|
||||
' <bg=blue;fg=white> Yes! Recipe updated! </>',
|
||||
' <bg=blue;fg=white> </>',
|
||||
'',
|
||||
]);
|
||||
|
||||
if ($hasConflicts) {
|
||||
$io->write([
|
||||
' The recipe was updated but with <bg=red;fg=white>one or more conflicts</>.',
|
||||
' Run <comment>git status</comment> to see them.',
|
||||
' After resolving, commit your changes like normal.',
|
||||
]);
|
||||
} else {
|
||||
if (!$patch->getPatch()) {
|
||||
// no changes were required
|
||||
$io->write([
|
||||
' No files were changed as a result of the update.',
|
||||
]);
|
||||
} else {
|
||||
$io->write([
|
||||
' Run <comment>git status</comment> or <comment>git diff --cached</comment> to see the changes.',
|
||||
' When you\'re ready, commit these changes like normal.',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if (0 !== \count($recipeUpdate->getCopyFromPackagePaths())) {
|
||||
$io->write([
|
||||
'',
|
||||
' <bg=red;fg=white>NOTE:</>',
|
||||
' This recipe copies the following paths from the bundle into your app:',
|
||||
]);
|
||||
foreach ($recipeUpdate->getCopyFromPackagePaths() as $source => $target) {
|
||||
$io->write(sprintf(' * %s => %s', $source, $target));
|
||||
}
|
||||
$io->write([
|
||||
'',
|
||||
' The recipe updater has no way of knowing if these files have changed since you originally installed the recipe.',
|
||||
' And so, no updates were made to these paths.',
|
||||
]);
|
||||
}
|
||||
|
||||
if (0 !== \count($patch->getRemovedPatches())) {
|
||||
if (1 === \count($patch->getRemovedPatches())) {
|
||||
$notes = [
|
||||
sprintf(' The file <comment>%s</comment> was not updated because it doesn\'t exist in your app.', array_keys($patch->getRemovedPatches())[0]),
|
||||
];
|
||||
} else {
|
||||
$notes = [' The following files were not updated because they don\'t exist in your app:'];
|
||||
foreach ($patch->getRemovedPatches() as $filename => $contents) {
|
||||
$notes[] = sprintf(' * <comment>%s</comment>', $filename);
|
||||
}
|
||||
}
|
||||
$io->write([
|
||||
'',
|
||||
' <bg=red;fg=white>NOTE:</>',
|
||||
]);
|
||||
$io->write($notes);
|
||||
$io->write('');
|
||||
if ($io->askConfirmation(' Would you like to save the "diff" to a file so you can review it? (Y/n) ')) {
|
||||
$patchFilename = str_replace('/', '.', $packageName).'.updates-for-deleted-files.patch';
|
||||
file_put_contents($this->rootDir.'/'.$patchFilename, implode("\n", $patch->getRemovedPatches()));
|
||||
$io->write([
|
||||
'',
|
||||
sprintf(' Saved diff to <info>%s</info>', $patchFilename),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($patch->getPatch()) {
|
||||
$io->write('');
|
||||
$io->write(' Calculating CHANGELOG...', false);
|
||||
$changelog = $this->generateChangelog($originalRecipe);
|
||||
$io->write("\r", false); // clear current line
|
||||
if ($changelog) {
|
||||
$io->write($changelog);
|
||||
} else {
|
||||
$io->write('No CHANGELOG could be calculated.');
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function getRecipe(PackageInterface $package, ?string $recipeRef = null, ?string $recipeVersion = null): ?Recipe
|
||||
{
|
||||
$operation = new InformationOperation($package);
|
||||
if (null !== $recipeRef) {
|
||||
$operation->setSpecificRecipeVersion($recipeRef, $recipeVersion);
|
||||
}
|
||||
$recipes = $this->downloader->getRecipes([$operation]);
|
||||
|
||||
if (0 === \count($recipes['manifests'] ?? [])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Recipe(
|
||||
$package,
|
||||
$package->getName(),
|
||||
$operation->getOperationType(),
|
||||
$recipes['manifests'][$package->getName()],
|
||||
$recipes['locks'][$package->getName()] ?? []
|
||||
);
|
||||
}
|
||||
|
||||
private function generateChangelog(Recipe $originalRecipe): ?array
|
||||
{
|
||||
$recipeData = $originalRecipe->getLock()['recipe'] ?? null;
|
||||
if (null === $recipeData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!isset($recipeData['ref']) || !isset($recipeData['repo']) || !isset($recipeData['branch']) || !isset($recipeData['version'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$currentRecipeVersionData = $this->githubApi->findRecipeCommitDataFromTreeRef(
|
||||
$originalRecipe->getName(),
|
||||
$recipeData['repo'],
|
||||
$recipeData['branch'],
|
||||
$recipeData['version'],
|
||||
$recipeData['ref']
|
||||
);
|
||||
|
||||
if (!$currentRecipeVersionData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$recipeVersions = $this->githubApi->getVersionsOfRecipe(
|
||||
$recipeData['repo'],
|
||||
$recipeData['branch'],
|
||||
$originalRecipe->getName()
|
||||
);
|
||||
if (!$recipeVersions) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$newerRecipeVersions = array_filter($recipeVersions, function ($version) use ($recipeData) {
|
||||
return version_compare($version, $recipeData['version'], '>');
|
||||
});
|
||||
|
||||
$newCommits = $currentRecipeVersionData['new_commits'];
|
||||
foreach ($newerRecipeVersions as $newerRecipeVersion) {
|
||||
$newCommits = array_merge(
|
||||
$newCommits,
|
||||
$this->githubApi->getCommitDataForPath($recipeData['repo'], $originalRecipe->getName().'/'.$newerRecipeVersion, $recipeData['branch'])
|
||||
);
|
||||
}
|
||||
|
||||
$newCommits = array_unique($newCommits);
|
||||
asort($newCommits);
|
||||
|
||||
$pullRequests = [];
|
||||
foreach ($newCommits as $commit => $date) {
|
||||
$pr = $this->githubApi->getPullRequestForCommit($commit, $recipeData['repo']);
|
||||
if ($pr) {
|
||||
$pullRequests[$pr['number']] = $pr;
|
||||
}
|
||||
}
|
||||
|
||||
$lines = [];
|
||||
// borrowed from symfony/console's OutputFormatterStyle
|
||||
$handlesHrefGracefully = 'JetBrains-JediTerm' !== getenv('TERMINAL_EMULATOR')
|
||||
&& (!getenv('KONSOLE_VERSION') || (int) getenv('KONSOLE_VERSION') > 201100);
|
||||
foreach ($pullRequests as $number => $data) {
|
||||
$url = $data['url'];
|
||||
if ($handlesHrefGracefully) {
|
||||
$url = "\033]8;;$url\033\\$number\033]8;;\033\\";
|
||||
}
|
||||
$lines[] = sprintf(' * %s (PR %s)', $data['title'], $url);
|
||||
}
|
||||
|
||||
return $lines;
|
||||
}
|
||||
|
||||
private function askForPackage(IOInterface $io, Lock $symfonyLock): ?string
|
||||
{
|
||||
$installedRepo = $this->getComposer()->getRepositoryManager()->getLocalRepository();
|
||||
|
||||
$operations = [];
|
||||
foreach ($symfonyLock->all() as $name => $lock) {
|
||||
if (isset($lock['recipe']['ref'])) {
|
||||
$package = $installedRepo->findPackage($name, '*') ?? new Package($name, $lock['version'], $lock['version']);
|
||||
$operations[] = new InformationOperation($package);
|
||||
}
|
||||
}
|
||||
|
||||
$recipes = $this->flex->fetchRecipes($operations, false);
|
||||
ksort($recipes);
|
||||
|
||||
$outdatedRecipes = [];
|
||||
foreach ($recipes as $name => $recipe) {
|
||||
$lockRef = $symfonyLock->get($name)['recipe']['ref'] ?? null;
|
||||
|
||||
if (null !== $lockRef && $recipe->getRef() !== $lockRef && !$recipe->isAuto()) {
|
||||
$outdatedRecipes[] = $name;
|
||||
}
|
||||
}
|
||||
|
||||
if (0 === \count($outdatedRecipes)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$question = 'Which outdated recipe would you like to update? (default: <info>0</info>)';
|
||||
|
||||
$choice = $io->select(
|
||||
$question,
|
||||
$outdatedRecipes,
|
||||
0
|
||||
);
|
||||
|
||||
return $outdatedRecipes[$choice];
|
||||
}
|
||||
|
||||
private function isIndexClean(IOInterface $io): bool
|
||||
{
|
||||
$output = '';
|
||||
|
||||
$this->getProcessExecutor()->execute('git status --porcelain --untracked-files=no', $output, $this->rootDir);
|
||||
if ('' !== trim($output)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function getProcessExecutor(): ProcessExecutor
|
||||
{
|
||||
if (null === $this->processExecutor) {
|
||||
$this->processExecutor = new ProcessExecutor($this->getIO());
|
||||
}
|
||||
|
||||
return $this->processExecutor;
|
||||
}
|
||||
}
|
||||
119
vendor/symfony/flex/src/Configurator.php
vendored
Normal file
119
vendor/symfony/flex/src/Configurator.php
vendored
Normal file
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex;
|
||||
|
||||
use Composer\Composer;
|
||||
use Composer\IO\IOInterface;
|
||||
use Symfony\Flex\Configurator\AbstractConfigurator;
|
||||
use Symfony\Flex\Update\RecipeUpdate;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class Configurator
|
||||
{
|
||||
private $composer;
|
||||
private $io;
|
||||
private $options;
|
||||
private $configurators;
|
||||
private $postInstallConfigurators;
|
||||
private $cache;
|
||||
|
||||
public function __construct(Composer $composer, IOInterface $io, Options $options)
|
||||
{
|
||||
$this->composer = $composer;
|
||||
$this->io = $io;
|
||||
$this->options = $options;
|
||||
// ordered list of configurators
|
||||
$this->configurators = [
|
||||
'bundles' => Configurator\BundlesConfigurator::class,
|
||||
'copy-from-recipe' => Configurator\CopyFromRecipeConfigurator::class,
|
||||
'copy-from-package' => Configurator\CopyFromPackageConfigurator::class,
|
||||
'env' => Configurator\EnvConfigurator::class,
|
||||
'dotenv' => Configurator\DotenvConfigurator::class,
|
||||
'container' => Configurator\ContainerConfigurator::class,
|
||||
'makefile' => Configurator\MakefileConfigurator::class,
|
||||
'composer-scripts' => Configurator\ComposerScriptsConfigurator::class,
|
||||
'gitignore' => Configurator\GitignoreConfigurator::class,
|
||||
'dockerfile' => Configurator\DockerfileConfigurator::class,
|
||||
'docker-compose' => Configurator\DockerComposeConfigurator::class,
|
||||
];
|
||||
$this->postInstallConfigurators = [
|
||||
'add-lines' => Configurator\AddLinesConfigurator::class,
|
||||
];
|
||||
}
|
||||
|
||||
public function install(Recipe $recipe, Lock $lock, array $options = [])
|
||||
{
|
||||
$manifest = $recipe->getManifest();
|
||||
foreach (array_keys($this->configurators) as $key) {
|
||||
if (isset($manifest[$key])) {
|
||||
$this->get($key)->configure($recipe, $manifest[$key], $lock, $options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run after all recipes have been installed to run post-install configurators.
|
||||
*/
|
||||
public function postInstall(Recipe $recipe, Lock $lock, array $options = [])
|
||||
{
|
||||
$manifest = $recipe->getManifest();
|
||||
foreach (array_keys($this->postInstallConfigurators) as $key) {
|
||||
if (isset($manifest[$key])) {
|
||||
$this->get($key)->configure($recipe, $manifest[$key], $lock, $options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function populateUpdate(RecipeUpdate $recipeUpdate): void
|
||||
{
|
||||
$originalManifest = $recipeUpdate->getOriginalRecipe()->getManifest();
|
||||
$newManifest = $recipeUpdate->getNewRecipe()->getManifest();
|
||||
$allConfigurators = array_merge($this->configurators, $this->postInstallConfigurators);
|
||||
foreach (array_keys($allConfigurators) as $key) {
|
||||
if (!isset($originalManifest[$key]) && !isset($newManifest[$key])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->get($key)->update($recipeUpdate, $originalManifest[$key] ?? [], $newManifest[$key] ?? []);
|
||||
}
|
||||
}
|
||||
|
||||
public function unconfigure(Recipe $recipe, Lock $lock)
|
||||
{
|
||||
$manifest = $recipe->getManifest();
|
||||
|
||||
$allConfigurators = array_merge($this->configurators, $this->postInstallConfigurators);
|
||||
|
||||
foreach (array_keys($allConfigurators) as $key) {
|
||||
if (isset($manifest[$key])) {
|
||||
$this->get($key)->unconfigure($recipe, $manifest[$key], $lock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function get($key): AbstractConfigurator
|
||||
{
|
||||
if (!isset($this->configurators[$key]) && !isset($this->postInstallConfigurators[$key])) {
|
||||
throw new \InvalidArgumentException(\sprintf('Unknown configurator "%s".', $key));
|
||||
}
|
||||
|
||||
if (isset($this->cache[$key])) {
|
||||
return $this->cache[$key];
|
||||
}
|
||||
|
||||
$class = isset($this->configurators[$key]) ? $this->configurators[$key] : $this->postInstallConfigurators[$key];
|
||||
|
||||
return $this->cache[$key] = new $class($this->composer, $this->io, $this->options);
|
||||
}
|
||||
}
|
||||
131
vendor/symfony/flex/src/Configurator/AbstractConfigurator.php
vendored
Normal file
131
vendor/symfony/flex/src/Configurator/AbstractConfigurator.php
vendored
Normal file
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex\Configurator;
|
||||
|
||||
use Composer\Composer;
|
||||
use Composer\IO\IOInterface;
|
||||
use Symfony\Flex\Lock;
|
||||
use Symfony\Flex\Options;
|
||||
use Symfony\Flex\Path;
|
||||
use Symfony\Flex\Recipe;
|
||||
use Symfony\Flex\Update\RecipeUpdate;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
abstract class AbstractConfigurator
|
||||
{
|
||||
protected $composer;
|
||||
protected $io;
|
||||
protected $options;
|
||||
protected $path;
|
||||
|
||||
public function __construct(Composer $composer, IOInterface $io, Options $options)
|
||||
{
|
||||
$this->composer = $composer;
|
||||
$this->io = $io;
|
||||
$this->options = $options;
|
||||
$this->path = new Path($options->get('root-dir'));
|
||||
}
|
||||
|
||||
abstract public function configure(Recipe $recipe, $config, Lock $lock, array $options = []);
|
||||
|
||||
abstract public function unconfigure(Recipe $recipe, $config, Lock $lock);
|
||||
|
||||
abstract public function update(RecipeUpdate $recipeUpdate, array $originalConfig, array $newConfig): void;
|
||||
|
||||
protected function write($messages, $verbosity = IOInterface::VERBOSE)
|
||||
{
|
||||
if (!\is_array($messages)) {
|
||||
$messages = [$messages];
|
||||
}
|
||||
foreach ($messages as $i => $message) {
|
||||
$messages[$i] = ' '.$message;
|
||||
}
|
||||
$this->io->writeError($messages, true, $verbosity);
|
||||
}
|
||||
|
||||
protected function isFileMarked(Recipe $recipe, string $file): bool
|
||||
{
|
||||
return is_file($file) && false !== strpos(file_get_contents($file), sprintf('###> %s ###', $recipe->getName()));
|
||||
}
|
||||
|
||||
protected function markData(Recipe $recipe, string $data): string
|
||||
{
|
||||
return "\n".sprintf('###> %s ###%s%s%s###< %s ###%s', $recipe->getName(), "\n", rtrim($data, "\r\n"), "\n", $recipe->getName(), "\n");
|
||||
}
|
||||
|
||||
protected function isFileXmlMarked(Recipe $recipe, string $file): bool
|
||||
{
|
||||
return is_file($file) && false !== strpos(file_get_contents($file), sprintf('###+ %s ###', $recipe->getName()));
|
||||
}
|
||||
|
||||
protected function markXmlData(Recipe $recipe, string $data): string
|
||||
{
|
||||
return "\n".sprintf(' <!-- ###+ %s ### -->%s%s%s <!-- ###- %s ### -->%s', $recipe->getName(), "\n", rtrim($data, "\r\n"), "\n", $recipe->getName(), "\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool True if section was found and replaced
|
||||
*/
|
||||
protected function updateData(string $file, string $data): bool
|
||||
{
|
||||
if (!file_exists($file)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$contents = file_get_contents($file);
|
||||
|
||||
$newContents = $this->updateDataString($contents, $data);
|
||||
if (null === $newContents) {
|
||||
return false;
|
||||
}
|
||||
|
||||
file_put_contents($file, $newContents);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null returns the updated content if the section was found, null if not found
|
||||
*/
|
||||
protected function updateDataString(string $contents, string $data): ?string
|
||||
{
|
||||
$pieces = explode("\n", trim($data));
|
||||
$startMark = trim(reset($pieces));
|
||||
$endMark = trim(end($pieces));
|
||||
|
||||
if (false === strpos($contents, $startMark) || false === strpos($contents, $endMark)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$pattern = '/'.preg_quote($startMark, '/').'.*?'.preg_quote($endMark, '/').'/s';
|
||||
|
||||
return preg_replace($pattern, trim($data), $contents);
|
||||
}
|
||||
|
||||
protected function extractSection(Recipe $recipe, string $contents): ?string
|
||||
{
|
||||
$section = $this->markData($recipe, '----');
|
||||
|
||||
$pieces = explode("\n", trim($section));
|
||||
$startMark = trim(reset($pieces));
|
||||
$endMark = trim(end($pieces));
|
||||
|
||||
$pattern = '/'.preg_quote($startMark, '/').'.*?'.preg_quote($endMark, '/').'/s';
|
||||
|
||||
$matches = [];
|
||||
preg_match($pattern, $contents, $matches);
|
||||
|
||||
return $matches[0] ?? null;
|
||||
}
|
||||
}
|
||||
270
vendor/symfony/flex/src/Configurator/AddLinesConfigurator.php
vendored
Normal file
270
vendor/symfony/flex/src/Configurator/AddLinesConfigurator.php
vendored
Normal file
@@ -0,0 +1,270 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Flex\Configurator;
|
||||
|
||||
use Composer\IO\IOInterface;
|
||||
use Symfony\Flex\Lock;
|
||||
use Symfony\Flex\Recipe;
|
||||
use Symfony\Flex\Update\RecipeUpdate;
|
||||
|
||||
/**
|
||||
* @author Kevin Bond <kevinbond@gmail.com>
|
||||
* @author Ryan Weaver <ryan@symfonycasts.com>
|
||||
*/
|
||||
class AddLinesConfigurator extends AbstractConfigurator
|
||||
{
|
||||
private const POSITION_TOP = 'top';
|
||||
private const POSITION_BOTTOM = 'bottom';
|
||||
private const POSITION_AFTER_TARGET = 'after_target';
|
||||
|
||||
private const VALID_POSITIONS = [
|
||||
self::POSITION_TOP,
|
||||
self::POSITION_BOTTOM,
|
||||
self::POSITION_AFTER_TARGET,
|
||||
];
|
||||
|
||||
/**
|
||||
* Holds file contents for files that have been loaded.
|
||||
* This allows us to "change" the contents of a file multiple
|
||||
* times before we actually write it out.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private $fileContents = [];
|
||||
|
||||
public function configure(Recipe $recipe, $config, Lock $lock, array $options = []): void
|
||||
{
|
||||
$this->fileContents = [];
|
||||
$this->executeConfigure($recipe, $config);
|
||||
|
||||
foreach ($this->fileContents as $file => $contents) {
|
||||
$this->write(sprintf('[add-lines] Patching file "%s"', $this->relativize($file)));
|
||||
file_put_contents($file, $contents);
|
||||
}
|
||||
}
|
||||
|
||||
public function unconfigure(Recipe $recipe, $config, Lock $lock): void
|
||||
{
|
||||
$this->fileContents = [];
|
||||
$this->executeUnconfigure($recipe, $config);
|
||||
|
||||
foreach ($this->fileContents as $file => $change) {
|
||||
$this->write(sprintf('[add-lines] Reverting file "%s"', $this->relativize($file)));
|
||||
file_put_contents($file, $change);
|
||||
}
|
||||
}
|
||||
|
||||
public function update(RecipeUpdate $recipeUpdate, array $originalConfig, array $newConfig): void
|
||||
{
|
||||
// manually check for "requires", as unconfigure ignores it
|
||||
$originalConfig = array_filter($originalConfig, function ($item) {
|
||||
return !isset($item['requires']) || $this->isPackageInstalled($item['requires']);
|
||||
});
|
||||
|
||||
// reset the file content cache
|
||||
$this->fileContents = [];
|
||||
$this->executeUnconfigure($recipeUpdate->getOriginalRecipe(), $originalConfig);
|
||||
$this->executeConfigure($recipeUpdate->getNewRecipe(), $newConfig);
|
||||
$newFiles = [];
|
||||
$originalFiles = [];
|
||||
foreach ($this->fileContents as $file => $contents) {
|
||||
// set the original file to the current contents
|
||||
$originalFiles[$this->relativize($file)] = file_get_contents($file);
|
||||
// and the new file where the old recipe was unconfigured, and the new configured
|
||||
$newFiles[$this->relativize($file)] = $contents;
|
||||
}
|
||||
$recipeUpdate->addOriginalFiles($originalFiles);
|
||||
$recipeUpdate->addNewFiles($newFiles);
|
||||
}
|
||||
|
||||
public function executeConfigure(Recipe $recipe, $config): void
|
||||
{
|
||||
foreach ($config as $patch) {
|
||||
if (!isset($patch['file'])) {
|
||||
$this->write(sprintf('The "file" key is required for the "add-lines" configurator for recipe "%s". Skipping', $recipe->getName()));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($patch['requires']) && !$this->isPackageInstalled($patch['requires'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($patch['content'])) {
|
||||
$this->write(sprintf('The "content" key is required for the "add-lines" configurator for recipe "%s". Skipping', $recipe->getName()));
|
||||
|
||||
continue;
|
||||
}
|
||||
$content = $patch['content'];
|
||||
|
||||
$file = $this->path->concatenate([$this->options->get('root-dir'), $this->options->expandTargetDir($patch['file'])]);
|
||||
$warnIfMissing = isset($patch['warn_if_missing']) && $patch['warn_if_missing'];
|
||||
if (!is_file($file)) {
|
||||
$this->write([
|
||||
sprintf('Could not add lines to file <info>%s</info> as it does not exist. Missing lines:', $patch['file']),
|
||||
'<comment>"""</comment>',
|
||||
$content,
|
||||
'<comment>"""</comment>',
|
||||
'',
|
||||
], $warnIfMissing ? IOInterface::NORMAL : IOInterface::VERBOSE);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($patch['position'])) {
|
||||
$this->write(sprintf('The "position" key is required for the "add-lines" configurator for recipe "%s". Skipping', $recipe->getName()));
|
||||
|
||||
continue;
|
||||
}
|
||||
$position = $patch['position'];
|
||||
if (!\in_array($position, self::VALID_POSITIONS, true)) {
|
||||
$this->write(sprintf('The "position" key must be one of "%s" for the "add-lines" configurator for recipe "%s". Skipping', implode('", "', self::VALID_POSITIONS), $recipe->getName()));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (self::POSITION_AFTER_TARGET === $position && !isset($patch['target'])) {
|
||||
$this->write(sprintf('The "target" key is required when "position" is "%s" for the "add-lines" configurator for recipe "%s". Skipping', self::POSITION_AFTER_TARGET, $recipe->getName()));
|
||||
|
||||
continue;
|
||||
}
|
||||
$target = isset($patch['target']) ? $patch['target'] : null;
|
||||
|
||||
$newContents = $this->getPatchedContents($file, $content, $position, $target, $warnIfMissing);
|
||||
$this->fileContents[$file] = $newContents;
|
||||
}
|
||||
}
|
||||
|
||||
public function executeUnconfigure(Recipe $recipe, $config): void
|
||||
{
|
||||
foreach ($config as $patch) {
|
||||
if (!isset($patch['file'])) {
|
||||
$this->write(sprintf('The "file" key is required for the "add-lines" configurator for recipe "%s". Skipping', $recipe->getName()));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ignore "requires": the target packages may have just become uninstalled.
|
||||
// Checking for a "content" match is enough.
|
||||
|
||||
$file = $this->path->concatenate([$this->options->get('root-dir'), $this->options->expandTargetDir($patch['file'])]);
|
||||
if (!is_file($file)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($patch['content'])) {
|
||||
$this->write(sprintf('The "content" key is required for the "add-lines" configurator for recipe "%s". Skipping', $recipe->getName()));
|
||||
|
||||
continue;
|
||||
}
|
||||
$value = $patch['content'];
|
||||
|
||||
$newContents = $this->getUnPatchedContents($file, $value);
|
||||
$this->fileContents[$file] = $newContents;
|
||||
}
|
||||
}
|
||||
|
||||
private function getPatchedContents(string $file, string $value, string $position, ?string $target, bool $warnIfMissing): string
|
||||
{
|
||||
$fileContents = $this->readFile($file);
|
||||
|
||||
if (false !== strpos($fileContents, $value)) {
|
||||
return $fileContents; // already includes value, skip
|
||||
}
|
||||
|
||||
switch ($position) {
|
||||
case self::POSITION_BOTTOM:
|
||||
$fileContents .= "\n".$value;
|
||||
|
||||
break;
|
||||
case self::POSITION_TOP:
|
||||
$fileContents = $value."\n".$fileContents;
|
||||
|
||||
break;
|
||||
case self::POSITION_AFTER_TARGET:
|
||||
$lines = explode("\n", $fileContents);
|
||||
$targetFound = false;
|
||||
foreach ($lines as $key => $line) {
|
||||
if (false !== strpos($line, $target)) {
|
||||
array_splice($lines, $key + 1, 0, $value);
|
||||
$targetFound = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
$fileContents = implode("\n", $lines);
|
||||
|
||||
if (!$targetFound) {
|
||||
$this->write([
|
||||
sprintf('Could not add lines after "%s" as no such string was found in "%s". Missing lines:', $target, $file),
|
||||
'<comment>"""</comment>',
|
||||
$value,
|
||||
'<comment>"""</comment>',
|
||||
'',
|
||||
], $warnIfMissing ? IOInterface::NORMAL : IOInterface::VERBOSE);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return $fileContents;
|
||||
}
|
||||
|
||||
private function getUnPatchedContents(string $file, $value): string
|
||||
{
|
||||
$fileContents = $this->readFile($file);
|
||||
|
||||
if (false === strpos($fileContents, $value)) {
|
||||
return $fileContents; // value already gone!
|
||||
}
|
||||
|
||||
if (false !== strpos($fileContents, "\n".$value)) {
|
||||
$value = "\n".$value;
|
||||
} elseif (false !== strpos($fileContents, $value."\n")) {
|
||||
$value = $value."\n";
|
||||
}
|
||||
|
||||
$position = strpos($fileContents, $value);
|
||||
|
||||
return substr_replace($fileContents, '', $position, \strlen($value));
|
||||
}
|
||||
|
||||
private function isPackageInstalled($packages): bool
|
||||
{
|
||||
if (\is_string($packages)) {
|
||||
$packages = [$packages];
|
||||
}
|
||||
|
||||
$installedRepo = $this->composer->getRepositoryManager()->getLocalRepository();
|
||||
|
||||
foreach ($packages as $packageName) {
|
||||
if (null === $installedRepo->findPackage($packageName, '*')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function relativize(string $path): string
|
||||
{
|
||||
$rootDir = $this->options->get('root-dir');
|
||||
if (0 === strpos($path, $rootDir)) {
|
||||
$path = substr($path, \strlen($rootDir) + 1);
|
||||
}
|
||||
|
||||
return ltrim($path, '/\\');
|
||||
}
|
||||
|
||||
private function readFile(string $file): string
|
||||
{
|
||||
if (isset($this->fileContents[$file])) {
|
||||
return $this->fileContents[$file];
|
||||
}
|
||||
|
||||
$fileContents = file_get_contents($file);
|
||||
$this->fileContents[$file] = $fileContents;
|
||||
|
||||
return $fileContents;
|
||||
}
|
||||
}
|
||||
150
vendor/symfony/flex/src/Configurator/BundlesConfigurator.php
vendored
Normal file
150
vendor/symfony/flex/src/Configurator/BundlesConfigurator.php
vendored
Normal file
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex\Configurator;
|
||||
|
||||
use Symfony\Flex\Lock;
|
||||
use Symfony\Flex\Recipe;
|
||||
use Symfony\Flex\Update\RecipeUpdate;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class BundlesConfigurator extends AbstractConfigurator
|
||||
{
|
||||
public function configure(Recipe $recipe, $bundles, Lock $lock, array $options = [])
|
||||
{
|
||||
$this->write('Enabling the package as a Symfony bundle');
|
||||
$registered = $this->configureBundles($bundles);
|
||||
$this->dump($this->getConfFile(), $registered);
|
||||
}
|
||||
|
||||
public function unconfigure(Recipe $recipe, $bundles, Lock $lock)
|
||||
{
|
||||
$this->write('Disabling the Symfony bundle');
|
||||
$file = $this->getConfFile();
|
||||
if (!file_exists($file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$registered = $this->load($file);
|
||||
foreach (array_keys($this->prepareBundles($bundles)) as $class) {
|
||||
unset($registered[$class]);
|
||||
}
|
||||
$this->dump($file, $registered);
|
||||
}
|
||||
|
||||
public function update(RecipeUpdate $recipeUpdate, array $originalConfig, array $newConfig): void
|
||||
{
|
||||
$originalBundles = $this->configureBundles($originalConfig, true);
|
||||
$recipeUpdate->setOriginalFile(
|
||||
$this->getLocalConfFile(),
|
||||
$this->buildContents($originalBundles)
|
||||
);
|
||||
|
||||
$newBundles = $this->configureBundles($newConfig, true);
|
||||
$recipeUpdate->setNewFile(
|
||||
$this->getLocalConfFile(),
|
||||
$this->buildContents($newBundles)
|
||||
);
|
||||
}
|
||||
|
||||
private function configureBundles(array $bundles, bool $resetEnvironments = false): array
|
||||
{
|
||||
$file = $this->getConfFile();
|
||||
$registered = $this->load($file);
|
||||
$classes = $this->prepareBundles($bundles);
|
||||
if (isset($classes[$fwb = 'Symfony\Bundle\FrameworkBundle\FrameworkBundle'])) {
|
||||
foreach ($classes[$fwb] as $env) {
|
||||
$registered[$fwb][$env] = true;
|
||||
}
|
||||
unset($classes[$fwb]);
|
||||
}
|
||||
foreach ($classes as $class => $envs) {
|
||||
// do not override existing configured envs for a bundle
|
||||
if (!isset($registered[$class]) || $resetEnvironments) {
|
||||
if ($resetEnvironments) {
|
||||
// used during calculating an "upgrade"
|
||||
// here, we want to "undo" the bundle's configuration entirely
|
||||
// then re-add it fresh, in case some environments have been
|
||||
// removed in an updated version of the recipe
|
||||
$registered[$class] = [];
|
||||
}
|
||||
|
||||
foreach ($envs as $env) {
|
||||
$registered[$class][$env] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $registered;
|
||||
}
|
||||
|
||||
private function prepareBundles(array $bundles): array
|
||||
{
|
||||
foreach ($bundles as $class => $envs) {
|
||||
$bundles[ltrim($class, '\\')] = $envs;
|
||||
}
|
||||
|
||||
return $bundles;
|
||||
}
|
||||
|
||||
private function load(string $file): array
|
||||
{
|
||||
$bundles = file_exists($file) ? (require $file) : [];
|
||||
if (!\is_array($bundles)) {
|
||||
$bundles = [];
|
||||
}
|
||||
|
||||
return $bundles;
|
||||
}
|
||||
|
||||
private function dump(string $file, array $bundles)
|
||||
{
|
||||
$contents = $this->buildContents($bundles);
|
||||
|
||||
if (!is_dir(\dirname($file))) {
|
||||
mkdir(\dirname($file), 0777, true);
|
||||
}
|
||||
|
||||
file_put_contents($file, $contents);
|
||||
|
||||
if (\function_exists('opcache_invalidate')) {
|
||||
opcache_invalidate($file);
|
||||
}
|
||||
}
|
||||
|
||||
private function buildContents(array $bundles): string
|
||||
{
|
||||
$contents = "<?php\n\nreturn [\n";
|
||||
foreach ($bundles as $class => $envs) {
|
||||
$contents .= " $class::class => [";
|
||||
foreach ($envs as $env => $value) {
|
||||
$booleanValue = var_export($value, true);
|
||||
$contents .= "'$env' => $booleanValue, ";
|
||||
}
|
||||
$contents = substr($contents, 0, -2)."],\n";
|
||||
}
|
||||
$contents .= "];\n";
|
||||
|
||||
return $contents;
|
||||
}
|
||||
|
||||
private function getConfFile(): string
|
||||
{
|
||||
return $this->options->get('root-dir').'/'.$this->getLocalConfFile();
|
||||
}
|
||||
|
||||
private function getLocalConfFile(): string
|
||||
{
|
||||
return $this->options->expandTargetDir('%CONFIG_DIR%/bundles.php');
|
||||
}
|
||||
}
|
||||
75
vendor/symfony/flex/src/Configurator/ComposerScriptsConfigurator.php
vendored
Normal file
75
vendor/symfony/flex/src/Configurator/ComposerScriptsConfigurator.php
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex\Configurator;
|
||||
|
||||
use Composer\Factory;
|
||||
use Composer\Json\JsonFile;
|
||||
use Composer\Json\JsonManipulator;
|
||||
use Symfony\Flex\Lock;
|
||||
use Symfony\Flex\Recipe;
|
||||
use Symfony\Flex\Update\RecipeUpdate;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class ComposerScriptsConfigurator extends AbstractConfigurator
|
||||
{
|
||||
public function configure(Recipe $recipe, $scripts, Lock $lock, array $options = [])
|
||||
{
|
||||
$json = new JsonFile(Factory::getComposerFile());
|
||||
|
||||
file_put_contents($json->getPath(), $this->configureScripts($scripts, $json));
|
||||
}
|
||||
|
||||
public function unconfigure(Recipe $recipe, $scripts, Lock $lock)
|
||||
{
|
||||
$json = new JsonFile(Factory::getComposerFile());
|
||||
|
||||
$jsonContents = $json->read();
|
||||
$autoScripts = $jsonContents['scripts']['auto-scripts'] ?? [];
|
||||
foreach (array_keys($scripts) as $cmd) {
|
||||
unset($autoScripts[$cmd]);
|
||||
}
|
||||
|
||||
$manipulator = new JsonManipulator(file_get_contents($json->getPath()));
|
||||
$manipulator->addSubNode('scripts', 'auto-scripts', $autoScripts);
|
||||
|
||||
file_put_contents($json->getPath(), $manipulator->getContents());
|
||||
}
|
||||
|
||||
public function update(RecipeUpdate $recipeUpdate, array $originalConfig, array $newConfig): void
|
||||
{
|
||||
$json = new JsonFile(Factory::getComposerFile());
|
||||
$jsonPath = ltrim(str_replace($recipeUpdate->getRootDir(), '', $json->getPath()), '/\\');
|
||||
|
||||
$recipeUpdate->setOriginalFile(
|
||||
$jsonPath,
|
||||
$this->configureScripts($originalConfig, $json)
|
||||
);
|
||||
$recipeUpdate->setNewFile(
|
||||
$jsonPath,
|
||||
$this->configureScripts($newConfig, $json)
|
||||
);
|
||||
}
|
||||
|
||||
private function configureScripts(array $scripts, JsonFile $json): string
|
||||
{
|
||||
$jsonContents = $json->read();
|
||||
$autoScripts = $jsonContents['scripts']['auto-scripts'] ?? [];
|
||||
$autoScripts = array_merge($autoScripts, $scripts);
|
||||
|
||||
$manipulator = new JsonManipulator(file_get_contents($json->getPath()));
|
||||
$manipulator->addSubNode('scripts', 'auto-scripts', $autoScripts);
|
||||
|
||||
return $manipulator->getContents();
|
||||
}
|
||||
}
|
||||
164
vendor/symfony/flex/src/Configurator/ContainerConfigurator.php
vendored
Normal file
164
vendor/symfony/flex/src/Configurator/ContainerConfigurator.php
vendored
Normal file
@@ -0,0 +1,164 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex\Configurator;
|
||||
|
||||
use Symfony\Flex\Lock;
|
||||
use Symfony\Flex\Recipe;
|
||||
use Symfony\Flex\Update\RecipeUpdate;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class ContainerConfigurator extends AbstractConfigurator
|
||||
{
|
||||
public function configure(Recipe $recipe, $parameters, Lock $lock, array $options = [])
|
||||
{
|
||||
$this->write('Setting parameters');
|
||||
$contents = $this->configureParameters($parameters);
|
||||
|
||||
if (null !== $contents) {
|
||||
file_put_contents($this->options->get('root-dir').'/'.$this->getServicesPath(), $contents);
|
||||
}
|
||||
}
|
||||
|
||||
public function unconfigure(Recipe $recipe, $parameters, Lock $lock)
|
||||
{
|
||||
$this->write('Unsetting parameters');
|
||||
$target = $this->options->get('root-dir').'/'.$this->getServicesPath();
|
||||
$lines = $this->removeParametersFromLines(file($target), $parameters);
|
||||
file_put_contents($target, implode('', $lines));
|
||||
}
|
||||
|
||||
public function update(RecipeUpdate $recipeUpdate, array $originalConfig, array $newConfig): void
|
||||
{
|
||||
$recipeUpdate->setOriginalFile(
|
||||
$this->getServicesPath(),
|
||||
$this->configureParameters($originalConfig, true)
|
||||
);
|
||||
|
||||
// for the new file, we need to update any values *and* remove any removed values
|
||||
$removedParameters = [];
|
||||
foreach ($originalConfig as $name => $value) {
|
||||
if (!isset($newConfig[$name])) {
|
||||
$removedParameters[$name] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$updatedFile = $this->configureParameters($newConfig, true);
|
||||
$lines = $this->removeParametersFromLines(explode("\n", $updatedFile), $removedParameters);
|
||||
|
||||
$recipeUpdate->setNewFile(
|
||||
$this->getServicesPath(),
|
||||
implode("\n", $lines)
|
||||
);
|
||||
}
|
||||
|
||||
private function configureParameters(array $parameters, bool $update = false): string
|
||||
{
|
||||
$target = $this->options->get('root-dir').'/'.$this->getServicesPath();
|
||||
$endAt = 0;
|
||||
$isParameters = false;
|
||||
$lines = [];
|
||||
foreach (file($target) as $i => $line) {
|
||||
$lines[] = $line;
|
||||
if (!$isParameters && !preg_match('/^parameters:/', $line)) {
|
||||
continue;
|
||||
}
|
||||
if (!$isParameters) {
|
||||
$isParameters = true;
|
||||
continue;
|
||||
}
|
||||
if (!preg_match('/^\s+.*/', $line) && '' !== trim($line)) {
|
||||
$endAt = $i - 1;
|
||||
$isParameters = false;
|
||||
continue;
|
||||
}
|
||||
foreach ($parameters as $key => $value) {
|
||||
$matches = [];
|
||||
if (preg_match(sprintf('/^\s+%s\:/', preg_quote($key, '/')), $line, $matches)) {
|
||||
if ($update) {
|
||||
$lines[$i] = substr($line, 0, \strlen($matches[0])).' '.str_replace("'", "''", $value)."\n";
|
||||
}
|
||||
|
||||
unset($parameters[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($parameters) {
|
||||
$parametersLines = [];
|
||||
if (!$endAt) {
|
||||
$parametersLines[] = "parameters:\n";
|
||||
}
|
||||
foreach ($parameters as $key => $value) {
|
||||
if (\is_array($value)) {
|
||||
$parametersLines[] = sprintf(" %s:\n%s", $key, $this->dumpYaml(2, $value));
|
||||
continue;
|
||||
}
|
||||
$parametersLines[] = sprintf(" %s: '%s'%s", $key, str_replace("'", "''", $value), "\n");
|
||||
}
|
||||
if (!$endAt) {
|
||||
$parametersLines[] = "\n";
|
||||
}
|
||||
array_splice($lines, $endAt, 0, $parametersLines);
|
||||
}
|
||||
|
||||
return implode('', $lines);
|
||||
}
|
||||
|
||||
private function removeParametersFromLines(array $sourceLines, array $parameters): array
|
||||
{
|
||||
$lines = [];
|
||||
foreach ($sourceLines as $line) {
|
||||
if ($this->removeParameters(1, $parameters, $line)) {
|
||||
continue;
|
||||
}
|
||||
$lines[] = $line;
|
||||
}
|
||||
|
||||
return $lines;
|
||||
}
|
||||
|
||||
private function removeParameters($level, $params, $line)
|
||||
{
|
||||
foreach ($params as $key => $value) {
|
||||
if (\is_array($value) && $this->removeParameters($level + 1, $value, $line)) {
|
||||
return true;
|
||||
}
|
||||
if (preg_match(sprintf('/^(\s{%d}|\t{%d})+%s\:/', 4 * $level, $level, preg_quote($key, '/')), $line)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function dumpYaml($level, $array): string
|
||||
{
|
||||
$line = '';
|
||||
foreach ($array as $key => $value) {
|
||||
$line .= str_repeat(' ', $level);
|
||||
if (!\is_array($value)) {
|
||||
$line .= sprintf("%s: '%s'\n", $key, str_replace("'", "''", $value));
|
||||
continue;
|
||||
}
|
||||
$line .= sprintf("%s:\n", $key).$this->dumpYaml($level + 1, $value);
|
||||
}
|
||||
|
||||
return $line;
|
||||
}
|
||||
|
||||
private function getServicesPath(): string
|
||||
{
|
||||
return $this->options->expandTargetDir('%CONFIG_DIR%/services.yaml');
|
||||
}
|
||||
}
|
||||
169
vendor/symfony/flex/src/Configurator/CopyFromPackageConfigurator.php
vendored
Normal file
169
vendor/symfony/flex/src/Configurator/CopyFromPackageConfigurator.php
vendored
Normal file
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex\Configurator;
|
||||
|
||||
use Symfony\Flex\Lock;
|
||||
use Symfony\Flex\Recipe;
|
||||
use Symfony\Flex\Update\RecipeUpdate;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class CopyFromPackageConfigurator extends AbstractConfigurator
|
||||
{
|
||||
public function configure(Recipe $recipe, $config, Lock $lock, array $options = [])
|
||||
{
|
||||
$this->write('Copying files from package');
|
||||
$packageDir = $this->composer->getInstallationManager()->getInstallPath($recipe->getPackage());
|
||||
$options = array_merge($this->options->toArray(), $options);
|
||||
|
||||
$files = $this->getFilesToCopy($config, $packageDir);
|
||||
foreach ($files as $source => $target) {
|
||||
$this->copyFile($source, $target, $options);
|
||||
}
|
||||
}
|
||||
|
||||
public function unconfigure(Recipe $recipe, $config, Lock $lock)
|
||||
{
|
||||
$this->write('Removing files from package');
|
||||
$packageDir = $this->composer->getInstallationManager()->getInstallPath($recipe->getPackage());
|
||||
$this->removeFiles($config, $packageDir, $this->options->get('root-dir'));
|
||||
}
|
||||
|
||||
public function update(RecipeUpdate $recipeUpdate, array $originalConfig, array $newConfig): void
|
||||
{
|
||||
$packageDir = $this->composer->getInstallationManager()->getInstallPath($recipeUpdate->getNewRecipe()->getPackage());
|
||||
foreach ($originalConfig as $source => $target) {
|
||||
if (isset($newConfig[$source])) {
|
||||
// path is in both, we cannot update
|
||||
$recipeUpdate->addCopyFromPackagePath(
|
||||
$packageDir.'/'.$source,
|
||||
$this->options->expandTargetDir($target)
|
||||
);
|
||||
|
||||
unset($newConfig[$source]);
|
||||
}
|
||||
|
||||
// if any paths were removed from the recipe, we'll keep them
|
||||
}
|
||||
|
||||
// any remaining files are new, and we can copy them
|
||||
foreach ($this->getFilesToCopy($newConfig, $packageDir) as $source => $target) {
|
||||
if (!file_exists($source)) {
|
||||
throw new \LogicException(sprintf('File "%s" does not exist!', $source));
|
||||
}
|
||||
|
||||
$recipeUpdate->setNewFile($target, file_get_contents($source));
|
||||
}
|
||||
}
|
||||
|
||||
private function getFilesToCopy(array $manifest, string $from): array
|
||||
{
|
||||
$files = [];
|
||||
foreach ($manifest as $source => $target) {
|
||||
$target = $this->options->expandTargetDir($target);
|
||||
if ('/' === substr($source, -1)) {
|
||||
$files = array_merge($files, $this->getFilesForDir($this->path->concatenate([$from, $source]), $this->path->concatenate([$target])));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$files[$this->path->concatenate([$from, $source])] = $target;
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
private function removeFiles(array $manifest, string $from, string $to)
|
||||
{
|
||||
foreach ($manifest as $source => $target) {
|
||||
$target = $this->options->expandTargetDir($target);
|
||||
if ('/' === substr($source, -1)) {
|
||||
$this->removeFilesFromDir($this->path->concatenate([$from, $source]), $this->path->concatenate([$to, $target]));
|
||||
} else {
|
||||
$targetPath = $this->path->concatenate([$to, $target]);
|
||||
if (file_exists($targetPath)) {
|
||||
@unlink($targetPath);
|
||||
$this->write(sprintf(' Removed <fg=green>"%s"</>', $this->path->relativize($targetPath)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function getFilesForDir(string $source, string $target): array
|
||||
{
|
||||
$iterator = $this->createSourceIterator($source, \RecursiveIteratorIterator::SELF_FIRST);
|
||||
$files = [];
|
||||
foreach ($iterator as $item) {
|
||||
$targetPath = $this->path->concatenate([$target, $iterator->getSubPathName()]);
|
||||
|
||||
$files[(string) $item] = $targetPath;
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $source The absolute path to the source file
|
||||
* @param string $target The relative (to root dir) path to the target
|
||||
*/
|
||||
public function copyFile(string $source, string $target, array $options)
|
||||
{
|
||||
$target = $this->options->get('root-dir').'/'.$target;
|
||||
if (is_dir($source)) {
|
||||
// directory will be created when a file is copied to it
|
||||
return;
|
||||
}
|
||||
|
||||
$overwrite = $options['force'] ?? false;
|
||||
if (!$this->options->shouldWriteFile($target, $overwrite)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!file_exists($source)) {
|
||||
throw new \LogicException(sprintf('File "%s" does not exist!', $source));
|
||||
}
|
||||
|
||||
if (!file_exists(\dirname($target))) {
|
||||
mkdir(\dirname($target), 0777, true);
|
||||
$this->write(sprintf(' Created <fg=green>"%s"</>', $this->path->relativize(\dirname($target))));
|
||||
}
|
||||
|
||||
file_put_contents($target, $this->options->expandTargetDir(file_get_contents($source)));
|
||||
@chmod($target, fileperms($target) | (fileperms($source) & 0111));
|
||||
$this->write(sprintf(' Created <fg=green>"%s"</>', $this->path->relativize($target)));
|
||||
}
|
||||
|
||||
private function removeFilesFromDir(string $source, string $target)
|
||||
{
|
||||
if (!is_dir($source)) {
|
||||
return;
|
||||
}
|
||||
$iterator = $this->createSourceIterator($source, \RecursiveIteratorIterator::CHILD_FIRST);
|
||||
foreach ($iterator as $item) {
|
||||
$targetPath = $this->path->concatenate([$target, $iterator->getSubPathName()]);
|
||||
if ($item->isDir()) {
|
||||
// that removes the dir only if it is empty
|
||||
@rmdir($targetPath);
|
||||
$this->write(sprintf(' Removed directory <fg=green>"%s"</>', $this->path->relativize($targetPath)));
|
||||
} else {
|
||||
@unlink($targetPath);
|
||||
$this->write(sprintf(' Removed <fg=green>"%s"</>', $this->path->relativize($targetPath)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function createSourceIterator(string $source, int $mode): \RecursiveIteratorIterator
|
||||
{
|
||||
return new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($source, \RecursiveDirectoryIterator::SKIP_DOTS), $mode);
|
||||
}
|
||||
}
|
||||
175
vendor/symfony/flex/src/Configurator/CopyFromRecipeConfigurator.php
vendored
Normal file
175
vendor/symfony/flex/src/Configurator/CopyFromRecipeConfigurator.php
vendored
Normal file
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex\Configurator;
|
||||
|
||||
use Symfony\Flex\Lock;
|
||||
use Symfony\Flex\Recipe;
|
||||
use Symfony\Flex\Update\RecipeUpdate;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class CopyFromRecipeConfigurator extends AbstractConfigurator
|
||||
{
|
||||
public function configure(Recipe $recipe, $config, Lock $lock, array $options = [])
|
||||
{
|
||||
$this->write('Copying files from recipe');
|
||||
$options = array_merge($this->options->toArray(), $options);
|
||||
|
||||
$lock->add($recipe->getName(), ['files' => $this->copyFiles($config, $recipe->getFiles(), $options)]);
|
||||
}
|
||||
|
||||
public function unconfigure(Recipe $recipe, $config, Lock $lock)
|
||||
{
|
||||
$this->write('Removing files from recipe');
|
||||
$this->removeFiles($config, $this->getRemovableFilesFromRecipeAndLock($recipe, $lock), $this->options->get('root-dir'));
|
||||
}
|
||||
|
||||
public function update(RecipeUpdate $recipeUpdate, array $originalConfig, array $newConfig): void
|
||||
{
|
||||
foreach ($recipeUpdate->getOriginalRecipe()->getFiles() as $filename => $data) {
|
||||
$recipeUpdate->setOriginalFile($filename, $data['contents']);
|
||||
}
|
||||
|
||||
$files = [];
|
||||
foreach ($recipeUpdate->getNewRecipe()->getFiles() as $filename => $data) {
|
||||
$recipeUpdate->setNewFile($filename, $data['contents']);
|
||||
|
||||
$files[] = $this->getLocalFilePath($recipeUpdate->getRootDir(), $filename);
|
||||
}
|
||||
$recipeUpdate->getLock()->add($recipeUpdate->getPackageName(), ['files' => $files]);
|
||||
}
|
||||
|
||||
private function getRemovableFilesFromRecipeAndLock(Recipe $recipe, Lock $lock): array
|
||||
{
|
||||
$lockedFiles = array_unique(
|
||||
array_reduce(
|
||||
array_column($lock->all(), 'files'),
|
||||
function (array $carry, array $package) {
|
||||
return array_merge($carry, $package);
|
||||
},
|
||||
[]
|
||||
)
|
||||
);
|
||||
|
||||
$removableFiles = $recipe->getFiles();
|
||||
|
||||
$lockedFiles = array_map('realpath', $lockedFiles);
|
||||
|
||||
// Compare file paths by their real path to abstract OS differences
|
||||
foreach (array_keys($removableFiles) as $file) {
|
||||
if (\in_array(realpath($file), $lockedFiles)) {
|
||||
unset($removableFiles[$file]);
|
||||
}
|
||||
}
|
||||
|
||||
return $removableFiles;
|
||||
}
|
||||
|
||||
private function copyFiles(array $manifest, array $files, array $options): array
|
||||
{
|
||||
$copiedFiles = [];
|
||||
$to = $options['root-dir'] ?? '.';
|
||||
|
||||
foreach ($manifest as $source => $target) {
|
||||
$target = $this->options->expandTargetDir($target);
|
||||
if ('/' === substr($source, -1)) {
|
||||
$copiedFiles = array_merge(
|
||||
$copiedFiles,
|
||||
$this->copyDir($source, $this->path->concatenate([$to, $target]), $files, $options)
|
||||
);
|
||||
} else {
|
||||
$copiedFiles[] = $this->copyFile($this->path->concatenate([$to, $target]), $files[$source]['contents'], $files[$source]['executable'], $options);
|
||||
}
|
||||
}
|
||||
|
||||
return $copiedFiles;
|
||||
}
|
||||
|
||||
private function copyDir(string $source, string $target, array $files, array $options): array
|
||||
{
|
||||
$copiedFiles = [];
|
||||
foreach ($files as $file => $data) {
|
||||
if (0 === strpos($file, $source)) {
|
||||
$file = $this->path->concatenate([$target, substr($file, \strlen($source))]);
|
||||
$copiedFiles[] = $this->copyFile($file, $data['contents'], $data['executable'], $options);
|
||||
}
|
||||
}
|
||||
|
||||
return $copiedFiles;
|
||||
}
|
||||
|
||||
private function copyFile(string $to, string $contents, bool $executable, array $options): string
|
||||
{
|
||||
$overwrite = $options['force'] ?? false;
|
||||
$basePath = $options['root-dir'] ?? '.';
|
||||
$copiedFile = $this->getLocalFilePath($basePath, $to);
|
||||
|
||||
if (!$this->options->shouldWriteFile($to, $overwrite)) {
|
||||
return $copiedFile;
|
||||
}
|
||||
|
||||
if (!is_dir(\dirname($to))) {
|
||||
mkdir(\dirname($to), 0777, true);
|
||||
}
|
||||
|
||||
file_put_contents($to, $this->options->expandTargetDir($contents));
|
||||
if ($executable) {
|
||||
@chmod($to, fileperms($to) | 0111);
|
||||
}
|
||||
|
||||
$this->write(sprintf(' Created <fg=green>"%s"</>', $this->path->relativize($to)));
|
||||
|
||||
return $copiedFile;
|
||||
}
|
||||
|
||||
private function removeFiles(array $manifest, array $files, string $to)
|
||||
{
|
||||
foreach ($manifest as $source => $target) {
|
||||
$target = $this->options->expandTargetDir($target);
|
||||
|
||||
if ('.git' === $target) {
|
||||
// never remove the main Git directory, even if it was created by a recipe
|
||||
continue;
|
||||
}
|
||||
|
||||
if ('/' === substr($source, -1)) {
|
||||
foreach (array_keys($files) as $file) {
|
||||
if (0 === strpos($file, $source)) {
|
||||
$this->removeFile($this->path->concatenate([$to, $target, substr($file, \strlen($source))]));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->removeFile($this->path->concatenate([$to, $target]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function removeFile(string $to)
|
||||
{
|
||||
if (!file_exists($to)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@unlink($to);
|
||||
$this->write(sprintf(' Removed <fg=green>"%s"</>', $this->path->relativize($to)));
|
||||
|
||||
if (0 === \count(glob(\dirname($to).'/*', \GLOB_NOSORT))) {
|
||||
@rmdir(\dirname($to));
|
||||
}
|
||||
}
|
||||
|
||||
private function getLocalFilePath(string $basePath, $destination): string
|
||||
{
|
||||
return str_replace($basePath.\DIRECTORY_SEPARATOR, '', $destination);
|
||||
}
|
||||
}
|
||||
404
vendor/symfony/flex/src/Configurator/DockerComposeConfigurator.php
vendored
Normal file
404
vendor/symfony/flex/src/Configurator/DockerComposeConfigurator.php
vendored
Normal file
@@ -0,0 +1,404 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex\Configurator;
|
||||
|
||||
use Composer\Composer;
|
||||
use Composer\Factory;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Json\JsonFile;
|
||||
use Composer\Json\JsonManipulator;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
use Symfony\Flex\Lock;
|
||||
use Symfony\Flex\Options;
|
||||
use Symfony\Flex\Recipe;
|
||||
use Symfony\Flex\Update\RecipeUpdate;
|
||||
|
||||
/**
|
||||
* Adds services and volumes to compose.yaml file.
|
||||
*
|
||||
* @author Kévin Dunglas <kevin@dunglas.dev>
|
||||
*/
|
||||
class DockerComposeConfigurator extends AbstractConfigurator
|
||||
{
|
||||
private $filesystem;
|
||||
|
||||
public static $configureDockerRecipes = null;
|
||||
|
||||
public function __construct(Composer $composer, IOInterface $io, Options $options)
|
||||
{
|
||||
parent::__construct($composer, $io, $options);
|
||||
|
||||
$this->filesystem = new Filesystem();
|
||||
}
|
||||
|
||||
public function configure(Recipe $recipe, $config, Lock $lock, array $options = [])
|
||||
{
|
||||
if (!self::shouldConfigureDockerRecipe($this->composer, $this->io, $recipe)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->configureDockerCompose($recipe, $config, $options['force'] ?? false);
|
||||
|
||||
$this->write('Docker Compose definitions have been modified. Please run "docker compose up --build" again to apply the changes.');
|
||||
}
|
||||
|
||||
public function unconfigure(Recipe $recipe, $config, Lock $lock)
|
||||
{
|
||||
$rootDir = $this->options->get('root-dir');
|
||||
foreach ($this->normalizeConfig($config) as $file => $extra) {
|
||||
if (null === $dockerComposeFile = $this->findDockerComposeFile($rootDir, $file)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$name = $recipe->getName();
|
||||
// Remove recipe and add break line
|
||||
$contents = preg_replace(sprintf('{%s+###> %s ###.*?###< %s ###%s+}s', "\n", $name, $name, "\n"), \PHP_EOL.\PHP_EOL, file_get_contents($dockerComposeFile), -1, $count);
|
||||
if (!$count) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($extra as $key => $value) {
|
||||
if (0 === preg_match(sprintf('{^%s:[ \t\r\n]*([ \t]+\w|#)}m', $key), $contents, $matches)) {
|
||||
$contents = preg_replace(sprintf('{\n?^%s:[ \t\r\n]*}sm', $key), '', $contents, -1, $count);
|
||||
}
|
||||
}
|
||||
|
||||
$this->write(sprintf('Removing Docker Compose entries from "%s"', $dockerComposeFile));
|
||||
file_put_contents($dockerComposeFile, ltrim($contents, "\n"));
|
||||
}
|
||||
|
||||
$this->write('Docker Compose definitions have been modified. Please run "docker compose up" again to apply the changes.');
|
||||
}
|
||||
|
||||
public function update(RecipeUpdate $recipeUpdate, array $originalConfig, array $newConfig): void
|
||||
{
|
||||
if (!self::shouldConfigureDockerRecipe($this->composer, $this->io, $recipeUpdate->getNewRecipe())) {
|
||||
return;
|
||||
}
|
||||
|
||||
$recipeUpdate->addOriginalFiles(
|
||||
$this->getContentsAfterApplyingRecipe($recipeUpdate->getRootDir(), $recipeUpdate->getOriginalRecipe(), $originalConfig)
|
||||
);
|
||||
|
||||
$recipeUpdate->addNewFiles(
|
||||
$this->getContentsAfterApplyingRecipe($recipeUpdate->getRootDir(), $recipeUpdate->getNewRecipe(), $newConfig)
|
||||
);
|
||||
}
|
||||
|
||||
public static function shouldConfigureDockerRecipe(Composer $composer, IOInterface $io, Recipe $recipe): bool
|
||||
{
|
||||
if (null !== self::$configureDockerRecipes) {
|
||||
return self::$configureDockerRecipes;
|
||||
}
|
||||
|
||||
if (null !== $dockerPreference = $composer->getPackage()->getExtra()['symfony']['docker'] ?? null) {
|
||||
self::$configureDockerRecipes = $dockerPreference;
|
||||
|
||||
return self::$configureDockerRecipes;
|
||||
}
|
||||
|
||||
if ('install' !== $recipe->getJob()) {
|
||||
// default to not configuring
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isset($_SERVER['SYMFONY_DOCKER'])) {
|
||||
$answer = self::askDockerSupport($io, $recipe);
|
||||
} elseif (filter_var($_SERVER['SYMFONY_DOCKER'], \FILTER_VALIDATE_BOOLEAN)) {
|
||||
$answer = 'p';
|
||||
} else {
|
||||
$answer = 'x';
|
||||
}
|
||||
|
||||
if ('n' === $answer) {
|
||||
self::$configureDockerRecipes = false;
|
||||
|
||||
return self::$configureDockerRecipes;
|
||||
}
|
||||
if ('y' === $answer) {
|
||||
self::$configureDockerRecipes = true;
|
||||
|
||||
return self::$configureDockerRecipes;
|
||||
}
|
||||
|
||||
// yes or no permanently
|
||||
self::$configureDockerRecipes = 'p' === $answer;
|
||||
$json = new JsonFile(Factory::getComposerFile());
|
||||
$manipulator = new JsonManipulator(file_get_contents($json->getPath()));
|
||||
$manipulator->addSubNode('extra', 'symfony.docker', self::$configureDockerRecipes);
|
||||
file_put_contents($json->getPath(), $manipulator->getContents());
|
||||
|
||||
return self::$configureDockerRecipes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the config and return the name of the main Docker Compose file if applicable.
|
||||
*/
|
||||
private function normalizeConfig(array $config): array
|
||||
{
|
||||
foreach ($config as $key => $val) {
|
||||
// Support for the short recipe syntax that modifies compose.yaml only
|
||||
if (isset($val[0])) {
|
||||
return ['compose.yaml' => $config];
|
||||
}
|
||||
|
||||
if (!str_starts_with($key, 'docker-')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the recipe still use the legacy "docker-compose.yml" names, remove the "docker-" prefix and change the extension
|
||||
$newKey = pathinfo(substr($key, 7), \PATHINFO_FILENAME).'.yaml';
|
||||
$config[$newKey] = $val;
|
||||
unset($config[$key]);
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the Docker Compose file according to these rules: https://docs.docker.com/compose/reference/envvars/#compose_file.
|
||||
*/
|
||||
private function findDockerComposeFile(string $rootDir, string $file): ?string
|
||||
{
|
||||
if (isset($_SERVER['COMPOSE_FILE'])) {
|
||||
$filenameToFind = pathinfo($file, \PATHINFO_FILENAME);
|
||||
$separator = $_SERVER['COMPOSE_PATH_SEPARATOR'] ?? ('\\' === \DIRECTORY_SEPARATOR ? ';' : ':');
|
||||
|
||||
$files = explode($separator, $_SERVER['COMPOSE_FILE']);
|
||||
foreach ($files as $f) {
|
||||
$filename = pathinfo($f, \PATHINFO_FILENAME);
|
||||
if ($filename !== $filenameToFind && "docker-$filenameToFind" !== $filename) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$this->filesystem->isAbsolutePath($f)) {
|
||||
$f = realpath(sprintf('%s/%s', $rootDir, $f));
|
||||
}
|
||||
|
||||
if ($this->filesystem->exists($f)) {
|
||||
return $f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// COMPOSE_FILE not set, or doesn't contain the file we're looking for
|
||||
$dir = $rootDir;
|
||||
do {
|
||||
if (
|
||||
$this->filesystem->exists($dockerComposeFile = sprintf('%s/%s', $dir, $file)) ||
|
||||
// Test with the ".yml" extension if the file doesn't end up with ".yaml"
|
||||
$this->filesystem->exists($dockerComposeFile = substr($dockerComposeFile, 0, -3).'ml') ||
|
||||
// Test with the legacy "docker-" suffix if "compose.ya?ml" doesn't exist
|
||||
$this->filesystem->exists($dockerComposeFile = sprintf('%s/docker-%s', $dir, $file)) ||
|
||||
$this->filesystem->exists($dockerComposeFile = substr($dockerComposeFile, 0, -3).'ml')
|
||||
) {
|
||||
return $dockerComposeFile;
|
||||
}
|
||||
|
||||
$previousDir = $dir;
|
||||
$dir = \dirname($dir);
|
||||
} while ($dir !== $previousDir);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function parse($level, $indent, $services): string
|
||||
{
|
||||
$line = '';
|
||||
foreach ($services as $key => $value) {
|
||||
$line .= str_repeat(' ', $indent * $level);
|
||||
if (!\is_array($value)) {
|
||||
if (\is_string($key)) {
|
||||
$line .= sprintf('%s:', $key);
|
||||
}
|
||||
$line .= sprintf("%s\n", $value);
|
||||
continue;
|
||||
}
|
||||
$line .= sprintf("%s:\n", $key).$this->parse($level + 1, $indent, $value);
|
||||
}
|
||||
|
||||
return $line;
|
||||
}
|
||||
|
||||
private function configureDockerCompose(Recipe $recipe, array $config, bool $update): void
|
||||
{
|
||||
$rootDir = $this->options->get('root-dir');
|
||||
foreach ($this->normalizeConfig($config) as $file => $extra) {
|
||||
$dockerComposeFile = $this->findDockerComposeFile($rootDir, $file);
|
||||
if (null === $dockerComposeFile) {
|
||||
$dockerComposeFile = $rootDir.'/'.$file;
|
||||
file_put_contents($dockerComposeFile, '');
|
||||
$this->write(sprintf(' Created <fg=green>"%s"</>', $file));
|
||||
}
|
||||
|
||||
if (!$update && $this->isFileMarked($recipe, $dockerComposeFile)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->write(sprintf('Adding Docker Compose definitions to "%s"', $dockerComposeFile));
|
||||
|
||||
$offset = 2;
|
||||
$node = null;
|
||||
$endAt = [];
|
||||
$startAt = [];
|
||||
$lines = [];
|
||||
$nodesLines = [];
|
||||
foreach (file($dockerComposeFile) as $i => $line) {
|
||||
$lines[] = $line;
|
||||
$ltrimedLine = ltrim($line, ' ');
|
||||
if (null !== $node) {
|
||||
$nodesLines[$node][$i] = $line;
|
||||
}
|
||||
|
||||
// Skip blank lines and comments
|
||||
if (('' !== $ltrimedLine && 0 === strpos($ltrimedLine, '#')) || '' === trim($line)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract Docker Compose keys (usually "services" and "volumes")
|
||||
if (!preg_match('/^[\'"]?([a-zA-Z0-9]+)[\'"]?:\s*$/', $line, $matches)) {
|
||||
// Detect indentation to use
|
||||
$offestLine = \strlen($line) - \strlen($ltrimedLine);
|
||||
if ($offset > $offestLine && 0 !== $offestLine) {
|
||||
$offset = $offestLine;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Keep end in memory (check break line on previous line)
|
||||
$endAt[$node] = !$i || '' !== trim($lines[$i - 1]) ? $i : $i - 1;
|
||||
$node = $matches[1];
|
||||
if (!isset($nodesLines[$node])) {
|
||||
$nodesLines[$node] = [];
|
||||
}
|
||||
if (!isset($startAt[$node])) {
|
||||
// the section contents starts at the next line
|
||||
$startAt[$node] = $i + 1;
|
||||
}
|
||||
}
|
||||
$endAt[$node] = \count($lines) + 1;
|
||||
|
||||
foreach ($extra as $key => $value) {
|
||||
if (isset($endAt[$key])) {
|
||||
$data = $this->markData($recipe, $this->parse(1, $offset, $value));
|
||||
$updatedContents = $this->updateDataString(implode('', $nodesLines[$key]), $data);
|
||||
if (null === $updatedContents) {
|
||||
// not an update: just add to section
|
||||
array_splice($lines, $endAt[$key], 0, $data);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$originalEndAt = $endAt[$key];
|
||||
$length = $endAt[$key] - $startAt[$key];
|
||||
array_splice($lines, $startAt[$key], $length, ltrim($updatedContents, "\n"));
|
||||
|
||||
// reset any start/end positions after this to the new positions
|
||||
foreach ($startAt as $sectionKey => $at) {
|
||||
if ($at > $originalEndAt) {
|
||||
$startAt[$sectionKey] = $at - $length - 1;
|
||||
}
|
||||
}
|
||||
foreach ($endAt as $sectionKey => $at) {
|
||||
if ($at > $originalEndAt) {
|
||||
$endAt[$sectionKey] = $at - $length;
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$lines[] = sprintf("\n%s:", $key);
|
||||
$lines[] = $this->markData($recipe, $this->parse(1, $offset, $value));
|
||||
}
|
||||
|
||||
file_put_contents($dockerComposeFile, implode('', $lines));
|
||||
}
|
||||
}
|
||||
|
||||
private function getContentsAfterApplyingRecipe(string $rootDir, Recipe $recipe, array $config): array
|
||||
{
|
||||
if (0 === \count($config)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$files = array_filter(array_map(function ($file) use ($rootDir) {
|
||||
return $this->findDockerComposeFile($rootDir, $file);
|
||||
}, array_keys($config)));
|
||||
|
||||
$originalContents = [];
|
||||
foreach ($files as $file) {
|
||||
$originalContents[$file] = file_exists($file) ? file_get_contents($file) : null;
|
||||
}
|
||||
|
||||
$this->configureDockerCompose(
|
||||
$recipe,
|
||||
$config,
|
||||
true
|
||||
);
|
||||
|
||||
$updatedContents = [];
|
||||
foreach ($files as $file) {
|
||||
$localPath = $file;
|
||||
if (0 === strpos($file, $rootDir)) {
|
||||
$localPath = substr($file, \strlen($rootDir) + 1);
|
||||
}
|
||||
$localPath = ltrim($localPath, '/\\');
|
||||
$updatedContents[$localPath] = file_exists($file) ? file_get_contents($file) : null;
|
||||
}
|
||||
|
||||
foreach ($originalContents as $file => $contents) {
|
||||
if (null === $contents) {
|
||||
if (file_exists($file)) {
|
||||
unlink($file);
|
||||
}
|
||||
} else {
|
||||
file_put_contents($file, $contents);
|
||||
}
|
||||
}
|
||||
|
||||
return $updatedContents;
|
||||
}
|
||||
|
||||
private static function askDockerSupport(IOInterface $io, Recipe $recipe): string
|
||||
{
|
||||
$warning = $io->isInteractive() ? 'WARNING' : 'IGNORING';
|
||||
$io->writeError(sprintf(' - <warning> %s </> %s', $warning, $recipe->getFormattedOrigin()));
|
||||
$question = ' The recipe for this package contains some Docker configuration.
|
||||
|
||||
This may create/update <comment>compose.yaml</comment> or update <comment>Dockerfile</comment> (if it exists).
|
||||
|
||||
Do you want to include Docker configuration from recipes?
|
||||
[<comment>y</>] Yes
|
||||
[<comment>n</>] No
|
||||
[<comment>p</>] Yes permanently, never ask again for this project
|
||||
[<comment>x</>] No permanently, never ask again for this project
|
||||
(defaults to <comment>y</>): ';
|
||||
|
||||
return $io->askAndValidate(
|
||||
$question,
|
||||
function ($value) {
|
||||
if (null === $value) {
|
||||
return 'y';
|
||||
}
|
||||
$value = strtolower($value[0]);
|
||||
if (!\in_array($value, ['y', 'n', 'p', 'x'], true)) {
|
||||
throw new \InvalidArgumentException('Invalid choice.');
|
||||
}
|
||||
|
||||
return $value;
|
||||
},
|
||||
null,
|
||||
'y'
|
||||
);
|
||||
}
|
||||
}
|
||||
125
vendor/symfony/flex/src/Configurator/DockerfileConfigurator.php
vendored
Normal file
125
vendor/symfony/flex/src/Configurator/DockerfileConfigurator.php
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex\Configurator;
|
||||
|
||||
use Symfony\Flex\Lock;
|
||||
use Symfony\Flex\Recipe;
|
||||
use Symfony\Flex\Update\RecipeUpdate;
|
||||
|
||||
/**
|
||||
* Adds commands to a Dockerfile.
|
||||
*
|
||||
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||
*/
|
||||
class DockerfileConfigurator extends AbstractConfigurator
|
||||
{
|
||||
public function configure(Recipe $recipe, $config, Lock $lock, array $options = [])
|
||||
{
|
||||
if (!DockerComposeConfigurator::shouldConfigureDockerRecipe($this->composer, $this->io, $recipe)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->configureDockerfile($recipe, $config, $options['force'] ?? false);
|
||||
}
|
||||
|
||||
public function unconfigure(Recipe $recipe, $config, Lock $lock)
|
||||
{
|
||||
if (!file_exists($dockerfile = $this->options->get('root-dir').'/Dockerfile')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$name = $recipe->getName();
|
||||
$contents = preg_replace(sprintf('{%s+###> %s ###.*?###< %s ###%s+}s', "\n", $name, $name, "\n"), "\n", file_get_contents($dockerfile), -1, $count);
|
||||
if (!$count) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->write('Removing Dockerfile entries');
|
||||
file_put_contents($dockerfile, ltrim($contents, "\n"));
|
||||
}
|
||||
|
||||
public function update(RecipeUpdate $recipeUpdate, array $originalConfig, array $newConfig): void
|
||||
{
|
||||
if (!DockerComposeConfigurator::shouldConfigureDockerRecipe($this->composer, $this->io, $recipeUpdate->getNewRecipe())) {
|
||||
return;
|
||||
}
|
||||
|
||||
$recipeUpdate->setOriginalFile(
|
||||
'Dockerfile',
|
||||
$this->getContentsAfterApplyingRecipe($recipeUpdate->getOriginalRecipe(), $originalConfig)
|
||||
);
|
||||
|
||||
$recipeUpdate->setNewFile(
|
||||
'Dockerfile',
|
||||
$this->getContentsAfterApplyingRecipe($recipeUpdate->getNewRecipe(), $newConfig)
|
||||
);
|
||||
}
|
||||
|
||||
private function configureDockerfile(Recipe $recipe, array $config, bool $update, bool $writeOutput = true): void
|
||||
{
|
||||
$dockerfile = $this->options->get('root-dir').'/Dockerfile';
|
||||
if (!file_exists($dockerfile) || (!$update && $this->isFileMarked($recipe, $dockerfile))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($writeOutput) {
|
||||
$this->write('Adding Dockerfile entries');
|
||||
}
|
||||
|
||||
$data = ltrim($this->markData($recipe, implode("\n", $config)), "\n");
|
||||
if ($this->updateData($dockerfile, $data)) {
|
||||
// done! Existing spot updated
|
||||
return;
|
||||
}
|
||||
|
||||
$lines = [];
|
||||
foreach (file($dockerfile) as $line) {
|
||||
$lines[] = $line;
|
||||
if (!preg_match('/^###> recipes ###$/', $line)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$lines[] = $data;
|
||||
}
|
||||
|
||||
file_put_contents($dockerfile, implode('', $lines));
|
||||
}
|
||||
|
||||
private function getContentsAfterApplyingRecipe(Recipe $recipe, array $config): ?string
|
||||
{
|
||||
if (0 === \count($config)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$dockerfile = $this->options->get('root-dir').'/Dockerfile';
|
||||
$originalContents = file_exists($dockerfile) ? file_get_contents($dockerfile) : null;
|
||||
|
||||
$this->configureDockerfile(
|
||||
$recipe,
|
||||
$config,
|
||||
true,
|
||||
false
|
||||
);
|
||||
|
||||
$updatedContents = file_exists($dockerfile) ? file_get_contents($dockerfile) : null;
|
||||
|
||||
if (null === $originalContents) {
|
||||
if (file_exists($dockerfile)) {
|
||||
unlink($dockerfile);
|
||||
}
|
||||
} else {
|
||||
file_put_contents($dockerfile, $originalContents);
|
||||
}
|
||||
|
||||
return $updatedContents;
|
||||
}
|
||||
}
|
||||
50
vendor/symfony/flex/src/Configurator/DotenvConfigurator.php
vendored
Normal file
50
vendor/symfony/flex/src/Configurator/DotenvConfigurator.php
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex\Configurator;
|
||||
|
||||
use Symfony\Flex\Lock;
|
||||
use Symfony\Flex\Recipe;
|
||||
use Symfony\Flex\Update\RecipeUpdate;
|
||||
|
||||
class DotenvConfigurator extends AbstractConfigurator
|
||||
{
|
||||
public function configure(Recipe $recipe, $vars, Lock $lock, array $options = [])
|
||||
{
|
||||
foreach ($vars as $suffix => $vars) {
|
||||
$configurator = new EnvConfigurator($this->composer, $this->io, $this->options, $suffix);
|
||||
$configurator->configure($recipe, $vars, $lock, $options);
|
||||
}
|
||||
}
|
||||
|
||||
public function unconfigure(Recipe $recipe, $vars, Lock $lock)
|
||||
{
|
||||
foreach ($vars as $suffix => $vars) {
|
||||
$configurator = new EnvConfigurator($this->composer, $this->io, $this->options, $suffix);
|
||||
$configurator->unconfigure($recipe, $vars, $lock);
|
||||
}
|
||||
}
|
||||
|
||||
public function update(RecipeUpdate $recipeUpdate, array $originalConfig, array $newConfig): void
|
||||
{
|
||||
foreach ($originalConfig as $suffix => $vars) {
|
||||
$configurator = new EnvConfigurator($this->composer, $this->io, $this->options, $suffix);
|
||||
$configurator->update($recipeUpdate, $vars, $newConfig[$suffix] ?? []);
|
||||
}
|
||||
|
||||
foreach ($newConfig as $suffix => $vars) {
|
||||
if (!isset($originalConfig[$suffix])) {
|
||||
$configurator = new EnvConfigurator($this->composer, $this->io, $this->options, $suffix);
|
||||
$configurator->update($recipeUpdate, [], $vars);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
295
vendor/symfony/flex/src/Configurator/EnvConfigurator.php
vendored
Normal file
295
vendor/symfony/flex/src/Configurator/EnvConfigurator.php
vendored
Normal file
@@ -0,0 +1,295 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex\Configurator;
|
||||
|
||||
use Composer\Composer;
|
||||
use Composer\IO\IOInterface;
|
||||
use Symfony\Flex\Lock;
|
||||
use Symfony\Flex\Options;
|
||||
use Symfony\Flex\Recipe;
|
||||
use Symfony\Flex\Update\RecipeUpdate;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class EnvConfigurator extends AbstractConfigurator
|
||||
{
|
||||
private string $suffix;
|
||||
|
||||
public function __construct(Composer $composer, IOInterface $io, Options $options, string $suffix = '')
|
||||
{
|
||||
parent::__construct($composer, $io, $options);
|
||||
$this->suffix = $suffix;
|
||||
}
|
||||
|
||||
public function configure(Recipe $recipe, $vars, Lock $lock, array $options = [])
|
||||
{
|
||||
$this->write('Adding environment variable defaults'.('' === $this->suffix ? '' : ' ('.$this->suffix.')'));
|
||||
|
||||
$this->configureEnvDist($recipe, $vars, $options['force'] ?? false);
|
||||
|
||||
if ('' !== $this->suffix) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!file_exists($this->options->get('root-dir').'/'.($this->options->get('runtime')['dotenv_path'] ?? '.env').'.test')) {
|
||||
$this->configurePhpUnit($recipe, $vars, $options['force'] ?? false);
|
||||
}
|
||||
}
|
||||
|
||||
public function unconfigure(Recipe $recipe, $vars, Lock $lock)
|
||||
{
|
||||
$this->unconfigureEnvFiles($recipe, $vars);
|
||||
$this->unconfigurePhpUnit($recipe, $vars);
|
||||
}
|
||||
|
||||
public function update(RecipeUpdate $recipeUpdate, array $originalConfig, array $newConfig): void
|
||||
{
|
||||
$recipeUpdate->addOriginalFiles(
|
||||
$this->getContentsAfterApplyingRecipe($recipeUpdate->getRootDir(), $recipeUpdate->getOriginalRecipe(), $originalConfig)
|
||||
);
|
||||
|
||||
$recipeUpdate->addNewFiles(
|
||||
$this->getContentsAfterApplyingRecipe($recipeUpdate->getRootDir(), $recipeUpdate->getNewRecipe(), $newConfig)
|
||||
);
|
||||
}
|
||||
|
||||
private function configureEnvDist(Recipe $recipe, $vars, bool $update)
|
||||
{
|
||||
$dotenvPath = $this->options->get('runtime')['dotenv_path'] ?? '.env';
|
||||
$files = '' === $this->suffix ? [$dotenvPath.'.dist', $dotenvPath] : [$dotenvPath.'.'.$this->suffix];
|
||||
|
||||
foreach ($files as $file) {
|
||||
$env = $this->options->get('root-dir').'/'.$file;
|
||||
if (!is_file($env)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$update && $this->isFileMarked($recipe, $env)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$data = '';
|
||||
foreach ($vars as $key => $value) {
|
||||
$existingValue = $update ? $this->findExistingValue($key, $env, $recipe) : null;
|
||||
$value = $this->evaluateValue($value, $existingValue);
|
||||
if ('#' === $key[0] && is_numeric(substr($key, 1))) {
|
||||
if ('' === $value) {
|
||||
$data .= "#\n";
|
||||
} else {
|
||||
$data .= '# '.$value."\n";
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = $this->options->expandTargetDir($value);
|
||||
if (false !== strpbrk($value, " \t\n&!\"")) {
|
||||
$value = '"'.str_replace(['\\', '"', "\t", "\n"], ['\\\\', '\\"', '\t', '\n'], $value).'"';
|
||||
}
|
||||
$data .= "$key=$value\n";
|
||||
}
|
||||
$data = $this->markData($recipe, $data);
|
||||
|
||||
if (!$this->updateData($env, $data)) {
|
||||
file_put_contents($env, $data, \FILE_APPEND);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function configurePhpUnit(Recipe $recipe, $vars, bool $update)
|
||||
{
|
||||
foreach (['phpunit.xml.dist', 'phpunit.xml'] as $file) {
|
||||
$phpunit = $this->options->get('root-dir').'/'.$file;
|
||||
if (!is_file($phpunit)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$update && $this->isFileXmlMarked($recipe, $phpunit)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$data = '';
|
||||
foreach ($vars as $key => $value) {
|
||||
$value = $this->evaluateValue($value);
|
||||
if ('#' === $key[0]) {
|
||||
if (is_numeric(substr($key, 1))) {
|
||||
$doc = new \DOMDocument();
|
||||
$data .= ' '.$doc->saveXML($doc->createComment(' '.$value.' '))."\n";
|
||||
} else {
|
||||
$value = $this->options->expandTargetDir($value);
|
||||
$doc = new \DOMDocument();
|
||||
$fragment = $doc->createElement('env');
|
||||
$fragment->setAttribute('name', substr($key, 1));
|
||||
$fragment->setAttribute('value', $value);
|
||||
$data .= ' '.str_replace(['<', '/>'], ['<!-- ', ' -->'], $doc->saveXML($fragment))."\n";
|
||||
}
|
||||
} else {
|
||||
$value = $this->options->expandTargetDir($value);
|
||||
$doc = new \DOMDocument();
|
||||
$fragment = $doc->createElement('env');
|
||||
$fragment->setAttribute('name', $key);
|
||||
$fragment->setAttribute('value', $value);
|
||||
$data .= ' '.$doc->saveXML($fragment)."\n";
|
||||
}
|
||||
}
|
||||
$data = $this->markXmlData($recipe, $data);
|
||||
|
||||
if (!$this->updateData($phpunit, $data)) {
|
||||
file_put_contents($phpunit, preg_replace('{^(\s+</php>)}m', $data.'$1', file_get_contents($phpunit)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function unconfigureEnvFiles(Recipe $recipe, $vars)
|
||||
{
|
||||
$dotenvPath = $this->options->get('runtime')['dotenv_path'] ?? '.env';
|
||||
$files = '' === $this->suffix ? [$dotenvPath, $dotenvPath.'.dist'] : [$dotenvPath.'.'.$this->suffix];
|
||||
|
||||
foreach ($files as $file) {
|
||||
$env = $this->options->get('root-dir').'/'.$file;
|
||||
if (!file_exists($env)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$contents = preg_replace(\sprintf('{%s*###> %s ###.*###< %s ###%s+}s', "\n", $recipe->getName(), $recipe->getName(), "\n"), "\n", file_get_contents($env), -1, $count);
|
||||
if (!$count) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->write(\sprintf('Removing environment variables from %s', $file));
|
||||
file_put_contents($env, $contents);
|
||||
}
|
||||
}
|
||||
|
||||
private function unconfigurePhpUnit(Recipe $recipe, $vars)
|
||||
{
|
||||
foreach (['phpunit.xml.dist', 'phpunit.xml'] as $file) {
|
||||
$phpunit = $this->options->get('root-dir').'/'.$file;
|
||||
if (!is_file($phpunit)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$contents = preg_replace(\sprintf('{%s*\s+<!-- ###\+ %s ### -->.*<!-- ###- %s ### -->%s+}s', "\n", $recipe->getName(), $recipe->getName(), "\n"), "\n", file_get_contents($phpunit), -1, $count);
|
||||
if (!$count) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->write(\sprintf('Removing environment variables from %s', $file));
|
||||
file_put_contents($phpunit, $contents);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates expressions like %generate(secret)%.
|
||||
*
|
||||
* If $originalValue is passed, and the value contains an expression.
|
||||
* the $originalValue is used.
|
||||
*/
|
||||
private function evaluateValue($value, ?string $originalValue = null)
|
||||
{
|
||||
if ('%generate(secret)%' === $value) {
|
||||
if (null !== $originalValue) {
|
||||
return $originalValue;
|
||||
}
|
||||
|
||||
return $this->generateRandomBytes();
|
||||
}
|
||||
if (preg_match('~^%generate\(secret,\s*([0-9]+)\)%$~', $value, $matches)) {
|
||||
if (null !== $originalValue) {
|
||||
return $originalValue;
|
||||
}
|
||||
|
||||
return $this->generateRandomBytes($matches[1]);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
private function generateRandomBytes($length = 16)
|
||||
{
|
||||
return bin2hex(random_bytes($length));
|
||||
}
|
||||
|
||||
private function getContentsAfterApplyingRecipe(string $rootDir, Recipe $recipe, array $vars): array
|
||||
{
|
||||
$dotenvPath = $this->options->get('runtime')['dotenv_path'] ?? '.env';
|
||||
$files = '' === $this->suffix ? [$dotenvPath, $dotenvPath.'.dist', 'phpunit.xml.dist', 'phpunit.xml'] : [$dotenvPath.'.'.$this->suffix];
|
||||
|
||||
if (0 === \count($vars)) {
|
||||
return array_fill_keys($files, null);
|
||||
}
|
||||
|
||||
$originalContents = [];
|
||||
foreach ($files as $file) {
|
||||
$originalContents[$file] = file_exists($rootDir.'/'.$file) ? file_get_contents($rootDir.'/'.$file) : null;
|
||||
}
|
||||
|
||||
$this->configureEnvDist(
|
||||
$recipe,
|
||||
$vars,
|
||||
true
|
||||
);
|
||||
|
||||
if ('' === $this->suffix && !file_exists($rootDir.'/'.$dotenvPath.'.test')) {
|
||||
$this->configurePhpUnit(
|
||||
$recipe,
|
||||
$vars,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
$updatedContents = [];
|
||||
foreach ($files as $file) {
|
||||
$updatedContents[$file] = file_exists($rootDir.'/'.$file) ? file_get_contents($rootDir.'/'.$file) : null;
|
||||
}
|
||||
|
||||
foreach ($originalContents as $file => $contents) {
|
||||
if (null === $contents) {
|
||||
if (file_exists($rootDir.'/'.$file)) {
|
||||
unlink($rootDir.'/'.$file);
|
||||
}
|
||||
} else {
|
||||
file_put_contents($rootDir.'/'.$file, $contents);
|
||||
}
|
||||
}
|
||||
|
||||
return $updatedContents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to find the existing value of an environment variable.
|
||||
*/
|
||||
private function findExistingValue(string $var, string $filename, Recipe $recipe): ?string
|
||||
{
|
||||
if (!file_exists($filename)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$contents = file_get_contents($filename);
|
||||
$section = $this->extractSection($recipe, $contents);
|
||||
if (!$section) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$lines = explode("\n", $section);
|
||||
foreach ($lines as $line) {
|
||||
if (!str_starts_with($line, \sprintf('%s=', $var))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return trim(substr($line, \strlen($var) + 1));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
105
vendor/symfony/flex/src/Configurator/GitignoreConfigurator.php
vendored
Normal file
105
vendor/symfony/flex/src/Configurator/GitignoreConfigurator.php
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex\Configurator;
|
||||
|
||||
use Symfony\Flex\Lock;
|
||||
use Symfony\Flex\Recipe;
|
||||
use Symfony\Flex\Update\RecipeUpdate;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class GitignoreConfigurator extends AbstractConfigurator
|
||||
{
|
||||
public function configure(Recipe $recipe, $vars, Lock $lock, array $options = [])
|
||||
{
|
||||
$this->write('Adding entries to .gitignore');
|
||||
|
||||
$this->configureGitignore($recipe, $vars, $options['force'] ?? false);
|
||||
}
|
||||
|
||||
public function unconfigure(Recipe $recipe, $vars, Lock $lock)
|
||||
{
|
||||
$file = $this->options->get('root-dir').'/.gitignore';
|
||||
if (!file_exists($file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$contents = preg_replace(sprintf('{%s*###> %s ###.*###< %s ###%s+}s', "\n", $recipe->getName(), $recipe->getName(), "\n"), "\n", file_get_contents($file), -1, $count);
|
||||
if (!$count) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->write('Removing entries in .gitignore');
|
||||
file_put_contents($file, ltrim($contents, "\r\n"));
|
||||
}
|
||||
|
||||
public function update(RecipeUpdate $recipeUpdate, array $originalConfig, array $newConfig): void
|
||||
{
|
||||
$recipeUpdate->setOriginalFile(
|
||||
'.gitignore',
|
||||
$this->getContentsAfterApplyingRecipe($recipeUpdate->getRootDir(), $recipeUpdate->getOriginalRecipe(), $originalConfig)
|
||||
);
|
||||
|
||||
$recipeUpdate->setNewFile(
|
||||
'.gitignore',
|
||||
$this->getContentsAfterApplyingRecipe($recipeUpdate->getRootDir(), $recipeUpdate->getNewRecipe(), $newConfig)
|
||||
);
|
||||
}
|
||||
|
||||
private function configureGitignore(Recipe $recipe, array $vars, bool $update)
|
||||
{
|
||||
$gitignore = $this->options->get('root-dir').'/.gitignore';
|
||||
if (!$update && $this->isFileMarked($recipe, $gitignore)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data = '';
|
||||
foreach ($vars as $value) {
|
||||
$value = $this->options->expandTargetDir($value);
|
||||
$data .= "$value\n";
|
||||
}
|
||||
$data = "\n".ltrim($this->markData($recipe, $data), "\r\n");
|
||||
|
||||
if (!$this->updateData($gitignore, $data)) {
|
||||
file_put_contents($gitignore, $data, \FILE_APPEND);
|
||||
}
|
||||
}
|
||||
|
||||
private function getContentsAfterApplyingRecipe(string $rootDir, Recipe $recipe, $vars): ?string
|
||||
{
|
||||
if (0 === \count($vars)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$file = $rootDir.'/.gitignore';
|
||||
$originalContents = file_exists($file) ? file_get_contents($file) : null;
|
||||
|
||||
$this->configureGitignore(
|
||||
$recipe,
|
||||
$vars,
|
||||
true
|
||||
);
|
||||
|
||||
$updatedContents = file_exists($file) ? file_get_contents($file) : null;
|
||||
|
||||
if (null === $originalContents) {
|
||||
if (file_exists($file)) {
|
||||
unlink($file);
|
||||
}
|
||||
} else {
|
||||
file_put_contents($file, $originalContents);
|
||||
}
|
||||
|
||||
return $updatedContents;
|
||||
}
|
||||
}
|
||||
124
vendor/symfony/flex/src/Configurator/MakefileConfigurator.php
vendored
Normal file
124
vendor/symfony/flex/src/Configurator/MakefileConfigurator.php
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex\Configurator;
|
||||
|
||||
use Symfony\Flex\Lock;
|
||||
use Symfony\Flex\Recipe;
|
||||
use Symfony\Flex\Update\RecipeUpdate;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class MakefileConfigurator extends AbstractConfigurator
|
||||
{
|
||||
public function configure(Recipe $recipe, $definitions, Lock $lock, array $options = [])
|
||||
{
|
||||
$this->write('Adding Makefile entries');
|
||||
|
||||
$this->configureMakefile($recipe, $definitions, $options['force'] ?? false);
|
||||
}
|
||||
|
||||
public function unconfigure(Recipe $recipe, $vars, Lock $lock)
|
||||
{
|
||||
if (!file_exists($makefile = $this->options->get('root-dir').'/Makefile')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$contents = preg_replace(sprintf('{%s*###> %s ###.*###< %s ###%s+}s', "\n", $recipe->getName(), $recipe->getName(), "\n"), "\n", file_get_contents($makefile), -1, $count);
|
||||
if (!$count) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->write(sprintf('Removing Makefile entries from %s', $makefile));
|
||||
if (!trim($contents)) {
|
||||
@unlink($makefile);
|
||||
} else {
|
||||
file_put_contents($makefile, ltrim($contents, "\r\n"));
|
||||
}
|
||||
}
|
||||
|
||||
public function update(RecipeUpdate $recipeUpdate, array $originalConfig, array $newConfig): void
|
||||
{
|
||||
$recipeUpdate->setOriginalFile(
|
||||
'Makefile',
|
||||
$this->getContentsAfterApplyingRecipe($recipeUpdate->getRootDir(), $recipeUpdate->getOriginalRecipe(), $originalConfig)
|
||||
);
|
||||
|
||||
$recipeUpdate->setNewFile(
|
||||
'Makefile',
|
||||
$this->getContentsAfterApplyingRecipe($recipeUpdate->getRootDir(), $recipeUpdate->getNewRecipe(), $newConfig)
|
||||
);
|
||||
}
|
||||
|
||||
private function configureMakefile(Recipe $recipe, array $definitions, bool $update)
|
||||
{
|
||||
$makefile = $this->options->get('root-dir').'/Makefile';
|
||||
if (!$update && $this->isFileMarked($recipe, $makefile)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data = $this->options->expandTargetDir(implode("\n", $definitions));
|
||||
$data = $this->markData($recipe, $data);
|
||||
$data = "\n".ltrim($data, "\r\n");
|
||||
|
||||
if (!file_exists($makefile)) {
|
||||
$envKey = $this->options->get('runtime')['env_var_name'] ?? 'APP_ENV';
|
||||
$dotenvPath = $this->options->get('runtime')['dotenv_path'] ?? '.env';
|
||||
file_put_contents(
|
||||
$this->options->get('root-dir').'/Makefile',
|
||||
<<<EOF
|
||||
ifndef {$envKey}
|
||||
include {$dotenvPath}
|
||||
endif
|
||||
|
||||
.DEFAULT_GOAL := help
|
||||
.PHONY: help
|
||||
help:
|
||||
@awk 'BEGIN {FS = ":.*?## "}; /^[a-zA-Z-]+:.*?## .*$$/ {printf "\033[32m%-15s\033[0m %s\\n", $$1, $$2}' Makefile | sort
|
||||
|
||||
EOF
|
||||
);
|
||||
}
|
||||
|
||||
if (!$this->updateData($makefile, $data)) {
|
||||
file_put_contents($makefile, $data, \FILE_APPEND);
|
||||
}
|
||||
}
|
||||
|
||||
private function getContentsAfterApplyingRecipe(string $rootDir, Recipe $recipe, array $definitions): ?string
|
||||
{
|
||||
if (0 === \count($definitions)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$file = $rootDir.'/Makefile';
|
||||
$originalContents = file_exists($file) ? file_get_contents($file) : null;
|
||||
|
||||
$this->configureMakefile(
|
||||
$recipe,
|
||||
$definitions,
|
||||
true
|
||||
);
|
||||
|
||||
$updatedContents = file_exists($file) ? file_get_contents($file) : null;
|
||||
|
||||
if (null === $originalContents) {
|
||||
if (file_exists($file)) {
|
||||
unlink($file);
|
||||
}
|
||||
} else {
|
||||
file_put_contents($file, $originalContents);
|
||||
}
|
||||
|
||||
return $updatedContents;
|
||||
}
|
||||
}
|
||||
469
vendor/symfony/flex/src/Downloader.php
vendored
Normal file
469
vendor/symfony/flex/src/Downloader.php
vendored
Normal file
@@ -0,0 +1,469 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex;
|
||||
|
||||
use Composer\Cache;
|
||||
use Composer\Composer;
|
||||
use Composer\DependencyResolver\Operation\OperationInterface;
|
||||
use Composer\DependencyResolver\Operation\UninstallOperation;
|
||||
use Composer\DependencyResolver\Operation\UpdateOperation;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Json\JsonFile;
|
||||
use Composer\Util\Http\Response as ComposerResponse;
|
||||
use Composer\Util\HttpDownloader;
|
||||
use Composer\Util\Loop;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class Downloader
|
||||
{
|
||||
private const DEFAULT_ENDPOINTS = [
|
||||
'https://raw.githubusercontent.com/symfony/recipes/flex/main/index.json',
|
||||
'https://raw.githubusercontent.com/symfony/recipes-contrib/flex/main/index.json',
|
||||
];
|
||||
private const MAX_LENGTH = 1000;
|
||||
|
||||
private static $versions;
|
||||
private static $aliases;
|
||||
|
||||
private $io;
|
||||
private $sess;
|
||||
private $cache;
|
||||
|
||||
private HttpDownloader $rfs;
|
||||
private $degradedMode = false;
|
||||
private $endpoints;
|
||||
private $index;
|
||||
private $conflicts;
|
||||
private $legacyEndpoint;
|
||||
private $caFile;
|
||||
private $enabled = true;
|
||||
private $composer;
|
||||
|
||||
public function __construct(Composer $composer, IOInterface $io, HttpDownloader $rfs)
|
||||
{
|
||||
if (getenv('SYMFONY_CAFILE')) {
|
||||
$this->caFile = getenv('SYMFONY_CAFILE');
|
||||
}
|
||||
|
||||
if (null === $endpoint = $composer->getPackage()->getExtra()['symfony']['endpoint'] ?? null) {
|
||||
$this->endpoints = self::DEFAULT_ENDPOINTS;
|
||||
} elseif (\is_array($endpoint) || false !== strpos($endpoint, '.json') || 'flex://defaults' === $endpoint) {
|
||||
$this->endpoints = array_values((array) $endpoint);
|
||||
if (\is_string($endpoint) && false !== strpos($endpoint, '.json')) {
|
||||
$this->endpoints[] = 'flex://defaults';
|
||||
}
|
||||
} else {
|
||||
$this->legacyEndpoint = rtrim($endpoint, '/');
|
||||
}
|
||||
|
||||
if (false === $endpoint = getenv('SYMFONY_ENDPOINT')) {
|
||||
// no-op
|
||||
} elseif (false !== strpos($endpoint, '.json') || 'flex://defaults' === $endpoint) {
|
||||
$this->endpoints ?? $this->endpoints = self::DEFAULT_ENDPOINTS;
|
||||
array_unshift($this->endpoints, $endpoint);
|
||||
$this->legacyEndpoint = null;
|
||||
} else {
|
||||
$this->endpoints = null;
|
||||
$this->legacyEndpoint = rtrim($endpoint, '/');
|
||||
}
|
||||
|
||||
if (null !== $this->endpoints) {
|
||||
if (false !== $i = array_search('flex://defaults', $this->endpoints, true)) {
|
||||
array_splice($this->endpoints, $i, 1, self::DEFAULT_ENDPOINTS);
|
||||
}
|
||||
|
||||
$this->endpoints = array_fill_keys($this->endpoints, []);
|
||||
}
|
||||
|
||||
$this->io = $io;
|
||||
$config = $composer->getConfig();
|
||||
$this->rfs = $rfs;
|
||||
$this->cache = new Cache($io, $config->get('cache-repo-dir').'/flex');
|
||||
$this->sess = bin2hex(random_bytes(16));
|
||||
$this->composer = $composer;
|
||||
}
|
||||
|
||||
public function getSessionId(): string
|
||||
{
|
||||
return $this->sess;
|
||||
}
|
||||
|
||||
public function isEnabled()
|
||||
{
|
||||
return $this->enabled;
|
||||
}
|
||||
|
||||
public function disable()
|
||||
{
|
||||
$this->enabled = false;
|
||||
}
|
||||
|
||||
public function getVersions()
|
||||
{
|
||||
$this->initialize();
|
||||
|
||||
return self::$versions ?? self::$versions = current($this->get([$this->legacyEndpoint.'/versions.json']));
|
||||
}
|
||||
|
||||
public function getAliases()
|
||||
{
|
||||
$this->initialize();
|
||||
|
||||
return self::$aliases ?? self::$aliases = current($this->get([$this->legacyEndpoint.'/aliases.json']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads recipes.
|
||||
*
|
||||
* @param OperationInterface[] $operations
|
||||
*/
|
||||
public function getRecipes(array $operations): array
|
||||
{
|
||||
$this->initialize();
|
||||
|
||||
if ($this->conflicts) {
|
||||
$lockedRepository = $this->composer->getLocker()->getLockedRepository();
|
||||
foreach ($this->conflicts as $conflicts) {
|
||||
foreach ($conflicts as $package => $versions) {
|
||||
foreach ($versions as $version => $conflicts) {
|
||||
foreach ($conflicts as $conflictingPackage => $constraint) {
|
||||
if ($lockedRepository->findPackage($conflictingPackage, $constraint)) {
|
||||
unset($this->index[$package][$version]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->conflicts = [];
|
||||
}
|
||||
|
||||
$data = [];
|
||||
$urls = [];
|
||||
$chunk = '';
|
||||
$recipeRef = null;
|
||||
foreach ($operations as $operation) {
|
||||
$o = 'i';
|
||||
if ($operation instanceof UpdateOperation) {
|
||||
$package = $operation->getTargetPackage();
|
||||
$o = 'u';
|
||||
} else {
|
||||
$package = $operation->getPackage();
|
||||
if ($operation instanceof UninstallOperation) {
|
||||
$o = 'r';
|
||||
}
|
||||
|
||||
if ($operation instanceof InformationOperation) {
|
||||
$recipeRef = $operation->getRecipeRef();
|
||||
}
|
||||
}
|
||||
|
||||
$version = $package->getPrettyVersion();
|
||||
if ($operation instanceof InformationOperation && $operation->getVersion()) {
|
||||
$version = $operation->getVersion();
|
||||
}
|
||||
if (0 === strpos($version, 'dev-') && isset($package->getExtra()['branch-alias'])) {
|
||||
$branchAliases = $package->getExtra()['branch-alias'];
|
||||
if (
|
||||
(isset($branchAliases[$version]) && $alias = $branchAliases[$version]) ||
|
||||
(isset($branchAliases['dev-main']) && $alias = $branchAliases['dev-main']) ||
|
||||
(isset($branchAliases['dev-trunk']) && $alias = $branchAliases['dev-trunk']) ||
|
||||
(isset($branchAliases['dev-develop']) && $alias = $branchAliases['dev-develop']) ||
|
||||
(isset($branchAliases['dev-default']) && $alias = $branchAliases['dev-default']) ||
|
||||
(isset($branchAliases['dev-latest']) && $alias = $branchAliases['dev-latest']) ||
|
||||
(isset($branchAliases['dev-next']) && $alias = $branchAliases['dev-next']) ||
|
||||
(isset($branchAliases['dev-current']) && $alias = $branchAliases['dev-current']) ||
|
||||
(isset($branchAliases['dev-support']) && $alias = $branchAliases['dev-support']) ||
|
||||
(isset($branchAliases['dev-tip']) && $alias = $branchAliases['dev-tip']) ||
|
||||
(isset($branchAliases['dev-master']) && $alias = $branchAliases['dev-master'])
|
||||
) {
|
||||
$version = $alias;
|
||||
}
|
||||
}
|
||||
|
||||
if ($recipeVersions = $this->index[$package->getName()] ?? null) {
|
||||
$version = explode('.', preg_replace('/^dev-|^v|\.x-dev$|-dev$/', '', $version));
|
||||
$version = $version[0].'.'.($version[1] ?? '9999999');
|
||||
|
||||
foreach (array_reverse($recipeVersions) as $v => $endpoint) {
|
||||
if (version_compare($version, $v, '<')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$data['locks'][$package->getName()]['version'] = $version;
|
||||
$data['locks'][$package->getName()]['recipe']['version'] = $v;
|
||||
$links = $this->endpoints[$endpoint]['_links'];
|
||||
|
||||
if (null !== $recipeRef && isset($links['archived_recipes_template'])) {
|
||||
if (isset($links['archived_recipes_template_relative'])) {
|
||||
$links['archived_recipes_template'] = preg_replace('{[^/\?]*+(?=\?|$)}', $links['archived_recipes_template_relative'], $endpoint, 1);
|
||||
}
|
||||
|
||||
$urls[] = strtr($links['archived_recipes_template'], [
|
||||
'{package_dotted}' => str_replace('/', '.', $package->getName()),
|
||||
'{ref}' => $recipeRef,
|
||||
]);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (isset($links['recipe_template_relative'])) {
|
||||
$links['recipe_template'] = preg_replace('{[^/\?]*+(?=\?|$)}', $links['recipe_template_relative'], $endpoint, 1);
|
||||
}
|
||||
|
||||
$urls[] = strtr($links['recipe_template'], [
|
||||
'{package_dotted}' => str_replace('/', '.', $package->getName()),
|
||||
'{package}' => $package->getName(),
|
||||
'{version}' => $v,
|
||||
]);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (\is_array($recipeVersions)) {
|
||||
$data['conflicts'][$package->getName()] = true;
|
||||
}
|
||||
|
||||
if (null !== $this->endpoints) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// FIXME: Multi name with getNames()
|
||||
$name = str_replace('/', ',', $package->getName());
|
||||
$path = sprintf('%s,%s%s', $name, $o, $version);
|
||||
if ($date = $package->getReleaseDate()) {
|
||||
$path .= ','.$date->format('U');
|
||||
}
|
||||
if (\strlen($chunk) + \strlen($path) > self::MAX_LENGTH) {
|
||||
$urls[] = $this->legacyEndpoint.'/p/'.$chunk;
|
||||
$chunk = $path;
|
||||
} elseif ($chunk) {
|
||||
$chunk .= ';'.$path;
|
||||
} else {
|
||||
$chunk = $path;
|
||||
}
|
||||
}
|
||||
if ($chunk) {
|
||||
$urls[] = $this->legacyEndpoint.'/p/'.$chunk;
|
||||
}
|
||||
|
||||
if (null === $this->endpoints) {
|
||||
foreach ($this->get($urls, true) as $body) {
|
||||
foreach ($body['manifests'] ?? [] as $name => $manifest) {
|
||||
$data['manifests'][$name] = $manifest;
|
||||
}
|
||||
foreach ($body['locks'] ?? [] as $name => $lock) {
|
||||
$data['locks'][$name] = $lock;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
foreach ($this->get($urls, true) as $body) {
|
||||
foreach ($body['manifests'] ?? [] as $name => $manifest) {
|
||||
if (null === $version = $data['locks'][$name]['recipe']['version'] ?? null) {
|
||||
continue;
|
||||
}
|
||||
$endpoint = $this->endpoints[$this->index[$name][$version]];
|
||||
|
||||
$data['locks'][$name]['recipe'] = [
|
||||
'repo' => $endpoint['_links']['repository'],
|
||||
'branch' => $endpoint['branch'],
|
||||
'version' => $version,
|
||||
'ref' => $manifest['ref'],
|
||||
];
|
||||
|
||||
foreach ($manifest['files'] ?? [] as $i => $file) {
|
||||
$manifest['files'][$i]['contents'] = \is_array($file['contents']) ? implode("\n", $file['contents']) : base64_decode($file['contents']);
|
||||
}
|
||||
|
||||
$data['manifests'][$name] = $manifest + [
|
||||
'repository' => $endpoint['_links']['repository'],
|
||||
'package' => $name,
|
||||
'version' => $version,
|
||||
'origin' => strtr($endpoint['_links']['origin_template'], [
|
||||
'{package}' => $name,
|
||||
'{version}' => $version,
|
||||
]),
|
||||
'is_contrib' => $endpoint['is_contrib'] ?? false,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to "hide" a recipe version so that the next most-recent will be returned.
|
||||
*
|
||||
* This is used when resolving "conflicts".
|
||||
*/
|
||||
public function removeRecipeFromIndex(string $packageName, string $version)
|
||||
{
|
||||
unset($this->index[$packageName][$version]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches and decodes JSON HTTP response bodies.
|
||||
*/
|
||||
private function get(array $urls, bool $isRecipe = false, int $try = 3): array
|
||||
{
|
||||
$responses = [];
|
||||
$retries = [];
|
||||
$options = [];
|
||||
|
||||
foreach ($urls as $url) {
|
||||
$cacheKey = self::generateCacheKey($url);
|
||||
$headers = [];
|
||||
|
||||
if (preg_match('{^https?://api\.github\.com/}', $url)) {
|
||||
$headers[] = 'Accept: application/vnd.github.v3.raw';
|
||||
} elseif (preg_match('{^https?://raw\.githubusercontent\.com/}', $url) && $this->io->hasAuthentication('github.com')) {
|
||||
$auth = $this->io->getAuthentication('github.com');
|
||||
if ('x-oauth-basic' === $auth['password']) {
|
||||
$headers[] = 'Authorization: token '.$auth['username'];
|
||||
}
|
||||
} elseif ($this->legacyEndpoint) {
|
||||
$headers[] = 'Package-Session: '.$this->sess;
|
||||
}
|
||||
|
||||
if ($contents = $this->cache->read($cacheKey)) {
|
||||
$cachedResponse = Response::fromJson(json_decode($contents, true));
|
||||
if ($lastModified = $cachedResponse->getHeader('last-modified')) {
|
||||
$headers[] = 'If-Modified-Since: '.$lastModified;
|
||||
}
|
||||
if ($eTag = $cachedResponse->getHeader('etag')) {
|
||||
$headers[] = 'If-None-Match: '.$eTag;
|
||||
}
|
||||
$responses[$url] = $cachedResponse->getBody();
|
||||
}
|
||||
|
||||
$options[$url] = $this->getOptions($headers);
|
||||
}
|
||||
|
||||
$loop = new Loop($this->rfs);
|
||||
$jobs = [];
|
||||
foreach ($urls as $url) {
|
||||
$jobs[] = $this->rfs->add($url, $options[$url])->then(function (ComposerResponse $response) use ($url, &$responses) {
|
||||
if (200 === $response->getStatusCode()) {
|
||||
$cacheKey = self::generateCacheKey($url);
|
||||
$responses[$url] = $this->parseJson($response->getBody(), $url, $cacheKey, $response->getHeaders())->getBody();
|
||||
}
|
||||
}, function (\Exception $e) use ($url, &$retries) {
|
||||
$retries[] = [$url, $e];
|
||||
});
|
||||
}
|
||||
$loop->wait($jobs);
|
||||
|
||||
if (!$retries) {
|
||||
return $responses;
|
||||
}
|
||||
|
||||
if (0 < --$try) {
|
||||
usleep(100000);
|
||||
|
||||
return $this->get(array_column($retries, 0), $isRecipe, $try) + $responses;
|
||||
}
|
||||
|
||||
foreach ($retries as [$url, $e]) {
|
||||
if (isset($responses[$url])) {
|
||||
$this->switchToDegradedMode($e, $url);
|
||||
} elseif ($isRecipe) {
|
||||
$this->io->writeError('<warning>Failed to download recipe: '.$e->getMessage().'</>');
|
||||
} else {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
return $responses;
|
||||
}
|
||||
|
||||
private function parseJson(string $json, string $url, string $cacheKey, array $lastHeaders): Response
|
||||
{
|
||||
$data = JsonFile::parseJson($json, $url);
|
||||
if (!empty($data['warning'])) {
|
||||
$this->io->writeError('<warning>Warning from '.$url.': '.$data['warning'].'</>');
|
||||
}
|
||||
if (!empty($data['info'])) {
|
||||
$this->io->writeError('<info>Info from '.$url.': '.$data['info'].'</>');
|
||||
}
|
||||
|
||||
$response = new Response($data, $lastHeaders);
|
||||
if ($cacheKey && ($response->getHeader('last-modified') || $response->getHeader('etag'))) {
|
||||
$this->cache->write($cacheKey, json_encode($response));
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
private function switchToDegradedMode(\Exception $e, string $url)
|
||||
{
|
||||
if (!$this->degradedMode) {
|
||||
$this->io->writeError('<warning>'.$e->getMessage().'</>');
|
||||
$this->io->writeError('<warning>'.$url.' could not be fully loaded, package information was loaded from the local cache and may be out of date</>');
|
||||
}
|
||||
$this->degradedMode = true;
|
||||
}
|
||||
|
||||
private function getOptions(array $headers): array
|
||||
{
|
||||
$options = ['http' => ['header' => $headers]];
|
||||
|
||||
if (null !== $this->caFile) {
|
||||
$options['ssl']['cafile'] = $this->caFile;
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
private function initialize()
|
||||
{
|
||||
if (null !== $this->index || null === $this->endpoints) {
|
||||
$this->index ?? $this->index = [];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$indexes = self::$versions = self::$aliases = [];
|
||||
|
||||
foreach ($this->get(array_keys($this->endpoints)) as $endpoint => $index) {
|
||||
$indexes[$endpoint] = $index;
|
||||
}
|
||||
|
||||
foreach ($this->endpoints as $endpoint => $config) {
|
||||
$config = $indexes[$endpoint] ?? [];
|
||||
foreach ($config['recipes'] ?? [] as $package => $versions) {
|
||||
$this->index[$package] = $this->index[$package] ?? array_fill_keys($versions, $endpoint);
|
||||
}
|
||||
$this->conflicts[] = $config['recipe-conflicts'] ?? [];
|
||||
self::$versions += $config['versions'] ?? [];
|
||||
self::$aliases += $config['aliases'] ?? [];
|
||||
unset($config['recipes'], $config['recipe-conflicts'], $config['versions'], $config['aliases']);
|
||||
$this->endpoints[$endpoint] = $config;
|
||||
}
|
||||
}
|
||||
|
||||
private static function generateCacheKey(string $url): string
|
||||
{
|
||||
$url = preg_replace('{^https://api.github.com/repos/([^/]++/[^/]++)/contents/}', '$1/', $url);
|
||||
$url = preg_replace('{^https://raw.githubusercontent.com/([^/]++/[^/]++)/}', '$1/', $url);
|
||||
|
||||
$key = preg_replace('{[^a-z0-9.]}i', '-', $url);
|
||||
|
||||
// eCryptfs can have problems with filenames longer than around 143 chars
|
||||
return \strlen($key) > 140 ? md5($url) : $key;
|
||||
}
|
||||
}
|
||||
38
vendor/symfony/flex/src/Event/UpdateEvent.php
vendored
Normal file
38
vendor/symfony/flex/src/Event/UpdateEvent.php
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex\Event;
|
||||
|
||||
use Composer\Script\Event;
|
||||
use Composer\Script\ScriptEvents;
|
||||
|
||||
class UpdateEvent extends Event
|
||||
{
|
||||
private $force;
|
||||
private $reset;
|
||||
|
||||
public function __construct(bool $force, bool $reset)
|
||||
{
|
||||
$this->name = ScriptEvents::POST_UPDATE_CMD;
|
||||
$this->force = $force;
|
||||
$this->reset = $reset;
|
||||
}
|
||||
|
||||
public function force(): bool
|
||||
{
|
||||
return $this->force;
|
||||
}
|
||||
|
||||
public function reset(): bool
|
||||
{
|
||||
return $this->reset;
|
||||
}
|
||||
}
|
||||
873
vendor/symfony/flex/src/Flex.php
vendored
Normal file
873
vendor/symfony/flex/src/Flex.php
vendored
Normal file
@@ -0,0 +1,873 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex;
|
||||
|
||||
use Composer\Command\GlobalCommand;
|
||||
use Composer\Composer;
|
||||
use Composer\Console\Application;
|
||||
use Composer\DependencyResolver\Operation\InstallOperation;
|
||||
use Composer\DependencyResolver\Operation\OperationInterface;
|
||||
use Composer\DependencyResolver\Operation\UninstallOperation;
|
||||
use Composer\DependencyResolver\Operation\UpdateOperation;
|
||||
use Composer\DependencyResolver\Transaction;
|
||||
use Composer\EventDispatcher\EventSubscriberInterface;
|
||||
use Composer\Factory;
|
||||
use Composer\Installer;
|
||||
use Composer\Installer\InstallerEvent;
|
||||
use Composer\Installer\InstallerEvents;
|
||||
use Composer\Installer\PackageEvent;
|
||||
use Composer\Installer\PackageEvents;
|
||||
use Composer\Installer\SuggestedPackagesReporter;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\IO\NullIO;
|
||||
use Composer\Json\JsonFile;
|
||||
use Composer\Json\JsonManipulator;
|
||||
use Composer\Package\BasePackage;
|
||||
use Composer\Package\Locker;
|
||||
use Composer\Package\Package;
|
||||
use Composer\Plugin\PluginEvents;
|
||||
use Composer\Plugin\PluginInterface;
|
||||
use Composer\Plugin\PrePoolCreateEvent;
|
||||
use Composer\Script\Event;
|
||||
use Composer\Script\ScriptEvents;
|
||||
use Composer\Semver\VersionParser;
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
use Symfony\Flex\Event\UpdateEvent;
|
||||
use Symfony\Flex\Unpack\Operation;
|
||||
use Symfony\Thanks\Thanks;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class Flex implements PluginInterface, EventSubscriberInterface
|
||||
{
|
||||
public static $storedOperations = [];
|
||||
|
||||
/**
|
||||
* @var Composer
|
||||
*/
|
||||
private $composer;
|
||||
|
||||
/**
|
||||
* @var IOInterface
|
||||
*/
|
||||
private $io;
|
||||
|
||||
private $config;
|
||||
private $options;
|
||||
private $configurator;
|
||||
private $downloader;
|
||||
|
||||
/**
|
||||
* @var Installer
|
||||
*/
|
||||
private $installer;
|
||||
private $postInstallOutput = [''];
|
||||
private $operations = [];
|
||||
private $lock;
|
||||
private $displayThanksReminder = 0;
|
||||
private $dryRun = false;
|
||||
private $reinstall;
|
||||
private static $activated = true;
|
||||
private static $aliasResolveCommands = [
|
||||
'require' => true,
|
||||
'update' => false,
|
||||
'remove' => false,
|
||||
'unpack' => true,
|
||||
];
|
||||
private $filter;
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function activate(Composer $composer, IOInterface $io)
|
||||
{
|
||||
if (!\extension_loaded('openssl')) {
|
||||
self::$activated = false;
|
||||
$io->writeError('<warning>Symfony Flex has been disabled. You must enable the openssl extension in your "php.ini" file.</>');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// to avoid issues when Flex is upgraded, we load all PHP classes now
|
||||
// that way, we are sure to use all classes from the same version
|
||||
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator(__DIR__, \FilesystemIterator::SKIP_DOTS)) as $file) {
|
||||
if ('.php' === substr($file, -4)) {
|
||||
class_exists(__NAMESPACE__.str_replace('/', '\\', substr($file, \strlen(__DIR__), -4)));
|
||||
}
|
||||
}
|
||||
|
||||
$this->composer = $composer;
|
||||
$this->io = $io;
|
||||
$this->config = $composer->getConfig();
|
||||
$this->options = $this->initOptions();
|
||||
|
||||
// if Flex is being upgraded, the original operations from the original Flex
|
||||
// instance are stored in the static property, so we can reuse them now.
|
||||
if (property_exists(self::class, 'storedOperations') && self::$storedOperations) {
|
||||
$this->operations = self::$storedOperations;
|
||||
self::$storedOperations = [];
|
||||
}
|
||||
|
||||
$symfonyRequire = preg_replace('/\.x$/', '.x-dev', getenv('SYMFONY_REQUIRE') ?: ($composer->getPackage()->getExtra()['symfony']['require'] ?? ''));
|
||||
|
||||
$rfs = Factory::createHttpDownloader($this->io, $this->config);
|
||||
|
||||
$this->downloader = $downloader = new Downloader($composer, $io, $rfs);
|
||||
|
||||
if ($symfonyRequire) {
|
||||
$this->filter = new PackageFilter($io, $symfonyRequire, $this->downloader);
|
||||
}
|
||||
|
||||
$composerFile = Factory::getComposerFile();
|
||||
$composerLock = 'json' === pathinfo($composerFile, \PATHINFO_EXTENSION) ? substr($composerFile, 0, -4).'lock' : $composerFile.'.lock';
|
||||
$symfonyLock = str_replace('composer', 'symfony', basename($composerLock));
|
||||
|
||||
$this->configurator = new Configurator($composer, $io, $this->options);
|
||||
$this->lock = new Lock(getenv('SYMFONY_LOCKFILE') ?: \dirname($composerLock).'/'.(basename($composerLock) !== $symfonyLock ? $symfonyLock : 'symfony.lock'));
|
||||
|
||||
$disable = true;
|
||||
foreach (array_merge($composer->getPackage()->getRequires() ?? [], $composer->getPackage()->getDevRequires() ?? []) as $link) {
|
||||
// recipes apply only when symfony/flex is found in "require" or "require-dev" in the root package
|
||||
if ('symfony/flex' === $link->getTarget()) {
|
||||
$disable = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($disable) {
|
||||
$downloader->disable();
|
||||
}
|
||||
|
||||
$backtrace = $this->configureInstaller();
|
||||
|
||||
foreach ($backtrace as $trace) {
|
||||
if (!isset($trace['object']) || !isset($trace['args'][0])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$trace['object'] instanceof Application || !$trace['args'][0] instanceof ArgvInput) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// In Composer 1.0.*, $input knows about option and argument definitions
|
||||
// Since Composer >=1.1, $input contains only raw values
|
||||
$input = $trace['args'][0];
|
||||
$app = $trace['object'];
|
||||
|
||||
$resolver = new PackageResolver($this->downloader);
|
||||
|
||||
try {
|
||||
$command = $input->getFirstArgument();
|
||||
$command = $command ? $app->find($command)->getName() : null;
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
}
|
||||
|
||||
if ('create-project' === $command) {
|
||||
if ($input->hasOption('remove-vcs')) {
|
||||
$input->setOption('remove-vcs', true);
|
||||
}
|
||||
} elseif ('update' === $command) {
|
||||
$this->displayThanksReminder = 1;
|
||||
} elseif ('outdated' === $command) {
|
||||
$symfonyRequire = null;
|
||||
}
|
||||
|
||||
if (isset(self::$aliasResolveCommands[$command])) {
|
||||
if ($input->hasArgument('packages')) {
|
||||
$input->setArgument('packages', $resolver->resolve($input->getArgument('packages'), self::$aliasResolveCommands[$command]));
|
||||
}
|
||||
}
|
||||
|
||||
if ($input->hasParameterOption('--prefer-lowest', true)) {
|
||||
// When prefer-lowest is set and no stable version has been released,
|
||||
// we consider "dev" more stable than "alpha", "beta" or "RC". This
|
||||
// allows testing lowest versions with potential fixes applied.
|
||||
BasePackage::$stabilities['dev'] = 1 + BasePackage::STABILITY_STABLE;
|
||||
}
|
||||
|
||||
$app->add(new Command\RecipesCommand($this, $this->lock, $rfs));
|
||||
$app->add(new Command\InstallRecipesCommand($this, $this->options->get('root-dir'), $this->options->get('runtime')['dotenv_path'] ?? '.env'));
|
||||
$app->add(new Command\UpdateRecipesCommand($this, $this->downloader, $rfs, $this->configurator, $this->options->get('root-dir')));
|
||||
$app->add(new Command\DumpEnvCommand($this->config, $this->options));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function deactivate(Composer $composer, IOInterface $io)
|
||||
{
|
||||
// store operations in case Flex is being upgraded
|
||||
self::$storedOperations = $this->operations;
|
||||
self::$activated = false;
|
||||
}
|
||||
|
||||
public function configureInstaller()
|
||||
{
|
||||
$backtrace = debug_backtrace();
|
||||
foreach ($backtrace as $trace) {
|
||||
if (isset($trace['object']) && $trace['object'] instanceof Installer) {
|
||||
$this->installer = $trace['object']->setSuggestedPackagesReporter(new SuggestedPackagesReporter(new NullIO()));
|
||||
|
||||
$updateAllowList = \Closure::bind(function () {
|
||||
return $this->updateAllowList;
|
||||
}, $this->installer, $this->installer)();
|
||||
|
||||
if (['php' => 0] === $updateAllowList) {
|
||||
$this->dryRun = true; // prevent recipes from being uninstalled when removing a pack
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($trace['object']) && $trace['object'] instanceof GlobalCommand) {
|
||||
$this->downloader->disable();
|
||||
}
|
||||
}
|
||||
|
||||
return $backtrace;
|
||||
}
|
||||
|
||||
public function configureProject(Event $event)
|
||||
{
|
||||
if (!$this->downloader->isEnabled()) {
|
||||
$this->io->writeError('<warning>Project configuration is disabled: "symfony/flex" not found in the root composer.json</>');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove LICENSE (which do not apply to the user project)
|
||||
@unlink('LICENSE');
|
||||
|
||||
// Update composer.json (project is proprietary by default)
|
||||
$file = Factory::getComposerFile();
|
||||
$contents = file_get_contents($file);
|
||||
$manipulator = new JsonManipulator($contents);
|
||||
$json = JsonFile::parseJson($contents);
|
||||
|
||||
// new projects are most of the time proprietary
|
||||
$manipulator->addMainKey('license', 'proprietary');
|
||||
|
||||
// extra.branch-alias doesn't apply to the project
|
||||
$manipulator->removeSubNode('extra', 'branch-alias');
|
||||
|
||||
// 'name' and 'description' are only required for public packages
|
||||
// don't use $manipulator->removeProperty() for BC with Composer 1.0
|
||||
$contents = preg_replace(['{^\s*+"name":.*,$\n}m', '{^\s*+"description":.*,$\n}m'], '', $manipulator->getContents(), 1);
|
||||
file_put_contents($file, $contents);
|
||||
|
||||
$this->updateComposerLock();
|
||||
}
|
||||
|
||||
public function recordFlexInstall(PackageEvent $event)
|
||||
{
|
||||
if (null === $this->reinstall && 'symfony/flex' === $event->getOperation()->getPackage()->getName()) {
|
||||
$this->reinstall = true;
|
||||
}
|
||||
}
|
||||
|
||||
public function record(PackageEvent $event)
|
||||
{
|
||||
if ($this->shouldRecordOperation($event->getOperation(), $event->isDevMode(), $event->getComposer())) {
|
||||
$this->operations[] = $event->getOperation();
|
||||
}
|
||||
}
|
||||
|
||||
public function recordOperations(InstallerEvent $event)
|
||||
{
|
||||
if (!$event->isExecutingOperations()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$versionParser = new VersionParser();
|
||||
$packages = [];
|
||||
foreach ($this->lock->all() as $name => $info) {
|
||||
if ('9999999.9999999' === $info['version']) {
|
||||
// Fix invalid versions found in some lock files
|
||||
$info['version'] = '99999.9999999';
|
||||
}
|
||||
$packages[] = new Package($name, $versionParser->normalize($info['version']), $info['version']);
|
||||
}
|
||||
|
||||
$transation = \Closure::bind(function () use ($packages, $event) {
|
||||
return new Transaction($packages, $event->getTransaction()->resultPackageMap);
|
||||
}, null, Transaction::class)();
|
||||
|
||||
foreach ($transation->getOperations() as $operation) {
|
||||
if (!$operation instanceof UninstallOperation && $this->shouldRecordOperation($operation, $event->isDevMode(), $event->getComposer())) {
|
||||
$this->operations[] = $operation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function update(Event $event, $operations = [])
|
||||
{
|
||||
if ($operations) {
|
||||
$this->operations = $operations;
|
||||
}
|
||||
|
||||
$this->install($event);
|
||||
|
||||
$file = Factory::getComposerFile();
|
||||
$contents = file_get_contents($file);
|
||||
$json = JsonFile::parseJson($contents);
|
||||
|
||||
if (!$this->reinstall && !isset($json['flex-require']) && !isset($json['flex-require-dev'])) {
|
||||
$this->unpack($event);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// merge "flex-require" with "require"
|
||||
$manipulator = new JsonManipulator($contents);
|
||||
$sortPackages = $this->composer->getConfig()->get('sort-packages');
|
||||
$symfonyVersion = $json['extra']['symfony']['require'] ?? null;
|
||||
$versions = $symfonyVersion ? $this->downloader->getVersions() : null;
|
||||
foreach (['require', 'require-dev'] as $type) {
|
||||
if (!isset($json['flex-'.$type])) {
|
||||
continue;
|
||||
}
|
||||
foreach ($json['flex-'.$type] as $package => $constraint) {
|
||||
if ($symfonyVersion && '*' === $constraint && isset($versions['splits'][$package])) {
|
||||
// replace unbounded constraints for symfony/* packages by extra.symfony.require
|
||||
$constraint = $symfonyVersion;
|
||||
}
|
||||
$manipulator->addLink($type, $package, $constraint, $sortPackages);
|
||||
}
|
||||
|
||||
$manipulator->removeMainKey('flex-'.$type);
|
||||
}
|
||||
|
||||
file_put_contents($file, $manipulator->getContents());
|
||||
|
||||
$this->reinstall($event, true);
|
||||
}
|
||||
|
||||
public function install(Event $event)
|
||||
{
|
||||
$rootDir = $this->options->get('root-dir');
|
||||
$runtime = $this->options->get('runtime');
|
||||
$dotenvPath = $rootDir.'/'.($runtime['dotenv_path'] ?? '.env');
|
||||
|
||||
if (!file_exists($dotenvPath) && !file_exists($dotenvPath.'.local') && file_exists($dotenvPath.'.dist') && false === strpos(file_get_contents($dotenvPath.'.dist'), '.env.local')) {
|
||||
copy($dotenvPath.'.dist', $dotenvPath);
|
||||
}
|
||||
|
||||
// Execute missing recipes
|
||||
$recipes = ScriptEvents::POST_UPDATE_CMD === $event->getName() ? $this->fetchRecipes($this->operations, $event instanceof UpdateEvent && $event->reset()) : [];
|
||||
$this->operations = []; // Reset the operation after getting recipes
|
||||
|
||||
if (2 === $this->displayThanksReminder) {
|
||||
$love = '\\' === \DIRECTORY_SEPARATOR ? 'love' : '💖 ';
|
||||
$star = '\\' === \DIRECTORY_SEPARATOR ? 'star' : '★ ';
|
||||
|
||||
$this->io->writeError('');
|
||||
$this->io->writeError('What about running <comment>composer global require symfony/thanks && composer thanks</> now?');
|
||||
$this->io->writeError(sprintf('This will spread some %s by sending a %s to the GitHub repositories of your fellow package maintainers.', $love, $star));
|
||||
}
|
||||
|
||||
$this->io->writeError('');
|
||||
|
||||
if (!$recipes) {
|
||||
if (ScriptEvents::POST_UPDATE_CMD === $event->getName()) {
|
||||
$this->finish($rootDir);
|
||||
}
|
||||
|
||||
if ($this->downloader->isEnabled()) {
|
||||
$this->io->writeError('Run <comment>composer recipes</> at any time to see the status of your Symfony recipes.');
|
||||
$this->io->writeError('');
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->io->writeError(sprintf('<info>Symfony operations: %d recipe%s (%s)</>', \count($recipes), \count($recipes) > 1 ? 's' : '', $this->downloader->getSessionId()));
|
||||
$installContribs = $this->composer->getPackage()->getExtra()['symfony']['allow-contrib'] ?? false;
|
||||
$manifest = null;
|
||||
$originalComposerJsonHash = $this->getComposerJsonHash();
|
||||
$postInstallRecipes = [];
|
||||
foreach ($recipes as $recipe) {
|
||||
if ('install' === $recipe->getJob() && !$installContribs && $recipe->isContrib()) {
|
||||
$warning = $this->io->isInteractive() ? 'WARNING' : 'IGNORING';
|
||||
$this->io->writeError(sprintf(' - <warning> %s </> %s', $warning, $this->formatOrigin($recipe)));
|
||||
$question = sprintf(' The recipe for this package comes from the "contrib" repository, which is open to community contributions.
|
||||
Review the recipe at %s
|
||||
|
||||
Do you want to execute this recipe?
|
||||
[<comment>y</>] Yes
|
||||
[<comment>n</>] No
|
||||
[<comment>a</>] Yes for all packages, only for the current installation session
|
||||
[<comment>p</>] Yes permanently, never ask again for this project
|
||||
(defaults to <comment>n</>): ', $recipe->getURL());
|
||||
$answer = $this->io->askAndValidate(
|
||||
$question,
|
||||
function ($value) {
|
||||
if (null === $value) {
|
||||
return 'n';
|
||||
}
|
||||
$value = strtolower($value[0]);
|
||||
if (!\in_array($value, ['y', 'n', 'a', 'p'])) {
|
||||
throw new \InvalidArgumentException('Invalid choice.');
|
||||
}
|
||||
|
||||
return $value;
|
||||
},
|
||||
null,
|
||||
'n'
|
||||
);
|
||||
if ('n' === $answer) {
|
||||
continue;
|
||||
}
|
||||
if ('a' === $answer) {
|
||||
$installContribs = true;
|
||||
}
|
||||
if ('p' === $answer) {
|
||||
$installContribs = true;
|
||||
$json = new JsonFile(Factory::getComposerFile());
|
||||
$manipulator = new JsonManipulator(file_get_contents($json->getPath()));
|
||||
$manipulator->addSubNode('extra', 'symfony.allow-contrib', true);
|
||||
file_put_contents($json->getPath(), $manipulator->getContents());
|
||||
}
|
||||
}
|
||||
|
||||
switch ($recipe->getJob()) {
|
||||
case 'install':
|
||||
$postInstallRecipes[] = $recipe;
|
||||
$this->io->writeError(sprintf(' - Configuring %s', $this->formatOrigin($recipe)));
|
||||
$this->configurator->install($recipe, $this->lock, [
|
||||
'force' => $event instanceof UpdateEvent && $event->force(),
|
||||
]);
|
||||
$manifest = $recipe->getManifest();
|
||||
if (isset($manifest['post-install-output'])) {
|
||||
$this->postInstallOutput[] = sprintf('<bg=yellow;fg=white> %s </> instructions:', $recipe->getName());
|
||||
$this->postInstallOutput[] = '';
|
||||
foreach ($manifest['post-install-output'] as $line) {
|
||||
$this->postInstallOutput[] = $this->options->expandTargetDir($line);
|
||||
}
|
||||
$this->postInstallOutput[] = '';
|
||||
}
|
||||
break;
|
||||
case 'update':
|
||||
break;
|
||||
case 'uninstall':
|
||||
$this->io->writeError(sprintf(' - Unconfiguring %s', $this->formatOrigin($recipe)));
|
||||
$this->configurator->unconfigure($recipe, $this->lock);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (method_exists($this->configurator, 'postInstall')) {
|
||||
foreach ($postInstallRecipes as $recipe) {
|
||||
$this->configurator->postInstall($recipe, $this->lock, [
|
||||
'force' => $event instanceof UpdateEvent && $event->force(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== $manifest) {
|
||||
array_unshift(
|
||||
$this->postInstallOutput,
|
||||
'<bg=blue;fg=white> </>',
|
||||
'<bg=blue;fg=white> What\'s next? </>',
|
||||
'<bg=blue;fg=white> </>',
|
||||
'',
|
||||
'<info>Some files have been created and/or updated to configure your new packages.</>',
|
||||
'Please <comment>review</>, <comment>edit</> and <comment>commit</> them: these files are <comment>yours</>.'
|
||||
);
|
||||
}
|
||||
|
||||
$this->finish($rootDir, $originalComposerJsonHash);
|
||||
}
|
||||
|
||||
public function finish(string $rootDir, ?string $originalComposerJsonHash = null): void
|
||||
{
|
||||
$this->synchronizePackageJson($rootDir);
|
||||
$this->lock->write();
|
||||
|
||||
if ($originalComposerJsonHash && $this->getComposerJsonHash() !== $originalComposerJsonHash) {
|
||||
$this->updateComposerLock();
|
||||
}
|
||||
}
|
||||
|
||||
private function synchronizePackageJson(string $rootDir)
|
||||
{
|
||||
$rootDir = realpath($rootDir);
|
||||
$vendorDir = trim((new Filesystem())->makePathRelative($this->config->get('vendor-dir'), $rootDir), '/');
|
||||
|
||||
$executor = new ScriptExecutor($this->composer, $this->io, $this->options);
|
||||
$synchronizer = new PackageJsonSynchronizer($rootDir, $vendorDir, $executor, $this->io);
|
||||
|
||||
if ($synchronizer->shouldSynchronize()) {
|
||||
$lockData = $this->composer->getLocker()->getLockData();
|
||||
|
||||
if ($synchronizer->synchronize(array_merge($lockData['packages'] ?? [], $lockData['packages-dev'] ?? []))) {
|
||||
$this->io->writeError('<info>Synchronizing package.json with PHP packages</>');
|
||||
$this->io->writeError('<warning>Don\'t forget to run npm install --force or yarn install --force to refresh your JavaScript dependencies!</>');
|
||||
$this->io->writeError('');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function uninstall(Composer $composer, IOInterface $io)
|
||||
{
|
||||
$this->lock->delete();
|
||||
}
|
||||
|
||||
public function enableThanksReminder()
|
||||
{
|
||||
if (1 === $this->displayThanksReminder) {
|
||||
$this->displayThanksReminder = !class_exists(Thanks::class, false) ? 2 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
public function executeAutoScripts(Event $event)
|
||||
{
|
||||
$event->stopPropagation();
|
||||
|
||||
// force reloading scripts as we might have added and removed during this run
|
||||
$json = new JsonFile(Factory::getComposerFile());
|
||||
$jsonContents = $json->read();
|
||||
|
||||
$executor = new ScriptExecutor($this->composer, $this->io, $this->options);
|
||||
foreach ($jsonContents['scripts']['auto-scripts'] as $cmd => $type) {
|
||||
$executor->execute($type, $cmd);
|
||||
}
|
||||
|
||||
$this->io->write($this->postInstallOutput);
|
||||
$this->postInstallOutput = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Recipe[]
|
||||
*/
|
||||
public function fetchRecipes(array $operations, bool $reset): array
|
||||
{
|
||||
if (!$this->downloader->isEnabled()) {
|
||||
$this->io->writeError('<warning>Symfony recipes are disabled: "symfony/flex" not found in the root composer.json</>');
|
||||
|
||||
return [];
|
||||
}
|
||||
$devPackages = null;
|
||||
$data = $this->downloader->getRecipes($operations);
|
||||
$manifests = $data['manifests'] ?? [];
|
||||
$locks = $data['locks'] ?? [];
|
||||
// symfony/flex recipes should always be applied first
|
||||
$flexRecipe = [];
|
||||
// symfony/framework-bundle recipe should always be applied first after the metapackages
|
||||
$recipes = [
|
||||
'symfony/framework-bundle' => null,
|
||||
];
|
||||
$packRecipes = [];
|
||||
$metaRecipes = [];
|
||||
|
||||
foreach ($operations as $operation) {
|
||||
if ($operation instanceof UpdateOperation) {
|
||||
$package = $operation->getTargetPackage();
|
||||
} else {
|
||||
$package = $operation->getPackage();
|
||||
}
|
||||
|
||||
// FIXME: Multi name with getNames()
|
||||
$name = $package->getName();
|
||||
$job = method_exists($operation, 'getOperationType') ? $operation->getOperationType() : $operation->getJobType();
|
||||
|
||||
if (!isset($manifests[$name]) && isset($data['conflicts'][$name])) {
|
||||
$this->io->writeError(sprintf(' - Skipping recipe for %s: all versions of the recipe conflict with your package versions.', $name));
|
||||
continue;
|
||||
}
|
||||
|
||||
while ($this->doesRecipeConflict($manifests[$name] ?? [], $operation)) {
|
||||
$this->downloader->removeRecipeFromIndex($name, $manifests[$name]['version']);
|
||||
$newData = $this->downloader->getRecipes([$operation]);
|
||||
$newManifests = $newData['manifests'] ?? [];
|
||||
|
||||
if (!isset($newManifests[$name])) {
|
||||
// no older recipe found
|
||||
$this->io->writeError(sprintf(' - Skipping recipe for %s: all versions of the recipe conflict with your package versions.', $name));
|
||||
|
||||
continue 2;
|
||||
}
|
||||
|
||||
// push the "old" recipe into the $manifests
|
||||
$manifests[$name] = $newManifests[$name];
|
||||
$locks[$name] = $newData['locks'][$name];
|
||||
}
|
||||
|
||||
if ($operation instanceof InstallOperation && isset($locks[$name])) {
|
||||
$ref = $this->lock->get($name)['recipe']['ref'] ?? null;
|
||||
if (!$reset && $ref && ($locks[$name]['recipe']['ref'] ?? null) === $ref) {
|
||||
continue;
|
||||
}
|
||||
$this->lock->set($name, $locks[$name]);
|
||||
} elseif ($operation instanceof UninstallOperation) {
|
||||
if (!$this->lock->has($name)) {
|
||||
continue;
|
||||
}
|
||||
$this->lock->remove($name);
|
||||
}
|
||||
|
||||
if (isset($manifests[$name])) {
|
||||
$recipe = new Recipe($package, $name, $job, $manifests[$name], $locks[$name] ?? []);
|
||||
|
||||
if ('symfony-pack' === $package->getType()) {
|
||||
$packRecipes[$name] = $recipe;
|
||||
} elseif ('metapackage' === $package->getType()) {
|
||||
$metaRecipes[$name] = $recipe;
|
||||
} elseif ('symfony/flex' === $name) {
|
||||
$flexRecipe = [$name => $recipe];
|
||||
} else {
|
||||
$recipes[$name] = $recipe;
|
||||
}
|
||||
} else {
|
||||
$bundles = [];
|
||||
|
||||
if (null === $devPackages) {
|
||||
$devPackages = array_column($this->composer->getLocker()->getLockData()['packages-dev'], 'name');
|
||||
}
|
||||
$envs = \in_array($name, $devPackages) ? ['dev', 'test'] : ['all'];
|
||||
$bundle = new SymfonyBundle($this->composer, $package, $job);
|
||||
foreach ($bundle->getClassNames() as $bundleClass) {
|
||||
$bundles[$bundleClass] = $envs;
|
||||
}
|
||||
|
||||
if ($bundles) {
|
||||
$manifest = [
|
||||
'origin' => sprintf('%s:%s@auto-generated recipe', $name, $package->getPrettyVersion()),
|
||||
'manifest' => ['bundles' => $bundles],
|
||||
];
|
||||
$recipes[$name] = new Recipe($package, $name, $job, $manifest);
|
||||
|
||||
if ($operation instanceof InstallOperation) {
|
||||
$this->lock->set($name, ['version' => $package->getPrettyVersion()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_merge($flexRecipe, $packRecipes, $metaRecipes, array_filter($recipes));
|
||||
}
|
||||
|
||||
public function truncatePackages(PrePoolCreateEvent $event)
|
||||
{
|
||||
if (!$this->filter) {
|
||||
return;
|
||||
}
|
||||
|
||||
$rootPackage = $this->composer->getPackage();
|
||||
$lockedPackages = $event->getRequest()->getFixedOrLockedPackages();
|
||||
|
||||
$event->setPackages($this->filter->removeLegacyPackages($event->getPackages(), $rootPackage, $lockedPackages));
|
||||
}
|
||||
|
||||
public function getComposerJsonHash(): string
|
||||
{
|
||||
return md5_file(Factory::getComposerFile());
|
||||
}
|
||||
|
||||
public function getLock(): Lock
|
||||
{
|
||||
if (null === $this->lock) {
|
||||
throw new \Exception('Cannot access lock before calling activate().');
|
||||
}
|
||||
|
||||
return $this->lock;
|
||||
}
|
||||
|
||||
private function initOptions(): Options
|
||||
{
|
||||
$extra = $this->composer->getPackage()->getExtra();
|
||||
|
||||
$options = array_merge([
|
||||
'bin-dir' => 'bin',
|
||||
'conf-dir' => 'conf',
|
||||
'config-dir' => 'config',
|
||||
'src-dir' => 'src',
|
||||
'var-dir' => 'var',
|
||||
'public-dir' => 'public',
|
||||
'root-dir' => $extra['symfony']['root-dir'] ?? '.',
|
||||
'runtime' => $extra['runtime'] ?? [],
|
||||
], $extra);
|
||||
|
||||
return new Options($options, $this->io);
|
||||
}
|
||||
|
||||
private function formatOrigin(Recipe $recipe): string
|
||||
{
|
||||
if (method_exists($recipe, 'getFormattedOrigin')) {
|
||||
return $recipe->getFormattedOrigin();
|
||||
}
|
||||
|
||||
// BC with upgrading from flex < 1.18
|
||||
$origin = $recipe->getOrigin();
|
||||
|
||||
// symfony/translation:3.3@github.com/symfony/recipes:branch
|
||||
if (!preg_match('/^([^:]++):([^@]++)@(.+)$/', $origin, $matches)) {
|
||||
return $origin;
|
||||
}
|
||||
|
||||
return sprintf('<info>%s</> (<comment>>=%s</>): From %s', $matches[1], $matches[2], 'auto-generated recipe' === $matches[3] ? '<comment>'.$matches[3].'</>' : $matches[3]);
|
||||
}
|
||||
|
||||
private function shouldRecordOperation(OperationInterface $operation, bool $isDevMode, ?Composer $composer = null): bool
|
||||
{
|
||||
if ($this->dryRun || $this->reinstall) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($operation instanceof UpdateOperation) {
|
||||
$package = $operation->getTargetPackage();
|
||||
} else {
|
||||
$package = $operation->getPackage();
|
||||
}
|
||||
|
||||
// when Composer runs with --no-dev, ignore uninstall operations on packages from require-dev
|
||||
if (!$isDevMode && $operation instanceof UninstallOperation) {
|
||||
foreach (($composer ?? $this->composer)->getLocker()->getLockData()['packages-dev'] as $p) {
|
||||
if ($package->getName() === $p['name']) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Multi name with getNames()
|
||||
$name = $package->getName();
|
||||
if ($operation instanceof InstallOperation) {
|
||||
if (!$this->lock->has($name)) {
|
||||
return true;
|
||||
}
|
||||
} elseif ($operation instanceof UninstallOperation) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function updateComposerLock()
|
||||
{
|
||||
$lock = substr(Factory::getComposerFile(), 0, -4).'lock';
|
||||
$composerJson = file_get_contents(Factory::getComposerFile());
|
||||
$lockFile = new JsonFile($lock, null, $this->io);
|
||||
$locker = new Locker($this->io, $lockFile, $this->composer->getInstallationManager(), $composerJson);
|
||||
$lockData = $locker->getLockData();
|
||||
$lockData['content-hash'] = Locker::getContentHash($composerJson);
|
||||
$lockFile->write($lockData);
|
||||
}
|
||||
|
||||
private function unpack(Event $event)
|
||||
{
|
||||
$jsonPath = Factory::getComposerFile();
|
||||
$json = JsonFile::parseJson(file_get_contents($jsonPath));
|
||||
$sortPackages = $this->composer->getConfig()->get('sort-packages');
|
||||
$unpackOp = new Operation(true, $sortPackages);
|
||||
|
||||
foreach (['require', 'require-dev'] as $type) {
|
||||
foreach ($json[$type] ?? [] as $package => $constraint) {
|
||||
$unpackOp->addPackage($package, $constraint, 'require-dev' === $type);
|
||||
}
|
||||
}
|
||||
|
||||
$unpacker = new Unpacker($this->composer, new PackageResolver($this->downloader), $this->dryRun);
|
||||
$result = $unpacker->unpack($unpackOp);
|
||||
|
||||
if (!$result->getUnpacked()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->io->writeError('<info>Unpacking Symfony packs</>');
|
||||
foreach ($result->getUnpacked() as $pkg) {
|
||||
$this->io->writeError(sprintf(' - Unpacked <info>%s</>', $pkg->getName()));
|
||||
}
|
||||
|
||||
$unpacker->updateLock($result, $this->io);
|
||||
|
||||
$this->reinstall($event, false);
|
||||
}
|
||||
|
||||
private function reinstall(Event $event, bool $update)
|
||||
{
|
||||
$this->reinstall = false;
|
||||
$event->stopPropagation();
|
||||
|
||||
$ed = $this->composer->getEventDispatcher();
|
||||
$disableScripts = !method_exists($ed, 'setRunScripts') || !((array) $ed)["\0*\0runScripts"];
|
||||
$composer = Factory::create($this->io, null, false, $disableScripts);
|
||||
|
||||
$installer = clone $this->installer;
|
||||
$installer->__construct(
|
||||
$this->io,
|
||||
$composer->getConfig(),
|
||||
$composer->getPackage(),
|
||||
$composer->getDownloadManager(),
|
||||
$composer->getRepositoryManager(),
|
||||
$composer->getLocker(),
|
||||
$composer->getInstallationManager(),
|
||||
$composer->getEventDispatcher(),
|
||||
$composer->getAutoloadGenerator()
|
||||
);
|
||||
if (method_exists($installer, 'setPlatformRequirementFilter')) {
|
||||
$installer->setPlatformRequirementFilter(((array) $this->installer)["\0*\0platformRequirementFilter"]);
|
||||
}
|
||||
|
||||
if (!$update) {
|
||||
$installer->setUpdateAllowList(['php']);
|
||||
}
|
||||
|
||||
$installer->run();
|
||||
|
||||
$this->io->write($this->postInstallOutput);
|
||||
$this->postInstallOutput = [];
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
if (!self::$activated) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$events = [
|
||||
PackageEvents::POST_PACKAGE_UPDATE => 'enableThanksReminder',
|
||||
PackageEvents::POST_PACKAGE_INSTALL => 'recordFlexInstall',
|
||||
PackageEvents::POST_PACKAGE_UNINSTALL => 'record',
|
||||
InstallerEvents::PRE_OPERATIONS_EXEC => 'recordOperations',
|
||||
PluginEvents::PRE_POOL_CREATE => 'truncatePackages',
|
||||
ScriptEvents::POST_CREATE_PROJECT_CMD => 'configureProject',
|
||||
ScriptEvents::POST_INSTALL_CMD => 'install',
|
||||
ScriptEvents::PRE_UPDATE_CMD => 'configureInstaller',
|
||||
ScriptEvents::POST_UPDATE_CMD => 'update',
|
||||
'auto-scripts' => 'executeAutoScripts',
|
||||
];
|
||||
|
||||
return $events;
|
||||
}
|
||||
|
||||
private function doesRecipeConflict(array $recipeData, OperationInterface $operation): bool
|
||||
{
|
||||
if (empty($recipeData['manifest']['conflict']) || $operation instanceof UninstallOperation) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$lockedRepository = $this->composer->getLocker()->getLockedRepository();
|
||||
|
||||
foreach ($recipeData['manifest']['conflict'] as $conflictingPackage => $constraint) {
|
||||
if ($lockedRepository->findPackage($conflictingPackage, $constraint)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
200
vendor/symfony/flex/src/GithubApi.php
vendored
Normal file
200
vendor/symfony/flex/src/GithubApi.php
vendored
Normal file
@@ -0,0 +1,200 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex;
|
||||
|
||||
use Composer\Util\HttpDownloader;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
|
||||
class GithubApi
|
||||
{
|
||||
/** @var HttpDownloader|RemoteFilesystem */
|
||||
private $downloader;
|
||||
|
||||
public function __construct($downloader)
|
||||
{
|
||||
$this->downloader = $downloader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to find data about when the recipe was installed.
|
||||
*
|
||||
* Returns an array containing:
|
||||
* commit: The git sha of the last commit of the recipe
|
||||
* date: The date of the commit
|
||||
* new_commits: An array of commit sha's in this recipe's directory+version since the commit
|
||||
* The key is the sha & the value is the date
|
||||
*/
|
||||
public function findRecipeCommitDataFromTreeRef(string $package, string $repo, string $branch, string $version, string $lockRef): ?array
|
||||
{
|
||||
$repositoryName = $this->getRepositoryName($repo);
|
||||
if (!$repositoryName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$recipePath = sprintf('%s/%s', $package, $version);
|
||||
$commitsData = $this->requestGitHubApi(sprintf(
|
||||
'https://api.github.com/repos/%s/commits?path=%s&sha=%s',
|
||||
$repositoryName,
|
||||
$recipePath,
|
||||
$branch
|
||||
));
|
||||
|
||||
$commitShas = [];
|
||||
foreach ($commitsData as $commitData) {
|
||||
$commitShas[$commitData['sha']] = $commitData['commit']['committer']['date'];
|
||||
// go back the commits one-by-one
|
||||
$treeUrl = $commitData['commit']['tree']['url'].'?recursive=true';
|
||||
|
||||
// fetch the full tree, then look for the tree for the package path
|
||||
$treeData = $this->requestGitHubApi($treeUrl);
|
||||
foreach ($treeData['tree'] as $treeItem) {
|
||||
if ($treeItem['path'] !== $recipePath) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($treeItem['sha'] === $lockRef) {
|
||||
// remove *this* commit from the new commits list
|
||||
array_pop($commitShas);
|
||||
|
||||
return [
|
||||
// shorten for brevity
|
||||
'commit' => substr($commitData['sha'], 0, 7),
|
||||
'date' => $commitData['commit']['committer']['date'],
|
||||
'new_commits' => $commitShas,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getVersionsOfRecipe(string $repo, string $branch, string $recipePath): ?array
|
||||
{
|
||||
$repositoryName = $this->getRepositoryName($repo);
|
||||
if (!$repositoryName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$url = sprintf(
|
||||
'https://api.github.com/repos/%s/contents/%s?ref=%s',
|
||||
$repositoryName,
|
||||
$recipePath,
|
||||
$branch
|
||||
);
|
||||
$contents = $this->requestGitHubApi($url);
|
||||
$versions = [];
|
||||
foreach ($contents as $fileData) {
|
||||
if ('dir' !== $fileData['type']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$versions[] = $fileData['name'];
|
||||
}
|
||||
|
||||
return $versions;
|
||||
}
|
||||
|
||||
public function getCommitDataForPath(string $repo, string $path, string $branch): array
|
||||
{
|
||||
$repositoryName = $this->getRepositoryName($repo);
|
||||
if (!$repositoryName) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$commitsData = $this->requestGitHubApi(sprintf(
|
||||
'https://api.github.com/repos/%s/commits?path=%s&sha=%s',
|
||||
$repositoryName,
|
||||
$path,
|
||||
$branch
|
||||
));
|
||||
|
||||
$data = [];
|
||||
foreach ($commitsData as $commitData) {
|
||||
$data[$commitData['sha']] = $commitData['commit']['committer']['date'];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function getPullRequestForCommit(string $commit, string $repo): ?array
|
||||
{
|
||||
$data = $this->requestGitHubApi('https://api.github.com/search/issues?q='.$commit.'+is:pull-request');
|
||||
|
||||
if (0 === \count($data['items'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$repositoryName = $this->getRepositoryName($repo);
|
||||
if (!$repositoryName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$bestItem = null;
|
||||
foreach ($data['items'] as $item) {
|
||||
// make sure the PR referenced isn't from a different repository
|
||||
if (false === strpos($item['html_url'], sprintf('%s/pull', $repositoryName))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (null === $bestItem) {
|
||||
$bestItem = $item;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// find the first PR to reference - avoids rare cases where an invalid
|
||||
// PR that references *many* commits is first
|
||||
// e.g. https://api.github.com/search/issues?q=a1a70353f64f405cfbacfc4ce860af623442d6e5
|
||||
if ($item['number'] < $bestItem['number']) {
|
||||
$bestItem = $item;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$bestItem) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'number' => $bestItem['number'],
|
||||
'url' => $bestItem['html_url'],
|
||||
'title' => $bestItem['title'],
|
||||
];
|
||||
}
|
||||
|
||||
private function requestGitHubApi(string $path)
|
||||
{
|
||||
$contents = $this->downloader->get($path)->getBody();
|
||||
|
||||
return json_decode($contents, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the "repo" stored in symfony.lock to a repository name.
|
||||
*
|
||||
* For example: "github.com/symfony/recipes" => "symfony/recipes"
|
||||
*/
|
||||
private function getRepositoryName(string $repo): ?string
|
||||
{
|
||||
// only supports public repository placement
|
||||
if (0 !== strpos($repo, 'github.com')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$parts = explode('/', $repo);
|
||||
if (3 !== \count($parts)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return implode('/', [$parts[1], $parts[2]]);
|
||||
}
|
||||
}
|
||||
95
vendor/symfony/flex/src/InformationOperation.php
vendored
Normal file
95
vendor/symfony/flex/src/InformationOperation.php
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Flex;
|
||||
|
||||
use Composer\DependencyResolver\Operation\OperationInterface;
|
||||
use Composer\Package\PackageInterface;
|
||||
|
||||
/**
|
||||
* @author Maxime Hélias <maximehelias16@gmail.com>
|
||||
*/
|
||||
class InformationOperation implements OperationInterface
|
||||
{
|
||||
private $package;
|
||||
private $recipeRef = null;
|
||||
private $version = null;
|
||||
|
||||
public function __construct(PackageInterface $package)
|
||||
{
|
||||
$this->package = $package;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call to get information about a specific version of a recipe.
|
||||
*
|
||||
* Both $recipeRef and $version would normally come from the symfony.lock file.
|
||||
*/
|
||||
public function setSpecificRecipeVersion(string $recipeRef, string $version)
|
||||
{
|
||||
$this->recipeRef = $recipeRef;
|
||||
$this->version = $version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns package instance.
|
||||
*
|
||||
* @return PackageInterface
|
||||
*/
|
||||
public function getPackage()
|
||||
{
|
||||
return $this->package;
|
||||
}
|
||||
|
||||
public function getRecipeRef(): ?string
|
||||
{
|
||||
return $this->recipeRef;
|
||||
}
|
||||
|
||||
public function getVersion(): ?string
|
||||
{
|
||||
return $this->version;
|
||||
}
|
||||
|
||||
public function getJobType()
|
||||
{
|
||||
return 'information';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getOperationType()
|
||||
{
|
||||
return 'information';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function show($lock)
|
||||
{
|
||||
$pretty = method_exists($this->package, 'getFullPrettyVersion') ? $this->package->getFullPrettyVersion() : $this->formatVersion($this->package);
|
||||
|
||||
return 'Information '.$this->package->getPrettyName().' ('.$pretty.')';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->show(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compatibility for Composer 1.x, not needed in Composer 2.
|
||||
*/
|
||||
public function getReason()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
89
vendor/symfony/flex/src/Lock.php
vendored
Normal file
89
vendor/symfony/flex/src/Lock.php
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex;
|
||||
|
||||
use Composer\Json\JsonFile;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class Lock
|
||||
{
|
||||
private $json;
|
||||
private $lock = [];
|
||||
private $changed = false;
|
||||
|
||||
public function __construct($lockFile)
|
||||
{
|
||||
$this->json = new JsonFile($lockFile);
|
||||
if ($this->json->exists()) {
|
||||
$this->lock = $this->json->read();
|
||||
}
|
||||
}
|
||||
|
||||
public function has($name): bool
|
||||
{
|
||||
return \array_key_exists($name, $this->lock);
|
||||
}
|
||||
|
||||
public function add($name, $data)
|
||||
{
|
||||
$current = $this->lock[$name] ?? [];
|
||||
$this->lock[$name] = array_merge($current, $data);
|
||||
$this->changed = true;
|
||||
}
|
||||
|
||||
public function get($name)
|
||||
{
|
||||
return $this->lock[$name] ?? null;
|
||||
}
|
||||
|
||||
public function set($name, $data)
|
||||
{
|
||||
if (!\array_key_exists($name, $this->lock) || $data !== $this->lock[$name]) {
|
||||
$this->lock[$name] = $data;
|
||||
$this->changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public function remove($name)
|
||||
{
|
||||
if (\array_key_exists($name, $this->lock)) {
|
||||
unset($this->lock[$name]);
|
||||
$this->changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public function write()
|
||||
{
|
||||
if (!$this->changed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->lock) {
|
||||
ksort($this->lock);
|
||||
$this->json->write($this->lock);
|
||||
} elseif ($this->json->exists()) {
|
||||
@unlink($this->json->getPath());
|
||||
}
|
||||
}
|
||||
|
||||
public function delete()
|
||||
{
|
||||
@unlink($this->json->getPath());
|
||||
}
|
||||
|
||||
public function all(): array
|
||||
{
|
||||
return $this->lock;
|
||||
}
|
||||
}
|
||||
88
vendor/symfony/flex/src/Options.php
vendored
Normal file
88
vendor/symfony/flex/src/Options.php
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex;
|
||||
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class Options
|
||||
{
|
||||
private $options;
|
||||
private $writtenFiles = [];
|
||||
private $io;
|
||||
|
||||
public function __construct(array $options = [], ?IOInterface $io = null)
|
||||
{
|
||||
$this->options = $options;
|
||||
$this->io = $io;
|
||||
}
|
||||
|
||||
public function get(string $name)
|
||||
{
|
||||
return $this->options[$name] ?? null;
|
||||
}
|
||||
|
||||
public function expandTargetDir(string $target): string
|
||||
{
|
||||
return preg_replace_callback('{%(.+?)%}', function ($matches) {
|
||||
$option = str_replace('_', '-', strtolower($matches[1]));
|
||||
if (!isset($this->options[$option])) {
|
||||
return $matches[0];
|
||||
}
|
||||
|
||||
return rtrim($this->options[$option], '/');
|
||||
}, $target);
|
||||
}
|
||||
|
||||
public function shouldWriteFile(string $file, bool $overwrite): bool
|
||||
{
|
||||
if (isset($this->writtenFiles[$file])) {
|
||||
return false;
|
||||
}
|
||||
$this->writtenFiles[$file] = true;
|
||||
|
||||
if (!file_exists($file)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!$overwrite) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!filesize($file)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
exec('git status --short --ignored --untracked-files=all -- '.ProcessExecutor::escape($file).' 2>&1', $output, $status);
|
||||
|
||||
if (0 !== $status) {
|
||||
return $this->io && $this->io->askConfirmation(sprintf('Cannot determine the state of the "%s" file, overwrite anyway? [y/N] ', $file), false);
|
||||
}
|
||||
|
||||
if (empty($output[0]) || preg_match('/^[ AMDRCU][ D][ \t]/', $output[0])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$name = basename($file);
|
||||
$name = \strlen($output[0]) - \strlen($name) === strrpos($output[0], $name) ? substr($output[0], 3) : $name;
|
||||
|
||||
return $this->io && $this->io->askConfirmation(sprintf('File "%s" has uncommitted changes, overwrite? [y/N] ', $name), false);
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
}
|
||||
155
vendor/symfony/flex/src/PackageFilter.php
vendored
Normal file
155
vendor/symfony/flex/src/PackageFilter.php
vendored
Normal file
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex;
|
||||
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Package\AliasPackage;
|
||||
use Composer\Package\PackageInterface;
|
||||
use Composer\Package\RootPackageInterface;
|
||||
use Composer\Semver\Constraint\Constraint;
|
||||
use Composer\Semver\Intervals;
|
||||
use Composer\Semver\VersionParser;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class PackageFilter
|
||||
{
|
||||
private $versions;
|
||||
private $versionParser;
|
||||
private $symfonyRequire;
|
||||
private $symfonyConstraints;
|
||||
private $downloader;
|
||||
private $io;
|
||||
|
||||
public function __construct(IOInterface $io, string $symfonyRequire, Downloader $downloader)
|
||||
{
|
||||
$this->versionParser = new VersionParser();
|
||||
$this->symfonyRequire = $symfonyRequire;
|
||||
$this->symfonyConstraints = $this->versionParser->parseConstraints($symfonyRequire);
|
||||
$this->downloader = $downloader;
|
||||
$this->io = $io;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PackageInterface[] $data
|
||||
* @param PackageInterface[] $lockedPackages
|
||||
*
|
||||
* @return PackageInterface[]
|
||||
*/
|
||||
public function removeLegacyPackages(array $data, RootPackageInterface $rootPackage, array $lockedPackages): array
|
||||
{
|
||||
if (!$this->symfonyConstraints || !$data) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$lockedVersions = [];
|
||||
foreach ($lockedPackages as $package) {
|
||||
$lockedVersions[$package->getName()] = [$package->getVersion()];
|
||||
if ($package instanceof AliasPackage) {
|
||||
$lockedVersions[$package->getName()][] = $package->getAliasOf()->getVersion();
|
||||
}
|
||||
}
|
||||
|
||||
$rootConstraints = [];
|
||||
foreach ($rootPackage->getRequires() + $rootPackage->getDevRequires() as $name => $link) {
|
||||
$rootConstraints[$name] = $link->getConstraint();
|
||||
}
|
||||
|
||||
$knownVersions = $this->getVersions();
|
||||
$filteredPackages = [];
|
||||
$symfonyPackages = [];
|
||||
$oneSymfony = false;
|
||||
foreach ($data as $package) {
|
||||
$name = $package->getName();
|
||||
$versions = [$package->getVersion()];
|
||||
if ($package instanceof AliasPackage) {
|
||||
$versions[] = $package->getAliasOf()->getVersion();
|
||||
}
|
||||
|
||||
if ('symfony/symfony' !== $name && (
|
||||
!isset($knownVersions['splits'][$name])
|
||||
|| array_intersect($versions, $lockedVersions[$name] ?? [])
|
||||
|| (isset($rootConstraints[$name]) && !Intervals::haveIntersections($this->symfonyConstraints, $rootConstraints[$name]))
|
||||
|| ('symfony/psr-http-message-bridge' === $name && 6.4 > $versions[0])
|
||||
)) {
|
||||
$filteredPackages[] = $package;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (null !== $alias = $package->getExtra()['branch-alias'][$package->getVersion()] ?? null) {
|
||||
$versions[] = $this->versionParser->normalize($alias);
|
||||
}
|
||||
|
||||
foreach ($versions as $version) {
|
||||
if ($this->symfonyConstraints->matches(new Constraint('==', $version))) {
|
||||
$filteredPackages[] = $package;
|
||||
$oneSymfony = $oneSymfony || 'symfony/symfony' === $name;
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
if ('symfony/symfony' === $name) {
|
||||
$symfonyPackages[] = $package;
|
||||
} elseif (null !== $this->io) {
|
||||
$this->io->writeError(sprintf('<info>Restricting packages listed in "symfony/symfony" to "%s"</>', $this->symfonyRequire));
|
||||
$this->io = null;
|
||||
}
|
||||
}
|
||||
|
||||
if ($symfonyPackages && !$oneSymfony) {
|
||||
$filteredPackages = array_merge($filteredPackages, $symfonyPackages);
|
||||
}
|
||||
|
||||
return $filteredPackages;
|
||||
}
|
||||
|
||||
private function getVersions(): array
|
||||
{
|
||||
if (null !== $this->versions) {
|
||||
return $this->versions;
|
||||
}
|
||||
|
||||
$versions = $this->downloader->getVersions();
|
||||
$this->downloader = null;
|
||||
$okVersions = [];
|
||||
|
||||
if (!isset($versions['splits'])) {
|
||||
throw new \LogicException('The Flex index is missing a "splits" entry. Did you forget to add "flex://defaults" in the "extra.symfony.endpoint" array of your composer.json?');
|
||||
}
|
||||
foreach ($versions['splits'] as $name => $vers) {
|
||||
foreach ($vers as $i => $v) {
|
||||
if (!isset($okVersions[$v])) {
|
||||
$okVersions[$v] = false;
|
||||
$w = '.x' === substr($v, -2) ? $versions['next'] : $v;
|
||||
|
||||
for ($j = 0; $j < 60; ++$j) {
|
||||
if ($this->symfonyConstraints->matches(new Constraint('==', $w.'.'.$j.'.0'))) {
|
||||
$okVersions[$v] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$okVersions[$v]) {
|
||||
unset($vers[$i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$vers || $vers === $versions['splits'][$name]) {
|
||||
unset($versions['splits'][$name]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->versions = $versions;
|
||||
}
|
||||
}
|
||||
403
vendor/symfony/flex/src/PackageJsonSynchronizer.php
vendored
Normal file
403
vendor/symfony/flex/src/PackageJsonSynchronizer.php
vendored
Normal file
@@ -0,0 +1,403 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex;
|
||||
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Json\JsonFile;
|
||||
use Composer\Json\JsonManipulator;
|
||||
use Composer\Semver\Semver;
|
||||
use Composer\Semver\VersionParser;
|
||||
use Seld\JsonLint\ParsingException;
|
||||
|
||||
/**
|
||||
* Synchronize package.json files detected in installed PHP packages with
|
||||
* the current application.
|
||||
*/
|
||||
class PackageJsonSynchronizer
|
||||
{
|
||||
private $rootDir;
|
||||
private $vendorDir;
|
||||
private $scriptExecutor;
|
||||
private $io;
|
||||
private $versionParser;
|
||||
|
||||
public function __construct(string $rootDir, string $vendorDir, ScriptExecutor $scriptExecutor, IOInterface $io)
|
||||
{
|
||||
$this->rootDir = $rootDir;
|
||||
$this->vendorDir = $vendorDir;
|
||||
$this->scriptExecutor = $scriptExecutor;
|
||||
$this->io = $io;
|
||||
$this->versionParser = new VersionParser();
|
||||
}
|
||||
|
||||
public function shouldSynchronize(): bool
|
||||
{
|
||||
return $this->rootDir && (file_exists($this->rootDir.'/package.json') || file_exists($this->rootDir.'/importmap.php'));
|
||||
}
|
||||
|
||||
public function synchronize(array $phpPackages): bool
|
||||
{
|
||||
if (file_exists($this->rootDir.'/importmap.php')) {
|
||||
$this->synchronizeForAssetMapper($phpPackages);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
JsonFile::parseJson(file_get_contents($this->rootDir.'/package.json'));
|
||||
} catch (ParsingException $e) {
|
||||
// if package.json is invalid (possible during a recipe upgrade), we can't update the file
|
||||
return false;
|
||||
}
|
||||
|
||||
$didChangePackageJson = $this->removeObsoletePackageJsonLinks();
|
||||
|
||||
$dependencies = [];
|
||||
|
||||
$phpPackages = $this->normalizePhpPackages($phpPackages);
|
||||
foreach ($phpPackages as $phpPackage) {
|
||||
foreach ($this->resolvePackageJsonDependencies($phpPackage) as $dependency => $constraint) {
|
||||
$dependencies[$dependency][$phpPackage['name']] = $constraint;
|
||||
}
|
||||
}
|
||||
|
||||
$didChangePackageJson = $this->registerDependenciesInPackageJson($dependencies) || $didChangePackageJson;
|
||||
|
||||
// Register controllers and entrypoints in controllers.json
|
||||
$this->updateControllersJsonFile($phpPackages);
|
||||
|
||||
return $didChangePackageJson;
|
||||
}
|
||||
|
||||
private function synchronizeForAssetMapper(array $phpPackages): void
|
||||
{
|
||||
$importMapEntries = [];
|
||||
$phpPackages = $this->normalizePhpPackages($phpPackages);
|
||||
foreach ($phpPackages as $phpPackage) {
|
||||
foreach ($this->resolveImportMapPackages($phpPackage) as $name => $dependencyConfig) {
|
||||
$importMapEntries[$name] = $dependencyConfig;
|
||||
}
|
||||
}
|
||||
|
||||
$this->updateImportMap($importMapEntries);
|
||||
$this->updateControllersJsonFile($phpPackages);
|
||||
}
|
||||
|
||||
private function removeObsoletePackageJsonLinks(): bool
|
||||
{
|
||||
$didChangePackageJson = false;
|
||||
|
||||
$manipulator = new JsonManipulator(file_get_contents($this->rootDir.'/package.json'));
|
||||
$content = json_decode($manipulator->getContents(), true);
|
||||
|
||||
$jsDependencies = $content['dependencies'] ?? [];
|
||||
$jsDevDependencies = $content['devDependencies'] ?? [];
|
||||
|
||||
foreach (['dependencies' => $jsDependencies, 'devDependencies' => $jsDevDependencies] as $key => $packages) {
|
||||
foreach ($packages as $name => $version) {
|
||||
if ('@' !== $name[0] || 0 !== strpos($version, 'file:'.$this->vendorDir.'/') || false === strpos($version, '/assets')) {
|
||||
continue;
|
||||
}
|
||||
if (file_exists($this->rootDir.'/'.substr($version, 5).'/package.json')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$manipulator->removeSubNode($key, $name);
|
||||
$didChangePackageJson = true;
|
||||
}
|
||||
}
|
||||
|
||||
file_put_contents($this->rootDir.'/package.json', $manipulator->getContents());
|
||||
|
||||
return $didChangePackageJson;
|
||||
}
|
||||
|
||||
private function resolvePackageJsonDependencies($phpPackage): array
|
||||
{
|
||||
$dependencies = [];
|
||||
|
||||
if (!$packageJson = $this->resolvePackageJson($phpPackage)) {
|
||||
return $dependencies;
|
||||
}
|
||||
|
||||
if ($packageJson->read()['symfony']['needsPackageAsADependency'] ?? true) {
|
||||
$dependencies['@'.$phpPackage['name']] = 'file:'.substr($packageJson->getPath(), 1 + \strlen($this->rootDir), -13);
|
||||
}
|
||||
|
||||
foreach ($packageJson->read()['peerDependencies'] ?? [] as $peerDependency => $constraint) {
|
||||
$dependencies[$peerDependency] = $constraint;
|
||||
}
|
||||
|
||||
return $dependencies;
|
||||
}
|
||||
|
||||
private function resolveImportMapPackages($phpPackage): array
|
||||
{
|
||||
if (!$packageJson = $this->resolvePackageJson($phpPackage)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$dependencies = [];
|
||||
|
||||
foreach ($packageJson->read()['symfony']['importmap'] ?? [] as $importMapName => $constraintConfig) {
|
||||
if (\is_array($constraintConfig)) {
|
||||
$constraint = $constraintConfig['version'] ?? [];
|
||||
$package = $constraintConfig['package'] ?? $importMapName;
|
||||
} else {
|
||||
$constraint = $constraintConfig;
|
||||
$package = $importMapName;
|
||||
}
|
||||
|
||||
if (0 === strpos($constraint, 'path:')) {
|
||||
$path = substr($constraint, 5);
|
||||
$path = str_replace('%PACKAGE%', \dirname($packageJson->getPath()), $path);
|
||||
|
||||
$dependencies[$importMapName] = [
|
||||
'path' => $path,
|
||||
];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$dependencies[$importMapName] = [
|
||||
'version' => $constraint,
|
||||
'package' => $package,
|
||||
];
|
||||
}
|
||||
|
||||
return $dependencies;
|
||||
}
|
||||
|
||||
private function registerDependenciesInPackageJson(array $flexDependencies): bool
|
||||
{
|
||||
$didChangePackageJson = false;
|
||||
|
||||
$manipulator = new JsonManipulator(file_get_contents($this->rootDir.'/package.json'));
|
||||
$content = json_decode($manipulator->getContents(), true);
|
||||
|
||||
foreach ($flexDependencies as $dependency => $constraints) {
|
||||
if (1 !== \count($constraints) && 1 !== \count(array_count_values($constraints))) {
|
||||
// If the flex packages have a colliding peer dependency, leave the resolution to the user
|
||||
continue;
|
||||
}
|
||||
|
||||
$constraint = array_shift($constraints);
|
||||
|
||||
$parentNode = isset($content['dependencies'][$dependency]) ? 'dependencies' : 'devDependencies';
|
||||
if (!isset($content[$parentNode][$dependency])) {
|
||||
$content['devDependencies'][$dependency] = $constraint;
|
||||
$didChangePackageJson = true;
|
||||
} elseif ($constraint !== $content[$parentNode][$dependency]) {
|
||||
if ($this->shouldUpdateConstraint($content[$parentNode][$dependency], $constraint)) {
|
||||
$content[$parentNode][$dependency] = $constraint;
|
||||
$didChangePackageJson = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($didChangePackageJson) {
|
||||
if (isset($content['dependencies'])) {
|
||||
$manipulator->addMainKey('dependencies', $content['dependencies']);
|
||||
}
|
||||
|
||||
if (isset($content['devDependencies'])) {
|
||||
$devDependencies = $content['devDependencies'];
|
||||
uksort($devDependencies, 'strnatcmp');
|
||||
$manipulator->addMainKey('devDependencies', $devDependencies);
|
||||
}
|
||||
|
||||
$newContents = $manipulator->getContents();
|
||||
if ($newContents === file_get_contents($this->rootDir.'/package.json')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
file_put_contents($this->rootDir.'/package.json', $manipulator->getContents());
|
||||
}
|
||||
|
||||
return $didChangePackageJson;
|
||||
}
|
||||
|
||||
private function shouldUpdateConstraint(string $existingConstraint, string $constraint)
|
||||
{
|
||||
try {
|
||||
$existingConstraint = $this->versionParser->parseConstraints($existingConstraint);
|
||||
$constraint = $this->versionParser->parseConstraints($constraint);
|
||||
|
||||
return !$existingConstraint->matches($constraint);
|
||||
} catch (\UnexpectedValueException $e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, array{path?: string, package?: string, version?: string}> $importMapEntries
|
||||
*/
|
||||
private function updateImportMap(array $importMapEntries): void
|
||||
{
|
||||
if (!$importMapEntries) {
|
||||
return;
|
||||
}
|
||||
|
||||
$importMapData = include $this->rootDir.'/importmap.php';
|
||||
|
||||
foreach ($importMapEntries as $name => $importMapEntry) {
|
||||
if (isset($importMapData[$name])) {
|
||||
if (!isset($importMapData[$name]['version'])) {
|
||||
// AssetMapper 6.3
|
||||
continue;
|
||||
}
|
||||
|
||||
$version = $importMapData[$name]['version'];
|
||||
$versionConstraint = $importMapEntry['version'] ?? null;
|
||||
|
||||
// if the version constraint is satisfied, skip - else, update the package
|
||||
if (Semver::satisfies($version, $versionConstraint)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->io->writeError(sprintf('Updating package <comment>%s</> from <info>%s</> to <info>%s</>.', $name, $version, $versionConstraint));
|
||||
}
|
||||
|
||||
if (isset($importMapEntry['path'])) {
|
||||
$arguments = [$name, '--path='.$importMapEntry['path']];
|
||||
$this->scriptExecutor->execute(
|
||||
'symfony-cmd',
|
||||
'importmap:require',
|
||||
$arguments
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($importMapEntry['version'])) {
|
||||
$packageName = $importMapEntry['package'].'@'.$importMapEntry['version'];
|
||||
if ($importMapEntry['package'] !== $name) {
|
||||
$packageName .= '='.$name;
|
||||
}
|
||||
$arguments = [$packageName];
|
||||
$this->scriptExecutor->execute(
|
||||
'symfony-cmd',
|
||||
'importmap:require',
|
||||
$arguments
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException(sprintf('Invalid importmap entry: "%s".', var_export($importMapEntry, true)));
|
||||
}
|
||||
}
|
||||
|
||||
private function updateControllersJsonFile(array $phpPackages)
|
||||
{
|
||||
if (!file_exists($controllersJsonPath = $this->rootDir.'/assets/controllers.json')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$previousControllersJson = (new JsonFile($controllersJsonPath))->read();
|
||||
} catch (ParsingException $e) {
|
||||
// if controllers.json is invalid (possible during a recipe upgrade), we can't update the file
|
||||
return;
|
||||
}
|
||||
$newControllersJson = [
|
||||
'controllers' => [],
|
||||
'entrypoints' => $previousControllersJson['entrypoints'],
|
||||
];
|
||||
|
||||
foreach ($phpPackages as $phpPackage) {
|
||||
if (!$packageJson = $this->resolvePackageJson($phpPackage)) {
|
||||
continue;
|
||||
}
|
||||
$name = '@'.$phpPackage['name'];
|
||||
|
||||
foreach ($packageJson->read()['symfony']['controllers'] ?? [] as $controllerName => $defaultConfig) {
|
||||
// If the package has just been added (no config), add the default config provided by the package
|
||||
if (!isset($previousControllersJson['controllers'][$name][$controllerName])) {
|
||||
$config = [];
|
||||
$config['enabled'] = $defaultConfig['enabled'];
|
||||
$config['fetch'] = $defaultConfig['fetch'] ?? 'eager';
|
||||
|
||||
if (isset($defaultConfig['autoimport'])) {
|
||||
$config['autoimport'] = $defaultConfig['autoimport'];
|
||||
}
|
||||
|
||||
$newControllersJson['controllers'][$name][$controllerName] = $config;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise, the package exists: merge new config with user config
|
||||
$previousConfig = $previousControllersJson['controllers'][$name][$controllerName];
|
||||
|
||||
$config = [];
|
||||
$config['enabled'] = $previousConfig['enabled'];
|
||||
$config['fetch'] = $previousConfig['fetch'] ?? 'eager';
|
||||
|
||||
if (isset($defaultConfig['autoimport'])) {
|
||||
$config['autoimport'] = [];
|
||||
|
||||
// Use for each autoimport either the previous config if one existed or the default config otherwise
|
||||
foreach ($defaultConfig['autoimport'] as $autoimport => $enabled) {
|
||||
$config['autoimport'][$autoimport] = $previousConfig['autoimport'][$autoimport] ?? $enabled;
|
||||
}
|
||||
}
|
||||
|
||||
$newControllersJson['controllers'][$name][$controllerName] = $config;
|
||||
}
|
||||
|
||||
foreach ($packageJson->read()['symfony']['entrypoints'] ?? [] as $entrypoint => $filename) {
|
||||
if (!isset($newControllersJson['entrypoints'][$entrypoint])) {
|
||||
$newControllersJson['entrypoints'][$entrypoint] = $filename;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
file_put_contents($controllersJsonPath, json_encode($newControllersJson, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES)."\n");
|
||||
}
|
||||
|
||||
private function resolvePackageJson(array $phpPackage): ?JsonFile
|
||||
{
|
||||
$packageDir = $this->rootDir.'/'.$this->vendorDir.'/'.$phpPackage['name'];
|
||||
|
||||
if (!\in_array('symfony-ux', $phpPackage['keywords'] ?? [], true)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (['/assets', '/Resources/assets', '/src/Resources/assets'] as $subdir) {
|
||||
$packageJsonPath = $packageDir.$subdir.'/package.json';
|
||||
|
||||
if (!file_exists($packageJsonPath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return new JsonFile($packageJsonPath);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function normalizePhpPackages(array $phpPackages): array
|
||||
{
|
||||
foreach ($phpPackages as $k => $phpPackage) {
|
||||
if (\is_string($phpPackage)) {
|
||||
// support for smooth upgrades from older flex versions
|
||||
$phpPackages[$k] = $phpPackage = [
|
||||
'name' => $phpPackage,
|
||||
'keywords' => ['symfony-ux'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $phpPackages;
|
||||
}
|
||||
}
|
||||
151
vendor/symfony/flex/src/PackageResolver.php
vendored
Normal file
151
vendor/symfony/flex/src/PackageResolver.php
vendored
Normal file
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex;
|
||||
|
||||
use Composer\Factory;
|
||||
use Composer\Package\Version\VersionParser;
|
||||
use Composer\Repository\PlatformRepository;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class PackageResolver
|
||||
{
|
||||
private static $SYMFONY_VERSIONS = ['lts', 'previous', 'stable', 'next', 'dev'];
|
||||
private $downloader;
|
||||
|
||||
public function __construct(Downloader $downloader)
|
||||
{
|
||||
$this->downloader = $downloader;
|
||||
}
|
||||
|
||||
public function resolve(array $arguments = [], bool $isRequire = false): array
|
||||
{
|
||||
// first pass split on : and = to resolve package names
|
||||
$packages = [];
|
||||
foreach ($arguments as $i => $argument) {
|
||||
if ((false !== $pos = strpos($argument, ':')) || (false !== $pos = strpos($argument, '='))) {
|
||||
$package = $this->resolvePackageName(substr($argument, 0, $pos), $i, $isRequire);
|
||||
$version = substr($argument, $pos + 1);
|
||||
$packages[] = $package.':'.$version;
|
||||
} else {
|
||||
$packages[] = $this->resolvePackageName($argument, $i, $isRequire);
|
||||
}
|
||||
}
|
||||
|
||||
// second pass to resolve versions
|
||||
$versionParser = new VersionParser();
|
||||
$requires = [];
|
||||
foreach ($versionParser->parseNameVersionPairs($packages) as $package) {
|
||||
$requires[] = $package['name'].$this->parseVersion($package['name'], $package['version'] ?? '', $isRequire);
|
||||
}
|
||||
|
||||
return array_unique($requires);
|
||||
}
|
||||
|
||||
public function parseVersion(string $package, string $version, bool $isRequire): string
|
||||
{
|
||||
if (0 !== strpos($package, 'symfony/')) {
|
||||
return $version ? ':'.$version : '';
|
||||
}
|
||||
|
||||
$versions = $this->downloader->getVersions();
|
||||
|
||||
if (!isset($versions['splits'][$package])) {
|
||||
return $version ? ':'.$version : '';
|
||||
}
|
||||
|
||||
if (!$version || '*' === $version) {
|
||||
try {
|
||||
$config = @json_decode(file_get_contents(Factory::getComposerFile()), true);
|
||||
} finally {
|
||||
if (!$isRequire || !(isset($config['extra']['symfony']['require']) || isset($config['require']['symfony/framework-bundle']))) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
$version = $config['extra']['symfony']['require'] ?? $config['require']['symfony/framework-bundle'];
|
||||
} elseif ('dev' === $version) {
|
||||
$version = '^'.$versions['dev-name'].'@dev';
|
||||
} elseif ('next' === $version) {
|
||||
$version = '^'.$versions[$version].'@dev';
|
||||
} elseif (\in_array($version, self::$SYMFONY_VERSIONS, true)) {
|
||||
$version = '^'.$versions[$version];
|
||||
}
|
||||
|
||||
return ':'.$version;
|
||||
}
|
||||
|
||||
private function resolvePackageName(string $argument, int $position, bool $isRequire): string
|
||||
{
|
||||
$skippedPackages = ['mirrors', 'nothing', ''];
|
||||
|
||||
if (!$isRequire) {
|
||||
$skippedPackages[] = 'lock';
|
||||
}
|
||||
|
||||
if (false !== strpos($argument, '/') || preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $argument) || preg_match('{(?<=[a-z0-9_/-])\*|\*(?=[a-z0-9_/-])}i', $argument) || \in_array($argument, $skippedPackages)) {
|
||||
return $argument;
|
||||
}
|
||||
|
||||
$aliases = $this->downloader->getAliases();
|
||||
|
||||
if (isset($aliases[$argument])) {
|
||||
$argument = $aliases[$argument];
|
||||
} else {
|
||||
// is it a version or an alias that does not exist?
|
||||
try {
|
||||
$versionParser = new VersionParser();
|
||||
$versionParser->parseConstraints($argument);
|
||||
} catch (\UnexpectedValueException $e) {
|
||||
// is it a special Symfony version?
|
||||
if (!\in_array($argument, self::$SYMFONY_VERSIONS, true)) {
|
||||
$this->throwAlternatives($argument, $position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $argument;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \UnexpectedValueException
|
||||
*/
|
||||
private function throwAlternatives(string $argument, int $position)
|
||||
{
|
||||
$alternatives = [];
|
||||
foreach ($this->downloader->getAliases() as $alias => $package) {
|
||||
$lev = levenshtein($argument, $alias);
|
||||
if ($lev <= \strlen($argument) / 3 || ('' !== $argument && false !== strpos($alias, $argument))) {
|
||||
$alternatives[$package][] = $alias;
|
||||
}
|
||||
}
|
||||
|
||||
// First position can only be a package name, not a version
|
||||
if ($alternatives || 0 === $position) {
|
||||
$message = sprintf('"%s" is not a valid alias.', $argument);
|
||||
if ($alternatives) {
|
||||
if (1 === \count($alternatives)) {
|
||||
$message .= " Did you mean this:\n";
|
||||
} else {
|
||||
$message .= " Did you mean one of these:\n";
|
||||
}
|
||||
foreach ($alternatives as $package => $aliases) {
|
||||
$message .= sprintf(" \"%s\", supported aliases: \"%s\"\n", $package, implode('", "', $aliases));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$message = sprintf('Could not parse version constraint "%s".', $argument);
|
||||
}
|
||||
|
||||
throw new \UnexpectedValueException($message);
|
||||
}
|
||||
}
|
||||
41
vendor/symfony/flex/src/Path.php
vendored
Normal file
41
vendor/symfony/flex/src/Path.php
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class Path
|
||||
{
|
||||
private $workingDirectory;
|
||||
|
||||
public function __construct($workingDirectory)
|
||||
{
|
||||
$this->workingDirectory = $workingDirectory;
|
||||
}
|
||||
|
||||
public function relativize(string $absolutePath): string
|
||||
{
|
||||
$relativePath = str_replace($this->workingDirectory, '.', $absolutePath);
|
||||
|
||||
return is_dir($absolutePath) ? rtrim($relativePath, '/').'/' : $relativePath;
|
||||
}
|
||||
|
||||
public function concatenate(array $parts): string
|
||||
{
|
||||
$first = array_shift($parts);
|
||||
|
||||
return array_reduce($parts, function (string $initial, string $next): string {
|
||||
return rtrim($initial, '/').'/'.ltrim($next, '/');
|
||||
}, $first);
|
||||
}
|
||||
}
|
||||
123
vendor/symfony/flex/src/Recipe.php
vendored
Normal file
123
vendor/symfony/flex/src/Recipe.php
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex;
|
||||
|
||||
use Composer\Package\PackageInterface;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class Recipe
|
||||
{
|
||||
private $package;
|
||||
private $name;
|
||||
private $job;
|
||||
private $data;
|
||||
private $lock;
|
||||
|
||||
public function __construct(PackageInterface $package, string $name, string $job, array $data, array $lock = [])
|
||||
{
|
||||
$this->package = $package;
|
||||
$this->name = $name;
|
||||
$this->job = $job;
|
||||
$this->data = $data;
|
||||
$this->lock = $lock;
|
||||
}
|
||||
|
||||
public function getPackage(): PackageInterface
|
||||
{
|
||||
return $this->package;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getJob(): string
|
||||
{
|
||||
return $this->job;
|
||||
}
|
||||
|
||||
public function getManifest(): array
|
||||
{
|
||||
if (!isset($this->data['manifest'])) {
|
||||
throw new \LogicException(sprintf('Manifest is not available for recipe "%s".', $this->name));
|
||||
}
|
||||
|
||||
return $this->data['manifest'];
|
||||
}
|
||||
|
||||
public function getFiles(): array
|
||||
{
|
||||
return $this->data['files'] ?? [];
|
||||
}
|
||||
|
||||
public function getOrigin(): string
|
||||
{
|
||||
return $this->data['origin'] ?? '';
|
||||
}
|
||||
|
||||
public function getFormattedOrigin(): string
|
||||
{
|
||||
if (!$this->getOrigin()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// symfony/translation:3.3@github.com/symfony/recipes:branch
|
||||
if (!preg_match('/^([^:]++):([^@]++)@(.+)$/', $this->getOrigin(), $matches)) {
|
||||
return $this->getOrigin();
|
||||
}
|
||||
|
||||
return sprintf('<info>%s</> (<comment>>=%s</>): From %s', $matches[1], $matches[2], 'auto-generated recipe' === $matches[3] ? '<comment>'.$matches[3].'</>' : $matches[3]);
|
||||
}
|
||||
|
||||
public function getURL(): string
|
||||
{
|
||||
if (!$this->data['origin']) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// symfony/translation:3.3@github.com/symfony/recipes:branch
|
||||
if (!preg_match('/^([^:]++):([^@]++)@([^:]++):(.+)$/', $this->data['origin'], $matches)) {
|
||||
// that excludes auto-generated recipes, which is what we want
|
||||
return '';
|
||||
}
|
||||
|
||||
return sprintf('https://%s/tree/%s/%s/%s', $matches[3], $matches[4], $matches[1], $matches[2]);
|
||||
}
|
||||
|
||||
public function isContrib(): bool
|
||||
{
|
||||
return $this->data['is_contrib'] ?? false;
|
||||
}
|
||||
|
||||
public function getRef()
|
||||
{
|
||||
return $this->lock['recipe']['ref'] ?? null;
|
||||
}
|
||||
|
||||
public function isAuto(): bool
|
||||
{
|
||||
return !isset($this->lock['recipe']);
|
||||
}
|
||||
|
||||
public function getVersion(): string
|
||||
{
|
||||
return $this->lock['recipe']['version'] ?? $this->lock['version'];
|
||||
}
|
||||
|
||||
public function getLock(): array
|
||||
{
|
||||
return $this->lock;
|
||||
}
|
||||
}
|
||||
90
vendor/symfony/flex/src/Response.php
vendored
Normal file
90
vendor/symfony/flex/src/Response.php
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class Response implements \JsonSerializable
|
||||
{
|
||||
private $body;
|
||||
private $origHeaders;
|
||||
private $headers;
|
||||
private $code;
|
||||
|
||||
/**
|
||||
* @param mixed $body The response as JSON
|
||||
*/
|
||||
public function __construct($body, array $headers = [], int $code = 200)
|
||||
{
|
||||
$this->body = $body;
|
||||
$this->origHeaders = $headers;
|
||||
$this->headers = $this->parseHeaders($headers);
|
||||
$this->code = $code;
|
||||
}
|
||||
|
||||
public function getStatusCode(): int
|
||||
{
|
||||
return $this->code;
|
||||
}
|
||||
|
||||
public function getHeader(string $name): string
|
||||
{
|
||||
return $this->headers[strtolower($name)][0] ?? '';
|
||||
}
|
||||
|
||||
public function getHeaders(string $name): array
|
||||
{
|
||||
return $this->headers[strtolower($name)] ?? [];
|
||||
}
|
||||
|
||||
public function getBody()
|
||||
{
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
public function getOrigHeaders(): array
|
||||
{
|
||||
return $this->origHeaders;
|
||||
}
|
||||
|
||||
public static function fromJson(array $json): self
|
||||
{
|
||||
$response = new self($json['body']);
|
||||
$response->headers = $json['headers'];
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return ['body' => $this->body, 'headers' => $this->headers];
|
||||
}
|
||||
|
||||
private function parseHeaders(array $headers): array
|
||||
{
|
||||
$values = [];
|
||||
foreach (array_reverse($headers) as $header) {
|
||||
if (preg_match('{^([^:]++):\s*(.+?)\s*$}i', $header, $match)) {
|
||||
$values[strtolower($match[1])][] = $match[2];
|
||||
} elseif (preg_match('{^HTTP/}i', $header)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
}
|
||||
139
vendor/symfony/flex/src/ScriptExecutor.php
vendored
Normal file
139
vendor/symfony/flex/src/ScriptExecutor.php
vendored
Normal file
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex;
|
||||
|
||||
use Composer\Composer;
|
||||
use Composer\EventDispatcher\ScriptExecutionException;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Semver\Constraint\MatchAllConstraint;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Output\StreamOutput;
|
||||
use Symfony\Component\Process\PhpExecutableFinder;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class ScriptExecutor
|
||||
{
|
||||
private $composer;
|
||||
private $io;
|
||||
private $options;
|
||||
private $executor;
|
||||
|
||||
public function __construct(Composer $composer, IOInterface $io, Options $options, ?ProcessExecutor $executor = null)
|
||||
{
|
||||
$this->composer = $composer;
|
||||
$this->io = $io;
|
||||
$this->options = $options;
|
||||
$this->executor = $executor ?: new ProcessExecutor();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ScriptExecutionException if the executed command returns a non-0 exit code
|
||||
*/
|
||||
public function execute(string $type, string $cmd, array $arguments = [])
|
||||
{
|
||||
$parsedCmd = $this->options->expandTargetDir($cmd);
|
||||
if (null === $expandedCmd = $this->expandCmd($type, $parsedCmd, $arguments)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$cmdOutput = new StreamOutput(fopen('php://temp', 'rw'), OutputInterface::VERBOSITY_VERBOSE, $this->io->isDecorated());
|
||||
$outputHandler = function ($type, $buffer) use ($cmdOutput) {
|
||||
$cmdOutput->write($buffer, false, OutputInterface::OUTPUT_RAW);
|
||||
};
|
||||
|
||||
$this->io->writeError(sprintf('Executing script %s', $parsedCmd), $this->io->isVerbose());
|
||||
$exitCode = $this->executor->execute($expandedCmd, $outputHandler);
|
||||
|
||||
$code = 0 === $exitCode ? ' <info>[OK]</>' : ' <error>[KO]</>';
|
||||
|
||||
if ($this->io->isVerbose()) {
|
||||
$this->io->writeError(sprintf('Executed script %s %s', $cmd, $code));
|
||||
} else {
|
||||
$this->io->writeError($code);
|
||||
}
|
||||
|
||||
if (0 !== $exitCode) {
|
||||
$this->io->writeError(' <error>[KO]</>');
|
||||
$this->io->writeError(sprintf('<error>Script %s returned with error code %s</>', $cmd, $exitCode));
|
||||
fseek($cmdOutput->getStream(), 0);
|
||||
foreach (explode("\n", stream_get_contents($cmdOutput->getStream())) as $line) {
|
||||
$this->io->writeError('!! '.$line);
|
||||
}
|
||||
|
||||
throw new ScriptExecutionException($cmd, $exitCode);
|
||||
}
|
||||
}
|
||||
|
||||
private function expandCmd(string $type, string $cmd, array $arguments)
|
||||
{
|
||||
switch ($type) {
|
||||
case 'symfony-cmd':
|
||||
return $this->expandSymfonyCmd($cmd, $arguments);
|
||||
case 'php-script':
|
||||
return $this->expandPhpScript($cmd, $arguments);
|
||||
case 'script':
|
||||
return $cmd;
|
||||
default:
|
||||
throw new \InvalidArgumentException(sprintf('Invalid symfony/flex auto-script in composer.json: "%s" is not a valid type of command.', $type));
|
||||
}
|
||||
}
|
||||
|
||||
private function expandSymfonyCmd(string $cmd, array $arguments)
|
||||
{
|
||||
$repo = $this->composer->getRepositoryManager()->getLocalRepository();
|
||||
if (!$repo->findPackage('symfony/console', new MatchAllConstraint())) {
|
||||
$this->io->writeError(sprintf('<warning>Skipping "%s" (needs symfony/console to run).</>', $cmd));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$console = ProcessExecutor::escape($this->options->get('root-dir').'/'.$this->options->get('bin-dir').'/console');
|
||||
if ($this->io->isDecorated()) {
|
||||
$console .= ' --ansi';
|
||||
}
|
||||
|
||||
return $this->expandPhpScript($console.' '.$cmd, $arguments);
|
||||
}
|
||||
|
||||
private function expandPhpScript(string $cmd, array $scriptArguments): string
|
||||
{
|
||||
$phpFinder = new PhpExecutableFinder();
|
||||
if (!$php = $phpFinder->find(false)) {
|
||||
throw new \RuntimeException('The PHP executable could not be found, add it to your PATH and try again.');
|
||||
}
|
||||
|
||||
$arguments = $phpFinder->findArguments();
|
||||
|
||||
if ($env = (string) getenv('COMPOSER_ORIGINAL_INIS')) {
|
||||
$paths = explode(\PATH_SEPARATOR, $env);
|
||||
$ini = array_shift($paths);
|
||||
} else {
|
||||
$ini = php_ini_loaded_file();
|
||||
}
|
||||
|
||||
if ($ini) {
|
||||
$arguments[] = '--php-ini='.$ini;
|
||||
}
|
||||
|
||||
if ($memoryLimit = (string) getenv('COMPOSER_MEMORY_LIMIT')) {
|
||||
$arguments[] = "-d memory_limit={$memoryLimit}";
|
||||
}
|
||||
|
||||
$phpArgs = implode(' ', array_map([ProcessExecutor::class, 'escape'], $arguments));
|
||||
$scriptArgs = implode(' ', array_map([ProcessExecutor::class, 'escape'], $scriptArguments));
|
||||
|
||||
return ProcessExecutor::escape($php).($phpArgs ? ' '.$phpArgs : '').' '.$cmd.($scriptArgs ? ' '.$scriptArgs : '');
|
||||
}
|
||||
}
|
||||
115
vendor/symfony/flex/src/SymfonyBundle.php
vendored
Normal file
115
vendor/symfony/flex/src/SymfonyBundle.php
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex;
|
||||
|
||||
use Composer\Composer;
|
||||
use Composer\Package\PackageInterface;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class SymfonyBundle
|
||||
{
|
||||
private $package;
|
||||
private $operation;
|
||||
private $vendorDir;
|
||||
|
||||
public function __construct(Composer $composer, PackageInterface $package, string $operation)
|
||||
{
|
||||
$this->package = $package;
|
||||
$this->operation = $operation;
|
||||
$this->vendorDir = rtrim($composer->getConfig()->get('vendor-dir'), '/');
|
||||
}
|
||||
|
||||
public function getClassNames(): array
|
||||
{
|
||||
$uninstall = 'uninstall' === $this->operation;
|
||||
$classes = [];
|
||||
$autoload = $this->package->getAutoload();
|
||||
$isSyliusPlugin = 'sylius-plugin' === $this->package->getType();
|
||||
foreach (['psr-4' => true, 'psr-0' => false] as $psr => $isPsr4) {
|
||||
if (!isset($autoload[$psr])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($autoload[$psr] as $namespace => $paths) {
|
||||
if (!\is_array($paths)) {
|
||||
$paths = [$paths];
|
||||
}
|
||||
foreach ($paths as $path) {
|
||||
foreach ($this->extractClassNames($namespace, $isSyliusPlugin) as $class) {
|
||||
// we only check class existence on install as we do have the code available
|
||||
// in contrast to uninstall operation
|
||||
if (!$uninstall && !$this->isBundleClass($class, $path, $isPsr4)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$classes[] = $class;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $classes;
|
||||
}
|
||||
|
||||
private function extractClassNames(string $namespace, bool $isSyliusPlugin): array
|
||||
{
|
||||
$namespace = trim($namespace, '\\');
|
||||
$class = $namespace.'\\';
|
||||
$parts = explode('\\', $namespace);
|
||||
$suffix = $parts[\count($parts) - 1];
|
||||
$endOfWord = substr($suffix, -6);
|
||||
|
||||
if ($isSyliusPlugin) {
|
||||
if ('Bundle' !== $endOfWord && 'Plugin' !== $endOfWord) {
|
||||
$suffix .= 'Bundle';
|
||||
}
|
||||
} elseif ('Bundle' !== $endOfWord) {
|
||||
$suffix .= 'Bundle';
|
||||
}
|
||||
|
||||
$classes = [$class.$suffix];
|
||||
$acc = '';
|
||||
foreach (\array_slice($parts, 0, -1) as $part) {
|
||||
if ('Bundle' === $part || ($isSyliusPlugin && 'Plugin' === $part)) {
|
||||
continue;
|
||||
}
|
||||
$classes[] = $class.$part.$suffix;
|
||||
$acc .= $part;
|
||||
$classes[] = $class.$acc.$suffix;
|
||||
}
|
||||
|
||||
return array_unique($classes);
|
||||
}
|
||||
|
||||
private function isBundleClass(string $class, string $path, bool $isPsr4): bool
|
||||
{
|
||||
$classPath = ($this->vendorDir ? $this->vendorDir.'/' : '').$this->package->getPrettyName().'/'.$path.'/';
|
||||
$parts = explode('\\', $class);
|
||||
$class = $parts[\count($parts) - 1];
|
||||
if (!$isPsr4) {
|
||||
$classPath .= str_replace('\\', '', implode('/', \array_slice($parts, 0, -1))).'/';
|
||||
}
|
||||
$classPath .= str_replace('\\', '/', $class).'.php';
|
||||
|
||||
if (!file_exists($classPath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// heuristic that should work in almost all cases
|
||||
$classContents = file_get_contents($classPath);
|
||||
|
||||
return (false !== strpos($classContents, 'Symfony\Component\HttpKernel\Bundle\Bundle'))
|
||||
|| (false !== strpos($classContents, 'Symfony\Component\HttpKernel\Bundle\AbstractBundle'));
|
||||
}
|
||||
}
|
||||
49
vendor/symfony/flex/src/Unpack/Operation.php
vendored
Normal file
49
vendor/symfony/flex/src/Unpack/Operation.php
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex\Unpack;
|
||||
|
||||
class Operation
|
||||
{
|
||||
private $packages = [];
|
||||
private $unpack;
|
||||
private $sort;
|
||||
|
||||
public function __construct(bool $unpack, bool $sort)
|
||||
{
|
||||
$this->unpack = $unpack;
|
||||
$this->sort = $sort;
|
||||
}
|
||||
|
||||
public function addPackage(string $name, string $version, bool $dev)
|
||||
{
|
||||
$this->packages[] = [
|
||||
'name' => $name,
|
||||
'version' => $version,
|
||||
'dev' => $dev,
|
||||
];
|
||||
}
|
||||
|
||||
public function getPackages(): array
|
||||
{
|
||||
return $this->packages;
|
||||
}
|
||||
|
||||
public function shouldUnpack(): bool
|
||||
{
|
||||
return $this->unpack;
|
||||
}
|
||||
|
||||
public function shouldSort(): bool
|
||||
{
|
||||
return $this->sort;
|
||||
}
|
||||
}
|
||||
55
vendor/symfony/flex/src/Unpack/Result.php
vendored
Normal file
55
vendor/symfony/flex/src/Unpack/Result.php
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex\Unpack;
|
||||
|
||||
use Composer\Package\PackageInterface;
|
||||
|
||||
class Result
|
||||
{
|
||||
private $unpacked = [];
|
||||
private $required = [];
|
||||
|
||||
public function addUnpacked(PackageInterface $package): bool
|
||||
{
|
||||
$name = $package->getName();
|
||||
|
||||
if (!isset($this->unpacked[$name])) {
|
||||
$this->unpacked[$name] = $package;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return PackageInterface[]
|
||||
*/
|
||||
public function getUnpacked(): array
|
||||
{
|
||||
return $this->unpacked;
|
||||
}
|
||||
|
||||
public function addRequired(string $package)
|
||||
{
|
||||
$this->required[] = $package;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getRequired(): array
|
||||
{
|
||||
// we need at least one package for the command to work properly
|
||||
return $this->required ?: ['symfony/flex'];
|
||||
}
|
||||
}
|
||||
208
vendor/symfony/flex/src/Unpacker.php
vendored
Normal file
208
vendor/symfony/flex/src/Unpacker.php
vendored
Normal file
@@ -0,0 +1,208 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex;
|
||||
|
||||
use Composer\Composer;
|
||||
use Composer\Config\JsonConfigSource;
|
||||
use Composer\Factory;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Json\JsonFile;
|
||||
use Composer\Json\JsonManipulator;
|
||||
use Composer\Package\Locker;
|
||||
use Composer\Package\Version\VersionSelector;
|
||||
use Composer\Repository\CompositeRepository;
|
||||
use Composer\Repository\RepositorySet;
|
||||
use Composer\Semver\VersionParser;
|
||||
use Symfony\Flex\Unpack\Operation;
|
||||
use Symfony\Flex\Unpack\Result;
|
||||
|
||||
class Unpacker
|
||||
{
|
||||
private $composer;
|
||||
private $resolver;
|
||||
private $dryRun;
|
||||
private $versionParser;
|
||||
|
||||
public function __construct(Composer $composer, PackageResolver $resolver, bool $dryRun)
|
||||
{
|
||||
$this->composer = $composer;
|
||||
$this->resolver = $resolver;
|
||||
$this->dryRun = $dryRun;
|
||||
$this->versionParser = new VersionParser();
|
||||
}
|
||||
|
||||
public function unpack(Operation $op, ?Result $result = null, &$links = [], bool $devRequire = false): Result
|
||||
{
|
||||
if (null === $result) {
|
||||
$result = new Result();
|
||||
}
|
||||
|
||||
$localRepo = $this->composer->getRepositoryManager()->getLocalRepository();
|
||||
foreach ($op->getPackages() as $package) {
|
||||
$pkg = $localRepo->findPackage($package['name'], '*');
|
||||
$pkg = $pkg ?? $this->composer->getRepositoryManager()->findPackage($package['name'], $package['version'] ?: '*');
|
||||
|
||||
// not unpackable or no --unpack flag or empty packs (markers)
|
||||
if (
|
||||
null === $pkg ||
|
||||
'symfony-pack' !== $pkg->getType() ||
|
||||
!$op->shouldUnpack() ||
|
||||
0 === \count($pkg->getRequires()) + \count($pkg->getDevRequires())
|
||||
) {
|
||||
$result->addRequired($package['name'].($package['version'] ? ':'.$package['version'] : ''));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$result->addUnpacked($pkg)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$requires = [];
|
||||
foreach ($pkg->getRequires() as $link) {
|
||||
$requires[$link->getTarget()] = $link;
|
||||
}
|
||||
$devRequires = $pkg->getDevRequires();
|
||||
|
||||
foreach ($devRequires as $i => $link) {
|
||||
if (!isset($requires[$link->getTarget()])) {
|
||||
throw new \RuntimeException(sprintf('Symfony pack "%s" must duplicate all entries from "require-dev" into "require" but entry "%s" was not found.', $package['name'], $link->getTarget()));
|
||||
}
|
||||
$devRequires[$i] = $requires[$link->getTarget()];
|
||||
unset($requires[$link->getTarget()]);
|
||||
}
|
||||
|
||||
$versionSelector = null;
|
||||
foreach ([$requires, $devRequires] as $dev => $requires) {
|
||||
$dev = $dev ?: $devRequire ?: $package['dev'];
|
||||
|
||||
foreach ($requires as $link) {
|
||||
if ('php' === $linkName = $link->getTarget()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$constraint = $link->getPrettyConstraint();
|
||||
$constraint = substr($this->resolver->parseVersion($linkName, $constraint, true), 1) ?: $constraint;
|
||||
|
||||
if ($subPkg = $localRepo->findPackage($linkName, '*')) {
|
||||
if ('symfony-pack' === $subPkg->getType()) {
|
||||
$subOp = new Operation(true, $op->shouldSort());
|
||||
$subOp->addPackage($subPkg->getName(), $constraint, $dev);
|
||||
$result = $this->unpack($subOp, $result, $links, $dev);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ('*' === $constraint) {
|
||||
if (null === $versionSelector) {
|
||||
$pool = new RepositorySet($this->composer->getPackage()->getMinimumStability(), $this->composer->getPackage()->getStabilityFlags());
|
||||
$pool->addRepository(new CompositeRepository($this->composer->getRepositoryManager()->getRepositories()));
|
||||
$versionSelector = new VersionSelector($pool);
|
||||
}
|
||||
|
||||
$constraint = $versionSelector->findRecommendedRequireVersion($subPkg);
|
||||
}
|
||||
}
|
||||
|
||||
$linkType = $dev ? 'require-dev' : 'require';
|
||||
$constraint = $this->versionParser->parseConstraints($constraint);
|
||||
|
||||
if (isset($links[$linkName])) {
|
||||
$links[$linkName]['constraints'][] = $constraint;
|
||||
if ('require' === $linkType) {
|
||||
$links[$linkName]['type'] = 'require';
|
||||
}
|
||||
} else {
|
||||
$links[$linkName] = [
|
||||
'type' => $linkType,
|
||||
'name' => $linkName,
|
||||
'constraints' => [$constraint],
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->dryRun || 1 < \func_num_args()) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$jsonPath = Factory::getComposerFile();
|
||||
$jsonContent = file_get_contents($jsonPath);
|
||||
$jsonStored = json_decode($jsonContent, true);
|
||||
$jsonManipulator = new JsonManipulator($jsonContent);
|
||||
|
||||
foreach ($links as $link) {
|
||||
// nothing to do, package is already present in the "require" section
|
||||
if (isset($jsonStored['require'][$link['name']])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($jsonStored['require-dev'][$link['name']])) {
|
||||
// nothing to do, package is already present in the "require-dev" section
|
||||
if ('require-dev' === $link['type']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// removes package from "require-dev", because it will be moved to "require"
|
||||
// save stored constraint
|
||||
$link['constraints'][] = $this->versionParser->parseConstraints($jsonStored['require-dev'][$link['name']]);
|
||||
$jsonManipulator->removeSubNode('require-dev', $link['name']);
|
||||
}
|
||||
|
||||
$constraint = end($link['constraints']);
|
||||
|
||||
if (!$jsonManipulator->addLink($link['type'], $link['name'], $constraint->getPrettyString(), $op->shouldSort())) {
|
||||
throw new \RuntimeException(sprintf('Unable to unpack package "%s".', $link['name']));
|
||||
}
|
||||
}
|
||||
|
||||
file_put_contents($jsonPath, $jsonManipulator->getContents());
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function updateLock(Result $result, IOInterface $io): void
|
||||
{
|
||||
$json = new JsonFile(Factory::getComposerFile());
|
||||
$manipulator = new JsonConfigSource($json);
|
||||
$locker = $this->composer->getLocker();
|
||||
$lockData = $locker->getLockData();
|
||||
|
||||
foreach ($result->getUnpacked() as $package) {
|
||||
$manipulator->removeLink('require-dev', $package->getName());
|
||||
foreach ($lockData['packages-dev'] as $i => $pkg) {
|
||||
if ($package->getName() === $pkg['name']) {
|
||||
unset($lockData['packages-dev'][$i]);
|
||||
}
|
||||
}
|
||||
$manipulator->removeLink('require', $package->getName());
|
||||
foreach ($lockData['packages'] as $i => $pkg) {
|
||||
if ($package->getName() === $pkg['name']) {
|
||||
unset($lockData['packages'][$i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
$jsonContent = file_get_contents($json->getPath());
|
||||
$lockData['packages'] = array_values($lockData['packages']);
|
||||
$lockData['packages-dev'] = array_values($lockData['packages-dev']);
|
||||
$lockData['content-hash'] = Locker::getContentHash($jsonContent);
|
||||
$lockFile = new JsonFile(substr($json->getPath(), 0, -4).'lock', null, $io);
|
||||
|
||||
if (!$this->dryRun) {
|
||||
$lockFile->write($lockData);
|
||||
}
|
||||
|
||||
// force removal of files under vendor/
|
||||
$locker = new Locker($io, $lockFile, $this->composer->getInstallationManager(), $jsonContent);
|
||||
$this->composer->setLocker($locker);
|
||||
}
|
||||
}
|
||||
45
vendor/symfony/flex/src/Update/DiffHelper.php
vendored
Normal file
45
vendor/symfony/flex/src/Update/DiffHelper.php
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex\Update;
|
||||
|
||||
class DiffHelper
|
||||
{
|
||||
public static function removeFilesFromPatch(string $patch, array $files, array &$removedPatches): string
|
||||
{
|
||||
foreach ($files as $filename) {
|
||||
$start = strpos($patch, sprintf('diff --git a/%s b/%s', $filename, $filename));
|
||||
if (false === $start) {
|
||||
throw new \LogicException(sprintf('Could not find file "%s" in the patch.', $filename));
|
||||
}
|
||||
|
||||
$end = strpos($patch, 'diff --git a/', $start + 1);
|
||||
$contentBefore = substr($patch, 0, $start);
|
||||
if (false === $end) {
|
||||
// last patch in the file
|
||||
$removedPatches[$filename] = rtrim(substr($patch, $start), "\n");
|
||||
$patch = rtrim($contentBefore, "\n");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$removedPatches[$filename] = rtrim(substr($patch, $start, $end - $start), "\n");
|
||||
$patch = $contentBefore.substr($patch, $end);
|
||||
}
|
||||
|
||||
// valid patches end with a blank line
|
||||
if ($patch && "\n" !== substr($patch, \strlen($patch) - 1, 1)) {
|
||||
$patch = $patch."\n";
|
||||
}
|
||||
|
||||
return $patch;
|
||||
}
|
||||
}
|
||||
52
vendor/symfony/flex/src/Update/RecipePatch.php
vendored
Normal file
52
vendor/symfony/flex/src/Update/RecipePatch.php
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex\Update;
|
||||
|
||||
class RecipePatch
|
||||
{
|
||||
private $patch;
|
||||
private $blobs;
|
||||
private $deletedFiles;
|
||||
private $removedPatches;
|
||||
|
||||
public function __construct(string $patch, array $blobs, array $deletedFiles, array $removedPatches = [])
|
||||
{
|
||||
$this->patch = $patch;
|
||||
$this->blobs = $blobs;
|
||||
$this->deletedFiles = $deletedFiles;
|
||||
$this->removedPatches = $removedPatches;
|
||||
}
|
||||
|
||||
public function getPatch(): string
|
||||
{
|
||||
return $this->patch;
|
||||
}
|
||||
|
||||
public function getBlobs(): array
|
||||
{
|
||||
return $this->blobs;
|
||||
}
|
||||
|
||||
public function getDeletedFiles(): array
|
||||
{
|
||||
return $this->deletedFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Patches for modified files that were removed because the file
|
||||
* has been deleted in the user's project.
|
||||
*/
|
||||
public function getRemovedPatches(): array
|
||||
{
|
||||
return $this->removedPatches;
|
||||
}
|
||||
}
|
||||
259
vendor/symfony/flex/src/Update/RecipePatcher.php
vendored
Normal file
259
vendor/symfony/flex/src/Update/RecipePatcher.php
vendored
Normal file
@@ -0,0 +1,259 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex\Update;
|
||||
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
use Symfony\Component\Filesystem\Exception\IOException;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
|
||||
class RecipePatcher
|
||||
{
|
||||
private $rootDir;
|
||||
private $filesystem;
|
||||
private $io;
|
||||
private $processExecutor;
|
||||
|
||||
public function __construct(string $rootDir, IOInterface $io)
|
||||
{
|
||||
$this->rootDir = $rootDir;
|
||||
$this->filesystem = new Filesystem();
|
||||
$this->io = $io;
|
||||
$this->processExecutor = new ProcessExecutor($io);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the patch. If it fails unexpectedly, an exception will be thrown.
|
||||
*
|
||||
* @return bool returns true if fully successful, false if conflicts were encountered
|
||||
*/
|
||||
public function applyPatch(RecipePatch $patch): bool
|
||||
{
|
||||
$withConflicts = $this->_applyPatchFile($patch);
|
||||
|
||||
foreach ($patch->getDeletedFiles() as $deletedFile) {
|
||||
if (file_exists($this->rootDir.'/'.$deletedFile)) {
|
||||
$this->execute(sprintf('git rm %s', ProcessExecutor::escape($deletedFile)), $this->rootDir);
|
||||
}
|
||||
}
|
||||
|
||||
return $withConflicts;
|
||||
}
|
||||
|
||||
public function generatePatch(array $originalFiles, array $newFiles): RecipePatch
|
||||
{
|
||||
$ignoredFiles = $this->getIgnoredFiles(array_keys($originalFiles) + array_keys($newFiles));
|
||||
|
||||
// null implies "file does not exist"
|
||||
$originalFiles = array_filter($originalFiles, function ($file, $fileName) use ($ignoredFiles) {
|
||||
return null !== $file && !\in_array($fileName, $ignoredFiles);
|
||||
}, \ARRAY_FILTER_USE_BOTH);
|
||||
|
||||
$newFiles = array_filter($newFiles, function ($file, $fileName) use ($ignoredFiles) {
|
||||
return null !== $file && !\in_array($fileName, $ignoredFiles);
|
||||
}, \ARRAY_FILTER_USE_BOTH);
|
||||
|
||||
$deletedFiles = [];
|
||||
// find removed files & record that they are deleted
|
||||
// unset them from originalFiles to avoid unnecessary blobs being added
|
||||
foreach ($originalFiles as $file => $contents) {
|
||||
if (!isset($newFiles[$file])) {
|
||||
$deletedFiles[] = $file;
|
||||
unset($originalFiles[$file]);
|
||||
}
|
||||
}
|
||||
|
||||
// If a file is being modified, but does not exist in the current project,
|
||||
// it cannot be patched. We generate the diff for these, but then remove
|
||||
// it from the patch (and optionally report this diff to the user).
|
||||
$modifiedFiles = array_intersect_key(array_keys($originalFiles), array_keys($newFiles));
|
||||
$deletedModifiedFiles = [];
|
||||
foreach ($modifiedFiles as $modifiedFile) {
|
||||
if (!file_exists($this->rootDir.'/'.$modifiedFile) && $originalFiles[$modifiedFile] !== $newFiles[$modifiedFile]) {
|
||||
$deletedModifiedFiles[] = $modifiedFile;
|
||||
}
|
||||
}
|
||||
|
||||
// Use git binary to get project path from repository root
|
||||
$prefix = trim($this->execute('git rev-parse --show-prefix', $this->rootDir));
|
||||
$tmpPath = sys_get_temp_dir().'/_flex_recipe_update'.uniqid(mt_rand(), true);
|
||||
$this->filesystem->mkdir($tmpPath);
|
||||
|
||||
try {
|
||||
$this->execute('git init', $tmpPath);
|
||||
$this->execute('git config commit.gpgsign false', $tmpPath);
|
||||
$this->execute('git config user.name "Flex Updater"', $tmpPath);
|
||||
$this->execute('git config user.email ""', $tmpPath);
|
||||
|
||||
$blobs = [];
|
||||
if (\count($originalFiles) > 0) {
|
||||
$this->writeFiles($originalFiles, $tmpPath);
|
||||
$this->execute('git add -A', $tmpPath);
|
||||
$this->execute('git commit -m "original files"', $tmpPath);
|
||||
|
||||
$blobs = $this->generateBlobs($originalFiles, $tmpPath);
|
||||
}
|
||||
|
||||
$this->writeFiles($newFiles, $tmpPath);
|
||||
$this->execute('git add -A', $tmpPath);
|
||||
|
||||
$patchString = $this->execute(sprintf('git diff --cached --src-prefix "a/%s" --dst-prefix "b/%s"', $prefix, $prefix), $tmpPath);
|
||||
$removedPatches = [];
|
||||
$patchString = DiffHelper::removeFilesFromPatch($patchString, $deletedModifiedFiles, $removedPatches);
|
||||
|
||||
return new RecipePatch(
|
||||
$patchString,
|
||||
$blobs,
|
||||
$deletedFiles,
|
||||
$removedPatches
|
||||
);
|
||||
} finally {
|
||||
try {
|
||||
$this->filesystem->remove($tmpPath);
|
||||
} catch (IOException $e) {
|
||||
// this can sometimes fail due to git file permissions
|
||||
// if that happens, just leave it: we're in the temp directory anyways
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function writeFiles(array $files, string $directory): void
|
||||
{
|
||||
foreach ($files as $filename => $contents) {
|
||||
$path = $directory.'/'.$filename;
|
||||
if (null === $contents) {
|
||||
if (file_exists($path)) {
|
||||
unlink($path);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!file_exists(\dirname($path))) {
|
||||
$this->filesystem->mkdir(\dirname($path));
|
||||
}
|
||||
file_put_contents($path, $contents);
|
||||
}
|
||||
}
|
||||
|
||||
private function execute(string $command, string $cwd): string
|
||||
{
|
||||
$output = '';
|
||||
$statusCode = $this->processExecutor->execute($command, $output, $cwd);
|
||||
|
||||
if (0 !== $statusCode) {
|
||||
throw new \LogicException(sprintf('Command "%s" failed: "%s". Output: "%s".', $command, $this->processExecutor->getErrorOutput(), $output));
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds git blobs for each original file.
|
||||
*
|
||||
* For patching to work, each original file & contents needs to be
|
||||
* available to git as a blob. This is because the patch contains
|
||||
* the ref to the original blob, and git uses that to find the
|
||||
* original file (which is needed for the 3-way merge).
|
||||
*/
|
||||
private function addMissingBlobs(array $blobs): array
|
||||
{
|
||||
$addedBlobs = [];
|
||||
foreach ($blobs as $hash => $contents) {
|
||||
$blobPath = $this->getBlobPath($this->rootDir, $hash);
|
||||
if (file_exists($blobPath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$addedBlobs[] = $blobPath;
|
||||
if (!file_exists(\dirname($blobPath))) {
|
||||
$this->filesystem->mkdir(\dirname($blobPath));
|
||||
}
|
||||
file_put_contents($blobPath, $contents);
|
||||
}
|
||||
|
||||
return $addedBlobs;
|
||||
}
|
||||
|
||||
private function generateBlobs(array $originalFiles, string $originalFilesRoot): array
|
||||
{
|
||||
$addedBlobs = [];
|
||||
foreach ($originalFiles as $filename => $contents) {
|
||||
// if the file didn't originally exist, no blob needed
|
||||
if (!file_exists($originalFilesRoot.'/'.$filename)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$hash = trim($this->execute('git hash-object '.ProcessExecutor::escape($filename), $originalFilesRoot));
|
||||
$addedBlobs[$hash] = file_get_contents($this->getBlobPath($originalFilesRoot, $hash));
|
||||
}
|
||||
|
||||
return $addedBlobs;
|
||||
}
|
||||
|
||||
private function getBlobPath(string $gitRoot, string $hash): string
|
||||
{
|
||||
$gitDir = trim($this->execute('git rev-parse --absolute-git-dir', $gitRoot));
|
||||
|
||||
$hashStart = substr($hash, 0, 2);
|
||||
$hashEnd = substr($hash, 2);
|
||||
|
||||
return $gitDir.'/objects/'.$hashStart.'/'.$hashEnd;
|
||||
}
|
||||
|
||||
private function _applyPatchFile(RecipePatch $patch)
|
||||
{
|
||||
if (!$patch->getPatch()) {
|
||||
// nothing to do!
|
||||
return true;
|
||||
}
|
||||
|
||||
$addedBlobs = $this->addMissingBlobs($patch->getBlobs());
|
||||
|
||||
$patchPath = $this->rootDir.'/_flex_recipe_update.patch';
|
||||
file_put_contents($patchPath, $patch->getPatch());
|
||||
|
||||
try {
|
||||
$this->execute('git update-index --refresh', $this->rootDir);
|
||||
|
||||
$output = '';
|
||||
$statusCode = $this->processExecutor->execute('git apply "_flex_recipe_update.patch" -3', $output, $this->rootDir);
|
||||
|
||||
if (0 === $statusCode) {
|
||||
// successful with no conflicts
|
||||
return true;
|
||||
}
|
||||
|
||||
if (false !== strpos($this->processExecutor->getErrorOutput(), 'with conflicts')) {
|
||||
// successful with conflicts
|
||||
return false;
|
||||
}
|
||||
|
||||
throw new \LogicException('Error applying the patch: '.$this->processExecutor->getErrorOutput());
|
||||
} finally {
|
||||
unlink($patchPath);
|
||||
// clean up any temporary blobs
|
||||
foreach ($addedBlobs as $filename) {
|
||||
unlink($filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function getIgnoredFiles(array $fileNames): array
|
||||
{
|
||||
$args = implode(' ', array_map([ProcessExecutor::class, 'escape'], $fileNames));
|
||||
$output = '';
|
||||
$this->processExecutor->execute(sprintf('git check-ignore %s', $args), $output, $this->rootDir);
|
||||
|
||||
return $this->processExecutor->splitLines($output);
|
||||
}
|
||||
}
|
||||
114
vendor/symfony/flex/src/Update/RecipeUpdate.php
vendored
Normal file
114
vendor/symfony/flex/src/Update/RecipeUpdate.php
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex\Update;
|
||||
|
||||
use Symfony\Flex\Lock;
|
||||
use Symfony\Flex\Recipe;
|
||||
|
||||
class RecipeUpdate
|
||||
{
|
||||
private $originalRecipe;
|
||||
private $newRecipe;
|
||||
private $lock;
|
||||
private $rootDir;
|
||||
|
||||
/** @var string[] */
|
||||
private $originalRecipeFiles = [];
|
||||
/** @var string[] */
|
||||
private $newRecipeFiles = [];
|
||||
private $copyFromPackagePaths = [];
|
||||
|
||||
public function __construct(Recipe $originalRecipe, Recipe $newRecipe, Lock $lock, string $rootDir)
|
||||
{
|
||||
$this->originalRecipe = $originalRecipe;
|
||||
$this->newRecipe = $newRecipe;
|
||||
$this->lock = $lock;
|
||||
$this->rootDir = $rootDir;
|
||||
}
|
||||
|
||||
public function getOriginalRecipe(): Recipe
|
||||
{
|
||||
return $this->originalRecipe;
|
||||
}
|
||||
|
||||
public function getNewRecipe(): Recipe
|
||||
{
|
||||
return $this->newRecipe;
|
||||
}
|
||||
|
||||
public function getLock(): Lock
|
||||
{
|
||||
return $this->lock;
|
||||
}
|
||||
|
||||
public function getRootDir(): string
|
||||
{
|
||||
return $this->rootDir;
|
||||
}
|
||||
|
||||
public function getPackageName(): string
|
||||
{
|
||||
return $this->originalRecipe->getName();
|
||||
}
|
||||
|
||||
public function setOriginalFile(string $filename, ?string $contents): void
|
||||
{
|
||||
$this->originalRecipeFiles[$filename] = $contents;
|
||||
}
|
||||
|
||||
public function setNewFile(string $filename, ?string $contents): void
|
||||
{
|
||||
$this->newRecipeFiles[$filename] = $contents;
|
||||
}
|
||||
|
||||
public function addOriginalFiles(array $files)
|
||||
{
|
||||
foreach ($files as $file => $contents) {
|
||||
if (null === $contents) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->setOriginalFile($file, $contents);
|
||||
}
|
||||
}
|
||||
|
||||
public function addNewFiles(array $files)
|
||||
{
|
||||
foreach ($files as $file => $contents) {
|
||||
if (null === $contents) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->setNewFile($file, $contents);
|
||||
}
|
||||
}
|
||||
|
||||
public function getOriginalFiles(): array
|
||||
{
|
||||
return $this->originalRecipeFiles;
|
||||
}
|
||||
|
||||
public function getNewFiles(): array
|
||||
{
|
||||
return $this->newRecipeFiles;
|
||||
}
|
||||
|
||||
public function getCopyFromPackagePaths(): array
|
||||
{
|
||||
return $this->copyFromPackagePaths;
|
||||
}
|
||||
|
||||
public function addCopyFromPackagePath(string $source, string $target)
|
||||
{
|
||||
$this->copyFromPackagePaths[$source] = $target;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user