initial commit
This commit is contained in:
19
vendor/symfony/flex/LICENSE
vendored
Normal file
19
vendor/symfony/flex/LICENSE
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2016-present Fabien Potencier
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
10
vendor/symfony/flex/README.md
vendored
Normal file
10
vendor/symfony/flex/README.md
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
<p align="center"><a href="https://symfony.com" target="_blank">
|
||||
<img src="https://symfony.com/logos/symfony_black_02.svg">
|
||||
</a></p>
|
||||
|
||||
[Symfony Flex][1] helps developers create [Symfony][2] applications, from the most
|
||||
simple micro-style projects to the more complex ones with dozens of
|
||||
dependencies.
|
||||
|
||||
[1]: https://symfony.com/doc/current/setup/flex.html
|
||||
[2]: https://symfony.com
|
||||
35
vendor/symfony/flex/composer.json
vendored
Normal file
35
vendor/symfony/flex/composer.json
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "symfony/flex",
|
||||
"type": "composer-plugin",
|
||||
"description": "Composer plugin for Symfony",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien.potencier@gmail.com"
|
||||
}
|
||||
],
|
||||
"minimum-stability": "dev",
|
||||
"require": {
|
||||
"php": ">=8.0",
|
||||
"composer-plugin-api": "^2.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"composer/composer": "^2.1",
|
||||
"symfony/dotenv": "^5.4|^6.0",
|
||||
"symfony/filesystem": "^5.4|^6.0",
|
||||
"symfony/phpunit-bridge": "^5.4|^6.0",
|
||||
"symfony/process": "^5.4|^6.0"
|
||||
},
|
||||
"conflict": {
|
||||
"composer/semver": "<1.7.2"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Flex\\": "src"
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"class": "Symfony\\Flex\\Flex"
|
||||
}
|
||||
}
|
||||
147
vendor/symfony/flex/src/Command/DumpEnvCommand.php
vendored
Normal file
147
vendor/symfony/flex/src/Command/DumpEnvCommand.php
vendored
Normal file
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex\Command;
|
||||
|
||||
use Composer\Command\BaseCommand;
|
||||
use Composer\Config;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Dotenv\Dotenv;
|
||||
use Symfony\Flex\Options;
|
||||
|
||||
class DumpEnvCommand extends BaseCommand
|
||||
{
|
||||
private $config;
|
||||
private $options;
|
||||
|
||||
public function __construct(Config $config, Options $options)
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->options = $options;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('symfony:dump-env')
|
||||
->setAliases(['dump-env'])
|
||||
->setDescription('Compiles .env files to .env.local.php.')
|
||||
->setDefinition([
|
||||
new InputArgument('env', InputArgument::OPTIONAL, 'The application environment to dump .env files for - e.g. "prod".'),
|
||||
])
|
||||
->addOption('empty', null, InputOption::VALUE_NONE, 'Ignore the content of .env files')
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$runtime = $this->options->get('runtime') ?? [];
|
||||
$envKey = $runtime['env_var_name'] ?? 'APP_ENV';
|
||||
|
||||
if ($env = $input->getArgument('env') ?? $runtime['env'] ?? null) {
|
||||
$_SERVER[$envKey] = $env;
|
||||
}
|
||||
|
||||
$path = $this->options->get('root-dir').'/'.($runtime['dotenv_path'] ?? '.env');
|
||||
|
||||
if (!$env || !$input->getOption('empty')) {
|
||||
$vars = $this->loadEnv($path, $env, $runtime);
|
||||
$env = $vars[$envKey];
|
||||
}
|
||||
|
||||
if ($input->getOption('empty')) {
|
||||
$vars = [$envKey => $env];
|
||||
}
|
||||
|
||||
$vars = var_export($vars, true);
|
||||
$vars = <<<EOF
|
||||
<?php
|
||||
|
||||
// This file was generated by running "composer dump-env $env"
|
||||
|
||||
return $vars;
|
||||
|
||||
EOF;
|
||||
file_put_contents($path.'.local.php', $vars, \LOCK_EX);
|
||||
|
||||
$this->getIO()->writeError('Successfully dumped .env files in <info>.env.local.php</>');
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function loadEnv(string $path, ?string $env, array $runtime): array
|
||||
{
|
||||
if (!file_exists($autoloadFile = $this->config->get('vendor-dir').'/autoload.php')) {
|
||||
throw new \RuntimeException(\sprintf('Please run "composer install" before running this command: "%s" not found.', $autoloadFile));
|
||||
}
|
||||
|
||||
require $autoloadFile;
|
||||
|
||||
if (!class_exists(Dotenv::class)) {
|
||||
throw new \RuntimeException('Please run "composer require symfony/dotenv" to load the ".env" files configuring the application.');
|
||||
}
|
||||
|
||||
$envKey = $runtime['env_var_name'] ?? 'APP_ENV';
|
||||
$globalsBackup = [$_SERVER, $_ENV];
|
||||
unset($_SERVER[$envKey]);
|
||||
$_ENV = [$envKey => $env];
|
||||
$_SERVER['SYMFONY_DOTENV_VARS'] = implode(',', array_keys($_SERVER));
|
||||
putenv('SYMFONY_DOTENV_VARS='.$_SERVER['SYMFONY_DOTENV_VARS']);
|
||||
|
||||
try {
|
||||
if (method_exists(Dotenv::class, 'usePutenv')) {
|
||||
$dotenv = new Dotenv();
|
||||
} else {
|
||||
$dotenv = new Dotenv(false);
|
||||
}
|
||||
|
||||
if (!$env && file_exists($p = "$path.local")) {
|
||||
$env = $_ENV[$envKey] = $dotenv->parse(file_get_contents($p), $p)[$envKey] ?? null;
|
||||
}
|
||||
|
||||
if (!$env) {
|
||||
throw new \RuntimeException(\sprintf('Please provide the name of the environment either by passing it as command line argument or by defining the "%s" variable in the ".env.local" file.', $envKey));
|
||||
}
|
||||
|
||||
$testEnvs = $runtime['test_envs'] ?? ['test'];
|
||||
|
||||
if (method_exists($dotenv, 'loadEnv')) {
|
||||
$dotenv->loadEnv($path, $envKey, 'dev', $testEnvs);
|
||||
} else {
|
||||
// fallback code in case your Dotenv component is not 4.2 or higher (when loadEnv() was added)
|
||||
$dotenv->load(file_exists($path) || !file_exists($p = "$path.dist") ? $path : $p);
|
||||
|
||||
if (!\in_array($env, $testEnvs, true) && file_exists($p = "$path.local")) {
|
||||
$dotenv->load($p);
|
||||
}
|
||||
|
||||
if (file_exists($p = "$path.$env")) {
|
||||
$dotenv->load($p);
|
||||
}
|
||||
|
||||
if (file_exists($p = "$path.$env.local")) {
|
||||
$dotenv->load($p);
|
||||
}
|
||||
}
|
||||
|
||||
unset($_ENV['SYMFONY_DOTENV_VARS']);
|
||||
$env = $_ENV;
|
||||
} finally {
|
||||
list($_SERVER, $_ENV) = $globalsBackup;
|
||||
}
|
||||
|
||||
return $env;
|
||||
}
|
||||
}
|
||||
182
vendor/symfony/flex/src/Command/InstallRecipesCommand.php
vendored
Normal file
182
vendor/symfony/flex/src/Command/InstallRecipesCommand.php
vendored
Normal file
@@ -0,0 +1,182 @@
|
||||
<?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)')
|
||||
->addOption('yes', null, InputOption::VALUE_NONE, "Answer prompt questions with 'yes' for all questions.")
|
||||
;
|
||||
}
|
||||
|
||||
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') || file_exists($this->rootDir.'/phpunit.dist.xml')) {
|
||||
touch($dotenvPath.'.test');
|
||||
}
|
||||
}
|
||||
|
||||
$this->flex->update(new UpdateEvent($force, (bool) $input->getOption('reset'), (bool) $input->getOption('yes')), $operations);
|
||||
|
||||
if ($force) {
|
||||
$output = [
|
||||
'',
|
||||
'<bg=blue;fg=white> </>',
|
||||
'<bg=blue;fg=white> Files have been reset to the latest version of the recipe. </>',
|
||||
'<bg=blue;fg=white> </>',
|
||||
'',
|
||||
' * Use <comment>git diff</> to inspect the changes.',
|
||||
'',
|
||||
' Not all of the changes will be relevant to your app: you now',
|
||||
' need to selectively add or revert them using e.g. a combination',
|
||||
' of <comment>git add -p</> and <comment>git checkout -p</>',
|
||||
'',
|
||||
];
|
||||
|
||||
if ($createEnvLocal) {
|
||||
$output[] = ' Dotenv files have been renamed: .env -> .env.local and .env.dist -> .env';
|
||||
$output[] = ' See https://symfony.com/doc/current/configuration/dot-env-changes.html';
|
||||
$output[] = '';
|
||||
}
|
||||
|
||||
$output[] = ' * Use <comment>git checkout .</> to revert the changes.';
|
||||
$output[] = '';
|
||||
|
||||
if ($createEnvLocal) {
|
||||
$root = '.' !== $this->rootDir ? $this->rootDir.'/' : '';
|
||||
$output[] = ' To revert the changes made to .env files, run';
|
||||
$output[] = \sprintf(' <comment>git mv %s %s</> && <comment>%s %s %1$s</>', ProcessExecutor::escape($root.$dotenvFile), ProcessExecutor::escape($root.$dotenvFile.'.dist'), $win ? 'rename' : 'mv', ProcessExecutor::escape($root.$dotenvFile.'.local'));
|
||||
$output[] = '';
|
||||
}
|
||||
|
||||
$output[] = ' New (untracked) files can be inspected using <comment>git clean --dry-run</>';
|
||||
$output[] = ' Add the new files you want to keep using <comment>git add</>';
|
||||
$output[] = ' then delete the rest using <comment>git clean --force</>';
|
||||
$output[] = '';
|
||||
|
||||
$io->write($output);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
344
vendor/symfony/flex/src/Command/RecipesCommand.php
vendored
Normal file
344
vendor/symfony/flex/src/Command/RecipesCommand.php
vendored
Normal file
@@ -0,0 +1,344 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex\Command;
|
||||
|
||||
use Composer\Command\BaseCommand;
|
||||
use Composer\Downloader\TransportException;
|
||||
use Composer\Package\Package;
|
||||
use Composer\Util\HttpDownloader;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Flex\GithubApi;
|
||||
use Symfony\Flex\InformationOperation;
|
||||
use Symfony\Flex\Lock;
|
||||
use Symfony\Flex\Recipe;
|
||||
|
||||
/**
|
||||
* @author Maxime Hélias <maximehelias16@gmail.com>
|
||||
*/
|
||||
class RecipesCommand extends BaseCommand
|
||||
{
|
||||
/** @var \Symfony\Flex\Flex */
|
||||
private $flex;
|
||||
|
||||
private Lock $symfonyLock;
|
||||
private GithubApi $githubApi;
|
||||
|
||||
public function __construct(/* cannot be type-hinted */ $flex, Lock $symfonyLock, HttpDownloader $downloader)
|
||||
{
|
||||
$this->flex = $flex;
|
||||
$this->symfonyLock = $symfonyLock;
|
||||
$this->githubApi = new GithubApi($downloader);
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('symfony:recipes')
|
||||
->setAliases(['recipes'])
|
||||
->setDescription('Shows information about all available recipes.')
|
||||
->setDefinition([
|
||||
new InputArgument('package', InputArgument::OPTIONAL, 'Package to inspect, if not provided all packages are.'),
|
||||
])
|
||||
->addOption('outdated', 'o', InputOption::VALUE_NONE, 'Show only recipes that are outdated')
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$installedRepo = $this->getComposer()->getRepositoryManager()->getLocalRepository();
|
||||
|
||||
// Inspect one or all packages
|
||||
$package = $input->getArgument('package');
|
||||
if (null !== $package) {
|
||||
$packages = [strtolower($package)];
|
||||
} else {
|
||||
$locker = $this->getComposer()->getLocker();
|
||||
$lockData = $locker->getLockData();
|
||||
|
||||
// Merge all packages installed
|
||||
$packages = array_column(array_merge($lockData['packages'], $lockData['packages-dev']), 'name');
|
||||
$packages = array_unique(array_merge($packages, array_keys($this->symfonyLock->all())));
|
||||
}
|
||||
|
||||
$operations = [];
|
||||
foreach ($packages as $name) {
|
||||
$pkg = $installedRepo->findPackage($name, '*');
|
||||
|
||||
if (!$pkg && $this->symfonyLock->has($name)) {
|
||||
$pkgVersion = $this->symfonyLock->get($name)['version'];
|
||||
$pkg = new Package($name, $pkgVersion, $pkgVersion);
|
||||
} elseif (!$pkg) {
|
||||
$this->getIO()->writeError(\sprintf('<error>Package %s is not installed</error>', $name));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$operations[] = new InformationOperation($pkg);
|
||||
}
|
||||
|
||||
$recipes = $this->flex->fetchRecipes($operations, false);
|
||||
ksort($recipes);
|
||||
|
||||
$nbRecipe = \count($recipes);
|
||||
if ($nbRecipe <= 0) {
|
||||
$this->getIO()->writeError('<error>No recipe found</error>');
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Display the information about a specific recipe
|
||||
if (1 === $nbRecipe) {
|
||||
$this->displayPackageInformation(current($recipes));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
$outdated = $input->getOption('outdated');
|
||||
|
||||
$write = [];
|
||||
$hasOutdatedRecipes = false;
|
||||
foreach ($recipes as $name => $recipe) {
|
||||
$lockRef = $this->symfonyLock->get($name)['recipe']['ref'] ?? null;
|
||||
|
||||
$additional = null;
|
||||
if (null === $lockRef && null !== $recipe->getRef()) {
|
||||
$additional = '<comment>(recipe not installed)</comment>';
|
||||
} elseif ($recipe->getRef() !== $lockRef && !$recipe->isAuto()) {
|
||||
$additional = '<comment>(update available)</comment>';
|
||||
}
|
||||
|
||||
if ($outdated && null === $additional) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$hasOutdatedRecipes = true;
|
||||
$write[] = \sprintf(' * %s %s', $name, $additional);
|
||||
}
|
||||
|
||||
// Nothing to display
|
||||
if (!$hasOutdatedRecipes) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$this->getIO()->write(array_merge([
|
||||
'',
|
||||
'<bg=blue;fg=white> </>',
|
||||
\sprintf('<bg=blue;fg=white> %s recipes. </>', $outdated ? ' Outdated' : 'Available'),
|
||||
'<bg=blue;fg=white> </>',
|
||||
'',
|
||||
], $write, [
|
||||
'',
|
||||
'Run:',
|
||||
' * <info>composer recipes vendor/package</info> to see details about a recipe.',
|
||||
' * <info>composer recipes:update vendor/package</info> to update that recipe.',
|
||||
'',
|
||||
]));
|
||||
|
||||
if ($outdated) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function displayPackageInformation(Recipe $recipe)
|
||||
{
|
||||
$io = $this->getIO();
|
||||
$recipeLock = $this->symfonyLock->get($recipe->getName());
|
||||
|
||||
$lockRef = $recipeLock['recipe']['ref'] ?? null;
|
||||
$lockRepo = $recipeLock['recipe']['repo'] ?? null;
|
||||
$lockFiles = $recipeLock['files'] ?? null;
|
||||
$lockBranch = $recipeLock['recipe']['branch'] ?? null;
|
||||
$lockVersion = $recipeLock['recipe']['version'] ?? $recipeLock['version'] ?? null;
|
||||
|
||||
if ('master' === $lockBranch && \in_array($lockRepo, ['github.com/symfony/recipes', 'github.com/symfony/recipes-contrib'])) {
|
||||
$lockBranch = 'main';
|
||||
}
|
||||
|
||||
$status = '<comment>up to date</comment>';
|
||||
if ($recipe->isAuto()) {
|
||||
$status = '<comment>auto-generated recipe</comment>';
|
||||
} elseif (null === $lockRef && null !== $recipe->getRef()) {
|
||||
$status = '<comment>recipe not installed</comment>';
|
||||
} elseif ($recipe->getRef() !== $lockRef) {
|
||||
$status = '<comment>update available</comment>';
|
||||
}
|
||||
|
||||
$gitSha = null;
|
||||
$commitDate = null;
|
||||
if (null !== $lockRef && null !== $lockRepo) {
|
||||
try {
|
||||
$recipeCommitData = $this->githubApi->findRecipeCommitDataFromTreeRef(
|
||||
$recipe->getName(),
|
||||
$lockRepo,
|
||||
$lockBranch ?? '',
|
||||
$lockVersion,
|
||||
$lockRef
|
||||
);
|
||||
$gitSha = $recipeCommitData ? $recipeCommitData['commit'] : null;
|
||||
$commitDate = $recipeCommitData ? $recipeCommitData['date'] : null;
|
||||
} catch (TransportException $exception) {
|
||||
$io->writeError('Error downloading exact git sha for installed recipe.');
|
||||
}
|
||||
}
|
||||
|
||||
$io->write('<info>name</info> : '.$recipe->getName());
|
||||
$io->write('<info>version</info> : '.($lockVersion ?? 'n/a'));
|
||||
$io->write('<info>status</info> : '.$status);
|
||||
if (!$recipe->isAuto() && null !== $lockVersion) {
|
||||
$recipeUrl = \sprintf(
|
||||
'https://%s/tree/%s/%s/%s',
|
||||
$lockRepo,
|
||||
// if something fails, default to the branch as the closest "sha"
|
||||
$gitSha ?? $lockBranch,
|
||||
$recipe->getName(),
|
||||
$lockVersion
|
||||
);
|
||||
|
||||
$io->write('<info>installed recipe</info> : '.$recipeUrl);
|
||||
}
|
||||
|
||||
if ($lockRef !== $recipe->getRef()) {
|
||||
$io->write('<info>latest recipe</info> : '.$recipe->getURL());
|
||||
}
|
||||
|
||||
if ($lockRef !== $recipe->getRef() && null !== $lockVersion) {
|
||||
$historyUrl = \sprintf(
|
||||
'https://%s/commits/%s/%s',
|
||||
$lockRepo,
|
||||
$lockBranch,
|
||||
$recipe->getName()
|
||||
);
|
||||
|
||||
// show commits since one second after the currently-installed recipe
|
||||
if (null !== $commitDate) {
|
||||
$historyUrl .= '?since=';
|
||||
$historyUrl .= (new \DateTime($commitDate))
|
||||
->setTimezone(new \DateTimeZone('UTC'))
|
||||
->modify('+1 seconds')
|
||||
->format('Y-m-d\TH:i:s\Z');
|
||||
}
|
||||
|
||||
$io->write('<info>recipe history</info> : '.$historyUrl);
|
||||
}
|
||||
|
||||
if (null !== $lockFiles) {
|
||||
$io->write('<info>files</info> : ');
|
||||
$io->write('');
|
||||
|
||||
$tree = $this->generateFilesTree($lockFiles);
|
||||
|
||||
$this->displayFilesTree($tree);
|
||||
}
|
||||
|
||||
if ($lockRef !== $recipe->getRef()) {
|
||||
$io->write([
|
||||
'',
|
||||
'Update this recipe by running:',
|
||||
\sprintf('<info>composer recipes:update %s</info>', $recipe->getName()),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
private function generateFilesTree(array $files): array
|
||||
{
|
||||
$tree = [];
|
||||
foreach ($files as $file) {
|
||||
$path = explode('/', $file);
|
||||
|
||||
$tree = array_merge_recursive($tree, $this->addNode($path));
|
||||
}
|
||||
|
||||
return $tree;
|
||||
}
|
||||
|
||||
private function addNode(array $node): array
|
||||
{
|
||||
$current = array_shift($node);
|
||||
|
||||
$subTree = [];
|
||||
if (null !== $current) {
|
||||
$subTree[$current] = $this->addNode($node);
|
||||
}
|
||||
|
||||
return $subTree;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note : We do not display file modification information with Configurator like ComposerScripts, Container, DockerComposer, Dockerfile, Env, Gitignore and Makefile.
|
||||
*/
|
||||
private function displayFilesTree(array $tree)
|
||||
{
|
||||
end($tree);
|
||||
$endKey = key($tree);
|
||||
foreach ($tree as $dir => $files) {
|
||||
$treeBar = '├';
|
||||
$total = \count($files);
|
||||
if (0 === $total || $endKey === $dir) {
|
||||
$treeBar = '└';
|
||||
}
|
||||
|
||||
$info = \sprintf(
|
||||
'%s──%s',
|
||||
$treeBar,
|
||||
$dir
|
||||
);
|
||||
$this->writeTreeLine($info);
|
||||
|
||||
$treeBar = str_replace('└', ' ', $treeBar);
|
||||
|
||||
$this->displayTree($files, $treeBar);
|
||||
}
|
||||
}
|
||||
|
||||
private function displayTree(array $tree, $previousTreeBar = '├', $level = 1)
|
||||
{
|
||||
$previousTreeBar = str_replace('├', '│', $previousTreeBar);
|
||||
$treeBar = $previousTreeBar.' ├';
|
||||
|
||||
$i = 0;
|
||||
$total = \count($tree);
|
||||
|
||||
foreach ($tree as $dir => $files) {
|
||||
++$i;
|
||||
if ($i === $total) {
|
||||
$treeBar = $previousTreeBar.' └';
|
||||
}
|
||||
|
||||
$info = \sprintf(
|
||||
'%s──%s',
|
||||
$treeBar,
|
||||
$dir
|
||||
);
|
||||
$this->writeTreeLine($info);
|
||||
|
||||
$treeBar = str_replace('└', ' ', $treeBar);
|
||||
|
||||
$this->displayTree($files, $treeBar, $level + 1);
|
||||
}
|
||||
}
|
||||
|
||||
private function writeTreeLine($line)
|
||||
{
|
||||
$io = $this->getIO();
|
||||
if (!$io->isDecorated()) {
|
||||
$line = str_replace(['└', '├', '──', '│'], ['`-', '|-', '-', '|'], $line);
|
||||
}
|
||||
|
||||
$io->write($line);
|
||||
}
|
||||
}
|
||||
415
vendor/symfony/flex/src/Command/UpdateRecipesCommand.php
vendored
Normal file
415
vendor/symfony/flex/src/Command/UpdateRecipesCommand.php
vendored
Normal file
@@ -0,0 +1,415 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex\Command;
|
||||
|
||||
use Composer\Command\BaseCommand;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Package\Package;
|
||||
use Composer\Package\PackageInterface;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
use Symfony\Component\Console\Exception\RuntimeException;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Flex\Configurator;
|
||||
use Symfony\Flex\Downloader;
|
||||
use Symfony\Flex\Flex;
|
||||
use Symfony\Flex\GithubApi;
|
||||
use Symfony\Flex\InformationOperation;
|
||||
use Symfony\Flex\Lock;
|
||||
use Symfony\Flex\Recipe;
|
||||
use Symfony\Flex\Update\RecipePatcher;
|
||||
use Symfony\Flex\Update\RecipeUpdate;
|
||||
|
||||
class UpdateRecipesCommand extends BaseCommand
|
||||
{
|
||||
/** @var Flex */
|
||||
private $flex;
|
||||
private $downloader;
|
||||
private $configurator;
|
||||
private $rootDir;
|
||||
private $githubApi;
|
||||
private $processExecutor;
|
||||
|
||||
public function __construct(/* cannot be type-hinted */ $flex, Downloader $downloader, $httpDownloader, Configurator $configurator, string $rootDir)
|
||||
{
|
||||
$this->flex = $flex;
|
||||
$this->downloader = $downloader;
|
||||
$this->configurator = $configurator;
|
||||
$this->rootDir = $rootDir;
|
||||
$this->githubApi = new GithubApi($httpDownloader);
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('symfony:recipes:update')
|
||||
->setAliases(['recipes:update'])
|
||||
->setDescription('Updates an already-installed recipe to the latest version.')
|
||||
->addArgument('package', InputArgument::OPTIONAL, 'Recipe that should be updated.')
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$win = '\\' === \DIRECTORY_SEPARATOR;
|
||||
$runtimeExceptionClass = class_exists(RuntimeException::class) ? RuntimeException::class : \RuntimeException::class;
|
||||
if (!@is_executable(strtok(exec($win ? 'where git' : 'command -v git'), \PHP_EOL))) {
|
||||
throw new $runtimeExceptionClass('Cannot run "recipes:update": git not found.');
|
||||
}
|
||||
|
||||
$io = $this->getIO();
|
||||
if (!$this->isIndexClean($io)) {
|
||||
$io->write([
|
||||
' Cannot run <comment>recipes:update</comment>: Your git index contains uncommitted changes.',
|
||||
' Please commit or stash them and try again!',
|
||||
]);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
$packageName = $input->getArgument('package');
|
||||
$symfonyLock = $this->flex->getLock();
|
||||
if (!$packageName) {
|
||||
$packageName = $this->askForPackage($io, $symfonyLock);
|
||||
|
||||
if (null === $packageName) {
|
||||
$io->writeError('All packages appear to be up-to-date!');
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$symfonyLock->has($packageName)) {
|
||||
$io->writeError([
|
||||
'Package not found inside symfony.lock. It looks like it\'s not installed?',
|
||||
\sprintf('Try running <info>composer recipes:install %s --force -v</info> to re-install the recipe.', $packageName),
|
||||
]);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
$packageLockData = $symfonyLock->get($packageName);
|
||||
if (!isset($packageLockData['recipe'])) {
|
||||
$io->writeError([
|
||||
'It doesn\'t look like this package had a recipe when it was originally installed.',
|
||||
'To install the latest version of the recipe, if there is one, run:',
|
||||
\sprintf(' <info>composer recipes:install %s --force -v</info>', $packageName),
|
||||
]);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
$recipeRef = $packageLockData['recipe']['ref'] ?? null;
|
||||
$recipeVersion = $packageLockData['recipe']['version'] ?? null;
|
||||
if (!$recipeRef || !$recipeVersion) {
|
||||
$io->writeError([
|
||||
'The version of the installed recipe was not saved into symfony.lock.',
|
||||
'This is possible if it was installed by an old version of Symfony Flex.',
|
||||
'Update the recipe by re-installing the latest version with:',
|
||||
\sprintf(' <info>composer recipes:install %s --force -v</info>', $packageName),
|
||||
]);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
$installedRepo = $this->getComposer()->getRepositoryManager()->getLocalRepository();
|
||||
$package = $installedRepo->findPackage($packageName, '*') ?? new Package($packageName, $packageLockData['version'], $packageLockData['version']);
|
||||
$originalRecipe = $this->getRecipe($package, $recipeRef, $recipeVersion);
|
||||
|
||||
if (null === $originalRecipe) {
|
||||
$io->writeError([
|
||||
'The original recipe version you have installed could not be found, it may be too old.',
|
||||
'Update the recipe by re-installing the latest version with:',
|
||||
\sprintf(' <info>composer recipes:install %s --force -v</info>', $packageName),
|
||||
]);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
$newRecipe = $this->getRecipe($package);
|
||||
|
||||
if ($newRecipe->getRef() === $originalRecipe->getRef()) {
|
||||
$io->write(\sprintf('This recipe for <info>%s</info> is already at the latest version.', $packageName));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
$io->write([
|
||||
\sprintf(' Updating recipe for <info>%s</info>...', $packageName),
|
||||
'',
|
||||
]);
|
||||
|
||||
$recipeUpdate = new RecipeUpdate($originalRecipe, $newRecipe, $symfonyLock, $this->rootDir);
|
||||
$this->configurator->populateUpdate($recipeUpdate);
|
||||
$originalComposerJsonHash = $this->flex->getComposerJsonHash();
|
||||
$patcher = new RecipePatcher($this->rootDir, $io, $symfonyLock);
|
||||
|
||||
try {
|
||||
$patch = $patcher->generatePatch($recipeUpdate->getOriginalFiles(), $recipeUpdate->getNewFiles());
|
||||
$hasConflicts = !$patcher->applyPatch($patch, $packageName);
|
||||
} catch (\Throwable $throwable) {
|
||||
$io->writeError([
|
||||
'<bg=red;fg=white>There was an error applying the recipe update patch</>',
|
||||
$throwable->getMessage(),
|
||||
'',
|
||||
'Update the recipe by re-installing the latest version with:',
|
||||
\sprintf(' <info>composer recipes:install %s --force -v</info>', $packageName),
|
||||
]);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
$symfonyLock->add($packageName, $newRecipe->getLock());
|
||||
$this->flex->finish($this->rootDir, $originalComposerJsonHash);
|
||||
|
||||
// stage symfony.lock, as all patched files with already be staged
|
||||
$cmdOutput = '';
|
||||
$this->getProcessExecutor()->execute('git add symfony.lock', $cmdOutput, $this->rootDir);
|
||||
|
||||
$io->write([
|
||||
' <bg=blue;fg=white> </>',
|
||||
' <bg=blue;fg=white> Yes! Recipe updated! </>',
|
||||
' <bg=blue;fg=white> </>',
|
||||
'',
|
||||
]);
|
||||
|
||||
if ($hasConflicts) {
|
||||
$io->write([
|
||||
' The recipe was updated but with <bg=red;fg=white>one or more conflicts</>.',
|
||||
' Run <comment>git status</comment> to see them.',
|
||||
' After resolving, commit your changes like normal.',
|
||||
]);
|
||||
} else {
|
||||
if (!$patch->getPatch()) {
|
||||
// no changes were required
|
||||
$io->write([
|
||||
' No files were changed as a result of the update.',
|
||||
]);
|
||||
} else {
|
||||
$io->write([
|
||||
' Run <comment>git status</comment> or <comment>git diff --cached</comment> to see the changes.',
|
||||
' When you\'re ready, commit these changes like normal.',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if (0 !== \count($recipeUpdate->getCopyFromPackagePaths())) {
|
||||
$io->write([
|
||||
'',
|
||||
' <bg=red;fg=white>NOTE:</>',
|
||||
' This recipe copies the following paths from the bundle into your app:',
|
||||
]);
|
||||
foreach ($recipeUpdate->getCopyFromPackagePaths() as $source => $target) {
|
||||
$io->write(\sprintf(' * %s => %s', $source, $target));
|
||||
}
|
||||
$io->write([
|
||||
'',
|
||||
' The recipe updater has no way of knowing if these files have changed since you originally installed the recipe.',
|
||||
' And so, no updates were made to these paths.',
|
||||
]);
|
||||
}
|
||||
|
||||
if (0 !== \count($patch->getRemovedPatches())) {
|
||||
if (1 === \count($patch->getRemovedPatches())) {
|
||||
$notes = [
|
||||
\sprintf(' The file <comment>%s</comment> was not updated because it doesn\'t exist in your app.', array_keys($patch->getRemovedPatches())[0]),
|
||||
];
|
||||
} else {
|
||||
$notes = [' The following files were not updated because they don\'t exist in your app:'];
|
||||
foreach ($patch->getRemovedPatches() as $filename => $contents) {
|
||||
$notes[] = \sprintf(' * <comment>%s</comment>', $filename);
|
||||
}
|
||||
}
|
||||
$io->write([
|
||||
'',
|
||||
' <bg=red;fg=white>NOTE:</>',
|
||||
]);
|
||||
$io->write($notes);
|
||||
$io->write('');
|
||||
if ($io->askConfirmation(' Would you like to save the "diff" to a file so you can review it? (Y/n) ')) {
|
||||
$patchFilename = str_replace('/', '.', $packageName).'.updates-for-deleted-files.patch';
|
||||
file_put_contents($this->rootDir.'/'.$patchFilename, implode("\n", $patch->getRemovedPatches()));
|
||||
$io->write([
|
||||
'',
|
||||
\sprintf(' Saved diff to <info>%s</info>', $patchFilename),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($patch->getPatch()) {
|
||||
$io->write('');
|
||||
$io->write(' Calculating CHANGELOG...', false);
|
||||
$changelog = $this->generateChangelog($originalRecipe);
|
||||
$io->write("\r", false); // clear current line
|
||||
if ($changelog) {
|
||||
$io->write($changelog);
|
||||
} else {
|
||||
$io->write('No CHANGELOG could be calculated.');
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function getRecipe(PackageInterface $package, ?string $recipeRef = null, ?string $recipeVersion = null): ?Recipe
|
||||
{
|
||||
$operation = new InformationOperation($package);
|
||||
if (null !== $recipeRef) {
|
||||
$operation->setSpecificRecipeVersion($recipeRef, $recipeVersion);
|
||||
}
|
||||
$recipes = $this->downloader->getRecipes([$operation]);
|
||||
|
||||
if (0 === \count($recipes['manifests'] ?? [])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Recipe(
|
||||
$package,
|
||||
$package->getName(),
|
||||
$operation->getOperationType(),
|
||||
$recipes['manifests'][$package->getName()],
|
||||
$recipes['locks'][$package->getName()] ?? []
|
||||
);
|
||||
}
|
||||
|
||||
private function generateChangelog(Recipe $originalRecipe): ?array
|
||||
{
|
||||
$recipeData = $originalRecipe->getLock()['recipe'] ?? null;
|
||||
if (null === $recipeData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!isset($recipeData['ref']) || !isset($recipeData['repo']) || !isset($recipeData['branch']) || !isset($recipeData['version'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$currentRecipeVersionData = $this->githubApi->findRecipeCommitDataFromTreeRef(
|
||||
$originalRecipe->getName(),
|
||||
$recipeData['repo'],
|
||||
$recipeData['branch'],
|
||||
$recipeData['version'],
|
||||
$recipeData['ref']
|
||||
);
|
||||
|
||||
if (!$currentRecipeVersionData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$recipeVersions = $this->githubApi->getVersionsOfRecipe(
|
||||
$recipeData['repo'],
|
||||
$recipeData['branch'],
|
||||
$originalRecipe->getName()
|
||||
);
|
||||
if (!$recipeVersions) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$newerRecipeVersions = array_filter($recipeVersions, function ($version) use ($recipeData) {
|
||||
return version_compare($version, $recipeData['version'], '>');
|
||||
});
|
||||
|
||||
$newCommits = $currentRecipeVersionData['new_commits'];
|
||||
foreach ($newerRecipeVersions as $newerRecipeVersion) {
|
||||
$newCommits = array_merge(
|
||||
$newCommits,
|
||||
$this->githubApi->getCommitDataForPath($recipeData['repo'], $originalRecipe->getName().'/'.$newerRecipeVersion, $recipeData['branch'])
|
||||
);
|
||||
}
|
||||
|
||||
$newCommits = array_unique($newCommits);
|
||||
asort($newCommits);
|
||||
|
||||
$pullRequests = [];
|
||||
foreach ($newCommits as $commit => $date) {
|
||||
$pr = $this->githubApi->getPullRequestForCommit($commit, $recipeData['repo']);
|
||||
if ($pr) {
|
||||
$pullRequests[$pr['number']] = $pr;
|
||||
}
|
||||
}
|
||||
|
||||
$lines = [];
|
||||
// borrowed from symfony/console's OutputFormatterStyle
|
||||
$handlesHrefGracefully = 'JetBrains-JediTerm' !== getenv('TERMINAL_EMULATOR')
|
||||
&& (!getenv('KONSOLE_VERSION') || (int) getenv('KONSOLE_VERSION') > 201100);
|
||||
foreach ($pullRequests as $number => $data) {
|
||||
$url = $data['url'];
|
||||
if ($handlesHrefGracefully) {
|
||||
$url = "\033]8;;$url\033\\$number\033]8;;\033\\";
|
||||
}
|
||||
$lines[] = \sprintf(' * %s (PR %s)', $data['title'], $url);
|
||||
}
|
||||
|
||||
return $lines;
|
||||
}
|
||||
|
||||
private function askForPackage(IOInterface $io, Lock $symfonyLock): ?string
|
||||
{
|
||||
$installedRepo = $this->getComposer()->getRepositoryManager()->getLocalRepository();
|
||||
|
||||
$operations = [];
|
||||
foreach ($symfonyLock->all() as $name => $lock) {
|
||||
if (isset($lock['recipe']['ref'])) {
|
||||
$package = $installedRepo->findPackage($name, '*') ?? new Package($name, $lock['version'], $lock['version']);
|
||||
$operations[] = new InformationOperation($package);
|
||||
}
|
||||
}
|
||||
|
||||
$recipes = $this->flex->fetchRecipes($operations, false);
|
||||
ksort($recipes);
|
||||
|
||||
$outdatedRecipes = [];
|
||||
foreach ($recipes as $name => $recipe) {
|
||||
$lockRef = $symfonyLock->get($name)['recipe']['ref'] ?? null;
|
||||
|
||||
if (null !== $lockRef && $recipe->getRef() !== $lockRef && !$recipe->isAuto()) {
|
||||
$outdatedRecipes[] = $name;
|
||||
}
|
||||
}
|
||||
|
||||
if (0 === \count($outdatedRecipes)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$question = 'Which outdated recipe would you like to update? (default: <info>0</info>)';
|
||||
|
||||
$choice = $io->select(
|
||||
$question,
|
||||
$outdatedRecipes,
|
||||
0
|
||||
);
|
||||
|
||||
return $outdatedRecipes[$choice];
|
||||
}
|
||||
|
||||
private function isIndexClean(IOInterface $io): bool
|
||||
{
|
||||
$output = '';
|
||||
|
||||
$this->getProcessExecutor()->execute('git status --porcelain --untracked-files=no', $output, $this->rootDir);
|
||||
if ('' !== trim($output)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function getProcessExecutor(): ProcessExecutor
|
||||
{
|
||||
if (null === $this->processExecutor) {
|
||||
$this->processExecutor = new ProcessExecutor($this->getIO());
|
||||
}
|
||||
|
||||
return $this->processExecutor;
|
||||
}
|
||||
}
|
||||
120
vendor/symfony/flex/src/Configurator.php
vendored
Normal file
120
vendor/symfony/flex/src/Configurator.php
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex;
|
||||
|
||||
use Composer\Composer;
|
||||
use Composer\IO\IOInterface;
|
||||
use Symfony\Flex\Configurator\AbstractConfigurator;
|
||||
use Symfony\Flex\Update\RecipeUpdate;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class Configurator
|
||||
{
|
||||
private $composer;
|
||||
private $io;
|
||||
private $options;
|
||||
private $configurators;
|
||||
private $postInstallConfigurators;
|
||||
private $cache;
|
||||
|
||||
public function __construct(Composer $composer, IOInterface $io, Options $options)
|
||||
{
|
||||
$this->composer = $composer;
|
||||
$this->io = $io;
|
||||
$this->options = $options;
|
||||
// ordered list of configurators
|
||||
$this->configurators = [
|
||||
'bundles' => Configurator\BundlesConfigurator::class,
|
||||
'copy-from-recipe' => Configurator\CopyFromRecipeConfigurator::class,
|
||||
'copy-from-package' => Configurator\CopyFromPackageConfigurator::class,
|
||||
'env' => Configurator\EnvConfigurator::class,
|
||||
'dotenv' => Configurator\DotenvConfigurator::class,
|
||||
'container' => Configurator\ContainerConfigurator::class,
|
||||
'makefile' => Configurator\MakefileConfigurator::class,
|
||||
'composer-scripts' => Configurator\ComposerScriptsConfigurator::class,
|
||||
'composer-commands' => Configurator\ComposerCommandsConfigurator::class,
|
||||
'gitignore' => Configurator\GitignoreConfigurator::class,
|
||||
'dockerfile' => Configurator\DockerfileConfigurator::class,
|
||||
'docker-compose' => Configurator\DockerComposeConfigurator::class,
|
||||
];
|
||||
$this->postInstallConfigurators = [
|
||||
'add-lines' => Configurator\AddLinesConfigurator::class,
|
||||
];
|
||||
}
|
||||
|
||||
public function install(Recipe $recipe, Lock $lock, array $options = [])
|
||||
{
|
||||
$manifest = $recipe->getManifest();
|
||||
foreach (array_keys($this->configurators) as $key) {
|
||||
if (isset($manifest[$key])) {
|
||||
$this->get($key)->configure($recipe, $manifest[$key], $lock, $options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run after all recipes have been installed to run post-install configurators.
|
||||
*/
|
||||
public function postInstall(Recipe $recipe, Lock $lock, array $options = [])
|
||||
{
|
||||
$manifest = $recipe->getManifest();
|
||||
foreach (array_keys($this->postInstallConfigurators) as $key) {
|
||||
if (isset($manifest[$key])) {
|
||||
$this->get($key)->configure($recipe, $manifest[$key], $lock, $options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function populateUpdate(RecipeUpdate $recipeUpdate): void
|
||||
{
|
||||
$originalManifest = $recipeUpdate->getOriginalRecipe()->getManifest();
|
||||
$newManifest = $recipeUpdate->getNewRecipe()->getManifest();
|
||||
$allConfigurators = array_merge($this->configurators, $this->postInstallConfigurators);
|
||||
foreach (array_keys($allConfigurators) as $key) {
|
||||
if (!isset($originalManifest[$key]) && !isset($newManifest[$key])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->get($key)->update($recipeUpdate, $originalManifest[$key] ?? [], $newManifest[$key] ?? []);
|
||||
}
|
||||
}
|
||||
|
||||
public function unconfigure(Recipe $recipe, Lock $lock)
|
||||
{
|
||||
$manifest = $recipe->getManifest();
|
||||
|
||||
$allConfigurators = array_merge($this->configurators, $this->postInstallConfigurators);
|
||||
|
||||
foreach (array_keys($allConfigurators) as $key) {
|
||||
if (isset($manifest[$key])) {
|
||||
$this->get($key)->unconfigure($recipe, $manifest[$key], $lock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function get($key): AbstractConfigurator
|
||||
{
|
||||
if (!isset($this->configurators[$key]) && !isset($this->postInstallConfigurators[$key])) {
|
||||
throw new \InvalidArgumentException(\sprintf('Unknown configurator "%s".', $key));
|
||||
}
|
||||
|
||||
if (isset($this->cache[$key])) {
|
||||
return $this->cache[$key];
|
||||
}
|
||||
|
||||
$class = isset($this->configurators[$key]) ? $this->configurators[$key] : $this->postInstallConfigurators[$key];
|
||||
|
||||
return $this->cache[$key] = new $class($this->composer, $this->io, $this->options);
|
||||
}
|
||||
}
|
||||
131
vendor/symfony/flex/src/Configurator/AbstractConfigurator.php
vendored
Normal file
131
vendor/symfony/flex/src/Configurator/AbstractConfigurator.php
vendored
Normal file
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex\Configurator;
|
||||
|
||||
use Composer\Composer;
|
||||
use Composer\IO\IOInterface;
|
||||
use Symfony\Flex\Lock;
|
||||
use Symfony\Flex\Options;
|
||||
use Symfony\Flex\Path;
|
||||
use Symfony\Flex\Recipe;
|
||||
use Symfony\Flex\Update\RecipeUpdate;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
abstract class AbstractConfigurator
|
||||
{
|
||||
protected $composer;
|
||||
protected $io;
|
||||
protected $options;
|
||||
protected $path;
|
||||
|
||||
public function __construct(Composer $composer, IOInterface $io, Options $options)
|
||||
{
|
||||
$this->composer = $composer;
|
||||
$this->io = $io;
|
||||
$this->options = $options;
|
||||
$this->path = new Path($options->get('root-dir'));
|
||||
}
|
||||
|
||||
abstract public function configure(Recipe $recipe, $config, Lock $lock, array $options = []);
|
||||
|
||||
abstract public function unconfigure(Recipe $recipe, $config, Lock $lock);
|
||||
|
||||
abstract public function update(RecipeUpdate $recipeUpdate, array $originalConfig, array $newConfig): void;
|
||||
|
||||
protected function write($messages, $verbosity = IOInterface::VERBOSE)
|
||||
{
|
||||
if (!\is_array($messages)) {
|
||||
$messages = [$messages];
|
||||
}
|
||||
foreach ($messages as $i => $message) {
|
||||
$messages[$i] = ' '.$message;
|
||||
}
|
||||
$this->io->writeError($messages, true, $verbosity);
|
||||
}
|
||||
|
||||
protected function isFileMarked(Recipe $recipe, string $file): bool
|
||||
{
|
||||
return is_file($file) && str_contains(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) && str_contains(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 (!str_contains($contents, $startMark) || !str_contains($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;
|
||||
}
|
||||
}
|
||||
274
vendor/symfony/flex/src/Configurator/AddLinesConfigurator.php
vendored
Normal file
274
vendor/symfony/flex/src/Configurator/AddLinesConfigurator.php
vendored
Normal file
@@ -0,0 +1,274 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Flex\Configurator;
|
||||
|
||||
use Composer\IO\IOInterface;
|
||||
use Symfony\Flex\Lock;
|
||||
use Symfony\Flex\Recipe;
|
||||
use Symfony\Flex\Update\RecipeUpdate;
|
||||
|
||||
/**
|
||||
* @author Kevin Bond <kevinbond@gmail.com>
|
||||
* @author Ryan Weaver <ryan@symfonycasts.com>
|
||||
*/
|
||||
class AddLinesConfigurator extends AbstractConfigurator
|
||||
{
|
||||
private const POSITION_TOP = 'top';
|
||||
private const POSITION_BOTTOM = 'bottom';
|
||||
private const POSITION_AFTER_TARGET = 'after_target';
|
||||
|
||||
private const VALID_POSITIONS = [
|
||||
self::POSITION_TOP,
|
||||
self::POSITION_BOTTOM,
|
||||
self::POSITION_AFTER_TARGET,
|
||||
];
|
||||
|
||||
/**
|
||||
* Holds file contents for files that have been loaded.
|
||||
* This allows us to "change" the contents of a file multiple
|
||||
* times before we actually write it out.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private $fileContents = [];
|
||||
|
||||
public function configure(Recipe $recipe, $config, Lock $lock, array $options = []): void
|
||||
{
|
||||
$this->fileContents = [];
|
||||
$this->executeConfigure($recipe, $config);
|
||||
|
||||
foreach ($this->fileContents as $file => $contents) {
|
||||
$this->write(\sprintf('[add-lines] Patching file "%s"', $this->relativize($file)));
|
||||
file_put_contents($file, $contents);
|
||||
}
|
||||
}
|
||||
|
||||
public function unconfigure(Recipe $recipe, $config, Lock $lock): void
|
||||
{
|
||||
$this->fileContents = [];
|
||||
$this->executeUnconfigure($recipe, $config);
|
||||
|
||||
foreach ($this->fileContents as $file => $change) {
|
||||
$this->write(\sprintf('[add-lines] Reverting file "%s"', $this->relativize($file)));
|
||||
file_put_contents($file, $change);
|
||||
}
|
||||
}
|
||||
|
||||
public function update(RecipeUpdate $recipeUpdate, array $originalConfig, array $newConfig): void
|
||||
{
|
||||
// manually check for "requires", as unconfigure ignores it
|
||||
$originalConfig = array_filter($originalConfig, function ($item) {
|
||||
return !isset($item['requires']) || $this->isPackageInstalled($item['requires']);
|
||||
});
|
||||
|
||||
// reset the file content cache
|
||||
$this->fileContents = [];
|
||||
$this->executeUnconfigure($recipeUpdate->getOriginalRecipe(), $originalConfig);
|
||||
$this->executeConfigure($recipeUpdate->getNewRecipe(), $newConfig);
|
||||
$newFiles = [];
|
||||
$originalFiles = [];
|
||||
foreach ($this->fileContents as $file => $contents) {
|
||||
// set the original file to the current contents
|
||||
$originalFiles[$this->relativize($file)] = file_get_contents($file);
|
||||
// and the new file where the old recipe was unconfigured, and the new configured
|
||||
$newFiles[$this->relativize($file)] = $contents;
|
||||
}
|
||||
$recipeUpdate->addOriginalFiles($originalFiles);
|
||||
$recipeUpdate->addNewFiles($newFiles);
|
||||
}
|
||||
|
||||
public function executeConfigure(Recipe $recipe, $config): void
|
||||
{
|
||||
foreach ($config as $patch) {
|
||||
if (!isset($patch['file'])) {
|
||||
$this->write(\sprintf('The "file" key is required for the "add-lines" configurator for recipe "%s". Skipping', $recipe->getName()));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($patch['requires']) && !$this->isPackageInstalled($patch['requires'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($patch['content'])) {
|
||||
$this->write(\sprintf('The "content" key is required for the "add-lines" configurator for recipe "%s". Skipping', $recipe->getName()));
|
||||
|
||||
continue;
|
||||
}
|
||||
$content = $patch['content'];
|
||||
|
||||
$file = $this->path->concatenate([$this->options->get('root-dir'), $this->options->expandTargetDir($patch['file'])]);
|
||||
$warnIfMissing = isset($patch['warn_if_missing']) && $patch['warn_if_missing'];
|
||||
if (!is_file($file)) {
|
||||
$this->write([
|
||||
\sprintf('Could not add lines to file <info>%s</info> as it does not exist. Missing lines:', $patch['file']),
|
||||
'<comment>"""</comment>',
|
||||
$content,
|
||||
'<comment>"""</comment>',
|
||||
'',
|
||||
], $warnIfMissing ? IOInterface::NORMAL : IOInterface::VERBOSE);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($patch['position'])) {
|
||||
$this->write(\sprintf('The "position" key is required for the "add-lines" configurator for recipe "%s". Skipping', $recipe->getName()));
|
||||
|
||||
continue;
|
||||
}
|
||||
$position = $patch['position'];
|
||||
if (!\in_array($position, self::VALID_POSITIONS, true)) {
|
||||
$this->write(\sprintf('The "position" key must be one of "%s" for the "add-lines" configurator for recipe "%s". Skipping', implode('", "', self::VALID_POSITIONS), $recipe->getName()));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (self::POSITION_AFTER_TARGET === $position && !isset($patch['target'])) {
|
||||
$this->write(\sprintf('The "target" key is required when "position" is "%s" for the "add-lines" configurator for recipe "%s". Skipping', self::POSITION_AFTER_TARGET, $recipe->getName()));
|
||||
|
||||
continue;
|
||||
}
|
||||
$target = isset($patch['target']) ? $patch['target'] : null;
|
||||
|
||||
$newContents = $this->getPatchedContents($file, $content, $position, $target, $warnIfMissing);
|
||||
$this->fileContents[$file] = $newContents;
|
||||
}
|
||||
}
|
||||
|
||||
public function executeUnconfigure(Recipe $recipe, $config): void
|
||||
{
|
||||
foreach ($config as $patch) {
|
||||
if (!isset($patch['file'])) {
|
||||
$this->write(\sprintf('The "file" key is required for the "add-lines" configurator for recipe "%s". Skipping', $recipe->getName()));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ignore "requires": the target packages may have just become uninstalled.
|
||||
// Checking for a "content" match is enough.
|
||||
|
||||
$file = $this->path->concatenate([$this->options->get('root-dir'), $this->options->expandTargetDir($patch['file'])]);
|
||||
if (!is_file($file)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($patch['content'])) {
|
||||
$this->write(\sprintf('The "content" key is required for the "add-lines" configurator for recipe "%s". Skipping', $recipe->getName()));
|
||||
|
||||
continue;
|
||||
}
|
||||
$value = $patch['content'];
|
||||
|
||||
$newContents = $this->getUnPatchedContents($file, $value);
|
||||
$this->fileContents[$file] = $newContents;
|
||||
}
|
||||
}
|
||||
|
||||
private function getPatchedContents(string $file, string $value, string $position, ?string $target, bool $warnIfMissing): string
|
||||
{
|
||||
$fileContents = $this->readFile($file);
|
||||
|
||||
if (str_contains($fileContents, $value)) {
|
||||
return $fileContents; // already includes value, skip
|
||||
}
|
||||
|
||||
switch ($position) {
|
||||
case self::POSITION_BOTTOM:
|
||||
$fileContents .= "\n".$value;
|
||||
|
||||
break;
|
||||
case self::POSITION_TOP:
|
||||
$fileContents = $value."\n".$fileContents;
|
||||
|
||||
break;
|
||||
case self::POSITION_AFTER_TARGET:
|
||||
$lines = explode("\n", $fileContents);
|
||||
$targetFound = false;
|
||||
foreach ($lines as $key => $line) {
|
||||
if (str_contains($line, $target)) {
|
||||
array_splice($lines, $key + 1, 0, $value);
|
||||
$targetFound = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
$fileContents = implode("\n", $lines);
|
||||
|
||||
if (!$targetFound) {
|
||||
$this->write([
|
||||
\sprintf('Could not add lines after "%s" as no such string was found in "%s". Missing lines:', $target, $file),
|
||||
'<comment>"""</comment>',
|
||||
$value,
|
||||
'<comment>"""</comment>',
|
||||
'',
|
||||
], $warnIfMissing ? IOInterface::NORMAL : IOInterface::VERBOSE);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return $fileContents;
|
||||
}
|
||||
|
||||
private function getUnPatchedContents(string $file, $value): string
|
||||
{
|
||||
$fileContents = $this->readFile($file);
|
||||
|
||||
if (!str_contains($fileContents, $value)) {
|
||||
return $fileContents; // value already gone!
|
||||
}
|
||||
|
||||
if (str_contains($fileContents, "\n".$value)) {
|
||||
$value = "\n".$value;
|
||||
} elseif (str_contains($fileContents, $value."\n")) {
|
||||
$value .= "\n";
|
||||
}
|
||||
|
||||
$position = strpos($fileContents, $value);
|
||||
|
||||
return substr_replace($fileContents, '', $position, \strlen($value));
|
||||
}
|
||||
|
||||
private function isPackageInstalled($packages): bool
|
||||
{
|
||||
if (\is_string($packages)) {
|
||||
$packages = [$packages];
|
||||
}
|
||||
|
||||
$installedRepo = $this->composer->getRepositoryManager()->getLocalRepository();
|
||||
|
||||
foreach ($packages as $package) {
|
||||
$package = explode(':', $package, 2);
|
||||
$packageName = $package[0];
|
||||
$constraint = $package[1] ?? '*';
|
||||
|
||||
if (null === $installedRepo->findPackage($packageName, $constraint)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function relativize(string $path): string
|
||||
{
|
||||
$rootDir = $this->options->get('root-dir');
|
||||
if (str_starts_with($path, $rootDir)) {
|
||||
$path = substr($path, \strlen($rootDir) + 1);
|
||||
}
|
||||
|
||||
return ltrim($path, '/\\');
|
||||
}
|
||||
|
||||
private function readFile(string $file): string
|
||||
{
|
||||
if (isset($this->fileContents[$file])) {
|
||||
return $this->fileContents[$file];
|
||||
}
|
||||
|
||||
$fileContents = file_get_contents($file);
|
||||
$this->fileContents[$file] = $fileContents;
|
||||
|
||||
return $fileContents;
|
||||
}
|
||||
}
|
||||
150
vendor/symfony/flex/src/Configurator/BundlesConfigurator.php
vendored
Normal file
150
vendor/symfony/flex/src/Configurator/BundlesConfigurator.php
vendored
Normal file
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex\Configurator;
|
||||
|
||||
use Symfony\Flex\Lock;
|
||||
use Symfony\Flex\Recipe;
|
||||
use Symfony\Flex\Update\RecipeUpdate;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class BundlesConfigurator extends AbstractConfigurator
|
||||
{
|
||||
public function configure(Recipe $recipe, $bundles, Lock $lock, array $options = [])
|
||||
{
|
||||
$this->write('Enabling the package as a Symfony bundle');
|
||||
$registered = $this->configureBundles($bundles);
|
||||
$this->dump($this->getConfFile(), $registered);
|
||||
}
|
||||
|
||||
public function unconfigure(Recipe $recipe, $bundles, Lock $lock)
|
||||
{
|
||||
$this->write('Disabling the Symfony bundle');
|
||||
$file = $this->getConfFile();
|
||||
if (!file_exists($file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$registered = $this->load($file);
|
||||
foreach (array_keys($this->prepareBundles($bundles)) as $class) {
|
||||
unset($registered[$class]);
|
||||
}
|
||||
$this->dump($file, $registered);
|
||||
}
|
||||
|
||||
public function update(RecipeUpdate $recipeUpdate, array $originalConfig, array $newConfig): void
|
||||
{
|
||||
$originalBundles = $this->configureBundles($originalConfig, true);
|
||||
$recipeUpdate->setOriginalFile(
|
||||
$this->getLocalConfFile(),
|
||||
$this->buildContents($originalBundles)
|
||||
);
|
||||
|
||||
$newBundles = $this->configureBundles($newConfig, true);
|
||||
$recipeUpdate->setNewFile(
|
||||
$this->getLocalConfFile(),
|
||||
$this->buildContents($newBundles)
|
||||
);
|
||||
}
|
||||
|
||||
private function configureBundles(array $bundles, bool $resetEnvironments = false): array
|
||||
{
|
||||
$file = $this->getConfFile();
|
||||
$registered = $this->load($file);
|
||||
$classes = $this->prepareBundles($bundles);
|
||||
if (isset($classes[$fwb = 'Symfony\Bundle\FrameworkBundle\FrameworkBundle'])) {
|
||||
foreach ($classes[$fwb] as $env) {
|
||||
$registered[$fwb][$env] = true;
|
||||
}
|
||||
unset($classes[$fwb]);
|
||||
}
|
||||
foreach ($classes as $class => $envs) {
|
||||
// do not override existing configured envs for a bundle
|
||||
if (!isset($registered[$class]) || $resetEnvironments) {
|
||||
if ($resetEnvironments) {
|
||||
// used during calculating an "upgrade"
|
||||
// here, we want to "undo" the bundle's configuration entirely
|
||||
// then re-add it fresh, in case some environments have been
|
||||
// removed in an updated version of the recipe
|
||||
$registered[$class] = [];
|
||||
}
|
||||
|
||||
foreach ($envs as $env) {
|
||||
$registered[$class][$env] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $registered;
|
||||
}
|
||||
|
||||
private function prepareBundles(array $bundles): array
|
||||
{
|
||||
foreach ($bundles as $class => $envs) {
|
||||
$bundles[ltrim($class, '\\')] = $envs;
|
||||
}
|
||||
|
||||
return $bundles;
|
||||
}
|
||||
|
||||
private function load(string $file): array
|
||||
{
|
||||
$bundles = file_exists($file) ? (require $file) : [];
|
||||
if (!\is_array($bundles)) {
|
||||
$bundles = [];
|
||||
}
|
||||
|
||||
return $bundles;
|
||||
}
|
||||
|
||||
private function dump(string $file, array $bundles)
|
||||
{
|
||||
$contents = $this->buildContents($bundles);
|
||||
|
||||
if (!is_dir(\dirname($file))) {
|
||||
mkdir(\dirname($file), 0777, true);
|
||||
}
|
||||
|
||||
file_put_contents($file, $contents);
|
||||
|
||||
if (\function_exists('opcache_invalidate')) {
|
||||
@opcache_invalidate($file);
|
||||
}
|
||||
}
|
||||
|
||||
private function buildContents(array $bundles): string
|
||||
{
|
||||
$contents = "<?php\n\nreturn [\n";
|
||||
foreach ($bundles as $class => $envs) {
|
||||
$contents .= " $class::class => [";
|
||||
foreach ($envs as $env => $value) {
|
||||
$booleanValue = var_export($value, true);
|
||||
$contents .= "'$env' => $booleanValue, ";
|
||||
}
|
||||
$contents = substr($contents, 0, -2)."],\n";
|
||||
}
|
||||
$contents .= "];\n";
|
||||
|
||||
return $contents;
|
||||
}
|
||||
|
||||
private function getConfFile(): string
|
||||
{
|
||||
return $this->options->get('root-dir').'/'.$this->getLocalConfFile();
|
||||
}
|
||||
|
||||
private function getLocalConfFile(): string
|
||||
{
|
||||
return $this->options->expandTargetDir('%CONFIG_DIR%/bundles.php');
|
||||
}
|
||||
}
|
||||
73
vendor/symfony/flex/src/Configurator/ComposerCommandsConfigurator.php
vendored
Normal file
73
vendor/symfony/flex/src/Configurator/ComposerCommandsConfigurator.php
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
<?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 Marcin Morawski <marcin@morawskim.pl>
|
||||
*/
|
||||
class ComposerCommandsConfigurator 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());
|
||||
|
||||
$manipulator = new JsonManipulator(file_get_contents($json->getPath()));
|
||||
foreach ($scripts as $key => $command) {
|
||||
$manipulator->removeSubNode('scripts', $key);
|
||||
}
|
||||
|
||||
file_put_contents($json->getPath(), $manipulator->getContents());
|
||||
}
|
||||
|
||||
public function update(RecipeUpdate $recipeUpdate, array $originalConfig, array $newConfig): void
|
||||
{
|
||||
$json = new JsonFile(Factory::getComposerFile());
|
||||
$jsonPath = $json->getPath();
|
||||
if (str_starts_with($jsonPath, $recipeUpdate->getRootDir())) {
|
||||
$jsonPath = substr($jsonPath, \strlen($recipeUpdate->getRootDir()));
|
||||
}
|
||||
$jsonPath = ltrim($jsonPath, '/\\');
|
||||
|
||||
$recipeUpdate->setOriginalFile(
|
||||
$jsonPath,
|
||||
$this->configureScripts($originalConfig, $json)
|
||||
);
|
||||
$recipeUpdate->setNewFile(
|
||||
$jsonPath,
|
||||
$this->configureScripts($newConfig, $json)
|
||||
);
|
||||
}
|
||||
|
||||
private function configureScripts(array $scripts, JsonFile $json): string
|
||||
{
|
||||
$manipulator = new JsonManipulator(file_get_contents($json->getPath()));
|
||||
foreach ($scripts as $cmdName => $script) {
|
||||
$manipulator->addSubNode('scripts', $cmdName, $script);
|
||||
}
|
||||
|
||||
return $manipulator->getContents();
|
||||
}
|
||||
}
|
||||
79
vendor/symfony/flex/src/Configurator/ComposerScriptsConfigurator.php
vendored
Normal file
79
vendor/symfony/flex/src/Configurator/ComposerScriptsConfigurator.php
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
<?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 = $json->getPath();
|
||||
if (str_starts_with($jsonPath, $recipeUpdate->getRootDir())) {
|
||||
$jsonPath = substr($jsonPath, \strlen($recipeUpdate->getRootDir()));
|
||||
}
|
||||
$jsonPath = ltrim($jsonPath, '/\\');
|
||||
|
||||
$recipeUpdate->setOriginalFile(
|
||||
$jsonPath,
|
||||
$this->configureScripts($originalConfig, $json)
|
||||
);
|
||||
$recipeUpdate->setNewFile(
|
||||
$jsonPath,
|
||||
$this->configureScripts($newConfig, $json)
|
||||
);
|
||||
}
|
||||
|
||||
private function configureScripts(array $scripts, JsonFile $json): string
|
||||
{
|
||||
$jsonContents = $json->read();
|
||||
$autoScripts = $jsonContents['scripts']['auto-scripts'] ?? [];
|
||||
$autoScripts = array_merge($autoScripts, $scripts);
|
||||
|
||||
$manipulator = new JsonManipulator(file_get_contents($json->getPath()));
|
||||
$manipulator->addSubNode('scripts', 'auto-scripts', $autoScripts);
|
||||
|
||||
return $manipulator->getContents();
|
||||
}
|
||||
}
|
||||
164
vendor/symfony/flex/src/Configurator/ContainerConfigurator.php
vendored
Normal file
164
vendor/symfony/flex/src/Configurator/ContainerConfigurator.php
vendored
Normal file
@@ -0,0 +1,164 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex\Configurator;
|
||||
|
||||
use Symfony\Flex\Lock;
|
||||
use Symfony\Flex\Recipe;
|
||||
use Symfony\Flex\Update\RecipeUpdate;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class ContainerConfigurator extends AbstractConfigurator
|
||||
{
|
||||
public function configure(Recipe $recipe, $parameters, Lock $lock, array $options = [])
|
||||
{
|
||||
$this->write('Setting parameters');
|
||||
$contents = $this->configureParameters($parameters);
|
||||
|
||||
if (null !== $contents) {
|
||||
file_put_contents($this->options->get('root-dir').'/'.$this->getServicesPath(), $contents);
|
||||
}
|
||||
}
|
||||
|
||||
public function unconfigure(Recipe $recipe, $parameters, Lock $lock)
|
||||
{
|
||||
$this->write('Unsetting parameters');
|
||||
$target = $this->options->get('root-dir').'/'.$this->getServicesPath();
|
||||
$lines = $this->removeParametersFromLines(file($target), $parameters);
|
||||
file_put_contents($target, implode('', $lines));
|
||||
}
|
||||
|
||||
public function update(RecipeUpdate $recipeUpdate, array $originalConfig, array $newConfig): void
|
||||
{
|
||||
$recipeUpdate->setOriginalFile(
|
||||
$this->getServicesPath(),
|
||||
$this->configureParameters($originalConfig, true)
|
||||
);
|
||||
|
||||
// for the new file, we need to update any values *and* remove any removed values
|
||||
$removedParameters = [];
|
||||
foreach ($originalConfig as $name => $value) {
|
||||
if (!isset($newConfig[$name])) {
|
||||
$removedParameters[$name] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$updatedFile = $this->configureParameters($newConfig, true);
|
||||
$lines = $this->removeParametersFromLines(explode("\n", $updatedFile), $removedParameters);
|
||||
|
||||
$recipeUpdate->setNewFile(
|
||||
$this->getServicesPath(),
|
||||
implode("\n", $lines)
|
||||
);
|
||||
}
|
||||
|
||||
private function configureParameters(array $parameters, bool $update = false): string
|
||||
{
|
||||
$target = $this->options->get('root-dir').'/'.$this->getServicesPath();
|
||||
$endAt = 0;
|
||||
$isParameters = false;
|
||||
$lines = [];
|
||||
foreach (file($target) as $i => $line) {
|
||||
$lines[] = $line;
|
||||
if (!$isParameters && !preg_match('/^parameters:/', $line)) {
|
||||
continue;
|
||||
}
|
||||
if (!$isParameters) {
|
||||
$isParameters = true;
|
||||
continue;
|
||||
}
|
||||
if (!preg_match('/^\s+.*/', $line) && '' !== trim($line)) {
|
||||
$endAt = $i - 1;
|
||||
$isParameters = false;
|
||||
continue;
|
||||
}
|
||||
foreach ($parameters as $key => $value) {
|
||||
$matches = [];
|
||||
if (preg_match(\sprintf('/^\s+%s\:/', preg_quote($key, '/')), $line, $matches)) {
|
||||
if ($update) {
|
||||
$lines[$i] = substr($line, 0, \strlen($matches[0])).' '.str_replace("'", "''", $value)."\n";
|
||||
}
|
||||
|
||||
unset($parameters[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($parameters) {
|
||||
$parametersLines = [];
|
||||
if (!$endAt) {
|
||||
$parametersLines[] = "parameters:\n";
|
||||
}
|
||||
foreach ($parameters as $key => $value) {
|
||||
if (\is_array($value)) {
|
||||
$parametersLines[] = \sprintf(" %s:\n%s", $key, $this->dumpYaml(2, $value));
|
||||
continue;
|
||||
}
|
||||
$parametersLines[] = \sprintf(" %s: '%s'%s", $key, str_replace("'", "''", $value), "\n");
|
||||
}
|
||||
if (!$endAt) {
|
||||
$parametersLines[] = "\n";
|
||||
}
|
||||
array_splice($lines, $endAt, 0, $parametersLines);
|
||||
}
|
||||
|
||||
return implode('', $lines);
|
||||
}
|
||||
|
||||
private function removeParametersFromLines(array $sourceLines, array $parameters): array
|
||||
{
|
||||
$lines = [];
|
||||
foreach ($sourceLines as $line) {
|
||||
if ($this->removeParameters(1, $parameters, $line)) {
|
||||
continue;
|
||||
}
|
||||
$lines[] = $line;
|
||||
}
|
||||
|
||||
return $lines;
|
||||
}
|
||||
|
||||
private function removeParameters($level, $params, $line)
|
||||
{
|
||||
foreach ($params as $key => $value) {
|
||||
if (\is_array($value) && $this->removeParameters($level + 1, $value, $line)) {
|
||||
return true;
|
||||
}
|
||||
if (preg_match(\sprintf('/^(\s{%d}|\t{%d})+%s\:/', 4 * $level, $level, preg_quote($key, '/')), $line)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function dumpYaml($level, $array): string
|
||||
{
|
||||
$line = '';
|
||||
foreach ($array as $key => $value) {
|
||||
$line .= str_repeat(' ', $level);
|
||||
if (!\is_array($value)) {
|
||||
$line .= \sprintf("%s: '%s'\n", $key, str_replace("'", "''", $value));
|
||||
continue;
|
||||
}
|
||||
$line .= \sprintf("%s:\n", $key).$this->dumpYaml($level + 1, $value);
|
||||
}
|
||||
|
||||
return $line;
|
||||
}
|
||||
|
||||
private function getServicesPath(): string
|
||||
{
|
||||
return $this->options->expandTargetDir('%CONFIG_DIR%/services.yaml');
|
||||
}
|
||||
}
|
||||
168
vendor/symfony/flex/src/Configurator/CopyFromPackageConfigurator.php
vendored
Normal file
168
vendor/symfony/flex/src/Configurator/CopyFromPackageConfigurator.php
vendored
Normal file
@@ -0,0 +1,168 @@
|
||||
<?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]), $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').'/'.$this->options->expandTargetDir($target);
|
||||
if (is_dir($source)) {
|
||||
// directory will be created when a file is copied to it
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->options->shouldWriteFile($target, $options['force'] ?? false, $options['assumeYesForPrompts'] ?? false)) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
149
vendor/symfony/flex/src/Configurator/CopyFromRecipeConfigurator.php
vendored
Normal file
149
vendor/symfony/flex/src/Configurator/CopyFromRecipeConfigurator.php
vendored
Normal file
@@ -0,0 +1,149 @@
|
||||
<?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');
|
||||
$rootDir = $this->options->get('root-dir');
|
||||
|
||||
foreach ($this->options->getRemovableFiles($recipe, $lock) as $file) {
|
||||
if ('.git' !== $file) { // never remove the main Git directory, even if it was created by a recipe
|
||||
$this->removeFile($this->path->concatenate([$rootDir, $file]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function update(RecipeUpdate $recipeUpdate, array $originalConfig, array $newConfig): void
|
||||
{
|
||||
foreach ($recipeUpdate->getOriginalRecipe()->getFiles() as $filename => $data) {
|
||||
$filename = $this->resolveTargetFolder($filename, $originalConfig);
|
||||
$recipeUpdate->setOriginalFile($filename, $data['contents']);
|
||||
}
|
||||
|
||||
$files = [];
|
||||
foreach ($recipeUpdate->getNewRecipe()->getFiles() as $filename => $data) {
|
||||
$filename = $this->resolveTargetFolder($filename, $newConfig);
|
||||
$recipeUpdate->setNewFile($filename, $data['contents']);
|
||||
|
||||
$files[] = $this->getLocalFilePath($recipeUpdate->getRootDir(), $filename);
|
||||
}
|
||||
|
||||
$recipeUpdate->getLock()->add($recipeUpdate->getPackageName(), ['files' => $files]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $config
|
||||
*/
|
||||
private function resolveTargetFolder(string $path, array $config): string
|
||||
{
|
||||
foreach ($config as $key => $target) {
|
||||
if (str_starts_with($path, $key)) {
|
||||
return $this->options->expandTargetDir($target).substr($path, \strlen($key));
|
||||
}
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
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 (str_starts_with($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
|
||||
{
|
||||
$basePath = $options['root-dir'] ?? '.';
|
||||
$copiedFile = $this->getLocalFilePath($basePath, $to);
|
||||
|
||||
if (!$this->options->shouldWriteFile($to, $options['force'] ?? false, $options['assumeYesForPrompts'] ?? false)) {
|
||||
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 removeFile(string $to)
|
||||
{
|
||||
if (!file_exists($to)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@unlink($to);
|
||||
$this->write(\sprintf(' Removed <fg=green>"%s"</>', $this->path->relativize($to)));
|
||||
|
||||
if (0 === \count(glob(\dirname($to).'/*', \GLOB_NOSORT))) {
|
||||
@rmdir(\dirname($to));
|
||||
}
|
||||
}
|
||||
|
||||
private function getLocalFilePath(string $basePath, $destination): string
|
||||
{
|
||||
return str_replace($basePath.\DIRECTORY_SEPARATOR, '', $destination);
|
||||
}
|
||||
}
|
||||
404
vendor/symfony/flex/src/Configurator/DockerComposeConfigurator.php
vendored
Normal file
404
vendor/symfony/flex/src/Configurator/DockerComposeConfigurator.php
vendored
Normal file
@@ -0,0 +1,404 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex\Configurator;
|
||||
|
||||
use Composer\Composer;
|
||||
use Composer\Factory;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Json\JsonFile;
|
||||
use Composer\Json\JsonManipulator;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
use Symfony\Flex\Lock;
|
||||
use Symfony\Flex\Options;
|
||||
use Symfony\Flex\Recipe;
|
||||
use Symfony\Flex\Update\RecipeUpdate;
|
||||
|
||||
/**
|
||||
* Adds services and volumes to compose.yaml file.
|
||||
*
|
||||
* @author Kévin Dunglas <kevin@dunglas.dev>
|
||||
*/
|
||||
class DockerComposeConfigurator extends AbstractConfigurator
|
||||
{
|
||||
private $filesystem;
|
||||
|
||||
public static $configureDockerRecipes;
|
||||
|
||||
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 = filter_var($dockerPreference, \FILTER_VALIDATE_BOOLEAN);
|
||||
|
||||
return self::$configureDockerRecipes;
|
||||
}
|
||||
|
||||
if ('install' !== $recipe->getJob()) {
|
||||
// default to not configuring
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isset($_SERVER['SYMFONY_DOCKER'])) {
|
||||
$answer = self::askDockerSupport($io, $recipe);
|
||||
} elseif (filter_var($_SERVER['SYMFONY_DOCKER'], \FILTER_VALIDATE_BOOLEAN)) {
|
||||
$answer = 'p';
|
||||
} else {
|
||||
$answer = 'x';
|
||||
}
|
||||
|
||||
if ('n' === $answer) {
|
||||
self::$configureDockerRecipes = false;
|
||||
|
||||
return self::$configureDockerRecipes;
|
||||
}
|
||||
if ('y' === $answer) {
|
||||
self::$configureDockerRecipes = true;
|
||||
|
||||
return self::$configureDockerRecipes;
|
||||
}
|
||||
|
||||
// yes or no permanently
|
||||
self::$configureDockerRecipes = 'p' === $answer;
|
||||
$json = new JsonFile(Factory::getComposerFile());
|
||||
$manipulator = new JsonManipulator(file_get_contents($json->getPath()));
|
||||
$manipulator->addSubNode('extra', 'symfony.docker', self::$configureDockerRecipes);
|
||||
file_put_contents($json->getPath(), $manipulator->getContents());
|
||||
|
||||
return self::$configureDockerRecipes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the config and return the name of the main Docker Compose file if applicable.
|
||||
*/
|
||||
private function normalizeConfig(array $config): array
|
||||
{
|
||||
foreach ($config as $key => $val) {
|
||||
// Support for the short recipe syntax that modifies compose.yaml only
|
||||
if (isset($val[0])) {
|
||||
return ['compose.yaml' => $config];
|
||||
}
|
||||
|
||||
if (!str_starts_with($key, 'docker-')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the recipe still use the legacy "docker-compose.yml" names, remove the "docker-" prefix and change the extension
|
||||
$newKey = pathinfo(substr($key, 7), \PATHINFO_FILENAME).'.yaml';
|
||||
$config[$newKey] = $val;
|
||||
unset($config[$key]);
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the Docker Compose file according to these rules: https://docs.docker.com/compose/reference/envvars/#compose_file.
|
||||
*/
|
||||
private function findDockerComposeFile(string $rootDir, string $file): ?string
|
||||
{
|
||||
if (isset($_SERVER['COMPOSE_FILE'])) {
|
||||
$filenameToFind = pathinfo($file, \PATHINFO_FILENAME);
|
||||
$separator = $_SERVER['COMPOSE_PATH_SEPARATOR'] ?? ('\\' === \DIRECTORY_SEPARATOR ? ';' : ':');
|
||||
|
||||
$files = explode($separator, $_SERVER['COMPOSE_FILE']);
|
||||
foreach ($files as $f) {
|
||||
$filename = pathinfo($f, \PATHINFO_FILENAME);
|
||||
if ($filename !== $filenameToFind && "docker-$filenameToFind" !== $filename) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$this->filesystem->isAbsolutePath($f)) {
|
||||
$f = realpath(\sprintf('%s/%s', $rootDir, $f));
|
||||
}
|
||||
|
||||
if ($this->filesystem->exists($f)) {
|
||||
return $f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// COMPOSE_FILE not set, or doesn't contain the file we're looking for
|
||||
$dir = $rootDir;
|
||||
do {
|
||||
if (
|
||||
$this->filesystem->exists($dockerComposeFile = \sprintf('%s/%s', $dir, $file))
|
||||
// Test with the ".yml" extension if the file doesn't end up with ".yaml"
|
||||
|| $this->filesystem->exists($dockerComposeFile = substr($dockerComposeFile, 0, -3).'ml')
|
||||
// Test with the legacy "docker-" suffix if "compose.ya?ml" doesn't exist
|
||||
|| $this->filesystem->exists($dockerComposeFile = \sprintf('%s/docker-%s', $dir, $file))
|
||||
|| $this->filesystem->exists($dockerComposeFile = substr($dockerComposeFile, 0, -3).'ml')
|
||||
) {
|
||||
return $dockerComposeFile;
|
||||
}
|
||||
|
||||
$previousDir = $dir;
|
||||
$dir = \dirname($dir);
|
||||
} while ($dir !== $previousDir);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function parse($level, $indent, $services): string
|
||||
{
|
||||
$line = '';
|
||||
foreach ($services as $key => $value) {
|
||||
$line .= str_repeat(' ', $indent * $level);
|
||||
if (!\is_array($value)) {
|
||||
if (\is_string($key)) {
|
||||
$line .= \sprintf('%s:', $key);
|
||||
}
|
||||
$line .= \sprintf("%s\n", $value);
|
||||
continue;
|
||||
}
|
||||
$line .= \sprintf("%s:\n", $key).$this->parse($level + 1, $indent, $value);
|
||||
}
|
||||
|
||||
return $line;
|
||||
}
|
||||
|
||||
private function configureDockerCompose(Recipe $recipe, array $config, bool $update): void
|
||||
{
|
||||
$rootDir = $this->options->get('root-dir');
|
||||
foreach ($this->normalizeConfig($config) as $file => $extra) {
|
||||
$dockerComposeFile = $this->findDockerComposeFile($rootDir, $file);
|
||||
if (null === $dockerComposeFile) {
|
||||
$dockerComposeFile = $rootDir.'/'.$file;
|
||||
file_put_contents($dockerComposeFile, '');
|
||||
$this->write(\sprintf(' Created <fg=green>"%s"</>', $file));
|
||||
}
|
||||
|
||||
if (!$update && $this->isFileMarked($recipe, $dockerComposeFile)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->write(\sprintf('Adding Docker Compose definitions to "%s"', $dockerComposeFile));
|
||||
|
||||
$offset = 2;
|
||||
$node = null;
|
||||
$endAt = [];
|
||||
$startAt = [];
|
||||
$lines = [];
|
||||
$nodesLines = [];
|
||||
foreach (file($dockerComposeFile) as $i => $line) {
|
||||
$lines[] = $line;
|
||||
$ltrimedLine = ltrim($line, ' ');
|
||||
if (null !== $node) {
|
||||
$nodesLines[$node][$i] = $line;
|
||||
}
|
||||
|
||||
// Skip blank lines and comments
|
||||
if (('' !== $ltrimedLine && str_starts_with($ltrimedLine, '#')) || '' === trim($line)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract Docker Compose keys (usually "services" and "volumes")
|
||||
if (!preg_match('/^[\'"]?([a-zA-Z0-9]+)[\'"]?:\s*$/', $line, $matches)) {
|
||||
// Detect indentation to use
|
||||
$offestLine = \strlen($line) - \strlen($ltrimedLine);
|
||||
if ($offset > $offestLine && 0 !== $offestLine) {
|
||||
$offset = $offestLine;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Keep end in memory (check break line on previous line)
|
||||
$endAt[$node] = !$i || '' !== trim($lines[$i - 1]) ? $i : $i - 1;
|
||||
$node = $matches[1];
|
||||
if (!isset($nodesLines[$node])) {
|
||||
$nodesLines[$node] = [];
|
||||
}
|
||||
if (!isset($startAt[$node])) {
|
||||
// the section contents starts at the next line
|
||||
$startAt[$node] = $i + 1;
|
||||
}
|
||||
}
|
||||
$endAt[$node] = \count($lines) + 1;
|
||||
|
||||
foreach ($extra as $key => $value) {
|
||||
if (isset($endAt[$key])) {
|
||||
$data = $this->markData($recipe, $this->parse(1, $offset, $value));
|
||||
$updatedContents = $this->updateDataString(implode('', $nodesLines[$key]), $data);
|
||||
if (null === $updatedContents) {
|
||||
// not an update: just add to section
|
||||
array_splice($lines, $endAt[$key], 0, $data);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$originalEndAt = $endAt[$key];
|
||||
$length = $endAt[$key] - $startAt[$key];
|
||||
array_splice($lines, $startAt[$key], $length, ltrim($updatedContents, "\n"));
|
||||
|
||||
// reset any start/end positions after this to the new positions
|
||||
foreach ($startAt as $sectionKey => $at) {
|
||||
if ($at > $originalEndAt) {
|
||||
$startAt[$sectionKey] = $at - $length - 1;
|
||||
}
|
||||
}
|
||||
foreach ($endAt as $sectionKey => $at) {
|
||||
if ($at > $originalEndAt) {
|
||||
$endAt[$sectionKey] = $at - $length;
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$lines[] = \sprintf("\n%s:", $key);
|
||||
$lines[] = $this->markData($recipe, $this->parse(1, $offset, $value));
|
||||
}
|
||||
|
||||
file_put_contents($dockerComposeFile, implode('', $lines));
|
||||
}
|
||||
}
|
||||
|
||||
private function getContentsAfterApplyingRecipe(string $rootDir, Recipe $recipe, array $config): array
|
||||
{
|
||||
if (0 === \count($config)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$files = array_filter(array_map(function ($file) use ($rootDir) {
|
||||
return $this->findDockerComposeFile($rootDir, $file);
|
||||
}, array_keys($config)));
|
||||
|
||||
$originalContents = [];
|
||||
foreach ($files as $file) {
|
||||
$originalContents[$file] = file_exists($file) ? file_get_contents($file) : null;
|
||||
}
|
||||
|
||||
$this->configureDockerCompose(
|
||||
$recipe,
|
||||
$config,
|
||||
true
|
||||
);
|
||||
|
||||
$updatedContents = [];
|
||||
foreach ($files as $file) {
|
||||
$localPath = $file;
|
||||
if (str_starts_with($file, $rootDir)) {
|
||||
$localPath = substr($file, \strlen($rootDir) + 1);
|
||||
}
|
||||
$localPath = ltrim($localPath, '/\\');
|
||||
$updatedContents[$localPath] = file_exists($file) ? file_get_contents($file) : null;
|
||||
}
|
||||
|
||||
foreach ($originalContents as $file => $contents) {
|
||||
if (null === $contents) {
|
||||
if (file_exists($file)) {
|
||||
unlink($file);
|
||||
}
|
||||
} else {
|
||||
file_put_contents($file, $contents);
|
||||
}
|
||||
}
|
||||
|
||||
return $updatedContents;
|
||||
}
|
||||
|
||||
private static function askDockerSupport(IOInterface $io, Recipe $recipe): string
|
||||
{
|
||||
$warning = $io->isInteractive() ? 'WARNING' : 'IGNORING';
|
||||
$io->writeError(\sprintf(' - <warning> %s </> %s', $warning, $recipe->getFormattedOrigin()));
|
||||
$question = ' The recipe for this package contains some Docker configuration.
|
||||
|
||||
This may create/update <comment>compose.yaml</comment> or update <comment>Dockerfile</comment> (if it exists).
|
||||
|
||||
Do you want to include Docker configuration from recipes?
|
||||
[<comment>y</>] Yes
|
||||
[<comment>n</>] No
|
||||
[<comment>p</>] Yes permanently, never ask again for this project
|
||||
[<comment>x</>] No permanently, never ask again for this project
|
||||
(defaults to <comment>y</>): ';
|
||||
|
||||
return $io->askAndValidate(
|
||||
$question,
|
||||
function ($value) {
|
||||
if (null === $value) {
|
||||
return 'y';
|
||||
}
|
||||
$value = strtolower($value[0]);
|
||||
if (!\in_array($value, ['y', 'n', 'p', 'x'], true)) {
|
||||
throw new \InvalidArgumentException('Invalid choice.');
|
||||
}
|
||||
|
||||
return $value;
|
||||
},
|
||||
null,
|
||||
'y'
|
||||
);
|
||||
}
|
||||
}
|
||||
125
vendor/symfony/flex/src/Configurator/DockerfileConfigurator.php
vendored
Normal file
125
vendor/symfony/flex/src/Configurator/DockerfileConfigurator.php
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex\Configurator;
|
||||
|
||||
use Symfony\Flex\Lock;
|
||||
use Symfony\Flex\Recipe;
|
||||
use Symfony\Flex\Update\RecipeUpdate;
|
||||
|
||||
/**
|
||||
* Adds commands to a Dockerfile.
|
||||
*
|
||||
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||
*/
|
||||
class DockerfileConfigurator extends AbstractConfigurator
|
||||
{
|
||||
public function configure(Recipe $recipe, $config, Lock $lock, array $options = [])
|
||||
{
|
||||
if (!DockerComposeConfigurator::shouldConfigureDockerRecipe($this->composer, $this->io, $recipe)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->configureDockerfile($recipe, $config, $options['force'] ?? false);
|
||||
}
|
||||
|
||||
public function unconfigure(Recipe $recipe, $config, Lock $lock)
|
||||
{
|
||||
if (!file_exists($dockerfile = $this->options->get('root-dir').'/Dockerfile')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$name = $recipe->getName();
|
||||
$contents = preg_replace(\sprintf('{%s+###> %s ###.*?###< %s ###%s+}s', "\n", $name, $name, "\n"), "\n", file_get_contents($dockerfile), -1, $count);
|
||||
if (!$count) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->write('Removing Dockerfile entries');
|
||||
file_put_contents($dockerfile, ltrim($contents, "\n"));
|
||||
}
|
||||
|
||||
public function update(RecipeUpdate $recipeUpdate, array $originalConfig, array $newConfig): void
|
||||
{
|
||||
if (!DockerComposeConfigurator::shouldConfigureDockerRecipe($this->composer, $this->io, $recipeUpdate->getNewRecipe())) {
|
||||
return;
|
||||
}
|
||||
|
||||
$recipeUpdate->setOriginalFile(
|
||||
'Dockerfile',
|
||||
$this->getContentsAfterApplyingRecipe($recipeUpdate->getOriginalRecipe(), $originalConfig)
|
||||
);
|
||||
|
||||
$recipeUpdate->setNewFile(
|
||||
'Dockerfile',
|
||||
$this->getContentsAfterApplyingRecipe($recipeUpdate->getNewRecipe(), $newConfig)
|
||||
);
|
||||
}
|
||||
|
||||
private function configureDockerfile(Recipe $recipe, array $config, bool $update, bool $writeOutput = true): void
|
||||
{
|
||||
$dockerfile = $this->options->get('root-dir').'/Dockerfile';
|
||||
if (!file_exists($dockerfile) || (!$update && $this->isFileMarked($recipe, $dockerfile))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($writeOutput) {
|
||||
$this->write('Adding Dockerfile entries');
|
||||
}
|
||||
|
||||
$data = ltrim($this->markData($recipe, implode("\n", $config)), "\n");
|
||||
if ($this->updateData($dockerfile, $data)) {
|
||||
// done! Existing spot updated
|
||||
return;
|
||||
}
|
||||
|
||||
$lines = [];
|
||||
foreach (file($dockerfile) as $line) {
|
||||
$lines[] = $line;
|
||||
if (!preg_match('/^###> recipes ###$/', $line)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$lines[] = $data;
|
||||
}
|
||||
|
||||
file_put_contents($dockerfile, implode('', $lines));
|
||||
}
|
||||
|
||||
private function getContentsAfterApplyingRecipe(Recipe $recipe, array $config): ?string
|
||||
{
|
||||
if (0 === \count($config)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$dockerfile = $this->options->get('root-dir').'/Dockerfile';
|
||||
$originalContents = file_exists($dockerfile) ? file_get_contents($dockerfile) : null;
|
||||
|
||||
$this->configureDockerfile(
|
||||
$recipe,
|
||||
$config,
|
||||
true,
|
||||
false
|
||||
);
|
||||
|
||||
$updatedContents = file_exists($dockerfile) ? file_get_contents($dockerfile) : null;
|
||||
|
||||
if (null === $originalContents) {
|
||||
if (file_exists($dockerfile)) {
|
||||
unlink($dockerfile);
|
||||
}
|
||||
} else {
|
||||
file_put_contents($dockerfile, $originalContents);
|
||||
}
|
||||
|
||||
return $updatedContents;
|
||||
}
|
||||
}
|
||||
50
vendor/symfony/flex/src/Configurator/DotenvConfigurator.php
vendored
Normal file
50
vendor/symfony/flex/src/Configurator/DotenvConfigurator.php
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex\Configurator;
|
||||
|
||||
use Symfony\Flex\Lock;
|
||||
use Symfony\Flex\Recipe;
|
||||
use Symfony\Flex\Update\RecipeUpdate;
|
||||
|
||||
class DotenvConfigurator extends AbstractConfigurator
|
||||
{
|
||||
public function configure(Recipe $recipe, $vars, Lock $lock, array $options = [])
|
||||
{
|
||||
foreach ($vars as $suffix => $vars) {
|
||||
$configurator = new EnvConfigurator($this->composer, $this->io, $this->options, $suffix);
|
||||
$configurator->configure($recipe, $vars, $lock, $options);
|
||||
}
|
||||
}
|
||||
|
||||
public function unconfigure(Recipe $recipe, $vars, Lock $lock)
|
||||
{
|
||||
foreach ($vars as $suffix => $vars) {
|
||||
$configurator = new EnvConfigurator($this->composer, $this->io, $this->options, $suffix);
|
||||
$configurator->unconfigure($recipe, $vars, $lock);
|
||||
}
|
||||
}
|
||||
|
||||
public function update(RecipeUpdate $recipeUpdate, array $originalConfig, array $newConfig): void
|
||||
{
|
||||
foreach ($originalConfig as $suffix => $vars) {
|
||||
$configurator = new EnvConfigurator($this->composer, $this->io, $this->options, $suffix);
|
||||
$configurator->update($recipeUpdate, $vars, $newConfig[$suffix] ?? []);
|
||||
}
|
||||
|
||||
foreach ($newConfig as $suffix => $vars) {
|
||||
if (!isset($originalConfig[$suffix])) {
|
||||
$configurator = new EnvConfigurator($this->composer, $this->io, $this->options, $suffix);
|
||||
$configurator->update($recipeUpdate, [], $vars);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
295
vendor/symfony/flex/src/Configurator/EnvConfigurator.php
vendored
Normal file
295
vendor/symfony/flex/src/Configurator/EnvConfigurator.php
vendored
Normal file
@@ -0,0 +1,295 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex\Configurator;
|
||||
|
||||
use Composer\Composer;
|
||||
use Composer\IO\IOInterface;
|
||||
use Symfony\Flex\Lock;
|
||||
use Symfony\Flex\Options;
|
||||
use Symfony\Flex\Recipe;
|
||||
use Symfony\Flex\Update\RecipeUpdate;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class EnvConfigurator extends AbstractConfigurator
|
||||
{
|
||||
private string $suffix;
|
||||
|
||||
public function __construct(Composer $composer, IOInterface $io, Options $options, string $suffix = '')
|
||||
{
|
||||
parent::__construct($composer, $io, $options);
|
||||
$this->suffix = $suffix;
|
||||
}
|
||||
|
||||
public function configure(Recipe $recipe, $vars, Lock $lock, array $options = [])
|
||||
{
|
||||
$this->write('Adding environment variable defaults'.('' === $this->suffix ? '' : ' ('.$this->suffix.')'));
|
||||
|
||||
$this->configureEnvDist($recipe, $vars, $options['force'] ?? false);
|
||||
|
||||
if ('' !== $this->suffix) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!file_exists($this->options->get('root-dir').'/'.($this->options->get('runtime')['dotenv_path'] ?? '.env').'.test')) {
|
||||
$this->configurePhpUnit($recipe, $vars, $options['force'] ?? false);
|
||||
}
|
||||
}
|
||||
|
||||
public function unconfigure(Recipe $recipe, $vars, Lock $lock)
|
||||
{
|
||||
$this->unconfigureEnvFiles($recipe, $vars);
|
||||
$this->unconfigurePhpUnit($recipe, $vars);
|
||||
}
|
||||
|
||||
public function update(RecipeUpdate $recipeUpdate, array $originalConfig, array $newConfig): void
|
||||
{
|
||||
$recipeUpdate->addOriginalFiles(
|
||||
$this->getContentsAfterApplyingRecipe($recipeUpdate->getRootDir(), $recipeUpdate->getOriginalRecipe(), $originalConfig)
|
||||
);
|
||||
|
||||
$recipeUpdate->addNewFiles(
|
||||
$this->getContentsAfterApplyingRecipe($recipeUpdate->getRootDir(), $recipeUpdate->getNewRecipe(), $newConfig)
|
||||
);
|
||||
}
|
||||
|
||||
private function configureEnvDist(Recipe $recipe, $vars, bool $update)
|
||||
{
|
||||
$dotenvPath = $this->options->get('runtime')['dotenv_path'] ?? '.env';
|
||||
$files = '' === $this->suffix ? [$dotenvPath.'.dist', $dotenvPath] : [$dotenvPath.'.'.$this->suffix];
|
||||
|
||||
foreach ($files as $file) {
|
||||
$env = $this->options->get('root-dir').'/'.$file;
|
||||
if (!is_file($env)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$update && $this->isFileMarked($recipe, $env)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$data = '';
|
||||
foreach ($vars as $key => $value) {
|
||||
$existingValue = $update ? $this->findExistingValue($key, $env, $recipe) : null;
|
||||
$value = $this->evaluateValue($value, $existingValue);
|
||||
if ('#' === $key[0] && is_numeric(substr($key, 1))) {
|
||||
if ('' === $value) {
|
||||
$data .= "#\n";
|
||||
} else {
|
||||
$data .= '# '.$value."\n";
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = $this->options->expandTargetDir($value);
|
||||
if (false !== strpbrk($value, " \t\n&!\"")) {
|
||||
$value = '"'.str_replace(['\\', '"', "\t", "\n"], ['\\\\', '\\"', '\t', '\n'], $value).'"';
|
||||
}
|
||||
$data .= "$key=$value\n";
|
||||
}
|
||||
$data = $this->markData($recipe, $data);
|
||||
|
||||
if (!$this->updateData($env, $data)) {
|
||||
file_put_contents($env, $data, \FILE_APPEND);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function configurePhpUnit(Recipe $recipe, $vars, bool $update)
|
||||
{
|
||||
foreach (['phpunit.xml.dist', 'phpunit.dist.xml', 'phpunit.xml'] as $file) {
|
||||
$phpunit = $this->options->get('root-dir').'/'.$file;
|
||||
if (!is_file($phpunit)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$update && $this->isFileXmlMarked($recipe, $phpunit)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$data = '';
|
||||
foreach ($vars as $key => $value) {
|
||||
$value = $this->evaluateValue($value);
|
||||
if ('#' === $key[0]) {
|
||||
if (is_numeric(substr($key, 1))) {
|
||||
$doc = new \DOMDocument();
|
||||
$data .= ' '.$doc->saveXML($doc->createComment(' '.$value.' '))."\n";
|
||||
} else {
|
||||
$value = $this->options->expandTargetDir($value);
|
||||
$doc = new \DOMDocument();
|
||||
$fragment = $doc->createElement('env');
|
||||
$fragment->setAttribute('name', substr($key, 1));
|
||||
$fragment->setAttribute('value', $value);
|
||||
$data .= ' '.str_replace(['<', '/>'], ['<!-- ', ' -->'], $doc->saveXML($fragment))."\n";
|
||||
}
|
||||
} else {
|
||||
$value = $this->options->expandTargetDir($value);
|
||||
$doc = new \DOMDocument();
|
||||
$fragment = $doc->createElement('env');
|
||||
$fragment->setAttribute('name', $key);
|
||||
$fragment->setAttribute('value', $value);
|
||||
$data .= ' '.$doc->saveXML($fragment)."\n";
|
||||
}
|
||||
}
|
||||
$data = $this->markXmlData($recipe, $data);
|
||||
|
||||
if (!$this->updateData($phpunit, $data)) {
|
||||
file_put_contents($phpunit, preg_replace('{^(\s+</php>)}m', $data.'$1', file_get_contents($phpunit)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function unconfigureEnvFiles(Recipe $recipe, $vars)
|
||||
{
|
||||
$dotenvPath = $this->options->get('runtime')['dotenv_path'] ?? '.env';
|
||||
$files = '' === $this->suffix ? [$dotenvPath, $dotenvPath.'.dist'] : [$dotenvPath.'.'.$this->suffix];
|
||||
|
||||
foreach ($files as $file) {
|
||||
$env = $this->options->get('root-dir').'/'.$file;
|
||||
if (!file_exists($env)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$contents = preg_replace(\sprintf('{%s*###> %s ###.*###< %s ###%s+}s', "\n", $recipe->getName(), $recipe->getName(), "\n"), "\n", file_get_contents($env), -1, $count);
|
||||
if (!$count) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->write(\sprintf('Removing environment variables from %s', $file));
|
||||
file_put_contents($env, $contents);
|
||||
}
|
||||
}
|
||||
|
||||
private function unconfigurePhpUnit(Recipe $recipe, $vars)
|
||||
{
|
||||
foreach (['phpunit.dist.xml', 'phpunit.xml.dist', 'phpunit.xml'] as $file) {
|
||||
$phpunit = $this->options->get('root-dir').'/'.$file;
|
||||
if (!is_file($phpunit)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$contents = preg_replace(\sprintf('{%s*\s+<!-- ###\+ %s ### -->.*<!-- ###- %s ### -->%s+}s', "\n", $recipe->getName(), $recipe->getName(), "\n"), "\n", file_get_contents($phpunit), -1, $count);
|
||||
if (!$count) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->write(\sprintf('Removing environment variables from %s', $file));
|
||||
file_put_contents($phpunit, $contents);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates expressions like %generate(secret)%.
|
||||
*
|
||||
* If $originalValue is passed, and the value contains an expression.
|
||||
* the $originalValue is used.
|
||||
*/
|
||||
private function evaluateValue($value, ?string $originalValue = null)
|
||||
{
|
||||
if ('%generate(secret)%' === $value) {
|
||||
if (null !== $originalValue) {
|
||||
return $originalValue;
|
||||
}
|
||||
|
||||
return $this->generateRandomBytes();
|
||||
}
|
||||
if (preg_match('~^%generate\(secret,\s*([0-9]+)\)%$~', $value, $matches)) {
|
||||
if (null !== $originalValue) {
|
||||
return $originalValue;
|
||||
}
|
||||
|
||||
return $this->generateRandomBytes($matches[1]);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
private function generateRandomBytes($length = 16)
|
||||
{
|
||||
return bin2hex(random_bytes($length));
|
||||
}
|
||||
|
||||
private function getContentsAfterApplyingRecipe(string $rootDir, Recipe $recipe, array $vars): array
|
||||
{
|
||||
$dotenvPath = $this->options->get('runtime')['dotenv_path'] ?? '.env';
|
||||
$files = '' === $this->suffix ? [$dotenvPath, $dotenvPath.'.dist', 'phpunit.dist.xml', 'phpunit.xml.dist', 'phpunit.xml'] : [$dotenvPath.'.'.$this->suffix];
|
||||
|
||||
if (0 === \count($vars)) {
|
||||
return array_fill_keys($files, null);
|
||||
}
|
||||
|
||||
$originalContents = [];
|
||||
foreach ($files as $file) {
|
||||
$originalContents[$file] = file_exists($rootDir.'/'.$file) ? file_get_contents($rootDir.'/'.$file) : null;
|
||||
}
|
||||
|
||||
$this->configureEnvDist(
|
||||
$recipe,
|
||||
$vars,
|
||||
true
|
||||
);
|
||||
|
||||
if ('' === $this->suffix && !file_exists($rootDir.'/'.$dotenvPath.'.test')) {
|
||||
$this->configurePhpUnit(
|
||||
$recipe,
|
||||
$vars,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
$updatedContents = [];
|
||||
foreach ($files as $file) {
|
||||
$updatedContents[$file] = file_exists($rootDir.'/'.$file) ? file_get_contents($rootDir.'/'.$file) : null;
|
||||
}
|
||||
|
||||
foreach ($originalContents as $file => $contents) {
|
||||
if (null === $contents) {
|
||||
if (file_exists($rootDir.'/'.$file)) {
|
||||
unlink($rootDir.'/'.$file);
|
||||
}
|
||||
} else {
|
||||
file_put_contents($rootDir.'/'.$file, $contents);
|
||||
}
|
||||
}
|
||||
|
||||
return $updatedContents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to find the existing value of an environment variable.
|
||||
*/
|
||||
private function findExistingValue(string $var, string $filename, Recipe $recipe): ?string
|
||||
{
|
||||
if (!file_exists($filename)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$contents = file_get_contents($filename);
|
||||
$section = $this->extractSection($recipe, $contents);
|
||||
if (!$section) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$lines = explode("\n", $section);
|
||||
foreach ($lines as $line) {
|
||||
if (!str_starts_with($line, \sprintf('%s=', $var))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return trim(substr($line, \strlen($var) + 1));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
105
vendor/symfony/flex/src/Configurator/GitignoreConfigurator.php
vendored
Normal file
105
vendor/symfony/flex/src/Configurator/GitignoreConfigurator.php
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex\Configurator;
|
||||
|
||||
use Symfony\Flex\Lock;
|
||||
use Symfony\Flex\Recipe;
|
||||
use Symfony\Flex\Update\RecipeUpdate;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class GitignoreConfigurator extends AbstractConfigurator
|
||||
{
|
||||
public function configure(Recipe $recipe, $vars, Lock $lock, array $options = [])
|
||||
{
|
||||
$this->write('Adding entries to .gitignore');
|
||||
|
||||
$this->configureGitignore($recipe, $vars, $options['force'] ?? false);
|
||||
}
|
||||
|
||||
public function unconfigure(Recipe $recipe, $vars, Lock $lock)
|
||||
{
|
||||
$file = $this->options->get('root-dir').'/.gitignore';
|
||||
if (!file_exists($file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$contents = preg_replace(\sprintf('{%s*###> %s ###.*###< %s ###%s+}s', "\n", $recipe->getName(), $recipe->getName(), "\n"), "\n", file_get_contents($file), -1, $count);
|
||||
if (!$count) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->write('Removing entries in .gitignore');
|
||||
file_put_contents($file, ltrim($contents, "\r\n"));
|
||||
}
|
||||
|
||||
public function update(RecipeUpdate $recipeUpdate, array $originalConfig, array $newConfig): void
|
||||
{
|
||||
$recipeUpdate->setOriginalFile(
|
||||
'.gitignore',
|
||||
$this->getContentsAfterApplyingRecipe($recipeUpdate->getRootDir(), $recipeUpdate->getOriginalRecipe(), $originalConfig)
|
||||
);
|
||||
|
||||
$recipeUpdate->setNewFile(
|
||||
'.gitignore',
|
||||
$this->getContentsAfterApplyingRecipe($recipeUpdate->getRootDir(), $recipeUpdate->getNewRecipe(), $newConfig)
|
||||
);
|
||||
}
|
||||
|
||||
private function configureGitignore(Recipe $recipe, array $vars, bool $update)
|
||||
{
|
||||
$gitignore = $this->options->get('root-dir').'/.gitignore';
|
||||
if (!$update && $this->isFileMarked($recipe, $gitignore)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data = '';
|
||||
foreach ($vars as $value) {
|
||||
$value = $this->options->expandTargetDir($value);
|
||||
$data .= "$value\n";
|
||||
}
|
||||
$data = "\n".ltrim($this->markData($recipe, $data), "\r\n");
|
||||
|
||||
if (!$this->updateData($gitignore, $data)) {
|
||||
file_put_contents($gitignore, $data, \FILE_APPEND);
|
||||
}
|
||||
}
|
||||
|
||||
private function getContentsAfterApplyingRecipe(string $rootDir, Recipe $recipe, $vars): ?string
|
||||
{
|
||||
if (0 === \count($vars)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$file = $rootDir.'/.gitignore';
|
||||
$originalContents = file_exists($file) ? file_get_contents($file) : null;
|
||||
|
||||
$this->configureGitignore(
|
||||
$recipe,
|
||||
$vars,
|
||||
true
|
||||
);
|
||||
|
||||
$updatedContents = file_exists($file) ? file_get_contents($file) : null;
|
||||
|
||||
if (null === $originalContents) {
|
||||
if (file_exists($file)) {
|
||||
unlink($file);
|
||||
}
|
||||
} else {
|
||||
file_put_contents($file, $originalContents);
|
||||
}
|
||||
|
||||
return $updatedContents;
|
||||
}
|
||||
}
|
||||
124
vendor/symfony/flex/src/Configurator/MakefileConfigurator.php
vendored
Normal file
124
vendor/symfony/flex/src/Configurator/MakefileConfigurator.php
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex\Configurator;
|
||||
|
||||
use Symfony\Flex\Lock;
|
||||
use Symfony\Flex\Recipe;
|
||||
use Symfony\Flex\Update\RecipeUpdate;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class MakefileConfigurator extends AbstractConfigurator
|
||||
{
|
||||
public function configure(Recipe $recipe, $definitions, Lock $lock, array $options = [])
|
||||
{
|
||||
$this->write('Adding Makefile entries');
|
||||
|
||||
$this->configureMakefile($recipe, $definitions, $options['force'] ?? false);
|
||||
}
|
||||
|
||||
public function unconfigure(Recipe $recipe, $vars, Lock $lock)
|
||||
{
|
||||
if (!file_exists($makefile = $this->options->get('root-dir').'/Makefile')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$contents = preg_replace(\sprintf('{%s*###> %s ###.*###< %s ###%s+}s', "\n", $recipe->getName(), $recipe->getName(), "\n"), "\n", file_get_contents($makefile), -1, $count);
|
||||
if (!$count) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->write(\sprintf('Removing Makefile entries from %s', $makefile));
|
||||
if (!trim($contents)) {
|
||||
@unlink($makefile);
|
||||
} else {
|
||||
file_put_contents($makefile, ltrim($contents, "\r\n"));
|
||||
}
|
||||
}
|
||||
|
||||
public function update(RecipeUpdate $recipeUpdate, array $originalConfig, array $newConfig): void
|
||||
{
|
||||
$recipeUpdate->setOriginalFile(
|
||||
'Makefile',
|
||||
$this->getContentsAfterApplyingRecipe($recipeUpdate->getRootDir(), $recipeUpdate->getOriginalRecipe(), $originalConfig)
|
||||
);
|
||||
|
||||
$recipeUpdate->setNewFile(
|
||||
'Makefile',
|
||||
$this->getContentsAfterApplyingRecipe($recipeUpdate->getRootDir(), $recipeUpdate->getNewRecipe(), $newConfig)
|
||||
);
|
||||
}
|
||||
|
||||
private function configureMakefile(Recipe $recipe, array $definitions, bool $update)
|
||||
{
|
||||
$makefile = $this->options->get('root-dir').'/Makefile';
|
||||
if (!$update && $this->isFileMarked($recipe, $makefile)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data = $this->options->expandTargetDir(implode("\n", $definitions));
|
||||
$data = $this->markData($recipe, $data);
|
||||
$data = "\n".ltrim($data, "\r\n");
|
||||
|
||||
if (!file_exists($makefile)) {
|
||||
$envKey = $this->options->get('runtime')['env_var_name'] ?? 'APP_ENV';
|
||||
$dotenvPath = $this->options->get('runtime')['dotenv_path'] ?? '.env';
|
||||
file_put_contents(
|
||||
$this->options->get('root-dir').'/Makefile',
|
||||
<<<EOF
|
||||
ifndef {$envKey}
|
||||
include {$dotenvPath}
|
||||
endif
|
||||
|
||||
.DEFAULT_GOAL := help
|
||||
.PHONY: help
|
||||
help:
|
||||
@awk 'BEGIN {FS = ":.*?## "}; /^[a-zA-Z-]+:.*?## .*$$/ {printf "\033[32m%-15s\033[0m %s\\n", $$1, $$2}' Makefile | sort
|
||||
|
||||
EOF
|
||||
);
|
||||
}
|
||||
|
||||
if (!$this->updateData($makefile, $data)) {
|
||||
file_put_contents($makefile, $data, \FILE_APPEND);
|
||||
}
|
||||
}
|
||||
|
||||
private function getContentsAfterApplyingRecipe(string $rootDir, Recipe $recipe, array $definitions): ?string
|
||||
{
|
||||
if (0 === \count($definitions)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$file = $rootDir.'/Makefile';
|
||||
$originalContents = file_exists($file) ? file_get_contents($file) : null;
|
||||
|
||||
$this->configureMakefile(
|
||||
$recipe,
|
||||
$definitions,
|
||||
true
|
||||
);
|
||||
|
||||
$updatedContents = file_exists($file) ? file_get_contents($file) : null;
|
||||
|
||||
if (null === $originalContents) {
|
||||
if (file_exists($file)) {
|
||||
unlink($file);
|
||||
}
|
||||
} else {
|
||||
file_put_contents($file, $originalContents);
|
||||
}
|
||||
|
||||
return $updatedContents;
|
||||
}
|
||||
}
|
||||
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;
|
||||
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\Package\BasePackage;
|
||||
use Composer\Util\Http\Response as ComposerResponse;
|
||||
use Composer\Util\HttpDownloader;
|
||||
use Composer\Util\Loop;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class Downloader
|
||||
{
|
||||
private const DEFAULT_ENDPOINTS = [
|
||||
'https://raw.githubusercontent.com/symfony/recipes/flex/main/index.json',
|
||||
'https://raw.githubusercontent.com/symfony/recipes-contrib/flex/main/index.json',
|
||||
];
|
||||
private const MAX_LENGTH = 1000;
|
||||
|
||||
private static $versions;
|
||||
private static $aliases;
|
||||
|
||||
private $io;
|
||||
private $sess;
|
||||
private $cache;
|
||||
|
||||
private HttpDownloader $rfs;
|
||||
private $degradedMode = false;
|
||||
private $endpoints;
|
||||
private $index;
|
||||
private $conflicts;
|
||||
private $legacyEndpoint;
|
||||
private $caFile;
|
||||
private $enabled = true;
|
||||
private $composer;
|
||||
|
||||
public function __construct(Composer $composer, IOInterface $io, HttpDownloader $rfs)
|
||||
{
|
||||
if (getenv('SYMFONY_CAFILE')) {
|
||||
$this->caFile = getenv('SYMFONY_CAFILE');
|
||||
}
|
||||
|
||||
if (null === $endpoint = $composer->getPackage()->getExtra()['symfony']['endpoint'] ?? null) {
|
||||
$this->endpoints = self::DEFAULT_ENDPOINTS;
|
||||
} elseif (\is_array($endpoint) || str_contains($endpoint, '.json') || 'flex://defaults' === $endpoint) {
|
||||
$this->endpoints = array_values((array) $endpoint);
|
||||
if (\is_string($endpoint) && str_contains($endpoint, '.json')) {
|
||||
$this->endpoints[] = 'flex://defaults';
|
||||
}
|
||||
} else {
|
||||
$this->legacyEndpoint = rtrim($endpoint, '/');
|
||||
}
|
||||
|
||||
if (false === $endpoint = getenv('SYMFONY_ENDPOINT')) {
|
||||
// no-op
|
||||
} elseif (str_contains($endpoint, '.json') || 'flex://defaults' === $endpoint) {
|
||||
$this->endpoints ?? $this->endpoints = self::DEFAULT_ENDPOINTS;
|
||||
array_unshift($this->endpoints, $endpoint);
|
||||
$this->legacyEndpoint = null;
|
||||
} else {
|
||||
$this->endpoints = null;
|
||||
$this->legacyEndpoint = rtrim($endpoint, '/');
|
||||
}
|
||||
|
||||
if (null !== $this->endpoints) {
|
||||
if (false !== $i = array_search('flex://defaults', $this->endpoints, true)) {
|
||||
array_splice($this->endpoints, $i, 1, self::DEFAULT_ENDPOINTS);
|
||||
}
|
||||
|
||||
$this->endpoints = array_fill_keys($this->endpoints, []);
|
||||
}
|
||||
|
||||
$this->io = $io;
|
||||
$config = $composer->getConfig();
|
||||
$this->rfs = $rfs;
|
||||
$this->cache = new Cache($io, $config->get('cache-repo-dir').'/flex');
|
||||
$this->sess = bin2hex(random_bytes(16));
|
||||
$this->composer = $composer;
|
||||
}
|
||||
|
||||
public function getSessionId(): string
|
||||
{
|
||||
return $this->sess;
|
||||
}
|
||||
|
||||
public function isEnabled()
|
||||
{
|
||||
return $this->enabled;
|
||||
}
|
||||
|
||||
public function disable()
|
||||
{
|
||||
$this->enabled = false;
|
||||
}
|
||||
|
||||
public function getVersions()
|
||||
{
|
||||
$this->initialize();
|
||||
|
||||
return self::$versions ?? self::$versions = current($this->get([$this->legacyEndpoint.'/versions.json']));
|
||||
}
|
||||
|
||||
public function getAliases()
|
||||
{
|
||||
$this->initialize();
|
||||
|
||||
return self::$aliases ?? self::$aliases = current($this->get([$this->legacyEndpoint.'/aliases.json']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads recipes.
|
||||
*
|
||||
* @param OperationInterface[] $operations
|
||||
*/
|
||||
public function getRecipes(array $operations): array
|
||||
{
|
||||
$this->initialize();
|
||||
|
||||
if ($this->conflicts) {
|
||||
$lockedRepository = $this->composer->getLocker()->getLockedRepository(true);
|
||||
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 (str_starts_with($version, 'dev-') && isset($package->getExtra()['branch-alias'])) {
|
||||
$branchAliases = $package->getExtra()['branch-alias'];
|
||||
if (
|
||||
(isset($branchAliases[$version]) && $alias = $branchAliases[$version])
|
||||
|| (isset($branchAliases['dev-main']) && $alias = $branchAliases['dev-main'])
|
||||
|| (isset($branchAliases['dev-trunk']) && $alias = $branchAliases['dev-trunk'])
|
||||
|| (isset($branchAliases['dev-develop']) && $alias = $branchAliases['dev-develop'])
|
||||
|| (isset($branchAliases['dev-default']) && $alias = $branchAliases['dev-default'])
|
||||
|| (isset($branchAliases['dev-latest']) && $alias = $branchAliases['dev-latest'])
|
||||
|| (isset($branchAliases['dev-next']) && $alias = $branchAliases['dev-next'])
|
||||
|| (isset($branchAliases['dev-current']) && $alias = $branchAliases['dev-current'])
|
||||
|| (isset($branchAliases['dev-support']) && $alias = $branchAliases['dev-support'])
|
||||
|| (isset($branchAliases['dev-tip']) && $alias = $branchAliases['dev-tip'])
|
||||
|| (isset($branchAliases['dev-master']) && $alias = $branchAliases['dev-master'])
|
||||
) {
|
||||
$version = $alias;
|
||||
}
|
||||
}
|
||||
|
||||
if ($recipeVersions = $this->index[$package->getName()] ?? null) {
|
||||
$version = explode('.', preg_replace('/^dev-|^v|\.x-dev$|-dev$/', '', $version));
|
||||
$version = $version[0].'.'.($version[1] ?? '9999999');
|
||||
|
||||
foreach (array_reverse($recipeVersions) as $v => $endpoint) {
|
||||
if (version_compare($version, $v, '<')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$data['locks'][$package->getName()]['version'] = $version;
|
||||
$data['locks'][$package->getName()]['recipe']['version'] = $v;
|
||||
$links = $this->endpoints[$endpoint]['_links'];
|
||||
|
||||
if (null !== $recipeRef && isset($links['archived_recipes_template'])) {
|
||||
if (isset($links['archived_recipes_template_relative'])) {
|
||||
$links['archived_recipes_template'] = preg_replace('{[^/\?]*+(?=\?|$)}', $links['archived_recipes_template_relative'], $endpoint, 1);
|
||||
}
|
||||
|
||||
$urls[] = strtr($links['archived_recipes_template'], [
|
||||
'{package_dotted}' => str_replace('/', '.', $package->getName()),
|
||||
'{ref}' => $recipeRef,
|
||||
]);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (isset($links['recipe_template_relative'])) {
|
||||
$links['recipe_template'] = preg_replace('{[^/\?]*+(?=\?|$)}', $links['recipe_template_relative'], $endpoint, 1);
|
||||
}
|
||||
|
||||
$urls[] = strtr($links['recipe_template'], [
|
||||
'{package_dotted}' => str_replace('/', '.', $package->getName()),
|
||||
'{package}' => $package->getName(),
|
||||
'{version}' => $v,
|
||||
]);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (\is_array($recipeVersions)) {
|
||||
$data['conflicts'][$package->getName()] = true;
|
||||
}
|
||||
|
||||
if (null !== $this->endpoints) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// FIXME: Multi name with getNames()
|
||||
$name = str_replace('/', ',', $package->getName());
|
||||
$path = \sprintf('%s,%s%s', $name, $o, $version);
|
||||
if ($date = $package->getReleaseDate()) {
|
||||
$path .= ','.$date->format('U');
|
||||
}
|
||||
if (\strlen($chunk) + \strlen($path) > self::MAX_LENGTH) {
|
||||
$urls[] = $this->legacyEndpoint.'/p/'.$chunk;
|
||||
$chunk = $path;
|
||||
} elseif ($chunk) {
|
||||
$chunk .= ';'.$path;
|
||||
} else {
|
||||
$chunk = $path;
|
||||
}
|
||||
}
|
||||
if ($chunk) {
|
||||
$urls[] = $this->legacyEndpoint.'/p/'.$chunk;
|
||||
}
|
||||
|
||||
if (null === $this->endpoints) {
|
||||
foreach ($this->get($urls, true) as $body) {
|
||||
foreach ($body['manifests'] ?? [] as $name => $manifest) {
|
||||
$data['manifests'][$name] = $manifest;
|
||||
}
|
||||
foreach ($body['locks'] ?? [] as $name => $lock) {
|
||||
$data['locks'][$name] = $lock;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
foreach ($this->get($urls, true) as $body) {
|
||||
foreach ($body['manifests'] ?? [] as $name => $manifest) {
|
||||
if (null === $version = $data['locks'][$name]['recipe']['version'] ?? null) {
|
||||
continue;
|
||||
}
|
||||
$endpoint = $this->endpoints[$this->index[$name][$version]];
|
||||
|
||||
$data['locks'][$name]['recipe'] = [
|
||||
'repo' => $endpoint['_links']['repository'],
|
||||
'branch' => $endpoint['branch'],
|
||||
'version' => $version,
|
||||
'ref' => $manifest['ref'],
|
||||
];
|
||||
|
||||
foreach ($manifest['files'] ?? [] as $i => $file) {
|
||||
$manifest['files'][$i]['contents'] = \is_array($file['contents']) ? implode("\n", $file['contents']) : base64_decode($file['contents']);
|
||||
}
|
||||
|
||||
$data['manifests'][$name] = $manifest + [
|
||||
'repository' => $endpoint['_links']['repository'],
|
||||
'package' => $name,
|
||||
'version' => $version,
|
||||
'origin' => strtr($endpoint['_links']['origin_template'], [
|
||||
'{package}' => $name,
|
||||
'{version}' => $version,
|
||||
]),
|
||||
'is_contrib' => $endpoint['is_contrib'] ?? false,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to "hide" a recipe version so that the next most-recent will be returned.
|
||||
*
|
||||
* This is used when resolving "conflicts".
|
||||
*/
|
||||
public function removeRecipeFromIndex(string $packageName, string $version)
|
||||
{
|
||||
unset($this->index[$packageName][$version]);
|
||||
}
|
||||
|
||||
public function getSymfonyPacks(array $packages)
|
||||
{
|
||||
$packs = [];
|
||||
foreach ($this->composer->getRepositoryManager()->getRepositories() as $repo) {
|
||||
if (!$packages) {
|
||||
break;
|
||||
}
|
||||
|
||||
$result = $repo->loadPackages($packages, BasePackage::$stabilities, []);
|
||||
|
||||
foreach ($result['packages'] ?? [] as $package) {
|
||||
if (!isset($packages[$package->getName()])) {
|
||||
continue;
|
||||
}
|
||||
if ('symfony-pack' === $package->getType()) {
|
||||
$packs[$package->getName()] = true;
|
||||
}
|
||||
unset($packages[$package->getName()]);
|
||||
}
|
||||
}
|
||||
|
||||
return array_keys($packs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches and decodes JSON HTTP response bodies.
|
||||
*/
|
||||
private function get(array $urls, bool $isRecipe = false, int $try = 3): array
|
||||
{
|
||||
$responses = [];
|
||||
$retries = [];
|
||||
$options = [];
|
||||
|
||||
foreach ($urls as $url) {
|
||||
$cacheKey = self::generateCacheKey($url);
|
||||
$headers = [];
|
||||
|
||||
if (preg_match('{^https?://api\.github\.com/}', $url)) {
|
||||
$headers[] = 'Accept: application/vnd.github.v3.raw';
|
||||
} elseif (preg_match('{^https?://raw\.githubusercontent\.com/}', $url) && $this->io->hasAuthentication('github.com')) {
|
||||
$auth = $this->io->getAuthentication('github.com');
|
||||
if ('x-oauth-basic' === $auth['password']) {
|
||||
$headers[] = 'Authorization: token '.$auth['username'];
|
||||
}
|
||||
} elseif ($this->legacyEndpoint) {
|
||||
$headers[] = 'Package-Session: '.$this->sess;
|
||||
}
|
||||
|
||||
if ($contents = $this->cache->read($cacheKey)) {
|
||||
$cachedResponse = Response::fromJson(json_decode($contents, true));
|
||||
if ($lastModified = $cachedResponse->getHeader('last-modified')) {
|
||||
$headers[] = 'If-Modified-Since: '.$lastModified;
|
||||
}
|
||||
if ($eTag = $cachedResponse->getHeader('etag')) {
|
||||
$headers[] = 'If-None-Match: '.$eTag;
|
||||
}
|
||||
$responses[$url] = $cachedResponse->getBody();
|
||||
}
|
||||
|
||||
$options[$url] = $this->getOptions($headers);
|
||||
}
|
||||
|
||||
$loop = new Loop($this->rfs);
|
||||
$jobs = [];
|
||||
foreach ($urls as $url) {
|
||||
$jobs[] = $this->rfs->add($url, $options[$url])->then(function (ComposerResponse $response) use ($url, &$responses) {
|
||||
if (200 === $response->getStatusCode()) {
|
||||
$cacheKey = self::generateCacheKey($url);
|
||||
$responses[$url] = $this->parseJson($response->getBody(), $url, $cacheKey, $response->getHeaders())->getBody();
|
||||
}
|
||||
}, function (\Exception $e) use ($url, &$retries) {
|
||||
$retries[] = [$url, $e];
|
||||
});
|
||||
}
|
||||
$loop->wait($jobs);
|
||||
|
||||
if (!$retries) {
|
||||
return $responses;
|
||||
}
|
||||
|
||||
if (0 < --$try) {
|
||||
usleep(100000);
|
||||
|
||||
return $this->get(array_column($retries, 0), $isRecipe, $try) + $responses;
|
||||
}
|
||||
|
||||
foreach ($retries as [$url, $e]) {
|
||||
if (isset($responses[$url])) {
|
||||
$this->switchToDegradedMode($e, $url);
|
||||
} elseif ($isRecipe) {
|
||||
$this->io->writeError('<warning>Failed to download recipe: '.$e->getMessage().'</>');
|
||||
} else {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
return $responses;
|
||||
}
|
||||
|
||||
private function parseJson(string $json, string $url, string $cacheKey, array $lastHeaders): Response
|
||||
{
|
||||
$data = JsonFile::parseJson($json, $url);
|
||||
if (!empty($data['warning'])) {
|
||||
$this->io->writeError('<warning>Warning from '.$url.': '.$data['warning'].'</>');
|
||||
}
|
||||
if (!empty($data['info'])) {
|
||||
$this->io->writeError('<info>Info from '.$url.': '.$data['info'].'</>');
|
||||
}
|
||||
|
||||
$response = new Response($data, $lastHeaders);
|
||||
if ($cacheKey && ($response->getHeader('last-modified') || $response->getHeader('etag'))) {
|
||||
$this->cache->write($cacheKey, json_encode($response));
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
private function switchToDegradedMode(\Exception $e, string $url)
|
||||
{
|
||||
if (!$this->degradedMode) {
|
||||
$this->io->writeError('<warning>'.$e->getMessage().'</>');
|
||||
$this->io->writeError('<warning>'.$url.' could not be fully loaded, package information was loaded from the local cache and may be out of date</>');
|
||||
}
|
||||
$this->degradedMode = true;
|
||||
}
|
||||
|
||||
private function getOptions(array $headers): array
|
||||
{
|
||||
$options = ['http' => ['header' => $headers]];
|
||||
|
||||
if (null !== $this->caFile) {
|
||||
$options['ssl']['cafile'] = $this->caFile;
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
private function initialize()
|
||||
{
|
||||
if (null !== $this->index || null === $this->endpoints) {
|
||||
$this->index ?? $this->index = [];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$indexes = self::$versions = self::$aliases = [];
|
||||
|
||||
foreach ($this->get(array_keys($this->endpoints)) as $endpoint => $index) {
|
||||
$indexes[$endpoint] = $index;
|
||||
}
|
||||
|
||||
foreach ($this->endpoints as $endpoint => $config) {
|
||||
$config = $indexes[$endpoint] ?? [];
|
||||
foreach ($config['recipes'] ?? [] as $package => $versions) {
|
||||
$this->index[$package] = $this->index[$package] ?? array_fill_keys($versions, $endpoint);
|
||||
}
|
||||
$this->conflicts[] = $config['recipe-conflicts'] ?? [];
|
||||
self::$versions += $config['versions'] ?? [];
|
||||
self::$aliases += $config['aliases'] ?? [];
|
||||
unset($config['recipes'], $config['recipe-conflicts'], $config['versions'], $config['aliases']);
|
||||
$this->endpoints[$endpoint] = $config;
|
||||
}
|
||||
}
|
||||
|
||||
private static function generateCacheKey(string $url): string
|
||||
{
|
||||
$url = preg_replace('{^https://api.github.com/repos/([^/]++/[^/]++)/contents/}', '$1/', $url);
|
||||
$url = preg_replace('{^https://raw.githubusercontent.com/([^/]++/[^/]++)/}', '$1/', $url);
|
||||
|
||||
$key = preg_replace('{[^a-z0-9.]}i', '-', $url);
|
||||
|
||||
// eCryptfs can have problems with filenames longer than around 143 chars
|
||||
return \strlen($key) > 140 ? md5($url) : $key;
|
||||
}
|
||||
}
|
||||
45
vendor/symfony/flex/src/Event/UpdateEvent.php
vendored
Normal file
45
vendor/symfony/flex/src/Event/UpdateEvent.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\Event;
|
||||
|
||||
use Composer\Script\Event;
|
||||
use Composer\Script\ScriptEvents;
|
||||
|
||||
class UpdateEvent extends Event
|
||||
{
|
||||
private $force;
|
||||
private $reset;
|
||||
private $assumeYesForPrompts;
|
||||
|
||||
public function __construct(bool $force, bool $reset, bool $assumeYesForPrompts)
|
||||
{
|
||||
$this->name = ScriptEvents::POST_UPDATE_CMD;
|
||||
$this->force = $force;
|
||||
$this->reset = $reset;
|
||||
$this->assumeYesForPrompts = $assumeYesForPrompts;
|
||||
}
|
||||
|
||||
public function force(): bool
|
||||
{
|
||||
return $this->force;
|
||||
}
|
||||
|
||||
public function reset(): bool
|
||||
{
|
||||
return $this->reset;
|
||||
}
|
||||
|
||||
public function assumeYesForPrompts(): bool
|
||||
{
|
||||
return $this->assumeYesForPrompts;
|
||||
}
|
||||
}
|
||||
874
vendor/symfony/flex/src/Flex.php
vendored
Normal file
874
vendor/symfony/flex/src/Flex.php
vendored
Normal file
@@ -0,0 +1,874 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex;
|
||||
|
||||
use Composer\Command\GlobalCommand;
|
||||
use Composer\Composer;
|
||||
use Composer\Console\Application;
|
||||
use Composer\DependencyResolver\Operation\InstallOperation;
|
||||
use Composer\DependencyResolver\Operation\OperationInterface;
|
||||
use Composer\DependencyResolver\Operation\UninstallOperation;
|
||||
use Composer\DependencyResolver\Operation\UpdateOperation;
|
||||
use Composer\DependencyResolver\Transaction;
|
||||
use Composer\EventDispatcher\EventSubscriberInterface;
|
||||
use Composer\Factory;
|
||||
use Composer\Installer;
|
||||
use Composer\Installer\InstallerEvent;
|
||||
use Composer\Installer\InstallerEvents;
|
||||
use Composer\Installer\PackageEvent;
|
||||
use Composer\Installer\PackageEvents;
|
||||
use Composer\Installer\SuggestedPackagesReporter;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\IO\NullIO;
|
||||
use Composer\Json\JsonFile;
|
||||
use Composer\Json\JsonManipulator;
|
||||
use Composer\Package\BasePackage;
|
||||
use Composer\Package\Locker;
|
||||
use Composer\Package\Package;
|
||||
use Composer\Plugin\PluginEvents;
|
||||
use Composer\Plugin\PluginInterface;
|
||||
use Composer\Plugin\PrePoolCreateEvent;
|
||||
use Composer\Script\Event;
|
||||
use Composer\Script\ScriptEvents;
|
||||
use Composer\Semver\VersionParser;
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
use Symfony\Flex\Event\UpdateEvent;
|
||||
use Symfony\Flex\Unpack\Operation;
|
||||
use Symfony\Thanks\Thanks;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class Flex implements PluginInterface, EventSubscriberInterface
|
||||
{
|
||||
public static $storedOperations = [];
|
||||
|
||||
/**
|
||||
* @var Composer
|
||||
*/
|
||||
private $composer;
|
||||
|
||||
/**
|
||||
* @var IOInterface
|
||||
*/
|
||||
private $io;
|
||||
|
||||
private $config;
|
||||
private $options;
|
||||
private $configurator;
|
||||
private $downloader;
|
||||
|
||||
/**
|
||||
* @var Installer
|
||||
*/
|
||||
private $installer;
|
||||
private $postInstallOutput = [''];
|
||||
private $operations = [];
|
||||
private $lock;
|
||||
private $displayThanksReminder = 0;
|
||||
private $reinstall;
|
||||
private static $activated = true;
|
||||
private static $aliasResolveCommands = [
|
||||
'require' => true,
|
||||
'update' => false,
|
||||
'remove' => false,
|
||||
];
|
||||
private $filter;
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function activate(Composer $composer, IOInterface $io)
|
||||
{
|
||||
if (!\extension_loaded('openssl')) {
|
||||
self::$activated = false;
|
||||
$io->writeError('<warning>Symfony Flex has been disabled. You must enable the openssl extension in your "php.ini" file.</>');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// to avoid issues when Flex is upgraded, we load all PHP classes now
|
||||
// that way, we are sure to use all classes from the same version
|
||||
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator(__DIR__, \FilesystemIterator::SKIP_DOTS)) as $file) {
|
||||
if ('.php' === substr($file, -4)) {
|
||||
class_exists(__NAMESPACE__.str_replace('/', '\\', substr($file, \strlen(__DIR__), -4)));
|
||||
}
|
||||
}
|
||||
|
||||
$composer->getInstallationManager()->addInstaller(new SymfonyPackInstaller($io));
|
||||
|
||||
$this->composer = $composer;
|
||||
$this->io = $io;
|
||||
$this->config = $composer->getConfig();
|
||||
|
||||
$composerFile = Factory::getComposerFile();
|
||||
$composerLock = 'json' === pathinfo($composerFile, \PATHINFO_EXTENSION) ? substr($composerFile, 0, -4).'lock' : $composerFile.'.lock';
|
||||
$symfonyLock = str_replace('composer', 'symfony', basename($composerLock));
|
||||
|
||||
$this->lock = new Lock(getenv('SYMFONY_LOCKFILE') ?: \dirname($composerLock).'/'.(basename($composerLock) !== $symfonyLock ? $symfonyLock : 'symfony.lock'));
|
||||
$this->options = $this->initOptions();
|
||||
|
||||
// if Flex is being upgraded, the original operations from the original Flex
|
||||
// instance are stored in the static property, so we can reuse them now.
|
||||
if (property_exists(Flex::class, 'storedOperations') && Flex::$storedOperations) {
|
||||
$this->operations = Flex::$storedOperations;
|
||||
Flex::$storedOperations = [];
|
||||
}
|
||||
|
||||
$symfonyRequire = preg_replace('/\.x$/', '.x-dev', getenv('SYMFONY_REQUIRE') ?: ($composer->getPackage()->getExtra()['symfony']['require'] ?? ''));
|
||||
|
||||
$rfs = $composer->getLoop()->getHttpDownloader();
|
||||
|
||||
$this->downloader = $downloader = new Downloader($composer, $io, $rfs);
|
||||
|
||||
if ($symfonyRequire) {
|
||||
$this->filter = new PackageFilter($io, $symfonyRequire, $this->downloader);
|
||||
}
|
||||
|
||||
$this->configurator = new Configurator($composer, $io, $this->options);
|
||||
|
||||
$disable = true;
|
||||
foreach (array_merge($composer->getPackage()->getRequires() ?? [], $composer->getPackage()->getDevRequires() ?? []) as $link) {
|
||||
// recipes apply only when symfony/flex is found in "require" or "require-dev" in the root package
|
||||
if ('symfony/flex' === $link->getTarget()) {
|
||||
$disable = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($disable) {
|
||||
$downloader->disable();
|
||||
}
|
||||
|
||||
$backtrace = $this->configureInstaller();
|
||||
|
||||
foreach ($backtrace as $trace) {
|
||||
if (!isset($trace['object']) || !isset($trace['args'][0])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$trace['object'] instanceof Application || !$trace['args'][0] instanceof ArgvInput) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// In Composer 1.0.*, $input knows about option and argument definitions
|
||||
// Since Composer >=1.1, $input contains only raw values
|
||||
$input = $trace['args'][0];
|
||||
$app = $trace['object'];
|
||||
|
||||
$resolver = new PackageResolver($this->downloader);
|
||||
|
||||
try {
|
||||
$command = $input->getFirstArgument();
|
||||
$command = $command ? $app->find($command)->getName() : null;
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
}
|
||||
|
||||
if ('create-project' === $command) {
|
||||
if ($input->hasOption('remove-vcs')) {
|
||||
$input->setOption('remove-vcs', true);
|
||||
}
|
||||
} elseif ('update' === $command) {
|
||||
$this->displayThanksReminder = 1;
|
||||
} elseif ('outdated' === $command) {
|
||||
$symfonyRequire = null;
|
||||
}
|
||||
|
||||
if (isset(self::$aliasResolveCommands[$command])) {
|
||||
if ($input->hasArgument('packages')) {
|
||||
$input->setArgument('packages', $resolver->resolve($input->getArgument('packages'), self::$aliasResolveCommands[$command]));
|
||||
}
|
||||
}
|
||||
|
||||
if ($input->hasParameterOption('--prefer-lowest', true)) {
|
||||
// When prefer-lowest is set and no stable version has been released,
|
||||
// we consider "dev" more stable than "alpha", "beta" or "RC". This
|
||||
// allows testing lowest versions with potential fixes applied.
|
||||
BasePackage::$stabilities['dev'] = 1 + BasePackage::STABILITY_STABLE;
|
||||
}
|
||||
|
||||
$app->add(new Command\RecipesCommand($this, $this->lock, $rfs));
|
||||
$app->add(new Command\InstallRecipesCommand($this, $this->options->get('root-dir'), $this->options->get('runtime')['dotenv_path'] ?? '.env'));
|
||||
$app->add(new Command\UpdateRecipesCommand($this, $this->downloader, $rfs, $this->configurator, $this->options->get('root-dir')));
|
||||
$app->add(new Command\DumpEnvCommand($this->config, $this->options));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function deactivate(Composer $composer, IOInterface $io)
|
||||
{
|
||||
// Using `Flex::` instead of `self::` to avoid issues when
|
||||
// composer renames plugin classes when upgrading them
|
||||
Flex::$storedOperations = $this->operations;
|
||||
self::$activated = false;
|
||||
}
|
||||
|
||||
public function configureInstaller()
|
||||
{
|
||||
$backtrace = debug_backtrace();
|
||||
foreach ($backtrace as $trace) {
|
||||
if (isset($trace['object']) && $trace['object'] instanceof Installer) {
|
||||
$this->installer = $trace['object']->setSuggestedPackagesReporter(new SuggestedPackagesReporter(new NullIO()));
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// new projects are most of the time proprietary
|
||||
$manipulator->addMainKey('license', 'proprietary');
|
||||
|
||||
// extra.branch-alias doesn't apply to the project
|
||||
$manipulator->removeSubNode('extra', 'branch-alias');
|
||||
|
||||
// 'name' and 'description' are only required for public packages
|
||||
// don't use $manipulator->removeProperty() for BC with Composer 1.0
|
||||
$contents = preg_replace(['{^\s*+"name":.*,$\n}m', '{^\s*+"description":.*,$\n}m'], '', $manipulator->getContents(), 1);
|
||||
file_put_contents($file, $contents);
|
||||
|
||||
$this->updateComposerLock();
|
||||
}
|
||||
|
||||
public function recordFlexInstall(PackageEvent $event)
|
||||
{
|
||||
if (null === $this->reinstall && 'symfony/flex' === $event->getOperation()->getPackage()->getName()) {
|
||||
$this->reinstall = true;
|
||||
}
|
||||
}
|
||||
|
||||
public function record(PackageEvent $event)
|
||||
{
|
||||
if ($this->shouldRecordOperation($event->getOperation(), $event->isDevMode(), $event->getComposer())) {
|
||||
$this->operations[] = $event->getOperation();
|
||||
}
|
||||
}
|
||||
|
||||
public function recordOperations(InstallerEvent $event)
|
||||
{
|
||||
if (!$event->isExecutingOperations()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$versionParser = new VersionParser();
|
||||
$packages = [];
|
||||
foreach ($this->lock->all() as $name => $info) {
|
||||
if ('9999999.9999999' === $info['version']) {
|
||||
// Fix invalid versions found in some lock files
|
||||
$info['version'] = '99999.9999999';
|
||||
}
|
||||
$packages[] = new Package($name, $versionParser->normalize($info['version']), $info['version']);
|
||||
}
|
||||
|
||||
$transation = \Closure::bind(function () use ($packages, $event) {
|
||||
return new Transaction($packages, $event->getTransaction()->resultPackageMap);
|
||||
}, null, Transaction::class)();
|
||||
|
||||
foreach ($transation->getOperations() as $operation) {
|
||||
if (!$operation instanceof UninstallOperation && $this->shouldRecordOperation($operation, $event->isDevMode(), $event->getComposer())) {
|
||||
$this->operations[] = $operation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function update(Event $event, $operations = [])
|
||||
{
|
||||
if ($operations) {
|
||||
$this->operations = $operations;
|
||||
}
|
||||
|
||||
$this->install($event);
|
||||
|
||||
$file = Factory::getComposerFile();
|
||||
$contents = file_get_contents($file);
|
||||
$json = JsonFile::parseJson($contents);
|
||||
|
||||
if (!$this->reinstall && !isset($json['flex-require']) && !isset($json['flex-require-dev'])) {
|
||||
$this->unpack($event);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// merge "flex-require" with "require"
|
||||
$manipulator = new JsonManipulator($contents);
|
||||
$sortPackages = $this->composer->getConfig()->get('sort-packages');
|
||||
$symfonyVersion = $json['extra']['symfony']['require'] ?? null;
|
||||
$versions = $symfonyVersion ? $this->downloader->getVersions() : null;
|
||||
foreach (['require', 'require-dev'] as $type) {
|
||||
if (!isset($json['flex-'.$type])) {
|
||||
continue;
|
||||
}
|
||||
foreach ($json['flex-'.$type] as $package => $constraint) {
|
||||
if ($symfonyVersion && '*' === $constraint && isset($versions['splits'][$package])) {
|
||||
// replace unbounded constraints for symfony/* packages by extra.symfony.require
|
||||
$constraint = $symfonyVersion;
|
||||
}
|
||||
$manipulator->addLink($type, $package, $constraint, $sortPackages);
|
||||
}
|
||||
|
||||
$manipulator->removeMainKey('flex-'.$type);
|
||||
}
|
||||
|
||||
file_put_contents($file, $manipulator->getContents());
|
||||
|
||||
$this->reinstall($event);
|
||||
}
|
||||
|
||||
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') && !str_contains(file_get_contents($dotenvPath.'.dist'), '.env.local')) {
|
||||
copy($dotenvPath.'.dist', $dotenvPath);
|
||||
}
|
||||
|
||||
// Execute missing recipes
|
||||
$recipes = ScriptEvents::POST_UPDATE_CMD === $event->getName() ? $this->fetchRecipes($this->operations, $event instanceof UpdateEvent && $event->reset()) : [];
|
||||
$this->operations = []; // Reset the operation after getting recipes
|
||||
|
||||
if (2 === $this->displayThanksReminder) {
|
||||
$love = '\\' === \DIRECTORY_SEPARATOR ? 'love' : '💖 ';
|
||||
$star = '\\' === \DIRECTORY_SEPARATOR ? 'star' : '★ ';
|
||||
|
||||
$this->io->writeError('');
|
||||
$this->io->writeError('What about running <comment>composer global require symfony/thanks && composer thanks</> now?');
|
||||
$this->io->writeError(\sprintf('This will spread some %s by sending a %s to the GitHub repositories of your fellow package maintainers.', $love, $star));
|
||||
}
|
||||
|
||||
$this->io->writeError('');
|
||||
|
||||
if (!$recipes) {
|
||||
if (ScriptEvents::POST_UPDATE_CMD === $event->getName()) {
|
||||
$this->finish($rootDir);
|
||||
}
|
||||
|
||||
if ($this->downloader->isEnabled()) {
|
||||
$this->io->writeError('Run <comment>composer recipes</> at any time to see the status of your Symfony recipes.');
|
||||
$this->io->writeError('');
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->io->writeError(\sprintf('<info>Symfony operations: %d recipe%s (%s)</>', \count($recipes), \count($recipes) > 1 ? 's' : '', $this->downloader->getSessionId()));
|
||||
$installContribs = $this->composer->getPackage()->getExtra()['symfony']['allow-contrib'] ?? false;
|
||||
$manifest = null;
|
||||
$originalComposerJsonHash = $this->getComposerJsonHash();
|
||||
$postInstallRecipes = [];
|
||||
foreach ($recipes as $recipe) {
|
||||
if ('install' === $recipe->getJob() && !$installContribs && $recipe->isContrib()) {
|
||||
$warning = $this->io->isInteractive() ? 'WARNING' : 'IGNORING';
|
||||
$this->io->writeError(\sprintf(' - <warning> %s </> %s', $warning, $this->formatOrigin($recipe)));
|
||||
$question = \sprintf(' The recipe for this package comes from the "contrib" repository, which is open to community contributions.
|
||||
Review the recipe at %s
|
||||
|
||||
Do you want to execute this recipe?
|
||||
[<comment>y</>] Yes
|
||||
[<comment>n</>] No
|
||||
[<comment>a</>] Yes for all packages, only for the current installation session
|
||||
[<comment>p</>] Yes permanently, never ask again for this project
|
||||
(defaults to <comment>n</>): ', $recipe->getURL());
|
||||
$answer = $this->io->askAndValidate(
|
||||
$question,
|
||||
function ($value) {
|
||||
if (null === $value) {
|
||||
return 'n';
|
||||
}
|
||||
$value = strtolower($value[0]);
|
||||
if (!\in_array($value, ['y', 'n', 'a', 'p'])) {
|
||||
throw new \InvalidArgumentException('Invalid choice.');
|
||||
}
|
||||
|
||||
return $value;
|
||||
},
|
||||
null,
|
||||
'n'
|
||||
);
|
||||
if ('n' === $answer) {
|
||||
continue;
|
||||
}
|
||||
if ('a' === $answer) {
|
||||
$installContribs = true;
|
||||
}
|
||||
if ('p' === $answer) {
|
||||
$installContribs = true;
|
||||
$json = new JsonFile(Factory::getComposerFile());
|
||||
$manipulator = new JsonManipulator(file_get_contents($json->getPath()));
|
||||
$manipulator->addSubNode('extra', 'symfony.allow-contrib', true);
|
||||
file_put_contents($json->getPath(), $manipulator->getContents());
|
||||
}
|
||||
}
|
||||
|
||||
switch ($recipe->getJob()) {
|
||||
case 'install':
|
||||
$postInstallRecipes[] = $recipe;
|
||||
$this->io->writeError(\sprintf(' - Configuring %s', $this->formatOrigin($recipe)));
|
||||
$this->configurator->install($recipe, $this->lock, [
|
||||
'force' => $event instanceof UpdateEvent && $event->force(),
|
||||
'assumeYesForPrompts' => $event instanceof UpdateEvent && $event->assumeYesForPrompts(),
|
||||
]);
|
||||
$manifest = $recipe->getManifest();
|
||||
if (isset($manifest['post-install-output'])) {
|
||||
$this->postInstallOutput[] = \sprintf('<bg=yellow;fg=white> %s </> instructions:', $recipe->getName());
|
||||
$this->postInstallOutput[] = '';
|
||||
foreach ($manifest['post-install-output'] as $line) {
|
||||
$this->postInstallOutput[] = $this->options->expandTargetDir($line);
|
||||
}
|
||||
$this->postInstallOutput[] = '';
|
||||
}
|
||||
break;
|
||||
case 'update':
|
||||
break;
|
||||
case 'uninstall':
|
||||
$this->io->writeError(\sprintf(' - Unconfiguring %s', $this->formatOrigin($recipe)));
|
||||
$this->configurator->unconfigure($recipe, $this->lock);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (method_exists($this->configurator, 'postInstall')) {
|
||||
foreach ($postInstallRecipes as $recipe) {
|
||||
$this->configurator->postInstall($recipe, $this->lock, [
|
||||
'force' => $event instanceof UpdateEvent && $event->force(),
|
||||
'assumeYesForPrompts' => $event instanceof UpdateEvent && $event->assumeYesForPrompts(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (!($this->composer->getPackage()->getExtra()['symfony/flex']['synchronize_package_json'] ?? true)) {
|
||||
$this->io->writeError('<info>Skip synchronizing package.json with PHP packages</>');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->downloader->isEnabled()) {
|
||||
$this->io->writeError('<warning>Synchronizing package.json is disabled: "symfony/flex" not found in the root composer.json</>');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$rootDir = realpath($rootDir);
|
||||
$vendorDir = trim((new Filesystem())->makePathRelative($this->config->get('vendor-dir'), $rootDir), '/');
|
||||
|
||||
$executor = new ScriptExecutor($this->composer, $this->io, $this->options);
|
||||
$synchronizer = new PackageJsonSynchronizer($rootDir, $vendorDir, $executor, $this->io);
|
||||
|
||||
if ($synchronizer->shouldSynchronize()) {
|
||||
$lockData = $this->composer->getLocker()->getLockData();
|
||||
|
||||
if ($synchronizer->synchronize(array_merge($lockData['packages'] ?? [], $lockData['packages-dev'] ?? []))) {
|
||||
$this->io->writeError('<info>Synchronizing package.json with PHP packages</>');
|
||||
$this->io->writeError('<warning>Don\'t forget to run npm install --force or yarn install --force to refresh your JavaScript dependencies!</>');
|
||||
$this->io->writeError('');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function uninstall(Composer $composer, IOInterface $io)
|
||||
{
|
||||
$this->lock->delete();
|
||||
}
|
||||
|
||||
public function enableThanksReminder()
|
||||
{
|
||||
if (1 === $this->displayThanksReminder) {
|
||||
$this->displayThanksReminder = !class_exists(Thanks::class, false) ? 2 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
public function executeAutoScripts(Event $event)
|
||||
{
|
||||
$event->stopPropagation();
|
||||
|
||||
// force reloading scripts as we might have added and removed during this run
|
||||
$json = new JsonFile(Factory::getComposerFile());
|
||||
$jsonContents = $json->read();
|
||||
|
||||
$executor = new ScriptExecutor($this->composer, $this->io, $this->options);
|
||||
foreach ($jsonContents['scripts']['auto-scripts'] as $cmd => $type) {
|
||||
$executor->execute($type, $cmd);
|
||||
}
|
||||
|
||||
$this->io->write($this->postInstallOutput);
|
||||
$this->postInstallOutput = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Recipe[]
|
||||
*/
|
||||
public function fetchRecipes(array $operations, bool $reset): array
|
||||
{
|
||||
if (!$this->downloader->isEnabled()) {
|
||||
$this->io->writeError('<warning>Symfony recipes are disabled: "symfony/flex" not found in the root composer.json</>');
|
||||
|
||||
return [];
|
||||
}
|
||||
$devPackages = null;
|
||||
$data = $this->downloader->getRecipes($operations);
|
||||
$manifests = $data['manifests'] ?? [];
|
||||
$locks = $data['locks'] ?? [];
|
||||
// symfony/flex recipes should always be applied first
|
||||
$flexRecipe = [];
|
||||
// symfony/framework-bundle recipe should always be applied first after the metapackages
|
||||
$recipes = [
|
||||
'symfony/framework-bundle' => null,
|
||||
];
|
||||
$packRecipes = [];
|
||||
$metaRecipes = [];
|
||||
|
||||
foreach ($operations as $operation) {
|
||||
if ($operation instanceof UpdateOperation) {
|
||||
$package = $operation->getTargetPackage();
|
||||
} else {
|
||||
$package = $operation->getPackage();
|
||||
}
|
||||
|
||||
// FIXME: Multi name with getNames()
|
||||
$name = $package->getName();
|
||||
$job = method_exists($operation, 'getOperationType') ? $operation->getOperationType() : $operation->getJobType();
|
||||
|
||||
if (!isset($manifests[$name]) && isset($data['conflicts'][$name])) {
|
||||
$this->io->writeError(\sprintf(' - Skipping recipe for %s: all versions of the recipe conflict with your package versions.', $name));
|
||||
continue;
|
||||
}
|
||||
|
||||
while ($this->doesRecipeConflict($manifests[$name] ?? [], $operation)) {
|
||||
$this->downloader->removeRecipeFromIndex($name, $manifests[$name]['version']);
|
||||
$newData = $this->downloader->getRecipes([$operation]);
|
||||
$newManifests = $newData['manifests'] ?? [];
|
||||
|
||||
if (!isset($newManifests[$name])) {
|
||||
// no older recipe found
|
||||
$this->io->writeError(\sprintf(' - Skipping recipe for %s: all versions of the recipe conflict with your package versions.', $name));
|
||||
|
||||
continue 2;
|
||||
}
|
||||
|
||||
// push the "old" recipe into the $manifests
|
||||
$manifests[$name] = $newManifests[$name];
|
||||
$locks[$name] = $newData['locks'][$name];
|
||||
}
|
||||
|
||||
if ($operation instanceof InstallOperation && isset($locks[$name])) {
|
||||
$ref = $this->lock->get($name)['recipe']['ref'] ?? null;
|
||||
if (!$reset && $ref && ($locks[$name]['recipe']['ref'] ?? null) === $ref) {
|
||||
continue;
|
||||
}
|
||||
$this->lock->set($name, $locks[$name]);
|
||||
} elseif ($operation instanceof UninstallOperation) {
|
||||
if (!$this->lock->has($name)) {
|
||||
continue;
|
||||
}
|
||||
$this->lock->remove($name);
|
||||
}
|
||||
|
||||
if (isset($manifests[$name])) {
|
||||
$recipe = new Recipe($package, $name, $job, $manifests[$name], $locks[$name] ?? []);
|
||||
|
||||
if ('symfony-pack' === $package->getType()) {
|
||||
$packRecipes[$name] = $recipe;
|
||||
} elseif ('metapackage' === $package->getType()) {
|
||||
$metaRecipes[$name] = $recipe;
|
||||
} elseif ('symfony/flex' === $name) {
|
||||
$flexRecipe = [$name => $recipe];
|
||||
} else {
|
||||
$recipes[$name] = $recipe;
|
||||
}
|
||||
} else {
|
||||
$bundles = [];
|
||||
|
||||
if (null === $devPackages) {
|
||||
$devPackages = array_column($this->composer->getLocker()->getLockData()['packages-dev'], 'name');
|
||||
}
|
||||
$envs = \in_array($name, $devPackages) ? ['dev', 'test'] : ['all'];
|
||||
$bundle = new SymfonyBundle($this->composer, $package, $job);
|
||||
foreach ($bundle->getClassNames() as $bundleClass) {
|
||||
$bundles[$bundleClass] = $envs;
|
||||
}
|
||||
|
||||
if ($bundles) {
|
||||
$manifest = [
|
||||
'origin' => \sprintf('%s:%s@auto-generated recipe', $name, $package->getPrettyVersion()),
|
||||
'manifest' => ['bundles' => $bundles],
|
||||
];
|
||||
$recipes[$name] = new Recipe($package, $name, $job, $manifest);
|
||||
|
||||
if ($operation instanceof InstallOperation) {
|
||||
$this->lock->set($name, ['version' => $package->getPrettyVersion()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_merge($flexRecipe, $packRecipes, $metaRecipes, array_filter($recipes));
|
||||
}
|
||||
|
||||
public function truncatePackages(PrePoolCreateEvent $event)
|
||||
{
|
||||
if (!$this->filter) {
|
||||
return;
|
||||
}
|
||||
|
||||
$rootPackage = $this->composer->getPackage();
|
||||
$lockedPackages = $event->getRequest()->getFixedOrLockedPackages();
|
||||
|
||||
$event->setPackages($this->filter->removeLegacyPackages($event->getPackages(), $rootPackage, $lockedPackages));
|
||||
}
|
||||
|
||||
public function getComposerJsonHash(): string
|
||||
{
|
||||
return md5_file(Factory::getComposerFile());
|
||||
}
|
||||
|
||||
public function getLock(): Lock
|
||||
{
|
||||
if (null === $this->lock) {
|
||||
throw new \Exception('Cannot access lock before calling activate().');
|
||||
}
|
||||
|
||||
return $this->lock;
|
||||
}
|
||||
|
||||
private function initOptions(): Options
|
||||
{
|
||||
$extra = $this->composer->getPackage()->getExtra();
|
||||
|
||||
$options = array_merge([
|
||||
'bin-dir' => 'bin',
|
||||
'conf-dir' => 'conf',
|
||||
'config-dir' => 'config',
|
||||
'src-dir' => 'src',
|
||||
'var-dir' => 'var',
|
||||
'public-dir' => 'public',
|
||||
'root-dir' => $extra['symfony']['root-dir'] ?? '.',
|
||||
'runtime' => $extra['runtime'] ?? [],
|
||||
], $extra);
|
||||
|
||||
return new Options($options, $this->io, $this->lock);
|
||||
}
|
||||
|
||||
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->reinstall) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($operation instanceof UpdateOperation) {
|
||||
$package = $operation->getTargetPackage();
|
||||
} else {
|
||||
$package = $operation->getPackage();
|
||||
}
|
||||
|
||||
// when Composer runs with --no-dev, ignore uninstall operations on packages from require-dev
|
||||
if (!$isDevMode && $operation instanceof UninstallOperation) {
|
||||
foreach (($composer ?? $this->composer)->getLocker()->getLockData()['packages-dev'] as $p) {
|
||||
if ($package->getName() === $p['name']) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Multi name with getNames()
|
||||
$name = $package->getName();
|
||||
if ($operation instanceof InstallOperation) {
|
||||
if (!$this->lock->has($name)) {
|
||||
return true;
|
||||
}
|
||||
} elseif ($operation instanceof UninstallOperation) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function updateComposerLock()
|
||||
{
|
||||
$lock = substr(Factory::getComposerFile(), 0, -4).'lock';
|
||||
$composerJson = file_get_contents(Factory::getComposerFile());
|
||||
$lockFile = new JsonFile($lock, null, $this->io);
|
||||
$locker = new Locker($this->io, $lockFile, $this->composer->getInstallationManager(), $composerJson);
|
||||
$lockData = $locker->getLockData();
|
||||
$lockData['content-hash'] = Locker::getContentHash($composerJson);
|
||||
$lockFile->write($lockData);
|
||||
}
|
||||
|
||||
private function unpack(Event $event)
|
||||
{
|
||||
$jsonPath = Factory::getComposerFile();
|
||||
$json = JsonFile::parseJson(file_get_contents($jsonPath));
|
||||
$sortPackages = $this->composer->getConfig()->get('sort-packages');
|
||||
$unpackOp = new Operation(true, $sortPackages);
|
||||
|
||||
foreach (['require', 'require-dev'] as $type) {
|
||||
foreach ($json[$type] ?? [] as $package => $constraint) {
|
||||
$unpackOp->addPackage($package, $constraint, 'require-dev' === $type);
|
||||
}
|
||||
}
|
||||
|
||||
$unpacker = new Unpacker($this->composer, new PackageResolver($this->downloader), false); // 3rd arg to ease upgrading from flex <= 2.6.0
|
||||
$result = $unpacker->unpack($unpackOp);
|
||||
|
||||
if (!$result->getUnpacked()) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($result->getUnpacked() as $pkg) {
|
||||
$this->io->writeError(\sprintf(' - Unpacked <info>%s</>', $pkg->getName()));
|
||||
}
|
||||
|
||||
$unpacker->updateLock($result, $this->io);
|
||||
}
|
||||
|
||||
private function reinstall(Event $event)
|
||||
{
|
||||
$this->reinstall = false;
|
||||
$event->stopPropagation();
|
||||
|
||||
$ed = $this->composer->getEventDispatcher();
|
||||
$disableScripts = !method_exists($ed, 'setRunScripts') || !((array) $ed)["\0*\0runScripts"];
|
||||
$composer = Factory::create($this->io, null, false, $disableScripts);
|
||||
$composer->getInstallationManager()->addInstaller(new SymfonyPackInstaller($this->io));
|
||||
|
||||
$installer = clone $this->installer;
|
||||
$installer->__construct(
|
||||
$this->io,
|
||||
$composer->getConfig(),
|
||||
$composer->getPackage(),
|
||||
$composer->getDownloadManager(),
|
||||
$composer->getRepositoryManager(),
|
||||
$composer->getLocker(),
|
||||
$composer->getInstallationManager(),
|
||||
$composer->getEventDispatcher(),
|
||||
$composer->getAutoloadGenerator()
|
||||
);
|
||||
if (method_exists($installer, 'setPlatformRequirementFilter')) {
|
||||
$installer->setPlatformRequirementFilter(((array) $this->installer)["\0*\0platformRequirementFilter"]);
|
||||
}
|
||||
|
||||
$installer->run();
|
||||
|
||||
$this->io->write($this->postInstallOutput);
|
||||
$this->postInstallOutput = [];
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
if (!self::$activated) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$events = [
|
||||
PackageEvents::POST_PACKAGE_UPDATE => 'enableThanksReminder',
|
||||
PackageEvents::POST_PACKAGE_INSTALL => 'recordFlexInstall',
|
||||
PackageEvents::POST_PACKAGE_UNINSTALL => 'record',
|
||||
InstallerEvents::PRE_OPERATIONS_EXEC => 'recordOperations',
|
||||
PluginEvents::PRE_POOL_CREATE => 'truncatePackages',
|
||||
ScriptEvents::POST_CREATE_PROJECT_CMD => 'configureProject',
|
||||
ScriptEvents::POST_INSTALL_CMD => 'install',
|
||||
ScriptEvents::PRE_UPDATE_CMD => 'configureInstaller',
|
||||
ScriptEvents::POST_UPDATE_CMD => 'update',
|
||||
'auto-scripts' => 'executeAutoScripts',
|
||||
];
|
||||
|
||||
return $events;
|
||||
}
|
||||
|
||||
private function doesRecipeConflict(array $recipeData, OperationInterface $operation): bool
|
||||
{
|
||||
if (empty($recipeData['manifest']['conflict']) || $operation instanceof UninstallOperation) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$lockedRepository = $this->composer->getLocker()->getLockedRepository(true);
|
||||
|
||||
foreach ($recipeData['manifest']['conflict'] as $conflictingPackage => $constraint) {
|
||||
if ($lockedRepository->findPackage($conflictingPackage, $constraint)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
200
vendor/symfony/flex/src/GithubApi.php
vendored
Normal file
200
vendor/symfony/flex/src/GithubApi.php
vendored
Normal file
@@ -0,0 +1,200 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex;
|
||||
|
||||
use Composer\Util\HttpDownloader;
|
||||
use Composer\Util\RemoteFilesystem;
|
||||
|
||||
class GithubApi
|
||||
{
|
||||
/** @var HttpDownloader|RemoteFilesystem */
|
||||
private $downloader;
|
||||
|
||||
public function __construct($downloader)
|
||||
{
|
||||
$this->downloader = $downloader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to find data about when the recipe was installed.
|
||||
*
|
||||
* Returns an array containing:
|
||||
* commit: The git sha of the last commit of the recipe
|
||||
* date: The date of the commit
|
||||
* new_commits: An array of commit sha's in this recipe's directory+version since the commit
|
||||
* The key is the sha & the value is the date
|
||||
*/
|
||||
public function findRecipeCommitDataFromTreeRef(string $package, string $repo, string $branch, string $version, string $lockRef): ?array
|
||||
{
|
||||
$repositoryName = $this->getRepositoryName($repo);
|
||||
if (!$repositoryName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$recipePath = \sprintf('%s/%s', $package, $version);
|
||||
$commitsData = $this->requestGitHubApi(\sprintf(
|
||||
'https://api.github.com/repos/%s/commits?path=%s&sha=%s',
|
||||
$repositoryName,
|
||||
$recipePath,
|
||||
$branch
|
||||
));
|
||||
|
||||
$commitShas = [];
|
||||
foreach ($commitsData as $commitData) {
|
||||
$commitShas[$commitData['sha']] = $commitData['commit']['committer']['date'];
|
||||
// go back the commits one-by-one
|
||||
$treeUrl = $commitData['commit']['tree']['url'].'?recursive=true';
|
||||
|
||||
// fetch the full tree, then look for the tree for the package path
|
||||
$treeData = $this->requestGitHubApi($treeUrl);
|
||||
foreach ($treeData['tree'] as $treeItem) {
|
||||
if ($treeItem['path'] !== $recipePath) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($treeItem['sha'] === $lockRef) {
|
||||
// remove *this* commit from the new commits list
|
||||
array_pop($commitShas);
|
||||
|
||||
return [
|
||||
// shorten for brevity
|
||||
'commit' => substr($commitData['sha'], 0, 7),
|
||||
'date' => $commitData['commit']['committer']['date'],
|
||||
'new_commits' => $commitShas,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getVersionsOfRecipe(string $repo, string $branch, string $recipePath): ?array
|
||||
{
|
||||
$repositoryName = $this->getRepositoryName($repo);
|
||||
if (!$repositoryName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$url = \sprintf(
|
||||
'https://api.github.com/repos/%s/contents/%s?ref=%s',
|
||||
$repositoryName,
|
||||
$recipePath,
|
||||
$branch
|
||||
);
|
||||
$contents = $this->requestGitHubApi($url);
|
||||
$versions = [];
|
||||
foreach ($contents as $fileData) {
|
||||
if ('dir' !== $fileData['type']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$versions[] = $fileData['name'];
|
||||
}
|
||||
|
||||
return $versions;
|
||||
}
|
||||
|
||||
public function getCommitDataForPath(string $repo, string $path, string $branch): array
|
||||
{
|
||||
$repositoryName = $this->getRepositoryName($repo);
|
||||
if (!$repositoryName) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$commitsData = $this->requestGitHubApi(\sprintf(
|
||||
'https://api.github.com/repos/%s/commits?path=%s&sha=%s',
|
||||
$repositoryName,
|
||||
$path,
|
||||
$branch
|
||||
));
|
||||
|
||||
$data = [];
|
||||
foreach ($commitsData as $commitData) {
|
||||
$data[$commitData['sha']] = $commitData['commit']['committer']['date'];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function getPullRequestForCommit(string $commit, string $repo): ?array
|
||||
{
|
||||
$data = $this->requestGitHubApi('https://api.github.com/search/issues?q='.$commit.'+is:pull-request');
|
||||
|
||||
if (0 === \count($data['items'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$repositoryName = $this->getRepositoryName($repo);
|
||||
if (!$repositoryName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$bestItem = null;
|
||||
foreach ($data['items'] as $item) {
|
||||
// make sure the PR referenced isn't from a different repository
|
||||
if (!str_contains($item['html_url'], \sprintf('%s/pull', $repositoryName))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (null === $bestItem) {
|
||||
$bestItem = $item;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// find the first PR to reference - avoids rare cases where an invalid
|
||||
// PR that references *many* commits is first
|
||||
// e.g. https://api.github.com/search/issues?q=a1a70353f64f405cfbacfc4ce860af623442d6e5
|
||||
if ($item['number'] < $bestItem['number']) {
|
||||
$bestItem = $item;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$bestItem) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'number' => $bestItem['number'],
|
||||
'url' => $bestItem['html_url'],
|
||||
'title' => $bestItem['title'],
|
||||
];
|
||||
}
|
||||
|
||||
private function requestGitHubApi(string $path)
|
||||
{
|
||||
$contents = $this->downloader->get($path)->getBody();
|
||||
|
||||
return json_decode($contents, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the "repo" stored in symfony.lock to a repository name.
|
||||
*
|
||||
* For example: "github.com/symfony/recipes" => "symfony/recipes"
|
||||
*/
|
||||
private function getRepositoryName(string $repo): ?string
|
||||
{
|
||||
// only supports public repository placement
|
||||
if (!str_starts_with($repo, 'github.com')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$parts = explode('/', $repo);
|
||||
if (3 !== \count($parts)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return implode('/', [$parts[1], $parts[2]]);
|
||||
}
|
||||
}
|
||||
88
vendor/symfony/flex/src/InformationOperation.php
vendored
Normal file
88
vendor/symfony/flex/src/InformationOperation.php
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
<?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;
|
||||
private $version;
|
||||
|
||||
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';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getOperationType()
|
||||
{
|
||||
return 'information';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function show($lock)
|
||||
{
|
||||
$pretty = method_exists($this->package, 'getFullPrettyVersion') ? $this->package->getFullPrettyVersion() : $this->formatVersion($this->package);
|
||||
|
||||
return 'Information '.$this->package->getPrettyName().' ('.$pretty.')';
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
142
vendor/symfony/flex/src/Options.php
vendored
Normal file
142
vendor/symfony/flex/src/Options.php
vendored
Normal file
@@ -0,0 +1,142 @@
|
||||
<?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;
|
||||
private $lockData;
|
||||
|
||||
public function __construct(array $options = [], ?IOInterface $io = null, ?Lock $lock = null)
|
||||
{
|
||||
$this->options = $options;
|
||||
$this->io = $io;
|
||||
$this->lockData = $lock?->all() ?? [];
|
||||
}
|
||||
|
||||
public function get(string $name)
|
||||
{
|
||||
return $this->options[$name] ?? null;
|
||||
}
|
||||
|
||||
public function expandTargetDir(string $target): string
|
||||
{
|
||||
$result = 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);
|
||||
|
||||
$phpunitDistFiles = [
|
||||
'phpunit.xml.dist' => true,
|
||||
'phpunit.dist.xml' => true,
|
||||
];
|
||||
|
||||
$rootDir = $this->get('root-dir');
|
||||
|
||||
if (null === $rootDir || !isset($phpunitDistFiles[$result]) || !is_dir($rootDir) || file_exists($rootDir.'/'.$result)) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
unset($phpunitDistFiles[$result]);
|
||||
$otherPhpunitDistFile = key($phpunitDistFiles);
|
||||
|
||||
return file_exists($rootDir.'/'.$otherPhpunitDistFile) ? $otherPhpunitDistFile : $result;
|
||||
}
|
||||
|
||||
public function shouldWriteFile(string $file, bool $overwrite, bool $skipQuestion): 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;
|
||||
}
|
||||
|
||||
if ($skipQuestion) {
|
||||
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 getRemovableFiles(Recipe $recipe, Lock $lock): array
|
||||
{
|
||||
if (null === $removableFiles = $this->lockData[$recipe->getName()]['files'] ?? null) {
|
||||
$removableFiles = [];
|
||||
foreach (array_keys($recipe->getFiles()) as $source => $target) {
|
||||
if (str_ends_with($source, '/')) {
|
||||
$removableFiles[] = $this->expandTargetDir($target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unset($this->lockData[$recipe->getName()]);
|
||||
$lockedFiles = array_count_values(array_merge(...array_column($lock->all(), 'files')));
|
||||
|
||||
$nonRemovableFiles = [];
|
||||
foreach ($removableFiles as $i => $file) {
|
||||
if (isset($lockedFiles[$file])) {
|
||||
$nonRemovableFiles[] = $file;
|
||||
unset($removableFiles[$i]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($nonRemovableFiles && $this->io) {
|
||||
$this->io?->writeError(' <warning>The following files are still referenced by other recipes, you might need to adjust them manually:</warning>');
|
||||
foreach ($nonRemovableFiles as $file) {
|
||||
$this->io?->writeError(' - '.$file);
|
||||
}
|
||||
}
|
||||
|
||||
return array_values($removableFiles);
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
}
|
||||
155
vendor/symfony/flex/src/PackageFilter.php
vendored
Normal file
155
vendor/symfony/flex/src/PackageFilter.php
vendored
Normal file
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex;
|
||||
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Package\AliasPackage;
|
||||
use Composer\Package\PackageInterface;
|
||||
use Composer\Package\RootPackageInterface;
|
||||
use Composer\Semver\Constraint\Constraint;
|
||||
use Composer\Semver\Intervals;
|
||||
use Composer\Semver\VersionParser;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class PackageFilter
|
||||
{
|
||||
private $versions;
|
||||
private $versionParser;
|
||||
private $symfonyRequire;
|
||||
private $symfonyConstraints;
|
||||
private $downloader;
|
||||
private $io;
|
||||
|
||||
public function __construct(IOInterface $io, string $symfonyRequire, Downloader $downloader)
|
||||
{
|
||||
$this->versionParser = new VersionParser();
|
||||
$this->symfonyRequire = $symfonyRequire;
|
||||
$this->symfonyConstraints = $this->versionParser->parseConstraints($symfonyRequire);
|
||||
$this->downloader = $downloader;
|
||||
$this->io = $io;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PackageInterface[] $data
|
||||
* @param PackageInterface[] $lockedPackages
|
||||
*
|
||||
* @return PackageInterface[]
|
||||
*/
|
||||
public function removeLegacyPackages(array $data, RootPackageInterface $rootPackage, array $lockedPackages): array
|
||||
{
|
||||
if (!$this->symfonyConstraints || !$data) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$lockedVersions = [];
|
||||
foreach ($lockedPackages as $package) {
|
||||
$lockedVersions[$package->getName()] = [$package->getVersion()];
|
||||
if ($package instanceof AliasPackage) {
|
||||
$lockedVersions[$package->getName()][] = $package->getAliasOf()->getVersion();
|
||||
}
|
||||
}
|
||||
|
||||
$rootConstraints = [];
|
||||
foreach ($rootPackage->getRequires() + $rootPackage->getDevRequires() as $name => $link) {
|
||||
$rootConstraints[$name] = $link->getConstraint();
|
||||
}
|
||||
|
||||
$knownVersions = null;
|
||||
$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 && (
|
||||
array_intersect($versions, $lockedVersions[$name] ?? [])
|
||||
|| (($knownVersions ??= $this->getVersions()) && !isset($knownVersions['splits'][$name]))
|
||||
|| (isset($rootConstraints[$name]) && !Intervals::haveIntersections($this->symfonyConstraints, $rootConstraints[$name]))
|
||||
|| ('symfony/psr-http-message-bridge' === $name && 6.4 > $versions[0])
|
||||
)) {
|
||||
$filteredPackages[] = $package;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (null !== $alias = $package->getExtra()['branch-alias'][$package->getVersion()] ?? null) {
|
||||
$versions[] = $this->versionParser->normalize($alias);
|
||||
}
|
||||
|
||||
foreach ($versions as $version) {
|
||||
if ($this->symfonyConstraints->matches(new Constraint('==', $version))) {
|
||||
$filteredPackages[] = $package;
|
||||
$oneSymfony = $oneSymfony || 'symfony/symfony' === $name;
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
if ('symfony/symfony' === $name) {
|
||||
$symfonyPackages[] = $package;
|
||||
} elseif (null !== $this->io) {
|
||||
$this->io->writeError(\sprintf('<info>Restricting packages listed in "symfony/symfony" to "%s"</>', $this->symfonyRequire));
|
||||
$this->io = null;
|
||||
}
|
||||
}
|
||||
|
||||
if ($symfonyPackages && !$oneSymfony) {
|
||||
$filteredPackages = array_merge($filteredPackages, $symfonyPackages);
|
||||
}
|
||||
|
||||
return $filteredPackages;
|
||||
}
|
||||
|
||||
private function getVersions(): array
|
||||
{
|
||||
if (null !== $this->versions) {
|
||||
return $this->versions;
|
||||
}
|
||||
|
||||
$versions = $this->downloader->getVersions();
|
||||
$this->downloader = null;
|
||||
$okVersions = [];
|
||||
|
||||
if (!isset($versions['splits'])) {
|
||||
throw new \LogicException('The Flex index is missing a "splits" entry. Did you forget to add "flex://defaults" in the "extra.symfony.endpoint" array of your composer.json?');
|
||||
}
|
||||
foreach ($versions['splits'] as $name => $vers) {
|
||||
foreach ($vers as $i => $v) {
|
||||
if (!isset($okVersions[$v])) {
|
||||
$okVersions[$v] = false;
|
||||
$w = '.x' === substr($v, -2) ? $versions['next'] : $v;
|
||||
|
||||
for ($j = 0; $j < 60; ++$j) {
|
||||
if ($this->symfonyConstraints->matches(new Constraint('==', $w.'.'.$j.'.0'))) {
|
||||
$okVersions[$v] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$okVersions[$v]) {
|
||||
unset($vers[$i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$vers || $vers === $versions['splits'][$name]) {
|
||||
unset($versions['splits'][$name]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->versions = $versions;
|
||||
}
|
||||
}
|
||||
423
vendor/symfony/flex/src/PackageJsonSynchronizer.php
vendored
Normal file
423
vendor/symfony/flex/src/PackageJsonSynchronizer.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;
|
||||
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Json\JsonFile;
|
||||
use Composer\Json\JsonManipulator;
|
||||
use Composer\Semver\Semver;
|
||||
use Composer\Semver\VersionParser;
|
||||
use Seld\JsonLint\ParsingException;
|
||||
|
||||
/**
|
||||
* Synchronize package.json files detected in installed PHP packages with
|
||||
* the current application.
|
||||
*/
|
||||
class PackageJsonSynchronizer
|
||||
{
|
||||
private $rootDir;
|
||||
private $vendorDir;
|
||||
private $scriptExecutor;
|
||||
private $io;
|
||||
private $versionParser;
|
||||
|
||||
public function __construct(string $rootDir, string $vendorDir, ScriptExecutor $scriptExecutor, IOInterface $io)
|
||||
{
|
||||
$this->rootDir = $rootDir;
|
||||
$this->vendorDir = $vendorDir;
|
||||
$this->scriptExecutor = $scriptExecutor;
|
||||
$this->io = $io;
|
||||
$this->versionParser = new VersionParser();
|
||||
}
|
||||
|
||||
public function shouldSynchronize(): bool
|
||||
{
|
||||
return $this->rootDir && (file_exists($this->rootDir.'/package.json') || file_exists($this->rootDir.'/importmap.php'));
|
||||
}
|
||||
|
||||
public function synchronize(array $phpPackages): bool
|
||||
{
|
||||
if (file_exists($this->rootDir.'/importmap.php')) {
|
||||
$this->synchronizeForAssetMapper($phpPackages);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
JsonFile::parseJson(file_get_contents($this->rootDir.'/package.json'));
|
||||
} catch (ParsingException $e) {
|
||||
// if package.json is invalid (possible during a recipe upgrade), we can't update the file
|
||||
return false;
|
||||
}
|
||||
|
||||
$didChangePackageJson = $this->removeObsoletePackageJsonLinks();
|
||||
|
||||
$dependencies = [];
|
||||
|
||||
$phpPackages = $this->normalizePhpPackages($phpPackages);
|
||||
foreach ($phpPackages as $phpPackage) {
|
||||
foreach ($this->resolvePackageJsonDependencies($phpPackage) as $dependency => $constraint) {
|
||||
$dependencies[$dependency][$phpPackage['name']] = $constraint;
|
||||
}
|
||||
}
|
||||
|
||||
$didChangePackageJson = $this->registerDependenciesInPackageJson($dependencies) || $didChangePackageJson;
|
||||
|
||||
// Register controllers and entrypoints in controllers.json
|
||||
$this->updateControllersJsonFile($phpPackages);
|
||||
|
||||
return $didChangePackageJson;
|
||||
}
|
||||
|
||||
private function synchronizeForAssetMapper(array $phpPackages): void
|
||||
{
|
||||
$importMapEntries = [];
|
||||
$phpPackages = $this->normalizePhpPackages($phpPackages);
|
||||
foreach ($phpPackages as $phpPackage) {
|
||||
foreach ($this->resolveImportMapPackages($phpPackage) as $name => $dependencyConfig) {
|
||||
$importMapEntries[$name] = $dependencyConfig;
|
||||
}
|
||||
}
|
||||
|
||||
$this->updateImportMap($importMapEntries);
|
||||
$this->updateControllersJsonFile($phpPackages);
|
||||
}
|
||||
|
||||
private function removeObsoletePackageJsonLinks(): bool
|
||||
{
|
||||
$didChangePackageJson = false;
|
||||
|
||||
$manipulator = new JsonManipulator(file_get_contents($this->rootDir.'/package.json'));
|
||||
$content = json_decode($manipulator->getContents(), true);
|
||||
|
||||
$jsDependencies = $content['dependencies'] ?? [];
|
||||
$jsDevDependencies = $content['devDependencies'] ?? [];
|
||||
|
||||
foreach (['dependencies' => $jsDependencies, 'devDependencies' => $jsDevDependencies] as $key => $packages) {
|
||||
foreach ($packages as $name => $version) {
|
||||
if ('@' !== $name[0] || !str_starts_with($version, 'file:'.$this->vendorDir.'/') || !str_contains($version, '/assets')) {
|
||||
continue;
|
||||
}
|
||||
if (file_exists($this->rootDir.'/'.substr($version, 5).'/package.json')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$manipulator->removeSubNode($key, $name);
|
||||
$didChangePackageJson = true;
|
||||
}
|
||||
}
|
||||
|
||||
file_put_contents($this->rootDir.'/package.json', $manipulator->getContents());
|
||||
|
||||
return $didChangePackageJson;
|
||||
}
|
||||
|
||||
private function resolvePackageJsonDependencies($phpPackage): array
|
||||
{
|
||||
$dependencies = [];
|
||||
|
||||
if (!$packageJson = $this->resolvePackageJson($phpPackage)) {
|
||||
return $dependencies;
|
||||
}
|
||||
|
||||
if ($packageJson->read()['symfony']['needsPackageAsADependency'] ?? true) {
|
||||
$dependencies['@'.$phpPackage['name']] = 'file:'.substr($packageJson->getPath(), 1 + \strlen($this->rootDir), -13);
|
||||
}
|
||||
|
||||
foreach ($packageJson->read()['peerDependencies'] ?? [] as $peerDependency => $constraint) {
|
||||
$dependencies[$peerDependency] = $constraint;
|
||||
}
|
||||
|
||||
return $dependencies;
|
||||
}
|
||||
|
||||
private function resolveImportMapPackages($phpPackage): array
|
||||
{
|
||||
if (!$packageJson = $this->resolvePackageJson($phpPackage)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$dependencies = [];
|
||||
|
||||
foreach ($packageJson->read()['symfony']['importmap'] ?? [] as $importMapName => $constraintConfig) {
|
||||
if (\is_string($constraintConfig)) {
|
||||
// Matches string constraint, like "^3.0" or "path:%PACKAGE%/script.js"
|
||||
$constraint = $constraintConfig;
|
||||
$package = $importMapName;
|
||||
$entrypoint = false;
|
||||
} elseif (\is_array($constraintConfig)) {
|
||||
// Matches array constraint, like {"version":"^3.0"} or {"version":"path:%PACKAGE%/script.js","entrypoint":true}
|
||||
// Note that non-path assets can't be entrypoint
|
||||
$constraint = $constraintConfig['version'] ?? '';
|
||||
$package = $constraintConfig['package'] ?? $importMapName;
|
||||
$entrypoint = $constraintConfig['entrypoint'] ?? false;
|
||||
} else {
|
||||
throw new \InvalidArgumentException(\sprintf('Invalid constraint config for key "%s": "%s" given, array or string expected.', $importMapName, var_export($constraintConfig, true)));
|
||||
}
|
||||
|
||||
// When "$constraintConfig" matches one of the following cases:
|
||||
// - "entrypoint:%PACKAGE%/script.js"
|
||||
// - {"version": "entrypoint:%PACKAGE%/script.js"}
|
||||
if (str_starts_with($constraint, 'entrypoint:')) {
|
||||
$entrypoint = true;
|
||||
$constraint = substr_replace($constraint, 'path:', 0, \strlen('entrypoint:'));
|
||||
}
|
||||
|
||||
if (str_starts_with($constraint, 'path:')) {
|
||||
$path = substr($constraint, 5);
|
||||
$path = str_replace('%PACKAGE%', \dirname($packageJson->getPath()), $path);
|
||||
|
||||
$dependencies[$importMapName] = [
|
||||
'path' => $path,
|
||||
'entrypoint' => $entrypoint,
|
||||
];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$dependencies[$importMapName] = [
|
||||
'version' => $constraint,
|
||||
'package' => $package,
|
||||
];
|
||||
}
|
||||
|
||||
return $dependencies;
|
||||
}
|
||||
|
||||
private function registerDependenciesInPackageJson(array $flexDependencies): bool
|
||||
{
|
||||
$didChangePackageJson = false;
|
||||
|
||||
$manipulator = new JsonManipulator(file_get_contents($this->rootDir.'/package.json'));
|
||||
$content = json_decode($manipulator->getContents(), true);
|
||||
|
||||
foreach ($flexDependencies as $dependency => $constraints) {
|
||||
if (1 !== \count($constraints) && 1 !== \count(array_count_values($constraints))) {
|
||||
// If the flex packages have a colliding peer dependency, leave the resolution to the user
|
||||
continue;
|
||||
}
|
||||
|
||||
$constraint = array_shift($constraints);
|
||||
|
||||
$parentNode = isset($content['dependencies'][$dependency]) ? 'dependencies' : 'devDependencies';
|
||||
if (!isset($content[$parentNode][$dependency])) {
|
||||
$content['devDependencies'][$dependency] = $constraint;
|
||||
$didChangePackageJson = true;
|
||||
} elseif ($constraint !== $content[$parentNode][$dependency]) {
|
||||
if ($this->shouldUpdateConstraint($content[$parentNode][$dependency], $constraint)) {
|
||||
$content[$parentNode][$dependency] = $constraint;
|
||||
$didChangePackageJson = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($didChangePackageJson) {
|
||||
if (isset($content['dependencies'])) {
|
||||
$manipulator->addMainKey('dependencies', $content['dependencies']);
|
||||
}
|
||||
|
||||
if (isset($content['devDependencies'])) {
|
||||
$devDependencies = $content['devDependencies'];
|
||||
uksort($devDependencies, 'strnatcmp');
|
||||
$manipulator->addMainKey('devDependencies', $devDependencies);
|
||||
}
|
||||
|
||||
$newContents = $manipulator->getContents();
|
||||
if ($newContents === file_get_contents($this->rootDir.'/package.json')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
file_put_contents($this->rootDir.'/package.json', $manipulator->getContents());
|
||||
}
|
||||
|
||||
return $didChangePackageJson;
|
||||
}
|
||||
|
||||
private function shouldUpdateConstraint(string $existingConstraint, string $constraint)
|
||||
{
|
||||
try {
|
||||
$existingConstraint = $this->versionParser->parseConstraints($existingConstraint);
|
||||
$constraint = $this->versionParser->parseConstraints($constraint);
|
||||
|
||||
return !$existingConstraint->matches($constraint);
|
||||
} catch (\UnexpectedValueException $e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, array{path?: string, package?: string, version?: string, entrypoint?: bool}> $importMapEntries
|
||||
*/
|
||||
private function updateImportMap(array $importMapEntries): void
|
||||
{
|
||||
if (!$importMapEntries) {
|
||||
return;
|
||||
}
|
||||
|
||||
$importMapData = include $this->rootDir.'/importmap.php';
|
||||
|
||||
foreach ($importMapEntries as $name => $importMapEntry) {
|
||||
if (isset($importMapData[$name])) {
|
||||
if (!isset($importMapData[$name]['version'])) {
|
||||
// AssetMapper 6.3
|
||||
continue;
|
||||
}
|
||||
|
||||
$version = $importMapData[$name]['version'];
|
||||
$versionConstraint = $importMapEntry['version'] ?? null;
|
||||
|
||||
// if the version constraint is satisfied, skip - else, update the package
|
||||
if (Semver::satisfies($version, $versionConstraint)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->io->writeError(\sprintf('Updating package <comment>%s</> from <info>%s</> to <info>%s</>.', $name, $version, $versionConstraint));
|
||||
}
|
||||
|
||||
if (isset($importMapEntry['path'])) {
|
||||
$arguments = [$name, '--path='.$importMapEntry['path']];
|
||||
if (isset($importMapEntry['entrypoint']) && true === $importMapEntry['entrypoint']) {
|
||||
$arguments[] = '--entrypoint';
|
||||
}
|
||||
|
||||
$this->scriptExecutor->execute(
|
||||
'symfony-cmd',
|
||||
'importmap:require',
|
||||
$arguments
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($importMapEntry['version'])) {
|
||||
$packageName = $importMapEntry['package'].'@'.$importMapEntry['version'];
|
||||
if ($importMapEntry['package'] !== $name) {
|
||||
$packageName .= '='.$name;
|
||||
}
|
||||
$arguments = [$packageName];
|
||||
$this->scriptExecutor->execute(
|
||||
'symfony-cmd',
|
||||
'importmap:require',
|
||||
$arguments
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException(\sprintf('Invalid importmap entry: "%s".', var_export($importMapEntry, true)));
|
||||
}
|
||||
}
|
||||
|
||||
private function updateControllersJsonFile(array $phpPackages)
|
||||
{
|
||||
if (!file_exists($controllersJsonPath = $this->rootDir.'/assets/controllers.json')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$previousControllersJson = (new JsonFile($controllersJsonPath))->read();
|
||||
} catch (ParsingException $e) {
|
||||
// if controllers.json is invalid (possible during a recipe upgrade), we can't update the file
|
||||
return;
|
||||
}
|
||||
$newControllersJson = [
|
||||
'controllers' => [],
|
||||
'entrypoints' => $previousControllersJson['entrypoints'],
|
||||
];
|
||||
|
||||
foreach ($phpPackages as $phpPackage) {
|
||||
if (!$packageJson = $this->resolvePackageJson($phpPackage)) {
|
||||
continue;
|
||||
}
|
||||
$name = '@'.$phpPackage['name'];
|
||||
|
||||
foreach ($packageJson->read()['symfony']['controllers'] ?? [] as $controllerName => $defaultConfig) {
|
||||
// If the package has just been added (no config), add the default config provided by the package
|
||||
if (!isset($previousControllersJson['controllers'][$name][$controllerName])) {
|
||||
$config = [];
|
||||
$config['enabled'] = $defaultConfig['enabled'];
|
||||
$config['fetch'] = $defaultConfig['fetch'] ?? 'eager';
|
||||
|
||||
if (isset($defaultConfig['autoimport'])) {
|
||||
$config['autoimport'] = $defaultConfig['autoimport'];
|
||||
}
|
||||
|
||||
$newControllersJson['controllers'][$name][$controllerName] = $config;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise, the package exists: merge new config with user config
|
||||
$previousConfig = $previousControllersJson['controllers'][$name][$controllerName];
|
||||
|
||||
$config = [];
|
||||
$config['enabled'] = $previousConfig['enabled'];
|
||||
$config['fetch'] = $previousConfig['fetch'] ?? 'eager';
|
||||
|
||||
if (isset($defaultConfig['autoimport'])) {
|
||||
$config['autoimport'] = [];
|
||||
|
||||
// Use for each autoimport either the previous config if one existed or the default config otherwise
|
||||
foreach ($defaultConfig['autoimport'] as $autoimport => $enabled) {
|
||||
$config['autoimport'][$autoimport] = $previousConfig['autoimport'][$autoimport] ?? $enabled;
|
||||
}
|
||||
}
|
||||
|
||||
$newControllersJson['controllers'][$name][$controllerName] = $config;
|
||||
}
|
||||
|
||||
foreach ($packageJson->read()['symfony']['entrypoints'] ?? [] as $entrypoint => $filename) {
|
||||
if (!isset($newControllersJson['entrypoints'][$entrypoint])) {
|
||||
$newControllersJson['entrypoints'][$entrypoint] = $filename;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
file_put_contents($controllersJsonPath, json_encode($newControllersJson, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES)."\n");
|
||||
}
|
||||
|
||||
private function resolvePackageJson(array $phpPackage): ?JsonFile
|
||||
{
|
||||
$packageDir = $this->rootDir.'/'.$this->vendorDir.'/'.$phpPackage['name'];
|
||||
|
||||
if (!\in_array('symfony-ux', $phpPackage['keywords'] ?? [], true)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (['/assets', '/Resources/assets', '/src/Resources/assets'] as $subdir) {
|
||||
$packageJsonPath = $packageDir.$subdir.'/package.json';
|
||||
|
||||
if (!file_exists($packageJsonPath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return new JsonFile($packageJsonPath);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function normalizePhpPackages(array $phpPackages): array
|
||||
{
|
||||
foreach ($phpPackages as $k => $phpPackage) {
|
||||
if (\is_string($phpPackage)) {
|
||||
// support for smooth upgrades from older flex versions
|
||||
$phpPackages[$k] = $phpPackage = [
|
||||
'name' => $phpPackage,
|
||||
'keywords' => ['symfony-ux'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $phpPackages;
|
||||
}
|
||||
}
|
||||
167
vendor/symfony/flex/src/PackageResolver.php
vendored
Normal file
167
vendor/symfony/flex/src/PackageResolver.php
vendored
Normal file
@@ -0,0 +1,167 @@
|
||||
<?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;
|
||||
use Composer\Semver\Constraint\MatchAllConstraint;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class PackageResolver
|
||||
{
|
||||
private static $SYMFONY_VERSIONS = ['lts', 'previous', 'stable', 'next', 'dev'];
|
||||
private $downloader;
|
||||
|
||||
public function __construct(Downloader $downloader)
|
||||
{
|
||||
$this->downloader = $downloader;
|
||||
}
|
||||
|
||||
public function resolve(array $arguments = [], bool $isRequire = false): array
|
||||
{
|
||||
// first pass split on : and = to resolve package names
|
||||
$packages = [];
|
||||
foreach ($arguments as $i => $argument) {
|
||||
if ((false !== $pos = strpos($argument, ':')) || (false !== $pos = strpos($argument, '='))) {
|
||||
$package = $this->resolvePackageName(substr($argument, 0, $pos), $i, $isRequire);
|
||||
$version = substr($argument, $pos + 1);
|
||||
$packages[] = $package.':'.$version;
|
||||
} else {
|
||||
$packages[] = $this->resolvePackageName($argument, $i, $isRequire);
|
||||
}
|
||||
}
|
||||
|
||||
// second pass to resolve versions
|
||||
$versionParser = new VersionParser();
|
||||
$requires = [];
|
||||
$toGuess = [];
|
||||
foreach ($versionParser->parseNameVersionPairs($packages) as $package) {
|
||||
$version = $this->parseVersion($package['name'], $package['version'] ?? '', $isRequire);
|
||||
if ('' !== $version) {
|
||||
unset($toGuess[$package['name']]);
|
||||
} elseif (!isset($requires[$package['name']])) {
|
||||
$toGuess[$package['name']] = new MatchAllConstraint();
|
||||
}
|
||||
$requires[$package['name']] = $package['name'].$version;
|
||||
}
|
||||
|
||||
if ($toGuess && $isRequire) {
|
||||
foreach ($this->downloader->getSymfonyPacks($toGuess) as $package) {
|
||||
$requires[$package] .= ':*';
|
||||
}
|
||||
}
|
||||
|
||||
return array_values($requires);
|
||||
}
|
||||
|
||||
public function parseVersion(string $package, string $version, bool $isRequire): string
|
||||
{
|
||||
$guess = 'guess' === ($version ?: 'guess');
|
||||
|
||||
if (!str_starts_with($package, 'symfony/')) {
|
||||
return $guess ? '' : ':'.$version;
|
||||
}
|
||||
|
||||
$versions = $this->downloader->getVersions();
|
||||
|
||||
if (!isset($versions['splits'][$package])) {
|
||||
return $guess ? '' : ':'.$version;
|
||||
}
|
||||
|
||||
if ($guess || '*' === $version) {
|
||||
try {
|
||||
$config = @json_decode(file_get_contents(Factory::getComposerFile()), true);
|
||||
} finally {
|
||||
if (!$isRequire || !isset($config['extra']['symfony']['require'])) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
$version = $config['extra']['symfony']['require'];
|
||||
} elseif ('dev' === $version) {
|
||||
$version = '^'.$versions['dev-name'].'@dev';
|
||||
} elseif ('next' === $version) {
|
||||
$version = '^'.$versions[$version].'@dev';
|
||||
} elseif (\in_array($version, self::$SYMFONY_VERSIONS, true)) {
|
||||
$version = '^'.$versions[$version];
|
||||
}
|
||||
|
||||
return ':'.$version;
|
||||
}
|
||||
|
||||
private function resolvePackageName(string $argument, int $position, bool $isRequire): string
|
||||
{
|
||||
$skippedPackages = ['mirrors', 'nothing', ''];
|
||||
|
||||
if (!$isRequire) {
|
||||
$skippedPackages[] = 'lock';
|
||||
}
|
||||
|
||||
if (str_contains($argument, '/') || preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $argument) || preg_match('{(?<=[a-z0-9_/-])\*|\*(?=[a-z0-9_/-])}i', $argument) || \in_array($argument, $skippedPackages)) {
|
||||
return $argument;
|
||||
}
|
||||
|
||||
$aliases = $this->downloader->getAliases();
|
||||
|
||||
if (isset($aliases[$argument])) {
|
||||
$argument = $aliases[$argument];
|
||||
} else {
|
||||
// is it a version or an alias that does not exist?
|
||||
try {
|
||||
$versionParser = new VersionParser();
|
||||
$versionParser->parseConstraints($argument);
|
||||
} catch (\UnexpectedValueException $e) {
|
||||
// is it a special Symfony version?
|
||||
if (!\in_array($argument, self::$SYMFONY_VERSIONS, true)) {
|
||||
$this->throwAlternatives($argument, $position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $argument;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \UnexpectedValueException
|
||||
*/
|
||||
private function throwAlternatives(string $argument, int $position)
|
||||
{
|
||||
$alternatives = [];
|
||||
foreach ($this->downloader->getAliases() as $alias => $package) {
|
||||
$lev = levenshtein($argument, $alias);
|
||||
if ($lev <= \strlen($argument) / 3 || ('' !== $argument && str_contains($alias, $argument))) {
|
||||
$alternatives[$package][] = $alias;
|
||||
}
|
||||
}
|
||||
|
||||
// First position can only be a package name, not a version
|
||||
if ($alternatives || 0 === $position) {
|
||||
$message = \sprintf('"%s" is not a valid alias.', $argument);
|
||||
if ($alternatives) {
|
||||
if (1 === \count($alternatives)) {
|
||||
$message .= " Did you mean this:\n";
|
||||
} else {
|
||||
$message .= " Did you mean one of these:\n";
|
||||
}
|
||||
foreach ($alternatives as $package => $aliases) {
|
||||
$message .= \sprintf(" \"%s\", supported aliases: \"%s\"\n", $package, implode('", "', $aliases));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$message = \sprintf('Could not parse version constraint "%s".', $argument);
|
||||
}
|
||||
|
||||
throw new \UnexpectedValueException($message);
|
||||
}
|
||||
}
|
||||
41
vendor/symfony/flex/src/Path.php
vendored
Normal file
41
vendor/symfony/flex/src/Path.php
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class Path
|
||||
{
|
||||
private $workingDirectory;
|
||||
|
||||
public function __construct($workingDirectory)
|
||||
{
|
||||
$this->workingDirectory = $workingDirectory;
|
||||
}
|
||||
|
||||
public function relativize(string $absolutePath): string
|
||||
{
|
||||
$relativePath = str_replace($this->workingDirectory, '.', $absolutePath);
|
||||
|
||||
return is_dir($absolutePath) ? rtrim($relativePath, '/').'/' : $relativePath;
|
||||
}
|
||||
|
||||
public function concatenate(array $parts): string
|
||||
{
|
||||
$first = array_shift($parts);
|
||||
|
||||
return array_reduce($parts, function (string $initial, string $next): string {
|
||||
return rtrim($initial, '/').'/'.ltrim($next, '/');
|
||||
}, $first);
|
||||
}
|
||||
}
|
||||
123
vendor/symfony/flex/src/Recipe.php
vendored
Normal file
123
vendor/symfony/flex/src/Recipe.php
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex;
|
||||
|
||||
use Composer\Package\PackageInterface;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class Recipe
|
||||
{
|
||||
private $package;
|
||||
private $name;
|
||||
private $job;
|
||||
private $data;
|
||||
private $lock;
|
||||
|
||||
public function __construct(PackageInterface $package, string $name, string $job, array $data, array $lock = [])
|
||||
{
|
||||
$this->package = $package;
|
||||
$this->name = $name;
|
||||
$this->job = $job;
|
||||
$this->data = $data;
|
||||
$this->lock = $lock;
|
||||
}
|
||||
|
||||
public function getPackage(): PackageInterface
|
||||
{
|
||||
return $this->package;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getJob(): string
|
||||
{
|
||||
return $this->job;
|
||||
}
|
||||
|
||||
public function getManifest(): array
|
||||
{
|
||||
if (!isset($this->data['manifest'])) {
|
||||
throw new \LogicException(\sprintf('Manifest is not available for recipe "%s".', $this->name));
|
||||
}
|
||||
|
||||
return $this->data['manifest'];
|
||||
}
|
||||
|
||||
public function getFiles(): array
|
||||
{
|
||||
return $this->data['files'] ?? [];
|
||||
}
|
||||
|
||||
public function getOrigin(): string
|
||||
{
|
||||
return $this->data['origin'] ?? '';
|
||||
}
|
||||
|
||||
public function getFormattedOrigin(): string
|
||||
{
|
||||
if (!$this->getOrigin()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// symfony/translation:3.3@github.com/symfony/recipes:branch
|
||||
if (!preg_match('/^([^:]++):([^@]++)@(.+)$/', $this->getOrigin(), $matches)) {
|
||||
return $this->getOrigin();
|
||||
}
|
||||
|
||||
return \sprintf('<info>%s</> (<comment>>=%s</>): From %s', $matches[1], $matches[2], 'auto-generated recipe' === $matches[3] ? '<comment>'.$matches[3].'</>' : $matches[3]);
|
||||
}
|
||||
|
||||
public function getURL(): string
|
||||
{
|
||||
if (!$this->data['origin']) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// symfony/translation:3.3@github.com/symfony/recipes:branch
|
||||
if (!preg_match('/^([^:]++):([^@]++)@([^:]++):(.+)$/', $this->data['origin'], $matches)) {
|
||||
// that excludes auto-generated recipes, which is what we want
|
||||
return '';
|
||||
}
|
||||
|
||||
return \sprintf('https://%s/tree/%s/%s/%s', $matches[3], $matches[4], $matches[1], $matches[2]);
|
||||
}
|
||||
|
||||
public function isContrib(): bool
|
||||
{
|
||||
return $this->data['is_contrib'] ?? false;
|
||||
}
|
||||
|
||||
public function getRef()
|
||||
{
|
||||
return $this->lock['recipe']['ref'] ?? null;
|
||||
}
|
||||
|
||||
public function isAuto(): bool
|
||||
{
|
||||
return !isset($this->lock['recipe']);
|
||||
}
|
||||
|
||||
public function getVersion(): string
|
||||
{
|
||||
return $this->lock['recipe']['version'] ?? $this->lock['version'];
|
||||
}
|
||||
|
||||
public function getLock(): array
|
||||
{
|
||||
return $this->lock;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
139
vendor/symfony/flex/src/ScriptExecutor.php
vendored
Normal file
139
vendor/symfony/flex/src/ScriptExecutor.php
vendored
Normal file
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex;
|
||||
|
||||
use Composer\Composer;
|
||||
use Composer\EventDispatcher\ScriptExecutionException;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Semver\Constraint\MatchAllConstraint;
|
||||
use Composer\Util\ProcessExecutor;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Output\StreamOutput;
|
||||
use Symfony\Component\Process\PhpExecutableFinder;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class ScriptExecutor
|
||||
{
|
||||
private $composer;
|
||||
private $io;
|
||||
private $options;
|
||||
private $executor;
|
||||
|
||||
public function __construct(Composer $composer, IOInterface $io, Options $options, ?ProcessExecutor $executor = null)
|
||||
{
|
||||
$this->composer = $composer;
|
||||
$this->io = $io;
|
||||
$this->options = $options;
|
||||
$this->executor = $executor ?: new ProcessExecutor();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ScriptExecutionException if the executed command returns a non-0 exit code
|
||||
*/
|
||||
public function execute(string $type, string $cmd, array $arguments = [])
|
||||
{
|
||||
$parsedCmd = $this->options->expandTargetDir($cmd);
|
||||
if (null === $expandedCmd = $this->expandCmd($type, $parsedCmd, $arguments)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$cmdOutput = new StreamOutput(fopen('php://temp', 'rw'), OutputInterface::VERBOSITY_VERBOSE, $this->io->isDecorated());
|
||||
$outputHandler = function ($type, $buffer) use ($cmdOutput) {
|
||||
$cmdOutput->write($buffer, false, OutputInterface::OUTPUT_RAW);
|
||||
};
|
||||
|
||||
$this->io->writeError(\sprintf('Executing script %s', $parsedCmd), $this->io->isVerbose());
|
||||
$exitCode = $this->executor->execute($expandedCmd, $outputHandler);
|
||||
|
||||
$code = 0 === $exitCode ? ' <info>[OK]</>' : ' <error>[KO]</>';
|
||||
|
||||
if ($this->io->isVerbose()) {
|
||||
$this->io->writeError(\sprintf('Executed script %s %s', $cmd, $code));
|
||||
} else {
|
||||
$this->io->writeError($code);
|
||||
}
|
||||
|
||||
if (0 !== $exitCode) {
|
||||
$this->io->writeError(' <error>[KO]</>');
|
||||
$this->io->writeError(\sprintf('<error>Script %s returned with error code %s</>', $cmd, $exitCode));
|
||||
fseek($cmdOutput->getStream(), 0);
|
||||
foreach (explode("\n", stream_get_contents($cmdOutput->getStream())) as $line) {
|
||||
$this->io->writeError('!! '.$line);
|
||||
}
|
||||
|
||||
throw new ScriptExecutionException($cmd, $exitCode);
|
||||
}
|
||||
}
|
||||
|
||||
private function expandCmd(string $type, string $cmd, array $arguments)
|
||||
{
|
||||
switch ($type) {
|
||||
case 'symfony-cmd':
|
||||
return $this->expandSymfonyCmd($cmd, $arguments);
|
||||
case 'php-script':
|
||||
return $this->expandPhpScript($cmd, $arguments);
|
||||
case 'script':
|
||||
return $cmd;
|
||||
default:
|
||||
throw new \InvalidArgumentException(\sprintf('Invalid symfony/flex auto-script in composer.json: "%s" is not a valid type of command.', $type));
|
||||
}
|
||||
}
|
||||
|
||||
private function expandSymfonyCmd(string $cmd, array $arguments)
|
||||
{
|
||||
$repo = $this->composer->getRepositoryManager()->getLocalRepository();
|
||||
if (!$repo->findPackage('symfony/console', new MatchAllConstraint())) {
|
||||
$this->io->writeError(\sprintf('<warning>Skipping "%s" (needs symfony/console to run).</>', $cmd));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$console = ProcessExecutor::escape($this->options->get('root-dir').'/'.$this->options->get('bin-dir').'/console');
|
||||
if ($this->io->isDecorated()) {
|
||||
$console .= ' --ansi';
|
||||
}
|
||||
|
||||
return $this->expandPhpScript($console.' '.$cmd, $arguments);
|
||||
}
|
||||
|
||||
private function expandPhpScript(string $cmd, array $scriptArguments): string
|
||||
{
|
||||
$phpFinder = new PhpExecutableFinder();
|
||||
if (!$php = $phpFinder->find(false)) {
|
||||
throw new \RuntimeException('The PHP executable could not be found, add it to your PATH and try again.');
|
||||
}
|
||||
|
||||
$arguments = $phpFinder->findArguments();
|
||||
|
||||
if ($env = (string) getenv('COMPOSER_ORIGINAL_INIS')) {
|
||||
$paths = explode(\PATH_SEPARATOR, $env);
|
||||
$ini = array_shift($paths);
|
||||
} else {
|
||||
$ini = php_ini_loaded_file();
|
||||
}
|
||||
|
||||
if ($ini) {
|
||||
$arguments[] = '--php-ini='.$ini;
|
||||
}
|
||||
|
||||
if ($memoryLimit = (string) getenv('COMPOSER_MEMORY_LIMIT')) {
|
||||
$arguments[] = "-d memory_limit={$memoryLimit}";
|
||||
}
|
||||
|
||||
$phpArgs = implode(' ', array_map([ProcessExecutor::class, 'escape'], $arguments));
|
||||
$scriptArgs = implode(' ', array_map([ProcessExecutor::class, 'escape'], $scriptArguments));
|
||||
|
||||
return ProcessExecutor::escape($php).($phpArgs ? ' '.$phpArgs : '').' '.$cmd.($scriptArgs ? ' '.$scriptArgs : '');
|
||||
}
|
||||
}
|
||||
115
vendor/symfony/flex/src/SymfonyBundle.php
vendored
Normal file
115
vendor/symfony/flex/src/SymfonyBundle.php
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex;
|
||||
|
||||
use Composer\Composer;
|
||||
use Composer\Package\PackageInterface;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class SymfonyBundle
|
||||
{
|
||||
private $package;
|
||||
private $operation;
|
||||
private $vendorDir;
|
||||
|
||||
public function __construct(Composer $composer, PackageInterface $package, string $operation)
|
||||
{
|
||||
$this->package = $package;
|
||||
$this->operation = $operation;
|
||||
$this->vendorDir = rtrim($composer->getConfig()->get('vendor-dir'), '/');
|
||||
}
|
||||
|
||||
public function getClassNames(): array
|
||||
{
|
||||
$uninstall = 'uninstall' === $this->operation;
|
||||
$classes = [];
|
||||
$autoload = $this->package->getAutoload();
|
||||
$isSyliusPlugin = 'sylius-plugin' === $this->package->getType();
|
||||
foreach (['psr-4' => true, 'psr-0' => false] as $psr => $isPsr4) {
|
||||
if (!isset($autoload[$psr])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($autoload[$psr] as $namespace => $paths) {
|
||||
if (!\is_array($paths)) {
|
||||
$paths = [$paths];
|
||||
}
|
||||
foreach ($paths as $path) {
|
||||
foreach ($this->extractClassNames($namespace, $isSyliusPlugin) as $class) {
|
||||
// we only check class existence on install as we do have the code available
|
||||
// in contrast to uninstall operation
|
||||
if (!$uninstall && !$this->isBundleClass($class, $path, $isPsr4)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$classes[] = $class;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $classes;
|
||||
}
|
||||
|
||||
private function extractClassNames(string $namespace, bool $isSyliusPlugin): array
|
||||
{
|
||||
$namespace = trim($namespace, '\\');
|
||||
$class = $namespace.'\\';
|
||||
$parts = explode('\\', $namespace);
|
||||
$suffix = $parts[\count($parts) - 1];
|
||||
$endOfWord = substr($suffix, -6);
|
||||
|
||||
if ($isSyliusPlugin) {
|
||||
if ('Bundle' !== $endOfWord && 'Plugin' !== $endOfWord) {
|
||||
$suffix .= 'Bundle';
|
||||
}
|
||||
} elseif ('Bundle' !== $endOfWord) {
|
||||
$suffix .= 'Bundle';
|
||||
}
|
||||
|
||||
$classes = [$class.$suffix];
|
||||
$acc = '';
|
||||
foreach (\array_slice($parts, 0, -1) as $part) {
|
||||
if ('Bundle' === $part || ($isSyliusPlugin && 'Plugin' === $part)) {
|
||||
continue;
|
||||
}
|
||||
$classes[] = $class.$part.$suffix;
|
||||
$acc .= $part;
|
||||
$classes[] = $class.$acc.$suffix;
|
||||
}
|
||||
|
||||
return array_unique($classes);
|
||||
}
|
||||
|
||||
private function isBundleClass(string $class, string $path, bool $isPsr4): bool
|
||||
{
|
||||
$classPath = ($this->vendorDir ? $this->vendorDir.'/' : '').$this->package->getPrettyName().'/'.$path.'/';
|
||||
$parts = explode('\\', $class);
|
||||
$class = $parts[\count($parts) - 1];
|
||||
if (!$isPsr4) {
|
||||
$classPath .= str_replace('\\', '', implode('/', \array_slice($parts, 0, -1))).'/';
|
||||
}
|
||||
$classPath .= str_replace('\\', '/', $class).'.php';
|
||||
|
||||
if (!file_exists($classPath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// heuristic that should work in almost all cases
|
||||
$classContents = file_get_contents($classPath);
|
||||
|
||||
return str_contains($classContents, 'Symfony\Component\HttpKernel\Bundle\Bundle')
|
||||
|| str_contains($classContents, 'Symfony\Component\HttpKernel\Bundle\AbstractBundle');
|
||||
}
|
||||
}
|
||||
22
vendor/symfony/flex/src/SymfonyPackInstaller.php
vendored
Normal file
22
vendor/symfony/flex/src/SymfonyPackInstaller.php
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
<?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\Installer\MetapackageInstaller;
|
||||
|
||||
class SymfonyPackInstaller extends MetapackageInstaller
|
||||
{
|
||||
public function supports($packageType): bool
|
||||
{
|
||||
return 'symfony-pack' === $packageType;
|
||||
}
|
||||
}
|
||||
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'];
|
||||
}
|
||||
}
|
||||
213
vendor/symfony/flex/src/Unpacker.php
vendored
Normal file
213
vendor/symfony/flex/src/Unpacker.php
vendored
Normal file
@@ -0,0 +1,213 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex;
|
||||
|
||||
use Composer\Composer;
|
||||
use Composer\Config\JsonConfigSource;
|
||||
use Composer\Factory;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Json\JsonFile;
|
||||
use Composer\Json\JsonManipulator;
|
||||
use Composer\Package\Locker;
|
||||
use Composer\Package\Version\VersionSelector;
|
||||
use Composer\Repository\CompositeRepository;
|
||||
use Composer\Repository\RepositorySet;
|
||||
use Composer\Semver\VersionParser;
|
||||
use Symfony\Flex\Unpack\Operation;
|
||||
use Symfony\Flex\Unpack\Result;
|
||||
|
||||
class Unpacker
|
||||
{
|
||||
private $composer;
|
||||
private $resolver;
|
||||
private $versionParser;
|
||||
|
||||
public function __construct(Composer $composer, PackageResolver $resolver)
|
||||
{
|
||||
$this->composer = $composer;
|
||||
$this->resolver = $resolver;
|
||||
$this->versionParser = new VersionParser();
|
||||
}
|
||||
|
||||
public function unpack(Operation $op, ?Result $result = null, &$links = [], bool $devRequire = false): Result
|
||||
{
|
||||
if (null === $result) {
|
||||
$result = new Result();
|
||||
}
|
||||
|
||||
$localRepo = $this->composer->getRepositoryManager()->getLocalRepository();
|
||||
foreach ($op->getPackages() as $package) {
|
||||
$pkg = $localRepo->findPackage($package['name'], '*');
|
||||
$pkg = $pkg ?? $this->composer->getRepositoryManager()->findPackage($package['name'], $package['version'] ?: '*');
|
||||
|
||||
// not unpackable or no --unpack flag or empty packs (markers)
|
||||
if (
|
||||
null === $pkg
|
||||
|| 'symfony-pack' !== $pkg->getType()
|
||||
|| !$op->shouldUnpack()
|
||||
|| 0 === \count($pkg->getRequires()) + \count($pkg->getDevRequires())
|
||||
) {
|
||||
$result->addRequired($package['name'].($package['version'] ? ':'.$package['version'] : ''));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$result->addUnpacked($pkg)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$requires = [];
|
||||
foreach ($pkg->getRequires() as $link) {
|
||||
$requires[$link->getTarget()] = $link;
|
||||
}
|
||||
$devRequires = $pkg->getDevRequires();
|
||||
|
||||
foreach ($devRequires as $i => $link) {
|
||||
if (!isset($requires[$link->getTarget()])) {
|
||||
throw new \RuntimeException(\sprintf('Symfony pack "%s" must duplicate all entries from "require-dev" into "require" but entry "%s" was not found.', $package['name'], $link->getTarget()));
|
||||
}
|
||||
$devRequires[$i] = $requires[$link->getTarget()];
|
||||
unset($requires[$link->getTarget()]);
|
||||
}
|
||||
|
||||
$versionSelector = null;
|
||||
foreach ([$requires, $devRequires] as $dev => $requires) {
|
||||
$dev = $dev ?: $devRequire ?: $package['dev'];
|
||||
|
||||
foreach ($requires as $link) {
|
||||
if ('php' === $linkName = $link->getTarget()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$constraint = $link->getPrettyConstraint();
|
||||
$constraint = substr($this->resolver->parseVersion($linkName, $constraint, true), 1) ?: $constraint;
|
||||
|
||||
if ($subPkg = $localRepo->findPackage($linkName, '*')) {
|
||||
if ('symfony-pack' === $subPkg->getType()) {
|
||||
$subOp = new Operation(true, $op->shouldSort());
|
||||
$subOp->addPackage($subPkg->getName(), $constraint, $dev);
|
||||
$result = $this->unpack($subOp, $result, $links, $dev);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ('*' === $constraint) {
|
||||
if (null === $versionSelector) {
|
||||
$pool = new RepositorySet($this->composer->getPackage()->getMinimumStability(), $this->composer->getPackage()->getStabilityFlags());
|
||||
$pool->addRepository(new CompositeRepository($this->composer->getRepositoryManager()->getRepositories()));
|
||||
$versionSelector = new VersionSelector($pool);
|
||||
}
|
||||
|
||||
$constraint = $versionSelector->findRecommendedRequireVersion($subPkg);
|
||||
}
|
||||
}
|
||||
|
||||
$linkType = $dev ? 'require-dev' : 'require';
|
||||
$constraint = $this->versionParser->parseConstraints($constraint);
|
||||
|
||||
if (isset($links[$linkName])) {
|
||||
$links[$linkName]['constraints'][] = $constraint;
|
||||
if ('require' === $linkType) {
|
||||
$links[$linkName]['type'] = 'require';
|
||||
}
|
||||
} else {
|
||||
$links[$linkName] = [
|
||||
'type' => $linkType,
|
||||
'name' => $linkName,
|
||||
'constraints' => [$constraint],
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (1 < \func_num_args()) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$jsonPath = Factory::getComposerFile();
|
||||
$jsonContent = file_get_contents($jsonPath);
|
||||
$jsonStored = json_decode($jsonContent, true);
|
||||
$jsonManipulator = new JsonManipulator($jsonContent);
|
||||
|
||||
foreach ($result->getUnpacked() as $pkg) {
|
||||
$localRepo->removePackage($pkg);
|
||||
$localRepo->setDevPackageNames(array_diff($localRepo->getDevPackageNames(), [$pkg->getName()]));
|
||||
$jsonManipulator->removeSubNode('require', $pkg->getName());
|
||||
$jsonManipulator->removeSubNode('require-dev', $pkg->getName());
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
$lockFile->write($lockData);
|
||||
|
||||
$locker = new Locker($io, $lockFile, $this->composer->getInstallationManager(), $jsonContent);
|
||||
$this->composer->setLocker($locker);
|
||||
|
||||
$localRepo = $this->composer->getRepositoryManager()->getLocalRepository();
|
||||
$localRepo->write($localRepo->getDevMode() ?? true, $this->composer->getInstallationManager());
|
||||
}
|
||||
}
|
||||
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 .= "\n";
|
||||
}
|
||||
|
||||
return $patch;
|
||||
}
|
||||
}
|
||||
52
vendor/symfony/flex/src/Update/RecipePatch.php
vendored
Normal file
52
vendor/symfony/flex/src/Update/RecipePatch.php
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex\Update;
|
||||
|
||||
class RecipePatch
|
||||
{
|
||||
private $patch;
|
||||
private $blobs;
|
||||
private $deletedFiles;
|
||||
private $removedPatches;
|
||||
|
||||
public function __construct(string $patch, array $blobs, array $deletedFiles, array $removedPatches = [])
|
||||
{
|
||||
$this->patch = $patch;
|
||||
$this->blobs = $blobs;
|
||||
$this->deletedFiles = $deletedFiles;
|
||||
$this->removedPatches = $removedPatches;
|
||||
}
|
||||
|
||||
public function getPatch(): string
|
||||
{
|
||||
return $this->patch;
|
||||
}
|
||||
|
||||
public function getBlobs(): array
|
||||
{
|
||||
return $this->blobs;
|
||||
}
|
||||
|
||||
public function getDeletedFiles(): array
|
||||
{
|
||||
return $this->deletedFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Patches for modified files that were removed because the file
|
||||
* has been deleted in the user's project.
|
||||
*/
|
||||
public function getRemovedPatches(): array
|
||||
{
|
||||
return $this->removedPatches;
|
||||
}
|
||||
}
|
||||
281
vendor/symfony/flex/src/Update/RecipePatcher.php
vendored
Normal file
281
vendor/symfony/flex/src/Update/RecipePatcher.php
vendored
Normal file
@@ -0,0 +1,281 @@
|
||||
<?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;
|
||||
use Symfony\Flex\Lock;
|
||||
|
||||
class RecipePatcher
|
||||
{
|
||||
private $rootDir;
|
||||
private $filesystem;
|
||||
private $io;
|
||||
private $processExecutor;
|
||||
private $symfonyLock;
|
||||
|
||||
public function __construct(string $rootDir, IOInterface $io, Lock $symfonyLock)
|
||||
{
|
||||
$this->rootDir = $rootDir;
|
||||
$this->filesystem = new Filesystem();
|
||||
$this->io = $io;
|
||||
$this->symfonyLock = $symfonyLock;
|
||||
$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, ?string $packageName = null): bool
|
||||
{
|
||||
$withConflicts = $this->_applyPatchFile($patch);
|
||||
$lockedFiles = $packageName ? array_count_values(array_merge(...array_column(array_filter($this->symfonyLock->all(), fn ($package) => $package !== $packageName, \ARRAY_FILTER_USE_KEY), 'files'))) : [];
|
||||
|
||||
$nonRemovableFiles = [];
|
||||
foreach ($patch->getDeletedFiles() as $deletedFile) {
|
||||
if (!file_exists($this->rootDir.'/'.$deletedFile)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($lockedFiles[$deletedFile])) {
|
||||
$nonRemovableFiles[] = $deletedFile;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->execute(\sprintf('git rm %s', ProcessExecutor::escape($deletedFile)), $this->rootDir);
|
||||
}
|
||||
|
||||
if ($nonRemovableFiles) {
|
||||
$this->io->writeError(' <warning>The following files were removed in the recipe, but are still referenced by other recipes. You might need to adjust them manually:</warning>');
|
||||
foreach ($nonRemovableFiles as $file) {
|
||||
$this->io->writeError(' - '.$file);
|
||||
}
|
||||
|
||||
$this->io->writeError('');
|
||||
}
|
||||
|
||||
return $withConflicts;
|
||||
}
|
||||
|
||||
public function generatePatch(array $originalFiles, array $newFiles): RecipePatch
|
||||
{
|
||||
$ignoredFiles = $this->getIgnoredFiles(array_keys($originalFiles) + array_keys($newFiles));
|
||||
|
||||
// null implies "file does not exist"
|
||||
$originalFiles = array_filter($originalFiles, function ($file, $fileName) use ($ignoredFiles) {
|
||||
return null !== $file && !\in_array($fileName, $ignoredFiles);
|
||||
}, \ARRAY_FILTER_USE_BOTH);
|
||||
|
||||
$newFiles = array_filter($newFiles, function ($file, $fileName) use ($ignoredFiles) {
|
||||
return null !== $file && !\in_array($fileName, $ignoredFiles);
|
||||
}, \ARRAY_FILTER_USE_BOTH);
|
||||
|
||||
$deletedFiles = [];
|
||||
// find removed files & record that they are deleted
|
||||
// unset them from originalFiles to avoid unnecessary blobs being added
|
||||
foreach ($originalFiles as $file => $contents) {
|
||||
if (!isset($newFiles[$file])) {
|
||||
$deletedFiles[] = $file;
|
||||
unset($originalFiles[$file]);
|
||||
}
|
||||
}
|
||||
|
||||
// If a file is being modified, but does not exist in the current project,
|
||||
// it cannot be patched. We generate the diff for these, but then remove
|
||||
// it from the patch (and optionally report this diff to the user).
|
||||
$modifiedFiles = array_intersect_key(array_keys($originalFiles), array_keys($newFiles));
|
||||
$deletedModifiedFiles = [];
|
||||
foreach ($modifiedFiles as $modifiedFile) {
|
||||
if (!file_exists($this->rootDir.'/'.$modifiedFile) && $originalFiles[$modifiedFile] !== $newFiles[$modifiedFile]) {
|
||||
$deletedModifiedFiles[] = $modifiedFile;
|
||||
}
|
||||
}
|
||||
|
||||
// Use git binary to get project path from repository root
|
||||
$prefix = trim($this->execute('git rev-parse --show-prefix', $this->rootDir));
|
||||
$tmpPath = sys_get_temp_dir().'/_flex_recipe_update'.uniqid(mt_rand(), true);
|
||||
$this->filesystem->mkdir($tmpPath);
|
||||
|
||||
try {
|
||||
$this->execute('git init', $tmpPath);
|
||||
$this->execute('git config commit.gpgsign false', $tmpPath);
|
||||
$this->execute('git config user.name "Flex Updater"', $tmpPath);
|
||||
$this->execute('git config user.email ""', $tmpPath);
|
||||
|
||||
$blobs = [];
|
||||
if (\count($originalFiles) > 0) {
|
||||
$this->writeFiles($originalFiles, $tmpPath);
|
||||
$this->execute('git add -A', $tmpPath);
|
||||
$this->execute('git commit -n -m "original files"', $tmpPath);
|
||||
|
||||
$blobs = $this->generateBlobs($originalFiles, $tmpPath);
|
||||
}
|
||||
|
||||
$this->writeFiles($newFiles, $tmpPath);
|
||||
$this->execute('git add -A', $tmpPath);
|
||||
|
||||
$patchString = $this->execute(\sprintf('git diff --cached --src-prefix "a/%s" --dst-prefix "b/%s"', $prefix, $prefix), $tmpPath);
|
||||
$removedPatches = [];
|
||||
$patchString = DiffHelper::removeFilesFromPatch($patchString, $deletedModifiedFiles, $removedPatches);
|
||||
|
||||
return new RecipePatch(
|
||||
$patchString,
|
||||
$blobs,
|
||||
$deletedFiles,
|
||||
$removedPatches
|
||||
);
|
||||
} finally {
|
||||
try {
|
||||
$this->filesystem->remove($tmpPath);
|
||||
} catch (IOException $e) {
|
||||
// this can sometimes fail due to git file permissions
|
||||
// if that happens, just leave it: we're in the temp directory anyways
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function writeFiles(array $files, string $directory): void
|
||||
{
|
||||
foreach ($files as $filename => $contents) {
|
||||
$path = $directory.'/'.$filename;
|
||||
if (null === $contents) {
|
||||
if (file_exists($path)) {
|
||||
unlink($path);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!file_exists(\dirname($path))) {
|
||||
$this->filesystem->mkdir(\dirname($path));
|
||||
}
|
||||
file_put_contents($path, $contents);
|
||||
}
|
||||
}
|
||||
|
||||
private function execute(string $command, string $cwd): string
|
||||
{
|
||||
$output = '';
|
||||
$statusCode = $this->processExecutor->execute($command, $output, $cwd);
|
||||
|
||||
if (0 !== $statusCode) {
|
||||
throw new \LogicException(\sprintf('Command "%s" failed: "%s". Output: "%s".', $command, $this->processExecutor->getErrorOutput(), $output));
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds git blobs for each original file.
|
||||
*
|
||||
* For patching to work, each original file & contents needs to be
|
||||
* available to git as a blob. This is because the patch contains
|
||||
* the ref to the original blob, and git uses that to find the
|
||||
* original file (which is needed for the 3-way merge).
|
||||
*/
|
||||
private function addMissingBlobs(array $blobs): array
|
||||
{
|
||||
$addedBlobs = [];
|
||||
foreach ($blobs as $hash => $contents) {
|
||||
$blobPath = $this->getBlobPath($this->rootDir, $hash);
|
||||
if (file_exists($blobPath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$addedBlobs[] = $blobPath;
|
||||
if (!file_exists(\dirname($blobPath))) {
|
||||
$this->filesystem->mkdir(\dirname($blobPath));
|
||||
}
|
||||
file_put_contents($blobPath, $contents);
|
||||
}
|
||||
|
||||
return $addedBlobs;
|
||||
}
|
||||
|
||||
private function generateBlobs(array $originalFiles, string $originalFilesRoot): array
|
||||
{
|
||||
$addedBlobs = [];
|
||||
foreach ($originalFiles as $filename => $contents) {
|
||||
// if the file didn't originally exist, no blob needed
|
||||
if (!file_exists($originalFilesRoot.'/'.$filename)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$hash = trim($this->execute('git hash-object '.ProcessExecutor::escape($filename), $originalFilesRoot));
|
||||
$addedBlobs[$hash] = file_get_contents($this->getBlobPath($originalFilesRoot, $hash));
|
||||
}
|
||||
|
||||
return $addedBlobs;
|
||||
}
|
||||
|
||||
private function getBlobPath(string $gitRoot, string $hash): string
|
||||
{
|
||||
$gitDir = trim($this->execute('git rev-parse --absolute-git-dir', $gitRoot));
|
||||
|
||||
$hashStart = substr($hash, 0, 2);
|
||||
$hashEnd = substr($hash, 2);
|
||||
|
||||
return $gitDir.'/objects/'.$hashStart.'/'.$hashEnd;
|
||||
}
|
||||
|
||||
private function _applyPatchFile(RecipePatch $patch)
|
||||
{
|
||||
if (!$patch->getPatch()) {
|
||||
// nothing to do!
|
||||
return true;
|
||||
}
|
||||
|
||||
$addedBlobs = $this->addMissingBlobs($patch->getBlobs());
|
||||
|
||||
$patchPath = $this->rootDir.'/_flex_recipe_update.patch';
|
||||
file_put_contents($patchPath, $patch->getPatch());
|
||||
|
||||
try {
|
||||
$this->execute('git update-index --refresh', $this->rootDir);
|
||||
|
||||
$output = '';
|
||||
$statusCode = $this->processExecutor->execute('git apply "_flex_recipe_update.patch" -3', $output, $this->rootDir);
|
||||
|
||||
if (0 === $statusCode) {
|
||||
// successful with no conflicts
|
||||
return true;
|
||||
}
|
||||
|
||||
if (str_contains($this->processExecutor->getErrorOutput(), 'with conflicts')) {
|
||||
// successful with conflicts
|
||||
return false;
|
||||
}
|
||||
|
||||
throw new \LogicException('Error applying the patch: '.$this->processExecutor->getErrorOutput());
|
||||
} finally {
|
||||
unlink($patchPath);
|
||||
// clean up any temporary blobs
|
||||
foreach ($addedBlobs as $filename) {
|
||||
unlink($filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function getIgnoredFiles(array $fileNames): array
|
||||
{
|
||||
$args = implode(' ', array_map([ProcessExecutor::class, 'escape'], $fileNames));
|
||||
$output = '';
|
||||
$this->processExecutor->execute(\sprintf('git check-ignore %s', $args), $output, $this->rootDir);
|
||||
|
||||
return $this->processExecutor->splitLines($output);
|
||||
}
|
||||
}
|
||||
114
vendor/symfony/flex/src/Update/RecipeUpdate.php
vendored
Normal file
114
vendor/symfony/flex/src/Update/RecipeUpdate.php
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Flex\Update;
|
||||
|
||||
use Symfony\Flex\Lock;
|
||||
use Symfony\Flex\Recipe;
|
||||
|
||||
class RecipeUpdate
|
||||
{
|
||||
private $originalRecipe;
|
||||
private $newRecipe;
|
||||
private $lock;
|
||||
private $rootDir;
|
||||
|
||||
/** @var string[] */
|
||||
private $originalRecipeFiles = [];
|
||||
/** @var string[] */
|
||||
private $newRecipeFiles = [];
|
||||
private $copyFromPackagePaths = [];
|
||||
|
||||
public function __construct(Recipe $originalRecipe, Recipe $newRecipe, Lock $lock, string $rootDir)
|
||||
{
|
||||
$this->originalRecipe = $originalRecipe;
|
||||
$this->newRecipe = $newRecipe;
|
||||
$this->lock = $lock;
|
||||
$this->rootDir = $rootDir;
|
||||
}
|
||||
|
||||
public function getOriginalRecipe(): Recipe
|
||||
{
|
||||
return $this->originalRecipe;
|
||||
}
|
||||
|
||||
public function getNewRecipe(): Recipe
|
||||
{
|
||||
return $this->newRecipe;
|
||||
}
|
||||
|
||||
public function getLock(): Lock
|
||||
{
|
||||
return $this->lock;
|
||||
}
|
||||
|
||||
public function getRootDir(): string
|
||||
{
|
||||
return $this->rootDir;
|
||||
}
|
||||
|
||||
public function getPackageName(): string
|
||||
{
|
||||
return $this->originalRecipe->getName();
|
||||
}
|
||||
|
||||
public function setOriginalFile(string $filename, ?string $contents): void
|
||||
{
|
||||
$this->originalRecipeFiles[$filename] = $contents;
|
||||
}
|
||||
|
||||
public function setNewFile(string $filename, ?string $contents): void
|
||||
{
|
||||
$this->newRecipeFiles[$filename] = $contents;
|
||||
}
|
||||
|
||||
public function addOriginalFiles(array $files)
|
||||
{
|
||||
foreach ($files as $file => $contents) {
|
||||
if (null === $contents) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->setOriginalFile($file, $contents);
|
||||
}
|
||||
}
|
||||
|
||||
public function addNewFiles(array $files)
|
||||
{
|
||||
foreach ($files as $file => $contents) {
|
||||
if (null === $contents) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->setNewFile($file, $contents);
|
||||
}
|
||||
}
|
||||
|
||||
public function getOriginalFiles(): array
|
||||
{
|
||||
return $this->originalRecipeFiles;
|
||||
}
|
||||
|
||||
public function getNewFiles(): array
|
||||
{
|
||||
return $this->newRecipeFiles;
|
||||
}
|
||||
|
||||
public function getCopyFromPackagePaths(): array
|
||||
{
|
||||
return $this->copyFromPackagePaths;
|
||||
}
|
||||
|
||||
public function addCopyFromPackagePath(string $source, string $target)
|
||||
{
|
||||
$this->copyFromPackagePaths[$source] = $target;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user