diff --git a/src/Console/Application.php b/src/Console/Application.php index efe475d1b..47d6e209b 100644 --- a/src/Console/Application.php +++ b/src/Console/Application.php @@ -78,6 +78,8 @@ public function getHeader(): string public function getCommands(): array { return [ + new Command\Composer\ComposerCheckVersion(), + new Command\Composer\ComposerVendorDir(), new Command\Compile($this->getHeader()), new Command\Diff(), new Command\Info(), diff --git a/src/Console/Command/Composer/ComposerCheckVersion.php b/src/Console/Command/Composer/ComposerCheckVersion.php new file mode 100644 index 000000000..1bc88ade7 --- /dev/null +++ b/src/Console/Command/Composer/ComposerCheckVersion.php @@ -0,0 +1,49 @@ + + * Théo Fidry + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace KevinGH\Box\Console\Command\Composer; + +use Fidry\Console\Command\Configuration; +use Fidry\Console\ExitCode; +use Fidry\Console\Input\IO; +use KevinGH\Box\Composer\ComposerOrchestrator; + +/** + * @private + */ +final class ComposerCheckVersion extends ComposerCommand +{ + public function getConfiguration(): Configuration + { + $parentConfig = parent::getConfiguration(); + + return new Configuration( + 'composer:check-version', + '🎵 Checks if the Composer executable used is compatible with Box', + <<<'HELP' + The %command.name% command will look for the Composer binary (in the system if not configured + in the configuration file) and check if its version is compatible with Box. + HELP, + $parentConfig->getArguments(), + $parentConfig->getOptions(), + ); + } + + protected function orchestrate(ComposerOrchestrator $composerOrchestrator, IO $io): int + { + $composerOrchestrator->checkVersion(); + + return ExitCode::SUCCESS; + } +} diff --git a/src/Console/Command/Composer/ComposerCommand.php b/src/Console/Command/Composer/ComposerCommand.php new file mode 100644 index 000000000..4e54b955d --- /dev/null +++ b/src/Console/Command/Composer/ComposerCommand.php @@ -0,0 +1,83 @@ + + * Théo Fidry + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace KevinGH\Box\Console\Command\Composer; + +use Fidry\Console\Command\Command; +use Fidry\Console\Command\Configuration; +use Fidry\Console\Input\IO; +use Fidry\FileSystem\FileSystem; +use KevinGH\Box\Composer\ComposerOrchestrator; +use KevinGH\Box\Composer\ComposerProcessFactory; +use Psr\Log\LogLevel; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Logger\ConsoleLogger; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Filesystem\Path; +use function Safe\getcwd; + +/** + * @private + */ +abstract class ComposerCommand implements Command +{ + private const COMPOSER_BIN_OPTION = 'composer-bin'; + + private const VERBOSITY_LEVEL_MAP = [ + LogLevel::NOTICE => OutputInterface::VERBOSITY_NORMAL, + LogLevel::INFO => OutputInterface::VERBOSITY_NORMAL, + LogLevel::DEBUG => OutputInterface::VERBOSITY_VERBOSE, + ]; + + public function getConfiguration(): Configuration + { + return new Configuration( + 'To configure.', + 'To configure.', + 'To configure.', + [], + [ + new InputOption( + self::COMPOSER_BIN_OPTION, + null, + InputOption::VALUE_REQUIRED, + 'Composer executable to use.', + ), + ], + ); + } + + final public function execute(IO $io): int + { + $composerOrchestrator = new ComposerOrchestrator( + ComposerProcessFactory::create( + self::getComposerExecutable($io), + $io, + ), + new ConsoleLogger($io->getOutput(), self::VERBOSITY_LEVEL_MAP), + new FileSystem(), + ); + + return $this->orchestrate($composerOrchestrator, $io); + } + + abstract protected function orchestrate(ComposerOrchestrator $composerOrchestrator, IO $io): int; + + private static function getComposerExecutable(IO $io): ?string + { + $composerBin = $io->getOption(self::COMPOSER_BIN_OPTION)->asNullableNonEmptyString(); + + return null === $composerBin ? null : Path::makeAbsolute($composerBin, getcwd()); + } +} diff --git a/src/Console/Command/Composer/ComposerVendorDir.php b/src/Console/Command/Composer/ComposerVendorDir.php new file mode 100644 index 000000000..5b54476e7 --- /dev/null +++ b/src/Console/Command/Composer/ComposerVendorDir.php @@ -0,0 +1,49 @@ + + * Théo Fidry + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace KevinGH\Box\Console\Command\Composer; + +use Fidry\Console\Command\Configuration; +use Fidry\Console\ExitCode; +use Fidry\Console\Input\IO; +use KevinGH\Box\Composer\ComposerOrchestrator; + +/** + * @private + */ +final class ComposerVendorDir extends ComposerCommand +{ + public function getConfiguration(): Configuration + { + $parentConfig = parent::getConfiguration(); + + return new Configuration( + 'composer:vendor-dir', + '🎵 Shows the Composer vendor-dir configured', + <<<'HELP' + The %command.name% command will look for the Composer binary (in the system if not configured + in the configuration file) and print the vendor-dir found. + HELP, + $parentConfig->getArguments(), + $parentConfig->getOptions(), + ); + } + + protected function orchestrate(ComposerOrchestrator $composerOrchestrator, IO $io): int + { + $io->writeln($composerOrchestrator->getVendorDir()); + + return ExitCode::SUCCESS; + } +} diff --git a/tests/Console/Command/Composer/ComposerCheckVersionTest.php b/tests/Console/Command/Composer/ComposerCheckVersionTest.php new file mode 100644 index 000000000..ca7e37d97 --- /dev/null +++ b/tests/Console/Command/Composer/ComposerCheckVersionTest.php @@ -0,0 +1,142 @@ + + * Théo Fidry + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace KevinGH\Box\Console\Command\Composer; + +use Exception; +use Fidry\Console\ExitCode; +use Fidry\Console\Test\CommandTester; +use Fidry\Console\Test\OutputAssertions; +use KevinGH\Box\Composer\IncompatibleComposerVersion; +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Filesystem\Path; +use function Safe\chdir; +use function Safe\getcwd; + +/** + * @covers \KevinGH\Box\Console\Command\Composer\ComposerCheckVersion + * + * @internal + */ +class ComposerCheckVersionTest extends TestCase +{ + private CommandTester $commandTester; + private string $cwd; + + protected function setUp(): void + { + $this->commandTester = CommandTester::fromConsoleCommand(new ComposerCheckVersion()); + + $this->cwd = getcwd(); + chdir(__DIR__); + } + + protected function tearDown(): void + { + chdir($this->cwd); + } + + /** + * @dataProvider compatibleComposerExecutableProvider + */ + public function test_it_succeeds_the_check_when_the_composer_version_is_compatible( + array $input, + array $options, + string $expectedOutput, + int $expectedStatusCode, + ): void { + $input['command'] = 'composer:check-version'; + + $this->commandTester->execute($input, $options); + + OutputAssertions::assertSameOutput( + $expectedOutput, + $expectedStatusCode, + $this->commandTester, + ); + } + + public static function compatibleComposerExecutableProvider(): iterable + { + $compatibleComposerPath = Path::normalize(__DIR__.'/compatible-composer.phar'); + + yield 'normal verbosity' => [ + [ + '--composer-bin' => 'compatible-composer.phar', + ], + [], + << [ + [ + '--composer-bin' => 'compatible-composer.phar', + ], + ['verbosity' => OutputInterface::VERBOSITY_QUIET], + '', + ExitCode::SUCCESS, + ]; + + yield 'no custom composer' => [ + [], + ['verbosity' => OutputInterface::VERBOSITY_QUIET], + '', + ExitCode::SUCCESS, + ]; + } + + /** + * @dataProvider incompatibleComposerExecutableProvider + */ + public function test_it_fails_the_check_when_the_composer_version_is_incompatible( + array $input, + array $options, + Exception $expected, + ): void { + $input['command'] = 'composer:check-version'; + + $this->expectExceptionObject($expected); + + $this->commandTester->execute($input, $options); + } + + public static function incompatibleComposerExecutableProvider(): iterable + { + yield 'normal verbosity' => [ + [ + '--composer-bin' => 'incompatible-composer.phar', + ], + [], + new IncompatibleComposerVersion( + 'The Composer version "2.0.14" does not satisfy the constraint "^2.2.0".', + ), + ]; + + yield 'quiet verbosity' => [ + [ + '--composer-bin' => 'incompatible-composer.phar', + ], + ['verbosity' => OutputInterface::VERBOSITY_QUIET], + new IncompatibleComposerVersion( + 'The Composer version "2.0.14" does not satisfy the constraint "^2.2.0".', + ), + ]; + } +} diff --git a/tests/Console/Command/Composer/ComposerVendorDirTest.php b/tests/Console/Command/Composer/ComposerVendorDirTest.php new file mode 100644 index 000000000..2ee185c44 --- /dev/null +++ b/tests/Console/Command/Composer/ComposerVendorDirTest.php @@ -0,0 +1,113 @@ + + * Théo Fidry + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace KevinGH\Box\Console\Command\Composer; + +use Fidry\Console\ExitCode; +use Fidry\Console\Test\CommandTester; +use Fidry\Console\Test\OutputAssertions; +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Filesystem\Path; +use function Safe\chdir; +use function Safe\getcwd; + +/** + * @covers \KevinGH\Box\Console\Command\Composer\ComposerVendorDir + * + * @internal + */ +class ComposerVendorDirTest extends TestCase +{ + private CommandTester $commandTester; + private string $cwd; + + protected function setUp(): void + { + $this->commandTester = CommandTester::fromConsoleCommand(new ComposerVendorDir()); + + $this->cwd = getcwd(); + chdir(__DIR__); + } + + protected function tearDown(): void + { + chdir($this->cwd); + } + + /** + * @dataProvider composerExecutableProvider + */ + public function test_it_retrieves_the_vendor_bin_directory_path( + array $input, + array $options, + string $expectedOutput, + int $expectedStatusCode, + ): void { + $input['command'] = 'composer:vendor-dir'; + + $this->commandTester->execute($input, $options); + + OutputAssertions::assertSameOutput( + $expectedOutput, + $expectedStatusCode, + $this->commandTester, + ); + } + + public static function composerExecutableProvider(): iterable + { + $compatibleComposerPath = Path::normalize(__DIR__.'/compatible-composer.phar'); + $incompatibleComposerPath = Path::normalize(__DIR__.'/incompatible-composer.phar'); + + yield 'normal verbosity' => [ + [ + '--composer-bin' => 'compatible-composer.phar', + ], + [], + << [ + [ + '--composer-bin' => 'compatible-composer.phar', + ], + ['verbosity' => OutputInterface::VERBOSITY_QUIET], + '', + ExitCode::SUCCESS, + ]; + + yield 'no custom composer' => [ + [], + ['verbosity' => OutputInterface::VERBOSITY_QUIET], + '', + ExitCode::SUCCESS, + ]; + + yield 'incompatible composer executable; quiet verbosity' => [ + [ + '--composer-bin' => 'incompatible-composer.phar', + ], + ['verbosity' => OutputInterface::VERBOSITY_QUIET], + // The output would be too unstable to test in normal verbosity + '', + ExitCode::SUCCESS, + ]; + } +} diff --git a/tests/Console/Command/Composer/box.json b/tests/Console/Command/Composer/box.json new file mode 100644 index 000000000..40293049e --- /dev/null +++ b/tests/Console/Command/Composer/box.json @@ -0,0 +1,5 @@ +{ + "$schema": "../../../../res/schema.json", + + "composerBin": "./composer.phar" +} \ No newline at end of file diff --git a/tests/Console/Command/Composer/compatible-composer.phar b/tests/Console/Command/Composer/compatible-composer.phar new file mode 100755 index 000000000..abebf102c Binary files /dev/null and b/tests/Console/Command/Composer/compatible-composer.phar differ diff --git a/tests/Console/Command/Composer/composer.json b/tests/Console/Command/Composer/composer.json new file mode 100644 index 000000000..9e26dfeeb --- /dev/null +++ b/tests/Console/Command/Composer/composer.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/tests/Console/Command/Composer/incompatible-composer.phar b/tests/Console/Command/Composer/incompatible-composer.phar new file mode 100755 index 000000000..db5d28c6b Binary files /dev/null and b/tests/Console/Command/Composer/incompatible-composer.phar differ