From 0123b2104df7f890c2b67324ea18ab8302b49b6b Mon Sep 17 00:00:00 2001 From: Canon Date: Wed, 4 Feb 2026 11:44:21 +0800 Subject: [PATCH] Misc --- Commands/ScanCommand.cs | 11 +----- Commands/UpdateCommand.cs | 14 +++----- Models/ModStatus.cs | 3 +- Services/GitService.cs | 14 ++------ Services/IGitService.cs | 1 - Services/ModDiscoveryService.cs | 64 +++++++++++++++++---------------- UI/ModTableRenderer.cs | 19 +++++----- UI/ProgressReporter.cs | 17 ++++----- 8 files changed, 56 insertions(+), 87 deletions(-) diff --git a/Commands/ScanCommand.cs b/Commands/ScanCommand.cs index bfb4bde..b868d7f 100644 --- a/Commands/ScanCommand.cs +++ b/Commands/ScanCommand.cs @@ -23,17 +23,8 @@ public class ScanCommand : AsyncCommand AnsiConsole.MarkupLine($"[grey]Using mods directory: {modsDirectory}[/]"); AnsiConsole.WriteLine(); - // Check for git - var gitService = new GitService(); - if (!await gitService.IsGitInstalledAsync(cancellationToken)) - { - AnsiConsole.MarkupLine("[yellow]Warning: Git is not installed or not in PATH.[/]"); - AnsiConsole.MarkupLine("[yellow]Git-based features will be limited.[/]"); - AnsiConsole.MarkupLine("[grey]Install git from: https://git-scm.com/downloads[/]"); - AnsiConsole.WriteLine(); - } - // Initialize services + var gitService = new GitService(); var gitHubApiService = new GitHubApiService(); var modDiscoveryService = new ModDiscoveryService(gitService, gitHubApiService); diff --git a/Commands/UpdateCommand.cs b/Commands/UpdateCommand.cs index 36e907a..e8b7abc 100644 --- a/Commands/UpdateCommand.cs +++ b/Commands/UpdateCommand.cs @@ -2,11 +2,13 @@ using FCPModUpdater.Commands.Settings; using FCPModUpdater.Models; using FCPModUpdater.Services; using FCPModUpdater.UI; +using JetBrains.Annotations; using Spectre.Console; using Spectre.Console.Cli; namespace FCPModUpdater.Commands; +[UsedImplicitly] public class UpdateCommand : AsyncCommand { public override async Task ExecuteAsync(CommandContext context, ModPathSettings settings, @@ -24,16 +26,8 @@ public class UpdateCommand : AsyncCommand AnsiConsole.MarkupLine($"[grey]Using mods directory: {modsDirectory}[/]"); AnsiConsole.WriteLine(); - // Check for git - var gitService = new GitService(); - if (!await gitService.IsGitInstalledAsync(cancellationToken)) - { - AnsiConsole.MarkupLine("[red]Error: Git is not installed or not in PATH.[/]"); - AnsiConsole.MarkupLine("[grey]Install git from: https://git-scm.com/downloads[/]"); - return 1; - } - // Initialize services + var gitService = new GitService(); var gitHubApiService = new GitHubApiService(); var modDiscoveryService = new ModDiscoveryService(gitService, gitHubApiService); @@ -54,7 +48,7 @@ public class UpdateCommand : AsyncCommand } AnsiConsole.MarkupLine($"[yellow]Found {updateableMods.Count} mod(s) with updates available:[/]"); - foreach (var mod in updateableMods) + foreach (InstalledMod mod in updateableMods) { AnsiConsole.MarkupLine($" • {mod.Name} [grey]({mod.CommitsBehind} commits behind)[/]"); } diff --git a/Models/ModStatus.cs b/Models/ModStatus.cs index fd1cc54..4085c13 100644 --- a/Models/ModStatus.cs +++ b/Models/ModStatus.cs @@ -15,6 +15,5 @@ public enum ModStatus public enum ModSource { Git, - Local, - Workshop + Local } diff --git a/Services/GitService.cs b/Services/GitService.cs index 5f09589..ffc8117 100644 --- a/Services/GitService.cs +++ b/Services/GitService.cs @@ -6,18 +6,6 @@ namespace FCPModUpdater.Services; public class GitService : IGitService { - private bool? _gitInstalled; - - public async Task IsGitInstalledAsync(CancellationToken ct = default) - { - if (_gitInstalled.HasValue) - return _gitInstalled.Value; - - var (exitCode, _, _) = await RunGitCommandAsync(".", "--version", ct); - _gitInstalled = exitCode == 0; - return _gitInstalled.Value; - } - public async Task IsGitRepositoryAsync(string path, CancellationToken ct = default) { var (exitCode, _, _) = await RunGitCommandAsync(path, "rev-parse --is-inside-work-tree", ct); @@ -214,6 +202,8 @@ public class GitService : IGitService BufferedCommandResult result = await Cli.Wrap("git") .WithArguments(arguments) .WithWorkingDirectory(workingDirectory) + .WithEnvironmentVariables(env => env + .Set("GIT_TERMINAL_PROMPT", "0")) // Disable interactive prompts .WithValidation(CommandResultValidation.None) .ExecuteBufferedAsync(cts.Token); diff --git a/Services/IGitService.cs b/Services/IGitService.cs index 9aef2a6..c89be85 100644 --- a/Services/IGitService.cs +++ b/Services/IGitService.cs @@ -4,7 +4,6 @@ namespace FCPModUpdater.Services; public interface IGitService { - Task IsGitInstalledAsync(CancellationToken ct = default); Task IsGitRepositoryAsync(string path, CancellationToken ct = default); Task GetRemoteUrlAsync(string path, CancellationToken ct = default); Task GetCurrentCommitAsync(string path, CancellationToken ct = default); diff --git a/Services/ModDiscoveryService.cs b/Services/ModDiscoveryService.cs index 78a3c05..3fdeee0 100644 --- a/Services/ModDiscoveryService.cs +++ b/Services/ModDiscoveryService.cs @@ -5,6 +5,7 @@ namespace FCPModUpdater.Services; public class ModDiscoveryService : IModDiscoveryService { private const string FcpOrgUrl = "github.com/FalloutCollaborationProject/"; + private static readonly string[] BranchSuffixes = ["-main", "-master", "-develop"]; private readonly IGitService _gitService; private readonly IGitHubApiService _gitHubApiService; @@ -25,7 +26,6 @@ public class ModDiscoveryService : IModDiscoveryService return []; } - var gitInstalled = await _gitService.IsGitInstalledAsync(ct); var orgRepos = await _gitHubApiService.GetOrganizationReposAsync(ct); var orgRepoNames = orgRepos.Select(r => r.Name).ToHashSet(StringComparer.OrdinalIgnoreCase); @@ -39,7 +39,7 @@ public class ModDiscoveryService : IModDiscoveryService var folderName = Path.GetFileName(dir); progress?.Report($"Scanning: {folderName}"); - InstalledMod? mod = await AnalyzeModDirectoryAsync(dir, folderName, gitInstalled, orgRepoNames, ct); + InstalledMod? mod = await AnalyzeModDirectoryAsync(dir, folderName, orgRepoNames, ct); if (mod != null) { mods.Add(mod); @@ -52,46 +52,24 @@ public class ModDiscoveryService : IModDiscoveryService private async Task AnalyzeModDirectoryAsync( string path, string folderName, - bool gitInstalled, HashSet orgRepoNames, CancellationToken ct) { - // Check if it's a Workshop mod (has PublishedFileId.txt) - var isWorkshop = File.Exists(Path.Combine(path, "About", "PublishedFileId.txt")); - - if (!gitInstalled) - { - // Can't check git status without git installed - // Check if folder name matches an org repo - if (orgRepoNames.Contains(folderName)) - { - return new InstalledMod - { - Name = folderName, - Path = path, - Source = isWorkshop ? ModSource.Workshop : ModSource.Local, - Status = ModStatus.NonGit, - MatchedRepoName = folderName - }; - } - - return null; - } - var isGitRepo = await _gitService.IsGitRepositoryAsync(path, ct); if (!isGitRepo) { // Not a git repo - check if it matches an org repo by name - if (orgRepoNames.Contains(folderName)) + var matchedRepo = MatchRepoName(folderName, orgRepoNames); + if (matchedRepo != null) { return new InstalledMod { Name = folderName, Path = path, - Source = isWorkshop ? ModSource.Workshop : ModSource.Local, + Source = ModSource.Local, Status = ModStatus.NonGit, - MatchedRepoName = folderName + MatchedRepoName = matchedRepo }; } @@ -104,7 +82,8 @@ public class ModDiscoveryService : IModDiscoveryService if (string.IsNullOrEmpty(remoteUrl)) { // Git repo without remote - check if folder matches org repo - if (orgRepoNames.Contains(folderName)) + var matchedRepo = MatchRepoName(folderName, orgRepoNames); + if (matchedRepo != null) { return new InstalledMod { @@ -113,7 +92,7 @@ public class ModDiscoveryService : IModDiscoveryService Source = ModSource.Git, Status = ModStatus.Error, ErrorMessage = "Git repository has no remote configured", - MatchedRepoName = folderName + MatchedRepoName = matchedRepo }; } @@ -125,7 +104,7 @@ public class ModDiscoveryService : IModDiscoveryService if (!isFcpMod) { // Check if folder name matches org repo (might be cloned from fork) - if (!orgRepoNames.Contains(folderName)) + if (MatchRepoName(folderName, orgRepoNames) == null) { return null; } @@ -182,6 +161,29 @@ public class ModDiscoveryService : IModDiscoveryService } } + /// + /// Tries to match a folder name to an org repo, accounting for GitHub ZIP download suffixes like -main, -master. + /// + private static string? MatchRepoName(string folderName, HashSet orgRepoNames) + { + // Direct match + if (orgRepoNames.Contains(folderName)) + return folderName; + + // Try stripping common branch suffixes (for ZIP downloads) + foreach (var suffix in BranchSuffixes) + { + if (folderName.EndsWith(suffix, StringComparison.OrdinalIgnoreCase)) + { + var baseName = folderName[..^suffix.Length]; + if (orgRepoNames.Contains(baseName)) + return baseName; + } + } + + return null; + } + private static ModStatus DetermineStatus(int behind, int ahead, bool hasLocalChanges, string? branch) { if (branch == null) diff --git a/UI/ModTableRenderer.cs b/UI/ModTableRenderer.cs index 077a155..c6062e9 100644 --- a/UI/ModTableRenderer.cs +++ b/UI/ModTableRenderer.cs @@ -24,7 +24,7 @@ public static class ModTableRenderer .AddColumn(new TableColumn("[bold]Commit[/]").Centered()) .AddColumn(new TableColumn("[bold]Status[/]").Centered()); - foreach (var mod in mods) + foreach (InstalledMod mod in mods) { table.AddRow( FormatModName(mod), @@ -48,14 +48,14 @@ public static class ModTableRenderer AnsiConsole.MarkupLine( $"[grey]Summary:[/] [green]{upToDate} up to date[/] | [yellow]{behind} updates available[/] | [cyan]{localChanges} with local changes[/] | [grey]{nonGit} non-git[/]"); - if (rateLimit.HasValue) - { - var resetTime = rateLimitReset.HasValue - ? $" (resets {rateLimitReset.Value.ToLocalTime():HH:mm})" - : ""; - var color = rateLimit.Value < 10 ? "yellow" : "grey"; - AnsiConsole.MarkupLine($"[{color}]GitHub API: {rateLimit.Value} requests remaining{resetTime}[/]"); - } + if (!rateLimit.HasValue) + return; + + var resetTime = rateLimitReset.HasValue + ? $" (resets {rateLimitReset.Value.ToLocalTime():HH:mm})" + : ""; + var color = rateLimit.Value < 10 ? "yellow" : "grey"; + AnsiConsole.MarkupLine($"[{color}]GitHub API: {rateLimit.Value} requests remaining{resetTime}[/]"); } private static string FormatModName(InstalledMod mod) @@ -75,7 +75,6 @@ public static class ModTableRenderer { ModSource.Git => "[green]Git[/]", ModSource.Local => "[grey]Local[/]", - ModSource.Workshop => "[blue]Workshop[/]", _ => "[grey]?[/]" }; } diff --git a/UI/ProgressReporter.cs b/UI/ProgressReporter.cs index debeec4..1d1907f 100644 --- a/UI/ProgressReporter.cs +++ b/UI/ProgressReporter.cs @@ -61,12 +61,12 @@ public static class ProgressReporter new SpinnerColumn()) .StartAsync(async ctx => { - var overallTask = ctx.AddTask($"[bold]{description}[/]", maxValue: items.Count); + ProgressTask overallTask = ctx.AddTask($"[bold]{description}[/]", maxValue: items.Count); - foreach (var item in items) + foreach (T item in items) { var name = nameSelector(item); - var itemTask = ctx.AddTask($" {name}"); + ProgressTask itemTask = ctx.AddTask($" {name}"); var progress = new Progress(p => itemTask.Value = p); @@ -76,14 +76,9 @@ public static class ProgressReporter results.Add((name, success, error)); itemTask.Value = 100; - if (!success) - { - itemTask.Description = $" [red]{name}[/]"; - } - else - { - itemTask.Description = $" [green]{name}[/]"; - } + itemTask.Description = success + ? $" [green]{name}[/]" + : $" [red]{name}[/]"; } catch (Exception ex) {