20 changed files with 544 additions and 6 deletions
@ -0,0 +1,22 @@ |
|||||
|
// Global using directives
|
||||
|
|
||||
|
global using System.Diagnostics.CodeAnalysis; |
||||
|
global using System.Net; |
||||
|
global using System.Text; |
||||
|
global using ICSharpCode.SharpZipLib.Core; |
||||
|
global using ICSharpCode.SharpZipLib.Zip; |
||||
|
global using Lion.AbpPro.Cli.Args; |
||||
|
global using Lion.AbpPro.Cli.Github; |
||||
|
global using Lion.AbpPro.Cli.Options; |
||||
|
global using Lion.AbpPro.Cli.Replace; |
||||
|
global using Lion.AbpPro.Cli.Utils; |
||||
|
global using Lion.AbpPro.Cli.Zip; |
||||
|
global using Microsoft.Extensions.DependencyInjection; |
||||
|
global using Microsoft.Extensions.Logging; |
||||
|
global using Microsoft.Extensions.Options; |
||||
|
global using Octokit; |
||||
|
global using Polly; |
||||
|
global using Polly.Retry; |
||||
|
global using Volo.Abp; |
||||
|
global using Volo.Abp.DependencyInjection; |
||||
|
global using Volo.Abp.IO; |
||||
@ -0,0 +1,19 @@ |
|||||
|
namespace Lion.AbpPro.Cli.Github; |
||||
|
|
||||
|
public interface ILionAbpProManager |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 获取最后一个版本
|
||||
|
/// </summary>
|
||||
|
Task<string> GetLatestSourceCodeVersionAsync(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 检查版本是否存在
|
||||
|
/// </summary>
|
||||
|
Task<bool> CheckSourceCodeVersionAsync(string version); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 下载源码
|
||||
|
/// </summary>
|
||||
|
Task<byte[]> DownloadAsync(string version,string outputPath); |
||||
|
} |
||||
@ -0,0 +1,68 @@ |
|||||
|
using Uri = System.Uri; |
||||
|
|
||||
|
namespace Lion.AbpPro.Cli.Github; |
||||
|
|
||||
|
public class LionAbpProManager : ITransientDependency, ILionAbpProManager |
||||
|
{ |
||||
|
private readonly LionAbpProOptions _options; |
||||
|
private readonly IHttpClientFactory _httpClientFactory; |
||||
|
|
||||
|
public LionAbpProManager(IOptions<LionAbpProOptions> options, IHttpClientFactory httpClientFactory) |
||||
|
{ |
||||
|
_httpClientFactory = httpClientFactory; |
||||
|
_options = options.Value; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 获取最后一个版本
|
||||
|
/// </summary>
|
||||
|
public async Task<string> GetLatestSourceCodeVersionAsync() |
||||
|
{ |
||||
|
var github = new GitHubClient(new ProductHeaderValue(_options.RepositoryId)) |
||||
|
{ |
||||
|
// 匿名访问,api会限流,所以需要设置访问令牌
|
||||
|
Credentials = new Credentials(_options.DecryptToken) |
||||
|
}; |
||||
|
|
||||
|
var release = await github.Repository.Release.GetLatest(_options.Owner, _options.RepositoryId); |
||||
|
return release?.TagName; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 检查版本是否存在
|
||||
|
/// </summary>
|
||||
|
public async Task<bool> CheckSourceCodeVersionAsync(string version) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
var github = new GitHubClient(new ProductHeaderValue(_options.RepositoryId)) |
||||
|
{ |
||||
|
// 匿名访问,api会限流,所以需要设置访问令牌
|
||||
|
Credentials = new Credentials(_options.DecryptToken) |
||||
|
}; |
||||
|
|
||||
|
var release = await github.Repository.Release.Get(_options.Owner, _options.RepositoryId, version); |
||||
|
return release != null; |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 下载源码
|
||||
|
/// </summary>
|
||||
|
public async Task<byte[]> DownloadAsync(string version, string outputPath) |
||||
|
{ |
||||
|
var httpClient = _httpClientFactory.CreateClient(); |
||||
|
var uri = new Uri($"https://github.com/{_options.Owner}/{_options.RepositoryId}/archive/refs/tags/{version}.zip"); |
||||
|
var response = await httpClient.GetAsync(uri); |
||||
|
DirectoryHelper.CreateIfNotExists(CliPaths.TemplateCache); |
||||
|
var content = await response.Content.ReadAsByteArrayAsync(); |
||||
|
response.Dispose(); |
||||
|
File.Delete(outputPath); |
||||
|
await File.WriteAllBytesAsync(outputPath, content); |
||||
|
return content; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
namespace Lion.AbpPro.Cli.Options; |
||||
|
|
||||
|
public class LionAbpProOptions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 仓库拥有者
|
||||
|
/// </summary>
|
||||
|
public string Owner { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 仓库Id
|
||||
|
/// </summary>
|
||||
|
public string RepositoryId { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Github Token
|
||||
|
/// </summary>
|
||||
|
public string Token { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 解密 Github Token
|
||||
|
/// </summary>
|
||||
|
public string DecryptToken => Cryptography.TokenHelper.Decrypt(Token); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 模板信息
|
||||
|
/// </summary>
|
||||
|
public List<LionAbpProTemplateOptions> Templates { get; set; } |
||||
|
|
||||
|
public string OldCompanyName { get; set; }= "Lion"; |
||||
|
|
||||
|
public string OldProjectName { get; set; } = "AbpPro"; |
||||
|
} |
||||
@ -0,0 +1,43 @@ |
|||||
|
namespace Lion.AbpPro.Cli.Options; |
||||
|
|
||||
|
public class LionAbpProTemplateOptions |
||||
|
{ |
||||
|
public LionAbpProTemplateOptions(string key, string name, string description, bool isSource = false) |
||||
|
{ |
||||
|
Key = key; |
||||
|
Name = name; |
||||
|
Description = description; |
||||
|
IsSource = isSource; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 模板key
|
||||
|
/// 对应templates下文件夹名称
|
||||
|
/// </summary>
|
||||
|
public string Key { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// cli -t 对应参数
|
||||
|
/// </summary>
|
||||
|
public string Name { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 描述
|
||||
|
/// </summary>
|
||||
|
public string Description { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 需要替换的文件
|
||||
|
/// </summary>
|
||||
|
public string ReplaceSuffix { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 需要排除的文件
|
||||
|
/// </summary>
|
||||
|
public string ExcludeFiles { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 是否源码版本
|
||||
|
/// </summary>
|
||||
|
public bool IsSource { get; set; } |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
namespace Lion.AbpPro.Cli.SourceCode; |
||||
|
|
||||
|
public interface ISourceCodeManager |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 获取源码
|
||||
|
/// </summary>
|
||||
|
/// <param name="version">版本</param>
|
||||
|
Task<TemplateFile> GetAsync(string version); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 解压
|
||||
|
/// </summary>
|
||||
|
void ExtractProjectZip(SourceCodeContext context); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 替换
|
||||
|
/// </summary>
|
||||
|
void ReplaceTemplates(SourceCodeContext context); |
||||
|
} |
||||
@ -0,0 +1,80 @@ |
|||||
|
namespace Lion.AbpPro.Cli.SourceCode; |
||||
|
|
||||
|
public class SourceCodeContext |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 模板信息
|
||||
|
/// </summary>
|
||||
|
public TemplateFile TemplateFile { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 需要替换的文件
|
||||
|
/// </summary>
|
||||
|
public string ReplaceSuffix { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 需要排除的文件
|
||||
|
/// </summary>
|
||||
|
public string ExcludeFiles { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 替换CompanyName
|
||||
|
/// </summary>
|
||||
|
public string OldCompanyName { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 替换ProjectName
|
||||
|
/// </summary>
|
||||
|
public string OldProjectName { get; set; } |
||||
|
/// <summary>
|
||||
|
/// New CompanyName
|
||||
|
/// </summary>
|
||||
|
public string CompanyName { get; set; } |
||||
|
/// <summary>
|
||||
|
/// New ProjectName
|
||||
|
/// </summary>
|
||||
|
public string ProjectName { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 输入文件夹
|
||||
|
/// </summary>
|
||||
|
public string OutputFolder { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 模板文件夹
|
||||
|
/// </summary>
|
||||
|
public string TemplateFolder { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 模板key
|
||||
|
/// </summary>
|
||||
|
public string TemplateKey { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 模板名称
|
||||
|
/// </summary>
|
||||
|
public string TemplateName { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 解压目录
|
||||
|
/// </summary>
|
||||
|
public string ExtractProjectPath { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 是否源码版本
|
||||
|
/// </summary>
|
||||
|
public bool IsSource { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 仓库拥有者
|
||||
|
/// </summary>
|
||||
|
public string Owner { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 仓库Id
|
||||
|
/// </summary>
|
||||
|
public string RepositoryId { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Github Token
|
||||
|
/// </summary>
|
||||
|
public string Token { get; set; } |
||||
|
} |
||||
@ -0,0 +1,135 @@ |
|||||
|
namespace Lion.AbpPro.Cli.SourceCode; |
||||
|
|
||||
|
public class SourceCodeManager : ITransientDependency, ISourceCodeManager |
||||
|
{ |
||||
|
private readonly ILogger<SourceCodeManager> _logger; |
||||
|
private readonly ILionAbpProManager _lionAbpProManager; |
||||
|
private readonly LionAbpProOptions _options; |
||||
|
|
||||
|
public SourceCodeManager(ILogger<SourceCodeManager> logger, IOptions<Options.LionAbpProOptions> options, ILionAbpProManager lionAbpProManager) |
||||
|
{ |
||||
|
_logger = logger; |
||||
|
_lionAbpProManager = lionAbpProManager; |
||||
|
_options = options.Value; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 获取源码
|
||||
|
/// </summary>
|
||||
|
/// <param name="version">版本</param>
|
||||
|
public async Task<TemplateFile> GetAsync(string version) |
||||
|
{ |
||||
|
var latestVersion = await _lionAbpProManager.GetLatestSourceCodeVersionAsync(); |
||||
|
if (version == null) |
||||
|
{ |
||||
|
version = latestVersion ?? throw new Exception("请检查版本是否正确"); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
if (!await _lionAbpProManager.CheckSourceCodeVersionAsync(version)) |
||||
|
{ |
||||
|
throw new Exception("没有找到指定的版本: " + version); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
var localCacheFile = Path.Combine(CliPaths.TemplateCache, _options.RepositoryId + "-" + version + ".zip"); |
||||
|
|
||||
|
_logger.LogInformation($"Lion AbpPro Version:{version}"); |
||||
|
_logger.LogInformation($"模板生成中......"); |
||||
|
if (!File.Exists(localCacheFile)) |
||||
|
{ |
||||
|
return new TemplateFile(version, localCacheFile, await _lionAbpProManager.DownloadAsync(version, localCacheFile)); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
return new TemplateFile(version, localCacheFile, await File.ReadAllBytesAsync(localCacheFile)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
public void ExtractProjectZip(SourceCodeContext context) |
||||
|
{ |
||||
|
using (var templateFileStream = new MemoryStream(context.TemplateFile.FileBytes)) |
||||
|
{ |
||||
|
using (var zipInputStream = new ZipInputStream(templateFileStream)) |
||||
|
{ |
||||
|
var zipEntry = zipInputStream.GetNextEntry(); |
||||
|
while (zipEntry != null) |
||||
|
{ |
||||
|
if (string.IsNullOrWhiteSpace(zipEntry.Name)) |
||||
|
{ |
||||
|
zipEntry = zipInputStream.GetNextEntry(); |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
var fullZipToPath = Path.Combine(CliPaths.TemplateCache, 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(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
context.ExtractProjectPath = Path.Combine(CliPaths.TemplateCache, _options.RepositoryId + "-" + context.TemplateFile.Version); |
||||
|
} |
||||
|
|
||||
|
public void MoveTemplate(SourceCodeContext context) |
||||
|
{ |
||||
|
// var codePath= Path.Combine(context.OutputFolder, context.CompanyName + "." + context.ProjectName);
|
||||
|
//
|
||||
|
// DirectoryHelper.DeleteIfExists(codePath,true);
|
||||
|
//
|
||||
|
// if (context.IsSource)
|
||||
|
// {
|
||||
|
// context.TemplateFolder = Path.Combine(context.OutputFolder, _options.RepositoryId + "-" + context.TemplateFile.Version);
|
||||
|
// }
|
||||
|
// else
|
||||
|
// {
|
||||
|
// // 获取本地源码地址
|
||||
|
// context.TemplateFolder = Path.Combine(context.OutputFolder, _options.RepositoryId + "-" + context.TemplateFile.Version, "templates", context.TemplateKey);
|
||||
|
// }
|
||||
|
//
|
||||
|
// Directory.Move(context.TemplateFolder, codePath);
|
||||
|
// context.OutputFolder = codePath;
|
||||
|
|
||||
|
DirectoryAndFileHelper.CopyFolder(context.ExtractProjectPath, context.OutputFolder, "docs"); |
||||
|
} |
||||
|
|
||||
|
public void ReplaceTemplates(SourceCodeContext context) |
||||
|
{ |
||||
|
ReplaceHelper.ReplaceTemplates(context.ExtractProjectPath, context.OldCompanyName, context.OldProjectName, context.CompanyName, context.ProjectName, context.ReplaceSuffix); |
||||
|
if (context.IsSource) |
||||
|
{ |
||||
|
context.TemplateFolder = context.ExtractProjectPath; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// 获取本地源码地址
|
||||
|
context.TemplateFolder = Path.Combine(context.ExtractProjectPath, _options.RepositoryId + "-" + context.TemplateFile.Version, "templates", context.TemplateKey); |
||||
|
} |
||||
|
|
||||
|
context.OutputFolder = Path.Combine(context.OutputFolder, context.CompanyName + "." + context.ProjectName); |
||||
|
DirectoryHelper.DeleteIfExists(context.OutputFolder, true); |
||||
|
DirectoryAndFileHelper.CopyFolder(context.TemplateFolder, context.OutputFolder, context.ExcludeFiles); |
||||
|
DirectoryHelper.DeleteIfExists(context.ExtractProjectPath, true); |
||||
|
_logger.LogInformation($"OutputFolder:{context.OutputFolder}"); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
namespace Lion.AbpPro.Cli.SourceCode; |
||||
|
|
||||
|
public class TemplateFile |
||||
|
{ |
||||
|
public TemplateFile(string version, string sourceCodePath, byte[] fileBytes) |
||||
|
{ |
||||
|
Version = version; |
||||
|
SourceCodePath = sourceCodePath; |
||||
|
FileBytes = fileBytes; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 模板文件字节流
|
||||
|
/// </summary>
|
||||
|
public byte[] FileBytes { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 版本
|
||||
|
/// </summary>
|
||||
|
public string Version { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 模板zip压缩包地址
|
||||
|
/// </summary>
|
||||
|
public string SourceCodePath { get; } |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.Testing; |
||||
|
|
||||
|
namespace Lion.AbpPro.Core.Cli |
||||
|
{ |
||||
|
public abstract class LionAbpProCoreCliTestBase : AbpIntegratedTest<LionAbpProCoreCliTestBaseModule> |
||||
|
{ |
||||
|
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) |
||||
|
{ |
||||
|
options.UseAutofac(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
using Lion.AbpPro.Cli; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.Autofac; |
||||
|
using Volo.Abp.Modularity; |
||||
|
using LionAbpProOptions = Lion.AbpPro.Cli.Options.LionAbpProOptions; |
||||
|
|
||||
|
namespace Lion.AbpPro.Core.Cli |
||||
|
{ |
||||
|
[DependsOn(typeof(AbpTestBaseModule), |
||||
|
typeof(AbpProCliCoreModule))] |
||||
|
public class LionAbpProCoreCliTestBaseModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
|
||||
|
Configure<LionAbpProOptions>(options => |
||||
|
{ |
||||
|
options.Owner = "WangJunZzz"; |
||||
|
options.RepositoryId = "abp-vnext-pro"; |
||||
|
options.Token = "abp-vnext-proghp_47vqiabp-vnext-provNkHKJguOJkdHvnxUabp-vnext-protij7Qbdn1Qy3fUabp-vnext-pro"; |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
using Lion.AbpPro.Cli.Github; |
||||
|
using Shouldly; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Lion.AbpPro.Core.Cli; |
||||
|
|
||||
|
public sealed class LionAbpProManagerTests : LionAbpProCoreCliTestBase |
||||
|
{ |
||||
|
private readonly ILionAbpProManager _lionAbpProManager; |
||||
|
|
||||
|
public LionAbpProManagerTests() |
||||
|
{ |
||||
|
_lionAbpProManager = GetRequiredService<ILionAbpProManager>(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task GetLatestSourceCodeVersionAsync() |
||||
|
{ |
||||
|
var result= await _lionAbpProManager.GetLatestSourceCodeVersionAsync(); |
||||
|
result.ShouldBe("7.2.2.3"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task CheckSourceCodeVersionAsync() |
||||
|
{ |
||||
|
var result= await _lionAbpProManager.CheckSourceCodeVersionAsync("7.2.2.3"); |
||||
|
result.ShouldBe(true); |
||||
|
|
||||
|
var result1= await _lionAbpProManager.CheckSourceCodeVersionAsync("1.2.2.3"); |
||||
|
result1.ShouldBe(false); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task DownloadAsync() |
||||
|
{ |
||||
|
//await _lionAbpProManager.DownloadAsync("7.2.2.3");
|
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -1,6 +1,6 @@ |
|||||
{ |
{ |
||||
"sdk": { |
"sdk": { |
||||
"version": "7.0.102", |
"version": "7.0.304", |
||||
"rollForward": "latestFeature" |
"rollForward": "latestFeature" |
||||
} |
} |
||||
} |
} |
||||
|
|||||
Loading…
Reference in new issue