Some checks failed
Release / release (push) Failing after 0s
Worker: - Wire UseWindowsService + Microsoft.Extensions.Hosting.WindowsServices so SCM's Service Control Protocol handshake succeeds. Previously the binary exited immediately under sc start, leaving the service registered but never running. Installer: - Pin SDK to .NET 9 (global.json) — SDK 10 dropped win-arm from its RID graph, breaking restore of the WPF project; .NET 9 keeps win-arm AND understands the .slnx solution format. - Force SelfContained=true and default RID=win-x64 when PublishSingleFile is set, so Rider Publish and CLI produce the same bundle. - Dark theme: set Background/Foreground explicitly on WizardWindow and SettingsWindow roots (WPF implicit styles don't cascade to derived Window types). Custom ComboBox template + ComboBoxItem style so dropdowns honour the dark palette instead of system defaults. - Throttle download progress to one report per MB and overwrite the same UI line (\r prefix marker) instead of appending per chunk. - Register ClaudeDo in HKLM\...\Uninstall so it appears in Apps & Features. Copy installer into InstallDir\uninstaller\ for the UninstallString, and schedule a cmd.exe trampoline to handle the self-delete case when Apps & Features launches the copy from inside the install dir. - Treat sc.exe stop exit 1062 (ERROR_SERVICE_NOT_ACTIVE) as success. - Delete the uninstall registry key during UninstallRunner. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
133 lines
5.1 KiB
C#
133 lines
5.1 KiB
C#
using System.Net.Http;
|
|
using System.Reflection;
|
|
using System.Windows;
|
|
using ClaudeDo.Installer.Core;
|
|
using ClaudeDo.Installer.Pages.InstallPage;
|
|
using ClaudeDo.Installer.Pages.PathsPage;
|
|
using ClaudeDo.Installer.Pages.ServicePage;
|
|
using ClaudeDo.Installer.Pages.UiSettingsPage;
|
|
using ClaudeDo.Installer.Pages.WelcomePage;
|
|
using ClaudeDo.Installer.Steps;
|
|
using ClaudeDo.Installer.Views;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
|
|
namespace ClaudeDo.Installer;
|
|
|
|
public partial class App : Application
|
|
{
|
|
private ServiceProvider? _services;
|
|
|
|
protected override async void OnStartup(StartupEventArgs e)
|
|
{
|
|
base.OnStartup(e);
|
|
|
|
_services = BuildServices();
|
|
|
|
var context = _services.GetRequiredService<InstallContext>();
|
|
context.InstallerVersion = GetInstallerVersion();
|
|
|
|
// Default install dir for detection — on upgrade we stay where we were.
|
|
var detector = _services.GetRequiredService<InstallModeDetector>();
|
|
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
|
|
|
// Read manifest up front so we can fall back to Config if the API times out
|
|
// on an existing install. If the API is slow, we do NOT want to drop an
|
|
// already-installed user into FreshInstall — that would risk overwriting them.
|
|
var existingManifest = InstallManifestStore.TryRead(context.InstallDirectory);
|
|
|
|
DetectedState state;
|
|
try
|
|
{
|
|
state = await detector.DetectAsync(context.InstallDirectory, cts.Token);
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
state = existingManifest is not null
|
|
? new DetectedState(InstallerMode.Config, existingManifest, null, null)
|
|
: 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);
|
|
mainWindow.Show();
|
|
}
|
|
|
|
protected override void OnExit(ExitEventArgs e)
|
|
{
|
|
_services?.Dispose();
|
|
base.OnExit(e);
|
|
}
|
|
|
|
private static string GetInstallerVersion()
|
|
{
|
|
var infoAttr = Assembly.GetExecutingAssembly()
|
|
.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
|
|
return infoAttr?.InformationalVersion ?? "0.0.0";
|
|
}
|
|
|
|
private static ServiceProvider BuildServices()
|
|
{
|
|
var sc = new ServiceCollection();
|
|
|
|
// Core
|
|
sc.AddSingleton<InstallContext>();
|
|
sc.AddSingleton<PageResolver>();
|
|
// HTTP + release client
|
|
sc.AddSingleton(_ => new HttpClient { Timeout = TimeSpan.FromSeconds(15) });
|
|
sc.AddSingleton<IReleaseClient>(sp => new ReleaseClient(sp.GetRequiredService<HttpClient>()));
|
|
sc.AddSingleton<InstallModeDetector>();
|
|
|
|
// Pages
|
|
sc.AddSingleton<IInstallerPage, WelcomePageViewModel>();
|
|
sc.AddSingleton<IInstallerPage, PathsPageViewModel>();
|
|
sc.AddSingleton<IInstallerPage, ServicePageViewModel>();
|
|
sc.AddSingleton<IInstallerPage, UiSettingsPageViewModel>();
|
|
sc.AddSingleton<IInstallerPage, InstallPageViewModel>();
|
|
|
|
// 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, InitDatabaseStep>();
|
|
sc.AddSingleton<IInstallStep, RegisterServiceStep>();
|
|
sc.AddSingleton<IInstallStep, CreateShortcutsStep>();
|
|
sc.AddSingleton<IInstallStep, WriteUninstallRegistryStep>();
|
|
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>();
|
|
|
|
// Runners
|
|
sc.AddSingleton<UninstallRunner>();
|
|
|
|
// ViewModels
|
|
sc.AddSingleton<WizardViewModel>();
|
|
sc.AddSingleton<SettingsViewModel>();
|
|
|
|
return sc.BuildServiceProvider();
|
|
}
|
|
}
|