diff --git a/src/Fallout.Cli/Commands/CakeCleanCommand.cs b/src/Fallout.Cli/Commands/CakeCleanCommand.cs
new file mode 100644
index 00000000..07205c4a
--- /dev/null
+++ b/src/Fallout.Cli/Commands/CakeCleanCommand.cs
@@ -0,0 +1,30 @@
+using System.Linq;
+using Fallout.Cli.Prompts;
+using Fallout.Common;
+using Fallout.Common.IO;
+
+namespace Fallout.Cli.Commands;
+
+///
+/// fallout :cake-clean: lists and optionally deletes the *.cake scripts.
+///
+public sealed class CakeCleanCommand : IFalloutCommand
+{
+ private readonly IConsolePrompts _prompts;
+
+ public CakeCleanCommand(IConsolePrompts prompts) => _prompts = prompts;
+
+ public string Name => "cake-clean";
+
+ public int Execute(string[] args, AbsolutePath rootDirectory, AbsolutePath buildScript)
+ {
+ var cakeFiles = Program.GetCakeFiles().ToList();
+ Host.Information("Found .cake files:");
+ cakeFiles.ForEach(x => Host.Debug($" - {x}"));
+
+ if (_prompts.PromptForConfirmation("Delete?"))
+ cakeFiles.ForEach(x => x.DeleteFile());
+
+ return 0;
+ }
+}
diff --git a/src/Fallout.Cli/Commands/CakeConvertCommand.cs b/src/Fallout.Cli/Commands/CakeConvertCommand.cs
new file mode 100644
index 00000000..5e0157e9
--- /dev/null
+++ b/src/Fallout.Cli/Commands/CakeConvertCommand.cs
@@ -0,0 +1,81 @@
+using System.Collections.Generic;
+using System.Linq;
+using Fallout.Cli.Prompts;
+using Fallout.Common;
+using Fallout.Common.Execution;
+using Fallout.Common.IO;
+using Fallout.Solutions;
+using Fallout.Common.Utilities;
+using static Fallout.Common.EnvironmentInfo;
+
+namespace Fallout.Cli.Commands;
+
+///
+/// fallout :cake-convert: best-effort conversion of *.cake scripts to Fallout C#.
+///
+public sealed class CakeConvertCommand : IFalloutCommand
+{
+ private readonly IConsolePrompts _prompts;
+
+ public CakeConvertCommand(IConsolePrompts prompts) => _prompts = prompts;
+
+ public string Name => "cake-convert";
+
+ // The .cake syntax-rewriting helpers (GetCakeFiles/GetCakeConvertedContent/GetCakePackages) and
+ // the shared GetConfiguration/AddOrReplacePackage helpers remain on Program until the #392
+ // collapse PR; GetCakeConvertedContent/GetCakePackages are also exercised directly by tests.
+ public int Execute(string[] args, AbsolutePath rootDirectory, AbsolutePath buildScript)
+ {
+ Program.PrintInfo();
+ Logging.Configure();
+ Telemetry.ConvertCake();
+ ProjectModelTasks.Initialize();
+
+ Host.Warning(
+ new[]
+ {
+ "Converting .cake files is a best effort approach using syntax rewriting.",
+ "Compile errors are to be expected, however, the following elements are currently covered:",
+ " - Target definitions",
+ " - Default targets",
+ " - Parameter declarations",
+ " - Absolute paths",
+ " - Globbing patterns",
+ " - Tool invocations (dotnet CLI, SignTool)",
+ " - Addin and tool references",
+ }.JoinNewLine());
+
+ Host.Debug();
+ if (!_prompts.PromptForConfirmation("Continue?"))
+ return 0;
+ Host.Debug();
+
+ if (buildScript == null &&
+ _prompts.PromptForConfirmation("Should a Fallout project be created for better results?"))
+ {
+ Program.Setup(args, rootDirectory: null, buildScript: null);
+ }
+
+ var buildScriptFile = WorkingDirectory / Program.CurrentBuildScriptName;
+ var buildProjectFile = buildScriptFile.Exists()
+ ? Program.GetConfiguration(buildScriptFile, evaluate: true)
+ .GetValueOrDefault(Program.BUILD_PROJECT_FILE, defaultValue: null)
+ : null;
+
+ foreach (var cakeFile in Program.GetCakeFiles())
+ {
+ var outputFile = cakeFile.Parent / cakeFile.NameWithoutExtension.Capitalize() + ".cs";
+ var content = Program.GetCakeConvertedContent(cakeFile.ReadAllText());
+ outputFile.WriteAllText(content);
+ }
+
+ if (buildProjectFile != null)
+ {
+ var packages = Program.GetCakeFiles().SelectMany(x => Program.GetCakePackages(x.ReadAllText()));
+ foreach (var package in packages)
+ Program.AddOrReplacePackage(package.Id, package.Version, package.Type, buildProjectFile);
+ }
+
+ return 0;
+ }
+}
diff --git a/src/Fallout.Cli/Program.Cake.cs b/src/Fallout.Cli/Program.Cake.cs
index 6169798d..5d9b7d76 100644
--- a/src/Fallout.Cli/Program.Cake.cs
+++ b/src/Fallout.Cli/Program.Cake.cs
@@ -19,74 +19,10 @@ partial class Program
{
public const string CAKE_FILE_PATTERN = "*.cake";
- public static int CakeConvert(string[] args, AbsolutePath rootDirectory, AbsolutePath buildScript)
- {
- PrintInfo();
- Logging.Configure();
- Telemetry.ConvertCake();
- ProjectModelTasks.Initialize();
-
- Host.Warning(
- new[]
- {
- "Converting .cake files is a best effort approach using syntax rewriting.",
- "Compile errors are to be expected, however, the following elements are currently covered:",
- " - Target definitions",
- " - Default targets",
- " - Parameter declarations",
- " - Absolute paths",
- " - Globbing patterns",
- " - Tool invocations (dotnet CLI, SignTool)",
- " - Addin and tool references",
- }.JoinNewLine());
-
- Host.Debug();
- if (!PromptForConfirmation("Continue?"))
- return 0;
- Host.Debug();
-
- if (buildScript == null &&
- PromptForConfirmation("Should a NUKE project be created for better results?"))
- {
- Setup(args, rootDirectory: null, buildScript: null);
- }
-
- var buildScriptFile = WorkingDirectory / CurrentBuildScriptName;
- var buildProjectFile = buildScriptFile.Exists()
- ? GetConfiguration(buildScriptFile, evaluate: true)
- .GetValueOrDefault(BUILD_PROJECT_FILE, defaultValue: null)
- : null;
-
- foreach (var cakeFile in GetCakeFiles())
- {
- var outputFile = cakeFile.Parent / cakeFile.NameWithoutExtension.Capitalize() + ".cs";
- var content = GetCakeConvertedContent(cakeFile.ReadAllText());
- outputFile.WriteAllText(content);
- }
-
- if (buildProjectFile != null)
- {
- var packages = GetCakeFiles().SelectMany(x => GetCakePackages(x.ReadAllText()));
- foreach (var package in packages)
- AddOrReplacePackage(package.Id, package.Version, package.Type, buildProjectFile);
- }
-
- return 0;
- }
-
- public static int CakeClean(string[] args, AbsolutePath rootDirectory, AbsolutePath buildScript)
- {
- var cakeFiles = GetCakeFiles().ToList();
- Host.Information("Found .cake files:");
- cakeFiles.ForEach(x => Host.Debug($" - {x}"));
-
- if (PromptForConfirmation("Delete?"))
- cakeFiles.ForEach(x => x.DeleteFile());
-
- return 0;
- }
-
- private static IEnumerable GetCakeFiles()
+ // Residual after the :cake-convert/:cake-clean commands moved to CakeConvertCommand/
+ // CakeCleanCommand: these .cake syntax-rewriting helpers stay on Program until the #392 collapse
+ // PR (GetCakeConvertedContent/GetCakePackages are exercised directly by tests).
+ internal static IEnumerable GetCakeFiles()
{
return (TryGetRootDirectoryFrom(WorkingDirectory) ?? WorkingDirectory).GlobFiles($"**/{CAKE_FILE_PATTERN}");
}
diff --git a/src/Fallout.Cli/Program.cs b/src/Fallout.Cli/Program.cs
index 933f7843..7548a71d 100644
--- a/src/Fallout.Cli/Program.cs
+++ b/src/Fallout.Cli/Program.cs
@@ -66,8 +66,11 @@ 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("cake-convert", CakeConvert));
- services.AddSingleton(new DelegateCommand("cake-clean", CakeClean));
+ services.AddSingleton();
+ 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("GetNextDirectory", GetNextDirectory));
services.AddSingleton(new DelegateCommand("PopDirectory", PopDirectory));
services.AddSingleton(new DelegateCommand("PushWithCurrentRootDirectory", PushWithCurrentRootDirectory));
diff --git a/tests/Fallout.Cli.Tests/Commands/CakeCleanCommandTests.cs b/tests/Fallout.Cli.Tests/Commands/CakeCleanCommandTests.cs
new file mode 100644
index 00000000..a44a4954
--- /dev/null
+++ b/tests/Fallout.Cli.Tests/Commands/CakeCleanCommandTests.cs
@@ -0,0 +1,21 @@
+using Fallout.Cli.Commands;
+using FluentAssertions;
+using Xunit;
+
+namespace Fallout.Cli.Tests.Commands;
+
+public class CakeCleanCommandTests
+{
+ [Fact]
+ public void Name_IsCakeClean()
+ => new CakeCleanCommand(new FakeConsolePrompts()).Name.Should().Be("cake-clean");
+
+ [Fact]
+ public void Execute_WhenDeletionDeclined_ReturnsZeroAndDeletesNothing()
+ {
+ // ConfirmationResult = false → the "Delete?" prompt is declined, so no .cake files are removed.
+ var prompts = new FakeConsolePrompts { ConfirmationResult = false };
+
+ new CakeCleanCommand(prompts).Execute([], rootDirectory: null, buildScript: null).Should().Be(0);
+ }
+}