Skip to content

Commit 2889375

Browse files
committed
Upgrade to .NET 10 and enhance plugin metadata
Updated GitHub Actions workflows to use `actions/setup-dotnet@v4` and upgraded the project to target .NET 10.0. Updated dependencies and renamed `manifest.json` to `metadata.json`. Enhanced `PluginMetadata` with `Specifications` and `Operations` properties. Added `PluginOperationMetadata` and `SpecificationMetadata` classes to represent plugin metadata. Refactored `PluginReflector` to dynamically extract specifications and operations metadata. Introduced `TypeExtensions` for type name utilities, supporting nullable types, arrays, and generics. #18
1 parent 40f6de6 commit 2889375

File tree

9 files changed

+252
-9
lines changed

9 files changed

+252
-9
lines changed

.github/workflows/build.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ jobs:
1414
uses: actions/checkout@v4
1515

1616
- name: Setup .NET Core
17-
uses: actions/setup-dotnet@v3.2.0
17+
uses: actions/setup-dotnet@v4
1818
with:
19-
dotnet-version: 9.0.200
19+
dotnet-version: 10.0.x
2020

2121
- name: Build
2222
run: dotnet build --configuration Release

.github/workflows/release.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ jobs:
5252
5353
#Build/pack the project
5454
- name: Setup .NET
55-
uses: actions/setup-dotnet@v3.2.0
55+
uses: actions/setup-dotnet@v4
5656
with:
57-
dotnet-version: 9.0.x
57+
dotnet-version: 10.0.x
5858

5959
- name: Publish
6060
shell: bash

src/FlowPack.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<OutputType>Exe</OutputType>
5-
<TargetFramework>net9.0</TargetFramework>
5+
<TargetFramework>net10.0</TargetFramework>
66
<ImplicitUsings>enable</ImplicitUsings>
77
<Nullable>enable</Nullable>
88
<AssemblyName>flowpack</AssemblyName>
@@ -14,7 +14,7 @@
1414
</PropertyGroup>
1515

1616
<ItemGroup>
17-
<PackageReference Include="FlowSynx.PluginCore" Version="1.3.4" />
17+
<PackageReference Include="FlowSynx.PluginCore" Version="1.4.0" />
1818
</ItemGroup>
1919

2020
</Project>

src/Packager.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ private void CreateFinalPackage(string packagePath, string pluginPath, string ma
113113

114114
using var archive = ZipFile.Open(packagePath, ZipArchiveMode.Create);
115115
archive.CreateEntryFromFile(pluginPath, $"{projectName}.plugin");
116-
archive.CreateEntryFromFile(manifestPath, "manifest.json");
116+
archive.CreateEntryFromFile(manifestPath, "metadata.json");
117117
archive.CreateEntryFromFile(checksumPath, $"{projectName}.plugin.sha256");
118118
}
119119

src/PluginMetadata.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,6 @@ public class PluginMetadata
1919
public required string CategoryId { get; set; }
2020
public required string MinimumFlowSynxVersion { get; set; }
2121
public string? TargetFlowSynxVersion { get; set; }
22+
public List<SpecificationMetadata> Specifications { get; set; } = new List<SpecificationMetadata>();
23+
public List<PluginOperationMetadata> Operations { get; set; } = new List<PluginOperationMetadata>();
2224
}

src/PluginOperationMetadata.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
namespace FlowPack;
2+
3+
public class PluginOperationMetadata
4+
{
5+
public string Name { get; set; } = string.Empty;
6+
public string? Description { get; set; }
7+
public List<PluginOperationParameterMetadata> Parameters { get; set; } = new List<PluginOperationParameterMetadata>();
8+
}
9+
10+
public class PluginOperationParameterMetadata
11+
{
12+
public string Name { get; set; } = string.Empty;
13+
public string? Description { get; set; }
14+
public string? Type { get; set; }
15+
public string? DefaultValue { get; set; }
16+
public bool? IsRequired { get; set; } = false;
17+
}

src/PluginReflector.cs

Lines changed: 118 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using FlowSynx.PluginCore;
2+
using System.Reflection;
23
using System.Text.Json;
34

45
namespace FlowPack;
@@ -52,13 +53,128 @@ public static class PluginReflector
5253
MinimumFlowSynxVersion = plugin.Metadata.MinimumFlowSynxVersion.ToString(),
5354
TargetFlowSynxVersion = plugin.Metadata.TargetFlowSynxVersion == null
5455
? ""
55-
: plugin.Metadata.TargetFlowSynxVersion.ToString()
56+
: plugin.Metadata.TargetFlowSynxVersion.ToString(),
57+
Specifications = ExtractSpecificationMetadata(plugin),
58+
Operations = ExtractOperations(plugin)
5659
};
5760

