release orchestrator v1 draft and build fixes

This commit is contained in:
master
2026-01-12 12:24:17 +02:00
parent f3de858c59
commit 9873f80830
1598 changed files with 240385 additions and 5944 deletions

View File

@@ -0,0 +1,202 @@
namespace StellaOps.Scanner.Analyzers.Plugin.Unified;
using StellaOps.Plugin.Abstractions;
using StellaOps.Plugin.Abstractions.Capabilities;
using StellaOps.Plugin.Abstractions.Context;
using StellaOps.Plugin.Abstractions.Health;
using StellaOps.Plugin.Abstractions.Lifecycle;
using StellaOps.Scanner.Analyzers.Lang;
using StellaOps.Scanner.Analyzers.Lang.Plugin;
/// <summary>
/// Adapts an existing ILanguageAnalyzer to the unified IPlugin and IAnalysisCapability interfaces.
/// This enables gradual migration of Scanner language analyzers to the unified plugin architecture.
/// </summary>
/// <remarks>
/// The adapter bridges the Scanner-specific ILanguageAnalyzer interface to the Plugin.Abstractions
/// IAnalysisCapability interface. The underlying analysis is delegated to the wrapped analyzer.
/// </remarks>
public sealed class AnalyzerPluginAdapter : IPlugin, IAnalysisCapability
{
private readonly ILanguageAnalyzer _inner;
private readonly ILanguageAnalyzerPlugin _plugin;
private readonly IServiceProvider _serviceProvider;
private IPluginContext? _context;
private PluginLifecycleState _state = PluginLifecycleState.Discovered;
private readonly string[] _filePatterns;
private readonly string[] _ecosystems;
/// <summary>
/// Creates a new adapter for an existing language analyzer.
/// </summary>
/// <param name="inner">The existing language analyzer to wrap.</param>
/// <param name="plugin">The plugin metadata for this analyzer.</param>
/// <param name="serviceProvider">Service provider for DI.</param>
/// <param name="filePatterns">File patterns this analyzer handles.</param>
/// <param name="ecosystems">Supported ecosystems.</param>
public AnalyzerPluginAdapter(
ILanguageAnalyzer inner,
ILanguageAnalyzerPlugin plugin,
IServiceProvider serviceProvider,
string[] filePatterns,
string[] ecosystems)
{
_inner = inner ?? throw new ArgumentNullException(nameof(inner));
_plugin = plugin ?? throw new ArgumentNullException(nameof(plugin));
_serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
_filePatterns = filePatterns ?? Array.Empty<string>();
_ecosystems = ecosystems ?? new[] { inner.Id };
}
/// <inheritdoc />
public PluginInfo Info => new(
Id: $"com.stellaops.analyzer.{_inner.Id}",
Name: _inner.DisplayName,
Version: "1.0.0",
Vendor: "Stella Ops",
Description: $"{_inner.DisplayName} language analyzer for dependency scanning");
/// <inheritdoc />
public PluginTrustLevel TrustLevel => PluginTrustLevel.BuiltIn;
/// <inheritdoc />
public PluginCapabilities Capabilities => PluginCapabilities.Analysis;
/// <inheritdoc />
public PluginLifecycleState State => _state;
#region IAnalysisCapability
/// <inheritdoc />
public string AnalysisType => _inner.Id;
/// <inheritdoc />
public IReadOnlyList<string> FilePatterns => _filePatterns;
/// <inheritdoc />
public IReadOnlyList<string> SupportedEcosystems => _ecosystems;
/// <inheritdoc />
public bool CanAnalyze(string filePath, ReadOnlySpan<byte> fileHeader)
{
// Check if file matches any of our patterns
var fileName = Path.GetFileName(filePath);
foreach (var pattern in _filePatterns)
{
if (MatchesPattern(fileName, pattern))
{
return true;
}
}
return false;
}
/// <inheritdoc />
public async Task<AnalysisResult> AnalyzeAsync(IAnalysisContext context, CancellationToken ct)
{
if (_state != PluginLifecycleState.Active)
{
return new AnalysisResult(
Success: false,
Components: Array.Empty<DiscoveredComponent>(),
Diagnostics: new[]
{
new AnalysisDiagnostic(
DiagnosticSeverity.Error,
"ANALYZER_NOT_ACTIVE",
$"Analyzer {_inner.Id} is not in active state (current: {_state})")
},
Metadata: new AnalysisMetadata(
AnalyzerType: _inner.Id,
AnalyzerVersion: Info.Version,
Duration: TimeSpan.Zero,
FilesProcessed: 0));
}
// Note: The ILanguageAnalyzer interface uses a different analysis model
// (LanguageAnalyzerContext + LanguageComponentWriter) than IAnalysisCapability.
// Full integration would require creating adapter context/writer classes.
// For now, we document this limitation and provide basic bridging.
throw new NotSupportedException(
$"Direct analysis via IAnalysisCapability is not yet supported for {_inner.Id}. " +
"Use the Scanner service with the existing ILanguageAnalyzer interface. " +
"Full IAnalysisCapability integration requires LanguageAnalyzerContext adapter.");
}
#endregion
#region IPlugin
/// <inheritdoc />
public async Task InitializeAsync(IPluginContext context, CancellationToken ct)
{
_context = context;
_state = PluginLifecycleState.Initializing;
// Verify the plugin is available
if (!_plugin.IsAvailable(_serviceProvider))
{
_state = PluginLifecycleState.Failed;
throw new InvalidOperationException(
$"Language analyzer plugin '{_plugin.Name}' is not available.");
}
_state = PluginLifecycleState.Active;
context.Logger.Info("Analyzer plugin adapter initialized for {AnalyzerId}", _inner.Id);
await Task.CompletedTask;
}
/// <inheritdoc />
public async Task<HealthCheckResult> HealthCheckAsync(CancellationToken ct)
{
try
{
var isAvailable = _plugin.IsAvailable(_serviceProvider);
if (isAvailable)
{
return HealthCheckResult.Healthy()
.WithDetails(new Dictionary<string, object>
{
["analyzerId"] = _inner.Id,
["displayName"] = _inner.DisplayName,
["filePatterns"] = string.Join(", ", _filePatterns),
["ecosystems"] = string.Join(", ", _ecosystems)
});
}
return HealthCheckResult.Unhealthy($"Analyzer '{_inner.Id}' is not available");
}
catch (Exception ex)
{
return HealthCheckResult.Unhealthy(ex);
}
}
/// <inheritdoc />
public ValueTask DisposeAsync()
{
_state = PluginLifecycleState.Stopped;
return ValueTask.CompletedTask;
}
#endregion
#region Helpers
private static bool MatchesPattern(string fileName, string pattern)
{
if (pattern.StartsWith("*"))
{
return fileName.EndsWith(pattern[1..], StringComparison.OrdinalIgnoreCase);
}
if (pattern.EndsWith("*"))
{
return fileName.StartsWith(pattern[..^1], StringComparison.OrdinalIgnoreCase);
}
return fileName.Equals(pattern, StringComparison.OrdinalIgnoreCase);
}
#endregion
}

