*/ 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->pass('Laravel boots successfully.'); } catch (Throwable $exception) { $this->fail('Laravel failed to boot: '.$exception->getMessage()); } } private function checkDatabase(): void { try { DB::connection()->getPdo(); $this->pass('Database connection is available.'); } catch (Throwable $exception) { $this->fail('Database connection failed: '.$exception->getMessage()); return; } if (! Schema::hasTable('migrations')) { $this->fail('The migrations table does not exist.'); return; } try { Artisan::call('migrate:status', ['--no-interaction' => true]); $output = Artisan::output(); if (str_contains($output, 'Pending')) { $this->fail('One or more database migrations are pending.'); } else { $this->pass('No pending database migrations were detected.'); } } catch (Throwable $exception) { $this->fail('Migration status check failed: '.$exception->getMessage()); } } private function checkModules(): void { $modulePath = base_path('Modules'); if (! is_dir($modulePath)) { $this->fail('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->pass("Module manifest is valid: {$name}."); } catch (JsonException $exception) { $this->fail(sprintf('Invalid module manifest %s: %s', $manifest, $exception->getMessage())); } } foreach (config('release.required_modules', []) as $required) { if (! in_array($required, $available, true)) { $this->warn("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) { continue; } if (isset($names[$name])) { $this->fail("Duplicate named route detected: {$name}."); } $names[$name] = true; } $this->pass(sprintf('Route registry loaded with %d route(s).', count($routes))); } catch (Throwable $exception) { $this->fail('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->pass($message); return; } $warning ? $this->warn($message) : $this->fail($message); } private function pass(string $message): void { $this->components->twoColumnDetail($message, 'PASS'); } private function warn(string $message): void { $this->warnings[] = $message; } private function fail(string $message): void { $this->failures[] = $message; } }