Merge pull request #3192 from abpframework/api-definition

Angular type script client proxy generator
pull/3193/head
Halil İbrahim Kalkan 6 years ago committed by GitHub
commit ced8c35b11
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -14,9 +14,9 @@ namespace Volo.Abp.AspNetCore.Mvc.ApiExploring
}
[HttpGet]
public ApplicationApiDescriptionModel Get()
public ApplicationApiDescriptionModel Get(ApplicationApiDescriptionModelRequestDto model)
{
return _modelProvider.CreateApiModel();
return _modelProvider.CreateApiModel(model);
}
}
}

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
@ -12,10 +13,13 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Volo.Abp.Application.Services;
using Volo.Abp.AspNetCore.Mvc.ApiExploring;
using Volo.Abp.AspNetCore.Mvc.Conventions;
using Volo.Abp.AspNetCore.Mvc.Utils;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Http.Modeling;
using Volo.Abp.Reflection;
using Volo.Abp.Threading;
namespace Volo.Abp.AspNetCore.Mvc
{
@ -39,7 +43,7 @@ namespace Volo.Abp.AspNetCore.Mvc
Logger = NullLogger<AspNetCoreApiDescriptionModelProvider>.Instance;
}
public ApplicationApiDescriptionModel CreateApiModel()
public ApplicationApiDescriptionModel CreateApiModel(ApplicationApiDescriptionModelRequestDto input)
{
//TODO: Can cache the model?
@ -54,19 +58,22 @@ namespace Volo.Abp.AspNetCore.Mvc
continue;
}
AddApiDescriptionToModel(apiDescription, model);
AddApiDescriptionToModel(apiDescription, model, input);
}
}
return model;
}
private void AddApiDescriptionToModel(ApiDescription apiDescription, ApplicationApiDescriptionModel model)
private void AddApiDescriptionToModel(
ApiDescription apiDescription,
ApplicationApiDescriptionModel applicationModel,
ApplicationApiDescriptionModelRequestDto input)
{
var controllerType = apiDescription.ActionDescriptor.AsControllerActionDescriptor().ControllerTypeInfo.AsType();
var setting = FindSetting(controllerType);
var moduleModel = model.GetOrAddModule(GetRootPath(controllerType, setting));
var moduleModel = applicationModel.GetOrAddModule(GetRootPath(controllerType, setting));
var controllerModel = moduleModel.GetOrAddController(controllerType.FullName, CalculateControllerName(controllerType, setting), controllerType, _modelOptions.IgnoredInterfaces);
@ -80,6 +87,7 @@ namespace Volo.Abp.AspNetCore.Mvc
}
Logger.LogDebug($"ActionApiDescriptionModel.Create: {controllerModel.ControllerName}.{uniqueMethodName}");
var actionModel = controllerModel.AddAction(uniqueMethodName, ActionApiDescriptionModel.Create(
uniqueMethodName,
method,
@ -88,6 +96,11 @@ namespace Volo.Abp.AspNetCore.Mvc
GetSupportedVersions(controllerType, method, setting)
));
if (input.IncludeTypes)
{
AddCustomTypesToModel(applicationModel, method);
}
AddParameterDescriptionsToModel(actionModel, method, apiDescription);
}
@ -149,6 +162,68 @@ namespace Volo.Abp.AspNetCore.Mvc
return supportedVersions.Select(v => v.ToString()).Distinct().ToList();
}
private void AddCustomTypesToModel(ApplicationApiDescriptionModel applicationModel, MethodInfo method)
{
foreach (var parameterInfo in method.GetParameters())
{
AddCustomTypesToModel(applicationModel, parameterInfo.ParameterType);
}
AddCustomTypesToModel(applicationModel, method.ReturnType);
}
private static void AddCustomTypesToModel(ApplicationApiDescriptionModel applicationModel, [CanBeNull] Type type)
{
if (type == null)
{
return;
}
type = AsyncHelper.UnwrapTask(type);
if (type == typeof(object) ||
type == typeof(void) ||
type == typeof(Enum) ||
type == typeof(ValueType) ||
TypeHelper.IsPrimitiveExtended(type))
{
return;
}
if (TypeHelper.IsEnumerable(type, out var itemType))
{
AddCustomTypesToModel(applicationModel, itemType);
return;
}
if (TypeHelper.IsDictionary(type, out var keyType, out var valueType))
{
AddCustomTypesToModel(applicationModel, keyType);
AddCustomTypesToModel(applicationModel, valueType);
return;
}
/* TODO: Add interfaces
*/
var typeName = ModelingTypeHelper.GetFullNameHandlingNullableAndGenerics(type);
if (applicationModel.Types.ContainsKey(typeName))
{
return;
}
var typeModel = TypeApiDescriptionModel.Create(type);
applicationModel.Types[typeName] = typeModel;
AddCustomTypesToModel(applicationModel, type.BaseType);
foreach (var propertyInfo in type.GetProperties())
{
AddCustomTypesToModel(applicationModel, propertyInfo.PropertyType);
}
}
private void AddParameterDescriptionsToModel(ActionApiDescriptionModel actionModel, MethodInfo method, ApiDescription apiDescription)
{
if (!apiDescription.ParameterDescriptions.Any())

@ -28,10 +28,11 @@ namespace Volo.Abp.Cli
options.Commands["add-package"] = typeof(AddPackageCommand);
options.Commands["add-module"] = typeof(AddModuleCommand);
options.Commands["login"] = typeof(LoginCommand);
options.Commands["logout"] = typeof(LogoutCommand);
options.Commands["logout"] = typeof(LogoutCommand);
options.Commands["generate-proxy"] = typeof(GenerateProxyCommand);
options.Commands["suite"] = typeof(SuiteCommand);
options.Commands["switch-to-preview"] = typeof(SwitchNightlyPreviewCommand);
options.Commands["switch-to-stable"] = typeof(SwitchStableCommand);
options.Commands["switch-to-stable"] = typeof(SwitchStableCommand);
});
}
}

