diff --git a/src/Configurator.php b/src/Configurator.php index da957f0bd..a8e9da8b9 100644 --- a/src/Configurator.php +++ b/src/Configurator.php @@ -41,6 +41,7 @@ public function __construct(Composer $composer, IOInterface $io, Options $option '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, diff --git a/src/Configurator/ComposerCommandsConfigurator.php b/src/Configurator/ComposerCommandsConfigurator.php new file mode 100644 index 000000000..c64165299 --- /dev/null +++ b/src/Configurator/ComposerCommandsConfigurator.php @@ -0,0 +1,69 @@ + + * + * 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 + */ +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 = ltrim(str_replace($recipeUpdate->getRootDir(), '', $json->getPath()), '/\\'); + + $recipeUpdate->setOriginalFile( + $jsonPath, + $this->configureScripts($originalConfig, $json) + ); + $recipeUpdate->setNewFile( + $jsonPath, + $this->configureScripts($newConfig, $json) + ); + } + + private function configureScripts(array $scripts, JsonFile $json): string + { + $manipulator = new JsonManipulator(file_get_contents($json->getPath())); + foreach ($scripts as $cmdName => $script) { + $manipulator->addSubNode('scripts', $cmdName, $script); + } + + return $manipulator->getContents(); + } +} diff --git a/tests/Configurator/ComposerCommandConfiguratorTest.php b/tests/Configurator/ComposerCommandConfiguratorTest.php new file mode 100644 index 000000000..d85a833e2 --- /dev/null +++ b/tests/Configurator/ComposerCommandConfiguratorTest.php @@ -0,0 +1,289 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Flex\Tests\Configurator; + +use Composer\Composer; +use Composer\IO\IOInterface; +use Composer\Util\Platform; +use PHPUnit\Framework\TestCase; +use Symfony\Flex\Configurator\ComposerCommandsConfigurator; +use Symfony\Flex\Lock; +use Symfony\Flex\Options; +use Symfony\Flex\Recipe; +use Symfony\Flex\Update\RecipeUpdate; + +class ComposerCommandConfiguratorTest extends TestCase +{ + protected function setUp(): void + { + @mkdir(FLEX_TEST_DIR); + if (method_exists(Platform::class, 'putEnv')) { + Platform::putEnv('COMPOSER', FLEX_TEST_DIR.'/composer.json'); + } else { + putenv('COMPOSER='.FLEX_TEST_DIR.'/composer.json'); + } + } + + protected function tearDown(): void + { + @unlink(FLEX_TEST_DIR.'/composer.json'); + @rmdir(FLEX_TEST_DIR); + if (method_exists(Platform::class, 'clearEnv')) { + Platform::clearEnv('COMPOSER'); + } else { + putenv('COMPOSER'); + } + } + + public function providerForConfigureMethod(): iterable + { + yield 'without_scripts_block' => [ + new \stdClass(), + << [ + [ + 'scripts' => [ + 'foo' => 'bar', + ], + ], + << [ + [ + 'scripts' => [ + 'auto-scripts' => [ + 'cache:clear' => 'symfony-cmd', + 'assets:install %PUBLIC_DIR%' => 'symfony-cmd', + ], + 'post-install-cmd' => ['@auto-scripts'], + 'post-update-cmd' => ['@auto-scripts'], + ], + ], + <<createMock(Composer::class), + $this->createMock(IOInterface::class), + new Options(['root-dir' => FLEX_TEST_DIR]) + ); + + $recipe = $this->getMockBuilder(Recipe::class)->disableOriginalConstructor()->getMock(); + $lock = $this->getMockBuilder(Lock::class)->disableOriginalConstructor()->getMock(); + + $configurator->configure($recipe, [ + 'do:cool-stuff' => 'symfony-cmd', + ], $lock); + $this->assertEquals( + $expectedComposerJson, + file_get_contents(FLEX_TEST_DIR.'/composer.json') + ); + } + + public function providerForUnconfigureMethod(): iterable + { + yield 'unconfigure_one_command_with_auto_scripts' => [ + [ + 'scripts' => [ + 'auto-scripts' => [ + 'cache:clear' => 'symfony-cmd', + 'assets:install %PUBLIC_DIR%' => 'symfony-cmd', + ], + 'post-install-cmd' => ['@auto-scripts'], + 'post-update-cmd' => ['@auto-scripts'], + 'do:cool-stuff' => 'symfony-cmd', + 'do:another-cool-stuff' => 'symfony-cmd-2', + ], + ], + << [ + [ + 'scripts' => [ + 'do:another-cool-stuff' => 'symfony-cmd-2', + 'do:cool-stuff' => 'symfony-cmd', + ], + ], + <<createMock(Composer::class), + $this->createMock(IOInterface::class), + new Options(['root-dir' => FLEX_TEST_DIR]) + ); + + $recipe = $this->createMock(Recipe::class); + $lock = $this->createMock(Lock::class); + + $configurator->unconfigure($recipe, [ + 'do:cool-stuff' => 'symfony-cmd', + ], $lock); + $this->assertEquals( + $expectedComposerJson, + file_get_contents(FLEX_TEST_DIR.'/composer.json') + ); + } + + public function testUpdate(): void + { + $configurator = new ComposerCommandsConfigurator( + $this->createMock(Composer::class), + $this->createMock(IOInterface::class), + new Options(['root-dir' => FLEX_TEST_DIR]) + ); + + $recipeUpdate = new RecipeUpdate( + $this->createMock(Recipe::class), + $this->createMock(Recipe::class), + $this->createMock(Lock::class), + FLEX_TEST_DIR + ); + + file_put_contents(FLEX_TEST_DIR.'/composer.json', json_encode([ + 'scripts' => [ + 'auto-scripts' => [ + 'cache:clear' => 'symfony-cmd', + 'assets:install %PUBLIC_DIR%' => 'symfony-cmd', + ], + 'post-install-cmd' => ['@auto-scripts'], + 'post-update-cmd' => ['@auto-scripts'], + 'foo' => 'bar', + ], + ], \JSON_PRETTY_PRINT)); + + $configurator->update( + $recipeUpdate, + ['foo' => 'bar'], + ['foo' => 'baz', 'do:cool-stuff' => 'symfony-cmd'] + ); + + $expectedComposerJsonOriginal = <<assertSame(['composer.json' => $expectedComposerJsonOriginal], $recipeUpdate->getOriginalFiles()); + + $expectedComposerJsonNew = <<assertSame(['composer.json' => $expectedComposerJsonNew], $recipeUpdate->getNewFiles()); + } +}