Ajout des vendor
This commit is contained in:
18
vendor/symfony/flex/.php-cs-fixer.dist.php
vendored
Normal file
18
vendor/symfony/flex/.php-cs-fixer.dist.php
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
$finder = PhpCsFixer\Finder::create()->in(__DIR__);
|
||||
|
||||
return (new PhpCsFixer\Config())
|
||||
->setFinder($finder)
|
||||
->setRules(array(
|
||||
'@Symfony' => true,
|
||||
'@Symfony:risky' => true,
|
||||
'fopen_flags' => false,
|
||||
'@PHPUnit48Migration:risky' => true,
|
||||
'array_syntax' => ['syntax' => 'short'],
|
||||
'ordered_imports' => true,
|
||||
'php_unit_no_expectation_annotation' => false, // part of `PHPUnitXYMigration:risky` ruleset, to be enabled when PHPUnit 4.x support will be dropped, as we don't want to rewrite exceptions handling twice
|
||||
'protected_to_private' => false,
|
||||
))
|
||||
->setRiskyAllowed(true)
|
||||
;
|
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
|
32
vendor/symfony/flex/composer.json
vendored
Normal file
32
vendor/symfony/flex/composer.json
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
{
|
||||
"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": ">=7.1",
|
||||
"composer-plugin-api": "^1.0|^2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"composer/composer": "^1.0.2|^2.0",
|
||||
"symfony/dotenv": "^4.4|^5.0|^6.0",
|
||||
"symfony/filesystem": "^4.4|^5.0|^6.0",
|
||||
"symfony/phpunit-bridge": "^4.4.12|^5.0|^6.0",
|
||||
"symfony/process": "^4.4|^5.0|^6.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Flex\\": "src"
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"class": "Symfony\\Flex\\Flex"
|
||||
}
|
||||
}
|
161
vendor/symfony/flex/src/Cache.php
vendored
Normal file
161
vendor/symfony/flex/src/Cache.php
vendored
Normal file
@ -0,0 +1,161 @@
|
||||
<?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 as BaseCache;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Package\RootPackageInterface;
|
||||
use Composer\Semver\Constraint\Constraint;
|
||||
use Composer\Semver\VersionParser;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class Cache extends BaseCache
|
||||
{
|
||||
private $versions;
|
||||
private $versionParser;
|
||||
private $symfonyRequire;
|
||||
private $rootConstraints = [];
|
||||
private $symfonyConstraints;
|
||||
private $downloader;
|
||||
private $io;
|
||||
|
||||
public function setSymfonyRequire(string $symfonyRequire, RootPackageInterface $rootPackage, Downloader $downloader, IOInterface $io = null)
|
||||
{
|
||||
$this->versionParser = new VersionParser();
|
||||
$this->symfonyRequire = $symfonyRequire;
|
||||
$this->symfonyConstraints = $this->versionParser->parseConstraints($symfonyRequire);
|
||||
$this->downloader = $downloader;
|
||||
$this->io = $io;
|
||||
|
||||
foreach ($rootPackage->getRequires() + $rootPackage->getDevRequires() as $name => $link) {
|
||||
$this->rootConstraints[$name] = $link->getConstraint();
|
||||
}
|
||||
}
|
||||
|
||||
public function read($file)
|
||||
{
|
||||
$content = parent::read($file);
|
||||
|
||||
if (0 === strpos($file, 'provider-symfony$') && \is_array($data = json_decode($content, true))) {
|
||||
$content = json_encode($this->removeLegacyTags($data));
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
public function removeLegacyTags(array $data): array
|
||||
{
|
||||
if (!$this->symfonyConstraints || !isset($data['packages'])) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
foreach ($data['packages'] as $name => $versions) {
|
||||
if (!isset($this->getVersions()['splits'][$name])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$rootConstraint = $this->rootConstraints[$name] ?? null;
|
||||
$rootVersions = [];
|
||||
|
||||
foreach ($versions as $version => $composerJson) {
|
||||
if (null !== $alias = $composerJson['extra']['branch-alias'][$version] ?? null) {
|
||||
$normalizedVersion = $this->versionParser->normalize($alias);
|
||||
} elseif (null === $normalizedVersion = $composerJson['version_normalized'] ?? null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$constraint = new Constraint('==', $normalizedVersion);
|
||||
|
||||
if ($rootConstraint && $rootConstraint->matches($constraint)) {
|
||||
$rootVersions[$version] = $composerJson;
|
||||
}
|
||||
|
||||
if (!$this->symfonyConstraints->matches($constraint)) {
|
||||
if (null !== $this->io) {
|
||||
$this->io->writeError(sprintf('<info>Restricting packages listed in "symfony/symfony" to "%s"</>', $this->symfonyRequire));
|
||||
$this->io = null;
|
||||
}
|
||||
unset($versions[$version]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($rootConstraint && !array_intersect_key($rootVersions, $versions)) {
|
||||
$versions = $rootVersions;
|
||||
}
|
||||
|
||||
$data['packages'][$name] = $versions;
|
||||
}
|
||||
|
||||
if (null === $symfonySymfony = $data['packages']['symfony/symfony'] ?? null) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
foreach ($symfonySymfony as $version => $composerJson) {
|
||||
if (null !== $alias = $composerJson['extra']['branch-alias'][$version] ?? null) {
|
||||
$normalizedVersion = $this->versionParser->normalize($alias);
|
||||
} elseif (null === $normalizedVersion = $composerJson['version_normalized'] ?? null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$this->symfonyConstraints->matches(new Constraint('==', $normalizedVersion))) {
|
||||
unset($symfonySymfony[$version]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($symfonySymfony) {
|
||||
$data['packages']['symfony/symfony'] = $symfonySymfony;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
for ($j = 0; $j < 60; ++$j) {
|
||||
if ($this->symfonyConstraints->matches(new Constraint('==', $v.'.'.$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;
|
||||
}
|
||||
}
|
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;
|
||||
}
|
||||
}
|
39
vendor/symfony/flex/src/Command/GenerateIdCommand.php
vendored
Normal file
39
vendor/symfony/flex/src/Command/GenerateIdCommand.php
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
<?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 Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
class GenerateIdCommand extends Command
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
// No-op to support downgrading to v1.12.x
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('symfony:generate-id');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$ui = new SymfonyStyle($input, $output);
|
||||
$ui->error('This command is a noop and should not be used anymore.');
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
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;
|
||||
}
|
||||
}
|
329
vendor/symfony/flex/src/Command/RecipesCommand.php
vendored
Normal file
329
vendor/symfony/flex/src/Command/RecipesCommand.php
vendored
Normal file
@ -0,0 +1,329 @@
|
||||
<?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 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 $symfonyLock;
|
||||
private $githubApi;
|
||||
|
||||
public function __construct(/* cannot be type-hinted */ $flex, Lock $symfonyLock, $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)
|
||||
{
|
||||
$installedRepo = $this->getComposer()->getRepositoryManager()->getLocalRepository();
|
||||
|
||||
// Inspect one or all packages
|
||||
$package = $input->getArgument('package');
|
||||
if (null !== $package) {
|
||||
$packages = [0 => ['name' => strtolower($package)]];
|
||||
} else {
|
||||
$locker = $this->getComposer()->getLocker();
|
||||
$lockData = $locker->getLockData();
|
||||
|
||||
// Merge all packages installed
|
||||
$packages = array_merge($lockData['packages'], $lockData['packages-dev']);
|
||||
}
|
||||
|
||||
$operations = [];
|
||||
foreach ($packages as $value) {
|
||||
if (null === $pkg = $installedRepo->findPackage($value['name'], '*')) {
|
||||
$this->getIO()->writeError(sprintf('<error>Package %s is not installed</error>', $value['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;
|
||||
/** @var Recipe $recipe */
|
||||
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;
|
||||
|
||||
$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='.(new \DateTime($commitDate))->modify('+1 seconds')->format('c\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);
|
||||
}
|
||||
}
|
36
vendor/symfony/flex/src/Command/RemoveCommand.php
vendored
Normal file
36
vendor/symfony/flex/src/Command/RemoveCommand.php
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
<?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\RemoveCommand as BaseRemoveCommand;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Flex\PackageResolver;
|
||||
|
||||
class RemoveCommand extends BaseRemoveCommand
|
||||
{
|
||||
private $resolver;
|
||||
|
||||
public function __construct(PackageResolver $resolver)
|
||||
{
|
||||
$this->resolver = $resolver;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$input->setArgument('packages', $this->resolver->resolve($input->getArgument('packages')));
|
||||
|
||||
return parent::execute($input, $output);
|
||||
}
|
||||
}
|
90
vendor/symfony/flex/src/Command/RequireCommand.php
vendored
Normal file
90
vendor/symfony/flex/src/Command/RequireCommand.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\Command;
|
||||
|
||||
use Composer\Command\RequireCommand as BaseRequireCommand;
|
||||
use Composer\Factory;
|
||||
use Composer\Json\JsonFile;
|
||||
use Composer\Json\JsonManipulator;
|
||||
use Composer\Plugin\PluginInterface;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Flex\PackageResolver;
|
||||
|
||||
class RequireCommand extends BaseRequireCommand
|
||||
{
|
||||
private $resolver;
|
||||
private $updateComposerLock;
|
||||
|
||||
public function __construct(PackageResolver $resolver, \Closure $updateComposerLock = null)
|
||||
{
|
||||
$this->resolver = $resolver;
|
||||
$this->updateComposerLock = $updateComposerLock;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
parent::configure();
|
||||
$this->addOption('no-unpack', null, InputOption::VALUE_NONE, '[DEPRECATED] Disable unpacking Symfony packs in composer.json.');
|
||||
$this->addOption('unpack', null, InputOption::VALUE_NONE, '[DEPRECATED] Unpacking is now enabled by default.');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
if ($input->getOption('no-unpack')) {
|
||||
$this->getIO()->writeError('<warning>The "--unpack" command line option is deprecated; unpacking is now enabled by default.</warning>');
|
||||
}
|
||||
|
||||
if ($input->getOption('unpack')) {
|
||||
$this->getIO()->writeError('<warning>The "--unpack" command line option is deprecated; unpacking is now enabled by default.</warning>');
|
||||
}
|
||||
|
||||
$packages = $this->resolver->resolve($input->getArgument('packages'), true);
|
||||
if ($packages) {
|
||||
$input->setArgument('packages', $this->resolver->resolve($input->getArgument('packages'), true));
|
||||
}
|
||||
|
||||
if (version_compare('2.0.0', PluginInterface::PLUGIN_API_VERSION, '>') && $input->hasOption('no-suggest')) {
|
||||
$input->setOption('no-suggest', true);
|
||||
}
|
||||
|
||||
$file = Factory::getComposerFile();
|
||||
$contents = file_get_contents($file);
|
||||
$json = JsonFile::parseJson($contents);
|
||||
|
||||
if (\array_key_exists('require-dev', $json) && !$json['require-dev'] && (new JsonManipulator($contents))->removeMainKey('require-dev')) {
|
||||
$manipulator = new JsonManipulator($contents);
|
||||
$manipulator->addLink('require-dev', 'php', '*');
|
||||
file_put_contents($file, $manipulator->getContents());
|
||||
} else {
|
||||
$file = null;
|
||||
}
|
||||
unset($contents, $json, $manipulator);
|
||||
|
||||
try {
|
||||
return parent::execute($input, $output) ?? 0;
|
||||
} finally {
|
||||
if (null !== $file) {
|
||||
$manipulator = new JsonManipulator(file_get_contents($file));
|
||||
$manipulator->removeSubNode('require-dev', 'php');
|
||||
file_put_contents($file, $manipulator->getContents());
|
||||
|
||||
if ($this->updateComposerLock) {
|
||||
($this->updateComposerLock)();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
128
vendor/symfony/flex/src/Command/UnpackCommand.php
vendored
Normal file
128
vendor/symfony/flex/src/Command/UnpackCommand.php
vendored
Normal file
@ -0,0 +1,128 @@
|
||||
<?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\Factory;
|
||||
use Composer\Installer;
|
||||
use Composer\Package\Version\VersionParser;
|
||||
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\PackageResolver;
|
||||
use Symfony\Flex\Unpack\Operation;
|
||||
use Symfony\Flex\Unpacker;
|
||||
|
||||
/**
|
||||
* @deprecated since Flex 1.4
|
||||
*/
|
||||
class UnpackCommand extends BaseCommand
|
||||
{
|
||||
private $resolver;
|
||||
|
||||
public function __construct(PackageResolver $resolver)
|
||||
{
|
||||
$this->resolver = $resolver;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('symfony:unpack')
|
||||
->setAliases(['unpack'])
|
||||
->setDescription('[DEPRECATED] Unpacks a Symfony pack.')
|
||||
->setDefinition([
|
||||
new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'Installed packages to unpack.'),
|
||||
new InputOption('sort-packages', null, InputOption::VALUE_NONE, 'Sorts packages'),
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$composer = $this->getComposer();
|
||||
$packages = $this->resolver->resolve($input->getArgument('packages'), true);
|
||||
$io = $this->getIO();
|
||||
$lockData = $composer->getLocker()->getLockData();
|
||||
$installedRepo = $composer->getRepositoryManager()->getLocalRepository();
|
||||
$versionParser = new VersionParser();
|
||||
$dryRun = $input->hasOption('dry-run') && $input->getOption('dry-run');
|
||||
|
||||
$io->writeError('<warning>Command "symfony:unpack" is deprecated, Symfony packs are always unpacked now.</>');
|
||||
|
||||
$op = new Operation(true, $input->getOption('sort-packages') || $composer->getConfig()->get('sort-packages'));
|
||||
foreach ($versionParser->parseNameVersionPairs($packages) as $package) {
|
||||
if (null === $pkg = $installedRepo->findPackage($package['name'], '*')) {
|
||||
$io->writeError(sprintf('<error>Package %s is not installed</>', $package['name']));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
$dev = false;
|
||||
foreach ($lockData['packages-dev'] as $p) {
|
||||
if ($package['name'] === $p['name']) {
|
||||
$dev = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$op->addPackage($pkg->getName(), $pkg->getVersion(), $dev);
|
||||
}
|
||||
|
||||
$unpacker = new Unpacker($composer, $this->resolver, $dryRun);
|
||||
$result = $unpacker->unpack($op);
|
||||
|
||||
// remove the packages themselves
|
||||
if (!$result->getUnpacked()) {
|
||||
$io->writeError('<info>Nothing to unpack</>');
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
$io->writeError('<info>Unpacking Symfony packs</>');
|
||||
foreach ($result->getUnpacked() as $pkg) {
|
||||
$io->writeError(sprintf(' - Unpacked <info>%s</>', $pkg->getName()));
|
||||
}
|
||||
|
||||
$unpacker->updateLock($result, $io);
|
||||
|
||||
if ($input->hasOption('no-install') && $input->getOption('no-install')) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$composer = Factory::create($io, null, true);
|
||||
$installer = Installer::create($io, $composer);
|
||||
$installer
|
||||
->setDryRun($dryRun)
|
||||
->setDevMode(true)
|
||||
->setDumpAutoloader(false)
|
||||
->setIgnorePlatformRequirements(true)
|
||||
->setUpdate(true)
|
||||
->setUpdateAllowList(['php'])
|
||||
;
|
||||
|
||||
if (method_exists($composer->getEventDispatcher(), 'setRunScripts')) {
|
||||
$composer->getEventDispatcher()->setRunScripts(false);
|
||||
} else {
|
||||
$installer->setRunScripts(false);
|
||||
}
|
||||
|
||||
if (method_exists($installer, 'setSkipSuggest')) {
|
||||
$installer->setSkipSuggest(true);
|
||||
}
|
||||
|
||||
return $installer->run();
|
||||
}
|
||||
}
|
41
vendor/symfony/flex/src/Command/UpdateCommand.php
vendored
Normal file
41
vendor/symfony/flex/src/Command/UpdateCommand.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\Command;
|
||||
|
||||
use Composer\Command\UpdateCommand as BaseUpdateCommand;
|
||||
use Composer\Plugin\PluginInterface;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Flex\PackageResolver;
|
||||
|
||||
class UpdateCommand extends BaseUpdateCommand
|
||||
{
|
||||
private $resolver;
|
||||
|
||||
public function __construct(PackageResolver $resolver)
|
||||
{
|
||||
$this->resolver = $resolver;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$input->setArgument('packages', $this->resolver->resolve($input->getArgument('packages')));
|
||||
|
||||
if (version_compare('2.0.0', PluginInterface::PLUGIN_API_VERSION, '>') && $input->hasOption('no-suggest')) {
|
||||
$input->setOption('no-suggest', true);
|
||||
}
|
||||
|
||||
return parent::execute($input, $output);
|
||||
}
|
||||
}
|
423
vendor/symfony/flex/src/Command/UpdateRecipesCommand.php
vendored
Normal file
423
vendor/symfony/flex/src/Command/UpdateRecipesCommand.php
vendored
Normal file
@ -0,0 +1,423 @@
|
||||
<?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\Util\ProcessExecutor;
|
||||
use Symfony\Component\Console\Exception\RuntimeException;
|
||||
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
|
||||
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;
|
||||
}
|
||||
|
||||
$originalRecipe = $this->getRecipe($packageName, $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($packageName);
|
||||
|
||||
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(string $packageName, string $recipeRef = null, string $recipeVersion = null): ?Recipe
|
||||
{
|
||||
$installedRepo = $this->getComposer()->getRepositoryManager()->getLocalRepository();
|
||||
$package = $installedRepo->findPackage($packageName, '*');
|
||||
if (null === $package) {
|
||||
throw new RuntimeException(sprintf('Could not find package "%s". Try running "composer install".', $packageName));
|
||||
}
|
||||
$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,
|
||||
$packageName,
|
||||
$operation->getOperationType(),
|
||||
$recipes['manifests'][$packageName],
|
||||
$recipes['locks'][$packageName] ?? []
|
||||
);
|
||||
}
|
||||
|
||||
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();
|
||||
$locker = $this->getComposer()->getLocker();
|
||||
$lockData = $locker->getLockData();
|
||||
|
||||
// Merge all packages installed
|
||||
$packages = array_merge($lockData['packages'], $lockData['packages-dev']);
|
||||
|
||||
$operations = [];
|
||||
foreach ($packages as $value) {
|
||||
if (null === $pkg = $installedRepo->findPackage($value['name'], '*')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$operations[] = new InformationOperation($pkg);
|
||||
}
|
||||
|
||||
$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;
|
||||
}
|
||||
}
|
58
vendor/symfony/flex/src/ComposerRepository.php
vendored
Normal file
58
vendor/symfony/flex/src/ComposerRepository.php
vendored
Normal file
@ -0,0 +1,58 @@
|
||||
<?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\Repository\ComposerRepository as BaseComposerRepository;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class ComposerRepository extends BaseComposerRepository
|
||||
{
|
||||
private $providerFiles;
|
||||
|
||||
protected function loadProviderListings($data)
|
||||
{
|
||||
if (null !== $this->providerFiles) {
|
||||
parent::loadProviderListings($data);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$data = [$data];
|
||||
|
||||
while ($data) {
|
||||
$this->providerFiles = [];
|
||||
foreach ($data as $data) {
|
||||
$this->loadProviderListings($data);
|
||||
}
|
||||
|
||||
$loadingFiles = $this->providerFiles;
|
||||
$this->providerFiles = null;
|
||||
$data = [];
|
||||
$this->rfs->download($loadingFiles, function (...$args) use (&$data) {
|
||||
$data[] = $this->fetchFile(...$args);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected function fetchFile($filename, $cacheKey = null, $sha256 = null, $storeLastModifiedTime = false)
|
||||
{
|
||||
if (null !== $this->providerFiles) {
|
||||
$this->providerFiles[] = [$filename, $cacheKey, $sha256, $storeLastModifiedTime];
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
return parent::fetchFile($filename, $cacheKey, $sha256, $storeLastModifiedTime);
|
||||
}
|
||||
}
|
97
vendor/symfony/flex/src/Configurator.php
vendored
Normal file
97
vendor/symfony/flex/src/Configurator.php
vendored
Normal file
@ -0,0 +1,97 @@
|
||||
<?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 $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,
|
||||
'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,
|
||||
];
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function populateUpdate(RecipeUpdate $recipeUpdate): void
|
||||
{
|
||||
$originalManifest = $recipeUpdate->getOriginalRecipe()->getManifest();
|
||||
$newManifest = $recipeUpdate->getNewRecipe()->getManifest();
|
||||
foreach (array_keys($this->configurators) 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();
|
||||
foreach (array_keys($this->configurators) as $key) {
|
||||
if (isset($manifest[$key])) {
|
||||
$this->get($key)->unconfigure($recipe, $manifest[$key], $lock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function get($key): AbstractConfigurator
|
||||
{
|
||||
if (!isset($this->configurators[$key])) {
|
||||
throw new \InvalidArgumentException(sprintf('Unknown configurator "%s".', $key));
|
||||
}
|
||||
|
||||
if (isset($this->cache[$key])) {
|
||||
return $this->cache[$key];
|
||||
}
|
||||
|
||||
$class = $this->configurators[$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)
|
||||
{
|
||||
if (!\is_array($messages)) {
|
||||
$messages = [$messages];
|
||||
}
|
||||
foreach ($messages as $i => $message) {
|
||||
$messages[$i] = ' '.$message;
|
||||
}
|
||||
$this->io->writeError($messages, true, IOInterface::VERBOSE);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
143
vendor/symfony/flex/src/Configurator/BundlesConfigurator.php
vendored
Normal file
143
vendor/symfony/flex/src/Configurator/BundlesConfigurator.php
vendored
Normal file
@ -0,0 +1,143 @@
|
||||
<?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);
|
||||
$recipeUpdate->setOriginalFile(
|
||||
$this->getLocalConfFile(),
|
||||
$this->buildContents($originalBundles)
|
||||
);
|
||||
|
||||
$newBundles = $this->configureBundles($newConfig);
|
||||
$recipeUpdate->setNewFile(
|
||||
$this->getLocalConfFile(),
|
||||
$this->buildContents($newBundles)
|
||||
);
|
||||
}
|
||||
|
||||
private function configureBundles(array $bundles): 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) {
|
||||
// if the class already existed, clear so we can update the envs
|
||||
if (isset($registered[$class])) {
|
||||
$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();
|
||||
}
|
||||
}
|
150
vendor/symfony/flex/src/Configurator/ContainerConfigurator.php
vendored
Normal file
150
vendor/symfony/flex/src/Configurator/ContainerConfigurator.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 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 = [];
|
||||
foreach (file($target) as $line) {
|
||||
if ($this->removeParameters(1, $parameters, $line)) {
|
||||
continue;
|
||||
}
|
||||
$lines[] = $line;
|
||||
}
|
||||
file_put_contents($target, implode('', $lines));
|
||||
}
|
||||
|
||||
public function update(RecipeUpdate $recipeUpdate, array $originalConfig, array $newConfig): void
|
||||
{
|
||||
if ($originalConfig) {
|
||||
$recipeUpdate->setOriginalFile(
|
||||
$this->getServicesPath(),
|
||||
$this->configureParameters($originalConfig, true)
|
||||
);
|
||||
}
|
||||
|
||||
if ($newConfig) {
|
||||
$recipeUpdate->setNewFile(
|
||||
$this->getServicesPath(),
|
||||
$this->configureParameters($newConfig, true)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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 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);
|
||||
}
|
||||
}
|
384
vendor/symfony/flex/src/Configurator/DockerComposeConfigurator.php
vendored
Normal file
384
vendor/symfony/flex/src/Configurator/DockerComposeConfigurator.php
vendored
Normal file
@ -0,0 +1,384 @@
|
||||
<?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 docker-compose.yml file.
|
||||
*
|
||||
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||
*/
|
||||
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 $val) {
|
||||
// Support for the short syntax recipe syntax that modifies docker-compose.yml only
|
||||
return isset($val[0]) ? ['docker-compose.yml' => $config] : $config;
|
||||
}
|
||||
|
||||
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'])) {
|
||||
$separator = $_SERVER['COMPOSE_PATH_SEPARATOR'] ?? ('\\' === \DIRECTORY_SEPARATOR ? ';' : ':');
|
||||
|
||||
$files = explode($separator, $_SERVER['COMPOSE_FILE']);
|
||||
foreach ($files as $f) {
|
||||
if ($file !== basename($f)) {
|
||||
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 {
|
||||
// Test with the ".yaml" extension if the file doesn't end up with ".yml".
|
||||
if (
|
||||
$this->filesystem->exists($dockerComposeFile = sprintf('%s/%s', $dir, $file)) ||
|
||||
$this->filesystem->exists($dockerComposeFile = substr($dockerComposeFile, 0, -2).'aml')
|
||||
) {
|
||||
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, "version: '3'\n");
|
||||
$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] = '' !== 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_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 = ltrim(str_replace($rootDir, '', $file), '/\\');
|
||||
$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>docker-compose.yml</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;
|
||||
}
|
||||
}
|
277
vendor/symfony/flex/src/Configurator/EnvConfigurator.php
vendored
Normal file
277
vendor/symfony/flex/src/Configurator/EnvConfigurator.php
vendored
Normal file
@ -0,0 +1,277 @@
|
||||
<?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 EnvConfigurator extends AbstractConfigurator
|
||||
{
|
||||
public function configure(Recipe $recipe, $vars, Lock $lock, array $options = [])
|
||||
{
|
||||
$this->write('Adding environment variable defaults');
|
||||
|
||||
$this->configureEnvDist($recipe, $vars, $options['force'] ?? false);
|
||||
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';
|
||||
|
||||
foreach ([$dotenvPath.'.dist', $dotenvPath] 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';
|
||||
|
||||
foreach ([$dotenvPath, $dotenvPath.'.dist'] 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 = [$dotenvPath, $dotenvPath.'.dist', 'phpunit.xml.dist', 'phpunit.xml'];
|
||||
|
||||
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 (!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 (0 !== strpos($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;
|
||||
}
|
||||
}
|
216
vendor/symfony/flex/src/CurlDownloader.php
vendored
Normal file
216
vendor/symfony/flex/src/CurlDownloader.php
vendored
Normal file
@ -0,0 +1,216 @@
|
||||
<?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\Downloader\TransportException;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class CurlDownloader
|
||||
{
|
||||
private $multiHandle;
|
||||
private $shareHandle;
|
||||
private $jobs = [];
|
||||
private $exceptions = [];
|
||||
|
||||
private static $options = [
|
||||
'http' => [
|
||||
'method' => \CURLOPT_CUSTOMREQUEST,
|
||||
'content' => \CURLOPT_POSTFIELDS,
|
||||
],
|
||||
'ssl' => [
|
||||
'cafile' => \CURLOPT_CAINFO,
|
||||
'capath' => \CURLOPT_CAPATH,
|
||||
],
|
||||
];
|
||||
|
||||
private static $timeInfo = [
|
||||
'total_time' => true,
|
||||
'namelookup_time' => true,
|
||||
'connect_time' => true,
|
||||
'pretransfer_time' => true,
|
||||
'starttransfer_time' => true,
|
||||
'redirect_time' => true,
|
||||
];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->multiHandle = $mh = curl_multi_init();
|
||||
curl_multi_setopt($mh, \CURLMOPT_PIPELINING, /*CURLPIPE_MULTIPLEX*/ 2);
|
||||
if (\defined('CURLMOPT_MAX_HOST_CONNECTIONS')) {
|
||||
curl_multi_setopt($mh, \CURLMOPT_MAX_HOST_CONNECTIONS, 8);
|
||||
}
|
||||
|
||||
$this->shareHandle = $sh = curl_share_init();
|
||||
curl_share_setopt($sh, \CURLSHOPT_SHARE, \CURL_LOCK_DATA_COOKIE);
|
||||
curl_share_setopt($sh, \CURLSHOPT_SHARE, \CURL_LOCK_DATA_DNS);
|
||||
curl_share_setopt($sh, \CURLSHOPT_SHARE, \CURL_LOCK_DATA_SSL_SESSION);
|
||||
}
|
||||
|
||||
public function get($origin, $url, $context, $file)
|
||||
{
|
||||
$params = stream_context_get_params($context);
|
||||
|
||||
$ch = curl_init();
|
||||
$hd = fopen('php://temp/maxmemory:32768', 'w+b');
|
||||
if ($file && !$fd = @fopen($file.'~', 'w+b')) {
|
||||
$file = null;
|
||||
}
|
||||
if (!$file) {
|
||||
$fd = @fopen('php://temp/maxmemory:524288', 'w+b');
|
||||
}
|
||||
$headers = array_diff($params['options']['http']['header'], ['Connection: close']);
|
||||
|
||||
if (!isset($params['options']['http']['protocol_version'])) {
|
||||
curl_setopt($ch, \CURLOPT_HTTP_VERSION, \CURL_HTTP_VERSION_1_0);
|
||||
} else {
|
||||
$headers[] = 'Connection: keep-alive';
|
||||
if (0 === strpos($url, 'https://') && \defined('CURL_VERSION_HTTP2') && \defined('CURL_HTTP_VERSION_2_0') && (\CURL_VERSION_HTTP2 & curl_version()['features'])) {
|
||||
curl_setopt($ch, \CURLOPT_HTTP_VERSION, \CURL_HTTP_VERSION_2_0);
|
||||
}
|
||||
}
|
||||
|
||||
curl_setopt($ch, \CURLOPT_URL, $url);
|
||||
curl_setopt($ch, \CURLOPT_HTTPHEADER, $headers);
|
||||
curl_setopt($ch, \CURLOPT_FOLLOWLOCATION, true);
|
||||
curl_setopt($ch, \CURLOPT_DNS_USE_GLOBAL_CACHE, false);
|
||||
curl_setopt($ch, \CURLOPT_WRITEHEADER, $hd);
|
||||
curl_setopt($ch, \CURLOPT_FILE, $fd);
|
||||
curl_setopt($ch, \CURLOPT_SHARE, $this->shareHandle);
|
||||
|
||||
foreach (self::$options as $type => $options) {
|
||||
foreach ($options as $name => $curlopt) {
|
||||
if (isset($params['options'][$type][$name])) {
|
||||
curl_setopt($ch, $curlopt, $params['options'][$type][$name]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$progress = array_diff_key(curl_getinfo($ch), self::$timeInfo);
|
||||
$this->jobs[(int) $ch] = [
|
||||
'progress' => $progress,
|
||||
'ch' => $ch,
|
||||
'callback' => $params['notification'],
|
||||
'file' => $file,
|
||||
'fd' => $fd,
|
||||
];
|
||||
|
||||
curl_multi_add_handle($this->multiHandle, $ch);
|
||||
$params['notification'](\STREAM_NOTIFY_RESOLVE, \STREAM_NOTIFY_SEVERITY_INFO, '', 0, 0, 0, false);
|
||||
$active = true;
|
||||
|
||||
try {
|
||||
while ($active && isset($this->jobs[(int) $ch])) {
|
||||
curl_multi_exec($this->multiHandle, $active);
|
||||
curl_multi_select($this->multiHandle);
|
||||
|
||||
while ($progress = curl_multi_info_read($this->multiHandle)) {
|
||||
if (!isset($this->jobs[$i = (int) $h = $progress['handle']])) {
|
||||
continue;
|
||||
}
|
||||
$progress = array_diff_key(curl_getinfo($h), self::$timeInfo);
|
||||
$job = $this->jobs[$i];
|
||||
unset($this->jobs[$i]);
|
||||
curl_multi_remove_handle($this->multiHandle, $h);
|
||||
try {
|
||||
$this->onProgress($h, $job['callback'], $progress, $job['progress']);
|
||||
|
||||
if ('' !== curl_error($h)) {
|
||||
throw new TransportException(curl_error($h));
|
||||
}
|
||||
if ($job['file'] && \CURLE_OK === curl_errno($h) && !isset($this->exceptions[$i])) {
|
||||
fclose($job['fd']);
|
||||
rename($job['file'].'~', $job['file']);
|
||||
}
|
||||
} catch (TransportException $e) {
|
||||
$this->exceptions[$i] = $e;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->jobs as $i => $h) {
|
||||
if (!isset($this->jobs[$i])) {
|
||||
continue;
|
||||
}
|
||||
$h = $this->jobs[$i]['ch'];
|
||||
$progress = array_diff_key(curl_getinfo($h), self::$timeInfo);
|
||||
|
||||
if ($this->jobs[$i]['progress'] !== $progress) {
|
||||
$previousProgress = $this->jobs[$i]['progress'];
|
||||
$this->jobs[$i]['progress'] = $progress;
|
||||
try {
|
||||
$this->onProgress($h, $this->jobs[$i]['callback'], $progress, $previousProgress);
|
||||
} catch (TransportException $e) {
|
||||
unset($this->jobs[$i]);
|
||||
curl_multi_remove_handle($this->multiHandle, $h);
|
||||
$this->exceptions[$i] = $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ('' !== curl_error($ch) || \CURLE_OK !== curl_errno($ch)) {
|
||||
$this->exceptions[(int) $ch] = new TransportException(curl_error($ch), curl_getinfo($ch, \CURLINFO_HTTP_CODE) ?: 0);
|
||||
}
|
||||
if (isset($this->exceptions[(int) $ch])) {
|
||||
throw $this->exceptions[(int) $ch];
|
||||
}
|
||||
} finally {
|
||||
if ($file && !isset($this->exceptions[(int) $ch])) {
|
||||
$fd = fopen($file, 'rb');
|
||||
}
|
||||
$progress = array_diff_key(curl_getinfo($ch), self::$timeInfo);
|
||||
$this->finishProgress($ch, $params['notification'], $progress);
|
||||
unset($this->jobs[(int) $ch], $this->exceptions[(int) $ch]);
|
||||
curl_multi_remove_handle($this->multiHandle, $ch);
|
||||
curl_close($ch);
|
||||
|
||||
rewind($hd);
|
||||
$headers = explode("\r\n", rtrim(stream_get_contents($hd)));
|
||||
fclose($hd);
|
||||
|
||||
rewind($fd);
|
||||
$contents = stream_get_contents($fd);
|
||||
fclose($fd);
|
||||
}
|
||||
|
||||
return [$headers, $contents];
|
||||
}
|
||||
|
||||
private function onProgress($ch, callable $notify, array $progress, array $previousProgress)
|
||||
{
|
||||
if (300 <= $progress['http_code'] && $progress['http_code'] < 400 || 0 > $progress['download_content_length']) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$previousProgress['http_code'] && $progress['http_code'] && $progress['http_code'] < 200 || 400 <= $progress['http_code']) {
|
||||
$code = 403 === $progress['http_code'] ? \STREAM_NOTIFY_AUTH_RESULT : \STREAM_NOTIFY_FAILURE;
|
||||
$notify($code, \STREAM_NOTIFY_SEVERITY_ERR, curl_error($ch), $progress['http_code'], 0, 0, false);
|
||||
}
|
||||
|
||||
if ($previousProgress['download_content_length'] < $progress['download_content_length']) {
|
||||
$notify(\STREAM_NOTIFY_FILE_SIZE_IS, \STREAM_NOTIFY_SEVERITY_INFO, '', 0, 0, (int) $progress['download_content_length'], false);
|
||||
}
|
||||
|
||||
if ($previousProgress['size_download'] < $progress['size_download']) {
|
||||
$notify(\STREAM_NOTIFY_PROGRESS, \STREAM_NOTIFY_SEVERITY_INFO, '', 0, (int) $progress['size_download'], (int) $progress['download_content_length'], false);
|
||||
}
|
||||
}
|
||||
|
||||
private function finishProgress($ch, callable $notify, array $progress)
|
||||
{
|
||||
if ($progress['download_content_length'] < 0) {
|
||||
$notify(\STREAM_NOTIFY_FILE_SIZE_IS, \STREAM_NOTIFY_SEVERITY_INFO, '', 0, 0, (int) $progress['size_download'], false);
|
||||
$notify(\STREAM_NOTIFY_PROGRESS, \STREAM_NOTIFY_SEVERITY_INFO, '', 0, (int) $progress['size_download'], (int) $progress['size_download'], false);
|
||||
}
|
||||
}
|
||||
}
|
494
vendor/symfony/flex/src/Downloader.php
vendored
Normal file
494
vendor/symfony/flex/src/Downloader.php
vendored
Normal file
@ -0,0 +1,494 @@
|
||||
<?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 as ComposerCache;
|
||||
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;
|
||||
|
||||
/** @var HttpDownloader|ParallelDownloader */
|
||||
private $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, $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 ComposerCache($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 setFlexId(string $id = null)
|
||||
{
|
||||
// No-op to support downgrading to v1.12.x
|
||||
}
|
||||
|
||||
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['recipes_template_relative'])) {
|
||||
$links['recipes_template'] = preg_replace('{[^/\?]*+(?=\?|$)}', $links['recipes_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) {
|
||||
$data['locks'][$package->getName()]['version'] = $version;
|
||||
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);
|
||||
}
|
||||
|
||||
if ($this->rfs instanceof HttpDownloader) {
|
||||
$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);
|
||||
} else {
|
||||
foreach ($urls as $i => $url) {
|
||||
$urls[$i] = [$url];
|
||||
}
|
||||
$this->rfs->download($urls, function ($url) use ($options, &$responses, &$retries, &$error) {
|
||||
try {
|
||||
$cacheKey = self::generateCacheKey($url);
|
||||
$origin = method_exists($this->rfs, 'getOrigin') ? $this->rfs::getOrigin($url) : parse_url($url, \PHP_URL_HOST);
|
||||
$json = $this->rfs->getContents($origin, $url, false, $options[$url]);
|
||||
if (200 === $this->rfs->findStatusCode($this->rfs->getLastHeaders())) {
|
||||
$responses[$url] = $this->parseJson($json, $url, $cacheKey, $this->rfs->getLastHeaders())->getBody();
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$retries[] = [$url, $e];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
1078
vendor/symfony/flex/src/Flex.php
vendored
Normal file
1078
vendor/symfony/flex/src/Flex.php
vendored
Normal file
@ -0,0 +1,1078 @@
|
||||
<?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\Pool;
|
||||
use Composer\DependencyResolver\Transaction;
|
||||
use Composer\Downloader\FileDownloader;
|
||||
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\Comparer\Comparer;
|
||||
use Composer\Package\Locker;
|
||||
use Composer\Package\Package;
|
||||
use Composer\Package\PackageInterface;
|
||||
use Composer\Plugin\PluginEvents;
|
||||
use Composer\Plugin\PluginInterface;
|
||||
use Composer\Plugin\PreFileDownloadEvent;
|
||||
use Composer\Plugin\PrePoolCreateEvent;
|
||||
use Composer\Repository\ComposerRepository as BaseComposerRepository;
|
||||
use Composer\Repository\RepositoryFactory;
|
||||
use Composer\Repository\RepositoryManager;
|
||||
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
|
||||
{
|
||||
/**
|
||||
* @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 $cacheDirPopulated = false;
|
||||
private $displayThanksReminder = 0;
|
||||
private $rfs;
|
||||
private $progress = true;
|
||||
private $dryRun = false;
|
||||
private static $activated = true;
|
||||
private static $repoReadingCommands = [
|
||||
'create-project' => true,
|
||||
'outdated' => true,
|
||||
'require' => true,
|
||||
'update' => true,
|
||||
'install' => true,
|
||||
];
|
||||
private static $aliasResolveCommands = [
|
||||
'require' => true,
|
||||
'update' => false,
|
||||
'remove' => false,
|
||||
'unpack' => true,
|
||||
];
|
||||
private $filter;
|
||||
|
||||
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();
|
||||
|
||||
$symfonyRequire = preg_replace('/\.x$/', '.x-dev', getenv('SYMFONY_REQUIRE') ?: ($composer->getPackage()->getExtra()['symfony']['require'] ?? ''));
|
||||
|
||||
if ($composer2 = version_compare('2.0.0', PluginInterface::PLUGIN_API_VERSION, '<=')) {
|
||||
$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);
|
||||
}
|
||||
|
||||
$setRepositories = null;
|
||||
} else {
|
||||
$rfs = Factory::createRemoteFilesystem($this->io, $this->config);
|
||||
$this->rfs = $rfs = new ParallelDownloader($this->io, $this->config, $rfs->getOptions(), $rfs->isTlsDisabled());
|
||||
|
||||
$this->downloader = $downloader = new Downloader($composer, $io, $this->rfs);
|
||||
|
||||
$rootPackage = $composer->getPackage();
|
||||
$manager = RepositoryFactory::manager($this->io, $this->config, $composer->getEventDispatcher(), $this->rfs);
|
||||
$setRepositories = \Closure::bind(function (RepositoryManager $manager) use (&$symfonyRequire, $rootPackage, $downloader) {
|
||||
$manager->repositoryClasses = $this->repositoryClasses;
|
||||
$manager->setRepositoryClass('composer', TruncatedComposerRepository::class);
|
||||
$manager->repositories = $this->repositories;
|
||||
$i = 0;
|
||||
foreach (RepositoryFactory::defaultRepos(null, $this->config, $manager) as $repo) {
|
||||
$manager->repositories[$i++] = $repo;
|
||||
if ($repo instanceof TruncatedComposerRepository && $symfonyRequire) {
|
||||
$repo->setSymfonyRequire($symfonyRequire, $rootPackage, $downloader, $this->io);
|
||||
}
|
||||
}
|
||||
$manager->setLocalRepository($this->getLocalRepository());
|
||||
}, $composer->getRepositoryManager(), RepositoryManager::class);
|
||||
|
||||
$setRepositories($manager);
|
||||
$composer->setRepositoryManager($manager);
|
||||
}
|
||||
|
||||
$this->configurator = new Configurator($composer, $io, $this->options);
|
||||
$this->lock = new Lock(getenv('SYMFONY_LOCKFILE') ?: str_replace('composer.json', 'symfony.lock', Factory::getComposerFile()));
|
||||
|
||||
$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();
|
||||
}
|
||||
|
||||
$populateRepoCacheDir = !$composer2 && __CLASS__ === self::class;
|
||||
if (!$composer2 && $composer->getPluginManager()) {
|
||||
foreach ($composer->getPluginManager()->getPlugins() as $plugin) {
|
||||
if (0 === strpos(\get_class($plugin), 'Hirak\Prestissimo\Plugin')) {
|
||||
if (method_exists($rfs, 'getRemoteContents')) {
|
||||
$plugin->disable();
|
||||
} else {
|
||||
$this->cacheDirPopulated = true;
|
||||
}
|
||||
$populateRepoCacheDir = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$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);
|
||||
|
||||
if (version_compare('1.1.0', PluginInterface::PLUGIN_API_VERSION, '>')) {
|
||||
$note = $app->has('self-update') ? sprintf('`php %s self-update`', $_SERVER['argv'][0]) : 'https://getcomposer.org/';
|
||||
$io->writeError('<warning>Some Symfony Flex features may not work as expected: your version of Composer is too old</>');
|
||||
$io->writeError(sprintf('<warning>Please upgrade using %s</>', $note));
|
||||
}
|
||||
|
||||
try {
|
||||
$command = $input->getFirstArgument();
|
||||
$command = $command ? $app->find($command)->getName() : null;
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
}
|
||||
|
||||
if ('create-project' === $command) {
|
||||
// detect Composer >=1.7 (using the Composer::VERSION constant doesn't work with snapshot builds)
|
||||
if (class_exists(Comparer::class)) {
|
||||
if ($input->hasOption('remove-vcs')) {
|
||||
$input->setOption('remove-vcs', true);
|
||||
}
|
||||
} else {
|
||||
$input->setInteractive(false);
|
||||
}
|
||||
$populateRepoCacheDir = $populateRepoCacheDir && !$input->hasOption('remove-vcs');
|
||||
} elseif ('update' === $command) {
|
||||
$this->displayThanksReminder = 1;
|
||||
} elseif ('outdated' === $command) {
|
||||
$symfonyRequire = null;
|
||||
if ($setRepositories) {
|
||||
$setRepositories($manager);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset(self::$aliasResolveCommands[$command])) {
|
||||
// early resolve for BC with Composer 1.0
|
||||
if ($input->hasArgument('packages')) {
|
||||
$input->setArgument('packages', $resolver->resolve($input->getArgument('packages'), self::$aliasResolveCommands[$command]));
|
||||
}
|
||||
|
||||
if (version_compare('2.0.0', PluginInterface::PLUGIN_API_VERSION, '>') && $input->hasOption('no-suggest')) {
|
||||
$input->setOption('no-suggest', true);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$composer2) {
|
||||
if ($input->hasParameterOption('--no-progress', true)) {
|
||||
$this->progress = false;
|
||||
}
|
||||
|
||||
if ($input->hasParameterOption('--dry-run', true)) {
|
||||
$this->dryRun = true;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if ($populateRepoCacheDir && isset(self::$repoReadingCommands[$command]) && ('install' !== $command || (file_exists($composerFile = Factory::getComposerFile()) && !file_exists(substr($composerFile, 0, -4).'lock')))) {
|
||||
$this->populateRepoCacheDir();
|
||||
}
|
||||
|
||||
$app->add(new Command\RequireCommand($resolver, \Closure::fromCallable([$this, 'updateComposerLock'])));
|
||||
$app->add(new Command\UpdateCommand($resolver));
|
||||
$app->add(new Command\RemoveCommand($resolver));
|
||||
$app->add(new Command\UnpackCommand($resolver));
|
||||
$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')));
|
||||
if (class_exists(Command\GenerateIdCommand::class)) {
|
||||
$app->add(new Command\GenerateIdCommand(null));
|
||||
}
|
||||
$app->add(new Command\DumpEnvCommand($this->config, $this->options));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public function deactivate(Composer $composer, IOInterface $io)
|
||||
{
|
||||
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()));
|
||||
}
|
||||
|
||||
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 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) {
|
||||
$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 ($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 (!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])) {
|
||||
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();
|
||||
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':
|
||||
$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 (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), '/');
|
||||
|
||||
$synchronizer = new PackageJsonSynchronizer($rootDir, $vendorDir);
|
||||
|
||||
if ($synchronizer->shouldSynchronize()) {
|
||||
$lockData = $this->composer->getLocker()->getLockData();
|
||||
|
||||
if (method_exists($synchronizer, 'addPackageJsonLink') && 'string' === (new \ReflectionParameter([$synchronizer, 'addPackageJsonLink'], 'phpPackage'))->getType()->getName()) {
|
||||
// support for smooth upgrades from older flex versions
|
||||
$lockData['packages'] = array_column($lockData['packages'] ?? [], 'name');
|
||||
$lockData['packages-dev'] = array_column($lockData['packages-dev'] ?? [], 'name');
|
||||
}
|
||||
|
||||
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('');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function uninstall(Composer $composer, IOInterface $io)
|
||||
{
|
||||
$this->lock->delete();
|
||||
}
|
||||
|
||||
public function enableThanksReminder()
|
||||
{
|
||||
if (1 === $this->displayThanksReminder) {
|
||||
$this->displayThanksReminder = !class_exists(Thanks::class, false) && version_compare('1.1.0', PluginInterface::PLUGIN_API_VERSION, '<=') ? 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 = [];
|
||||
}
|
||||
|
||||
public function populateProvidersCacheDir(InstallerEvent $event)
|
||||
{
|
||||
$listed = [];
|
||||
$packages = [];
|
||||
$pool = $event->getPool();
|
||||
$pool = \Closure::bind(function () {
|
||||
foreach ($this->providerRepos as $k => $repo) {
|
||||
$this->providerRepos[$k] = new class($repo) extends BaseComposerRepository {
|
||||
private $repo;
|
||||
|
||||
public function __construct($repo)
|
||||
{
|
||||
$this->repo = $repo;
|
||||
}
|
||||
|
||||
public function whatProvides(Pool $pool, $name, $bypassFilters = false)
|
||||
{
|
||||
$packages = [];
|
||||
foreach ($this->repo->whatProvides($pool, $name, $bypassFilters) as $k => $p) {
|
||||
$packages[$k] = clone $p;
|
||||
}
|
||||
|
||||
return $packages;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return $this;
|
||||
}, clone $pool, $pool)();
|
||||
|
||||
foreach ($event->getRequest()->getJobs() as $job) {
|
||||
if ('install' !== $job['cmd'] || false === strpos($job['packageName'], '/')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$listed[$job['packageName']] = true;
|
||||
$packages[] = [$job['packageName'], $job['constraint']];
|
||||
}
|
||||
|
||||
$loadExtraRepos = !(new \ReflectionMethod(Pool::class, 'match'))->isPublic(); // Detect Composer < 1.7.3
|
||||
$this->rfs->download($packages, function ($packageName, $constraint) use (&$listed, &$packages, $pool, $loadExtraRepos) {
|
||||
foreach ($pool->whatProvides($packageName, $constraint, true) as $package) {
|
||||
$links = $loadExtraRepos ? array_merge($package->getRequires(), $package->getConflicts(), $package->getReplaces()) : $package->getRequires();
|
||||
foreach ($links as $link) {
|
||||
if (isset($listed[$link->getTarget()]) || false === strpos($link->getTarget(), '/')) {
|
||||
continue;
|
||||
}
|
||||
$listed[$link->getTarget()] = true;
|
||||
$packages[] = [$link->getTarget(), $link->getConstraint()];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function populateFilesCacheDir(InstallerEvent $event)
|
||||
{
|
||||
if ($this->cacheDirPopulated || $this->dryRun) {
|
||||
return;
|
||||
}
|
||||
$this->cacheDirPopulated = true;
|
||||
|
||||
$downloads = [];
|
||||
$cacheDir = rtrim($this->config->get('cache-files-dir'), '\/').\DIRECTORY_SEPARATOR;
|
||||
$getCacheKey = function (PackageInterface $package, $processedUrl) {
|
||||
return $this->getCacheKey($package, $processedUrl);
|
||||
};
|
||||
$getCacheKey = \Closure::bind($getCacheKey, new FileDownloader($this->io, $this->config), FileDownloader::class);
|
||||
|
||||
foreach ($event->getOperations() as $op) {
|
||||
if ('install' === $op->getJobType()) {
|
||||
$package = $op->getPackage();
|
||||
} elseif ('update' === $op->getJobType()) {
|
||||
$package = $op->getTargetPackage();
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$fileUrl = $package->getDistUrl()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($package->getDistMirrors()) {
|
||||
$fileUrl = current($package->getDistUrls());
|
||||
}
|
||||
|
||||
if (!preg_match('/^https?:/', $fileUrl) || !$originUrl = parse_url($fileUrl, \PHP_URL_HOST)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (file_exists($file = $cacheDir.$getCacheKey($package, $fileUrl))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@mkdir(\dirname($file), 0775, true);
|
||||
|
||||
if (!is_dir(\dirname($file))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (preg_match('#^https://github\.com/#', $package->getSourceUrl()) && preg_match('#^https://api\.github\.com/repos(/[^/]++/[^/]++/)zipball(.++)$#', $fileUrl, $m)) {
|
||||
$fileUrl = sprintf('https://codeload.github.com%slegacy.zip%s', $m[1], $m[2]);
|
||||
}
|
||||
|
||||
$downloads[] = [$originUrl, $fileUrl, [], $file, false];
|
||||
}
|
||||
|
||||
if (1 < \count($downloads)) {
|
||||
$this->rfs->download($downloads, [$this->rfs, 'get'], false, $this->progress);
|
||||
}
|
||||
}
|
||||
|
||||
public function onFileDownload(PreFileDownloadEvent $event)
|
||||
{
|
||||
if ($event->getRemoteFilesystem() !== $this->rfs) {
|
||||
$event->setRemoteFilesystem($this->rfs->setNextOptions($event->getRemoteFilesystem()->getOptions()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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,
|
||||
];
|
||||
$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), true, IOInterface::VERBOSE);
|
||||
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), true, IOInterface::VERBOSE);
|
||||
|
||||
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])) {
|
||||
if ('metapackage' === $package->getType()) {
|
||||
$metaRecipes[$name] = new Recipe($package, $name, $job, $manifests[$name], $locks[$name] ?? []);
|
||||
} elseif ('symfony/flex' === $name) {
|
||||
$flexRecipe = [$name => new Recipe($package, $name, $job, $manifests[$name], $locks[$name] ?? [])];
|
||||
} else {
|
||||
$recipes[$name] = new Recipe($package, $name, $job, $manifests[$name], $locks[$name] ?? []);
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($manifests[$name])) {
|
||||
$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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_merge($flexRecipe, $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) {
|
||||
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 populateRepoCacheDir()
|
||||
{
|
||||
$repos = [];
|
||||
|
||||
foreach ($this->composer->getPackage()->getRepositories() as $name => $repo) {
|
||||
if (!isset($repo['type']) || 'composer' !== $repo['type'] || !empty($repo['force-lazy-providers'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!preg_match('#^http(s\??)?://#', $repo['url'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$repo = new ComposerRepository($repo, $this->io, $this->config, null, $this->rfs);
|
||||
|
||||
$repos[] = [$repo];
|
||||
}
|
||||
|
||||
$this->rfs->download($repos, function ($repo) {
|
||||
ParallelDownloader::$cacheNext = true;
|
||||
$repo->getProviderNames();
|
||||
});
|
||||
}
|
||||
|
||||
private function updateComposerLock()
|
||||
{
|
||||
$lock = substr(Factory::getComposerFile(), 0, -4).'lock';
|
||||
$composerJson = file_get_contents(Factory::getComposerFile());
|
||||
$lockFile = new JsonFile($lock, null, $this->io);
|
||||
if (version_compare('2.0.0', PluginInterface::PLUGIN_API_VERSION, '>')) {
|
||||
$locker = new Locker($this->io, $lockFile, $this->composer->getRepositoryManager(), $this->composer->getInstallationManager(), $composerJson);
|
||||
} else {
|
||||
$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)
|
||||
{
|
||||
$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 (!$update) {
|
||||
$installer->setUpdateAllowList(['php']);
|
||||
}
|
||||
|
||||
if (method_exists($installer, 'setSkipSuggest')) {
|
||||
$installer->setSkipSuggest(true);
|
||||
}
|
||||
|
||||
$installer->run();
|
||||
|
||||
$this->io->write($this->postInstallOutput);
|
||||
$this->postInstallOutput = [];
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
if (!self::$activated) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$events = [
|
||||
ScriptEvents::POST_CREATE_PROJECT_CMD => 'configureProject',
|
||||
ScriptEvents::POST_INSTALL_CMD => 'install',
|
||||
ScriptEvents::PRE_UPDATE_CMD => 'configureInstaller',
|
||||
ScriptEvents::POST_UPDATE_CMD => 'update',
|
||||
'auto-scripts' => 'executeAutoScripts',
|
||||
];
|
||||
|
||||
if (version_compare('2.0.0', PluginInterface::PLUGIN_API_VERSION, '>')) {
|
||||
$events += [
|
||||
PackageEvents::POST_PACKAGE_INSTALL => 'record',
|
||||
PackageEvents::POST_PACKAGE_UPDATE => [['record'], ['enableThanksReminder']],
|
||||
PackageEvents::POST_PACKAGE_UNINSTALL => 'record',
|
||||
InstallerEvents::PRE_DEPENDENCIES_SOLVING => [['populateProvidersCacheDir', \PHP_INT_MAX]],
|
||||
InstallerEvents::POST_DEPENDENCIES_SOLVING => [['populateFilesCacheDir', \PHP_INT_MAX]],
|
||||
PackageEvents::PRE_PACKAGE_INSTALL => [['populateFilesCacheDir', ~\PHP_INT_MAX]],
|
||||
PackageEvents::PRE_PACKAGE_UPDATE => [['populateFilesCacheDir', ~\PHP_INT_MAX]],
|
||||
PluginEvents::PRE_FILE_DOWNLOAD => 'onFileDownload',
|
||||
];
|
||||
} else {
|
||||
$events += [
|
||||
PackageEvents::POST_PACKAGE_UPDATE => 'enableThanksReminder',
|
||||
InstallerEvents::PRE_OPERATIONS_EXEC => 'recordOperations',
|
||||
PluginEvents::PRE_POOL_CREATE => 'truncatePackages',
|
||||
];
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
204
vendor/symfony/flex/src/GithubApi.php
vendored
Normal file
204
vendor/symfony/flex/src/GithubApi.php
vendored
Normal file
@ -0,0 +1,204 @@
|
||||
<?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);
|
||||
|
||||
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)
|
||||
{
|
||||
if ($this->downloader instanceof HttpDownloader) {
|
||||
$contents = $this->downloader->get($path)->getBody();
|
||||
} else {
|
||||
$contents = $this->downloader->getContents('api.github.com', $path, false);
|
||||
}
|
||||
|
||||
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]]);
|
||||
}
|
||||
}
|
91
vendor/symfony/flex/src/InformationOperation.php
vendored
Normal file
91
vendor/symfony/flex/src/InformationOperation.php
vendored
Normal file
@ -0,0 +1,91 @@
|
||||
<?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}
|
||||
*/
|
||||
public function getOperationType()
|
||||
{
|
||||
return 'information';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
154
vendor/symfony/flex/src/PackageFilter.php
vendored
Normal file
154
vendor/symfony/flex/src/PackageFilter.php
vendored
Normal file
@ -0,0 +1,154 @@
|
||||
<?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]))
|
||||
)) {
|
||||
$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;
|
||||
}
|
||||
}
|
249
vendor/symfony/flex/src/PackageJsonSynchronizer.php
vendored
Normal file
249
vendor/symfony/flex/src/PackageJsonSynchronizer.php
vendored
Normal file
@ -0,0 +1,249 @@
|
||||
<?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;
|
||||
use Composer\Json\JsonManipulator;
|
||||
use Seld\JsonLint\ParsingException;
|
||||
|
||||
/**
|
||||
* Synchronize package.json files detected in installed PHP packages with
|
||||
* the current application.
|
||||
*/
|
||||
class PackageJsonSynchronizer
|
||||
{
|
||||
private $rootDir;
|
||||
private $vendorDir;
|
||||
|
||||
public function __construct(string $rootDir, string $vendorDir = 'vendor')
|
||||
{
|
||||
$this->rootDir = $rootDir;
|
||||
$this->vendorDir = $vendorDir;
|
||||
}
|
||||
|
||||
public function shouldSynchronize(): bool
|
||||
{
|
||||
return $this->rootDir && file_exists($this->rootDir.'/package.json');
|
||||
}
|
||||
|
||||
public function synchronize(array $phpPackages): bool
|
||||
{
|
||||
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 = [];
|
||||
|
||||
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'],
|
||||
];
|
||||
}
|
||||
|
||||
foreach ($this->resolvePackageDependencies($phpPackage) as $dependency => $constraint) {
|
||||
$dependencies[$dependency][$phpPackage['name']] = $constraint;
|
||||
}
|
||||
}
|
||||
|
||||
$didChangePackageJson = $this->registerDependencies($dependencies) || $didChangePackageJson;
|
||||
|
||||
// Register controllers and entrypoints in controllers.json
|
||||
$this->registerWebpackResources($phpPackages);
|
||||
|
||||
return $didChangePackageJson;
|
||||
}
|
||||
|
||||
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 resolvePackageDependencies($phpPackage): array
|
||||
{
|
||||
$dependencies = [];
|
||||
|
||||
if (!$packageJson = $this->resolvePackageJson($phpPackage)) {
|
||||
return $dependencies;
|
||||
}
|
||||
|
||||
$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 registerDependencies(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]) {
|
||||
$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 registerWebpackResources(array $phpPackages)
|
||||
{
|
||||
if (!file_exists($controllersJsonPath = $this->rootDir.'/assets/controllers.json')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$previousControllersJson = (new JsonFile($controllersJsonPath))->read();
|
||||
$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;
|
||||
}
|
||||
}
|
145
vendor/symfony/flex/src/PackageResolver.php
vendored
Normal file
145
vendor/symfony/flex/src/PackageResolver.php
vendored
Normal file
@ -0,0 +1,145 @@
|
||||
<?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);
|
||||
$version = substr($argument, $pos + 1);
|
||||
$packages[] = $package.':'.$version;
|
||||
} else {
|
||||
$packages[] = $this->resolvePackageName($argument, $i);
|
||||
}
|
||||
}
|
||||
|
||||
// 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): string
|
||||
{
|
||||
if (false !== strpos($argument, '/') || preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $argument) || preg_match('{(?<=[a-z0-9_/-])\*|\*(?=[a-z0-9_/-])}i', $argument) || \in_array($argument, ['lock', 'mirrors', 'nothing', ''])) {
|
||||
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);
|
||||
}
|
||||
}
|
287
vendor/symfony/flex/src/ParallelDownloader.php
vendored
Normal file
287
vendor/symfony/flex/src/ParallelDownloader.php
vendored
Normal file
@ -0,0 +1,287 @@
|
||||
<?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\Config;
|
||||
use Composer\Downloader\TransportException;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
|
||||
/**
|
||||
* Speedup Composer by downloading packages in parallel.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class ParallelDownloader extends RemoteFilesystem
|
||||
{
|
||||
private $io;
|
||||
private $downloader;
|
||||
private $quiet = true;
|
||||
private $progress = true;
|
||||
private $nextCallback;
|
||||
private $downloadCount;
|
||||
private $nextOptions = [];
|
||||
private $sharedState;
|
||||
private $fileName;
|
||||
private $lastHeaders;
|
||||
|
||||
public static $cacheNext = false;
|
||||
protected static $cache = [];
|
||||
|
||||
public function __construct(IOInterface $io, Config $config, array $options = [], $disableTls = false)
|
||||
{
|
||||
$this->io = $io;
|
||||
if (!method_exists(parent::class, 'getRemoteContents')) {
|
||||
$this->io->writeError('Composer >=1.7 not found, downloads will happen in sequence', true, IOInterface::DEBUG);
|
||||
} elseif (!\extension_loaded('curl')) {
|
||||
$this->io->writeError('ext-curl not found, downloads will happen in sequence', true, IOInterface::DEBUG);
|
||||
} else {
|
||||
$this->downloader = new CurlDownloader();
|
||||
}
|
||||
parent::__construct($io, $config, $options, $disableTls);
|
||||
}
|
||||
|
||||
public function download(array &$nextArgs, callable $nextCallback, bool $quiet = true, bool $progress = true)
|
||||
{
|
||||
$previousState = [$this->quiet, $this->progress, $this->downloadCount, $this->nextCallback, $this->sharedState];
|
||||
$this->quiet = $quiet;
|
||||
$this->progress = $progress;
|
||||
$this->downloadCount = \count($nextArgs);
|
||||
$this->nextCallback = $nextCallback;
|
||||
$this->sharedState = (object) [
|
||||
'bytesMaxCount' => 0,
|
||||
'bytesMax' => 0,
|
||||
'bytesTransferred' => 0,
|
||||
'nextArgs' => &$nextArgs,
|
||||
'nestingLevel' => 0,
|
||||
'maxNestingReached' => false,
|
||||
'lastProgress' => 0,
|
||||
'lastUpdate' => microtime(true),
|
||||
];
|
||||
|
||||
if (!$this->quiet) {
|
||||
if (!$this->downloader && method_exists(parent::class, 'getRemoteContents')) {
|
||||
$this->io->writeError('<warning>Enable the "cURL" PHP extension for faster downloads</>');
|
||||
}
|
||||
|
||||
$note = '';
|
||||
if ($this->io->isDecorated()) {
|
||||
$note = '\\' === \DIRECTORY_SEPARATOR ? '' : (false !== stripos(\PHP_OS, 'darwin') ? '🎵' : '🎶');
|
||||
$note .= $this->downloader ? ('\\' !== \DIRECTORY_SEPARATOR ? ' 💨' : '') : '';
|
||||
}
|
||||
|
||||
$this->io->writeError('');
|
||||
$this->io->writeError(sprintf('<info>Prefetching %d packages</> %s', $this->downloadCount, $note));
|
||||
$this->io->writeError(' - Downloading', false);
|
||||
if ($this->progress) {
|
||||
$this->io->writeError(' (<comment>0%</>)', false);
|
||||
}
|
||||
}
|
||||
try {
|
||||
$this->getNext();
|
||||
if ($this->quiet) {
|
||||
// no-op
|
||||
} elseif ($this->progress) {
|
||||
$this->io->overwriteError(' (<comment>100%</>)');
|
||||
} else {
|
||||
$this->io->writeError(' (<comment>100%</>)');
|
||||
}
|
||||
} finally {
|
||||
if (!$this->quiet) {
|
||||
$this->io->writeError('');
|
||||
}
|
||||
list($this->quiet, $this->progress, $this->downloadCount, $this->nextCallback, $this->sharedState) = $previousState;
|
||||
}
|
||||
}
|
||||
|
||||
public function getOptions()
|
||||
{
|
||||
$options = array_replace_recursive(parent::getOptions(), $this->nextOptions);
|
||||
$this->nextOptions = [];
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
public function setNextOptions(array $options)
|
||||
{
|
||||
$this->nextOptions = parent::getOptions() !== $options ? $options : [];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getLastHeaders()
|
||||
{
|
||||
return $this->lastHeaders ?? parent::getLastHeaders();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function copy($originUrl, $fileUrl, $fileName, $progress = true, $options = [])
|
||||
{
|
||||
$options = array_replace_recursive($this->nextOptions, $options);
|
||||
$this->nextOptions = [];
|
||||
$rfs = clone $this;
|
||||
$rfs->fileName = $fileName;
|
||||
$rfs->progress = $this->progress && $progress;
|
||||
|
||||
try {
|
||||
return $rfs->get($originUrl, $fileUrl, $options, $fileName, $rfs->progress);
|
||||
} finally {
|
||||
$rfs->lastHeaders = null;
|
||||
$this->lastHeaders = $rfs->getLastHeaders();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getContents($originUrl, $fileUrl, $progress = true, $options = [])
|
||||
{
|
||||
return $this->copy($originUrl, $fileUrl, null, $progress, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function callbackGet($notificationCode, $severity, $message, $messageCode, $bytesTransferred, $bytesMax, $nativeDownload = true)
|
||||
{
|
||||
if (!$nativeDownload && \STREAM_NOTIFY_SEVERITY_ERR === $severity) {
|
||||
throw new TransportException($message, $messageCode);
|
||||
}
|
||||
|
||||
parent::callbackGet($notificationCode, $severity, $message, $messageCode, $bytesTransferred, $bytesMax);
|
||||
|
||||
if (!$state = $this->sharedState) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (\STREAM_NOTIFY_FILE_SIZE_IS === $notificationCode) {
|
||||
++$state->bytesMaxCount;
|
||||
$state->bytesMax += $bytesMax;
|
||||
}
|
||||
|
||||
if (!$bytesMax || \STREAM_NOTIFY_PROGRESS !== $notificationCode) {
|
||||
if ($state->nextArgs && !$nativeDownload) {
|
||||
$this->getNext();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (0 < $state->bytesMax) {
|
||||
$progress = $state->bytesMaxCount / $this->downloadCount;
|
||||
$progress *= 100 * ($state->bytesTransferred + $bytesTransferred) / $state->bytesMax;
|
||||
} else {
|
||||
$progress = 0;
|
||||
}
|
||||
|
||||
if ($bytesTransferred === $bytesMax) {
|
||||
$state->bytesTransferred += $bytesMax;
|
||||
}
|
||||
|
||||
if (null !== $state->nextArgs && !$this->quiet && $this->progress && 1 <= $progress - $state->lastProgress) {
|
||||
$progressTime = microtime(true);
|
||||
|
||||
if (5 <= $progress - $state->lastProgress || 1 <= $progressTime - $state->lastUpdate) {
|
||||
$state->lastProgress = $progress;
|
||||
$this->io->overwriteError(sprintf(' (<comment>%d%%</>)', $progress), false);
|
||||
$state->lastUpdate = microtime(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$nativeDownload || !$state->nextArgs || $bytesTransferred === $bytesMax || $state->maxNestingReached) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (5 < $state->nestingLevel) {
|
||||
$state->maxNestingReached = true;
|
||||
} else {
|
||||
$this->getNext();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getRemoteContents($originUrl, $fileUrl, $context, array &$responseHeaders = null, $maxFileSize = null)
|
||||
{
|
||||
if (isset(self::$cache[$fileUrl])) {
|
||||
self::$cacheNext = false;
|
||||
|
||||
$result = self::$cache[$fileUrl];
|
||||
|
||||
if (3 < \func_num_args()) {
|
||||
list($responseHeaders, $result) = $result;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
if (self::$cacheNext) {
|
||||
self::$cacheNext = false;
|
||||
|
||||
if (3 < \func_num_args()) {
|
||||
$result = $this->getRemoteContents($originUrl, $fileUrl, $context, $responseHeaders, $maxFileSize);
|
||||
self::$cache[$fileUrl] = [$responseHeaders, $result];
|
||||
} else {
|
||||
$result = $this->getRemoteContents($originUrl, $fileUrl, $context);
|
||||
self::$cache[$fileUrl] = $result;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
if (!$this->downloader || !preg_match('/^https?:/', $fileUrl)) {
|
||||
return parent::getRemoteContents($originUrl, $fileUrl, $context, $responseHeaders, $maxFileSize);
|
||||
}
|
||||
|
||||
try {
|
||||
$result = $this->downloader->get($originUrl, $fileUrl, $context, $this->fileName);
|
||||
|
||||
if (3 < \func_num_args()) {
|
||||
list($responseHeaders, $result) = $result;
|
||||
}
|
||||
|
||||
return $result;
|
||||
} catch (TransportException $e) {
|
||||
$this->io->writeError('Retrying download: '.$e->getMessage(), true, IOInterface::DEBUG);
|
||||
|
||||
return parent::getRemoteContents($originUrl, $fileUrl, $context, $responseHeaders, $maxFileSize);
|
||||
} catch (\Throwable $e) {
|
||||
$responseHeaders = [];
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
private function getNext()
|
||||
{
|
||||
$state = $this->sharedState;
|
||||
++$state->nestingLevel;
|
||||
|
||||
try {
|
||||
while ($state->nextArgs && (!$state->maxNestingReached || 1 === $state->nestingLevel)) {
|
||||
try {
|
||||
$state->maxNestingReached = false;
|
||||
($this->nextCallback)(...array_shift($state->nextArgs));
|
||||
} catch (TransportException $e) {
|
||||
$this->io->writeError('Skipping download: '.$e->getMessage(), true, IOInterface::DEBUG);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
--$state->nestingLevel;
|
||||
}
|
||||
}
|
||||
}
|
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;
|
||||
}
|
||||
}
|
87
vendor/symfony/flex/src/Response.php
vendored
Normal file
87
vendor/symfony/flex/src/Response.php
vendored
Normal file
@ -0,0 +1,87 @@
|
||||
<?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;
|
||||
}
|
||||
|
||||
#[\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;
|
||||
}
|
||||
}
|
135
vendor/symfony/flex/src/ScriptExecutor.php
vendored
Normal file
135
vendor/symfony/flex/src/ScriptExecutor.php
vendored
Normal file
@ -0,0 +1,135 @@
|
||||
<?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\EmptyConstraint;
|
||||
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)
|
||||
{
|
||||
$parsedCmd = $this->options->expandTargetDir($cmd);
|
||||
if (null === $expandedCmd = $this->expandCmd($type, $parsedCmd)) {
|
||||
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)
|
||||
{
|
||||
switch ($type) {
|
||||
case 'symfony-cmd':
|
||||
return $this->expandSymfonyCmd($cmd);
|
||||
case 'php-script':
|
||||
return $this->expandPhpScript($cmd);
|
||||
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)
|
||||
{
|
||||
$repo = $this->composer->getRepositoryManager()->getLocalRepository();
|
||||
if (!$repo->findPackage('symfony/console', class_exists(MatchAllConstraint::class) ? new MatchAllConstraint() : new EmptyConstraint())) {
|
||||
$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);
|
||||
}
|
||||
|
||||
private function expandPhpScript(string $cmd): 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;
|
||||
}
|
||||
|
||||
$phpArgs = implode(' ', array_map([ProcessExecutor::class, 'escape'], $arguments));
|
||||
|
||||
return ProcessExecutor::escape($php).($phpArgs ? ' '.$phpArgs : '').' '.$cmd;
|
||||
}
|
||||
}
|
112
vendor/symfony/flex/src/SymfonyBundle.php
vendored
Normal file
112
vendor/symfony/flex/src/SymfonyBundle.php
vendored
Normal file
@ -0,0 +1,112 @@
|
||||
<?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
|
||||
return false !== strpos(file_get_contents($classPath), 'Symfony\Component\HttpKernel\Bundle\Bundle');
|
||||
}
|
||||
}
|
44
vendor/symfony/flex/src/TruncatedComposerRepository.php
vendored
Normal file
44
vendor/symfony/flex/src/TruncatedComposerRepository.php
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
<?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\Config;
|
||||
use Composer\EventDispatcher\EventDispatcher;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Package\RootPackageInterface;
|
||||
use Composer\Repository\ComposerRepository as BaseComposerRepository;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class TruncatedComposerRepository extends BaseComposerRepository
|
||||
{
|
||||
public function __construct(array $repoConfig, IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, RemoteFilesystem $rfs = null)
|
||||
{
|
||||
parent::__construct($repoConfig, $io, $config, $eventDispatcher, $rfs);
|
||||
|
||||
$this->cache = new Cache($io, $config->get('cache-repo-dir').'/'.preg_replace('{[^a-z0-9.]}i', '-', $this->url), 'a-z0-9.$');
|
||||
}
|
||||
|
||||
public function setSymfonyRequire(string $symfonyRequire, RootPackageInterface $rootPackage, Downloader $downloader, IOInterface $io)
|
||||
{
|
||||
$this->cache->setSymfonyRequire($symfonyRequire, $rootPackage, $downloader, $io);
|
||||
}
|
||||
|
||||
protected function fetchFile($filename, $cacheKey = null, $sha256 = null, $storeLastModifiedTime = false)
|
||||
{
|
||||
$data = parent::fetchFile($filename, $cacheKey, $sha256, $storeLastModifiedTime);
|
||||
|
||||
return \is_array($data) ? $this->cache->removeLegacyTags($data) : $data;
|
||||
}
|
||||
}
|
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'];
|
||||
}
|
||||
}
|
215
vendor/symfony/flex/src/Unpacker.php
vendored
Normal file
215
vendor/symfony/flex/src/Unpacker.php
vendored
Normal file
@ -0,0 +1,215 @@
|
||||
<?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\DependencyResolver\Pool;
|
||||
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\Plugin\PluginInterface;
|
||||
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 = class_exists(RepositorySet::class) ? RepositorySet::class : Pool::class;
|
||||
$pool = new $pool($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/
|
||||
if (version_compare('2.0.0', PluginInterface::PLUGIN_API_VERSION, '>')) {
|
||||
$locker = new Locker($io, $lockFile, $this->composer->getRepositoryManager(), $this->composer->getInstallationManager(), $jsonContent);
|
||||
} else {
|
||||
$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;
|
||||
}
|
||||
}
|
45
vendor/symfony/flex/src/Update/RecipePatch.php
vendored
Normal file
45
vendor/symfony/flex/src/Update/RecipePatch.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 RecipePatch
|
||||
{
|
||||
private $patch;
|
||||
private $blobs;
|
||||
private $removedPatches;
|
||||
|
||||
public function __construct(string $patch, array $blobs, array $removedPatches = [])
|
||||
{
|
||||
$this->patch = $patch;
|
||||
$this->blobs = $blobs;
|
||||
$this->removedPatches = $removedPatches;
|
||||
}
|
||||
|
||||
public function getPatch(): string
|
||||
{
|
||||
return $this->patch;
|
||||
}
|
||||
|
||||
public function getBlobs(): array
|
||||
{
|
||||
return $this->blobs;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
226
vendor/symfony/flex/src/Update/RecipePatcher.php
vendored
Normal file
226
vendor/symfony/flex/src/Update/RecipePatcher.php
vendored
Normal file
@ -0,0 +1,226 @@
|
||||
<?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
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function generatePatch(array $originalFiles, array $newFiles): RecipePatch
|
||||
{
|
||||
// null implies "file does not exist"
|
||||
$originalFiles = array_filter($originalFiles, function ($file) {
|
||||
return null !== $file;
|
||||
});
|
||||
$newFiles = array_filter($newFiles, function ($file) {
|
||||
return null !== $file;
|
||||
});
|
||||
|
||||
// find removed files and add them so they will be deleted
|
||||
foreach ($originalFiles as $file => $contents) {
|
||||
if (!isset($newFiles[$file])) {
|
||||
$newFiles[$file] = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
$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('git diff --cached', $tmpPath);
|
||||
$removedPatches = [];
|
||||
$patchString = DiffHelper::removeFilesFromPatch($patchString, $deletedModifiedFiles, $removedPatches);
|
||||
|
||||
return new RecipePatch(
|
||||
$patchString,
|
||||
$blobs,
|
||||
$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->rootDir.'/'.$this->getBlobPath($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($originalFilesRoot.'/'.$this->getBlobPath($hash));
|
||||
}
|
||||
|
||||
return $addedBlobs;
|
||||
}
|
||||
|
||||
private function getBlobPath(string $hash): string
|
||||
{
|
||||
$hashStart = substr($hash, 0, 2);
|
||||
$hashEnd = substr($hash, 2);
|
||||
|
||||
return '.git/objects/'.$hashStart.'/'.$hashEnd;
|
||||
}
|
||||
}
|
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