@ -0,0 +1,731 @@
using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Newtonsoft.Json.Linq;
using Volo.Abp.Cli.Args;
using Volo.Abp.Cli.ProjectBuilding;
using Volo.Abp.Cli.ProjectBuilding.Building;
using Volo.Abp.DependencyInjection;
using System.Collections.Generic;
using System.Linq;
using System.Net;
namespace Volo.Abp.Cli.Commands
{
public class GenerateProxyCommand : IConsoleCommand, ITransientDependency
{
public static Dictionary<string, Dictionary<string, string>> propertyList = new Dictionary<string, Dictionary<string, string>>();
public ILogger<GenerateProxyCommand> Logger { get; set; }
protected TemplateProjectBuilder TemplateProjectBuilder { get; }
public GenerateProxyCommand(TemplateProjectBuilder templateProjectBuilder)
{
TemplateProjectBuilder = templateProjectBuilder;
Logger = NullLogger<GenerateProxyCommand>.Instance;
}
public async Task ExecuteAsync(CommandLineArgs commandLineArgs)
{
var angularPath = $"angular.json";
if (!File.Exists(angularPath))
{
throw new CliUsageException(
"angular.json file not found. You must run this command in angular folder." +
Environment.NewLine + Environment.NewLine +
GetUsageInfo()
);
}
var module = commandLineArgs.Options.GetOrNull(Options.Module.Short, Options.Module.Long);
module = module == null ? "app" : module.ToLower();
var apiUrl = commandLineArgs.Options.GetOrNull(Options.ApiUrl.Short, Options.ApiUrl.Long);
if (string.IsNullOrWhiteSpace(apiUrl))
{
var environmentJson = File.ReadAllText("src/environments/environment.ts").Split("export const environment = ")[1].Replace(";", " ");
var environment = JObject.Parse(environmentJson);
apiUrl = environment["apis"]["default"]["url"].ToString();
}
apiUrl += "/api/abp/api-definition?IncludeTypes=true";
var uiFramework = GetUiFramework(commandLineArgs);
WebClient client = new WebClient();
string json = client.DownloadString(apiUrl);
//var sr = File.OpenText("api-definition.json");
//var json = sr.ReadToEnd();
Logger.LogInformation("Downloading api definition...");
Logger.LogInformation("Api Url: " + apiUrl);
var data = JObject.Parse(json);
Logger.LogInformation("Modules are combining");
var moduleList = GetCombinedModules(data, module);
if (moduleList.Count < 1)
{
throw new CliUsageException(
"Module can not find!" +
Environment.NewLine + Environment.NewLine +
GetUsageInfo()
);
}
Logger.LogInformation("Modules and types are creating");
foreach (var moduleItem in moduleList)
{
var moduleValue = JObject.Parse(moduleItem.Value);
var rootPath = moduleItem.Key;
Logger.LogInformation($"{rootPath} directory is creating");
Directory.CreateDirectory($"src/app/{rootPath}/shared/models");
Directory.CreateDirectory($"src/app/{rootPath}/shared/services");
var serviceIndexList = new List<string>();
var modelIndexList = new List<string>();
foreach (var controller in moduleValue.Root.ToList().Select(item => item.First))
{
var serviceFileText = new StringBuilder();
serviceFileText.AppendLine("[firstTypeList]");
serviceFileText.AppendLine("import { Injectable } from '@angular/core';");
serviceFileText.AppendLine("import { Observable } from 'rxjs';");
serviceFileText.AppendLine("[secondTypeList]");
serviceFileText.AppendLine("");
serviceFileText.AppendLine("@Injectable({providedIn: 'root'})");
serviceFileText.AppendLine("export class [controllerName]Service {");
serviceFileText.AppendLine(" constructor(private restService: RestService) {}");
serviceFileText.AppendLine("");
var firstTypeList = new List<string>();
var secondTypeList = new List<string>();
var controllerName = (string)controller["controllerName"];
var controllerServiceName = controllerName.PascalToKebabCase() + ".service.ts";
foreach (var actionItem in controller["actions"])
{
var action = actionItem.First;
var actionName = (string)action["uniqueName"];
actionName = (char.ToLower(actionName[0]) + actionName.Substring(1)).Replace("Async", "").Replace("Controller", "");
var returnValueType = (string)action["returnValue"]["type"];
var parameters = action["parameters"];
var parametersText = new StringBuilder();
var parametersIndex = 0;
var bodyExtra = "";
var modelBindingExtra = "";
var modelBindingExtraList = new List<string>();
var parameterModel = new List<ParameterModel>();
foreach (var parameter in parameters.OrderBy(p => p["bindingSourceId"]))
{
var bindingSourceId = (string)parameter["bindingSourceId"];
bindingSourceId = char.ToLower(bindingSourceId[0]) + bindingSourceId.Substring(1);
var name = (string)parameter["name"];
var typeSimple = (string)parameter["typeSimple"];
var typeArray = ((string)parameter["type"]).Split(".");
var type = (typeArray[typeArray.Length - 1]).TrimEnd('>');
var isOptional = (bool)parameter["isOptional"];
var defaultValue = (string)parameter["defaultValue"];
var modelIndex = CreateType(data, (string)parameter["type"], rootPath, modelIndexList);
if (!string.IsNullOrWhiteSpace(modelIndex))
{
modelIndexList.Add(modelIndex);
}
if (bindingSourceId == "body")
{
bodyExtra = ", body";
parameterModel = AddParameter(bindingSourceId, type, isOptional, defaultValue, bindingSourceId, parameterModel);
}
else if (bindingSourceId == "path")
{
parameterModel = AddParameter(name, typeSimple, isOptional, defaultValue, bindingSourceId, parameterModel);
}
else if (bindingSourceId == "modelBinding")
{
var parameterNameOnMethod = (string)parameter["nameOnMethod"];
var parametersOnMethod = action["parametersOnMethod"];
foreach (var parameterOnMethod in parametersOnMethod)
{
var parametersOnMethodName = (string)parameterOnMethod["name"];
if (parametersOnMethodName == parameterNameOnMethod)
{
typeSimple = (string)parameterOnMethod["typeSimple"];
typeArray = ((string)parameterOnMethod["type"]).Split(".");
type = typeArray[typeArray.Length - 1];
isOptional = (bool)parameterOnMethod["isOptional"];
defaultValue = (string)parameterOnMethod["defaultValue"];
if (typeSimple == "string" || typeSimple == "boolean" || typeSimple == "number")
{
parameterModel = AddParameter(name, typeSimple, isOptional, defaultValue, bindingSourceId, parameterModel);
}
modelIndex = CreateType(data, (string)parameterOnMethod["type"], rootPath, modelIndexList);
if (!string.IsNullOrWhiteSpace(modelIndex))
{
modelIndexList.Add(modelIndex);
}
}
}
if (typeSimple != "string" && typeSimple != "boolean" && typeSimple != "number")
{
parametersText.Append($"params = {{}} as {type}");
modelBindingExtra = ", params";
if (!string.IsNullOrWhiteSpace(modelIndex))
{
secondTypeList.Add(type);
}
else {
firstTypeList.Add(type);
}
break;
}
}
}
if (parameterModel != null && parameterModel.Count > 0)
{
foreach (var parameterItem in parameterModel.OrderBy(p => p.DisplayOrder))
{
var parameterItemModelName = parameterItem.Type.PascalToKebabCase() + ".ts";
var parameterItemModelPath = $"src/app/{rootPath}/shared/models/{parameterItemModelName}";
if (parameterItem.BindingSourceId == "body" && !File.Exists(parameterItemModelPath))
{
parameterItem.Type = "any";
}
parametersIndex++;
if (parametersIndex > 1)
{
parametersText.Append(", ");
}
parametersText.Append(parameterItem.Name + (parameterItem.IsOptional ? "?" : "") + ": " + parameterItem.Type + (parameterItem.Value != null ? (" = " + (string.IsNullOrWhiteSpace(parameterItem.Value) ? "''" : parameterItem.Value)) : ""));
if (parameterItem.BindingSourceId == "modelBinding")
{
modelBindingExtraList.Add(parameterItem.Name);
}
else if (parameterItem.BindingSourceId == "body" && File.Exists(parameterItemModelPath))
{
secondTypeList.Add(parameterItem.Type);
}
}
}
if (returnValueType != null)
{
if (returnValueType.IndexOf('<') > -1)
{
var firstTypeArray = returnValueType.Split("<")[0].Split(".");
var firstType = firstTypeArray[firstTypeArray.Length - 1];
var secondTypeArray = returnValueType.Split("<")[1].Split(".");
var secondType = secondTypeArray[secondTypeArray.Length - 1].TrimEnd('>');
var secondTypeModelName = secondType.PascalToKebabCase() + ".ts";
var secondTypeModelPath = $"src/app/{rootPath}/shared/models/{secondTypeModelName}";
if (firstType == "List" && !File.Exists(secondTypeModelPath))
{
secondType = "any";
}
serviceFileText.AppendLine(
firstType == "List"
? $" {actionName}({parametersText}): Observable<{secondType}[]> {{"
: $" {actionName}({parametersText}): Observable<{firstType}<{secondType}>> {{");
if (firstType != "List")
{
firstTypeList.Add(firstType);
}
if (secondType != "any")
{
secondTypeList.Add(secondType);
}
}
else
{
var typeArray = returnValueType.Split(".");
var type = typeArray[typeArray.Length - 1].TrimEnd('>');
type = type switch
{
"Void" => "void",
"String" => "string",
"IActionResult" => "void",
"ActionResult" => "void",
_ => type
};
serviceFileText.AppendLine(
$" {actionName}({parametersText}): Observable<{type}> {{");
if (type != "void" && type != "string")
{
secondTypeList.Add(type);
}
}
var modelIndex = CreateType(data, returnValueType, rootPath, modelIndexList);
if (!string.IsNullOrWhiteSpace(modelIndex))
{
modelIndexList.Add(modelIndex);
}
}
if (modelBindingExtraList != null && modelBindingExtraList.Count > 0)
{
modelBindingExtra = ", params: { " + string.Join(", ", modelBindingExtraList.ToArray()) + " }";
}
var url = ((string)action["url"]).Replace("/{", "/${");
var httpMethod = (string)action["httpMethod"];
serviceFileText.AppendLine(
url.Contains("${")
? $" return this.restService.request({{ url: `/{url}`, method: '{httpMethod}'{bodyExtra}{modelBindingExtra} }});"
: $" return this.restService.request({{ url: '/{url}', method: '{httpMethod}'{bodyExtra}{modelBindingExtra} }});");
serviceFileText.AppendLine(" }");
}
serviceIndexList.Add(controllerServiceName.Replace(".ts", ""));
if (firstTypeList != null && firstTypeList.Count > 0)
{
var firstTypeListDistinct = ", " + string.Join(", ", firstTypeList.Where(p => p != "void").Distinct().ToArray());
serviceFileText.Replace("[firstTypeList]",
$"import {{ RestService {firstTypeListDistinct}}} from '@abp/ng.core';");
}
else
{
serviceFileText.Replace("[firstTypeList]",
$"import {{ RestService }} from '@abp/ng.core';");
}
if (secondTypeList != null && secondTypeList.Count > 0)
{
var secondTypeListDistinct = string.Join(", ", secondTypeList.Where(p => p != "void").Distinct().ToArray());
serviceFileText.Replace("[secondTypeList]",
$"import {{{secondTypeListDistinct}}} from '../models';");
}
else
{
serviceFileText.Replace("[secondTypeList]", "");
}
serviceFileText.AppendLine("}");
serviceFileText.Replace("[controllerName]", controllerName);
File.WriteAllText($"src/app/{rootPath}/shared/services/{controllerServiceName}", serviceFileText.ToString());
}
var serviceIndexFileText = new StringBuilder();
foreach (var serviceIndexItem in serviceIndexList.Distinct())
{
serviceIndexFileText.AppendLine($"export * from './{serviceIndexItem}';");
}
File.WriteAllText($"src/app/{rootPath}/shared/services/index.ts", serviceIndexFileText.ToString());
var modelIndexFileText = new StringBuilder();
foreach (var modelIndexItem in modelIndexList.Distinct())
{
modelIndexFileText.AppendLine($"export * from './{modelIndexItem}';");
}
File.WriteAllText($"src/app/{rootPath}/shared/models/index.ts", modelIndexFileText.ToString());
}
Logger.LogInformation("Completed!");
}
private Dictionary<string, string> GetCombinedModules(JToken data, string module)
{
var moduleList = new Dictionary<string, string>();
foreach (var moduleItem in data["modules"])
{
var rootPath = ((string)moduleItem.First["rootPath"]).ToLower();
if (moduleList.Any(p => p.Key == rootPath))
{
var value = moduleList[rootPath];
moduleList[rootPath] = value.TrimEnd('}') + "," + moduleItem.First["controllers"].ToString().TrimStart('{');
}
else
{
moduleList.Add(rootPath, moduleItem.First["controllers"].ToString());
}
}
if (module != "all")
{
moduleList = moduleList.Where(p => p.Key.ToLower() == module).ToDictionary(p => p.Key, s => s.Value);
}
return moduleList;
}
private static string CreateType(JObject data, string returnValueType, string rootPath, List<string> modelIndexList)
{
var type = data["types"][returnValueType];
if (type == null)
{
return null;
}
if (returnValueType.StartsWith("Volo.Abp.Application.Dtos")
|| returnValueType.StartsWith("System.Collections")
|| returnValueType == "System.String"
|| returnValueType == "System.Void"
|| returnValueType.Contains("System.Net.HttpStatusCode?")
|| returnValueType.Contains("IActionResult")
|| returnValueType.Contains("ActionResult")
|| returnValueType.Contains("IStringValueType")
|| returnValueType.Contains("IValueValidator")
)
{
return null;
}
var typeNameSplit = returnValueType.Split(".");
var typeName = typeNameSplit[typeNameSplit.Length - 1];
if (typeName.Contains("HttpStatusCode"))
{
}
var typeModelName = typeName.Replace("<", "").Replace(">", "").Replace("?","").PascalToKebabCase() + ".ts";
var path = $"src/app/{rootPath}/shared/models/{typeModelName}";
var modelFileText = new StringBuilder();
var baseType = (string)type["baseType"];
var extends = "";
var customBaseTypeName = "";
var baseTypeName = "";
if (!string.IsNullOrWhiteSpace(baseType))
{
var baseTypeSplit = baseType.Split(".");
baseTypeName = baseTypeSplit[baseTypeSplit.Length - 1].Replace("<", "").Replace(">", "");
var baseTypeKebabCase = "./" + baseTypeName.PascalToKebabCase();
if (baseType != "System.Enum")
{
if (baseType.Contains("Volo.Abp.Application.Dtos"))
{
baseTypeKebabCase = "@abp/ng.core";
baseTypeName = baseType.Split("Volo.Abp.Application.Dtos")[1].Split("<")[0].TrimStart('.');
customBaseTypeName = baseType.Split("Volo.Abp.Application.Dtos")[1].Replace("System.Guid", "string").TrimStart('.');
}
if (baseTypeName.Contains("guid") || baseTypeName.Contains("Guid"))
{
baseTypeName = "string";
}
modelFileText.AppendLine($"import {{ {baseTypeName} }} from '{baseTypeKebabCase}';");
extends = "extends " + (!string.IsNullOrWhiteSpace(customBaseTypeName) ? customBaseTypeName : baseTypeName);
var modelIndex = CreateType(data, baseType, rootPath, modelIndexList);
if (!string.IsNullOrWhiteSpace(modelIndex))
{
modelIndexList.Add(modelIndex);
}
}
}
if (baseType == "System.Enum" && (bool)type["isEnum"])
{
modelFileText.AppendLine($"export enum {typeName} {{");
var enumNameList = type["enumNames"].ToArray();
var enumValueList = type["enumValues"].ToArray();
for (var i = 0; i < enumNameList.Length; i++)
{
modelFileText.AppendLine($" {enumNameList[i]} = {enumValueList[i]},");
}
modelFileText.AppendLine("}");
}
else
{
modelFileText.AppendLine("");
modelFileText.AppendLine($"export class {typeName} {extends} {{");
foreach (var property in type["properties"])
{
var propertyName = (string)property["name"];
propertyName = (char.ToLower(propertyName[0]) + propertyName.Substring(1));
var typeSimple = (string)property["typeSimple"];
var modelIndex = CreateType(data, (string)property["type"], rootPath, modelIndexList);
if (typeSimple.IndexOf("[") > -1 && typeSimple.IndexOf("]") > -1)
{
typeSimple = typeSimple.Replace("[", "").Replace("]", "") + "[]";
}
if (typeSimple.StartsWith("Volo.Abp"))
{
var typeSimpleSplit = typeSimple.Split(".");
typeSimple = typeSimpleSplit[typeSimpleSplit.Length - 1];
}
if (typeSimple.StartsWith("System.Object"))
{
typeSimple = typeSimple.Replace("System.Object", "object");
}
if (typeSimple.Contains("?"))
{
typeSimple = typeSimple.Replace("?", "");
propertyName += "?";
}
if (typeSimple != "boolean"
&& typeSimple != "string"
&& typeSimple != "number"
&& typeSimple != "boolean[]"
&& typeSimple != "string[]"
&& typeSimple != "number[]"
)
{
var typeSimpleModelName = typeSimple.PascalToKebabCase() + ".ts";
var modelPath = $"src/app/{rootPath}/shared/models/{typeSimpleModelName}";
if (!File.Exists(modelPath))
{
typeSimple = "any" + (typeSimple.Contains("[]") ? "[]" : "");
}
}
if (propertyList.Any(p => p.Key == baseTypeName && p.Value.Any(q => q.Key == propertyName && q.Value == typeSimple)))
{
continue;
}
if (!string.IsNullOrWhiteSpace(modelIndex))
{
var propertyTypeSplit = ((string)property["type"]).Split(".");
var propertyType = propertyTypeSplit[propertyTypeSplit.Length - 1];
modelFileText.Insert(0, "");
modelFileText.Insert(0, $"import {{ {propertyType} }} from '../models';");
modelFileText.Insert(0, "");
modelIndexList.Add(modelIndex);
}
if (propertyList.Any(p => p.Key == typeName && !p.Value.Any(q => q.Key == propertyName)))
{
propertyList[typeName].Add(propertyName, typeSimple);
}
else if (!propertyList.Any(p => p.Key == typeName))
{
var newProperty = new Dictionary<string, string>();
newProperty.Add(propertyName, typeSimple);
propertyList.Add(typeName, newProperty);
}
modelFileText.AppendLine($" {propertyName}: {typeSimple};");
}
modelFileText.AppendLine("");
modelFileText.AppendLine($" constructor(initialValues: Partial<{typeName}> = {{}}) {{");
if (!string.IsNullOrWhiteSpace(baseType))
{
modelFileText.AppendLine(" super(initialValues);");
modelFileText.AppendLine(" }");
}
else
{
modelFileText.AppendLine(" if (initialValues) {");
modelFileText.AppendLine(" for (const key in initialValues) {");
modelFileText.AppendLine(" if (initialValues.hasOwnProperty(key)) {");
modelFileText.AppendLine(" this[key] = initialValues[key];");
modelFileText.AppendLine(" }");
modelFileText.AppendLine(" }");
modelFileText.AppendLine(" }");
modelFileText.AppendLine(" }");
}
modelFileText.AppendLine("}");
}
File.WriteAllText($"src/app/{rootPath}/shared/models/{typeModelName}", modelFileText.ToString());
return typeModelName.Replace(".ts", "");
}
private static List<ParameterModel> AddParameter(string parameterName, string type, bool parameterIsOptional, string parameterDefaultValue, string bindingSourceId, List<ParameterModel> parameterModel)
{
if (parameterDefaultValue != "null")
{
parameterModel.Add(new ParameterModel
{
DisplayOrder = 3,
Name = parameterName,
Type = type,
Value = parameterDefaultValue,
BindingSourceId = bindingSourceId
});
}
else if (parameterDefaultValue == "null" && parameterIsOptional)
{
parameterModel.Add(new ParameterModel
{
DisplayOrder = 2,
Name = parameterName,
IsOptional = true,
Type = type,
BindingSourceId = bindingSourceId
});
}
else
{
parameterModel.Add(new ParameterModel
{
DisplayOrder = 1,
Name = parameterName,
Type = type,
BindingSourceId = bindingSourceId
});
}
return parameterModel;
}
public string GetUsageInfo()
{
var sb = new StringBuilder();
sb.AppendLine("");
sb.AppendLine("Usage:");
sb.AppendLine("");
sb.AppendLine(" abp generate-proxy [options]");
sb.AppendLine("");
sb.AppendLine("Options:");
sb.AppendLine("");
sb.AppendLine("-a|--apiUrl <api-url> (default: environment.ts>apis>default>url)");
sb.AppendLine("-u|--ui <ui-framework> (default: angular)");
sb.AppendLine("-m|--module <module> (default: app)");
sb.AppendLine("");
sb.AppendLine("Examples:");
sb.AppendLine("");
sb.AppendLine(" abp generate-proxy --apiUrl https://www.volosoft.com");
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 "Generates typescript service proxies and DTOs";
}
private UiFramework GetUiFramework(CommandLineArgs commandLineArgs)
{
var optionValue = commandLineArgs.Options.GetOrNull(Options.UiFramework.Short, Options.UiFramework.Long);
switch (optionValue)
{
case "none":
return UiFramework.None;
case "mvc":
return UiFramework.Mvc;
case "angular":
return UiFramework.Angular;
default:
return UiFramework.Angular;
}
}
public static class Options
{
public static class Module
{
public const string Short = "m";
public const string Long = "module";
}
public static class ApiUrl
{
public const string Short = "a";
public const string Long = "apiUrl";
}
public static class UiFramework
{
public const string Short = "u";
public const string Long = "ui";
}
}
}
public static class StringExtensions
{
public static string PascalToKebabCase(this string value)
{
if (string.IsNullOrEmpty(value))
{
return value;
}
return Regex.Replace(
value,
"(?<!^)([A-Z][a-z]|(?<=[a-z])[A-Z])",
"-$1",
RegexOptions.Compiled)
.Trim()
.ToLower();
}
}
public class ParameterModel
{
public int DisplayOrder { get; set; }
public string Name { get; set; }
public string Value { get; set; }
public string Type { get; set; }
public bool IsOptional { get; set; }
public string BindingSourceId { get; set; }
}
}

