mirror of https://github.com/abpframework/abp.git
31 changed files with 726 additions and 103 deletions
@ -0,0 +1,169 @@ |
|||
using System; |
|||
using System.IO; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using ICSharpCode.SharpZipLib.Core; |
|||
using ICSharpCode.SharpZipLib.Zip; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Logging.Abstractions; |
|||
using Volo.Abp.Cli.Args; |
|||
using Volo.Abp.Cli.ProjectBuilding; |
|||
using Volo.Abp.Cli.ProjectBuilding.Building; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Cli.Commands |
|||
{ |
|||
public class GetSourceCommand : IConsoleCommand, ITransientDependency |
|||
{ |
|||
public ModuleProjectBuilder ModuleProjectBuilder { get; } |
|||
|
|||
public ILogger<NewCommand> Logger { get; set; } |
|||
|
|||
public GetSourceCommand(ModuleProjectBuilder moduleProjectBuilder) |
|||
{ |
|||
ModuleProjectBuilder = moduleProjectBuilder; |
|||
Logger = NullLogger<NewCommand>.Instance; |
|||
} |
|||
|
|||
public async Task ExecuteAsync(CommandLineArgs commandLineArgs) |
|||
{ |
|||
if (commandLineArgs.Target == null) |
|||
{ |
|||
throw new CliUsageException( |
|||
"Module name is missing!" + |
|||
Environment.NewLine + Environment.NewLine + |
|||
GetUsageInfo() |
|||
); |
|||
} |
|||
|
|||
Logger.LogInformation("Downloading source code of " + commandLineArgs.Target); |
|||
|
|||
var version = commandLineArgs.Options.GetOrNull(Options.Version.Short, Options.Version.Long); |
|||
if (version != null) |
|||
{ |
|||
Logger.LogInformation("Version: " + version); |
|||
} |
|||
|
|||
var outputFolder = commandLineArgs.Options.GetOrNull(Options.OutputFolder.Short, Options.OutputFolder.Long); |
|||
if (outputFolder != null) |
|||
{ |
|||
if (!Directory.Exists(outputFolder)) |
|||
{ |
|||
Directory.CreateDirectory(outputFolder); |
|||
} |
|||
|
|||
outputFolder = Path.GetFullPath(outputFolder); |
|||
} |
|||
else |
|||
{ |
|||
outputFolder = Directory.GetCurrentDirectory(); |
|||
} |
|||
|
|||
Logger.LogInformation("Output folder: " + outputFolder); |
|||
|
|||
var gitHubLocalRepositoryPath = commandLineArgs.Options.GetOrNull(Options.GitHubLocalRepositoryPath.Long); |
|||
if (gitHubLocalRepositoryPath != null) |
|||
{ |
|||
Logger.LogInformation("GitHub Local Repository Path: " + gitHubLocalRepositoryPath); |
|||
} |
|||
|
|||
commandLineArgs.Options.Add(CliConsts.Command, commandLineArgs.Command); |
|||
|
|||
var result = await ModuleProjectBuilder.BuildAsync( |
|||
new ProjectBuildArgs( |
|||
SolutionName.Parse(commandLineArgs.Target), |
|||
commandLineArgs.Target, |
|||
version, |
|||
DatabaseProvider.NotSpecified, |
|||
UiFramework.NotSpecified, |
|||
gitHubLocalRepositoryPath, |
|||
commandLineArgs.Options |
|||
) |
|||
); |
|||
|
|||
using (var templateFileStream = new MemoryStream(result.ZipContent)) |
|||
{ |
|||
using (var zipInputStream = new ZipInputStream(templateFileStream)) |
|||
{ |
|||
var zipEntry = zipInputStream.GetNextEntry(); |
|||
while (zipEntry != null) |
|||
{ |
|||
var fullZipToPath = Path.Combine(outputFolder, zipEntry.Name); |
|||
var directoryName = Path.GetDirectoryName(fullZipToPath); |
|||
|
|||
if (!string.IsNullOrEmpty(directoryName)) |
|||
{ |
|||
Directory.CreateDirectory(directoryName); |
|||
} |
|||
|
|||
var fileName = Path.GetFileName(fullZipToPath); |
|||
if (fileName.Length == 0) |
|||
{ |
|||
zipEntry = zipInputStream.GetNextEntry(); |
|||
continue; |
|||
} |
|||
|
|||
var buffer = new byte[4096]; // 4K is optimum
|
|||
using (var streamWriter = File.Create(fullZipToPath)) |
|||
{ |
|||
StreamUtils.Copy(zipInputStream, streamWriter, buffer); |
|||
} |
|||
|
|||
zipEntry = zipInputStream.GetNextEntry(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
Logger.LogInformation($"'{commandLineArgs.Target}' has been successfully downloaded to '{outputFolder}'"); |
|||
} |
|||
|
|||
public string GetUsageInfo() |
|||
{ |
|||
var sb = new StringBuilder(); |
|||
|
|||
sb.AppendLine(""); |
|||
sb.AppendLine("Usage:"); |
|||
sb.AppendLine(""); |
|||
sb.AppendLine(" abp get-source <module-name> [options]"); |
|||
sb.AppendLine(""); |
|||
sb.AppendLine("Options:"); |
|||
sb.AppendLine(""); |
|||
sb.AppendLine("-o|--output-folder <output-folder> (default: current folder)"); |
|||
sb.AppendLine("-v|--version <version> (default: latest version)"); |
|||
sb.AppendLine(""); |
|||
sb.AppendLine("Examples:"); |
|||
sb.AppendLine(""); |
|||
sb.AppendLine(" abp get-source Volo.Blogging"); |
|||
sb.AppendLine(" abp get-source Volo.Blogging -o d:\\my-project"); |
|||
sb.AppendLine(""); |
|||
sb.AppendLine("See the documentation for more info: https://docs.abp.io/en/abp/latest/CLI"); |
|||
|
|||
return sb.ToString(); |
|||
} |
|||
|
|||
public string GetShortDescription() |
|||
{ |
|||
return "Downloads the source code of the specified module."; |
|||
} |
|||
|
|||
public static class Options |
|||
{ |
|||
public static class OutputFolder |
|||
{ |
|||
public const string Short = "o"; |
|||
public const string Long = "output-folder"; |
|||
} |
|||
|
|||
public static class GitHubLocalRepositoryPath |
|||
{ |
|||
public const string Long = "abp-path"; |
|||
} |
|||
|
|||
public static class Version |
|||
{ |
|||
public const string Short = "v"; |
|||
public const string Long = "version"; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
namespace Volo.Abp.Cli.ProjectBuilding.Building |
|||
{ |
|||
public class ModuleInfo |
|||
{ |
|||
public string Name { get; set; } |
|||
|
|||
public string Namespace { get; set; } |
|||
|
|||
public string DocumentUrl { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
using Volo.Abp.Cli.ProjectBuilding.Building.Steps; |
|||
|
|||
namespace Volo.Abp.Cli.ProjectBuilding.Building |
|||
{ |
|||
public static class ModuleProjectBuildPipelineBuilder |
|||
{ |
|||
public static ProjectBuildPipeline Build(ProjectBuildContext context) |
|||
{ |
|||
var pipeline = new ProjectBuildPipeline(context); |
|||
|
|||
pipeline.Steps.Add(new FileEntryListReadStep()); |
|||
pipeline.Steps.Add(new ProjectReferenceReplaceStep()); |
|||
pipeline.Steps.Add(new ReplaceCommonPropsStep()); |
|||
pipeline.Steps.Add(new CreateProjectResultZipStep()); |
|||
|
|||
return pipeline; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,96 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.Text.RegularExpressions; |
|||
using System.Xml; |
|||
using Volo.Abp.Cli.ProjectBuilding.Files; |
|||
|
|||
namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps |
|||
{ |
|||
public class ReplaceCommonPropsStep : ProjectBuildPipelineStep |
|||
{ |
|||
public override void Execute(ProjectBuildContext context) |
|||
{ |
|||
new CommonPropsReplacer(context.Files).Run(); |
|||
} |
|||
|
|||
private class CommonPropsReplacer |
|||
{ |
|||
private readonly List<FileEntry> _entries; |
|||
|
|||
public CommonPropsReplacer( |
|||
List<FileEntry> entries) |
|||
{ |
|||
_entries = entries; |
|||
} |
|||
|
|||
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 XmlDocument() { PreserveWhitespace = true }; |
|||
|
|||
doc.Load(GenerateStreamFromString(content)); |
|||
|
|||
return ProcessReferenceNodes(doc, content); |
|||
} |
|||
|
|||
private string ProcessReferenceNodes(XmlDocument doc, string content) |
|||
{ |
|||
Check.NotNull(content, nameof(content)); |
|||
|
|||
var importNodes = doc.SelectNodes("/Project/Import[@Project]"); |
|||
|
|||
if (importNodes == null) |
|||
{ |
|||
return doc.OuterXml; |
|||
} |
|||
|
|||
foreach (XmlNode node in importNodes) |
|||
{ |
|||
if (!(node.Attributes?["Project"]?.Value?.EndsWith("\\common.props") ?? false)) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
node.ParentNode?.RemoveChild(node); |
|||
} |
|||
|
|||
var propertyGroupNodes = doc.SelectNodes("/Project/PropertyGroup"); |
|||
|
|||
if (propertyGroupNodes == null || propertyGroupNodes.Count < 1) |
|||
{ |
|||
return doc.OuterXml; |
|||
} |
|||
|
|||
var firstPropertyGroupNode = propertyGroupNodes.Item(0); |
|||
var langNode = doc.CreateElement("LangVersion"); |
|||
langNode.InnerText = "latest"; |
|||
firstPropertyGroupNode?.PrependChild(langNode); |
|||
|
|||
return doc.OuterXml; |
|||
} |
|||
|
|||
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,10 @@ |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Cli.ProjectBuilding.Building; |
|||
|
|||
namespace Volo.Abp.Cli.ProjectBuilding |
|||
{ |
|||
public interface IModuleInfoProvider |
|||
{ |
|||
Task<ModuleInfo> GetAsync(string name); |
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
using System.Net.Http; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Volo.Abp.Cli.ProjectBuilding |
|||
{ |
|||
public interface IRemoteServiceExceptionHandler |
|||
{ |
|||
Task EnsureSuccessfulHttpResponseAsync(HttpResponseMessage responseMessage); |
|||
|
|||
Task<string> GetAbpRemoteServiceErrorAsync(HttpResponseMessage responseMessage); |
|||
} |
|||
} |
|||
@ -0,0 +1,58 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Json; |
|||
using Volo.Abp.Cli.Http; |
|||
using Volo.Abp.Cli.ProjectBuilding.Building; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Threading; |
|||
|
|||
namespace Volo.Abp.Cli.ProjectBuilding |
|||
{ |
|||
public class ModuleInfoProvider : IModuleInfoProvider, ITransientDependency |
|||
{ |
|||
public IJsonSerializer JsonSerializer { get; } |
|||
public ICancellationTokenProvider CancellationTokenProvider { get; } |
|||
public IRemoteServiceExceptionHandler RemoteServiceExceptionHandler { get; } |
|||
|
|||
public ModuleInfoProvider( |
|||
IJsonSerializer jsonSerializer, |
|||
ICancellationTokenProvider cancellationTokenProvider, |
|||
IRemoteServiceExceptionHandler remoteServiceExceptionHandler) |
|||
{ |
|||
JsonSerializer = jsonSerializer; |
|||
CancellationTokenProvider = cancellationTokenProvider; |
|||
RemoteServiceExceptionHandler = remoteServiceExceptionHandler; |
|||
} |
|||
|
|||
public async Task<ModuleInfo> GetAsync(string name) |
|||
{ |
|||
var moduleList = await GetModuleListAsync(); |
|||
|
|||
var module = moduleList.FirstOrDefault(m => m.Name == name); |
|||
|
|||
if (module == null) |
|||
{ |
|||
throw new Exception("Module not found!"); |
|||
} |
|||
|
|||
return module; |
|||
} |
|||
|
|||
private async Task<List<ModuleInfo>> GetModuleListAsync() |
|||
{ |
|||
using (var client = new CliHttpClient()) |
|||
{ |
|||
var responseMessage = await client.GetAsync( |
|||
$"{CliUrls.WwwAbpIo}api/download/modules/", |
|||
CancellationTokenProvider.Token |
|||
); |
|||
|
|||
await RemoteServiceExceptionHandler.EnsureSuccessfulHttpResponseAsync(responseMessage); |
|||
var result = await responseMessage.Content.ReadAsStringAsync(); |
|||
return JsonSerializer.Deserialize<List<ModuleInfo>>(result); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,109 @@ |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Logging.Abstractions; |
|||
using Microsoft.Extensions.Options; |
|||
using System; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Cli.Commands; |
|||
using Volo.Abp.Cli.Licensing; |
|||
using Volo.Abp.Cli.ProjectBuilding.Analyticses; |
|||
using Volo.Abp.Cli.ProjectBuilding.Building; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Json; |
|||
|
|||
namespace Volo.Abp.Cli.ProjectBuilding |
|||
{ |
|||
public class ModuleProjectBuilder : IProjectBuilder, ITransientDependency |
|||
{ |
|||
public ILogger<ModuleProjectBuilder> Logger { get; set; } |
|||
|
|||
protected ISourceCodeStore SourceCodeStore { get; } |
|||
protected IModuleInfoProvider ModuleInfoProvider { get; } |
|||
protected ICliAnalyticsCollect CliAnalyticsCollect { get; } |
|||
protected CliOptions Options { get; } |
|||
protected IJsonSerializer JsonSerializer { get; } |
|||
protected IApiKeyService ApiKeyService { get; } |
|||
|
|||
public ModuleProjectBuilder(ISourceCodeStore sourceCodeStore, |
|||
IModuleInfoProvider moduleInfoProvider, |
|||
ICliAnalyticsCollect cliAnalyticsCollect, |
|||
IOptions<CliOptions> options, |
|||
IJsonSerializer jsonSerializer, |
|||
IApiKeyService apiKeyService) |
|||
{ |
|||
SourceCodeStore = sourceCodeStore; |
|||
ModuleInfoProvider = moduleInfoProvider; |
|||
CliAnalyticsCollect = cliAnalyticsCollect; |
|||
Options = options.Value; |
|||
JsonSerializer = jsonSerializer; |
|||
ApiKeyService = apiKeyService; |
|||
|
|||
Logger = NullLogger<ModuleProjectBuilder>.Instance; |
|||
} |
|||
|
|||
public async Task<ProjectBuildResult> BuildAsync(ProjectBuildArgs args) |
|||
{ |
|||
var moduleInfo = await GetModuleInfoAsync(args); |
|||
|
|||
var templateFile = await SourceCodeStore.GetAsync( |
|||
args.TemplateName, |
|||
SourceCodeTypes.Module, |
|||
args.Version |
|||
); |
|||
|
|||
var apiKeyResult = await ApiKeyService.GetApiKeyOrNullAsync(); |
|||
if (apiKeyResult?.ApiKey != null) |
|||
{ |
|||
args.ExtraProperties["api-key"] = apiKeyResult.ApiKey; |
|||
} |
|||
|
|||
if (apiKeyResult?.LicenseCode != null) |
|||
{ |
|||
args.ExtraProperties["license-code"] = apiKeyResult.LicenseCode; |
|||
} |
|||
|
|||
var context = new ProjectBuildContext( |
|||
null, |
|||
moduleInfo, |
|||
templateFile, |
|||
args |
|||
); |
|||
|
|||
ModuleProjectBuildPipelineBuilder.Build(context).Execute(); |
|||
|
|||
if (!moduleInfo.DocumentUrl.IsNullOrEmpty()) |
|||
{ |
|||
Logger.LogInformation("Check out the documents at " + moduleInfo.DocumentUrl); |
|||
} |
|||
|
|||
// Exclude unwanted or known options.
|
|||
var options = args.ExtraProperties |
|||
.Where(x => !x.Key.Equals(CliConsts.Command, StringComparison.InvariantCultureIgnoreCase)) |
|||
.Where(x => !x.Key.Equals(NewCommand.Options.OutputFolder.Long, StringComparison.InvariantCultureIgnoreCase) && |
|||
!x.Key.Equals(NewCommand.Options.OutputFolder.Short, StringComparison.InvariantCultureIgnoreCase)) |
|||
.Where(x => !x.Key.Equals(NewCommand.Options.Version.Long, StringComparison.InvariantCultureIgnoreCase) && |
|||
!x.Key.Equals(NewCommand.Options.Version.Short, StringComparison.InvariantCultureIgnoreCase)) |
|||
.Select(x => x.Key).ToList(); |
|||
|
|||
await CliAnalyticsCollect.CollectAsync(new CliAnalyticsCollectInputDto |
|||
{ |
|||
Tool = Options.ToolName, |
|||
Command = args.ExtraProperties.ContainsKey(CliConsts.Command) ? args.ExtraProperties[CliConsts.Command] : "", |
|||
DatabaseProvider = null, |
|||
IsTiered = null, |
|||
UiFramework = null, |
|||
Options = JsonSerializer.Serialize(options), |
|||
ProjectName = null, |
|||
TemplateName = args.TemplateName, |
|||
TemplateVersion = templateFile.Version |
|||
}); |
|||
|
|||
return new ProjectBuildResult(context.Result.ZipContent, args.TemplateName); |
|||
} |
|||
|
|||
private async Task<ModuleInfo> GetModuleInfoAsync(ProjectBuildArgs args) |
|||
{ |
|||
return await ModuleInfoProvider.GetAsync(args.TemplateName); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,92 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Net.Http; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using Newtonsoft.Json; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Http; |
|||
using Volo.Abp.Json; |
|||
|
|||
namespace Volo.Abp.Cli.ProjectBuilding |
|||
{ |
|||
public class RemoteServiceExceptionHandler : IRemoteServiceExceptionHandler, ITransientDependency |
|||
{ |
|||
private readonly IJsonSerializer _jsonSerializer; |
|||
|
|||
public RemoteServiceExceptionHandler(IJsonSerializer jsonSerializer) |
|||
{ |
|||
_jsonSerializer = jsonSerializer; |
|||
} |
|||
|
|||
public async Task EnsureSuccessfulHttpResponseAsync(HttpResponseMessage responseMessage) |
|||
{ |
|||
if (responseMessage == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (responseMessage.IsSuccessStatusCode) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var exceptionMessage = "Remote server returns '" + (int)responseMessage.StatusCode + "-" + responseMessage.ReasonPhrase + "'. "; |
|||
|
|||
var remoteServiceErrorMessage = await GetAbpRemoteServiceErrorAsync(responseMessage); |
|||
if (remoteServiceErrorMessage != null) |
|||
{ |
|||
exceptionMessage += remoteServiceErrorMessage; |
|||
} |
|||
|
|||
throw new Exception(exceptionMessage); |
|||
} |
|||
|
|||
public async Task<string> GetAbpRemoteServiceErrorAsync(HttpResponseMessage responseMessage) |
|||
{ |
|||
var errorResult = _jsonSerializer.Deserialize<RemoteServiceErrorResponse> |
|||
( |
|||
await responseMessage.Content.ReadAsStringAsync() |
|||
); |
|||
|
|||
if (errorResult?.Error == null) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
var sbError = new StringBuilder(); |
|||
if (!string.IsNullOrWhiteSpace(errorResult.Error.Code)) |
|||
{ |
|||
sbError.Append("Code: " + errorResult.Error.Code); |
|||
} |
|||
|
|||
if (!string.IsNullOrWhiteSpace(errorResult.Error.Message)) |
|||
{ |
|||
if (sbError.Length > 0) |
|||
{ |
|||
sbError.Append(" - "); |
|||
} |
|||
|
|||
sbError.Append("Message: " + errorResult.Error.Message); |
|||
} |
|||
|
|||
if (errorResult.Error.ValidationErrors != null && errorResult.Error.ValidationErrors.Any()) |
|||
{ |
|||
if (sbError.Length > 0) |
|||
{ |
|||
sbError.Append(" - "); |
|||
} |
|||
|
|||
sbError.AppendLine("Validation Errors: "); |
|||
for (var i = 0; i < errorResult.Error.ValidationErrors.Length; i++) |
|||
{ |
|||
var validationError = errorResult.Error.ValidationErrors[i]; |
|||
sbError.AppendLine("Validation error #" + i + ": " + validationError.Message + " - Members: " + validationError.Members.JoinAsString(", ") + "."); |
|||
} |
|||
} |
|||
|
|||
return sbError.ToString(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
namespace Volo.Abp.Cli.ProjectBuilding |
|||
{ |
|||
public static class SourceCodeTypes |
|||
{ |
|||
public const string Template = "template"; |
|||
|
|||
public const string Module = "module"; |
|||
} |
|||
} |
|||
Loading…
Reference in new issue