From 782cb27988be6d47d001766e7fdbace10b27734e Mon Sep 17 00:00:00 2001 From: J-T-McC Date: Thu, 6 Feb 2025 11:11:54 -0800 Subject: [PATCH 1/8] Update dependencies & test formatting --- README.md | 4 +- composer.json | 10 +- phpunit.xml | 46 +++-- .../Commands/DeployCommandTest.php | 169 +++++++++--------- .../Commands/ListCommandTest.php | 13 +- .../Services/AtomicDeploymentServiceTest.php | 113 +++++++----- .../Services/DeploymentTest.php | 84 +++++---- tests/TestCase.php | 81 +++------ tests/Unit/Enum/DeploymentStatusTest.php | 2 +- tests/Unit/Enum/EnumTestTrait.php | 18 +- tests/Unit/Helpers/FileHelperTest.php | 44 ++--- tests/Unit/Services/ExecServiceTest.php | 24 +-- 12 files changed, 304 insertions(+), 304 deletions(-) rename tests/{Integration => Feature}/Commands/DeployCommandTest.php (68%) rename tests/{Integration => Feature}/Commands/ListCommandTest.php (84%) rename tests/{Integration => Feature}/Services/AtomicDeploymentServiceTest.php (73%) rename tests/{Integration => Feature}/Services/DeploymentTest.php (66%) diff --git a/README.md b/README.md index 72215a4..88bbfcb 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,8 @@ The purpose of this package is to introduce local zero-downtime deployments into ## Requirements -* Laravel 7 | 8 -* PHP ^7.4 | ^8.0 +* Laravel >= 11.x+ +* PHP >= 8.3 ## Installation diff --git a/composer.json b/composer.json index 9898d1d..f3ba8d8 100644 --- a/composer.json +++ b/composer.json @@ -11,13 +11,13 @@ } ], "require": { - "php": "^7.4|^8.0" + "php": "^8.3" }, "require-dev": { - "laravel/framework": "^8.12", - "mockery/mockery": "^1.4", - "phpunit/phpunit": "^9.3.3", - "orchestra/testbench": "^6.9" + "laravel/framework": "^11.0", + "mockery/mockery": "^1.6.10", + "phpunit/phpunit": "^10.5.35|^11.3.6", + "orchestra/testbench": "^9.0" }, "autoload": { "psr-4": { diff --git a/phpunit.xml b/phpunit.xml index c18e235..ab8ec23 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,27 +1,23 @@ - - - - ./tests/Unit - - - ./tests/Integration - - - - - ./app - - - - - - - - - - + + + + ./tests/Unit + + + ./tests/Feature + + + + + + + + + + + + ./app + + diff --git a/tests/Integration/Commands/DeployCommandTest.php b/tests/Feature/Commands/DeployCommandTest.php similarity index 68% rename from tests/Integration/Commands/DeployCommandTest.php rename to tests/Feature/Commands/DeployCommandTest.php index c6f8074..b80e6f8 100644 --- a/tests/Integration/Commands/DeployCommandTest.php +++ b/tests/Feature/Commands/DeployCommandTest.php @@ -4,6 +4,7 @@ use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\Artisan; +use Illuminate\Support\Facades\Event; use JTMcC\AtomicDeployments\Events\DeploymentFailed; use JTMcC\AtomicDeployments\Events\DeploymentSuccessful; use JTMcC\AtomicDeployments\Exceptions\InvalidPathException; @@ -15,13 +16,12 @@ class DeployCommandTest extends TestCase { use RefreshDatabase; - /** - * @test - */ - public function it_allows_dry_run_with_no_mutations() + public function test_it_allows_dry_run_with_no_mutations() { + // Act Artisan::call('atomic-deployments:deploy --dry-run --directory=test-dir-1'); + // Assert $this->seeInConsoleOutput([ 'Deployment directory option set - Deployment will use directory: test-dir-1', 'Running Deployment...', @@ -34,24 +34,21 @@ public function it_allows_dry_run_with_no_mutations() ]); $this->dontSeeInConsoleOutput('Atomic deployment rollback has been requested'); - - $this->assertFalse($this->fileSystem->exists($this->deploymentsPath.'/test-dir-1/')); + $this->assertFalse($this->fileSystem->exists($this->deploymentsPath . '/test-dir-1/')); $this->assertFalse($this->fileSystem->exists($this->deploymentLink)); $this->assertEmpty(AtomicDeployment::all()); } - /** - * @test - */ - public function it_does_not_migrate_on_dry_run() + public function test_it_does_not_migrate_on_dry_run() { + // Collect Artisan::call('atomic-deployments:deploy --directory=test-dir-1'); + $this->fileSystem->ensureDirectoryExists($this->deploymentsPath . '/test-dir-1/migration/test-folder'); - //add file to 'deployment' that does not exist in 'build' for migrate test - $this->fileSystem->ensureDirectoryExists($this->deploymentsPath.'/test-dir-1/migration/test-folder'); - + // Act Artisan::call('atomic-deployments:deploy --dry-run --directory=test-dir-2'); + // Assert $this->seeInConsoleOutput([ 'Deployment directory option set - Deployment will use directory: test-dir-2', 'Running Deployment...', @@ -60,18 +57,16 @@ public function it_does_not_migrate_on_dry_run() ]); $this->dontSeeInConsoleOutput('Atomic deployment rollback has been requested'); - - $this->assertTrue($this->fileSystem->exists($this->deploymentsPath.'/test-dir-1/migration/test-folder')); - $this->assertFalse($this->fileSystem->exists($this->deploymentsPath.'/test-dir-2/migration/test-folder')); + $this->assertTrue($this->fileSystem->exists($this->deploymentsPath . '/test-dir-1/migration/test-folder')); + $this->assertFalse($this->fileSystem->exists($this->deploymentsPath . '/test-dir-2/migration/test-folder')); } - /** - * @test - */ - public function it_allows_run_with_mutations() + public function test_it_allows_run_with_mutations() { + // Act Artisan::call('atomic-deployments:deploy --directory=test-dir'); + // Assert $this->seeInConsoleOutput([ 'Deployment directory option set - Deployment will use directory: test-dir', 'Running Deployment...', @@ -84,28 +79,25 @@ public function it_allows_run_with_mutations() 'Atomic deployment rollback has been requested', ]); - $this->assertTrue($this->fileSystem->exists($this->deploymentsPath.'/test-dir/build-contents-folder')); + $this->assertTrue($this->fileSystem->exists($this->deploymentsPath . '/test-dir/build-contents-folder')); $this->assertTrue($this->fileSystem->exists($this->deploymentLink)); $deployment = AtomicDeployment::first(); $this->assertNotEmpty($deployment); - $this->assertTrue((int) $deployment->deployment_status === DeploymentStatus::SUCCESS); + $this->assertTrue((int)$deployment->deployment_status === DeploymentStatus::SUCCESS); } - /** - * @test - */ - public function it_allows_migrate_on_run() + public function test_it_allows_migrate_on_run() { + // Collect Artisan::call('atomic-deployments:deploy --directory=test-dir-1'); + $this->fileSystem->ensureDirectoryExists($this->deploymentsPath . '/test-dir-1/migration/test-folder'); + $this->assertFalse($this->fileSystem->exists($this->deploymentsPath . '/test-dir-2/migration/test-folder')); - //add file to 'deployment' that does not exist in 'build' for migrate test - $this->fileSystem->ensureDirectoryExists($this->deploymentsPath.'/test-dir-1/migration/test-folder'); - - $this->assertFalse($this->fileSystem->exists($this->deploymentsPath.'/test-dir-2/migration/test-folder')); - + // Act Artisan::call('atomic-deployments:deploy --directory=test-dir-2'); + // Assert $this->seeInConsoleOutput([ 'Deployment directory option set - Deployment will use directory: test-dir-2', 'Running Deployment...', @@ -114,118 +106,119 @@ public function it_allows_migrate_on_run() ]); $this->dontSeeInConsoleOutput('Atomic deployment rollback has been requested'); - - $this->assertTrue($this->fileSystem->exists($this->deploymentsPath.'/test-dir-1/migration/test-folder')); - - //confirm migrate logic copied test folder - $this->assertTrue($this->fileSystem->exists($this->deploymentsPath.'/test-dir-2/migration/test-folder')); + $this->assertTrue($this->fileSystem->exists($this->deploymentsPath . '/test-dir-1/migration/test-folder')); + $this->assertTrue($this->fileSystem->exists($this->deploymentsPath . '/test-dir-2/migration/test-folder')); } - /** - * @test - */ - public function it_allows_swapping_between_deployments() + public function test_it_allows_swapping_between_deployments() { - - //create two builds + // Collect Artisan::call('atomic-deployments:deploy --directory=test-dir-1'); Artisan::call('atomic-deployments:deploy --directory=test-dir-2'); + $deployment1 = AtomicDeployment::where('commit_hash', 'test-dir-1')->first()->append( + 'isCurrentlyDeployed' + )->toArray(); + $deployment2 = AtomicDeployment::where('commit_hash', 'test-dir-2')->first()->append( + 'isCurrentlyDeployed' + )->toArray(); - $deployment1 = AtomicDeployment::where('commit_hash', 'test-dir-1')->first()->append('isCurrentlyDeployed')->toArray(); - $deployment2 = AtomicDeployment::where('commit_hash', 'test-dir-2')->first()->append('isCurrentlyDeployed')->toArray(); - - //confirm our last build is currently deployed $this->assertFalse($deployment1['isCurrentlyDeployed']); $this->assertTrue($deployment2['isCurrentlyDeployed']); + // Act Artisan::call('atomic-deployments:deploy --hash=test-dir-fake'); - //confirm build must exist when attempting to swap + // Assert $this->seeInConsoleOutput([ 'Updating symlink to previous build: test-dir-fake', 'Build not found for hash: test-dir-fake', ]); + // Act Artisan::call('atomic-deployments:deploy --hash=test-dir-1'); - //swap build to our first deployment + // Assert $this->seeInConsoleOutput([ 'Updating symlink to previous build: test-dir-1', 'Build link confirmed', ]); - $deployment1 = AtomicDeployment::where('commit_hash', 'test-dir-1')->first()->append('isCurrentlyDeployed')->toArray(); - $deployment2 = AtomicDeployment::where('commit_hash', 'test-dir-2')->first()->append('isCurrentlyDeployed')->toArray(); + $deployment1 = AtomicDeployment::where('commit_hash', 'test-dir-1')->first()->append( + 'isCurrentlyDeployed' + )->toArray(); + $deployment2 = AtomicDeployment::where('commit_hash', 'test-dir-2')->first()->append( + 'isCurrentlyDeployed' + )->toArray(); - //confirm first deployment is now live and second is not $this->assertTrue($deployment1['isCurrentlyDeployed']); $this->assertFalse($deployment2['isCurrentlyDeployed']); } - /** - * @test - */ - public function it_cleans_old_build_folders_based_on_build_limit() + public function test_it_cleans_old_build_folders_based_on_build_limit() { - $this->app['config']->set('atomic-deployments.build-limit', 1); - - Artisan::call('atomic-deployments:deploy --directory=test-dir-1'); - Artisan::call('atomic-deployments:deploy --directory=test-dir-2'); - - $this->assertTrue(AtomicDeployment::all()->count() === 1); - $this->assertTrue(AtomicDeployment::withTrashed()->get()->count() === 2); - - AtomicDeployment::truncate(); - + // Collect $this->app['config']->set('atomic-deployments.build-limit', 3); + // Act Artisan::call('atomic-deployments:deploy --directory=test-dir-1'); Artisan::call('atomic-deployments:deploy --directory=test-dir-2'); Artisan::call('atomic-deployments:deploy --directory=test-dir-3'); Artisan::call('atomic-deployments:deploy --directory=test-dir-4'); Artisan::call('atomic-deployments:deploy --directory=test-dir-5'); + // Assert $this->assertTrue(AtomicDeployment::all()->count() === 3); $this->assertTrue(AtomicDeployment::withTrashed()->get()->count() === 5); } - /** - * @test - */ - public function it_dispatches_deployment_successful_event_on_build() + public function test_it_dispatches_deployment_successful_event_on_build() { - $this->expectsEvents(DeploymentSuccessful::class); + // Collect + Event::fake(); + + // Act Artisan::call('atomic-deployments:deploy --directory=test-dir-1'); + + // Assert + Event::assertDispatched(DeploymentSuccessful::class); } - /** - * @test - */ - public function it_dispatches_deployment_successful_event_on_deployment_swap() + public function test_it_dispatches_deployment_successful_event_on_deployment_swap() { + // Collect + Event::fake(); Artisan::call('atomic-deployments:deploy --directory=test-dir-1'); Artisan::call('atomic-deployments:deploy --directory=test-dir-2'); - - $deployment = AtomicDeployment::where('commit_hash', 'test-dir-2')->first()->append('isCurrentlyDeployed')->toArray(); + $deployment = AtomicDeployment::where('commit_hash', 'test-dir-2')->first()->append( + 'isCurrentlyDeployed' + )->toArray(); $this->assertTrue($deployment['isCurrentlyDeployed']); - $this->expectsEvents(DeploymentSuccessful::class); - + // Act Artisan::call('atomic-deployments:deploy --hash=test-dir-1'); - $deployment = AtomicDeployment::where('commit_hash', 'test-dir-1')->first()->append('isCurrentlyDeployed')->toArray(); + + // Assert + $deployment = AtomicDeployment::where('commit_hash', 'test-dir-1')->first()->append( + 'isCurrentlyDeployed' + )->toArray(); $this->assertTrue($deployment['isCurrentlyDeployed']); + Event::assertDispatched(DeploymentSuccessful::class); } - /** - * @test - */ - public function it_dispatches_deployment_failed_event_on_build_fail() + public function test_it_dispatches_deployment_failed_event_on_build_fail() { - //force invalid path exception - $this->expectException(InvalidPathException::class); + // Collect + Event::fake(); $this->app['config']->set('atomic-deployments.build-path', $this->buildPath); - $this->app['config']->set('atomic-deployments.deployments-path', $this->buildPath.'/deployments'); - $this->expectsEvents(DeploymentFailed::class); + $this->app['config']->set('atomic-deployments.deployments-path', $this->buildPath . '/deployments'); + + // Assert + $this->expectException(InvalidPathException::class); + + // Act Artisan::call('atomic-deployments:deploy --directory=test-dir-1'); + + // Assert + Event::assertDispatched(DeploymentFailed::class); } -} +} \ No newline at end of file diff --git a/tests/Integration/Commands/ListCommandTest.php b/tests/Feature/Commands/ListCommandTest.php similarity index 84% rename from tests/Integration/Commands/ListCommandTest.php rename to tests/Feature/Commands/ListCommandTest.php index f8108f1..59cd077 100644 --- a/tests/Integration/Commands/ListCommandTest.php +++ b/tests/Feature/Commands/ListCommandTest.php @@ -11,21 +11,18 @@ class ListCommandTest extends TestCase { use RefreshDatabase; - /** - * @test - */ - public function it_lists_available_builds() + public function test_it_lists_available_builds() { - Artisan::call('atomic-deployments:deploy --directory=test-dir-1'); + // Collect Artisan::call('atomic-deployments:deploy --directory=test-dir-2'); Artisan::call('atomic-deployments:deploy --directory=test-dir-3'); Artisan::call('atomic-deployments:deploy --directory=test-dir-4'); Artisan::call('atomic-deployments:deploy --directory=test-dir-5'); - AtomicDeployment::find(1)->delete(); - + // Act Artisan::call('atomic-deployments:list'); + // Assert $this->seeInConsoleOutput([ 'ID', 'Commit Hash', @@ -42,4 +39,4 @@ public function it_lists_available_builds() $this->dontSeeInConsoleOutput("{$this->deploymentsPath}/test-dir-1"); } -} +} \ No newline at end of file diff --git a/tests/Integration/Services/AtomicDeploymentServiceTest.php b/tests/Feature/Services/AtomicDeploymentServiceTest.php similarity index 73% rename from tests/Integration/Services/AtomicDeploymentServiceTest.php rename to tests/Feature/Services/AtomicDeploymentServiceTest.php index a1b286a..7b16e23 100644 --- a/tests/Integration/Services/AtomicDeploymentServiceTest.php +++ b/tests/Feature/Services/AtomicDeploymentServiceTest.php @@ -3,6 +3,7 @@ namespace Tests\Integration\Services; use Illuminate\Foundation\Testing\RefreshDatabase; +use Illuminate\Support\Facades\Event; use JTMcC\AtomicDeployments\Events\DeploymentFailed; use JTMcC\AtomicDeployments\Events\DeploymentSuccessful; use JTMcC\AtomicDeployments\Exceptions\InvalidPathException; @@ -14,93 +15,106 @@ class AtomicDeploymentServiceTest extends TestCase { use RefreshDatabase; - /** - * @test - */ - public function it_links_deployment() + public function test_it_links_deployment() { + // Collect $atomicDeployment = self::getAtomicDeployment(); + + // Act $atomicDeployment->linkDeployment(); + + // Assert $this->assertTrue($atomicDeployment->getDeployment()->isDeployed()); } - /** - * @test - */ - public function it_registers_previous_deployment_on_boot() + public function test_it_registers_previous_deployment_on_boot() { + // Collect $atomicDeployment = self::getAtomicDeployment(); $this->assertTrue(empty($atomicDeployment->getInitialDeploymentPath())); + + // Act $atomicDeployment->linkDeployment(); $atomicDeployment = self::getAtomicDeployment(); + + // Assert $this->assertTrue(!empty($atomicDeployment->getInitialDeploymentPath())); } - /** - * @test - */ - public function it_creates_a_deployment_directory() + public function test_it_creates_a_deployment_directory() { + // Collect $atomicDeployment = self::getAtomicDeployment(); + + // Act $atomicDeployment->createDeploymentDirectory(); + + // Assert $this->assertTrue($this->fileSystem->exists($atomicDeployment->getDeployment()->getPath())); } - /** - * @test - */ - public function it_copies_deployment_contents_to_deployment_directory() + public function test_it_copies_deployment_contents_to_deployment_directory() { + // Collect $atomicDeployment = self::getAtomicDeployment('abc123'); $atomicDeployment->createDeploymentDirectory(); + + // Act $atomicDeployment->copyDeploymentContents(); + + // Assert $this->assertTrue($this->fileSystem->exists($atomicDeployment->getDeployment()->getPath().'/build-contents-folder')); } - /** - * @test - */ - public function it_updates_deployment_status_record() + public function test_it_updates_deployment_status_record() { + // Collect $hash = '123abc'; $this->assertEmpty(AtomicDeployment::where('commit_hash', $hash)->first()); + + // Act $atomicDeployment = self::getAtomicDeployment($hash); $atomicDeployment->updateDeploymentStatus(DeploymentStatus::RUNNING); + + // Assert $record = AtomicDeployment::where('commit_hash', $hash)->first(); $this->assertTrue((int) $record->deployment_status === DeploymentStatus::RUNNING); } - /** - * @test - */ - public function it_confirms_symbolic_link() + public function test_it_confirms_symbolic_link() { + // Collect $hash = '123abc'; + + // Act $atomicDeployment = self::getAtomicDeployment($hash); $atomicDeployment->linkDeployment(); + + // Assert $this->assertTrue($atomicDeployment->confirmSymbolicLink()); } - /** - * @test - */ - public function it_doesnt_allow_deployments_folder_to_be_subdirectory_of_build_folder() + public function test_it_doesnt_allow_deployments_folder_to_be_subdirectory_of_build_folder() { + // Collect $this->app['config']->set('atomic-deployments.build-path', $this->buildPath); $this->app['config']->set('atomic-deployments.deployments-path', $this->buildPath.'/deployments'); - $this->expectException(InvalidPathException::class); $atomicDeployment = self::getAtomicDeployment(); + + // Act + $this->expectException(InvalidPathException::class); + + // Act $atomicDeployment->createDeploymentDirectory(); } - /** - * @test - */ - public function it_rolls_back_symbolic_link_to_deployment_detected_on_boot() + public function test_it_rolls_back_symbolic_link_to_deployment_detected_on_boot() { + // Collect $atomicDeployment1 = self::getAtomicDeployment(); $atomicDeployment1->createDeploymentDirectory(); $atomicDeployment1->linkDeployment(); + $this->assertTrue($atomicDeployment1->getDeployment()->isDeployed()); $atomicDeployment2 = self::getAtomicDeployment('abc123'); @@ -110,38 +124,47 @@ public function it_rolls_back_symbolic_link_to_deployment_detected_on_boot() $this->assertTrue($atomicDeployment2->getDeployment()->isDeployed()); $this->assertFalse($atomicDeployment1->getDeployment()->isDeployed()); + // Act $atomicDeployment2->rollback(); + // Assert $this->assertTrue($atomicDeployment1->getDeployment()->isDeployed()); } - /** - * @test - */ - public function it_calls_closure_on_success() + public function test_it_calls_closure_on_success() { - $this->expectsEvents(DeploymentSuccessful::class); + // Collect + Event::fake(); $success = false; + + // Act self::getAtomicDeployment()->deploy(function () use (&$success) { $success = true; }); + + // Assert $this->assertTrue($success); + Event::assertDispatched(DeploymentSuccessful::class); } - /** - * @test - */ - public function it_calls_closure_on_failure() + public function test_it_calls_closure_on_failure() { + // Collect + Event::fake(); $this->app['config']->set('atomic-deployments.build-path', $this->buildPath); $this->app['config']->set('atomic-deployments.deployments-path', $this->buildPath.'/deployments'); - $this->expectsEvents(DeploymentFailed::class); - $this->expectException(InvalidPathException::class); $failed = false; $atomicDeployment = self::getAtomicDeployment(); + + $this->expectException(InvalidPathException::class); + + // Act $atomicDeployment->deploy(fn () => '', function () use (&$failed) { $failed = true; }); + + // Assert $this->assertTrue($failed); + Event::assertDispatched(DeploymentFailed::class); } -} +} \ No newline at end of file diff --git a/tests/Integration/Services/DeploymentTest.php b/tests/Feature/Services/DeploymentTest.php similarity index 66% rename from tests/Integration/Services/DeploymentTest.php rename to tests/Feature/Services/DeploymentTest.php index b453ea2..e370a84 100644 --- a/tests/Integration/Services/DeploymentTest.php +++ b/tests/Feature/Services/DeploymentTest.php @@ -13,91 +13,107 @@ class DeploymentTest extends TestCase { use RefreshDatabase; - /** - * @test - */ - public function it_links_and_confirms_deployment() + public function test_it_links_and_confirms_deployment() { + // Collect $deployment = self::getDeployment(); + + // Act $deployment->createDirectory(); $deployment->link(); + + // Assert $this->assertTrue($deployment->isDeployed()); } - /** - * @test - */ - public function it_sets_deployment_directory() + public function test_it_sets_deployment_directory() { + // Collect $deployment = self::getDeployment(); + + // Act $deployment->setDirectory('abc123'); + + // Assert $this->assertTrue($deployment->getDirectory() === 'abc123'); } - /** - * @test - */ - public function it_names_deployment_folder_using_config_directory_naming_git() + public function test_it_names_deployment_folder_using_config_directory_naming_git() { + // Collect $gitHash = Exec::getGitHash(); $deployment = self::getDeployment(); + + // Act $deployment->createDirectory(); + + // Assert $this->assertTrue($deployment->getDirectoryName() === $gitHash); } - /** - * @test - */ - public function it_names_deployment_folder_using_config_directory_naming_rand() + public function test_it_names_deployment_folder_using_config_directory_naming_rand() { + // Collect $this->app['config']->set('atomic-deployments.directory-naming', 'rand'); $gitHash = Exec::getGitHash(); $deployment = self::getDeployment(); + + // Act $deployment->createDirectory(); + + // Assert $this->assertNotEmpty(trim($deployment->getDirectoryName())); $this->assertTrue($deployment->getDirectoryName() !== $gitHash); } - /** - * @test - */ - public function it_names_deployment_folder_using_config_directory_naming_datetime() + public function test_it_names_deployment_folder_using_config_directory_naming_datetime() { + // Collect $this->app['config']->set('atomic-deployments.directory-naming', 'datetime'); $shouldFind = Carbon::now()->format('Y-m-d_H-i'); $deployment = self::getDeployment(); + + // Act $deployment->createDirectory(); + + // Assert $this->assertNotEmpty(trim($deployment->getDirectoryName())); $this->assertStringContainsString($shouldFind, $deployment->getDirectoryName()); } - /** - * @test - */ - public function it_sets_deployment_path() + public function test_it_sets_deployment_path() { + // Collect $deployment = self::getDeployment(); + + // Act $deployment->setPath(); + + // Assert $this->assertNotEmpty(trim($deployment->getPath())); } - /** - * @test - */ - public function it_creates_a_directory() + public function test_it_creates_a_directory() { + // Collect $deployment = self::getDeployment(); + + // Act $deployment->createDirectory(); + + // Assert $this->assertTrue($this->fileSystem->exists($deployment->getPath())); } - /** - * @test - */ - public function it_updates_model_status() + public function test_it_updates_model_status() { + // Collect $deployment = self::getDeployment(); + + // Act $deployment->updateStatus(DeploymentStatus::SUCCESS); - $this->assertTrue((int) AtomicDeployment::first()->deployment_status === DeploymentStatus::SUCCESS); + + // Assert + $this->assertTrue((int)AtomicDeployment::first()->deployment_status === DeploymentStatus::SUCCESS); } -} +} \ No newline at end of file diff --git a/tests/TestCase.php b/tests/TestCase.php index c24bce1..fe06f2b 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -11,18 +11,13 @@ abstract class TestCase extends BaseTestCase { - const tmpFolder = __DIR__.'/tmp/'; - public $buildPath; - public $deploymentLink; - public $deploymentsPath; - + const string TMP_FOLDER = __DIR__ . '/tmp/'; + public string $buildPath; + public string $deploymentLink; + public string $deploymentsPath; public ?Filesystem $fileSystem = null; - public $mockConsoleOutput = false; - /** - * Setup the test environment. - */ protected function setUp(): void { parent::setUp(); @@ -30,22 +25,21 @@ protected function setUp(): void Artisan::call('migrate', ['--database' => 'sqlite']); $this->fileSystem = new Filesystem(); - $this->fileSystem->deleteDirectory(self::tmpFolder); - $this->fileSystem->makeDirectory(self::tmpFolder); + $this->fileSystem->deleteDirectory(self::TMP_FOLDER); + $this->fileSystem->makeDirectory(self::TMP_FOLDER); $config = $this->app->config->get('atomic-deployments'); - $this->buildPath = static::tmpFolder.$config['build-path']; - $this->deploymentLink = static::tmpFolder.$config['deployment-link']; - $this->deploymentsPath = static::tmpFolder.$config['deployments-path']; + $this->buildPath = self::TMP_FOLDER . $config['build-path']; + $this->deploymentLink = self::TMP_FOLDER . $config['deployment-link']; + $this->deploymentsPath = self::TMP_FOLDER . $config['deployments-path']; $this->app['config']->set('atomic-deployments.build-path', $this->buildPath); $this->app['config']->set('atomic-deployments.deployment-link', $this->deploymentLink); $this->app['config']->set('atomic-deployments.deployments-path', $this->deploymentsPath); - $this->app['config']->set('atomic-deployments.migrate', [ - 'migration/*', - ]); - $this->fileSystem->ensureDirectoryExists($this->buildPath.'/build-contents-folder'); + $this->app['config']->set('atomic-deployments.migrate', ['migration/*']); + + $this->fileSystem->ensureDirectoryExists($this->buildPath . '/build-contents-folder'); $this->fileSystem->ensureDirectoryExists($this->deploymentsPath); Event::fake(); @@ -54,65 +48,45 @@ protected function setUp(): void public function tearDown(): void { parent::tearDown(); - $this->fileSystem->deleteDirectory(self::tmpFolder); + $this->fileSystem->deleteDirectory(self::TMP_FOLDER); } - /** - * Define environment setup. - * - * @param \Illuminate\Foundation\Application $app - * - * @return void - */ - protected function getEnvironmentSetUp($app) + protected function getEnvironmentSetUp($app): void { - // Setup default database to use sqlite :memory: $app['config']->set('database.default', 'sqlite'); $app['config']->set('database.connections.sqlite', [ - 'driver' => 'sqlite', + 'driver' => 'sqlite', 'database' => ':memory:', - 'prefix' => '', + 'prefix' => '', ]); } - protected function getPackageProviders($app) + protected function getPackageProviders($app): array { return [\JTMcC\AtomicDeployments\AtomicDeploymentsServiceProvider::class]; } - /** - * @param string|array $searchStrings - */ - protected function seeInConsoleOutput($searchStrings) + protected function seeInConsoleOutput(string|array $searchStrings): void { - if (!is_array($searchStrings)) { - $searchStrings = [$searchStrings]; - } - + $searchStrings = (array)$searchStrings; $output = Artisan::output(); foreach ($searchStrings as $searchString) { - $this->assertStringContainsStringIgnoringCase((string) $searchString, $output); + $this->assertStringContainsStringIgnoringCase((string)$searchString, $output); } } - /** - * @param string|array $searchStrings - */ - protected function dontSeeInConsoleOutput($searchStrings) + protected function dontSeeInConsoleOutput(string|array $searchStrings): void { - if (!is_array($searchStrings)) { - $searchStrings = [$searchStrings]; - } - + $searchStrings = (array)$searchStrings; $output = Artisan::output(); foreach ($searchStrings as $searchString) { - $this->assertStringNotContainsString((string) $searchString, $output); + $this->assertStringNotContainsString((string)$searchString, $output); } } - public static function getAtomicDeployment($hash = '') + public static function getAtomicDeployment(string $hash = ''): AtomicDeploymentService { $atomicDeployment = AtomicDeploymentService::create(); @@ -123,11 +97,8 @@ public static function getAtomicDeployment($hash = '') return $atomicDeployment; } - /** - * @return Deployment - */ - public static function getDeployment() + public static function getDeployment(): Deployment { return app(Deployment::class); } -} +} \ No newline at end of file diff --git a/tests/Unit/Enum/DeploymentStatusTest.php b/tests/Unit/Enum/DeploymentStatusTest.php index 660a986..47f1fa1 100644 --- a/tests/Unit/Enum/DeploymentStatusTest.php +++ b/tests/Unit/Enum/DeploymentStatusTest.php @@ -10,7 +10,7 @@ class DeploymentStatusTest extends TestCase use EnumTestTrait; const expected = [ - 'FAILED' => 0, + 'FAILED' => 0, 'RUNNING' => 1, 'SUCCESS' => 2, ]; diff --git a/tests/Unit/Enum/EnumTestTrait.php b/tests/Unit/Enum/EnumTestTrait.php index 9d134a7..4541000 100644 --- a/tests/Unit/Enum/EnumTestTrait.php +++ b/tests/Unit/Enum/EnumTestTrait.php @@ -4,26 +4,26 @@ trait EnumTestTrait { - /** - * @test - */ - public function property_names_and_values_have_not_changed() + public function test_it_checks_property_names_and_values_have_not_changed() { + // Collect $class = self::model; $enum = new $class(); + + // Assert $this->assertTrue(empty(array_diff_assoc(static::expected, $enum->getConstants()))); } - /** - * @test - */ - public function can_get_property_from_value() + public function test_it_can_get_property_from_value() { + // Collect $class = self::model; $enum = new $class(); $props = $enum->getConstants(); $category = array_keys($props)[0]; $value = array_values($props)[0]; + + // Assert $this->assertTrue($enum->getNameFromValue($value) === $category); } -} +} \ No newline at end of file diff --git a/tests/Unit/Helpers/FileHelperTest.php b/tests/Unit/Helpers/FileHelperTest.php index fa37043..4e073bc 100644 --- a/tests/Unit/Helpers/FileHelperTest.php +++ b/tests/Unit/Helpers/FileHelperTest.php @@ -9,43 +9,45 @@ class FileHelperTest extends TestCase { - /** - * @test - */ - public function it_throws_invalid_path_exception_on_invalid_path() + public function test_it_throws_invalid_path_exception_on_invalid_path() { + // Act $this->expectException(InvalidPathException::class); + + // Act FileHelper::confirmPathsExist('not_a_real_path'); } - /** - * @test - */ - public function it_updates_symbolic_links_to_new_path() + public function test_it_updates_symbolic_links_to_new_path() { + // Collect + $oldSite = self::TMP_FOLDER . 'build/site'; + $oldContent = $oldSite . '/content'; + $oldLink = $oldSite . '/link'; - //create test build & deployment scenario - $oldSite = self::tmpFolder.'build/site'; - $oldContent = $oldSite.'/content'; - $oldLink = $oldSite.'/link'; - - $newSite = self::tmpFolder.'deployments/site'; - $newContent = $newSite.'/content'; - $newLink = $newSite.'/link'; + $newSite = self::TMP_FOLDER . 'deployments/site'; + $newContent = $newSite . '/content'; + $newLink = $newSite . '/link'; $this->fileSystem->ensureDirectoryExists($oldContent); $this->fileSystem->ensureDirectoryExists($newSite); - //link to old content + // Act Exec::ln($oldLink, $oldContent); + + // Assert $this->assertTrue(Exec::readlink($oldLink) === $oldContent); - //copy old content to deployment folder and confirm link still points to build folder - Exec::rsync($oldSite.'/', $newSite.'/'); + // Act + Exec::rsync($oldSite . '/', $newSite . '/'); + + // Assert $this->assertTrue(Exec::readlink($newLink) === $oldContent); - //convert links to new deployment path and confirm + // Act FileHelper::recursivelyUpdateSymlinks($oldSite, $newSite); + + // Assert $this->assertTrue(Exec::readlink($newLink) === $newContent); } -} +} \ No newline at end of file diff --git a/tests/Unit/Services/ExecServiceTest.php b/tests/Unit/Services/ExecServiceTest.php index e110c68..6cdcf42 100644 --- a/tests/Unit/Services/ExecServiceTest.php +++ b/tests/Unit/Services/ExecServiceTest.php @@ -7,25 +7,27 @@ class ExecServiceTest extends TestCase { - /** - * @test - */ - public function it_can_create_and_read_symbolic_link() + public function test_it_can_create_and_read_symbolic_link() { + // Act Exec::ln($this->deploymentLink, $this->deploymentsPath); + + // Assert $this->assertTrue(Exec::readlink($this->deploymentLink) === $this->deploymentsPath); } - /** - * @test - */ - public function it_can_remote_sync_folders() + public function test_it_can_remote_sync_folders() { - $from = $this->buildPath.'/to-move'; + // Collect + $from = $this->buildPath . '/to-move'; $to = $this->deploymentsPath; - $confirm = $this->deploymentsPath.'/to-move'; + $confirm = $this->deploymentsPath . '/to-move'; $this->fileSystem->makeDirectory($from); + + // Act Exec::rsync($from, $to); + + // Assert $this->assertTrue($this->fileSystem->isDirectory($confirm)); } -} +} \ No newline at end of file From 7db3137236bd33d6d86673013f53dbaa7b0748a7 Mon Sep 17 00:00:00 2001 From: J-T-McC Date: Thu, 6 Feb 2025 11:15:59 -0800 Subject: [PATCH 2/8] csfixer run --- composer.json | 3 +- src/AtomicDeploymentsServiceProvider.php | 2 +- src/Commands/DeployCommand.php | 2 +- src/Commands/ListCommand.php | 2 +- src/Events/DeploymentFailed.php | 6 +-- src/Events/DeploymentSuccessful.php | 6 +-- src/Exceptions/AreYouInsaneException.php | 2 +- src/Exceptions/ExecuteFailedException.php | 2 +- src/Exceptions/InvalidPathException.php | 2 +- src/Helpers/ConsoleOutput.php | 9 +---- src/Helpers/FileHelper.php | 8 +--- src/Models/AtomicDeployment.php | 6 +-- src/Models/Enums/DeploymentStatus.php | 2 + src/Models/Enums/Enum.php | 10 ++--- src/Services/AtomicDeploymentService.php | 39 ++++++------------- src/Services/Deployment.php | 38 +++++------------- src/Services/Exec.php | 27 ++++--------- src/Services/Output.php | 17 -------- tests/Feature/Commands/DeployCommandTest.php | 24 ++++++------ tests/Feature/Commands/ListCommandTest.php | 3 +- .../Services/AtomicDeploymentServiceTest.php | 4 +- tests/Feature/Services/DeploymentTest.php | 4 +- tests/TestCase.php | 31 ++++++++------- tests/Unit/Enum/EnumTestTrait.php | 6 +-- tests/Unit/Helpers/FileHelperTest.php | 16 ++++---- tests/Unit/Services/ExecServiceTest.php | 6 +-- 26 files changed, 102 insertions(+), 175 deletions(-) diff --git a/composer.json b/composer.json index f3ba8d8..3ec9624 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,8 @@ "laravel/framework": "^11.0", "mockery/mockery": "^1.6.10", "phpunit/phpunit": "^10.5.35|^11.3.6", - "orchestra/testbench": "^9.0" + "orchestra/testbench": "^9.0", + "laravel/pint": "^1.20" }, "autoload": { "psr-4": { diff --git a/src/AtomicDeploymentsServiceProvider.php b/src/AtomicDeploymentsServiceProvider.php index 459c106..01699af 100644 --- a/src/AtomicDeploymentsServiceProvider.php +++ b/src/AtomicDeploymentsServiceProvider.php @@ -24,7 +24,7 @@ public function register() $this->app->bind(DeploymentInterface::class, config('atomic-deployments.deployment-class')); $this->app->bind(AtomicDeploymentService::class, function ($app, $params) { - if (empty($params) || (count($params) && !is_a($params[0], DeploymentInterface::class))) { + if (empty($params) || (count($params) && ! is_a($params[0], DeploymentInterface::class))) { array_unshift($params, $app->make(DeploymentInterface::class)); } diff --git a/src/Commands/DeployCommand.php b/src/Commands/DeployCommand.php index 8644b57..3db1576 100644 --- a/src/Commands/DeployCommand.php +++ b/src/Commands/DeployCommand.php @@ -30,7 +30,7 @@ public function handle() $deploymentModel = AtomicDeployment::successful()->where('commit_hash', $hash)->first(); - if (!$deploymentModel || !$deploymentModel->hasDeployment) { + if (! $deploymentModel || ! $deploymentModel->hasDeployment) { Output::warn("Build not found for hash: {$hash}"); } else { $atomicDeployment = AtomicDeploymentService::create( diff --git a/src/Commands/ListCommand.php b/src/Commands/ListCommand.php index 0e7528a..7c6fd43 100644 --- a/src/Commands/ListCommand.php +++ b/src/Commands/ListCommand.php @@ -31,7 +31,7 @@ public function handle() return $deployment; }); - if (!$deployments->count()) { + if (! $deployments->count()) { ConsoleOutput::info('No deployments found'); return; diff --git a/src/Events/DeploymentFailed.php b/src/Events/DeploymentFailed.php index e028d83..ce2c761 100644 --- a/src/Events/DeploymentFailed.php +++ b/src/Events/DeploymentFailed.php @@ -14,15 +14,13 @@ class DeploymentFailed implements ShouldQueue use SerializesModels; public AtomicDeploymentService $deploymentService; + public ?AtomicDeployment $deployment = null; /** * DeploymentSuccessful constructor. - * - * @param AtomicDeploymentService $deploymentService - * @param AtomicDeployment|null $deployment */ - public function __construct(AtomicDeploymentService $deploymentService, AtomicDeployment $deployment = null) + public function __construct(AtomicDeploymentService $deploymentService, ?AtomicDeployment $deployment = null) { $this->deploymentService = $deploymentService; $this->deployment = $deployment; diff --git a/src/Events/DeploymentSuccessful.php b/src/Events/DeploymentSuccessful.php index ec93aa5..8fb5d7e 100644 --- a/src/Events/DeploymentSuccessful.php +++ b/src/Events/DeploymentSuccessful.php @@ -14,15 +14,13 @@ class DeploymentSuccessful implements ShouldQueue use SerializesModels; public AtomicDeploymentService $deploymentService; + public ?AtomicDeployment $deployment = null; /** * DeploymentSuccessful constructor. - * - * @param AtomicDeploymentService $deploymentService - * @param AtomicDeployment|null $deployment */ - public function __construct(AtomicDeploymentService $deploymentService, AtomicDeployment $deployment = null) + public function __construct(AtomicDeploymentService $deploymentService, ?AtomicDeployment $deployment = null) { $this->deploymentService = $deploymentService; $this->deployment = $deployment; diff --git a/src/Exceptions/AreYouInsaneException.php b/src/Exceptions/AreYouInsaneException.php index 99d53c9..1b46f8b 100644 --- a/src/Exceptions/AreYouInsaneException.php +++ b/src/Exceptions/AreYouInsaneException.php @@ -7,7 +7,7 @@ class AreYouInsaneException extends Exception { - public function __construct($message = '', $code = 0, Throwable $previous = null) + public function __construct($message = '', $code = 0, ?Throwable $previous = null) { parent::__construct("(╯°□°)╯︵ ┻━┻: {$message}", $code, $previous); } diff --git a/src/Exceptions/ExecuteFailedException.php b/src/Exceptions/ExecuteFailedException.php index cbc33f1..626aaa0 100644 --- a/src/Exceptions/ExecuteFailedException.php +++ b/src/Exceptions/ExecuteFailedException.php @@ -7,7 +7,7 @@ class ExecuteFailedException extends Exception { - public function __construct($message = '', $code = 0, Throwable $previous = null) + public function __construct($message = '', $code = 0, ?Throwable $previous = null) { parent::__construct("exec failed: {$message}", $code, $previous); } diff --git a/src/Exceptions/InvalidPathException.php b/src/Exceptions/InvalidPathException.php index 9600692..da4125b 100644 --- a/src/Exceptions/InvalidPathException.php +++ b/src/Exceptions/InvalidPathException.php @@ -7,7 +7,7 @@ class InvalidPathException extends Exception { - public function __construct($message = '', $code = 0, Throwable $previous = null) + public function __construct($message = '', $code = 0, ?Throwable $previous = null) { parent::__construct("Invalid Path: {$message}", $code, $previous); } diff --git a/src/Helpers/ConsoleOutput.php b/src/Helpers/ConsoleOutput.php index 61de03c..eb5faf3 100644 --- a/src/Helpers/ConsoleOutput.php +++ b/src/Helpers/ConsoleOutput.php @@ -8,21 +8,14 @@ class ConsoleOutput { public static ?Command $runningCommand = null; - /** - * @param Command $runningCommand - */ public function setOutput(Command $runningCommand) { static::$runningCommand = $runningCommand; } - /** - * @param string $method - * @param $arguments - */ public static function __callStatic(string $method, $arguments) { - if (!static::$runningCommand) { + if (! static::$runningCommand) { return; } diff --git a/src/Helpers/FileHelper.php b/src/Helpers/FileHelper.php index 2e37d77..7695153 100644 --- a/src/Helpers/FileHelper.php +++ b/src/Helpers/FileHelper.php @@ -12,16 +12,12 @@ class FileHelper { /** - * @param string ...$paths - * * @throws InvalidPathException - * - * @return bool */ public static function confirmPathsExist(string ...$paths): bool { foreach ($paths as $path) { - if (!File::exists($path)) { + if (! File::exists($path)) { throw new InvalidPathException("{$path} does not exist"); } } @@ -32,8 +28,6 @@ public static function confirmPathsExist(string ...$paths): bool /** * Recursively update symbolic links with new endpoint. * - * @param $from - * @param $to * * @throws ExecuteFailedException */ diff --git a/src/Models/AtomicDeployment.php b/src/Models/AtomicDeployment.php index 8dfcf9e..3b645c3 100644 --- a/src/Models/AtomicDeployment.php +++ b/src/Models/AtomicDeployment.php @@ -43,13 +43,13 @@ public function getHasDeploymentAttribute() } /** - * @throws \JTMcC\AtomicDeployments\Exceptions\ExecuteFailedException - * * @return bool + * + * @throws \JTMcC\AtomicDeployments\Exceptions\ExecuteFailedException */ public function getIsCurrentlyDeployedAttribute() { - if (!$this->hasDeployment) { + if (! $this->hasDeployment) { return false; } diff --git a/src/Models/Enums/DeploymentStatus.php b/src/Models/Enums/DeploymentStatus.php index dae8caf..cf920f3 100644 --- a/src/Models/Enums/DeploymentStatus.php +++ b/src/Models/Enums/DeploymentStatus.php @@ -5,6 +5,8 @@ class DeploymentStatus extends Enum { const FAILED = 0; + const RUNNING = 1; + const SUCCESS = 2; } diff --git a/src/Models/Enums/Enum.php b/src/Models/Enums/Enum.php index 69e05b5..ef7f6c0 100644 --- a/src/Models/Enums/Enum.php +++ b/src/Models/Enums/Enum.php @@ -9,9 +9,9 @@ class Enum private static ?array $constCacheArray = null; /** - * @throws \ReflectionException - * * @return array|mixed + * + * @throws \ReflectionException */ public static function getConstants() { @@ -19,7 +19,7 @@ public static function getConstants() self::$constCacheArray = []; } $calledClass = get_called_class(); - if (!array_key_exists($calledClass, self::$constCacheArray)) { + if (! array_key_exists($calledClass, self::$constCacheArray)) { $reflect = new ReflectionClass($calledClass); self::$constCacheArray[$calledClass] = $reflect->getConstants(); } @@ -28,11 +28,9 @@ public static function getConstants() } /** - * @param int $value + * @return false|int|string|null * * @throws \ReflectionException - * - * @return false|int|string|null */ public static function getNameFromValue(int $value) { diff --git a/src/Services/AtomicDeploymentService.php b/src/Services/AtomicDeploymentService.php index be523ea..a2c3f09 100644 --- a/src/Services/AtomicDeploymentService.php +++ b/src/Services/AtomicDeploymentService.php @@ -20,13 +20,13 @@ class AtomicDeploymentService protected DeploymentInterface $deployment; protected bool $dryRun; + protected array $migrate; protected string $initialDeploymentPath = ''; /** - * @param mixed ...$args - * + * @param mixed ...$args * @return self */ public static function create(...$args) @@ -34,11 +34,6 @@ public static function create(...$args) return app(static::class, $args); } - /** - * @param DeploymentInterface $deployment - * @param array $migrate - * @param bool $dryRun - */ public function __construct(DeploymentInterface $deployment, array $migrate = [], bool $dryRun = false) { $this->deployment = $deployment; @@ -58,9 +53,6 @@ public function getDeployment() return $this->deployment; } - /** - * @return string - */ public function getInitialDeploymentPath(): string { return $this->initialDeploymentPath; @@ -68,9 +60,6 @@ public function getInitialDeploymentPath(): string /** * Run full deployment. - * - * @param Closure|null $successCallback - * @param Closure|null $failedCallback */ public function deploy(?Closure $successCallback = null, ?Closure $failedCallback = null): void { @@ -110,9 +99,6 @@ public function deploy(?Closure $successCallback = null, ?Closure $failedCallbac } } - /** - * @param int $status - */ public function updateDeploymentStatus(int $status): void { if ($this->isDryRun()) { @@ -137,8 +123,6 @@ public function linkDeployment(): void /** * @throws ExecuteFailedException - * - * @return bool */ public function confirmSymbolicLink(): bool { @@ -150,7 +134,7 @@ public function confirmSymbolicLink(): bool return true; } - if (!$this->deployment->isDeployed()) { + if (! $this->deployment->isDeployed()) { throw new ExecuteFailedException( 'Expected deployment link to direct to '. $this->deployment->getPath().' but found '. @@ -198,13 +182,13 @@ public function copyDeploymentContents(): void */ public function copyMigrationContents(): void { - if (!empty($this->initialDeploymentPath) && count($this->migrate)) { + if (! empty($this->initialDeploymentPath) && count($this->migrate)) { if ($this->isDryRun()) { Output::warn('Dry run - skipping migrations'); } collect($this->migrate)->each(function ($pattern) { - if (!$this->isDryRun()) { + if (! $this->isDryRun()) { Output::info("Running migration for pattern {$pattern}"); } @@ -214,7 +198,7 @@ public function copyMigrationContents(): void foreach (File::glob($rootFrom.$pattern) as $from) { $dir = $from; - if (!File::isDirectory($dir)) { + if (! File::isDirectory($dir)) { $dir = File::dirname($dir); } @@ -224,6 +208,7 @@ public function copyMigrationContents(): void if ($this->isDryRun()) { Output::warn("Dry run - migrate: \r\n - {$from}\r\n - {$to}"); Output::line(); + continue; } @@ -232,7 +217,7 @@ public function copyMigrationContents(): void Exec::rsync($from, $to); } - if (!$this->isDryRun()) { + if (! $this->isDryRun()) { Output::info("Finished migration for pattern {$pattern}"); } }); @@ -264,18 +249,18 @@ public function rollback(): void { Output::warn('Atomic deployment rollback has been requested'); - if (!$this->isDryRun()) { + if (! $this->isDryRun()) { $currentPath = $this->deployment->getCurrentPath(); if ( - //confirm if we need to revert the link + // confirm if we need to revert the link $this->initialDeploymentPath && $this->initialDeploymentPath !== $currentPath ) { Output::emergency("Attempting to link deployment at {$this->initialDeploymentPath}"); try { - //attempt to revert link to our original path + // attempt to revert link to our original path Exec::ln($this->deployment->getLink(), $this->initialDeploymentPath); if ($this->deployment->getCurrentPath() === $this->initialDeploymentPath) { Output::info('Successfully rolled back symbolic link'); @@ -342,7 +327,7 @@ public function cleanBuilds(int $limit): void Output::info("Deleting {$deployment->commit_hash}"); - if (!$this->isDryRun()) { + if (! $this->isDryRun()) { $deployment->delete(); Output::info('Deployment deleted'); } else { diff --git a/src/Services/Deployment.php b/src/Services/Deployment.php index 719b250..e3bb783 100644 --- a/src/Services/Deployment.php +++ b/src/Services/Deployment.php @@ -18,17 +18,19 @@ class Deployment implements DeploymentInterface protected AtomicDeployment $model; protected string $buildPath; + protected string $deploymentLink; + protected string $deploymentsPath; + protected string $directoryNaming; protected string $deploymentPath = ''; + protected string $deploymentDirectory = ''; /** * Deployment constructor. - * - * @param AtomicDeployment $model */ public function __construct(AtomicDeployment $model) { @@ -67,13 +69,10 @@ public function setDirectory(string $name = ''): void { $this->deploymentDirectory = trim($name); - //update deployment path to use new directory + // update deployment path to use new directory $this->setPath(); } - /** - * @return string - */ public function getDirectory(): string { return $this->deploymentDirectory; @@ -83,8 +82,6 @@ public function getDirectory(): string * Get the current symlinked deployment path. * * @throws ExecuteFailedException - * - * @return string */ public function getCurrentPath(): string { @@ -97,9 +94,9 @@ public function getCurrentPath(): string } /** - * @throws ExecuteFailedException - * * @return string + * + * @throws ExecuteFailedException */ public function getDirectoryName() { @@ -138,8 +135,6 @@ public function setPath(): void * * @throws ExecuteFailedException * @throws InvalidPathException - * - * @return string */ public function getPath(): string { @@ -151,8 +146,6 @@ public function getPath(): string } /** - * @param int $status - * * @throws ExecuteFailedException * @throws InvalidPathException */ @@ -161,9 +154,9 @@ public function updateStatus(int $status): void $this->model->updateOrCreate( ['deployment_path' => $this->getPath()], [ - 'commit_hash' => $this->deploymentDirectory, - 'build_path' => $this->buildPath, - 'deployment_link' => $this->deploymentLink, + 'commit_hash' => $this->deploymentDirectory, + 'build_path' => $this->buildPath, + 'deployment_link' => $this->deploymentLink, 'deployment_status' => $status, ] ); @@ -183,25 +176,16 @@ public function copyContents() Exec::rsync("{$this->buildPath}/", "{$this->deploymentPath}/"); } - /** - * @return AtomicDeployment - */ public function getModel(): AtomicDeployment { return $this->model; } - /** - * @return string - */ public function getBuildPath(): string { return $this->buildPath; } - /** - * @return string - */ public function getLink(): string { return $this->deploymentLink; @@ -209,8 +193,6 @@ public function getLink(): string /** * @throws ExecuteFailedException - * - * @return bool */ public function isDeployed(): bool { diff --git a/src/Services/Exec.php b/src/Services/Exec.php index d5b39c6..8c39a9c 100644 --- a/src/Services/Exec.php +++ b/src/Services/Exec.php @@ -7,12 +7,9 @@ class Exec { /** - * @param string $command - * @param array $arguments + * @return string * * @throws ExecuteFailedException - * - * @return string */ private static function run(string $command, array $arguments = []) { @@ -25,8 +22,8 @@ private static function run(string $command, array $arguments = []) $result = trim(exec($command, $output, $status)); - //non zero status means execution failed - //see https://www.linuxtopia.org/online_books/advanced_bash_scripting_guide/exitcodes.html + // non zero status means execution failed + // see https://www.linuxtopia.org/online_books/advanced_bash_scripting_guide/exitcodes.html if ($status) { throw new ExecuteFailedException("resulted in exit code {$status}"); } @@ -35,11 +32,9 @@ private static function run(string $command, array $arguments = []) } /** - * @param $link + * @return string * * @throws ExecuteFailedException - * - * @return string */ public static function readlink($link) { @@ -47,12 +42,9 @@ public static function readlink($link) } /** - * @param string $link - * @param string $path + * @return string * * @throws ExecuteFailedException - * - * @return string */ public static function ln(string $link, string $path) { @@ -60,12 +52,9 @@ public static function ln(string $link, string $path) } /** - * @param string $from - * @param string $to + * @return string * * @throws ExecuteFailedException - * - * @return string */ public static function rsync(string $from, string $to) { @@ -73,9 +62,9 @@ public static function rsync(string $from, string $to) } /** - * @throws ExecuteFailedException - * * @return string + * + * @throws ExecuteFailedException */ public static function getGitHash() { diff --git a/src/Services/Output.php b/src/Services/Output.php index caec160..f705ded 100644 --- a/src/Services/Output.php +++ b/src/Services/Output.php @@ -9,8 +9,6 @@ class Output { /** * Print throwable to console | log. - * - * @param \Throwable $obj */ public static function throwable(\Throwable $obj): void { @@ -29,9 +27,6 @@ public static function throwable(\Throwable $obj): void ); } - /** - * @param string $message - */ public static function alert(string $message): void { ConsoleOutput::line(''); @@ -39,36 +34,24 @@ public static function alert(string $message): void Log::info($message); } - /** - * @param string $message - */ public static function error(string $message): void { ConsoleOutput::error($message); Log::error($message); } - /** - * @param string $message - */ public static function emergency(string $message): void { ConsoleOutput::error($message); Log::emergency($message); } - /** - * @param $message - */ public static function info(string $message): void { ConsoleOutput::info($message); Log::info($message); } - /** - * @param $message - */ public static function warn(string $message): void { ConsoleOutput::warn($message); diff --git a/tests/Feature/Commands/DeployCommandTest.php b/tests/Feature/Commands/DeployCommandTest.php index b80e6f8..bd13681 100644 --- a/tests/Feature/Commands/DeployCommandTest.php +++ b/tests/Feature/Commands/DeployCommandTest.php @@ -34,7 +34,7 @@ public function test_it_allows_dry_run_with_no_mutations() ]); $this->dontSeeInConsoleOutput('Atomic deployment rollback has been requested'); - $this->assertFalse($this->fileSystem->exists($this->deploymentsPath . '/test-dir-1/')); + $this->assertFalse($this->fileSystem->exists($this->deploymentsPath.'/test-dir-1/')); $this->assertFalse($this->fileSystem->exists($this->deploymentLink)); $this->assertEmpty(AtomicDeployment::all()); } @@ -43,7 +43,7 @@ public function test_it_does_not_migrate_on_dry_run() { // Collect Artisan::call('atomic-deployments:deploy --directory=test-dir-1'); - $this->fileSystem->ensureDirectoryExists($this->deploymentsPath . '/test-dir-1/migration/test-folder'); + $this->fileSystem->ensureDirectoryExists($this->deploymentsPath.'/test-dir-1/migration/test-folder'); // Act Artisan::call('atomic-deployments:deploy --dry-run --directory=test-dir-2'); @@ -57,8 +57,8 @@ public function test_it_does_not_migrate_on_dry_run() ]); $this->dontSeeInConsoleOutput('Atomic deployment rollback has been requested'); - $this->assertTrue($this->fileSystem->exists($this->deploymentsPath . '/test-dir-1/migration/test-folder')); - $this->assertFalse($this->fileSystem->exists($this->deploymentsPath . '/test-dir-2/migration/test-folder')); + $this->assertTrue($this->fileSystem->exists($this->deploymentsPath.'/test-dir-1/migration/test-folder')); + $this->assertFalse($this->fileSystem->exists($this->deploymentsPath.'/test-dir-2/migration/test-folder')); } public function test_it_allows_run_with_mutations() @@ -79,20 +79,20 @@ public function test_it_allows_run_with_mutations() 'Atomic deployment rollback has been requested', ]); - $this->assertTrue($this->fileSystem->exists($this->deploymentsPath . '/test-dir/build-contents-folder')); + $this->assertTrue($this->fileSystem->exists($this->deploymentsPath.'/test-dir/build-contents-folder')); $this->assertTrue($this->fileSystem->exists($this->deploymentLink)); $deployment = AtomicDeployment::first(); $this->assertNotEmpty($deployment); - $this->assertTrue((int)$deployment->deployment_status === DeploymentStatus::SUCCESS); + $this->assertTrue((int) $deployment->deployment_status === DeploymentStatus::SUCCESS); } public function test_it_allows_migrate_on_run() { // Collect Artisan::call('atomic-deployments:deploy --directory=test-dir-1'); - $this->fileSystem->ensureDirectoryExists($this->deploymentsPath . '/test-dir-1/migration/test-folder'); - $this->assertFalse($this->fileSystem->exists($this->deploymentsPath . '/test-dir-2/migration/test-folder')); + $this->fileSystem->ensureDirectoryExists($this->deploymentsPath.'/test-dir-1/migration/test-folder'); + $this->assertFalse($this->fileSystem->exists($this->deploymentsPath.'/test-dir-2/migration/test-folder')); // Act Artisan::call('atomic-deployments:deploy --directory=test-dir-2'); @@ -106,8 +106,8 @@ public function test_it_allows_migrate_on_run() ]); $this->dontSeeInConsoleOutput('Atomic deployment rollback has been requested'); - $this->assertTrue($this->fileSystem->exists($this->deploymentsPath . '/test-dir-1/migration/test-folder')); - $this->assertTrue($this->fileSystem->exists($this->deploymentsPath . '/test-dir-2/migration/test-folder')); + $this->assertTrue($this->fileSystem->exists($this->deploymentsPath.'/test-dir-1/migration/test-folder')); + $this->assertTrue($this->fileSystem->exists($this->deploymentsPath.'/test-dir-2/migration/test-folder')); } public function test_it_allows_swapping_between_deployments() @@ -210,7 +210,7 @@ public function test_it_dispatches_deployment_failed_event_on_build_fail() // Collect Event::fake(); $this->app['config']->set('atomic-deployments.build-path', $this->buildPath); - $this->app['config']->set('atomic-deployments.deployments-path', $this->buildPath . '/deployments'); + $this->app['config']->set('atomic-deployments.deployments-path', $this->buildPath.'/deployments'); // Assert $this->expectException(InvalidPathException::class); @@ -221,4 +221,4 @@ public function test_it_dispatches_deployment_failed_event_on_build_fail() // Assert Event::assertDispatched(DeploymentFailed::class); } -} \ No newline at end of file +} diff --git a/tests/Feature/Commands/ListCommandTest.php b/tests/Feature/Commands/ListCommandTest.php index 59cd077..19a399f 100644 --- a/tests/Feature/Commands/ListCommandTest.php +++ b/tests/Feature/Commands/ListCommandTest.php @@ -4,7 +4,6 @@ use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\Artisan; -use JTMcC\AtomicDeployments\Models\AtomicDeployment; use Tests\TestCase; class ListCommandTest extends TestCase @@ -39,4 +38,4 @@ public function test_it_lists_available_builds() $this->dontSeeInConsoleOutput("{$this->deploymentsPath}/test-dir-1"); } -} \ No newline at end of file +} diff --git a/tests/Feature/Services/AtomicDeploymentServiceTest.php b/tests/Feature/Services/AtomicDeploymentServiceTest.php index 7b16e23..02c8d89 100644 --- a/tests/Feature/Services/AtomicDeploymentServiceTest.php +++ b/tests/Feature/Services/AtomicDeploymentServiceTest.php @@ -38,7 +38,7 @@ public function test_it_registers_previous_deployment_on_boot() $atomicDeployment = self::getAtomicDeployment(); // Assert - $this->assertTrue(!empty($atomicDeployment->getInitialDeploymentPath())); + $this->assertTrue(! empty($atomicDeployment->getInitialDeploymentPath())); } public function test_it_creates_a_deployment_directory() @@ -167,4 +167,4 @@ public function test_it_calls_closure_on_failure() $this->assertTrue($failed); Event::assertDispatched(DeploymentFailed::class); } -} \ No newline at end of file +} diff --git a/tests/Feature/Services/DeploymentTest.php b/tests/Feature/Services/DeploymentTest.php index e370a84..baad5aa 100644 --- a/tests/Feature/Services/DeploymentTest.php +++ b/tests/Feature/Services/DeploymentTest.php @@ -114,6 +114,6 @@ public function test_it_updates_model_status() $deployment->updateStatus(DeploymentStatus::SUCCESS); // Assert - $this->assertTrue((int)AtomicDeployment::first()->deployment_status === DeploymentStatus::SUCCESS); + $this->assertTrue((int) AtomicDeployment::first()->deployment_status === DeploymentStatus::SUCCESS); } -} \ No newline at end of file +} diff --git a/tests/TestCase.php b/tests/TestCase.php index fe06f2b..b373ec2 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -11,11 +11,16 @@ abstract class TestCase extends BaseTestCase { - const string TMP_FOLDER = __DIR__ . '/tmp/'; + const string TMP_FOLDER = __DIR__.'/tmp/'; + public string $buildPath; + public string $deploymentLink; + public string $deploymentsPath; + public ?Filesystem $fileSystem = null; + public $mockConsoleOutput = false; protected function setUp(): void @@ -24,28 +29,28 @@ protected function setUp(): void Artisan::call('migrate', ['--database' => 'sqlite']); - $this->fileSystem = new Filesystem(); + $this->fileSystem = new Filesystem; $this->fileSystem->deleteDirectory(self::TMP_FOLDER); $this->fileSystem->makeDirectory(self::TMP_FOLDER); $config = $this->app->config->get('atomic-deployments'); - $this->buildPath = self::TMP_FOLDER . $config['build-path']; - $this->deploymentLink = self::TMP_FOLDER . $config['deployment-link']; - $this->deploymentsPath = self::TMP_FOLDER . $config['deployments-path']; + $this->buildPath = self::TMP_FOLDER.$config['build-path']; + $this->deploymentLink = self::TMP_FOLDER.$config['deployment-link']; + $this->deploymentsPath = self::TMP_FOLDER.$config['deployments-path']; $this->app['config']->set('atomic-deployments.build-path', $this->buildPath); $this->app['config']->set('atomic-deployments.deployment-link', $this->deploymentLink); $this->app['config']->set('atomic-deployments.deployments-path', $this->deploymentsPath); $this->app['config']->set('atomic-deployments.migrate', ['migration/*']); - $this->fileSystem->ensureDirectoryExists($this->buildPath . '/build-contents-folder'); + $this->fileSystem->ensureDirectoryExists($this->buildPath.'/build-contents-folder'); $this->fileSystem->ensureDirectoryExists($this->deploymentsPath); Event::fake(); } - public function tearDown(): void + protected function tearDown(): void { parent::tearDown(); $this->fileSystem->deleteDirectory(self::TMP_FOLDER); @@ -68,21 +73,21 @@ protected function getPackageProviders($app): array protected function seeInConsoleOutput(string|array $searchStrings): void { - $searchStrings = (array)$searchStrings; + $searchStrings = (array) $searchStrings; $output = Artisan::output(); foreach ($searchStrings as $searchString) { - $this->assertStringContainsStringIgnoringCase((string)$searchString, $output); + $this->assertStringContainsStringIgnoringCase((string) $searchString, $output); } } protected function dontSeeInConsoleOutput(string|array $searchStrings): void { - $searchStrings = (array)$searchStrings; + $searchStrings = (array) $searchStrings; $output = Artisan::output(); foreach ($searchStrings as $searchString) { - $this->assertStringNotContainsString((string)$searchString, $output); + $this->assertStringNotContainsString((string) $searchString, $output); } } @@ -90,7 +95,7 @@ public static function getAtomicDeployment(string $hash = ''): AtomicDeploymentS { $atomicDeployment = AtomicDeploymentService::create(); - if (!empty($hash)) { + if (! empty($hash)) { $atomicDeployment->getDeployment()->setDirectory($hash); } @@ -101,4 +106,4 @@ public static function getDeployment(): Deployment { return app(Deployment::class); } -} \ No newline at end of file +} diff --git a/tests/Unit/Enum/EnumTestTrait.php b/tests/Unit/Enum/EnumTestTrait.php index 4541000..d0eb47f 100644 --- a/tests/Unit/Enum/EnumTestTrait.php +++ b/tests/Unit/Enum/EnumTestTrait.php @@ -8,7 +8,7 @@ public function test_it_checks_property_names_and_values_have_not_changed() { // Collect $class = self::model; - $enum = new $class(); + $enum = new $class; // Assert $this->assertTrue(empty(array_diff_assoc(static::expected, $enum->getConstants()))); @@ -18,7 +18,7 @@ public function test_it_can_get_property_from_value() { // Collect $class = self::model; - $enum = new $class(); + $enum = new $class; $props = $enum->getConstants(); $category = array_keys($props)[0]; $value = array_values($props)[0]; @@ -26,4 +26,4 @@ public function test_it_can_get_property_from_value() // Assert $this->assertTrue($enum->getNameFromValue($value) === $category); } -} \ No newline at end of file +} diff --git a/tests/Unit/Helpers/FileHelperTest.php b/tests/Unit/Helpers/FileHelperTest.php index 4e073bc..1338236 100644 --- a/tests/Unit/Helpers/FileHelperTest.php +++ b/tests/Unit/Helpers/FileHelperTest.php @@ -21,13 +21,13 @@ public function test_it_throws_invalid_path_exception_on_invalid_path() public function test_it_updates_symbolic_links_to_new_path() { // Collect - $oldSite = self::TMP_FOLDER . 'build/site'; - $oldContent = $oldSite . '/content'; - $oldLink = $oldSite . '/link'; + $oldSite = self::TMP_FOLDER.'build/site'; + $oldContent = $oldSite.'/content'; + $oldLink = $oldSite.'/link'; - $newSite = self::TMP_FOLDER . 'deployments/site'; - $newContent = $newSite . '/content'; - $newLink = $newSite . '/link'; + $newSite = self::TMP_FOLDER.'deployments/site'; + $newContent = $newSite.'/content'; + $newLink = $newSite.'/link'; $this->fileSystem->ensureDirectoryExists($oldContent); $this->fileSystem->ensureDirectoryExists($newSite); @@ -39,7 +39,7 @@ public function test_it_updates_symbolic_links_to_new_path() $this->assertTrue(Exec::readlink($oldLink) === $oldContent); // Act - Exec::rsync($oldSite . '/', $newSite . '/'); + Exec::rsync($oldSite.'/', $newSite.'/'); // Assert $this->assertTrue(Exec::readlink($newLink) === $oldContent); @@ -50,4 +50,4 @@ public function test_it_updates_symbolic_links_to_new_path() // Assert $this->assertTrue(Exec::readlink($newLink) === $newContent); } -} \ No newline at end of file +} diff --git a/tests/Unit/Services/ExecServiceTest.php b/tests/Unit/Services/ExecServiceTest.php index 6cdcf42..c9f325f 100644 --- a/tests/Unit/Services/ExecServiceTest.php +++ b/tests/Unit/Services/ExecServiceTest.php @@ -19,9 +19,9 @@ public function test_it_can_create_and_read_symbolic_link() public function test_it_can_remote_sync_folders() { // Collect - $from = $this->buildPath . '/to-move'; + $from = $this->buildPath.'/to-move'; $to = $this->deploymentsPath; - $confirm = $this->deploymentsPath . '/to-move'; + $confirm = $this->deploymentsPath.'/to-move'; $this->fileSystem->makeDirectory($from); // Act @@ -30,4 +30,4 @@ public function test_it_can_remote_sync_folders() // Assert $this->assertTrue($this->fileSystem->isDirectory($confirm)); } -} \ No newline at end of file +} From ce124cb0cf16ddcf76fb855f1cc111a246c6d514 Mon Sep 17 00:00:00 2001 From: J-T-McC Date: Thu, 6 Feb 2025 11:50:58 -0800 Subject: [PATCH 3/8] phpstan updates --- composer.json | 3 +- src/Commands/DeployCommand.php | 5 +-- src/Commands/ListCommand.php | 2 +- src/Helpers/ConsoleOutput.php | 9 ++++- src/Models/AtomicDeployment.php | 35 +++++++++++--------- src/Services/AtomicDeploymentService.php | 2 +- tests/Feature/Commands/DeployCommandTest.php | 24 +++++++------- 7 files changed, 47 insertions(+), 33 deletions(-) diff --git a/composer.json b/composer.json index 3ec9624..b1f0c11 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,8 @@ "mockery/mockery": "^1.6.10", "phpunit/phpunit": "^10.5.35|^11.3.6", "orchestra/testbench": "^9.0", - "laravel/pint": "^1.20" + "laravel/pint": "^1.20", + "phpstan/phpstan": "^2.1" }, "autoload": { "psr-4": { diff --git a/src/Commands/DeployCommand.php b/src/Commands/DeployCommand.php index 3db1576..f02f61f 100644 --- a/src/Commands/DeployCommand.php +++ b/src/Commands/DeployCommand.php @@ -18,7 +18,7 @@ class DeployCommand extends BaseCommand protected $description = 'Deploy a clone of your latest build and attach symlink'; - public function handle() + public function handle(): void { Output::alert('Running Atomic Deployment'); @@ -30,7 +30,8 @@ public function handle() $deploymentModel = AtomicDeployment::successful()->where('commit_hash', $hash)->first(); - if (! $deploymentModel || ! $deploymentModel->hasDeployment) { + /** @var AtomicDeployment $deploymentModel */ + if (! $deploymentModel || ! $deploymentModel->has_deployment) { Output::warn("Build not found for hash: {$hash}"); } else { $atomicDeployment = AtomicDeploymentService::create( diff --git a/src/Commands/ListCommand.php b/src/Commands/ListCommand.php index 7c6fd43..3c5b9b0 100644 --- a/src/Commands/ListCommand.php +++ b/src/Commands/ListCommand.php @@ -25,7 +25,7 @@ public function handle() 'deployment_status', 'created_at', )->get()->map(function ($deployment) { - $deployment->append('isCurrentlyDeployed'); + $deployment->append('is_currently_deployed'); $deployment->deployment_status = DeploymentStatus::getNameFromValue($deployment->deployment_status); return $deployment; diff --git a/src/Helpers/ConsoleOutput.php b/src/Helpers/ConsoleOutput.php index eb5faf3..3d857b1 100644 --- a/src/Helpers/ConsoleOutput.php +++ b/src/Helpers/ConsoleOutput.php @@ -4,11 +4,18 @@ use Illuminate\Console\Command; +/** + * @method static void line(string $string) + * @method static void alert(string $string) + * @method static void error(string $string) + * @method static void info(string $string) + * @method static void table(string $string) + */ class ConsoleOutput { public static ?Command $runningCommand = null; - public function setOutput(Command $runningCommand) + public function setOutput(Command $runningCommand): void { static::$runningCommand = $runningCommand; } diff --git a/src/Models/AtomicDeployment.php b/src/Models/AtomicDeployment.php index 3b645c3..b8f5b11 100644 --- a/src/Models/AtomicDeployment.php +++ b/src/Models/AtomicDeployment.php @@ -2,6 +2,8 @@ namespace JTMcC\AtomicDeployments\Models; +use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Facades\File; @@ -9,6 +11,14 @@ use JTMcC\AtomicDeployments\Models\Enums\DeploymentStatus; use JTMcC\AtomicDeployments\Services\Exec; +/** + * @mixin Builder + * + * @method static Builder successful() + * + * @property-read bool $has_deployment + * @property-read bool $is_currently_deployed + */ class AtomicDeployment extends Model { use SoftDeletes; @@ -25,7 +35,7 @@ protected static function boot() { parent::boot(); static::deleting(function ($model) { - if ($model->isCurrentlyDeployed) { + if ($model->is_currently_deployed) { throw new AreYouInsaneException('Cannot delete live deployment'); } $model->deleteDeployment(); @@ -37,28 +47,23 @@ public function scopeSuccessful($query) return $query->where('deployment_status', DeploymentStatus::SUCCESS); } - public function getHasDeploymentAttribute() + public function hasDeployment(): Attribute { - return File::isDirectory($this->deployment_path); + return Attribute::make( + get: fn () => File::isDirectory($this->deployment_path), + ); } - /** - * @return bool - * - * @throws \JTMcC\AtomicDeployments\Exceptions\ExecuteFailedException - */ - public function getIsCurrentlyDeployedAttribute() + public function isCurrentlyDeployed(): Attribute { - if (! $this->hasDeployment) { - return false; - } - - return Exec::readlink($this->deployment_link) === $this->deployment_path; + return Attribute::make( + get: fn () => $this->has_deployment && Exec::readlink($this->deployment_link) === $this->deployment_path, + ); } public function deleteDeployment() { - if ($this->hasDeployment) { + if ($this->has_deployment) { File::deleteDirectory($this->deployment_path); } diff --git a/src/Services/AtomicDeploymentService.php b/src/Services/AtomicDeploymentService.php index a2c3f09..c1f5417 100644 --- a/src/Services/AtomicDeploymentService.php +++ b/src/Services/AtomicDeploymentService.php @@ -317,7 +317,7 @@ public function cleanBuilds(int $limit): void Output::info('Found '.$countOfBuildsToRemove.' '.Pluralizer::plural('folder', $countOfBuildsToRemove).' to be removed'); foreach ($buildsToRemove as $deployment) { - if ($deployment->isCurrentlyDeployed) { + if ($deployment->is_currently_deployed) { Output::warn('Current linked path has appeared in the directory cleaning logic'); Output::warn('This either means you currently have an old build deployed or there is a problem with your deployment data'); Output::warn('Skipping deletion'); diff --git a/tests/Feature/Commands/DeployCommandTest.php b/tests/Feature/Commands/DeployCommandTest.php index bd13681..0882ec2 100644 --- a/tests/Feature/Commands/DeployCommandTest.php +++ b/tests/Feature/Commands/DeployCommandTest.php @@ -116,14 +116,14 @@ public function test_it_allows_swapping_between_deployments() Artisan::call('atomic-deployments:deploy --directory=test-dir-1'); Artisan::call('atomic-deployments:deploy --directory=test-dir-2'); $deployment1 = AtomicDeployment::where('commit_hash', 'test-dir-1')->first()->append( - 'isCurrentlyDeployed' + 'is_currently_deployed' )->toArray(); $deployment2 = AtomicDeployment::where('commit_hash', 'test-dir-2')->first()->append( - 'isCurrentlyDeployed' + 'is_currently_deployed' )->toArray(); - $this->assertFalse($deployment1['isCurrentlyDeployed']); - $this->assertTrue($deployment2['isCurrentlyDeployed']); + $this->assertFalse($deployment1['is_currently_deployed']); + $this->assertTrue($deployment2['is_currently_deployed']); // Act Artisan::call('atomic-deployments:deploy --hash=test-dir-fake'); @@ -144,14 +144,14 @@ public function test_it_allows_swapping_between_deployments() ]); $deployment1 = AtomicDeployment::where('commit_hash', 'test-dir-1')->first()->append( - 'isCurrentlyDeployed' + 'is_currently_deployed' )->toArray(); $deployment2 = AtomicDeployment::where('commit_hash', 'test-dir-2')->first()->append( - 'isCurrentlyDeployed' + 'is_currently_deployed' )->toArray(); - $this->assertTrue($deployment1['isCurrentlyDeployed']); - $this->assertFalse($deployment2['isCurrentlyDeployed']); + $this->assertTrue($deployment1['is_currently_deployed']); + $this->assertFalse($deployment2['is_currently_deployed']); } public function test_it_cleans_old_build_folders_based_on_build_limit() @@ -190,18 +190,18 @@ public function test_it_dispatches_deployment_successful_event_on_deployment_swa Artisan::call('atomic-deployments:deploy --directory=test-dir-1'); Artisan::call('atomic-deployments:deploy --directory=test-dir-2'); $deployment = AtomicDeployment::where('commit_hash', 'test-dir-2')->first()->append( - 'isCurrentlyDeployed' + 'is_currently_deployed' )->toArray(); - $this->assertTrue($deployment['isCurrentlyDeployed']); + $this->assertTrue($deployment['is_currently_deployed']); // Act Artisan::call('atomic-deployments:deploy --hash=test-dir-1'); // Assert $deployment = AtomicDeployment::where('commit_hash', 'test-dir-1')->first()->append( - 'isCurrentlyDeployed' + 'is_currently_deployed' )->toArray(); - $this->assertTrue($deployment['isCurrentlyDeployed']); + $this->assertTrue($deployment['is_currently_deployed']); Event::assertDispatched(DeploymentSuccessful::class); } From d328a1ced310ea2dc90da48f1f0a4e2966baa372 Mon Sep 17 00:00:00 2001 From: J-T-McC Date: Thu, 6 Feb 2025 12:13:42 -0800 Subject: [PATCH 4/8] phpstan updates --- src/Commands/ListCommand.php | 6 +++--- src/Helpers/ConsoleOutput.php | 3 ++- src/Interfaces/DeploymentInterface.php | 6 ++++++ src/Models/AtomicDeployment.php | 5 +++++ src/Services/AtomicDeploymentService.php | 6 +++++- src/Services/Deployment.php | 18 ++++++------------ 6 files changed, 27 insertions(+), 17 deletions(-) diff --git a/src/Commands/ListCommand.php b/src/Commands/ListCommand.php index 3c5b9b0..dd91089 100644 --- a/src/Commands/ListCommand.php +++ b/src/Commands/ListCommand.php @@ -12,19 +12,19 @@ class ListCommand extends BaseCommand protected $description = 'List currently available deployments'; - public function handle() + public function handle(): void { ConsoleOutput::line(''); ConsoleOutput::alert('Available Deployments'); - $deployments = AtomicDeployment::select( + $deployments = AtomicDeployment::query()->select([ 'id', 'commit_hash', 'deployment_path', 'deployment_link', 'deployment_status', 'created_at', - )->get()->map(function ($deployment) { + ])->get()->map(function (AtomicDeployment $deployment) { $deployment->append('is_currently_deployed'); $deployment->deployment_status = DeploymentStatus::getNameFromValue($deployment->deployment_status); diff --git a/src/Helpers/ConsoleOutput.php b/src/Helpers/ConsoleOutput.php index 3d857b1..04f3ae8 100644 --- a/src/Helpers/ConsoleOutput.php +++ b/src/Helpers/ConsoleOutput.php @@ -6,10 +6,11 @@ /** * @method static void line(string $string) + * @method static void warn(string $string) * @method static void alert(string $string) * @method static void error(string $string) * @method static void info(string $string) - * @method static void table(string $string) + * @method static void table(array $titles, array $rows) */ class ConsoleOutput { diff --git a/src/Interfaces/DeploymentInterface.php b/src/Interfaces/DeploymentInterface.php index bde32ab..17dd40d 100644 --- a/src/Interfaces/DeploymentInterface.php +++ b/src/Interfaces/DeploymentInterface.php @@ -23,4 +23,10 @@ public function getLink(); public function getModel(); public function updateStatus(int $status); + + public function isDeployed(): bool; + + public function getDirectoryName(): string; + + public function createDirectory(); } diff --git a/src/Models/AtomicDeployment.php b/src/Models/AtomicDeployment.php index b8f5b11..fb707a0 100644 --- a/src/Models/AtomicDeployment.php +++ b/src/Models/AtomicDeployment.php @@ -18,6 +18,11 @@ * * @property-read bool $has_deployment * @property-read bool $is_currently_deployed + * @property string $commit_hash + * @property int $deployment_status + * @property string $build_path + * @property string $deployment_path + * @property string $deployment_link */ class AtomicDeployment extends Model { diff --git a/src/Services/AtomicDeploymentService.php b/src/Services/AtomicDeploymentService.php index c1f5417..490e54d 100644 --- a/src/Services/AtomicDeploymentService.php +++ b/src/Services/AtomicDeploymentService.php @@ -5,6 +5,7 @@ namespace JTMcC\AtomicDeployments\Services; use Closure; +use Illuminate\Database\Eloquent\Collection; use Illuminate\Support\Facades\File; use Illuminate\Support\Pluralizer; use JTMcC\AtomicDeployments\Events\DeploymentFailed; @@ -310,13 +311,15 @@ public function cleanBuilds(int $limit): void ->limit($limit) ->pluck('id'); - $buildsToRemove = AtomicDeployment::whereNotIn('id', $buildIDs)->get(); + /* @var Collection $buildsToRemove */ + $buildsToRemove = AtomicDeployment::query()->whereNotIn('id', $buildIDs)->get(); $countOfBuildsToRemove = $buildsToRemove->count(); Output::info('Found '.$countOfBuildsToRemove.' '.Pluralizer::plural('folder', $countOfBuildsToRemove).' to be removed'); foreach ($buildsToRemove as $deployment) { + if ($deployment->is_currently_deployed) { Output::warn('Current linked path has appeared in the directory cleaning logic'); Output::warn('This either means you currently have an old build deployed or there is a problem with your deployment data'); @@ -328,6 +331,7 @@ public function cleanBuilds(int $limit): void Output::info("Deleting {$deployment->commit_hash}"); if (! $this->isDryRun()) { + // @phpstan-ignore-next-line $deployment->delete(); Output::info('Deployment deleted'); } else { diff --git a/src/Services/Deployment.php b/src/Services/Deployment.php index e3bb783..aada502 100644 --- a/src/Services/Deployment.php +++ b/src/Services/Deployment.php @@ -94,21 +94,15 @@ public function getCurrentPath(): string } /** - * @return string - * * @throws ExecuteFailedException */ - public function getDirectoryName() + public function getDirectoryName(): string { - switch ($this->directoryNaming) { - case 'datetime': - return Carbon::now()->format('Y-m-d_H-i-s'); - case 'rand': - return Str::random(5).time(); - case 'git': - default: - return Exec::getGitHash(); - } + return match ($this->directoryNaming) { + 'datetime' => Carbon::now()->format('Y-m-d_H-i-s'), + 'rand' => Str::random(5).time(), + default => Exec::getGitHash(), + }; } /** From 664ef9d32323bc6a526c09b5225e71a4c772c84c Mon Sep 17 00:00:00 2001 From: J-T-McC Date: Thu, 6 Feb 2025 12:22:41 -0800 Subject: [PATCH 5/8] phpstan updates --- src/Commands/DeployCommand.php | 4 +- src/Commands/ListCommand.php | 2 - src/Interfaces/DeploymentInterface.php | 4 +- src/Models/AtomicDeployment.php | 4 ++ src/Models/Enums/DeploymentStatus.php | 10 ++--- src/Models/Enums/Enum.php | 39 ------------------- src/Services/AtomicDeploymentService.php | 2 +- src/Services/Deployment.php | 3 +- tests/Feature/Commands/DeployCommandTest.php | 2 +- .../Services/AtomicDeploymentServiceTest.php | 2 +- tests/Feature/Services/DeploymentTest.php | 2 +- tests/Unit/Enum/DeploymentStatusTest.php | 19 --------- tests/Unit/Enum/EnumTestTrait.php | 29 -------------- 13 files changed, 19 insertions(+), 103 deletions(-) delete mode 100644 src/Models/Enums/Enum.php delete mode 100644 tests/Unit/Enum/DeploymentStatusTest.php delete mode 100644 tests/Unit/Enum/EnumTestTrait.php diff --git a/src/Commands/DeployCommand.php b/src/Commands/DeployCommand.php index f02f61f..3b3eabe 100644 --- a/src/Commands/DeployCommand.php +++ b/src/Commands/DeployCommand.php @@ -30,8 +30,8 @@ public function handle(): void $deploymentModel = AtomicDeployment::successful()->where('commit_hash', $hash)->first(); - /** @var AtomicDeployment $deploymentModel */ - if (! $deploymentModel || ! $deploymentModel->has_deployment) { + /** @var null|AtomicDeployment $deploymentModel */ + if (! $deploymentModel?->has_deployment) { Output::warn("Build not found for hash: {$hash}"); } else { $atomicDeployment = AtomicDeploymentService::create( diff --git a/src/Commands/ListCommand.php b/src/Commands/ListCommand.php index dd91089..9010ca1 100644 --- a/src/Commands/ListCommand.php +++ b/src/Commands/ListCommand.php @@ -4,7 +4,6 @@ use JTMcC\AtomicDeployments\Helpers\ConsoleOutput; use JTMcC\AtomicDeployments\Models\AtomicDeployment; -use JTMcC\AtomicDeployments\Models\Enums\DeploymentStatus; class ListCommand extends BaseCommand { @@ -26,7 +25,6 @@ public function handle(): void 'created_at', ])->get()->map(function (AtomicDeployment $deployment) { $deployment->append('is_currently_deployed'); - $deployment->deployment_status = DeploymentStatus::getNameFromValue($deployment->deployment_status); return $deployment; }); diff --git a/src/Interfaces/DeploymentInterface.php b/src/Interfaces/DeploymentInterface.php index 17dd40d..6728bc0 100644 --- a/src/Interfaces/DeploymentInterface.php +++ b/src/Interfaces/DeploymentInterface.php @@ -2,6 +2,8 @@ namespace JTMcC\AtomicDeployments\Interfaces; +use JTMcC\AtomicDeployments\Models\Enums\DeploymentStatus; + interface DeploymentInterface { public function getBuildPath(); @@ -22,7 +24,7 @@ public function getLink(); public function getModel(); - public function updateStatus(int $status); + public function updateStatus(DeploymentStatus $status); public function isDeployed(): bool; diff --git a/src/Models/AtomicDeployment.php b/src/Models/AtomicDeployment.php index fb707a0..83da6c5 100644 --- a/src/Models/AtomicDeployment.php +++ b/src/Models/AtomicDeployment.php @@ -36,6 +36,10 @@ class AtomicDeployment extends Model 'deployment_link', ]; + protected $casts = [ + 'deployment_status' => DeploymentStatus::class, + ]; + protected static function boot() { parent::boot(); diff --git a/src/Models/Enums/DeploymentStatus.php b/src/Models/Enums/DeploymentStatus.php index cf920f3..00590ba 100644 --- a/src/Models/Enums/DeploymentStatus.php +++ b/src/Models/Enums/DeploymentStatus.php @@ -2,11 +2,9 @@ namespace JTMcC\AtomicDeployments\Models\Enums; -class DeploymentStatus extends Enum +enum DeploymentStatus: int { - const FAILED = 0; - - const RUNNING = 1; - - const SUCCESS = 2; + case FAILED = 0; + case RUNNING = 1; + case SUCCESS = 2; } diff --git a/src/Models/Enums/Enum.php b/src/Models/Enums/Enum.php deleted file mode 100644 index ef7f6c0..0000000 --- a/src/Models/Enums/Enum.php +++ /dev/null @@ -1,39 +0,0 @@ -getConstants(); - } - - return self::$constCacheArray[$calledClass]; - } - - /** - * @return false|int|string|null - * - * @throws \ReflectionException - */ - public static function getNameFromValue(int $value) - { - return array_search($value, self::getConstants(), true) ?? null; - } -} diff --git a/src/Services/AtomicDeploymentService.php b/src/Services/AtomicDeploymentService.php index 490e54d..60135bd 100644 --- a/src/Services/AtomicDeploymentService.php +++ b/src/Services/AtomicDeploymentService.php @@ -100,7 +100,7 @@ public function deploy(?Closure $successCallback = null, ?Closure $failedCallbac } } - public function updateDeploymentStatus(int $status): void + public function updateDeploymentStatus(DeploymentStatus $status): void { if ($this->isDryRun()) { Output::warn('Dry run - Skipping deployment status update'); diff --git a/src/Services/Deployment.php b/src/Services/Deployment.php index aada502..cb6f780 100644 --- a/src/Services/Deployment.php +++ b/src/Services/Deployment.php @@ -12,6 +12,7 @@ use JTMcC\AtomicDeployments\Helpers\FileHelper; use JTMcC\AtomicDeployments\Interfaces\DeploymentInterface; use JTMcC\AtomicDeployments\Models\AtomicDeployment; +use JTMcC\AtomicDeployments\Models\Enums\DeploymentStatus; class Deployment implements DeploymentInterface { @@ -143,7 +144,7 @@ public function getPath(): string * @throws ExecuteFailedException * @throws InvalidPathException */ - public function updateStatus(int $status): void + public function updateStatus(DeploymentStatus $status): void { $this->model->updateOrCreate( ['deployment_path' => $this->getPath()], diff --git a/tests/Feature/Commands/DeployCommandTest.php b/tests/Feature/Commands/DeployCommandTest.php index 0882ec2..dc0fc60 100644 --- a/tests/Feature/Commands/DeployCommandTest.php +++ b/tests/Feature/Commands/DeployCommandTest.php @@ -84,7 +84,7 @@ public function test_it_allows_run_with_mutations() $deployment = AtomicDeployment::first(); $this->assertNotEmpty($deployment); - $this->assertTrue((int) $deployment->deployment_status === DeploymentStatus::SUCCESS); + $this->assertTrue($deployment->deployment_status === DeploymentStatus::SUCCESS); } public function test_it_allows_migrate_on_run() diff --git a/tests/Feature/Services/AtomicDeploymentServiceTest.php b/tests/Feature/Services/AtomicDeploymentServiceTest.php index 02c8d89..5c1e89d 100644 --- a/tests/Feature/Services/AtomicDeploymentServiceTest.php +++ b/tests/Feature/Services/AtomicDeploymentServiceTest.php @@ -78,7 +78,7 @@ public function test_it_updates_deployment_status_record() // Assert $record = AtomicDeployment::where('commit_hash', $hash)->first(); - $this->assertTrue((int) $record->deployment_status === DeploymentStatus::RUNNING); + $this->assertTrue($record->deployment_status === DeploymentStatus::RUNNING); } public function test_it_confirms_symbolic_link() diff --git a/tests/Feature/Services/DeploymentTest.php b/tests/Feature/Services/DeploymentTest.php index baad5aa..d813cf5 100644 --- a/tests/Feature/Services/DeploymentTest.php +++ b/tests/Feature/Services/DeploymentTest.php @@ -114,6 +114,6 @@ public function test_it_updates_model_status() $deployment->updateStatus(DeploymentStatus::SUCCESS); // Assert - $this->assertTrue((int) AtomicDeployment::first()->deployment_status === DeploymentStatus::SUCCESS); + $this->assertTrue(AtomicDeployment::first()->deployment_status === DeploymentStatus::SUCCESS); } } diff --git a/tests/Unit/Enum/DeploymentStatusTest.php b/tests/Unit/Enum/DeploymentStatusTest.php deleted file mode 100644 index 47f1fa1..0000000 --- a/tests/Unit/Enum/DeploymentStatusTest.php +++ /dev/null @@ -1,19 +0,0 @@ - 0, - 'RUNNING' => 1, - 'SUCCESS' => 2, - ]; - - const model = DeploymentStatus::class; -} diff --git a/tests/Unit/Enum/EnumTestTrait.php b/tests/Unit/Enum/EnumTestTrait.php deleted file mode 100644 index d0eb47f..0000000 --- a/tests/Unit/Enum/EnumTestTrait.php +++ /dev/null @@ -1,29 +0,0 @@ -assertTrue(empty(array_diff_assoc(static::expected, $enum->getConstants()))); - } - - public function test_it_can_get_property_from_value() - { - // Collect - $class = self::model; - $enum = new $class; - $props = $enum->getConstants(); - $category = array_keys($props)[0]; - $value = array_values($props)[0]; - - // Assert - $this->assertTrue($enum->getNameFromValue($value) === $category); - } -} From 30f816ec73c299884ff4b9f56e4c17828731217a Mon Sep 17 00:00:00 2001 From: J-T-McC Date: Thu, 6 Feb 2025 12:56:08 -0800 Subject: [PATCH 6/8] formatting updates --- src/AtomicDeploymentsServiceProvider.php | 4 +- src/Commands/DeployCommand.php | 95 +++++++++++++---------- src/Commands/ListCommand.php | 28 ++++--- src/Exceptions/AreYouInsaneException.php | 2 +- src/Exceptions/ExecuteFailedException.php | 2 +- src/Exceptions/InvalidPathException.php | 2 +- src/Helpers/ConsoleOutput.php | 5 +- src/Helpers/FileHelper.php | 5 +- src/Interfaces/DeploymentInterface.php | 23 +++--- src/Models/AtomicDeployment.php | 10 +-- src/Services/AtomicDeploymentService.php | 50 ++++++------ src/Services/Deployment.php | 2 +- src/Services/Exec.php | 28 ++----- 13 files changed, 130 insertions(+), 126 deletions(-) diff --git a/src/AtomicDeploymentsServiceProvider.php b/src/AtomicDeploymentsServiceProvider.php index 01699af..4d19162 100644 --- a/src/AtomicDeploymentsServiceProvider.php +++ b/src/AtomicDeploymentsServiceProvider.php @@ -10,12 +10,12 @@ class AtomicDeploymentsServiceProvider extends ServiceProvider { - public function boot() + public function boot(): void { $this->loadMigrationsFrom(__DIR__.'/../database/migrations'); } - public function register() + public function register(): void { $this->mergeConfigFrom(__DIR__.'/../config/atomic-deployments.php', 'atomic-deployments'); $this->registerPublishables(); diff --git a/src/Commands/DeployCommand.php b/src/Commands/DeployCommand.php index 3b3eabe..f3e9400 100644 --- a/src/Commands/DeployCommand.php +++ b/src/Commands/DeployCommand.php @@ -8,13 +8,14 @@ use JTMcC\AtomicDeployments\Services\AtomicDeploymentService; use JTMcC\AtomicDeployments\Services\Deployment; use JTMcC\AtomicDeployments\Services\Output; +use Throwable; class DeployCommand extends BaseCommand { - protected $signature = 'atomic-deployments:deploy - {--hash= : Specify a previous deployments commit hash/deploy-dir to deploy } - {--directory= : Define your deploy folder name. Defaults to current HEAD hash } - {--dry-run : Test and log deployment steps }'; + protected $signature = 'atomic-deployments:deploy + {--hash= : Specify a previous deployments commit hash/deploy-dir to deploy } + {--directory= : Define your deploy folder name. Defaults to current HEAD hash } + {--dry-run : Test and log deployment steps }'; protected $description = 'Deploy a clone of your latest build and attach symlink'; @@ -26,47 +27,59 @@ public function handle(): void $dryRun = $this->option('dry-run'); if ($hash = $this->option('hash')) { - Output::info("Updating symlink to previous build: {$hash}"); - - $deploymentModel = AtomicDeployment::successful()->where('commit_hash', $hash)->first(); - - /** @var null|AtomicDeployment $deploymentModel */ - if (! $deploymentModel?->has_deployment) { - Output::warn("Build not found for hash: {$hash}"); - } else { - $atomicDeployment = AtomicDeploymentService::create( - new Deployment($deploymentModel), - $migrate, - $dryRun - ); - - try { - $atomicDeployment->getDeployment()->link(); - $atomicDeployment->confirmSymbolicLink(); - DeploymentSuccessful::dispatch($atomicDeployment, $deploymentModel); - } catch (\Throwable $e) { - $atomicDeployment->fail(); - Output::throwable($e); - } - } + $this->deployPreviousBuild($hash, $migrate, $dryRun); } else { - $atomicDeployment = AtomicDeploymentService::create($migrate, $dryRun); - - Output::info('Running Deployment...'); - - try { - if ($deployDir = trim($this->option('directory'))) { - Output::info("Deployment directory option set - Deployment will use directory: {$deployDir} "); - $atomicDeployment->getDeployment()->setDirectory($deployDir); - } - $atomicDeployment->deploy(fn () => $atomicDeployment->cleanBuilds(config('atomic-deployments.build-limit'))); - } catch (\Throwable $e) { - $atomicDeployment->fail(); - Output::throwable($e); - } + $this->deployCurrentBuild($migrate, $dryRun); } Output::info('Finished'); ConsoleOutput::line(''); } + + private function deployPreviousBuild(string $hash, array $migrate, bool $dryRun): void + { + Output::info("Updating symlink to previous build: {$hash}"); + + $deploymentModel = AtomicDeployment::successful()->where('commit_hash', $hash)->first(); + + if (! $deploymentModel?->has_deployment) { + Output::warn("Build not found for hash: {$hash}"); + + return; + } + + $atomicDeployment = AtomicDeploymentService::create( + new Deployment($deploymentModel), + $migrate, + $dryRun + ); + + try { + $atomicDeployment->getDeployment()->link(); + $atomicDeployment->confirmSymbolicLink(); + DeploymentSuccessful::dispatch($atomicDeployment, $deploymentModel); + } catch (Throwable $e) { + $atomicDeployment->fail(); + Output::throwable($e); + } + } + + private function deployCurrentBuild(array $migrate, bool $dryRun): void + { + $atomicDeployment = AtomicDeploymentService::create($migrate, $dryRun); + + Output::info('Running Deployment...'); + + try { + if ($deployDir = trim($this->option('directory'))) { + Output::info("Deployment directory option set - Deployment will use directory: {$deployDir}"); + $atomicDeployment->getDeployment()->setDirectory($deployDir); + } + $atomicDeployment->deploy(fn () => $atomicDeployment->cleanBuilds(config('atomic-deployments.build-limit')) + ); + } catch (Throwable $e) { + $atomicDeployment->fail(); + Output::throwable($e); + } + } } diff --git a/src/Commands/ListCommand.php b/src/Commands/ListCommand.php index 9010ca1..563a92a 100644 --- a/src/Commands/ListCommand.php +++ b/src/Commands/ListCommand.php @@ -16,18 +16,22 @@ public function handle(): void ConsoleOutput::line(''); ConsoleOutput::alert('Available Deployments'); - $deployments = AtomicDeployment::query()->select([ - 'id', - 'commit_hash', - 'deployment_path', - 'deployment_link', - 'deployment_status', - 'created_at', - ])->get()->map(function (AtomicDeployment $deployment) { - $deployment->append('is_currently_deployed'); - - return $deployment; - }); + $deployments = AtomicDeployment::query() + ->select([ + 'id', + 'commit_hash', + 'deployment_path', + 'deployment_link', + 'deployment_status', + 'created_at', + ]) + ->get() + // @phpstan-ignore-next-line + ->map(function (AtomicDeployment $deployment) { + $deployment->append('is_currently_deployed'); + + return $deployment; + }); if (! $deployments->count()) { ConsoleOutput::info('No deployments found'); diff --git a/src/Exceptions/AreYouInsaneException.php b/src/Exceptions/AreYouInsaneException.php index 1b46f8b..fd9c477 100644 --- a/src/Exceptions/AreYouInsaneException.php +++ b/src/Exceptions/AreYouInsaneException.php @@ -7,7 +7,7 @@ class AreYouInsaneException extends Exception { - public function __construct($message = '', $code = 0, ?Throwable $previous = null) + public function __construct(string $message = '', int $code = 0, ?Throwable $previous = null) { parent::__construct("(╯°□°)╯︵ ┻━┻: {$message}", $code, $previous); } diff --git a/src/Exceptions/ExecuteFailedException.php b/src/Exceptions/ExecuteFailedException.php index 626aaa0..478753e 100644 --- a/src/Exceptions/ExecuteFailedException.php +++ b/src/Exceptions/ExecuteFailedException.php @@ -7,7 +7,7 @@ class ExecuteFailedException extends Exception { - public function __construct($message = '', $code = 0, ?Throwable $previous = null) + public function __construct(string $message = '', int $code = 0, ?Throwable $previous = null) { parent::__construct("exec failed: {$message}", $code, $previous); } diff --git a/src/Exceptions/InvalidPathException.php b/src/Exceptions/InvalidPathException.php index da4125b..d054273 100644 --- a/src/Exceptions/InvalidPathException.php +++ b/src/Exceptions/InvalidPathException.php @@ -7,7 +7,7 @@ class InvalidPathException extends Exception { - public function __construct($message = '', $code = 0, ?Throwable $previous = null) + public function __construct(string $message = '', int $code = 0, ?Throwable $previous = null) { parent::__construct("Invalid Path: {$message}", $code, $previous); } diff --git a/src/Helpers/ConsoleOutput.php b/src/Helpers/ConsoleOutput.php index 04f3ae8..98a3ae7 100644 --- a/src/Helpers/ConsoleOutput.php +++ b/src/Helpers/ConsoleOutput.php @@ -3,6 +3,7 @@ namespace JTMcC\AtomicDeployments\Helpers; use Illuminate\Console\Command; +use Illuminate\Database\Eloquent\Collection; /** * @method static void line(string $string) @@ -10,7 +11,7 @@ * @method static void alert(string $string) * @method static void error(string $string) * @method static void info(string $string) - * @method static void table(array $titles, array $rows) + * @method static void table(string[] $titles, Collection $rows) */ class ConsoleOutput { @@ -21,7 +22,7 @@ public function setOutput(Command $runningCommand): void static::$runningCommand = $runningCommand; } - public static function __callStatic(string $method, $arguments) + public static function __callStatic(string $method, array $arguments) { if (! static::$runningCommand) { return; diff --git a/src/Helpers/FileHelper.php b/src/Helpers/FileHelper.php index 7695153..f480a25 100644 --- a/src/Helpers/FileHelper.php +++ b/src/Helpers/FileHelper.php @@ -28,17 +28,18 @@ public static function confirmPathsExist(string ...$paths): bool /** * Recursively update symbolic links with new endpoint. * - * * @throws ExecuteFailedException */ - public static function recursivelyUpdateSymlinks($from, $to) + public static function recursivelyUpdateSymlinks(string $from, string $to): void { $dir = new RecursiveDirectoryIterator($to); + foreach (new RecursiveIteratorIterator($dir) as $file) { if (is_link($file)) { $link = $file->getPathName(); $target = $file->getLinkTarget(); $newPath = str_replace($from, $to, $target); + if ($target !== $newPath) { Exec::ln($link, $newPath); } diff --git a/src/Interfaces/DeploymentInterface.php b/src/Interfaces/DeploymentInterface.php index 6728bc0..f9e384c 100644 --- a/src/Interfaces/DeploymentInterface.php +++ b/src/Interfaces/DeploymentInterface.php @@ -2,33 +2,34 @@ namespace JTMcC\AtomicDeployments\Interfaces; +use JTMcC\AtomicDeployments\Models\AtomicDeployment; use JTMcC\AtomicDeployments\Models\Enums\DeploymentStatus; interface DeploymentInterface { - public function getBuildPath(); + public function getBuildPath(): string; - public function setDirectory(string $name = ''); + public function setDirectory(string $name = ''): void; - public function setPath(); + public function setPath(): void; - public function getPath(); + public function getPath(): string; - public function getCurrentPath(); + public function getCurrentPath(): string; - public function copyContents(); + public function copyContents(): void; - public function link(); + public function link(): void; - public function getLink(); + public function getLink(): string; - public function getModel(); + public function getModel(): AtomicDeployment; - public function updateStatus(DeploymentStatus $status); + public function updateStatus(DeploymentStatus $status): void; public function isDeployed(): bool; public function getDirectoryName(): string; - public function createDirectory(); + public function createDirectory(): void; } diff --git a/src/Models/AtomicDeployment.php b/src/Models/AtomicDeployment.php index 83da6c5..cd35a89 100644 --- a/src/Models/AtomicDeployment.php +++ b/src/Models/AtomicDeployment.php @@ -43,10 +43,12 @@ class AtomicDeployment extends Model protected static function boot() { parent::boot(); + static::deleting(function ($model) { if ($model->is_currently_deployed) { throw new AreYouInsaneException('Cannot delete live deployment'); } + $model->deleteDeployment(); }); } @@ -56,26 +58,24 @@ public function scopeSuccessful($query) return $query->where('deployment_status', DeploymentStatus::SUCCESS); } - public function hasDeployment(): Attribute + protected function hasDeployment(): Attribute { return Attribute::make( get: fn () => File::isDirectory($this->deployment_path), ); } - public function isCurrentlyDeployed(): Attribute + protected function isCurrentlyDeployed(): Attribute { return Attribute::make( get: fn () => $this->has_deployment && Exec::readlink($this->deployment_link) === $this->deployment_path, ); } - public function deleteDeployment() + public function deleteDeployment(): void { if ($this->has_deployment) { File::deleteDirectory($this->deployment_path); } - - return $this; } } diff --git a/src/Services/AtomicDeploymentService.php b/src/Services/AtomicDeploymentService.php index 60135bd..144d79b 100644 --- a/src/Services/AtomicDeploymentService.php +++ b/src/Services/AtomicDeploymentService.php @@ -15,6 +15,7 @@ use JTMcC\AtomicDeployments\Interfaces\DeploymentInterface; use JTMcC\AtomicDeployments\Models\AtomicDeployment; use JTMcC\AtomicDeployments\Models\Enums\DeploymentStatus; +use Throwable; class AtomicDeploymentService { @@ -28,9 +29,8 @@ class AtomicDeploymentService /** * @param mixed ...$args - * @return self */ - public static function create(...$args) + public static function create(...$args): self { return app(static::class, $args); } @@ -46,10 +46,7 @@ public function __construct(DeploymentInterface $deployment, array $migrate = [] $this->initialDeploymentPath = $deployment->getCurrentPath(); } - /** - * @return DeploymentInterface - */ - public function getDeployment() + public function getDeployment(): DeploymentInterface { return $this->deployment; } @@ -59,9 +56,6 @@ public function getInitialDeploymentPath(): string return $this->initialDeploymentPath; } - /** - * Run full deployment. - */ public function deploy(?Closure $successCallback = null, ?Closure $failedCallback = null): void { try { @@ -72,7 +66,7 @@ public function deploy(?Closure $successCallback = null, ?Closure $failedCallbac Output::info('Checking for previous deployment'); Output::info($this->initialDeploymentPath ? - "Previous deployment detected at {$this->initialDeploymentPath}" : + sprintf('Previous deployment detected at %s', $this->initialDeploymentPath) : 'No previous deployment detected for this link'); $this->updateDeploymentStatus(DeploymentStatus::RUNNING); @@ -91,9 +85,10 @@ public function deploy(?Closure $successCallback = null, ?Closure $failedCallbac if ($successCallback) { $successCallback($this); } - } catch (\Throwable $e) { + } catch (Throwable $e) { $this->fail(); Output::throwable($e); + if ($failedCallback) { $failedCallback($this); } @@ -112,7 +107,7 @@ public function updateDeploymentStatus(DeploymentStatus $status): void public function linkDeployment(): void { - Output::info("Creating symbolic link: {$this->deployment->getLink()} -> {$this->deployment->getPath()}"); + Output::info(sprintf('Creating symbolic link: %s -> %s', $this->deployment->getLink(), $this->deployment->getPath())); if ($this->isDryRun()) { Output::warn('Dry run - Skipping symbolic link deployment'); @@ -137,9 +132,11 @@ public function confirmSymbolicLink(): bool if (! $this->deployment->isDeployed()) { throw new ExecuteFailedException( - 'Expected deployment link to direct to '. - $this->deployment->getPath().' but found '. - $this->deployment->getCurrentPath() + sprintf( + 'Expected deployment link to direct to %s but found %s', + $this->deployment->getPath(), + $this->deployment->getCurrentPath() + ) ); } @@ -150,7 +147,7 @@ public function confirmSymbolicLink(): bool public function createDeploymentDirectory(): void { - Output::info("Creating directory at {$this->deployment->getPath()}"); + Output::info(sprintf('Creating directory at %s', $this->deployment->getPath())); if ($this->isDryRun()) { Output::warn('Dry run - Skipping creating deployment directory'); @@ -188,9 +185,9 @@ public function copyMigrationContents(): void Output::warn('Dry run - skipping migrations'); } - collect($this->migrate)->each(function ($pattern) { + collect($this->migrate)->each(function (string $pattern): void { if (! $this->isDryRun()) { - Output::info("Running migration for pattern {$pattern}"); + Output::info(sprintf('Running migration for pattern %s', $pattern)); } $rootFrom = rtrim($this->initialDeploymentPath, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR; @@ -207,7 +204,7 @@ public function copyMigrationContents(): void $to = str_replace($rootFrom, $rootTo, $from); if ($this->isDryRun()) { - Output::warn("Dry run - migrate: \r\n - {$from}\r\n - {$to}"); + Output::warn(sprintf("Dry run - migrate: \r\n - %s\r\n - %s", $from, $to)); Output::line(); continue; @@ -219,7 +216,7 @@ public function copyMigrationContents(): void } if (! $this->isDryRun()) { - Output::info("Finished migration for pattern {$pattern}"); + Output::info(sprintf('Finished migration for pattern %s', $pattern)); } }); } @@ -228,7 +225,7 @@ public function copyMigrationContents(): void /** * @throws ExecuteFailedException */ - public function updateSymlinks() + public function updateSymlinks(): void { Output::info('Correcting old symlinks that still reference the build directory'); @@ -258,7 +255,7 @@ public function rollback(): void $this->initialDeploymentPath && $this->initialDeploymentPath !== $currentPath ) { - Output::emergency("Attempting to link deployment at {$this->initialDeploymentPath}"); + Output::emergency(sprintf('Attempting to link deployment at %s', $this->initialDeploymentPath)); try { // attempt to revert link to our original path @@ -295,8 +292,9 @@ public function fail(): void public function shutdown(): void { - if ($error = error_get_last()) { + if (error_get_last()) { Output::error('Error detected during shutdown, requesting rollback'); + $this->fail(); } } @@ -304,7 +302,7 @@ public function shutdown(): void public function cleanBuilds(int $limit): void { Output::alert('Running Build Cleanup'); - Output::info("Max deployment directories allowed set to {$limit}"); + Output::info(sprintf('Max deployment directories allowed set to %d', $limit)); $buildIDs = AtomicDeployment::successful() ->orderBy('id', 'desc') @@ -316,7 +314,7 @@ public function cleanBuilds(int $limit): void $countOfBuildsToRemove = $buildsToRemove->count(); - Output::info('Found '.$countOfBuildsToRemove.' '.Pluralizer::plural('folder', $countOfBuildsToRemove).' to be removed'); + Output::info(sprintf('Found %d %s to be removed', $countOfBuildsToRemove, Pluralizer::plural('folder', $countOfBuildsToRemove))); foreach ($buildsToRemove as $deployment) { @@ -328,7 +326,7 @@ public function cleanBuilds(int $limit): void return; } - Output::info("Deleting {$deployment->commit_hash}"); + Output::info(sprintf('Deleting %s', $deployment->commit_hash)); if (! $this->isDryRun()) { // @phpstan-ignore-next-line diff --git a/src/Services/Deployment.php b/src/Services/Deployment.php index cb6f780..1585d6b 100644 --- a/src/Services/Deployment.php +++ b/src/Services/Deployment.php @@ -161,7 +161,7 @@ public function updateStatus(DeploymentStatus $status): void * @throws ExecuteFailedException * @throws InvalidPathException */ - public function copyContents() + public function copyContents(): void { FileHelper::confirmPathsExist( $this->buildPath, diff --git a/src/Services/Exec.php b/src/Services/Exec.php index 8c39a9c..4ae60ca 100644 --- a/src/Services/Exec.php +++ b/src/Services/Exec.php @@ -7,66 +7,52 @@ class Exec { /** - * @return string - * * @throws ExecuteFailedException */ - private static function run(string $command, array $arguments = []) + private static function run(string $command, array $arguments = []): string { - $arguments = array_map(fn ($argument) => escapeshellarg($argument), $arguments); - + $arguments = array_map('escapeshellarg', $arguments); $command = escapeshellcmd(count($arguments) ? sprintf($command, ...$arguments) : $command); $output = []; $status = null; - $result = trim(exec($command, $output, $status)); - // non zero status means execution failed - // see https://www.linuxtopia.org/online_books/advanced_bash_scripting_guide/exitcodes.html if ($status) { - throw new ExecuteFailedException("resulted in exit code {$status}"); + throw new ExecuteFailedException(sprintf('Command resulted in exit code %d', $status)); } return $result; } /** - * @return string - * * @throws ExecuteFailedException */ - public static function readlink($link) + public static function readlink(string $link): string { return self::run('readlink -f %s', [$link]); } /** - * @return string - * * @throws ExecuteFailedException */ - public static function ln(string $link, string $path) + public static function ln(string $link, string $path): string { return self::run('ln -sfn %s %s', [$path, $link]); } /** - * @return string - * * @throws ExecuteFailedException */ - public static function rsync(string $from, string $to) + public static function rsync(string $from, string $to): string { return self::run('rsync -aW --no-compress %s %s', [$from, $to]); } /** - * @return string - * * @throws ExecuteFailedException */ - public static function getGitHash() + public static function getGitHash(): string { return self::run('git log --pretty="%h" -n1'); } From 831bb00619cbe9f4b48e90f8035e4a464c169025 Mon Sep 17 00:00:00 2001 From: J-T-McC Date: Thu, 6 Feb 2025 13:07:20 -0800 Subject: [PATCH 7/8] formatting updates --- src/Commands/DeployCommand.php | 8 ++++++-- src/Commands/ListCommand.php | 1 + src/Models/AtomicDeployment.php | 6 +++--- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Commands/DeployCommand.php b/src/Commands/DeployCommand.php index f3e9400..b1e6594 100644 --- a/src/Commands/DeployCommand.php +++ b/src/Commands/DeployCommand.php @@ -40,6 +40,7 @@ private function deployPreviousBuild(string $hash, array $migrate, bool $dryRun) { Output::info("Updating symlink to previous build: {$hash}"); + /** @var null|AtomicDeployment $deploymentModel */ $deploymentModel = AtomicDeployment::successful()->where('commit_hash', $hash)->first(); if (! $deploymentModel?->has_deployment) { @@ -75,8 +76,11 @@ private function deployCurrentBuild(array $migrate, bool $dryRun): void Output::info("Deployment directory option set - Deployment will use directory: {$deployDir}"); $atomicDeployment->getDeployment()->setDirectory($deployDir); } - $atomicDeployment->deploy(fn () => $atomicDeployment->cleanBuilds(config('atomic-deployments.build-limit')) - ); + + $atomicDeployment + ->deploy( + fn () => $atomicDeployment->cleanBuilds(config('atomic-deployments.build-limit')) + ); } catch (Throwable $e) { $atomicDeployment->fail(); Output::throwable($e); diff --git a/src/Commands/ListCommand.php b/src/Commands/ListCommand.php index 563a92a..9852610 100644 --- a/src/Commands/ListCommand.php +++ b/src/Commands/ListCommand.php @@ -41,6 +41,7 @@ public function handle(): void $titles = ['ID', 'Commit Hash', 'Path', 'SymLink', 'Status', 'Created', 'Live']; + // @phpstan-ignore-next-line ConsoleOutput::table($titles, $deployments); ConsoleOutput::line(''); } diff --git a/src/Models/AtomicDeployment.php b/src/Models/AtomicDeployment.php index cd35a89..249aa81 100644 --- a/src/Models/AtomicDeployment.php +++ b/src/Models/AtomicDeployment.php @@ -40,11 +40,11 @@ class AtomicDeployment extends Model 'deployment_status' => DeploymentStatus::class, ]; - protected static function boot() + protected static function boot(): void { parent::boot(); - static::deleting(function ($model) { + static::deleting(function (AtomicDeployment $model) { if ($model->is_currently_deployed) { throw new AreYouInsaneException('Cannot delete live deployment'); } @@ -53,7 +53,7 @@ protected static function boot() }); } - public function scopeSuccessful($query) + public function scopeSuccessful($query): Builder { return $query->where('deployment_status', DeploymentStatus::SUCCESS); } From 0989b3b15289f20975727bb940a83c1626981af4 Mon Sep 17 00:00:00 2001 From: J-T-McC Date: Thu, 6 Feb 2025 13:13:51 -0800 Subject: [PATCH 8/8] updated github actions --- .github/workflows/lint.yml | 31 +++++++++++++++++++++++++++ .github/workflows/phpstan.yml | 37 +++++++++++++++++++++++++++++++++ .github/workflows/run_tests.yml | 12 +++++------ phpstan.neon | 10 +++++++++ 4 files changed, 83 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/lint.yml create mode 100644 .github/workflows/phpstan.yml create mode 100644 phpstan.neon diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..5a67aed --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,31 @@ +name: Fix Code Style + +on: [push] + +jobs: + lint: + runs-on: ubuntu-latest + strategy: + fail-fast: true + matrix: + php: [8.4] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: json, dom, curl, libxml, mbstring + coverage: none + + - name: Install Pint + run: composer global require laravel/pint + + - name: Run Pint + run: pint + + - name: Commit linted files + uses: stefanzweifel/git-auto-commit-action@v5 diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml new file mode 100644 index 0000000..8b884e1 --- /dev/null +++ b/.github/workflows/phpstan.yml @@ -0,0 +1,37 @@ +name: PHPStan + +on: + pull_request: + types: [opened, synchronize, reopened] + +jobs: + phpstan: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.4' + coverage: none + tools: composer + + - name: Get Composer cache directory + id: composer-cache + run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + + - name: Setup cache + uses: actions/cache@v4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies + run: + composer install --prefer-dist --no-suggest --no-progress + + - name: Run PHPStan + run: ./vendor/bin/phpstan analyse diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 3365e04..391d045 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -9,14 +9,12 @@ jobs: fail-fast: true matrix: os: [ubuntu-latest] - php: [7.4, 8.0] - laravel: [8.*, 7.*] + php: [8.3, 8.4] + laravel: [11.*] dependency-version: [prefer-lowest, prefer-stable] include: - - laravel: 8.* - testbench: 6.* - - laravel: 7.* - testbench: 5.* + - laravel: 11.* + testbench: 9.* name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }} - ${{ matrix.os }} @@ -28,7 +26,7 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick + extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv coverage: none - name: Install dependencies diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..2621711 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,10 @@ +parameters: + + paths: + - src + + # The level 9 is the highest level + level: 5 + + universalObjectCratesClasses: + - Illuminate\Http\Resources\Json\JsonResource