feat(installer): self-update pre-flight before wizard
This commit is contained in:
@@ -22,6 +22,104 @@ public partial class App : Application
|
|||||||
{
|
{
|
||||||
base.OnStartup(e);
|
base.OnStartup(e);
|
||||||
|
|
||||||
|
// --- Self-update pre-flight ---
|
||||||
|
// Resolve current exe path. Assembly.Location may point to a .dll for apphost-based
|
||||||
|
// .NET apps; swap to the .exe companion when that happens.
|
||||||
|
var currentExePath = Assembly.GetEntryAssembly()!.Location;
|
||||||
|
if (currentExePath.EndsWith(".dll", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
currentExePath = System.IO.Path.ChangeExtension(currentExePath, ".exe");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Arg form: --replace-self "<old-path>"
|
||||||
|
var replaceSelfIndex = Array.FindIndex(e.Args, a => a.Equals("--replace-self", StringComparison.OrdinalIgnoreCase));
|
||||||
|
if (replaceSelfIndex >= 0 && replaceSelfIndex + 1 < e.Args.Length)
|
||||||
|
{
|
||||||
|
var oldPath = e.Args[replaceSelfIndex + 1];
|
||||||
|
var relaunched = await SelfUpdater.HandleReplaceSelfAsync(
|
||||||
|
oldPath: oldPath,
|
||||||
|
currentExePath: currentExePath,
|
||||||
|
launchProcess: path =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(path) { UseShellExecute = true });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch { return false; }
|
||||||
|
});
|
||||||
|
if (relaunched)
|
||||||
|
{
|
||||||
|
Shutdown(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Replacement failed — fall through to normal wizard from the temp location.
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Normal launch: check for a newer installer.
|
||||||
|
using var selfUpdateHttp = new HttpClient { Timeout = TimeSpan.FromSeconds(10) };
|
||||||
|
var selfUpdateReleases = new ReleaseClient(selfUpdateHttp);
|
||||||
|
var currentVersion = GetInstallerVersion();
|
||||||
|
|
||||||
|
var decision = await SelfUpdater.DecideUpdateAsync(selfUpdateReleases, currentVersion, CancellationToken.None);
|
||||||
|
if (decision.Kind == SelfUpdateDecisionKind.UpdateAvailable)
|
||||||
|
{
|
||||||
|
var prompt = new SelfUpdatePromptWindow(currentVersion, decision.LatestVersion!);
|
||||||
|
DarkTitleBar.Apply(prompt);
|
||||||
|
var ok = prompt.ShowDialog() == true;
|
||||||
|
if (!ok)
|
||||||
|
{
|
||||||
|
Shutdown(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (prompt.Choice == SelfUpdateChoice.Update)
|
||||||
|
{
|
||||||
|
prompt.ShowProgress("Downloading...");
|
||||||
|
var tempDir = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "ClaudeDo.Installer.Update");
|
||||||
|
var verifiedPath = await SelfUpdater.DownloadAndVerifyAsync(
|
||||||
|
selfUpdateReleases,
|
||||||
|
decision.InstallerAsset!,
|
||||||
|
decision.ChecksumsAsset!,
|
||||||
|
tempDir,
|
||||||
|
new Progress<long>(_ => { }),
|
||||||
|
CancellationToken.None);
|
||||||
|
|
||||||
|
if (verifiedPath is null)
|
||||||
|
{
|
||||||
|
MessageBox.Show(prompt,
|
||||||
|
"Update download or verification failed. Continuing with current installer.",
|
||||||
|
"ClaudeDo Installer", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var psi = new System.Diagnostics.ProcessStartInfo(verifiedPath)
|
||||||
|
{
|
||||||
|
UseShellExecute = true,
|
||||||
|
};
|
||||||
|
psi.ArgumentList.Add("--replace-self");
|
||||||
|
psi.ArgumentList.Add(currentExePath);
|
||||||
|
System.Diagnostics.Process.Start(psi);
|
||||||
|
Shutdown(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MessageBox.Show(prompt,
|
||||||
|
"Failed to launch updated installer: " + ex.Message + "\nContinuing with current installer.",
|
||||||
|
"ClaudeDo Installer", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// SelfUpdateChoice.Continue — fall through to normal wizard.
|
||||||
|
}
|
||||||
|
// No-update or check failed — fall through to normal wizard.
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Existing wizard start-up unchanged below this line ---
|
||||||
|
|
||||||
_services = BuildServices();
|
_services = BuildServices();
|
||||||
|
|
||||||
var context = _services.GetRequiredService<InstallContext>();
|
var context = _services.GetRequiredService<InstallContext>();
|
||||||
|
|||||||
25
src/ClaudeDo.Installer/Views/SelfUpdatePromptWindow.xaml
Normal file
25
src/ClaudeDo.Installer/Views/SelfUpdatePromptWindow.xaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<Window x:Class="ClaudeDo.Installer.Views.SelfUpdatePromptWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
Title="ClaudeDo Installer Update"
|
||||||
|
Width="460" Height="200"
|
||||||
|
WindowStartupLocation="CenterScreen"
|
||||||
|
ResizeMode="NoResize"
|
||||||
|
Background="#1a1a1a" Foreground="#f0f0f0">
|
||||||
|
<Grid Margin="20">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<TextBlock Grid.Row="0" FontSize="16" FontWeight="SemiBold" Text="A newer installer is available"/>
|
||||||
|
<TextBlock Grid.Row="1" Margin="0,8,0,0" TextWrapping="Wrap" x:Name="DetailText"/>
|
||||||
|
<TextBlock Grid.Row="2" Margin="0,12,0,0" TextWrapping="Wrap" Foreground="#a0a0a0" x:Name="ProgressText" Visibility="Collapsed"/>
|
||||||
|
<StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Right">
|
||||||
|
<Button x:Name="UpdateBtn" Content="Update" MinWidth="90" Margin="4,0" Padding="10,4" Click="UpdateBtn_Click" IsDefault="True"/>
|
||||||
|
<Button x:Name="ContinueBtn" Content="Continue anyway" MinWidth="140" Margin="4,0" Padding="10,4" Click="ContinueBtn_Click"/>
|
||||||
|
<Button x:Name="CancelBtn" Content="Cancel" MinWidth="90" Margin="4,0" Padding="10,4" Click="CancelBtn_Click" IsCancel="True"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
42
src/ClaudeDo.Installer/Views/SelfUpdatePromptWindow.xaml.cs
Normal file
42
src/ClaudeDo.Installer/Views/SelfUpdatePromptWindow.xaml.cs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Installer.Views;
|
||||||
|
|
||||||
|
public enum SelfUpdateChoice { Update, Continue, Cancel }
|
||||||
|
|
||||||
|
public partial class SelfUpdatePromptWindow : Window
|
||||||
|
{
|
||||||
|
public SelfUpdateChoice Choice { get; private set; } = SelfUpdateChoice.Cancel;
|
||||||
|
|
||||||
|
public SelfUpdatePromptWindow(string currentVersion, string latestVersion)
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
DetailText.Text = $"Installer v{latestVersion} is available (you are running v{currentVersion}). Update before continuing?";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ShowProgress(string text)
|
||||||
|
{
|
||||||
|
ProgressText.Text = text;
|
||||||
|
ProgressText.Visibility = Visibility.Visible;
|
||||||
|
UpdateBtn.IsEnabled = false;
|
||||||
|
ContinueBtn.IsEnabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateBtn_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Choice = SelfUpdateChoice.Update;
|
||||||
|
DialogResult = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ContinueBtn_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Choice = SelfUpdateChoice.Continue;
|
||||||
|
DialogResult = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CancelBtn_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Choice = SelfUpdateChoice.Cancel;
|
||||||
|
DialogResult = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user