添加项目文件。
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace />
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.FileSystemGlobbing" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="NuGet.Versioning" Version="6.11.0" />
|
||||
<PackageReference Include="Polly.Extensions.Http" Version="3.0.0" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.4.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Volo.Abp.Core" Version="9.0.0" />
|
||||
<PackageReference Include="Volo.Abp.Json" Version="9.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Volo.Abp.Cli.Args;
|
||||
|
||||
public class CommandLineArgs
|
||||
{
|
||||
public string? Command { get; }
|
||||
|
||||
public string? Target { get; }
|
||||
|
||||
public HuaCommandLineOptions Options { get; }
|
||||
|
||||
public CommandLineArgs(string? command = null, string? target = null)
|
||||
{
|
||||
Command = command;
|
||||
Target = target;
|
||||
Options = new HuaCommandLineOptions();
|
||||
}
|
||||
|
||||
public static CommandLineArgs Empty()
|
||||
{
|
||||
return new CommandLineArgs();
|
||||
}
|
||||
|
||||
public bool IsCommand(string command)
|
||||
{
|
||||
return string.Equals(Command, command, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
if (Command != null)
|
||||
{
|
||||
sb.Append("Command: ").AppendLine(Command);
|
||||
}
|
||||
|
||||
if (Target != null)
|
||||
{
|
||||
sb.Append("Target: ").AppendLine(Target);
|
||||
}
|
||||
|
||||
if (Options.Any())
|
||||
{
|
||||
sb.AppendLine("Options:");
|
||||
foreach (var option in Options)
|
||||
{
|
||||
sb.Append(" - ").Append(option.Key).Append(" = ").AppendLine(option.Value);
|
||||
}
|
||||
}
|
||||
|
||||
if (sb.Length <= 0)
|
||||
{
|
||||
sb.Append("<EMPTY>");
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
|
||||
namespace Volo.Abp.Cli.Args;
|
||||
|
||||
public class CommandLineArgumentParser : ICommandLineArgumentParser, ITransientDependency
|
||||
{
|
||||
public CommandLineArgs Parse(string[] args)
|
||||
{
|
||||
if (args == null || args.Length == 0)
|
||||
{
|
||||
return CommandLineArgs.Empty();
|
||||
}
|
||||
|
||||
var argumentList = args.ToList();
|
||||
|
||||
// Command
|
||||
var command = argumentList[0];
|
||||
argumentList.RemoveAt(0);
|
||||
|
||||
if (!argumentList.Any())
|
||||
{
|
||||
return new CommandLineArgs(command);
|
||||
}
|
||||
|
||||
// Target
|
||||
var target = argumentList[0];
|
||||
if (target.StartsWith("-"))
|
||||
{
|
||||
target = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
argumentList.RemoveAt(0);
|
||||
}
|
||||
|
||||
if (!argumentList.Any())
|
||||
{
|
||||
return new CommandLineArgs(command, target);
|
||||
}
|
||||
|
||||
// Options
|
||||
var commandLineArgs = new CommandLineArgs(command, target);
|
||||
|
||||
while (argumentList.Any())
|
||||
{
|
||||
var optionName = ParseOptionName(argumentList[0]);
|
||||
argumentList.RemoveAt(0);
|
||||
|
||||
if (!argumentList.Any())
|
||||
{
|
||||
commandLineArgs.Options[optionName] = null;
|
||||
break;
|
||||
}
|
||||
|
||||
if (IsOptionName(argumentList[0]))
|
||||
{
|
||||
commandLineArgs.Options[optionName] = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
commandLineArgs.Options[optionName] = argumentList[0];
|
||||
argumentList.RemoveAt(0);
|
||||
}
|
||||
|
||||
return commandLineArgs;
|
||||
}
|
||||
|
||||
public CommandLineArgs Parse(string lineText)
|
||||
{
|
||||
return Parse(GetArgsArrayFromLine(lineText));
|
||||
}
|
||||
|
||||
private static bool IsOptionName(string argument)
|
||||
{
|
||||
return argument.StartsWith("-") || argument.StartsWith("--");
|
||||
}
|
||||
|
||||
private static string ParseOptionName(string argument)
|
||||
{
|
||||
if (argument.StartsWith("--"))
|
||||
{
|
||||
if (argument.Length <= 2)
|
||||
{
|
||||
throw new ArgumentException("Should specify an option name after '--' prefix!");
|
||||
}
|
||||
|
||||
return argument.Substring(2);
|
||||
}
|
||||
|
||||
if (argument.StartsWith("-"))
|
||||
{
|
||||
if (argument.Length <= 1)
|
||||
{
|
||||
throw new ArgumentException("Should specify an option name after '-' prefix!");
|
||||
}
|
||||
|
||||
return argument.Substring(1);
|
||||
}
|
||||
|
||||
throw new ArgumentException("Option names should start with '-' or '--'.");
|
||||
}
|
||||
|
||||
private static string[] GetArgsArrayFromLine(string lineText)
|
||||
{
|
||||
var args = new List<string>();
|
||||
var currentArgBuilder = new StringBuilder();
|
||||
string currentArg;
|
||||
var isInQuotes = false;
|
||||
|
||||
for (int i = 0; i < lineText.Length; i++)
|
||||
{
|
||||
var c = lineText[i];
|
||||
if (c == ' ' && !isInQuotes)
|
||||
{
|
||||
currentArg = currentArgBuilder.ToString();
|
||||
if (!string.IsNullOrWhiteSpace(currentArg))
|
||||
{
|
||||
args.Add(currentArg);
|
||||
}
|
||||
|
||||
currentArgBuilder = new StringBuilder();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (c == '\"')
|
||||
{
|
||||
isInQuotes = !isInQuotes;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentArgBuilder.Append(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
currentArg = currentArgBuilder.ToString();
|
||||
if (!string.IsNullOrWhiteSpace(currentArg))
|
||||
{
|
||||
args.Add(currentArg);
|
||||
}
|
||||
|
||||
return args.ToArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Volo.Abp.Cli.Args;
|
||||
|
||||
public class HuaCommandLineOptions : Dictionary<string, string?>
|
||||
{
|
||||
public string? GetOrNull(string name, params string[] alternativeNames)
|
||||
{
|
||||
var value = this.GetOrDefault(name);
|
||||
if (!string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
if (alternativeNames != null && alternativeNames.Length > 0)
|
||||
{
|
||||
foreach (var alternativeName in alternativeNames)
|
||||
{
|
||||
value = this.GetOrDefault(alternativeName);
|
||||
if (!string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Volo.Abp.Cli.Args;
|
||||
|
||||
public interface ICommandLineArgumentParser
|
||||
{
|
||||
CommandLineArgs Parse(string[] args);
|
||||
|
||||
CommandLineArgs Parse(string lineText);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Volo.Abp.Cli;
|
||||
|
||||
public static class CliConsts
|
||||
{
|
||||
public const string Command = "HuaCliCommand";
|
||||
|
||||
public const string HttpClientName = "HuaHttpClient";
|
||||
|
||||
public const string AppSettingsJsonFileName = "appsettings.json";
|
||||
|
||||
public const string AppSettingsSecretJsonFileName = "appsettings.secrets.json";
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Volo.Abp.Cli;
|
||||
|
||||
public static class CliPaths
|
||||
{
|
||||
public static string TemplateCache => Path.Combine(HuaRootPath, "templates");
|
||||
public static string Log => Path.Combine(HuaRootPath, "cli", "logs");
|
||||
public static string Root => Path.Combine(HuaRootPath, "cli");
|
||||
|
||||
public static readonly string HuaRootPath = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".hua");
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Volo.Abp.Cli.Args;
|
||||
using Volo.Abp.Cli.Commands;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
|
||||
namespace Volo.Abp.Cli;
|
||||
|
||||
public class CliService : ITransientDependency
|
||||
{
|
||||
public ILogger<CliService> Logger { get; set; }
|
||||
protected ICommandLineArgumentParser CommandLineArgumentParser { get; }
|
||||
protected ICommandSelector CommandSelector { get; }
|
||||
protected IServiceScopeFactory ServiceScopeFactory { get; }
|
||||
|
||||
public CliService(
|
||||
ICommandLineArgumentParser commandLineArgumentParser,
|
||||
ICommandSelector commandSelector,
|
||||
IServiceScopeFactory serviceScopeFactory)
|
||||
{
|
||||
CommandLineArgumentParser = commandLineArgumentParser;
|
||||
CommandSelector = commandSelector;
|
||||
ServiceScopeFactory = serviceScopeFactory;
|
||||
Logger = NullLogger<CliService>.Instance;
|
||||
}
|
||||
|
||||
public async Task RunAsync(string[] args)
|
||||
{
|
||||
var commandLineArgs = CommandLineArgumentParser.Parse(args);
|
||||
|
||||
try
|
||||
{
|
||||
await RunInternalAsync(commandLineArgs);
|
||||
}
|
||||
catch (CliUsageException usageException)
|
||||
{
|
||||
Logger.LogWarning(usageException.Message);
|
||||
Environment.ExitCode = 1;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "An unexpected error occurred.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RunInternalAsync(CommandLineArgs commandLineArgs)
|
||||
{
|
||||
var commandType = CommandSelector.Select(commandLineArgs);
|
||||
|
||||
using (var scope = ServiceScopeFactory.CreateScope())
|
||||
{
|
||||
var command = (IConsoleCommand)scope.ServiceProvider.GetRequiredService(commandType);
|
||||
await command.ExecuteAsync(commandLineArgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Volo.Abp.Cli;
|
||||
|
||||
public static class CliUrls
|
||||
{
|
||||
public const string HuaApiBaseUrl = "https://api.hua.com";
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
|
||||
namespace Volo.Abp.Cli;
|
||||
|
||||
public class CliUsageException : Exception
|
||||
{
|
||||
public CliUsageException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public CliUsageException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
using System;
|
||||
using Volo.Abp.Cli.Args;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
|
||||
namespace Volo.Abp.Cli.Commands;
|
||||
|
||||
public class CommandSelector : ICommandSelector, ITransientDependency
|
||||
{
|
||||
protected HuaCliOptions Options { get; }
|
||||
|
||||
public CommandSelector(IOptions<HuaCliOptions> options)
|
||||
{
|
||||
Options = options.Value;
|
||||
}
|
||||
|
||||
public Type Select(CommandLineArgs commandLineArgs)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(commandLineArgs.Command))
|
||||
{
|
||||
return typeof(HelpCommand);
|
||||
}
|
||||
|
||||
Options.Commands.TryGetValue(commandLineArgs.Command, out var commandType);
|
||||
return commandType ?? typeof(HelpCommand);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Volo.Abp.Cli.Args;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
|
||||
namespace Volo.Abp.Cli.Commands;
|
||||
|
||||
public class HelpCommand : IConsoleCommand, ITransientDependency
|
||||
{
|
||||
public const string Name = "help";
|
||||
|
||||
public ILogger<HelpCommand> Logger { get; set; }
|
||||
protected HuaCliOptions HuaCliOptions { get; }
|
||||
protected IServiceScopeFactory ServiceScopeFactory { get; }
|
||||
|
||||
public HelpCommand(IOptions<HuaCliOptions> cliOptions,
|
||||
IServiceScopeFactory serviceScopeFactory)
|
||||
{
|
||||
ServiceScopeFactory = serviceScopeFactory;
|
||||
Logger = NullLogger<HelpCommand>.Instance;
|
||||
HuaCliOptions = cliOptions.Value;
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(CommandLineArgs commandLineArgs)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(commandLineArgs.Target))
|
||||
{
|
||||
Logger.LogInformation(GetUsageInfo());
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
if (!HuaCliOptions.Commands.ContainsKey(commandLineArgs.Target))
|
||||
{
|
||||
Logger.LogWarning($"There is no command named {commandLineArgs.Target}.");
|
||||
Logger.LogInformation(GetUsageInfo());
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
var commandType = HuaCliOptions.Commands[commandLineArgs.Target];
|
||||
|
||||
using (var scope = ServiceScopeFactory.CreateScope())
|
||||
{
|
||||
var command = (IConsoleCommand)scope.ServiceProvider.GetRequiredService(commandType);
|
||||
Logger.LogInformation(command.GetUsageInfo());
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public string GetUsageInfo()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("Usage:");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(" hua <command> <target> [options]");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("Command List:");
|
||||
sb.AppendLine();
|
||||
|
||||
foreach (var command in HuaCliOptions.Commands.ToArray().OrderBy(x => x.Key))
|
||||
{
|
||||
var method = command.Value.GetMethod("GetShortDescription", BindingFlags.Static | BindingFlags.Public);
|
||||
if (method == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var shortDescription = (string)method.Invoke(null, null);
|
||||
|
||||
sb.Append(" > ");
|
||||
sb.Append(command.Key);
|
||||
sb.Append(string.IsNullOrWhiteSpace(shortDescription) ? "" : ":");
|
||||
sb.Append(" ");
|
||||
sb.AppendLine(shortDescription);
|
||||
}
|
||||
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("To get a detailed help for a command:");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(" hua help <command>");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static string GetShortDescription()
|
||||
{
|
||||
return "Show command line help. Write ` hua help <command> `";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
using Volo.Abp.Cli.Args;
|
||||
|
||||
namespace Volo.Abp.Cli.Commands;
|
||||
|
||||
public interface ICommandSelector
|
||||
{
|
||||
Type Select(CommandLineArgs commandLineArgs);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Threading.Tasks;
|
||||
using Volo.Abp.Cli.Args;
|
||||
|
||||
namespace Volo.Abp.Cli.Commands;
|
||||
|
||||
public interface IConsoleCommand
|
||||
{
|
||||
Task ExecuteAsync(CommandLineArgs commandLineArgs);
|
||||
|
||||
string GetUsageInfo();
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Volo.Abp.Cli.Args;
|
||||
using Volo.Abp.Cli.ProjectBuilding;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
|
||||
namespace Volo.Abp.Cli.Commands;
|
||||
|
||||
public class NewCommand : IConsoleCommand, ITransientDependency
|
||||
{
|
||||
public const string Name = "new";
|
||||
|
||||
public ILogger<NewCommand> Logger { get; set; }
|
||||
protected TemplateProjectBuilder TemplateProjectBuilder { get; }
|
||||
protected ITemplateInfoProvider TemplateInfoProvider { get; }
|
||||
|
||||
public NewCommand(
|
||||
ITemplateInfoProvider templateInfoProvider,
|
||||
TemplateProjectBuilder templateProjectBuilder)
|
||||
{
|
||||
TemplateInfoProvider = templateInfoProvider;
|
||||
TemplateProjectBuilder = templateProjectBuilder;
|
||||
Logger = NullLogger<NewCommand>.Instance;
|
||||
}
|
||||
|
||||
public async Task ExecuteAsync(CommandLineArgs commandLineArgs)
|
||||
{
|
||||
var projectName = commandLineArgs.Target;
|
||||
if (string.IsNullOrWhiteSpace(projectName))
|
||||
{
|
||||
throw new CliUsageException(
|
||||
"Project name is missing!" +
|
||||
Environment.NewLine + Environment.NewLine +
|
||||
GetUsageInfo());
|
||||
}
|
||||
|
||||
Logger.LogInformation("Creating your project...");
|
||||
Logger.LogInformation("Project name: " + projectName);
|
||||
|
||||
var template = commandLineArgs.Options.GetOrNull(Options.Template.Short, Options.Template.Long);
|
||||
if (template != null)
|
||||
{
|
||||
Logger.LogInformation("Template: " + template);
|
||||
}
|
||||
else
|
||||
{
|
||||
template = "app";
|
||||
}
|
||||
|
||||
var version = commandLineArgs.Options.GetOrNull(Options.Version.Short, Options.Version.Long);
|
||||
|
||||
var outputDir = commandLineArgs.Options.GetOrNull(
|
||||
Options.OutputDirectory.Short, Options.OutputDirectory.Long);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(outputDir))
|
||||
{
|
||||
outputDir = Path.Combine(Directory.GetCurrentDirectory(), projectName);
|
||||
}
|
||||
else
|
||||
{
|
||||
outputDir = Path.Combine(outputDir, projectName);
|
||||
}
|
||||
|
||||
var templateInfo = await TemplateInfoProvider.GetAsync(template);
|
||||
if (templateInfo == null)
|
||||
{
|
||||
throw new CliUsageException($"Template '{template}' not found.");
|
||||
}
|
||||
|
||||
var databaseProvider = commandLineArgs.Options.GetOrNull(
|
||||
Options.DatabaseProvider.Short, Options.DatabaseProvider.Long);
|
||||
|
||||
var uiFramework = commandLineArgs.Options.GetOrNull(
|
||||
Options.UiFramework.Short, Options.UiFramework.Long);
|
||||
|
||||
var connectionString = commandLineArgs.Options.GetOrNull(
|
||||
Options.ConnectionString.Short, Options.ConnectionString.Long);
|
||||
|
||||
var createSolutionFolder = commandLineArgs.Options.ContainsKey(
|
||||
Options.CreateSolutionFolder.Long);
|
||||
|
||||
var noRandomPort = commandLineArgs.Options.ContainsKey(
|
||||
Options.NoRandomPort.Long);
|
||||
|
||||
var dryRun = commandLineArgs.Options.ContainsKey(
|
||||
Options.DryRun.Short) || commandLineArgs.Options.ContainsKey(Options.DryRun.Long);
|
||||
|
||||
var buildArgs = new ProjectBuildArgs(
|
||||
projectName,
|
||||
templateInfo,
|
||||
version,
|
||||
outputDir,
|
||||
databaseProvider,
|
||||
uiFramework,
|
||||
connectionString,
|
||||
createSolutionFolder,
|
||||
noRandomPort,
|
||||
dryRun
|
||||
);
|
||||
|
||||
await TemplateProjectBuilder.BuildAsync(buildArgs);
|
||||
|
||||
Logger.LogInformation($"Project '{projectName}' created successfully at: {outputDir}");
|
||||
}
|
||||
|
||||
public string GetUsageInfo()
|
||||
{
|
||||
return @"Usage:
|
||||
|
||||
hua new <project-name> [options]
|
||||
|
||||
Options:
|
||||
-t|--template <template-name> (default: app)
|
||||
-v|--version <version> Semantic version number
|
||||
-o|--output <output-directory> Output directory
|
||||
-db|--database-provider <provider> Database provider (ef)
|
||||
-u|--ui-framework <framework> UI framework (mvc / none)
|
||||
-cs|--connection-string <string> Connection string
|
||||
--create-solution-folder Create solution folder
|
||||
--no-random-port Don't randomize ports
|
||||
-d|--dry-run Dry run without creating files
|
||||
|
||||
Examples:
|
||||
|
||||
hua new MyProject
|
||||
hua new MyProject -t app -db ef -u mvc
|
||||
hua new MyProject -o D:\Projects";
|
||||
}
|
||||
|
||||
public static string GetShortDescription()
|
||||
{
|
||||
return "Create a new solution based on a template.";
|
||||
}
|
||||
|
||||
public static class Options
|
||||
{
|
||||
public static class Template
|
||||
{
|
||||
public const string Short = "t";
|
||||
public const string Long = "template";
|
||||
}
|
||||
|
||||
public static class Version
|
||||
{
|
||||
public const string Short = "v";
|
||||
public const string Long = "version";
|
||||
}
|
||||
|
||||
public static class OutputDirectory
|
||||
{
|
||||
public const string Short = "o";
|
||||
public const string Long = "output";
|
||||
}
|
||||
|
||||
public static class DatabaseProvider
|
||||
{
|
||||
public const string Short = "db";
|
||||
public const string Long = "database-provider";
|
||||
}
|
||||
|
||||
public static class UiFramework
|
||||
{
|
||||
public const string Short = "u";
|
||||
public const string Long = "ui-framework";
|
||||
}
|
||||
|
||||
public static class ConnectionString
|
||||
{
|
||||
public const string Short = "cs";
|
||||
public const string Long = "connection-string";
|
||||
}
|
||||
|
||||
public static class CreateSolutionFolder
|
||||
{
|
||||
public const string Long = "create-solution-folder";
|
||||
}
|
||||
|
||||
public static class NoRandomPort
|
||||
{
|
||||
public const string Long = "no-random-port";
|
||||
}
|
||||
|
||||
public static class DryRun
|
||||
{
|
||||
public const string Short = "d";
|
||||
public const string Long = "dry-run";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Volo.Abp.Cli.Args;
|
||||
using Volo.Abp.Cli.ProjectModification;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
|
||||
namespace Volo.Abp.Cli.Commands;
|
||||
|
||||
public class UpdateCommand : IConsoleCommand, ITransientDependency
|
||||
{
|
||||
public const string Name = "update";
|
||||
|
||||
public ILogger<UpdateCommand> Logger { get; set; }
|
||||
protected NugetPackagesVersionUpdater NugetPackagesVersionUpdater { get; }
|
||||
|
||||
public UpdateCommand(NugetPackagesVersionUpdater nugetPackagesVersionUpdater)
|
||||
{
|
||||
NugetPackagesVersionUpdater = nugetPackagesVersionUpdater;
|
||||
Logger = NullLogger<UpdateCommand>.Instance;
|
||||
}
|
||||
|
||||
public async Task ExecuteAsync(CommandLineArgs commandLineArgs)
|
||||
{
|
||||
var version = commandLineArgs.Options.GetOrNull(
|
||||
Options.Version.Short, Options.Version.Long);
|
||||
|
||||
var dryRun = commandLineArgs.Options.ContainsKey(
|
||||
Options.DryRun.Short) || commandLineArgs.Options.ContainsKey(Options.DryRun.Long);
|
||||
|
||||
if (dryRun)
|
||||
{
|
||||
Logger.LogInformation("Dry run mode. No changes will be made.");
|
||||
}
|
||||
|
||||
await NugetPackagesVersionUpdater.UpdatePackagesAsync(version, dryRun);
|
||||
|
||||
if (version != null)
|
||||
{
|
||||
Logger.LogInformation($"Updated packages to version {version}.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogInformation("Updated packages to the latest version.");
|
||||
}
|
||||
}
|
||||
|
||||
public string GetUsageInfo()
|
||||
{
|
||||
return @"Usage:
|
||||
|
||||
hua update [options]
|
||||
|
||||
Options:
|
||||
-v|--version <version> Target version number
|
||||
-d|--dry-run Dry run without making changes
|
||||
|
||||
Examples:
|
||||
|
||||
hua update
|
||||
hua update -v 9.0.0
|
||||
hua update --dry-run";
|
||||
}
|
||||
|
||||
public static string GetShortDescription()
|
||||
{
|
||||
return "Update NuGet packages to the specified or latest version.";
|
||||
}
|
||||
|
||||
public static class Options
|
||||
{
|
||||
public static class Version
|
||||
{
|
||||
public const string Short = "v";
|
||||
public const string Long = "version";
|
||||
}
|
||||
|
||||
public static class DryRun
|
||||
{
|
||||
public const string Short = "d";
|
||||
public const string Long = "dry-run";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
|
||||
namespace Volo.Abp.Cli.Configuration;
|
||||
|
||||
public class ConfigReader : IConfigReader, ITransientDependency
|
||||
{
|
||||
public async Task<HuaCliConfig> ReadAsync(string directory)
|
||||
{
|
||||
var configFilePath = Path.Combine(directory, "hua-cli.json");
|
||||
|
||||
if (!File.Exists(configFilePath))
|
||||
{
|
||||
return new HuaCliConfig();
|
||||
}
|
||||
|
||||
var json = await File.ReadAllTextAsync(configFilePath);
|
||||
return JsonConvert.DeserializeObject<HuaCliConfig>(json) ?? new HuaCliConfig();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Volo.Abp.Cli.Configuration;
|
||||
|
||||
public class HuaCliConfig
|
||||
{
|
||||
public string? DefaultTemplate { get; set; }
|
||||
public string? DefaultDatabaseProvider { get; set; }
|
||||
public string? DefaultUiFramework { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Volo.Abp.Cli.Configuration;
|
||||
|
||||
public interface IConfigReader
|
||||
{
|
||||
Task<HuaCliConfig> ReadAsync(string directory);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Volo.Abp.Cli.Http;
|
||||
|
||||
public class CliHttpClientHandler : HttpClientHandler
|
||||
{
|
||||
public CliHttpClientHandler()
|
||||
{
|
||||
// Allow all certificates for development scenarios
|
||||
ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Volo.Abp.Cli.Commands;
|
||||
using Volo.Abp.Json;
|
||||
using Volo.Abp.Modularity;
|
||||
|
||||
namespace Volo.Abp.Cli;
|
||||
|
||||
[DependsOn(
|
||||
typeof(AbpJsonModule)
|
||||
)]
|
||||
public class AbpCliCoreModule : AbpModule
|
||||
{
|
||||
public override void ConfigureServices(ServiceConfigurationContext context)
|
||||
{
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
|
||||
Configure<HuaCliOptions>(options =>
|
||||
{
|
||||
options.Commands[HelpCommand.Name] = typeof(HelpCommand);
|
||||
options.Commands[NewCommand.Name] = typeof(NewCommand);
|
||||
options.Commands[UpdateCommand.Name] = typeof(UpdateCommand);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Volo.Abp.Cli;
|
||||
|
||||
public class HuaCliOptions
|
||||
{
|
||||
public Dictionary<string, Type> Commands { get; }
|
||||
|
||||
public bool CacheTemplates { get; set; } = true;
|
||||
|
||||
public string ToolName { get; set; } = "CLI";
|
||||
|
||||
public HuaCliOptions()
|
||||
{
|
||||
Commands = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Volo.Abp.Cli.ProjectBuilding.Building;
|
||||
|
||||
public class FileEntry
|
||||
{
|
||||
public string RelativePath { get; set; } = string.Empty;
|
||||
public string Content { get; set; } = string.Empty;
|
||||
public bool IsDirectory { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Volo.Abp.Cli.ProjectBuilding.Building;
|
||||
|
||||
public class ProjectBuildContext
|
||||
{
|
||||
public string ProjectName { get; }
|
||||
public string TemplateName { get; }
|
||||
public string? Version { get; }
|
||||
public string OutputDirectory { get; }
|
||||
public string? DatabaseProvider { get; }
|
||||
public string? UiFramework { get; }
|
||||
public string? ConnectionString { get; }
|
||||
public bool CreateSolutionFolder { get; }
|
||||
public bool NoRandomPort { get; }
|
||||
public string TemplatePath { get; set; }
|
||||
public List<FileEntry> Files { get; } = new List<FileEntry>();
|
||||
|
||||
public ProjectBuildContext(
|
||||
string projectName,
|
||||
string templateName,
|
||||
string? version,
|
||||
string outputDirectory,
|
||||
string? databaseProvider,
|
||||
string? uiFramework,
|
||||
string? connectionString,
|
||||
bool createSolutionFolder,
|
||||
bool noRandomPort,
|
||||
string templatePath)
|
||||
{
|
||||
ProjectName = projectName;
|
||||
TemplateName = templateName;
|
||||
Version = version;
|
||||
OutputDirectory = outputDirectory;
|
||||
DatabaseProvider = databaseProvider;
|
||||
UiFramework = uiFramework;
|
||||
ConnectionString = connectionString;
|
||||
CreateSolutionFolder = createSolutionFolder;
|
||||
NoRandomPort = noRandomPort;
|
||||
TemplatePath = templatePath;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Volo.Abp.Cli.ProjectBuilding.Building;
|
||||
|
||||
public class ProjectBuildPipeline
|
||||
{
|
||||
private readonly List<ProjectBuildPipelineStep> _steps = new List<ProjectBuildPipelineStep>();
|
||||
|
||||
public void AddStep(ProjectBuildPipelineStep step)
|
||||
{
|
||||
_steps.Add(step);
|
||||
}
|
||||
|
||||
public async Task ExecuteAsync()
|
||||
{
|
||||
foreach (var step in _steps)
|
||||
{
|
||||
await step.ExecuteAsync(null!);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Volo.Abp.Cli.ProjectBuilding.Building;
|
||||
|
||||
public abstract class ProjectBuildPipelineStep
|
||||
{
|
||||
public abstract Task ExecuteAsync(ProjectBuildContext context);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps;
|
||||
|
||||
public class CreateProjectResultStep : ProjectBuildPipelineStep
|
||||
{
|
||||
public override Task ExecuteAsync(ProjectBuildContext context)
|
||||
{
|
||||
Directory.CreateDirectory(context.OutputDirectory);
|
||||
|
||||
foreach (var file in context.Files)
|
||||
{
|
||||
if (file.IsDirectory)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var filePath = Path.Combine(context.OutputDirectory, file.RelativePath);
|
||||
var directory = Path.GetDirectoryName(filePath);
|
||||
|
||||
if (!string.IsNullOrEmpty(directory))
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
}
|
||||
|
||||
// Replace placeholder variables in content
|
||||
var content = file.Content
|
||||
.Replace("__PROJECT_NAME__", context.ProjectName)
|
||||
.Replace("__VERSION__", context.Version ?? "1.0.0");
|
||||
|
||||
File.WriteAllText(filePath, content);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps;
|
||||
|
||||
public class FileEntryListReadStep : ProjectBuildPipelineStep
|
||||
{
|
||||
public override async Task ExecuteAsync(ProjectBuildContext context)
|
||||
{
|
||||
if (!File.Exists(context.TemplatePath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var fileStream = File.OpenRead(context.TemplatePath);
|
||||
using var zipStream = new ICSharpCode.SharpZipLib.Zip.ZipInputStream(fileStream);
|
||||
|
||||
var entry = zipStream.GetNextEntry();
|
||||
while (entry != null)
|
||||
{
|
||||
if (!entry.IsDirectory)
|
||||
{
|
||||
using var reader = new StreamReader(zipStream);
|
||||
var content = await reader.ReadToEndAsync();
|
||||
|
||||
context.Files.Add(new FileEntry
|
||||
{
|
||||
RelativePath = entry.Name,
|
||||
Content = content,
|
||||
IsDirectory = false
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Files.Add(new FileEntry
|
||||
{
|
||||
RelativePath = entry.Name,
|
||||
IsDirectory = true
|
||||
});
|
||||
}
|
||||
|
||||
entry = zipStream.GetNextEntry();
|
||||
}
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps;
|
||||
|
||||
public class ProjectReferenceReplaceStep : ProjectBuildPipelineStep
|
||||
{
|
||||
public override Task ExecuteAsync(ProjectBuildContext context)
|
||||
{
|
||||
foreach (var file in context.Files)
|
||||
{
|
||||
if (file.IsDirectory)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
file.Content = file.Content.Replace(
|
||||
$"MyCompanyName.MyProjectName", context.ProjectName);
|
||||
|
||||
file.Content = file.Content.Replace(
|
||||
"MyProjectName", context.ProjectName);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps;
|
||||
|
||||
public class SolutionRenameStep : ProjectBuildPipelineStep
|
||||
{
|
||||
public override Task ExecuteAsync(ProjectBuildContext context)
|
||||
{
|
||||
var oldName = context.TemplateName;
|
||||
|
||||
foreach (var file in context.Files)
|
||||
{
|
||||
file.RelativePath = file.RelativePath.Replace(oldName, context.ProjectName);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps;
|
||||
|
||||
public class TemplateCodeDeleteStep : ProjectBuildPipelineStep
|
||||
{
|
||||
public override Task ExecuteAsync(ProjectBuildContext context)
|
||||
{
|
||||
// Remove files marked as template-only
|
||||
var filesToRemove = context.Files
|
||||
.Where(f => f.RelativePath.Contains("__TEMPLATE__"))
|
||||
.ToList();
|
||||
|
||||
foreach (var file in filesToRemove)
|
||||
{
|
||||
context.Files.Remove(file);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
using Volo.Abp.Cli.ProjectBuilding.Building.Steps;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
|
||||
namespace Volo.Abp.Cli.ProjectBuilding.Building;
|
||||
|
||||
public class TemplateProjectBuildPipelineBuilder : ITransientDependency
|
||||
{
|
||||
public ProjectBuildPipeline Build(ProjectBuildContext context)
|
||||
{
|
||||
var pipeline = new ProjectBuildPipeline();
|
||||
|
||||
pipeline.AddStep(new FileEntryListReadStep());
|
||||
pipeline.AddStep(new SolutionRenameStep());
|
||||
pipeline.AddStep(new ProjectReferenceReplaceStep());
|
||||
pipeline.AddStep(new TemplateCodeDeleteStep());
|
||||
pipeline.AddStep(new CreateProjectResultStep());
|
||||
|
||||
return pipeline;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
|
||||
namespace Volo.Abp.Cli.ProjectBuilding;
|
||||
|
||||
public class BuiltInSourceCodeStore : ISourceCodeStore, ITransientDependency
|
||||
{
|
||||
public Task<string> GetAsync(string name, string? version = null)
|
||||
{
|
||||
var templatePath = Path.Combine(
|
||||
Path.GetDirectoryName(typeof(BuiltInSourceCodeStore).Assembly.Location) ?? ".",
|
||||
"Templates",
|
||||
"BuiltIn",
|
||||
$"{name}.zip");
|
||||
|
||||
if (File.Exists(templatePath))
|
||||
{
|
||||
return Task.FromResult(templatePath);
|
||||
}
|
||||
|
||||
throw new FileNotFoundException($"Template '{name}' not found at {templatePath}.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Volo.Abp.Cli.ProjectBuilding;
|
||||
|
||||
public interface ISourceCodeStore
|
||||
{
|
||||
Task<string> GetAsync(string name, string? version = null);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Volo.Abp.Cli.ProjectBuilding;
|
||||
|
||||
public interface ITemplateInfoProvider
|
||||
{
|
||||
Task<TemplateInfo?> GetAsync(string name);
|
||||
|
||||
Task<TemplateInfo> GetDefaultAsync();
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
namespace Volo.Abp.Cli.ProjectBuilding;
|
||||
|
||||
public class ProjectBuildArgs
|
||||
{
|
||||
public string ProjectName { get; }
|
||||
public TemplateInfo Template { get; }
|
||||
public string? Version { get; }
|
||||
public string OutputDirectory { get; }
|
||||
public string? DatabaseProvider { get; }
|
||||
public string? UiFramework { get; }
|
||||
public string? ConnectionString { get; }
|
||||
public bool CreateSolutionFolder { get; }
|
||||
public bool NoRandomPort { get; }
|
||||
public bool DryRun { get; }
|
||||
|
||||
public ProjectBuildArgs(
|
||||
string projectName,
|
||||
TemplateInfo template,
|
||||
string? version,
|
||||
string outputDirectory,
|
||||
string? databaseProvider,
|
||||
string? uiFramework,
|
||||
string? connectionString,
|
||||
bool createSolutionFolder,
|
||||
bool noRandomPort,
|
||||
bool dryRun)
|
||||
{
|
||||
ProjectName = projectName;
|
||||
Template = template;
|
||||
Version = version;
|
||||
OutputDirectory = outputDirectory;
|
||||
DatabaseProvider = databaseProvider;
|
||||
UiFramework = uiFramework;
|
||||
ConnectionString = connectionString;
|
||||
CreateSolutionFolder = createSolutionFolder;
|
||||
NoRandomPort = noRandomPort;
|
||||
DryRun = dryRun;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace Volo.Abp.Cli.ProjectBuilding;
|
||||
|
||||
public abstract class TemplateInfo
|
||||
{
|
||||
public string Name { get; }
|
||||
public string Description { get; }
|
||||
public string? Version { get; }
|
||||
|
||||
protected TemplateInfo(string name, string description, string? version = null)
|
||||
{
|
||||
Name = name;
|
||||
Description = description;
|
||||
Version = version;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using System.Threading.Tasks;
|
||||
using Volo.Abp.Cli.ProjectBuilding.Templates.App;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
|
||||
namespace Volo.Abp.Cli.ProjectBuilding;
|
||||
|
||||
public class TemplateInfoProvider : ITemplateInfoProvider, ITransientDependency
|
||||
{
|
||||
public Task<TemplateInfo?> GetAsync(string name)
|
||||
{
|
||||
TemplateInfo? template = name switch
|
||||
{
|
||||
"app" => new AppTemplate(),
|
||||
_ => null
|
||||
};
|
||||
|
||||
return Task.FromResult(template);
|
||||
}
|
||||
|
||||
public Task<TemplateInfo> GetDefaultAsync()
|
||||
{
|
||||
return Task.FromResult<TemplateInfo>(new AppTemplate());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Volo.Abp.Cli.ProjectBuilding.Building;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
|
||||
namespace Volo.Abp.Cli.ProjectBuilding;
|
||||
|
||||
public class TemplateProjectBuilder : ITransientDependency
|
||||
{
|
||||
protected ISourceCodeStore SourceCodeStore { get; }
|
||||
protected TemplateProjectBuildPipelineBuilder PipelineBuilder { get; }
|
||||
|
||||
public TemplateProjectBuilder(
|
||||
ISourceCodeStore sourceCodeStore,
|
||||
TemplateProjectBuildPipelineBuilder pipelineBuilder)
|
||||
{
|
||||
SourceCodeStore = sourceCodeStore;
|
||||
PipelineBuilder = pipelineBuilder;
|
||||
}
|
||||
|
||||
public async Task BuildAsync(ProjectBuildArgs args)
|
||||
{
|
||||
if (args.DryRun)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var templatePath = await SourceCodeStore.GetAsync(
|
||||
args.Template.Name, args.Version);
|
||||
|
||||
var context = new ProjectBuildContext(
|
||||
args.ProjectName,
|
||||
args.Template.Name,
|
||||
args.Version,
|
||||
args.OutputDirectory,
|
||||
args.DatabaseProvider,
|
||||
args.UiFramework,
|
||||
args.ConnectionString,
|
||||
args.CreateSolutionFolder,
|
||||
args.NoRandomPort,
|
||||
templatePath);
|
||||
|
||||
var pipeline = PipelineBuilder.Build(context);
|
||||
await pipeline.ExecuteAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Volo.Abp.Cli.ProjectBuilding.Templates.App;
|
||||
|
||||
public class AppTemplate : TemplateInfo
|
||||
{
|
||||
public AppTemplate()
|
||||
: base("app",
|
||||
"Standard layered application (Application / Application.Contracts / Domain / Domain.Shared / EntityFrameworkCore / Web / HttpApi)",
|
||||
null)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
|
||||
namespace Volo.Abp.Cli.ProjectModification;
|
||||
|
||||
public class NugetPackagesVersionUpdater : ITransientDependency
|
||||
{
|
||||
public Task UpdatePackagesAsync(string? version, bool dryRun)
|
||||
{
|
||||
var currentDir = Directory.GetCurrentDirectory();
|
||||
var csprojFiles = Directory.GetFiles(currentDir, "*.csproj", SearchOption.AllDirectories);
|
||||
|
||||
foreach (var csprojPath in csprojFiles)
|
||||
{
|
||||
// Skip packages folder
|
||||
if (csprojPath.Replace('\\', '/').Contains("/packages/"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var content = File.ReadAllText(csprojPath);
|
||||
var originalContent = content;
|
||||
|
||||
// Update Volo.Abp.* package references
|
||||
if (!string.IsNullOrEmpty(version))
|
||||
{
|
||||
content = UpdatePackageReferences(content, version);
|
||||
}
|
||||
|
||||
if (!dryRun && content != originalContent)
|
||||
{
|
||||
File.WriteAllText(csprojPath, content);
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private string UpdatePackageReferences(string csprojContent, string version)
|
||||
{
|
||||
// Simple version replacement for PackageReference elements
|
||||
var lines = csprojContent.Split('\n');
|
||||
for (int i = 0; i < lines.Length; i++)
|
||||
{
|
||||
if (lines[i].Contains("<PackageReference") &&
|
||||
lines[i].Contains("Version="))
|
||||
{
|
||||
var line = lines[i];
|
||||
var versionStart = line.IndexOf("Version=\"");
|
||||
if (versionStart >= 0)
|
||||
{
|
||||
var versionEnd = line.IndexOf("\"", versionStart + 9);
|
||||
if (versionEnd >= 0)
|
||||
{
|
||||
lines[i] = line.Substring(0, versionStart + 9) + version +
|
||||
line.Substring(versionEnd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return string.Join("\n", lines);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
|
||||
namespace Volo.Abp.Cli.Utils;
|
||||
|
||||
public interface ICmdHelper
|
||||
{
|
||||
Task<CmdResult> RunAsync(string command, string? workingDirectory = null);
|
||||
}
|
||||
|
||||
public class CmdResult
|
||||
{
|
||||
public string Output { get; set; } = string.Empty;
|
||||
public string Error { get; set; } = string.Empty;
|
||||
public int ExitCode { get; set; }
|
||||
public bool IsSuccess => ExitCode == 0;
|
||||
}
|
||||
|
||||
public class CmdHelper : ICmdHelper, ITransientDependency
|
||||
{
|
||||
public async Task<CmdResult> RunAsync(string command, string? workingDirectory = null)
|
||||
{
|
||||
var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||
var processStartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = isWindows ? "cmd.exe" : "/bin/bash",
|
||||
Arguments = isWindows ? $"/c \"{command}\"" : $"-c \"{command}\"",
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(workingDirectory))
|
||||
{
|
||||
processStartInfo.WorkingDirectory = workingDirectory;
|
||||
}
|
||||
|
||||
using var process = new Process { StartInfo = processStartInfo };
|
||||
process.Start();
|
||||
|
||||
var output = await process.StandardOutput.ReadToEndAsync();
|
||||
var error = await process.StandardError.ReadToEndAsync();
|
||||
await process.WaitForExitAsync();
|
||||
|
||||
return new CmdResult
|
||||
{
|
||||
Output = output,
|
||||
Error = error,
|
||||
ExitCode = process.ExitCode
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
|
||||
namespace Volo.Abp.Cli.Utils;
|
||||
|
||||
public interface IConsoleHelper
|
||||
{
|
||||
void WriteSuccess(string message);
|
||||
void WriteInfo(string message);
|
||||
void WriteWarning(string message);
|
||||
void WriteError(string message);
|
||||
}
|
||||
|
||||
public class ConsoleHelper : IConsoleHelper, ITransientDependency
|
||||
{
|
||||
public void WriteSuccess(string message)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.WriteLine(message);
|
||||
Console.ResetColor();
|
||||
}
|
||||
|
||||
public void WriteInfo(string message)
|
||||
{
|
||||
Console.ResetColor();
|
||||
Console.WriteLine(message);
|
||||
}
|
||||
|
||||
public void WriteWarning(string message)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine(message);
|
||||
Console.ResetColor();
|
||||
}
|
||||
|
||||
public void WriteError(string message)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.Error.WriteLine(message);
|
||||
Console.ResetColor();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using System.IO;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
|
||||
namespace Volo.Abp.Cli.Utils;
|
||||
|
||||
public interface IPathHelper
|
||||
{
|
||||
string GetCurrentDirectory();
|
||||
bool IsDirectory(string path);
|
||||
bool IsFile(string path);
|
||||
}
|
||||
|
||||
public class PathHelper : IPathHelper, ITransientDependency
|
||||
{
|
||||
public string GetCurrentDirectory()
|
||||
{
|
||||
return Directory.GetCurrentDirectory();
|
||||
}
|
||||
|
||||
public bool IsDirectory(string path)
|
||||
{
|
||||
return Directory.Exists(path);
|
||||
}
|
||||
|
||||
public bool IsFile(string path)
|
||||
{
|
||||
return File.Exists(path);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using NuGet.Versioning;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
|
||||
namespace Volo.Abp.Cli.Version;
|
||||
|
||||
public class CliVersionService : ITransientDependency
|
||||
{
|
||||
public Task<SemanticVersion> GetCurrentCliVersionAsync()
|
||||
{
|
||||
var assembly = typeof(CliVersionService).Assembly;
|
||||
var version = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?
|
||||
.InformationalVersion;
|
||||
|
||||
if (string.IsNullOrEmpty(version))
|
||||
{
|
||||
version = "1.0.0";
|
||||
}
|
||||
|
||||
// Remove git commit hash suffix if present
|
||||
var plusIndex = version.IndexOf('+');
|
||||
if (plusIndex >= 0)
|
||||
{
|
||||
version = version.Substring(0, plusIndex);
|
||||
}
|
||||
|
||||
return Task.FromResult(SemanticVersion.Parse(version));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using NuGet.Versioning;
|
||||
|
||||
namespace Volo.Abp.Cli.Version;
|
||||
|
||||
public class LatestVersionInfo
|
||||
{
|
||||
public SemanticVersion? Version { get; set; }
|
||||
public string? Message { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NuGet.Versioning;
|
||||
using Volo.Abp.Cli.Http;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
|
||||
namespace Volo.Abp.Cli.Version;
|
||||
|
||||
public class PackageVersionCheckerService : ITransientDependency
|
||||
{
|
||||
protected IHttpClientFactory HttpClientFactory { get; }
|
||||
|
||||
public PackageVersionCheckerService(IHttpClientFactory httpClientFactory)
|
||||
{
|
||||
HttpClientFactory = httpClientFactory;
|
||||
}
|
||||
|
||||
public async Task<LatestVersionInfo?> GetLatestVersionOrNullAsync(string packageId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var client = HttpClientFactory.CreateClient(CliConsts.HttpClientName);
|
||||
var response = await client.GetStringAsync(
|
||||
$"https://api.nuget.org/v3-flatcontainer/{packageId.ToLowerInvariant()}/index.json");
|
||||
|
||||
var json = JObject.Parse(response);
|
||||
var versions = json["versions"]?.ToObject<string[]>();
|
||||
|
||||
if (versions == null || versions.Length == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
SemanticVersion? latestVersion = null;
|
||||
foreach (var versionStr in versions)
|
||||
{
|
||||
if (SemanticVersion.TryParse(versionStr, out var parsed))
|
||||
{
|
||||
if (!parsed.IsPrerelease && (latestVersion == null || parsed > latestVersion))
|
||||
{
|
||||
latestVersion = parsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return latestVersion != null
|
||||
? new LatestVersionInfo { Version = latestVersion }
|
||||
: null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<Nullable>enable</Nullable>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<PackAsTool>true</PackAsTool>
|
||||
<ToolCommandName>hua</ToolCommandName>
|
||||
<RootNamespace />
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Volo.Abp.Autofac" Version="9.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Hua.Cli.Core\Hua.Cli.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,13 @@
|
||||
using Volo.Abp.Autofac;
|
||||
using Volo.Abp.Cli;
|
||||
using Volo.Abp.Modularity;
|
||||
|
||||
namespace Hua.Cli;
|
||||
|
||||
[DependsOn(
|
||||
typeof(AbpCliCoreModule),
|
||||
typeof(AbpAutofacModule)
|
||||
)]
|
||||
public class HuaCliModule : AbpModule
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
using Volo.Abp;
|
||||
using Volo.Abp.Cli;
|
||||
|
||||
namespace Hua.Cli;
|
||||
|
||||
public class Program
|
||||
{
|
||||
private static async Task Main(string[] args)
|
||||
{
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.MinimumLevel.Debug()
|
||||
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
|
||||
.MinimumLevel.Override("Volo.Abp", LogEventLevel.Warning)
|
||||
.MinimumLevel.Override("Volo.Abp.Cli", LogEventLevel.Information)
|
||||
.Enrich.FromLogContext()
|
||||
.WriteTo.Console()
|
||||
.WriteTo.File(
|
||||
Path.Combine(CliPaths.Log, "hua-cli.log"),
|
||||
rollingInterval: RollingInterval.Day,
|
||||
fileSizeLimitBytes: 10 * 1024 * 1024,
|
||||
retainedFileCountLimit: 10
|
||||
)
|
||||
.CreateLogger();
|
||||
|
||||
try
|
||||
{
|
||||
using var application = AbpApplicationFactory.Create<HuaCliModule>(options =>
|
||||
{
|
||||
options.UseAutofac();
|
||||
options.Services.AddLogging(c => c.AddSerilog());
|
||||
});
|
||||
|
||||
application.Initialize();
|
||||
|
||||
await application.ServiceProvider
|
||||
.GetRequiredService<CliService>()
|
||||
.RunAsync(args);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Fatal(ex, "Host terminated unexpectedly.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Log.CloseAndFlush();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user