diff --git a/src/Composer/ComposerOrchestrator.php b/src/Composer/ComposerOrchestrator.php index 2d298471e..15b805a9e 100644 --- a/src/Composer/ComposerOrchestrator.php +++ b/src/Composer/ComposerOrchestrator.php @@ -67,7 +67,7 @@ public function getVersion(): string $this->logger->info($getVersionProcess->getCommandLine()); - $getVersionProcess->run(env: $this->processFactory->getDefaultEnvVars()); + $getVersionProcess->run(); if (false === $getVersionProcess->isSuccessful()) { throw UndetectableComposerVersion::forFailedProcess($getVersionProcess); @@ -108,6 +108,25 @@ public function checkVersion(): void } } + public function getVendorDir(): string + { + $vendorDirProcess = $this->processFactory->getVendorDirProcess(); + + $this->logger->info($vendorDirProcess->getCommandLine()); + + $vendorDirProcess->run(); + + if (false === $vendorDirProcess->isSuccessful()) { + throw new RuntimeException( + 'Could not retrieve the vendor dir.', + 0, + new ProcessFailedException($vendorDirProcess), + ); + } + + return trim($vendorDirProcess->getOutput()); + } + public function dumpAutoload( SymbolsRegistry $symbolsRegistry, string $prefix, @@ -135,7 +154,7 @@ private function dumpAutoloader(bool $noDev): void $this->logger->info($dumpAutoloadProcess->getCommandLine()); - $dumpAutoloadProcess->run(env: $this->processFactory->getDefaultEnvVars()); + $dumpAutoloadProcess->run(); if (false === $dumpAutoloadProcess->isSuccessful()) { throw new RuntimeException( @@ -165,20 +184,6 @@ private function dumpAutoloader(bool $noDev): void private function retrieveAutoloadFile(): string { - $vendorDirProcess = $this->processFactory->getAutoloadFileProcess(); - - $this->logger->info($vendorDirProcess->getCommandLine()); - - $vendorDirProcess->run(env: $this->processFactory->getDefaultEnvVars()); - - if (false === $vendorDirProcess->isSuccessful()) { - throw new RuntimeException( - 'Could not retrieve the vendor dir.', - 0, - new ProcessFailedException($vendorDirProcess), - ); - } - - return trim($vendorDirProcess->getOutput()).'/autoload.php'; + return $this->getVendorDir().'/autoload.php'; } } diff --git a/src/Composer/ComposerProcessFactory.php b/src/Composer/ComposerProcessFactory.php index 1f0507029..1a77228d0 100644 --- a/src/Composer/ComposerProcessFactory.php +++ b/src/Composer/ComposerProcessFactory.php @@ -35,6 +35,7 @@ public static function create( $composerExecutable ?? self::retrieveComposerExecutable(), self::retrieveSubProcessVerbosity($io), $io->isDecorated(), + self::getDefaultEnvVars(), ); } @@ -42,17 +43,23 @@ public function __construct( public readonly string $composerExecutable, private ?string $verbosity, private bool $ansi, + private array $defaultEnvironmentVariables, ) { } public function getVersionProcess(): Process { // Never use ANSI support here as we want to parse the raw output. - return new Process([ - $this->composerExecutable, - '--version', - '--no-ansi', - ]); + return $this->createProcess( + [ + $this->composerExecutable, + '--version', + '--no-ansi', + ], + // Ensure that even if this command gets executed within the app with --quiet it still + // works. + ['SHELL_VERBOSITY' => 0], + ); } public function getDumpAutoloaderProcess(bool $noDev): Process @@ -71,12 +78,12 @@ public function getDumpAutoloaderProcess(bool $noDev): Process $composerCommand[] = '--ansi'; } - return new Process($composerCommand); + return $this->createProcess($composerCommand); } - public function getAutoloadFileProcess(): Process + public function getVendorDirProcess(): Process { - return new Process([ + return $this->createProcess([ $this->composerExecutable, 'config', 'vendor-dir', @@ -84,6 +91,17 @@ public function getAutoloadFileProcess(): Process ]); } + private function createProcess(array $command, array $environmentVariables = []): Process + { + return new Process( + $command, + env: [ + ...$this->defaultEnvironmentVariables, + ...$environmentVariables, + ], + ); + } + private static function retrieveSubProcessVerbosity(IO $io): ?string { if ($io->isDebug()) { @@ -97,7 +115,7 @@ private static function retrieveSubProcessVerbosity(IO $io): ?string return null; } - public function getDefaultEnvVars(): array + private static function getDefaultEnvVars(): array { $vars = []; 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..1a4bcef5c --- /dev/null +++ b/src/Console/Command/Composer/ComposerCheckVersion.php @@ -0,0 +1,59 @@ + + * 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\ExitCode; +use Fidry\Console\Input\IO; +use Fidry\FileSystem\FileSystem; +use KevinGH\Box\Composer\ComposerOrchestrator; +use KevinGH\Box\Composer\ComposerProcessFactory; +use KevinGH\Box\Console\ConfigurationLoader; +use KevinGH\Box\Console\ConfigurationLocator; +use Psr\Log\LogLevel; +use RuntimeException; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Logger\ConsoleLogger; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @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..91690368d --- /dev/null +++ b/src/Console/Command/Composer/ComposerCommand.php @@ -0,0 +1,91 @@ + + * 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\ExitCode; +use Fidry\Console\Input\IO; +use Fidry\FileSystem\FileSystem; +use KevinGH\Box\Composer\ComposerOrchestrator; +use KevinGH\Box\Composer\ComposerProcessFactory; +use KevinGH\Box\Console\ConfigurationLoader; +use KevinGH\Box\Console\ConfigurationLocator; +use Psr\Log\LogLevel; +use RuntimeException; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Logger\ConsoleLogger; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @private + */ +abstract class ComposerCommand implements Command +{ + private const FILE_ARGUMENT = 'file'; + + 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 InputArgument( + self::FILE_ARGUMENT, + InputArgument::OPTIONAL, + 'The configuration file. (default: box.json, box.json.dist)', + ), + ], + ); + } + + final public function execute(IO $io): int + { + $composerOrchestrator = new ComposerOrchestrator( + ComposerProcessFactory::create( + $this->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 function getComposerExecutable(IO $io): ?string + { + try { + $config = ConfigurationLoader::getConfig( + $io->getArgument(self::FILE_ARGUMENT)->asNullableNonEmptyString() ?? ConfigurationLocator::findDefaultPath(), + $io, + false, + ); + + return $config->getComposerBin(); + } catch (RuntimeException) { + return null; + } + } +} diff --git a/src/Console/Command/Composer/ComposerVendorDir.php b/src/Console/Command/Composer/ComposerVendorDir.php new file mode 100644 index 000000000..69419304b --- /dev/null +++ b/src/Console/Command/Composer/ComposerVendorDir.php @@ -0,0 +1,59 @@ + + * 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\ExitCode; +use Fidry\Console\Input\IO; +use Fidry\FileSystem\FileSystem; +use KevinGH\Box\Composer\ComposerOrchestrator; +use KevinGH\Box\Composer\ComposerProcessFactory; +use KevinGH\Box\Console\ConfigurationLoader; +use KevinGH\Box\Console\ConfigurationLocator; +use Psr\Log\LogLevel; +use RuntimeException; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Logger\ConsoleLogger; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @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; + } +}