#nullable enable using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Xml.Linq; namespace ShrinkSDK.Godot.Editor; public static class ShrinkProjectPackageEditor { public static readonly IReadOnlyDictionary RecommendedPackages = new Dictionary(StringComparer.OrdinalIgnoreCase) { ["ShrinkSDK.Godot"] = "0.1.0", ["ShrinkSDK.EventBus"] = "2.1.0", ["ShrinkSDK.Context.Core"] = "0.2.0", ["ShrinkSDK.Command"] = "0.3.0", ["ShrinkSDK.Command.Integration.EventBus"] = "0.2.0", ["ShrinkSDK.Command.Integration.Network"] = "0.2.0", ["ShrinkSDK.Command.Integration.App"] = "0.2.0", ["ShrinkSDK.Network"] = "0.3.0", ["ShrinkSDK.Network.Integration.EventBus"] = "0.2.0", ["ShrinkSDK.Network.Integration.App"] = "0.2.0", ["ShrinkSDK.DataSaver"] = "2.3.0", ["ShrinkSDK.DataSaver.Integration.EventBus"] = "2.2.0", ["ShrinkSDK.DataSaver.Integration.App"] = "0.2.0", ["ShrinkSDK.App.Core"] = "0.2.0", ["ShrinkSDK.Context.AppAdapter"] = "0.2.0", ["ShrinkSDK.App.Starter.Basic"] = "0.3.0", ["ShrinkSDK.ModFramework"] = "0.3.0", ["ShrinkSDK.ModFramework.Godot"] = "0.1.0", ["ShrinkSDK.Tutorial"] = "0.2.0", ["ShrinkSDK.Tutorial.Godot"] = "0.1.0" }; public static IReadOnlyDictionary ReadPackageReferences(string projectPath) { var document = XDocument.Load(projectPath, LoadOptions.PreserveWhitespace); return document.Descendants("PackageReference") .Select(element => new { Id = ((string?)element.Attribute("Include") ?? (string?)element.Attribute("Update"))?.Trim(), Version = ((string?)element.Attribute("Version") ?? (string?)element.Attribute("VersionOverride") ?? element.Element("Version")?.Value)?.Trim() ?? string.Empty }) .Where(item => !string.IsNullOrWhiteSpace(item.Id)) .GroupBy(item => item.Id!, StringComparer.OrdinalIgnoreCase) .ToDictionary(group => group.Key, group => group.First().Version, StringComparer.OrdinalIgnoreCase); } public static void SetPackageReference(string projectPath, string packageId, string? version) { var document = XDocument.Load(projectPath, LoadOptions.PreserveWhitespace); var root = document.Root ?? throw new InvalidDataException("The C# project has no root element."); var references = root.Descendants("PackageReference").Where(element => string.Equals((string?)element.Attribute("Include"), packageId, StringComparison.OrdinalIgnoreCase)).ToArray(); foreach (var duplicate in references.Skip(1)) duplicate.Remove(); if (string.IsNullOrWhiteSpace(version)) { foreach (var reference in references) reference.Remove(); } else if (references.Length > 0) { var reference = references[0]; if (reference.Attribute("VersionOverride") != null) reference.SetAttributeValue("VersionOverride", version); else if (reference.Attribute("Version") != null) reference.SetAttributeValue("Version", version); else if (reference.Element("Version") != null) reference.Element("Version")!.Value = version; else reference.SetAttributeValue("Version", version); } else { var group = root.Elements("ItemGroup").FirstOrDefault(element => element.Elements("PackageReference").Any()); if (group == null) { group = new XElement("ItemGroup"); root.Add(group); } group.Add(new XElement("PackageReference", new XAttribute("Include", packageId), new XAttribute("Version", version))); } WriteAtomically(projectPath, document); } public static void EnsureShrinkFeed(string nugetConfigPath) { XDocument document; if (File.Exists(nugetConfigPath)) document = XDocument.Load(nugetConfigPath, LoadOptions.PreserveWhitespace); else document = new XDocument(new XDeclaration("1.0", "utf-8", null), new XElement("configuration")); var root = document.Root ?? throw new InvalidDataException("NuGet.Config has no root element."); var sources = root.Element("packageSources"); if (sources == null) { sources = new XElement("packageSources"); root.Add(sources); } var existing = sources.Elements("add").FirstOrDefault(element => string.Equals((string?)element.Attribute("key"), "ShrinkSDK", StringComparison.OrdinalIgnoreCase)); if (existing == null) sources.Add(new XElement("add", new XAttribute("key", "ShrinkSDK"), new XAttribute("value", "https://git.crash.work/api/packages/ShrinkSDK/nuget/index.json"))); else existing.SetAttributeValue("value", "https://git.crash.work/api/packages/ShrinkSDK/nuget/index.json"); WriteAtomically(nugetConfigPath, document); } public static async Task<(int ExitCode, string Output)> RunDotnetAsync(string projectDirectory, string arguments, CancellationToken cancellationToken = default) { var output = new StringBuilder(); using var process = new Process { StartInfo = new ProcessStartInfo("dotnet", arguments) { WorkingDirectory = projectDirectory, UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true } }; process.OutputDataReceived += (_, args) => { if (args.Data != null) output.AppendLine(args.Data); }; process.ErrorDataReceived += (_, args) => { if (args.Data != null) output.AppendLine(args.Data); }; process.Start(); process.BeginOutputReadLine(); process.BeginErrorReadLine(); await process.WaitForExitAsync(cancellationToken); return (process.ExitCode, output.ToString()); } private static void WriteAtomically(string path, XDocument document) { Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(path))!); var temporaryPath = path + ".shrinksdk.tmp"; using (var writer = new StreamWriter(temporaryPath, false, new UTF8Encoding(false))) document.Save(writer, SaveOptions.DisableFormatting); File.Move(temporaryPath, path, true); } }