Refactor code structure for improved readability and maintainability
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-12-06 21:48:12 +02:00
parent f6c22854a4
commit dd0067ea0b
105 changed files with 12662 additions and 427 deletions

View File

@@ -29,6 +29,7 @@ using StellaOps.Cli.Prompts;
using StellaOps.Cli.Services;
using StellaOps.Cli.Services.Models;
using StellaOps.Cli.Services.Models.AdvisoryAi;
using StellaOps.Cli.Services.Models.Bun;
using StellaOps.Cli.Services.Models.Ruby;
using StellaOps.Cli.Telemetry;
using StellaOps.Cryptography;
@@ -40,6 +41,7 @@ using StellaOps.Scanner.Analyzers.Lang.Node;
using StellaOps.Scanner.Analyzers.Lang.Python;
using StellaOps.Scanner.Analyzers.Lang.Ruby;
using StellaOps.Scanner.Analyzers.Lang.Php;
using StellaOps.Scanner.Analyzers.Lang.Bun;
using StellaOps.Policy;
using StellaOps.PolicyDsl;
@@ -8327,6 +8329,191 @@ internal static class CommandHandlers
}
}
public static async Task HandleBunInspectAsync(
IServiceProvider services,
string? rootPath,
string format,
bool verbose,
CancellationToken cancellationToken)
{
await using var scope = services.CreateAsyncScope();
var logger = scope.ServiceProvider.GetRequiredService<ILoggerFactory>().CreateLogger("bun-inspect");
var verbosity = scope.ServiceProvider.GetRequiredService<VerbosityState>();
var previousLevel = verbosity.MinimumLevel;
verbosity.MinimumLevel = verbose ? LogLevel.Debug : LogLevel.Information;
using var activity = CliActivitySource.Instance.StartActivity("cli.bun.inspect", ActivityKind.Internal);
activity?.SetTag("stellaops.cli.command", "bun inspect");
using var duration = CliMetrics.MeasureCommandDuration("bun inspect");
var outcome = "unknown";
try
{
var normalizedFormat = string.IsNullOrWhiteSpace(format)
? "table"
: format.Trim().ToLowerInvariant();
if (normalizedFormat is not ("table" or "json"))
{
throw new InvalidOperationException("Format must be either 'table' or 'json'.");
}
var targetRoot = string.IsNullOrWhiteSpace(rootPath)
? Directory.GetCurrentDirectory()
: Path.GetFullPath(rootPath);
if (!Directory.Exists(targetRoot))
{
throw new DirectoryNotFoundException($"Directory '{targetRoot}' was not found.");
}
logger.LogInformation("Inspecting Bun workspace in {Root}.", targetRoot);
activity?.SetTag("stellaops.cli.bun.root", targetRoot);
var engine = new LanguageAnalyzerEngine(new ILanguageAnalyzer[] { new BunLanguageAnalyzer() });
var context = new LanguageAnalyzerContext(targetRoot, TimeProvider.System);
var result = await engine.AnalyzeAsync(context, cancellationToken).ConfigureAwait(false);
var report = BunInspectReport.Create(result.ToSnapshots());
activity?.SetTag("stellaops.cli.bun.package_count", report.Packages.Count);
if (string.Equals(normalizedFormat, "json", StringComparison.Ordinal))
{
var options = new JsonSerializerOptions(JsonSerializerDefaults.Web)
{
WriteIndented = true
};
Console.WriteLine(JsonSerializer.Serialize(report, options));
}
else
{
RenderBunInspectReport(report);
}
outcome = report.Packages.Count == 0 ? "empty" : "ok";
Environment.ExitCode = 0;
}
catch (DirectoryNotFoundException ex)
{
outcome = "not_found";
logger.LogError(ex.Message);
Environment.ExitCode = 71;
}
catch (InvalidOperationException ex)
{
outcome = "invalid";
logger.LogError(ex.Message);
Environment.ExitCode = 64;
}
catch (Exception ex)
{
outcome = "error";
logger.LogError(ex, "Bun inspect failed.");
Environment.ExitCode = 70;
}
finally
{
verbosity.MinimumLevel = previousLevel;
CliMetrics.RecordBunInspect(outcome);
}
}
public static async Task HandleBunResolveAsync(
IServiceProvider services,
string? imageReference,
string? scanId,
string format,
bool verbose,
CancellationToken cancellationToken)
{
await using var scope = services.CreateAsyncScope();
var client = scope.ServiceProvider.GetRequiredService<IBackendOperationsClient>();
var logger = scope.ServiceProvider.GetRequiredService<ILoggerFactory>().CreateLogger("bun-resolve");
var verbosity = scope.ServiceProvider.GetRequiredService<VerbosityState>();
var previousLevel = verbosity.MinimumLevel;
verbosity.MinimumLevel = verbose ? LogLevel.Debug : LogLevel.Information;
using var activity = CliActivitySource.Instance.StartActivity("cli.bun.resolve", ActivityKind.Client);
activity?.SetTag("stellaops.cli.command", "bun resolve");
using var duration = CliMetrics.MeasureCommandDuration("bun resolve");
var outcome = "unknown";
try
{
var normalizedFormat = string.IsNullOrWhiteSpace(format)
? "table"
: format.Trim().ToLowerInvariant();
if (normalizedFormat is not ("table" or "json"))
{
throw new InvalidOperationException("Format must be either 'table' or 'json'.");
}
var identifier = !string.IsNullOrWhiteSpace(scanId)
? scanId!.Trim()
: imageReference?.Trim();
if (string.IsNullOrWhiteSpace(identifier))
{
throw new InvalidOperationException("An --image or --scan-id value is required.");
}
logger.LogInformation("Resolving Bun packages for scan {ScanId}.", identifier);
activity?.SetTag("stellaops.cli.scan_id", identifier);
var inventory = await client.GetBunPackagesAsync(identifier, cancellationToken).ConfigureAwait(false);
if (inventory is null)
{
outcome = "empty";
Environment.ExitCode = 0;
AnsiConsole.MarkupLine("[yellow]Bun package inventory is not available for scan {0}.[/]", Markup.Escape(identifier));
return;
}
var report = BunResolveReport.Create(inventory);
if (!report.HasPackages)
{
AnsiConsole.MarkupLine("[yellow]No Bun packages found for scan {0}.[/]", Markup.Escape(identifier));
}
else if (string.Equals(normalizedFormat, "json", StringComparison.Ordinal))
{
var options = new JsonSerializerOptions(JsonSerializerDefaults.Web)
{
WriteIndented = true
};
Console.WriteLine(JsonSerializer.Serialize(report, options));
}
else
{
RenderBunResolveReport(report);
}
outcome = report.HasPackages ? "ok" : "empty";
Environment.ExitCode = 0;
}
catch (InvalidOperationException ex)
{
outcome = "invalid";
logger.LogError(ex.Message);
Environment.ExitCode = 64;
}
catch (HttpRequestException ex)
{
outcome = "network_error";
logger.LogError(ex, "Failed to resolve Bun packages.");
Environment.ExitCode = 69;
}
catch (Exception ex)
{
outcome = "error";
logger.LogError(ex, "Bun resolve failed.");
Environment.ExitCode = 70;
}
finally
{
verbosity.MinimumLevel = previousLevel;
CliMetrics.RecordBunResolve(outcome);
}
}
private static void RenderPythonInspectReport(IReadOnlyList<LanguageComponentSnapshot> snapshots)
{
if (snapshots.Count == 0)
@@ -8384,6 +8571,64 @@ internal static class CommandHandlers
AnsiConsole.Write(table);
}
private static void RenderBunInspectReport(BunInspectReport report)
{
if (!report.Packages.Any())
{
AnsiConsole.MarkupLine("[yellow]No Bun packages detected.[/]");
return;
}
var table = new Table().Border(TableBorder.Rounded);
table.AddColumn("Package");
table.AddColumn("Version");
table.AddColumn("Source");
table.AddColumn("Dev");
table.AddColumn("Direct");
foreach (var entry in report.Packages)
{
var dev = entry.IsDev ? "[grey]yes[/]" : "-";
var direct = entry.IsDirect ? "[blue]yes[/]" : "-";
table.AddRow(
Markup.Escape(entry.Name),
Markup.Escape(entry.Version ?? "-"),
Markup.Escape(entry.Source ?? "-"),
dev,
direct);
}
AnsiConsole.Write(table);
AnsiConsole.MarkupLine($"[grey]Total packages: {report.Packages.Count}[/]");
}
private static void RenderBunResolveReport(BunResolveReport report)
{
if (!report.HasPackages)
{
AnsiConsole.MarkupLine("[yellow]No Bun packages found.[/]");
return;
}
var table = new Table().Border(TableBorder.Rounded);
table.AddColumn("Package");
table.AddColumn("Version");
table.AddColumn("Source");
table.AddColumn("Integrity");
foreach (var entry in report.Packages)
{
table.AddRow(
Markup.Escape(entry.Name),
Markup.Escape(entry.Version ?? "-"),
Markup.Escape(entry.Source ?? "-"),
Markup.Escape(entry.Integrity ?? "-"));
}
AnsiConsole.Write(table);
AnsiConsole.MarkupLine($"[grey]Scan: {Markup.Escape(report.ScanId ?? "-")} • Total: {report.Packages.Count}[/]");
}
private static void RenderRubyInspectReport(RubyInspectReport report)
{
if (!report.Packages.Any())
@@ -8999,6 +9244,163 @@ internal static class CommandHandlers
}
}
private sealed class BunInspectReport
{
[JsonPropertyName("packages")]
public IReadOnlyList<BunInspectEntry> Packages { get; }
private BunInspectReport(IReadOnlyList<BunInspectEntry> packages)
{
Packages = packages;
}
public static BunInspectReport Create(IEnumerable<LanguageComponentSnapshot>? snapshots)
{
var source = snapshots?.ToArray() ?? Array.Empty<LanguageComponentSnapshot>();
var entries = source
.Where(static snapshot => string.Equals(snapshot.Type, "npm", StringComparison.OrdinalIgnoreCase))
.Select(BunInspectEntry.FromSnapshot)
.OrderBy(static entry => entry.Name, StringComparer.OrdinalIgnoreCase)
.ThenBy(static entry => entry.Version ?? string.Empty, StringComparer.OrdinalIgnoreCase)
.ToArray();
return new BunInspectReport(entries);
}
}
private sealed record BunInspectEntry(
[property: JsonPropertyName("name")] string Name,
[property: JsonPropertyName("version")] string? Version,
[property: JsonPropertyName("source")] string? Source,
[property: JsonPropertyName("isDev")] bool IsDev,
[property: JsonPropertyName("isDirect")] bool IsDirect,
[property: JsonPropertyName("resolved")] string? Resolved,
[property: JsonPropertyName("integrity")] string? Integrity)
{
public static BunInspectEntry FromSnapshot(LanguageComponentSnapshot snapshot)
{
var metadata = BunMetadataHelpers.Clone(snapshot.Metadata);
var source = BunMetadataHelpers.GetString(metadata, "source");
var isDev = BunMetadataHelpers.GetBool(metadata, "dev") ?? false;
var isDirect = BunMetadataHelpers.GetBool(metadata, "direct") ?? false;
var resolved = BunMetadataHelpers.GetString(metadata, "resolved");
var integrity = BunMetadataHelpers.GetString(metadata, "integrity");
return new BunInspectEntry(
snapshot.Name ?? "-",
snapshot.Version,
source,
isDev,
isDirect,
resolved,
integrity);
}
}
private static class BunMetadataHelpers
{
public static IDictionary<string, string?> Clone(IDictionary<string, string?>? metadata)
{
if (metadata is null || metadata.Count == 0)
{
return new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
}
var clone = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
foreach (var pair in metadata)
{
clone[pair.Key] = pair.Value;
}
return clone;
}
public static string? GetString(IDictionary<string, string?> metadata, string key)
{
if (metadata.TryGetValue(key, out var value))
{
return value;
}
foreach (var pair in metadata)
{
if (string.Equals(pair.Key, key, StringComparison.OrdinalIgnoreCase))
{
return pair.Value;
}
}
return null;
}
public static bool? GetBool(IDictionary<string, string?> metadata, string key)
{
var value = GetString(metadata, key);
if (string.IsNullOrWhiteSpace(value))
{
return null;
}
if (bool.TryParse(value, out var parsed))
{
return parsed;
}
return null;
}
}
private sealed class BunResolveReport
{
[JsonPropertyName("scanId")]
public string? ScanId { get; }
[JsonPropertyName("packages")]
public IReadOnlyList<BunResolveEntry> Packages { get; }
[JsonIgnore]
public bool HasPackages => Packages.Count > 0;
private BunResolveReport(string? scanId, IReadOnlyList<BunResolveEntry> packages)
{
ScanId = scanId;
Packages = packages;
}
public static BunResolveReport Create(BunPackageInventory? inventory)
{
if (inventory is null)
{
return new BunResolveReport(null, Array.Empty<BunResolveEntry>());
}
var entries = inventory.Packages
.Select(BunResolveEntry.FromPackage)
.OrderBy(static entry => entry.Name, StringComparer.OrdinalIgnoreCase)
.ThenBy(static entry => entry.Version ?? string.Empty, StringComparer.OrdinalIgnoreCase)
.ToArray();
return new BunResolveReport(inventory.ScanId, entries);
}
}
private sealed record BunResolveEntry(
[property: JsonPropertyName("name")] string Name,
[property: JsonPropertyName("version")] string? Version,
[property: JsonPropertyName("source")] string? Source,
[property: JsonPropertyName("integrity")] string? Integrity)
{
public static BunResolveEntry FromPackage(BunPackageItem package)
{
return new BunResolveEntry(
package.Name,
package.Version,
package.Source,
package.Integrity);
}
}
private sealed record LockValidationEntry(
[property: JsonPropertyName("name")] string Name,
[property: JsonPropertyName("version")] string? Version,