diff --git a/src/Fallout.Cli/Program.Update.cs b/src/Fallout.Cli/Commands/UpdateCommand.cs
similarity index 59%
rename from src/Fallout.Cli/Program.Update.cs
rename to src/Fallout.Cli/Commands/UpdateCommand.cs
index 145bf805c..6bf7ce2da 100644
--- a/src/Fallout.Cli/Program.Update.cs
+++ b/src/Fallout.Cli/Commands/UpdateCommand.cs
@@ -1,7 +1,7 @@
-using System;
using System.IO;
using System.Linq;
using System.Text.Json.Nodes;
+using Fallout.Cli.Prompts;
using Fallout.Common;
using Fallout.Common.Execution;
using Fallout.Common.IO;
@@ -10,37 +10,48 @@
using Fallout.Common.Utilities;
using static Fallout.Common.Constants;
-namespace Fallout.Cli;
+namespace Fallout.Cli.Commands;
-partial class Program
+///
+/// fallout :update: updates the build scripts, build project, configuration file and global.json.
+///
+public sealed class UpdateCommand : IFalloutCommand
{
- public static int Update(string[] args, AbsolutePath rootDirectory, AbsolutePath buildScript)
+ private readonly IConsolePrompts _prompts;
+
+ public UpdateCommand(IConsolePrompts prompts) => _prompts = prompts;
+
+ public string Name => "update";
+
+ // GetConfiguration / WriteBuildScripts / WriteConfigurationFile / BUILD_PROJECT_FILE remain on
+ // Program as internal residual until the #392 collapse PR extracts them into services.
+ public int Execute(string[] args, AbsolutePath rootDirectory, AbsolutePath buildScript)
{
- PrintInfo();
+ Program.PrintInfo();
Logging.Configure();
Assert.NotNull(rootDirectory);
if (buildScript != null)
{
- ConfirmExecution("Update build scripts", () => UpdateBuildScripts(rootDirectory, buildScript));
- ConfirmExecution("Update build project", () => UpdateBuildProject(buildScript));
+ _prompts.ConfirmExecution("Update build scripts", () => UpdateBuildScripts(rootDirectory, buildScript));
+ _prompts.ConfirmExecution("Update build project", () => UpdateBuildProject(buildScript));
}
- ConfirmExecution("Update configuration file", () => UpdateConfigurationFile(rootDirectory));
- ConfirmExecution("Update global.json", () => UpdateGlobalJsonFile(rootDirectory));
+ _prompts.ConfirmExecution("Update configuration file", () => UpdateConfigurationFile(rootDirectory));
+ _prompts.ConfirmExecution("Update global.json", () => UpdateGlobalJsonFile(rootDirectory));
- ShowCompletion("Updates");
+ _prompts.ShowCompletion("Updates");
return 0;
}
private static void UpdateBuildScripts(AbsolutePath rootDirectory, AbsolutePath buildScript)
{
- var configuration = GetConfiguration(buildScript, evaluate: true);
- var buildProjectFile = (AbsolutePath) configuration[BUILD_PROJECT_FILE];
+ var configuration = Program.GetConfiguration(buildScript, evaluate: true);
+ var buildProjectFile = (AbsolutePath) configuration[Program.BUILD_PROJECT_FILE];
- WriteBuildScripts(
+ Program.WriteBuildScripts(
scriptDirectory: buildScript.Parent,
rootDirectory,
buildDirectory: buildProjectFile.NotNull().Parent,
@@ -49,8 +60,8 @@ private static void UpdateBuildScripts(AbsolutePath rootDirectory, AbsolutePath
private static void UpdateBuildProject(AbsolutePath buildScript)
{
- var configuration = GetConfiguration(buildScript, evaluate: true);
- var projectFile = configuration[BUILD_PROJECT_FILE];
+ var configuration = Program.GetConfiguration(buildScript, evaluate: true);
+ var projectFile = configuration[Program.BUILD_PROJECT_FILE];
ProjectModelTasks.Initialize();
ProjectUpdater.Update(projectFile);
}
@@ -64,7 +75,7 @@ private static void UpdateConfigurationFile(AbsolutePath rootDirectory)
var solutionFile = rootDirectory / configurationFile.ReadAllLines().FirstOrDefault(x => !x.IsNullOrEmpty());
configurationFile.DeleteFile();
- WriteConfigurationFile(rootDirectory, solutionFile);
+ Program.WriteConfigurationFile(rootDirectory, solutionFile);
Host.Warning($"The previous {FalloutFileName} file was transformed to a {FalloutDirectoryName} directory.");
Host.Warning($"The .tmp directory can be cleared, as it is moved to {FalloutDirectoryName}/temp as well.");
if (solutionFile != null)
diff --git a/src/Fallout.Cli/Program.cs b/src/Fallout.Cli/Program.cs
index 64b03492c..155e4603e 100644
--- a/src/Fallout.Cli/Program.cs
+++ b/src/Fallout.Cli/Program.cs
@@ -61,7 +61,10 @@ private static void RegisterCommands(IServiceCollection services)
// Legacy handlers still living on Program, adapted until they are extracted into command
// types. Each conversion deletes one line here plus its Program.X.cs partial.
- services.AddSingleton(new DelegateCommand("update", Update));
+ services.AddSingleton();
+
+ // Legacy handlers still living on Program, adapted until they are extracted into command
+ // types. Each conversion deletes one line here plus its Program.X.cs partial.
services.AddSingleton(new DelegateCommand("cake-convert", CakeConvert));
services.AddSingleton(new DelegateCommand("cake-clean", CakeClean));
services.AddSingleton(new DelegateCommand("secrets", Secrets));
diff --git a/tests/Fallout.Cli.Tests/Commands/FakeConsolePrompts.cs b/tests/Fallout.Cli.Tests/Commands/FakeConsolePrompts.cs
new file mode 100644
index 000000000..b0f63edcf
--- /dev/null
+++ b/tests/Fallout.Cli.Tests/Commands/FakeConsolePrompts.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Collections.Generic;
+using Fallout.Cli.Prompts;
+
+namespace Fallout.Cli.Tests.Commands;
+
+///
+/// Configurable test double for exercising command interaction logic
+/// without a real console.
+///
+internal sealed class FakeConsolePrompts : IConsolePrompts
+{
+ /// Answer returned by .
+ public bool ConfirmationResult { get; init; }
+
+ /// When false, does not run its action (simulates a decline).
+ public bool InvokeConfirmedActions { get; init; }
+
+ /// Titles passed to , in order.
+ public List Completions { get; } = new();
+
+ public bool PromptForConfirmation(string question) => ConfirmationResult;
+ public void ShowInput(string emoji, string title, string value) { }
+ public void ShowCompletion(string title) => Completions.Add(title);
+ public void ClearPreviousLine() { }
+ public string PromptForInput(string question, string defaultValue = null) => defaultValue;
+ public string PromptForSecret(string title, int? minLength = null) => string.Empty;
+ public T PromptForChoice(string question, params (T Value, string Description)[] choices) => default;
+
+ public void ConfirmExecution(string title, Action action)
+ {
+ if (InvokeConfirmedActions)
+ action();
+ }
+}
diff --git a/tests/Fallout.Cli.Tests/Commands/UpdateCommandTests.cs b/tests/Fallout.Cli.Tests/Commands/UpdateCommandTests.cs
new file mode 100644
index 000000000..d3c554f66
--- /dev/null
+++ b/tests/Fallout.Cli.Tests/Commands/UpdateCommandTests.cs
@@ -0,0 +1,35 @@
+using System;
+using System.IO;
+using Fallout.Cli.Commands;
+using Fallout.Common.IO;
+using FluentAssertions;
+using Xunit;
+
+namespace Fallout.Cli.Tests.Commands;
+
+public class UpdateCommandTests
+{
+ [Fact]
+ public void Name_IsUpdate()
+ => new UpdateCommand(new FakeConsolePrompts()).Name.Should().Be("update");
+
+ [Fact]
+ public void Execute_NoBuildScript_DeclineAll_ReturnsZeroAndReportsCompletion()
+ {
+ var dir = (AbsolutePath)Path.Combine(Path.GetTempPath(), "fallout-update-" + Guid.NewGuid().ToString("N"));
+ dir.CreateDirectory();
+ var prompts = new FakeConsolePrompts { InvokeConfirmedActions = false };
+ try
+ {
+ // No build script and every confirmation declined → no update steps run, but the command
+ // still completes cleanly.
+ new UpdateCommand(prompts).Execute([], dir, buildScript: null).Should().Be(0);
+
+ prompts.Completions.Should().Contain("Updates");
+ }
+ finally
+ {
+ Directory.Delete(dir, recursive: true);
+ }
+ }
+}