@ -12,6 +12,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="NETStandard.Library" Version="2.0.3" />
<PackageReference Include="Serilog.Extensions.Logging" Version="3.0.1" />
<PackageReference Include="Serilog.Sinks.File" Version="4.1.0" />

@ -1,5 +1,6 @@
using System.Collections.Generic;
using JetBrains.Annotations;
using Volo.Abp;
namespace System
{
@ -17,8 +18,10 @@ namespace System
/// Internally uses <see cref="Type.IsAssignableFrom"/>.
/// </summary>
/// <typeparam name="TTarget">Target type</typeparam> (as reverse).
public static bool IsAssignableTo<TTarget>(this Type type)
public static bool IsAssignableTo<TTarget>([NotNull] this Type type)
{
Check.NotNull(type, nameof(type));
return type.IsAssignableTo(typeof(TTarget));
}
@ -30,8 +33,11 @@ namespace System
/// </summary>
/// <param name="type">this type</param>
/// <param name="targetType">Target type</param>
public static bool IsAssignableTo(this Type type, Type targetType)
public static bool IsAssignableTo([NotNull] this Type type, [NotNull] Type targetType)
{
Check.NotNull(type, nameof(type));
Check.NotNull(targetType, nameof(targetType));
return targetType.IsAssignableFrom(type);
}
@ -40,8 +46,10 @@ namespace System
/// </summary>
/// <param name="type">The type to get its base classes.</param>
/// <param name="includeObject">True, to include the standard <see cref="object"/> type in the returned array.</param>
public static Type[] GetBaseClasses(this Type type, bool includeObject = true)
public static Type[] GetBaseClasses([NotNull] this Type type, bool includeObject = true)
{
Check.NotNull(type, nameof(type));
var types = new List<Type>();
AddTypeAndBaseTypesRecursively(types, type.BaseType, includeObject);
return types.ToArray();
@ -52,6 +60,8 @@ namespace System
[CanBeNull] Type type,
bool includeObject)
{
Check.NotNull(types, nameof(types));
if (type == null)
{
return;

@ -1,4 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
@ -54,6 +57,53 @@ namespace Volo.Abp.Reflection
return t;
}
public static bool IsEnumerable(Type type, out Type itemType, bool includePrimitives = true)
{
if (!includePrimitives && IsPrimitiveExtended(type))
{
itemType = null;
return false;
}
var enumerableTypes = ReflectionHelper.GetImplementedGenericTypes(type, typeof(IEnumerable<>));
if (enumerableTypes.Count == 1)
{
itemType = enumerableTypes[0].GenericTypeArguments[0];
return true;
}
if (typeof(IEnumerable).IsAssignableFrom(type))
{
itemType = typeof(object);
return true;
}
itemType = null;
return false;
}
public static bool IsDictionary(Type type, out Type keyType, out Type valueType)
{
var enumerableTypes = ReflectionHelper.GetImplementedGenericTypes(type, typeof(IDictionary<,>));
if (enumerableTypes.Count == 1)
{
keyType = enumerableTypes[0].GenericTypeArguments[0];
valueType = enumerableTypes[0].GenericTypeArguments[2];
return true;
}
if (typeof(IDictionary).IsAssignableFrom(type))
{
keyType = typeof(object);
valueType = typeof(object);
return true;
}
keyType = null;
valueType = null;
return false;
}
private static bool IsPrimitiveExtendedInternal(Type type, bool includeEnums)
{
if (type.IsPrimitive)

@ -27,6 +27,11 @@ namespace Volo.Abp.Threading
return type == typeof(Task) || (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Task<>));
}
public static bool IsTaskOfT([NotNull] this Type type)
{
return type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Task<>);
}
/// <summary>
/// Returns void if given type is Task.
/// Return T, if given type is Task{T}.
@ -41,7 +46,7 @@ namespace Volo.Abp.Threading
return typeof(void);
}
if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Task<>))
if (type.IsTaskOfT())
{
return type.GenericTypeArguments[0];
}

