feat: add stella-callgraph-node for JavaScript/TypeScript call graph extraction
- Implemented a new tool `stella-callgraph-node` that extracts call graphs from JavaScript/TypeScript projects using Babel AST. - Added command-line interface with options for JSON output and help. - Included functionality to analyze project structure, detect functions, and build call graphs. - Created a package.json file for dependency management. feat: introduce stella-callgraph-python for Python call graph extraction - Developed `stella-callgraph-python` to extract call graphs from Python projects using AST analysis. - Implemented command-line interface with options for JSON output and verbose logging. - Added framework detection to identify popular web frameworks and their entry points. - Created an AST analyzer to traverse Python code and extract function definitions and calls. - Included requirements.txt for project dependencies. chore: add framework detection for Python projects - Implemented framework detection logic to identify frameworks like Flask, FastAPI, Django, and others based on project files and import patterns. - Enhanced the AST analyzer to recognize entry points based on decorators and function definitions.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using System.Reflection;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Router.Common.Models;
|
||||
|
||||
@@ -12,6 +13,7 @@ public sealed class GeneratedEndpointDiscoveryProvider : IEndpointDiscoveryProvi
|
||||
private readonly StellaMicroserviceOptions _options;
|
||||
private readonly ILogger<GeneratedEndpointDiscoveryProvider> _logger;
|
||||
private readonly ReflectionEndpointDiscoveryProvider _reflectionFallback;
|
||||
private readonly IServiceProviderIsService? _serviceProviderIsService;
|
||||
|
||||
private const string GeneratedProviderTypeName = "StellaOps.Microservice.Generated.GeneratedEndpointProvider";
|
||||
|
||||
@@ -20,11 +22,16 @@ public sealed class GeneratedEndpointDiscoveryProvider : IEndpointDiscoveryProvi
|
||||
/// </summary>
|
||||
public GeneratedEndpointDiscoveryProvider(
|
||||
StellaMicroserviceOptions options,
|
||||
ILogger<GeneratedEndpointDiscoveryProvider> logger)
|
||||
ILogger<GeneratedEndpointDiscoveryProvider> logger,
|
||||
IServiceProviderIsService? serviceProviderIsService = null)
|
||||
{
|
||||
_options = options;
|
||||
_logger = logger;
|
||||
_reflectionFallback = new ReflectionEndpointDiscoveryProvider(options);
|
||||
_serviceProviderIsService = serviceProviderIsService;
|
||||
_reflectionFallback = new ReflectionEndpointDiscoveryProvider(
|
||||
options,
|
||||
assemblies: null,
|
||||
serviceProviderIsService: serviceProviderIsService);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -65,33 +72,38 @@ public sealed class GeneratedEndpointDiscoveryProvider : IEndpointDiscoveryProvi
|
||||
{
|
||||
try
|
||||
{
|
||||
// Look in the entry assembly first
|
||||
var entryAssembly = Assembly.GetEntryAssembly();
|
||||
var providerType = entryAssembly?.GetType(GeneratedProviderTypeName);
|
||||
|
||||
if (providerType != null)
|
||||
static Type? GetProviderType(Assembly? assembly)
|
||||
{
|
||||
return (IGeneratedEndpointProvider)Activator.CreateInstance(providerType)!;
|
||||
if (assembly is null) return null;
|
||||
var type = assembly.GetType(GeneratedProviderTypeName);
|
||||
if (type is null) return null;
|
||||
return typeof(IGeneratedEndpointProvider).IsAssignableFrom(type) ? type : null;
|
||||
}
|
||||
|
||||
// Also check the calling assembly
|
||||
var callingAssembly = Assembly.GetCallingAssembly();
|
||||
providerType = callingAssembly.GetType(GeneratedProviderTypeName);
|
||||
var providerTypes = new List<Type>();
|
||||
|
||||
if (providerType != null)
|
||||
// Look in the entry and calling assemblies first.
|
||||
var entryProviderType = GetProviderType(Assembly.GetEntryAssembly());
|
||||
if (entryProviderType is not null)
|
||||
{
|
||||
return (IGeneratedEndpointProvider)Activator.CreateInstance(providerType)!;
|
||||
providerTypes.Add(entryProviderType);
|
||||
}
|
||||
|
||||
// Check all loaded assemblies
|
||||
var callingProviderType = GetProviderType(Assembly.GetCallingAssembly());
|
||||
if (callingProviderType is not null)
|
||||
{
|
||||
providerTypes.Add(callingProviderType);
|
||||
}
|
||||
|
||||
// Check all loaded assemblies (integration tests may host multiple microservices in one process).
|
||||
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
try
|
||||
{
|
||||
providerType = assembly.GetType(GeneratedProviderTypeName);
|
||||
if (providerType != null)
|
||||
var providerType = GetProviderType(assembly);
|
||||
if (providerType is not null)
|
||||
{
|
||||
return (IGeneratedEndpointProvider)Activator.CreateInstance(providerType)!;
|
||||
providerTypes.Add(providerType);
|
||||
}
|
||||
}
|
||||
catch
|
||||
@@ -99,6 +111,78 @@ public sealed class GeneratedEndpointDiscoveryProvider : IEndpointDiscoveryProvi
|
||||
// Ignore assembly loading errors
|
||||
}
|
||||
}
|
||||
|
||||
providerTypes = providerTypes.Distinct().ToList();
|
||||
if (providerTypes.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// If multiple providers exist (e.g., test process hosts several microservices), pick the provider that
|
||||
// best matches the currently-registered handler types in DI.
|
||||
if (_serviceProviderIsService is not null && providerTypes.Count > 1)
|
||||
{
|
||||
IGeneratedEndpointProvider? best = null;
|
||||
var bestScore = 0;
|
||||
|
||||
foreach (var providerType in providerTypes)
|
||||
{
|
||||
IGeneratedEndpointProvider? providerInstance;
|
||||
try
|
||||
{
|
||||
providerInstance =
|
||||
(IGeneratedEndpointProvider?)Activator.CreateInstance(providerType, nonPublic: true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (providerInstance is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var score = 0;
|
||||
try
|
||||
{
|
||||
foreach (var handlerType in providerInstance.GetHandlerTypes())
|
||||
{
|
||||
if (_serviceProviderIsService.IsService(handlerType))
|
||||
{
|
||||
score++;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
score = 0;
|
||||
}
|
||||
|
||||
if (score > bestScore)
|
||||
{
|
||||
bestScore = score;
|
||||
best = providerInstance;
|
||||
}
|
||||
}
|
||||
|
||||
if (best is not null && bestScore > 0)
|
||||
{
|
||||
_logger.LogDebug(
|
||||
"Selected generated endpoint provider {ProviderType} (matched {MatchCount} handlers)",
|
||||
best.GetType().FullName,
|
||||
bestScore);
|
||||
return best;
|
||||
}
|
||||
}
|
||||
|
||||
// Deterministic fallback: choose the provider type with the lowest assembly name.
|
||||
var selectedProviderType = providerTypes
|
||||
.OrderBy(t => t.Assembly.GetName().Name, StringComparer.OrdinalIgnoreCase)
|
||||
.ThenBy(t => t.FullName, StringComparer.Ordinal)
|
||||
.First();
|
||||
|
||||
return (IGeneratedEndpointProvider)Activator.CreateInstance(selectedProviderType, nonPublic: true)!;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Reflection;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using StellaOps.Router.Common.Models;
|
||||
|
||||
namespace StellaOps.Microservice;
|
||||
@@ -10,16 +11,21 @@ public sealed class ReflectionEndpointDiscoveryProvider : IEndpointDiscoveryProv
|
||||
{
|
||||
private readonly StellaMicroserviceOptions _options;
|
||||
private readonly IEnumerable<Assembly> _assemblies;
|
||||
private readonly IServiceProviderIsService? _serviceProviderIsService;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReflectionEndpointDiscoveryProvider"/> class.
|
||||
/// </summary>
|
||||
/// <param name="options">The microservice options.</param>
|
||||
/// <param name="assemblies">The assemblies to scan for endpoints.</param>
|
||||
public ReflectionEndpointDiscoveryProvider(StellaMicroserviceOptions options, IEnumerable<Assembly>? assemblies = null)
|
||||
public ReflectionEndpointDiscoveryProvider(
|
||||
StellaMicroserviceOptions options,
|
||||
IEnumerable<Assembly>? assemblies = null,
|
||||
IServiceProviderIsService? serviceProviderIsService = null)
|
||||
{
|
||||
_options = options;
|
||||
_assemblies = assemblies ?? AppDomain.CurrentDomain.GetAssemblies();
|
||||
_serviceProviderIsService = serviceProviderIsService;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -42,6 +48,11 @@ public sealed class ReflectionEndpointDiscoveryProvider : IEndpointDiscoveryProv
|
||||
$"Type {type.FullName} has [StellaEndpoint] but does not implement IStellaEndpoint.");
|
||||
}
|
||||
|
||||
if (_serviceProviderIsService is not null && !_serviceProviderIsService.IsService(type))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var claims = attribute.RequiredClaims
|
||||
.Select(c => new ClaimRequirement { Type = c })
|
||||
.ToList();
|
||||
@@ -54,7 +65,8 @@ public sealed class ReflectionEndpointDiscoveryProvider : IEndpointDiscoveryProv
|
||||
Path = attribute.Path,
|
||||
DefaultTimeout = TimeSpan.FromSeconds(attribute.TimeoutSeconds),
|
||||
SupportsStreaming = attribute.SupportsStreaming,
|
||||
RequiringClaims = claims
|
||||
RequiringClaims = claims,
|
||||
HandlerType = type
|
||||
};
|
||||
|
||||
endpoints.Add(descriptor);
|
||||
|
||||
Reference in New Issue
Block a user