diff --git a/abp_io/src/Volo.AbpWebSite.Web/publish-ignore.json b/abp_io/src/Volo.AbpWebSite.Web/publish-ignore.json
index 89e3176a69..c5e2fd7c51 100644
--- a/abp_io/src/Volo.AbpWebSite.Web/publish-ignore.json
+++ b/abp_io/src/Volo.AbpWebSite.Web/publish-ignore.json
@@ -3,6 +3,7 @@
"TemplateFiles/": {},
"Logs/": {},
"wwwroot/files/": {},
+ "wwwroot/downloads/": {},
"appsettings.json": {},
"web.config": {}
}
diff --git a/framework/Volo.Abp.sln b/framework/Volo.Abp.sln
index 7987604e98..41eb7f1aeb 100644
--- a/framework/Volo.Abp.sln
+++ b/framework/Volo.Abp.sln
@@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.28803.156
+# Visual Studio 15
+VisualStudioVersion = 15.0.28307.168
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}"
EndProject
@@ -218,14 +218,18 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp", "src\Volo.Abp\Vo
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.Authentication.JwtBearer", "src\Volo.Abp.AspNetCore.Authentication.JwtBearer\Volo.Abp.AspNetCore.Authentication.JwtBearer.csproj", "{46C6336C-A1D8-4858-98CE-6F4C698C5A77}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Cli", "src\Volo.Abp.Cli\Volo.Abp.Cli.csproj", "{69168816-4394-4DDA-BB6B-C21983D37F0B}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Cli", "src\Volo.Abp.Cli\Volo.Abp.Cli.csproj", "{69168816-4394-4DDA-BB6B-C21983D37F0B}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.FluentValidation", "src\Volo.Abp.FluentValidation\Volo.Abp.FluentValidation.csproj", "{43D5FE61-ECBF-4B16-AD95-0043E18EB93A}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.FluentValidation", "src\Volo.Abp.FluentValidation\Volo.Abp.FluentValidation.csproj", "{43D5FE61-ECBF-4B16-AD95-0043E18EB93A}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.FluentValidation.Tests", "test\Volo.Abp.FluentValidation.Tests\Volo.Abp.FluentValidation.Tests.csproj", "{E9E1714F-7ED2-4BD1-BA4A-BA06E398288A}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.FluentValidation.Tests", "test\Volo.Abp.FluentValidation.Tests\Volo.Abp.FluentValidation.Tests.csproj", "{E9E1714F-7ED2-4BD1-BA4A-BA06E398288A}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Cli.Tests", "test\Volo.Abp.Cli.Tests\Volo.Abp.Cli.Tests.csproj", "{92B70EFF-C1B1-4D1D-8BCE-D116908FC6FF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.EntityFrameworkCore.Sqlite", "src\Volo.Abp.EntityFrameworkCore.Sqlite\Volo.Abp.EntityFrameworkCore.Sqlite.csproj", "{58CF8957-5045-4F81-884D-72DF48F721CC}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Cli.Core", "src\Volo.Abp.Cli.Core\Volo.Abp.Cli.Core.csproj", "{3DA9923E-048E-4FE7-9748-3A0194F5D196}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -668,10 +672,18 @@ Global
{E9E1714F-7ED2-4BD1-BA4A-BA06E398288A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E9E1714F-7ED2-4BD1-BA4A-BA06E398288A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E9E1714F-7ED2-4BD1-BA4A-BA06E398288A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {92B70EFF-C1B1-4D1D-8BCE-D116908FC6FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {92B70EFF-C1B1-4D1D-8BCE-D116908FC6FF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {92B70EFF-C1B1-4D1D-8BCE-D116908FC6FF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {92B70EFF-C1B1-4D1D-8BCE-D116908FC6FF}.Release|Any CPU.Build.0 = Release|Any CPU
{58CF8957-5045-4F81-884D-72DF48F721CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{58CF8957-5045-4F81-884D-72DF48F721CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{58CF8957-5045-4F81-884D-72DF48F721CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{58CF8957-5045-4F81-884D-72DF48F721CC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3DA9923E-048E-4FE7-9748-3A0194F5D196}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3DA9923E-048E-4FE7-9748-3A0194F5D196}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3DA9923E-048E-4FE7-9748-3A0194F5D196}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3DA9923E-048E-4FE7-9748-3A0194F5D196}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -786,7 +798,9 @@ Global
{69168816-4394-4DDA-BB6B-C21983D37F0B} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{43D5FE61-ECBF-4B16-AD95-0043E18EB93A} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{E9E1714F-7ED2-4BD1-BA4A-BA06E398288A} = {447C8A77-E5F0-4538-8687-7383196D04EA}
+ {92B70EFF-C1B1-4D1D-8BCE-D116908FC6FF} = {447C8A77-E5F0-4538-8687-7383196D04EA}
{58CF8957-5045-4F81-884D-72DF48F721CC} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
+ {3DA9923E-048E-4FE7-9748-3A0194F5D196} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5}
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj b/framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj
new file mode 100644
index 0000000000..07a488123c
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj
@@ -0,0 +1,27 @@
+
+
+
+
+
+ netstandard2.0
+ $(AssetTargetFallback);portable-net45+win8+wp8+wpa81;
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs
new file mode 100644
index 0000000000..555fe14ca4
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs
@@ -0,0 +1,19 @@
+using System.Text;
+using Volo.Abp.Domain;
+using Volo.Abp.Json;
+using Volo.Abp.Modularity;
+
+namespace Volo.Abp.Cli
+{
+ [DependsOn(
+ typeof(AbpDddDomainModule),
+ typeof(AbpJsonModule)
+ )]
+ public class AbpCliCoreModule : AbpModule
+ {
+ public override void ConfigureServices(ServiceConfigurationContext context)
+ {
+ Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
+ }
+ }
+}
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Args/CommandLineArgs.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Args/CommandLineArgs.cs
new file mode 100644
index 0000000000..624c96afe5
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Args/CommandLineArgs.cs
@@ -0,0 +1,61 @@
+using System.Linq;
+using System.Text;
+using JetBrains.Annotations;
+
+namespace Volo.Abp.Cli.Args
+{
+ public class CommandLineArgs
+ {
+ [CanBeNull]
+ public string Command { get; }
+
+ [CanBeNull]
+ public string Target { get; }
+
+ [NotNull]
+ public CommandLineOptions Options { get; }
+
+ public CommandLineArgs([CanBeNull] string command = null, [CanBeNull] string target = null)
+ {
+ Command = command;
+ Target = target;
+ Options = new CommandLineOptions();
+ }
+
+ public static CommandLineArgs Empty()
+ {
+ return new CommandLineArgs();
+ }
+
+ public override string ToString()
+ {
+ var sb = new StringBuilder();
+
+ if (Command != null)
+ {
+ sb.AppendLine($"Command: {Command}");
+ }
+
+ if (Target != null)
+ {
+ sb.AppendLine($"Target: {Target}");
+ }
+
+ if (Options.Any())
+ {
+ sb.AppendLine("Options:");
+ foreach (var option in Options)
+ {
+ sb.AppendLine($" - {option.Key} = {option.Value}");
+ }
+ }
+
+ if (sb.Length <= 0)
+ {
+ sb.Append("");
+ }
+
+ return sb.ToString();
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Args/CommandLineArgumentParser.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Args/CommandLineArgumentParser.cs
new file mode 100644
index 0000000000..7125699546
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Args/CommandLineArgumentParser.cs
@@ -0,0 +1,104 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Volo.Abp.DependencyInjection;
+
+namespace Volo.Abp.Cli.Args
+{
+ public class CommandLineArgumentParser : ICommandLineArgumentParser, ITransientDependency
+ {
+ public CommandLineArgs Parse(string[] args)
+ {
+ if (args.IsNullOrEmpty())
+ {
+ return CommandLineArgs.Empty();
+ }
+
+ var argumentList = args.ToList();
+
+ //Get command
+
+ var command = argumentList[0];
+ argumentList.RemoveAt(0);
+
+ if (!argumentList.Any())
+ {
+ return new CommandLineArgs(command);
+ }
+
+ //Get target
+
+ var target = argumentList[0];
+ if (target.StartsWith("-"))
+ {
+ target = null;
+ }
+ else
+ {
+ argumentList.RemoveAt(0);
+ }
+
+ if (!argumentList.Any())
+ {
+ return new CommandLineArgs(command, target);
+ }
+
+ //Get options
+
+ var commandLineArgs = new CommandLineArgs(command, target);
+
+ while (argumentList.Any())
+ {
+ var optionName = ParseOptionName(argumentList[0]);
+ argumentList.RemoveAt(0);
+
+ if (!argumentList.Any())
+ {
+ commandLineArgs.Options[optionName] = null;
+ break;
+ }
+
+ if (IsOptionName(argumentList[0]))
+ {
+ commandLineArgs.Options[optionName] = null;
+ continue;
+ }
+
+ commandLineArgs.Options[optionName] = argumentList[0];
+ argumentList.RemoveAt(0);
+ }
+
+ return commandLineArgs;
+ }
+
+ private static bool IsOptionName(string argument)
+ {
+ return argument.StartsWith("-") || argument.StartsWith("--");
+ }
+
+ private static string ParseOptionName(string argument)
+ {
+ if (argument.StartsWith("--"))
+ {
+ if (argument.Length <= 2)
+ {
+ throw new ArgumentException("Should specify an option name after '--' prefix!");
+ }
+
+ return argument.RemovePreFix("--");
+ }
+
+ if (argument.StartsWith("-"))
+ {
+ if (argument.Length <= 1)
+ {
+ throw new ArgumentException("Should specify an option name after '--' prefix!");
+ }
+
+ return argument.RemovePreFix("-");
+ }
+
+ throw new ArgumentException("Option names should start with '-' or '--'.");
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Args/CommandLineOptions.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Args/CommandLineOptions.cs
new file mode 100644
index 0000000000..3e72d6e434
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Args/CommandLineOptions.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Collections.Generic;
+using JetBrains.Annotations;
+
+namespace Volo.Abp.Cli.Args
+{
+ public class CommandLineOptions : Dictionary
+ {
+ [CanBeNull]
+ public string GetOrNull([NotNull] string name, params string[] alternativeNames)
+ {
+ Check.NotNullOrWhiteSpace(name, nameof(name));
+
+ var value = this.GetOrDefault(name);
+ if (!value.IsNullOrWhiteSpace())
+ {
+ return value;
+ }
+
+ if (!alternativeNames.IsNullOrEmpty())
+ {
+ foreach (var alternativeName in alternativeNames)
+ {
+ value = this.GetOrDefault(alternativeName);
+ if (!value.IsNullOrWhiteSpace())
+ {
+ return value;
+ }
+ }
+ }
+
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Args/ICommandLineArgumentParser.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Args/ICommandLineArgumentParser.cs
new file mode 100644
index 0000000000..c467541210
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Args/ICommandLineArgumentParser.cs
@@ -0,0 +1,7 @@
+namespace Volo.Abp.Cli.Args
+{
+ public interface ICommandLineArgumentParser
+ {
+ CommandLineArgs Parse(string[] args);
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliPaths.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliPaths.cs
new file mode 100644
index 0000000000..b0296671b3
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliPaths.cs
@@ -0,0 +1,13 @@
+using System;
+using System.IO;
+
+namespace Volo.Abp.Cli
+{
+ public static class CliPaths
+ {
+ public static string TemplateCachePath => Path.Combine(AbpRootPath, "templates");
+ public static string CliLogPath => Path.Combine(AbpRootPath, "cli", "logs");
+
+ private static readonly string AbpRootPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".abp");
+ }
+}
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliService.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliService.cs
new file mode 100644
index 0000000000..502ab294e6
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliService.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Reflection;
+using Microsoft.Extensions.DependencyInjection;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using Volo.Abp.Cli.Args;
+using Volo.Abp.Cli.Commands;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.ProjectBuilding;
+
+namespace Volo.Abp.Cli
+{
+ public class CliService : ITransientDependency
+ {
+ public static string Version => typeof(AbpCliCoreModule).Assembly.GetFileVersion();
+
+ public ILogger Logger { get; set; }
+
+ protected ICommandLineArgumentParser CommandLineArgumentParser { get; }
+ protected ICommandSelector CommandSelector { get; }
+ public IHybridServiceScopeFactory ServiceScopeFactory { get; }
+
+ public CliService(
+ ICommandLineArgumentParser commandLineArgumentParser,
+ ICommandSelector commandSelector,
+ IHybridServiceScopeFactory serviceScopeFactory)
+ {
+ CommandLineArgumentParser = commandLineArgumentParser;
+ CommandSelector = commandSelector;
+ ServiceScopeFactory = serviceScopeFactory;
+
+ Logger = NullLogger.Instance;
+ }
+
+ public async Task RunAsync(string[] args)
+ {
+ Logger.LogInformation("ABP CLI (abp.io)");
+ Logger.LogInformation("Version: " + Version);
+
+ var commandLineArgs = CommandLineArgumentParser.Parse(args);
+ var commandType = CommandSelector.Select(commandLineArgs);
+
+ using (var scope = ServiceScopeFactory.CreateScope())
+ {
+ var command = (IConsoleCommand)scope.ServiceProvider.GetRequiredService(commandType);
+ await command.ExecuteAsync(commandLineArgs);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CommandSelector.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CommandSelector.cs
new file mode 100644
index 0000000000..32268540a7
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CommandSelector.cs
@@ -0,0 +1,22 @@
+using System;
+using Volo.Abp.Cli.Args;
+using Volo.Abp.DependencyInjection;
+
+namespace Volo.Abp.Cli.Commands
+{
+ public class CommandSelector : ICommandSelector, ITransientDependency
+ {
+ public Type Select(CommandLineArgs commandLineArgs)
+ {
+ //TODO: Create options to define commands
+ //TODO: Get from dependency injection instead of new?
+
+ if (commandLineArgs.Command == "new")
+ {
+ return typeof(NewProjectCommand);
+ }
+
+ return typeof(MainHelpCommand);
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ICommandSelector.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ICommandSelector.cs
new file mode 100644
index 0000000000..507f8ab452
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/ICommandSelector.cs
@@ -0,0 +1,10 @@
+using System;
+using Volo.Abp.Cli.Args;
+
+namespace Volo.Abp.Cli.Commands
+{
+ public interface ICommandSelector
+ {
+ Type Select(CommandLineArgs commandLineArgs);
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/IConsoleCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/IConsoleCommand.cs
new file mode 100644
index 0000000000..2b1129aab2
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/IConsoleCommand.cs
@@ -0,0 +1,10 @@
+using System.Threading.Tasks;
+using Volo.Abp.Cli.Args;
+
+namespace Volo.Abp.Cli.Commands
+{
+ public interface IConsoleCommand
+ {
+ Task ExecuteAsync(CommandLineArgs commandLineArgs);
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/MainHelpCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/MainHelpCommand.cs
new file mode 100644
index 0000000000..96f7132934
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/MainHelpCommand.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Threading.Tasks;
+using Volo.Abp.Cli.Args;
+using Volo.Abp.DependencyInjection;
+
+namespace Volo.Abp.Cli.Commands
+{
+ public class MainHelpCommand : IConsoleCommand, ITransientDependency
+ {
+ public Task ExecuteAsync(CommandLineArgs commandLineArgs)
+ {
+ Console.WriteLine("*********** ABP CLI ****************");
+ Console.WriteLine();
+ Console.WriteLine("Usage:");
+ Console.WriteLine();
+ Console.WriteLine(" abp [options]");
+ return Task.CompletedTask;
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewProjectCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewProjectCommand.cs
new file mode 100644
index 0000000000..a1e69d14ca
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewProjectCommand.cs
@@ -0,0 +1,91 @@
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using Ionic.Zip;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using Volo.Abp.Cli.Args;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.ProjectBuilding;
+using Volo.Abp.ProjectBuilding.Building;
+
+namespace Volo.Abp.Cli.Commands
+{
+ public class NewProjectCommand : IConsoleCommand, ITransientDependency
+ {
+ public ILogger Logger { get; set; }
+
+ protected ProjectBuilder ProjectBuilder { get; }
+
+ public NewProjectCommand(ProjectBuilder projectBuilder)
+ {
+ ProjectBuilder = projectBuilder;
+
+ Logger = NullLogger.Instance;
+ }
+
+ public async Task ExecuteAsync(CommandLineArgs commandLineArgs)
+ {
+ if (commandLineArgs.Target == null)
+ {
+ Logger.LogWarning("Project name is missing.");
+ Logger.LogWarning("Usage:");
+ Logger.LogWarning(" abp new [-t ]");
+ Logger.LogWarning("Examples:");
+ Logger.LogWarning(" abp new Acme.BookStore");
+ Logger.LogWarning(" abp new Acme.BookStore mvc");
+ return;
+ }
+
+ Logger.LogInformation("Creating a new project...");
+ Logger.LogInformation("Project name: " + commandLineArgs.Target);
+
+ var result = await ProjectBuilder.BuildAsync(
+ new ProjectBuildArgs(
+ SolutionName.Parse(commandLineArgs.Target),
+ GetDatabaseProviderOrNull(commandLineArgs),
+ commandLineArgs.Options.GetOrNull(Options.Template.Short, Options.Template.Long)
+ )
+ );
+
+ using (var templateFileStream = new MemoryStream(result.ZipContent))
+ {
+ using (var templateZipFile = ZipFile.Read(templateFileStream))
+ {
+ templateZipFile.ExtractAll(Directory.GetCurrentDirectory(), ExtractExistingFileAction.Throw);
+ }
+ }
+
+ Logger.LogInformation($"Successfully created the project '{commandLineArgs.Target}'");
+ }
+
+ protected virtual DatabaseProvider GetDatabaseProviderOrNull(CommandLineArgs commandLineArgs)
+ {
+ var optionValue = commandLineArgs.Options.GetOrNull(Options.DatabaseProvider.Short, Options.DatabaseProvider.Long);
+ switch (optionValue)
+ {
+ case "ef":
+ return DatabaseProvider.EntityFrameworkCore;
+ case "mongodb":
+ return DatabaseProvider.MongoDb;
+ default:
+ return DatabaseProvider.NotSpecified;
+ }
+ }
+
+ public static class Options
+ {
+ public static class Template
+ {
+ public const string Short = "t";
+ public const string Long = "template";
+ }
+
+ public static class DatabaseProvider
+ {
+ public const string Short = "d";
+ public const string Long = "databaseProvider";
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/AbpIoTemplateStore.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/AbpIoTemplateStore.cs
new file mode 100644
index 0000000000..6f3956f604
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/AbpIoTemplateStore.cs
@@ -0,0 +1,45 @@
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using Volo.Abp.Cli;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.IO;
+
+namespace Volo.Abp.ProjectBuilding
+{
+ public class AbpIoTemplateStore : ITemplateStore, ITransientDependency
+ {
+ public ILogger Logger { get; set; }
+
+ public AbpIoTemplateStore()
+ {
+ Logger = NullLogger.Instance;
+ }
+
+ public async Task GetAsync(string templateName, string version)
+ {
+ var localCacheFolder = Path.Combine(CliPaths.TemplateCachePath, version);
+ DirectoryHelper.CreateIfNotExists(localCacheFolder);
+
+ var localCacheFile = Path.Combine(localCacheFolder, templateName + ".zip");
+ if (File.Exists(localCacheFile))
+ {
+ Logger.LogInformation("Using cached template: " + templateName + ", version: " + version);
+ return new TemplateFile(File.ReadAllBytes(localCacheFile));
+ }
+
+ Logger.LogInformation("Downloading template: " + templateName + ", version: " + version);
+
+ using (var client = new System.Net.Http.HttpClient())
+ {
+ client.Timeout = TimeSpan.FromMinutes(5);
+ var downloadUrl = "https://abp.io/downloads/templates/" + version + "/" + templateName + ".zip";
+ var fileContents = await client.GetByteArrayAsync(downloadUrl);
+ File.WriteAllBytes(localCacheFile, fileContents);
+ return new TemplateFile(fileContents);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/DatabaseProvider.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/DatabaseProvider.cs
new file mode 100644
index 0000000000..0352bed89c
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/DatabaseProvider.cs
@@ -0,0 +1,9 @@
+namespace Volo.Abp.ProjectBuilding.Building
+{
+ public enum DatabaseProvider
+ {
+ NotSpecified = 0,
+ EntityFrameworkCore = 1,
+ MongoDb = 2
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/GithubRepositoryInfo.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/GithubRepositoryInfo.cs
new file mode 100644
index 0000000000..7a7277f8a4
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/GithubRepositoryInfo.cs
@@ -0,0 +1,25 @@
+using System;
+
+namespace Volo.Abp.ProjectBuilding.Building
+{
+ public class GithubRepositoryInfo
+ {
+ public string RepositoryNameWithOrganization { get; }
+
+ public string RepositoryName { get; }
+
+ public string AccessToken { get; }
+
+ public GithubRepositoryInfo(string repositoryNameWithOrganization, string accessToken)
+ {
+ if (!repositoryNameWithOrganization.Contains("/"))
+ {
+ throw new ApplicationException($"{nameof(repositoryNameWithOrganization)} '{repositoryNameWithOrganization}' is not valid! It should be formatted as 'organization-name/repository-name'." );
+ }
+
+ RepositoryNameWithOrganization = repositoryNameWithOrganization;
+ RepositoryName = repositoryNameWithOrganization.Split('/')[1];
+ AccessToken = accessToken;
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/ProjectBuildContext.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/ProjectBuildContext.cs
new file mode 100644
index 0000000000..7e16a377c6
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/ProjectBuildContext.cs
@@ -0,0 +1,37 @@
+using JetBrains.Annotations;
+using Volo.Abp.ProjectBuilding.Files;
+
+namespace Volo.Abp.ProjectBuilding.Building
+{
+ public class ProjectBuildContext
+ {
+ [NotNull]
+ public TemplateFile TemplateFile { get; }
+
+ [NotNull]
+ public string Version { get; }
+
+ [NotNull]
+ public ProjectBuildArgs BuildArgs { get; }
+
+ [NotNull]
+ public TemplateInfo Template { get; }
+
+ public FileEntryList Files { get; set; }
+
+ public ProjectResult Result { get; set; }
+
+ public ProjectBuildContext([NotNull] TemplateInfo template,
+ [NotNull] TemplateFile templateFile,
+ [NotNull] ProjectBuildArgs buildArgs,
+ [NotNull] string version)
+ {
+ Template = Check.NotNull(template, nameof(template));
+ TemplateFile = Check.NotNull(templateFile, nameof(templateFile));
+ BuildArgs = Check.NotNull(buildArgs, nameof(buildArgs));
+ Version = Check.NotNullOrWhiteSpace(version, nameof(version));
+
+ Result = new ProjectResult();
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/ProjectBuildContextExtensions.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/ProjectBuildContextExtensions.cs
new file mode 100644
index 0000000000..b4623e6744
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/ProjectBuildContextExtensions.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Linq;
+using Volo.Abp.ProjectBuilding.Files;
+
+namespace Volo.Abp.ProjectBuilding.Building
+{
+ public static class ProjectBuildContextExtensions
+ {
+ public static FileEntry GetFile(this ProjectBuildContext context, string filePath)
+ {
+ var file = context.Files.FirstOrDefault(f => f.Name == filePath);
+ if (file == null)
+ {
+ throw new ApplicationException("Could not find file: " + filePath);
+ }
+
+ return file;
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/ProjectBuildPipeline.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/ProjectBuildPipeline.cs
new file mode 100644
index 0000000000..c4afe2aab6
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/ProjectBuildPipeline.cs
@@ -0,0 +1,22 @@
+using System.Collections.Generic;
+
+namespace Volo.Abp.ProjectBuilding.Building
+{
+ public class ProjectBuildPipeline
+ {
+ public List Steps { get; }
+
+ public ProjectBuildPipeline()
+ {
+ Steps = new List();
+ }
+
+ public void Execute(ProjectBuildContext context)
+ {
+ foreach (var step in Steps)
+ {
+ step.Execute(context);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/ProjectBuildPipelineBuilder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/ProjectBuildPipelineBuilder.cs
new file mode 100644
index 0000000000..35ea1bfe39
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/ProjectBuildPipelineBuilder.cs
@@ -0,0 +1,21 @@
+using Volo.Abp.ProjectBuilding.Building.Steps;
+
+namespace Volo.Abp.ProjectBuilding.Building
+{
+ public static class ProjectBuildPipelineBuilder
+ {
+ public static ProjectBuildPipeline Build(ProjectBuildContext context)
+ {
+ var pipeline = new ProjectBuildPipeline();
+
+ pipeline.Steps.Add(new FileEntryListReadStep());
+ pipeline.Steps.AddRange(context.Template.GetCustomSteps(context));
+ pipeline.Steps.Add(new NugetReferenceReplaceStep());
+ pipeline.Steps.Add(new TemplateCodeDeleteStep());
+ pipeline.Steps.Add(new SolutionRenameStep());
+ pipeline.Steps.Add(new CreateProjectResultZipStep());
+
+ return pipeline;
+ }
+ }
+}
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/ProjectBuildPipelineStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/ProjectBuildPipelineStep.cs
new file mode 100644
index 0000000000..3080224ed2
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/ProjectBuildPipelineStep.cs
@@ -0,0 +1,7 @@
+namespace Volo.Abp.ProjectBuilding.Building
+{
+ public abstract class ProjectBuildPipelineStep
+ {
+ public abstract void Execute(ProjectBuildContext context);
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/ProjectResult.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/ProjectResult.cs
new file mode 100644
index 0000000000..19230adfb5
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/ProjectResult.cs
@@ -0,0 +1,7 @@
+namespace Volo.Abp.ProjectBuilding.Building
+{
+ public class ProjectResult
+ {
+ public byte[] ZipContent { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/Steps/CreateProjectResultZipStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/Steps/CreateProjectResultZipStep.cs
new file mode 100644
index 0000000000..a2b468effd
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/Steps/CreateProjectResultZipStep.cs
@@ -0,0 +1,23 @@
+using Ionic.Zip;
+using Volo.Abp.ProjectBuilding.Files;
+using Volo.Abp.ProjectBuilding.Zipping;
+
+namespace Volo.Abp.ProjectBuilding.Building.Steps
+{
+ public class CreateProjectResultZipStep : ProjectBuildPipelineStep
+ {
+ public override void Execute(ProjectBuildContext context)
+ {
+ context.Result.ZipContent = CreateZipFileFromEntries(context.Files);
+ }
+
+ private static byte[] CreateZipFileFromEntries(FileEntryList entries)
+ {
+ using (var resultZipFile = new ZipFile())
+ {
+ entries.CopyToZipFile(resultZipFile);
+ return resultZipFile.GetBytes();
+ }
+ }
+ }
+}
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/Steps/FileEntryListReadStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/Steps/FileEntryListReadStep.cs
new file mode 100644
index 0000000000..22f1d8840f
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/Steps/FileEntryListReadStep.cs
@@ -0,0 +1,26 @@
+using System.IO;
+using Ionic.Zip;
+using Volo.Abp.ProjectBuilding.Files;
+using Volo.Abp.ProjectBuilding.Zipping;
+
+namespace Volo.Abp.ProjectBuilding.Building.Steps
+{
+ public class FileEntryListReadStep : ProjectBuildPipelineStep
+ {
+ public override void Execute(ProjectBuildContext context)
+ {
+ context.Files = GetEntriesFromZipFile(context.TemplateFile.FileBytes);
+ }
+
+ private static FileEntryList GetEntriesFromZipFile(byte[] fileBytes)
+ {
+ using (var memoryStream = new MemoryStream(fileBytes))
+ {
+ using (var templateZipFile = ZipFile.Read(memoryStream))
+ {
+ return templateZipFile.ToFileEntryList();
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/Steps/NugetReferenceReplaceStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/Steps/NugetReferenceReplaceStep.cs
new file mode 100644
index 0000000000..3d41c682dc
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/Steps/NugetReferenceReplaceStep.cs
@@ -0,0 +1,113 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using HtmlAgilityPack;
+using Volo.Abp.ProjectBuilding.Files;
+
+namespace Volo.Abp.ProjectBuilding.Building.Steps
+{
+ public class NugetReferenceReplaceStep : ProjectBuildPipelineStep
+ {
+ public override void Execute(ProjectBuildContext context)
+ {
+ new NugetReferenceReplacer(
+ context.Files,
+ "MyCompanyName",
+ "MyProjectName",
+ context.Version
+ ).Run();
+ }
+
+ private class NugetReferenceReplacer
+ {
+ private readonly List _entries;
+ private readonly string _companyNamePlaceHolder;
+ private readonly string _projectNamePlaceHolder;
+ private readonly string _latestNugetPackageVersion;
+
+ public NugetReferenceReplacer(List entries, string companyNamePlaceHolder, string projectNamePlaceHolder, string latestNugetPackageVersion)
+ {
+ _entries = entries;
+ _companyNamePlaceHolder = companyNamePlaceHolder;
+ _projectNamePlaceHolder = projectNamePlaceHolder;
+ _latestNugetPackageVersion = latestNugetPackageVersion;
+ }
+
+ public void Run()
+ {
+ foreach (var fileEntry in _entries)
+ {
+ if (fileEntry.Name.EndsWith(".csproj"))
+ {
+ fileEntry.SetContent(ProcessFileContent(fileEntry.Content));
+ }
+ }
+ }
+
+ private string ProcessFileContent(string content)
+ {
+ Check.NotNull(content, nameof(content));
+
+ var doc = new HtmlDocument();
+
+ doc.Load(GenerateStreamFromString(content));
+
+ var nodes = doc.DocumentNode.SelectNodes("//projectreference[@include]");
+
+ if (nodes == null)
+ {
+ return content;
+ }
+
+ return ProcessReferenceNodes(nodes, content);
+ }
+
+ private string ProcessReferenceNodes(HtmlNodeCollection nodes, string content)
+ {
+ Check.NotNull(nodes, nameof(nodes));
+ Check.NotNull(content, nameof(content));
+
+ foreach (var node in nodes)
+ {
+ var valueAttr = node.Attributes.FirstOrDefault(a => a.Name.ToLower() == "include");
+
+ // ReSharper disable once PossibleNullReferenceException : Can not be null because nodes are selected with include attribute filter in previous method
+ if (valueAttr.Value.Contains($"{_companyNamePlaceHolder}.{_projectNamePlaceHolder}"))
+ {
+ continue;
+ }
+
+ var newValue = ConvertToNugetReference(valueAttr.Value);
+
+ var oldLine = $" file.Name.StartsWith(_projectFolderPath));
+ var solutionFile = context.GetFile(_solutionFilePath);
+ solutionFile.NormalizeLineEndings();
+ solutionFile.SetLines(RemoveProject(solutionFile.GetLines().ToList()));
+ }
+
+ private List RemoveProject(List solutionFileLines)
+ {
+ var projectKey = FindProjectKey(solutionFileLines);
+ var newSolutionFileLines = new List();
+ var firstOccurence = true;
+
+ for (var i = 0; i < solutionFileLines.Count; ++i)
+ {
+ if (solutionFileLines[i].Contains(projectKey))
+ {
+ if (firstOccurence)
+ {
+ firstOccurence = false;
+ ++i; //Skip "EndProject" line too.
+ }
+
+ continue;
+ }
+
+ newSolutionFileLines.Add(solutionFileLines[i]);
+ }
+
+ return newSolutionFileLines;
+ }
+
+ private string FindProjectKey(List solutionFileLines)
+ {
+ foreach (var solutionFileLine in solutionFileLines)
+ {
+ if (solutionFileLine.Contains(_projectName))
+ {
+ var curlyBracketStartIndex = solutionFileLine.LastIndexOf("{", StringComparison.OrdinalIgnoreCase);
+ var curlyBracketEndIndex = solutionFileLine.LastIndexOf("}", StringComparison.OrdinalIgnoreCase);
+ return solutionFileLine.Substring(curlyBracketStartIndex + 1, curlyBracketEndIndex - curlyBracketStartIndex - 1);
+ }
+ }
+
+ throw new ApplicationException($"The solution file '{_solutionFilePath}' does not contain a project '{_projectName}'");
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/Steps/SolutionRenameStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/Steps/SolutionRenameStep.cs
new file mode 100644
index 0000000000..20f2810633
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/Steps/SolutionRenameStep.cs
@@ -0,0 +1,116 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Volo.Abp.ProjectBuilding.Files;
+
+namespace Volo.Abp.ProjectBuilding.Building.Steps
+{
+ public class SolutionRenameStep : ProjectBuildPipelineStep
+ {
+ public override void Execute(ProjectBuildContext context)
+ {
+ new SolutionRenamer(
+ context.Files,
+ "MyCompanyName",
+ "MyProjectName",
+ context.BuildArgs.SolutionName.CompanyName,
+ context.BuildArgs.SolutionName.ProjectName
+ ).Run();
+ }
+
+ private class SolutionRenamer
+ {
+ private readonly List _entries;
+ private readonly string _companyNamePlaceHolder;
+ private readonly string _projectNamePlaceHolder;
+
+ private readonly string _companyName;
+ private readonly string _projectName;
+
+ public SolutionRenamer(List entries, string companyNamePlaceHolder, string projectNamePlaceHolder, string companyName, string projectName)
+ {
+ if (string.IsNullOrWhiteSpace(companyName))
+ {
+ companyName = null;
+ }
+
+ if (companyNamePlaceHolder == null && companyName != null)
+ {
+ throw new UserFriendlyException($"Can not set {nameof(companyName)} if {nameof(companyNamePlaceHolder)} is null.");
+ }
+
+ _entries = entries;
+
+ _companyNamePlaceHolder = companyNamePlaceHolder;
+ _projectNamePlaceHolder = projectNamePlaceHolder ?? throw new ArgumentNullException(nameof(projectNamePlaceHolder));
+
+ _companyName = companyName;
+ _projectName = projectName ?? throw new ArgumentNullException(nameof(projectName));
+ }
+
+ public void Run()
+ {
+ if (_companyNamePlaceHolder != null && _companyName != null)
+ {
+ RenameDirectoryRecursively(_companyNamePlaceHolder, _companyName);
+ RenameAllFiles(_companyNamePlaceHolder, _companyName);
+ ReplaceContent(_companyNamePlaceHolder, _companyName);
+ }
+ else if (_companyNamePlaceHolder != null)
+ {
+ RenameDirectoryRecursively(_companyNamePlaceHolder + "." + _projectNamePlaceHolder, _projectNamePlaceHolder);
+ RenameAllFiles(_companyNamePlaceHolder + "." + _projectNamePlaceHolder, _projectNamePlaceHolder);
+ ReplaceContent(_companyNamePlaceHolder + "." + _projectNamePlaceHolder, _projectNamePlaceHolder);
+ }
+
+ RenameDirectoryRecursively(_projectNamePlaceHolder, _projectName);
+ RenameAllFiles(_projectNamePlaceHolder, _projectName);
+ ReplaceContent(_projectNamePlaceHolder, _projectName);
+ }
+
+ private void RenameDirectoryRecursively(string placeHolder, string name)
+ {
+ foreach (var entry in _entries.Where(e => e.IsDirectory))
+ {
+ if (entry.Name.Contains(placeHolder))
+ {
+ entry.SetName(entry.Name.Replace(placeHolder, name));
+ }
+ }
+ }
+
+ private void RenameAllFiles(string placeHolder, string name)
+ {
+ foreach (var entry in _entries.Where(e => !e.IsDirectory))
+ {
+ if (entry.Name.Contains(placeHolder))
+ {
+ entry.SetName(entry.Name.Replace(placeHolder, name));
+ }
+ }
+ }
+
+ private void ReplaceContent(string placeHolder, string name)
+ {
+ foreach (var entry in _entries.Where(e => !e.IsDirectory))
+ {
+ if (entry.Content.Length < placeHolder.Length)
+ {
+ continue;
+ }
+
+ if (entry.IsBinaryFile)
+ {
+ continue;
+ }
+
+ var newContent = entry.Content.Replace(placeHolder, name);
+ if (newContent != entry.Content)
+ {
+ entry.SetContent(newContent);
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/Steps/SwitchEntityFrameworkCoreToMongoDbStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/Steps/SwitchEntityFrameworkCoreToMongoDbStep.cs
new file mode 100644
index 0000000000..6b77808f55
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/Steps/SwitchEntityFrameworkCoreToMongoDbStep.cs
@@ -0,0 +1,80 @@
+using System;
+using Volo.Abp.ProjectBuilding.Files;
+
+namespace Volo.Abp.ProjectBuilding.Building.Steps
+{
+ public class SwitchEntityFrameworkCoreToMongoDbStep : ProjectBuildPipelineStep
+ {
+ public override void Execute(ProjectBuildContext context)
+ {
+ ChangeProjectReference(context);
+ ChangeWebModuleUsage(context);
+ ChangeConnectionString(context);
+ }
+
+ private void ChangeProjectReference(ProjectBuildContext context)
+ {
+ var file = context.GetFile("/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj");
+
+ file.NormalizeLineEndings();
+
+ var lines = file.GetLines();
+ for (var i = 0; i < lines.Length; i++)
+ {
+ if (lines[i].Contains("ProjectReference") && lines[i].Contains("MyCompanyName.MyProjectName.EntityFrameworkCore"))
+ {
+ lines[i] = lines[i].Replace("EntityFrameworkCore", "MongoDB");
+ file.SetLines(lines);
+ return;
+ }
+ }
+
+ throw new ApplicationException("Could not find the EntityFrameworkCore reference in the MyCompanyName.MyProjectName.Web.csproj!");
+ }
+
+ private void ChangeWebModuleUsage(ProjectBuildContext context)
+ {
+ var file = context.GetFile("/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebModule.cs");
+
+ file.NormalizeLineEndings();
+
+ file.RemoveTemplateCodeIfNot("EntityFrameworkCore");
+
+ var lines = file.GetLines();
+
+ for (var i = 0; i < lines.Length; i++)
+ {
+ if (lines[i].Contains("using MyCompanyName.MyProjectName.EntityFrameworkCore"))
+ {
+ lines[i] = "using MyCompanyName.MyProjectName.MongoDb;";
+ }
+ else if (lines[i].Contains("MyProjectNameEntityFrameworkCoreModule"))
+ {
+ lines[i] = lines[i].Replace("MyProjectNameEntityFrameworkCoreModule", "MyProjectNameMongoDbModule");
+ }
+ }
+
+ file.SetLines(lines);
+ }
+
+ private void ChangeConnectionString(ProjectBuildContext context)
+ {
+ var file = context.GetFile("/src/MyCompanyName.MyProjectName.Web/appsettings.json");
+
+ file.NormalizeLineEndings();
+
+ var lines = file.GetLines();
+ for (var i = 0; i < lines.Length; i++)
+ {
+ if (lines[i].Contains("Default") && lines[i].Contains("Database"))
+ {
+ lines[i] = " \"Default\": \"mongodb://localhost:27017|MyProjectName\"";
+ file.SetLines(lines);
+ return;
+ }
+ }
+
+ throw new ApplicationException("Could not find the 'Default' connection string in appsettings.json file!");
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/Steps/TemplateCodeDeleteStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/Steps/TemplateCodeDeleteStep.cs
new file mode 100644
index 0000000000..aa3990905a
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/Steps/TemplateCodeDeleteStep.cs
@@ -0,0 +1,19 @@
+using Volo.Abp.ProjectBuilding.Files;
+
+namespace Volo.Abp.ProjectBuilding.Building.Steps
+{
+ public class TemplateCodeDeleteStep : ProjectBuildPipelineStep
+ {
+ public override void Execute(ProjectBuildContext context)
+ {
+ foreach (var file in context.Files)
+ {
+ if (file.Name.EndsWith(".cs")) //TODO: Why only cs!
+ {
+ file.RemoveTemplateCode();
+ file.RemoveTemplateCodeMarkers();
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/TemplateInfo.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/TemplateInfo.cs
new file mode 100644
index 0000000000..c85d1d571c
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Building/TemplateInfo.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using JetBrains.Annotations;
+
+namespace Volo.Abp.ProjectBuilding.Building
+{
+ public abstract class TemplateInfo
+ {
+ public string Name { get; }
+
+ public DatabaseProvider DefaultDatabaseProvider { get; }
+
+ protected TemplateInfo(
+ [NotNull] string name,
+ DatabaseProvider defaultDatabaseProvider = DatabaseProvider.NotSpecified)
+ {
+ Name = Check.NotNullOrWhiteSpace(name, nameof(name));
+ DefaultDatabaseProvider = Check.NotNull(defaultDatabaseProvider, nameof(defaultDatabaseProvider));
+ }
+
+ public virtual IEnumerable GetCustomSteps(ProjectBuildContext context)
+ {
+ return Array.Empty();
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/DownloadInfo.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/DownloadInfo.cs
new file mode 100644
index 0000000000..9d07f6e022
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/DownloadInfo.cs
@@ -0,0 +1,37 @@
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using Volo.Abp.Domain.Entities.Auditing;
+using Volo.Abp.ProjectBuilding.Building;
+
+namespace Volo.Abp.ProjectBuilding
+{
+ [Table("Downloads")]
+ public class DownloadInfo : CreationAuditedAggregateRoot
+ {
+ [Required]
+ [StringLength(128)]
+ public string ProjectName { get; set; }
+
+ [Required]
+ [StringLength(42)]
+ public string TemplateName { get; set; }
+
+ [Required]
+ public DatabaseProvider DatabaseProvider { get; set; }
+
+ [Required]
+ [StringLength(20)]
+ public string Version { get; set; }
+
+ public int CreationDuration { get; set; }
+
+ public DownloadInfo(string projectName, string templateName, DatabaseProvider databaseProvider, string version, int creationDuration)
+ {
+ ProjectName = projectName;
+ TemplateName = templateName;
+ DatabaseProvider = databaseProvider;
+ Version = version;
+ CreationDuration = creationDuration;
+ }
+ }
+}
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Files/FileEntry.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Files/FileEntry.cs
new file mode 100644
index 0000000000..5dd67846d2
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Files/FileEntry.cs
@@ -0,0 +1,126 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Volo.Abp.ProjectBuilding.Files
+{
+ public class FileEntry
+ {
+ private static string[] BinaryFileExtensions = {
+ ".exe", ".dll", ".bin",
+ ".suo", ".obj", ".pdb",
+ ".png", "jpg", "jpeg", ".ico"
+ ,".woff", ".woff2", ".eot", ".svg", ".ttf"
+ };
+
+ public string Name { get; private set; }
+
+ public bool IsDirectory { get; }
+
+ public Encoding Encoding { get; }
+
+ public byte[] Bytes { get; private set; }
+
+ public string Content { get; private set; }
+
+ public bool IsBinaryFile { get; private set; }
+
+ public FileEntry(string name, byte[] bytes, bool isDirectory)
+ {
+ Name = name;
+ Bytes = bytes;
+ IsDirectory = isDirectory;
+
+ Encoding = CalculateEncoding();
+ Content = Encoding.GetString(bytes);
+ IsBinaryFile = CalculateIsBinaryFile();
+ }
+
+ public void SetContent(string fileContent)
+ {
+ Content = fileContent;
+ Bytes = Encoding.GetBytes(fileContent);
+ }
+
+ public string[] GetLines()
+ {
+ return Content.Split(new[] { Environment.NewLine, "\n" }, StringSplitOptions.None);
+ }
+
+ public void SetLines(IEnumerable lines)
+ {
+ SetContent(lines.JoinAsString(Environment.NewLine));
+ }
+
+ public void SetName(string fileName)
+ {
+ Name = fileName;
+ IsBinaryFile = CalculateIsBinaryFile();
+ }
+
+ public void NormalizeLineEndings()
+ {
+ if (Content.IsNullOrEmpty())
+ {
+ return;
+ }
+
+ SetContent(Content.NormalizeLineEndings());
+ }
+
+ public override string ToString()
+ {
+ var str = new StringBuilder(Name);
+
+ if (IsDirectory)
+ {
+ str.Append(" [DIR]");
+ }
+
+ str.Append($" [{Bytes.Length}]");
+
+ return str.ToString();
+ }
+
+ private Encoding CalculateEncoding()
+ {
+ if (Bytes.IsNullOrEmpty())
+ {
+ return Encoding.ASCII;
+ }
+
+ if (Bytes[0] == 0x2b && Bytes[1] == 0x2f && Bytes[2] == 0x76)
+ {
+ return Encoding.UTF7;
+ }
+
+ if (Bytes[0] == 0xef && Bytes[1] == 0xbb && Bytes[2] == 0xbf)
+ {
+ return Encoding.UTF8;
+ }
+
+ if (Bytes[0] == 0xff && Bytes[1] == 0xfe)
+ {
+ return Encoding.Unicode; //UTF-16LE
+ }
+
+ if (Bytes[0] == 0xfe && Bytes[1] == 0xff)
+ {
+ return Encoding.BigEndianUnicode; //UTF-16BE
+ }
+
+ if (Bytes[0] == 0 && Bytes[1] == 0 && Bytes[2] == 0xfe && Bytes[3] == 0xff)
+ {
+ return Encoding.UTF32;
+ }
+
+ return Encoding.UTF8;
+ }
+
+ private bool CalculateIsBinaryFile()
+ {
+ return BinaryFileExtensions.Any(ext => Name.EndsWith(ext));
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Files/FileEntryExtensions.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Files/FileEntryExtensions.cs
new file mode 100644
index 0000000000..19bf75456c
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Files/FileEntryExtensions.cs
@@ -0,0 +1,81 @@
+using System.Collections.Generic;
+
+namespace Volo.Abp.ProjectBuilding.Files
+{
+ public static class FileEntryExtensions
+ {
+ public static void RemoveTemplateCode(this FileEntry file)
+ {
+ RemoveMarkedTemplateCode(file, "");
+ }
+
+ public static void RemoveTemplateCodeIf(this FileEntry file, string condition)
+ {
+ RemoveByCondition(file, "IF", condition);
+ }
+
+ public static void RemoveTemplateCodeIfNot(this FileEntry file, string condition)
+ {
+ RemoveByCondition(file, "IF-NOT", condition);
+ }
+
+ public static void RemoveTemplateCodeMarkers(this FileEntry file)
+ {
+ file.NormalizeLineEndings();
+
+ var lines = file.GetLines();
+ var newLines = new List();
+
+ for (int i = 0; i < lines.Length; i++)
+ {
+ if (lines[i].Contains(""))
+ {
+ //TODO: What if we use inline like: some-code
+ //TODO: This logic skips the code in that case. Should handle it
+ ++i;
+ }
+
+ if (i < lines.Length)
+ {
+ newLines.Add(lines[i]);
+ }
+ }
+
+ file.SetLines(newLines);
+ }
+
+ private static void RemoveByCondition(this FileEntry file, string conditionName, string condition)
+ {
+ RemoveMarkedTemplateCode(file, $"");
+ }
+
+ private static void RemoveMarkedTemplateCode(this FileEntry file, string beginMark)
+ {
+ file.NormalizeLineEndings();
+
+ var lines = file.GetLines();
+ var newLines = new List();
+
+ for (int i = 0; i < lines.Length; i++)
+ {
+ if (lines[i].Contains(beginMark))
+ {
+ while (!lines[i].Contains("") && i < lines.Length)
+ {
+ ++i;
+ }
+
+ ++i;
+ }
+
+ if (i < lines.Length)
+ {
+ newLines.Add(lines[i]);
+ }
+ }
+
+ file.SetLines(newLines);
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Files/FileEntryList.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Files/FileEntryList.cs
new file mode 100644
index 0000000000..34543e31fa
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Files/FileEntryList.cs
@@ -0,0 +1,13 @@
+using System.Collections.Generic;
+
+namespace Volo.Abp.ProjectBuilding.Files
+{
+ public class FileEntryList : List
+ {
+ public FileEntryList(IEnumerable entries)
+ : base(entries)
+ {
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Files/FileEntryListExtensions.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Files/FileEntryListExtensions.cs
new file mode 100644
index 0000000000..1a1a47c1c6
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Files/FileEntryListExtensions.cs
@@ -0,0 +1,24 @@
+using System;
+using Ionic.Zip;
+
+namespace Volo.Abp.ProjectBuilding.Files
+{
+ public static class FileEntryListExtensions
+ {
+ public static void CopyToZipFile(this FileEntryList fileEntryList, ZipFile zipFile)
+ {
+ foreach (var entry in fileEntryList)
+ {
+ try
+ {
+ zipFile.AddEntry(entry.Name, entry.Bytes);
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e);
+ throw;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Github/GithubRelease.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Github/GithubRelease.cs
new file mode 100644
index 0000000000..449f9a43d5
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Github/GithubRelease.cs
@@ -0,0 +1,21 @@
+using System;
+using Newtonsoft.Json;
+
+namespace Volo.Abp.ProjectBuilding.Github
+{
+ [JsonObject]
+ public class GithubRelease
+ {
+ [JsonProperty("id")]
+ public int Id { get; set; }
+
+ [JsonProperty("name")]
+ public string Name { get; set; }
+
+ [JsonProperty("prerelease")]
+ public bool IsPrerelease { get; set; }
+
+ [JsonProperty("published_at")]
+ public DateTime PublishTime { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/IProjectBuilder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/IProjectBuilder.cs
new file mode 100644
index 0000000000..c037442711
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/IProjectBuilder.cs
@@ -0,0 +1,9 @@
+using System.Threading.Tasks;
+
+namespace Volo.Abp.ProjectBuilding
+{
+ public interface IProjectBuilder
+ {
+ Task BuildAsync(ProjectBuildArgs args);
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/ITemplateInfoProvider.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/ITemplateInfoProvider.cs
new file mode 100644
index 0000000000..789e11b341
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/ITemplateInfoProvider.cs
@@ -0,0 +1,11 @@
+using Volo.Abp.ProjectBuilding.Building;
+
+namespace Volo.Abp.ProjectBuilding
+{
+ public interface ITemplateInfoProvider
+ {
+ TemplateInfo GetDefault();
+
+ TemplateInfo Get(string name);
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/ITemplateStore.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/ITemplateStore.cs
new file mode 100644
index 0000000000..f09faa8603
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/ITemplateStore.cs
@@ -0,0 +1,9 @@
+using System.Threading.Tasks;
+
+namespace Volo.Abp.ProjectBuilding
+{
+ public interface ITemplateStore
+ {
+ Task GetAsync(string templateInfoName, string version);
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/ProjectBuildArgs.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/ProjectBuildArgs.cs
new file mode 100644
index 0000000000..fffeb87a6c
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/ProjectBuildArgs.cs
@@ -0,0 +1,26 @@
+using JetBrains.Annotations;
+using Volo.Abp.ProjectBuilding.Building;
+
+namespace Volo.Abp.ProjectBuilding
+{
+ public class ProjectBuildArgs
+ {
+ [NotNull]
+ public SolutionName SolutionName { get; }
+
+ [CanBeNull]
+ public string TemplateName { get; set; }
+
+ public DatabaseProvider DatabaseProvider { get; set; }
+
+ public ProjectBuildArgs(
+ [NotNull] SolutionName solutionName,
+ DatabaseProvider databaseProvider = DatabaseProvider.NotSpecified,
+ [CanBeNull] string templateName = null)
+ {
+ DatabaseProvider = databaseProvider;
+ TemplateName = templateName;
+ SolutionName = Check.NotNull(solutionName, nameof(solutionName));
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/ProjectBuildResult.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/ProjectBuildResult.cs
new file mode 100644
index 0000000000..2acf036ddc
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/ProjectBuildResult.cs
@@ -0,0 +1,15 @@
+namespace Volo.Abp.ProjectBuilding
+{
+ public class ProjectBuildResult
+ {
+ public byte[] ZipContent { get; }
+
+ public string ProjectName { get; }
+
+ public ProjectBuildResult(byte[] zipContent, string projectName)
+ {
+ ZipContent = zipContent;
+ ProjectName = projectName;
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/ProjectBuilder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/ProjectBuilder.cs
new file mode 100644
index 0000000000..3a5c7a6b8a
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/ProjectBuilder.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Threading.Tasks;
+using Volo.Abp.Cli;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.ProjectBuilding.Building;
+
+namespace Volo.Abp.ProjectBuilding
+{
+ public class ProjectBuilder : IProjectBuilder, ITransientDependency
+ {
+ protected ITemplateStore TemplateStore { get; }
+ protected ITemplateInfoProvider TemplateInfoProvider { get; }
+
+ public ProjectBuilder(ITemplateStore templateStore, ITemplateInfoProvider templateInfoProvider)
+ {
+ TemplateStore = templateStore;
+ TemplateInfoProvider = templateInfoProvider;
+ }
+
+ public async Task BuildAsync(ProjectBuildArgs args)
+ {
+ var templateInfo = GetTemplateInfo(args);
+
+ args.TemplateName = templateInfo.Name;
+
+ if (args.DatabaseProvider == DatabaseProvider.NotSpecified)
+ {
+ if (templateInfo.DefaultDatabaseProvider != DatabaseProvider.NotSpecified)
+ {
+ args.DatabaseProvider = templateInfo.DefaultDatabaseProvider;
+ }
+ }
+
+ var version = CliService.Version;
+
+ var templateFile = await TemplateStore.GetAsync(args.TemplateName, version);
+
+ var context = new ProjectBuildContext(
+ templateInfo,
+ templateFile,
+ args,
+ version
+ );
+
+ ProjectBuildPipelineBuilder.Build(context).Execute(context);
+
+ return new ProjectBuildResult(context.Result.ZipContent, args.SolutionName.ProjectName);
+ }
+
+ private TemplateInfo GetTemplateInfo(ProjectBuildArgs args)
+ {
+ if (args.TemplateName.IsNullOrWhiteSpace())
+ {
+ return TemplateInfoProvider.GetDefault();
+ }
+ else
+ {
+ return TemplateInfoProvider.Get(args.TemplateName);
+ }
+ }
+ }
+}
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/SolutionName.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/SolutionName.cs
new file mode 100644
index 0000000000..0e274f5ceb
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/SolutionName.cs
@@ -0,0 +1,50 @@
+using System;
+
+namespace Volo.Abp.ProjectBuilding
+{
+ public class SolutionName
+ {
+ public string FullName { get; }
+
+ public string CompanyName { get; }
+
+ public string ProjectName { get; }
+
+ private SolutionName(string fullName, string companyName, string projectName)
+ {
+ FullName = fullName;
+ CompanyName = companyName;
+ ProjectName = projectName;
+ }
+
+ public static SolutionName Parse(string fullName)
+ {
+ if (fullName.Length < 1)
+ {
+ throw new UserFriendlyException("Solution name is not valid. It should be 1 character length at minimum.");
+ }
+
+ string companyName = null;
+ var projectName = fullName;
+
+ if (fullName.Contains("."))
+ {
+ var lastDotIndex = fullName.LastIndexOf(".", StringComparison.OrdinalIgnoreCase);
+ companyName = fullName.Substring(0, lastDotIndex);
+ projectName = fullName.Substring(lastDotIndex + 1);
+
+ if (companyName.Length < 1)
+ {
+ throw new UserFriendlyException("Solution name is not valid. Company name should be 1 character length at minimum.");
+ }
+
+ if (projectName.Length < 1)
+ {
+ throw new UserFriendlyException("Solution name is not valid. Project name should be 1 character length at minimum.");
+ }
+ }
+
+ return new SolutionName(fullName, companyName, projectName);
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/TemplateFile.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/TemplateFile.cs
new file mode 100644
index 0000000000..d1f839e27b
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/TemplateFile.cs
@@ -0,0 +1,12 @@
+namespace Volo.Abp.ProjectBuilding
+{
+ public class TemplateFile
+ {
+ public byte[] FileBytes { get; }
+
+ public TemplateFile(byte[] fileBytes)
+ {
+ FileBytes = fileBytes;
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/TemplateInfoProvider.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/TemplateInfoProvider.cs
new file mode 100644
index 0000000000..e386bd7ef1
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/TemplateInfoProvider.cs
@@ -0,0 +1,30 @@
+using System;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.ProjectBuilding.Building;
+using Volo.Abp.ProjectBuilding.Templates;
+
+namespace Volo.Abp.ProjectBuilding
+{
+ public class TemplateInfoProvider : ITemplateInfoProvider, ITransientDependency
+ {
+ public TemplateInfo GetDefault()
+ {
+ return Get(MvcApplicationTemplate.TemplateName);
+ }
+
+ public TemplateInfo Get(string name)
+ {
+ switch (name)
+ {
+ case MvcApplicationTemplate.TemplateName:
+ return new MvcApplicationTemplate();
+ case MvcModuleTemplate.TemplateName:
+ return new MvcModuleTemplate();
+ case ServiceTemplate.TemplateName:
+ return new ServiceTemplate();
+ default:
+ throw new Exception("There is no template found with given name: " + name);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Templates/MvcApplicationTemplate.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Templates/MvcApplicationTemplate.cs
new file mode 100644
index 0000000000..581fe9fcb5
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Templates/MvcApplicationTemplate.cs
@@ -0,0 +1,51 @@
+using System.Collections.Generic;
+using Volo.Abp.ProjectBuilding.Building;
+using Volo.Abp.ProjectBuilding.Building.Steps;
+
+namespace Volo.Abp.ProjectBuilding.Templates
+{
+ public class MvcApplicationTemplate : TemplateInfo
+ {
+ ///
+ /// "mvc".
+ ///
+ public const string TemplateName = "mvc";
+
+ public MvcApplicationTemplate()
+ : base(TemplateName, DatabaseProvider.EntityFrameworkCore)
+ {
+ }
+
+ public override IEnumerable GetCustomSteps(ProjectBuildContext context)
+ {
+ var steps = new List();
+ SwitchDatabaseProvider(context, steps);
+ RemoveOtherDatabaseProviders(context, steps);
+ return steps;
+ }
+
+ private static void SwitchDatabaseProvider(ProjectBuildContext context, List steps)
+ {
+ if (context.BuildArgs.DatabaseProvider == DatabaseProvider.MongoDb)
+ {
+ steps.Add(new SwitchEntityFrameworkCoreToMongoDbStep());
+ }
+ }
+
+ private static void RemoveOtherDatabaseProviders(ProjectBuildContext context, List steps)
+ {
+ if (context.BuildArgs.DatabaseProvider != DatabaseProvider.EntityFrameworkCore)
+ {
+ steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.EntityFrameworkCore"));
+ steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations"));
+ steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Application.Tests", projectFolderPath: "test/MyCompanyName.MyProjectName.Application.Tests"));
+ steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Web.Tests", projectFolderPath: "test/MyCompanyName.MyProjectName.Web.Tests"));
+ }
+
+ if (context.BuildArgs.DatabaseProvider != DatabaseProvider.MongoDb)
+ {
+ steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.MongoDB"));
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Templates/MvcModuleTemplate.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Templates/MvcModuleTemplate.cs
new file mode 100644
index 0000000000..d2831d32b0
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Templates/MvcModuleTemplate.cs
@@ -0,0 +1,18 @@
+using Volo.Abp.ProjectBuilding.Building;
+
+namespace Volo.Abp.ProjectBuilding.Templates
+{
+ public class MvcModuleTemplate : TemplateInfo
+ {
+ ///
+ /// "mvcmodule".
+ ///
+ public const string TemplateName = "mvcmodule";
+
+ public MvcModuleTemplate()
+ : base(TemplateName)
+ {
+
+ }
+ }
+}
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Templates/ServiceTemplate.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Templates/ServiceTemplate.cs
new file mode 100644
index 0000000000..4b547eead0
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Templates/ServiceTemplate.cs
@@ -0,0 +1,18 @@
+using Volo.Abp.ProjectBuilding.Building;
+
+namespace Volo.Abp.ProjectBuilding.Templates
+{
+ public class ServiceTemplate : TemplateInfo
+ {
+ ///
+ /// "service".
+ ///
+ public const string TemplateName = "service";
+
+ public ServiceTemplate()
+ : base(TemplateName)
+ {
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Zipping/ZipFileExtensions.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Zipping/ZipFileExtensions.cs
new file mode 100644
index 0000000000..97536bd6ea
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectBuilding/Zipping/ZipFileExtensions.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Ionic.Zip;
+using Volo.Abp.ProjectBuilding.Files;
+
+namespace Volo.Abp.ProjectBuilding.Zipping
+{
+ public static class ZipFileExtensions
+ {
+ public static byte[] GetBytes(this ZipFile zipFile)
+ {
+ using (var ms = new MemoryStream())
+ {
+ zipFile.Save(ms);
+ return ms.ToArray();
+ }
+ }
+
+ public static FileEntryList ToFileEntryList(this ZipFile zipFile, string rootFolder = null)
+ {
+ var zipEntries = zipFile.Entries;
+
+ if (rootFolder != null)
+ {
+ zipEntries = zipEntries.Where(entry => entry.FileName.StartsWith(rootFolder)).ToList();
+ }
+
+ var fileEntries = new List();
+
+ foreach (var zipEntry in zipEntries)
+ {
+ using (var entryStream = zipEntry.OpenReader())
+ {
+ var fileName = zipEntry.FileName;
+
+ if (rootFolder != null)
+ {
+ fileName = fileName.RemovePreFix(rootFolder);
+ }
+
+ if (fileName.IsNullOrEmpty())
+ {
+ continue;
+ }
+
+ fileName = fileName.EnsureStartsWith('/');
+
+ fileEntries.Add(new FileEntry(fileName, entryStream.GetAllBytes(), zipEntry.IsDirectory));
+ }
+ }
+
+ return new FileEntryList(fileEntries);
+ }
+ }
+}
diff --git a/framework/src/Volo.Abp.Cli/Program.cs b/framework/src/Volo.Abp.Cli/Program.cs
deleted file mode 100644
index 5e9dcd4006..0000000000
--- a/framework/src/Volo.Abp.Cli/Program.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using System;
-
-namespace Volo.Abp.Cli
-{
- public class Program
- {
- private static void Main(string[] args)
- {
- Console.WriteLine("Welcome to ABP CLI..! This tool is in development. There is no functionality yet. So, bye bye... ;)");
- }
- }
-}
diff --git a/framework/src/Volo.Abp.Cli/Volo.Abp.Cli.csproj b/framework/src/Volo.Abp.Cli/Volo.Abp.Cli.csproj
index 29b80cb98a..4f0de79850 100644
--- a/framework/src/Volo.Abp.Cli/Volo.Abp.Cli.csproj
+++ b/framework/src/Volo.Abp.Cli/Volo.Abp.Cli.csproj
@@ -7,6 +7,20 @@
netcoreapp2.2
true
abp
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/AbpCliModule.cs b/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/AbpCliModule.cs
new file mode 100644
index 0000000000..339041ef8a
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/AbpCliModule.cs
@@ -0,0 +1,14 @@
+using Volo.Abp.Autofac;
+using Volo.Abp.Modularity;
+
+namespace Volo.Abp.Cli
+{
+ [DependsOn(
+ typeof(AbpCliCoreModule),
+ typeof(AbpAutofacModule)
+ )]
+ public class AbpCliModule : AbpModule
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/Program.cs b/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/Program.cs
new file mode 100644
index 0000000000..02759655ff
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/Program.cs
@@ -0,0 +1,40 @@
+using Microsoft.Extensions.DependencyInjection;
+using Serilog;
+using Serilog.Events;
+using System.IO;
+using Volo.Abp.Threading;
+
+namespace Volo.Abp.Cli
+{
+ public class Program
+ {
+ private static void Main(string[] args)
+ {
+ Log.Logger = new LoggerConfiguration()
+ .MinimumLevel.Information()
+ .MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
+ .Enrich.FromLogContext()
+ .WriteTo.File(Path.Combine(CliPaths.CliLogPath, "abp-cli-logs.txt"))
+ .WriteTo.Console()
+ .CreateLogger();
+
+ using (var application = AbpApplicationFactory.Create(
+ options =>
+ {
+ options.UseAutofac();
+ options.Services.AddLogging(c => c.AddSerilog());
+ }))
+ {
+ application.Initialize();
+
+ AsyncHelper.RunSync(
+ () => application.ServiceProvider
+ .GetRequiredService()
+ .RunAsync(args)
+ );
+
+ application.Shutdown();
+ }
+ }
+ }
+}
diff --git a/framework/src/Volo.Abp.Core/System/Reflection/AbpAssemblyExtensions.cs b/framework/src/Volo.Abp.Core/System/Reflection/AbpAssemblyExtensions.cs
new file mode 100644
index 0000000000..0e61770618
--- /dev/null
+++ b/framework/src/Volo.Abp.Core/System/Reflection/AbpAssemblyExtensions.cs
@@ -0,0 +1,12 @@
+using System.Diagnostics;
+
+namespace System.Reflection
+{
+ public static class AbpAssemblyExtensions
+ {
+ public static string GetFileVersion(this Assembly assembly)
+ {
+ return FileVersionInfo.GetVersionInfo(assembly.Location).FileVersion;
+ }
+ }
+}
diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationBase.cs b/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationBase.cs
index a4862789f2..8036760f6a 100644
--- a/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationBase.cs
+++ b/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationBase.cs
@@ -56,7 +56,7 @@ namespace Volo.Abp
public virtual void Dispose()
{
-
+ //TODO: Shutdown if not done before?
}
protected virtual void SetServiceProvider(IServiceProvider serviceProvider)
diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Modularity/ModuleManager.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Modularity/ModuleManager.cs
index d79ae0b843..934948dc03 100644
--- a/framework/src/Volo.Abp.Core/Volo/Abp/Modularity/ModuleManager.cs
+++ b/framework/src/Volo.Abp.Core/Volo/Abp/Modularity/ModuleManager.cs
@@ -42,12 +42,12 @@ namespace Volo.Abp.Modularity
}
}
- _logger.LogInformation("Initialized all modules.");
+ _logger.LogInformation("Initialized all ABP modules.");
}
private void LogListOfModules()
{
- _logger.LogInformation("Loaded modules:");
+ _logger.LogInformation("Loaded ABP modules:");
foreach (var module in _moduleContainer.Modules)
{
diff --git a/framework/test/Volo.Abp.Cli.Tests/Volo.Abp.Cli.Tests.csproj b/framework/test/Volo.Abp.Cli.Tests/Volo.Abp.Cli.Tests.csproj
new file mode 100644
index 0000000000..3f094594d0
--- /dev/null
+++ b/framework/test/Volo.Abp.Cli.Tests/Volo.Abp.Cli.Tests.csproj
@@ -0,0 +1,15 @@
+
+
+
+ netcoreapp2.2
+ latest
+
+
+
+
+
+
+
+
+
+
diff --git a/framework/test/Volo.Abp.Cli.Tests/Volo/Abp/Cli/AbpCliTestBase.cs b/framework/test/Volo.Abp.Cli.Tests/Volo/Abp/Cli/AbpCliTestBase.cs
new file mode 100644
index 0000000000..6f6393b7ae
--- /dev/null
+++ b/framework/test/Volo.Abp.Cli.Tests/Volo/Abp/Cli/AbpCliTestBase.cs
@@ -0,0 +1,7 @@
+namespace Volo.Abp.Cli
+{
+ public class AbpCliTestBase : AbpIntegratedTest
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/framework/test/Volo.Abp.Cli.Tests/Volo/Abp/Cli/AbpCliTestModule.cs b/framework/test/Volo.Abp.Cli.Tests/Volo/Abp/Cli/AbpCliTestModule.cs
new file mode 100644
index 0000000000..8c2101052b
--- /dev/null
+++ b/framework/test/Volo.Abp.Cli.Tests/Volo/Abp/Cli/AbpCliTestModule.cs
@@ -0,0 +1,13 @@
+using Volo.Abp.Modularity;
+
+namespace Volo.Abp.Cli
+{
+ [DependsOn(
+ typeof(AbpTestBaseModule),
+ typeof(AbpCliModule)
+ )]
+ public class AbpCliTestModule : AbpModule
+ {
+
+ }
+}
diff --git a/framework/test/Volo.Abp.Cli.Tests/Volo/Abp/Cli/CommandLineArgumentParser_Tests.cs b/framework/test/Volo.Abp.Cli.Tests/Volo/Abp/Cli/CommandLineArgumentParser_Tests.cs
new file mode 100644
index 0000000000..c8d31990cf
--- /dev/null
+++ b/framework/test/Volo.Abp.Cli.Tests/Volo/Abp/Cli/CommandLineArgumentParser_Tests.cs
@@ -0,0 +1,68 @@
+using Shouldly;
+using System.Linq;
+using Volo.Abp.Cli.Args;
+using Xunit;
+
+namespace Volo.Abp.Cli
+{
+ public class CommandLineArgumentParser_Tests : AbpCliTestBase
+ {
+ private readonly ICommandLineArgumentParser _commandLineArgumentParser;
+
+ public CommandLineArgumentParser_Tests()
+ {
+ _commandLineArgumentParser = GetRequiredService();
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData(new object[] { new string[0] })]
+ public void Should_Parse_Empty_Arguments(string[] args)
+ {
+ var commandLineArgs = _commandLineArgumentParser.Parse(args);
+ commandLineArgs.Command.ShouldBeNull();
+ commandLineArgs.Target.ShouldBeNull();
+ commandLineArgs.Options.Any().ShouldBeFalse();
+ }
+
+ [Fact]
+ public void Should_Work_With_Only_Command()
+ {
+ var commandLineArgs = _commandLineArgumentParser.Parse(new[] { "mycommand" });
+ commandLineArgs.Command.ShouldBe("mycommand");
+ commandLineArgs.Target.ShouldBeNull();
+ commandLineArgs.Options.Any().ShouldBeFalse();
+ }
+
+ [Fact]
+ public void Should_Work_With_Command_And_Target()
+ {
+ var commandLineArgs = _commandLineArgumentParser.Parse(new[] { "mycommand", "mytarget" });
+ commandLineArgs.Command.ShouldBe("mycommand");
+ commandLineArgs.Target.ShouldBe("mytarget");
+ commandLineArgs.Options.Any().ShouldBeFalse();
+ }
+
+ [Fact]
+ public void Should_Work_With_Command_And_Target_And_Options()
+ {
+ var commandLineArgs = _commandLineArgumentParser.Parse(new[]
+ {
+ "mycommand", "mytarget", "-a", "value1", "-b", "--optionC", "value2"
+ });
+
+ commandLineArgs.Command.ShouldBe("mycommand");
+ commandLineArgs.Target.ShouldBe("mytarget");
+ commandLineArgs.Options.Any().ShouldBeTrue();
+
+ commandLineArgs.Options.ShouldContainKey("a");
+ commandLineArgs.Options["a"].ShouldBe("value1");
+
+ commandLineArgs.Options.ShouldContainKey("b");
+ commandLineArgs.Options["b"].ShouldBeNull();
+
+ commandLineArgs.Options.ShouldContainKey("optionC");
+ commandLineArgs.Options["optionC"].ShouldBe("value2");
+ }
+ }
+}
diff --git a/nupkg/common.ps1 b/nupkg/common.ps1
index 6ebae9a0b5..100bfe3b89 100644
--- a/nupkg/common.ps1
+++ b/nupkg/common.ps1
@@ -53,6 +53,7 @@ $projects = (
"framework/src/Volo.Abp.BackgroundWorkers",
"framework/src/Volo.Abp.Caching",
"framework/src/Volo.Abp.Castle.Core",
+ "framework/src/Volo.Abp.Cli.Core",
"framework/src/Volo.Abp.Cli",
"framework/src/Volo.Abp.Core",
"framework/src/Volo.Abp.Data",