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>
164 lines
5.3 KiB
C#
164 lines
5.3 KiB
C#
using System.Collections.ObjectModel;
|
|
using System.Diagnostics;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using ClaudeDo.Installer.Core;
|
|
using ClaudeDo.Installer.Steps;
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
using CommunityToolkit.Mvvm.Input;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
|
|
namespace ClaudeDo.Installer.Pages.InstallPage;
|
|
|
|
public partial class InstallPageViewModel : ObservableObject, IInstallerPage
|
|
{
|
|
private readonly InstallContext _context;
|
|
private readonly IServiceProvider _serviceProvider;
|
|
private InstallPageView? _view;
|
|
private CancellationTokenSource? _cts;
|
|
|
|
public string Title => "Install";
|
|
public string Icon => "\uE896";
|
|
public int Order => 99;
|
|
public bool ShowInWizard => true;
|
|
public bool ShowInSettings => false;
|
|
public UserControl View => _view ??= new InstallPageView { DataContext = this };
|
|
|
|
public ObservableCollection<StepViewModel> Steps { get; } = [];
|
|
|
|
[ObservableProperty] private bool _isInstalling;
|
|
[ObservableProperty] private bool _isComplete;
|
|
[ObservableProperty] private bool _hasErrors;
|
|
[ObservableProperty] private double _overallProgress;
|
|
|
|
public InstallPageViewModel(InstallContext context, IServiceProvider serviceProvider)
|
|
{
|
|
_context = context;
|
|
_serviceProvider = serviceProvider;
|
|
}
|
|
|
|
public Task LoadAsync()
|
|
{
|
|
Steps.Clear();
|
|
if (_context.Mode == InstallerMode.Update)
|
|
{
|
|
Steps.Add(new StepViewModel("Stop Worker Service"));
|
|
Steps.Add(new StepViewModel("Download and Extract"));
|
|
Steps.Add(new StepViewModel("Start Worker Service"));
|
|
Steps.Add(new StepViewModel("Write Install Manifest"));
|
|
}
|
|
else
|
|
{
|
|
Steps.Add(new StepViewModel("Download and Extract"));
|
|
Steps.Add(new StepViewModel("Write Configuration"));
|
|
Steps.Add(new StepViewModel("Initialize Database"));
|
|
Steps.Add(new StepViewModel("Register Windows Service"));
|
|
Steps.Add(new StepViewModel("Create Shortcuts"));
|
|
Steps.Add(new StepViewModel("Register in Add/Remove Programs"));
|
|
Steps.Add(new StepViewModel("Write Install Manifest"));
|
|
}
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public Task ApplyAsync() => RunInstallAsync();
|
|
|
|
public bool Validate() => true;
|
|
|
|
[RelayCommand]
|
|
private async Task RunInstallAsync()
|
|
{
|
|
if (IsInstalling) return;
|
|
|
|
IsInstalling = true;
|
|
IsComplete = false;
|
|
HasErrors = false;
|
|
OverallProgress = 0;
|
|
|
|
_cts = new CancellationTokenSource();
|
|
|
|
var progress = new Progress<StepProgress>(p =>
|
|
{
|
|
var step = Steps.FirstOrDefault(s => s.Name == p.StepName);
|
|
if (step is null) return;
|
|
|
|
step.Status = p.Status;
|
|
if (p.Message is not null)
|
|
{
|
|
// Messages starting with "\r" overwrite the previous line (live progress).
|
|
if (p.Message.StartsWith('\r'))
|
|
{
|
|
var line = p.Message[1..];
|
|
if (step.Messages.Count > 0 && step.Messages[^1].StartsWith(" "))
|
|
step.Messages[^1] = line;
|
|
else
|
|
step.Messages.Add(line);
|
|
}
|
|
else
|
|
{
|
|
step.Messages.Add(p.Message);
|
|
}
|
|
}
|
|
|
|
if (p.Status is StepStatus.Running && !step.IsExpanded)
|
|
step.IsExpanded = true;
|
|
|
|
if (p.Status is StepStatus.Done or StepStatus.Failed)
|
|
{
|
|
var completed = Steps.Count(s => s.Status is StepStatus.Done or StepStatus.Failed);
|
|
OverallProgress = (double)completed / Steps.Count * 100;
|
|
}
|
|
});
|
|
|
|
try
|
|
{
|
|
IEnumerable<IInstallStep> steps;
|
|
if (_context.Mode == InstallerMode.Update)
|
|
{
|
|
steps = new IInstallStep[]
|
|
{
|
|
_serviceProvider.GetRequiredService<StopServiceStep>(),
|
|
_serviceProvider.GetRequiredService<DownloadAndExtractStep>(),
|
|
_serviceProvider.GetRequiredService<StartServiceStep>(),
|
|
_serviceProvider.GetRequiredService<WriteInstallManifestStep>(),
|
|
};
|
|
}
|
|
else
|
|
{
|
|
steps = _serviceProvider.GetServices<IInstallStep>();
|
|
}
|
|
|
|
var runner = new InstallerService(steps);
|
|
var results = await runner.ExecuteAsync(_context, progress, _cts.Token);
|
|
HasErrors = results.Any(r => !r.Result.Success);
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
HasErrors = true;
|
|
}
|
|
finally
|
|
{
|
|
IsInstalling = false;
|
|
IsComplete = true;
|
|
_cts.Dispose();
|
|
_cts = null;
|
|
}
|
|
}
|
|
|
|
[RelayCommand]
|
|
private void CancelInstall()
|
|
{
|
|
_cts?.Cancel();
|
|
}
|
|
|
|
[RelayCommand]
|
|
private void LaunchApp()
|
|
{
|
|
var appExe = System.IO.Path.Combine(_context.InstallDirectory, "app", "ClaudeDo.App.exe");
|
|
if (System.IO.File.Exists(appExe))
|
|
{
|
|
Process.Start(new ProcessStartInfo(appExe) { UseShellExecute = true });
|
|
Application.Current.Shutdown();
|
|
}
|
|
}
|
|
}
|