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));