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