diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json index fff1876a67..0edbb7a5dc 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json @@ -196,6 +196,7 @@ "MultipleDBOptionsExplanation": "The framework can work with any data source, while the following providers are officially developed and supported;", "SelectLanguage": "Select language", "LatestArticleOnCommunity": "Latest Article on ABP Community", - "Register": "Register" + "Register": "Register", + "IsDownloadable": "Is downloadable" } } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddPackageCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddPackageCommand.cs index 36530a7979..96740fa418 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddPackageCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddPackageCommand.cs @@ -35,11 +35,16 @@ namespace Volo.Abp.Cli.Commands } var version = commandLineArgs.Options.GetOrNull(Options.Version.Short, Options.Version.Long); + var withSourceCode =commandLineArgs.Options.ContainsKey(Options.SourceCode.Long); + var addSourceCodeToSolutionFile = withSourceCode && commandLineArgs.Options.ContainsKey("add-to-solution-file"); await ProjectNugetPackageAdder.AddAsync( GetProjectFile(commandLineArgs), commandLineArgs.Target, - version + version, + true, + withSourceCode, + addSourceCodeToSolutionFile ); } @@ -128,6 +133,11 @@ namespace Volo.Abp.Cli.Commands public const string Short = "v"; public const string Long = "version"; } + + public static class SourceCode + { + public const string Long = "with-source-code"; + } } } } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GetSourceCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GetSourceCommand.cs index 5bf5041c22..281183bfb5 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GetSourceCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GetSourceCommand.cs @@ -59,7 +59,7 @@ namespace Volo.Abp.Cli.Commands commandLineArgs.Options.Add(CliConsts.Command, commandLineArgs.Command); - await _sourceCodeDownloadService.DownloadAsync( + await _sourceCodeDownloadService.DownloadModuleAsync( commandLineArgs.Target, outputFolder, version, gitHubAbpLocalRepositoryPath, gitHubVoloLocalRepositoryPath, commandLineArgs.Options); } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/SourceCodeDownloadService.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/SourceCodeDownloadService.cs index 6306619e56..ad1c5cb7fe 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/SourceCodeDownloadService.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/SourceCodeDownloadService.cs @@ -17,15 +17,17 @@ namespace Volo.Abp.Cli.Commands.Services public class SourceCodeDownloadService : ITransientDependency { public ModuleProjectBuilder ModuleProjectBuilder { get; } + public PackageProjectBuilder PackageProjectBuilder { get; } public ILogger Logger { get; set; } - public SourceCodeDownloadService(ModuleProjectBuilder moduleProjectBuilder) + public SourceCodeDownloadService(ModuleProjectBuilder moduleProjectBuilder, PackageProjectBuilder packageProjectBuilder) { ModuleProjectBuilder = moduleProjectBuilder; + PackageProjectBuilder = packageProjectBuilder; Logger = NullLogger.Instance; } - public async Task DownloadAsync(string moduleName, string outputFolder, string version, string gitHubAbpLocalRepositoryPath, string gitHubVoloLocalRepositoryPath, AbpCommandLineOptions options) + public async Task DownloadModuleAsync(string moduleName, string outputFolder, string version, string gitHubAbpLocalRepositoryPath, string gitHubVoloLocalRepositoryPath, AbpCommandLineOptions options) { Logger.LogInformation("Downloading source code of " + moduleName); Logger.LogInformation("Version: " + version); @@ -90,6 +92,62 @@ namespace Volo.Abp.Cli.Commands.Services Logger.LogInformation($"'{moduleName}' has been successfully downloaded to '{outputFolder}'"); } + public async Task DownloadPackageAsync(string packageName, string outputFolder, string version) + { + Logger.LogInformation("Downloading source code of " + packageName); + Logger.LogInformation("Version: " + version); + Logger.LogInformation("Output folder: " + outputFolder); + + var result = await PackageProjectBuilder.BuildAsync( + new ProjectBuildArgs( + SolutionName.Parse(packageName), + packageName, + version + ) + ); + + using (var templateFileStream = new MemoryStream(result.ZipContent)) + { + using (var zipInputStream = new ZipInputStream(templateFileStream)) + { + var zipEntry = zipInputStream.GetNextEntry(); + while (zipEntry != null) + { + if (IsAngularTestFile(zipEntry.Name)) + { + zipEntry = zipInputStream.GetNextEntry(); + continue; + } + + 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($"'{packageName}' has been successfully downloaded to '{outputFolder}'"); + } + private bool IsAngularTestFile(string zipEntryName) { if (string.IsNullOrEmpty(zipEntryName)) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs index bebe44542d..29ea93bd3d 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs @@ -159,6 +159,11 @@ namespace Volo.Abp.Cli.ProjectBuilding private async Task GetTemplateNugetVersionAsync(string name, string type, string version) { + if (type != SourceCodeTypes.Template) + { + return null; + } + try { var url = $"{CliUrls.WwwAbpIo}api/download/{type}/get-nuget-version/"; diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/PackageProjectBuildPipelineBuilder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/PackageProjectBuildPipelineBuilder.cs new file mode 100644 index 0000000000..79245dfaec --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/PackageProjectBuildPipelineBuilder.cs @@ -0,0 +1,21 @@ +using Volo.Abp.Cli.ProjectBuilding.Building.Steps; +using Volo.Abp.Cli.ProjectBuilding.Templates; + +namespace Volo.Abp.Cli.ProjectBuilding.Building +{ + public static class PackageProjectBuildPipelineBuilder + { + 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 ReplaceConfigureAwaitPropsStep()); + pipeline.Steps.Add(new CreateProjectResultZipStep()); + + return pipeline; + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ProjectBuildContext.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ProjectBuildContext.cs index 8b5522165f..1b5542318d 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ProjectBuildContext.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ProjectBuildContext.cs @@ -1,5 +1,6 @@ using JetBrains.Annotations; using Volo.Abp.Cli.ProjectBuilding.Files; +using Volo.Abp.Cli.ProjectModification; namespace Volo.Abp.Cli.ProjectBuilding.Building { @@ -15,22 +16,26 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building public ModuleInfo Module { get; } + public NugetPackageInfo Package { get; } + public FileEntryList Files { get; set; } public ProjectResult Result { get; set; } - + public ProjectBuildContext( TemplateInfo template, ModuleInfo module, + NugetPackageInfo package, [NotNull] TemplateFile templateFile, [NotNull] ProjectBuildArgs buildArgs) { Template = template; Module = module; + Package = package; TemplateFile = Check.NotNull(templateFile, nameof(templateFile)); BuildArgs = Check.NotNull(buildArgs, nameof(buildArgs)); Result = new ProjectResult(); } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/INugetPackageInfoProvider.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/INugetPackageInfoProvider.cs new file mode 100644 index 0000000000..ddac517f3e --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/INugetPackageInfoProvider.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using Volo.Abp.Cli.ProjectModification; + +namespace Volo.Abp.Cli.ProjectBuilding +{ + public interface INugetPackageInfoProvider + { + Task GetAsync(string name); + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ModuleProjectBuilder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ModuleProjectBuilder.cs index 17b9585f16..029b687015 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ModuleProjectBuilder.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ModuleProjectBuilder.cs @@ -67,6 +67,7 @@ namespace Volo.Abp.Cli.ProjectBuilding var context = new ProjectBuildContext( null, moduleInfo, + null, templateFile, args ); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/NugetPackageInfoProvider.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/NugetPackageInfoProvider.cs new file mode 100644 index 0000000000..06c39e66f7 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/NugetPackageInfoProvider.cs @@ -0,0 +1,62 @@ +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.ProjectModification; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Threading; + +namespace Volo.Abp.Cli.ProjectBuilding +{ + public class NugetPackageInfoProvider : INugetPackageInfoProvider, ITransientDependency + { + public IJsonSerializer JsonSerializer { get; } + public ICancellationTokenProvider CancellationTokenProvider { get; } + public IRemoteServiceExceptionHandler RemoteServiceExceptionHandler { get; } + + private readonly CliHttpClientFactory _cliHttpClientFactory; + + public NugetPackageInfoProvider( + IJsonSerializer jsonSerializer, + ICancellationTokenProvider cancellationTokenProvider, + IRemoteServiceExceptionHandler remoteServiceExceptionHandler, + CliHttpClientFactory cliHttpClientFactory) + { + JsonSerializer = jsonSerializer; + CancellationTokenProvider = cancellationTokenProvider; + RemoteServiceExceptionHandler = remoteServiceExceptionHandler; + _cliHttpClientFactory = cliHttpClientFactory; + } + + public async Task GetAsync(string name) + { + var packageList = await GetPackageListInternalAsync(); + + var package = packageList.FirstOrDefault(m => m.Name == name); + + if (package == null) + { + throw new Exception("Package is not found or downloadable!"); + } + + return package; + } + + private async Task> GetPackageListInternalAsync() + { + var client = _cliHttpClientFactory.CreateClient(); + + using (var responseMessage = await client.GetAsync( + $"{CliUrls.WwwAbpIo}api/download/packages/", + CancellationTokenProvider.Token + )) + { + await RemoteServiceExceptionHandler.EnsureSuccessfulHttpResponseAsync(responseMessage); + var result = await responseMessage.Content.ReadAsStringAsync(); + return JsonSerializer.Deserialize>(result); + } + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/PackageProjectBuilder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/PackageProjectBuilder.cs new file mode 100644 index 0000000000..04518332a4 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/PackageProjectBuilder.cs @@ -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.Cli.ProjectModification; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Json; + +namespace Volo.Abp.Cli.ProjectBuilding +{ + public class PackageProjectBuilder : IProjectBuilder, ITransientDependency + { + public ILogger Logger { get; set; } + protected ISourceCodeStore SourceCodeStore { get; } + protected INugetPackageInfoProvider NugetPackageInfoProvider { get; } + protected ICliAnalyticsCollect CliAnalyticsCollect { get; } + protected AbpCliOptions Options { get; } + protected IJsonSerializer JsonSerializer { get; } + protected IApiKeyService ApiKeyService { get; } + + public PackageProjectBuilder(ISourceCodeStore sourceCodeStore, + INugetPackageInfoProvider nugetPackageInfoProvider, + ICliAnalyticsCollect cliAnalyticsCollect, + IOptions options, + IJsonSerializer jsonSerializer, + IApiKeyService apiKeyService) + { + SourceCodeStore = sourceCodeStore; + NugetPackageInfoProvider = nugetPackageInfoProvider; + CliAnalyticsCollect = cliAnalyticsCollect; + Options = options.Value; + JsonSerializer = jsonSerializer; + ApiKeyService = apiKeyService; + + Logger = NullLogger.Instance; + } + + public async Task BuildAsync(ProjectBuildArgs args) + { + var packageInfo = await GetPackageInfoAsync(args); + + var templateFile = await SourceCodeStore.GetAsync( + args.TemplateName, + SourceCodeTypes.Package, + args.Version, + null, + args.ExtraProperties.ContainsKey(GetSourceCommand.Options.Preview.Long) + ); + + 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, + null, + packageInfo, + templateFile, + args + ); + + PackageProjectBuildPipelineBuilder.Build(context).Execute(); + + // 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)) + .Where(x => !x.Key.Equals(NewCommand.Options.TemplateSource.Short, StringComparison.InvariantCultureIgnoreCase) && + !x.Key.Equals(NewCommand.Options.TemplateSource.Long, 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 GetPackageInfoAsync(ProjectBuildArgs args) + { + return await NugetPackageInfoProvider.GetAsync(args.TemplateName); + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/SourceCodeTypes.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/SourceCodeTypes.cs index 5f7ad58ef7..813ffe6b54 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/SourceCodeTypes.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/SourceCodeTypes.cs @@ -5,5 +5,7 @@ public const string Template = "template"; public const string Module = "module"; + + public const string Package = "package"; } } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/TemplateProjectBuilder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/TemplateProjectBuilder.cs index cbbb094d11..5823c3c488 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/TemplateProjectBuilder.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/TemplateProjectBuilder.cs @@ -105,6 +105,7 @@ namespace Volo.Abp.Cli.ProjectBuilding var context = new ProjectBuildContext( templateInfo, null, + null, templateFile, args ); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ProjectNugetPackageAdder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ProjectNugetPackageAdder.cs index f2b8160d53..5fb5564909 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ProjectNugetPackageAdder.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/ProjectNugetPackageAdder.cs @@ -9,6 +9,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Volo.Abp.Cli.Args; using Volo.Abp.Cli.Commands; +using Volo.Abp.Cli.Commands.Services; using Volo.Abp.Cli.Http; using Volo.Abp.Cli.ProjectBuilding; using Volo.Abp.Cli.Utils; @@ -22,6 +23,8 @@ namespace Volo.Abp.Cli.ProjectModification { public ILogger Logger { get; set; } public BundleCommand BundleCommand { get; } + public SourceCodeDownloadService SourceCodeDownloadService { get; } + public SolutionFileModifier SolutionFileModifier { get; } protected IJsonSerializer JsonSerializer { get; } protected ProjectNpmPackageAdder NpmPackageAdder { get; } @@ -38,7 +41,9 @@ namespace Volo.Abp.Cli.ProjectModification ModuleClassDependcyAdder moduleClassDependcyAdder, IRemoteServiceExceptionHandler remoteServiceExceptionHandler, BundleCommand bundleCommand, - CliHttpClientFactory cliHttpClientFactory) + CliHttpClientFactory cliHttpClientFactory, + SourceCodeDownloadService sourceCodeDownloadService, + SolutionFileModifier solutionFileModifier) { JsonSerializer = jsonSerializer; NpmPackageAdder = npmPackageAdder; @@ -46,21 +51,151 @@ namespace Volo.Abp.Cli.ProjectModification ModuleClassDependcyAdder = moduleClassDependcyAdder; RemoteServiceExceptionHandler = remoteServiceExceptionHandler; BundleCommand = bundleCommand; + SourceCodeDownloadService = sourceCodeDownloadService; + SolutionFileModifier = solutionFileModifier; _cliHttpClientFactory = cliHttpClientFactory; Logger = NullLogger.Instance; } - public async Task AddAsync(string projectFile, string packageName, string version = null) + public async Task AddAsync( + string projectFile, + string packageName, + string version = null, + bool useDotnetCliToInstall = true, + bool withSourceCode = false, + bool addSourceCodeToSolutionFile = false) { await AddAsync( projectFile, await FindNugetPackageInfoAsync(packageName), + version, + useDotnetCliToInstall, + withSourceCode, + addSourceCodeToSolutionFile + ); + } + + public async Task AddAsync( + string projectFile, + NugetPackageInfo package, + string version = null, + bool useDotnetCliToInstall = true, + bool withSourceCode = false, + bool addSourceCodeToSolutionFile = false) + { + await AddAsPackageReference(projectFile, package, version, useDotnetCliToInstall); + + if (withSourceCode) + { + await AddSourceCode(projectFile, package, version); + await ConvertPackageReferenceToProjectReference(projectFile, package); + + if (addSourceCodeToSolutionFile) + { + await SolutionFileModifier.AddPackageToSolutionFileAsync(package, FindSolutionFile(projectFile)); + } + } + } + + private async Task ConvertPackageReferenceToProjectReference(string projectFile, NugetPackageInfo package) + { + var content = File.ReadAllText(projectFile); + var doc = new XmlDocument() {PreserveWhitespace = true}; + + doc.Load(StreamHelper.GenerateStreamFromString(content)); + + var nodes = doc.SelectNodes( + $"/Project/ItemGroup/PackageReference[starts-with(@Include, '{package.Name}')]"); + + if (nodes == null) + { + return; + } + + var downloadedProjectPath = FindRelativeFolderToDownloadPackage(projectFile, package); + var oldNodeIncludeValue = nodes[0]?.Attributes?["Include"]?.Value; + + if (package.Name == oldNodeIncludeValue) + { + var referenceProjectPath = $"{downloadedProjectPath}\\{package.Name}.csproj"; + + var newNode = doc.CreateElement("ProjectReference"); + var includeAttr = doc.CreateAttribute("Include"); + includeAttr.Value = referenceProjectPath; + newNode.Attributes.Append(includeAttr); + + nodes[0]?.ParentNode?.ReplaceChild(newNode, nodes[0]); + } + + File.WriteAllText(projectFile, doc.OuterXml); + } + + private async Task AddSourceCode(string projectFile, NugetPackageInfo package, string version = null) + { + var targetFolder = FindFolderToDownloadPackage(projectFile, package); + + if (Directory.Exists(targetFolder)) + { + Directory.Delete(targetFolder, true); + } + + await DownloadSourceCode(targetFolder, package, version); + } + + private string FindFolderToDownloadPackage(string projectFile, NugetPackageInfo package) + { + return Path.Combine(FindSolutionFolder(projectFile), "packages", package.Name); + } + + private string FindRelativeFolderToDownloadPackage(string projectFile, NugetPackageInfo package) + { + var folder = Path.Combine(FindSolutionFolder(projectFile), "packages", package.Name); + + return new Uri(projectFile).MakeRelativeUri(new Uri(folder)).ToString().Replace("/", "\\"); + } + + private async Task DownloadSourceCode(string targetFolder, NugetPackageInfo package, string version = null) + { + await SourceCodeDownloadService.DownloadPackageAsync( + package.Name, + targetFolder, version ); } - public async Task AddAsync(string projectFile, NugetPackageInfo package, string version = null, - bool useDotnetCliToInstall = true) + private string FindSolutionFile(string projectFile) + { + var folder = FindSolutionFolder(projectFile); + + return Directory.GetFiles(folder, "*.sln", SearchOption.TopDirectoryOnly).FirstOrDefault(); + } + + private string FindSolutionFolder(string projectFile) + { + var targetFolder = Path.GetDirectoryName(projectFile); + + do + { + if (Directory.GetParent(targetFolder) != null) + { + targetFolder = Directory.GetParent(targetFolder).FullName; + } + else + { + return Path.GetDirectoryName(projectFile); + } + + if (Directory.GetFiles(targetFolder, "*.sln", SearchOption.TopDirectoryOnly).Any()) + { + break; + } + } while (targetFolder != null); + + return targetFolder; + } + + private async Task AddAsPackageReference(string projectFile, NugetPackageInfo package, string version, + bool useDotnetCliToInstall) { var projectFileContent = File.ReadAllText(projectFile); @@ -105,7 +240,9 @@ namespace Volo.Abp.Cli.ProjectModification ModuleClassDependcyAdder.Add(moduleFiles.First(), package.ModuleClass); } - if (useDotnetCliToInstall && (package.Target == NuGetPackageTarget.Blazor || package.Target == NuGetPackageTarget.BlazorServer || package.Target == NuGetPackageTarget.BlazorWebAssembly)) + if (useDotnetCliToInstall && (package.Target == NuGetPackageTarget.Blazor || + package.Target == NuGetPackageTarget.BlazorServer || + package.Target == NuGetPackageTarget.BlazorWebAssembly)) { await RunBundleForBlazorAsync(projectFile); } @@ -125,7 +262,7 @@ namespace Volo.Abp.Cli.ProjectModification private Task AddToCsprojManuallyAsync(string projectFile, NugetPackageInfo package, string version = null) { var projectFileContent = File.ReadAllText(projectFile); - var doc = new XmlDocument() { PreserveWhitespace = true }; + var doc = new XmlDocument() {PreserveWhitespace = true}; doc.Load(StreamHelper.GenerateStreamFromString(projectFileContent)); var itemGroupNodes = doc.SelectNodes("/Project/ItemGroup"); @@ -166,7 +303,7 @@ namespace Volo.Abp.Cli.ProjectModification private string GetAbpVersionOrNull(string projectFileContent) { - var doc = new XmlDocument() { PreserveWhitespace = true }; + var doc = new XmlDocument() {PreserveWhitespace = true}; doc.Load(StreamHelper.GenerateStreamFromString(projectFileContent)); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionFileModifier.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionFileModifier.cs index 9eda6efa10..ac40e8fb3b 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionFileModifier.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionFileModifier.cs @@ -15,7 +15,8 @@ namespace Volo.Abp.Cli.ProjectModification var solutionFileContent = File.ReadAllText(solutionFile); solutionFileContent.NormalizeLineEndings(); var lines = solutionFileContent.Split(new[] {Environment.NewLine, "\n"}, StringSplitOptions.None); - File.WriteAllText(solutionFile, RemoveProject(lines.ToList(), projectName).JoinAsString(Environment.NewLine)); + File.WriteAllText(solutionFile, + RemoveProject(lines.ToList(), projectName).JoinAsString(Environment.NewLine)); } public async Task AddModuleToSolutionFileAsync(ModuleWithMastersInfo module, string solutionFile) @@ -23,6 +24,49 @@ namespace Volo.Abp.Cli.ProjectModification await AddModuleAsync(module, solutionFile); } + public async Task AddPackageToSolutionFileAsync(NugetPackageInfo package, string solutionFile) + { + await AddPackageAsync(package, solutionFile); + } + + private async Task AddPackageAsync(NugetPackageInfo package, string solutionFile) + { + var srcFolderId = await AddNewFolderAndGetIdOrGetExistingIdAsync(solutionFile, "src"); + + var file = File.ReadAllText(solutionFile); + var lines = file.Split(Environment.NewLine).ToList(); + + if (lines.Any(l => l.Contains($"\"{package.Name}\""))) + { + return; + } + + var projectGuid = Guid.NewGuid().ToString(); + + var newProjectLine = "Project(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"" + package.Name + "\"," + + " \"packages\\" + package.Name + "\\" + + "\\" + package.Name + ".csproj\", \"{" + projectGuid + "}\"" + + Environment.NewLine + "EndProject"; + + lines.InsertAfter(l => l.Trim().Equals("EndProject"), newProjectLine); + + var newPostSolutionLine = + " {" + projectGuid + "}.Debug|Any CPU.ActiveCfg = Debug|Any CPU" + Environment.NewLine + + " {" + projectGuid + "}.Debug|Any CPU.Build.0 = Debug|Any CPU" + Environment.NewLine + + " {" + projectGuid + "}.Release|Any CPU.ActiveCfg = Release|Any CPU" + Environment.NewLine + + " {" + projectGuid + "}.Release|Any CPU.Build.0 = Release|Any CPU"; + + lines.InsertAfter(l => l.Contains("GlobalSection") && l.Contains("ProjectConfigurationPlatforms"), + newPostSolutionLine); + + var newPreSolutionLine = + " {" + projectGuid + "} = {" + srcFolderId + "}"; + + lines.InsertAfter(l => l.Contains("GlobalSection") && l.Contains("NestedProjects"), newPreSolutionLine); + + File.WriteAllText(solutionFile, string.Join(Environment.NewLine, lines)); + } + private List RemoveProject(List solutionFileLines, string projectName) { var projectKey = FindProjectKey(solutionFileLines, projectName); @@ -63,7 +107,8 @@ namespace Volo.Abp.Cli.ProjectModification { var curlyBracketStartIndex = solutionFileLine.LastIndexOf("{", StringComparison.OrdinalIgnoreCase); var curlyBracketEndIndex = solutionFileLine.LastIndexOf("}", StringComparison.OrdinalIgnoreCase); - return solutionFileLine.Substring(curlyBracketStartIndex + 1, curlyBracketEndIndex - curlyBracketStartIndex - 1); + return solutionFileLine.Substring(curlyBracketStartIndex + 1, + curlyBracketEndIndex - curlyBracketStartIndex - 1); } } @@ -72,13 +117,14 @@ namespace Volo.Abp.Cli.ProjectModification private async Task AddModuleAsync(ModuleWithMastersInfo module, string solutionFile) { - var srcModuleFolderId = await AddNewFolderAndGetIdOrGetExistingId(solutionFile, module.Name, await AddNewFolderAndGetIdOrGetExistingId(solutionFile, "modules")); - var testModuleFolderId = await AddNewFolderAndGetIdOrGetExistingId(solutionFile, module.Name + ".Tests", await AddNewFolderAndGetIdOrGetExistingId(solutionFile, "test")); + var srcModuleFolderId = await AddNewFolderAndGetIdOrGetExistingIdAsync(solutionFile, module.Name, + await AddNewFolderAndGetIdOrGetExistingIdAsync(solutionFile, "modules")); + var testModuleFolderId = await AddNewFolderAndGetIdOrGetExistingIdAsync(solutionFile, module.Name + ".Tests", + await AddNewFolderAndGetIdOrGetExistingIdAsync(solutionFile, "test")); var file = File.ReadAllText(solutionFile); var lines = file.Split(Environment.NewLine).ToList(); - var projectsUnderModule = Directory.GetFiles( Path.Combine(Path.GetDirectoryName(solutionFile), "modules", module.Name), "*.csproj", @@ -92,7 +138,7 @@ namespace Volo.Abp.Cli.ProjectModification foreach (var projectPath in projectsUnderModule) { var parentFolderId = projectsUnderTest.Contains(projectPath) ? testModuleFolderId : srcModuleFolderId; - var projectId = Path.GetFileName(projectPath).Replace(".csproj",""); + var projectId = Path.GetFileName(projectPath).Replace(".csproj", ""); var projectParentFolderInModule = projectsUnderTest.Contains(projectPath) ? "test" : "src"; if (lines.Any(l => l.Contains($"\"{projectId}\""))) @@ -103,8 +149,9 @@ namespace Volo.Abp.Cli.ProjectModification var projectGuid = Guid.NewGuid().ToString(); var newProjectLine = "Project(\"{9A19103F-16F7-4668-BE54-9A1E7A4F7556}\") = \"" + projectId + "\"," + - " \"modules\\" + module.Name + "\\"+ projectParentFolderInModule + "\\" + projectId + "\\" + projectId + ".csproj\", \"{" + projectGuid + "}\"" - + Environment.NewLine + "EndProject"; + " \"modules\\" + module.Name + "\\" + projectParentFolderInModule + "\\" + + projectId + "\\" + projectId + ".csproj\", \"{" + projectGuid + "}\"" + + Environment.NewLine + "EndProject"; lines.InsertAfter(l => l.Trim().Equals("EndProject"), newProjectLine); @@ -114,12 +161,13 @@ namespace Volo.Abp.Cli.ProjectModification " {" + projectGuid + "}.Release|Any CPU.ActiveCfg = Release|Any CPU" + Environment.NewLine + " {" + projectGuid + "}.Release|Any CPU.Build.0 = Release|Any CPU"; - lines.InsertAfter(l=>l.Contains("GlobalSection") && l.Contains("ProjectConfigurationPlatforms"), newPostSolutionLine); + lines.InsertAfter(l => l.Contains("GlobalSection") && l.Contains("ProjectConfigurationPlatforms"), + newPostSolutionLine); var newPreSolutionLine = - " {"+ projectGuid + "} = {"+ parentFolderId + "}"; + " {" + projectGuid + "} = {" + parentFolderId + "}"; - lines.InsertAfter(l=>l.Contains("GlobalSection") && l.Contains("NestedProjects"), newPreSolutionLine); + lines.InsertAfter(l => l.Contains("GlobalSection") && l.Contains("NestedProjects"), newPreSolutionLine); } File.WriteAllText(solutionFile, string.Join(Environment.NewLine, lines)); @@ -133,19 +181,21 @@ namespace Volo.Abp.Cli.ProjectModification } } - private async Task AddNewFolderAndGetIdOrGetExistingId(string solutionFile, string folderName, string parentFolderId = null) + private async Task AddNewFolderAndGetIdOrGetExistingIdAsync(string solutionFile, string folderName, + string parentFolderId = null) { var file = File.ReadAllText(solutionFile); var lines = file.Split(Environment.NewLine).ToList(); string folderId; var folderLineIndex = lines.FindIndex(l => - l.Contains("2150E333-8FDC-42A3-9474-1A3956D46DE8") && l.Contains("\""+ folderName + "\"")); + l.Contains("2150E333-8FDC-42A3-9474-1A3956D46DE8") && l.Contains("\"" + folderName + "\"")); if (folderLineIndex < 0) { folderId = Guid.NewGuid().ToString(); - var newFolderLine = "Project(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \""+ folderName + "\", \""+ folderName + "\", \"{" + folderId + "}\"" + var newFolderLine = "Project(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"" + folderName + "\", \"" + + folderName + "\", \"{" + folderId + "}\"" + Environment.NewLine + "EndProject"; lines.InsertAfter(l => l.Trim().Equals("EndProject"), newFolderLine); @@ -155,7 +205,8 @@ namespace Volo.Abp.Cli.ProjectModification var newPreSolutionLine = " {" + folderId + "} = {" + parentFolderId + "}"; - lines.InsertAfter(l => l.Contains("GlobalSection") && l.Contains("NestedProjects"), newPreSolutionLine); + lines.InsertAfter(l => l.Contains("GlobalSection") && l.Contains("NestedProjects"), + newPreSolutionLine); } } else diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs index 44d8c26a9f..caf47e0a87 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionModuleAdder.cs @@ -365,7 +365,7 @@ namespace Volo.Abp.Cli.ProjectModification } else { - await SourceCodeDownloadService.DownloadAsync( + await SourceCodeDownloadService.DownloadModuleAsync( module.Name, targetModuleFolder, version,