Skip to content

feat(plugins): List plugins with errors #67

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 52 additions & 11 deletions app/Console/Commands/Plugin/ListCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,75 @@

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.');

return 0;
}

$rows = $plugins->map(function ($plugin) {
return [
$rows = [];

foreach ($plugins as $plugin) {
$status = $plugin['enabled'] ? '<fg=green>Enabled</>' : '<fg=red>Disabled</>';
$error = null;

// Check for common plugin issues
if (isset($plugin['error'])) {
$hasErrors = true;
$status = '<fg=yellow>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 = '<fg=yellow>Error</>';
$errorMessages[] = $error;
}
}

$rows[] = [
'id' => $plugin['id'] ?? '',
'name' => $plugin['name'],
'name' => $plugin['name'] ?? 'Unknown',
'version' => $plugin['manifest']['version'] ?? '1.0.0',
'status' => $plugin['enabled'] ? '<fg=green>Enabled</>' : '<fg=red>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;
}
Expand Down
124 changes: 102 additions & 22 deletions app/Services/PluginManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -125,59 +125,118 @@ 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}");

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,
'provider' => $providerClass,
'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,
];
}
}

Expand Down Expand Up @@ -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
{
Expand All @@ -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,
Expand All @@ -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}");
}
});
}
Expand Down