Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 17 additions & 38 deletions .claude/skills/generalupdate-advanced/templates/BowlIntegration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,65 +2,44 @@
using GeneralUpdate.Core.Models;

/// <summary>
/// 【Skill 自动生成】Bowl 崩溃守护集成
///
/// Bowl 是一个跨平台的崩溃监控助手,在升级完成后监控主应用的启动情况。
/// 如果主应用崩溃,Bowl 会:
/// 1. 生成 MiniDump 文件
/// 2. 写入 CrashReport.json(崩溃诊断报告)
/// 3. 可选:从备份自动回滚
/// 4. 触发 OnCrash 回调
///
/// 使用方式:
/// 在 Upgrade 完成后启动 Bowl(由 StartAppAsync 自动处理)
/// 或者在主应用启动后手动调用 Bowl.LaunchAsync()
/// [Skill Generated] Bowl crash daemon integration.
/// Bowl monitors the main app after update. On crash it generates:
/// - MiniDump (.dmp)
/// - Crash report (.json)
/// - System diagnostics (event log / drivers / system info)
/// - Auto-restore from backup (optional)
///
/// NuGet: dotnet add package GeneralUpdate.Bowl
///
/// ⚠️ 注意事项:
/// 1. Bowl 目前仅在 Windows 上充分测试
/// 2. 回滚依赖于更新前的备份(BackupEnabled = true
/// 3. 备份保留最多 3 个版本
/// 4. Bowl 需要使用 procdump 工具(Windows
/// Notes:
/// - Bowl is fully tested on Windows only
/// - Rollback depends on BackupEnabled = true
/// - Keeps only the 3 most recent backups
/// - Requires procdump tool (Windows)
/// </summary>
public static class BowlIntegration
{
/// <summary>
/// 启动 Bowl 崩溃守护
/// </summary>
public static async Task StartBowlAsync(string appPath, string installPath)
{
Console.WriteLine("[Bowl] 启动崩溃守护进程...");
Console.WriteLine("[Bowl] Starting crash daemon...");

var bowl = new Bowl();

// 注册崩溃回调
bowl.OnCrash += (crashReport) =>
{
Console.WriteLine($"[Bowl] 检测到崩溃!");
Console.WriteLine($"[Bowl] 原因: {crashReport.CrashReason}");
Console.WriteLine($"[Bowl] Dump 文件: {crashReport.DumpFilePath}");
Console.WriteLine($"[Bowl] Crash detected!");
Console.WriteLine($"[Bowl] Reason: {crashReport.CrashReason}");
Console.WriteLine($"[Bowl] Dump file: {crashReport.DumpFilePath}");

// 自动回滚(前提是有备份)
if (crashReport.AutoRestore)
{
Console.WriteLine("[Bowl] 正在回滚到备份版本...");
// Bowl 会自动从备份目录恢复
}
Console.WriteLine("[Bowl] Restoring from backup...");
};

// 启动监控
await bowl.LaunchAsync(new BowlOptions
{
// 要监控的主应用路径
TargetAppPath = appPath,
// 安装目录(用于定位备份)
InstallPath = installPath,
// 启用自动回滚
AutoRestore = true,
// 崩溃报告输出目录
ReportOutputPath = Path.Combine(
installPath, "CrashReports")
ReportOutputPath = Path.Combine(installPath, "CrashReports")
});
}
}
72 changes: 22 additions & 50 deletions .claude/skills/generalupdate-advanced/templates/CustomHooks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,98 +2,70 @@
using GeneralUpdate.Core.Models;

