From 22517cf479c6962d3a7d05f8748f048be92e7ffc Mon Sep 17 00:00:00 2001 From: Trusted97 Date: Fri, 19 Jun 2026 10:45:21 +0200 Subject: [PATCH] refactor!: add layer for improve internal logic --- Makefile | 2 +- README.md | 10 +- composer.json | 10 +- phpstan.neon | 2 + src/Command/AbstractWhitecatCommand.php | 57 ++++++ src/Command/DockerSetupCommand.php | 12 +- src/Command/GithubInitCommand.php | 14 +- src/Command/GithubIssueCommand.php | 14 +- src/Command/GithubPullCommand.php | 14 +- src/Command/GithubWorkflowCommand.php | 12 +- src/Command/PhpCsFixerCommand.php | 12 +- src/Command/PhpStanCommand.php | 12 +- src/Command/PhpUnitCommand.php | 12 +- src/Console/CommandMessages.php | 18 ++ src/Console/WhitecatApplicationFactory.php | 38 ++++ src/Helper/ComposerHelper.php | 8 +- src/Helper/ComposerPackageChecker.php | 54 ++++++ src/Helper/DirectoryCopyHelper.php | 8 +- src/Helper/FileCopyHelper.php | 6 +- src/Service/DockerSetupService.php | 15 +- src/Service/GithubInitService.php | 24 ++- src/Service/GithubIssueService.php | 23 ++- src/Service/GithubPullService.php | 23 ++- src/Service/GithubWorkflowService.php | 38 ++-- src/Service/PhpCsFixerService.php | 46 +---- src/Service/PhpStanService.php | 46 +---- src/Service/PhpUnitService.php | 49 +---- tests/Command/DockerCommandTest.php | 33 ---- tests/Command/GithubInitCommandTest.php | 33 ---- tests/Command/GithubIssueCommandTest.php | 33 ---- tests/Command/GithubPullCommandTest.php | 33 ---- tests/Command/GithubWorkflowCommandTest.php | 33 ---- tests/Command/PhpCsFixerCommandTest.php | 33 ---- tests/Command/PhpStanCommandTest.php | 33 ---- tests/Command/PhpUnitCommandTest.php | 33 ---- tests/Command/WhitecatCommandTest.php | 151 ++++++++++++++++ tests/Helper/ComposerHelperTest.php | 50 ++++++ tests/Service/DockerSetupServiceTest.php | 56 ------ tests/Service/GithubInitServiceTest.php | 57 ------ tests/Service/GithubIssueServiceTest.php | 57 ------ tests/Service/GithubPullServiceTest.php | 54 ------ tests/Service/GithubWorkflowServiceTest.php | 187 -------------------- tests/Service/PhpCsFixerServiceTest.php | 128 -------------- tests/Service/PhpStanServiceTest.php | 129 -------------- tests/Service/PhpUnitServiceTest.php | 161 ----------------- tests/Support/TempProject.php | 40 +++++ whitecat | 33 ++-- 47 files changed, 555 insertions(+), 1391 deletions(-) create mode 100644 src/Command/AbstractWhitecatCommand.php create mode 100644 src/Console/CommandMessages.php create mode 100644 src/Console/WhitecatApplicationFactory.php create mode 100644 src/Helper/ComposerPackageChecker.php delete mode 100644 tests/Command/DockerCommandTest.php delete mode 100644 tests/Command/GithubInitCommandTest.php delete mode 100644 tests/Command/GithubIssueCommandTest.php delete mode 100644 tests/Command/GithubPullCommandTest.php delete mode 100644 tests/Command/GithubWorkflowCommandTest.php delete mode 100644 tests/Command/PhpCsFixerCommandTest.php delete mode 100644 tests/Command/PhpStanCommandTest.php delete mode 100644 tests/Command/PhpUnitCommandTest.php create mode 100644 tests/Command/WhitecatCommandTest.php create mode 100644 tests/Helper/ComposerHelperTest.php delete mode 100644 tests/Service/DockerSetupServiceTest.php delete mode 100644 tests/Service/GithubInitServiceTest.php delete mode 100644 tests/Service/GithubIssueServiceTest.php delete mode 100644 tests/Service/GithubPullServiceTest.php delete mode 100644 tests/Service/GithubWorkflowServiceTest.php delete mode 100644 tests/Service/PhpCsFixerServiceTest.php delete mode 100644 tests/Service/PhpStanServiceTest.php delete mode 100644 tests/Service/PhpUnitServiceTest.php create mode 100644 tests/Support/TempProject.php diff --git a/Makefile b/Makefile index c243b47..6478727 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ test: @echo "Running test..." docker exec -it whitecat-82 composer test -php-cs-fixer: +style: @echo "Launching php-cs-fixer" docker exec -it whitecat-82 composer php-cs-fixer diff --git a/README.md b/README.md index f479761..6bd4846 100644 --- a/README.md +++ b/README.md @@ -37,12 +37,20 @@ composer require --dev trusted97/whitecat ### Usage -This command list all possible command available in whitecat +List the commands available in Whitecat: ``` sh vendor/bin/whitecat list ``` +Every generator command writes to the current working directory by default. To target another project without changing directories, pass `--working-dir`: + +``` sh +vendor/bin/whitecat phpunit:init --working-dir=/path/to/project +``` + +Commands that depend on Composer tooling validate the target project's `composer.json` first and print the exact `composer require --dev ...` command when a dependency is missing. + #### Docker setup This command setup basic docker environment for your library diff --git a/composer.json b/composer.json index 87a8324..280cbe6 100644 --- a/composer.json +++ b/composer.json @@ -43,11 +43,11 @@ "sort-packages": true }, "scripts": { - "test": "vendor/bin/phpunit -c phpunit.xml --no-coverage", - "test-coverage-text": "XDEBUG_MODE=coverage vendor/bin/phpunit -c phpunit.xml --colors=never --coverage-text", - "test-coverage-html": "XDEBUG_MODE=coverage vendor/bin/phpunit -c phpunit.xml --coverage-html coverage", - "php-cs-fixer": "vendor/bin/php-cs-fixer fix --verbose", - "php-cs-fixer-dry-run": "vendor/bin/php-cs-fixer fix --verbose --diff --dry-run", + "test": "php -d error_reporting=8191 vendor/bin/phpunit -c phpunit.xml --no-coverage", + "test-coverage-text": "XDEBUG_MODE=coverage php -d error_reporting=8191 vendor/bin/phpunit -c phpunit.xml --colors=never --coverage-text", + "test-coverage-html": "XDEBUG_MODE=coverage php -d error_reporting=8191 vendor/bin/phpunit -c phpunit.xml --coverage-html coverage", + "php-cs-fixer": "PHP_CS_FIXER_IGNORE_ENV=1 vendor/bin/php-cs-fixer fix --verbose --sequential", + "php-cs-fixer-dry-run": "PHP_CS_FIXER_IGNORE_ENV=1 vendor/bin/php-cs-fixer fix --verbose --diff --dry-run --sequential", "phpstan" : "vendor/bin/phpstan analyse src -l 10", "phpstan-test" : "vendor/bin/phpstan analyse tests -l 10" } diff --git a/phpstan.neon b/phpstan.neon index 0b966e9..f5c7489 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -3,3 +3,5 @@ parameters: paths: - src - tests + parallel: + maximumNumberOfProcesses: 1 diff --git a/src/Command/AbstractWhitecatCommand.php b/src/Command/AbstractWhitecatCommand.php new file mode 100644 index 0000000..6ad01f9 --- /dev/null +++ b/src/Command/AbstractWhitecatCommand.php @@ -0,0 +1,57 @@ +addOption( + name: 'working-dir', + mode: InputOption::VALUE_REQUIRED, + description: 'Run the command as if it was started in the given project directory.' + ); + + $this->setHelp($this->commandHelp()); + } + + final protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + + try { + return $this->runCommand($io, new Filesystem(), $this->resolveWorkingDirectory($input)); + } catch (\Throwable $throwable) { + $io->error($throwable->getMessage()); + $io->note('Run the command again with -vvv for a stack trace, or check that the target project is writable.'); + + return self::FAILURE; + } + } + + abstract protected function runCommand(SymfonyStyle $io, Filesystem $filesystem, string $workingDirectory): int; + + protected function commandHelp(): string + { + return 'Generates project files in the current directory. Use --working-dir to target another project.'; + } + + private function resolveWorkingDirectory(InputInterface $input): string + { + $workingDirectory = $input->getOption('working-dir'); + + if (!\is_string($workingDirectory) || 1 !== \preg_match('/\S/u', $workingDirectory)) { + $workingDirectory = \getcwd() ?: '.'; + } + + return Path::canonicalize($workingDirectory); + } +} diff --git a/src/Command/DockerSetupCommand.php b/src/Command/DockerSetupCommand.php index 6a8dd55..528ad05 100644 --- a/src/Command/DockerSetupCommand.php +++ b/src/Command/DockerSetupCommand.php @@ -3,9 +3,6 @@ namespace Whitecat\Command; use Symfony\Component\Console\Attribute\AsCommand; -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Filesystem\Filesystem; use Whitecat\Service\DockerSetupService; @@ -15,13 +12,10 @@ description: 'Generate basic docker setup for PHP library', hidden: false )] -class DockerSetupCommand extends Command +class DockerSetupCommand extends AbstractWhitecatCommand { - protected function execute(InputInterface $input, OutputInterface $output): int + protected function runCommand(SymfonyStyle $io, Filesystem $filesystem, string $workingDirectory): int { - $io = new SymfonyStyle($input, $output); - $fs = new Filesystem(); - - return (new DockerSetupService($io, $fs))->run(); + return (new DockerSetupService($io, $filesystem, $workingDirectory))->run(); } } diff --git a/src/Command/GithubInitCommand.php b/src/Command/GithubInitCommand.php index 962b4a1..3c7429e 100644 --- a/src/Command/GithubInitCommand.php +++ b/src/Command/GithubInitCommand.php @@ -3,25 +3,19 @@ namespace Whitecat\Command; use Symfony\Component\Console\Attribute\AsCommand; -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Filesystem\Filesystem; use Whitecat\Service\GithubInitService; #[AsCommand( name: 'github:init', - description: 'Generate basic Github directory with starter files', + description: 'Generate basic GitHub directory with starter files', hidden: false )] -class GithubInitCommand extends Command +class GithubInitCommand extends AbstractWhitecatCommand { - protected function execute(InputInterface $input, OutputInterface $output): int + protected function runCommand(SymfonyStyle $io, Filesystem $filesystem, string $workingDirectory): int { - $io = new SymfonyStyle($input, $output); - $fs = new Filesystem(); - - return (new GithubInitService($io, $fs))->run(); + return (new GithubInitService($io, $filesystem, $workingDirectory))->run(); } } diff --git a/src/Command/GithubIssueCommand.php b/src/Command/GithubIssueCommand.php index 9cc0b03..d760716 100644 --- a/src/Command/GithubIssueCommand.php +++ b/src/Command/GithubIssueCommand.php @@ -3,25 +3,19 @@ namespace Whitecat\Command; use Symfony\Component\Console\Attribute\AsCommand; -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Filesystem\Filesystem; use Whitecat\Service\GithubIssueService; #[AsCommand( name: 'github:issue', - description: 'Generate basic template for Github issue', + description: 'Generate basic template for GitHub issue', hidden: false )] -class GithubIssueCommand extends Command +class GithubIssueCommand extends AbstractWhitecatCommand { - protected function execute(InputInterface $input, OutputInterface $output): int + protected function runCommand(SymfonyStyle $io, Filesystem $filesystem, string $workingDirectory): int { - $io = new SymfonyStyle($input, $output); - $fs = new Filesystem(); - - return (new GithubIssueService($io, $fs))->run(); + return (new GithubIssueService($io, $filesystem, $workingDirectory))->run(); } } diff --git a/src/Command/GithubPullCommand.php b/src/Command/GithubPullCommand.php index 2543bc8..1399598 100644 --- a/src/Command/GithubPullCommand.php +++ b/src/Command/GithubPullCommand.php @@ -3,25 +3,19 @@ namespace Whitecat\Command; use Symfony\Component\Console\Attribute\AsCommand; -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Filesystem\Filesystem; use Whitecat\Service\GithubPullService; #[AsCommand( name: 'github:pull', - description: 'Generate basic template for Github pull request', + description: 'Generate basic template for GitHub pull request', hidden: false )] -class GithubPullCommand extends Command +class GithubPullCommand extends AbstractWhitecatCommand { - protected function execute(InputInterface $input, OutputInterface $output): int + protected function runCommand(SymfonyStyle $io, Filesystem $filesystem, string $workingDirectory): int { - $io = new SymfonyStyle($input, $output); - $fs = new Filesystem(); - - return (new GithubPullService($io, $fs))->run(); + return (new GithubPullService($io, $filesystem, $workingDirectory))->run(); } } diff --git a/src/Command/GithubWorkflowCommand.php b/src/Command/GithubWorkflowCommand.php index fcbc6eb..b60d9a1 100644 --- a/src/Command/GithubWorkflowCommand.php +++ b/src/Command/GithubWorkflowCommand.php @@ -3,9 +3,6 @@ namespace Whitecat\Command; use Symfony\Component\Console\Attribute\AsCommand; -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Filesystem\Filesystem; use Whitecat\Service\GithubWorkflowService; @@ -15,13 +12,10 @@ description: 'Generate basic workflow for PHP library', hidden: false )] -class GithubWorkflowCommand extends Command +class GithubWorkflowCommand extends AbstractWhitecatCommand { - protected function execute(InputInterface $input, OutputInterface $output): int + protected function runCommand(SymfonyStyle $io, Filesystem $filesystem, string $workingDirectory): int { - $io = new SymfonyStyle($input, $output); - $fs = new Filesystem(); - - return (new GithubWorkflowService($io, $fs))->run(); + return (new GithubWorkflowService($io, $filesystem, $workingDirectory))->run(); } } diff --git a/src/Command/PhpCsFixerCommand.php b/src/Command/PhpCsFixerCommand.php index a5058cd..1ba6474 100644 --- a/src/Command/PhpCsFixerCommand.php +++ b/src/Command/PhpCsFixerCommand.php @@ -3,9 +3,6 @@ namespace Whitecat\Command; use Symfony\Component\Console\Attribute\AsCommand; -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Filesystem\Filesystem; use Whitecat\Service\PhpCsFixerService; @@ -15,13 +12,10 @@ description: 'Setup basic php-cs-fixer for PHP library', hidden: false )] -class PhpCsFixerCommand extends Command +class PhpCsFixerCommand extends AbstractWhitecatCommand { - protected function execute(InputInterface $input, OutputInterface $output): int + protected function runCommand(SymfonyStyle $io, Filesystem $filesystem, string $workingDirectory): int { - $io = new SymfonyStyle($input, $output); - $fs = new Filesystem(); - - return (new PhpCsFixerService($io, $fs))->run(); + return (new PhpCsFixerService($io, $filesystem, $workingDirectory))->run(); } } diff --git a/src/Command/PhpStanCommand.php b/src/Command/PhpStanCommand.php index cc54fdf..acda4b9 100644 --- a/src/Command/PhpStanCommand.php +++ b/src/Command/PhpStanCommand.php @@ -3,9 +3,6 @@ namespace Whitecat\Command; use Symfony\Component\Console\Attribute\AsCommand; -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Filesystem\Filesystem; use Whitecat\Service\PhpStanService; @@ -15,13 +12,10 @@ description: 'Setup basic phpstan for PHP library', hidden: false )] -class PhpStanCommand extends Command +class PhpStanCommand extends AbstractWhitecatCommand { - protected function execute(InputInterface $input, OutputInterface $output): int + protected function runCommand(SymfonyStyle $io, Filesystem $filesystem, string $workingDirectory): int { - $io = new SymfonyStyle($input, $output); - $fs = new Filesystem(); - - return (new PhpStanService($io, $fs))->run(); + return (new PhpStanService($io, $filesystem, $workingDirectory))->run(); } } diff --git a/src/Command/PhpUnitCommand.php b/src/Command/PhpUnitCommand.php index abe2b12..e5af6d1 100644 --- a/src/Command/PhpUnitCommand.php +++ b/src/Command/PhpUnitCommand.php @@ -3,9 +3,6 @@ namespace Whitecat\Command; use Symfony\Component\Console\Attribute\AsCommand; -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Filesystem\Filesystem; use Whitecat\Service\PhpUnitService; @@ -15,13 +12,10 @@ description: 'Setup basic phpunit for PHP library', hidden: false )] -class PhpUnitCommand extends Command +class PhpUnitCommand extends AbstractWhitecatCommand { - protected function execute(InputInterface $input, OutputInterface $output): int + protected function runCommand(SymfonyStyle $io, Filesystem $filesystem, string $workingDirectory): int { - $io = new SymfonyStyle($input, $output); - $fs = new Filesystem(); - - return (new PhpUnitService($io, $fs))->run(); + return (new PhpUnitService($io, $filesystem, $workingDirectory))->run(); } } diff --git a/src/Console/CommandMessages.php b/src/Console/CommandMessages.php new file mode 100644 index 0000000..97389e1 --- /dev/null +++ b/src/Console/CommandMessages.php @@ -0,0 +1,18 @@ +addCommands([ + new DockerSetupCommand(), + new GithubInitCommand(), + new GithubIssueCommand(), + new GithubPullCommand(), + new GithubWorkflowCommand(), + new PhpCsFixerCommand(), + new PhpStanCommand(), + new PhpUnitCommand(), + ]); + + return $application; + } + + private static function version(): string + { + return '1.x-dev'; + } +} diff --git a/src/Helper/ComposerHelper.php b/src/Helper/ComposerHelper.php index 3858fb3..2efd931 100644 --- a/src/Helper/ComposerHelper.php +++ b/src/Helper/ComposerHelper.php @@ -16,8 +16,8 @@ public function getComposerContent(string $composerPath): array { $composerContent = \file_get_contents($composerPath); - if (!$composerContent) { - throw new InvalidComposerException(); + if (false === $composerContent || 1 !== \preg_match('/\S/u', $composerContent)) { + throw new InvalidComposerException(\sprintf('Unable to read a valid composer.json file at "%s".', $composerPath)); } $composer = \json_decode( @@ -27,8 +27,8 @@ public function getComposerContent(string $composerPath): array flags: \JSON_THROW_ON_ERROR ); - if (!\is_array($composer)) { - throw new InvalidComposerException(); + if (!\is_array($composer) || \array_is_list($composer)) { + throw new InvalidComposerException(\sprintf('The composer.json file at "%s" must decode to a JSON object.', $composerPath)); } return $composer; diff --git a/src/Helper/ComposerPackageChecker.php b/src/Helper/ComposerPackageChecker.php new file mode 100644 index 0000000..79b879d --- /dev/null +++ b/src/Helper/ComposerPackageChecker.php @@ -0,0 +1,54 @@ +composerHelper->getComposerContent(Path::join($workingDirectory, 'composer.json')); + } catch (\JsonException $jsonException) { + $io->error(\sprintf('composer.json is not valid JSON: %s', $jsonException->getMessage())); + $io->note('Fix composer.json and run this command again.'); + + return Command::FAILURE; + } catch (InvalidComposerException $exception) { + $io->error($exception->getMessage()); + $io->note('Run this command from a Composer project or pass --working-dir=/path/to/project.'); + + return Command::FAILURE; + } + + $requireDev = $composer['require-dev'] ?? null; + + if (!\is_array($requireDev)) { + $io->error('composer.json must contain an object-shaped "require-dev" section.'); + $io->note(\sprintf('Install the dependency with: composer require --dev %s', $packageName)); + + return Command::FAILURE; + } + + if (!\array_key_exists($packageName, $requireDev)) { + $io->warning(\sprintf('%s is not installed in require-dev.', $packageName)); + $io->note(\sprintf('Install it with: composer require --dev %s', $packageName)); + + return Command::FAILURE; + } + + return Command::SUCCESS; + } +} diff --git a/src/Helper/DirectoryCopyHelper.php b/src/Helper/DirectoryCopyHelper.php index bc65ab2..81461c0 100644 --- a/src/Helper/DirectoryCopyHelper.php +++ b/src/Helper/DirectoryCopyHelper.php @@ -11,6 +11,7 @@ class DirectoryCopyHelper public function __construct( protected readonly SymfonyStyle $io, protected readonly Filesystem $fs, + private readonly string $workingDirectory = '.', ) { } @@ -21,8 +22,9 @@ public function copyDirectory( string $sourceDirectory, string $distDirectory, ): void { - $checkExists = $this->fs->exists($sourceDirectory); - $override = true; + $targetDirectory = Path::join($this->workingDirectory, $sourceDirectory); + $checkExists = $this->fs->exists($targetDirectory); + $override = true; if ($checkExists) { $override = $this->io->confirm( @@ -35,7 +37,7 @@ public function copyDirectory( $this->io->comment($overrideCommentMessage); $this->fs->mirror( originDir: Path::normalize(__DIR__ . '/../../' . $distDirectory), - targetDir: $sourceDirectory + targetDir: $targetDirectory ); } else { $this->io->comment($skippedMessage); diff --git a/src/Helper/FileCopyHelper.php b/src/Helper/FileCopyHelper.php index 4e8708c..87922d2 100644 --- a/src/Helper/FileCopyHelper.php +++ b/src/Helper/FileCopyHelper.php @@ -11,6 +11,7 @@ class FileCopyHelper public function __construct( protected readonly SymfonyStyle $io, protected readonly Filesystem $fs, + private readonly string $workingDirectory = '.', ) { } @@ -22,7 +23,8 @@ public function copyFile( string $sourceFileDirectory, string $distFileDirectory, ): void { - $checkExists = $this->fs->exists($sourceFileDirectory . $fileName); + $targetFile = Path::join($this->workingDirectory, $sourceFileDirectory, $fileName); + $checkExists = $this->fs->exists($targetFile); $override = true; if ($checkExists) { @@ -36,7 +38,7 @@ public function copyFile( $this->io->comment($overrideCommentMessage); $this->fs->copy( originFile: Path::normalize(__DIR__ . '/../../' . $distFileDirectory . $fileName), - targetFile: $sourceFileDirectory . $fileName, + targetFile: $targetFile, overwriteNewerFiles: true ); } else { diff --git a/src/Service/DockerSetupService.php b/src/Service/DockerSetupService.php index e5f9507..8469dd9 100644 --- a/src/Service/DockerSetupService.php +++ b/src/Service/DockerSetupService.php @@ -2,7 +2,6 @@ namespace Whitecat\Service; -use PHPUnit\Framework\Attributes\CoversNothing; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Filesystem\Exception\IOExceptionInterface; @@ -23,12 +22,13 @@ class DockerSetupService public function __construct( protected readonly SymfonyStyle $io, protected readonly Filesystem $fs, + protected readonly string $workingDirectory = '.', ) { $this->dockerComposePath = Path::normalize(DirectoryPath::DOCKER_COMPOSE->value); $this->dockerDirectoryPath = Path::normalize(DirectoryPath::DOCKER->value); $this->dockerDistDirectoryPath = Path::normalize(DirectoryPath::DIST_DOCKER->value); - $this->fileCopyHelper = new FileCopyHelper($this->io, $this->fs); - $this->directoryCopyHelper = new DirectoryCopyHelper($this->io, $this->fs); + $this->fileCopyHelper = new FileCopyHelper($this->io, $this->fs, $this->workingDirectory); + $this->directoryCopyHelper = new DirectoryCopyHelper($this->io, $this->fs, $this->workingDirectory); } public function run(): int @@ -38,15 +38,15 @@ public function run(): int $this->addDockerDirectory(); $this->addDockerCompose(); - $this->io->success('All work was correctly done!'); + $this->io->success('Done. Your project files are ready.'); return Command::SUCCESS; } - #[CoversNothing] protected function addDockerDirectory(): void { - $dockerDirectoryExists = $this->fs->exists($this->dockerDirectoryPath); + $dockerDirectoryTarget = Path::join($this->workingDirectory, $this->dockerDirectoryPath); + $dockerDirectoryExists = $this->fs->exists($dockerDirectoryTarget); $override = true; if ($dockerDirectoryExists) { @@ -58,7 +58,7 @@ protected function addDockerDirectory(): void if ($override) { try { - $this->fs->mkdir($this->dockerDirectoryPath); + $this->fs->mkdir($dockerDirectoryTarget); $this->io->comment('Docker directory created'); } catch (IOExceptionInterface $IOException) { $this->io->error( @@ -88,7 +88,6 @@ protected function addDockerDirectory(): void ); } - #[CoversNothing] protected function addDockerCompose(): void { $this->fileCopyHelper->copyFile( diff --git a/src/Service/GithubInitService.php b/src/Service/GithubInitService.php index fd88202..a8ad7bb 100644 --- a/src/Service/GithubInitService.php +++ b/src/Service/GithubInitService.php @@ -2,7 +2,6 @@ namespace Whitecat\Service; -use PHPUnit\Framework\Attributes\CoversNothing; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Filesystem\Exception\IOExceptionInterface; @@ -19,14 +18,15 @@ class GithubInitService public function __construct( protected readonly SymfonyStyle $io, protected readonly Filesystem $fs, + protected readonly string $workingDirectory = '.', ) { $this->githubDirectoryPath = Path::normalize(DirectoryPath::GITHUB->value); - $this->fileCopyHelper = new FileCopyHelper($this->io, $this->fs); + $this->fileCopyHelper = new FileCopyHelper($this->io, $this->fs, $this->workingDirectory); } public function run(): int { - $this->io->title('Github init'); + $this->io->title('GitHub init'); $this->addGithubDirectory(); $this->addReadme(); @@ -34,28 +34,28 @@ public function run(): int $this->addGitAttributes(); $this->addGitIgnore(); - $this->io->success('All work was correctly done!'); + $this->io->success('Done. Your project files are ready.'); return Command::SUCCESS; } - #[CoversNothing] protected function addGithubDirectory(): void { - $githubDirectoryExists = $this->fs->exists($this->githubDirectoryPath); + $githubDirectoryTarget = Path::join($this->workingDirectory, $this->githubDirectoryPath); + $githubDirectoryExists = $this->fs->exists($githubDirectoryTarget); $override = true; if ($githubDirectoryExists) { $override = $this->io->confirm( - question: 'It seems that github directory already exists, do you want to continue?', + question: 'It seems that GitHub directory already exists, do you want to continue?', default: false ); } if ($override) { try { - $this->fs->mkdir($this->githubDirectoryPath); - $this->io->comment('Github directory created'); + $this->fs->mkdir($githubDirectoryTarget); + $this->io->comment('GitHub directory created'); } catch (IOExceptionInterface $IOException) { $this->io->error( \sprintf( @@ -72,11 +72,10 @@ protected function addGithubDirectory(): void ); } } else { - $this->io->comment('Skipped creation of Github directory'); + $this->io->comment('Skipped creation of GitHub directory'); } } - #[CoversNothing] protected function addEditorConfig(): void { $this->fileCopyHelper->copyFile( @@ -89,7 +88,6 @@ protected function addEditorConfig(): void ); } - #[CoversNothing] protected function addGitAttributes(): void { $this->fileCopyHelper->copyFile( @@ -102,7 +100,6 @@ protected function addGitAttributes(): void ); } - #[CoversNothing] protected function addGitIgnore(): void { $this->fileCopyHelper->copyFile( @@ -115,7 +112,6 @@ protected function addGitIgnore(): void ); } - #[CoversNothing] protected function addReadme(): void { $this->fileCopyHelper->copyFile( diff --git a/src/Service/GithubIssueService.php b/src/Service/GithubIssueService.php index 7044a26..751e1ab 100644 --- a/src/Service/GithubIssueService.php +++ b/src/Service/GithubIssueService.php @@ -2,7 +2,6 @@ namespace Whitecat\Service; -use PHPUnit\Framework\Attributes\CoversNothing; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Filesystem\Exception\IOExceptionInterface; @@ -19,42 +18,43 @@ class GithubIssueService public function __construct( protected readonly SymfonyStyle $io, protected readonly Filesystem $fs, + protected readonly string $workingDirectory = '.', ) { $this->githubIssueDirectoryPath = Path::normalize(DirectoryPath::ISSUE->value); - $this->fileCopyHelper = new FileCopyHelper($this->io, $this->fs); + $this->fileCopyHelper = new FileCopyHelper($this->io, $this->fs, $this->workingDirectory); } public function run(): int { - $this->io->title('Github issue'); + $this->io->title('GitHub issue'); $this->addGithubIssueDirectory(); $this->addBugReportIssue(); $this->addFeatureRequestIssue(); $this->addIssueConfigFile(); - $this->io->success('All work was correctly done!'); + $this->io->success('Done. Your project files are ready.'); return Command::SUCCESS; } - #[CoversNothing] protected function addGithubIssueDirectory(): void { - $githubIssueDirectoryExists = $this->fs->exists($this->githubIssueDirectoryPath); + $githubIssueDirectoryTarget = Path::join($this->workingDirectory, $this->githubIssueDirectoryPath); + $githubIssueDirectoryExists = $this->fs->exists($githubIssueDirectoryTarget); $override = true; if ($githubIssueDirectoryExists) { $override = $this->io->confirm( - question: 'It seems that github issue directory already exists, do you want to continue?', + question: 'It seems that GitHub issue directory already exists, do you want to continue?', default: false ); } if ($override) { try { - $this->fs->mkdir($this->githubIssueDirectoryPath); - $this->io->comment('Github issue directory created'); + $this->fs->mkdir($githubIssueDirectoryTarget); + $this->io->comment('GitHub issue directory created'); } catch (IOExceptionInterface $IOException) { $this->io->error( \sprintf( @@ -71,11 +71,10 @@ protected function addGithubIssueDirectory(): void ); } } else { - $this->io->comment('Skipped creation of Github issue directory'); + $this->io->comment('Skipped creation of GitHub issue directory'); } } - #[CoversNothing] protected function addBugReportIssue(): void { $this->fileCopyHelper->copyFile( @@ -88,7 +87,6 @@ protected function addBugReportIssue(): void ); } - #[CoversNothing] protected function addFeatureRequestIssue(): void { $this->fileCopyHelper->copyFile( @@ -101,7 +99,6 @@ protected function addFeatureRequestIssue(): void ); } - #[CoversNothing] protected function addIssueConfigFile(): void { $this->fileCopyHelper->copyFile( diff --git a/src/Service/GithubPullService.php b/src/Service/GithubPullService.php index 5c74fd7..934d8da 100644 --- a/src/Service/GithubPullService.php +++ b/src/Service/GithubPullService.php @@ -2,7 +2,6 @@ namespace Whitecat\Service; -use PHPUnit\Framework\Attributes\CoversNothing; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Filesystem\Exception\IOExceptionInterface; @@ -19,42 +18,43 @@ class GithubPullService public function __construct( protected readonly SymfonyStyle $io, protected readonly Filesystem $fs, + protected readonly string $workingDirectory = '.', ) { $this->githubPullDirectoryPath = Path::normalize(DirectoryPath::PULL->value); - $this->fileCopyHelper = new FileCopyHelper($this->io, $this->fs); + $this->fileCopyHelper = new FileCopyHelper($this->io, $this->fs, $this->workingDirectory); } public function run(): int { - $this->io->title('Github pull'); + $this->io->title('GitHub pull'); $this->addGithubPullDirectory(); $this->addFixPullRequestTemplate(); $this->addImprovementPullRequestTemplate(); $this->addNewFeaturePullRequestTemplate(); - $this->io->success('All work was correctly done!'); + $this->io->success('Done. Your project files are ready.'); return Command::SUCCESS; } - #[CoversNothing] protected function addGithubPullDirectory(): void { - $githubIssueDirectoryExists = $this->fs->exists($this->githubPullDirectoryPath); + $githubPullDirectoryTarget = Path::join($this->workingDirectory, $this->githubPullDirectoryPath); + $githubIssueDirectoryExists = $this->fs->exists($githubPullDirectoryTarget); $override = true; if ($githubIssueDirectoryExists) { $override = $this->io->confirm( - question: 'It seems that github pull request directory already exists, do you want to continue?', + question: 'It seems that GitHub pull request directory already exists, do you want to continue?', default: false ); } if ($override) { try { - $this->fs->mkdir($this->githubPullDirectoryPath); - $this->io->comment('Github pull request directory created'); + $this->fs->mkdir($githubPullDirectoryTarget); + $this->io->comment('GitHub pull request directory created'); } catch (IOExceptionInterface $IOException) { $this->io->error( \sprintf( @@ -71,11 +71,10 @@ protected function addGithubPullDirectory(): void ); } } else { - $this->io->comment('Skipped creation of Github pull request directory'); + $this->io->comment('Skipped creation of GitHub pull request directory'); } } - #[CoversNothing] protected function addFixPullRequestTemplate(): void { $this->fileCopyHelper->copyFile( @@ -88,7 +87,6 @@ protected function addFixPullRequestTemplate(): void ); } - #[CoversNothing] protected function addImprovementPullRequestTemplate(): void { $this->fileCopyHelper->copyFile( @@ -101,7 +99,6 @@ protected function addImprovementPullRequestTemplate(): void ); } - #[CoversNothing] protected function addNewFeaturePullRequestTemplate(): void { $this->fileCopyHelper->copyFile( diff --git a/src/Service/GithubWorkflowService.php b/src/Service/GithubWorkflowService.php index 3034095..0ee6159 100644 --- a/src/Service/GithubWorkflowService.php +++ b/src/Service/GithubWorkflowService.php @@ -2,7 +2,6 @@ namespace Whitecat\Service; -use PHPUnit\Framework\Attributes\CoversNothing; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Filesystem\Exception\IOExceptionInterface; @@ -19,14 +18,15 @@ class GithubWorkflowService public function __construct( protected readonly SymfonyStyle $io, protected readonly Filesystem $fs, + protected readonly string $workingDirectory = '.', ) { $this->workflowDirectoryPath = Path::normalize(DirectoryPath::WORKFLOW->value); - $this->fileCopyHelper = new FileCopyHelper($this->io, $this->fs); + $this->fileCopyHelper = new FileCopyHelper($this->io, $this->fs, $this->workingDirectory); } public function run(): int { - $this->io->title('Github workflow'); + $this->io->title('GitHub workflow'); $this->addGithubWorkflowDirectory(); @@ -44,7 +44,7 @@ public function run(): int switch ($choice) { case 'PHPUnit & Coverage (CodeCov)': $this->addGithubTestAction(); - $this->io->warning('Remember to add your Codecov Token in Github Secrets'); + $this->io->warning('Remember to add your Codecov Token in GitHub Secrets'); break; case 'Deploy to Amazon ECS': $this->addAmazonECSDeployAction(); @@ -63,28 +63,28 @@ public function run(): int break; } - $this->io->success('All work was correctly done!'); + $this->io->success('Done. Your project files are ready.'); return Command::SUCCESS; } - #[CoversNothing] protected function addGithubWorkflowDirectory(): void { - $workflowDirectoryExists = $this->fs->exists($this->workflowDirectoryPath); + $workflowDirectoryTarget = Path::join($this->workingDirectory, $this->workflowDirectoryPath); + $workflowDirectoryExists = $this->fs->exists($workflowDirectoryTarget); $override = true; if ($workflowDirectoryExists) { $override = $this->io->confirm( - question: 'It seems that github workflow directory already exists, do you want to continue?', + question: 'It seems that GitHub workflow directory already exists, do you want to continue?', default: false ); } if ($override) { try { - $this->fs->mkdir($this->workflowDirectoryPath); - $this->io->comment('Github workflow directory created'); + $this->fs->mkdir($workflowDirectoryTarget); + $this->io->comment('GitHub workflow directory created'); } catch (IOExceptionInterface $IOException) { $this->io->error( \sprintf( @@ -101,29 +101,27 @@ protected function addGithubWorkflowDirectory(): void ); } } else { - $this->io->comment('Skipped creation of github workflow directory'); + $this->io->comment('Skipped creation of GitHub workflow directory'); } } - #[CoversNothing] protected function addGithubTestAction(): void { $this->fileCopyHelper->copyFile( fileName: 'test.yaml', - questionMessage: 'It seems that github action for test already exists, do you want to override?', - overrideCommentMessage: 'Adding github action for phpunit and code coverage', - skippedMessage: 'Skipped creation of github action for phpunit and code coverage', + questionMessage: 'It seems that GitHub action for test already exists, do you want to override?', + overrideCommentMessage: 'Adding GitHub action for phpunit and code coverage', + skippedMessage: 'Skipped creation of GitHub action for phpunit and code coverage', sourceFileDirectory: $this->workflowDirectoryPath, distFileDirectory: DirectoryPath::DIST_WORKFLOW->value ); } - #[CoversNothing] protected function addGoogleGKEDeployAction(): void { $this->fileCopyHelper->copyFile( fileName: 'deploy_google_gke.yaml', - questionMessage: 'It seems that github action for deploy on Google Kubernetes Engine already exists, do you want to override?', + questionMessage: 'It seems that GitHub action for deploy on Google Kubernetes Engine already exists, do you want to override?', overrideCommentMessage: 'Adding Google GKE Deploy action', skippedMessage: 'Skipped creation of Google GKE Deploy action', sourceFileDirectory: $this->workflowDirectoryPath, @@ -131,12 +129,11 @@ protected function addGoogleGKEDeployAction(): void ); } - #[CoversNothing] protected function addAmazonECSDeployAction(): void { $this->fileCopyHelper->copyFile( fileName: 'deploy_aws_ecs.yaml', - questionMessage: 'It seems that github action for deploy on Amazon ECS already exists, do you want to override?', + questionMessage: 'It seems that GitHub action for deploy on Amazon ECS already exists, do you want to override?', overrideCommentMessage: 'Adding Amazon ECS Deploy action', skippedMessage: 'Skipped creation of Amazon ECS Deploy action', sourceFileDirectory: $this->workflowDirectoryPath, @@ -144,12 +141,11 @@ protected function addAmazonECSDeployAction(): void ); } - #[CoversNothing] protected function addTerraformDeployAction(): void { $this->fileCopyHelper->copyFile( fileName: 'terraform.yaml', - questionMessage: 'It seems that github action for deploy on terraform already exists, do you want to override?', + questionMessage: 'It seems that GitHub action for deploy on terraform already exists, do you want to override?', overrideCommentMessage: 'Adding Terraform Deploy action', skippedMessage: 'Skipped creation of Terraform Deploy action', sourceFileDirectory: $this->workflowDirectoryPath, diff --git a/src/Service/PhpCsFixerService.php b/src/Service/PhpCsFixerService.php index 8e27e50..460f023 100644 --- a/src/Service/PhpCsFixerService.php +++ b/src/Service/PhpCsFixerService.php @@ -2,75 +2,45 @@ namespace Whitecat\Service; -use PHPUnit\Framework\Attributes\CoversNothing; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Filesystem\Path; use Whitecat\Enums\DirectoryPath; -use Whitecat\Exception\InvalidComposerException; -use Whitecat\Helper\ComposerHelper; +use Whitecat\Helper\ComposerPackageChecker; use Whitecat\Helper\FileCopyHelper; class PhpCsFixerService { protected readonly string $workflowDirectoryPath; protected FileCopyHelper $fileCopyHelper; - protected ComposerHelper $composerHelper; + protected ComposerPackageChecker $composerPackageChecker; public function __construct( protected readonly SymfonyStyle $io, protected readonly Filesystem $fs, + protected readonly string $workingDirectory = '.', ) { - $this->workflowDirectoryPath = Path::normalize(DirectoryPath::WORKFLOW->value); - $this->fileCopyHelper = new FileCopyHelper($this->io, $this->fs); - $this->composerHelper = new ComposerHelper(); + $this->workflowDirectoryPath = Path::normalize(DirectoryPath::WORKFLOW->value); + $this->fileCopyHelper = new FileCopyHelper($this->io, $this->fs, $this->workingDirectory); + $this->composerPackageChecker = new ComposerPackageChecker(); } public function run(): int { $this->io->title('Setup basic php-cs-fixer'); - try { - $composer = $this->composerHelper->getComposerContent( - composerPath: './composer.json' - ); - } catch (\JsonException $jsonException) { - $errorMessage = \sprintf('Error: %s ', $jsonException->getMessage()); - $this->io->error($errorMessage); - - return Command::FAILURE; - } catch (InvalidComposerException) { - $this->io->error('Invalid composer.json file!'); - - return Command::FAILURE; - } - - $requireDev = $composer['require-dev']; - - if (!\is_array($requireDev)) { - $this->io->error('The require-dev section in composer.json is invalid or missing!'); - - return Command::FAILURE; - } - - $isInstalledPhpCsFixer = \array_key_exists('friendsofphp/php-cs-fixer', $requireDev); - - if (!$isInstalledPhpCsFixer) { - $this->io->warning('It seems that php-cs-fixer is not installed'); - $this->io->warning('Launch in terminal \'composer require --dev friendsofphp/php-cs-fixer\''); - + if (Command::SUCCESS !== $this->composerPackageChecker->ensureDevPackageInstalled($this->io, $this->workingDirectory, 'friendsofphp/php-cs-fixer')) { return Command::FAILURE; } $this->addPhpCsFixer(); - $this->io->success('All work was correctly done!'); + $this->io->success('Done. Your project files are ready.'); return Command::SUCCESS; } - #[CoversNothing] protected function addPhpCsFixer(): void { $this->fileCopyHelper->copyFile( diff --git a/src/Service/PhpStanService.php b/src/Service/PhpStanService.php index af0d03d..4efcb43 100644 --- a/src/Service/PhpStanService.php +++ b/src/Service/PhpStanService.php @@ -2,75 +2,45 @@ namespace Whitecat\Service; -use PHPUnit\Framework\Attributes\CoversNothing; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Filesystem\Path; use Whitecat\Enums\DirectoryPath; -use Whitecat\Exception\InvalidComposerException; -use Whitecat\Helper\ComposerHelper; +use Whitecat\Helper\ComposerPackageChecker; use Whitecat\Helper\FileCopyHelper; class PhpStanService { protected readonly string $workflowDirectoryPath; protected FileCopyHelper $fileCopyHelper; - protected ComposerHelper $composerHelper; + protected ComposerPackageChecker $composerPackageChecker; public function __construct( protected readonly SymfonyStyle $io, protected readonly Filesystem $fs, + protected readonly string $workingDirectory = '.', ) { - $this->workflowDirectoryPath = Path::normalize(DirectoryPath::WORKFLOW->value); - $this->fileCopyHelper = new FileCopyHelper($this->io, $this->fs); - $this->composerHelper = new ComposerHelper(); + $this->workflowDirectoryPath = Path::normalize(DirectoryPath::WORKFLOW->value); + $this->fileCopyHelper = new FileCopyHelper($this->io, $this->fs, $this->workingDirectory); + $this->composerPackageChecker = new ComposerPackageChecker(); } public function run(): int { $this->io->title('Setup basic PHPStan'); - try { - $composer = $this->composerHelper->getComposerContent( - composerPath: './composer.json' - ); - } catch (\JsonException $jsonException) { - $errorMessage = \sprintf('Error: %s ', $jsonException->getMessage()); - $this->io->error($errorMessage); - - return Command::FAILURE; - } catch (InvalidComposerException) { - $this->io->error('Invalid composer.json file!'); - - return Command::FAILURE; - } - - $requireDev = $composer['require-dev']; - - if (!\is_array($requireDev)) { - $this->io->error('The require-dev section in composer.json is invalid or missing!'); - - return Command::FAILURE; - } - - $isInstalledPhpStan = \array_key_exists('phpstan/phpstan', $requireDev); - - if (!$isInstalledPhpStan) { - $this->io->warning('It seems that phpstan/phpstan is not installed'); - $this->io->warning('Launch in terminal \'composer require --dev phpstan/phpstan\''); - + if (Command::SUCCESS !== $this->composerPackageChecker->ensureDevPackageInstalled($this->io, $this->workingDirectory, 'phpstan/phpstan')) { return Command::FAILURE; } $this->addPHPUnitConfigFile(); - $this->io->success('All work was correctly done!'); + $this->io->success('Done. Your project files are ready.'); return Command::SUCCESS; } - #[CoversNothing] protected function addPHPUnitConfigFile(): void { $this->fileCopyHelper->copyFile( diff --git a/src/Service/PhpUnitService.php b/src/Service/PhpUnitService.php index 1cc04a1..bbe20f9 100644 --- a/src/Service/PhpUnitService.php +++ b/src/Service/PhpUnitService.php @@ -2,14 +2,12 @@ namespace Whitecat\Service; -use PHPUnit\Framework\Attributes\CoversNothing; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Filesystem\Path; use Whitecat\Enums\DirectoryPath; -use Whitecat\Exception\InvalidComposerException; -use Whitecat\Helper\ComposerHelper; +use Whitecat\Helper\ComposerPackageChecker; use Whitecat\Helper\DirectoryCopyHelper; use Whitecat\Helper\FileCopyHelper; @@ -18,63 +16,35 @@ class PhpUnitService protected readonly string $workflowDirectoryPath; protected FileCopyHelper $fileCopyHelper; protected DirectoryCopyHelper $directoryCopyHelper; - protected ComposerHelper $composerHelper; + protected ComposerPackageChecker $composerPackageChecker; public function __construct( protected readonly SymfonyStyle $io, protected readonly Filesystem $fs, + protected readonly string $workingDirectory = '.', ) { - $this->workflowDirectoryPath = Path::normalize(DirectoryPath::WORKFLOW->value); - $this->fileCopyHelper = new FileCopyHelper($this->io, $this->fs); - $this->directoryCopyHelper = new DirectoryCopyHelper($this->io, $this->fs); - $this->composerHelper = new ComposerHelper(); + $this->workflowDirectoryPath = Path::normalize(DirectoryPath::WORKFLOW->value); + $this->fileCopyHelper = new FileCopyHelper($this->io, $this->fs, $this->workingDirectory); + $this->directoryCopyHelper = new DirectoryCopyHelper($this->io, $this->fs, $this->workingDirectory); + $this->composerPackageChecker = new ComposerPackageChecker(); } public function run(): int { $this->io->title('Setup basic PHPUnit'); - try { - $composer = $this->composerHelper->getComposerContent( - composerPath: './composer.json' - ); - } catch (\JsonException $jsonException) { - $errorMessage = \sprintf('Error: %s ', $jsonException->getMessage()); - $this->io->error($errorMessage); - - return Command::FAILURE; - } catch (InvalidComposerException) { - $this->io->error('Invalid composer.json file!'); - - return Command::FAILURE; - } - - $requireDev = $composer['require-dev']; - - if (!\is_array($requireDev)) { - $this->io->error('The require-dev section in composer.json is invalid or missing!'); - - return Command::FAILURE; - } - - $isInstalledPhpUnit = \array_key_exists('phpunit/phpunit', $requireDev); - - if (!$isInstalledPhpUnit) { - $this->io->warning('It seems that phpunit/phpunit is not installed'); - $this->io->warning('Launch in terminal \'composer require --dev phpunit/phpunit\''); - + if (Command::SUCCESS !== $this->composerPackageChecker->ensureDevPackageInstalled($this->io, $this->workingDirectory, 'phpunit/phpunit')) { return Command::FAILURE; } $this->addTestDirectory(); $this->addPHPUnitConfigFile(); - $this->io->success('All work was correctly done!'); + $this->io->success('Done. Your project files are ready.'); return Command::SUCCESS; } - #[CoversNothing] protected function addPHPUnitConfigFile(): void { $this->fileCopyHelper->copyFile( @@ -87,7 +57,6 @@ protected function addPHPUnitConfigFile(): void ); } - #[CoversNothing] protected function addTestDirectory(): void { $this->directoryCopyHelper->copyDirectory( diff --git a/tests/Command/DockerCommandTest.php b/tests/Command/DockerCommandTest.php deleted file mode 100644 index 0038506..0000000 --- a/tests/Command/DockerCommandTest.php +++ /dev/null @@ -1,33 +0,0 @@ -add(new DockerSetupCommand()); - $installerCommand = $application->find('docker:setup'); - - $this->assertInstanceOf(Command::class, $installerCommand); - - $commandTester = new CommandTester($installerCommand); - $commandTester->execute([]); - - $commandTester->assertCommandIsSuccessful(); - - $this->assertStringContainsString( - 'Docker setup', - $commandTester->getDisplay() - ); - - $this->assertSame(0, $commandTester->getStatusCode()); - } -} diff --git a/tests/Command/GithubInitCommandTest.php b/tests/Command/GithubInitCommandTest.php deleted file mode 100644 index ccb219b..0000000 --- a/tests/Command/GithubInitCommandTest.php +++ /dev/null @@ -1,33 +0,0 @@ -add(new GithubInitCommand()); - $installerCommand = $application->find('github:init'); - - $this->assertInstanceOf(Command::class, $installerCommand); - - $commandTester = new CommandTester($installerCommand); - $commandTester->execute([]); - - $commandTester->assertCommandIsSuccessful(); - - $this->assertStringContainsString( - 'Github init', - $commandTester->getDisplay() - ); - - $this->assertSame(0, $commandTester->getStatusCode()); - } -} diff --git a/tests/Command/GithubIssueCommandTest.php b/tests/Command/GithubIssueCommandTest.php deleted file mode 100644 index bc08c87..0000000 --- a/tests/Command/GithubIssueCommandTest.php +++ /dev/null @@ -1,33 +0,0 @@ -add(new GithubIssueCommand()); - $installerCommand = $application->find('github:issue'); - - $this->assertInstanceOf(Command::class, $installerCommand); - - $commandTester = new CommandTester($installerCommand); - $commandTester->execute([]); - - $commandTester->assertCommandIsSuccessful(); - - $this->assertStringContainsString( - 'Github issue', - $commandTester->getDisplay() - ); - - $this->assertSame(0, $commandTester->getStatusCode()); - } -} diff --git a/tests/Command/GithubPullCommandTest.php b/tests/Command/GithubPullCommandTest.php deleted file mode 100644 index af67aac..0000000 --- a/tests/Command/GithubPullCommandTest.php +++ /dev/null @@ -1,33 +0,0 @@ -add(new GithubPullCommand()); - $installerCommand = $application->find('github:pull'); - - $this->assertInstanceOf(Command::class, $installerCommand); - - $commandTester = new CommandTester($installerCommand); - $commandTester->execute([]); - - $commandTester->assertCommandIsSuccessful(); - - $this->assertStringContainsString( - 'Github pull', - $commandTester->getDisplay() - ); - - $this->assertSame(0, $commandTester->getStatusCode()); - } -} diff --git a/tests/Command/GithubWorkflowCommandTest.php b/tests/Command/GithubWorkflowCommandTest.php deleted file mode 100644 index 9099e1b..0000000 --- a/tests/Command/GithubWorkflowCommandTest.php +++ /dev/null @@ -1,33 +0,0 @@ -add(new GithubWorkflowCommand()); - $installerCommand = $application->find('github:workflow'); - - $this->assertInstanceOf(Command::class, $installerCommand); - - $commandTester = new CommandTester($installerCommand); - $commandTester->execute([]); - - $commandTester->assertCommandIsSuccessful(); - - $this->assertStringContainsString( - 'Github workflow', - $commandTester->getDisplay() - ); - - $this->assertSame(0, $commandTester->getStatusCode()); - } -} diff --git a/tests/Command/PhpCsFixerCommandTest.php b/tests/Command/PhpCsFixerCommandTest.php deleted file mode 100644 index 7ba5429..0000000 --- a/tests/Command/PhpCsFixerCommandTest.php +++ /dev/null @@ -1,33 +0,0 @@ -add(new PhpCsFixerCommand()); - $installerCommand = $application->find('php-cs-fixer:init'); - - $this->assertInstanceOf(Command::class, $installerCommand); - - $commandTester = new CommandTester($installerCommand); - $commandTester->execute([]); - - $commandTester->assertCommandIsSuccessful(); - - $this->assertStringContainsString( - 'Setup basic php-cs-fixer', - $commandTester->getDisplay() - ); - - $this->assertSame(0, $commandTester->getStatusCode()); - } -} diff --git a/tests/Command/PhpStanCommandTest.php b/tests/Command/PhpStanCommandTest.php deleted file mode 100644 index 175233e..0000000 --- a/tests/Command/PhpStanCommandTest.php +++ /dev/null @@ -1,33 +0,0 @@ -add(new PhpStanCommand()); - $phpStanCommand = $application->find('phpstan:init'); - - $this->assertInstanceOf(Command::class, $phpStanCommand); - - $commandTester = new CommandTester($phpStanCommand); - $commandTester->execute([]); - - $commandTester->assertCommandIsSuccessful(); - - $this->assertStringContainsString( - 'Setup basic PHPStan', - $commandTester->getDisplay() - ); - - $this->assertSame(0, $commandTester->getStatusCode()); - } -} diff --git a/tests/Command/PhpUnitCommandTest.php b/tests/Command/PhpUnitCommandTest.php deleted file mode 100644 index ad96ff5..0000000 --- a/tests/Command/PhpUnitCommandTest.php +++ /dev/null @@ -1,33 +0,0 @@ -add(new PhpUnitCommand()); - $installerCommand = $application->find('phpunit:init'); - - $this->assertInstanceOf(Command::class, $installerCommand); - - $commandTester = new CommandTester($installerCommand); - $commandTester->execute([]); - - $commandTester->assertCommandIsSuccessful(); - - $this->assertStringContainsString( - 'Setup basic PHPUnit', - $commandTester->getDisplay() - ); - - $this->assertSame(0, $commandTester->getStatusCode()); - } -} diff --git a/tests/Command/WhitecatCommandTest.php b/tests/Command/WhitecatCommandTest.php new file mode 100644 index 0000000..0be4021 --- /dev/null +++ b/tests/Command/WhitecatCommandTest.php @@ -0,0 +1,151 @@ +tearDownTempProject(); + } + + /** @return iterable, list}> */ + public static function generatorCommands(): iterable + { + yield 'docker setup' => [ + 'docker:setup', + [], + ['docker-compose.yml', 'docker/php-8.1/Dockerfile'], + ]; + + yield 'github init' => [ + 'github:init', + [], + ['README.md', '.editorconfig', '.gitattributes', '.gitignore'], + ]; + + yield 'github issue templates' => [ + 'github:issue', + [], + ['.github/ISSUE_TEMPLATE/BUG-REPORT.yml', '.github/ISSUE_TEMPLATE/FEATURE-REQUEST.yml', '.github/ISSUE_TEMPLATE/config.yml'], + ]; + + yield 'github pull request templates' => [ + 'github:pull', + [], + ['.github/PULL_REQUEST_TEMPLATE/FIX.md', '.github/PULL_REQUEST_TEMPLATE/IMPROVEMENT.md', '.github/PULL_REQUEST_TEMPLATE/NEW_FEATURE.md'], + ]; + } + + /** @param list $interactiveInput @param list $expectedFiles */ + #[DataProvider('generatorCommands')] + public function testGeneratorCommandsCreateExpectedFiles(string $commandName, array $interactiveInput, array $expectedFiles): void + { + $project = $this->createTempProject(); + $tester = $this->commandTester($commandName); + $tester->setInputs($interactiveInput); + + $statusCode = $tester->execute(['--working-dir' => $project], ['capture_stderr_separately' => true]); + + $this->assertSame(Command::SUCCESS, $statusCode); + $this->assertSame('', $tester->getErrorOutput()); + $this->assertStringContainsString('Done. Your project files are ready.', $tester->getDisplay()); + + foreach ($expectedFiles as $expectedFile) { + $this->assertFileExists($project . '/' . $expectedFile, \sprintf('Expected %s to be generated.', $expectedFile)); + } + } + + public function testGithubWorkflowCommandCanGenerateSelectedWorkflow(): void + { + $project = $this->createTempProject(); + $tester = $this->commandTester('github:workflow'); + $tester->setInputs(['0']); + + $statusCode = $tester->execute(['--working-dir' => $project], ['capture_stderr_separately' => true]); + + $this->assertSame(Command::SUCCESS, $statusCode); + $this->assertSame('', $tester->getErrorOutput()); + $this->assertStringContainsString('Remember to add your Codecov Token in GitHub Secrets', $tester->getDisplay()); + $this->assertFileExists($project . '/.github/workflows/test.yaml'); + } + + /** @return iterable, string}> */ + public static function composerAwareCommands(): iterable + { + yield 'php-cs-fixer' => ['php-cs-fixer:init', ['require-dev' => ['friendsofphp/php-cs-fixer' => '^3.65']], '.php-cs-fixer.dist.php']; + yield 'phpstan' => ['phpstan:init', ['require-dev' => ['phpstan/phpstan' => '^2.0']], 'phpstan.neon']; + yield 'phpunit' => ['phpunit:init', ['require-dev' => ['phpunit/phpunit' => '^11.0']], 'phpunit.xml']; + } + + /** @param array $composer */ + #[DataProvider('composerAwareCommands')] + public function testComposerAwareCommandsValidateDependenciesAndCreateFiles(string $commandName, array $composer, string $expectedFile): void + { + $project = $this->createTempProject(); + $this->writeComposer($project, $composer); + $tester = $this->commandTester($commandName); + + $statusCode = $tester->execute(['--working-dir' => $project], ['capture_stderr_separately' => true]); + + $this->assertSame(Command::SUCCESS, $statusCode); + $this->assertSame('', $tester->getErrorOutput()); + $this->assertFileExists($project . '/' . $expectedFile); + } + + public function testComposerAwareCommandFailsWithActionableMessageWhenDependencyIsMissing(): void + { + $project = $this->createTempProject(); + $this->writeComposer($project, ['require-dev' => []]); + $tester = $this->commandTester('phpstan:init'); + + $statusCode = $tester->execute(['--working-dir' => $project], ['capture_stderr_separately' => true]); + + $this->assertSame(Command::FAILURE, $statusCode); + $this->assertStringContainsString('phpstan/phpstan is not installed in require-dev.', $tester->getDisplay()); + $this->assertStringContainsString('composer require --dev phpstan/phpstan', $tester->getDisplay()); + $this->assertFileDoesNotExist($project . '/phpstan.neon'); + } + + public function testComposerAwareCommandFailsWithInvalidJsonMessage(): void + { + $project = $this->createTempProject(); + $this->writeComposer($project, '{"require-dev":'); + $tester = $this->commandTester('phpunit:init'); + + $statusCode = $tester->execute(['--working-dir' => $project], ['capture_stderr_separately' => true]); + + $this->assertSame(Command::FAILURE, $statusCode); + $this->assertStringContainsString('composer.json is not valid JSON', $tester->getDisplay()); + $this->assertSame('', $tester->getErrorOutput()); + } + + public function testCommandHelpIncludesWorkingDirectoryOption(): void + { + $application = WhitecatApplicationFactory::create(); + $definition = $application->find('github:init')->getDefinition(); + + $this->assertTrue($definition->hasOption('working-dir')); + $this->assertSame( + 'Run the command as if it was started in the given project directory.', + $definition->getOption('working-dir')->getDescription() + ); + } + + private function commandTester(string $commandName): CommandTester + { + $application = WhitecatApplicationFactory::create(); + $application->setAutoExit(false); + + return new CommandTester($application->find($commandName)); + } +} diff --git a/tests/Helper/ComposerHelperTest.php b/tests/Helper/ComposerHelperTest.php new file mode 100644 index 0000000..e928712 --- /dev/null +++ b/tests/Helper/ComposerHelperTest.php @@ -0,0 +1,50 @@ +tearDownTempProject(); + } + + public function testReadsComposerJsonAsAssociativeArray(): void + { + $project = $this->createTempProject(); + $this->writeComposer($project, ['name' => 'vendor/package', 'require-dev' => ['phpunit/phpunit' => '^11']]); + + $composer = (new ComposerHelper())->getComposerContent($project . '/composer.json'); + + $this->assertSame('vendor/package', $composer['name']); + $this->assertSame('^11', $composer['require-dev']['phpunit/phpunit']); + } + + public function testEmptyComposerFileIsRejected(): void + { + $project = $this->createTempProject(); + \file_put_contents($project . '/composer.json', ''); + + $this->expectException(InvalidComposerException::class); + $this->expectExceptionMessage('Unable to read a valid composer.json file'); + + (new ComposerHelper())->getComposerContent($project . '/composer.json'); + } + + public function testJsonArrayComposerFileIsRejected(): void + { + $project = $this->createTempProject(); + \file_put_contents($project . '/composer.json', '[]'); + + $this->expectException(InvalidComposerException::class); + + (new ComposerHelper())->getComposerContent($project . '/composer.json'); + } +} diff --git a/tests/Service/DockerSetupServiceTest.php b/tests/Service/DockerSetupServiceTest.php deleted file mode 100644 index aee8d64..0000000 --- a/tests/Service/DockerSetupServiceTest.php +++ /dev/null @@ -1,56 +0,0 @@ -getMockBuilder(SymfonyStyle::class) - ->disableOriginalConstructor() - ->getMock(); - - $mockFs = $this->getMockBuilder(Filesystem::class) - ->disableOriginalConstructor() - ->getMock(); - - $dockerService = new DockerSetupService($mockIo, $mockFs); - $this->assertInstanceOf(DockerSetupService::class, $dockerService); - } - - public function testRun(): void - { - $mockIo = $this->getMockBuilder(SymfonyStyle::class) - ->disableOriginalConstructor() - ->getMock(); - - $mockFs = $this->getMockBuilder(Filesystem::class) - ->disableOriginalConstructor() - ->getMock(); - - $mockIo->expects($this->once()) - ->method('title') - ->with('Docker setup'); - $mockIo->expects($this->once()) - ->method('success') - ->with('All work was correctly done!'); - - $mockFs->expects($this->once()) - ->method('mkdir'); - - // Create DockerService instance - $dockerService = new DockerSetupService($mockIo, $mockFs); - - // Run the method - $result = $dockerService->run(); - - // Assertions - $this->assertSame(Command::SUCCESS, $result); - } -} diff --git a/tests/Service/GithubInitServiceTest.php b/tests/Service/GithubInitServiceTest.php deleted file mode 100644 index 19d8756..0000000 --- a/tests/Service/GithubInitServiceTest.php +++ /dev/null @@ -1,57 +0,0 @@ -getMockBuilder(SymfonyStyle::class) - ->disableOriginalConstructor() - ->getMock(); - - $mockFs = $this->getMockBuilder(Filesystem::class) - ->disableOriginalConstructor() - ->getMock(); - - $githubIssueService = new GithubInitService($mockIo, $mockFs); - $this->assertInstanceOf(GithubInitService::class, $githubIssueService); - } - - public function testRun(): void - { - $mockIo = $this->getMockBuilder(SymfonyStyle::class) - ->disableOriginalConstructor() - ->getMock(); - - $mockFs = $this->getMockBuilder(Filesystem::class) - ->disableOriginalConstructor() - ->getMock(); - - $mockIo->expects($this->once()) - ->method('title') - ->with('Github init'); - - $mockFs->expects($this->once()) - ->method('mkdir'); - - $mockIo->expects($this->once()) - ->method('success') - ->with('All work was correctly done!'); - - // Create GithubIssueService instance - $githubIssueService = new GithubInitService($mockIo, $mockFs); - - // Run the method - $result = $githubIssueService->run(); - - // Assertions - $this->assertSame(Command::SUCCESS, $result); - } -} diff --git a/tests/Service/GithubIssueServiceTest.php b/tests/Service/GithubIssueServiceTest.php deleted file mode 100644 index 2e48578..0000000 --- a/tests/Service/GithubIssueServiceTest.php +++ /dev/null @@ -1,57 +0,0 @@ -getMockBuilder(SymfonyStyle::class) - ->disableOriginalConstructor() - ->getMock(); - - $mockFs = $this->getMockBuilder(Filesystem::class) - ->disableOriginalConstructor() - ->getMock(); - - $githubIssueService = new GithubIssueService($mockIo, $mockFs); - $this->assertInstanceOf(GithubIssueService::class, $githubIssueService); - } - - public function testRun(): void - { - $mockIo = $this->getMockBuilder(SymfonyStyle::class) - ->disableOriginalConstructor() - ->getMock(); - - $mockFs = $this->getMockBuilder(Filesystem::class) - ->disableOriginalConstructor() - ->getMock(); - - $mockIo->expects($this->once()) - ->method('title') - ->with('Github issue'); - - $mockFs->expects($this->once()) - ->method('mkdir'); - - $mockIo->expects($this->once()) - ->method('success') - ->with('All work was correctly done!'); - - // Create GithubIssueService instance - $githubIssueService = new GithubIssueService($mockIo, $mockFs); - - // Run the method - $result = $githubIssueService->run(); - - // Assertions - $this->assertSame(Command::SUCCESS, $result); - } -} diff --git a/tests/Service/GithubPullServiceTest.php b/tests/Service/GithubPullServiceTest.php deleted file mode 100644 index 42ae1ec..0000000 --- a/tests/Service/GithubPullServiceTest.php +++ /dev/null @@ -1,54 +0,0 @@ -getMockBuilder(SymfonyStyle::class) - ->disableOriginalConstructor() - ->getMock(); - - $mockFs = $this->getMockBuilder(Filesystem::class) - ->disableOriginalConstructor() - ->getMock(); - - $githubIssueService = new GithubPullService($mockIo, $mockFs); - $this->assertInstanceOf(GithubPullService::class, $githubIssueService); - } - - public function testRun(): void - { - $mockIo = $this->getMockBuilder(SymfonyStyle::class) - ->disableOriginalConstructor() - ->getMock(); - - $mockFs = $this->getMockBuilder(Filesystem::class) - ->disableOriginalConstructor() - ->getMock(); - - $mockIo->expects($this->once()) - ->method('title') - ->with('Github pull'); - - $mockFs->expects($this->once()) - ->method('mkdir'); - - $mockIo->expects($this->once()) - ->method('success') - ->with('All work was correctly done!'); - - $githubIssueService = new GithubPullService($mockIo, $mockFs); - - $result = $githubIssueService->run(); - - $this->assertSame(Command::SUCCESS, $result); - } -} diff --git a/tests/Service/GithubWorkflowServiceTest.php b/tests/Service/GithubWorkflowServiceTest.php deleted file mode 100644 index 5802511..0000000 --- a/tests/Service/GithubWorkflowServiceTest.php +++ /dev/null @@ -1,187 +0,0 @@ -getMockBuilder(InputInterface::class)->getMock(); - $output = $this->getMockBuilder(OutputInterface::class)->getMock(); - $this->symfonyStyle = new SymfonyStyle($input, $output); - $this->filesystem = new Filesystem(); - } - - public function testConstruct(): void - { - $githubWorkflowService = new GithubWorkflowService($this->symfonyStyle, $this->filesystem); - $this->assertInstanceOf(GithubWorkflowService::class, $githubWorkflowService); - } - - public function testRunPHPUnitWorkflow(): void - { - $mockIo = $this->getMockBuilder(SymfonyStyle::class) - ->disableOriginalConstructor() - ->getMock(); - - $mockFs = $this->getMockBuilder(Filesystem::class) - ->disableOriginalConstructor() - ->getMock(); - - $mockIo->expects($this->once()) - ->method('title') - ->with('Github workflow'); - - $mockIo->expects($this->once()) - ->method('choice') - ->with( - $this->equalTo('Select the workflow to create'), - $this->equalTo([ - 'PHPUnit & Coverage (CodeCov)', - 'Deploy to Amazon ECS', - 'Deploy to Google Kubernetes Engine', - 'Terraform Deploy', - 'All', - ]) - ) - ->willReturn('PHPUnit & Coverage (CodeCov)'); - - $mockIo->expects($this->once()) - ->method('warning') - ->with('Remember to add your Codecov Token in Github Secrets'); - - $mockIo->expects($this->once()) - ->method('success') - ->with('All work was correctly done!'); - - $service = new GithubWorkflowService($mockIo, $mockFs); - $result = $service->run(); - - $this->assertSame(Command::SUCCESS, $result); - } - - public function testRunAmazonECSWorkflow(): void - { - $mockIo = $this->getMockBuilder(SymfonyStyle::class) - ->disableOriginalConstructor() - ->getMock(); - - $mockIo->expects($this->once()) - ->method('title') - ->with('Github workflow'); - - $mockIo->expects($this->once()) - ->method('choice') - ->with( - $this->equalTo('Select the workflow to create'), - $this->equalTo([ - 'PHPUnit & Coverage (CodeCov)', - 'Deploy to Amazon ECS', - 'Deploy to Google Kubernetes Engine', - 'Terraform Deploy', - 'All', - ]) - ) - ->willReturn('Deploy to Amazon ECS'); - - $mockIo->expects($this->once()) - ->method('success') - ->with('All work was correctly done!'); - - $mockFs = $this->getMockBuilder(Filesystem::class) - ->disableOriginalConstructor() - ->getMock(); - - $service = new GithubWorkflowService($mockIo, $mockFs); - $result = $service->run(); - - $this->assertSame(Command::SUCCESS, $result); - } - - public function testRunGoogleKubernetesWorklow(): void - { - $mockIo = $this->getMockBuilder(SymfonyStyle::class) - ->disableOriginalConstructor() - ->getMock(); - - $mockIo->expects($this->once()) - ->method('title') - ->with('Github workflow'); - - $mockIo->expects($this->once()) - ->method('choice') - ->with( - $this->equalTo('Select the workflow to create'), - $this->equalTo([ - 'PHPUnit & Coverage (CodeCov)', - 'Deploy to Amazon ECS', - 'Deploy to Google Kubernetes Engine', - 'Terraform Deploy', - 'All', - ]) - ) - ->willReturn('Deploy to Google Kubernetes Engine'); - - $mockIo->expects($this->once()) - ->method('success') - ->with('All work was correctly done!'); - - $mockFs = $this->getMockBuilder(Filesystem::class) - ->disableOriginalConstructor() - ->getMock(); - - $service = new GithubWorkflowService($mockIo, $mockFs); - $result = $service->run(); - - $this->assertSame(Command::SUCCESS, $result); - } - - public function testRunTerraformWorklow(): void - { - $mockIo = $this->getMockBuilder(SymfonyStyle::class) - ->disableOriginalConstructor() - ->getMock(); - - $mockIo->expects($this->once()) - ->method('title') - ->with('Github workflow'); - - $mockIo->expects($this->once()) - ->method('choice') - ->with( - $this->equalTo('Select the workflow to create'), - $this->equalTo([ - 'PHPUnit & Coverage (CodeCov)', - 'Deploy to Amazon ECS', - 'Deploy to Google Kubernetes Engine', - 'Terraform Deploy', - 'All', - ]) - ) - ->willReturn('Terraform Deploy'); - - $mockIo->expects($this->once()) - ->method('success') - ->with('All work was correctly done!'); - - $mockFs = $this->getMockBuilder(Filesystem::class) - ->disableOriginalConstructor() - ->getMock(); - - $service = new GithubWorkflowService($mockIo, $mockFs); - $result = $service->run(); - - $this->assertSame(Command::SUCCESS, $result); - } -} diff --git a/tests/Service/PhpCsFixerServiceTest.php b/tests/Service/PhpCsFixerServiceTest.php deleted file mode 100644 index 21ad82b..0000000 --- a/tests/Service/PhpCsFixerServiceTest.php +++ /dev/null @@ -1,128 +0,0 @@ -getMockBuilder(SymfonyStyle::class) - ->disableOriginalConstructor() - ->getMock(); - - $mockFs = $this->getMockBuilder(Filesystem::class) - ->disableOriginalConstructor() - ->getMock(); - - $phpCsFixerService = new PhpCsFixerService($mockIo, $mockFs); - $this->assertInstanceOf(PhpCsFixerService::class, $phpCsFixerService); - } - - public function testFailedRun(): void - { - $mockIo = $this->getMockBuilder(SymfonyStyle::class) - ->disableOriginalConstructor() - ->getMock(); - - $mockFs = $this->getMockBuilder(Filesystem::class) - ->disableOriginalConstructor() - ->getMock(); - - $mockIo->expects($this->once()) - ->method('title') - ->with('Setup basic php-cs-fixer'); - - // Mock ComposerHelper - $mockComposerHelper = $this->getMockBuilder(ComposerHelper::class) - ->getMock(); - - $mockComposerHelper->expects($this->once()) - ->method('getComposerContent') - ->with('./composer.json') - ->willReturn(['require-dev' => []]); - - $mockIo->expects($this->never()) - ->method('error'); - - // Create PhpCsFixerService instance - $phpCsFixerService = new PhpCsFixerService($mockIo, $mockFs); - $phpCsFixerServiceReflection = new \ReflectionClass(PhpCsFixerService::class); - $composerHelperProperty = $phpCsFixerServiceReflection->getProperty('composerHelper'); - $composerHelperProperty->setAccessible(true); - $composerHelperProperty->setValue($phpCsFixerService, $mockComposerHelper); - - // Run the method - $result = $phpCsFixerService->run(); - - // Assertions - $this->assertSame(Command::FAILURE, $result); - } - - public function testSuccessRun(): void - { - $mockIo = $this->getMockBuilder(SymfonyStyle::class) - ->disableOriginalConstructor() - ->getMock(); - - $mockFs = $this->getMockBuilder(Filesystem::class) - ->disableOriginalConstructor() - ->getMock(); - - $mockIo->expects($this->once()) - ->method('title') - ->with('Setup basic php-cs-fixer'); - - // Mock ComposerHelper - $mockComposerHelper = $this->getMockBuilder(ComposerHelper::class) - ->getMock(); - - $mockComposerHelper->expects($this->once()) - ->method('getComposerContent') - ->willReturn(['require-dev' => [ - 'friendsofphp/php-cs-fixer' => '^3.14', - ]]); - - $mockIo->expects($this->never()) - ->method('error'); - - // Mock FileCopyHelper - $mockFileCopyHelper = $this->getMockBuilder(FileCopyHelper::class) - ->disableOriginalConstructor() - ->getMock(); - - $mockFileCopyHelper->expects($this->once()) - ->method('copyFile') - ->with( - '.php-cs-fixer.dist.php', - 'It seems that a .php-cs-fixer.dist.php already exists, do you want to override?', - 'Adding .php-cs-fixer.dist.php', - 'Skipped creation of .php-cs-fixer.dist.php', - '', - 'dist/' - ); - - // Create PhpCsFixerService instance - $phpCsFixerService = new PhpCsFixerService($mockIo, $mockFs); - $phpCsFixerServiceReflection = new \ReflectionClass(PhpCsFixerService::class); - $composerHelperProperty = $phpCsFixerServiceReflection->getProperty('composerHelper'); - $composerHelperProperty->setAccessible(true); - $composerHelperProperty->setValue($phpCsFixerService, $mockComposerHelper); - $fileCopyHelperProperty = $phpCsFixerServiceReflection->getProperty('fileCopyHelper'); - $fileCopyHelperProperty->setAccessible(true); - $fileCopyHelperProperty->setValue($phpCsFixerService, $mockFileCopyHelper); - - // Run the method - $result = $phpCsFixerService->run(); - - // Assertions - $this->assertSame(Command::SUCCESS, $result); - } -} diff --git a/tests/Service/PhpStanServiceTest.php b/tests/Service/PhpStanServiceTest.php deleted file mode 100644 index 9e55330..0000000 --- a/tests/Service/PhpStanServiceTest.php +++ /dev/null @@ -1,129 +0,0 @@ -getMockBuilder(SymfonyStyle::class) - ->disableOriginalConstructor() - ->getMock(); - - $mockFs = $this->getMockBuilder(Filesystem::class) - ->disableOriginalConstructor() - ->getMock(); - - $phpStanService = new PhpStanService($mockIo, $mockFs); - $this->assertInstanceOf(PhpStanService::class, $phpStanService); - } - - public function testFailedRun(): void - { - $mockIo = $this->getMockBuilder(SymfonyStyle::class) - ->disableOriginalConstructor() - ->getMock(); - - $mockFs = $this->getMockBuilder(Filesystem::class) - ->disableOriginalConstructor() - ->getMock(); - - $mockIo->expects($this->once()) - ->method('title') - ->with('Setup basic PHPStan'); - - // Mock ComposerHelper - $mockComposerHelper = $this->getMockBuilder(ComposerHelper::class) - ->getMock(); - - $mockComposerHelper->expects($this->once()) - ->method('getComposerContent') - ->with('./composer.json') - ->willReturn(['require-dev' => []]); - - $mockIo->expects($this->never()) - ->method('error'); - - // Create PhpCsFixerService instance - $phpStanService = new PhpStanService($mockIo, $mockFs); - $phpStanServiceReflection = new \ReflectionClass(PhpStanService::class); - $composerHelperProperty = $phpStanServiceReflection->getProperty('composerHelper'); - $composerHelperProperty->setAccessible(true); - $composerHelperProperty->setValue($phpStanService, $mockComposerHelper); - - // Run the method - $result = $phpStanService->run(); - - // Assertions - $this->assertSame(Command::FAILURE, $result); - } - - public function testSuccessRun(): void - { - $mockIo = $this->getMockBuilder(SymfonyStyle::class) - ->disableOriginalConstructor() - ->getMock(); - - $mockFs = $this->getMockBuilder(Filesystem::class) - ->disableOriginalConstructor() - ->getMock(); - - $mockIo->expects($this->once()) - ->method('title') - ->with('Setup basic PHPStan'); - - // Mock ComposerHelper - $mockComposerHelper = $this->getMockBuilder(ComposerHelper::class) - ->getMock(); - - $mockComposerHelper->expects($this->once()) - ->method('getComposerContent') - ->willReturn(['require-dev' => [ - 'phpstan/phpstan' => '^1.10', - ]]); - - $mockIo->expects($this->never()) - ->method('error'); - - // Mock FileCopyHelper - $mockFileCopyHelper = $this->getMockBuilder(FileCopyHelper::class) - ->disableOriginalConstructor() - ->getMock(); - - $mockFileCopyHelper->expects($this->once()) - ->method('copyFile') - ->with( - 'phpstan.neon', - 'It seems that a phpstan.neon already exists, do you want to override?', - 'Adding phpstan.neon', - 'Skipped creation of phpstan.neon', - '', - 'dist/' - ); - - // Create PhpCsFixerService instance - $phpStanService = new PhpStanService($mockIo, $mockFs); - $phpStanServiceReflection = new \ReflectionClass(PhpStanService::class); - $composerHelperProperty = $phpStanServiceReflection->getProperty('composerHelper'); - $composerHelperProperty->setAccessible(true); - $composerHelperProperty->setValue($phpStanService, $mockComposerHelper); - $fileCopyHelperProperty = $phpStanServiceReflection->getProperty('fileCopyHelper'); - $fileCopyHelperProperty->setAccessible(true); - $fileCopyHelperProperty->setValue($phpStanService, $mockFileCopyHelper); - - // Run the method - $result = $phpStanService->run(); - - // Assertions - $this->assertSame(Command::SUCCESS, $result); - } -} diff --git a/tests/Service/PhpUnitServiceTest.php b/tests/Service/PhpUnitServiceTest.php deleted file mode 100644 index 897b50f..0000000 --- a/tests/Service/PhpUnitServiceTest.php +++ /dev/null @@ -1,161 +0,0 @@ -getMockBuilder(SymfonyStyle::class) - ->disableOriginalConstructor() - ->getMock(); - - $mockFs = $this->getMockBuilder(Filesystem::class) - ->disableOriginalConstructor() - ->getMock(); - - $phpCsFixerService = new PhpUnitService($mockIo, $mockFs); - $this->assertInstanceOf(PhpUnitService::class, $phpCsFixerService); - } - - public function testSuccessRun(): void - { - $mockIo = $this->getMockBuilder(SymfonyStyle::class) - ->disableOriginalConstructor() - ->getMock(); - - $mockFs = $this->getMockBuilder(Filesystem::class) - ->disableOriginalConstructor() - ->getMock(); - - $mockIo->expects($this->once()) - ->method('title') - ->with('Setup basic PHPUnit'); - - // Mock ComposerHelper - $mockComposerHelper = $this->getMockBuilder(ComposerHelper::class) - ->getMock(); - - $mockComposerHelper->expects($this->once()) - ->method('getComposerContent') - ->with('./composer.json') - ->willReturn(['require-dev' => [ - 'phpunit/phpunit' => '^10', - ]]); - - $mockIo->expects($this->never()) - ->method('error'); - - $mockIo->expects($this->never()) - ->method('warning'); - - // Mock FileCopyHelper - $mockFileCopyHelper = $this->getMockBuilder(FileCopyHelper::class) - ->disableOriginalConstructor() - ->getMock(); - - $mockFileCopyHelper->expects($this->once()) - ->method('copyFile') - ->with( - 'phpunit.xml', - 'It seems that a phpunit.xml already exists, do you want to override?', - 'Adding phpunit.xml', - 'Skipped creation of phpunit.xml', - '', - 'dist/' - ); - - // Mock DirectoryCopyHelper - $mockDirectoryCopyHelper = $this->getMockBuilder(DirectoryCopyHelper::class) - ->disableOriginalConstructor() - ->getMock(); - - $mockDirectoryCopyHelper->expects($this->once()) - ->method('copyDirectory') - ->with( - 'It seems that config for PHPUnit already exists, do you want to override?', - 'Adding PHPUnit config', - 'Skipped creation of PHPUnit config', - 'tests', - 'dist/tests/' - ); - - // Create PhpUnitService instance and inject dependencies - $phpUnitService = new PhpUnitService($mockIo, $mockFs); - $phpUnitServiceReflection = new \ReflectionClass(PhpUnitService::class); - $composerHelperProperty = $phpUnitServiceReflection->getProperty('composerHelper'); - $composerHelperProperty->setValue($phpUnitService, $mockComposerHelper); - $fileCopyHelperProperty = $phpUnitServiceReflection->getProperty('fileCopyHelper'); - $fileCopyHelperProperty->setValue($phpUnitService, $mockFileCopyHelper); - $directoryCopyHelperProperty = $phpUnitServiceReflection->getProperty('directoryCopyHelper'); - $directoryCopyHelperProperty->setValue($phpUnitService, $mockDirectoryCopyHelper); - - // Run the method - $result = $phpUnitService->run(); - - // Assertions - $this->assertSame(Command::SUCCESS, $result); - } - - public function testFailedRun(): void - { - $mockIo = $this->getMockBuilder(SymfonyStyle::class) - ->disableOriginalConstructor() - ->getMock(); - - $mockFs = $this->getMockBuilder(Filesystem::class) - ->disableOriginalConstructor() - ->getMock(); - - $mockIo->expects($this->once()) - ->method('title') - ->with('Setup basic PHPUnit'); - - // Mock ComposerHelper - $mockComposerHelper = $this->getMockBuilder(ComposerHelper::class) - ->getMock(); - - $mockComposerHelper->expects($this->once()) - ->method('getComposerContent') - ->willReturn(['require-dev' => []]); - - // Mock FileCopyHelper - $mockFileCopyHelper = $this->getMockBuilder(FileCopyHelper::class) - ->disableOriginalConstructor() - ->getMock(); - - $mockFileCopyHelper->expects($this->never())->method('copyFile'); - - // Mock DirectoryCopyHelper - $mockDirectoryCopyHelper = $this->getMockBuilder(DirectoryCopyHelper::class) - ->disableOriginalConstructor() - ->getMock(); - - $mockDirectoryCopyHelper->expects($this->never())->method('copyDirectory'); - - // Create PhpUnitService instance and inject dependencies - $phpUnitService = new PhpUnitService($mockIo, $mockFs); - $phpUnitServiceReflection = new \ReflectionClass(PhpUnitService::class); - $composerHelperProperty = $phpUnitServiceReflection->getProperty('composerHelper'); - $composerHelperProperty->setValue($phpUnitService, $mockComposerHelper); - $fileCopyHelperProperty = $phpUnitServiceReflection->getProperty('fileCopyHelper'); - $fileCopyHelperProperty->setValue($phpUnitService, $mockFileCopyHelper); - $directoryCopyHelperProperty = $phpUnitServiceReflection->getProperty('directoryCopyHelper'); - $directoryCopyHelperProperty->setValue($phpUnitService, $mockDirectoryCopyHelper); - - // Run the method - $result = $phpUnitService->run(); - - // Assertions - $this->assertSame(Command::FAILURE, $result); - } -} diff --git a/tests/Support/TempProject.php b/tests/Support/TempProject.php new file mode 100644 index 0000000..b16aef8 --- /dev/null +++ b/tests/Support/TempProject.php @@ -0,0 +1,40 @@ + */ + private array $temporaryDirectories = []; + + protected function createTempProject(): string + { + $directory = \sys_get_temp_dir() . '/whitecat-test-' . \bin2hex(\random_bytes(8)); + (new Filesystem())->mkdir($directory); + $this->temporaryDirectories[] = $directory; + + return $directory; + } + + protected function writeComposer(string $projectDirectory, array|string $contents): void + { + $json = \is_array($contents) + ? \json_encode($contents, \JSON_PRETTY_PRINT | \JSON_THROW_ON_ERROR) + : $contents; + + \file_put_contents($projectDirectory . '/composer.json', $json); + } + + protected function tearDownTempProject(): void + { + $filesystem = new Filesystem(); + + foreach ($this->temporaryDirectories as $directory) { + $filesystem->remove($directory); + } + + $this->temporaryDirectories = []; + } +} diff --git a/whitecat b/whitecat index 076b1b9..2957d36 100644 --- a/whitecat +++ b/whitecat @@ -15,11 +15,11 @@ if (!\version_compare(\PHP_VERSION, \PHP_VERSION, '=')) { exit(1); } -if (\version_compare('8.1.0', \PHP_VERSION, '>')) { +if (\version_compare('8.2.0', \PHP_VERSION, '>')) { \fwrite( \STDERR, \sprintf( - 'This version of Whitecat requires PHP >= 8.1.' . \PHP_EOL . + 'This version of Whitecat requires PHP >= 8.2.' . \PHP_EOL . 'You are using PHP %s (%s).' . \PHP_EOL, \PHP_VERSION, \PHP_BINARY @@ -58,30 +58,19 @@ if (!\defined('WHITECAT_COMPOSER_INSTALL')) { require WHITECAT_COMPOSER_INSTALL; -use Symfony\Component\Console\Application; -use Whitecat\Command\DockerSetupCommand; -use Whitecat\Command\GithubInitCommand; -use Whitecat\Command\GithubIssueCommand; -use Whitecat\Command\GithubPullCommand; -use Whitecat\Command\GithubWorkflowCommand; -use Whitecat\Command\PhpCsFixerCommand; -use Whitecat\Command\PhpStanCommand; -use Whitecat\Command\PhpUnitCommand; +use Whitecat\Console\WhitecatApplicationFactory; -$application = new Application(); +$application = WhitecatApplicationFactory::create(); try { - $application->add(new DockerSetupCommand()); - $application->add(new GithubInitCommand()); - $application->add(new GithubIssueCommand()); - $application->add(new GithubPullCommand()); - $application->add(new GithubWorkflowCommand()); - $application->add(new PhpCsFixerCommand()); - $application->add(new PhpStanCommand()); - $application->add(new PhpUnitCommand()); $application->run(); -} catch (Exception $e) { - echo \sprintf('Error code: %d , Error type: %s', $e->getCode(), $e->getMessage()); +} catch (\Throwable $throwable) { + \fwrite( + \STDERR, + \sprintf('Whitecat failed: %s' . \PHP_EOL, $throwable->getMessage()) + ); + + exit(1); } __HALT_COMPILER();