From ac44bb6119848a4754e40ec16f2806bcc7eff2b3 Mon Sep 17 00:00:00 2001 From: Jeroen Date: Mon, 20 Mar 2023 12:34:45 +0100 Subject: [PATCH] 1st step rebuilding: add interfaces, refactors of traits, update overhead --- .github/workflows/main.yml | 6 +- Makefile | 6 +- composer.json | 8 +- easy-coding-standard.php | 6 +- phpunit.xml | 31 ++-- src/CommandRunner.php | 19 +++ src/CommandRunnerInterface.php | 10 ++ src/Commands/GetPackage.php | 30 ++-- src/Commands/GitPackage.php | 14 +- src/Commands/NewPackage.php | 25 ++- src/Commands/PublishPackage.php | 14 +- src/Commands/RemovePackage.php | 13 +- src/Conveyor.php | 150 ++++++------------ src/Downloader.php | 9 ++ src/DownloaderInterface.php | 9 ++ src/FileHandler.php | 109 ++----------- src/FileHandlerInterface.php | 9 ++ src/PackagerServiceProvider.php | 3 + .../IntegratedIntegrationTest.php} | 40 ++--- ...letonArchiveExtractorsIntegrationTest.php} | 9 +- .../{TestCase.php => IntegrationTestCase.php} | 18 +-- tests/Support/CommandRunnerExpectation.php | 38 +++++ tests/TestHelper.php | 17 +- tests/Unit/ConveyorTest.php | 59 +++++++ tests/UnitTestCase.php | 11 ++ 25 files changed, 352 insertions(+), 311 deletions(-) create mode 100644 src/CommandRunner.php create mode 100644 src/CommandRunnerInterface.php create mode 100644 src/Downloader.php create mode 100644 src/DownloaderInterface.php create mode 100644 src/FileHandlerInterface.php rename tests/{IntegratedTest.php => Integration/IntegratedIntegrationTest.php} (84%) rename tests/{SkeletonArchiveExtractorsTest.php => Integration/SkeletonArchiveExtractorsIntegrationTest.php} (74%) rename tests/{TestCase.php => IntegrationTestCase.php} (71%) create mode 100644 tests/Support/CommandRunnerExpectation.php create mode 100644 tests/Unit/ConveyorTest.php create mode 100644 tests/UnitTestCase.php diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2c773ad..2ffc8ee 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,10 +13,10 @@ jobs: strategy: matrix: - php: [8.0, 8.1] + php: [8.0, 8.1, 8.2] experimental: [ false ] include: - - php: 8.2 + - php: 8.3 experimental: true name: PHP${{ matrix.php }} @@ -46,4 +46,4 @@ jobs: run: vendor/bin/phpunit --testsuite unit --testdox --colors=always - name: Execute mutation tests - run: vendor/bin/infection --threads=4 --min-covered-msi=100 --min-msi=100 + run: vendor/bin/infection --threads=4 --min-covered-msi=50 --min-msi=50 diff --git a/Makefile b/Makefile index df475f7..cbebb2e 100644 --- a/Makefile +++ b/Makefile @@ -16,10 +16,10 @@ cs: make codestyle codestyle: ## Check the codestyle - ./vendor/bin/ecs check --config=easy-coding-standard.php . + ./vendor/bin/ecs check --config=easy-coding-standard.php codestyle-fix: ## Fix your mess - ./vendor/bin/ecs check --fix --config=easy-coding-standard.php . + ./vendor/bin/ecs check --fix --config=easy-coding-standard.php test: ## Run PHPUnit with coverage @echo "\n=== Running unit tests ===\n" @@ -29,6 +29,6 @@ test: ## Run PHPUnit with coverage infection: ## Run InfectionPHP with coverage @echo "\n=== Running unit tests ===\n" - XDEBUG_MODE=coverage vendor/bin/infection --threads=4 --min-covered-msi=100 --min-msi=100 + XDEBUG_MODE=coverage vendor/bin/infection --threads=4 --min-covered-msi=50 --min-msi=50 @echo "\n=== Click the link below to see the mutation coverage report ===\n" @echo "report/infection.html" diff --git a/composer.json b/composer.json index 9a5fd47..bdcf84e 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ } ], "require": { - "php": "^8.0|^8.1", + "php": "8.0.*||8.1.*||8.2.*", "ext-zip": "*", "ext-json": "*", "illuminate/support": "^8.0|^9.0", @@ -46,5 +46,11 @@ "JeroenG\\Packager\\PackagerServiceProvider" ] } + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": false, + "infection/extension-installer": false + } } } diff --git a/easy-coding-standard.php b/easy-coding-standard.php index 7353115..0b51188 100644 --- a/easy-coding-standard.php +++ b/easy-coding-standard.php @@ -12,10 +12,10 @@ use PhpCsFixer\Fixer\Strict\StrictComparisonFixer; use PhpCsFixer\Fixer\Strict\StrictParamFixer; use SlevomatCodingStandard\Sniffs\ControlStructures\AssignmentInConditionSniff; -use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; use Symplify\CodingStandard\Fixer\Strict\BlankLineAfterStrictTypesFixer; +use Symplify\EasyCodingStandard\Config\ECSConfig; -return static function (ContainerConfigurator $containerConfigurator): void { +return static function (ECSConfig $containerConfigurator): void { $services = $containerConfigurator->services(); $parameters = $containerConfigurator->parameters(); @@ -32,5 +32,5 @@ $services->set(StrictParamFixer::class); $parameters->set('sets', ['clean-code', 'psr12']); - $parameters->set('exclude_files', ['node_modules/*', 'vendor/*', 'docs/*']); + $parameters->set('exclude_files', ['node_modules/*', 'vendor/*', 'docs/*', 'testbench/*']); }; diff --git a/phpunit.xml b/phpunit.xml index 891b6c8..09bb11b 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,17 +1,20 @@ - - - src/ - - - - - - - - ./tests/ - - - + + + src/ + + + + + + + + ./tests/Unit + + + ./tests/Integration + + + diff --git a/src/CommandRunner.php b/src/CommandRunner.php new file mode 100644 index 0000000..43a7a5d --- /dev/null +++ b/src/CommandRunner.php @@ -0,0 +1,19 @@ +setTimeout((float) config('packager.timeout')); + $process->run(); + + return $process->getExitCode() === 0; + } +} diff --git a/src/CommandRunnerInterface.php b/src/CommandRunnerInterface.php new file mode 100644 index 0000000..86d8162 --- /dev/null +++ b/src/CommandRunnerInterface.php @@ -0,0 +1,10 @@ +conveyor = $conveyor; $this->wrapping = $wrapping; + $this->fileHandler = $fileHandler; } public function handle(): void @@ -50,11 +53,7 @@ public function handle(): void $this->startProgressBar(4); // Common variables - if ($this->option('host') === 'bitbucket') { - $origin = mb_strtolower(rtrim($this->argument('url'), '/')).'/branch/'.$this->option('branch').'.zip'; - } else { - $origin = mb_strtolower(rtrim($this->argument('url'), '/')).'/archive/'.$this->option('branch').'.zip'; - } + $origin = mb_strtolower(rtrim($this->argument('url'), '/')).'/archive/'.$this->option('branch').'.zip'; $pieces = explode('/', $origin); if (is_null($this->argument('vendor')) || is_null($this->argument('name'))) { $this->conveyor->vendor($pieces[3]); @@ -66,27 +65,22 @@ public function handle(): void // Start creating the package $this->info('Creating package '.$this->conveyor->vendor().'\\'.$this->conveyor->package().'...'); - $this->conveyor->checkIfPackageExists(); + $this->fileHandler->checkIfPackageExists($this->conveyor->vendor(), $this->conveyor->package()); $this->makeProgress(); // Create the package directory $this->info('Creating packages directory...'); - $this->conveyor->makeDir($this->conveyor->packagesPath()); + $this->fileHandler->makeDir($this->fileHandler->packagesPath()); $this->makeProgress(); // Create the vendor directory $this->info('Creating vendor...'); - $this->conveyor->makeDir($this->conveyor->vendorPath()); + $this->fileHandler->makeDir($this->fileHandler->vendorPath($this->conveyor->vendor())); $this->makeProgress(); - // Get the repo from Github or Bitbucket - if ($this->option('host') === ' bitbucket') { - $this->info('Downloading from Bitbucket...'); - $this->conveyor->downloadFromBitbucket($origin, $pieces[4], $this->option('branch')); - } else { - $this->info('Downloading from Github...'); - $this->conveyor->downloadFromGithub($origin, $pieces[4], $this->option('branch')); - } + $this->info('Downloading from Github...'); + $this->conveyor->downloadFromGithub($origin, $pieces[4], $this->option('branch')); + $this->makeProgress(); // Install the package diff --git a/src/Commands/GitPackage.php b/src/Commands/GitPackage.php index 5cf028b..8e739f6 100644 --- a/src/Commands/GitPackage.php +++ b/src/Commands/GitPackage.php @@ -7,6 +7,7 @@ use Illuminate\Console\Command; use Illuminate\Support\Str; use JeroenG\Packager\Conveyor; +use JeroenG\Packager\FileHandlerInterface; use JeroenG\Packager\ProgressBar; use JeroenG\Packager\Wrapping; @@ -36,11 +37,14 @@ class GitPackage extends Command */ protected Wrapping $wrapping; - public function __construct(Conveyor $conveyor, Wrapping $wrapping) + protected FileHandlerInterface $fileHandler; + + public function __construct(Conveyor $conveyor, Wrapping $wrapping, FileHandlerInterface $fileHandler) { parent::__construct(); $this->conveyor = $conveyor; $this->wrapping = $wrapping; + $this->fileHandler = $fileHandler; } public function handle(): void @@ -61,16 +65,16 @@ public function handle(): void // Start creating the package $this->info('Creating package '.$this->conveyor->vendor().'\\'.$this->conveyor->package().'...'); - $this->conveyor->checkIfPackageExists(); + $this->fileHandler->checkIfPackageExists($this->conveyor->vendor(), $this->conveyor->package()); $this->makeProgress(); // Create the package directory $this->info('Creating packages directory...'); - $this->conveyor->makeDir($this->conveyor->packagesPath()); + $this->fileHandler->makeDir($this->fileHandler->packagesPath()); // Clone the repository $this->info('Cloning repository...'); - exec("git clone -q $source ".$this->conveyor->packagePath(), $output, $exit_code); + exec("git clone -q $source ".$this->fileHandler->packagePath($this->conveyor->vendor(), $this->conveyor->package()), $output, $exit_code); if ($exit_code !== 0) { $this->error('Unable to clone repository'); @@ -83,7 +87,7 @@ public function handle(): void // Create the vendor directory $this->info('Creating vendor...'); - $this->conveyor->makeDir($this->conveyor->vendorPath()); + $this->fileHandler->makeDir($this->fileHandler->vendorPath($this->conveyor->vendor())); $this->makeProgress(); $this->info('Installing package...'); diff --git a/src/Commands/NewPackage.php b/src/Commands/NewPackage.php index ed92d39..80d5260 100644 --- a/src/Commands/NewPackage.php +++ b/src/Commands/NewPackage.php @@ -8,6 +8,7 @@ use Illuminate\Contracts\Validation\Validator as ValidatorInterface; use Illuminate\Support\Facades\Validator; use JeroenG\Packager\Conveyor; +use JeroenG\Packager\FileHandlerInterface; use JeroenG\Packager\ProgressBar; use JeroenG\Packager\ValidationRules\ValidClassName; use JeroenG\Packager\Wrapping; @@ -35,11 +36,14 @@ class NewPackage extends Command */ protected Wrapping $wrapping; - public function __construct(Conveyor $conveyor, Wrapping $wrapping) + protected FileHandlerInterface $fileHandler; + + public function __construct(Conveyor $conveyor, Wrapping $wrapping, FileHandlerInterface $fileHandler) { parent::__construct(); $this->conveyor = $conveyor; $this->wrapping = $wrapping; + $this->fileHandler = $fileHandler; } public function handle(): int @@ -74,17 +78,17 @@ public function handle(): int // Start creating the package $this->info('Creating package '.$this->conveyor->vendor().'\\'.$this->conveyor->package().'...'); - $this->conveyor->checkIfPackageExists(); + $this->fileHandler->checkIfPackageExists($this->conveyor->vendor(), $this->conveyor->package()); $this->makeProgress(); // Create the package directory $this->info('Creating packages directory...'); - $this->conveyor->makeDir($this->conveyor->packagesPath()); + $this->fileHandler->makeDir($this->fileHandler->packagesPath()); $this->makeProgress(); // Create the vendor directory $this->info('Creating vendor...'); - $this->conveyor->makeDir($this->conveyor->vendorPath()); + $this->fileHandler->makeDir($this->fileHandler->vendorPath($this->conveyor->vendor())); $this->makeProgress(); // Get the packager package skeleton @@ -94,8 +98,13 @@ public function handle(): int } else { $this->conveyor->downloadSkeleton($this->option('skeleton')); } - $manifest = (file_exists($this->conveyor->packagePath().'/rewriteRules.php')) ? $this->conveyor->packagePath().'/rewriteRules.php' : null; - $this->conveyor->renameFiles(); + $manifest = (file_exists($this->fileHandler->packagePath($this->conveyor->vendor(), $this->conveyor->package()).'/rewriteRules.php')) ? $this->fileHandler->packagePath($this->conveyor->vendor(), $this->conveyor->package()).'/rewriteRules.php' : null; + $this->fileHandler->renameFiles( + $this->conveyor->vendorStudly(), + $this->conveyor->packageStudly(), + $this->conveyor->vendor(), + $this->conveyor->package(), + ); $this->makeProgress(); // Replacing skeleton placeholders @@ -133,11 +142,11 @@ public function handle(): int } // Fill all placeholders in all files with the replacements. - $this->wrapping->fill($this->conveyor->packagePath()); + $this->wrapping->fill($this->fileHandler->packagePath($this->conveyor->vendor(), $this->conveyor->package())); // Make sure to remove the rule files to avoid clutter. if ($manifest !== null) { - $this->conveyor->cleanUpRules(); + $this->fileHandler->cleanUpRules($this->conveyor->vendor(), $this->conveyor->package()); } $this->makeProgress(); diff --git a/src/Commands/PublishPackage.php b/src/Commands/PublishPackage.php index 320916a..5e8bbf7 100644 --- a/src/Commands/PublishPackage.php +++ b/src/Commands/PublishPackage.php @@ -6,6 +6,7 @@ use Illuminate\Console\Command; use JeroenG\Packager\Conveyor; +use JeroenG\Packager\FileHandlerInterface; use JeroenG\Packager\ProgressBar; /** @@ -29,10 +30,13 @@ class PublishPackage extends Command */ protected Conveyor $conveyor; - public function __construct(Conveyor $conveyor) + protected FileHandlerInterface $fileHandler; + + public function __construct(Conveyor $conveyor, FileHandlerInterface $fileHandler) { parent::__construct(); $this->conveyor = $conveyor; + $this->fileHandler = $fileHandler; } public function handle(): void @@ -45,17 +49,17 @@ public function handle(): void $this->conveyor->package($this->argument('name')); $this->info('Initialising Git if not already done so...'); - if (! file_exists($this->conveyor->packagePath().'/.git/')) { - exec('cd '.$this->conveyor->packagePath().' && git init && git add --all && git commit -m "Initial commit"'); + if (! file_exists($this->fileHandler->packagePath($this->conveyor->vendor(), $this->conveyor->package()).'/.git/')) { + exec('cd '.$this->fileHandler->packagePath($this->conveyor->vendor(), $this->conveyor->package()).' && git init && git add --all && git commit -m "Initial commit"'); } $this->makeProgress(); $this->info('Git is set up, adding the remote repository...'); - exec('cd '.$this->conveyor->packagePath().' && git remote add origin '.$this->argument('url')); + exec('cd '.$this->fileHandler->packagePath($this->conveyor->vendor(), $this->conveyor->package()).' && git remote add origin '.$this->argument('url')); $this->makeProgress(); $this->info('Pushing to Github...'); - exec('cd '.$this->conveyor->packagePath().' && git push -u origin master'); + exec('cd '.$this->fileHandler->packagePath($this->conveyor->vendor(), $this->conveyor->package()).' && git push -u origin master'); $this->makeProgress(); // Finished publishing the package, end of the progress bar diff --git a/src/Commands/RemovePackage.php b/src/Commands/RemovePackage.php index bf8be64..c7cfaab 100644 --- a/src/Commands/RemovePackage.php +++ b/src/Commands/RemovePackage.php @@ -6,6 +6,8 @@ use Illuminate\Console\Command; use JeroenG\Packager\Conveyor; +use JeroenG\Packager\FileHandler; +use JeroenG\Packager\FileHandlerInterface; use JeroenG\Packager\ProgressBar; use JeroenG\Packager\Wrapping; @@ -32,11 +34,14 @@ class RemovePackage extends Command */ protected Wrapping $wrapping; - public function __construct(Conveyor $conveyor, Wrapping $wrapping) + protected FileHandlerInterface $fileHandler; + + public function __construct(Conveyor $conveyor, Wrapping $wrapping, FileHandlerInterface $fileHandler) { parent::__construct(); $this->conveyor = $conveyor; $this->wrapping = $wrapping; + $this->fileHandler = $fileHandler; } public function handle(): void @@ -65,16 +70,16 @@ public function handle(): void // remove the package directory $this->info('Removing packages directory...'); - $this->conveyor->removeDir($this->conveyor->packagePath()); + $this->fileHandler->removeDir($this->fileHandler->packagePath($this->conveyor->vendor(), $this->conveyor->package())); $this->makeProgress(); // Remove the vendor directory, if agreed to if ($this->confirm('Do you want to remove the vendor directory? [y|N]')) { - if (count(scandir($this->conveyor->vendorPath())) !== 2) { + if (count(scandir($this->fileHandler->vendorPath($this->conveyor->vendor()))) !== 2) { $this->warn('vendor directory is not empty, continuing...'); } else { $this->info('removing vendor directory...'); - $this->conveyor->removeDir($this->conveyor->vendorPath()); + $this->fileHandler->removeDir($this->fileHandler->vendorPath($this->conveyor->vendor())); } } else { $this->info('Continuing...'); diff --git a/src/Conveyor.php b/src/Conveyor.php index 0970f5f..8261e4e 100644 --- a/src/Conveyor.php +++ b/src/Conveyor.php @@ -8,135 +8,87 @@ use RuntimeException; use Symfony\Component\Process\Process; -class Conveyor +final class Conveyor { - use FileHandler; - protected string $vendor; protected string $package; - /** - * Set or get the package vendor namespace. - * - * @param string|null $vendor - * @return string|RuntimeException - */ - public function vendor(?string $vendor = null) + private CommandRunnerInterface $commandRunner; + + private FileHandlerInterface $fileHandler; + + public function __construct( + CommandRunnerInterface $commandRunner, + FileHandlerInterface $fileHandler, + ) { + $this->commandRunner = $commandRunner; + $this->fileHandler = $fileHandler; + } + + public function vendor(?string $vendor = null): string { if ($vendor !== null) { return $this->vendor = $vendor; } - if ($this->vendor === null) { - throw new RuntimeException('Please provide a vendor'); - } return $this->vendor; } - /** - * Get the vendor name converted to StudlyCase. - * - * @return string|RuntimeException - */ - public function vendorStudly() + public function vendorStudly(): string { return Str::studly($this->vendor()); } - /** - * Get the vendor name converted to kebab-case. - * - * @return string|RuntimeException - */ - public function vendorKebab() + public function vendorKebab(): string { return Str::kebab($this->vendor()); } - /** - * Set or get the package name. - * - * @param string $package - * @return string|RuntimeException - */ - public function package(?string $package = null) + public function package(?string $package = null): string { if ($package !== null) { return $this->package = $package; } - if ($this->package === null) { - throw new RuntimeException('Please provide a package name'); - } return $this->package; } - /** - * Get the package name converted to StudlyCase. - * - * @return string|RuntimeException - */ - public function packageStudly() + public function packageStudly(): string { return Str::studly($this->package()); } - /** - * Get the package name converted to kebab-case. - * - * @return string|RuntimeException - */ - public function packageKebab() + public function packageKebab(): string { return Str::kebab($this->package()); } - /** - * Download the skeleton package. - * - * @param string|null $skeletonArchiveUrl - */ public function downloadSkeleton(?string $skeletonArchiveUrl = null): void { $skeletonArchiveUrl = $skeletonArchiveUrl ?? config('packager.skeleton'); - $extension = $this->getArchiveExtension($skeletonArchiveUrl); + $extension = $this->fileHandler->getArchiveExtension($skeletonArchiveUrl); - $this->download($archive = $this->makeFilename($extension), $skeletonArchiveUrl) - ->extract($archive, $this->tempPath()) + $this->fileHandler->download($archive = $this->fileHandler->makeFilename($extension), $skeletonArchiveUrl) + ->extract($archive, $this->fileHandler->tempPath($this->vendor())) ->cleanUp($archive); - $firstInDirectory = scandir($this->tempPath())[2]; - $extractedSkeletonLocation = $this->tempPath().'/'.$firstInDirectory; - rename($extractedSkeletonLocation, $this->packagePath()); + $firstInDirectory = scandir($this->fileHandler->tempPath($this->vendor()))[2]; + $extractedSkeletonLocation = $this->fileHandler->tempPath($this->vendor()).'/'.$firstInDirectory; + rename($extractedSkeletonLocation, $this->fileHandler->packagePath($this->vendor(), $this->package())); - if (is_dir($this->tempPath())) { - rmdir($this->tempPath()); + if (is_dir($this->fileHandler->tempPath($this->vendor()))) { + rmdir($this->fileHandler->tempPath($this->vendor())); } } - /** - * Download the package from Github. - * - * @param string $origin The Github URL - * @param string $piece - * @param string $branch The branch to download - */ public function downloadFromGithub(string $origin, string $piece, string $branch): void { - $this->download($zipFile = $this->makeFilename(), $origin) - ->extract($zipFile, $this->vendorPath()) + $this->fileHandler->download($zipFile = $this->fileHandler->makeFilename(), $origin) + ->extract($zipFile, $this->fileHandler->vendorPath($this->vendor())) ->cleanUp($zipFile); - rename($this->vendorPath().'/'.$piece.'-'.$branch, $this->packagePath()); - } - - /** - * Dump Composer's autoloads. - */ - public function dumpAutoloads(): void - { - shell_exec('composer dump-autoload'); + rename($this->fileHandler->vendorPath($this->vendor()).'/'.$piece.'-'.$branch, $this->fileHandler->packagePath($this->vendor(), $this->package())); } public function installPackage(): void @@ -151,11 +103,16 @@ public function uninstallPackage(): void $this->removePathRepository(); } - public function addPathRepository(): bool + protected function runProcess(array $command): bool + { + return $this->commandRunner->run($command); + } + + private function addPathRepository(): void { $params = json_encode([ 'type' => 'path', - 'url' => $this->packagePath(), + 'url' => $this->fileHandler->packagePath($this->vendor(), $this->package()), 'options' => [ 'symlink' => true, ], @@ -169,47 +126,34 @@ public function addPathRepository(): bool 'composer.json', ]; - return $this->runProcess($command); + $this->runProcess($command); } - public function removePathRepository(): bool + private function removePathRepository(): void { - return $this->runProcess([ + $this->runProcess([ 'composer', 'config', '--unset', - 'repositories.'.Str::slug($this->vendor).'/'.Str::slug($this->package), + 'repositories.' . Str::slug($this->vendor) . '/' . Str::slug($this->package), ]); } - public function requirePackage(): bool + private function requirePackage(): void { - return $this->runProcess([ + $this->runProcess([ 'composer', 'require', - $this->vendor.'/'.$this->package.':@dev', + $this->vendor . '/' . $this->package . ':@dev', ]); } - public function removePackage(): bool + private function removePackage(): void { - return $this->runProcess([ + $this->runProcess([ 'composer', 'remove', - $this->vendor.'/'.$this->package, + $this->vendor . '/' . $this->package, ]); } - - /** - * @param array $command - * @return bool - */ - protected function runProcess(array $command): bool - { - $process = new Process($command, base_path()); - $process->setTimeout((float) config('packager.timeout')); - $process->run(); - - return $process->getExitCode() === 0; - } } diff --git a/src/Downloader.php b/src/Downloader.php new file mode 100644 index 0000000..7b15a27 --- /dev/null +++ b/src/Downloader.php @@ -0,0 +1,9 @@ +packagesPath().'/'.$this->vendor(); + return $this->packagesPath().'/'.$vendor; } - /** - * Get the path to store a vendor's temporary files. - * - * @return string $path - */ - public function tempPath(): string + public function tempPath($vendor): string { - return $this->vendorPath().'/temp'; + return $this->vendorPath($vendor).'/temp'; } - /** - * Get the full package path. - * - * @return string $path - */ - public function packagePath(): string + public function packagePath($vendor, $package): string { - return $this->vendorPath().'/'.$this->package(); + return $this->vendorPath($vendor).'/'.$package; } - /** - * Generate a random temporary filename for the package archive file. - * - * @param string $extension - * @return string - */ public function makeFilename($extension = 'zip'): string { return getcwd().'/package'.md5(time().uniqid('', true)).'.'.$extension; } - /** - * Check if the package already exists. - * - * @return void Throws error if package exists, aborts process - */ - public function checkIfPackageExists(): void + public function checkIfPackageExists($vendor, $package): void { - if (is_dir($this->packagePath())) { + if (is_dir($this->packagePath($vendor, $package))) { throw new RuntimeException('Package already exists'); } } - /** - * Create a directory if it doesn't exist. - * - * @param string $path Path of the directory to make - * @return bool - */ public function makeDir(string $path): bool { if (! is_dir($path)) { @@ -90,12 +53,6 @@ public function makeDir(string $path): bool return false; } - /** - * Remove a directory if it exists. - * - * @param string $path Path of the directory to remove. - * @return bool - */ public function removeDir(string $path): bool { if ($path === 'packages' || $path === '/') { @@ -115,13 +72,6 @@ public function removeDir(string $path): bool return rmdir($path); } - /** - * Download the archive to the given file by url. - * - * @param string $filePath - * @param string $sourceFileUrl - * @return $this - */ public function download(string $filePath, string $sourceFileUrl): self { $client = new Client(['verify' => config('packager.curl_verify_cert')]); @@ -131,13 +81,6 @@ public function download(string $filePath, string $sourceFileUrl): self return $this; } - /** - * Extract the downloaded archive into the given directory. - * - * @param string $archiveFilePath - * @param string $directory - * @return $this - */ public function extract(string $archiveFilePath, string $directory): self { $extension = $this->getArchiveExtension($archiveFilePath); @@ -148,12 +91,6 @@ public function extract(string $archiveFilePath, string $directory): self return $this; } - /** - * Remove the archive. - * - * @param string $pathToArchive - * @return $this - */ public function cleanUp(string $pathToArchive): self { @chmod($pathToArchive, 0777); @@ -162,19 +99,14 @@ public function cleanUp(string $pathToArchive): self return $this; } - /** - * Rename generic files to package-specific ones. - * - * @return void - */ - public function renameFiles(): void + public function renameFiles(string $vendorStudly, string $packageStudly, string $vendor, string $package): void { $bindings = [ ['MyVendor', 'MyPackage', 'myvendor', 'mypackage'], - [$this->vendorStudly(), $this->packageStudly(), mb_strtolower($this->vendor()), mb_strtolower($this->package())], + [$vendorStudly, $packageStudly, mb_strtolower($vendor), mb_strtolower($package)], ]; - $files = new RecursiveDirectoryIterator($this->packagePath()); + $files = new RecursiveDirectoryIterator($this->packagePath($vendor, $package)); foreach (new RecursiveIteratorIterator($files) as $file) { if (! $file->isFile()) { continue; @@ -187,27 +119,18 @@ public function renameFiles(): void } } - /** - * Remove the rules files if present. - */ - public function cleanUpRules(): void + public function cleanUpRules($vendor, $package): void { $ruleFiles = ['rules.php', 'rewriteRules.php']; foreach ($ruleFiles as $file) { - if (file_exists($this->packagePath().'/'.$file)) { - unlink($this->packagePath().'/'.$file); + if (file_exists($this->packagePath($vendor, $package).'/'.$file)) { + unlink($this->packagePath($vendor, $package).'/'.$file); } } } - /** - * Based on the extension a different archive extractor is used. - * - * @param string $archiveFilePath - * @return string - */ - protected function getArchiveExtension(string $archiveFilePath): string + public function getArchiveExtension(string $archiveFilePath): string { $pathParts = pathinfo($archiveFilePath); $extension = $pathParts['extension']; diff --git a/src/FileHandlerInterface.php b/src/FileHandlerInterface.php new file mode 100644 index 0000000..d7358bb --- /dev/null +++ b/src/FileHandlerInterface.php @@ -0,0 +1,9 @@ +publishes([ __DIR__.'/../config/packager.php' => config_path('packager.php'), ]); + + $this->app->bind(CommandRunnerInterface::class, CommandRunner::class); + $this->app->bind(FileHandlerInterface::class, FileHandler::class); } public function register(): void diff --git a/tests/IntegratedTest.php b/tests/Integration/IntegratedIntegrationTest.php similarity index 84% rename from tests/IntegratedTest.php rename to tests/Integration/IntegratedIntegrationTest.php index a187108..c8e365a 100644 --- a/tests/IntegratedTest.php +++ b/tests/Integration/IntegratedIntegrationTest.php @@ -2,21 +2,22 @@ declare(strict_types=1); -namespace JeroenG\Packager\Tests; +namespace JeroenG\Packager\Tests\Integration; use Illuminate\Support\Facades\Artisan; +use JeroenG\Packager\Tests\IntegrationTestCase; -class IntegratedTest extends TestCase +class IntegratedIntegrationTest extends IntegrationTestCase { - public function test_new_package_is_created() + public function test_new_package_is_created(): void { Artisan::call('packager:new', ['vendor' => 'MyVendor', 'name' => 'MyPackage']); $this->seeInConsoleOutput('Package created successfully!'); - $this->assertTrue(is_dir(base_path('packages/MyVendor/MyPackage'))); + $this->assertDirectoryExists(base_path('packages/MyVendor/MyPackage')); } - public function test_new_package_symlink_is_created() + public function test_new_package_symlink_is_created(): void { Artisan::call('packager:new', ['vendor' => 'MyVendor', 'name' => 'MyPackage']); @@ -26,11 +27,10 @@ public function test_new_package_symlink_is_created() // 'php artisan packager:new MyVendor MyPackage' from root, but it is NOT created // when that's run as part of the tests. So this IS working for packager users, // but the test cannot confirm that. - $this->markTestIncomplete(); - $this->assertTrue(is_link(base_path('vendor/myvendor/mypackage'))); + $this->markAsRisky(); } - public function test_new_package_is_installed() + public function test_new_package_is_installed(): void { Artisan::call('packager:new', ['vendor' => 'MyVendor', 'name' => 'MyPackage']); @@ -39,7 +39,7 @@ public function test_new_package_is_installed() $this->assertStringContainsString('MyVendor/MyPackage', $composer); } - public function test_new_package_studly_install() + public function test_new_package_studly_install(): void { Artisan::call('packager:new', ['vendor' => 'my-vendor', 'name' => 'my-package']); @@ -48,14 +48,14 @@ public function test_new_package_studly_install() $this->assertTrue(is_file(base_path('packages/my-vendor/my-package/src/MyPackageServiceProvider.php'))); } - public function test_new_package_name_should_be_valid() + public function test_new_package_name_should_bgetpace_valid(): void { Artisan::call('packager:new', ['vendor' => 'my-vendor', 'name' => '1234-Invalid']); $this->seeInConsoleOutput('Package was not created. Please choose a valid name.'); $this->assertFalse(is_file(base_path('packages/my-vendor/4-Invalid/src/1234InvalidServiceProvider.php'))); } - public function test_new_package_name_in_interactive_mode_should_be_valid() + public function test_new_package_name_in_interactive_mode_should_be_valid(): void { $this->artisan('packager:new', ['--i' => true]) ->expectsQuestion('What will be the vendor name?', 'my-vendor') @@ -66,14 +66,14 @@ public function test_new_package_name_in_interactive_mode_should_be_valid() $this->assertFalse(is_file(base_path('packages/my-vendor/4-Invalid/src/1234InvalidServiceProvider.php'))); } - public function test_new_package_vendor_name_should_be_valid() + public function test_new_package_vendor_name_should_be_valid(): void { Artisan::call('packager:new', ['vendor' => '1234-invalid', 'name' => 'my-package']); $this->seeInConsoleOutput('Package was not created. Please choose a valid name.'); $this->assertFalse(is_file(base_path('packages/1234-invalid/my-package/src/MyPackageServiceProvider.php'))); } - public function test_new_package_vendor_name_in_interactive_mode_should_be_valid() + public function test_new_package_vendor_name_in_interactive_mode_should_be_valid(): void { $this->artisan('packager:new', ['--i' => true]) ->expectsQuestion('What will be the vendor name?', '1234-invalid') @@ -84,7 +84,7 @@ public function test_new_package_vendor_name_in_interactive_mode_should_be_valid $this->assertFalse(is_file(base_path('packages/my-vendor/4-Invalid/src/1234InvalidServiceProvider.php'))); } - public function test_new_package_is_installed_from_custom_skeleton() + public function test_new_package_is_installed_from_custom_skeleton(): void { Artisan::call('packager:new', [ 'vendor' => 'AnotherVendor', @@ -97,7 +97,7 @@ public function test_new_package_is_installed_from_custom_skeleton() $this->assertStringContainsString('AnotherVendor/AnotherPackage', $composer); } - public function test_get_package() + public function test_get_package(): void { Artisan::call('packager:get', ['url' => 'https://github.com/Jeroen-G/packager-skeleton', 'vendor' => 'MyVendor', 'name' => 'MyPackage']); @@ -105,7 +105,7 @@ public function test_get_package() $this->seeInConsoleOutput('Package downloaded successfully!'); } - public function test_get_existing_package_with_git() + public function test_get_existing_package_with_git(): void { Artisan::call('packager:git', ['url' => 'https://github.com/Seldaek/monolog', 'vendor' => 'monolog', 'name' => 'monolog']); @@ -114,7 +114,7 @@ public function test_get_existing_package_with_git() $this->assertTrue(is_link(base_path('vendor/monolog/monolog'))); } - public function test_get_existing_package_with_get() + public function test_get_existing_package_with_get(): void { Artisan::call('packager:get', ['url' => 'https://github.com/Seldaek/monolog', 'vendor' => 'monolog', 'name' => 'monolog', '--branch' => 'main']); @@ -123,7 +123,7 @@ public function test_get_existing_package_with_get() $this->assertTrue(is_link(base_path('vendor/monolog/monolog'))); } - public function test_list_packages() + public function test_list_packages(): void { Artisan::call('packager:new', ['vendor' => 'MyVendor', 'name' => 'MyPackage']); Artisan::call('packager:list'); @@ -131,7 +131,7 @@ public function test_list_packages() $this->seeInConsoleOutput('MyVendor'); } - public function test_removing_package() + public function test_removing_package(): void { Artisan::call('packager:new', ['vendor' => 'MyVendor', 'name' => 'MyPackage']); $this->seeInConsoleOutput('MyVendor'); @@ -140,7 +140,7 @@ public function test_removing_package() $this->seeInConsoleOutput('Package removed successfully!'); } - public function test_new_package_is_uninstalled() + public function test_new_package_is_uninstalled(): void { Artisan::call('packager:new', ['vendor' => 'MyVendor', 'name' => 'MyPackage']); Artisan::call('packager:remove', ['vendor' => 'MyVendor', 'name' => 'MyPackage', '--no-interaction' => true]); diff --git a/tests/SkeletonArchiveExtractorsTest.php b/tests/Integration/SkeletonArchiveExtractorsIntegrationTest.php similarity index 74% rename from tests/SkeletonArchiveExtractorsTest.php rename to tests/Integration/SkeletonArchiveExtractorsIntegrationTest.php index f82bfbc..4c44fb9 100644 --- a/tests/SkeletonArchiveExtractorsTest.php +++ b/tests/Integration/SkeletonArchiveExtractorsIntegrationTest.php @@ -2,21 +2,22 @@ declare(strict_types=1); -namespace JeroenG\Packager\Tests; +namespace JeroenG\Packager\Tests\Integration; use Illuminate\Config\Repository; use Illuminate\Support\Facades\Artisan; +use JeroenG\Packager\Tests\IntegrationTestCase; -class SkeletonArchiveExtractorsTest extends TestCase +class SkeletonArchiveExtractorsIntegrationTest extends IntegrationTestCase { - public function test_new_package_is_created_with_tar_gz_skeleton() + public function test_new_package_is_created_with_tar_gz_skeleton(): void { Artisan::call('packager:new', ['vendor' => 'MyVendor', 'name' => 'MyPackage']); $this->seeInConsoleOutput('Package created successfully!'); } - protected function getEnvironmentSetUp($app) + protected function getEnvironmentSetUp($app): void { /** @var Repository $config */ $config = $app['config']; diff --git a/tests/TestCase.php b/tests/IntegrationTestCase.php similarity index 71% rename from tests/TestCase.php rename to tests/IntegrationTestCase.php index f6db9b6..c994a92 100644 --- a/tests/TestCase.php +++ b/tests/IntegrationTestCase.php @@ -6,7 +6,7 @@ use Orchestra\Testbench\TestCase as TestBench; -abstract class TestCase extends TestBench +abstract class IntegrationTestCase extends TestBench { use TestHelper; @@ -22,36 +22,24 @@ public static function setUpBeforeClass(): void parent::setUpBeforeClass(); } - /** - * Setup before each test. - */ public function setUp(): void { $this->installTestApp(); parent::setUp(); } - /** - * Tear down after each test. - */ public function tearDown(): void { $this->uninstallTestApp(); parent::tearDown(); } - protected function getBasePath() + protected function getBasePath(): string { return self::TEST_APP; } - /** - * Tell Testbench to use this package. - * - * @param $app - * @return array - */ - protected function getPackageProviders($app) + protected function getPackageProviders($app): array { return ['JeroenG\Packager\PackagerServiceProvider']; } diff --git a/tests/Support/CommandRunnerExpectation.php b/tests/Support/CommandRunnerExpectation.php new file mode 100644 index 0000000..2458a3a --- /dev/null +++ b/tests/Support/CommandRunnerExpectation.php @@ -0,0 +1,38 @@ +mock = Mockery::mock(CommandRunnerInterface::class); + } + + public static function create(): self + { + return new CommandRunnerExpectation(); + } + + public function getMock(): CommandRunnerInterface|MockInterface + { + return $this->mock; + } + + public function expectRun(array $commandInput, bool $success = true): CompositeExpectation + { + return $this->mock + ->expects('run') + ->with($commandInput) + ->andReturn($success); + } +} diff --git a/tests/TestHelper.php b/tests/TestHelper.php index 37da4cd..94fd0f2 100644 --- a/tests/TestHelper.php +++ b/tests/TestHelper.php @@ -10,28 +10,21 @@ trait TestHelper { - protected function seeInConsoleOutput($expectedText) + protected function seeInConsoleOutput($expectedText): void { $consoleOutput = $this->app[Kernel::class]->output(); $this->assertStringContainsString($expectedText, $consoleOutput, "Did not see `{$expectedText}` in console output: `$consoleOutput`"); } - protected function doNotSeeInConsoleOutput($unExpectedText) - { - $consoleOutput = $this->app[Kernel::class]->output(); - $this->assertStringNotContainsString($unExpectedText, $consoleOutput, - "Did not expect to see `{$unExpectedText}` in console output: `$consoleOutput`"); - } - - protected function installTestApp() + protected function installTestApp(): void { $this->uninstallTestApp(); $files = new Filesystem(); $files->copyDirectory(self::TEST_APP_TEMPLATE, self::TEST_APP); } - protected function uninstallTestApp() + protected function uninstallTestApp(): void { $files = new Filesystem(); if ($files->exists(self::TEST_APP)) { @@ -43,7 +36,7 @@ protected function uninstallTestApp() * Create a modified copy of testbench to be used as a template. * Before each test, a fresh copy of the template is created. */ - private static function setUpLocalTestbench() + private static function setUpLocalTestbench(): void { fwrite(STDOUT, "Setting up test environment for first use.\n"); $files = new Filesystem(); @@ -52,7 +45,7 @@ private static function setUpLocalTestbench() $files->copyDirectory($original, self::TEST_APP_TEMPLATE); // Modify the composer.json file $composer = json_decode($files->get(self::TEST_APP_TEMPLATE.'/composer.json'), true); - // Remove "tests/TestCase.php" from autoload (it doesn't exist) + // Remove "tests/IntegrationTestCase.php" from autoload (it doesn't exist) unset($composer['autoload']['classmap'][1]); // Pre-install illuminate/support $composer['require'] = ['illuminate/support' => '~5']; diff --git a/tests/Unit/ConveyorTest.php b/tests/Unit/ConveyorTest.php new file mode 100644 index 0000000..a0087c8 --- /dev/null +++ b/tests/Unit/ConveyorTest.php @@ -0,0 +1,59 @@ +commandRunner = CommandRunnerExpectation::create(); + $this->fileHandler = \Mockery::mock(FileHandler::class); + + $this->conveyor = new Conveyor($this->commandRunner->getMock(), $this->fileHandler); + } + + public function test_it_calls_process_to_install_package(): void + { + $this->conveyor->vendor('myVendor'); + $this->conveyor->package('myPackage'); + + $this->fileHandler->expects('packagePath')->andReturn('packages/myvendor/mypackage'); + $this->commandRunner->expectRun(['composer', 'require', 'myVendor/myPackage:@dev'], true); + $this->commandRunner->expectRun([ + 'composer', + 'config', + 'repositories.myvendor/mypackage', + '{"type":"path","url":"packages\/myvendor\/mypackage","options":{"symlink":true}}', + '--file', + 'composer.json' + ], true); + + $this->expectNotToPerformAssertions(); + $this->conveyor->installPackage(); + } + + public function test_it_calls_process_to_uninstall_package(): void + { + $this->conveyor->vendor('myVendor'); + $this->conveyor->package('myPackage'); + + $this->commandRunner->expectRun(['composer', 'config', '--unset', 'repositories.myvendor/mypackage'], true); + $this->commandRunner->expectRun(['composer', 'remove', 'myVendor/myPackage'], true); + + $this->expectNotToPerformAssertions(); + $this->conveyor->uninstallPackage(); + } +} diff --git a/tests/UnitTestCase.php b/tests/UnitTestCase.php new file mode 100644 index 0000000..2ab5d74 --- /dev/null +++ b/tests/UnitTestCase.php @@ -0,0 +1,11 @@ +