diff --git a/.vscode/launch.json b/.vscode/launch.json index 47f3a49..92ba1f8 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,7 +10,8 @@ "args": ["i"], "cwd": "${workspaceFolder}", "console": "externalTerminal", - "stopAtEntry": false + "stopAtEntry": false, + "brokeredServicePipeName": "undefined", } ] } \ No newline at end of file diff --git a/src/Commands/BuildCommand.cs b/src/Commands/BuildCommand.cs index d7ceda9..a01ebba 100644 --- a/src/Commands/BuildCommand.cs +++ b/src/Commands/BuildCommand.cs @@ -17,7 +17,7 @@ public class BuildCommand : AbstractCommand public override IEnumerable Keywords => ["b", "build"]; - public override IEnumerable Parameters => Enumerable.Empty(); + public override IEnumerable Parameters => []; public override string Description => "Builds a project"; diff --git a/src/Commands/CodeGenerateCommand.cs b/src/Commands/CodeGenerateCommand.cs index 892b655..98a424c 100644 --- a/src/Commands/CodeGenerateCommand.cs +++ b/src/Commands/CodeGenerateCommand.cs @@ -20,7 +20,7 @@ public class CodeGenerateCommand : AbstractCommand public override IEnumerable Keywords => ["g", "generate"]; - public override IEnumerable Parameters => Enumerable.Empty(); + public override IEnumerable Parameters => []; public override string Description => "Generates code files for Xperience objects"; diff --git a/src/Commands/DeleteCommand.cs b/src/Commands/DeleteCommand.cs index 75239b6..7e58cd0 100644 --- a/src/Commands/DeleteCommand.cs +++ b/src/Commands/DeleteCommand.cs @@ -21,7 +21,7 @@ public class DeleteCommand : AbstractCommand public override IEnumerable Keywords => ["d", "delete"]; - public override IEnumerable Parameters => Enumerable.Empty(); + public override IEnumerable Parameters => []; public override string Description => "Deletes a project and its database"; diff --git a/src/Commands/HelpCommand.cs b/src/Commands/HelpCommand.cs index d50bd6f..691f1ea 100644 --- a/src/Commands/HelpCommand.cs +++ b/src/Commands/HelpCommand.cs @@ -33,7 +33,7 @@ public class HelpCommand : AbstractCommand public override IEnumerable Keywords => ["?", "help"]; - public override IEnumerable Parameters => Enumerable.Empty(); + public override IEnumerable Parameters => []; public override string Description => "Displays the help menu (this screen)"; diff --git a/src/Commands/InstallCommand.cs b/src/Commands/InstallCommand.cs index bcd05e6..9b330a3 100644 --- a/src/Commands/InstallCommand.cs +++ b/src/Commands/InstallCommand.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using Xperience.Manager.Configuration; +using Xperience.Manager.Helpers; using Xperience.Manager.Options; using Xperience.Manager.Services; using Xperience.Manager.Wizards; @@ -14,17 +15,19 @@ namespace Xperience.Manager.Commands /// public class InstallCommand : AbstractCommand { + private const string DATABASE = "db"; private readonly ToolProfile newInstallationProfile = new(); private readonly IShellRunner shellRunner; private readonly IConfigManager configManager; private readonly IScriptBuilder scriptBuilder; - private readonly IWizard wizard; + private readonly IWizard projectWizard; + private readonly IWizard dbWizard; public override IEnumerable Keywords => ["i", "install"]; - public override IEnumerable Parameters => Enumerable.Empty(); + public override IEnumerable Parameters => [DATABASE]; public override string Description => "Installs a new XbK instance"; @@ -41,9 +44,15 @@ internal InstallCommand() } - public InstallCommand(IShellRunner shellRunner, IScriptBuilder scriptBuilder, IWizard wizard, IConfigManager configManager) + public InstallCommand( + IShellRunner shellRunner, + IScriptBuilder scriptBuilder, + IWizard projectWizard, + IWizard dbWizard, + IConfigManager configManager) { - this.wizard = wizard; + this.dbWizard = dbWizard; + this.projectWizard = projectWizard; this.shellRunner = shellRunner; this.configManager = configManager; this.scriptBuilder = scriptBuilder; @@ -57,22 +66,39 @@ public override async Task Execute(ToolProfile? profile, string? action) return; } - // Override default values of InstallOptions with values from config file - wizard.Options = await configManager.GetDefaultInstallOptions(); - var options = await wizard.Run(); - AnsiConsole.WriteLine(); + // Override default values of InstallDatabaseOptions and InstallProjectOptions with values from config file + dbWizard.Options = await configManager.GetDefaultInstallDatabaseOptions(); + projectWizard.Options = await configManager.GetDefaultInstallProjectOptions(); + + // Only install database if "db" argument is passed + InstallDatabaseOptions? dbOptions = null; + if (!string.IsNullOrEmpty(action) && action.Equals(DATABASE)) + { + dbOptions = await dbWizard.Run(InstallDatabaseWizard.SKIP_EXISTINGDB_STEP); + await InstallDatabaseTool(); + await CreateDatabase(dbOptions, true); + + return; + } + + var projectOptions = await projectWizard.Run(); + if (!IsAdminTemplate(projectOptions)) + { + dbOptions = await dbWizard.Run(); + } - newInstallationProfile.ProjectName = options.ProjectName; - newInstallationProfile.WorkingDirectory = $"{options.InstallRootPath}\\{options.ProjectName}"; + newInstallationProfile.ProjectName = projectOptions.ProjectName; + newInstallationProfile.WorkingDirectory = $"{projectOptions.InstallRootPath}\\{projectOptions.ProjectName}"; + AnsiConsole.WriteLine(); await CreateWorkingDirectory(); - await InstallTemplate(options); - await CreateProjectFiles(options); + await InstallTemplate(projectOptions); + await CreateProjectFiles(projectOptions); // Admin boilerplate project doesn't require database install or profile - if (!IsAdminTemplate(options)) + if (!IsAdminTemplate(projectOptions) && dbOptions is not null) { - await CreateDatabase(options); + await CreateDatabase(dbOptions, false); await configManager.AddProfile(newInstallationProfile); // Select new profile @@ -113,7 +139,7 @@ private async Task CreateWorkingDirectory() } - private async Task CreateDatabase(InstallOptions options) + private async Task CreateDatabase(InstallDatabaseOptions options, bool isDatabaseOnly) { if (StopProcessing) { @@ -125,6 +151,12 @@ private async Task CreateDatabase(InstallOptions options) string databaseScript = scriptBuilder.SetScript(ScriptType.DatabaseInstall) .WithPlaceholders(options) .Build(); + // Database-only install requires removal of "dotnet" from the script to run global tool + if (isDatabaseOnly) + { + databaseScript = databaseScript.Replace("dotnet", ""); + } + await shellRunner.Execute(new(databaseScript) { ErrorHandler = ErrorDataReceived, @@ -133,7 +165,7 @@ await shellRunner.Execute(new(databaseScript) } - private async Task CreateProjectFiles(InstallOptions options) + private async Task CreateProjectFiles(InstallProjectOptions options) { if (StopProcessing) { @@ -167,8 +199,43 @@ await shellRunner.Execute(new(installScript) }).WaitForExitAsync(); } + private async Task InstallDatabaseTool() + { + if (StopProcessing) + { + return; + } + + // Get desired database tool version + var versions = await NuGetVersionHelper.GetPackageVersions(Constants.DATABASE_TOOL); + var filtered = versions.Where(v => !v.IsPrerelease && !v.IsLegacyVersion && v.Major >= 25) + .Select(v => v.Version) + .OrderByDescending(v => v); + var toolVersion = AnsiConsole.Prompt(new SelectionPrompt() + .Title($"Which [{Constants.PROMPT_COLOR}]version[/]?") + .PageSize(10) + .UseConverter(v => $"{v.Major}.{v.Minor}.{v.Build}") + .MoreChoicesText("Scroll for more...") + .AddChoices(filtered)); + + AnsiConsole.WriteLine(); + AnsiConsole.MarkupLineInterpolated($"[{Constants.EMPHASIS_COLOR}]Uninstalling previous database tool...[/]"); + + string uninstallScript = scriptBuilder.SetScript(ScriptType.UninstallDatabaseTool).Build(); + // Don't use base error handler for uninstall script as it throws when no tool is installed + // Just skip uninstall step in case of error and try to continue + await shellRunner.Execute(new(uninstallScript)).WaitForExitAsync(); + + AnsiConsole.MarkupLineInterpolated($"[{Constants.EMPHASIS_COLOR}]Installing database tool version {toolVersion}...[/]"); + + string installScript = scriptBuilder.SetScript(ScriptType.InstallDatabaseTool) + .AppendVersion(toolVersion) + .Build(); + await shellRunner.Execute(new(installScript) { ErrorHandler = ErrorDataReceived }).WaitForExitAsync(); + } + - private async Task InstallTemplate(InstallOptions options) + private async Task InstallTemplate(InstallProjectOptions options) { if (StopProcessing) { @@ -180,8 +247,7 @@ private async Task InstallTemplate(InstallOptions options) string uninstallScript = scriptBuilder.SetScript(ScriptType.TemplateUninstall).Build(); // Don't use base error handler for uninstall script as it throws when no templates are installed // Just skip uninstall step in case of error and try to continue - var uninstallCmd = shellRunner.Execute(new(uninstallScript)); - await uninstallCmd.WaitForExitAsync(); + await shellRunner.Execute(new(uninstallScript)).WaitForExitAsync(); AnsiConsole.MarkupLineInterpolated($"[{Constants.EMPHASIS_COLOR}]Installing template version {options.Version}...[/]"); @@ -189,11 +255,11 @@ private async Task InstallTemplate(InstallOptions options) .WithPlaceholders(options) .AppendVersion(options.Version) .Build(); - var installCmd = shellRunner.Execute(new(installScript) { ErrorHandler = ErrorDataReceived }); - await installCmd.WaitForExitAsync(); + await shellRunner.Execute(new(installScript) { ErrorHandler = ErrorDataReceived }).WaitForExitAsync(); } - private bool IsAdminTemplate(InstallOptions options) => options?.Template.Equals(Constants.TEMPLATE_ADMIN, StringComparison.OrdinalIgnoreCase) ?? false; + private static bool IsAdminTemplate(InstallProjectOptions options) => + options?.Template.Equals(Constants.TEMPLATE_ADMIN, StringComparison.OrdinalIgnoreCase) ?? false; } } diff --git a/src/Commands/MacroCommand.cs b/src/Commands/MacroCommand.cs index 265c954..502fc89 100644 --- a/src/Commands/MacroCommand.cs +++ b/src/Commands/MacroCommand.cs @@ -20,7 +20,7 @@ public class MacroCommand : AbstractCommand public override IEnumerable Keywords => ["m", "macros"]; - public override IEnumerable Parameters => Enumerable.Empty(); + public override IEnumerable Parameters => []; public override string Description => "Re-signs macro signatures"; diff --git a/src/Commands/SettingsCommand.cs b/src/Commands/SettingsCommand.cs index d559f84..26bc6f1 100644 --- a/src/Commands/SettingsCommand.cs +++ b/src/Commands/SettingsCommand.cs @@ -23,7 +23,7 @@ public class SettingsCommand : AbstractCommand public override IEnumerable Keywords => ["s", "settings"]; - public override IEnumerable Parameters => Enumerable.Empty(); + public override IEnumerable Parameters => []; public override string Description => "Configures the appsettings.json of a project"; diff --git a/src/Commands/UpdateCommand.cs b/src/Commands/UpdateCommand.cs index 5dabd57..fb01f71 100644 --- a/src/Commands/UpdateCommand.cs +++ b/src/Commands/UpdateCommand.cs @@ -30,7 +30,7 @@ public class UpdateCommand : AbstractCommand public override IEnumerable Keywords => ["u", "update"]; - public override IEnumerable Parameters => Enumerable.Empty(); + public override IEnumerable Parameters => []; public override string Description => "Updates a project's NuGet packages and database version"; diff --git a/src/Configuration/ToolConfiguration.cs b/src/Configuration/ToolConfiguration.cs index 844102c..c00c8d5 100644 --- a/src/Configuration/ToolConfiguration.cs +++ b/src/Configuration/ToolConfiguration.cs @@ -26,9 +26,15 @@ public class ToolConfiguration /// - /// The stored in the configuration file. + /// The stored in the configuration file. /// - public InstallOptions? DefaultInstallOptions { get; set; } + public InstallProjectOptions? DefaultInstallProjectOptions { get; set; } + + + /// + /// The stored in the configuration file. + /// + public InstallDatabaseOptions? DefaultInstallDatabaseOptions { get; set; } /// diff --git a/src/Constants.cs b/src/Constants.cs index 55be231..8fb41e7 100644 --- a/src/Constants.cs +++ b/src/Constants.cs @@ -15,6 +15,7 @@ public static class Constants public const string EMPHASIS_COLOR = "deepskyblue3_1"; public const string PROMPT_COLOR = "lightgoldenrod2_2"; + public const string DATABASE_TOOL = "Kentico.Xperience.DbManager"; public const string TEMPLATES_PACKAGE = "kentico.xperience.templates"; public const string TEMPLATE_SAMPLE = "kentico-xperience-sample-mvc"; public const string TEMPLATE_BLANK = "kentico-xperience-mvc"; diff --git a/src/Options/InstallOptions.cs b/src/Options/InstallDatabaseOptions.cs similarity index 69% rename from src/Options/InstallOptions.cs rename to src/Options/InstallDatabaseOptions.cs index 4b95c12..0103923 100644 --- a/src/Options/InstallOptions.cs +++ b/src/Options/InstallDatabaseOptions.cs @@ -5,41 +5,10 @@ namespace Xperience.Manager.Options { /// - /// The options used to install Xperience by Kentico project files and databases, used by . + /// The options used to install Xperience by Kentico databases, used by . /// - public class InstallOptions : IWizardOptions + public class InstallDatabaseOptions : IWizardOptions { - /// - /// The version of the Xperience by Kentico templates and database to install. - /// - public Version? Version { get; set; } - - - /// - /// The name of the template to install. - /// - public string Template { get; set; } = "kentico-xperience-sample-mvc"; - - - /// - /// The name of the Xperience by Kentico project. - /// - public string ProjectName { get; set; } = "xbk"; - - - /// - /// The absolute path of the parent directory to install the project within. E.g. if set to - /// "C:\inetpub\wwwroot" a new installation will be created in "C:\inetpub\wwwroot\projectname." - /// - public string InstallRootPath { get; set; } = Environment.CurrentDirectory; - - - /// - /// If true, the "--cloud" parameter is used during installation. - /// - public bool UseCloud { get; set; } = false; - - /// /// The name of the new database. /// diff --git a/src/Options/InstallProjectOptions.cs b/src/Options/InstallProjectOptions.cs new file mode 100644 index 0000000..363df28 --- /dev/null +++ b/src/Options/InstallProjectOptions.cs @@ -0,0 +1,40 @@ +using Xperience.Manager.Commands; + +namespace Xperience.Manager.Options +{ + /// + /// The options used to install Xperience by Kentico project files, used by . + /// + public class InstallProjectOptions : IWizardOptions + { + /// + /// The version of the Xperience by Kentico templates and database to install. + /// + public Version? Version { get; set; } + + + /// + /// The name of the template to install. + /// + public string Template { get; set; } = "kentico-xperience-sample-mvc"; + + + /// + /// The name of the Xperience by Kentico project. + /// + public string ProjectName { get; set; } = "xbk"; + + + /// + /// The absolute path of the parent directory to install the project within. E.g. if set to + /// "C:\inetpub\wwwroot" a new installation will be created in "C:\inetpub\wwwroot\projectname." + /// + public string InstallRootPath { get; set; } = Environment.CurrentDirectory; + + + /// + /// If true, the "--cloud" parameter is used during installation. + /// + public bool UseCloud { get; set; } = false; + } +} diff --git a/src/Services/ConfigManager.cs b/src/Services/ConfigManager.cs index 9259b9b..6b250cc 100644 --- a/src/Services/ConfigManager.cs +++ b/src/Services/ConfigManager.cs @@ -1,7 +1,7 @@ using System.Reflection; using Newtonsoft.Json; - +using Newtonsoft.Json.Linq; using Xperience.Manager.Configuration; using Xperience.Manager.Options; @@ -69,7 +69,8 @@ public async Task GetConfig() public async Task EnsureConfigFile() { - var toolVersion = Assembly.GetExecutingAssembly().GetName().Version ?? throw new InvalidOperationException("The tool version couldn't be retrieved."); + var toolVersion = Assembly.GetExecutingAssembly().GetName().Version ?? + throw new InvalidOperationException("The tool version couldn't be retrieved."); if (File.Exists(Constants.CONFIG_FILENAME)) { await MigrateConfig(toolVersion); @@ -79,16 +80,25 @@ public async Task EnsureConfigFile() await WriteConfig(new ToolConfiguration { Version = toolVersion, - DefaultInstallOptions = new() + DefaultInstallProjectOptions = new(), + DefaultInstallDatabaseOptions = new() }); } - public async Task GetDefaultInstallOptions() + public async Task GetDefaultInstallProjectOptions() { var config = await GetConfig(); - return config.DefaultInstallOptions ?? new(); + return config.DefaultInstallProjectOptions ?? new(); + } + + + public async Task GetDefaultInstallDatabaseOptions() + { + var config = await GetConfig(); + + return config.DefaultInstallDatabaseOptions ?? new(); } @@ -126,6 +136,14 @@ private async Task MigrateConfig(Version toolVersion) } // Perform any migrations from old config version to new version here + string text = await File.ReadAllTextAsync(Constants.CONFIG_FILENAME); + var json = JsonConvert.DeserializeObject(text) ?? + throw new InvalidOperationException("Unable to read configuration file for migration."); + if ((config.Version?.ToString().Equals("4.0.0.0") ?? false) && toolVersion.ToString().Equals("4.1.0.0")) + { + Migrate40To41(json, config); + } + config.Version = toolVersion; await WriteConfig(config); @@ -154,6 +172,31 @@ private async Task SetCurrentProfileInternal(ToolProfile profile) } - private Task WriteConfig(ToolConfiguration config) => File.WriteAllTextAsync(Constants.CONFIG_FILENAME, JsonConvert.SerializeObject(config, Formatting.Indented)); + private static Task WriteConfig(ToolConfiguration config) => + File.WriteAllTextAsync(Constants.CONFIG_FILENAME, JsonConvert.SerializeObject(config, Formatting.Indented)); + + + private static void Migrate40To41(JObject oldConfig, ToolConfiguration newConfig) + { + var oldInstallOptions = oldConfig["DefaultInstallOptions"]; + + var dbOptions = new InstallDatabaseOptions(); + dbOptions.DatabaseName = oldInstallOptions?["DatabaseName"]?.ToString() ?? dbOptions.DatabaseName; + dbOptions.ServerName = oldInstallOptions?["ServerName"]?.ToString() ?? dbOptions.ServerName; + newConfig.DefaultInstallDatabaseOptions = dbOptions; + + var projectOptions = new InstallProjectOptions(); + projectOptions.Template = oldInstallOptions?["Template"]?.ToString() ?? projectOptions.Template; + projectOptions.ProjectName = oldInstallOptions?["ProjectName"]?.ToString() ?? projectOptions.ProjectName; + projectOptions.InstallRootPath = oldInstallOptions?["InstallRootPath"]?.ToString() ?? projectOptions.InstallRootPath; + projectOptions.UseCloud = bool.Parse(oldInstallOptions?["UseCloud"]?.ToString() ?? projectOptions.UseCloud.ToString()); + string? oldVersion = oldInstallOptions?["Version"]?.ToString(); + if (!string.IsNullOrEmpty(oldVersion)) + { + projectOptions.Version = Version.Parse(oldVersion); + } + + newConfig.DefaultInstallProjectOptions = projectOptions; + } } } diff --git a/src/Services/Interfaces/IConfigManager.cs b/src/Services/Interfaces/IConfigManager.cs index 5a09b43..b095e2f 100644 --- a/src/Services/Interfaces/IConfigManager.cs +++ b/src/Services/Interfaces/IConfigManager.cs @@ -34,10 +34,17 @@ public interface IConfigManager : IService /// - /// Gets the specified by the tool configuration file, or a new instance if + /// Gets the specified by the tool configuration file, or a new instance if /// the configuration can't be read. /// - public Task GetDefaultInstallOptions(); + public Task GetDefaultInstallProjectOptions(); + + + /// + /// Gets the specified by the tool configuration file, or a new instance if + /// the configuration can't be read. + /// + public Task GetDefaultInstallDatabaseOptions(); /// diff --git a/src/Services/ScriptBuilder.cs b/src/Services/ScriptBuilder.cs index d0094c2..908d584 100644 --- a/src/Services/ScriptBuilder.cs +++ b/src/Services/ScriptBuilder.cs @@ -10,12 +10,14 @@ public class ScriptBuilder : IScriptBuilder private const string BUILD_SCRIPT = "dotnet build"; private const string MKDIR_SCRIPT = $"mkdir"; - private const string INSTALL_PROJECT_SCRIPT = $"dotnet new {nameof(InstallOptions.Template)} -n {nameof(InstallOptions.ProjectName)}"; - private const string INSTALL_DATABASE_SCRIPT = $"dotnet kentico-xperience-dbmanager -- -s \"{nameof(InstallOptions.ServerName)}\" -d \"{nameof(InstallOptions.DatabaseName)}\" -a \"{nameof(InstallOptions.AdminPassword)}\" --use-existing-database {nameof(InstallOptions.UseExistingDatabase)}"; - private const string UNINSTALL_TEMPLATE_SCRIPT = "dotnet new uninstall kentico.xperience.templates"; - private const string INSTALL_TEMPLATE_SCRIPT = "dotnet new install kentico.xperience.templates"; + private const string INSTALL_PROJECT_SCRIPT = $"dotnet new {nameof(InstallProjectOptions.Template)} -n {nameof(InstallProjectOptions.ProjectName)}"; + private const string INSTALL_DATABASE_SCRIPT = $"dotnet kentico-xperience-dbmanager -- -s \"{nameof(InstallDatabaseOptions.ServerName)}\" -d \"{nameof(InstallDatabaseOptions.DatabaseName)}\" -a \"{nameof(InstallDatabaseOptions.AdminPassword)}\" --use-existing-database {nameof(InstallDatabaseOptions.UseExistingDatabase)}"; + private const string UNINSTALL_TEMPLATE_SCRIPT = $"dotnet new uninstall {Constants.TEMPLATES_PACKAGE}"; + private const string INSTALL_TEMPLATE_SCRIPT = $"dotnet new install {Constants.TEMPLATES_PACKAGE}"; private const string UPDATE_PACKAGE_SCRIPT = $"dotnet add package {nameof(UpdateOptions.PackageName)}"; private const string UPDATE_DATABASE_SCRIPT = "dotnet run --no-build --kxp-update -- --skip-confirmation"; + private const string INSTALL_DBTOOL_SCRIPT = $"dotnet tool install {Constants.DATABASE_TOOL} -g"; + private const string UNINSTALL_DBTOOL_SCRIPT = $"dotnet tool uninstall {Constants.DATABASE_TOOL} -g"; private const string CI_STORE_SCRIPT = "dotnet run --no-build --kxp-ci-store"; private const string CI_RESTORE_SCRIPT = "dotnet run --no-build --kxp-ci-restore"; private const string CD_NEW_CONFIG_SCRIPT = $"dotnet run --no-build -- --kxp-cd-config --path \"{nameof(ContinuousDeploymentConfig.ConfigPath)}\""; @@ -105,7 +107,7 @@ public IScriptBuilder AppendVersion(Version? version) { currentScript += $"::{version}"; } - else if (currentScriptType.Equals(ScriptType.PackageUpdate)) + else if (currentScriptType.Equals(ScriptType.PackageUpdate) || currentScriptType.Equals(ScriptType.InstallDatabaseTool)) { currentScript += $" --version {version}"; } @@ -116,9 +118,9 @@ public IScriptBuilder AppendVersion(Version? version) public string Build() { - if (!ValidateScript()) + if (string.IsNullOrEmpty(currentScript)) { - throw new InvalidOperationException("The script is empty or contains placeholder values."); + throw new InvalidOperationException("The script is empty."); } return currentScript; @@ -173,20 +175,14 @@ public IScriptBuilder SetScript(ScriptType type) ScriptType.GenerateCode => CODEGEN_SCRIPT, ScriptType.DeleteDirectory => DELETE_FOLDER_SCRIPT, ScriptType.ExecuteSql => RUN_SQL_QUERY, + ScriptType.UninstallDatabaseTool => UNINSTALL_DBTOOL_SCRIPT, + ScriptType.InstallDatabaseTool => INSTALL_DBTOOL_SCRIPT, ScriptType.None => string.Empty, _ => string.Empty, }; return this; } - - - private bool ValidateScript() - { - var propertyNames = typeof(InstallOptions).GetProperties().Select(p => p.Name); - - return !string.IsNullOrEmpty(currentScript) && !propertyNames.Any(currentScript.Contains); - } } @@ -296,5 +292,15 @@ public enum ScriptType /// The script which executes a SQL query against a database. /// ExecuteSql, + + /// + /// The script which uninstalls the Kentico.Xperience.DbManager global tool. + /// + UninstallDatabaseTool, + + /// + /// The script which installs the Kentico.Xperience.DbManager global tool. + /// + InstallDatabaseTool, } } diff --git a/src/Wizards/Base/AbstractWizard.cs b/src/Wizards/Base/AbstractWizard.cs index 6911791..06cab1f 100644 --- a/src/Wizards/Base/AbstractWizard.cs +++ b/src/Wizards/Base/AbstractWizard.cs @@ -15,12 +15,12 @@ namespace Xperience.Manager.Wizards public TOptions Options { get; set; } = new(); - public abstract Task InitSteps(); + public abstract Task InitSteps(params string[] args); - public async Task Run() + public async Task Run(params string[] args) { - await InitSteps(); + await InitSteps(args); do { await Steps.Current.Execute(); diff --git a/src/Wizards/Base/IWizard.cs b/src/Wizards/Base/IWizard.cs index 7718e7a..75461f0 100644 --- a/src/Wizards/Base/IWizard.cs +++ b/src/Wizards/Base/IWizard.cs @@ -17,17 +17,18 @@ public interface IWizard where TOptions : IWizardOptions public TOptions Options { get; set; } - /// /// Initializes the with the s required to /// populate the . /// - public Task InitSteps(); + /// Optional arguments to pass to the step initialization. + public Task InitSteps(params string[] args); /// /// Requests user input to generate the . /// - public Task Run(); + /// Optional arguments to pass to the wizard. + public Task Run(params string[] args); } } diff --git a/src/Wizards/CodeGenerateWizard.cs b/src/Wizards/CodeGenerateWizard.cs index 1389913..89a6f6c 100644 --- a/src/Wizards/CodeGenerateWizard.cs +++ b/src/Wizards/CodeGenerateWizard.cs @@ -19,7 +19,7 @@ public class CodeGenerateWizard : AbstractWizard ]; - public override Task InitSteps() + public override Task InitSteps(params string[] args) { Steps.Add(new Step(new() { diff --git a/src/Wizards/InstallDatabaseWizard.cs b/src/Wizards/InstallDatabaseWizard.cs new file mode 100644 index 0000000..7c7cd37 --- /dev/null +++ b/src/Wizards/InstallDatabaseWizard.cs @@ -0,0 +1,59 @@ +using Spectre.Console; + +using Xperience.Manager.Options; +using Xperience.Manager.Steps; + +namespace Xperience.Manager.Wizards +{ + /// + /// A wizard which generates an for installing Xperience by Kentico databases. + /// + public class InstallDatabaseWizard : AbstractWizard + { + public const string SKIP_EXISTINGDB_STEP = "skipexistingdbstep"; + + + public override Task InitSteps(params string[] args) + { + var serverPrompt = new TextPrompt($"Enter the [{Constants.PROMPT_COLOR}]SQL server[/] name:"); + if (!string.IsNullOrEmpty(Options.ServerName)) + { + serverPrompt.DefaultValue(Options.ServerName); + } + Steps.Add(new Step(new() + { + Prompt = serverPrompt, + ValueReceiver = (v) => Options.ServerName = v + })); + + Steps.Add(new Step(new() + { + Prompt = new TextPrompt($"Enter the [{Constants.PROMPT_COLOR}]database[/] name:") + .AllowEmpty() + .DefaultValue(Options.DatabaseName), + ValueReceiver = (v) => Options.DatabaseName = v + })); + + var useExistingPrompt = new ConfirmationPrompt($"Use [{Constants.PROMPT_COLOR}]existing[/] database?") + { + DefaultValue = Options.UseExistingDatabase + }; + Steps.Add(new Step(new() + { + Prompt = useExistingPrompt, + ValueReceiver = (v) => Options.UseExistingDatabase = v, + SkipChecker = () => args.Contains(SKIP_EXISTINGDB_STEP) + })); + + Steps.Add(new Step(new() + { + Prompt = new TextPrompt($"Enter the admin [{Constants.PROMPT_COLOR}]password[/]:") + .AllowEmpty() + .DefaultValue(Options.AdminPassword), + ValueReceiver = (v) => Options.AdminPassword = v + })); + + return Task.CompletedTask; + } + } +} diff --git a/src/Wizards/InstallWizard.cs b/src/Wizards/InstallProjectWizard.cs similarity index 58% rename from src/Wizards/InstallWizard.cs rename to src/Wizards/InstallProjectWizard.cs index c33b026..eebcb8c 100644 --- a/src/Wizards/InstallWizard.cs +++ b/src/Wizards/InstallProjectWizard.cs @@ -7,9 +7,9 @@ namespace Xperience.Manager.Wizards { /// - /// A wizard which generates an for installing Xperience by Kentico. + /// A wizard which generates an for installing Xperience by Kentico project files. /// - public class InstallWizard : AbstractWizard + public class InstallProjectWizard : AbstractWizard { private readonly IEnumerable templates = [ Constants.TEMPLATE_SAMPLE, @@ -18,7 +18,7 @@ public class InstallWizard : AbstractWizard ]; - public override async Task InitSteps() + public override async Task InitSteps(params string[] args) { var versions = await NuGetVersionHelper.GetPackageVersions(Constants.TEMPLATES_PACKAGE); var filtered = versions.Where(v => !v.IsPrerelease && !v.IsLegacyVersion && v.Major >= 25) @@ -68,48 +68,6 @@ public override async Task InitSteps() ValueReceiver = (v) => Options.UseCloud = v, SkipChecker = IsAdminTemplate })); - - - var serverPrompt = new TextPrompt($"Enter the [{Constants.PROMPT_COLOR}]SQL server[/] name:"); - if (!string.IsNullOrEmpty(Options.ServerName)) - { - serverPrompt.DefaultValue(Options.ServerName); - } - Steps.Add(new Step(new() - { - Prompt = serverPrompt, - ValueReceiver = (v) => Options.ServerName = v, - SkipChecker = IsAdminTemplate - })); - - Steps.Add(new Step(new() - { - Prompt = new TextPrompt($"Enter the [{Constants.PROMPT_COLOR}]database[/] name:") - .AllowEmpty() - .DefaultValue(Options.DatabaseName), - ValueReceiver = (v) => Options.DatabaseName = v, - SkipChecker = IsAdminTemplate - })); - - var useExistingPrompt = new ConfirmationPrompt($"Use [{Constants.PROMPT_COLOR}]existing[/] database?") - { - DefaultValue = Options.UseExistingDatabase - }; - Steps.Add(new Step(new() - { - Prompt = useExistingPrompt, - ValueReceiver = (v) => Options.UseExistingDatabase = v, - SkipChecker = IsAdminTemplate - })); - - Steps.Add(new Step(new() - { - Prompt = new TextPrompt($"Enter the admin [{Constants.PROMPT_COLOR}]password[/]:") - .AllowEmpty() - .DefaultValue(Options.AdminPassword), - ValueReceiver = (v) => Options.AdminPassword = v, - SkipChecker = IsAdminTemplate - })); } diff --git a/src/Wizards/MacroWizard.cs b/src/Wizards/MacroWizard.cs index 1b8a57f..2b0644c 100644 --- a/src/Wizards/MacroWizard.cs +++ b/src/Wizards/MacroWizard.cs @@ -10,7 +10,7 @@ namespace Xperience.Manager.Wizards /// public class MacroWizard : AbstractWizard { - public override Task InitSteps() + public override Task InitSteps(params string[] args) { Steps.Add(new Step(new() { diff --git a/src/Wizards/NewProfileWizard.cs b/src/Wizards/NewProfileWizard.cs index b72f518..7fbd1fe 100644 --- a/src/Wizards/NewProfileWizard.cs +++ b/src/Wizards/NewProfileWizard.cs @@ -10,7 +10,7 @@ namespace Xperience.Manager.Wizards /// public class NewProfileWizard : AbstractWizard { - public override Task InitSteps() + public override Task InitSteps(params string[] args) { Steps.Add(new Step(new() { diff --git a/src/Wizards/RepositoryConfigurationWizard.cs b/src/Wizards/RepositoryConfigurationWizard.cs index 6a269c6..a297eda 100644 --- a/src/Wizards/RepositoryConfigurationWizard.cs +++ b/src/Wizards/RepositoryConfigurationWizard.cs @@ -20,7 +20,7 @@ public class RepositoryConfigurationWizard : AbstractWizard(new() { diff --git a/src/Wizards/SettingsWizard.cs b/src/Wizards/SettingsWizard.cs index 1b18088..99c9257 100644 --- a/src/Wizards/SettingsWizard.cs +++ b/src/Wizards/SettingsWizard.cs @@ -10,7 +10,7 @@ namespace Xperience.Manager.Wizards /// public class SettingsWizard : AbstractWizard { - public override Task InitSteps() + public override Task InitSteps(params string[] args) { Steps.Add(new Step(new() { diff --git a/src/Wizards/UpdateWizard.cs b/src/Wizards/UpdateWizard.cs index 1ac4bc3..efbbd3a 100644 --- a/src/Wizards/UpdateWizard.cs +++ b/src/Wizards/UpdateWizard.cs @@ -11,7 +11,7 @@ namespace Xperience.Manager.Wizards /// public class UpdateWizard : AbstractWizard { - public override async Task InitSteps() + public override async Task InitSteps(params string[] args) { var versions = await NuGetVersionHelper.GetPackageVersions(Constants.TEMPLATES_PACKAGE); var filtered = versions.Where(v => !v.IsPrerelease && !v.IsLegacyVersion && v.Major >= 25)