From ab926272ed463ff2dd46783a1999f5c1a70e5357 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Mon, 24 May 2021 18:42:49 +0800 Subject: [PATCH] Add install-libs command --- .../Volo/Abp/Cli/AbpCliCoreModule.cs | 8 + .../Abp/Cli/Commands/InstallLibsCommand.cs | 78 ++++++++ .../Volo/Abp/Cli/LIbs/IInstallLibsService.cs | 9 + .../Volo/Abp/Cli/LIbs/InstallLibsService.cs | 189 ++++++++++++++++++ .../Volo/Abp/Cli/LIbs/ResourceMapping.cs | 53 +++++ 5 files changed, 337 insertions(+) create mode 100644 framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/InstallLibsCommand.cs create mode 100644 framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/LIbs/IInstallLibsService.cs create mode 100644 framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/LIbs/InstallLibsService.cs create mode 100644 framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/LIbs/ResourceMapping.cs diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs index b3de9d71ff..08da2e3d00 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs @@ -2,9 +2,11 @@ using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Cli.Commands; using Volo.Abp.Cli.Http; +using Volo.Abp.Cli.LIbs; using Volo.Abp.Domain; using Volo.Abp.IdentityModel; using Volo.Abp.Json; +using Volo.Abp.Json.SystemTextJson; using Volo.Abp.Minify; using Volo.Abp.Modularity; @@ -25,6 +27,11 @@ namespace Volo.Abp.Cli Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + Configure(options => + { + options.UnsupportedTypes.Add(typeof(ResourceMapping)); + }); + Configure(options => { //TODO: Define constants like done for GenerateProxyCommand.Name. @@ -49,6 +56,7 @@ namespace Volo.Abp.Cli options.Commands["build"] = typeof(BuildCommand); options.Commands["bundle"] = typeof(BundleCommand); options.Commands["create-migration-and-run-migrator"] = typeof(CreateMigrationAndRunMigratorCommand); + options.Commands["install-libs"] = typeof(InstallLibsCommand); }); } } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/InstallLibsCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/InstallLibsCommand.cs new file mode 100644 index 0000000000..17f7abfd6c --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/InstallLibsCommand.cs @@ -0,0 +1,78 @@ +using System; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Volo.Abp.Cli.Args; +using Volo.Abp.Cli.LIbs; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Cli.Commands +{ + public class InstallLibsCommand : IConsoleCommand, ITransientDependency + { + public ILogger Logger { get; set; } + + protected IInstallLibsService InstallLibsService { get; } + + public InstallLibsCommand(IInstallLibsService installLibsService) + { + InstallLibsService = installLibsService; + Logger = NullLogger.Instance; + } + + public async Task ExecuteAsync(CommandLineArgs commandLineArgs) + { + var workingDirectoryArg = commandLineArgs.Options.GetOrNull( + Options.WorkingDirectory.Short, + Options.WorkingDirectory.Long + ); + + var workingDirectory = workingDirectoryArg ?? Directory.GetCurrentDirectory(); + + if (!Directory.Exists(workingDirectory)) + { + throw new CliUsageException( + "Specified directory does not exist." + + Environment.NewLine + Environment.NewLine + + GetUsageInfo() + ); + } + + await InstallLibsService.InstallLibs(workingDirectory); + } + + public string GetUsageInfo() + { + var sb = new StringBuilder(); + + sb.AppendLine(""); + sb.AppendLine("Usage:"); + sb.AppendLine(""); + sb.AppendLine(" abp install-libs"); + sb.AppendLine(""); + sb.AppendLine("Options:"); + sb.AppendLine(""); + sb.AppendLine("-wd|--working-directory (default: empty)"); + 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 "Bundles all third party styles and scripts required by modules and updates index.html file."; + } + + public static class Options + { + public static class WorkingDirectory + { + public const string Short = "wd"; + public const string Long = "working-directory"; + } + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/LIbs/IInstallLibsService.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/LIbs/IInstallLibsService.cs new file mode 100644 index 0000000000..af96df6e22 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/LIbs/IInstallLibsService.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.Cli.LIbs +{ + public interface IInstallLibsService + { + Task InstallLibs(string directory); + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/LIbs/InstallLibsService.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/LIbs/InstallLibsService.cs new file mode 100644 index 0000000000..50a6260bba --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/LIbs/InstallLibsService.cs @@ -0,0 +1,189 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using NuGet.Versioning; +using Volo.Abp.Cli.Utils; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Json; + +namespace Volo.Abp.Cli.LIbs +{ + public class InstallLibsService : IInstallLibsService, ITransientDependency + { + public ILogger Logger { get; set; } + + private readonly IJsonSerializer _jsonSerializer; + + public InstallLibsService(IJsonSerializer jsonSerializer) + { + _jsonSerializer = jsonSerializer; + Logger = NullLogger.Instance; + } + + public async Task InstallLibs(string directory) + { + var projectFiles = Directory.GetFiles(directory, "*.csproj"); + if (!projectFiles.Any()) + { + Logger.LogError("No project file found in the directory."); + return; + } + + if (!await CanInstallLibs(directory)) + { + Logger.LogWarning( + "abp install-libs command is available for MVC, Razor Page, and Blazor-Server UI types"); + return; + } + + if (!IsNpmInstalled()) + { + Logger.LogWarning("NPM is not installed, visit https://nodejs.org/en/download/ and install NPM"); + return; + } + + if (IsYarnAvailable()) + { + RunYarn(directory); + } + else + { + RunNpmInstall(directory); + } + + await CleanAndCopyResources(directory); + } + + private async Task CleanAndCopyResources(string fileDirectory) + { + var mappingFiles = Directory.GetFiles(fileDirectory, "abp.resourcemapping.js", SearchOption.AllDirectories); + var resourceMapping = new ResourceMapping + { + Clean = new List {"./wwwroot/libs"} + }; + + foreach (var mappingFile in mappingFiles) + { + using (var reader = File.OpenText(mappingFile)) + { + var mappingFileContent = await reader.ReadToEndAsync(); + + var mapping = _jsonSerializer.Deserialize(mappingFileContent + .Replace("module.exports", string.Empty) + .Replace("=", string.Empty).Trim().TrimEnd(';')); + + mapping.ReplaceAliases(); + + resourceMapping.Clean.AddRange(mapping.Clean); + mapping.Aliases.ToList().ForEach(x => + { + resourceMapping.Aliases.AddIfNotContains(new KeyValuePair(x.Key, x.Value)); + }); + mapping.Mappings.ToList().ForEach(x => + { + resourceMapping.Mappings.AddIfNotContains(new KeyValuePair(x.Key, x.Value)); + }); + } + } + + CleanDirsAndFiles(fileDirectory, resourceMapping); + CopyResourcesToLibs(fileDirectory, resourceMapping); + } + + private void CopyResourcesToLibs(string fileDirectory, ResourceMapping resourceMapping) + { + foreach (var mapping in resourceMapping.Mappings) + { + var sourcePath = Path.Combine(fileDirectory, mapping.Key); + var destPath = Path.Combine(fileDirectory, mapping.Value); + + if (Path.HasExtension(sourcePath) && File.Exists(sourcePath)) + { + Directory.CreateDirectory(Path.GetFullPath(destPath)); + File.Copy(sourcePath, Path.Combine(destPath, Path.GetFileName(sourcePath)), true); + } + else + { + var files = Directory.GetFiles(fileDirectory, mapping.Key); + + Directory.CreateDirectory(Path.GetFullPath(destPath)); + + foreach (var file in files) + { + File.Copy(file, Path.Combine(destPath, Path.GetFileName(file)), true); + } + } + } + } + + private async Task CanInstallLibs(string fileDirectory) + { + var projectFiles = Directory.GetFiles(fileDirectory, "*.csproj"); + + using (var reader = File.OpenText(projectFiles[0])) + { + var projectFileContent = await reader.ReadToEndAsync(); + + return projectFileContent.Contains("Microsoft.NET.Sdk.Web"); + } + } + + private void CleanDirsAndFiles(string directory, ResourceMapping resourceMapping) + { + foreach (var cleanPattern in resourceMapping.Clean) + { + CleanDirsAndFiles(directory, cleanPattern); + } + } + + private void CleanDirsAndFiles(string directory, string patterns) + { + foreach (var file in Directory.GetFiles(directory, patterns)) + { + File.Delete(file); + } + + foreach (var dir in Directory.GetDirectories(directory, patterns)) + { + Directory.Delete(dir, true); + } + } + + private void RunNpmInstall(string directory) + { + Logger.LogInformation($"Running npm install on {directory}"); + CmdHelper.RunCmd($"cd {directory} && npm install"); + } + + private void RunYarn(string directory) + { + Logger.LogInformation($"Running Yarn on {directory}"); + CmdHelper.RunCmd($"cd {directory} && yarn"); + } + + private bool IsNpmInstalled() + { + var output = CmdHelper.RunCmdAndGetOutput("npm -v").Trim(); + return SemanticVersion.TryParse(output, out _); + } + + private bool IsYarnAvailable() + { + var output = CmdHelper.RunCmdAndGetOutput("npm list yarn -g").Trim(); + if (output.Contains("empty")) + { + return false; + } + + if (!SemanticVersion.TryParse(output.Substring(output.IndexOf('@') + 1), out var version)) + { + return false; + } + + return version > SemanticVersion.Parse("1.20.0"); + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/LIbs/ResourceMapping.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/LIbs/ResourceMapping.cs new file mode 100644 index 0000000000..ed46b833ad --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/LIbs/ResourceMapping.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Volo.Abp.Cli.LIbs +{ + public class ResourceMapping + { + public Dictionary Aliases { get; set; } + + public List Clean { get; set; } + + public Dictionary Mappings { get; set; } + + public ResourceMapping() + { + Aliases = new Dictionary + { + {"@node_modules", "./node_modules"}, + {"@libs", "./wwwroot/libs"}, + }; + Clean = new List(); + Mappings = new Dictionary(); + } + + public void ReplaceAliases() + { + for (var i = 0; i < Mappings.Count; i++) + { + var mapping = Mappings.ElementAt(i); + Mappings.Remove(mapping.Key); + + var key = mapping.Key; + var value = mapping.Value; + + foreach (var alias in Aliases) + { + key = key.Replace(alias.Key, alias.Value); + value = value.Replace(alias.Key, alias.Value); + } + + Mappings[key] = value; + } + + for (var i = 0; i < Clean.Count; i++) + { + foreach (var alias in Aliases) + { + Clean[i] = Clean[i].Replace(alias.Key, alias.Value); + } + } + } + } +}