mergeConfigFrom( __DIR__.'/../config/boost.php', 'boost' ); if (! $this->shouldRun()) { return; } $this->app->singleton(BoostManager::class, fn (): BoostManager => new BoostManager); $this->app->singleton(Roster::class, fn (): Roster => Roster::scan(base_path())); $this->app->singleton(GuidelineConfig::class, fn (): GuidelineConfig => new GuidelineConfig); $this->app->singleton(GuidelineAssist::class, fn ($app): GuidelineAssist => new GuidelineAssist( $app->make(Roster::class), $app->make(GuidelineConfig::class) )); } public function boot(Router $router): void { if (! $this->shouldRun()) { return; } Mcp::local('laravel-boost', Boost::class); $this->registerPublishing(); $this->registerCommands(); $this->registerRoutes(); if (config('boost.browser_logs_watcher', true)) { $this->registerBrowserLogger(); $this->callAfterResolving('blade.compiler', $this->registerBladeDirectives(...)); $this->hookIntoResponses($router); } } protected function registerPublishing(): void { if ($this->app->runningInConsole()) { $this->publishes([ __DIR__.'/../config/boost.php' => config_path('boost.php'), ], 'boost-config'); } } protected function registerCommands(): void { if ($this->app->runningInConsole()) { $this->commands([ Console\StartCommand::class, Console\InstallCommand::class, Console\UpdateCommand::class, Console\ExecuteToolCommand::class, Console\AddSkillCommand::class, Console\ListSkillCommand::class, ]); } } protected function registerRoutes(): void { Route::post('/_boost/browser-logs', function (Request $request) { $logs = $request->input('logs', []); // Handle sendBeacon's text/plain content type. if (empty($logs) && ! $request->isJson()) { $decoded = json_decode($request->getContent(), true); $logs = $decoded['logs'] ?? []; } /** @var Logger $logger */ $logger = Log::channel('browser'); /** * @var array{ * type: 'error'|'warn'|'info'|'log'|'table'|'window_error'|'uncaught_error'|'unhandled_rejection', * timestamp: string, * data: array, * url:string, * userAgent:string * } $log */ foreach ($logs as $log) { $logger->write( level: match ($log['type']) { 'warn' => 'warning', 'log', 'table' => 'debug', 'window_error', 'uncaught_error', 'unhandled_rejection' => 'error', default => $log['type'] }, message: self::buildLogMessageFromData($log['data']), context: [ 'url' => $log['url'], 'user_agent' => $log['userAgent'] ?: null, 'timestamp' => $log['timestamp'] ?: now()->toIso8601String(), ] ); } return response()->json(['status' => 'logged']); }) ->name('boost.browser-logs') ->withoutMiddleware(VerifyCsrfToken::class); } /** * Build a string message for the log based on various input types. Single-dimensional, and multi: * "data": {"message":"Unhandled Promise Rejection","reason":{"name":"TypeError","message":"NetworkError when attempting to fetch resource.","stack":""}}] */ private static function buildLogMessageFromData(array $data): string { $messages = []; foreach ($data as $value) { $messages[] = match (true) { is_array($value) => self::buildLogMessageFromData($value), is_string($value), is_numeric($value) => (string) $value, is_bool($value) => $value ? 'true' : 'false', is_null($value) => 'null', is_object($value) => json_encode($value), default => $value, }; } return implode(' ', $messages); } protected function registerBrowserLogger(): void { if (config('logging.channels.browser') !== null) { return; } config([ 'logging.channels.browser' => [ 'driver' => 'single', 'path' => storage_path('logs'.DIRECTORY_SEPARATOR.'browser.log'), 'level' => env('LOG_LEVEL', 'debug'), 'days' => 14, ], ]); } protected function registerBladeDirectives(BladeCompiler $bladeCompiler): void { $bladeCompiler->directive('boostJs', fn (): string => ''); } protected function hookIntoResponses(Router $router): void { $this->app->booted(function () use ($router): void { $router->pushMiddlewareToGroup('web', InjectBoost::class); }); } protected function shouldRun(): bool { if (! config('boost.enabled', true)) { return false; } if (app()->runningUnitTests()) { return false; } // Only enable Boost on local environments or when debug is true if (! app()->environment('local') && config('app.debug', false) !== true) { return false; } return true; } }