*/ private array $failures = []; /** @var array */ private array $warnings = []; public function handle(): int { $this->components->info('Content Manager '.config('release.version').' release audit'); $this->checkApplication(); $this->checkDatabase(); $this->checkModules(); $this->checkRoutes(); $this->checkProductionConfiguration(); $this->newLine(); foreach ($this->warnings as $warning) { $this->components->warn($warning); } foreach ($this->failures as $failure) { $this->components->error($failure); } if ($this->failures !== []) { $this->components->error(sprintf( 'Release audit failed with %d error(s) and %d warning(s).', count($this->failures), count($this->warnings), )); return self::FAILURE; } if ($this->option('fail-on-warning') && $this->warnings !== []) { $this->components->error('Release audit contains warnings and --fail-on-warning was supplied.'); return self::FAILURE; } $this->components->success(sprintf( 'Release audit passed with %d warning(s).', count($this->warnings), )); return self::SUCCESS; } private function checkApplication(): void { $this->check((bool) config('app.key'), 'Application key is configured.'); $this->check(is_writable(storage_path()), 'Storage directory is writable.'); $this->check(is_writable(base_path('bootstrap/cache')), 'Bootstrap cache directory is writable.'); try { Artisan::call('about', ['--only' => 'environment']); $this->recordPass('Laravel boots successfully.'); } catch (Throwable $exception) { $this->recordFailure('Laravel failed to boot: '.$exception->getMessage()); } } private function checkDatabase(): void { try { DB::connection()->getPdo(); $this->recordPass('Database connection is available.'); } catch (Throwable $exception) { $this->recordFailure('Database connection failed: '.$exception->getMessage()); return; } if (! Schema::hasTable('migrations')) { $this->recordFailure('The migrations table does not exist.'); return; } try { Artisan::call('migrate:status', ['--no-interaction' => true]); $output = Artisan::output(); if (str_contains($output, 'Pending')) { $this->recordFailure('One or more database migrations are pending.'); } else { $this->recordPass('No pending database migrations were detected.'); } } catch (Throwable $exception) { $this->recordFailure('Migration status check failed: '.$exception->getMessage()); } } private function checkModules(): void { $modulePath = base_path('Modules'); if (! is_dir($modulePath)) { $this->recordFailure('Modules directory is missing.'); return; } $available = []; foreach (glob($modulePath.'/*/module.json') ?: [] as $manifest) { try { $data = json_decode((string) file_get_contents($manifest), true, 512, JSON_THROW_ON_ERROR); $name = $data['name'] ?? basename(dirname($manifest)); $available[] = $name; $this->recordPass("Module manifest is valid: {$name}."); } catch (JsonException $exception) { $this->recordFailure(sprintf('Invalid module manifest %s: %s', $manifest, $exception->getMessage())); } } foreach (config('release.required_modules', []) as $required) { if (! in_array($required, $available, true)) { $this->recordWarning("Expected module is not present: {$required}."); } } } private function checkRoutes(): void { try { $routes = Route::getRoutes(); $names = []; foreach ($routes as $route) { $name = $route->getName(); if ($name === null || trim($name) === '' || str_ends_with($name, '.')) { continue; } if (isset($names[$name])) { $this->recordFailure("Duplicate named route detected: {$name}."); } $names[$name] = true; } $this->recordPass(sprintf('Route registry loaded with %d route(s).', count($routes))); } catch (Throwable $exception) { $this->recordFailure('Route audit failed: '.$exception->getMessage()); } } private function checkProductionConfiguration(): void { if (! $this->option('production')) { return; } $this->check(app()->environment('production'), 'APP_ENV is production.'); $this->check(config('app.debug') === false, 'APP_DEBUG is disabled.'); $this->check(config('app.url') !== null, 'APP_URL is configured.'); $this->check(config('session.secure') === true, 'Secure session cookies are enabled.', warning: true); $this->check(config('marketplace.allow_remote_install', false) === false, 'Remote marketplace installation is disabled.', warning: true); $this->check(config('cloud-devops.allow_execution', false) === false, 'Cloud deployment execution is disabled until explicitly configured.', warning: true); } private function check(bool $condition, string $message, bool $warning = false): void { if ($condition) { $this->recordPass($message); return; } $warning ? $this->recordWarning($message) : $this->recordFailure($message); } private function recordPass(string $message): void { $this->components->twoColumnDetail($message, 'PASS'); } private function recordWarning(string $message): void { $this->warnings[] = $message; } private function recordFailure(string $message): void { $this->failures[] = $message; } }