mirror of
https://github.com/opdavies/versa.git
synced 2025-02-08 18:35:03 +00:00
Refactor to separate commands
This commit is contained in:
parent
1350899e8f
commit
b4e7a71fe3
1
notes
1
notes
|
@ -1,3 +1,2 @@
|
||||||
Add TypeScript and JavaScript suppport - e.g. Fractal
|
Add TypeScript and JavaScript suppport - e.g. Fractal
|
||||||
Review https://github.com/phpstan/phpstan-symfony
|
Review https://github.com/phpstan/phpstan-symfony
|
||||||
Refactor to separate commands rather than using a single command application?
|
|
||||||
|
|
35
src/Console/Command/AbstractCommand.php
Normal file
35
src/Console/Command/AbstractCommand.php
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Command;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
|
|
||||||
|
abstract class AbstractCommand extends Command
|
||||||
|
{
|
||||||
|
protected function configure(): void
|
||||||
|
{
|
||||||
|
$this->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: '.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
80
src/Console/Command/BuildCommand.php
Normal file
80
src/Console/Command/BuildCommand.php
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Command;
|
||||||
|
|
||||||
|
use App\Enum\ProjectType;
|
||||||
|
use App\Process\Process;
|
||||||
|
use RuntimeException;
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Filesystem\Filesystem;
|
||||||
|
|
||||||
|
final class BuildCommand extends AbstractCommand
|
||||||
|
{
|
||||||
|
public function execute(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
$projectType = null;
|
||||||
|
|
||||||
|
$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']);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
28
src/Console/Command/InstallCommand.php
Normal file
28
src/Console/Command/InstallCommand.php
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Command;
|
||||||
|
|
||||||
|
use App\Process\Process;
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
|
final class InstallCommand extends AbstractCommand
|
||||||
|
{
|
||||||
|
public function execute(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
$extraArgs = $input->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;
|
||||||
|
}
|
||||||
|
}
|
72
src/Console/Command/RunCommand.php
Normal file
72
src/Console/Command/RunCommand.php
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Command;
|
||||||
|
|
||||||
|
use App\Enum\ProjectType;
|
||||||
|
use App\Process\Process;
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Filesystem\Filesystem;
|
||||||
|
|
||||||
|
final class RunCommand extends AbstractCommand
|
||||||
|
{
|
||||||
|
public function execute(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
$projectType = null;
|
||||||
|
|
||||||
|
$extraArgs = $input->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;
|
||||||
|
}
|
||||||
|
}
|
44
src/Console/Command/TestCommand.php
Normal file
44
src/Console/Command/TestCommand.php
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Command;
|
||||||
|
|
||||||
|
use App\Process\Process;
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
|
final class TestCommand extends AbstractCommand
|
||||||
|
{
|
||||||
|
public function execute(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
$extraArgs = $input->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;
|
||||||
|
}
|
||||||
|
}
|
170
versa
170
versa
|
@ -3,165 +3,19 @@
|
||||||
<?php
|
<?php
|
||||||
require __DIR__.'/vendor/autoload.php';
|
require __DIR__.'/vendor/autoload.php';
|
||||||
|
|
||||||
use App\Enum\ProjectType;
|
use App\Console\Command\BuildCommand;
|
||||||
use App\Process\Process;
|
use App\Console\Command\InstallCommand;
|
||||||
use Symfony\Component\Console\Input\InputArgument;
|
use App\Console\Command\RunCommand;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use App\Console\Command\TestCommand;
|
||||||
use Symfony\Component\Console\SingleCommandApplication;
|
use Symfony\Component\Console\Application;
|
||||||
use Symfony\Component\Filesystem\Filesystem;
|
|
||||||
|
|
||||||
$application = new SingleCommandApplication();
|
$application = new Application();
|
||||||
|
|
||||||
$application->addArgument(
|
$application->addCommands([
|
||||||
name: 'command',
|
new BuildCommand(name: 'build'),
|
||||||
mode: InputArgument::REQUIRED,
|
new InstallCommand(name: 'install'),
|
||||||
description: 'The command to run',
|
new RunCommand(name: 'run'),
|
||||||
);
|
new TestCommand(name: 'test'),
|
||||||
|
]);
|
||||||
$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->run();
|
$application->run();
|
||||||
|
|
Loading…
Reference in a new issue