Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 143 additions & 0 deletions src/Fallout.Cli/Commands/SetupCommand.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// <c>fallout :setup</c>: scaffolds a new build (build scripts, build project, configuration) interactively.
/// </summary>
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) : "<none>");

#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;
}
}
117 changes: 7 additions & 110 deletions src/Fallout.Cli/Program.Setup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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) : "<none>");

#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<string> content,
string buildProjectFileRelative,
Expand Down
2 changes: 1 addition & 1 deletion src/Fallout.Cli/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,10 @@ private static void RegisterCommands(IServiceCollection services)
services.AddSingleton<IFalloutCommand, CompleteCommand>();
services.AddSingleton<IFalloutCommand, GetConfigurationCommand>();
services.AddSingleton<IFalloutCommand, AddPackageCommand>();
services.AddSingleton<IFalloutCommand, SetupCommand>();

// 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<IFalloutCommand>(new DelegateCommand("setup", Setup));
services.AddSingleton<IFalloutCommand>(new DelegateCommand("update", Update));
services.AddSingleton<IFalloutCommand>(new DelegateCommand("cake-convert", CakeConvert));
services.AddSingleton<IFalloutCommand>(new DelegateCommand("cake-clean", CakeClean));
Expand Down