Add Fractal to versa build and automatically

...select the language if there is a `package.json` file
This commit is contained in:
Oliver Davies 2024-02-22 00:31:29 +00:00
parent c5248bb061
commit 18ce1e84c6
5 changed files with 117 additions and 42 deletions

View file

@ -4,6 +4,7 @@
### Added
* Add initial JavaScript/TypeScript/Fractal support to `versa install` and `versa run`.
* Add a Symfony project type.
* Automatically use PHPUnit or ParaTest based on `require-dev` dependencies.
* Automatically find the PHP project type (i.e. Drupal or Sculpin) based on its `composer.json` dependencies.

View file

@ -2,6 +2,7 @@
namespace App\Console\Command;
use App\Enum\ProjectLanguage;
use App\Enum\ProjectType;
use App\Process\Process;
use RuntimeException;
@ -14,6 +15,7 @@ final class BuildCommand extends AbstractCommand
{
public function execute(InputInterface $input, OutputInterface $output): int
{
$projectLanguage = null;
$projectType = null;
$extraArgs = $input->getOption('extra-args');
@ -25,6 +27,8 @@ final class BuildCommand extends AbstractCommand
// based on its dependencies.
// TODO: move this logic to a service so it can be tested.
if ($filesystem->exists($workingDir.'/composer.json')) {
$projectLanguage = ProjectLanguage::PHP->value;
$json = json_decode(
json: strval(file_get_contents($workingDir.'/composer.json')),
associative: true,
@ -39,40 +43,61 @@ final class BuildCommand extends AbstractCommand
} elseif (in_array(needle: 'symfony/framework-bundle', haystack: $dependencies, strict: true)) {
$projectType = ProjectType::Symfony->value;
}
} elseif ($filesystem->exists($workingDir.'/fractal.config.js')) {
$projectLanguage = ProjectLanguage::JavaScript->value;
$projectType = ProjectType::Fractal->value;
}
// Even if the project type is found automatically, still override it with
// the option value if there is one.
// Even if the project language or type is found automatically, still
// override it with the option value if there is one.
$projectLanguage = $input->getOption('language') ?? $projectLanguage;
$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,
);
switch ($projectLanguage) {
case ProjectLanguage::PHP->value:
switch ($projectType) {
case ProjectType::Drupal->value:
if ($isDockerCompose) {
$process = Process::create(
command: ['docker', 'compose', 'build'],
extraArgs: $extraArgs,
workingDir: $workingDir,
);
$process->run();
$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;
}
case ProjectLanguage::JavaScript->value:
switch ($projectType) {
case ProjectType::Fractal->value:
$process = Process::create(
command: ['npx', 'fractal', 'build'],
extraArgs: $extraArgs,
workingDir: $workingDir,
);
$process->run();
break;
}
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;

View file

@ -21,10 +21,13 @@ final class InstallCommand extends AbstractCommand
// TODO: validate the language is an allowed value.
$filesystem = new Filesystem();
// TODO: Composer in Docker Compose?
$process = Process::create(
command: $this->getCommand(
language: $input->getOption('language'),
filesystem: $filesystem,
language: $this->getProjectLanguage($filesystem, $workingDir, $input),
workingDir: $workingDir,
),
extraArgs: $extraArgs,
@ -38,14 +41,13 @@ final class InstallCommand extends AbstractCommand
}
/**
* @param Filesystem $filesystem
* @param non-empty-string $language
* @param non-empty-string $workingDir
* @return non-empty-array<int, non-empty-string>
*/
private function getCommand(string $language, string $workingDir): array
private function getCommand(Filesystem $filesystem, string $language, string $workingDir): array
{
$filesystem = new Filesystem();
if ($language === ProjectLanguage::JavaScript->value) {
if ($filesystem->exists($workingDir.'/yarn.lock')) {
return ['yarn'];
@ -59,4 +61,22 @@ final class InstallCommand extends AbstractCommand
return ['composer', 'install'];
}
/**
* @param Filesystem $filesystem
* @param non-empty-string $workingDir
* @param InputInterface $input
* @return non-empty-string
*/
private function getProjectLanguage(Filesystem $filesystem, string $workingDir, InputInterface $input): string {
$projectLanguage = null;
// Determine the language based on the files.
if ($filesystem->exists($workingDir.'/composer.json')) {
$projectLanguage = ProjectLanguage::PHP->value;
} elseif ($filesystem->exists($workingDir.'/package.json')) {
$projectLanguage = ProjectLanguage::JavaScript->value;
}
return $input->getOption('language') ?? $projectLanguage;
}
}

View file

@ -2,6 +2,7 @@
namespace App\Console\Command;
use App\Enum\ProjectLanguage;
use App\Enum\ProjectType;
use App\Process\Process;
use Symfony\Component\Console\Command\Command;
@ -13,31 +14,45 @@ final class RunCommand extends AbstractCommand
{
public function execute(InputInterface $input, OutputInterface $output): int
{
$projectLanguage = null;
$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.
$json = json_decode(
json: strval(file_get_contents($workingDir.'/composer.json')),
associative: true,
);
if ($filesystem->exists($workingDir.'/composer.json')) {
$projectLanguage = ProjectLanguage::PHP->value;
$dependencies = array_keys($json['require']);
$json = json_decode(
json: strval(file_get_contents($workingDir.'/composer.json')),
associative: true,
);
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;
$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;
}
} elseif ($filesystem->exists($workingDir.'/package.json')) {
$projectLanguage = ProjectLanguage::JavaScript->value;
if ($filesystem->exists($workingDir.'/fractal.config.js')) {
$projectType = ProjectType::Fractal->value;
}
}
// Even if the project type is found automatically, still override it with
// the option value if there is one.
$projectLanguage = $input->getOption('language') ?? $projectLanguage;
$projectType = $input->getOption('type') ?? $projectType;
$filesystem = new Filesystem();
@ -49,19 +64,30 @@ final class RunCommand extends AbstractCommand
extraArgs: $extraArgs,
workingDir: $workingDir,
);
$process->setTimeout(null);
$process->setTimeout(null);
$process->run();
} else {
switch ($projectType) {
case ProjectType::Fractal->value:
$process = Process::create(
command: ['npx', 'fractal', 'start', '--sync'],
extraArgs: $extraArgs,
workingDir: $workingDir,
);
$process->setTimeout(null);
$process->run();
break;
case ProjectType::Sculpin->value:
$process = Process::create(
command: ['./vendor/bin/sculpin', 'generate', '--server', '--watch'],
extraArgs: $extraArgs,
workingDir: $workingDir,
);
$process->setTimeout(null);
$process->setTimeout(null);
$process->run();
break;
}

View file

@ -6,6 +6,9 @@ namespace App\Enum;
enum ProjectType: string
{
// JavaScript.
case Fractal = 'fractal';
// PHP.
case Drupal = 'drupal';
case Sculpin = 'sculpin';