feat(installer): async mode detection + mode-aware DI wiring

This commit is contained in:
Mika Kuns
2026-04-15 10:01:20 +02:00
parent 12e532718c
commit 01c29bb6f6

View File

@@ -1,3 +1,6 @@
using System.IO;
using System.Net.Http;
using System.Reflection;
using System.Windows; using System.Windows;
using ClaudeDo.Installer.Core; using ClaudeDo.Installer.Core;
using ClaudeDo.Installer.Pages.InstallPage; using ClaudeDo.Installer.Pages.InstallPage;
@@ -15,16 +18,45 @@ public partial class App : Application
{ {
private ServiceProvider? _services; private ServiceProvider? _services;
protected override void OnStartup(StartupEventArgs e) protected override async void OnStartup(StartupEventArgs e)
{ {
base.OnStartup(e); base.OnStartup(e);
_services = BuildServices(); _services = BuildServices();
// TODO(Task 11): replace with async InstallModeDetector var context = _services.GetRequiredService<InstallContext>();
Window mainWindow = new WizardWindow context.InstallerVersion = GetInstallerVersion();
// Default install dir for detection — on upgrade we stay where we were.
var detector = _services.GetRequiredService<InstallModeDetector>();
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
DetectedState state;
try
{ {
DataContext = _services.GetRequiredService<WizardViewModel>() state = await detector.DetectAsync(context.InstallDirectory, cts.Token);
}
catch (OperationCanceledException)
{
state = new DetectedState(InstallerMode.FreshInstall, null, null, null);
}
context.Mode = state.Mode;
context.InstalledVersion = state.Existing?.Version;
context.LatestVersion = state.LatestVersion;
if (state.Existing is not null)
context.InstallDirectory = state.Existing.InstallDir;
Window mainWindow = state.Mode switch
{
InstallerMode.FreshInstall or InstallerMode.Update => new WizardWindow
{
DataContext = _services.GetRequiredService<WizardViewModel>()
},
InstallerMode.Config => new SettingsWindow
{
DataContext = _services.GetRequiredService<SettingsViewModel>()
},
_ => throw new InvalidOperationException($"Unknown installer mode: {state.Mode}")
}; };
DarkTitleBar.Apply(mainWindow); DarkTitleBar.Apply(mainWindow);
@@ -37,6 +69,13 @@ public partial class App : Application
base.OnExit(e); base.OnExit(e);
} }
private static string GetInstallerVersion()
{
var infoAttr = Assembly.GetExecutingAssembly()
.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
return infoAttr?.InformationalVersion ?? "0.0.0";
}
private static ServiceProvider BuildServices() private static ServiceProvider BuildServices()
{ {
var sc = new ServiceCollection(); var sc = new ServiceCollection();
@@ -46,6 +85,11 @@ public partial class App : Application
sc.AddSingleton<PageResolver>(); sc.AddSingleton<PageResolver>();
sc.AddSingleton<InstallerService>(); sc.AddSingleton<InstallerService>();
// HTTP + release client
sc.AddSingleton(_ => new HttpClient { Timeout = TimeSpan.FromSeconds(15) });
sc.AddSingleton<IReleaseClient>(sp => new ReleaseClient(sp.GetRequiredService<HttpClient>()));
sc.AddSingleton<InstallModeDetector>();
// Pages // Pages
sc.AddSingleton<IInstallerPage, WelcomePageViewModel>(); sc.AddSingleton<IInstallerPage, WelcomePageViewModel>();
sc.AddSingleton<IInstallerPage, PathsPageViewModel>(); sc.AddSingleton<IInstallerPage, PathsPageViewModel>();
@@ -53,11 +97,22 @@ public partial class App : Application
sc.AddSingleton<IInstallerPage, UiSettingsPageViewModel>(); sc.AddSingleton<IInstallerPage, UiSettingsPageViewModel>();
sc.AddSingleton<IInstallerPage, InstallPageViewModel>(); sc.AddSingleton<IInstallerPage, InstallPageViewModel>();
// Steps (registration order = execution order) // Steps — execution order matters for the FreshInstall pipeline (IEnumerable<IInstallStep>).
// Double-registered as both IInstallStep and concrete type so Task 15's Update pipeline
// can pull them out individually via GetRequiredService<T>().
sc.AddSingleton<DownloadAndExtractStep>();
sc.AddSingleton<IInstallStep>(sp => sp.GetRequiredService<DownloadAndExtractStep>());
sc.AddSingleton<IInstallStep, WriteConfigStep>(); sc.AddSingleton<IInstallStep, WriteConfigStep>();
sc.AddSingleton<IInstallStep, InitDatabaseStep>(); sc.AddSingleton<IInstallStep, InitDatabaseStep>();
sc.AddSingleton<IInstallStep, RegisterServiceStep>(); sc.AddSingleton<IInstallStep, RegisterServiceStep>();
sc.AddSingleton<IInstallStep, CreateShortcutsStep>(); sc.AddSingleton<IInstallStep, CreateShortcutsStep>();
sc.AddSingleton<WriteInstallManifestStep>();
sc.AddSingleton<IInstallStep>(sp => sp.GetRequiredService<WriteInstallManifestStep>());
// Stop/Start — NOT registered as IInstallStep (not part of default FreshInstall pipeline).
// Pulled by Update flow + Repair/Uninstall.
sc.AddSingleton<StopServiceStep>();
sc.AddSingleton<StartServiceStep>();
// ViewModels // ViewModels
sc.AddSingleton<WizardViewModel>(); sc.AddSingleton<WizardViewModel>();