feat(installer): add check abstraction and EnvironmentCheckService

Scaffolding for environment checks: IEnvironmentCheck, CheckResult/
CheckSeverity/CheckStatus, and EnvironmentCheckService that runs checks
in parallel while preserving input order, never throws (a failing
check becomes Unknown), and reports HasBlockingError only for
Error+Failed.
This commit is contained in:
mika kuns
2026-08-05 19:11:28 +02:00
parent bdee731376
commit 8b26e23d73
4 changed files with 194 additions and 0 deletions
@@ -0,0 +1,25 @@
namespace ClaudeDo.Installer.Checks;
public enum CheckSeverity { Warning, Error }
public enum CheckStatus { Ok, Failed, Unknown }
public sealed record CheckResult(
string Id,
CheckSeverity Severity,
CheckStatus Status,
string TitleKey,
string Message,
string? HintKey,
string? HelpUrl,
string? Detail)
{
public static CheckResult Ok(string id, CheckSeverity severity, string titleKey, string message, string? detail = null) =>
new(id, severity, CheckStatus.Ok, titleKey, message, HintKey: null, HelpUrl: null, detail);
public static CheckResult Fail(string id, CheckSeverity severity, string titleKey, string message, string? hintKey = null, string? helpUrl = null, string? detail = null) =>
new(id, severity, CheckStatus.Failed, titleKey, message, hintKey, helpUrl, detail);
public static CheckResult Unknown(string id, CheckSeverity severity, string titleKey, string message, string? detail = null) =>
new(id, severity, CheckStatus.Unknown, titleKey, message, HintKey: null, HelpUrl: null, detail);
}
@@ -0,0 +1,37 @@
using ClaudeDo.Installer.Core;
namespace ClaudeDo.Installer.Checks;
public sealed record EnvironmentCheckReport(IReadOnlyList<CheckResult> Results)
{
public bool HasBlockingError => Results.Any(r => r.Severity == CheckSeverity.Error && r.Status == CheckStatus.Failed);
}
public sealed class EnvironmentCheckService
{
private readonly IReadOnlyList<IEnvironmentCheck> _checks;
public EnvironmentCheckService(IEnumerable<IEnvironmentCheck> checks)
{
_checks = checks.ToList();
}
public async Task<EnvironmentCheckReport> RunAllAsync(InstallContext ctx, CancellationToken ct)
{
var tasks = _checks.Select(check => RunSafeAsync(check, ctx, ct)).ToArray();
var results = await Task.WhenAll(tasks);
return new EnvironmentCheckReport(results);
}
private static async Task<CheckResult> RunSafeAsync(IEnvironmentCheck check, InstallContext ctx, CancellationToken ct)
{
try
{
return await check.RunAsync(ctx, ct);
}
catch (Exception ex)
{
return CheckResult.Unknown(check.Id, check.Severity, string.Empty, ex.Message, ex.Message);
}
}
}
@@ -0,0 +1,10 @@
using ClaudeDo.Installer.Core;
namespace ClaudeDo.Installer.Checks;
public interface IEnvironmentCheck
{
string Id { get; }
CheckSeverity Severity { get; }
Task<CheckResult> RunAsync(InstallContext ctx, CancellationToken ct);
}