diff --git a/src/Fallout.Cli/Commands/SetupCommand.cs b/src/Fallout.Cli/Commands/SetupCommand.cs
new file mode 100644
index 00000000..fda9bb7e
--- /dev/null
+++ b/src/Fallout.Cli/Commands/SetupCommand.cs
@@ -0,0 +1,143 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using Fallout.Cli.Prompts;
+using Fallout.Common;
+using Fallout.Common.Execution;
+using Fallout.Common.IO;
+using Fallout.Common.Tooling;
+using Fallout.Common.Utilities;
+using Fallout.Common.Utilities.Collections;
+using Spectre.Console;
+using static Fallout.Common.Constants;
+using static Fallout.Common.EnvironmentInfo;
+using static Fallout.Common.Utilities.TemplateUtility;
+
+namespace Fallout.Cli.Commands;
+
+///
+/// fallout :setup: scaffolds a new build (build scripts, build project, configuration) interactively.
+///
+public sealed class SetupCommand : IFalloutCommand
+{
+ private const string TARGET_FRAMEWORK = "net8.0";
+
+ private readonly IConsolePrompts _prompts;
+
+ public SetupCommand(IConsolePrompts prompts) => _prompts = prompts;
+
+ public string Name => "setup";
+
+ // The scaffolding helpers (WriteBuildScripts/WriteConfigurationFile/UpdateSolutionFileContent/
+ // GetTemplate) remain on Program as internal residual until the #392 collapse PR extracts them.
+ public int Execute(string[] args, AbsolutePath rootDirectory, AbsolutePath buildScript)
+ {
+ Program.PrintInfo();
+ Logging.Configure();
+ Telemetry.SetupBuild();
+
+ AnsiConsole.WriteLine();
+ AnsiConsole.MarkupLine("[bold]Let's setup a new build![/]");
+ AnsiConsole.WriteLine();
+
+ #region Basic
+
+ var nukeLatestReleaseVersion = NuGetVersionResolver.GetLatestVersion(FalloutCommonPackageId, includePrereleases: false);
+ var nukeLatestPrereleaseVersion = NuGetVersionResolver.GetLatestVersion(FalloutCommonPackageId, includePrereleases: true);
+ var nukeLatestLocalVersion = NuGetPackageResolver.GetGlobalInstalledPackage(FalloutCommonPackageId, version: null, packagesConfigFile: null)
+ ?.Version.ToString();
+
+ if (rootDirectory == null)
+ rootDirectory = WorkingDirectory.FindParentOrSelf(x => x.ContainsDirectory(".git") || x.ContainsDirectory(".svn"));
+
+ if (rootDirectory == null)
+ {
+ Host.Warning("Could not find root directory. Falling back to working directory ...");
+ rootDirectory = WorkingDirectory;
+ }
+ _prompts.ShowInput("deciduous_tree", "Root directory", rootDirectory);
+
+ var buildProjectName = _prompts.PromptForInput("How should the project be named?", "_build");
+ _prompts.ClearPreviousLine();
+ _prompts.ShowInput("bookmark", "Build project name", buildProjectName);
+
+ var buildProjectRelativeDirectory = _prompts.PromptForInput("Where should the project be located?", "./build");
+ _prompts.ClearPreviousLine();
+ _prompts.ShowInput("round_pushpin", "Build project location", buildProjectRelativeDirectory);
+
+ var nukeVersion = _prompts.PromptForChoice("Which Fallout.Common version should be used?",
+ new[]
+ {
+ ("latest release", nukeLatestReleaseVersion.GetAwaiter().GetResult()),
+ ("latest prerelease", nukeLatestPrereleaseVersion.GetAwaiter().GetResult()),
+ ("latest local", nukeLatestLocalVersion),
+ ("same as global tool", typeof(SetupCommand).GetTypeInfo().Assembly.GetVersionText())
+ }
+ .Where(x => x.Item2 != null)
+ .Distinct(x => x.Item2)
+ .Select(x => (x.Item2, $"{x.Item2} ({x.Item1})")).ToArray());
+ _prompts.ShowInput("gem_stone", "Fallout.Common version", nukeVersion);
+
+ var solutionFile = (AbsolutePath) _prompts.PromptForChoice(
+ "Which solution should be the default?",
+ choices: new DirectoryInfo(rootDirectory)
+ .EnumerateFiles("*", SearchOption.AllDirectories)
+ .Where(x => x.FullName.EndsWithOrdinalIgnoreCase(".sln"))
+ .OrderByDescending(x => x.FullName)
+ .Select(x => (x, rootDirectory.GetRelativePathTo(x.FullName).ToString()))
+ .Concat((null, "None")).ToArray())?.FullName;
+ _prompts.ShowInput("toolbox", "Default solution", solutionFile != null ? rootDirectory.GetRelativePathTo(solutionFile) : "");
+
+ #endregion
+
+ #region Generation
+
+ var buildDirectory = rootDirectory / buildProjectRelativeDirectory;
+ var buildProjectFile = rootDirectory / buildProjectRelativeDirectory / buildProjectName + ".csproj";
+ var buildProjectGuid = Guid.NewGuid().ToString().ToUpper();
+
+ (rootDirectory / FalloutDirectoryName).CreateDirectory();
+
+ Program.WriteBuildScripts(
+ scriptDirectory: WorkingDirectory,
+ rootDirectory,
+ buildDirectory,
+ buildProjectName);
+
+ Program.WriteConfigurationFile(rootDirectory, solutionFile);
+
+ if (solutionFile != null)
+ {
+ var solutionFileContent = solutionFile.ReadAllLines().ToList();
+ var buildProjectFileRelative = solutionFile.Parent.GetWinRelativePathTo(buildProjectFile);
+ Program.UpdateSolutionFileContent(solutionFileContent, buildProjectFileRelative, buildProjectGuid, buildProjectName);
+ solutionFile.WriteAllLines(solutionFileContent, Encoding.UTF8);
+ }
+
+ buildProjectFile.WriteAllLines(
+ FillTemplate(
+ Program.GetTemplate("_build.csproj"),
+ GetDictionary(
+ new
+ {
+ RootDirectory = buildDirectory.GetWinRelativePathTo(rootDirectory),
+ ScriptDirectory = buildDirectory.GetWinRelativePathTo(WorkingDirectory),
+ TargetFramework = TARGET_FRAMEWORK,
+ TelemetryVersion = Telemetry.CurrentVersion,
+ NukeVersion = nukeVersion,
+ })));
+
+ (buildDirectory / "Directory.Build.props").WriteAllLines(Program.GetTemplate("Directory.Build.props"));
+ (buildDirectory / "Directory.Build.targets").WriteAllLines(Program.GetTemplate("Directory.Build.targets"));
+ (buildDirectory / "Build.cs").WriteAllLines(FillTemplate(Program.GetTemplate("Build.cs")));
+ (buildDirectory / "Configuration.cs").WriteAllLines(Program.GetTemplate("Configuration.cs"));
+
+ #endregion
+
+ _prompts.ShowCompletion("Setup");
+
+ return 0;
+ }
+}
diff --git a/src/Fallout.Cli/Program.Setup.cs b/src/Fallout.Cli/Program.Setup.cs
index fd0ce670..036d1c68 100644
--- a/src/Fallout.Cli/Program.Setup.cs
+++ b/src/Fallout.Cli/Program.Setup.cs
@@ -22,119 +22,16 @@ partial class Program
{
// ReSharper disable InconsistentNaming
- private const string TARGET_FRAMEWORK = "net8.0";
private const string PROJECT_KIND = "9A19103F-16F7-4668-BE54-9A1E7A4F7556";
- // ReSharper disable once CognitiveComplexity
- public static int Setup(string[] args, AbsolutePath rootDirectory, AbsolutePath buildScript)
- {
- PrintInfo();
- Logging.Configure();
- Telemetry.SetupBuild();
-
- AnsiConsole.WriteLine();
- AnsiConsole.MarkupLine("[bold]Let's setup a new build![/]");
- AnsiConsole.WriteLine();
-
- #region Basic
-
- var nukeLatestReleaseVersion = NuGetVersionResolver.GetLatestVersion(FalloutCommonPackageId, includePrereleases: false);
- var nukeLatestPrereleaseVersion = NuGetVersionResolver.GetLatestVersion(FalloutCommonPackageId, includePrereleases: true);
- var nukeLatestLocalVersion = NuGetPackageResolver.GetGlobalInstalledPackage(FalloutCommonPackageId, version: null, packagesConfigFile: null)
- ?.Version.ToString();
-
- if (rootDirectory == null)
- rootDirectory = WorkingDirectory.FindParentOrSelf(x => x.ContainsDirectory(".git") || x.ContainsDirectory(".svn"));
-
- if (rootDirectory == null)
- {
- Host.Warning("Could not find root directory. Falling back to working directory ...");
- rootDirectory = WorkingDirectory;
- }
- ShowInput("deciduous_tree", "Root directory", rootDirectory);
-
- var buildProjectName = PromptForInput("How should the project be named?", "_build");
- ClearPreviousLine();
- ShowInput("bookmark", "Build project name", buildProjectName);
-
- var buildProjectRelativeDirectory = PromptForInput("Where should the project be located?", "./build");
- ClearPreviousLine();
- ShowInput("round_pushpin", "Build project location", buildProjectRelativeDirectory);
-
- var nukeVersion = PromptForChoice("Which Fallout.Common version should be used?",
- new[]
- {
- ("latest release", nukeLatestReleaseVersion.GetAwaiter().GetResult()),
- ("latest prerelease", nukeLatestPrereleaseVersion.GetAwaiter().GetResult()),
- ("latest local", nukeLatestLocalVersion),
- ("same as global tool", typeof(Program).GetTypeInfo().Assembly.GetVersionText())
- }
- .Where(x => x.Item2 != null)
- .Distinct(x => x.Item2)
- .Select(x => (x.Item2, $"{x.Item2} ({x.Item1})")).ToArray());
- ShowInput("gem_stone", "Fallout.Common version", nukeVersion);
-
- var solutionFile = (AbsolutePath) PromptForChoice(
- "Which solution should be the default?",
- choices: new DirectoryInfo(rootDirectory)
- .EnumerateFiles("*", SearchOption.AllDirectories)
- .Where(x => x.FullName.EndsWithOrdinalIgnoreCase(".sln"))
- .OrderByDescending(x => x.FullName)
- .Select(x => (x, rootDirectory.GetRelativePathTo(x.FullName).ToString()))
- .Concat((null, "None")).ToArray())?.FullName;
- ShowInput("toolbox", "Default solution", solutionFile != null ? rootDirectory.GetRelativePathTo(solutionFile) : "");
-
- #endregion
-
- #region Generation
-
- var buildDirectory = rootDirectory / buildProjectRelativeDirectory;
- var buildProjectFile = rootDirectory / buildProjectRelativeDirectory / buildProjectName + ".csproj";
- var buildProjectGuid = Guid.NewGuid().ToString().ToUpper();
-
- (rootDirectory / FalloutDirectoryName).CreateDirectory();
-
- WriteBuildScripts(
- scriptDirectory: WorkingDirectory,
- rootDirectory,
- buildDirectory,
- buildProjectName);
-
- WriteConfigurationFile(rootDirectory, solutionFile);
-
- if (solutionFile != null)
- {
- var solutionFileContent = solutionFile.ReadAllLines().ToList();
- var buildProjectFileRelative = solutionFile.Parent.GetWinRelativePathTo(buildProjectFile);
- UpdateSolutionFileContent(solutionFileContent, buildProjectFileRelative, buildProjectGuid, buildProjectName);
- solutionFile.WriteAllLines(solutionFileContent, Encoding.UTF8);
- }
-
- buildProjectFile.WriteAllLines(
- FillTemplate(
- GetTemplate("_build.csproj"),
- GetDictionary(
- new
- {
- RootDirectory = buildDirectory.GetWinRelativePathTo(rootDirectory),
- ScriptDirectory = buildDirectory.GetWinRelativePathTo(WorkingDirectory),
- TargetFramework = TARGET_FRAMEWORK,
- TelemetryVersion = Telemetry.CurrentVersion,
- NukeVersion = nukeVersion,
- })));
-
- (buildDirectory / "Directory.Build.props").WriteAllLines(GetTemplate("Directory.Build.props"));
- (buildDirectory / "Directory.Build.targets").WriteAllLines(GetTemplate("Directory.Build.targets"));
- (buildDirectory / "Build.cs").WriteAllLines(FillTemplate(GetTemplate("Build.cs")));
- (buildDirectory / "Configuration.cs").WriteAllLines(GetTemplate("Configuration.cs"));
-
- #endregion
-
- ShowCompletion("Setup");
-
- return 0;
- }
+ // Transitional shim: cake (still a legacy handler) invokes setup directly. Removed once cake is
+ // converted; the dispatcher itself resolves SetupCommand from the registry, not this.
+ internal static int Setup(string[] args, AbsolutePath rootDirectory, AbsolutePath buildScript)
+ => new Commands.SetupCommand(s_prompts).Execute(args, rootDirectory, buildScript);
+ // Residual after the :setup command moved to SetupCommand: these scaffolding helpers are shared
+ // with update (and UpdateSolutionFileContent is exercised directly by tests). They move into a
+ // scaffolding service in the #392 collapse PR.
internal static void UpdateSolutionFileContent(
List content,
string buildProjectFileRelative,
diff --git a/src/Fallout.Cli/Program.cs b/src/Fallout.Cli/Program.cs
index cbd1dcab..64b03492 100644
--- a/src/Fallout.Cli/Program.cs
+++ b/src/Fallout.Cli/Program.cs
@@ -57,10 +57,10 @@ private static void RegisterCommands(IServiceCollection services)
services.AddSingleton();
services.AddSingleton();
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("setup", Setup));
services.AddSingleton(new DelegateCommand("update", Update));
services.AddSingleton(new DelegateCommand("cake-convert", CakeConvert));
services.AddSingleton(new DelegateCommand("cake-clean", CakeClean));