diff --git a/src/Console/DeployCommand.php b/src/Console/DeployCommand.php new file mode 100644 index 00000000..5f4df124 --- /dev/null +++ b/src/Console/DeployCommand.php @@ -0,0 +1,88 @@ +token) { + $this->error('No NIGHTWATCH_TOKEN environment variable configured.'); + + return 1; + } + + $tag = config('nightwatch.deployment') ?? ''; + + $baseUrl = $_SERVER['NIGHTWATCH_BASE_URL'] ?? 'https://nightwatch.laravel.com'; + + try { + $response = Http::connectTimeout(5) + ->timeout(10) + ->withHeaders([ + 'Authorization' => "Bearer {$this->token}", + 'Accept' => 'application/json', + ]) + ->post("{$baseUrl}/api/deployments", [ + 'timestamp' => CarbonImmutable::now()->timestamp, + 'version' => $tag, + ]); + + if ($response->successful()) { + $this->info('Deployment successful'); + + return 0; + } else { + $message = $response->body(); + + if (strlen($message) > 1005) { + $message = substr($message, 0, 1000).'[...]'; + } + + $this->error("Deployment failed: {$response->status()} [{$message}]"); + + return 1; + } + } catch (Throwable $e) { + $this->error("Deployment failed: [{$e->getMessage()}]"); + + return 1; + } + } +} diff --git a/src/NightwatchServiceProvider.php b/src/NightwatchServiceProvider.php index c40c38ec..d18e6bb1 100644 --- a/src/NightwatchServiceProvider.php +++ b/src/NightwatchServiceProvider.php @@ -40,6 +40,7 @@ use Illuminate\Support\Facades\Context; use Illuminate\Support\ServiceProvider; use Laravel\Nightwatch\Console\AgentCommand; +use Laravel\Nightwatch\Console\DeployCommand; use Laravel\Nightwatch\Facades\Nightwatch; use Laravel\Nightwatch\Factories\Logger; use Laravel\Nightwatch\Hooks\ArtisanStartingListener; @@ -192,6 +193,7 @@ private function registerBindings(): void $this->registerLogger(); $this->registerMiddleware(); $this->registerAgentCommand(); + $this->registerDeployCommand(); $this->buildAndRegisterCore(); } @@ -226,6 +228,13 @@ private function registerAgentCommand(): void )); } + private function registerDeployCommand(): void + { + $this->app->singleton(DeployCommand::class, fn () => new DeployCommand( + token: $this->nightwatchConfig['token'] ?? null, + )); + } + private function buildAndRegisterCore(): void { $clock = new Clock; @@ -295,6 +304,7 @@ private function registerCommands(): void $this->commands([ Console\AgentCommand::class, Console\StatusCommand::class, + Console\DeployCommand::class, ]); } diff --git a/tests/Feature/Console/DeployCommandTest.php b/tests/Feature/Console/DeployCommandTest.php new file mode 100644 index 00000000..e9388453 --- /dev/null +++ b/tests/Feature/Console/DeployCommandTest.php @@ -0,0 +1,101 @@ +start('NIGHTWATCH_DEPLOY="v1.2.3" \ + vendor/bin/testbench nightwatch:deploy' + ); + + try { + $result = $process->wait(function ($type, $o) use (&$output, $process) { + $output .= $o; + + $process->signal(SIGTERM); + + $tries = 0; + + while ($tries < 3) { + if (! $process->running()) { + return; + } + + $tries++; + sleep(1); + } + + $process->signal(SIGKILL); + }); + } catch (ProcessTimedOutException $e) { + throw new RuntimeException('Failed to deploy or stop the agent running. Output:'.PHP_EOL.$output, previous: $e); + } + + $this->assertStringContainsString('Deployment successful', $output); + } + + public function test_it_fails_when_the_deploy_command_is_run_without_a_token(): void + { + $process = Process::timeout(10)->start('NIGHTWATCH_DEPLOY="v1.2.3" \ + NIGHTWATCH_TOKEN="" \ + vendor/bin/testbench nightwatch:deploy'); + + try { + $process->wait(function ($type, $o) use (&$output, $process) { + $output .= $o; + + $process->signal(SIGTERM); + + $tries = 0; + + while ($tries < 3) { + if (! $process->running()) { + return; + } + + $tries++; + sleep(1); + } + + $process->signal(SIGKILL); + }); + } catch (ProcessTimedOutException $e) { + throw new RuntimeException('Failed to deploy or stop the agent running. Output:'.PHP_EOL.$output, previous: $e); + } + + $this->assertStringContainsString('No NIGHTWATCH_TOKEN environment variable configured.', $process->output()); + } + + public function test_it_handles_http_errors(): void + { + Http::fake([ + $_SERVER['NIGHTWATCH_BASE_URL'].'/api/deployments' => Http::response('Whoops!', 500), + ]); + + $this->artisan('nightwatch:deploy') + ->expectsOutput('Deployment failed: 500 [Whoops!]') + ->assertExitCode(1); + } + + public function test_it_handles_throwable_errors(): void + { + Http::fake([ + $_SERVER['NIGHTWATCH_BASE_URL'].'/api/deployments' => Http::failedConnection('Whoops!'), + ]); + + $this->artisan('nightwatch:deploy') + ->expectsOutput('Deployment failed: [Whoops!]') + ->assertExitCode(1); + } +}