From b4e7a71fe360a7a205eb78e3e00fb171f57a78d9 Mon Sep 17 00:00:00 2001 From: Oliver Davies Date: Wed, 21 Feb 2024 12:48:33 +0000 Subject: [PATCH] Refactor to separate commands --- notes | 1 - src/Console/Command/AbstractCommand.php | 35 +++++ src/Console/Command/BuildCommand.php | 80 +++++++++++ src/Console/Command/InstallCommand.php | 28 ++++ src/Console/Command/RunCommand.php | 72 ++++++++++ src/Console/Command/TestCommand.php | 44 ++++++ versa | 170 ++---------------------- 7 files changed, 271 insertions(+), 159 deletions(-) create mode 100644 src/Console/Command/AbstractCommand.php create mode 100644 src/Console/Command/BuildCommand.php create mode 100644 src/Console/Command/InstallCommand.php create mode 100644 src/Console/Command/RunCommand.php create mode 100644 src/Console/Command/TestCommand.php diff --git a/notes b/notes index 3a4fa7a..cbcf423 100644 --- a/notes +++ b/notes @@ -1,3 +1,2 @@ Add TypeScript and JavaScript suppport - e.g. Fractal Review https://github.com/phpstan/phpstan-symfony -Refactor to separate commands rather than using a single command application? diff --git a/src/Console/Command/AbstractCommand.php b/src/Console/Command/AbstractCommand.php new file mode 100644 index 0000000..44d56da --- /dev/null +++ b/src/Console/Command/AbstractCommand.php @@ -0,0 +1,35 @@ +addOption( + name: 'extra-args', + shortcut: 'a', + mode: InputArgument::OPTIONAL, + description: 'Any additonal arguments to pass to the command.', + ); + + $this->addOption( + name: 'type', + shortcut: 't', + mode: InputArgument::OPTIONAL, + description: 'The project type', + suggestedValues: ['drupal', 'sculpin'], + ); + + $this->addOption( + name: 'working-dir', + shortcut: 'd', + mode: InputArgument::OPTIONAL, + description: 'The project\'s working directory', + default: '.', + ); + } +} diff --git a/src/Console/Command/BuildCommand.php b/src/Console/Command/BuildCommand.php new file mode 100644 index 0000000..3b29b8c --- /dev/null +++ b/src/Console/Command/BuildCommand.php @@ -0,0 +1,80 @@ +getOption('extra-args'); + $workingDir = $input->getOption('working-dir'); + + $filesystem = new Filesystem(); + + // Attempt to prepopulate some of the options, such as the project type + // based on its dependencies. + // TODO: move this logic to a service so it can be tested. + if ($filesystem->exists($workingDir.'/composer.json')) { + $json = json_decode( + json: strval(file_get_contents($workingDir.'/composer.json')), + associative: true, + ); + + $dependencies = array_keys($json['require']); + + if (in_array(needle: 'drupal/core', haystack: $dependencies, strict: true) || in_array(needle: 'drupal/core-recommended', haystack: $dependencies, strict: true)) { + $projectType = ProjectType::Drupal->value; + } elseif (in_array(needle: 'sculpin/sculpin', haystack: $dependencies, strict: true)) { + $projectType = ProjectType::Sculpin->value; + } elseif (in_array(needle: 'symfony/framework-bundle', haystack: $dependencies, strict: true)) { + $projectType = ProjectType::Symfony->value; + } + } + + // Even if the project type is found automatically, still override it with + // the option value if there is one. + $projectType = $input->getOption('type') ?? $projectType; + + $isDockerCompose = $filesystem->exists($workingDir . '/docker-compose.yaml'); + + switch ($projectType) { + case ProjectType::Drupal->value: + if ($isDockerCompose) { + $process = Process::create( + command: ['docker', 'compose', 'build'], + extraArgs: $extraArgs, + workingDir: $workingDir, + ); + + $process->run(); + } + break; + + case ProjectType::Symfony->value: + // TODO: run humbug/box if added to generate a phar? + throw new RuntimeException('No build command set for Symfony projects.'); + + case ProjectType::Sculpin->value: + $process = Process::create( + command: ['./vendor/bin/sculpin', 'generate'], + extraArgs: $extraArgs, + workingDir: $workingDir, + ); + + $process->run(); + break; + } + + return Command::SUCCESS; + } +} diff --git a/src/Console/Command/InstallCommand.php b/src/Console/Command/InstallCommand.php new file mode 100644 index 0000000..683db48 --- /dev/null +++ b/src/Console/Command/InstallCommand.php @@ -0,0 +1,28 @@ +getOption('extra-args'); + $workingDir = $input->getOption('working-dir'); + + // TODO: Composer in Docker Compose? + $process = Process::create( + command: ['composer', 'install'], + extraArgs: $extraArgs, + workingDir: $workingDir, + ); + + $process->run(); + + return Command::SUCCESS; + } +} diff --git a/src/Console/Command/RunCommand.php b/src/Console/Command/RunCommand.php new file mode 100644 index 0000000..637e6bd --- /dev/null +++ b/src/Console/Command/RunCommand.php @@ -0,0 +1,72 @@ +getOption('extra-args'); + $workingDir = $input->getOption('working-dir'); + + // Attempt to prepopulate some of the options, such as the project type + // based on its dependencies. + // TODO: move this logic to a service so it can be tested. + $json = json_decode( + json: strval(file_get_contents($workingDir.'/composer.json')), + associative: true, + ); + + $dependencies = array_keys($json['require']); + + if (in_array(needle: 'drupal/core', haystack: $dependencies, strict: true) || in_array(needle: 'drupal/core-recommended', haystack: $dependencies, strict: true)) { + $projectType = ProjectType::Drupal->value; + } elseif (in_array(needle: 'sculpin/sculpin', haystack: $dependencies, strict: true)) { + $projectType = ProjectType::Sculpin->value; + } elseif (in_array(needle: 'symfony/framework-bundle', haystack: $dependencies, strict: true)) { + $projectType = ProjectType::Symfony->value; + } + + // Even if the project type is found automatically, still override it with + // the option value if there is one. + $projectType = $input->getOption('type') ?? $projectType; + + $filesystem = new Filesystem(); + $isDockerCompose = $filesystem->exists($workingDir . '/docker-compose.yaml'); + + if ($isDockerCompose) { + $process = Process::create( + command: ['docker', 'compose', 'up'], + extraArgs: $extraArgs, + workingDir: $workingDir, + ); + $process->setTimeout(null); + + $process->run(); + } else { + switch ($projectType) { + case ProjectType::Sculpin->value: + $process = Process::create( + command: ['./vendor/bin/sculpin', 'generate', '--server', '--watch'], + extraArgs: $extraArgs, + workingDir: $workingDir, + ); + $process->setTimeout(null); + + $process->run(); + break; + } + } + + return Command::SUCCESS; + } +} diff --git a/src/Console/Command/TestCommand.php b/src/Console/Command/TestCommand.php new file mode 100644 index 0000000..a3f9045 --- /dev/null +++ b/src/Console/Command/TestCommand.php @@ -0,0 +1,44 @@ +getOption('extra-args'); + $workingDir = $input->getOption('working-dir'); + + // TODO: move this logic to a service so it can be tested. + $json = json_decode( + json: strval(file_get_contents($workingDir.'/composer.json')), + associative: true, + ); + + // TODO: what if there are no dev dependencies? + $devDependencies = array_keys($json['require-dev']); + + // TODO: Pest and Behat. + if (in_array(needle: 'brianium/paratest', haystack: $devDependencies, strict: true)) { + $command = ['./vendor/bin/paratest']; + } else { + $command = ['./vendor/bin/phpunit']; + } + + // TODO: commands in Docker Compose? + $process = Process::create( + command: $command, + extraArgs: $extraArgs, + workingDir: $workingDir, + ); + + $process->run(); + + return Command::SUCCESS; + } +} diff --git a/versa b/versa index 103dd30..a34edb0 100755 --- a/versa +++ b/versa @@ -3,165 +3,19 @@ addArgument( - name: 'command', - mode: InputArgument::REQUIRED, - description: 'The command to run', -); - -$application->addOption( - name: 'extra-args', - shortcut: 'a', - mode: InputArgument::OPTIONAL, - description: 'Any additonal arguments to pass to the command.', -); - -$application->addOption( - name: 'type', - shortcut: 't', - mode: InputArgument::OPTIONAL, - description: 'The project type', - suggestedValues: ['drupal', 'sculpin'], -); - -$application->addOption( - name: 'working-dir', - shortcut: 'd', - mode: InputArgument::OPTIONAL, - description: 'The project\'s working directory', - default: '.', -); - -$application->setCode(function (InputInterface $input): int { - $projectType = null; - - $devDependencies = []; - - $extraArgs = $input->getOption('extra-args'); - $workingDir = $input->getOption('working-dir'); - - $filesystem = new Filesystem(); - - // Attempt to prepopulate some of the options, such as the project type - // based on its dependencies. - // TODO: move this logic to a service so it can be tested. - if ($filesystem->exists($workingDir.'/composer.json')) { - $json = json_decode( - json: strval(file_get_contents($workingDir.'/composer.json')), - associative: true, - ); - - $dependencies = array_keys($json['require']); - // TODO: what if there are no dev dependencies? - $devDependencies = array_keys($json['require-dev']); - - if (in_array(needle: 'drupal/core', haystack: $dependencies, strict: true) || in_array(needle: 'drupal/core-recommended', haystack: $dependencies, strict: true)) { - $projectType = ProjectType::Drupal->value; - } elseif (in_array(needle: 'sculpin/sculpin', haystack: $dependencies, strict: true)) { - $projectType = ProjectType::Sculpin->value; - } elseif (in_array(needle: 'symfony/framework-bundle', haystack: $dependencies, strict: true)) { - $projectType = ProjectType::Symfony->value; - } - } - - // Even if the project type is found automatically, still override it with - // the option value if there is one. - $projectType = $input->getOption('type') ?? $projectType; - - $isDockerCompose = $filesystem->exists($workingDir . '/docker-compose.yaml'); - - // TODO: only allow defined commands - build, install, test, run. - switch ($input->getArgument('command')) { - case 'build': - switch ($projectType) { - case ProjectType::Drupal->value: - if ($isDockerCompose) { - $process = Process::create( - command: ['docker', 'compose', 'build'], - extraArgs: $extraArgs, - workingDir: $workingDir, - ); - $process->run(); - } - break; - - case ProjectType::Symfony->value: - // TODO: run humbug/box if added to generate a phar? - throw new RuntimeException('No build command set for Symfony projects.'); - - case ProjectType::Sculpin->value: - $process = Process::create( - command: ['./vendor/bin/sculpin', 'generate'], - extraArgs: $extraArgs, - workingDir: $workingDir, - ); - $process->run(); - break; - } - break; - - case 'install': - // TODO: Composer in Docker Compose? - $process = Process::create( - command: ['composer', 'install'], - extraArgs: $extraArgs, - workingDir: $workingDir, - ); - $process->run(); - break; - - case 'run': - if ($isDockerCompose) { - $process = Process::create( - command: ['docker', 'compose', 'up'], - extraArgs: $extraArgs, - workingDir: $workingDir, - ); - $process->setTimeout(null); - $process->run(); - } else { - switch ($projectType) { - case ProjectType::Sculpin->value: - $process = Process::create( - command: ['./vendor/bin/sculpin', 'generate', '--server', '--watch'], - extraArgs: $extraArgs, - workingDir: $workingDir, - ); - $process->setTimeout(null); - $process->run(); - break; - } - } - break; - - case 'test': - // TODO: Pest and Behat. - if (in_array(needle: 'brianium/paratest', haystack: $devDependencies, strict: true)) { - $command = ['./vendor/bin/paratest']; - } else { - $command = ['./vendor/bin/phpunit']; - } - - // TODO: commands in Docker Compose? - $process = Process::create( - command: $command, - extraArgs: $extraArgs, - workingDir: $workingDir, - ); - $process->run(); - break; - } - - return 0; -}); +$application->addCommands([ + new BuildCommand(name: 'build'), + new InstallCommand(name: 'install'), + new RunCommand(name: 'run'), + new TestCommand(name: 'test'), +]); $application->run();