From ab926272ed463ff2dd46783a1999f5c1a70e5357 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Mon, 24 May 2021 18:42:49 +0800 Subject: [PATCH 1/3] 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); + } + } + } + } +} From bc38fb717e399ea8fbf4282788a001b2f6c19011 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Sat, 29 May 2021 10:55:53 +0800 Subject: [PATCH 2/3] Support glob --- .../Volo.Abp.Cli.Core.csproj | 1 + .../Abp/Cli/Commands/InstallLibsCommand.cs | 2 +- .../Volo/Abp/Cli/LIbs/InstallLibsService.cs | 75 +++++++++++++------ 3 files changed, 56 insertions(+), 22 deletions(-) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj b/framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj index 1b7603194a..4f28bad500 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj +++ b/framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj @@ -13,6 +13,7 @@ + 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 index 17f7abfd6c..23248ed5e4 100644 --- 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 @@ -63,7 +63,7 @@ namespace Volo.Abp.Cli.Commands public string GetShortDescription() { - return "Bundles all third party styles and scripts required by modules and updates index.html file."; + return "Restore all npm packages and copy to libs."; } public static class Options 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 index 50a6260bba..7416859486 100644 --- 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 @@ -1,7 +1,10 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; +using Microsoft.Extensions.FileSystemGlobbing; +using Microsoft.Extensions.FileSystemGlobbing.Abstractions; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using NuGet.Versioning; @@ -77,7 +80,7 @@ namespace Volo.Abp.Cli.LIbs mapping.ReplaceAliases(); - resourceMapping.Clean.AddRange(mapping.Clean); + mapping.Clean.ForEach(c => resourceMapping.Clean.AddIfNotContains(c)); mapping.Aliases.ToList().ForEach(x => { resourceMapping.Aliases.AddIfNotContains(new KeyValuePair(x.Key, x.Value)); @@ -97,24 +100,21 @@ namespace Volo.Abp.Cli.LIbs { 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 = FindFiles(fileDirectory, mapping.Key); + + foreach (var file in files) { - var files = Directory.GetFiles(fileDirectory, mapping.Key); + var destFilePath = Path.Combine(destPath, Path.GetFileName(file)); + if (File.Exists(destFilePath)) + { + continue; + } Directory.CreateDirectory(Path.GetFullPath(destPath)); + File.Copy(file, destFilePath); - foreach (var file in files) - { - File.Copy(file, Path.Combine(destPath, Path.GetFileName(file)), true); - } } } } @@ -133,23 +133,56 @@ namespace Volo.Abp.Cli.LIbs private void CleanDirsAndFiles(string directory, ResourceMapping resourceMapping) { - foreach (var cleanPattern in resourceMapping.Clean) + var files = FindFiles(directory, resourceMapping.Clean.ToArray()); + + foreach (var file in files) { - CleanDirsAndFiles(directory, cleanPattern); + if (File.Exists(file)) + { + File.Delete(file); + } + } + + foreach (var directoryInfo in Directory.GetDirectories(Path.Combine(directory, resourceMapping.Clean.First()),"*", SearchOption.AllDirectories).Reverse()) + { + if (!Directory.EnumerateFileSystemEntries(directoryInfo).Any()) + { + Directory.Delete(directoryInfo); + } } } - private void CleanDirsAndFiles(string directory, string patterns) + private string[] FindFiles(string directory, params string[] patterns) { - foreach (var file in Directory.GetFiles(directory, patterns)) + var matcher = new Matcher(); + + foreach (var pattern in patterns) { - File.Delete(file); + if (pattern.StartsWith("!")) + { + matcher.AddExclude(NormalizeGlob(pattern).TrimStart('!')); + } + else + { + matcher.AddInclude(NormalizeGlob(pattern)); + } } - foreach (var dir in Directory.GetDirectories(directory, patterns)) + var result = matcher.Execute(new DirectoryInfoWrapper(new DirectoryInfo(directory))); + + return result.Files.Select(x => Path.Combine(directory, x.Path)).ToArray(); + } + + private string NormalizeGlob(string pattern) + { + pattern = pattern.Replace("//", "/"); + + if (!Path.HasExtension(pattern) && !pattern.EndsWith("*")) { - Directory.Delete(dir, true); + return pattern.EnsureEndsWith('/') + "**"; } + + return pattern; } private void RunNpmInstall(string directory) From b4a7c9ccda6cd322ebc3e257157ab7e75dd66a96 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Sat, 29 May 2021 11:28:54 +0800 Subject: [PATCH 3/3] Update document --- docs/en/CLI.md | 16 ++++++++++++++++ docs/zh-Hans/CLI.md | 17 +++++++++++++++++ .../Volo/Abp/Cli/Commands/InstallLibsCommand.cs | 2 +- 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/docs/en/CLI.md b/docs/en/CLI.md index f2e7df219b..829b9480c7 100644 --- a/docs/en/CLI.md +++ b/docs/en/CLI.md @@ -42,6 +42,7 @@ Here, the list of all available commands before explaining their details: * **`login`**: Authenticates on your computer with your [abp.io](https://abp.io/) username and password. * **`logout`**: Logouts from your computer if you've authenticated before. * **`bundle`**: Generates script and style references for an ABP Blazor project. +* **`install-libs`**: Install NPM Packages for MVC / Razor Pages and Blazor Server UI types. ### help @@ -433,3 +434,18 @@ abp bundle [options] `bundle` command reads the `appsettings.json` file inside the Blazor project for bundling options. For more details about managing style and script references in Blazor apps, see [Managing Global Scripts & Styles](UI/Blazor/Global-Scripts-Styles.md) +### install-libs + +This command install NPM Packages for MVC / Razor Pages and Blazor Server UI types. Its **executing directory** or passed ```--working-directory``` parameter's directory must contain a project file(*.csproj). + +`install-libs` command reads the `abp.resourcemapping.js` file to manage package. For more details see [Client Side Package Management](UI/AspNetCore/Client-Side-Package-Management.md). + +Usage: + +````bash +abp install-libs [options] +```` + +#### Options + +* ```--working-directory``` or ```-wd```: Specifies the working directory. This option is useful when executing directory doesn't contain a project file. \ No newline at end of file diff --git a/docs/zh-Hans/CLI.md b/docs/zh-Hans/CLI.md index 262f546bf2..4325ee1f07 100644 --- a/docs/zh-Hans/CLI.md +++ b/docs/zh-Hans/CLI.md @@ -39,6 +39,7 @@ dotnet tool update -g Volo.Abp.Cli * **`translate`**: 当源代码控制存储库中有多个JSON[本地化](Localization.md文件时,可简化翻译本地化文件的过程. * **`login`**: 使用你在[abp.io](https://abp.io/)的用户名和密码在你的计算机上认证. * **`logout`**: 在你的计算机注销认证. +* **`install-libs`**: 为 MVC / Razor Pages 和 Blazor Server UI 类型安装NPM包. ### help @@ -339,3 +340,19 @@ abp login -p -o # You can enter both your ``` abp logout ``` + +### install-libs + +为 MVC / Razor Pages 和 Blazor Server UI 类型安装NPM包, 它的 **执行目录** 或者传递的 ```--working-directory``` 目录必须包含一个项目文件(*.csproj). + +`install-libs` 命令读取 `abp.resourcemapping.js` 来管理包. 参阅[客户端包管理](UI/AspNetCore/Client-Side-Package-Management.md)了解更多细节. + +用法: + +````bash +abp install-libs [options] +```` + +#### Options + +* ```--working-directory``` 或 ```-wd```: 指定工作目录, 当执行目录不包含项目文件时会很有用. \ No newline at end of file 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 index 23248ed5e4..36db14856f 100644 --- 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 @@ -63,7 +63,7 @@ namespace Volo.Abp.Cli.Commands public string GetShortDescription() { - return "Restore all npm packages and copy to libs."; + return "Install NPM Packages for MVC / Razor Pages and Blazor Server UI types."; } public static class Options