mirror of https://github.com/abpframework/abp.git
committed by
GitHub
63 changed files with 2014 additions and 20 deletions
@ -0,0 +1,27 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback> |
||||
|
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
||||
|
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
||||
|
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Ionic.Zip" Version="1.9.1.8" /> |
||||
|
<PackageReference Include="HtmlAgilityPack.NetCore" Version="1.5.0.1" /> |
||||
|
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.2.0" /> |
||||
|
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.5.1" /> |
||||
|
<PackageReference Include="System.Security.Permissions" Version="4.5.0" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\Volo.Abp.Ddd.Domain\Volo.Abp.Ddd.Domain.csproj" /> |
||||
|
<ProjectReference Include="..\Volo.Abp.Json\Volo.Abp.Json.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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("<EMPTY>"); |
||||
|
} |
||||
|
|
||||
|
return sb.ToString(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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 '--'."); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,35 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using JetBrains.Annotations; |
||||
|
|
||||
|
namespace Volo.Abp.Cli.Args |
||||
|
{ |
||||
|
public class CommandLineOptions : Dictionary<string, string> |
||||
|
{ |
||||
|
[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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
namespace Volo.Abp.Cli.Args |
||||
|
{ |
||||
|
public interface ICommandLineArgumentParser |
||||
|
{ |
||||
|
CommandLineArgs Parse(string[] args); |
||||
|
} |
||||
|
} |
||||
@ -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"); |
||||
|
} |
||||
|
} |
||||
@ -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<CliService> 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<CliService>.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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
using System; |
||||
|
using Volo.Abp.Cli.Args; |
||||
|
|
||||
|
namespace Volo.Abp.Cli.Commands |
||||
|
{ |
||||
|
public interface ICommandSelector |
||||
|
{ |
||||
|
Type Select(CommandLineArgs commandLineArgs); |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
@ -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 <command> <target> [options]"); |
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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<NewProjectCommand> Logger { get; set; } |
||||
|
|
||||
|
protected ProjectBuilder ProjectBuilder { get; } |
||||
|
|
||||
|
public NewProjectCommand(ProjectBuilder projectBuilder) |
||||
|
{ |
||||
|
ProjectBuilder = projectBuilder; |
||||
|
|
||||
|
Logger = NullLogger<NewProjectCommand>.Instance; |
||||
|
} |
||||
|
|
||||
|
public async Task ExecuteAsync(CommandLineArgs commandLineArgs) |
||||
|
{ |
||||
|
if (commandLineArgs.Target == null) |
||||
|
{ |
||||
|
Logger.LogWarning("Project name is missing."); |
||||
|
Logger.LogWarning("Usage:"); |
||||
|
Logger.LogWarning(" abp new <project-name> [-t <template-name>]"); |
||||
|
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"; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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<AbpIoTemplateStore> Logger { get; set; } |
||||
|
|
||||
|
public AbpIoTemplateStore() |
||||
|
{ |
||||
|
Logger = NullLogger<AbpIoTemplateStore>.Instance; |
||||
|
} |
||||
|
|
||||
|
public async Task<TemplateFile> 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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
namespace Volo.Abp.ProjectBuilding.Building |
||||
|
{ |
||||
|
public enum DatabaseProvider |
||||
|
{ |
||||
|
NotSpecified = 0, |
||||
|
EntityFrameworkCore = 1, |
||||
|
MongoDb = 2 |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Volo.Abp.ProjectBuilding.Building |
||||
|
{ |
||||
|
public class ProjectBuildPipeline |
||||
|
{ |
||||
|
public List<ProjectBuildPipelineStep> Steps { get; } |
||||
|
|
||||
|
public ProjectBuildPipeline() |
||||
|
{ |
||||
|
Steps = new List<ProjectBuildPipelineStep>(); |
||||
|
} |
||||
|
|
||||
|
public void Execute(ProjectBuildContext context) |
||||
|
{ |
||||
|
foreach (var step in Steps) |
||||
|
{ |
||||
|
step.Execute(context); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
namespace Volo.Abp.ProjectBuilding.Building |
||||
|
{ |
||||
|
public abstract class ProjectBuildPipelineStep |
||||
|
{ |
||||
|
public abstract void Execute(ProjectBuildContext context); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
namespace Volo.Abp.ProjectBuilding.Building |
||||
|
{ |
||||
|
public class ProjectResult |
||||
|
{ |
||||
|
public byte[] ZipContent { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -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(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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<FileEntry> _entries; |
||||
|
private readonly string _companyNamePlaceHolder; |
||||
|
private readonly string _projectNamePlaceHolder; |
||||
|
private readonly string _latestNugetPackageVersion; |
||||
|
|
||||
|
public NugetReferenceReplacer(List<FileEntry> 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 = $"<ProjectReference Include=\"{valueAttr.Value}\""; |
||||
|
var oldLineAlt = $"<ProjectReference Include=\"{valueAttr.Value}\""; |
||||
|
var newLine = $"<PackageReference Include=\"{newValue}\" Version=\"{_latestNugetPackageVersion}\""; |
||||
|
|
||||
|
content = content.Replace(oldLine, newLine); |
||||
|
content = content.Replace(oldLineAlt, newLine); |
||||
|
} |
||||
|
|
||||
|
return content; |
||||
|
} |
||||
|
|
||||
|
private string ConvertToNugetReference(string oldValue) |
||||
|
{ |
||||
|
var directory = new DirectoryInfo(oldValue); |
||||
|
|
||||
|
var newValue = directory.Name.Replace(".csproj", ""); |
||||
|
|
||||
|
return newValue; |
||||
|
} |
||||
|
|
||||
|
private static Stream GenerateStreamFromString(string s) |
||||
|
{ |
||||
|
var stream = new MemoryStream(); |
||||
|
var writer = new StreamWriter(stream); |
||||
|
writer.Write(s); |
||||
|
writer.Flush(); |
||||
|
stream.Position = 0; |
||||
|
return stream; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,71 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
|
||||
|
namespace Volo.Abp.ProjectBuilding.Building.Steps |
||||
|
{ |
||||
|
public class RemoveProjectFromSolutionStep : ProjectBuildPipelineStep |
||||
|
{ |
||||
|
private readonly string _projectName; |
||||
|
private readonly string _solutionFilePath; |
||||
|
private readonly string _projectFolderPath; |
||||
|
|
||||
|
public RemoveProjectFromSolutionStep( |
||||
|
string projectName, |
||||
|
string solutionFilePath = null, |
||||
|
string projectFolderPath = null) |
||||
|
{ |
||||
|
_projectName = projectName; |
||||
|
_solutionFilePath = solutionFilePath ?? "/MyCompanyName.MyProjectName.sln"; |
||||
|
_projectFolderPath = projectFolderPath ?? ("/src/" + projectName); |
||||
|
} |
||||
|
|
||||
|
public override void Execute(ProjectBuildContext context) |
||||
|
{ |
||||
|
context.Files.RemoveAll(file => file.Name.StartsWith(_projectFolderPath)); |
||||
|
var solutionFile = context.GetFile(_solutionFilePath); |
||||
|
solutionFile.NormalizeLineEndings(); |
||||
|
solutionFile.SetLines(RemoveProject(solutionFile.GetLines().ToList())); |
||||
|
} |
||||
|
|
||||
|
private List<string> RemoveProject(List<string> solutionFileLines) |
||||
|
{ |
||||
|
var projectKey = FindProjectKey(solutionFileLines); |
||||
|
var newSolutionFileLines = new List<string>(); |
||||
|
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<string> 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}'"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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<FileEntry> _entries; |
||||
|
private readonly string _companyNamePlaceHolder; |
||||
|
private readonly string _projectNamePlaceHolder; |
||||
|
|
||||
|
private readonly string _companyName; |
||||
|
private readonly string _projectName; |
||||
|
|
||||
|
public SolutionRenamer(List<FileEntry> 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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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!"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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<ProjectBuildPipelineStep> GetCustomSteps(ProjectBuildContext context) |
||||
|
{ |
||||
|
return Array.Empty<ProjectBuildPipelineStep>(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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<int> |
||||
|
{ |
||||
|
[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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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<string> 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)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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, "<TEMPLATE-REMOVE>"); |
||||
|
} |
||||
|
|
||||
|
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<string>(); |
||||
|
|
||||
|
for (int i = 0; i < lines.Length; i++) |
||||
|
{ |
||||
|
if (lines[i].Contains("<TEMPLATE-REMOVE") || |
||||
|
lines[i].Contains("</TEMPLATE-REMOVE>")) |
||||
|
{ |
||||
|
//TODO: What if we use inline like: <TEMPLATE-REMOVE IF-NOT="..."> some-code </TEMPLATE-REMOVE>
|
||||
|
//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, $"<TEMPLATE-REMOVE {conditionName}='{condition}'>"); |
||||
|
} |
||||
|
|
||||
|
private static void RemoveMarkedTemplateCode(this FileEntry file, string beginMark) |
||||
|
{ |
||||
|
file.NormalizeLineEndings(); |
||||
|
|
||||
|
var lines = file.GetLines(); |
||||
|
var newLines = new List<string>(); |
||||
|
|
||||
|
for (int i = 0; i < lines.Length; i++) |
||||
|
{ |
||||
|
if (lines[i].Contains(beginMark)) |
||||
|
{ |
||||
|
while (!lines[i].Contains("</TEMPLATE-REMOVE>") && i < lines.Length) |
||||
|
{ |
||||
|
++i; |
||||
|
} |
||||
|
|
||||
|
++i; |
||||
|
} |
||||
|
|
||||
|
if (i < lines.Length) |
||||
|
{ |
||||
|
newLines.Add(lines[i]); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
file.SetLines(newLines); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Volo.Abp.ProjectBuilding.Files |
||||
|
{ |
||||
|
public class FileEntryList : List<FileEntry> |
||||
|
{ |
||||
|
public FileEntryList(IEnumerable<FileEntry> entries) |
||||
|
: base(entries) |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Volo.Abp.ProjectBuilding |
||||
|
{ |
||||
|
public interface IProjectBuilder |
||||
|
{ |
||||
|
Task<ProjectBuildResult> BuildAsync(ProjectBuildArgs args); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
using Volo.Abp.ProjectBuilding.Building; |
||||
|
|
||||
|
namespace Volo.Abp.ProjectBuilding |
||||
|
{ |
||||
|
public interface ITemplateInfoProvider |
||||
|
{ |
||||
|
TemplateInfo GetDefault(); |
||||
|
|
||||
|
TemplateInfo Get(string name); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Volo.Abp.ProjectBuilding |
||||
|
{ |
||||
|
public interface ITemplateStore |
||||
|
{ |
||||
|
Task<TemplateFile> GetAsync(string templateInfoName, string version); |
||||
|
} |
||||
|
} |
||||
@ -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)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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<ProjectBuildResult> 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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
namespace Volo.Abp.ProjectBuilding |
||||
|
{ |
||||
|
public class TemplateFile |
||||
|
{ |
||||
|
public byte[] FileBytes { get; } |
||||
|
|
||||
|
public TemplateFile(byte[] fileBytes) |
||||
|
{ |
||||
|
FileBytes = fileBytes; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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 |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// "mvc".
|
||||
|
/// </summary>
|
||||
|
public const string TemplateName = "mvc"; |
||||
|
|
||||
|
public MvcApplicationTemplate() |
||||
|
: base(TemplateName, DatabaseProvider.EntityFrameworkCore) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public override IEnumerable<ProjectBuildPipelineStep> GetCustomSteps(ProjectBuildContext context) |
||||
|
{ |
||||
|
var steps = new List<ProjectBuildPipelineStep>(); |
||||
|
SwitchDatabaseProvider(context, steps); |
||||
|
RemoveOtherDatabaseProviders(context, steps); |
||||
|
return steps; |
||||
|
} |
||||
|
|
||||
|
private static void SwitchDatabaseProvider(ProjectBuildContext context, List<ProjectBuildPipelineStep> steps) |
||||
|
{ |
||||
|
if (context.BuildArgs.DatabaseProvider == DatabaseProvider.MongoDb) |
||||
|
{ |
||||
|
steps.Add(new SwitchEntityFrameworkCoreToMongoDbStep()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static void RemoveOtherDatabaseProviders(ProjectBuildContext context, List<ProjectBuildPipelineStep> 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")); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
using Volo.Abp.ProjectBuilding.Building; |
||||
|
|
||||
|
namespace Volo.Abp.ProjectBuilding.Templates |
||||
|
{ |
||||
|
public class MvcModuleTemplate : TemplateInfo |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// "mvcmodule".
|
||||
|
/// </summary>
|
||||
|
public const string TemplateName = "mvcmodule"; |
||||
|
|
||||
|
public MvcModuleTemplate() |
||||
|
: base(TemplateName) |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
using Volo.Abp.ProjectBuilding.Building; |
||||
|
|
||||
|
namespace Volo.Abp.ProjectBuilding.Templates |
||||
|
{ |
||||
|
public class ServiceTemplate : TemplateInfo |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// "service".
|
||||
|
/// </summary>
|
||||
|
public const string TemplateName = "service"; |
||||
|
|
||||
|
public ServiceTemplate() |
||||
|
: base(TemplateName) |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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<FileEntry>(); |
||||
|
|
||||
|
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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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... ;)"); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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 |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -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<AbpCliModule>( |
||||
|
options => |
||||
|
{ |
||||
|
options.UseAutofac(); |
||||
|
options.Services.AddLogging(c => c.AddSerilog()); |
||||
|
})) |
||||
|
{ |
||||
|
application.Initialize(); |
||||
|
|
||||
|
AsyncHelper.RunSync( |
||||
|
() => application.ServiceProvider |
||||
|
.GetRequiredService<CliService>() |
||||
|
.RunAsync(args) |
||||
|
); |
||||
|
|
||||
|
application.Shutdown(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netcoreapp2.2</TargetFramework> |
||||
|
<LangVersion>latest</LangVersion> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\AbpTestBase\AbpTestBase.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Volo.Abp.Cli\Volo.Abp.Cli.csproj" /> |
||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.0.1" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,7 @@ |
|||||
|
namespace Volo.Abp.Cli |
||||
|
{ |
||||
|
public class AbpCliTestBase : AbpIntegratedTest<AbpCliTestModule> |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace Volo.Abp.Cli |
||||
|
{ |
||||
|
[DependsOn( |
||||
|
typeof(AbpTestBaseModule), |
||||
|
typeof(AbpCliModule) |
||||
|
)] |
||||
|
public class AbpCliTestModule : AbpModule |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -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<ICommandLineArgumentParser>(); |
||||
|
} |
||||
|
|
||||
|
[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"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue