Extract logic to determine which package manager

..is used

Always Composer for PHP.
npm, yarn or pnpm for node.
This commit is contained in:
Oliver Davies 2024-02-26 07:17:04 +00:00
parent b2ccc4027f
commit c6a6b4de5d
9 changed files with 202 additions and 24 deletions

View file

@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace App\Action;
use App\Enum\PackageManager;
use App\Enum\ProjectLanguage;
use Symfony\Component\Filesystem\Filesystem;
final class DeterminePackageManager
{
public function __construct(
private Filesystem $filesystem,
private string $projectLanguage,
private string $workingDir = '.',
) {
}
public function getPackageManager(): string
{
if ($this->projectLanguage === ProjectLanguage::JavaScript->value) {
if ($this->filesystem->exists($this->workingDir.'/pnpm-lock.yaml')) {
return PackageManager::pnpm->value;
}
if ($this->filesystem->exists($this->workingDir.'/yarn.lock')) {
return PackageManager::yarn->value;
}
return PackageManager::npm->value;
}
// TODO: throw an Exception if the language cannot be determined instead of returning a default.
return PackageManager::Composer->value;
}
}

View file

@ -5,7 +5,7 @@ namespace App\Action;
use App\Enum\ProjectLanguage;
use Symfony\Component\Filesystem\Filesystem;
final class DetermineProjectLanguage
final class DetermineProjectLanguage implements DetermineProjectLanguageInterface
{
public function __construct(
private Filesystem $filesystem,
@ -13,9 +13,6 @@ final class DetermineProjectLanguage
) {
}
/**
* @return non-empty-string
*/
public function getLanguage(): string
{
if ($this->filesystem->exists($this->workingDir.'/composer.json')) {

View file

@ -0,0 +1,11 @@
<?php
namespace App\Action;
interface DetermineProjectLanguageInterface
{
/**
* @return non-empty-string
*/
public function getLanguage(): string;
}

View file

@ -2,9 +2,12 @@
namespace App\Console\Command;
use App\Action\DeterminePackageManager;
use App\Action\DetermineProjectLanguage;
use App\Enum\PackageManager;
use App\Enum\ProjectLanguage;
use App\Process\Process;
use RuntimeException;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
@ -29,11 +32,7 @@ final class InstallCommand extends AbstractCommand
// TODO: Composer in Docker Compose?
$process = Process::create(
args: explode(separator: ' ', string: strval($args)),
command: $this->getCommand(
filesystem: $filesystem,
language: $language,
workingDir: $workingDir,
),
command: $this->getCommand(language: $language, workingDir: $workingDir),
workingDir: $workingDir,
);
@ -44,23 +43,35 @@ 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>
* @throws RuntimeException If the lanuage cannot be determined.
*/
private function getCommand(Filesystem $filesystem, string $language, string $workingDir): array
private function getCommand(string $language, string $workingDir): array
{
if ($language === ProjectLanguage::JavaScript->value) {
if ($filesystem->exists($workingDir.'/yarn.lock')) {
return ['yarn'];
} elseif ($filesystem->exists($workingDir.'/pnpm-lock.yaml')) {
return ['composer', 'install'];
} elseif ($language === ProjectLanguage::JavaScript->value) {
$packageManager = new DeterminePackageManager(
filesystem: $this->filesystem,
projectLanguage: $language,
workingDir: $workingDir,
);
switch ($packageManager->getPackageManager()) {
case PackageManager::pnpm->value:
return ['pnpm', 'install'];
} else {
case PackageManager::yarn->value:
return ['yarn'];
default:
return ['npm', 'install'];
}
}
return ['composer', 'install'];
// TODO: add a test to ensure the exception is thrown?
throw new RuntimeException('Project language cannot be determined.');
}
}

View file

@ -2,7 +2,9 @@
namespace App\Console\Command;
use App\Action\DeterminePackageManager;
use App\Action\DetermineProjectLanguage;
use App\Enum\PackageManager;
use App\Enum\ProjectLanguage;
use App\Process\Process;
use Symfony\Component\Console\Command\Command;
@ -47,12 +49,24 @@ final class PackageInstallCommand extends AbstractCommand
break;
case ProjectLanguage::JavaScript->value:
if ($this->filesystem->exists($workingDir.'/yarn.lock')) {
$command = ['yarn', 'add'];
} elseif ($this->filesystem->exists($workingDir.'/pnpm-lock.yaml')) {
$packageManager = new DeterminePackageManager(
filesystem: $this->filesystem,
projectLanguage: $language,
workingDir: $workingDir,
);
switch ($packageManager->getPackageManager()) {
case PackageManager::pnpm->value:
$command = ['pnpm', 'install'];
} else {
break;
case PackageManager::yarn->value:
$command = ['yarn', 'add'];
break;
default:
$command = ['npm', 'install'];
break;
}
$process = Process::create(

View file

@ -0,0 +1,11 @@
<?php
namespace App\Enum;
enum PackageManager: string
{
case Composer = 'composer';
case npm = 'npm';
case pnpm = 'pnpm';
case yarn = 'yarn';
}

View file

@ -0,0 +1,94 @@
<?php
declare(strict_types=1);
namespace App\Tests;
use App\Action\DeterminePackageManager;
use App\Action\DetermineProjectLanguage;
use App\Action\DetermineProjectLanguageInterface;
use App\Enum\PackageManager;
use App\Enum\ProjectLanguage;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Filesystem\Filesystem;
final class DeterminePackageManagerTest extends TestCase
{
/** @test */
public function it_finds_php(): void
{
$filesystem = $this->createMock(Filesystem::class);
$filesystem
->method('exists')
->with('./composer.json')
->willReturn(true);
$action = new DeterminePackageManager(
filesystem: $filesystem,
projectLanguage: ProjectLanguage::PHP->value,
);
self::assertSame(
actual: $action->getPackageManager(),
expected: PackageManager::Composer->value,
);
}
public function lockFileProvider(): array
{
return [
'npm' => [
'./package-lock.json',
PackageManager::npm->value,
[
['./package-lock.json', true],
['./pnpm-lock.yaml', false],
['./yarn.lock', false],
],
],
'pnpm' => [
'./pnpm-lock.yaml',
PackageManager::pnpm->value,
[
['./package-lock.json', false],
['./pnpm-lock.yaml', true],
['./yarn.lock', false],
],
],
'yarn' => [
'./yarn.lock',
PackageManager::yarn->value,
[
['./package-lock.json', false],
['./pnpm-lock.yaml', false],
['./yarn.lock', true],
],
],
];
}
/**
* @dataProvider lockFileProvider
* @test
*/
public function it_finds_node(
string $lockFile,
string $expectedPackageManager,
array $valueMap,
): void {
$filesystem = $this->createMock(Filesystem::class);
$filesystem
->method('exists')
->will(self::returnValueMap($valueMap));
$action = new DeterminePackageManager(
filesystem: $filesystem,
projectLanguage: ProjectLanguage::JavaScript->value,
);
self::assertSame(
actual: $action->getPackageManager(),
expected: $expectedPackageManager,
);
}
}

View file

@ -1,5 +1,7 @@
<?php
namespace App\Tests;
use App\Action\DetermineProjectLanguage;
use App\Enum\ProjectLanguage;
use PHPUnit\Framework\TestCase;

1
versa
View file

@ -3,6 +3,7 @@
<?php
require __DIR__.'/vendor/autoload.php';
use App\Action\DeterminePackageManager;
use App\Console\Command\BuildCommand;
use App\Console\Command\InstallCommand;
use App\Console\Command\PackageInstallCommand;