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",