From 091f116195e632d5eca8f0eabe06accb761cd794 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lder=20Carvalho?= Date: Tue, 16 Apr 2024 19:53:32 +0100 Subject: [PATCH] ADD cmf menu and options/shortcuts for install env, run env, local db manager, generate various stuff and bump interactive --- cmf-cli/Builders/ProcessCommand.cs | 23 +- cmf-cli/Commands/bump/BumpCommand.cs | 106 +++- .../Commands/bump/BumpInteractiveCommand.cs | 84 +++ .../dbManager/BackupDatabaseCommand.cs | 66 +++ .../Commands/dbManager/DbManagerCommand.cs | 31 ++ .../dbManager/DeleteDatabaseBackupCommand.cs | 75 +++ .../dbManager/RestoreDatabaseCommand.cs | 80 +++ cmf-cli/Commands/generate/GenerateCommand.cs | 32 ++ .../generate/GenerateDocumentationCommand.cs | 52 ++ .../Commands/generate/GenerateLBOsCommand.cs | 50 ++ .../generate/GenerateReleaseNotesCommand.cs | 247 +++++++++ .../install/InstallBusinessCommand.cs | 48 ++ .../install/InstallBusinessDatabaseCommand.cs | 65 +++ .../install/InstallBusinessLocalEnvCommand.cs | 50 ++ cmf-cli/Commands/install/InstallCommand.cs | 53 ++ .../Commands/install/InstallHelpCommand.cs | 46 ++ .../Commands/install/InstallHtmlCommand.cs | 39 ++ cmf-cli/Commands/install/InstallIotCommand.cs | 39 ++ cmf-cli/Commands/menu/MenuCommand.cs | 488 +++++++++++++++++ cmf-cli/Commands/run/RunCommand.cs | 82 +++ cmf-cli/Commands/run/RunHTMLCommand.cs | 57 ++ cmf-cli/Commands/run/RunHelpCommand.cs | 58 ++ cmf-cli/Commands/run/RunHostCommand.cs | 82 +++ cmf-cli/Commands/run/RunMessageBusCommand.cs | 72 +++ .../PackageType/BusinessPackageTypeHandler.cs | 24 +- cmf-cli/Utilities/CmfGenericUtilities.cs | 74 +++ cmf-cli/cmf.csproj | 1 + core/Constants/CoreConstants.cs | 46 +- core/Objects/CmfPackage.cs | 433 ++++++++++----- core/Utilities/FileSystemUtilities.cs | 334 ++++++++---- core/Utilities/GenericUtilities.cs | 501 ++++++++++++++++-- core/core.csproj | 1 + 32 files changed, 3095 insertions(+), 344 deletions(-) create mode 100644 cmf-cli/Commands/bump/BumpInteractiveCommand.cs create mode 100644 cmf-cli/Commands/dbManager/BackupDatabaseCommand.cs create mode 100644 cmf-cli/Commands/dbManager/DbManagerCommand.cs create mode 100644 cmf-cli/Commands/dbManager/DeleteDatabaseBackupCommand.cs create mode 100644 cmf-cli/Commands/dbManager/RestoreDatabaseCommand.cs create mode 100644 cmf-cli/Commands/generate/GenerateCommand.cs create mode 100644 cmf-cli/Commands/generate/GenerateDocumentationCommand.cs create mode 100644 cmf-cli/Commands/generate/GenerateLBOsCommand.cs create mode 100644 cmf-cli/Commands/generate/GenerateReleaseNotesCommand.cs create mode 100644 cmf-cli/Commands/install/InstallBusinessCommand.cs create mode 100644 cmf-cli/Commands/install/InstallBusinessDatabaseCommand.cs create mode 100644 cmf-cli/Commands/install/InstallBusinessLocalEnvCommand.cs create mode 100644 cmf-cli/Commands/install/InstallCommand.cs create mode 100644 cmf-cli/Commands/install/InstallHelpCommand.cs create mode 100644 cmf-cli/Commands/install/InstallHtmlCommand.cs create mode 100644 cmf-cli/Commands/install/InstallIotCommand.cs create mode 100644 cmf-cli/Commands/menu/MenuCommand.cs create mode 100644 cmf-cli/Commands/run/RunCommand.cs create mode 100644 cmf-cli/Commands/run/RunHTMLCommand.cs create mode 100644 cmf-cli/Commands/run/RunHelpCommand.cs create mode 100644 cmf-cli/Commands/run/RunHostCommand.cs create mode 100644 cmf-cli/Commands/run/RunMessageBusCommand.cs create mode 100644 cmf-cli/Utilities/CmfGenericUtilities.cs diff --git a/cmf-cli/Builders/ProcessCommand.cs b/cmf-cli/Builders/ProcessCommand.cs index 7164ba9af..a23edbc31 100644 --- a/cmf-cli/Builders/ProcessCommand.cs +++ b/cmf-cli/Builders/ProcessCommand.cs @@ -5,25 +5,25 @@ using System; using System.IO.Abstractions; using System.Linq; +using System.Media; using System.Threading.Tasks; namespace Cmf.CLI.Builders { /// - /// /// public abstract class ProcessCommand : IProcessCommand { /// - /// the underlying file system + /// the underlying file system /// protected IFileSystem fileSystem = new FileSystem(); /// - /// Gets or sets the working directory. + /// Gets or sets the working directory. /// /// - /// The working directory. + /// The working directory. /// public IDirectoryInfo WorkingDirectory { get; set; } @@ -33,9 +33,10 @@ public virtual bool Condition() } /// - /// Executes this instance. + /// Executes this instance. /// - /// + /// + /// public Task Exec() { foreach (var step in this.GetSteps()) @@ -88,6 +89,11 @@ public Task Exec() ps.WaitForExit(); if (ps.ExitCode != 0) { + if (Environment.OSVersion.Platform == PlatformID.Win32NT) + { + // SoundPlayer is only supported on windows + new SoundPlayer(@"C:\Windows\Media\chord.wav").PlaySync(); + } throw new CliException($"Command '{command} {String.Join(' ', step.Args)}' did not finish successfully: Exit code {ps.ExitCode}. Please check the log for more details"); } ps.Dispose(); @@ -102,9 +108,10 @@ public Task Exec() } /// - /// Gets the steps. + /// Gets the steps. /// - /// + /// + /// public abstract ProcessBuildStep[] GetSteps(); } } \ No newline at end of file diff --git a/cmf-cli/Commands/bump/BumpCommand.cs b/cmf-cli/Commands/bump/BumpCommand.cs index 1d780d6b5..ca0ebcaa6 100644 --- a/cmf-cli/Commands/bump/BumpCommand.cs +++ b/cmf-cli/Commands/bump/BumpCommand.cs @@ -14,16 +14,16 @@ namespace Cmf.CLI.Commands { /// - /// /// - /// + /// [CmfCommand("bump", Id = "bump")] public class BumpCommand : BaseCommand { /// - /// Configure command + /// Configure command /// - /// + /// + /// public override void Configure(Command cmd) { cmd.AddArgument(new Argument( @@ -43,20 +43,44 @@ public override void Configure(Command cmd) aliases: new string[] { "-r", "--root" }, description: "Will bump only versions under a specific root folder (i.e. 1.0.0)")); + cmd.AddOption(new Option( + aliases: new string[] { "-p", "--parentDep" }, + getDefaultValue: () => false, + description: "Will bump the dependency version on parent packages")); + + cmd.AddOption(new Option( + aliases: new string[] { "-f", "--renameVersionFolders" }, + getDefaultValue: () => false, + description: "Will rename Version folders from old to new version")); + // Add the handler - cmd.Handler = CommandHandler.Create(Execute); + cmd.Handler = CommandHandler.Create(Execute); } /// - /// Executes the specified package path. + /// Executes the specified package path. /// - /// The package path. - /// The version. - /// The version for build Nr. - /// The root. - /// - /// - public void Execute(DirectoryInfo packagePath, string version, string buildNr, string root) + /// + /// The package path. + /// + /// + /// The version. + /// + /// + /// The version for build Nr. + /// + /// + /// The root. + /// + /// + /// Flag indicating if the dependency version in all parent packages will be updated. + /// + /// + /// Flag indicating if version folders will be renamed. + /// + /// + /// + public void Execute(DirectoryInfo packagePath, string version, string buildNr, string root, bool parentDep, bool renameVersionFolders) { using var activity = ExecutionContext.ServiceProvider?.GetService()?.StartExtendedActivity(this.GetType().Name); IFileInfo cmfpackageFile = this.fileSystem.FileInfo.New($"{packagePath}/{CliConstants.CmfPackageFileName}"); @@ -69,20 +93,44 @@ public void Execute(DirectoryInfo packagePath, string version, string buildNr, s // Reading cmfPackage CmfPackage cmfPackage = CmfPackage.Load(cmfpackageFile); - Execute(cmfPackage, version, buildNr, root); + Execute( + cmfPackage, + version, + buildNr, + root, + packagesToUpdateDep: parentDep ? FileSystemUtilities.GetAllPackages(fileSystem) : null, + renameVersionFolders: renameVersionFolders + ); } /// - /// Executes the specified CMF package. + /// Executes the specified CMF package. /// - /// The CMF package. - /// The version. - /// The version for build Nr. - /// The root. - /// - public void Execute(CmfPackage cmfPackage, string version, string buildNr, string root) + /// + /// The CMF package. + /// + /// + /// The version. + /// + /// + /// The version for build Nr. + /// + /// + /// The root. + /// + /// + /// CmfPackages to check and update dependency version. + /// Default = null (means that dependency version wont be updated in any package) + /// + /// + /// Flag indicating if version folders will be renamed. + /// + /// + /// + public void Execute(CmfPackage cmfPackage, string version, string buildNr, string root, CmfPackageCollection packagesToUpdateDep = null, bool renameVersionFolders = false) { - IDirectoryInfo packageDirectory = cmfPackage.GetFileInfo().Directory; + string oldVersion = cmfPackage.Version; + IPackageTypeHandler packageTypeHandler = PackageTypeFactory.GetPackageTypeHandler(cmfPackage); // Will execute specific bump code per Package Type @@ -95,6 +143,20 @@ public void Execute(CmfPackage cmfPackage, string version, string buildNr, strin // will save with new version cmfPackage.SaveCmfPackage(); + + // Update Version in package dependencies + if (packagesToUpdateDep.HasAny()) + { + foreach (CmfPackage packageToUpdate in packagesToUpdateDep) + { + packageToUpdate.UpdateDependency(cmfPackage); + } + } + + if (renameVersionFolders) + { + cmfPackage.RenameVersionFolders(oldVersion); + } } } } \ No newline at end of file diff --git a/cmf-cli/Commands/bump/BumpInteractiveCommand.cs b/cmf-cli/Commands/bump/BumpInteractiveCommand.cs new file mode 100644 index 000000000..6b5838485 --- /dev/null +++ b/cmf-cli/Commands/bump/BumpInteractiveCommand.cs @@ -0,0 +1,84 @@ +using Cmf.CLI.Core; +using Cmf.CLI.Core.Attributes; +using Cmf.CLI.Core.Objects; +using Cmf.CLI.Utilities; +using System; +using System.CommandLine; +using System.CommandLine.NamingConventionBinder; + +namespace Cmf.CLI.Commands.Bump +{ + /// + /// BumpInteractiveCommand + /// + /// + [CmfCommand("interactive", Id = "bump_interactive", ParentId = "bump")] + public class BumpInteractiveCommand : BaseCommand + { + /// + /// Configure command + /// + /// + /// Command + /// + public override void Configure(Command cmd) + { + cmd.Handler = CommandHandler.Create(Execute); + } + + /// + /// Executes the command + /// + public void Execute() + { + CmfPackageCollection allPackages = FileSystemUtilities.GetAllPackages(fileSystem); + + foreach (CmfPackage package in allPackages) + { + Log.Verbose(""); + Log.Verbose($"{package.PackageId}"); + Log.Verbose($"Current version: {package.Version}"); + string option = GenericUtilities.ReadStringValueFromConsole(prompt: "Bump? (Y/n):", allowEmptyValueInput: true, allowedValues: new string[] { "y", "n" }); + + if (string.IsNullOrEmpty(option) || option.IgnoreCaseEquals("y")) + { + Version version = new Version(package.Version); + string newVersion = string.Empty; + switch (GenericUtilities.ReadStringValueFromConsole(prompt: "Version to bump (a - Major) (i - Minor) (p - Patch):", allowedValues: new string[] { "a", "i", "p" })) + { + case "a": + { + newVersion = $"{version.Major + 1}.0.0"; + break; + } + case "i": + { + newVersion = $"{version.Major}.{version.Minor + 1}.0"; + break; + } + case "p": + { + newVersion = $"{version.Major}.{version.Minor}.{version.Build + 1}"; + break; + } + } + + string updateDepInParOption = GenericUtilities.ReadStringValueFromConsole(prompt: "Update in Parent Packages? (Y/n):", allowEmptyValueInput: true, allowedValues: new string[] { "y", "n" }); + string renameVersionFoldersOption = GenericUtilities.ReadStringValueFromConsole(prompt: "Rename Version folders? (Y/n):", allowEmptyValueInput: true, allowedValues: new string[] { "y", "n" }); + + // Change Current Directory because BumpCommand uses it + Environment.CurrentDirectory = package.GetFileInfo().Directory.FullName; + + new BumpCommand().Execute( + cmfPackage: package, + version: newVersion, + buildNr: null, + root: null, + packagesToUpdateDep: (string.IsNullOrEmpty(updateDepInParOption) || updateDepInParOption.IgnoreCaseEquals("y")) ? allPackages : null, + renameVersionFolders: string.IsNullOrEmpty(renameVersionFoldersOption) || renameVersionFoldersOption.IgnoreCaseEquals("y") + ); + } + } + } + } +} \ No newline at end of file diff --git a/cmf-cli/Commands/dbManager/BackupDatabaseCommand.cs b/cmf-cli/Commands/dbManager/BackupDatabaseCommand.cs new file mode 100644 index 000000000..aaf99825e --- /dev/null +++ b/cmf-cli/Commands/dbManager/BackupDatabaseCommand.cs @@ -0,0 +1,66 @@ +using Cmf.CLI.Core; +using Cmf.CLI.Core.Attributes; +using Cmf.CLI.Core.Constants; +using Cmf.CLI.Core.Objects; +using Cmf.CLI.Utilities; +using System.CommandLine; +using System.CommandLine.NamingConventionBinder; + +namespace Cmf.CLI.Commands.DbManager +{ + /// + /// BackupDatabaseCommand + /// + [CmfCommand("backup", Id = "dbmanager_backup", ParentId = "dbmanager")] + public class BackupDatabaseCommand : BaseCommand + { + /// + /// Configure command + /// + /// + /// Command + /// + public override void Configure(Command cmd) + { + cmd.AddOption( + new Option( + aliases: new[] { "--backupName" }, + getDefaultValue: () => null, + description: "Backup Name." + ) + ); + + cmd.Handler = CommandHandler.Create(Execute); + } + + /// + /// Executes the command + /// + public void Execute(string backupName = null) + { + string defaultDbName = ExecutionContext.Instance.ProjectConfig.EnvironmentName; + string currentDb = GenericUtilities.GetCurrentDb(fileSystem); + + if (backupName.IsNullOrEmpty()) + { + backupName = GenericUtilities.ReadStringValueFromConsole(prompt: $"Backup name (Press ENTER to use the current '{currentDb}'):", allowEmptyValueInput: true); + if (backupName.IsNullOrEmpty()) + { + backupName = currentDb; + } + } + + string backupFileName = $"Custom-{backupName}.bak"; + + Log.Status("Backing up database", (_) => + { + GenericUtilities.ExecuteSqlCommand( + connectionString: $"Data Source=127.0.0.1,1433;Initial Catalog=master;User ID={CoreConstants.LocalDbUser};Password={CoreConstants.LocalDbPassword}", + sqlCommand: $"USE[{defaultDbName}]; BACKUP DATABASE [{defaultDbName}] TO DISK = N'/DBDumps/{backupFileName}' WITH NOFORMAT, INIT, NAME = N'{defaultDbName}-Full Database Backup', SKIP, NOREWIND, NOUNLOAD, STATS = 10" + ); + + Log.Information($"DB backup '{backupFileName}' created."); + }); + } + } +} \ No newline at end of file diff --git a/cmf-cli/Commands/dbManager/DbManagerCommand.cs b/cmf-cli/Commands/dbManager/DbManagerCommand.cs new file mode 100644 index 000000000..28f57c727 --- /dev/null +++ b/cmf-cli/Commands/dbManager/DbManagerCommand.cs @@ -0,0 +1,31 @@ +using Cmf.CLI.Core.Attributes; +using System.CommandLine; +using System.CommandLine.NamingConventionBinder; + +namespace Cmf.CLI.Commands +{ + /// + /// DbManagerCommand + /// + [CmfCommand("dbmanager", Id = "dbmanager")] + public class DbManagerCommand : BaseCommand + { + /// + /// Configure command + /// + /// + /// Command + /// + public override void Configure(Command cmd) + { + cmd.Handler = CommandHandler.Create(Execute); + } + + /// + /// Executes the command + /// + public void Execute() + { + } + } +} \ No newline at end of file diff --git a/cmf-cli/Commands/dbManager/DeleteDatabaseBackupCommand.cs b/cmf-cli/Commands/dbManager/DeleteDatabaseBackupCommand.cs new file mode 100644 index 000000000..04366e8b4 --- /dev/null +++ b/cmf-cli/Commands/dbManager/DeleteDatabaseBackupCommand.cs @@ -0,0 +1,75 @@ +using Cmf.CLI.Core; +using Cmf.CLI.Core.Attributes; +using Cmf.CLI.Utilities; +using System.CommandLine; +using System.CommandLine.NamingConventionBinder; +using System.IO; + +namespace Cmf.CLI.Commands.DbManager +{ + /// + /// DeleteDatabaseBackupCommand + /// + [CmfCommand("delete", Id = "dbmanager_delete", ParentId = "dbmanager")] + public class DeleteDatabaseBackupCommand : BaseCommand + { + /// + /// Configure command + /// + /// + /// Command + /// + public override void Configure(Command cmd) + { + cmd.AddOption( + new Option( + aliases: new[] { "-f", "--backupFileName" }, + getDefaultValue: () => null, + description: "Backup file name (e.g.: Custom-Babkup.bak)." + ) + ); + + cmd.Handler = CommandHandler.Create(Execute); + } + + /// + /// Executes the command + /// + public void Execute(string backupFileName = null) + { + Log.Verbose(""); + + // Set initial backupFilePath + string backupFilePath = fileSystem.Path.Combine(FileSystemUtilities.GetProjectRoot(fileSystem).FullName, "LocalEnvironment", "DBDumps", backupFileName.IsNullOrEmpty() ? "" : backupFileName); + + string[] dbBackups = GenericUtilities.GetBdBackups(fileSystem); + + if (backupFileName.IsNullOrEmpty()) + { + Log.Verbose("Select DB backup file to delete:"); + for (int i = 0; i < dbBackups.Length; i++) + { + Log.Verbose($"{i + 1} - {Path.GetFileName(dbBackups[i])}"); + } + int option = GenericUtilities.ReadIntValueFromConsole( + prompt: "Option:", + minValue: 1, + maxValue: dbBackups.Length + ); + backupFilePath = dbBackups[option - 1]; + backupFileName = Path.GetFileName(backupFilePath); + } + else if (!File.Exists(backupFilePath)) + { + throw new FileNotFoundException($"DB Backup '{backupFileName}' doesn't exist."); + } + + Log.Status("Deleting backup file", (_) => + { + File.Delete(backupFilePath); + + Log.Information($"Deleted '{backupFileName}'."); + }); + } + } +} \ No newline at end of file diff --git a/cmf-cli/Commands/dbManager/RestoreDatabaseCommand.cs b/cmf-cli/Commands/dbManager/RestoreDatabaseCommand.cs new file mode 100644 index 000000000..f9232e304 --- /dev/null +++ b/cmf-cli/Commands/dbManager/RestoreDatabaseCommand.cs @@ -0,0 +1,80 @@ +using Cmf.CLI.Core; +using Cmf.CLI.Core.Attributes; +using Cmf.CLI.Core.Constants; +using Cmf.CLI.Core.Objects; +using Cmf.CLI.Utilities; +using System.CommandLine; +using System.CommandLine.NamingConventionBinder; +using System.IO; + +namespace Cmf.CLI.Commands.DbManager +{ + /// + /// RestoreDatabaseCommand + /// + [CmfCommand("restore", Id = "dbmanager_restore", ParentId = "dbmanager")] + public class RestoreDatabaseCommand : BaseCommand + { + /// + /// Configure command + /// + /// + /// Command + /// + public override void Configure(Command cmd) + { + cmd.AddOption( + new Option( + aliases: new[] { "-f", "--backupFileName" }, + getDefaultValue: () => null, + description: "Backup Name (e.g.: Custom-Babkup.bak)." + ) + ); + + cmd.Handler = CommandHandler.Create(Execute); + } + + /// + /// Executes the command + /// + public void Execute(string backupFileName = null) + { + string defaultDbName = ExecutionContext.Instance.ProjectConfig.EnvironmentName; + + Log.Verbose(""); + Log.Verbose($"Current database: {GenericUtilities.GetCurrentDb(fileSystem)}"); + Log.Warning("Close all programs using the DB (e.g.: HostService)"); + + string[] dbBackups = GenericUtilities.GetBdBackups(fileSystem); + + if (backupFileName.IsNullOrEmpty()) + { + Log.Verbose("Select DB backup to restore:"); + for (int i = 0; i < dbBackups.Length; i++) + { + Log.Verbose($"{i + 1} - {Path.GetFileName(dbBackups[i])}"); + } + int option = GenericUtilities.ReadIntValueFromConsole( + prompt: "Option:", + minValue: 1, + maxValue: dbBackups.Length + ); + backupFileName = Path.GetFileName(dbBackups[option - 1]); + } + else if (!File.Exists(fileSystem.Path.Combine(FileSystemUtilities.GetProjectRoot(fileSystem).FullName, "LocalEnvironment", "DBDumps", backupFileName))) + { + throw new FileNotFoundException($"DB Backup '{backupFileName}' doesn't exist."); + } + + Log.Status("Restoring database", (_) => + { + GenericUtilities.ExecuteSqlCommand( + connectionString: $"Data Source=127.0.0.1,1433;Initial Catalog=master;User ID={CoreConstants.LocalDbUser};Password={CoreConstants.LocalDbPassword}", + sqlCommand: $"USE[master] RESTORE DATABASE[{defaultDbName}] FROM DISK = N'/DBDumps/{backupFileName}' WITH FILE = 1, MOVE N'{defaultDbName}_Primary' TO N'/var/opt/mssql/data/{defaultDbName}.mdf', MOVE N'{defaultDbName}_FG_MainTableDat_1' TO N'/var/opt/mssql/data/{defaultDbName}_FG_MainTableDat_1.ndf', MOVE N'{defaultDbName}_FG_MainTableIdx_1' TO N'/var/opt/mssql/data/{defaultDbName}_FG_MainTableIdx_1.ndf', MOVE N'{defaultDbName}_FG_HstTableDat_1' TO N'/var/opt/mssql/data/{defaultDbName}_FG_HstTableDat_1.ndf', MOVE N'{defaultDbName}_FG_HstTableIdx_1' TO N'/var/opt/mssql/data/{defaultDbName}_FG_HstTableIdx_1.ndf', MOVE N'{defaultDbName}_FG_MappingTableIdx_1' TO N'/var/opt/mssql/data/{defaultDbName}_FG_MappingTableIdx_1.ndf', MOVE N'{defaultDbName}_FG_MappingTableDat_1' TO N'/var/opt/mssql/data/{defaultDbName}_FG_MappingTableDat_1.ndf', MOVE N'{defaultDbName}_FG_MappingHstIdx_1' TO N'/var/opt/mssql/data/{defaultDbName}_FG_MappingHstIdx_1.ndf', MOVE N'{defaultDbName}_FG_MappingHstDat_1' TO N'/var/opt/mssql/data/{defaultDbName}_FG_MappingHstDat_1.ndf', MOVE N'{defaultDbName}_FG_IntegrationTableIdx_1' TO N'/var/opt/mssql/data/{defaultDbName}_FG_IntegrationTableIdx_1.ndf', MOVE N'{defaultDbName}_FG_IntegrationTableDat_1' TO N'/var/opt/mssql/data/{defaultDbName}_FG_IntegrationTableDat_1.ndf', MOVE N'{defaultDbName}_FG_IntegrationHstIdx_1' TO N'/var/opt/mssql/data/{defaultDbName}_FG_IntegrationHstIdx_1.ndf', MOVE N'{defaultDbName}_FG_IntegrationHstDat_1' TO N'/var/opt/mssql/data/{defaultDbName}_FG_IntegrationHstDat_1.ndf', MOVE N'{defaultDbName}_log' TO N'/var/opt/mssql/data/{defaultDbName}.ldf', NOUNLOAD, STATS = 5" + ); + + Log.Information($"Restored '{backupFileName}'."); + }); + } + } +} \ No newline at end of file diff --git a/cmf-cli/Commands/generate/GenerateCommand.cs b/cmf-cli/Commands/generate/GenerateCommand.cs new file mode 100644 index 000000000..a6f7844bb --- /dev/null +++ b/cmf-cli/Commands/generate/GenerateCommand.cs @@ -0,0 +1,32 @@ +using Cmf.CLI.Core.Attributes; +using System.CommandLine; +using System.CommandLine.NamingConventionBinder; + +namespace Cmf.CLI.Commands +{ + /// + /// GenerateCommand + /// + /// + [CmfCommand("generate", Id = "generate")] + public class GenerateCommand : BaseCommand + { + /// + /// Configure command + /// + /// + /// Command + /// + public override void Configure(Command cmd) + { + cmd.Handler = CommandHandler.Create(Execute); + } + + /// + /// Executes the command + /// + public void Execute() + { + } + } +} \ No newline at end of file diff --git a/cmf-cli/Commands/generate/GenerateDocumentationCommand.cs b/cmf-cli/Commands/generate/GenerateDocumentationCommand.cs new file mode 100644 index 000000000..45ae1ef81 --- /dev/null +++ b/cmf-cli/Commands/generate/GenerateDocumentationCommand.cs @@ -0,0 +1,52 @@ +using Cmf.CLI.Builders; +using Cmf.CLI.Core.Attributes; +using Cmf.CLI.Core.Enums; +using Cmf.CLI.Utilities; +using System; +using System.CommandLine; +using System.CommandLine.NamingConventionBinder; +using System.IO.Abstractions; + +namespace Cmf.CLI.Commands.Generate +{ + /// + /// GenerateDocumentationCommand + /// + [CmfCommand("documentation", Id = "generate_documentation", ParentId = "generate")] + public class GenerateDocumentationCommand : BaseCommand + { + /// + /// Configure command + /// + /// + /// Command + /// + public override void Configure(Command cmd) + { + cmd.Handler = CommandHandler.Create(Execute); + } + + /// + /// Executes the command + /// + public void Execute() + { + IDirectoryInfo helpDirectory = GenericUtilities.SelectPackage(fileSystem, packageType: PackageType.Help).GetFileInfo().Directory; + + Environment.CurrentDirectory = helpDirectory.FullName; + + new GenerateBasedOnTemplatesCommand().Execute(); + new GenerateMenuItemsCommand().Execute(); + + new GulpCommand() + { + GulpFile = "gulpfile.js", + Task = "build", + DisplayName = "Build Help", + GulpJS = "node_modules/gulp/bin/gulp.js", + Args = new[] { "--production" }, + WorkingDirectory = helpDirectory + }.Exec(); + } + } +} \ No newline at end of file diff --git a/cmf-cli/Commands/generate/GenerateLBOsCommand.cs b/cmf-cli/Commands/generate/GenerateLBOsCommand.cs new file mode 100644 index 000000000..82efda104 --- /dev/null +++ b/cmf-cli/Commands/generate/GenerateLBOsCommand.cs @@ -0,0 +1,50 @@ +using Cmf.CLI.Builders; +using Cmf.CLI.Core; +using Cmf.CLI.Core.Attributes; +using Cmf.CLI.Core.Enums; +using Cmf.CLI.Utilities; +using System; +using System.CommandLine; +using System.CommandLine.NamingConventionBinder; + +namespace Cmf.CLI.Commands.Generate +{ + /// + /// GenerateLBOsCommand + /// + [CmfCommand("lbos", Id = "generate_lbos", ParentId = "generate")] + public class GenerateLBOsCommand : BaseCommand + { + /// + /// Configure command + /// + /// + /// Command + /// + public override void Configure(Command cmd) + { + cmd.Handler = CommandHandler.Create(Execute); + } + + /// + /// Executes the command + /// + public void Execute() + { + string powershellExe = GenericUtilities.GetPowerShellExecutable(); + string generateLBOsScriptPath = fileSystem.Path.Join("Libs", "LBOs", "generateLBOs.ps1"); + + CmfGenericUtilities.StartProjectDbContainer(fileSystem); + CmfGenericUtilities.BuildAllPackagesOfType(PackageType.Business, fileSystem); + + Log.Information("GenerateLBOs PowerShell Script"); + new CmdCommand() + { + DisplayName = $"{powershellExe} .\\{generateLBOsScriptPath}", + Command = $"{powershellExe} .\\{generateLBOsScriptPath}", + Args = Array.Empty(), + WorkingDirectory = FileSystemUtilities.GetProjectRoot(fileSystem) + }.Exec(); + } + } +} \ No newline at end of file diff --git a/cmf-cli/Commands/generate/GenerateReleaseNotesCommand.cs b/cmf-cli/Commands/generate/GenerateReleaseNotesCommand.cs new file mode 100644 index 000000000..1d2e7e157 --- /dev/null +++ b/cmf-cli/Commands/generate/GenerateReleaseNotesCommand.cs @@ -0,0 +1,247 @@ +using Cmf.CLI.Core; +using Cmf.CLI.Core.Attributes; +using Cmf.CLI.Core.Constants; +using Cmf.CLI.Core.Enums; +using Cmf.CLI.Core.Objects; +using Cmf.CLI.Utilities; +using Newtonsoft.Json; +using System; +using System.CommandLine; +using System.CommandLine.NamingConventionBinder; +using System.IO; +using System.IO.Abstractions; +using System.Net.Http; +using System.Text; +using System.Text.RegularExpressions; +using System.Web; + +namespace Cmf.CLI.Commands.Generate +{ + /// + /// GenerateReleaseNotesCommand + /// + [CmfCommand("releasenotes", Id = "generate_releasenotes", ParentId = "generate")] + public class GenerateReleaseNotesCommand : BaseCommand + { + /// + /// Configure command + /// + /// + /// Command + /// + public override void Configure(Command cmd) + { + cmd.AddOption( + new Option( + aliases: new[] { "--date" }, + getDefaultValue: () => DateTime.Now.ToString("yyyy/MM/dd"), + description: "Release Notes date." + ) + ); + + cmd.AddOption( + new Option( + aliases: new[] { "--includes" }, + getDefaultValue: () => "-", + description: "List of previous package versions included in current package (separated by \",\" comma)." + ) + ); + + cmd.AddOption( + new Option( + aliases: new[] { "--customDependsOnVersion" }, + getDefaultValue: () => "-", + description: "Package version in which current Package depends on." + ) + ); + + cmd.AddOption( + new Option( + aliases: new[] { "--tfsProject" }, + getDefaultValue: () => ExecutionContext.Instance.ProjectConfig.ProjectName, + description: "Tfs Project Name." + ) + ); + + cmd.AddOption( + new Option( + aliases: new[] { "--tfsProjectTeam" }, + description: "Tfs Project Team Name." + ) + { IsRequired = true } + ); + + cmd.AddOption( + new Option( + aliases: new[] { "--removeHtmlTags" }, + getDefaultValue: () => true, + description: "Remove Html Tags." + ) + ); + + cmd.AddOption( + new Option( + aliases: new[] { "--onlyResolvedOrClosed" }, + getDefaultValue: () => false, + description: "Get only Work Items with State \"Resolved\" or \"Closed\"." + ) + ); + + cmd.Handler = CommandHandler.Create(Execute); + } + + /// + /// Executes the command + /// + public void Execute(string date = null, string includes = "-", string customDependsOnVersion = "-", string tfsProject = null, string tfsProjectTeam = null, bool removeHtmlTags = true, bool onlyResolvedOrClosed = false) + { + #region Input Date + + // If no Date was inputed in parameter (when executing trough `cmf menu`) + if (string.IsNullOrEmpty(date)) + { + int yearInput = GenericUtilities.ReadIntValueFromConsole(prompt: "Year (empty to use current):", allowEmptyValueInput: true); + int year = yearInput == 0 ? DateTime.Now.Year : yearInput; + + int monthInput = GenericUtilities.ReadIntValueFromConsole(prompt: "Month (empty to use current):", minValue: 1, maxValue: 12, allowEmptyValueInput: true); + int month = monthInput == 0 ? DateTime.Now.Month : monthInput; + + int dayInput = GenericUtilities.ReadIntValueFromConsole(prompt: "Day (empty to use current):", minValue: 1, maxValue: DateTime.DaysInMonth(year, month), allowEmptyValueInput: true); + int day = dayInput == 0 ? DateTime.Now.Day : dayInput; + date = new DateTime(year, month, day).ToString("yyyy/MM/dd"); + } + + if (!DateTime.TryParse(date, out DateTime __)) + { + Log.Error("Invalid input date. Try again."); + return; + } + + #endregion Input Date + + #region Validate/Set Input data + + tfsProject = string.IsNullOrEmpty(tfsProject) ? ExecutionContext.Instance.ProjectConfig.ProjectName : tfsProject; + + if (string.IsNullOrEmpty(tfsProjectTeam)) + { + tfsProjectTeam = GenericUtilities.ReadValueFromConsole(prompt: "Tfs Project Team:"); + } + + #endregion Validate/Set Input data + + #region Get WI and Generate Release Notes file + + CmfPackage helpPackage = GenericUtilities.SelectPackage(fileSystem, packageType: PackageType.Help); + IFileInfo helpPackageFileInfo = helpPackage.GetFileInfo(); + + string srcPackage = Directory.GetDirectories($@"{helpPackageFileInfo.Directory}\src\packages")[0]; + string releaseNotesFolderPath = Array.Find(Directory.GetDirectories($@"{srcPackage}\assets"), dir => dir.Contains("releasenotes")); + if (string.IsNullOrEmpty(releaseNotesFolderPath)) + { + Log.Error("Release Notes folder not found."); + } + string releaseNotesTemplateFile = $@"{releaseNotesFolderPath}\packagetemplate"; + + string query = $"SELECT [System.Id], [System.Title], [System.WorkItemType], [System.State], [Project.ReleaseNotes] FROM workitems WHERE [System.TeamProject] = \"{tfsProject}\" AND [System.WorkItemType] IN (\"User Story\", \"Bug\") AND NOT [System.Tags] CONTAINS \"Internal\" AND NOT [System.Tags] CONTAINS \"Product\" AND NOT [System.Tags] CONTAINS \"T&M\" AND [Project.DocumentationImpact] CONTAINS \"{helpPackage.Version}\" {(onlyResolvedOrClosed ? "AND ([System.State] = \"Resolved\" OR [System.State] = \"Closed\")" : "")} ORDER BY [System.WorkItemType] DESC"; + StringContent body = new StringContent(JsonConvert.SerializeObject(new { query }), Encoding.UTF8, "application/json"); + + using HttpClient client = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true }); + client.DefaultRequestHeaders.Add("ContentType", "application/json"); + client.DefaultRequestHeaders.Add("Accept", "application/json;api-version=4.1;excludeUrls=true"); + client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate, br"); + + string uri = $"{CoreConstants.TfsServerUrl}/{HttpUtility.UrlPathEncode(tfsProject)}/{HttpUtility.UrlPathEncode(tfsProjectTeam)}/_apis/wit/wiql?timePrecision=true&$top=50"; + + // TODO: FIX returning bad response (might be related with server?) + /* + * when using a random Tfs Project Team, it gets an error response body with a readable message + * when using a correct Tfs Project Team, it gets an unreadable response with strange characters (responseContent = "\u001f�\b\0\0\0\0\0\u0004\0��\a`\u001cI�%&/m�{\u007fJ�J��t�\b�`\u0013$ؐ...) + */ + HttpResponseMessage response = client.PostAsync(uri, body).Result; + string responseContent = response.Content.ReadAsStringAsync().Result; + dynamic responseObject = JsonConvert.DeserializeObject(responseContent); + + if (!response.IsSuccessStatusCode) + { + throw new CliException(responseObject["message"].Value); + } + + string columns = string.Empty; + string workItemIds = string.Empty; + + foreach (dynamic column in responseObject.columns) + { + if (!string.IsNullOrEmpty(columns)) + columns += ","; + columns += column.referenceName; + } + + foreach (dynamic column in responseObject.workItems) + { + if (!string.IsNullOrEmpty(workItemIds)) + workItemIds += ","; + workItemIds += column.id; + } + + string releaseNotesContent = string.Empty; + if (!string.IsNullOrEmpty(workItemIds)) + { + string workItemsUri = $"{CoreConstants.TfsServerUrl}/_apis/wit/workItems?ids={workItemIds}&fields={columns}"; + string workItemsResponseContent = client.GetAsync(workItemsUri).GetAwaiter().GetResult().Content.ReadAsStringAsync().GetAwaiter().GetResult(); + dynamic workItems = JsonConvert.DeserializeObject(workItemsResponseContent); + + releaseNotesContent += "## User Stories/Bugs\n\n"; + releaseNotesContent += "| Title | Notes |\n"; + releaseNotesContent += "| :----------- | :--------------- |\n"; + + foreach (dynamic column in responseObject.workItems) + { + string workItemId = column.id.ToString(); + dynamic item = null; + foreach (dynamic wi in workItems.value) + { + if (wi.id.ToString() == workItemId) + { + item = wi.fields; + break; + } + } + if (item == null) + throw new CliException("Item not found"); + + string releasenotes = item.Project.ReleaseNotes ?? "-"; + if (removeHtmlTags) + { + releasenotes = Regex.Replace(releasenotes, "<[^>]*?>", ""); + } + + string idSection = $"**{item.id}**"; + bool isBug = item.System.WorkItemType != "User Story"; + if (isBug) + { + idSection = "" + idSection + ""; + } + releaseNotesContent += $"| {idSection} {item.System.Title} | {releasenotes} |\n"; + } + } + + string packageTemplateContent = File.ReadAllText(releaseNotesTemplateFile, Encoding.UTF8) + .Replace("@PackageId@", helpPackage.PackageName) + .Replace("@SprintNumber@", Regex.Replace(helpPackage.Version, "Sprint", "")) + .Replace("@PackageVersion@", helpPackage.Version) + .Replace("@ExpectedReleaseDate@", date) + .Replace("@PackageDeliverablesIncluded@", includes) + .Replace("@MESVersion@", ExecutionContext.Instance.ProjectConfig.MESVersion.ToString()) + .Replace("@CustomDependsOn@", customDependsOnVersion) + .Replace("@UserStories@", releaseNotesContent); + + string outputFileName = releaseNotesTemplateFile.Replace("packagetemplate", $"{date.Replace("/", "")}-{helpPackage.Version.Replace(".", "_")}.md"); + File.WriteAllText(outputFileName, packageTemplateContent, Encoding.UTF8); + + #endregion Get WI and Generate Release Notes file + + new GenerateDocumentationCommand().Execute(); + } + } +} \ No newline at end of file diff --git a/cmf-cli/Commands/install/InstallBusinessCommand.cs b/cmf-cli/Commands/install/InstallBusinessCommand.cs new file mode 100644 index 000000000..607cd30b1 --- /dev/null +++ b/cmf-cli/Commands/install/InstallBusinessCommand.cs @@ -0,0 +1,48 @@ +using Cmf.CLI.Core.Attributes; +using System.CommandLine; +using System.CommandLine.NamingConventionBinder; + +namespace Cmf.CLI.Commands.Install +{ + /// + /// InstallBusinessCommand + /// + [CmfCommand("business", Id = "install_business", ParentId = "install")] + public class InstallBusinessCommand : BaseCommand + { + /// + /// Configure command + /// + /// + /// Command + /// + public override void Configure(Command cmd) + { + cmd.AddOption( + new Option( + aliases: new[] { "--licenseId" }, + description: "Project License Id" + ) + { IsRequired = true } + ); + + cmd.Handler = CommandHandler.Create(Execute); + } + + /// + /// Executes the commands: + /// + /// + /// + /// + /// + /// + /// Project License Id + /// + public void Execute(long licenseId) + { + new InstallBusinessDatabaseCommand().Execute(licenseId); + new InstallBusinessLocalEnvCommand().Execute(); + } + } +} \ No newline at end of file diff --git a/cmf-cli/Commands/install/InstallBusinessDatabaseCommand.cs b/cmf-cli/Commands/install/InstallBusinessDatabaseCommand.cs new file mode 100644 index 000000000..28097446b --- /dev/null +++ b/cmf-cli/Commands/install/InstallBusinessDatabaseCommand.cs @@ -0,0 +1,65 @@ +using Cmf.CLI.Builders; +using Cmf.CLI.Core; +using Cmf.CLI.Core.Attributes; +using Cmf.CLI.Core.Objects; +using Cmf.CLI.Utilities; +using System.CommandLine; +using System.CommandLine.NamingConventionBinder; +using System.IO.Abstractions; + +namespace Cmf.CLI.Commands.Install +{ + /// + /// InstallBusinessDatabaseCommand + /// + [CmfCommand("database", Id = "install_business_database", ParentId = "install_business")] + public class InstallBusinessDatabaseCommand : BaseCommand + { + /// + /// Configure command + /// + /// + /// Command + /// + public override void Configure(Command cmd) + { + cmd.AddOption( + new Option( + aliases: new[] { "--licenseId" }, + description: "Project License Id" + ) + { IsRequired = true } + ); + + cmd.Handler = CommandHandler.Create(Execute); + } + + /// + /// Executes the command + /// + ///cmf-dev local db --license {licenseId}; + /// + /// + /// + /// Project License Id + /// + public void Execute(long licenseId) + { + IDirectoryInfo projectRoot = FileSystemUtilities.GetProjectRoot(fileSystem); + + // Set default DB as current DB + Log.Information($"Setting default DB as {ExecutionContext.Instance.ProjectConfig.EnvironmentName}"); + GenericUtilities.SetCurrentDb(fileSystem, $"{ExecutionContext.Instance.ProjectConfig.EnvironmentName}"); + + // cmf-dev local db --license {licenseId}; + Log.Information("Install Business Database"); + new CmdCommand() + { + DisplayName = $"cmf-dev local db --license {licenseId}", + Command = "cmf-dev local db", + Args = new string[] { $"--license {licenseId}" }, + WorkingDirectory = projectRoot + }.Exec(); + } + } +} \ No newline at end of file diff --git a/cmf-cli/Commands/install/InstallBusinessLocalEnvCommand.cs b/cmf-cli/Commands/install/InstallBusinessLocalEnvCommand.cs new file mode 100644 index 000000000..43b3cc9cc --- /dev/null +++ b/cmf-cli/Commands/install/InstallBusinessLocalEnvCommand.cs @@ -0,0 +1,50 @@ +using Cmf.CLI.Builders; +using Cmf.CLI.Core; +using Cmf.CLI.Core.Attributes; +using Cmf.CLI.Utilities; +using System; +using System.CommandLine; +using System.CommandLine.NamingConventionBinder; +using System.IO.Abstractions; + +namespace Cmf.CLI.Commands.Install +{ + /// + /// InstallBusinessLocalEnvCommand + /// + [CmfCommand("localenv", Id = "install_business_localenv", ParentId = "install_business")] + public class InstallBusinessLocalEnvCommand : BaseCommand + { + /// + /// Configure command + /// + /// + /// Command + /// + public override void Configure(Command cmd) + { + cmd.Handler = CommandHandler.Create(Execute); + } + + /// + /// Executes the command + /// + ///cmf-dev local env; + /// + /// + public void Execute() + { + IDirectoryInfo projectRoot = FileSystemUtilities.GetProjectRoot(fileSystem); + + // cmf-dev local env; + Log.Information("Install Business Local Env"); + new CmdCommand() + { + DisplayName = "cmf-dev local env", + Command = "cmf-dev local env", + Args = Array.Empty(), + WorkingDirectory = projectRoot + }.Exec(); + } + } +} \ No newline at end of file diff --git a/cmf-cli/Commands/install/InstallCommand.cs b/cmf-cli/Commands/install/InstallCommand.cs new file mode 100644 index 000000000..050e1b3a7 --- /dev/null +++ b/cmf-cli/Commands/install/InstallCommand.cs @@ -0,0 +1,53 @@ +using Cmf.CLI.Commands.Install; +using Cmf.CLI.Core.Attributes; +using System.CommandLine; +using System.CommandLine.NamingConventionBinder; + +namespace Cmf.CLI.Commands +{ + /// + /// InstallCommand + /// + /// + [CmfCommand("install", Id = "install")] + public class InstallCommand : BaseCommand + { + /// + /// Configure command + /// + /// + /// Command + /// + public override void Configure(Command cmd) + { + cmd.AddOption( + new Option( + aliases: new[] { "--licenseId" }, + description: "Project License Id" + ) + { IsRequired = true } + ); + + cmd.Handler = CommandHandler.Create(Execute); + } + + /// + /// Installs everything needed for the local environment to work + /// Executes the commands: + /// + /// + /// + /// + /// + /// + /// + /// Project License Id + /// + public void Execute(long licenseId) + { + new InstallBusinessCommand().Execute(licenseId); + new InstallHtmlCommand().Execute(); + new InstallHelpCommand().Execute(); + } + } +} \ No newline at end of file diff --git a/cmf-cli/Commands/install/InstallHelpCommand.cs b/cmf-cli/Commands/install/InstallHelpCommand.cs new file mode 100644 index 000000000..479e8c45b --- /dev/null +++ b/cmf-cli/Commands/install/InstallHelpCommand.cs @@ -0,0 +1,46 @@ +using Cmf.CLI.Core; +using Cmf.CLI.Core.Attributes; +using Cmf.CLI.Core.Enums; +using Cmf.CLI.Core.Objects; +using Cmf.CLI.Utilities; +using System; +using System.CommandLine; +using System.CommandLine.NamingConventionBinder; +using System.IO.Abstractions; + +namespace Cmf.CLI.Commands.Install +{ + /// + /// InstallHelpCommand + /// + [CmfCommand("help", Id = "install_help", ParentId = "install")] + public class InstallHelpCommand : BaseCommand + { + /// + /// Configure command + /// + /// + /// Command + /// + public override void Configure(Command cmd) + { + cmd.Handler = CommandHandler.Create(Execute); + } + + /// + /// Executes the command + /// + public void Execute() + { + CmfPackage helpPackage = GenericUtilities.SelectPackage(fileSystem, packageType: PackageType.Help); + + Log.Information($"Install Help: {helpPackage.PackageId}"); + IDirectoryInfo helpPackageDirectory = helpPackage.GetFileInfo().Directory; + + // Set the "Environment.CurrentDirectory" to the Help Package directory because "GenerateBasedOnTemplatesCommand" is using the "Environment.CurrentDirectory" + Environment.CurrentDirectory = helpPackageDirectory.FullName; + + new BuildCommand(fileSystem).Execute(helpPackageDirectory); + } + } +} \ No newline at end of file diff --git a/cmf-cli/Commands/install/InstallHtmlCommand.cs b/cmf-cli/Commands/install/InstallHtmlCommand.cs new file mode 100644 index 000000000..11a7bf27a --- /dev/null +++ b/cmf-cli/Commands/install/InstallHtmlCommand.cs @@ -0,0 +1,39 @@ +using Cmf.CLI.Core; +using Cmf.CLI.Core.Attributes; +using Cmf.CLI.Core.Enums; +using Cmf.CLI.Core.Objects; +using Cmf.CLI.Utilities; +using System.CommandLine; +using System.CommandLine.NamingConventionBinder; + +namespace Cmf.CLI.Commands.Install +{ + /// + /// InstallHtmlCommand + /// + [CmfCommand("html", Id = "install_html", ParentId = "install")] + public class InstallHtmlCommand : BaseCommand + { + /// + /// Configure command + /// + /// + /// Command + /// + public override void Configure(Command cmd) + { + cmd.Handler = CommandHandler.Create(Execute); + } + + /// + /// Executes the command + /// + public void Execute() + { + CmfPackage htmlPackage = GenericUtilities.SelectPackage(fileSystem, packageType: PackageType.HTML); + + Log.Information($"Install HTML: {htmlPackage.PackageId}"); + new BuildCommand(fileSystem).Execute(htmlPackage.GetFileInfo().Directory); + } + } +} \ No newline at end of file diff --git a/cmf-cli/Commands/install/InstallIotCommand.cs b/cmf-cli/Commands/install/InstallIotCommand.cs new file mode 100644 index 000000000..560122abb --- /dev/null +++ b/cmf-cli/Commands/install/InstallIotCommand.cs @@ -0,0 +1,39 @@ +using Cmf.CLI.Core; +using Cmf.CLI.Core.Attributes; +using Cmf.CLI.Core.Enums; +using Cmf.CLI.Core.Objects; +using Cmf.CLI.Utilities; +using System.CommandLine; +using System.CommandLine.NamingConventionBinder; + +namespace Cmf.CLI.Commands.Install +{ + /// + /// InstallIotCommand + /// + [CmfCommand("iot", Id = "install_iot", ParentId = "install")] + public class InstallIotCommand : BaseCommand + { + /// + /// Configure command + /// + /// + /// Command + /// + public override void Configure(Command cmd) + { + cmd.Handler = CommandHandler.Create(Execute); + } + + /// + /// Executes the command + /// + public void Execute() + { + CmfPackage iotPackage = GenericUtilities.SelectPackage(fileSystem, packageType: PackageType.IoT); + + Log.Information($"Install IoT: {iotPackage.PackageId}"); + new BuildCommand(fileSystem).Execute(iotPackage.GetFileInfo().Directory); + } + } +} \ No newline at end of file diff --git a/cmf-cli/Commands/menu/MenuCommand.cs b/cmf-cli/Commands/menu/MenuCommand.cs new file mode 100644 index 000000000..6860c7f36 --- /dev/null +++ b/cmf-cli/Commands/menu/MenuCommand.cs @@ -0,0 +1,488 @@ +using Cmf.CLI.Commands.Bump; +using Cmf.CLI.Commands.DbManager; +using Cmf.CLI.Commands.Generate; +using Cmf.CLI.Commands.Install; +using Cmf.CLI.Commands.Run; +using Cmf.CLI.Core; +using Cmf.CLI.Core.Attributes; +using Cmf.CLI.Core.Objects; +using Cmf.CLI.Utilities; +using System; +using System.Collections.Generic; +using System.CommandLine; +using System.CommandLine.NamingConventionBinder; + +namespace Cmf.CLI.Commands +{ + /// + /// MenuCommand + /// + /// + [CmfCommand("menu", Id = "menu")] + public class MenuCommand : BaseCommand + { + /// + /// Configure command + /// + /// + /// Command + /// + public override void Configure(Command cmd) + { + cmd.AddOption( + new Option( + aliases: new[] { "--licenseId" }, + description: "Project License Id" + ) + { IsRequired = true } + ); + + cmd.Handler = CommandHandler.Create(Execute); + } + + /// + /// Executes the command + /// + /// + /// Project License Id + /// + public void Execute(long licenseId) + { + GenericUtilities.EnsureIsRunningAsAdmin(); + + Menu menu = new Menu( + prompt: "Select what to run:", + items: new List + { + new MenuItem( + identifier: 1, + title: "Install All", + action: () => new InstallCommand().Execute(licenseId), + children: new List + { + new MenuItem( + identifier: 11, + title: "Install Business", + action: () => new InstallBusinessCommand().Execute(licenseId) + ), + new MenuItem( + identifier: 12, + title: "Install HTML", + action: () => new InstallHtmlCommand().Execute() + ), + new MenuItem( + identifier: 13, + title: "Install Help", + action: () => new InstallHelpCommand().Execute() + ), + new MenuItem( + identifier: 14, + title: "Install IoT", + action: () => new InstallIotCommand().Execute() + ) + } + ), + new MenuItem( + identifier: 2, + title: "Run All (MessageBus | Host | HTML)", + action: () => new RunCommand().Execute(), + children: new List + { + new MenuItem( + identifier: 21, + title: "Run MessageBus", + action: () => new RunMessageBusCommand().Execute() + ), + new MenuItem( + identifier: 22, + title: "Run Host", + action: () => new RunHostCommand().Execute() + ), + new MenuItem( + identifier: 23, + title: "Run HTML", + action: () => new RunHTMLCommand().Execute() + ), + new MenuItem( + identifier: 24, + title: "Run Help", + action: () => new RunHelpCommand().Execute() + ) + } + ), + new MenuItem( + identifier: 3, + title: "Local DB Manager", + children: new List + { + new MenuItem( + identifier: 31, + title: "Backup Local DB", + action: () => new BackupDatabaseCommand().Execute() + ), + new MenuItem( + identifier: 32, + title: "Restore Local DB", + action: () => new RestoreDatabaseCommand().Execute() + ), + new MenuItem( + identifier: 33, + title: "Delete Local DB Backup File", + action: () => new DeleteDatabaseBackupCommand().Execute() + ) + }, + disabled: true + ), + new MenuItem( + identifier: 4, + title: "Generate LBOs", + action: () => new GenerateLBOsCommand().Execute() + ), + new MenuItem( + identifier: 5, + title: "Generate Documentation", + action: () => new GenerateDocumentationCommand().Execute() + ), + new MenuItem( + identifier: 6, + title: "Generate Release Notes (BROKEN)", + action: () => new GenerateReleaseNotesCommand().Execute() + ), + new MenuItem( + identifier: 7, + title: "Bump Version (Interactive)", + action: () => new BumpInteractiveCommand().Execute() + ), + } + ); + + Log.Information($"---------- {ExecutionContext.Instance.ProjectConfig.Tenant} ----------"); + Log.Verbose($"Current License: {licenseId}"); + Log.Verbose($"Current Database: {GenericUtilities.GetCurrentDb(fileSystem)}"); + Log.Verbose(""); + menu.Run(); + } + } + + /// + /// Represents a Menu with a Prompt and a List of . + /// + public class Menu + { + /// + /// Gets or sets the prompt. + /// + /// + /// Text written in console before printing the Menu Items + /// + public string Prompt { get; set; } + + /// + /// Gets or sets the items. + /// + /// + /// List of Menu Items + /// + public List Items { get; set; } = new List(); + + #region Constructors + + /// + /// Initializes a new instance of the class with an empty List of . + /// + /// + /// See + /// + public Menu(string prompt) + { + Prompt = prompt; + } + + /// + /// Initializes a new instance of the class with given List of . + /// + /// + /// See + /// + /// + /// See + /// + public Menu(string prompt, List items) : this(prompt) + { + Items = items; + } + + #endregion Constructors + + #region Private Methods/Functions + + /// + /// Searches for a with a specific identifier, including nested levels (Children of Children). + /// + /// + /// Identifier of the to find. + /// + /// + /// Returns a that matches the provided identifier and is not disabled. + /// + /// If a matching item is found in the first level of children, it is returned immediately. If not, the method searches in the Items Children + /// and Children of Children (2nd and subsequent levels) until a matching item is found, and that item is returned. If no matching item is + /// found, a null value is returned. + /// + /// + private MenuItem FindItem(int identifier) + { + // Find in Menu.Items (1st level) + MenuItem item = Items.Find(item => item.Identifier == identifier && !item.Disabled); + if (!item.IsNullOrEmpty()) return item; + + foreach (MenuItem menuItem in Items) + { + // Find in Menu.Items.Children recursively (2nd and subsequent levels) + item = menuItem.FindChildren(identifier); + if (!item.IsNullOrEmpty()) break; + } + + return item; + } + + /// + /// Reads an option from the console until a valid is found. + /// + /// + /// + /// + private MenuItem ReadOption() + { + MenuItem item = null; + + while (item.IsNullOrEmpty()) + { + int option = GenericUtilities.ReadIntValueFromConsole(prompt: "Option:"); + item = FindItem(option); + if (item.IsNullOrEmpty()) + { + Log.Error("Invalid option. Try again."); + } + } + + return item; + } + + #endregion Private Methods/Functions + + #region Public Methods/Functions + + /// + /// Runs the Menu + /// + public void Run() + { + // Print the Menu + Log.Verbose(Prompt); + foreach (MenuItem item in Items) + { + item.Print(); + } + + // Read and execute the MenuItem.Action + ReadOption().Action(); + } + + #endregion Public Methods/Functions + } + + /// + /// Represents a MenuItem that can execute and have Children . + /// + public class MenuItem + { + /// + /// Gets or sets the identifier. + /// + /// + /// Number that the user will input to execute this + /// + public int Identifier { get; set; } + + /// + /// Gets or sets the title. + /// + /// + /// Text that will be written next to the . + /// + public string Title { get; set; } + + /// + /// Gets or sets a value indicating whether disabled. + /// + /// + /// Indicates if the is disabled or not. Disabled means that no will be displayed. + /// + public bool Disabled { get; set; } + + /// + /// Gets or sets the action. + /// + /// + /// Action that will be executed when the user chooses an option from the menu + /// + public Action Action { get; set; } + + /// + /// Gets or sets the children. + /// + /// + /// List of that are children + /// + public List Children { get; set; } = new List(); + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// + /// The identifier. + /// + /// + /// The title. + /// + public MenuItem(int identifier, string title) + { + Identifier = identifier; + Title = title; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The identifier. + /// + /// + /// The title. + /// + /// + /// The children. + /// + /// + /// If true, disabled. + /// + public MenuItem(int identifier, string title, List children, bool disabled = false) : this(identifier, title) + { + Children = children; + Disabled = disabled; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The identifier. + /// + /// + /// The title. + /// + /// + /// The action. + /// + public MenuItem(int identifier, string title, Action action) : this(identifier, title) + { + Action = action; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The identifier. + /// + /// + /// The title. + /// + /// + /// The action. + /// + /// + /// The children. + /// + public MenuItem(int identifier, string title, Action action, List children) : this(identifier, title, children) + { + Action = action; + } + + #endregion Constructors + + #region Private Methods/Functions + + /// + /// Prints the Menu Item + /// + /// + /// Current recursion level. This will add spaces when printing. + /// + private void PrintToConsole(int currentRecursionLevel = 0) + { + // Calculate the indentation based on the current recursion level. + string indentation = string.Empty; + for (int i = 0; i < currentRecursionLevel; i++) + { + indentation += " "; + } + + Log.Verbose($"{indentation}{(Disabled ? "X" : Identifier)} - {Title}"); + } + + #endregion Private Methods/Functions + + #region Public Methods/Functions + + /// + /// Recursively Prints the Menu Item and its Children + /// + /// + /// Current Recursion level. Always starts counting from 0. + /// + public void Print(int level = 0) + { + PrintToConsole(level); + foreach (MenuItem child in Children) + { + child.Print(level + 1); + } + } + + /// + /// Recursively searches for a with a specific identifier within the Children collection. + /// + /// + /// Identifier of the to find. + /// + /// + /// Returns a that matches the provided identifier and is not disabled. + /// + /// If a matching item is found in the first level of children, it is returned immediately. If not, the method recursively searches through the + /// Children of Children (2nd and subsequent levels) until a matching item is found, and that item is returned. If no matching item is found, a + /// null value is returned. + /// + /// + public MenuItem FindChildren(int identifier) + { + // Find in MenuItem.Children (1st level) + MenuItem item = Children.Find(item => item.Identifier == identifier && !item.Disabled); + if (!item.IsNullOrEmpty()) return item; + + foreach (MenuItem child in Children) + { + // Find in MenuItem.Children.Children recursively (2nd and subsequent levels) + item = child.FindChildren(identifier); + if (!item.IsNullOrEmpty()) break; + } + + return item; + } + + #endregion Public Methods/Functions + } +} \ No newline at end of file diff --git a/cmf-cli/Commands/run/RunCommand.cs b/cmf-cli/Commands/run/RunCommand.cs new file mode 100644 index 000000000..8999261bb --- /dev/null +++ b/cmf-cli/Commands/run/RunCommand.cs @@ -0,0 +1,82 @@ +using Cmf.CLI.Commands.Run; +using Cmf.CLI.Core.Attributes; +using Cmf.CLI.Utilities; +using System.CommandLine; +using System.CommandLine.NamingConventionBinder; +using System.IO.Abstractions; + +namespace Cmf.CLI.Commands +{ + /// + /// RunCommand + /// + /// + [CmfCommand("run", Id = "run")] + public class RunCommand : BaseCommand + { + /// + /// Configure command + /// + /// + /// Command + /// + public override void Configure(Command cmd) + { + cmd.Handler = CommandHandler.Create(Execute); + } + + /// + /// Runs everything needed to use the Local Environment + /// Executes the commands: + /// + /// + /// + /// + /// + /// + public void Execute() + { + GenericUtilities.EnsureWindowsTerminalIsInstalled(); + + IDirectoryInfo projectRoot = FileSystemUtilities.GetProjectRoot(fileSystem); + + /* + * Use Windows Terminal to run all + * This will create 3 new WindowsTerminal tabs, each of them executing a specific command in the CLI. + */ + + // Create Windows Terminal tab for MessageBus + GenericUtilities.ExecutePowerShellCommand("wt.exe", + "-w 0", + "nt", + "--title MessageBus", + "--suppressApplicationTitle", + $"-d {projectRoot}", + GenericUtilities.GetPowerShellExecutable(), + @"-c cmf run messagebus" + ); + + // Create Windows Terminal tab for Host + GenericUtilities.ExecutePowerShellCommand("wt.exe", + "-w 0", + "nt", + "--title Host", + "--suppressApplicationTitle", + $"-d {projectRoot}", + GenericUtilities.GetPowerShellExecutable(), + @"-c cmf run host" + ); + + // Create Windows Terminal tab for HTML + GenericUtilities.ExecutePowerShellCommand("wt.exe", + "-w 0", + "nt", + "--title HTML", + "--suppressApplicationTitle", + $"-d {projectRoot}", + GenericUtilities.GetPowerShellExecutable(), + @"-c cmf run html" + ); + } + } +} \ No newline at end of file diff --git a/cmf-cli/Commands/run/RunHTMLCommand.cs b/cmf-cli/Commands/run/RunHTMLCommand.cs new file mode 100644 index 000000000..bc6215683 --- /dev/null +++ b/cmf-cli/Commands/run/RunHTMLCommand.cs @@ -0,0 +1,57 @@ +using Cmf.CLI.Builders; +using Cmf.CLI.Core.Attributes; +using Cmf.CLI.Core.Enums; +using Cmf.CLI.Core.Objects; +using Cmf.CLI.Utilities; +using System.CommandLine; +using System.CommandLine.NamingConventionBinder; + +namespace Cmf.CLI.Commands.Run +{ + /// + /// RunHTMLCommand + /// + /// + [CmfCommand("html", Id = "run_html", ParentId = "run")] + public class RunHTMLCommand : BaseCommand + { + /// + /// Configure command + /// + /// + /// Command + /// + public override void Configure(Command cmd) + { + cmd.Handler = CommandHandler.Create(Execute); + } + + /// + /// Runs the HTML + /// + public void Execute() + { + CmfPackage htmlPackage = GenericUtilities.SelectPackage(fileSystem, packageType: PackageType.HTML); + + // Build HTML + new GulpCommand() + { + GulpFile = "gulpfile.js", + Task = "build", + DisplayName = "Gulp Build", + GulpJS = "node_modules/gulp/bin/gulp.js", + WorkingDirectory = htmlPackage.GetFileInfo().Directory + }.Exec(); + + // Start and Watch HTML (allows Rebuild on changes) + new GulpCommand() + { + GulpFile = "gulpfile.js", + Task = "start watch", + DisplayName = "Gulp Start Watch", + GulpJS = "node_modules/gulp/bin/gulp.js", + WorkingDirectory = htmlPackage.GetFileInfo().Directory + }.Exec(); + } + } +} \ No newline at end of file diff --git a/cmf-cli/Commands/run/RunHelpCommand.cs b/cmf-cli/Commands/run/RunHelpCommand.cs new file mode 100644 index 000000000..8bc0e3cd4 --- /dev/null +++ b/cmf-cli/Commands/run/RunHelpCommand.cs @@ -0,0 +1,58 @@ +using Cmf.CLI.Builders; +using Cmf.CLI.Core.Attributes; +using Cmf.CLI.Core.Enums; +using Cmf.CLI.Core.Objects; +using Cmf.CLI.Utilities; +using System.CommandLine; +using System.CommandLine.NamingConventionBinder; + +namespace Cmf.CLI.Commands.Run +{ + /// + /// RunHelpCommand + /// + /// + [CmfCommand("help", Id = "run_help", ParentId = "run")] + public class RunHelpCommand : BaseCommand + { + /// + /// Configure command + /// + /// + /// Command + /// + public override void Configure(Command cmd) + { + cmd.Handler = CommandHandler.Create(Execute); + } + + /// + /// Executes the command + /// + public void Execute() + { + CmfPackage helpPackage = GenericUtilities.SelectPackage(fileSystem, packageType: PackageType.Help); + + // Build Help + new GulpCommand() + { + GulpFile = "gulpfile.js", + Task = "build", + DisplayName = "Gulp Build", + GulpJS = "node_modules/gulp/bin/gulp.js", + Args = new[] { "--production" }, + WorkingDirectory = helpPackage.GetFileInfo().Directory + }.Exec(); + + // Start and Watch Help (allows Rebuild on changes) + new GulpCommand() + { + GulpFile = "gulpfile.js", + Task = "start watch", + DisplayName = "Gulp Start Watch", + GulpJS = "node_modules/gulp/bin/gulp.js", + WorkingDirectory = helpPackage.GetFileInfo().Directory + }.Exec(); + } + } +} \ No newline at end of file diff --git a/cmf-cli/Commands/run/RunHostCommand.cs b/cmf-cli/Commands/run/RunHostCommand.cs new file mode 100644 index 000000000..2cd1cc3aa --- /dev/null +++ b/cmf-cli/Commands/run/RunHostCommand.cs @@ -0,0 +1,82 @@ +using Cmf.CLI.Core; +using Cmf.CLI.Core.Attributes; +using Cmf.CLI.Core.Enums; +using Cmf.CLI.Utilities; +using System; +using System.CommandLine; +using System.CommandLine.NamingConventionBinder; +using System.Diagnostics; +using System.Threading; + +namespace Cmf.CLI.Commands.Run +{ + /// + /// RunHostCommand + /// + /// + [CmfCommand("host", Id = "run_host", ParentId = "run")] + public class RunHostCommand : BaseCommand + { + /// + /// Configure command + /// + /// + /// Command + /// + public override void Configure(Command cmd) + { + cmd.Handler = CommandHandler.Create(Execute); + } + + /// + /// Runs the Host service + /// Press 'R' to restart the Host execution + /// + public void Execute() + { + string businessTierFolderPath = fileSystem.Path.Combine(FileSystemUtilities.GetProjectRoot(fileSystem).FullName, "LocalEnvironment", "BusinessTier"); + string hostExePath = fileSystem.Path.Combine(businessTierFolderPath, "Cmf.Foundation.Services.HostService.exe"); + + Environment.CurrentDirectory = businessTierFolderPath; + + while (true) + { + CmfGenericUtilities.StartProjectDbContainer(fileSystem); + + CmfGenericUtilities.BuildAllPackagesOfType(PackageType.Business, fileSystem); + + Environment.CurrentDirectory = businessTierFolderPath; + + // Create HostService process + using Process process = new Process(); + process.StartInfo.FileName = hostExePath; + process.Start(); + Log.Information("Host Service process started. Press 'R' to rebuild and restart."); + + while (!process.HasExited) + { + if (Console.KeyAvailable) + { + ConsoleKeyInfo key = Console.ReadKey(true); + if (key.Key == ConsoleKey.R) + { + process.Kill(); + GenericUtilities.FullConsoleClear(); + process.WaitForExit(); + + CmfGenericUtilities.BuildAllPackagesOfType(PackageType.Business, fileSystem); + + process.Start(); + Log.Information("Host Service process rebuild and restarted."); + } + } + Thread.Sleep(1000); + } + + Log.Information("Process Terminated! Restarting in 30 sec."); + Thread.Sleep(30000); + GenericUtilities.FullConsoleClear(); + } + } + } +} \ No newline at end of file diff --git a/cmf-cli/Commands/run/RunMessageBusCommand.cs b/cmf-cli/Commands/run/RunMessageBusCommand.cs new file mode 100644 index 000000000..c5b6f6149 --- /dev/null +++ b/cmf-cli/Commands/run/RunMessageBusCommand.cs @@ -0,0 +1,72 @@ +using Cmf.CLI.Core; +using Cmf.CLI.Core.Attributes; +using Cmf.CLI.Utilities; +using System; +using System.CommandLine; +using System.CommandLine.NamingConventionBinder; +using System.Diagnostics; +using System.IO.Abstractions; +using System.Threading; + +namespace Cmf.CLI.Commands.Run +{ + /// + /// RunMessageBusCommand + /// + /// + [CmfCommand("messagebus", Id = "run_messagebus", ParentId = "run")] + public class RunMessageBusCommand : BaseCommand + { + /// + /// Configure command + /// + /// + /// Command + /// + public override void Configure(Command cmd) + { + cmd.Handler = CommandHandler.Create(Execute); + } + + /// + /// Runs the Message Bus + /// Press 'R' to restart the MessageBus execution + /// + public void Execute() + { + IDirectoryInfo projectRoot = FileSystemUtilities.GetProjectRoot(fileSystem); + + string messageBusFolderPath = fileSystem.Path.Combine(projectRoot.FullName, "LocalEnvironment", "MessageBusGateway"); + string messageBusExePath = fileSystem.Path.Combine(messageBusFolderPath, "Cmf.MessageBus.Gateway.exe"); + + Environment.CurrentDirectory = messageBusFolderPath; + + while (true) + { + using Process process = new Process(); + process.StartInfo.FileName = messageBusExePath; + + process.Start(); + Log.Information("Process started. Press 'R' to restart."); + + while (!process.HasExited) + { + if (Console.KeyAvailable) + { + ConsoleKeyInfo key = Console.ReadKey(true); + if (key.Key == ConsoleKey.R) + { + GenericUtilities.FullConsoleClear(); + process.Kill(); + process.WaitForExit(); + + process.Start(); + Log.Information("Process restarted."); + } + } + Thread.Sleep(1000); + } + } + } + } +} \ No newline at end of file diff --git a/cmf-cli/Handlers/PackageType/BusinessPackageTypeHandler.cs b/cmf-cli/Handlers/PackageType/BusinessPackageTypeHandler.cs index 0334e7c5d..7bb9a95fb 100644 --- a/cmf-cli/Handlers/PackageType/BusinessPackageTypeHandler.cs +++ b/cmf-cli/Handlers/PackageType/BusinessPackageTypeHandler.cs @@ -10,15 +10,16 @@ namespace Cmf.CLI.Handlers { /// - /// /// - /// + /// public class BusinessPackageTypeHandler : PackageTypeHandler { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The CMF package. + /// + /// The CMF package. + /// public BusinessPackageTypeHandler(CmfPackage cmfPackage) : base(cmfPackage) { cmfPackage.SetDefaultValues( @@ -67,7 +68,6 @@ public BusinessPackageTypeHandler(CmfPackage cmfPackage) : base(cmfPackage) Command = "build", DisplayName = "Build Business Solution", Solution = this.fileSystem.FileInfo.New(Path.Join(cmfPackage.GetFileInfo().Directory.FullName, "Business.sln")), - Configuration = "Release", WorkingDirectory = cmfPackage.GetFileInfo().Directory, Args = new [] { "--no-restore "} }, @@ -83,11 +83,17 @@ public BusinessPackageTypeHandler(CmfPackage cmfPackage) : base(cmfPackage) } /// - /// Bumps the specified CMF package. + /// Bumps the specified CMF package. /// - /// The version. - /// The version for build Nr. - /// The bump information. + /// + /// The version. + /// + /// + /// The version for build Nr. + /// + /// + /// The bump information. + /// public override void Bump(string version, string buildNr, Dictionary bumpInformation = null) { base.Bump(version, buildNr, bumpInformation); diff --git a/cmf-cli/Utilities/CmfGenericUtilities.cs b/cmf-cli/Utilities/CmfGenericUtilities.cs new file mode 100644 index 000000000..47bc5a162 --- /dev/null +++ b/cmf-cli/Utilities/CmfGenericUtilities.cs @@ -0,0 +1,74 @@ +using Cmf.CLI.Builders; +using Cmf.CLI.Commands; +using Cmf.CLI.Core; +using Cmf.CLI.Core.Enums; +using Cmf.CLI.Core.Objects; +using System; +using System.IO.Abstractions; + +namespace Cmf.CLI.Utilities +{ + /// + /// Utilities Class for CMF + /// + public static class CmfGenericUtilities + { + /// + /// Builds all packages of a given type. + /// + /// + /// Type of package to be built. + /// + /// + /// FileSystem + /// + public static void BuildAllPackagesOfType(PackageType packageType, IFileSystem fileSystem) + { + IDirectoryInfo projectRoot = FileSystemUtilities.GetProjectRoot(fileSystem); + + // Get packages of given type + CmfPackageCollection packages = projectRoot.LoadCmfPackagesFromSubDirectories(packageType: packageType); + + BuildCommand buildCommand = new BuildCommand(fileSystem); + + // Build All packages + foreach (CmfPackage package in packages) + { + Log.Information($"Build {Enum.GetName(typeof(PackageType), packageType)}: {package.PackageId}"); + buildCommand.Execute(package.GetFileInfo().Directory); + } + } + + /// + /// Stop any running project DB containers and start current project DB container. + /// + /// + /// FileSystem` + /// + public static void StartProjectDbContainer(IFileSystem fileSystem) + { + string powershellExe = GenericUtilities.GetPowerShellExecutable(); + IDirectoryInfo projectRoot = FileSystemUtilities.GetProjectRoot(fileSystem); + + Log.Information("Stopping other project containers if any is running"); + new CmdCommand() + { + DisplayName = $"{powershellExe} -C \"if (wsl docker ps -q --filter name=\"-\") {{ wsl docker stop $(wsl docker ps -q --filter name=\"-\") }}\"", + Command = $"{powershellExe} -C \"if (wsl docker ps -q --filter name=\"-\") {{ wsl docker stop $(wsl docker ps -q --filter name=\"-\") }}\"", + Args = Array.Empty(), + WorkingDirectory = projectRoot + }.Exec(); + + string dockerDbContainerName = $"{ExecutionContext.Instance.ProjectConfig.EnvironmentName}-DB"; + + Log.Information("Starting project container"); + new CmdCommand() + { + DisplayName = $"wsl docker start {dockerDbContainerName}", + Command = $"wsl docker start {dockerDbContainerName}", + Args = Array.Empty(), + WorkingDirectory = projectRoot + }.Exec(); + } + } +} \ No newline at end of file diff --git a/cmf-cli/cmf.csproj b/cmf-cli/cmf.csproj index cd180c18a..7065028c0 100644 --- a/cmf-cli/cmf.csproj +++ b/cmf-cli/cmf.csproj @@ -33,6 +33,7 @@ + diff --git a/core/Constants/CoreConstants.cs b/core/Constants/CoreConstants.cs index 362758223..fc0919734 100644 --- a/core/Constants/CoreConstants.cs +++ b/core/Constants/CoreConstants.cs @@ -1,74 +1,88 @@ namespace Cmf.CLI.Core.Constants { /// - /// /// public static class CoreConstants { #region Folders /// - /// The folder install dependencies + /// The folder install dependencies /// public const string FolderInstallDependencies = "installDependencies"; - #endregion + #endregion Folders #region Files /// - /// The deployment framework manifest template + /// The deployment framework manifest template /// public const string DeploymentFrameworkManifestFileName = "manifest.xml"; /// - /// The CMF package file name + /// The CMF package file name /// public const string CmfPackageFileName = "cmfpackage.json"; /// - /// The package json + /// The package json /// public const string PackageJson = "package.json"; /// - /// The project config file name, located in the project root + /// The project config file name, located in the project root /// public const string ProjectConfigFileName = ".project-config.json"; /// - /// The repositories config file name, usually located in the project root + /// The repositories config file name, usually located in the project root /// public const string RepositoriesConfigFileName = "repositories.json"; - #endregion + #endregion Files #region Generic /// - /// The root package default keyword + /// The root package default keyword /// public const string RootPackageDefaultKeyword = "cmf-root-package"; /// - /// npm.js repository url + /// npm.js repository url /// public const string NpmJsUrl = "https://registry.npmjs.com"; - #endregion + /// + /// Tfs server url + /// + public const string TfsServerUrl = "https://tfs.criticalmanufacturing.com/ImplementationProjects"; + + /// + /// Local DB user name + /// + public const string LocalDbUser = "MESUser"; + + /// + /// Local DB password + /// + public const string LocalDbPassword = "localEnvPassword"; + + #endregion Generic #region Tokens /// - /// The token start element + /// The token start element /// public const string TokenStartElement = "$("; /// - /// The token end element + /// The token end element /// public const string TokenEndElement = ")"; - - #endregion + + #endregion Tokens } } \ No newline at end of file diff --git a/core/Objects/CmfPackage.cs b/core/Objects/CmfPackage.cs index 041cefe70..fcebdae90 100644 --- a/core/Objects/CmfPackage.cs +++ b/core/Objects/CmfPackage.cs @@ -11,28 +11,26 @@ using System.IO.Abstractions; using System.IO.Compression; using System.Linq; -using System.Runtime.CompilerServices; using System.Text.RegularExpressions; using System.Xml.Linq; namespace Cmf.CLI.Core.Objects { /// - /// /// - /// + /// [JsonObject] public class CmfPackage : IEquatable { #region Private Properties /// - /// The file information + /// The file information /// private IFileInfo FileInfo; /// - /// Should we set the defaults values as described in the package handler? + /// Should we set the defaults values as described in the package handler? /// internal bool IsToSetDefaultValues; @@ -46,19 +44,19 @@ public class CmfPackage : IEquatable public IFileSystem FileSystem => fileSystem; /// - /// Gets the file name of the package. + /// Gets the file name of the package. /// /// - /// The file name of the package. + /// The file name of the package. /// [JsonIgnore] public string PackageName => $"{PackageId}.{Version}"; /// - /// Gets the name of the zip package. + /// Gets the name of the zip package. /// /// - /// The name of the zip package. + /// The name of the zip package. /// [JsonIgnore] public string ZipPackageName => $"{PackageName}.zip"; @@ -68,212 +66,212 @@ public class CmfPackage : IEquatable #region Public Properties /// - /// Gets the name. + /// Gets the name. /// /// - /// The name. + /// The name. /// [JsonProperty(Order = 0)] public string Name { get; private set; } /// - /// Gets or sets the package identifier. + /// Gets or sets the package identifier. /// /// - /// The package identifier. + /// The package identifier. /// [JsonProperty(Order = 1)] public string PackageId { get; private set; } /// - /// Gets or sets the version. + /// Gets or sets the version. /// /// - /// The version. + /// The version. /// [JsonProperty(Order = 2)] public string Version { get; private set; } /// - /// Gets or sets the description. + /// Gets or sets the description. /// /// - /// The description. + /// The description. /// [JsonProperty(Order = 3)] public string Description { get; private set; } /// - /// Gets or sets the type of the package. + /// Gets or sets the type of the package. /// /// - /// The type of the package. + /// The type of the package. /// [JsonConverter(typeof(StringEnumConverter))] [JsonProperty(Order = 4)] public PackageType PackageType { get; private set; } /// - /// Gets or sets the target directory where the package contents should be installed. - /// This is used when the package is installed using Deployment Framework and ignored when it is installed using Environment Manager. + /// Gets or sets the target directory where the package contents should be installed. This is used when the package is installed using Deployment + /// Framework and ignored when it is installed using Environment Manager. /// /// - /// The target directory. + /// The target directory. /// [JsonProperty(Order = 5)] public string TargetDirectory { get; private set; } /// - /// Gets or sets the target layer, which means the container in which the packages contents should be installed. - /// This is used when the package is installed using Environment Manager and ignored when it is installed using Deployment Framework. + /// Gets or sets the target layer, which means the container in which the packages contents should be installed. This is used when the package is + /// installed using Environment Manager and ignored when it is installed using Deployment Framework. /// /// - /// The target layer. + /// The target layer. /// [JsonProperty(Order = 6)] public string TargetLayer { get; private set; } /// - /// Gets or sets a value indicating whether this instance is installable. + /// Gets or sets a value indicating whether this instance is installable. /// /// - /// true if this instance is installable; otherwise, false. + /// true if this instance is installable; otherwise, false. /// [JsonProperty(Order = 7)] public bool? IsInstallable { get; private set; } /// - /// Gets or sets the is unique install. + /// Gets or sets the is unique install. /// /// - /// The is unique install. + /// The is unique install. /// [JsonProperty(Order = 8)] public bool? IsUniqueInstall { get; private set; } /// - /// Gets or sets the is root package. + /// Gets or sets the is root package. /// /// - /// The is root package. + /// The is root package. /// [JsonProperty(Order = 9)] [JsonIgnore] public string Keywords { get; private set; } /// - /// Should we set the default steps as described in the handler? + /// Should we set the default steps as described in the handler? /// /// - /// true to set the default steps; otherwise, false. + /// true to set the default steps; otherwise, false. /// [JsonProperty(Order = 10)] [JsonIgnore] public bool? IsToSetDefaultSteps { get; private set; } /// - /// Gets or sets the dependencies. + /// Gets or sets the dependencies. /// /// - /// The dependencies. + /// The dependencies. /// [JsonProperty(Order = 11)] public DependencyCollection Dependencies { get; private set; } /// - /// Gets or sets the steps. + /// Gets or sets the steps. /// /// - /// The steps. + /// The steps. /// [JsonProperty(Order = 12)] public List Steps { get; set; } /// - /// Gets or sets the content to pack. + /// Gets or sets the content to pack. /// /// - /// The content to pack. + /// The content to pack. /// [JsonProperty(Order = 13)] public List ContentToPack { get; private set; } /// - /// Gets or sets the deployment framework UI file. + /// Gets or sets the deployment framework UI file. /// /// - /// The deployment framework UI file. + /// The deployment framework UI file. /// [JsonProperty(Order = 14)] public List XmlInjection { get; private set; } /// - /// Gets or sets the Test Package Id. + /// Gets or sets the Test Package Id. /// /// - /// The Test Package Id. + /// The Test Package Id. /// [JsonProperty(Order = 15)] public DependencyCollection TestPackages { get; set; } /// - /// The location of the package + /// The location of the package /// [JsonProperty(Order = 16)] [JsonIgnore] public PackageLocation Location { get; private set; } /// - /// Handler Version + /// Handler Version /// [JsonProperty(Order = 17)] public int HandlerVersion { get; private set; } /// - /// Should the Deployment Framework wait for the Integration Entries created by the package - /// This fails the package installation if any Integration Entry fails + /// Should the Deployment Framework wait for the Integration Entries created by the package This fails the package installation if any Integration + /// Entry fails /// [JsonProperty(Order = 18)] public bool? WaitForIntegrationEntries { get; private set; } /// - /// The Uri of the package + /// The Uri of the package /// [JsonProperty(Order = 19)] [JsonIgnore] public Uri Uri { get; private set; } /// - /// The df package type + /// The df package type /// [JsonConverter(typeof(StringEnumConverter))] [JsonProperty(Order = 20)] public PackageType? DFPackageType { get; set; } /// - /// Gets or sets the build steps. + /// Gets or sets the build steps. /// /// - /// The build steps. + /// The build steps. /// [JsonProperty(Order = 21)] public List BuildSteps { get; set; } /// - /// Gets or sets the Related packages, and sets what are the expected behavior. + /// Gets or sets the Related packages, and sets what are the expected behavior. /// /// - /// Packages that should be built/packed before/after the context package + /// Packages that should be built/packed before/after the context package /// [JsonProperty(Order = 22)] public RelatedPackageCollection RelatedPackages { get; set; } /// - /// Gets or sets the target directory where the dependencies contents should be extracted. - /// This is used when the package dependencies are restored in the restore and build commands. + /// Gets or sets the target directory where the dependencies contents should be extracted. This is used when the package dependencies are restored + /// in the restore and build commands. /// /// - /// The dependencies target directory. + /// The dependencies target directory. /// [JsonProperty(Order = 23)] public string DependenciesDirectory { get; set; } @@ -283,25 +281,59 @@ public class CmfPackage : IEquatable #region Constructors /// - /// Initializes a new instance of the class. - /// - /// The name. - /// The package identifier. - /// The version. - /// The description. - /// Type of the package. - /// The target directory. - /// The target layer. - /// The is installable. - /// The is unique install. - /// The keywords. - /// The is to set default steps. - /// The dependencies. - /// The steps. - /// The content to pack. - /// The XML injection. - /// should wait for integration entries to complete - /// The test Packages. + /// Initializes a new instance of the class. + /// + /// + /// The name. + /// + /// + /// The package identifier. + /// + /// + /// The version. + /// + /// + /// The description. + /// + /// + /// Type of the package. + /// + /// + /// The target directory. + /// + /// + /// The target layer. + /// + /// + /// The is installable. + /// + /// + /// The is unique install. + /// + /// + /// The keywords. + /// + /// + /// The is to set default steps. + /// + /// + /// The dependencies. + /// + /// + /// The steps. + /// + /// + /// The content to pack. + /// + /// + /// The XML injection. + /// + /// + /// should wait for integration entries to complete + /// + /// + /// The test Packages. + /// [JsonConstructor] public CmfPackage(string name, string packageId, string version, string description, PackageType packageType, string targetDirectory, string targetLayer, bool? isInstallable, bool? isUniqueInstall, string keywords, @@ -328,16 +360,17 @@ public CmfPackage(string name, string packageId, string version, string descript } /// - /// initialize an empty CmfPackage + /// initialize an empty CmfPackage /// public CmfPackage() : this(fileSystem: new FileSystem()) { } /// - /// Initialize an empty CmfPackage with a specific file system + /// Initialize an empty CmfPackage with a specific file system /// - /// + /// + /// public CmfPackage(IFileSystem fileSystem) { this.fileSystem = fileSystem; @@ -349,7 +382,7 @@ public CmfPackage(IFileInfo fileInfo) : this(ExecutionContext.Instance.FileSyste } /// - /// Initialize CmfPackage with PackageId, Version and Uri + /// Initialize CmfPackage with PackageId, Version and Uri /// public CmfPackage(string packageId, string version, Uri uri) { @@ -363,7 +396,7 @@ public CmfPackage(string packageId, string version, Uri uri) #region Public Methods /// - /// Validates the package. + /// Validates the package. /// public void ValidatePackage() { @@ -400,11 +433,13 @@ public void ValidatePackage() } /// - /// Indicates whether the current object is equal to another object of the same type. + /// Indicates whether the current object is equal to another object of the same type. /// - /// An object to compare with this object. + /// + /// An object to compare with this object. + /// /// - /// if the current object is equal to the parameter; otherwise, . + /// if the current object is equal to the parameter; otherwise, . /// public bool Equals(CmfPackage other) { @@ -426,10 +461,10 @@ public bool Equals(CmfPackage other) } /// - /// Gets or sets the file information. + /// Gets or sets the file information. /// /// - /// The file information. + /// The file information. /// public IFileInfo GetFileInfo() { @@ -437,26 +472,45 @@ public IFileInfo GetFileInfo() } /// - /// Gets or sets the file information. + /// Gets or sets the file information. /// - /// The file information. + /// + /// The file information. + /// public void SetFileInfo(IFileInfo value) { FileInfo = value; } /// - /// Sets the defaults. + /// Sets the defaults. /// - /// The name. - /// The target directory. - /// The target layer container. - /// The is installable. - /// The is unique install. - /// The keywords. - /// should we wait for integration entries to complete - /// The steps. - /// + /// + /// The name. + /// + /// + /// The target directory. + /// + /// + /// The target layer container. + /// + /// + /// The is installable. + /// + /// + /// The is unique install. + /// + /// + /// The keywords. + /// + /// + /// should we wait for integration entries to complete + /// + /// + /// The steps. + /// + /// + /// public void SetDefaultValues( string name = null, string targetDirectory = null, @@ -506,23 +560,31 @@ public void SetDefaultValues( } /// - /// Sets the version. + /// Sets the version. /// - /// The version. - /// + /// + /// The version. + /// + /// + /// public void SetVersion(string version) { Version = version; } /// - /// Builds a dependency tree by attaching the CmfPackage objects to the parent's dependencies - /// Can run recursively and fetch packages from a DF repository. - /// Supports cycles + /// Builds a dependency tree by attaching the CmfPackage objects to the parent's dependencies Can run recursively and fetch packages from a DF + /// repository. Supports cycles /// - /// the address of the package repositories (currently only folders are supported) - /// should we run recursively - /// this CmfPackage for chaining, but the method itself is mutable + /// + /// the address of the package repositories (currently only folders are supported) + /// + /// + /// should we run recursively + /// + /// + /// this CmfPackage for chaining, but the method itself is mutable + /// public void LoadDependencies(IEnumerable repoUris, StatusContext ctx, bool recurse = false) { using var activity = ExecutionContext.ServiceProvider?.GetService()?.StartExtendedActivity("CmfPackage LoadDependencies"); @@ -586,36 +648,44 @@ public void LoadDependencies(IEnumerable repoUris, StatusContext ctx, bool } /// - /// Should Serialize Handler Version + /// Should Serialize Handler Version /// - /// returns false if handler version is 0 otherwise true + /// + /// returns false if handler version is 0 otherwise true + /// public bool ShouldSerializeHandlerVersion() { return HandlerVersion != 0; } /// - /// Shoulds the serialize DF package type. + /// Shoulds the serialize DF package type. /// - /// returns false if DF Package Type is null and Package Type is different then Generic, otherwise true + /// + /// returns false if DF Package Type is null and Package Type is different then Generic, otherwise true + /// public bool ShouldSerializeDFPackageType() { return DFPackageType != null && PackageType == PackageType.Generic; } /// - /// Shoulds the serialize Related Packages + /// Shoulds the serialize Related Packages /// - /// returns false if Related Packages is null or empty + /// + /// returns false if Related Packages is null or empty + /// public bool ShouldSerializeRelatedPackages() { return RelatedPackages.HasAny(); } /// - /// Should the Dependencies Directory be serialized + /// Should the Dependencies Directory be serialized /// - /// returns false if Dependencies Directory is null or empty + /// + /// returns false if Dependencies Directory is null or empty + /// public bool ShouldSerializeDependenciesDirectory() { return !string.IsNullOrWhiteSpace(DependenciesDirectory); @@ -624,15 +694,22 @@ public bool ShouldSerializeDependenciesDirectory() #region Static Methods /// - /// Loads the specified file. + /// Loads the specified file. /// - /// The file. - /// - /// the underlying file system - /// + /// + /// The file. + /// + /// + /// + /// + /// the underlying file system + /// + /// + /// /// /// - /// + /// + /// public static CmfPackage Load(IFileInfo file, bool setDefaultValues = false, IFileSystem fileSystem = null) { fileSystem ??= ExecutionContext.Instance.FileSystem; @@ -654,10 +731,12 @@ public static CmfPackage Load(IFileInfo file, bool setDefaultValues = false, IFi } /// - /// Load Method for an instantiated CmfPackage object + /// Load Method for an instantiated CmfPackage object /// - /// - /// + /// + /// + /// + /// public void Load(bool setDefaultValues = false) { if (!FileInfo.Exists) @@ -671,14 +750,13 @@ public void Load(bool setDefaultValues = false) Location = PackageLocation.Local; RelatedPackages?.Load(this); - } /// - /// Similar to Load, but without deserialization. - /// Only sets PackageId and Version + /// Similar to Load, but without deserialization. Only sets PackageId and Version /// - /// + /// + /// public void Peek() { if (!FileInfo.Exists) @@ -697,12 +775,17 @@ public void Peek() } /// - /// Gets the URI from repos. + /// Gets the URI from repos. /// - /// The repo directories. - /// - /// - /// + /// + /// The repo directories. + /// + /// + /// + /// + /// + /// + /// public static CmfPackage LoadFromRepo(IDirectoryInfo[] repoDirectories, string packageId, string version) { if (version is null) @@ -744,12 +827,20 @@ public static CmfPackage LoadFromRepo(IDirectoryInfo[] repoDirectories, string p } /// - /// Create a CmfPackage object from a DF package manifest + /// Create a CmfPackage object from a DF package manifest /// - /// the manifest content - /// should set default values - /// the underlying file system - /// a CmfPackage + /// + /// the manifest content + /// + /// + /// should set default values + /// + /// + /// the underlying file system + /// + /// + /// a CmfPackage + /// public static CmfPackage FromManifest(string manifest, bool setDefaultValues = false, IFileSystem fileSystem = null) { fileSystem ??= new FileSystem(); @@ -827,10 +918,10 @@ public static CmfPackage FromManifest(string manifest, bool setDefaultValues = f #region Utilities /// - /// Determines whether [is root package]. + /// Determines whether [is root package]. /// /// - /// true if [is root package] [the specified CMF package]; otherwise, false. + /// true if [is root package] [the specified CMF package]; otherwise, false. /// public bool IsRootPackage() { @@ -838,7 +929,7 @@ public bool IsRootPackage() } /// - /// Saves the CMF package. + /// Saves the CMF package. /// public void SaveCmfPackage() { @@ -861,20 +952,70 @@ public void SaveCmfPackage() } /// - /// Equalses the specified object. + /// Updates the version of a dependency in a given package. /// - /// The object. - /// + /// + /// The package to find and update the Version in the parent package Dependencies. + /// + public void UpdateDependency(CmfPackage updatedPackage) + { + Dependency dependency = null; + + if (dependency.IsNullOrEmpty() && Dependencies.HasAny()) + { + dependency = Dependencies.FirstOrDefault(dep => dep.Id.IgnoreCaseEquals(updatedPackage.PackageId)); + } + + if (dependency.IsNullOrEmpty() && TestPackages.HasAny()) + { + dependency = TestPackages.FirstOrDefault(dep => dep.Id.IgnoreCaseEquals(updatedPackage.PackageId)); + } + + if (!dependency.IsNullOrEmpty()) + { + dependency.Version = updatedPackage.Version; + SaveCmfPackage(); + } + } + + /// + /// Renames all folders with a specific version in their name with the new version. + /// + /// + /// Version number of the folders that need to be renamed. + /// + public void RenameVersionFolders(string oldVersion) + { + // Gets all Version folders inside current package + foreach (IDirectoryInfo folder in GetFileInfo().Directory.GetDirectories(oldVersion, SearchOption.AllDirectories).Where(dir => dir.FullName.Split(PackageId).Length - 1 == 1)) + { + string oldPath = folder.FullName; + string newPath = folder.FullName.Replace(oldVersion, Version); + folder.MoveTo(newPath); + Log.Information($"Will rename '{oldPath}' to '{newPath}'"); + } + } + + /// + /// Equalses the specified object. + /// + /// + /// The object. + /// + /// + /// public override bool Equals(object obj) { return Equals(obj as CmfPackage); } /// - /// Gets the hash code. + /// Gets the hash code. /// - /// - /// + /// + /// + /// + /// public override int GetHashCode() { throw new NotImplementedException(); diff --git a/core/Utilities/FileSystemUtilities.cs b/core/Utilities/FileSystemUtilities.cs index ef5128385..01fc70bfa 100644 --- a/core/Utilities/FileSystemUtilities.cs +++ b/core/Utilities/FileSystemUtilities.cs @@ -1,4 +1,8 @@ -using Newtonsoft.Json; +using Cmf.CLI.Core; +using Cmf.CLI.Core.Constants; +using Cmf.CLI.Core.Enums; +using Cmf.CLI.Core.Objects; +using Newtonsoft.Json; using System; using System.Collections.Generic; using System.IO; @@ -8,33 +12,47 @@ using System.Text.Json; using System.Xml; using System.Xml.Linq; -using Cmf.CLI.Core; -using Cmf.CLI.Core.Constants; -using Cmf.CLI.Core.Enums; -using Cmf.CLI.Core.Objects; namespace Cmf.CLI.Utilities { /// - /// /// public static class FileSystemUtilities { #region Public Methods /// - /// Gets the files to pack. + /// Gets the files to pack. /// - /// The content to pack. - /// Name of the source dir. - /// Name of the dest dir. - /// The content to ignore. - /// if set to true [copy sub dirs]. - /// if set to true [is copy dependencies]. - /// The files to pack. - /// the underlying file system - /// - /// $"Source directory does not exist or could not be found: {sourceDirName} + /// + /// The content to pack. + /// + /// + /// Name of the source dir. + /// + /// + /// Name of the dest dir. + /// + /// + /// The content to ignore. + /// + /// + /// if set to true [copy sub dirs]. + /// + /// + /// if set to true [is copy dependencies]. + /// + /// + /// The files to pack. + /// + /// + /// the underlying file system + /// + /// + /// + /// + /// $"Source directory does not exist or could not be found: {sourceDirName} + /// public static List GetFilesToPack(ContentToPack contentToPack, string sourceDirName, string destDirName, IFileSystem fileSystem, List contentToIgnore = null, bool copySubDirs = true, bool isCopyDependencies = false, List filesToPack = null) { if (filesToPack == null) @@ -103,16 +121,30 @@ public static List GetFilesToPack(ContentToPack contentToPack, strin } /// - /// Directories copy. + /// Directories copy. /// - /// Name of the source dir. - /// Name of the dest dir. - /// The exclusions. - /// if set to true [copy sub dirs]. - /// if set to true [is copy dependencies]. - /// the underlying file system - /// Source directory does not exist or could not be found: " - /// + sourceDirName + /// + /// Name of the source dir. + /// + /// + /// Name of the dest dir. + /// + /// + /// The exclusions. + /// + /// + /// if set to true [copy sub dirs]. + /// + /// + /// if set to true [is copy dependencies]. + /// + /// + /// the underlying file system + /// + /// + /// Source directory does not exist or could not be found: " + /// + sourceDirName + /// public static void CopyDirectory(string sourceDirName, string destDirName, IFileSystem fileSystem, List contentToIgnore = null, bool copySubDirs = true, bool isCopyDependencies = false) { // Get the subdirectories for the specified directory. @@ -164,10 +196,13 @@ public static void CopyDirectory(string sourceDirName, string destDirName, IFile } /// - /// Reads to string. + /// Reads to string. /// - /// The fi. - /// + /// + /// The fi. + /// + /// + /// public static string ReadToString(this IFileInfo fi) { //open for read operation @@ -186,10 +221,13 @@ public static string ReadToString(this IFileInfo fi) } /// - /// Reads to string list. + /// Reads to string list. /// - /// The fi. - /// + /// + /// The fi. + /// + /// + /// public static List ReadToStringList(this IFileInfo fi) { List result = new(); @@ -219,10 +257,13 @@ public static List ReadToStringList(this IFileInfo fi) } /// - /// Gets the package root. + /// Gets the package root. /// - /// - /// Cannot find package root. Are you in a valid package directory? + /// + /// + /// + /// Cannot find package root. Are you in a valid package directory? + /// public static IDirectoryInfo GetPackageRoot(IFileSystem fileSystem, string workingDir = null) { var cwd = fileSystem.DirectoryInfo.New(workingDir ?? fileSystem.Directory.GetCurrentDirectory()); @@ -236,11 +277,16 @@ public static IDirectoryInfo GetPackageRoot(IFileSystem fileSystem, string worki } /// - /// Gets the project root. + /// Gets the project root. /// - /// - /// Cannot find project root. Are you in a valid project directory? - /// Cannot find project root. Are you in a valid project directory? + /// + /// + /// + /// Cannot find project root. Are you in a valid project directory? + /// + /// + /// Cannot find project root. Are you in a valid project directory? + /// public static IDirectoryInfo GetProjectRoot(IFileSystem fileSystem, bool throwException = false) { var cwd = fileSystem.DirectoryInfo.New(fileSystem.Directory.GetCurrentDirectory()); @@ -259,13 +305,22 @@ public static IDirectoryInfo GetProjectRoot(IFileSystem fileSystem, bool throwEx } /// - /// Gets the package root of type package root. + /// Gets the package root of type package root. /// - /// The current working directory - /// Type of the package. - /// the underlying file system - /// - /// Cannot find project root. Are you in a valid project directory? + /// + /// The current working directory + /// + /// + /// Type of the package. + /// + /// + /// the underlying file system + /// + /// + /// + /// + /// Cannot find project root. Are you in a valid project directory? + /// public static IDirectoryInfo GetPackageRootByType(string directoryName, PackageType packageType, IFileSystem fileSystem) { var cwd = fileSystem.DirectoryInfo.New(directoryName); @@ -289,11 +344,37 @@ public static IDirectoryInfo GetPackageRootByType(string directoryName, PackageT } /// - /// Reads the environment configuration. + /// Retrieves all CmfPackages and orders them by PackageId, including the root CmfPackage at the beginning. + /// + /// + /// FileSystem + /// + /// + /// `CmfPackageCollection` object that contains all the CmfPackages loaded from subdirectories within the project root directory. The CmfPackages + /// are ordered by their PackageId, and the Root CmfPackage is added to the beginning of the collection. + /// + public static CmfPackageCollection GetAllPackages(IFileSystem fileSystem) + { + IDirectoryInfo projectRoot = GetProjectRoot(fileSystem); + CmfPackageCollection packages = new CmfPackageCollection(); + + // Load all CmfPackages and Order by PackageId and add Root CmfPackage to the beginning of the sequence + Log.Status("Loading all CmfPackages", (_) => packages.AddRange(projectRoot.LoadCmfPackagesFromSubDirectories().OrderBy(p => p.PackageId).Prepend(CmfPackage.Load(fileSystem.FileInfo.New(fileSystem.Path.Join(projectRoot.FullName, CoreConstants.CmfPackageFileName)))))); + + return packages; + } + + /// + /// Reads the environment configuration. /// - /// Name of the env configuration. - /// the underlying file system - /// + /// + /// Name of the env configuration. + /// + /// + /// the underlying file system + /// + /// + /// public static JsonDocument ReadEnvironmentConfig(string envConfigName, IFileSystem fileSystem) { if (!envConfigName.EndsWith(".json")) @@ -311,9 +392,10 @@ public static JsonDocument ReadEnvironmentConfig(string envConfigName, IFileSyst } /// - /// Reads the project configuration. + /// Reads the project configuration. /// - /// + /// + /// [Obsolete("Use ExecutionContext.Instance.ProjectConfig")] public static JsonDocument ReadProjectConfig(IFileSystem fileSystem) { @@ -325,10 +407,14 @@ public static JsonDocument ReadProjectConfig(IFileSystem fileSystem) } /// - /// Read DF repositories config from filesystem + /// Read DF repositories config from filesystem /// - /// The filesystem object - /// a RepositoriesConfig object + /// + /// The filesystem object + /// + /// + /// a RepositoriesConfig object + /// public static RepositoriesConfig ReadRepositoriesConfig(IFileSystem fileSystem) { var cwd = fileSystem.DirectoryInfo.New(fileSystem.Directory.GetCurrentDirectory()); @@ -360,10 +446,14 @@ public static RepositoriesConfig ReadRepositoriesConfig(IFileSystem fileSystem) } /// - /// Copies the contents of input to output. Doesn't close either stream. + /// Copies the contents of input to output. Doesn't close either stream. /// - /// The input. - /// The output. + /// + /// The input. + /// + /// + /// The output. + /// public static void CopyStream(Stream input, Stream output) { byte[] buffer = new byte[8 * 1024]; @@ -375,11 +465,17 @@ public static void CopyStream(Stream input, Stream output) } /// - /// Copies the install dependencies. + /// Copies the install dependencies. /// - /// The package output dir. - /// Type of the package. - /// the underlying file system + /// + /// The package output dir. + /// + /// + /// Type of the package. + /// + /// + /// the underlying file system + /// public static void CopyInstallDependenciesFiles(IDirectoryInfo packageOutputDir, PackageType packageType, IFileSystem fileSystem) { string sourceDirectory = fileSystem.Path.Join(AppDomain.CurrentDomain.BaseDirectory, CoreConstants.FolderInstallDependencies, packageType.ToString()); @@ -387,12 +483,19 @@ public static void CopyInstallDependenciesFiles(IDirectoryInfo packageOutputDir, } /// - /// Gets the output dir. + /// Gets the output dir. /// - /// The CMF package. - /// The output dir. - /// if set to true [force]. - /// + /// + /// The CMF package. + /// + /// + /// The output dir. + /// + /// + /// if set to true [force]. + /// + /// + /// public static IDirectoryInfo GetOutputDir(CmfPackage cmfPackage, IDirectoryInfo outputDir, bool force) { // Create OutputDir @@ -421,12 +524,19 @@ public static IDirectoryInfo GetOutputDir(CmfPackage cmfPackage, IDirectoryInfo } /// - /// Gets the package output dir. + /// Gets the package output dir. /// - /// The CMF package. - /// The package directory. - /// the underlying file system - /// + /// + /// The CMF package. + /// + /// + /// The package directory. + /// + /// + /// the underlying file system + /// + /// + /// public static IDirectoryInfo GetPackageOutputDir(CmfPackage cmfPackage, IDirectoryInfo packageDirectory, IFileSystem fileSystem) { // Clear and Create packageOutputDir @@ -444,10 +554,12 @@ public static IDirectoryInfo GetPackageOutputDir(CmfPackage cmfPackage, IDirecto } /// - /// Get Manifest File From package + /// Get Manifest File From package /// - /// - /// + /// + /// + /// + /// public static XDocument GetManifestFromPackage(string packageFile, IFileSystem fileSystem = null) { fileSystem ??= new FileSystem(); @@ -473,11 +585,14 @@ public static XDocument GetManifestFromPackage(string packageFile, IFileSystem f } /// - /// Get File Content From package + /// Get File Content From package /// - /// - /// - /// + /// + /// + /// + /// + /// + /// public static string GetFileContentFromPackage(string packageFile, string filename) { using (FileStream zipToOpen = new FileInfo(packageFile).OpenRead()) @@ -497,12 +612,19 @@ public static string GetFileContentFromPackage(string packageFile, string filena } /// - /// Create a Zip file at filePath from the directory content + /// Create a Zip file at filePath from the directory content /// - /// The file system implementation - /// The path of the resulting zip file - /// The directory to zip - /// + /// + /// The file system implementation + /// + /// + /// The path of the resulting zip file + /// + /// + /// The directory to zip + /// + /// + /// public static void ZipDirectory(IFileSystem fileSystem, string filePath, IDirectoryInfo directory) { using (var memoryStream = new MemoryStream()) @@ -531,10 +653,12 @@ public static void ZipDirectory(IFileSystem fileSystem, string filePath, IDirect } /// - /// Get Directory from a FileSystem dependending if Uri is UNC + /// Get Directory from a FileSystem dependending if Uri is UNC /// - /// - /// + /// + /// + /// + /// public static IDirectoryInfo GetDirectory(this Uri uri) { string path = uri.IsUnc ? uri.OriginalString : uri.LocalPath; @@ -543,20 +667,24 @@ public static IDirectoryInfo GetDirectory(this Uri uri) } /// - /// Get Directory Name from a FileSystem dependending if Uri is UNC + /// Get Directory Name from a FileSystem dependending if Uri is UNC /// - /// - /// + /// + /// + /// + /// public static string GetDirectoryName(this Uri uri) { return GetDirectory(uri).FullName; } /// - /// Get File from a FileSystem dependending if Uri is UNC + /// Get File from a FileSystem dependending if Uri is UNC /// - /// - /// + /// + /// + /// + /// public static IFileInfo GetFile(this Uri uri) { string path = uri.IsUnc ? uri.OriginalString : uri.LocalPath; @@ -565,24 +693,28 @@ public static IFileInfo GetFile(this Uri uri) } /// - /// Get Directory Name from a FileSystem dependending if Uri is UNC + /// Get Directory Name from a FileSystem dependending if Uri is UNC /// - /// - /// + /// + /// + /// + /// public static string GetFileName(this Uri uri) { return GetFile(uri).FullName; } - #endregion + #endregion Public Methods #region Private Methods /// - /// Get all files and folders from a directory + /// Get all files and folders from a directory /// - /// - /// + /// + /// + /// + /// private static IEnumerable AllFilesAndFolders(this IDirectoryInfo dir) { foreach (var f in dir.GetFiles()) @@ -595,6 +727,6 @@ private static IEnumerable AllFilesAndFolders(this IDirectoryIn } } - #endregion + #endregion Private Methods } -} +} \ No newline at end of file diff --git a/core/Utilities/GenericUtilities.cs b/core/Utilities/GenericUtilities.cs index d3690b07a..a79708be9 100644 --- a/core/Utilities/GenericUtilities.cs +++ b/core/Utilities/GenericUtilities.cs @@ -1,28 +1,35 @@ +using Cmf.CLI.Core; +using Cmf.CLI.Core.Enums; +using Cmf.CLI.Core.Objects; +using Spectre.Console; using System; using System.Collections.Generic; +using System.Data.SqlClient; +using System.Diagnostics; using System.IO; using System.IO.Abstractions; -using Cmf.CLI.Core; -using Cmf.CLI.Core.Objects; -using Spectre.Console; +using System.Linq; +using System.Security.Principal; namespace Cmf.CLI.Utilities { /// - /// /// public static class GenericUtilities { #region Public Methods /// - /// Will create a new version based on the old and new inputs + /// Will create a new version based on the old and new inputs /// - /// - /// - /// + /// + /// + /// + /// + /// + /// /// - /// the new version + /// the new version /// public static string RetrieveNewVersion(string currentVersion, string version, string buildNr) { @@ -39,13 +46,16 @@ public static string RetrieveNewVersion(string currentVersion, string version, s } /// - /// Will create a new version based on the old and new inputs + /// Will create a new version based on the old and new inputs /// - /// - /// - /// + /// + /// + /// + /// + /// + /// /// - /// the new version + /// the new version /// public static string RetrieveNewPresentationVersion(string currentVersion, string version, string buildNr) { @@ -61,14 +71,17 @@ public static string RetrieveNewPresentationVersion(string currentVersion, strin } /// - /// Get current version based on string, for - /// the format 1.0.0-1234 - /// where 1.0.0 will be the version - /// and the 1234 will be the build number + /// Get current version based on string, for the format 1.0.0-1234 where 1.0.0 will be the version and the 1234 will be the build number /// - /// Source information to be parsed - /// Version Number - /// Build Number + /// + /// Source information to be parsed + /// + /// + /// Version Number + /// + /// + /// Build Number + /// public static void GetCurrentPresentationVersion(string source, out string version, out string buildNr) { version = string.Empty; @@ -86,15 +99,27 @@ public static void GetCurrentPresentationVersion(string source, out string versi } /// - /// Get Package from Repository + /// Get Package from Repository /// - /// Target directory for the package - /// Repository Uri - /// - /// Package Identifier - /// Package Version - /// the underlying file system - /// + /// + /// Target directory for the package + /// + /// + /// Repository Uri + /// + /// + /// + /// + /// Package Identifier + /// + /// + /// Package Version + /// + /// + /// the underlying file system + /// + /// + /// public static bool GetPackageFromRepository(IDirectoryInfo outputDir, Uri repoUri, bool force, string packageId, string packageVersion, IFileSystem fileSystem) { bool packageFound = false; @@ -142,12 +167,19 @@ public static bool GetPackageFromRepository(IDirectoryInfo outputDir, Uri repoUr } /// - /// Flatten a tree + /// Flatten a tree /// - /// The top level tree items - /// a function that for each tree node returns its children - /// The tree node type - /// + /// + /// The top level tree items + /// + /// + /// a function that for each tree node returns its children + /// + /// + /// The tree node type + /// + /// + /// public static IEnumerable Flatten( this IEnumerable items, Func> getChildren) @@ -170,10 +202,12 @@ public static IEnumerable Flatten( } /// - /// Converts a JsonObject to an Uri + /// Converts a JsonObject to an Uri /// - /// - /// + /// + /// + /// + /// #nullable enable public static Uri? JsonObjectToUri(dynamic value) @@ -182,9 +216,11 @@ public static IEnumerable Flatten( } /// - /// Builds a tree representation of a CmfPackage dependency tree + /// Builds a tree representation of a CmfPackage dependency tree /// - /// the root package + /// + /// the root package + /// public static Tree BuildTree(CmfPackage pkg) { var tree = new Tree($"{pkg.PackageId}@{pkg.Version} [[{pkg.Location.ToString()}]]"); @@ -216,16 +252,397 @@ public static Tree BuildTree(CmfPackage pkg) } /// - /// /// - /// - /// + /// + /// + /// + /// public static bool IsEnvVarTruthy(string envVarName) { var enableConsoleExporter = System.Environment.GetEnvironmentVariable(envVarName); return enableConsoleExporter is "1" or "true" or "TRUE" or "True"; } + /// + /// Gets all packages of a specified type and allows the user to choose from multiple packages if more than one is found. + /// + /// + /// FileSystem + /// + /// + /// to select + /// + /// + /// Selected + /// + public static CmfPackage SelectPackage(IFileSystem fileSystem, PackageType packageType) + { + CmfPackageCollection packages = new CmfPackageCollection(); + + Log.Status($"Loading {packageType} CmfPackages", (_) => + { + IDirectoryInfo projectRoot = FileSystemUtilities.GetProjectRoot(fileSystem); + + // Get Packages of given Type + packages = projectRoot.LoadCmfPackagesFromSubDirectories(packageType); + }); + + int packageIndex = 0; + + // If there are more than 1 package + if (packages.Count > 1) + { + Log.Verbose($"Multiple {packageType} Packages found."); + Log.Verbose("Select the Package:"); + int index = 0; + foreach (CmfPackage package in packages) + { + Log.Verbose($"{index + 1} - {package.PackageId}"); + index++; + } + int option = ReadIntValueFromConsole(prompt: "Option:", minValue: 1, maxValue: packages.Count); + packageIndex = option - 1; + } + + return packages[packageIndex]; + } + + /// + /// Clears the console screen and removes all previous output. + /// + public static void FullConsoleClear() + { + Console.Clear(); + Console.WriteLine("\x1b[3J"); + Console.Clear(); + } + + /// + /// Reads a value of type T from the console until the value is valid (not null or of type T). + /// + /// + /// Message or question that will be displayed to the user when they are prompted to enter a value in the console. + /// + /// + /// Value of type `T` that is read from the console input after converting it from a string. If the input is null, the method recursively calls + /// itself until a non-null input is provided. If the input cannot be converted to type `T` due to a , an error message + /// is logged, and the method recursively calls itself in order to read a new value from the console. + /// + public static T? ReadValueFromConsole(string prompt, bool allowEmptyValueInput = false) + { + Console.Write($" {prompt} "); + string? input = Console.ReadLine(); + + try + { + // If empty value is received but not allowed + if (!allowEmptyValueInput && input?.Length == 0) + { + throw new CliException(); + } + else if (allowEmptyValueInput && input?.Length == 0) + { + return default; + } + + return (T)Convert.ChangeType(input, typeof(T)); + } + catch (Exception ex) when (ex is CliException || ex is FormatException) + { + Log.Error("Invalid input. Try again."); + return ReadValueFromConsole(prompt); // Recursively call until valid input + } + } + + /// + /// Reads an integer value from the console with optional minimum and maximum value constraints. + /// + /// + /// Message or question that will be displayed to the user when they are prompted to enter a value in the console. + /// + /// + /// Minimum value that the user input must be greater than or equal to in order to be considered valid. If a `minValue` is not specified, the user + /// input will not be validated in this scenario. + /// + /// + /// Maximum value that the user input must be less than or equal to in order to be considered valid. If a `maxValue` is not specified, the user + /// input will not be validated in this scenario. + /// + /// + /// An integer value that is read from the console input, after validating it against optional minimum and maximum values. + /// + public static int ReadIntValueFromConsole(string prompt, int? minValue = null, int? maxValue = null, bool allowEmptyValueInput = false) + { + string errorMessage = string.Empty; + int value = ReadValueFromConsole(prompt: prompt, allowEmptyValueInput: allowEmptyValueInput); + + if (allowEmptyValueInput && value == 0) + { + return value; + } + + bool hasMinAndMaxValue = !minValue.IsNullOrEmpty() && !maxValue.IsNullOrEmpty(); + bool hasMinValue = !minValue.IsNullOrEmpty() && maxValue.IsNullOrEmpty(); + bool hasMaxValue = minValue.IsNullOrEmpty() && !maxValue.IsNullOrEmpty(); + + // If we have a `minValue` and the `value` is greater than or equal to the `minValue` + bool isValueGraterThanOrEqualToMinValue = !minValue.IsNullOrEmpty() && value >= minValue; + // If we have a `maxValue` and the `value` is less than or equal to the `maxValue` + bool isValueLessThanOrEqualToMaxValue = !maxValue.IsNullOrEmpty() && value <= maxValue; + + if (hasMinAndMaxValue && isValueGraterThanOrEqualToMinValue && isValueLessThanOrEqualToMaxValue) + { + return value; + } + else if (hasMinValue && isValueGraterThanOrEqualToMinValue) + { + return value; + } + else if (hasMaxValue && isValueLessThanOrEqualToMaxValue) + { + return value; + } + else if (!hasMinAndMaxValue) + { + return value; + } + + // Build error messages + if (hasMinAndMaxValue) + { + errorMessage = $"The value must be between {minValue} and {maxValue}. Try again."; + } + else if (hasMinValue) + { + errorMessage = $"The value must be grater than or equal to {minValue}. Try again."; + } + else if (hasMaxValue) + { + errorMessage = $"The value must be less than or equal to {maxValue}. Try again."; + } + + Log.Error(errorMessage); + return ReadIntValueFromConsole(prompt, minValue, maxValue); + } + + /// + /// Reads a string value from the console with validation for allowed values and empty input. + /// + /// + /// Message or question that will be displayed to the user when they are prompted to enter a value in the console. + /// + /// + /// Determines whether an empty value input is allowed or not. If set to `true`, the user can input an empty string as a value. If set to `false`, + /// the user must provide a non-empty string. + /// + /// + /// A string value that is read from the console input. + /// + public static string? ReadStringValueFromConsole(string prompt, bool allowEmptyValueInput = false, params string[] allowedValues) + { + string errorMessage = string.Empty; + string? value = ReadValueFromConsole(prompt: prompt, allowEmptyValueInput: allowEmptyValueInput); + + bool isValueAllowed = allowedValues.Length > 0 && allowedValues.Contains(value, StringComparer.OrdinalIgnoreCase); + bool isEmptyAndAllowedEmptyValue = allowEmptyValueInput && string.IsNullOrEmpty(value); + + if ( + (allowedValues.Length == 0 && isEmptyAndAllowedEmptyValue) + || (allowedValues.Length > 0 && isEmptyAndAllowedEmptyValue) + || allowedValues.Length == 0 + || isValueAllowed + || (isValueAllowed && isEmptyAndAllowedEmptyValue) + ) + { + return value; + } + + if (!isValueAllowed) + { + errorMessage = "The value is not allowed. Try again."; + } + + Log.Error(errorMessage); + return ReadStringValueFromConsole(prompt, allowEmptyValueInput, allowedValues); + } + + /// + /// Determines the appropriate PowerShell executable based on the operating system platform. + /// + /// + /// Path to the PowerShell executable based on the operating system. + /// On Windows, it checks if `pwsh.exe` exists in the default path, and if not, it returns `powershell.exe`. + /// On Unix-like systems (Linux, macOS), it returns `/usr/bin/pwsh`. + /// + public static string GetPowerShellExecutable() + { + // Check if pwsh is installed on Windows + if (Environment.OSVersion.Platform == PlatformID.Win32NT) + { + if (File.Exists(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "PowerShell", "7", "pwsh.exe"))) + { + return "pwsh.exe"; + } + else + { + return "powershell.exe"; + } + } + else // Assume Unix-like systems (Linux, macOS) + { + return "/usr/bin/pwsh"; + } + } + + /// + /// Executes a PowerShell command with specified arguments and outputs the result. + /// + /// This function was created because, for some reason, the "CmdCommand" doesn't always work as expected. E.g.: when trying to open a tab in + /// WindowsTerminal, it opens 2 tabs, one of those not executing any command. + /// + /// + /// + /// PowerShell command to execute. + /// + /// + /// OPTIONAL. Arguments to add to the comment. + /// + public static void ExecutePowerShellCommand(string command, params string[] arguments) + { + try + { + // Create a ProcessStartInfo object to configure the process to start + ProcessStartInfo psi = new ProcessStartInfo + { + FileName = GetPowerShellExecutable(), + Arguments = $"-NoProfile -ExecutionPolicy unrestricted -Command \"{command} {string.Join(' ', arguments)}\";", + UseShellExecute = false, // Don't use the system shell to execute the command + RedirectStandardOutput = true // Redirect the standard output of the command + }; + + // Create and start the process + Process process = new Process + { + StartInfo = psi + }; + process.Start(); + + // Read the output of the command and display it + string output = process.StandardOutput.ReadToEnd(); + Console.WriteLine(output); + + // Wait for the process to exit + process.WaitForExit(); + } + catch (Exception ex) + { + Console.WriteLine("Error executing PowerShell command: " + ex.Message); + } + } + + /// + /// Checks if Windows Terminal is installed and throws an exception if it is not. + /// + public static void EnsureWindowsTerminalIsInstalled() + { + if (!File.Exists(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Microsoft", "WindowsApps", "wt.exe"))) + { + throw new CliException("Windows Terminal is not installed"); + } + } + + /// + /// Checks if the CLI is running as an administrator on a Windows platform and throws an exception if it is not. + /// + public static void EnsureIsRunningAsAdmin() + { + if (Environment.OSVersion.Platform == PlatformID.Win32NT && !new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator)) + { + throw new CliException("CLI not running as Administrator."); + } + } + + /// + /// Write DB Name to "CurrentDB" file + /// + /// + /// FileSystem + /// + /// + /// Name of the database to be set as the current database. + /// + public static void SetCurrentDb(IFileSystem fileSystem, string dbToSet) + { + File.WriteAllText(fileSystem.Path.Combine(FileSystemUtilities.GetProjectRoot(fileSystem).FullName, "LocalEnvironment", "DBDumps", "CurrentDB"), dbToSet); + } + + /// + /// Gets the DB Name from "CurrentDB" file + /// + /// + /// FileSystem + /// + /// + /// Current DB Name + /// + public static string GetCurrentDb(IFileSystem fileSystem) + { + string curentDbFilePath = fileSystem.Path.Combine(FileSystemUtilities.GetProjectRoot(fileSystem).FullName, "LocalEnvironment", "DBDumps", "CurrentDB"); + + if (!File.Exists(curentDbFilePath)) + { + SetCurrentDb(fileSystem, $"{ExecutionContext.Instance.ProjectConfig.EnvironmentName}"); + return $"{ExecutionContext.Instance.ProjectConfig.EnvironmentName}"; + } + else + { + return File.ReadAllText(curentDbFilePath); + } + } + + /// + /// Gets existing/available DB Backups from `DBDumps` folder + /// + /// + /// FileSystem + /// + /// + /// File paths to backup files + /// + public static string[] GetBdBackups(IFileSystem fileSystem) + { + string dbDumpsDirectoryPath = fileSystem.Path.Combine(FileSystemUtilities.GetProjectRoot(fileSystem).FullName, "LocalEnvironment", "DBDumps"); + + return Directory.GetFiles(dbDumpsDirectoryPath, "*.bak").Where(file => Path.GetFileNameWithoutExtension(file).StartsWith("Custom-")).ToArray(); + } + + /// + /// Executes a SQL command + /// + /// + /// String that contains information needed to establish a connection to a database. + /// + ///Data Source=*IP*,*PORT*;Initial Catalog=master;User ID=*USERNAME*;Password=*PASSWORD* + /// + /// + /// + /// SQL query or command that you want to execute + /// + public static void ExecuteSqlCommand(string connectionString, string sqlCommand) + { + // Creating a SqlConnection object to connect to the database + using SqlConnection connection = new SqlConnection(connectionString); + + // Opening the connection + connection.Open(); + + // Creating a SqlCommand object with the SQL command and SqlConnection + using SqlCommand command = new SqlCommand(sqlCommand, connection); + + // Executing the SQL command + command.ExecuteNonQuery(); + } + #endregion Public Methods #region Private Methods diff --git a/core/core.csproj b/core/core.csproj index 580dd6d1a..4bab59b17 100644 --- a/core/core.csproj +++ b/core/core.csproj @@ -25,6 +25,7 @@ +