88 lines
3.6 KiB
C#
88 lines
3.6 KiB
C#
using BlueLaminate.Cli.Commands;
|
|
using BlueLaminate.Cli.Logging;
|
|
using BlueLaminate.Core.DependencyInjection;
|
|
using BlueLaminate.EFCore.Data;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Hosting;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Options;
|
|
using OpenTelemetry;
|
|
using OpenTelemetry.Resources;
|
|
using System.CommandLine;
|
|
|
|
// Generic Host = composition root. The exact same wiring a web frontend would use:
|
|
// configuration → AddBlueLaminateCore → resolve services per command scope. Args are
|
|
// deliberately NOT handed to the host (System.CommandLine owns parsing; the host's
|
|
// command-line config provider would reject bare verbs like "sync-skins"). The
|
|
// content root is the binary directory so appsettings.json is found regardless of CWD.
|
|
var builder = Host.CreateApplicationBuilder(new HostApplicationBuilderSettings
|
|
{
|
|
ContentRootPath = AppContext.BaseDirectory,
|
|
});
|
|
|
|
// Reuse the connection string stored in the EFCore project's user secrets (dev).
|
|
builder.Configuration.AddUserSecrets<SkinTrackerDbContextFactory>(optional: true);
|
|
|
|
// OpenTelemetry logging through a compact console sink that prints one
|
|
// "{utc timestamp} {message}" line per record. Swapping in an OTLP exporter later
|
|
// is a change here. ClearProviders drops the default console logger so we don't
|
|
// double-print.
|
|
builder.Logging.ClearProviders();
|
|
// IHttpClientFactory logs each request at Information under these categories; mute
|
|
// to Warning so the compact console stays one line per app message.
|
|
builder.Logging.AddFilter("System.Net.Http.HttpClient", LogLevel.Warning);
|
|
// EF Core logs every SQL command at Information; we only care about failures, so
|
|
// raise its floor to Warning (failed commands still log at Error).
|
|
builder.Logging.AddFilter("Microsoft.EntityFrameworkCore", LogLevel.Warning);
|
|
builder.Logging.AddOpenTelemetry(otel =>
|
|
{
|
|
otel.SetResourceBuilder(
|
|
ResourceBuilder.CreateDefault().AddService("BlueLaminate.Cli"));
|
|
otel.IncludeFormattedMessage = true;
|
|
otel.AddProcessor(new SimpleLogRecordExportProcessor(new CompactConsoleLogExporter()));
|
|
});
|
|
|
|
builder.Services.AddBlueLaminateCore(builder.Configuration);
|
|
|
|
using var host = builder.Build();
|
|
|
|
// This CLI builds the host but doesn't run it, so ValidateOnStart won't fire on its
|
|
// own — trigger it explicitly. Invalid configuration (e.g. CsFloat:MaxLimit out of
|
|
// range) fails fast here with a clear message instead of being silently clamped.
|
|
try
|
|
{
|
|
host.Services.GetRequiredService<IStartupValidator>().Validate();
|
|
}
|
|
catch (OptionsValidationException ex)
|
|
{
|
|
Console.Error.WriteLine("Invalid configuration:");
|
|
foreach (var failure in ex.Failures)
|
|
{
|
|
Console.Error.WriteLine($" - {failure}");
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
// System.CommandLine builds the command tree, parsing, and help. Each command lives
|
|
// in its own file under Commands/ and resolves its service from a DI scope.
|
|
var root = new RootCommand("BlueLaminate CLI — Counter-Strike skin tracker tools.")
|
|
{
|
|
SyncSkinsCommand.Build(host),
|
|
FetchListingsCommand.Build(host),
|
|
SweepListingsCommand.Build(host),
|
|
SweepCatalogCommand.Build(host),
|
|
};
|
|
|
|
// Ctrl+C → cancel the action's token so long-running commands (e.g. sweep-catalog,
|
|
// which loops until stopped) unwind gracefully instead of hard-killing the process
|
|
// mid-write.
|
|
using var cts = new CancellationTokenSource();
|
|
Console.CancelKeyPress += (_, e) =>
|
|
{
|
|
e.Cancel = true; // prevent immediate termination; let the token cancel cleanly
|
|
cts.Cancel();
|
|
};
|
|
|
|
return await root.Parse(args).InvokeAsync(cancellationToken: cts.Token);
|