feat: 拆分 CLI 与网页宿主,并在无有效城市时暂停等人确认
将原单体 MAF1 拆成 MAF1(控制台工作流)和 MAF1.Web(可视化编排)。 抽城市失败时暂停:网页弹确认、CLI 控制台询问,超时采用默认结束查询; 也可改填城市后继续。共享决策模型放在 MAF1.Core。
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
using System.Text.Json;
|
||||
using MAF1.PluginContract;
|
||||
|
||||
namespace MAF1.Plugins;
|
||||
|
||||
public sealed class PluginScanner(PluginOptions options)
|
||||
{
|
||||
public PluginScanResult Scan()
|
||||
{
|
||||
string root = options.ResolveRoot();
|
||||
Directory.CreateDirectory(root);
|
||||
|
||||
List<LoadedPlugin> plugins = [];
|
||||
List<CatalogIssue> issues = [];
|
||||
HashSet<string> 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<PluginManifest>(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<PluginPort> ports)
|
||||
=> ports.GroupBy(p => p.Name, StringComparer.OrdinalIgnoreCase).Any(g => g.Count() > 1);
|
||||
}
|
||||
|
||||
public sealed record PluginScanResult(
|
||||
string Root,
|
||||
IReadOnlyList<LoadedPlugin> Plugins,
|
||||
IReadOnlyList<CatalogIssue> Issues);
|
||||
Reference in New Issue
Block a user