From 775dae5b08a1d84ba1da6b98e921016325a490f8 Mon Sep 17 00:00:00 2001 From: Joshua Estes Date: Sat, 19 Oct 2024 18:32:03 -0400 Subject: [PATCH] updates (#234) --- .php-cs-fixer.dist.php | 3 +- build/hooks/pre-commit | 4 +- .../Bard/Tests/Console/ApplicationTest.php | 75 ++++++++++++++++ .../Tests/Console/Command/AddCommandTest.php | 67 +++++++++++++++ .../Tests/Console/Command/CopyCommandTest.php | 66 ++++++++++++++ .../Console/Command/MergeCommandTest.php | 85 +++++++++++++++++++ .../Tests/Console/Command/PushCommandTest.php | 65 ++++++++++++++ .../Console/Command/ReleaseCommandTest.php | 72 ++++++++++++++++ .../Console/Command/SplitCommandTest.php | 63 ++++++++++++++ src/SonsOfPHP/Bard/Tests/JsonFileTest.php | 62 +++++++++++--- .../Worker/File/Bard/AddPackageWorkerTest.php | 68 +++++++++++++++ src/SonsOfPHP/Bard/Tests/fixtures/test.json | 2 +- .../Bard/src/Console/Application.php | 3 +- .../src/Console/Command/AbstractCommand.php | 34 +++++++- .../Bard/src/Console/Command/AddCommand.php | 78 ++++++++--------- .../Bard/src/Console/Command/CopyCommand.php | 76 +++++++++++++---- .../src/Console/Command/InstallCommand.php | 4 +- .../Bard/src/Console/Command/MergeCommand.php | 11 +-- .../src/Console/Command/ReleaseCommand.php | 38 ++++----- src/SonsOfPHP/Bard/src/JsonFile.php | 50 +++++++---- src/SonsOfPHP/Bard/src/JsonFileInterface.php | 28 ++++++ .../src/Worker/File/Bard/AddPackageWorker.php | 40 +++++++++ ...ateVersion.php => UpdateVersionWorker.php} | 7 +- 23 files changed, 875 insertions(+), 126 deletions(-) create mode 100644 src/SonsOfPHP/Bard/Tests/Console/ApplicationTest.php create mode 100644 src/SonsOfPHP/Bard/Tests/Console/Command/AddCommandTest.php create mode 100644 src/SonsOfPHP/Bard/Tests/Console/Command/CopyCommandTest.php create mode 100644 src/SonsOfPHP/Bard/Tests/Console/Command/MergeCommandTest.php create mode 100644 src/SonsOfPHP/Bard/Tests/Console/Command/PushCommandTest.php create mode 100644 src/SonsOfPHP/Bard/Tests/Console/Command/ReleaseCommandTest.php create mode 100644 src/SonsOfPHP/Bard/Tests/Console/Command/SplitCommandTest.php create mode 100644 src/SonsOfPHP/Bard/Tests/Worker/File/Bard/AddPackageWorkerTest.php create mode 100644 src/SonsOfPHP/Bard/src/JsonFileInterface.php create mode 100644 src/SonsOfPHP/Bard/src/Worker/File/Bard/AddPackageWorker.php rename src/SonsOfPHP/Bard/src/Worker/File/Bard/{UpdateVersion.php => UpdateVersionWorker.php} (73%) diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 98ccfbdf..96b650a6 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -22,12 +22,13 @@ // Rule sets '@PER-CS' => true, '@PHP80Migration:risky' => true, - '@PHP81Migration' => true, + '@PHP82Migration' => true, '@PHPUnit100Migration:risky' => true, // Rules 'no_unused_imports' => true, 'ordered_imports' => true, + 'heredoc_indentation' => false, //'php_unit_test_class_requires_covers' => true, ]) ->setFinder($finder) diff --git a/build/hooks/pre-commit b/build/hooks/pre-commit index d8cbe12e..9a7a91f9 100644 --- a/build/hooks/pre-commit +++ b/build/hooks/pre-commit @@ -88,10 +88,10 @@ fi # <<< psalm # >>> PHPUnit -PHPUNIT="bin/phpunit" +PHPUNIT="tools/phpunit/vendor/bin/phpunit" if [ -x $PHPUNIT ]; then __info "Running phpunit" - XDEBUG_MODE=off php -dxdebug.mode=off $PHPUNIT --testsuite unit + XDEBUG_MODE=off php -dxdebug.mode=off -dapc.enable_cli=1 $PHPUNIT --testsuite=all if [ $? -ne 0 ]; then __fail "Unit Tests failed, fix your shit. Can also use --no-verify to skip checks" fi diff --git a/src/SonsOfPHP/Bard/Tests/Console/ApplicationTest.php b/src/SonsOfPHP/Bard/Tests/Console/ApplicationTest.php new file mode 100644 index 00000000..7687cc47 --- /dev/null +++ b/src/SonsOfPHP/Bard/Tests/Console/ApplicationTest.php @@ -0,0 +1,75 @@ +application = new Application(); + } + + public function testItsNameIsCorrect(): void + { + $this->assertSame('Bard', $this->application->getName()); + } + + public function testItHasAddCommand(): void + { + $this->assertTrue($this->application->has('add')); + } + + public function testItHasCopyCommand(): void + { + $this->assertTrue($this->application->has('copy')); + } + + public function testItHasInitCommand(): void + { + $this->assertTrue($this->application->has('init')); + } + + public function testItHasInstallCommand(): void + { + $this->assertTrue($this->application->has('install')); + } + + public function testItHasMergeCommand(): void + { + $this->assertTrue($this->application->has('merge')); + } +} diff --git a/src/SonsOfPHP/Bard/Tests/Console/Command/AddCommandTest.php b/src/SonsOfPHP/Bard/Tests/Console/Command/AddCommandTest.php new file mode 100644 index 00000000..3d9e9647 --- /dev/null +++ b/src/SonsOfPHP/Bard/Tests/Console/Command/AddCommandTest.php @@ -0,0 +1,67 @@ +application = new Application(); + $this->command = $this->application->get('add'); + } + + public function testItsNameIsCorrect(): void + { + $commandTester = new CommandTester($this->command); + + $commandTester->execute([ + 'path' => 'tmp/repo', + 'repository' => 'git@repo:repo.git', + '--dry-run' => true, + '-vvv' => true, + ]); + + $commandTester->assertCommandIsSuccessful(); + } +} diff --git a/src/SonsOfPHP/Bard/Tests/Console/Command/CopyCommandTest.php b/src/SonsOfPHP/Bard/Tests/Console/Command/CopyCommandTest.php new file mode 100644 index 00000000..fd2e8af0 --- /dev/null +++ b/src/SonsOfPHP/Bard/Tests/Console/Command/CopyCommandTest.php @@ -0,0 +1,66 @@ +application = new Application(); + $this->command = $this->application->get('copy'); + } + + public function testItExecutesSuccessfully(): void + { + $commandTester = new CommandTester($this->command); + + $commandTester->execute([ + 'source' => 'LICENSE', + '--dry-run' => true, + '-vvv' => true, + ]); + + $commandTester->assertCommandIsSuccessful(); + } +} diff --git a/src/SonsOfPHP/Bard/Tests/Console/Command/MergeCommandTest.php b/src/SonsOfPHP/Bard/Tests/Console/Command/MergeCommandTest.php new file mode 100644 index 00000000..ccb5696a --- /dev/null +++ b/src/SonsOfPHP/Bard/Tests/Console/Command/MergeCommandTest.php @@ -0,0 +1,85 @@ +application = new Application(); + $this->command = $this->application->get('merge'); + } + + public function testItExecutesSuccessfully(): void + { + $commandTester = new CommandTester($this->command); + + $commandTester->execute([ + '--dry-run' => true, + '-vvv' => true, + ]); + + $commandTester->assertCommandIsSuccessful(); + } +} diff --git a/src/SonsOfPHP/Bard/Tests/Console/Command/PushCommandTest.php b/src/SonsOfPHP/Bard/Tests/Console/Command/PushCommandTest.php new file mode 100644 index 00000000..2e71154b --- /dev/null +++ b/src/SonsOfPHP/Bard/Tests/Console/Command/PushCommandTest.php @@ -0,0 +1,65 @@ +application = new Application(); + $this->command = $this->application->get('push'); + } + + public function testItExecutesSuccessfully(): void + { + $commandTester = new CommandTester($this->command); + + $commandTester->execute([ + '--dry-run' => true, + '-vvv' => true, + ]); + + $commandTester->assertCommandIsSuccessful(); + } +} diff --git a/src/SonsOfPHP/Bard/Tests/Console/Command/ReleaseCommandTest.php b/src/SonsOfPHP/Bard/Tests/Console/Command/ReleaseCommandTest.php new file mode 100644 index 00000000..0220aab7 --- /dev/null +++ b/src/SonsOfPHP/Bard/Tests/Console/Command/ReleaseCommandTest.php @@ -0,0 +1,72 @@ +application = new Application(); + $this->command = $this->application->get('release'); + } + + public function testItsNameIsCorrect(): void + { + $commandTester = new CommandTester($this->command); + + $commandTester->execute([ + 'release' => 'patch', + '--dry-run' => true, + '-vvv' => true, + ]); + + $commandTester->assertCommandIsSuccessful(); + } +} diff --git a/src/SonsOfPHP/Bard/Tests/Console/Command/SplitCommandTest.php b/src/SonsOfPHP/Bard/Tests/Console/Command/SplitCommandTest.php new file mode 100644 index 00000000..0f65882c --- /dev/null +++ b/src/SonsOfPHP/Bard/Tests/Console/Command/SplitCommandTest.php @@ -0,0 +1,63 @@ +application = new Application(); + $this->command = $this->application->get('split'); + } + + public function testItsNameIsCorrect(): void + { + $commandTester = new CommandTester($this->command); + + $commandTester->execute([ + '--dry-run' => true, + '-vvv' => true, + ]); + + $commandTester->assertCommandIsSuccessful(); + } +} diff --git a/src/SonsOfPHP/Bard/Tests/JsonFileTest.php b/src/SonsOfPHP/Bard/Tests/JsonFileTest.php index 550a1300..3854fd35 100644 --- a/src/SonsOfPHP/Bard/Tests/JsonFileTest.php +++ b/src/SonsOfPHP/Bard/Tests/JsonFileTest.php @@ -5,30 +5,66 @@ namespace SonsOfPHP\Bard\Tests; use PHPUnit\Framework\Attributes\CoversClass; -use PHPUnit\Framework\Attributes\UsesClass; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; use SonsOfPHP\Bard\JsonFile; -use SonsOfPHP\Component\Json\AbstractEncoderDecoder; -use SonsOfPHP\Component\Json\Json; -use SonsOfPHP\Component\Json\JsonDecoder; +#[Group('bard')] #[CoversClass(JsonFile::class)] -#[UsesClass(AbstractEncoderDecoder::class)] -#[UsesClass(Json::class)] -#[UsesClass(JsonDecoder::class)] final class JsonFileTest extends TestCase { - public function testGetFilename(): void + private function getDefaultConfig(): array { - $json = new JsonFile(__DIR__ . '/fixtures/test.json'); + return [ + 'version' => '1.2.3', + 'packages' => [ + [ + 'path' => 'path/to/Repo', + 'repository' => 'git@github.com:SonsOfPHP/read-only-repo.git', + ], + ], + ]; + } + + //protected function setUp(): void + //{ + //} + + protected function tearDown(): void + { + $file = new JsonFile(__DIR__ . '/fixtures/test.json'); + $file->setConfig($this->getDefaultConfig()); + $file->save(); + } + + public function testItLoadsDefaultConfig(): void + { + $file = new JsonFile(__DIR__ . '/fixtures/test.json'); + $this->assertSame($this->getDefaultConfig(), $file->getConfig()); + } + + public function testItsAbleToReturnCorrectSection(): void + { + $file = new JsonFile(__DIR__ . '/fixtures/test.json'); - $this->assertIsString($json->getFilename()); + $this->assertSame('1.2.3', $file->getSection('version')); } - public function testGetSection(): void + public function testItCanUpdateExistingSection(): void { - $json = new JsonFile(__DIR__ . '/fixtures/test.json'); + $file = new JsonFile(__DIR__ . '/fixtures/test.json'); + $file = $file->setSection('version', '1.2.4'); + $this->assertSame('1.2.4', $file->getSection('version')); + } + + public function testItCanConvertUpdatedConfigToJson(): void + { + $file = new JsonFile(__DIR__ . '/fixtures/test.json'); + $file = $file->setSection('version', '1.2.4'); + + $json = json_decode($file->toJson(), true); - $this->assertSame('1.2.3', $json->getSection('version')); + $this->assertArrayHasKey('version', $json); + $this->assertSame('1.2.4', $json['version']); } } diff --git a/src/SonsOfPHP/Bard/Tests/Worker/File/Bard/AddPackageWorkerTest.php b/src/SonsOfPHP/Bard/Tests/Worker/File/Bard/AddPackageWorkerTest.php new file mode 100644 index 00000000..1d918a3d --- /dev/null +++ b/src/SonsOfPHP/Bard/Tests/Worker/File/Bard/AddPackageWorkerTest.php @@ -0,0 +1,68 @@ +worker = new AddPackageWorker([ + 'path' => 'src/test', + 'repository' => 'git@github.com:vendor/repo.git', + ]); + + $this->jsonFile = $this->createMock(JsonFileInterface::class); + } + + public function testItImplementsCorrectInterface(): void + { + $this->assertInstanceOf(WorkerInterface::class, $this->worker); + } + + public function testItWillThrowExceptionWhenPackageAtSamePathExists(): void + { + $this->jsonFile->method('getSection')->willReturn([ + [ + 'path' => 'src/test', + 'repository' => 'git@github.com:vendor/repo.git', + ], + ]); + + $this->expectException('Exception'); + $this->worker->apply($this->jsonFile); + } + + public function testItWillAddNewPackage(): void + { + $this->jsonFile->expects($this->once())->method('setSection') + ->with( + 'packages', + $this->callback(function ($packages): true { + $this->assertSame([[ + 'path' => 'src/test', + 'repository' => 'git@github.com:vendor/repo.git', + ]], $packages); + return true; + }) + ) + ; + + $this->worker->apply($this->jsonFile); + } +} diff --git a/src/SonsOfPHP/Bard/Tests/fixtures/test.json b/src/SonsOfPHP/Bard/Tests/fixtures/test.json index e4eb7479..34d96df4 100644 --- a/src/SonsOfPHP/Bard/Tests/fixtures/test.json +++ b/src/SonsOfPHP/Bard/Tests/fixtures/test.json @@ -6,4 +6,4 @@ "repository": "git@github.com:SonsOfPHP/read-only-repo.git" } ] -} +} \ No newline at end of file diff --git a/src/SonsOfPHP/Bard/src/Console/Application.php b/src/SonsOfPHP/Bard/src/Console/Application.php index 492d60ab..a39043be 100644 --- a/src/SonsOfPHP/Bard/src/Console/Application.php +++ b/src/SonsOfPHP/Bard/src/Console/Application.php @@ -23,7 +23,7 @@ */ final class Application extends BaseApplication { - public const VERSION = '0.1.x'; + public const VERSION = '%bard.version%'; public function __construct() { @@ -51,6 +51,7 @@ protected function getDefaultInputDefinition(): InputDefinition $definition = parent::getDefaultInputDefinition(); $definition->addOption(new InputOption('working-dir', 'd', InputOption::VALUE_REQUIRED, 'Working Directory', getcwd())); // $definition->addOption(new InputOption('branch', 'b', InputOption::VALUE_REQUIRED, 'Mainline Branch', 'main')); + $definition->addOption(new InputOption('config', null, InputOption::VALUE_REQUIRED, 'Path to bard.json', 'bard.json')); return $definition; } diff --git a/src/SonsOfPHP/Bard/src/Console/Command/AbstractCommand.php b/src/SonsOfPHP/Bard/src/Console/Command/AbstractCommand.php index f913a2f1..0f0bd7a3 100644 --- a/src/SonsOfPHP/Bard/src/Console/Command/AbstractCommand.php +++ b/src/SonsOfPHP/Bard/src/Console/Command/AbstractCommand.php @@ -4,9 +4,41 @@ namespace SonsOfPHP\Bard\Console\Command; +use SonsOfPHP\Bard\JsonFile; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\FormatterHelper; +use Symfony\Component\Console\Helper\ProcessHelper; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; /** * @author Joshua Estes */ -abstract class AbstractCommand extends Command {} +abstract class AbstractCommand extends Command +{ + protected JsonFile $bardConfig; + + protected SymfonyStyle $bardStyle; + + protected function initialize(InputInterface $input, OutputInterface $output): void + { + $this->bardConfig = new JsonFile($input->getOption('working-dir') . '/bard.json'); + $config = $input->getOption('config'); + if ('bard.json' !== $config) { + $this->bardConfig = new JsonFile($config); + } + + $this->bardStyle = new SymfonyStyle($input, $output); + } + + protected function getFormatterHelper(): FormatterHelper + { + return $this->getHelper('formatter'); + } + + protected function getProcessHelper(): ProcessHelper + { + return $this->getHelper('process'); + } +} diff --git a/src/SonsOfPHP/Bard/src/Console/Command/AddCommand.php b/src/SonsOfPHP/Bard/src/Console/Command/AddCommand.php index 54db0c5e..8b0536c4 100644 --- a/src/SonsOfPHP/Bard/src/Console/Command/AddCommand.php +++ b/src/SonsOfPHP/Bard/src/Console/Command/AddCommand.php @@ -4,12 +4,11 @@ namespace SonsOfPHP\Bard\Console\Command; -use SonsOfPHP\Bard\JsonFile; +use SonsOfPHP\Bard\Worker\File\Bard\AddPackageWorker; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Process\Process; /** @@ -21,70 +20,65 @@ protected function configure(): void { $this ->setName('add') - ->setDescription('Add new repo') + ->setDescription('Add read-only repo') ->addOption('branch', null, InputOption::VALUE_REQUIRED, 'What branch we working with?', 'main') ->addOption('dry-run', null, InputOption::VALUE_NONE, 'Dry Run (Do not make any changes)') ->addArgument('path', InputArgument::REQUIRED, 'Path where code will be') ->addArgument('repository', InputArgument::REQUIRED, 'Repository Uri') + ->addUsage('src/SonsOfPHP/Bard git@github.com:SonsOfPHP/bard.git') + ->addUsage('--branch=master src/SonsOfPHP/Bard git@github.com:SonsOfPHP/bard.git') + ->addUsage('--dry-run src/SonsOfPHP/Bard git@github.com:SonsOfPHP/bard.git') ->setHelp( <<<'HELP' - The add command will add additional repositories that need to be managed - into the `bard.json` file. +The add command will add additional repositories that need to be managed +into the `bard.json` file. - Examples: +Examples: - %command.full_name% src/SonsOfPHP/Bard git@github.com:vendor/package.git + %command.full_name% src/SonsOfPHP/Bard git@github.com:vendor/package.git - Read more at https://docs.sonsofphp.com/bard/ - HELP +Read more at https://docs.sonsofphp.com/bard/ +HELP ) ; } protected function execute(InputInterface $input, OutputInterface $output): int { - $bardConfig = new JsonFile($input->getOption('working-dir') . '/bard.json'); - $io = new SymfonyStyle($input, $output); - $isDryRun = $input->getOption('dry-run'); + $this->bardConfig = $this->bardConfig->with(new AddPackageWorker([ + 'path' => $input->getArgument('path'), + 'repository' => $input->getArgument('repository'), + ])); - // --- - if (null === $packages = $bardConfig->getSection('packages')) { - $packages = []; - } + $isDryRun = $input->getOption('dry-run'); + $process = new Process([ + 'git', + 'subtree', + 'add', + '--prefix', + $input->getArgument('path'), + $input->getArgument('repository'), + $input->getOption('branch'), + '--squash', + ]); - foreach ($packages as $pkg) { - if ($pkg['path'] === $input->getArgument('path')) { - $io->error([ - sprintf('It appears that the path "%s" is currently being used.', $pkg['path']), - 'Please check your bard.json file', - ]); - return self::FAILURE; - } + if ($this->bardStyle->isDebug()) { + $this->bardStyle->block($process->getCommandLine(), 'CMD'); } - $packages[] = [ - 'path' => $input->getArgument('path'), - 'repository' => $input->getArgument('repository'), - ]; - // --- - - $bardConfig = $bardConfig->setSection('packages', $packages); - $commands = [ - ['git', 'subtree', 'add', '--prefix', $input->getArgument('path'), $input->getArgument('repository'), $input->getOption('branch'), '--squash'], - ]; - foreach ($commands as $cmd) { - $process = new Process($cmd); - $io->text($process->getCommandLine()); - if (!$isDryRun) { - $this->getHelper('process')->mustRun($output, $process, sprintf('There was and error running command: %s', $process->getCommandLine())); - } + if (!$isDryRun) { + $this->getProcessHelper()->mustRun($output, $process, sprintf('There was and error running command: %s', $process->getCommandLine())); } if (!$isDryRun) { - file_put_contents($bardConfig->getFilename(), $bardConfig->toJson()); + $this->bardConfig->save(); } - $io->success('Package has been added.'); + $this->bardStyle->success('Package has been added.'); + + if ($isDryRun) { + $this->bardStyle->info('Dry-run enabled, bard config file was not updated'); + } return self::SUCCESS; } diff --git a/src/SonsOfPHP/Bard/src/Console/Command/CopyCommand.php b/src/SonsOfPHP/Bard/src/Console/Command/CopyCommand.php index 9401ca5f..efba21e6 100644 --- a/src/SonsOfPHP/Bard/src/Console/Command/CopyCommand.php +++ b/src/SonsOfPHP/Bard/src/Console/Command/CopyCommand.php @@ -10,7 +10,6 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Process\Process; /** @@ -24,26 +23,29 @@ protected function configure(): void ->setName('copy') ->setDescription('Copies a file to each package') ->addOption('dry-run', null, InputOption::VALUE_NONE, 'Dry Run (Do not make any changes)') + ->addOption('overwrite', null, InputOption::VALUE_NONE, 'If file exists, overwrite it') ->addArgument('source', InputArgument::REQUIRED, 'Source file to copy') ->addArgument('package', InputArgument::OPTIONAL, 'Which package?') + ->addUsage('LICENSE') + ->addUsage('LICENSE sonsofphp/bard') + ->addUsage('--overwrite LICENSE sonsofphp/bard') ->setHelp( <<<'HELP' - The copy command will copy whatever file you give it to all the other - repositories it is managing. This is useful for LICENSE files. +The copy command will copy whatever file you give it to all the +other repositories it is managing. This is useful for LICENSE files. - Examples: +Examples: - %command.full_name% LICENSE + %command.full_name% LICENSE - Read more at https://docs.sonsofphp.com/bard/ - HELP +Read more at https://docs.sonsofphp.com/bard/ +HELP ) ; } protected function execute(InputInterface $input, OutputInterface $output): int { - $io = new SymfonyStyle($input, $output); $isDryRun = $input->getOption('dry-run'); // --- @@ -55,26 +57,64 @@ protected function execute(InputInterface $input, OutputInterface $output): int // --- // --- - $bardJsonFile = new JsonFile($input->getOption('working-dir') . '/bard.json'); - foreach ($bardJsonFile->getSection('packages') as $pkg) { - $pkgComposerFile = realpath($input->getOption('working-dir') . '/' . $pkg['path'] . '/composer.json'); - $pkgComposerJsonFile = new JsonFile($pkgComposerFile); - $pkgName = $pkgComposerJsonFile->getSection('name'); + foreach ($this->bardConfig->getSection('packages') as $pkg) { + $pkgPath = realpath($input->getOption('working-dir') . '/' . $pkg['path']); + $pkgFile = new JsonFile($pkgPath . '/composer.json'); + $pkgName = $pkgFile->getSection('name'); if (null !== $input->getArgument('package') && $pkgName !== $input->getArgument('package')) { continue; } - $process = new Process(['cp', $sourceFile, $pkg['path']]); - $io->text($process->getCommandLine()); - if (!$isDryRun) { - $this->getHelper('process')->run($output, $process); + $doesFileExists = file_exists($pkgPath . '/' . $input->getArgument('source')); + + if ( + (($doesFileExists && true === $input->getOption('overwrite')) || false === $doesFileExists) + && !$isDryRun + ) { + $process = new Process(['cp', $sourceFile, $pkgPath]); + //$worker = (new CopyFileWorker($source))->apply($pkg['path']); + $this->getProcessHelper()->run($output, $process); } + + $message = match ($doesFileExists) { + true => match ($input->getOption('overwrite')) { + true => sprintf( + 'Updated "%s/%s"', + $pkg['path'], + $input->getArgument('source'), + ), + false => sprintf( + 'File Exists "%s/%s" and has not been updated', + $pkg['path'], + $input->getArgument('source'), + ), + }, + false => sprintf( + 'Copied "%s" to "%s"', + $input->getArgument('source'), + $pkg['path'] + ), + }; + + $style = match ($doesFileExists) { + false => 'fg=white', + true => match ($input->getOption('overwrite')) { + false => 'fg=red', + true => 'fg=green', + }, + }; + + $this->bardStyle->text($this->getFormatterHelper()->formatSection($pkgName, $message, $style)); } // --- - $io->success(sprintf('File "%s" has been copied to all managed repos.', $sourceFile)); + $this->bardStyle->success(sprintf('File "%s" has been copied to all managed repos.', $sourceFile)); + + if ($isDryRun) { + $this->bardStyle->info('Dry-run enabled, nothing was modified'); + } return self::SUCCESS; } diff --git a/src/SonsOfPHP/Bard/src/Console/Command/InstallCommand.php b/src/SonsOfPHP/Bard/src/Console/Command/InstallCommand.php index a0cd8b18..87ba16ba 100644 --- a/src/SonsOfPHP/Bard/src/Console/Command/InstallCommand.php +++ b/src/SonsOfPHP/Bard/src/Console/Command/InstallCommand.php @@ -22,8 +22,6 @@ protected function configure(): void ; } - protected function initialize(InputInterface $input, OutputInterface $output): void {} - protected function execute(InputInterface $input, OutputInterface $output): int { $bardJsonFile = new JsonFile($input->getOption('working-dir') . '/bard.json'); @@ -38,7 +36,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int '--working-dir', $pkg['path'], ]); - $this->getHelper('process')->run($output, $process); + $this->getProcessHelper()->run($output, $process); } return self::SUCCESS; diff --git a/src/SonsOfPHP/Bard/src/Console/Command/MergeCommand.php b/src/SonsOfPHP/Bard/src/Console/Command/MergeCommand.php index fbda5c42..e8674120 100644 --- a/src/SonsOfPHP/Bard/src/Console/Command/MergeCommand.php +++ b/src/SonsOfPHP/Bard/src/Console/Command/MergeCommand.php @@ -17,7 +17,6 @@ use SonsOfPHP\Bard\Worker\File\Composer\Root\UpdateReplaceSection; use SonsOfPHP\Bard\Worker\File\Composer\Root\UpdateRequireDevSection; use SonsOfPHP\Bard\Worker\File\Composer\Root\UpdateRequireSection; -use Symfony\Component\Console\Helper\HelperInterface; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -31,12 +30,10 @@ */ final class MergeCommand extends AbstractCommand { - private JsonFile $bardConfig; + protected JsonFile $bardConfig; private string $mainComposerFile; - private ?HelperInterface $formatter = null; - protected function configure(): void { $this @@ -67,8 +64,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int $io = new SymfonyStyle($input, $output); $isDryRun = $input->getOption('dry-run'); - $this->formatter = $this->getHelper('formatter'); - $rootComposerJsonFile = new JsonFile($input->getOption('working-dir') . '/composer.json'); // Clean out a few of the sections in root composer.json file @@ -82,7 +77,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int foreach ($this->bardConfig->getSection('packages') as $pkg) { $pkgComposerFile = realpath($input->getOption('working-dir') . '/' . $pkg['path'] . '/composer.json'); if (!file_exists($pkgComposerFile)) { - $output->writeln(sprintf('No "%s" found, skipping', $packageComposerFile)); + $output->writeln(sprintf('No "%s" found, skipping', $pkgComposerFile)); continue; } @@ -92,7 +87,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int continue; } - $output->writeln($this->formatter->formatSection('bard', sprintf('Merging "%s" into root composer.json', $pkgComposerJsonFile->getSection('name')))); + $output->writeln($this->getFormatterHelper()->formatSection('bard', sprintf('Merging "%s" into root composer.json', $pkgComposerJsonFile->getSection('name')))); // Update root composer.json $rootComposerJsonFile = $rootComposerJsonFile->with(new UpdateReplaceSection($pkgComposerJsonFile)); diff --git a/src/SonsOfPHP/Bard/src/Console/Command/ReleaseCommand.php b/src/SonsOfPHP/Bard/src/Console/Command/ReleaseCommand.php index 190d40bc..b92e9fef 100644 --- a/src/SonsOfPHP/Bard/src/Console/Command/ReleaseCommand.php +++ b/src/SonsOfPHP/Bard/src/Console/Command/ReleaseCommand.php @@ -6,7 +6,7 @@ use RuntimeException; use SonsOfPHP\Bard\JsonFile; -use SonsOfPHP\Bard\Worker\File\Bard\UpdateVersion; +use SonsOfPHP\Bard\Worker\File\Bard\UpdateVersionWorker; use SonsOfPHP\Bard\Worker\File\Composer\Package\BranchAlias; use SonsOfPHP\Bard\Worker\File\Composer\Root\UpdateReplaceSection; use SonsOfPHP\Component\Version\Version; @@ -26,7 +26,7 @@ */ final class ReleaseCommand extends AbstractCommand { - private JsonFile $bardConfig; + protected JsonFile $bardConfig; private VersionInterface|null $releaseVersion = null; @@ -45,16 +45,16 @@ protected function configure(): void ->addOption('branch', null, InputOption::VALUE_REQUIRED, 'What branch we working with?', 'main') ->addArgument('release', InputArgument::REQUIRED, 'Next Release you want to start? Use format ..-+ or "major", "minor", "patch"') ->setHelp( - <<<'EOT' - This command allows you to create a new release and will update the various - repos that have been configured. The current version can be found in the - `bard.json` file. This will will update the version based on the type of release - that you are doing. + <<<'HELP' +This command allows you to create a new release and will update the various +repos that have been configured. The current version can be found in the +`bard.json` file. This will will update the version based on the type of release +that you are doing. - %command.full_name% + %command.full_name% - Read more at https://docs.SonsOfPHP.com - EOT +Read more at https://docs.SonsOfPHP.com +HELP ); } @@ -145,7 +145,7 @@ private function pullLatestChanges(InputInterface $input, OutputInterface $outpu $process = new Process(['git', 'pull', 'origin', $input->getOption('branch')]); $this->io->text($process->getCommandLine()); if (!$this->isDryRun) { - $this->getHelper('process')->mustRun($output, $process, sprintf('There was and error running command: %s', $process->getCommandLine())); + $this->getProcessHelper()->mustRun($output, $process, sprintf('There was and error running command: %s', $process->getCommandLine())); } $this->io->success('Done'); @@ -156,7 +156,7 @@ private function updateReplace(InputInterface $input, OutputInterface $output): $this->io->section('updating root composer.json "replace" section with package information'); foreach ($this->bardConfig->getSection('packages') as $pkg) { $pkgComposerJsonFile = new JsonFile(realpath($input->getOption('working-dir') . '/' . $pkg['path'] . '/composer.json')); - $output->writeln($this->getHelper('formatter')->formatSection($pkgComposerJsonFile->getSection('name'), 'Updating root composer.json')); + $output->writeln($this->getFormatterHelper()->formatSection($pkgComposerJsonFile->getSection('name'), 'Updating root composer.json')); $this->rootComposerJsonFile = $this->rootComposerJsonFile->with(new UpdateReplaceSection($pkgComposerJsonFile)); } @@ -181,7 +181,7 @@ private function tagReleaseAndPushMonorepo(InputInterface $input, OutputInterfac $process = new Process($cmd); $this->io->text($process->getCommandLine()); if (!$this->isDryRun) { - $this->getHelper('process')->mustRun($output, $process, sprintf('There was and error running command: %s', $process->getCommandLine())); + $this->getProcessHelper()->mustRun($output, $process, sprintf('There was and error running command: %s', $process->getCommandLine())); } } @@ -194,7 +194,7 @@ private function tagReleaseAndPushPackages(InputInterface $input, OutputInterfac foreach ($this->bardConfig->getSection('packages') as $pkg) { $pkgComposerJsonFile = new JsonFile(realpath($input->getOption('working-dir') . '/' . $pkg['path'] . '/composer.json')); $pkgName = $pkgComposerJsonFile->getSection('name'); - $output->writeln($this->getHelper('formatter')->formatSection($pkgName, 'Releasing...')); + $output->writeln($this->getFormatterHelper()->formatSection($pkgName, 'Releasing...')); $processCommands = [ ['git', 'subtree', 'split', '-P', $pkg['path'], '-b', $pkgName], ['git', 'checkout', $pkgName], @@ -209,11 +209,11 @@ private function tagReleaseAndPushPackages(InputInterface $input, OutputInterfac $process = new Process($cmd); $this->io->text($process->getCommandLine()); if (!$this->isDryRun) { - $this->getHelper('process')->mustRun($output, $process, sprintf('There was and error running command: %s', $process->getCommandLine())); + $this->getProcessHelper()->mustRun($output, $process, sprintf('There was and error running command: %s', $process->getCommandLine())); } } - $output->writeln($this->getHelper('formatter')->formatSection($pkgName, '...Done')); + $output->writeln($this->getFormatterHelper()->formatSection($pkgName, '...Done')); $this->io->newLine(); } @@ -241,7 +241,7 @@ private function updateBranchAliasForPackages(InputInterface $input, OutputInter foreach ($this->bardConfig->getSection('packages') as $pkg) { $pkgComposerJsonFile = new JsonFile(realpath($input->getOption('working-dir') . '/' . $pkg['path'] . '/composer.json')); $pkgComposerJsonFile = $pkgComposerJsonFile->with(new BranchAlias($this->rootComposerJsonFile)); - $output->writeln($this->getHelper('formatter')->formatSection($pkgComposerJsonFile->getSection('name'), 'Updated branch alias to "' . $branchAlias . '"')); + $output->writeln($this->getFormatterHelper()->formatSection($pkgComposerJsonFile->getSection('name'), 'Updated branch alias to "' . $branchAlias . '"')); if (!$this->isDryRun) { $pkgComposerJsonFile->save(); } @@ -253,7 +253,7 @@ private function updateBranchAliasForPackages(InputInterface $input, OutputInter private function updateBardConfigVersion(): void { $this->io->section('Updating version in bard.json'); - $this->bardConfig = $this->bardConfig->with(new UpdateVersion($this->releaseVersion)); + $this->bardConfig = $this->bardConfig->with(new UpdateVersionWorker($this->releaseVersion)); if (!$this->isDryRun) { $this->bardConfig->save(); } @@ -273,7 +273,7 @@ private function commitAndPushNewChanges(InputInterface $input, OutputInterface $process = new Process($cmd); $this->io->text($process->getCommandLine()); if (!$this->isDryRun) { - $this->getHelper('process')->mustRun($output, $process, sprintf('There was and error running command: %s', $process->getCommandLine())); + $this->getProcessHelper()->mustRun($output, $process, sprintf('There was and error running command: %s', $process->getCommandLine())); } } diff --git a/src/SonsOfPHP/Bard/src/JsonFile.php b/src/SonsOfPHP/Bard/src/JsonFile.php index 911fe6fd..eca71c7b 100644 --- a/src/SonsOfPHP/Bard/src/JsonFile.php +++ b/src/SonsOfPHP/Bard/src/JsonFile.php @@ -9,24 +9,39 @@ * * @author Joshua Estes */ -final class JsonFile +final class JsonFile extends \SplFileInfo implements JsonFileInterface { private array $config = []; - public function __construct( - private string $filename, - ) { - $this->load(); - } + private bool $loaded = false; + /** + * Loads the json file so it can be processed. + */ private function load(): void { - $this->config = json_decode(file_get_contents($this->filename), true); + if ($this->loaded) { + return; + } + + // @todo Check file exists and is readable + + $this->config = json_decode(file_get_contents($this->getRealPath()), true); + $this->loaded = true; } - public function getFilename(): string + public function getConfig(): array { - return $this->filename; + $this->load(); + + return $this->config; + } + + public function setConfig(array $config): void + { + $this->load(); + + $this->config = $config; } /** @@ -36,20 +51,17 @@ public function getFilename(): string */ public function getSection(string $section): mixed { - if ([] === $this->config) { - $this->load(); - } + $this->load(); return $this->config[$section] ?? null; } /** + * @param array|int|string|null $value */ public function setSection(string $section, $value): self { - if ([] === $this->config) { - $this->load(); - } + $this->load(); $clone = clone $this; $clone->config[$section] = $value; @@ -59,6 +71,8 @@ public function setSection(string $section, $value): self public function toJson(): string { + $this->load(); + return json_encode($this->config, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); } @@ -72,6 +86,10 @@ public function with($operator): self public function save(): void { - file_put_contents($this->filename, $this->toJson()); + $this->load(); + + // @todo check file is writeable + + file_put_contents($this->getRealPath(), $this->toJson()); } } diff --git a/src/SonsOfPHP/Bard/src/JsonFileInterface.php b/src/SonsOfPHP/Bard/src/JsonFileInterface.php new file mode 100644 index 00000000..ff92b1ae --- /dev/null +++ b/src/SonsOfPHP/Bard/src/JsonFileInterface.php @@ -0,0 +1,28 @@ + + */ +interface JsonFileInterface +{ + /** + * @return string + */ + public function getFilename(); + + /** + * Grabs and returns a section from the JSON file + * + * @return array|int|string|null + */ + public function getSection(string $section): mixed; + + /** + * @param array|int|string|null $value + */ + public function setSection(string $section, $value): self; +} diff --git a/src/SonsOfPHP/Bard/src/Worker/File/Bard/AddPackageWorker.php b/src/SonsOfPHP/Bard/src/Worker/File/Bard/AddPackageWorker.php new file mode 100644 index 00000000..503abc2b --- /dev/null +++ b/src/SonsOfPHP/Bard/src/Worker/File/Bard/AddPackageWorker.php @@ -0,0 +1,40 @@ + + */ +final readonly class AddPackageWorker implements WorkerInterface +{ + public function __construct( + private array $config, + ) {} + + public function apply(JsonFileInterface $bardConfig): JsonFileInterface + { + if (null === $packages = $bardConfig->getSection('packages')) { + $packages = []; + } + + foreach ($packages as $pkg) { + if ($pkg['path'] === $this->config['path']) { + // @todo PackageAlreadyExistsException + throw new \Exception(sprintf( + 'Package already exists at path "%s" in "%s"', + $pkg['path'], + $bardConfig->getFilename(), + )); + } + } + + $packages[] = $this->config; + + return $bardConfig->setSection('packages', $packages); + } +} diff --git a/src/SonsOfPHP/Bard/src/Worker/File/Bard/UpdateVersion.php b/src/SonsOfPHP/Bard/src/Worker/File/Bard/UpdateVersionWorker.php similarity index 73% rename from src/SonsOfPHP/Bard/src/Worker/File/Bard/UpdateVersion.php rename to src/SonsOfPHP/Bard/src/Worker/File/Bard/UpdateVersionWorker.php index 1029353c..f54f52f0 100644 --- a/src/SonsOfPHP/Bard/src/Worker/File/Bard/UpdateVersion.php +++ b/src/SonsOfPHP/Bard/src/Worker/File/Bard/UpdateVersionWorker.php @@ -9,9 +9,14 @@ use SonsOfPHP\Component\Version\VersionInterface; /** + * Updates the version in bard.json + * + * Example: + * $jsonFile->with(new UpdateVersion($version)); + * * @author Joshua Estes */ -final readonly class UpdateVersion implements WorkerInterface +final readonly class UpdateVersionWorker implements WorkerInterface { public function __construct(private VersionInterface $version) {}