View File

@@ -0,0 +1,174 @@
namespace StellaOps.Scanner.Analyzers.Plugin.Unified;
using Microsoft.Extensions.DependencyInjection;
using StellaOps.Plugin.Abstractions;
using StellaOps.Plugin.Abstractions.Capabilities;
using StellaOps.Scanner.Analyzers.Lang;
using StellaOps.Scanner.Analyzers.Lang.Plugin;
/// <summary>
/// Factory for creating unified analyzer plugin adapters from existing analyzers.
/// </summary>
public sealed class AnalyzerPluginAdapterFactory
{
private readonly ILanguageAnalyzerPluginCatalog _catalog;
private readonly IServiceProvider _serviceProvider;
private readonly Dictionary<string, AnalyzerPluginAdapter> _adapters = new(StringComparer.OrdinalIgnoreCase);
private readonly object _lock = new();
// Known file patterns for each analyzer type
private static readonly Dictionary<string, string[]> KnownFilePatterns = new(StringComparer.OrdinalIgnoreCase)
{
["dotnet"] = new[] { "*.csproj", "*.fsproj", "*.vbproj", "*.sln", "packages.config", "*.deps.json", "Directory.Packages.props" },
["go"] = new[] { "go.mod", "go.sum", "Gopkg.lock", "Gopkg.toml" },
["java"] = new[] { "pom.xml", "build.gradle", "build.gradle.kts", "*.jar" },
["node"] = new[] { "package.json", "package-lock.json", "yarn.lock", "pnpm-lock.yaml" },
["python"] = new[] { "requirements.txt", "Pipfile", "Pipfile.lock", "pyproject.toml", "poetry.lock", "setup.py" },
["ruby"] = new[] { "Gemfile", "Gemfile.lock", "*.gemspec" },
["rust"] = new[] { "Cargo.toml", "Cargo.lock" },
["php"] = new[] { "composer.json", "composer.lock" },
["deno"] = new[] { "deno.json", "deno.jsonc", "import_map.json" },
["bun"] = new[] { "bun.lockb", "bunfig.toml" },
};
private static readonly Dictionary<string, string[]> KnownEcosystems = new(StringComparer.OrdinalIgnoreCase)
{
["dotnet"] = new[] { "nuget", "dotnet" },
["go"] = new[] { "go", "golang" },
["java"] = new[] { "maven", "gradle", "java" },
["node"] = new[] { "npm", "yarn", "pnpm", "nodejs" },
["python"] = new[] { "pypi", "pip", "poetry", "python" },
["ruby"] = new[] { "rubygems", "bundler", "ruby" },
["rust"] = new[] { "cargo", "crates.io", "rust" },
["php"] = new[] { "composer", "packagist", "php" },
["deno"] = new[] { "deno", "jsr" },
["bun"] = new[] { "bun", "npm" },
};
/// <summary>
/// Creates a new factory instance.
/// </summary>
/// <param name="catalog">The language analyzer plugin catalog.</param>
/// <param name="serviceProvider">Service provider for DI.</param>
public AnalyzerPluginAdapterFactory(
ILanguageAnalyzerPluginCatalog catalog,
IServiceProvider serviceProvider)
{
_catalog = catalog ?? throw new ArgumentNullException(nameof(catalog));
_serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
}
/// <summary>
/// Gets all available unified analyzer plugins.
/// </summary>
/// <returns>List of unified analyzer plugins.</returns>
public IReadOnlyList<IPlugin> GetAllPlugins()
{
var analyzers = _catalog.CreateAnalyzers(_serviceProvider);
var result = new List<IPlugin>();
foreach (var analyzer in analyzers)
{
var adapter = GetOrCreateAdapter(analyzer);
if (adapter != null)
{
result.Add(adapter);
}
}
return result;
}
/// <summary>
/// Gets a unified analyzer plugin by analyzer ID.
/// </summary>
/// <param name="analyzerId">Analyzer identifier.</param>
/// <returns>Unified analyzer plugin, or null if not found.</returns>
public IPlugin? GetPlugin(string analyzerId)
{
var analyzers = _catalog.CreateAnalyzers(_serviceProvider);
var analyzer = analyzers.FirstOrDefault(a =>
a.Id.Equals(analyzerId, StringComparison.OrdinalIgnoreCase));
if (analyzer == null)
return null;
return GetOrCreateAdapter(analyzer);
}
/// <summary>
/// Gets the analysis capability for an analyzer.
/// </summary>
/// <param name="analyzerId">Analyzer identifier.</param>
/// <returns>Analysis capability, or null if not found.</returns>
public IAnalysisCapability? GetCapability(string analyzerId)
{
return GetPlugin(analyzerId) as IAnalysisCapability;
}
private AnalyzerPluginAdapter? GetOrCreateAdapter(ILanguageAnalyzer analyzer)
{
lock (_lock)
{
if (_adapters.TryGetValue(analyzer.Id, out var existing))
{
return existing;
}
var plugin = _catalog.Plugins.FirstOrDefault(p =>
{
try
{
var created = p.CreateAnalyzer(_serviceProvider);
return created?.Id.Equals(analyzer.Id, StringComparison.OrdinalIgnoreCase) == true;
}
catch
{
return false;
}
});
if (plugin == null)
{
return null;
}
var filePatterns = KnownFilePatterns.TryGetValue(analyzer.Id, out var patterns)
? patterns
: Array.Empty<string>();
var ecosystems = KnownEcosystems.TryGetValue(analyzer.Id, out var eco)
? eco
: new[] { analyzer.Id };
var adapter = new AnalyzerPluginAdapter(
analyzer,
plugin,
_serviceProvider,
filePatterns,
ecosystems);
_adapters[analyzer.Id] = adapter;
return adapter;
}
}
}
/// <summary>
/// Extension methods for registering unified analyzer plugin services.
/// </summary>
public static class AnalyzerPluginAdapterExtensions
{
/// <summary>
/// Adds unified analyzer plugin adapter services to the service collection.
/// </summary>
/// <param name="services">Service collection.</param>
/// <returns>Service collection for chaining.</returns>
public static IServiceCollection AddUnifiedAnalyzerPlugins(this IServiceCollection services)
{
services.AddSingleton<AnalyzerPluginAdapterFactory>();
return services;
}
}

View File

@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Description>Unified plugin adapter for Scanner language analyzers</Description>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\__Libraries\StellaOps.Scanner.Analyzers.Lang\StellaOps.Scanner.Analyzers.Lang.csproj" />
<ProjectReference Include="..\..\Plugin\StellaOps.Plugin.Abstractions\StellaOps.Plugin.Abstractions.csproj" />
</ItemGroup>
</Project>