@ -56,8 +56,8 @@ namespace Volo.Abp.Http.Client.DynamicProxying
var found = true;
for (int i = 0; i < methodParameters.Length; i++)
{
if (!TypeMatches(action.ParametersOnMethod[i], methodParameters[i]))
{
if (!TypeMatches(action.ParametersOnMethod[i], methodParameters[i]))
{
found = false;
break;

@ -10,6 +10,8 @@ namespace Volo.Abp.Http.Modeling
{
public IDictionary<string, ModuleApiDescriptionModel> Modules { get; set; }
public IDictionary<string, TypeApiDescriptionModel> Types { get; set; }
private ApplicationApiDescriptionModel()
{
@ -19,8 +21,8 @@ namespace Volo.Abp.Http.Modeling
{
return new ApplicationApiDescriptionModel
{
//TODO: Why ConcurrentDictionary?
Modules = new ConcurrentDictionary<string, ModuleApiDescriptionModel>()
Modules = new ConcurrentDictionary<string, ModuleApiDescriptionModel>(), //TODO: Why ConcurrentDictionary?
Types = new Dictionary<string, TypeApiDescriptionModel>()
};
}

@ -0,0 +1,7 @@
namespace Volo.Abp.Http.Modeling
{
public class ApplicationApiDescriptionModelRequestDto
{
public bool IncludeTypes { get; set; }
}
}

@ -10,7 +10,7 @@ namespace Volo.Abp.Http.Modeling
{
public string ControllerName { get; set; }
public string TypeAsString { get; set; }
public string Type { get; set; }
public List<ControllerInterfaceApiDescriptionModel> Interfaces { get; set; }
@ -26,7 +26,7 @@ namespace Volo.Abp.Http.Modeling
return new ControllerApiDescriptionModel
{
ControllerName = controllerName,
TypeAsString = type.GetFullNameWithAssemblyName(),
Type = type.FullName,
Actions = new Dictionary<string, ActionApiDescriptionModel>(),
Interfaces = type
.GetInterfaces()
@ -52,7 +52,7 @@ namespace Volo.Abp.Http.Modeling
{
var subModel = new ControllerApiDescriptionModel
{
TypeAsString = TypeAsString,
Type = Type,
Interfaces = Interfaces,
ControllerName = ControllerName,
Actions = new Dictionary<string, ActionApiDescriptionModel>()
@ -71,7 +71,7 @@ namespace Volo.Abp.Http.Modeling
public bool Implements(Type interfaceType)
{
return Interfaces.Any(i => i.TypeAsString == interfaceType.GetFullNameWithAssemblyName());
return Interfaces.Any(i => i.Type == interfaceType.FullName);
}
public override string ToString()

@ -5,7 +5,7 @@ namespace Volo.Abp.Http.Modeling
[Serializable]
public class ControllerInterfaceApiDescriptionModel
{
public string TypeAsString { get; set; }
public string Type { get; set; }
private ControllerInterfaceApiDescriptionModel()
{
@ -16,7 +16,7 @@ namespace Volo.Abp.Http.Modeling
{
return new ControllerInterfaceApiDescriptionModel
{
TypeAsString = type.GetFullNameWithAssemblyName()
Type = type.FullName
};
}
}

@ -2,6 +2,6 @@ namespace Volo.Abp.Http.Modeling
{
public interface IApiDescriptionModelProvider
{
ApplicationApiDescriptionModel CreateApiModel();
ApplicationApiDescriptionModel CreateApiModel(ApplicationApiDescriptionModelRequestDto input);
}
}

@ -10,13 +10,17 @@ namespace Volo.Abp.Http.Modeling
public string TypeAsString { get; set; }
public string Type { get; set; }
public string TypeSimple { get; set; }
public bool IsOptional { get; set; }
public object DefaultValue { get; set; }
private MethodParameterApiDescriptionModel()
{
}
public static MethodParameterApiDescriptionModel Create(ParameterInfo parameterInfo)
@ -25,6 +29,8 @@ namespace Volo.Abp.Http.Modeling
{
Name = parameterInfo.Name,
TypeAsString = parameterInfo.ParameterType.GetFullNameWithAssemblyName(),
Type = parameterInfo.ParameterType != null ? ModelingTypeHelper.GetFullNameHandlingNullableAndGenerics(parameterInfo.ParameterType) : null,
TypeSimple = parameterInfo.ParameterType != null ? ModelingTypeHelper.GetSimplifiedName(parameterInfo.ParameterType) : null,
IsOptional = parameterInfo.IsOptional,
DefaultValue = parameterInfo.HasDefaultValue ? parameterInfo.DefaultValue : null
};

@ -0,0 +1,127 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
namespace Volo.Abp.Http.Modeling
{
public static class ModelingTypeHelper
{
public static string GetFullNameHandlingNullableAndGenerics([NotNull] Type type)
{
Check.NotNull(type, nameof(type));
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
{
return type.GenericTypeArguments[0].FullName + "?";
}
if (type.IsGenericType)
{
var genericType = type.GetGenericTypeDefinition();
return $"{genericType.FullName.Left(genericType.FullName.IndexOf('`'))}<{type.GenericTypeArguments.Select(GetFullNameHandlingNullableAndGenerics).JoinAsString(",")}>";
}
return type.FullName;
}
public static string GetSimplifiedName([NotNull] Type type)
{
Check.NotNull(type, nameof(type));
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
{
return GetSimplifiedName(type.GenericTypeArguments[0]) + "?";
}
if (type.IsGenericType)
{
var genericType = type.GetGenericTypeDefinition();
return $"{genericType.FullName.Left(genericType.FullName.IndexOf('`'))}<{type.GenericTypeArguments.Select(GetSimplifiedName).JoinAsString(",")}>";
}
if (type == typeof(string))
{
return "string";
}
else if (type == typeof(int))
{
return "number";
}
else if (type == typeof(long))
{
return "number";
}
else if (type == typeof(bool))
{
return "boolean";
}
else if (type == typeof(char))
{
return "string";
}
else if (type == typeof(double))
{
return "number";
}
else if (type == typeof(float))
{
return "number";
}
else if (type == typeof(decimal))
{
return "number";
}
else if (type == typeof(DateTime))
{
return "string";
}
else if (type == typeof(DateTimeOffset))
{
return "string";
}
else if (type == typeof(TimeSpan))
{
return "string";
}
else if (type == typeof(Guid))
{
return "string";
}
else if (type == typeof(byte))
{
return "number";
}
else if (type == typeof(sbyte))
{
return "number";
}
else if (type == typeof(short))
{
return "number";
}
else if (type == typeof(ushort))
{
return "number";
}
else if (type == typeof(uint))
{
return "number";
}
else if (type == typeof(ulong))
{
return "number";
}
else if (type == typeof(IntPtr))
{
return "number";
}
else if (type == typeof(UIntPtr))
{
return "number";
}
return type.FullName;
}
}
}

@ -9,7 +9,9 @@ namespace Volo.Abp.Http.Modeling
public string Name { get; set; }
public string TypeAsString { get; set; }
public string Type { get; set; }
public string TypeSimple { get; set; }
public bool IsOptional { get; set; }
@ -32,7 +34,8 @@ namespace Volo.Abp.Http.Modeling
{
Name = name,
NameOnMethod = nameOnMethod,
TypeAsString = type?.GetFullNameWithAssemblyName(),
Type = type != null ? ModelingTypeHelper.GetFullNameHandlingNullableAndGenerics(type) : null,
TypeSimple = type != null ? ModelingTypeHelper.GetSimplifiedName(type) : null,
IsOptional = isOptional,
DefaultValue = defaultValue,
ConstraintTypes = constraintTypes,

@ -0,0 +1,46 @@
using System;
using System.Reflection;
using Volo.Abp.Reflection;
namespace Volo.Abp.Http.Modeling
{
[Serializable]
public class PropertyApiDescriptionModel
{
public string Name { get; set; }
public string Type { get; set; }
public string TypeSimple { get; set; }
//TODO: Validation rules for this property
public static PropertyApiDescriptionModel Create(PropertyInfo propertyInfo)
{
string typeName;
string simpleTypeName;
if (TypeHelper.IsEnumerable(propertyInfo.PropertyType, out var itemType, includePrimitives: false))
{
typeName = $"[{ModelingTypeHelper.GetFullNameHandlingNullableAndGenerics(itemType)}]";
simpleTypeName = $"[{ModelingTypeHelper.GetSimplifiedName(itemType)}]";
}
else if (TypeHelper.IsDictionary(propertyInfo.PropertyType, out var keyType, out var valueType))
{
typeName = $"{{{ModelingTypeHelper.GetFullNameHandlingNullableAndGenerics(keyType)}:{ModelingTypeHelper.GetFullNameHandlingNullableAndGenerics(valueType)}}}";
simpleTypeName = $"{{{ModelingTypeHelper.GetSimplifiedName(keyType)}:{ModelingTypeHelper.GetSimplifiedName(valueType)}}}";
}
else
{
typeName = ModelingTypeHelper.GetFullNameHandlingNullableAndGenerics(propertyInfo.PropertyType);
simpleTypeName = ModelingTypeHelper.GetSimplifiedName(propertyInfo.PropertyType);
}
return new PropertyApiDescriptionModel
{
Name = propertyInfo.Name,
Type = typeName,
TypeSimple = simpleTypeName
};
}
}
}

@ -6,7 +6,9 @@ namespace Volo.Abp.Http.Modeling
[Serializable]
public class ReturnValueApiDescriptionModel
{
public string TypeAsString { get; set; }
public string Type { get; set; }
public string TypeSimple { get; set; }
private ReturnValueApiDescriptionModel()
{
@ -15,9 +17,12 @@ namespace Volo.Abp.Http.Modeling
public static ReturnValueApiDescriptionModel Create(Type type)
{
var unwrappedType = AsyncHelper.UnwrapTask(type);
return new ReturnValueApiDescriptionModel
{
TypeAsString = AsyncHelper.UnwrapTask(type).GetFullNameWithAssemblyName()
Type = ModelingTypeHelper.GetFullNameHandlingNullableAndGenerics(unwrappedType),
TypeSimple = ModelingTypeHelper.GetSimplifiedName(unwrappedType)
};
}
}

@ -0,0 +1,54 @@
using System;
using System.Linq;
namespace Volo.Abp.Http.Modeling
{
[Serializable]
public class TypeApiDescriptionModel
{
public string BaseType { get; set; }
public bool IsEnum { get; set; }
public string[] EnumNames { get; set; }
public object[] EnumValues { get; set; }
public PropertyApiDescriptionModel[] Properties { get; set; }
private TypeApiDescriptionModel()
{
}
public static TypeApiDescriptionModel Create(Type type)
{
var baseType = type.BaseType;
if (baseType == typeof(object))
{
baseType = null;
}
var typeModel = new TypeApiDescriptionModel
{
IsEnum = type.IsEnum,
BaseType = baseType != null ? ModelingTypeHelper.GetFullNameHandlingNullableAndGenerics(baseType) : null
};
if (typeModel.IsEnum)
{
typeModel.EnumNames = type.GetEnumNames();
typeModel.EnumValues = type.GetEnumValues().Cast<object>().ToArray();
}
else
{
typeModel.Properties = type
.GetProperties()
.Select(PropertyApiDescriptionModel.Create)
.ToArray();
}
return typeModel;
}
}
}

@ -56,7 +56,7 @@ namespace Volo.Abp.Http.ProxyScripting.Generators.JQuery
private static void AddControllerScript(StringBuilder script, ControllerApiDescriptionModel controller)
{
var controllerName = GetNormalizedTypeName(controller.TypeAsString);
var controllerName = GetNormalizedTypeName(controller.Type);
script.AppendLine($" // controller {controllerName}");
script.AppendLine();
@ -129,7 +129,7 @@ namespace Volo.Abp.Http.ProxyScripting.Generators.JQuery
script.AppendLine(" url: abp.appPath + '" + ProxyScriptingHelper.GenerateUrlWithParameters(action) + "',");
script.Append(" type: '" + httpMethod + "'");
if (action.ReturnValue.TypeAsString == typeof(void).GetFullNameWithAssemblyName())
if (action.ReturnValue.Type == typeof(void).FullName)
{
script.AppendLine(",");
script.Append(" dataType: null");

@ -49,7 +49,7 @@ namespace Volo.Abp.Http.ProxyScripting
private string CreateScript(ProxyScriptingModel scriptingModel)
{
var apiModel = _modelProvider.CreateApiModel();
var apiModel = _modelProvider.CreateApiModel(new ApplicationApiDescriptionModelRequestDto {IncludeTypes = false});
if (scriptingModel.IsPartialRequest())
{

@ -0,0 +1,124 @@
export class ListResultDto<T> {
items?: T[];
constructor(initialValues: Partial<ListResultDto<T>> = {}) {
for (const key in initialValues) {
if (initialValues.hasOwnProperty(key)) {
this[key] = initialValues[key];
}
}
}
}
export class PagedResultDto<T> extends ListResultDto<T> {
totalCount?: number;
constructor(initialValues: Partial<PagedResultDto<T>> = {}) {
super(initialValues);
}
}
export class LimitedResultRequestDto {
maxResultCount = 10;
constructor(initialValues: Partial<LimitedResultRequestDto> = {}) {
for (const key in initialValues) {
if (initialValues.hasOwnProperty(key)) {
this[key] = initialValues[key];
}
}
}
}
export class PagedResultRequestDto extends LimitedResultRequestDto {
skipCount?: number;
constructor(initialValues: Partial<PagedResultRequestDto> = {}) {
super(initialValues);
}
}
export class PagedAndSortedResultRequestDto extends PagedResultRequestDto {
sorting?: string;
constructor(initialValues: Partial<PagedAndSortedResultRequestDto> = {}) {
super(initialValues);
}
}
export class EntityDto<TKey = string> {
id?: TKey;
constructor(initialValues: Partial<EntityDto<TKey>> = {}) {
for (const key in initialValues) {
if (initialValues.hasOwnProperty(key)) {
this[key] = initialValues[key];
}
}
}
}
export class CreationAuditedEntityDto<TPrimaryKey = string> extends EntityDto<TPrimaryKey> {
creationTime?: string | Date;
creatorId?: string;
constructor(initialValues: Partial<CreationAuditedEntityDto<TPrimaryKey>> = {}) {
super(initialValues);
}
}
export class CreationAuditedEntityWithUserDto<
TUserDto,
TPrimaryKey = string
> extends CreationAuditedEntityDto<TPrimaryKey> {
creator?: TUserDto;
constructor(
initialValues: Partial<CreationAuditedEntityWithUserDto<TUserDto, TPrimaryKey>> = {},
) {
super(initialValues);
}
}
export class AuditedEntityDto<TPrimaryKey = string> extends CreationAuditedEntityDto<TPrimaryKey> {
lastModificationTime?: string | Date;
lastModifierId?: string;
constructor(initialValues: Partial<AuditedEntityDto<TPrimaryKey>> = {}) {
super(initialValues);
}
}
export class AuditedEntityWithUserDto<TUserDto, TPrimaryKey = string> extends AuditedEntityDto<
TPrimaryKey
> {
creator?: TUserDto;
lastModifier?: TUserDto;
constructor(initialValues: Partial<AuditedEntityWithUserDto<TUserDto, TPrimaryKey>> = {}) {
super(initialValues);
}
}
export class FullAuditedEntityDto<TPrimaryKey = string> extends AuditedEntityDto<TPrimaryKey> {
isDeleted?: boolean;
deleterId?: string;
deletionTime?: Date | string;
constructor(initialValues: Partial<FullAuditedEntityDto<TPrimaryKey>> = {}) {
super(initialValues);
}
}
export class FullAuditedEntityWithUserDto<
TUserDto,
TPrimaryKey = string
> extends FullAuditedEntityDto<TPrimaryKey> {
creator?: TUserDto;
lastModifier?: TUserDto;
deleter?: TUserDto;
constructor(initialValues: Partial<FullAuditedEntityWithUserDto<TUserDto, TPrimaryKey>> = {}) {
super(initialValues);
}
}

@ -1,7 +1,8 @@
export * from './application-configuration';
export * from './common';
export * from './config';
export * from './dtos';
export * from './profile';
export * from './replaceable-components';
export * from './replaceable-components';
export * from './rest';
export * from './session';

Loading…
Cancel
Save