using Microsoft.Extensions.Logging; using StellaOps.Plugin.Abstractions; using StellaOps.Plugin.Abstractions.Attributes; using StellaOps.Plugin.Abstractions.Manifest; using System.Reflection; namespace StellaOps.Plugin.Host.Discovery; /// /// Discovers plugins embedded in loaded assemblies. /// public sealed class EmbeddedPluginDiscovery : IPluginDiscovery { private readonly ILogger _logger; private readonly IReadOnlyList _assemblies; /// /// Creates a new embedded plugin discovery instance. /// /// Logger instance. /// Optional list of assemblies to scan. If null, scans all loaded assemblies. public EmbeddedPluginDiscovery( ILogger logger, IEnumerable? assemblies = null) { _logger = logger; _assemblies = assemblies?.ToList() ?? []; } /// public Task> DiscoverAsync( IEnumerable searchPaths, CancellationToken ct) { // searchPaths are ignored for embedded discovery // We only scan the provided assemblies or the application assemblies var manifests = new List(); var assembliesToScan = _assemblies.Count > 0 ? _assemblies : GetApplicationAssemblies(); foreach (var assembly in assembliesToScan) { ct.ThrowIfCancellationRequested(); try { var pluginTypes = assembly.GetTypes() .Where(t => t.IsClass && !t.IsAbstract && typeof(IPlugin).IsAssignableFrom(t)) .ToList(); foreach (var pluginType in pluginTypes) { try { var manifest = CreateManifestFromType(pluginType); if (manifest != null) { manifests.Add(manifest); _logger.LogDebug( "Discovered embedded plugin {PluginId} in assembly {Assembly}", manifest.Info.Id, assembly.GetName().Name); } } catch (Exception ex) { _logger.LogWarning(ex, "Failed to create manifest for type {Type} in assembly {Assembly}", pluginType.FullName, assembly.GetName().Name); } } } catch (ReflectionTypeLoadException ex) { _logger.LogWarning(ex, "Failed to load types from assembly {Assembly}", assembly.GetName().Name); } } return Task.FromResult>(manifests); } /// public Task DiscoverSingleAsync(PluginSource source, CancellationToken ct) { if (source.Type != PluginSourceType.Embedded) throw new ArgumentException($"Unsupported source type: {source.Type}", nameof(source)); // Location should be the fully qualified type name var typeName = source.Location; foreach (var assembly in GetApplicationAssemblies()) { var type = assembly.GetType(typeName); if (type != null && typeof(IPlugin).IsAssignableFrom(type)) { var manifest = CreateManifestFromType(type); if (manifest != null) return Task.FromResult(manifest); } } throw new InvalidOperationException($"Embedded plugin type not found: {typeName}"); } private static IReadOnlyList GetApplicationAssemblies() { var entryAssembly = Assembly.GetEntryAssembly(); if (entryAssembly == null) return AppDomain.CurrentDomain.GetAssemblies(); var referencedNames = entryAssembly.GetReferencedAssemblies(); var assemblies = new List { entryAssembly }; foreach (var name in referencedNames) { try { assemblies.Add(Assembly.Load(name)); } catch { // Skip assemblies that can't be loaded } } return assemblies; } private static PluginManifest? CreateManifestFromType(Type pluginType) { var attribute = pluginType.GetCustomAttribute(); if (attribute == null) return null; return new PluginManifest { Info = new PluginInfo( Id: attribute.Id, Name: attribute.Name ?? pluginType.Name, Version: attribute.Version ?? "1.0.0", Vendor: attribute.Vendor ?? "Unknown", Description: attribute.Description), EntryPoint = pluginType.FullName!, AssemblyPath = pluginType.Assembly.Location, Capabilities = [], Dependencies = [], Permissions = [], Tags = [] }; } }