61+
private static List<SpecificationMetadata> ExtractSpecificationMetadata(IPlugin plugin)
62+
{
63+
if (plugin == null)
64+
throw new ArgumentNullException(nameof(plugin));
65+
66+
var result = new List<SpecificationMetadata>();
67+
68+
// Try to find the concrete Specifications type
69+
var specProperty = plugin.GetType().GetProperty("Specifications", BindingFlags.Public | BindingFlags.Instance);
70+
if (specProperty == null)
71+
return result;
72+
73+
Type specType;
74+
75+
// If the property is already instantiated, use its type
76+
var specInstance = specProperty.GetValue(plugin);
77+
if (specInstance != null)
78+
{
79+
specType = specInstance.GetType();
80+
}
81+
else
82+
{
83+
var declaredType = specProperty.PropertyType;
84+
specType = plugin.GetType().Assembly
85+
.GetTypes()
86+
.FirstOrDefault(t =>
87+
declaredType.IsAssignableFrom(t) &&
88+
!t.IsAbstract &&
89+
t.IsClass
90+
) ?? throw new InvalidOperationException("No concrete Specifications class found.");
91+
92+
// Create an instance to get default values
93+
specInstance = Activator.CreateInstance(specType);
94+
}
95+
96+
// Iterate over properties of the concrete type
97+
foreach (var prop in specType.GetProperties(BindingFlags.Public | BindingFlags.Instance))
98+
{
99+
var attr = prop.GetCustomAttribute<SpecificationMetadataAttribute>();
100+
if (attr == null)
101+
continue;
102+
103+
var defaultValue = specInstance != null ? prop.GetValue(specInstance) : null;
104+
105+
result.Add(new SpecificationMetadata
106+
{
107+
Name = prop.Name,
108+
Description = attr.Description,
109+
Type = TypeExtensions.GetCanonicalAiTypeName(prop.PropertyType),
110+
DefaultValue = defaultValue?.ToString(),
111+
IsRequired = attr.IsRequired
112+
});
113+
}
114+
115+
return result;
116+
}
117+
118+
private static List<PluginOperationMetadata> ExtractOperations(IPlugin plugin)
119+
{
120+
var operations = new List<PluginOperationMetadata>();
121+
var asm = plugin.GetType().Assembly;
122+
123+
foreach (var type in asm.GetTypes())
124+
{
125+
if (type.IsAbstract || type.IsInterface)
126+
continue;
127+
128+
var opInterface = type.GetInterfaces()
129+
.FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition().Name == "IPluginOperation`2");
130+
131+
if (opInterface == null)
132+
continue;
133+
134+
object? opInstance = null;
135+
try { opInstance = Activator.CreateInstance(type); } catch { continue; }
136+
137+
var nameProp = type.GetProperty("Name", BindingFlags.Instance | BindingFlags.Public);
138+
var descProp = type.GetProperty("Description", BindingFlags.Instance | BindingFlags.Public);
139+
140+
var opMeta = new PluginOperationMetadata
141+
{
142+
Name = nameProp?.GetValue(opInstance)?.ToString() ?? type.Name,
143+
Description = descProp?.GetValue(opInstance)?.ToString()
144+
};
145+
146+
var paramType = opInterface.GetGenericArguments()[0];
147+
List<PluginOperationParameterMetadata> paramMetas = new();
148+
149+
foreach (var prop in paramType.GetProperties(BindingFlags.Instance | BindingFlags.Public))
150+
{
151+
var attr = prop.GetCustomAttribute<OperationParameterMetadataAttribute>();
152+
if (attr == null) continue;
153+
154+
object? paramInstance = null;
155+
try { paramInstance = Activator.CreateInstance(paramType); } catch { }
156+
157+
paramMetas.Add(new PluginOperationParameterMetadata
158+
{
159+
Name = prop.Name,
160+
Description = attr.Description,
161+
Type = TypeExtensions.GetCanonicalAiTypeName(prop.PropertyType),
162+
DefaultValue = paramInstance != null ? prop.GetValue(paramInstance)?.ToString() : null,
163+
IsRequired = attr.IsRequired
164+
});
165+
}
166+
167+
opMeta.Parameters = paramMetas;
168+
operations.Add(opMeta);
169+
}
170+
171+
return operations;
172+
}
173+
58174
public static string SaveMetadataToFile(PluginMetadata metadata, string outputDirectory)
59175
{
60176
var json = JsonSerializer.Serialize(metadata, new JsonSerializerOptions { WriteIndented = true });
61-
var path = Path.Combine(outputDirectory, "manifest.json");
177+
var path = Path.Combine(outputDirectory, "metadata.json");
62178
File.WriteAllText(path, json);
63179
return path;
64180
}

