diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddModuleCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddModuleCommand.cs index 68204389f1..553f2a76c2 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddModuleCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/AddModuleCommand.cs @@ -36,7 +36,7 @@ namespace Volo.Abp.Cli.Commands ); } - private string GetSolutionFile(CommandLineArgs commandLineArgs) + protected virtual string GetSolutionFile(CommandLineArgs commandLineArgs) { var providedSolutionFile = PathHelper.NormalizePath( commandLineArgs.Options.GetOrNull( diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/ModuleInfo.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/ModuleInfo.cs new file mode 100644 index 0000000000..0221b957e5 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/ModuleInfo.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace Volo.Abp.ProjectModification +{ + public class ModuleInfo + { + public string Name { get; set; } + + public string DisplayName { get; set; } + + public List NugetPackages { get; set; } + + public List NpmPackages { get; set; } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/NpmPackageInfo.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/NpmPackageInfo.cs index 09ee9a8042..0778a4edce 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/NpmPackageInfo.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/NpmPackageInfo.cs @@ -5,7 +5,5 @@ public string Name { get; set; } public int ApplicationType { get; set; } //TODO: Enum? - - public bool IsPro { get; set; } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/NugetPackageInfo.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/NugetPackageInfo.cs index 59974f23d3..b30d447d30 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/NugetPackageInfo.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/NugetPackageInfo.cs @@ -6,7 +6,7 @@ public string ModuleClass { get; set; } - public int Target { get; set; } //TODO: Enum? + public NugetPackageTarget Target { get; set; } public NpmPackageInfo DependedNpmPackage { get; set; } } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/NugetPackageTarget.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/NugetPackageTarget.cs new file mode 100644 index 0000000000..93acba204d --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/NugetPackageTarget.cs @@ -0,0 +1,15 @@ +namespace Volo.Abp.ProjectModification +{ + public enum NugetPackageTarget : byte + { + DomainShared = 1, + Domain = 2, + ApplicationContracts = 3, + Application = 4, + HttpApi = 5, + HttpApiClient = 6, + Web = 7, + EntityFrameworkCore = 8, + MongoDB = 9 + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/ProjectNpmPackageAdder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/ProjectNpmPackageAdder.cs index e6566ce21f..8b3c5432fa 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/ProjectNpmPackageAdder.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/ProjectNpmPackageAdder.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Volo.Abp.DependencyInjection; +using Volo.Abp.IO; namespace Volo.Abp.ProjectModification { @@ -26,15 +27,16 @@ namespace Volo.Abp.ProjectModification Logger.LogInformation($"Installing '{npmPackage.Name}' package to the project '{packageJsonFilePath}'..."); - Logger.LogInformation("yarn add " + npmPackage.Name + "..."); - var procStartInfo = new ProcessStartInfo("cmd.exe", "/C yarn add " + npmPackage.Name); - procStartInfo.WindowStyle = ProcessWindowStyle.Normal; - Process.Start(procStartInfo).WaitForExit(); + using (DirectoryHelper.ChangeCurrentDirectory(directory)) + { + Logger.LogInformation("yarn add " + npmPackage.Name + "... " + Directory.GetCurrentDirectory()); + var procStartInfo = new ProcessStartInfo("cmd.exe", "/C yarn add " + npmPackage.Name); + Process.Start(procStartInfo).WaitForExit(); - Logger.LogInformation("gulp..."); - procStartInfo = new ProcessStartInfo("cmd.exe", "/C gulp"); - procStartInfo.WindowStyle = ProcessWindowStyle.Normal; - Process.Start(procStartInfo).WaitForExit(); + Logger.LogInformation("gulp... " + Directory.GetCurrentDirectory()); + procStartInfo = new ProcessStartInfo("cmd.exe", "/C gulp"); + Process.Start(procStartInfo).WaitForExit(); + } return Task.CompletedTask; } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/ProjectNugetPackageAdder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/ProjectNugetPackageAdder.cs index b6b7355f6d..821193676e 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/ProjectNugetPackageAdder.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/ProjectNugetPackageAdder.cs @@ -1,13 +1,16 @@ -using System; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net; using System.Net.Http; 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; using Volo.Abp.Json; namespace Volo.Abp.ProjectModification @@ -36,29 +39,45 @@ namespace Volo.Abp.ProjectModification public async Task AddAsync(string projectFile, string packageName) { - var packageInfo = await FindNugetPackageInfoAsync(packageName); - if (packageInfo == null) + await AddAsync( + projectFile, + await FindNugetPackageInfoAsync(packageName) + ); + } + + public async Task AddAsync(string projectFile, NugetPackageInfo package) + { + using (DirectoryHelper.ChangeCurrentDirectory(Path.GetDirectoryName(projectFile))) { - return; - } + Logger.LogInformation($"Installing '{package.Name}' package to the project '{Path.GetFileNameWithoutExtension(projectFile)}'..."); - Logger.LogInformation($"Installing '{packageName}' package to the project '{Path.GetFileNameWithoutExtension(projectFile)}'..."); - var procStartInfo = new ProcessStartInfo("dotnet", "add package " + packageName); - Process.Start(procStartInfo).WaitForExit(); + var procStartInfo = new ProcessStartInfo("dotnet", "add package " + package.Name); + Process.Start(procStartInfo).WaitForExit(); - var moduleFile = ModuleClassFinder.Find(projectFile).First(); - //TODO: Handle multiple module classes! - ModuleClassDependcyAdder.Add(moduleFile, packageInfo.ModuleClass); + var moduleFiles = ModuleClassFinder.Find(projectFile); + if (moduleFiles.Count == 0) + { + throw new CliUsageException($"Could not find a class derived from AbpModule in the project {projectFile}"); + } - if (packageInfo.DependedNpmPackage != null) - { - await NpmPackageAdder.AddAsync(Path.GetDirectoryName(projectFile), packageInfo.DependedNpmPackage); - } + if (moduleFiles.Count > 1) + { + throw new CliUsageException($"There are multiple classes derived from AbpModule in the project {projectFile}: " + moduleFiles.JoinAsString(", ")); + } + + ModuleClassDependcyAdder.Add(moduleFiles.First(), package.ModuleClass); + + if (package.DependedNpmPackage != null) + { + await NpmPackageAdder.AddAsync(Path.GetDirectoryName(projectFile), package.DependedNpmPackage); + } - Logger.LogInformation("Successfully installed."); + Logger.LogInformation("Successfully installed."); + } } - private async Task FindNugetPackageInfoAsync(string moduleName) + + protected virtual async Task FindNugetPackageInfoAsync(string moduleName) { using (var client = new HttpClient()) { @@ -70,14 +89,10 @@ namespace Volo.Abp.ProjectModification { if (response.StatusCode == HttpStatusCode.NotFound) { - Logger.LogError($"ERROR: '{moduleName}' module could not be found."); - } - else - { - Logger.LogError($"ERROR: Remote server returns '{response.StatusCode}'"); + throw new CliUsageException($"'{moduleName}' nuget package could not be found!"); } - return null; + throw new Exception($"ERROR: Remote server returns '{response.StatusCode}'"); } var responseContent = await response.Content.ReadAsStringAsync(); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/SolutionModuleAdder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/SolutionModuleAdder.cs index 75f957fecf..70956c911d 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/SolutionModuleAdder.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/ProjectModification/SolutionModuleAdder.cs @@ -1,17 +1,147 @@ -using System.Threading.Tasks; +using System; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; using JetBrains.Annotations; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Newtonsoft.Json; +using Volo.Abp.Cli; using Volo.Abp.DependencyInjection; +using Volo.Abp.Json; namespace Volo.Abp.ProjectModification { public class SolutionModuleAdder : ITransientDependency { - public virtual async Task AddAsync([NotNull] string solutionFile, [NotNull] string moduleName) + public ILogger Logger { get; set; } + + protected IJsonSerializer JsonSerializer { get; } + protected ProjectNugetPackageAdder ProjectNugetPackageAdder { get; } + + public SolutionModuleAdder( + IJsonSerializer jsonSerializer, + ProjectNugetPackageAdder projectNugetPackageAdder) + { + JsonSerializer = jsonSerializer; + ProjectNugetPackageAdder = projectNugetPackageAdder; + Logger = NullLogger.Instance; + } + + public virtual async Task AddAsync( + [NotNull] string solutionFile, + [NotNull] string moduleName) { Check.NotNull(solutionFile, nameof(solutionFile)); Check.NotNull(moduleName, nameof(moduleName)); + var module = await FindModuleInfoAsync(moduleName); + var projectFiles = GetProjectFiles(solutionFile); + + foreach (var nugetPackage in module.NugetPackages) + { + var targetProjectFile = FindTargetProjectFile(projectFiles, nugetPackage.Target); + if (targetProjectFile == null) + { + continue; + } + + await ProjectNugetPackageAdder.AddAsync(targetProjectFile, nugetPackage); + } + } + + private string FindTargetProjectFile(string[] projectFiles, NugetPackageTarget target) + { + if (!projectFiles.Any()) + { + return null; + } + + var assemblyNames = projectFiles + .Select( + filePath => filePath + .Replace("\\", "/") + .Substring(filePath.LastIndexOf("/", StringComparison.OrdinalIgnoreCase) + 1) + .RemovePostFix(StringComparison.OrdinalIgnoreCase, ".csproj") + ).ToArray(); + + switch (target) + { + case NugetPackageTarget.Web: + return FindProjectEndsWith(assemblyNames, ".Web"); + case NugetPackageTarget.EntityFrameworkCore: + return FindProjectEndsWith(assemblyNames, ".EntityFrameworkCore") ?? + FindProjectEndsWith(assemblyNames, ".Web"); + case NugetPackageTarget.MongoDB: + return FindProjectEndsWith(assemblyNames, ".MongoDB") ?? + FindProjectEndsWith(assemblyNames, ".Web"); + case NugetPackageTarget.Application: + return FindProjectEndsWith(assemblyNames, ".Application") ?? + FindProjectEndsWith(assemblyNames, ".Web"); + case NugetPackageTarget.ApplicationContracts: + return FindProjectEndsWith(assemblyNames, ".Application.Contracts"); + case NugetPackageTarget.Domain: + return FindProjectEndsWith(assemblyNames, ".Domain") ?? + FindProjectEndsWith(assemblyNames, ".Application") ?? + FindProjectEndsWith(assemblyNames, ".Web"); + case NugetPackageTarget.DomainShared: + return FindProjectEndsWith(assemblyNames, ".Domain.Shared"); + case NugetPackageTarget.HttpApi: + return FindProjectEndsWith(assemblyNames, ".HttpApi") ?? + FindProjectEndsWith(assemblyNames, ".Web"); + case NugetPackageTarget.HttpApiClient: + return FindProjectEndsWith(assemblyNames, ".HttpApi.Client"); + default: + throw new ApplicationException($"{nameof(NugetPackageTarget)} has not implemented: {target}"); + } + } + + private static string FindProjectEndsWith(string[] projectAssemblyNames, string postfix) + { + return projectAssemblyNames.FirstOrDefault(p => p.EndsWith(postfix, StringComparison.OrdinalIgnoreCase)); + } + + protected virtual string[] GetProjectFiles(string solutionFile) + { + var baseProjectFolder = GetBaseProjectFolder(solutionFile); + return Directory.GetFiles(baseProjectFolder, "*.csproj", SearchOption.AllDirectories); + } + + protected virtual string GetBaseProjectFolder(string solutionFile) + { + var baseFolder = Path.GetDirectoryName(solutionFile); + var srcFolder = Path.Combine(baseFolder, "src"); + if (Directory.Exists(srcFolder)) + { + baseFolder = srcFolder; + } + + return baseFolder; + } + + protected virtual async Task FindModuleInfoAsync(string moduleName) + { + using (var client = new HttpClient()) + { + var url = "https://localhost:44328/api/app/module/byName/?name=" + moduleName; + + var response = await client.GetAsync(url); + + if (!response.IsSuccessStatusCode) + { + if (response.StatusCode == HttpStatusCode.NotFound) + { + throw new CliUsageException($"ERROR: '{moduleName}' module could not be found!"); + } + + throw new Exception($"ERROR: Remote server returns '{response.StatusCode}'"); + } + var responseContent = await response.Content.ReadAsStringAsync(); + return JsonSerializer.Deserialize(responseContent); + } } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/IO/DirectoryHelper.cs b/framework/src/Volo.Abp.Core/Volo/Abp/IO/DirectoryHelper.cs index c4185cbbed..b864695430 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/IO/DirectoryHelper.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/IO/DirectoryHelper.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; using JetBrains.Annotations; namespace Volo.Abp.IO @@ -45,5 +46,22 @@ namespace Volo.Abp.IO return IsSubDirectoryOf(parentDirectory, parentOfChild); } + + public static IDisposable ChangeCurrentDirectory(string targetDirectory) + { + var currentDirectory = Directory.GetCurrentDirectory(); + + if (currentDirectory.Equals(targetDirectory, StringComparison.OrdinalIgnoreCase)) + { + return NullDisposable.Instance; + } + + Directory.SetCurrentDirectory(targetDirectory); + + return new DisposeAction(() => + { + Directory.SetCurrentDirectory(currentDirectory); + }); + } } }