From f63023a06e4ce4577430a7976d738b0c5824b06b Mon Sep 17 00:00:00 2001 From: nfebe Date: Sun, 8 Jun 2025 22:55:48 +0100 Subject: [PATCH] feat(plugins): List plugins with errors Before now, plugins with errors would not appear on the `plugin:list`, now such plugins would appear with status error and user can use `--debug` flag to get more information. Signed-off-by: nfebe --- app/Console/Commands/Plugin/ListCommand.php | 63 ++++++++-- app/Services/PluginManager.php | 124 ++++++++++++++++---- 2 files changed, 154 insertions(+), 33 deletions(-) diff --git a/app/Console/Commands/Plugin/ListCommand.php b/app/Console/Commands/Plugin/ListCommand.php index 51a143e..04a6f3d 100644 --- a/app/Console/Commands/Plugin/ListCommand.php +++ b/app/Console/Commands/Plugin/ListCommand.php @@ -4,13 +4,17 @@ class ListCommand extends PluginCommand { - protected $signature = 'plugin:list'; + protected $signature = 'plugin:list + {--debug : Show detailed error information for problematic plugins}'; - protected $description = 'List all available plugins'; + protected $description = 'List all available plugins.'; public function handle() { $plugins = $this->pluginManager->discover(); + $debug = $this->option('debug'); + $hasErrors = false; + $errorMessages = []; if ($plugins->isEmpty()) { $this->info('No plugins found.'); @@ -18,20 +22,57 @@ public function handle() return 0; } - $rows = $plugins->map(function ($plugin) { - return [ + $rows = []; + + foreach ($plugins as $plugin) { + $status = $plugin['enabled'] ? 'Enabled' : 'Disabled'; + $error = null; + + // Check for common plugin issues + if (isset($plugin['error'])) { + $hasErrors = true; + $status = 'Error'; + $error = $plugin['error']; + $errorMessages[] = "Plugin {$plugin['id']}: {$error}"; + } elseif ($debug) { + // Additional debug checks + if (! class_exists($plugin['provider'] ?? '')) { + $error = "Provider class not found: {$plugin['provider']}"; + $status = 'Error'; + $errorMessages[] = $error; + } + } + + $rows[] = [ 'id' => $plugin['id'] ?? '', - 'name' => $plugin['name'], + 'name' => $plugin['name'] ?? 'Unknown', 'version' => $plugin['manifest']['version'] ?? '1.0.0', - 'status' => $plugin['enabled'] ? 'Enabled' : 'Disabled', + 'status' => $status, 'description' => $plugin['manifest']['description'] ?? 'No description', + 'error' => $debug ? ($error ?? '') : '', ]; - })->toArray(); + } + + $headers = ['ID', 'Name', 'Version', 'Status', 'Description']; + if ($debug) { + $headers[] = 'Error'; + } + + $this->table($headers, $rows); + + if ($hasErrors) { + $this->error('Some plugins have issues. Use --debug for more details.'); - $this->table( - ['ID', 'Name', 'Version', 'Status', 'Description'], - $rows - ); + if ($debug) { + $this->line(''); + $this->warn('Error Details:'); + foreach ($errorMessages as $message) { + $this->line(" - $message"); + } + } + + return 1; + } return 0; } diff --git a/app/Services/PluginManager.php b/app/Services/PluginManager.php index 074c5a7..c05396c 100644 --- a/app/Services/PluginManager.php +++ b/app/Services/PluginManager.php @@ -125,11 +125,19 @@ public function discover(): Collection protected function loadPlugin(string $path): ?array { $manifestPath = "{$path}/plugin.json"; + $pluginDir = basename($path); if (! File::exists($manifestPath)) { - Log::warning("Plugin manifest not found: {$manifestPath}"); + $error = "Plugin manifest not found: {$manifestPath}"; + Log::error($error); - return null; + return [ + 'id' => $pluginDir, + 'name' => $pluginDir, + 'path' => $path, + 'error' => $error, + 'enabled' => false, + ]; } Log::debug("Loading plugin from: {$path}"); @@ -137,37 +145,70 @@ protected function loadPlugin(string $path): ?array try { $manifest = $this->readManifest($manifestPath); if (! $manifest) { - Log::warning("Plugin at {$path} is missing plugin.json"); - - return null; + $error = "Plugin at {$path} has an invalid plugin.json file"; + Log::warning($error); + + return [ + 'id' => $pluginDir, + 'name' => $pluginDir, + 'path' => $path, + 'error' => $error, + 'enabled' => false, + ]; } if (! isset($manifest['namespace']) || ! isset($manifest['provider'])) { - Log::warning("Plugin at {$path} is missing required fields in plugin.json"); - - return null; + $error = 'Plugin is missing required fields in plugin.json (namespace and provider are required)'; + Log::warning("Plugin at {$path}: {$error}"); + + return [ + 'id' => $manifest['id'] ?? $pluginDir, + 'name' => $manifest['name'] ?? $pluginDir, + 'path' => $path, + 'manifest' => $manifest, + 'error' => $error, + 'enabled' => false, + ]; } if (! isset($manifest['id'])) { - Log::warning("Plugin manifest missing required 'id' field: {$manifestPath}"); - - return null; + $error = "Plugin manifest missing required 'id' field"; + Log::warning("{$manifestPath}: {$error}"); + + return [ + 'id' => $pluginDir, + 'name' => $manifest['name'] ?? $pluginDir, + 'path' => $path, + 'manifest' => $manifest, + 'error' => $error, + 'enabled' => $manifest['enabled'] ?? false, + ]; } $providerClass = $manifest['provider']; - $namespace = rtrim($manifest['namespace'] ?? basename($path), '\\').'\\'; + $namespace = rtrim($manifest['namespace'], '\\').'\\'; $this->registerPluginAutoloading($namespace, $path); if (! class_exists($providerClass)) { - Log::warning("Plugin provider class not found: {$providerClass} in {$path}"); - - return null; + $error = "Provider class not found: {$providerClass}"; + Log::warning("Plugin {$manifest['id']}: {$error}"); + + return [ + 'id' => $manifest['id'], + 'name' => $manifest['name'] ?? $pluginDir, + 'path' => $path, + 'namespace' => $namespace, + 'manifest' => $manifest, + 'provider' => $providerClass, + 'error' => $error, + 'enabled' => $manifest['enabled'] ?? false, + ]; } return [ 'id' => $manifest['id'], - 'name' => $manifest['name'] ?? basename($path), + 'name' => $manifest['name'] ?? $pluginDir, 'path' => $path, 'namespace' => $namespace, 'manifest' => $manifest, @@ -175,9 +216,27 @@ protected function loadPlugin(string $path): ?array 'enabled' => $manifest['enabled'] ?? true, ]; } catch (\JsonException $e) { - Log::error("Failed to parse plugin manifest: {$manifestPath} - ".$e->getMessage()); + $error = 'Failed to parse plugin manifest: '.$e->getMessage(); + Log::error("{$manifestPath}: {$error}"); - return null; + return [ + 'id' => $pluginDir, + 'name' => $pluginDir, + 'path' => $path, + 'error' => $error, + 'enabled' => false, + ]; + } catch (\Exception $e) { + $error = 'Error loading plugin: '.$e->getMessage(); + Log::error("{$path}: {$error}"); + + return [ + 'id' => $pluginDir, + 'name' => $pluginDir, + 'path' => $path, + 'error' => $error, + 'enabled' => false, + ]; } } @@ -394,7 +453,10 @@ protected function readManifest(string $manifestPath): ?array } /** - * Check if a plugin is enabled by its ID + * Check if a plugin is enabled and valid. + * + * @param string $pluginId The plugin ID to check + * @return bool True if the plugin is enabled and valid, false otherwise */ public function isPluginEnabled(string $pluginId): bool { @@ -403,12 +465,28 @@ public function isPluginEnabled(string $pluginId): bool return false; } + if (isset($plugin['error'])) { + return false; + } + + // Check if provider exists and is valid + if (! isset($plugin['provider']) || empty($plugin['provider'])) { + return false; + } + + if (! class_exists($plugin['provider'])) { + Log::warning("Plugin provider class not found: {$plugin['provider']}"); + + return false; + } + $manifestPath = $plugin['path'].'/plugin.json'; try { $manifest = $this->readManifest($manifestPath); - return $manifest ? ($manifest['enabled'] ?? true) : false; + return $manifest ? (bool) ($manifest['enabled'] ?? true) : false; + } catch (\RuntimeException $e) { Log::error('Error reading plugin manifest', [ 'plugin' => $pluginId, @@ -429,13 +507,15 @@ public function registerPlugins(): void Log::debug("Registered plugin service provider: {$plugin['provider']}"); } catch (\Exception $e) { Log::error('Failed to register plugin service provider', [ - 'provider' => $plugin['provider'] ?? 'unknown', + 'plugin' => $plugin['id'], + 'provider' => $plugin['provider'], 'error' => $e->getMessage(), ]); throw $e; } } else { - Log::debug("Skipping disabled plugin: {$plugin['id']}"); + $reason = isset($plugin['error']) ? "error: {$plugin['error']}" : 'disabled'; + Log::debug("Skipping plugin {$plugin['id']}: {$reason}"); } }); }