up the blokcing tasks
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Risk Bundle CI / risk-bundle-build (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Risk Bundle CI / risk-bundle-offline-kit (push) Has been cancelled
Risk Bundle CI / publish-checksums (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Risk Bundle CI / risk-bundle-build (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Risk Bundle CI / risk-bundle-offline-kit (push) Has been cancelled
Risk Bundle CI / publish-checksums (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled
This commit is contained in:
@@ -23,6 +23,8 @@ using Microsoft.Extensions.Options;
|
||||
using Spectre.Console;
|
||||
using Spectre.Console.Rendering;
|
||||
using StellaOps.Auth.Client;
|
||||
using StellaOps.ExportCenter.Client;
|
||||
using StellaOps.ExportCenter.Client.Models;
|
||||
using StellaOps.Cli.Configuration;
|
||||
using StellaOps.Cli.Output;
|
||||
using StellaOps.Cli.Prompts;
|
||||
@@ -24774,8 +24776,485 @@ stella policy test {policyName}.stella
|
||||
|
||||
#endregion
|
||||
|
||||
#region Export Handlers (CLI-EXPORT-35-037)
|
||||
|
||||
internal static async Task<int> HandleExportProfilesListAsync(
|
||||
IServiceProvider services,
|
||||
int? limit,
|
||||
string? cursor,
|
||||
bool json,
|
||||
bool verbose,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
SetVerbosity(services, verbose);
|
||||
var client = services.GetRequiredService<IExportCenterClient>();
|
||||
|
||||
var response = await client.ListProfilesAsync(cursor, limit, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (json)
|
||||
{
|
||||
AnsiConsole.WriteLine(JsonSerializer.Serialize(response, JsonOptions));
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (response.Profiles.Count == 0)
|
||||
{
|
||||
AnsiConsole.MarkupLine("[yellow]No export profiles found.[/]");
|
||||
return 0;
|
||||
}
|
||||
|
||||
var table = new Table();
|
||||
table.AddColumn("Profile ID");
|
||||
table.AddColumn("Name");
|
||||
table.AddColumn("Adapter");
|
||||
table.AddColumn("Format");
|
||||
table.AddColumn("Signing");
|
||||
table.AddColumn("Created");
|
||||
table.AddColumn("Updated");
|
||||
|
||||
foreach (var profile in response.Profiles)
|
||||
{
|
||||
table.AddRow(
|
||||
Markup.Escape(profile.ProfileId),
|
||||
Markup.Escape(profile.Name),
|
||||
Markup.Escape(profile.Adapter),
|
||||
Markup.Escape(profile.OutputFormat),
|
||||
profile.SigningEnabled ? "[green]Yes[/]" : "[grey]No[/]",
|
||||
profile.CreatedAt.ToString("u", CultureInfo.InvariantCulture),
|
||||
profile.UpdatedAt?.ToString("u", CultureInfo.InvariantCulture) ?? "[grey]-[/]");
|
||||
}
|
||||
|
||||
AnsiConsole.Write(table);
|
||||
return 0;
|
||||
}
|
||||
|
||||
internal static async Task<int> HandleExportProfileShowAsync(
|
||||
IServiceProvider services,
|
||||
string profileId,
|
||||
bool json,
|
||||
bool verbose,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
SetVerbosity(services, verbose);
|
||||
var client = services.GetRequiredService<IExportCenterClient>();
|
||||
|
||||
var profile = await client.GetProfileAsync(profileId, cancellationToken).ConfigureAwait(false);
|
||||
if (profile is null)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[red]Profile not found:[/] {Markup.Escape(profileId)}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (json)
|
||||
{
|
||||
AnsiConsole.WriteLine(JsonSerializer.Serialize(profile, JsonOptions));
|
||||
return 0;
|
||||
}
|
||||
|
||||
var profileTable = new Table { Border = TableBorder.Rounded };
|
||||
profileTable.AddColumn("Field");
|
||||
profileTable.AddColumn("Value");
|
||||
profileTable.AddRow("Profile ID", Markup.Escape(profile.ProfileId));
|
||||
profileTable.AddRow("Name", Markup.Escape(profile.Name));
|
||||
profileTable.AddRow("Description", string.IsNullOrWhiteSpace(profile.Description) ? "[grey]-[/]" : Markup.Escape(profile.Description));
|
||||
profileTable.AddRow("Adapter", Markup.Escape(profile.Adapter));
|
||||
profileTable.AddRow("Format", Markup.Escape(profile.OutputFormat));
|
||||
profileTable.AddRow("Signing", profile.SigningEnabled ? "[green]Enabled[/]" : "[grey]Disabled[/]");
|
||||
profileTable.AddRow("Created", profile.CreatedAt.ToString("u", CultureInfo.InvariantCulture));
|
||||
profileTable.AddRow("Updated", profile.UpdatedAt?.ToString("u", CultureInfo.InvariantCulture) ?? "[grey]-[/]");
|
||||
|
||||
if (profile.Selectors is { Count: > 0 })
|
||||
{
|
||||
var selectorTable = new Table { Title = new TableTitle("Selectors") };
|
||||
selectorTable.AddColumn("Key");
|
||||
selectorTable.AddColumn("Value");
|
||||
foreach (var selector in profile.Selectors)
|
||||
{
|
||||
selectorTable.AddRow(Markup.Escape(selector.Key), Markup.Escape(selector.Value));
|
||||
}
|
||||
|
||||
AnsiConsole.Write(profileTable);
|
||||
AnsiConsole.WriteLine();
|
||||
AnsiConsole.Write(selectorTable);
|
||||
}
|
||||
else
|
||||
{
|
||||
AnsiConsole.Write(profileTable);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
internal static async Task<int> HandleExportRunsListAsync(
|
||||
IServiceProvider services,
|
||||
string? profileId,
|
||||
int? limit,
|
||||
string? cursor,
|
||||
bool json,
|
||||
bool verbose,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
SetVerbosity(services, verbose);
|
||||
var client = services.GetRequiredService<IExportCenterClient>();
|
||||
|
||||
var response = await client.ListRunsAsync(profileId, cursor, limit, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (json)
|
||||
{
|
||||
AnsiConsole.WriteLine(JsonSerializer.Serialize(response, JsonOptions));
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (response.Runs.Count == 0)
|
||||
{
|
||||
AnsiConsole.MarkupLine("[yellow]No export runs found.[/]");
|
||||
return 0;
|
||||
}
|
||||
|
||||
var table = new Table();
|
||||
table.AddColumn("Run ID");
|
||||
table.AddColumn("Profile");
|
||||
table.AddColumn("Status");
|
||||
table.AddColumn("Progress");
|
||||
table.AddColumn("Started");
|
||||
table.AddColumn("Completed");
|
||||
table.AddColumn("Bundle");
|
||||
|
||||
foreach (var run in response.Runs)
|
||||
{
|
||||
table.AddRow(
|
||||
Markup.Escape(run.RunId),
|
||||
Markup.Escape(run.ProfileId),
|
||||
Markup.Escape(run.Status),
|
||||
run.Progress.HasValue ? $"{run.Progress.Value}%" : "[grey]-[/]",
|
||||
run.StartedAt?.ToString("u", CultureInfo.InvariantCulture) ?? "[grey]-[/]",
|
||||
run.CompletedAt?.ToString("u", CultureInfo.InvariantCulture) ?? "[grey]-[/]",
|
||||
string.IsNullOrWhiteSpace(run.BundleHash) ? "[grey]-[/]" : Markup.Escape(run.BundleHash));
|
||||
}
|
||||
|
||||
AnsiConsole.Write(table);
|
||||
if (response.HasMore && !string.IsNullOrWhiteSpace(response.ContinuationToken))
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[yellow]More available. Use --cursor {Markup.Escape(response.ContinuationToken)}[/]");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
internal static async Task<int> HandleExportRunShowAsync(
|
||||
IServiceProvider services,
|
||||
string runId,
|
||||
bool json,
|
||||
bool verbose,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
SetVerbosity(services, verbose);
|
||||
var client = services.GetRequiredService<IExportCenterClient>();
|
||||
|
||||
var run = await client.GetRunAsync(runId, cancellationToken).ConfigureAwait(false);
|
||||
if (run is null)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[red]Run not found:[/] {Markup.Escape(runId)}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (json)
|
||||
{
|
||||
AnsiConsole.WriteLine(JsonSerializer.Serialize(run, JsonOptions));
|
||||
return 0;
|
||||
}
|
||||
|
||||
var table = new Table { Border = TableBorder.Rounded };
|
||||
table.AddColumn("Field");
|
||||
table.AddColumn("Value");
|
||||
table.AddRow("Run ID", Markup.Escape(run.RunId));
|
||||
table.AddRow("Profile ID", Markup.Escape(run.ProfileId));
|
||||
table.AddRow("Status", Markup.Escape(run.Status));
|
||||
table.AddRow("Progress", run.Progress.HasValue ? $"{run.Progress.Value}%" : "[grey]-[/]");
|
||||
table.AddRow("Started", run.StartedAt?.ToString("u", CultureInfo.InvariantCulture) ?? "[grey]-[/]");
|
||||
table.AddRow("Completed", run.CompletedAt?.ToString("u", CultureInfo.InvariantCulture) ?? "[grey]-[/]");
|
||||
table.AddRow("Bundle Hash", string.IsNullOrWhiteSpace(run.BundleHash) ? "[grey]-[/]" : Markup.Escape(run.BundleHash));
|
||||
table.AddRow("Bundle URL", string.IsNullOrWhiteSpace(run.BundleUrl) ? "[grey]-[/]" : Markup.Escape(run.BundleUrl));
|
||||
table.AddRow("Error Code", string.IsNullOrWhiteSpace(run.ErrorCode) ? "[grey]-[/]" : Markup.Escape(run.ErrorCode));
|
||||
table.AddRow("Error Message", string.IsNullOrWhiteSpace(run.ErrorMessage) ? "[grey]-[/]" : Markup.Escape(run.ErrorMessage));
|
||||
|
||||
AnsiConsole.Write(table);
|
||||
return 0;
|
||||
}
|
||||
|
||||
internal static async Task<int> HandleExportRunDownloadAsync(
|
||||
IServiceProvider services,
|
||||
string runId,
|
||||
string outputPath,
|
||||
bool overwrite,
|
||||
string? verifyHash,
|
||||
string runType,
|
||||
bool verbose,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
SetVerbosity(services, verbose);
|
||||
var client = services.GetRequiredService<IExportCenterClient>();
|
||||
|
||||
if (File.Exists(outputPath) && !overwrite)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[red]Output file already exists:[/] {Markup.Escape(outputPath)} (use --overwrite to replace)");
|
||||
return 1;
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(outputPath)) ?? ".");
|
||||
|
||||
Stream? stream = null;
|
||||
if (string.Equals(runType, "attestation", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
stream = await client.DownloadAttestationExportAsync(runId, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
stream = await client.DownloadEvidenceExportAsync(runId, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (stream is null)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[red]Export bundle not available for run:[/] {Markup.Escape(runId)}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
await using (stream)
|
||||
await using (var fileStream = File.Create(outputPath))
|
||||
{
|
||||
await stream.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(verifyHash))
|
||||
{
|
||||
await using var file = File.OpenRead(outputPath);
|
||||
var hash = await SHA256.HashDataAsync(file, cancellationToken).ConfigureAwait(false);
|
||||
var hashString = Convert.ToHexString(hash).ToLowerInvariant();
|
||||
if (!string.Equals(hashString, verifyHash.Trim(), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[red]Hash verification failed.[/] expected={Markup.Escape(verifyHash)}, actual={hashString}");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
AnsiConsole.MarkupLine($"[green]Bundle written to[/] {Markup.Escape(outputPath)}");
|
||||
return 0;
|
||||
}
|
||||
|
||||
internal static async Task<int> HandleExportStartEvidenceAsync(
|
||||
IServiceProvider services,
|
||||
string profileId,
|
||||
string[]? selectors,
|
||||
string? callbackUrl,
|
||||
bool json,
|
||||
bool verbose,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
SetVerbosity(services, verbose);
|
||||
var client = services.GetRequiredService<IExportCenterClient>();
|
||||
|
||||
var selectorMap = ParseSelectorMap(selectors);
|
||||
var request = new CreateEvidenceExportRequest(profileId, selectorMap, callbackUrl);
|
||||
var response = await client.CreateEvidenceExportAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (json)
|
||||
{
|
||||
AnsiConsole.WriteLine(JsonSerializer.Serialize(response, JsonOptions));
|
||||
return 0;
|
||||
}
|
||||
|
||||
AnsiConsole.MarkupLine($"[green]Export started.[/] runId={Markup.Escape(response.RunId)} status={Markup.Escape(response.Status)}");
|
||||
return 0;
|
||||
}
|
||||
|
||||
internal static async Task<int> HandleExportStartAttestationAsync(
|
||||
IServiceProvider services,
|
||||
string profileId,
|
||||
string[]? selectors,
|
||||
bool includeTransparencyLog,
|
||||
string? callbackUrl,
|
||||
bool json,
|
||||
bool verbose,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
SetVerbosity(services, verbose);
|
||||
var client = services.GetRequiredService<IExportCenterClient>();
|
||||
|
||||
var selectorMap = ParseSelectorMap(selectors);
|
||||
var request = new CreateAttestationExportRequest(profileId, selectorMap, includeTransparencyLog, callbackUrl);
|
||||
var response = await client.CreateAttestationExportAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (json)
|
||||
{
|
||||
AnsiConsole.WriteLine(JsonSerializer.Serialize(response, JsonOptions));
|
||||
return 0;
|
||||
}
|
||||
|
||||
AnsiConsole.MarkupLine($"[green]Attestation export started.[/] runId={Markup.Escape(response.RunId)} status={Markup.Escape(response.Status)}");
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static IReadOnlyDictionary<string, string>? ParseSelectorMap(string[]? selectors)
|
||||
{
|
||||
if (selectors is null || selectors.Length == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var selector in selectors)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(selector))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var parts = selector.Split('=', 2, StringSplitOptions.TrimEntries);
|
||||
if (parts.Length != 2 || string.IsNullOrWhiteSpace(parts[0]) || string.IsNullOrWhiteSpace(parts[1]))
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[yellow]Ignoring selector with invalid format (expected key=value):[/] {Markup.Escape(selector)}");
|
||||
continue;
|
||||
}
|
||||
|
||||
result[parts[0]] = parts[1];
|
||||
}
|
||||
|
||||
return result.Count == 0 ? null : result;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Notify Handlers (CLI-PARITY-41-002)
|
||||
|
||||
internal static async Task<int> HandleNotifySimulateAsync(
|
||||
IServiceProvider services,
|
||||
string? tenant,
|
||||
string? eventsFile,
|
||||
string? rulesFile,
|
||||
bool enabledOnly,
|
||||
int? lookbackMinutes,
|
||||
int? maxEvents,
|
||||
string? eventKind,
|
||||
bool includeNonMatches,
|
||||
bool json,
|
||||
bool verbose,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
SetVerbosity(services, verbose);
|
||||
var client = services.GetRequiredService<INotifyClient>();
|
||||
|
||||
var eventsPayload = LoadJsonElement(eventsFile);
|
||||
var rulesPayload = LoadJsonElement(rulesFile);
|
||||
|
||||
var request = new NotifySimulationRequest
|
||||
{
|
||||
TenantId = tenant,
|
||||
Events = eventsPayload,
|
||||
Rules = rulesPayload,
|
||||
EnabledRulesOnly = enabledOnly,
|
||||
HistoricalLookbackMinutes = lookbackMinutes,
|
||||
MaxEvents = maxEvents,
|
||||
EventKindFilter = eventKind,
|
||||
IncludeNonMatches = includeNonMatches
|
||||
};
|
||||
|
||||
var result = await client.SimulateAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (json)
|
||||
{
|
||||
AnsiConsole.WriteLine(JsonSerializer.Serialize(result, JsonOptions));
|
||||
return 0;
|
||||
}
|
||||
|
||||
AnsiConsole.MarkupLine(result.SimulationId is null
|
||||
? "[yellow]Simulation completed.[/]"
|
||||
: $"[green]Simulation {Markup.Escape(result.SimulationId)} completed.[/]");
|
||||
|
||||
var table = new Table();
|
||||
table.AddColumn("Total Events");
|
||||
table.AddColumn("Total Rules");
|
||||
table.AddColumn("Matched Events");
|
||||
table.AddColumn("Actions");
|
||||
table.AddColumn("Duration (ms)");
|
||||
|
||||
table.AddRow(
|
||||
(result.TotalEvents ?? 0).ToString(CultureInfo.InvariantCulture),
|
||||
(result.TotalRules ?? 0).ToString(CultureInfo.InvariantCulture),
|
||||
(result.MatchedEvents ?? 0).ToString(CultureInfo.InvariantCulture),
|
||||
(result.TotalActionsTriggered ?? 0).ToString(CultureInfo.InvariantCulture),
|
||||
result.DurationMs?.ToString("0.00", CultureInfo.InvariantCulture) ?? "-");
|
||||
|
||||
AnsiConsole.Write(table);
|
||||
return 0;
|
||||
}
|
||||
|
||||
internal static async Task<int> HandleNotifyAckAsync(
|
||||
IServiceProvider services,
|
||||
string? tenant,
|
||||
string? incidentId,
|
||||
string? token,
|
||||
string? acknowledgedBy,
|
||||
string? comment,
|
||||
bool json,
|
||||
bool verbose,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
SetVerbosity(services, verbose);
|
||||
var client = services.GetRequiredService<INotifyClient>();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(token) && string.IsNullOrWhiteSpace(incidentId))
|
||||
{
|
||||
AnsiConsole.MarkupLine("[red]Either --token or --incident-id is required.[/]");
|
||||
return 1;
|
||||
}
|
||||
|
||||
var request = new NotifyAckRequest
|
||||
{
|
||||
TenantId = tenant,
|
||||
IncidentId = incidentId,
|
||||
Token = token,
|
||||
AcknowledgedBy = acknowledgedBy,
|
||||
Comment = comment
|
||||
};
|
||||
|
||||
var result = await client.AckAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (json)
|
||||
{
|
||||
AnsiConsole.WriteLine(JsonSerializer.Serialize(result, JsonOptions));
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[red]Acknowledge failed:[/] {Markup.Escape(result.Error ?? "unknown error")}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
AnsiConsole.MarkupLine($"[green]Acknowledged.[/] incidentId={Markup.Escape(result.IncidentId ?? incidentId ?? "n/a")}");
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static JsonElement? LoadJsonElement(string? filePath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filePath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var content = File.ReadAllText(filePath);
|
||||
using var doc = JsonDocument.Parse(content);
|
||||
return doc.RootElement.Clone();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[yellow]Failed to load JSON from {Markup.Escape(filePath)}:[/] {Markup.Escape(ex.Message)}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
internal static async Task<int> HandleNotifyChannelsListAsync(
|
||||
IServiceProvider services,
|
||||
string? tenant,
|
||||
|
||||
Reference in New Issue
Block a user