using System.Text.Json;
using MAF1.PluginContract;
namespace MAF1.Plugins;
///
/// 扫描 exe 旁边的 plugins/*/plugin.json。源码目录 Plugins/ 不会被运行时直接读到,要先 build 复制到输出目录。
///
public sealed class PluginScanner(PluginOptions options)
{
public PluginScanResult Scan()
{
string root = options.ResolveRoot();
Directory.CreateDirectory(root);
List plugins = [];
List issues = [];
HashSet ids = new(StringComparer.OrdinalIgnoreCase);
foreach (string folder in Directory.GetDirectories(root))
{
string folderName = Path.GetFileName(folder);
if (folderName.StartsWith('_') || folderName.StartsWith('.'))
{
continue;
}
string manifestPath = Path.Combine(folder, "plugin.json");
if (!File.Exists(manifestPath))
{
issues.Add(new CatalogIssue
{
Level = "warning",
Source = folderName,
Message = "目录里没有 plugin.json,已跳过。",
});
continue;
}
PluginManifest? manifest;
try
{
string json = File.ReadAllText(manifestPath);
manifest = JsonSerializer.Deserialize(json, PluginJson.Options);
}
catch (Exception ex)
{
issues.Add(new CatalogIssue
{
Level = "error",
Source = folderName,
Message = $"plugin.json 无法解析:{ex.Message}",
});
continue;
}
if (manifest is null || string.IsNullOrWhiteSpace(manifest.Id))
{
issues.Add(new CatalogIssue
{
Level = "error",
Source = folderName,
Message = "plugin.json 缺少 id。",
});
continue;
}
if (string.IsNullOrWhiteSpace(manifest.Launch.Command))
{
issues.Add(new CatalogIssue
{
Level = "error",
Source = manifest.Id,
Message = "plugin.json 缺少 launch.command,宿主不会替插件拼命令。",
});
continue;
}
if (!ids.Add(manifest.Id))
{
issues.Add(new CatalogIssue
{
Level = "error",
Source = manifest.Id,
Message = $"插件 id `{manifest.Id}` 重复,已忽略目录 {folderName}。",
});
continue;
}
if (DuplicatePortNames(manifest.Inputs))
{
issues.Add(new CatalogIssue { Level = "error", Source = manifest.Id, Message = "inputs 字段名重复。" });
continue;
}
if (DuplicatePortNames(manifest.Outputs))
{
issues.Add(new CatalogIssue { Level = "error", Source = manifest.Id, Message = "outputs 字段名重复。" });
continue;
}
plugins.Add(new LoadedPlugin
{
FolderName = folderName,
FolderPath = folder,
Manifest = manifest,
});
}
return new PluginScanResult(root, plugins, issues);
}
private static bool DuplicatePortNames(List ports)
=> ports.GroupBy(p => p.Name, StringComparer.OrdinalIgnoreCase).Any(g => g.Count() > 1);
}
public sealed record PluginScanResult(
string Root,
IReadOnlyList Plugins,
IReadOnlyList Issues);