setName('require-recipe'); $this->setDescription('Invoke this command to inline a recipe into your root composer.json'); $this->addArgument( 'recipe', InputArgument::REQUIRED, 'Recipe name to require inline' ); $this->addArgument( 'version', InputArgument::REQUIRED, 'Version to require' ); $this->addUsage('silverstripe/recipe-blogging 1.0.0'); $this->setHelp( <<composer require-recipe silverstripe/recipe-blogging 1.0.0 adds the following: "require": { "silverstripe/blog": "3.0.0", "silverstripe/lumberjack": "3.0.1", "silverstripe/comments": "2.1.0" }, "provide": { "silverstripe/recipe-blogging": "1.0.0" } HELP ); } public function execute(InputInterface $input, OutputInterface $output) { $recipe = $input->getArgument('recipe'); $version = $input->getArgument('version'); // Get existing composer data $composerData = $this->loadComposer(getcwd()); if (isset($composerData['provide'][$recipe])) { $output->writeln("This recipe is already added to provide"); return -1; } // Ensure composer require includes this recipe $returnCode = $this->requireRecipe($output, $recipe, $version); if ($returnCode) { return $returnCode; } // Get composer data for both root and newly installed recipe $composerData = $this->loadComposer(getcwd()); $recipeData = $this->loadComposer(getcwd().'/vendor/'.$recipe); // Promote all dependencies if (!empty($recipeData['require'])) { $output->writeln("Inlining all dependencies for recipe $recipe:"); foreach ($recipeData['require'] as $dependencyName => $dependencyVersion) { $output->writeln(" * Inline dependency $dependencyName as $dependencyVersion"); $composerData['require'][$dependencyName] = $dependencyVersion; } } // Move recipe from 'require' to 'provide' unset($composerData['require'][$recipe]); if (!isset($composerData['provide'])) { $composerData['provide'] = []; } $composerData['provide'][$recipe] = $version; // Update composer.json and synchronise composer.lock $this->saveComposer(getcwd(), $composerData); $this->updateProject($output); return $returnCode; } /** * Load composer data from the given directory * * @param string $directory * @return array */ protected function loadComposer($directory) { $path = $directory.'/composer.json'; if (!file_exists($path)) { throw new BadMethodCallException("Could not find composer.json"); } $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 $recipe * @param $version * @return int */ protected function requireRecipe(OutputInterface $output, $recipe, $version) { /** @var RequireCommand $command */ $command = $this->getApplication()->find('require'); $package = $recipe . ':' . $version; $arguments = [ 'command' => 'require', 'packages' => [$package], ]; $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; } }