using System.Management.Automation; using System.Reflection; using System.Text; using System.Text.Json; using LANCommander.SDK.PowerShell.Extensions; if (args.Length == 0) { Console.Error.WriteLine("Usage: LANCommander.CompletionGenerator "); return 1; } var outputPath = args[0]; var assembly = typeof(InitialSessionStateExtensions).Assembly; var cmdletTypes = assembly.GetTypes() .Where(t => t.GetCustomAttribute() != null) .OrderBy(t => t.GetCustomAttribute()!.VerbName + "-" + t.GetCustomAttribute()!.NounName); var sb = new StringBuilder(); sb.AppendLine("// Auto-generated by LANCommander.CompletionGenerator — do not edit manually"); sb.AppendLine(); sb.AppendLine("export interface CmdletParameter {"); sb.AppendLine(" name: string;"); sb.AppendLine(" type: string;"); sb.AppendLine(" mandatory: boolean;"); sb.AppendLine(" position: number | null;"); sb.AppendLine(" helpMessage: string | null;"); sb.AppendLine(" aliases: string[];"); sb.AppendLine("}"); sb.AppendLine(); sb.AppendLine("export interface CmdletDefinition {"); sb.AppendLine(" name: string;"); sb.AppendLine(" description: string | null;"); sb.AppendLine(" outputType: string | null;"); sb.AppendLine(" parameters: CmdletParameter[];"); sb.AppendLine("}"); sb.AppendLine(); sb.AppendLine("export const cmdlets: CmdletDefinition[] = ["); foreach (var type in cmdletTypes) { var cmdletAttr = type.GetCustomAttribute()!; var name = $"{cmdletAttr.VerbName}-{cmdletAttr.NounName}"; var outputTypeAttr = type.GetCustomAttribute(); string? outputType = null; if (outputTypeAttr?.Type?.Length > 0) outputType = GetFriendlyTypeName(outputTypeAttr.Type[0].Type); // Use XML doc summary if available via the Summary property pattern, otherwise use null var descriptionLines = type.GetCustomAttributes() .Where(a => a.GetType().Name == "DescriptionAttribute") .Select(a => a.GetType().GetProperty("Description")?.GetValue(a)?.ToString()) .FirstOrDefault(); // Fall back to XML summary comment — not available via reflection, use class summary convention string? description = descriptionLines; sb.AppendLine(" {"); sb.AppendLine($" name: {JsonEncode(name)},"); sb.AppendLine($" description: {JsonEncode(description)},"); sb.AppendLine($" outputType: {JsonEncode(outputType)},"); sb.AppendLine(" parameters: ["); var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance) .Where(p => p.GetCustomAttribute() != null) .OrderBy(p => { var pa = p.GetCustomAttribute()!; return pa.Position == int.MinValue ? int.MaxValue : pa.Position; }) .ThenBy(p => p.Name); foreach (var prop in properties) { var paramAttr = prop.GetCustomAttribute()!; var aliasAttr = prop.GetCustomAttribute(); var aliases = aliasAttr?.AliasNames?.ToArray() ?? Array.Empty(); var position = paramAttr.Position == int.MinValue ? (int?)null : paramAttr.Position; sb.AppendLine(" {"); sb.AppendLine($" name: {JsonEncode(prop.Name)},"); sb.AppendLine($" type: {JsonEncode(GetFriendlyTypeName(prop.PropertyType))},"); sb.AppendLine($" mandatory: {(paramAttr.Mandatory ? "true" : "false")},"); sb.AppendLine($" position: {(position.HasValue ? position.Value.ToString() : "null")},"); sb.AppendLine($" helpMessage: {JsonEncode(paramAttr.HelpMessage)},"); sb.AppendLine($" aliases: [{string.Join(", ", aliases.Select(a => JsonEncode(a)))}],"); sb.AppendLine(" },"); } sb.AppendLine(" ],"); sb.AppendLine(" },"); } sb.AppendLine("];"); sb.AppendLine(); // Generate type definitions for complex objects used as script variables var variableTypes = new Dictionary { ["GameManifest"] = typeof(LANCommander.SDK.Models.Manifest.Game), ["ToolManifest"] = typeof(LANCommander.SDK.Models.Manifest.Tool), ["RedistributableManifest"] = typeof(LANCommander.SDK.Models.Manifest.Redistributable), ["Server"] = typeof(LANCommander.SDK.Models.Server), ["Game"] = typeof(LANCommander.SDK.Models.Game), ["User"] = typeof(LANCommander.SDK.Models.User), ["Tool"] = typeof(LANCommander.SDK.Models.Tool), ["Redistributable"] = typeof(LANCommander.SDK.Models.Redistributable), }; sb.AppendLine("export interface TypeProperty {"); sb.AppendLine(" name: string;"); sb.AppendLine(" type: string;"); sb.AppendLine("}"); sb.AppendLine(); sb.AppendLine("export interface TypeDefinition {"); sb.AppendLine(" name: string;"); sb.AppendLine(" properties: TypeProperty[];"); sb.AppendLine("}"); sb.AppendLine(); sb.AppendLine("export const variableTypes: TypeDefinition[] = ["); foreach (var (typeName, clrType) in variableTypes.OrderBy(kv => kv.Key)) { sb.AppendLine(" {"); sb.AppendLine($" name: {JsonEncode(typeName)},"); sb.AppendLine(" properties: ["); var props = clrType.GetProperties(BindingFlags.Public | BindingFlags.Instance) .Where(p => p.CanRead) .OrderBy(p => p.Name); foreach (var prop in props) { sb.AppendLine(" {"); sb.AppendLine($" name: {JsonEncode(prop.Name)},"); sb.AppendLine($" type: {JsonEncode(GetPropertyTypeName(prop.PropertyType))},"); sb.AppendLine(" },"); } sb.AppendLine(" ],"); sb.AppendLine(" },"); } sb.AppendLine("];"); sb.AppendLine(); // Generate ScriptType enum values sb.AppendLine("export const scriptTypeValues: string[] = ["); foreach (var value in Enum.GetNames()) { sb.AppendLine($" {JsonEncode(value)},"); } sb.AppendLine("];"); var directory = Path.GetDirectoryName(outputPath); if (!string.IsNullOrEmpty(directory)) Directory.CreateDirectory(directory); File.WriteAllText(outputPath, sb.ToString(), Encoding.UTF8); Console.WriteLine($"Generated completions for {cmdletTypes.Count()} cmdlets and {variableTypes.Count} variable types -> {outputPath}"); return 0; static string JsonEncode(string? value) { if (value == null) return "null"; return JsonSerializer.Serialize(value); } static string GetPropertyTypeName(Type type) { var underlying = Nullable.GetUnderlyingType(type); if (underlying != null) return GetPropertyTypeName(underlying) + "?"; if (type.IsArray) return GetPropertyTypeName(type.GetElementType()!) + "[]"; if (type.IsGenericType) { var genericDef = type.GetGenericTypeDefinition(); if (genericDef == typeof(IEnumerable<>) || genericDef == typeof(ICollection<>) || genericDef == typeof(List<>) || genericDef == typeof(IList<>)) { var elementType = type.GetGenericArguments()[0]; return GetPropertyTypeName(elementType) + "[]"; } } // Check non-generic IEnumerable if (type != typeof(string) && type.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>))) { var elementType = type.GetInterfaces() .First(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>)) .GetGenericArguments()[0]; return GetPropertyTypeName(elementType) + "[]"; } if (type == typeof(string)) return "string"; if (type == typeof(int) || type == typeof(long)) return "int"; if (type == typeof(uint)) return "uint"; if (type == typeof(double) || type == typeof(float)) return "double"; if (type == typeof(bool)) return "bool"; if (type == typeof(Guid)) return "Guid"; if (type == typeof(DateTime)) return "DateTime"; if (type == typeof(Uri)) return "Uri"; if (type.IsEnum) return type.Name; return type.Name; } static string GetFriendlyTypeName(Type? type) { if (type == null) return "object"; // Handle nullable types var underlying = Nullable.GetUnderlyingType(type); if (underlying != null) return GetFriendlyTypeName(underlying) + "?"; // Handle arrays if (type.IsArray) return GetFriendlyTypeName(type.GetElementType()) + "[]"; // Handle SwitchParameter if (type == typeof(SwitchParameter)) return "SwitchParameter"; // Handle common types if (type == typeof(string)) return "string"; if (type == typeof(int)) return "int"; if (type == typeof(long)) return "long"; if (type == typeof(uint)) return "uint"; if (type == typeof(double)) return "double"; if (type == typeof(float)) return "float"; if (type == typeof(bool)) return "bool"; if (type == typeof(byte)) return "byte"; if (type == typeof(byte[])) return "byte[]"; if (type == typeof(Guid)) return "Guid"; if (type == typeof(Uri)) return "Uri"; if (type == typeof(object)) return "object"; // Handle SecureString if (type == typeof(System.Security.SecureString)) return "SecureString"; return type.Name; }