src/PluginSpecificationMetadata.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace FlowPack;
2+
3+
public class SpecificationMetadata
4+
{
5+
public string Name { get; set; } = string.Empty;
6+
public string? Description { get; set; }
7+
public string? Type { get; set; }
8+
public string? DefaultValue { get; set; }
9+
public bool? IsRequired { get; set; } = false;
10+
}

src/TypeExtensions.cs

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
namespace FlowPack;
2+
3+
public static class TypeExtensions
4+
{
5+
private static readonly Dictionary<Type, string> _csharpAliases = new()
6+
{
7+
{ typeof(void), "void" },
8+
{ typeof(bool), "bool" },
9+
{ typeof(byte), "byte" },
10+
{ typeof(sbyte), "sbyte" },
11+
{ typeof(char), "char" },
12+
{ typeof(decimal), "decimal" },
13+
{ typeof(double), "double" },
14+
{ typeof(float), "float" },
15+
{ typeof(int), "int" },
16+
{ typeof(uint), "uint" },
17+
{ typeof(long), "long" },
18+
{ typeof(ulong), "ulong" },
19+
{ typeof(object), "object" },
20+
{ typeof(short), "short" },
21+
{ typeof(ushort), "ushort" },
22+
{ typeof(string), "string" }
23+
};
24+
25+
public static string GetCanonicalAiTypeName(Type type)
26+
{
27+
if (type == null)
28+
throw new ArgumentNullException(nameof(type));
29+
30+
// Nullable<T> → T?
31+
var underlyingNullable = Nullable.GetUnderlyingType(type);
32+
if (underlyingNullable != null)
33+
return $"CSharp:{GetCanonicalAiTypeName(underlyingNullable)}?";
34+
35+
// Arrays
36+
if (type.IsArray)
37+
{
38+
var elem = GetCanonicalAiTypeName(type.GetElementType()!);
39+
return $"CSharp:{elem}{new string('[', type.GetArrayRank())}{new string(']', type.GetArrayRank())}";
40+
}
41+
42+
// Generic types
43+
if (type.IsGenericType)
44+
{
45+
string typeName = type.GetGenericTypeDefinition().FullName!;
46+
int backtick = typeName.IndexOf('`');
47+
if (backtick > 0)
48+
typeName = typeName[..backtick];
49+
50+
var args = type.GetGenericArguments()
51+
.Select(GetCanonicalAiTypeName);
52+
53+
return $"CSharp:{typeName}[{string.Join(", ", args)}]";
54+
}
55+
56+
// Non-generic, non-nullable, non-array
57+
return $"CSharp:{type.FullName}";
58+
}
59+
60+
public static string GetFriendlyTypeName(Type type)
61+
{
62+
if (type == null)
63+
throw new ArgumentNullException(nameof(type));
64+
65+
// Handle arrays
66+
if (type.IsArray)
67+
{
68+
return $"{GetFriendlyTypeName(type.GetElementType()!)}[{new string(',', type.GetArrayRank() - 1)}]";
69+
}
70+
71+
// Handle nullable types
72+
var underlyingNullable = Nullable.GetUnderlyingType(type);
73+
if (underlyingNullable != null)
74+
{
75+
return $"{GetFriendlyTypeName(underlyingNullable)}?";
76+
}
77+
78+
// Handle generic types
79+
if (type.IsGenericType)
80+
{
81+
var typeDef = type.GetGenericTypeDefinition();
82+
var genericArgs = type.GetGenericArguments().Select(GetFriendlyTypeName).ToArray();
83+
84+
var typeName = type.Name;
85+
var backtickIndex = typeName.IndexOf('`');
86+
if (backtickIndex > 0)
87+
typeName = typeName.Substring(0, backtickIndex);
88+
89+
return $"{typeName}<{string.Join(", ", genericArgs)}>";
90+
}
91+
92+
// Map to C# alias if possible
93+
if (_csharpAliases.TryGetValue(type, out var alias))
94+
return alias;
95+
96+
return type.Name; // fallback to CLR type name
97+
}
98+
}

0 commit comments

Comments
 (0)