diff --git a/src/RecipeCommandBehaviour.php b/src/RecipeCommandBehaviour.php
new file mode 100644
index 0000000..e7a3202
--- /dev/null
+++ b/src/RecipeCommandBehaviour.php
@@ -0,0 +1,185 @@
+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;
+ }
+}
diff --git a/src/RequireRecipeCommand.php b/src/RequireRecipeCommand.php
index fec84aa..f514cfd 100644
--- a/src/RequireRecipeCommand.php
+++ b/src/RequireRecipeCommand.php
@@ -2,18 +2,19 @@
namespace SilverStripe\RecipePlugin;
-use BadMethodCallException;
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\InputInterface;
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
{
+ use RecipeCommandBehaviour;
+
public function configure()
{
$this->setName('require-recipe');
@@ -52,14 +53,11 @@ HELP
public function execute(InputInterface $input, OutputInterface $output)
{
- // Get args and existing composer data
$recipe = $input->getArgument('recipe');
$constraint = $input->getArgument('version');
- // Check if this is already installed
+ // Check if this is already installed and notify users
$installedVersion = $this->findInstalledVersion($recipe);
-
- // Notify users of which version is being updated
if ($installedVersion) {
if ($constraint) {
$output->writeln(
@@ -87,18 +85,23 @@ HELP
return $returnCode;
}
- // Get composer data for both root and newly installed recipe
+ // Begin modification of composer.json
$composerData = $this->loadComposer(getcwd() .'/composer.json');
- $recipeData = $this->loadComposer(getcwd().'/vendor/'.$recipe.'/composer.json');
- // Promote all dependencies
- if (!empty($recipeData['require'])) {
+ // Get composer data for both root and newly installed recipe
+ $installedRecipe = $this
+ ->getComposer()
+ ->getRepositoryManager()
+ ->getLocalRepository()
+ ->findPackage($recipe, '*');
+ if ($installedRecipe) {
$output->writeln("Inlining all dependencies for recipe {$recipe}:");
- foreach ($recipeData['require'] as $dependencyName => $dependencyVersion) {
+ foreach ($installedRecipe->getRequires() as $requireName => $require) {
+ $requireVersion = $require->getPrettyConstraint();
$output->writeln(
- " * Inline dependency {$dependencyName} as {$dependencyVersion}"
+ " * Inline dependency {$requireName} as {$requireVersion}"
);
- $composerData['require'][$dependencyName] = $dependencyVersion;
+ $composerData['require'][$requireName] = $requireVersion;
}
}
@@ -114,155 +117,4 @@ HELP
$this->saveComposer(getcwd(), $composerData);
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;
- }
}