/// <summary>
/// 【Skill 自动生成】自定义生命周期 Hooks
/// [Skill Generated] Custom lifecycle hooks.
/// Implements IUpdateHooks for full lifecycle control.
/// All methods have default implementations (return null/true) — override only what you need.
///
/// 实现 IUpdateHooks 接口,在更新的各个生命周期阶段插入自定义逻辑。
/// 所有方法都有默认实现(返回 null/true),只需重写需要的方法。
///
/// 使用方式:
/// Usage:
/// .Hooks<MyCustomHooks>()
/// </summary>
public class MyCustomHooks : IUpdateHooks
{
/// <summary>
/// 更新开始前调用。返回 false 中止更新。
/// 可用于:检查磁盘空间、检查是否在营业时间、用户确认等。
/// </summary>
/// <summary>Called before update starts. Return false to abort.</summary>
public async Task<bool> OnBeforeUpdateAsync(UpdateContext context)
{
Console.WriteLine($"[Hooks] 开始更新: {context.CurrentVersion} {context.LastVersion}");
Console.WriteLine($"[Hooks] Update starting: {context.CurrentVersion} -> {context.LastVersion}");

// 检查磁盘空间
// Check disk space
var drive = new DriveInfo(Path.GetPathRoot(context.InstallPath)!);
if (drive.AvailableFreeSpace < 100 * 1024 * 1024) // 100MB 最低要求
if (drive.AvailableFreeSpace < 100 * 1024 * 1024)
{
Console.WriteLine("[Hooks] 磁盘空间不足,中止更新");
Console.WriteLine("[Hooks] Insufficient disk space, aborting");
return false;
}

return true; // true = 继续更新
return true;
}

/// <summary>
/// 下载完成后调用(在 Client 进程)
/// 可用于:下载后扫描、日志记录、通知 UI
/// </summary>
/// <summary>Called after download completes (Client process).</summary>
public async Task OnDownloadCompletedAsync(UpdateContext context)
{
Console.WriteLine($"[Hooks] 下载完成: {context.LastVersion}");
// 可以在这里触发 UI 通知
Console.WriteLine($"[Hooks] Download complete: {context.LastVersion}");
await Task.CompletedTask;
}

/// <summary>
/// 更新完成后调用(在 Upgrade 进程,替换文件后)
/// 可用于:清理临时文件、更新数据库 schema、迁移用户配置
/// </summary>
/// <summary>Called after update applies (Upgrade process). Clean up temp files, migrate configs.</summary>
public async Task OnAfterUpdateAsync(UpdateContext context)
{
Console.WriteLine($"[Hooks] 更新完成: {context.LastVersion}");

// 清理临时文件
Console.WriteLine($"[Hooks] Update complete: {context.LastVersion}");
var tempDir = context.UpdatePath;
if (Directory.Exists(tempDir))
{
try { Directory.Delete(tempDir, true); }
catch { /* 忽略清理中的错误 */ }
}

try { Directory.Delete(tempDir, true); } catch { }
await Task.CompletedTask;
}

/// <summary>
/// 更新过程出错时调用
/// 可用于:错误日志、通知管理员、触发回滚
/// </summary>
/// <summary>Called on update error. Log the error, notify admin, trigger rollback.</summary>
public async Task OnUpdateErrorAsync(UpdateContext context, Exception exception)
{
Console.WriteLine($"[Hooks] 更新失败: {exception.Message}");
// 记录错误日志
File.WriteAllText(
Path.Combine(context.InstallPath, "update_error.log"),
Console.WriteLine($"[Hooks] Update failed: {exception.Message}");
File.WriteAllText(Path.Combine(context.InstallPath, "update_error.log"),
$"[{DateTime.UtcNow}] {exception}");
await Task.CompletedTask;
}

/// <summary>
/// 启动主应用前调用(在 Upgrade 进程)
/// 可用于:修改配置文件、设置环境变量、检查版本兼容性
/// 返回 false 阻止主应用启动
/// </summary>
/// <summary>Called before starting the main app. Return false to prevent launch.</summary>
public async Task<bool> OnBeforeStartAppAsync(UpdateContext context)
{
Console.WriteLine($"[Hooks] 准备启动主应用: {context.MainAppName}");
Console.WriteLine($"[Hooks] Preparing to launch: {context.MainAppName}");

// Linux/MacOS 上设置可执行权限
// Set executable permissions on Linux/macOS
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
{
var appPath = Path.Combine(context.InstallPath, context.MainAppName ?? "");
if (File.Exists(appPath))
{
await UnixPermissionHooks.SetExecutablePermissionAsync(appPath);
}
}

return true; // true = 启动主应用
return true;
}
}
64 changes: 18 additions & 46 deletions .claude/skills/generalupdate-advanced/templates/CustomStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,74 +2,52 @@
using GeneralUpdate.Core.Pipeline;

/// <summary>
/// 【Skill 自动生成】自定义平台更新策略
/// [Skill Generated] Custom platform update strategy.
/// Completely replaces default strategies (WindowsStrategy / LinuxStrategy / MacStrategy).
///
/// 完全替换 GeneralUpdate 的默认策略(WindowsStrategy / LinuxStrategy / MacStrategy)。
/// 适用于需要完全控制更新行为的场景。
///
/// 使用方式:
/// Usage:
/// .Strategy<MyCustomStrategy>()
///
/// IStrategy 接口主要方法:
/// - ExecuteAsync(UpdateContext):执行策略主体
/// - StartAppAsync(UpdateContext):启动主应用
/// </summary>
public class MyCustomStrategy : AbstractStrategy
{
/// <summary>
/// 自定义策略入口
/// </summary>
public override async Task ExecuteAsync(UpdateContext context)
{
Console.WriteLine("[CustomStrategy] 执行自定义更新策略");
Console.WriteLine("[CustomStrategy] Executing custom update strategy");

// 1. 前置检查
// 1. Pre-update check
if (await Hooks.SafeOnBeforeUpdateAsync(context) == false)
{
Console.WriteLine("[CustomStrategy] 前置检查未通过,中止更新");
Console.WriteLine("[CustomStrategy] Pre-check failed, aborting");
return;
}

// 2. 对每个版本执行管道
// 2. Process each version through the pipeline
foreach (var version in context.UpdateVersions)
{
Console.WriteLine($"[CustomStrategy] 处理版本: {version.Version}");
Console.WriteLine($"[CustomStrategy] Processing version: {version.Version}");

// 2a. 构建管道(可自定义)
var pipeline = new PipelineBuilder(context)
.UseMiddleware<HashMiddleware>() // SHA256 校验
.UseMiddleware<CompressMiddleware>() // 解压 ZIP
.UseMiddleware<HashMiddleware>()
.UseMiddleware<CompressMiddleware>()
.Build();

// 2b. 执行管道
await pipeline.ExecuteAsync(context, version);

Console.WriteLine($"[CustomStrategy] 版本 {version.Version} 处理完成");
Console.WriteLine($"[CustomStrategy] Version {version.Version} done");
}

// 3. 后置处理
// 3. Post-update
await Hooks.SafeOnAfterUpdateAsync(context);

// 4. 启动主应用
// 4. Start main app
await StartAppAsync(context);
}

/// <summary>
/// 启动主应用
/// </summary>
public override async Task StartAppAsync(UpdateContext context)
{
Console.WriteLine("[CustomStrategy] 启动主应用");

var appPath = Path.Combine(
context.InstallPath,
context.MainAppName ?? "MyApp.exe");

Console.WriteLine("[CustomStrategy] Starting main app");
var appPath = Path.Combine(context.InstallPath, context.MainAppName ?? "MyApp.exe");
if (!File.Exists(appPath))
{
throw new FileNotFoundException(
$"主应用不存在: {appPath}");
}
throw new FileNotFoundException($"App not found: {appPath}");

var process = Process.Start(new ProcessStartInfo
{
Expand All @@ -79,17 +57,11 @@ public override async Task StartAppAsync(UpdateContext context)
});

if (process == null)
{
throw new InvalidOperationException(
$"无法启动主应用: {appPath}");
}
throw new InvalidOperationException($"Failed to start: {appPath}");

Console.WriteLine($"[CustomStrategy] 主应用已启动 (PID: {process.Id})");
Console.WriteLine($"[CustomStrategy] App started (PID: {process.Id})");
}

/// <summary>
/// 实现原样退出
/// </summary>
public override async Task ExecuteAsync(UpdateContext context, string pipeHandle)
{
await ExecuteAsync(context);
Expand Down
Loading