mirror of
https://github.com/silverstripe/recipe-plugin.git
synced 2024-10-22 14:05:55 +02:00
Update to use composer root object
Move shared commands to trait
This commit is contained in:
parent
d1d1b59662
commit
fb72c0fcc4
185
src/RecipeCommandBehaviour.php
Normal file
185
src/RecipeCommandBehaviour.php
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\RecipePlugin;
|
||||||
|
|
||||||
|
use BadMethodCallException;
|
||||||
|
use Composer\Command\RequireCommand;
|
||||||
|
use Composer\Command\UpdateCommand;
|
||||||
|
use Composer\Composer;
|
||||||
|
use MongoDB\Driver\Exception\InvalidArgumentException;
|
||||||
|
use Symfony\Component\Console\Application;
|
||||||
|
use Symfony\Component\Console\Input\ArrayInput;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
|
trait RecipeCommandBehaviour
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Gets the application instance for this command.
|
||||||
|
*
|
||||||
|
* @return Application An Application instance
|
||||||
|
*/
|
||||||
|
public abstract function getApplication();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param bool $required
|
||||||
|
* @param bool|null $disablePlugins
|
||||||
|
* @throws \RuntimeException
|
||||||
|
* @return Composer
|
||||||
|
*/
|
||||||
|
public abstract function getComposer($required = true, $disablePlugins = null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the cached composer instance
|
||||||
|
*/
|
||||||
|
public abstract function resetComposer();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load composer data from the given directory
|
||||||
|
*
|
||||||
|
* @param string $path
|
||||||
|
* @param array|null $default If file doesn't exist use this default. If null, file is mandatory and there is
|
||||||
|
* no default
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function loadComposer($path, $default = null)
|
||||||
|
{
|
||||||
|
if (!file_exists($path)) {
|
||||||
|
if (isset($default)) {
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
|
throw new BadMethodCallException("Could not find " . basename($path));
|
||||||
|
}
|
||||||
|
$data = json_decode(file_get_contents($path), true);
|
||||||
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||||
|
throw new \LogicException("Invalid composer.json with error: " . json_last_error_msg());
|
||||||
|
}
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save the given data to the composer file in the given directory
|
||||||
|
*
|
||||||
|
* @param string $directory
|
||||||
|
* @param array $data
|
||||||
|
*/
|
||||||
|
protected function saveComposer($directory, $data)
|
||||||
|
{
|
||||||
|
$path = $directory.'/composer.json';
|
||||||
|
if (!file_exists($path)) {
|
||||||
|
throw new BadMethodCallException("Could not find composer.json");
|
||||||
|
}
|
||||||
|
$content = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||||
|
// Make sure errors are reported
|
||||||
|
if (json_last_error()) {
|
||||||
|
throw new InvalidArgumentException("Invalid composer.json data with error: " . json_last_error_msg());
|
||||||
|
}
|
||||||
|
file_put_contents($path, $content);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param OutputInterface $output
|
||||||
|
* @param string $recipe
|
||||||
|
* @param string $constraint
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
protected function requireRecipe(OutputInterface $output, $recipe, $constraint = null)
|
||||||
|
{
|
||||||
|
/** @var RequireCommand $command */
|
||||||
|
$command = $this->getApplication()->find('require');
|
||||||
|
$packages = [$recipe];
|
||||||
|
if ($constraint) {
|
||||||
|
$packages[] = $constraint;
|
||||||
|
}
|
||||||
|
$arguments = [
|
||||||
|
'command' => 'require',
|
||||||
|
'packages' => $packages,
|
||||||
|
];
|
||||||
|
$requireInput = new ArrayInput($arguments);
|
||||||
|
$returnCode = $command->run($requireInput, $output);
|
||||||
|
|
||||||
|
// Flush modified composer object
|
||||||
|
$this->resetComposer();
|
||||||
|
return $returnCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the project
|
||||||
|
*
|
||||||
|
* @param OutputInterface $output
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
protected function updateProject(OutputInterface $output)
|
||||||
|
{
|
||||||
|
/** @var UpdateCommand $command */
|
||||||
|
$command = $this->getApplication()->find('update');
|
||||||
|
$arguments = [ 'command' => 'update' ];
|
||||||
|
$requireInput = new ArrayInput($arguments);
|
||||||
|
$returnCode = $command->run($requireInput, $output);
|
||||||
|
|
||||||
|
// Flush modified composer object
|
||||||
|
$this->resetComposer();
|
||||||
|
return $returnCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find installed version or constraint
|
||||||
|
*
|
||||||
|
* @param string $recipe
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function findInstalledVersion($recipe)
|
||||||
|
{
|
||||||
|
// Check locker
|
||||||
|
$installed = $this->getComposer()->getLocker()->getLockedRepository()->findPackage($recipe, '*');
|
||||||
|
if ($installed) {
|
||||||
|
return $installed->getPrettyVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check provides
|
||||||
|
$provides = $this->getComposer()->getPackage()->getProvides();
|
||||||
|
if (isset($provides[$recipe])) {
|
||||||
|
return $provides[$recipe]->getPrettyConstraint();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check requires
|
||||||
|
$requires = $this->getComposer()->getPackage()->getRequires();
|
||||||
|
if (isset($requires[$recipe])) {
|
||||||
|
return $provides[$recipe]->getPrettyConstraint();
|
||||||
|
}
|
||||||
|
|
||||||
|
// No existing version
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Guess constraint to use if not provided
|
||||||
|
*
|
||||||
|
* @param string $existingVersion Known installed version
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function findBestConstraint($existingVersion)
|
||||||
|
{
|
||||||
|
// Cannot guess without existing version
|
||||||
|
if (!$existingVersion) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Existing version is already a ^1.0.0 or ~1.0.0 constraint
|
||||||
|
if (preg_match('#^[~^]#', $existingVersion)) {
|
||||||
|
return $existingVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Existing version is already a dev constraint
|
||||||
|
if (stristr($existingVersion, 'dev') !== false) {
|
||||||
|
return $existingVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Numeric-only version maps to semver constraint
|
||||||
|
if (preg_match('#^([\d.]+)$#', $existingVersion)) {
|
||||||
|
return "^{$existingVersion}";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cannot guess; Let composer choose (equivalent to `composer require vendor/library`)
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -2,18 +2,19 @@
|
|||||||
|
|
||||||
namespace SilverStripe\RecipePlugin;
|
namespace SilverStripe\RecipePlugin;
|
||||||
|
|
||||||
use BadMethodCallException;
|
|
||||||
use Composer\Command\BaseCommand;
|
use Composer\Command\BaseCommand;
|
||||||
use Composer\Command\RequireCommand;
|
|
||||||
use Composer\Command\UpdateCommand;
|
|
||||||
use MongoDB\Driver\Exception\InvalidArgumentException;
|
|
||||||
use Symfony\Component\Console\Input\ArrayInput;
|
|
||||||
use Symfony\Component\Console\Input\InputArgument;
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the 'require-recipe' command which allows a new recipe to be installed, but also
|
||||||
|
* soft-updates any existing recipe.
|
||||||
|
*/
|
||||||
class RequireRecipeCommand extends BaseCommand
|
class RequireRecipeCommand extends BaseCommand
|
||||||
{
|
{
|
||||||
|
use RecipeCommandBehaviour;
|
||||||
|
|
||||||
public function configure()
|
public function configure()
|
||||||
{
|
{
|
||||||
$this->setName('require-recipe');
|
$this->setName('require-recipe');
|
||||||
@ -52,14 +53,11 @@ HELP
|
|||||||
|
|
||||||
public function execute(InputInterface $input, OutputInterface $output)
|
public function execute(InputInterface $input, OutputInterface $output)
|
||||||
{
|
{
|
||||||
// Get args and existing composer data
|
|
||||||
$recipe = $input->getArgument('recipe');
|
$recipe = $input->getArgument('recipe');
|
||||||
$constraint = $input->getArgument('version');
|
$constraint = $input->getArgument('version');
|
||||||
|
|
||||||
// Check if this is already installed
|
// Check if this is already installed and notify users
|
||||||
$installedVersion = $this->findInstalledVersion($recipe);
|
$installedVersion = $this->findInstalledVersion($recipe);
|
||||||
|
|
||||||
// Notify users of which version is being updated
|
|
||||||
if ($installedVersion) {
|
if ($installedVersion) {
|
||||||
if ($constraint) {
|
if ($constraint) {
|
||||||
$output->writeln(
|
$output->writeln(
|
||||||
@ -87,18 +85,23 @@ HELP
|
|||||||
return $returnCode;
|
return $returnCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get composer data for both root and newly installed recipe
|
// Begin modification of composer.json
|
||||||
$composerData = $this->loadComposer(getcwd() .'/composer.json');
|
$composerData = $this->loadComposer(getcwd() .'/composer.json');
|
||||||
$recipeData = $this->loadComposer(getcwd().'/vendor/'.$recipe.'/composer.json');
|
|
||||||
|
|
||||||
// Promote all dependencies
|
// Get composer data for both root and newly installed recipe
|
||||||
if (!empty($recipeData['require'])) {
|
$installedRecipe = $this
|
||||||
|
->getComposer()
|
||||||
|
->getRepositoryManager()
|
||||||
|
->getLocalRepository()
|
||||||
|
->findPackage($recipe, '*');
|
||||||
|
if ($installedRecipe) {
|
||||||
$output->writeln("Inlining all dependencies for recipe <info>{$recipe}</info>:");
|
$output->writeln("Inlining all dependencies for recipe <info>{$recipe}</info>:");
|
||||||
foreach ($recipeData['require'] as $dependencyName => $dependencyVersion) {
|
foreach ($installedRecipe->getRequires() as $requireName => $require) {
|
||||||
|
$requireVersion = $require->getPrettyConstraint();
|
||||||
$output->writeln(
|
$output->writeln(
|
||||||
" * Inline dependency <info>{$dependencyName}</info> as <info>{$dependencyVersion}</info>"
|
" * Inline dependency <info>{$requireName}</info> as <info>{$requireVersion}</info>"
|
||||||
);
|
);
|
||||||
$composerData['require'][$dependencyName] = $dependencyVersion;
|
$composerData['require'][$requireName] = $requireVersion;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,155 +117,4 @@ HELP
|
|||||||
$this->saveComposer(getcwd(), $composerData);
|
$this->saveComposer(getcwd(), $composerData);
|
||||||
return $this->updateProject($output);
|
return $this->updateProject($output);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Load composer data from the given directory
|
|
||||||
*
|
|
||||||
* @param string $path
|
|
||||||
* @param array|null $default If file doesn't exist use this default. If null, file is mandatory and there is
|
|
||||||
* no default
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
protected function loadComposer($path, $default = null)
|
|
||||||
{
|
|
||||||
if (!file_exists($path)) {
|
|
||||||
if (isset($default)) {
|
|
||||||
return $default;
|
|
||||||
}
|
|
||||||
throw new BadMethodCallException("Could not find " . basename($path));
|
|
||||||
}
|
|
||||||
$data = json_decode(file_get_contents($path), true);
|
|
||||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
|
||||||
throw new \LogicException("Invalid composer.json with error: " . json_last_error_msg());
|
|
||||||
}
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Save the given data to the composer file in the given directory
|
|
||||||
*
|
|
||||||
* @param string $directory
|
|
||||||
* @param array $data
|
|
||||||
*/
|
|
||||||
protected function saveComposer($directory, $data)
|
|
||||||
{
|
|
||||||
$path = $directory.'/composer.json';
|
|
||||||
if (!file_exists($path)) {
|
|
||||||
throw new BadMethodCallException("Could not find composer.json");
|
|
||||||
}
|
|
||||||
$content = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
|
||||||
// Make sure errors are reported
|
|
||||||
if (json_last_error()) {
|
|
||||||
throw new InvalidArgumentException("Invalid composer.json data with error: " . json_last_error_msg());
|
|
||||||
}
|
|
||||||
file_put_contents($path, $content);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param OutputInterface $output
|
|
||||||
* @param string $recipe
|
|
||||||
* @param string $constraint
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
protected function requireRecipe(OutputInterface $output, $recipe, $constraint = null)
|
|
||||||
{
|
|
||||||
/** @var RequireCommand $command */
|
|
||||||
$command = $this->getApplication()->find('require');
|
|
||||||
$packages = [$recipe];
|
|
||||||
if ($constraint) {
|
|
||||||
$packages[] = $constraint;
|
|
||||||
}
|
|
||||||
$arguments = [
|
|
||||||
'command' => 'require',
|
|
||||||
'packages' => $packages,
|
|
||||||
];
|
|
||||||
$requireInput = new ArrayInput($arguments);
|
|
||||||
$returnCode = $command->run($requireInput, $output);
|
|
||||||
return $returnCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the project
|
|
||||||
*
|
|
||||||
* @param OutputInterface $output
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
protected function updateProject(OutputInterface $output)
|
|
||||||
{
|
|
||||||
/** @var UpdateCommand $command */
|
|
||||||
$command = $this->getApplication()->find('update');
|
|
||||||
$arguments = [ 'command' => 'update' ];
|
|
||||||
$requireInput = new ArrayInput($arguments);
|
|
||||||
$returnCode = $command->run($requireInput, $output);
|
|
||||||
return $returnCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $recipe
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
protected function findInstalledVersion($recipe)
|
|
||||||
{
|
|
||||||
// Check composer.lock file
|
|
||||||
$lockData = $this->loadComposer(getcwd() . '/composer.lock', []);
|
|
||||||
if (isset($lockData['packages'])) {
|
|
||||||
foreach ($lockData['packages'] as $package) {
|
|
||||||
// Get version of installed file
|
|
||||||
if (isset($package['name']) && isset($package['version']) && $package['name'] === $recipe) {
|
|
||||||
$version = $package['version'];
|
|
||||||
// Trim leading `v` from `v1.0.0`
|
|
||||||
if (preg_match('#v([\d.]+)#i', $version)) {
|
|
||||||
return substr($version, 1);
|
|
||||||
}
|
|
||||||
return $version;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check composer.json
|
|
||||||
$composerData = $this->loadComposer(getcwd() . '/composer.json');
|
|
||||||
|
|
||||||
// Check provide for previously inlined recipe
|
|
||||||
if (isset($composerData['provide'][$recipe])) {
|
|
||||||
return $composerData['provide'][$recipe];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check existing constraints, or installed version
|
|
||||||
if (isset($composerData['require'][$recipe])) {
|
|
||||||
return $composerData['require'][$recipe];
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Guess constraint to use if not provided
|
|
||||||
*
|
|
||||||
* @param string $existingVersion Known installed version
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
protected function findBestConstraint($existingVersion)
|
|
||||||
{
|
|
||||||
// Cannot guess without existing version
|
|
||||||
if (!$existingVersion) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Existing version is already a ^1.0.0 or ~1.0.0 constraint
|
|
||||||
if (preg_match('#^[~^]#', $existingVersion)) {
|
|
||||||
return $existingVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Existing version is already a dev constraint
|
|
||||||
if (stristr($existingVersion, 'dev') !== false) {
|
|
||||||
return $existingVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Numeric-only version maps to semver constraint
|
|
||||||
if (preg_match('#^([\d.]+)$#', $existingVersion)) {
|
|
||||||
return "^{$existingVersion}";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cannot guess; Let composer choose (equivalent to `composer require vendor/library`)
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user