diff --git a/README.md b/README.md index 545a2ea6b..dc4fd86ac 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [![Release Version Badge](https://img.shields.io/github/v/release/marticliment/UniGetUI?style=for-the-badge)](https://github.com/marticliment/UniGetUI/releases) [![Issues Badge](https://img.shields.io/github/issues/marticliment/UniGetUI?style=for-the-badge)](https://github.com/marticliment/UniGetUI/issues) [![Closed Issues Badge](https://img.shields.io/github/issues-closed/marticliment/UniGetUI?color=%238256d0&style=for-the-badge)](https://github.com/marticliment/UniGetUI/issues?q=is%3Aissue+is%3Aclosed)
-The main goal of this project is to create an intuitive GUI for the most common CLI package managers for Windows 10 and 11, such as [WinGet](https://learn.microsoft.com/en-us/windows/package-manager/), [Scoop](https://scoop.sh/), [Chocolatey](https://chocolatey.org/), [Pip](https://pypi.org/), [Npm](https://www.npmjs.com/), [.NET Tool](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-tool-install), [PowerShell Gallery](https://www.powershellgallery.com/) and more (Check out the package manager compatibility table)!. +The main goal of this project is to create an intuitive GUI for the most common CLI package managers for Windows 10 and 11, such as [WinGet](https://learn.microsoft.com/en-us/windows/package-manager/), [Scoop](https://scoop.sh/), [Chocolatey](https://chocolatey.org/), [Pip](https://pypi.org/), [Npm](https://www.npmjs.com/), [Bun](https://bun.com), [.NET Tool](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-tool-install), [PowerShell Gallery](https://www.powershellgallery.com/) and more (Check out the package manager compatibility table)!. With this app, you can easily download, install, update, and uninstall any software published on the supported package managers — and much more! ![image](https://github.com/user-attachments/assets/7cb447ca-ee8b-4bce-8561-b9332fb0139a) @@ -89,7 +89,7 @@ UniGetUI has a built-in autoupdater. However, it can also be updated like any ot ## Features - - Install, update, and remove software from your system easily at one click: UniGetUI combines the packages from the most used package managers for windows: Winget, Chocolatey, Scoop, Pip, Npm and .NET Tool. + - Install, update, and remove software from your system easily at one click: UniGetUI combines the packages from the most used package managers for windows: Winget, Chocolatey, Scoop, Pip, Npm, Bun, and .NET Tool. - Discover new packages and filter them to easily find the package you want. - View detailed metadata about any package before installing it. Get the direct download URL or the name of the publisher, as well as the size of the download. - Easily bulk-install, update, or uninstall multiple packages at once selecting multiple packages before performing an operation @@ -137,13 +137,13 @@ To translate UniGetUI to other languages or to update an old translation, please |   Persian - فارسی‎ | 98% | [ehinium](https://github.com/ehinium), [MobinMardi](https://github.com/MobinMardi) | |   Finnish - Suomi | 100% | [simakuutio](https://github.com/simakuutio) | |   Filipino - Filipino | 96% | [infyProductions](https://github.com/infyProductions) | -|   French - Français | 100% | BreatFR, [Entropiness](https://github.com/Entropiness), Evans Costa, [PikPakPik](https://github.com/PikPakPik), Rémi Guerrero, [W1L7dev](https://github.com/W1L7dev) | +|   French - Français | 99% | BreatFR, [Entropiness](https://github.com/Entropiness), Evans Costa, [PikPakPik](https://github.com/PikPakPik), Rémi Guerrero, [W1L7dev](https://github.com/W1L7dev) | |   Gujarati - ગુજરાતી | 6% | | |   Hindi - हिंदी | 92% | [Ashu-r](https://github.com/Ashu-r), [atharva_xoxo](https://github.com/atharva_xoxo), [satanarious](https://github.com/satanarious) | |   Croatian - Hrvatski | 99% | [AndrejFeher](https://github.com/AndrejFeher), Ivan Nuić, Stjepan Treger | |   Hebrew - עִבְרִית‎ | 99% | [maximunited](https://github.com/maximunited), Oryan Hassidim | |   Hungarian - Magyar | 99% | [gidano](https://github.com/gidano) | -|   Italian - Italiano | 100% | David Senoner, [giacobot](https://github.com/giacobot), [maicol07](https://github.com/maicol07), [mapi68](https://github.com/mapi68), [mrfranza](https://github.com/mrfranza), Rosario Di Mauro | +|   Italian - Italiano | 99% | David Senoner, [giacobot](https://github.com/giacobot), [maicol07](https://github.com/maicol07), [mapi68](https://github.com/mapi68), [mrfranza](https://github.com/mrfranza), Rosario Di Mauro | |   Indonesian - Bahasa Indonesia | 99% | [agrinfauzi](https://github.com/agrinfauzi), [arthackrc](https://github.com/arthackrc), [joenior](https://github.com/joenior), [nrarfn](https://github.com/nrarfn) | |   Japanese - 日本語 | 99% | [anmoti](https://github.com/anmoti), [BHCrusher1](https://github.com/BHCrusher1), [nob-swik](https://github.com/nob-swik), Nobuhiro Shintaku, sho9029, [tacostea](https://github.com/tacostea), Yuki Takase | |   Georgian - ქართული | 98% | [marticliment](https://github.com/marticliment), [ppvnf](https://github.com/ppvnf) | @@ -165,7 +165,7 @@ To translate UniGetUI to other languages or to update an old translation, please |   Albanian - Shqip | 100% | [RDN000](https://github.com/RDN000) | |   Sinhala - සිංහල | 10% | [SashikaSandeepa](https://github.com/SashikaSandeepa), [Savithu-s3](https://github.com/Savithu-s3), [ttheek](https://github.com/ttheek) | |   Slovene - Slovenščina | 88% | [rumplin](https://github.com/rumplin) | -|   Swedish - Svenska | 100% | [curudel](https://github.com/curudel), [Hi-there-how-are-u](https://github.com/Hi-there-how-are-u), [kakmonster](https://github.com/kakmonster), [umeaboy](https://github.com/umeaboy) | +|   Swedish - Svenska | 99% | [curudel](https://github.com/curudel), [Hi-there-how-are-u](https://github.com/Hi-there-how-are-u), [kakmonster](https://github.com/kakmonster), [umeaboy](https://github.com/umeaboy) | |   Tamil - தமிழ் | 4% | [nochilli](https://github.com/nochilli) | |   Tagalog - Tagalog | 11% | lasersPew, [znarfm](https://github.com/znarfm) | |   Thai - ภาษาไทย | 81% | [apaeisara](https://github.com/apaeisara), [dulapahv](https://github.com/dulapahv), [hanchain](https://github.com/hanchain), [rikoprushka](https://github.com/rikoprushka), [vestearth](https://github.com/vestearth) | diff --git a/src/UniGetUI.PackageEngine.Managers.Bun/Bun.cs b/src/UniGetUI.PackageEngine.Managers.Bun/Bun.cs new file mode 100644 index 000000000..4966ce7c8 --- /dev/null +++ b/src/UniGetUI.PackageEngine.Managers.Bun/Bun.cs @@ -0,0 +1,225 @@ +using System.Diagnostics; +using System.Text.Json.Nodes; +using UniGetUI.Core.Data; +using UniGetUI.Core.Tools; +using UniGetUI.Interface.Enums; +using UniGetUI.PackageEngine.Classes.Manager; +using UniGetUI.PackageEngine.Classes.Manager.ManagerHelpers; +using UniGetUI.PackageEngine.Enums; +using UniGetUI.PackageEngine.ManagerClasses.Classes; +using UniGetUI.PackageEngine.ManagerClasses.Manager; +using UniGetUI.PackageEngine.PackageClasses; +using UniGetUI.PackageEngine.Structs; + +namespace UniGetUI.PackageEngine.Managers.BunManager +{ + public class Bun : PackageManager + { + public Bun() + { + Capabilities = new ManagerCapabilities + { + CanRunAsAdmin = true, + SupportsCustomVersions = true, + CanDownloadInstaller = true, + SupportsCustomScopes = true, + CanListDependencies = true, + SupportsPreRelease = true, + SupportsProxy = ProxySupport.No, + SupportsProxyAuth = false + }; + + Properties = new ManagerProperties + { + Name = "Bun", + Description = CoreTools.Translate("A npmjs package manager written in Zig. Full of libraries and other utilities that orbit the javascript world
Contains: Node javascript libraries and other related utilities"), + IconId = IconType.Node, + ColorIconId = "node_color", + ExecutableFriendlyName = "bun", + InstallVerb = "install", + UninstallVerb = "uninstall", + UpdateVerb = "install", + DefaultSource = new ManagerSource(this, "Bun", new Uri("https://www.npmjs.com/")), + KnownSources = [new ManagerSource(this, "Bun", new Uri("https://www.npmjs.com/"))], + + }; + + DetailsHelper = new BunPkgDetailsHelper(this); + OperationHelper = new BunPkgOperationHelper(this); + } + + protected override IReadOnlyList FindPackages_UnSafe(string query) + { + using Process p = new() + { + StartInfo = new ProcessStartInfo + { + FileName = Status.ExecutablePath, + Arguments = Status.ExecutableCallArgs + " search \"" + query + "\" --json", + RedirectStandardOutput = true, + RedirectStandardError = true, + RedirectStandardInput = true, + UseShellExecute = false, + CreateNoWindow = true, + WorkingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), + StandardOutputEncoding = System.Text.Encoding.UTF8 + } + }; + + IProcessTaskLogger logger = TaskLogger.CreateNew(LoggableTaskType.FindPackages, p); + p.Start(); + + string? line; + List Packages = []; + while ((line = p.StandardOutput.ReadLine()) is not null) + { + logger.AddToStdOut(line); + if (line.StartsWith("{")) + { + JsonNode? node = JsonNode.Parse(line); + string? id = node?["name"]?.ToString(); + string? version = node?["version"]?.ToString(); + if (id is not null && version is not null) + { + Packages.Add(new Package(CoreTools.FormatAsName(id), id, version, DefaultSource, this)); + } + else + { + logger.AddToStdErr("Line could not be parsed: " + line); + } + } + } + + logger.AddToStdErr(p.StandardError.ReadToEnd()); + p.WaitForExit(); + logger.Close(p.ExitCode); + + return Packages; + } + + protected override IReadOnlyList GetAvailableUpdates_UnSafe() + { + List Packages = []; + foreach (var options in new OverridenInstallationOptions[] { new(PackageScope.Local), new(PackageScope.Global) }) + { + using Process p = new() + { + StartInfo = new ProcessStartInfo + { + FileName = Status.ExecutablePath, + Arguments = Status.ExecutableCallArgs + " outdated --json" + (options.Scope == PackageScope.Global ? " --global" : ""), + RedirectStandardOutput = true, + RedirectStandardError = true, + RedirectStandardInput = true, + UseShellExecute = false, + CreateNoWindow = true, + WorkingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), + StandardOutputEncoding = System.Text.Encoding.UTF8 + } + }; + + IProcessTaskLogger logger = TaskLogger.CreateNew(LoggableTaskType.ListUpdates, p); + p.Start(); + + string strContents = p.StandardOutput.ReadToEnd(); + logger.AddToStdOut(strContents); + JsonObject? contents = null; + if (strContents.Any()) contents = JsonNode.Parse(strContents) as JsonObject; + foreach (var (packageId, packageData) in contents?.ToDictionary() ?? []) + { + string? version = packageData?["current"]?.ToString(); + string? newVersion = packageData?["latest"]?.ToString(); + if (version is not null && newVersion is not null) + { + Packages.Add(new Package(CoreTools.FormatAsName(packageId), packageId, version, newVersion, + DefaultSource, this, options)); + } + } + + logger.AddToStdErr(p.StandardError.ReadToEnd()); + p.WaitForExit(); + logger.Close(p.ExitCode); + } + return Packages; + } + + protected override IReadOnlyList GetInstalledPackages_UnSafe() + { + List Packages = []; + foreach (var options in new OverridenInstallationOptions[] { new(PackageScope.Local), new(PackageScope.Global) }) + { + using Process p = new() + { + StartInfo = new ProcessStartInfo + { + FileName = Status.ExecutablePath, + Arguments = Status.ExecutableCallArgs + " list --json" + (options.Scope == PackageScope.Global ? " --global" : ""), + RedirectStandardOutput = true, + RedirectStandardError = true, + RedirectStandardInput = true, + UseShellExecute = false, + CreateNoWindow = true, + WorkingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), + StandardOutputEncoding = System.Text.Encoding.UTF8 + } + }; + + IProcessTaskLogger logger = TaskLogger.CreateNew(LoggableTaskType.ListInstalledPackages, p); + p.Start(); + + string strContents = p.StandardOutput.ReadToEnd(); + logger.AddToStdOut(strContents); + JsonObject? contents = null; + if (strContents.Any()) contents = (JsonNode.Parse(strContents) as JsonObject)?["dependencies"] as JsonObject; + foreach (var (packageId, packageData) in contents?.ToDictionary() ?? []) + { + string? version = packageData?["version"]?.ToString(); + if (version is not null) + { + Packages.Add(new Package(CoreTools.FormatAsName(packageId), packageId, version, DefaultSource, this, options)); + } + } + + logger.AddToStdErr(p.StandardError.ReadToEnd()); + p.WaitForExit(); + logger.Close(p.ExitCode); + } + + return Packages; + } + + public override IReadOnlyList FindCandidateExecutableFiles() + => CoreTools.WhichMultiple("bun.cmd"); + + protected override void _loadManagerExecutableFile(out bool found, out string path, out string callArguments) + { + var (_found, _executable) = GetExecutableFile(); + + found = _found; + path = CoreData.PowerShell5; + callArguments = $"-NoProfile -ExecutionPolicy Bypass -Command \"{_executable.Replace(" ", "` ")}\" "; + } + + protected override void _loadManagerVersion(out string version) + { + Process process = new() + { + StartInfo = new ProcessStartInfo + { + FileName = Status.ExecutablePath, + Arguments = Status.ExecutableCallArgs + "--version", + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + RedirectStandardInput = true, + CreateNoWindow = true, + WorkingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), + StandardOutputEncoding = System.Text.Encoding.UTF8 + } + }; + process.Start(); + version = process.StandardOutput.ReadToEnd().Trim(); + process.WaitForExit(); + } + } +} diff --git a/src/UniGetUI.PackageEngine.Managers.Bun/Helpers/BunPkgDetailsHelper.cs b/src/UniGetUI.PackageEngine.Managers.Bun/Helpers/BunPkgDetailsHelper.cs new file mode 100644 index 000000000..cc6c3351b --- /dev/null +++ b/src/UniGetUI.PackageEngine.Managers.Bun/Helpers/BunPkgDetailsHelper.cs @@ -0,0 +1,175 @@ +using System.Diagnostics; +using System.Globalization; +using System.Text.Json.Nodes; +using UniGetUI.Core.IconEngine; +using UniGetUI.Core.Logging; +using UniGetUI.PackageEngine.Classes.Manager.BaseProviders; +using UniGetUI.PackageEngine.Enums; +using UniGetUI.PackageEngine.Interfaces; +using UniGetUI.PackageEngine.ManagerClasses.Classes; + +namespace UniGetUI.PackageEngine.Managers.BunManager +{ + internal sealed class BunPkgDetailsHelper : BasePkgDetailsHelper + { + public BunPkgDetailsHelper(Bun manager) : base(manager) { } + + protected override void GetDetails_UnSafe(IPackageDetails details) + { + try + { + details.InstallerType = "Tarball"; + details.ManifestUrl = new Uri($"https://www.npmjs.com/package/{details.Package.Id}"); + details.ReleaseNotesUrl = new Uri($"https://www.npmjs.com/package/{details.Package.Id}?activeTab=versions"); + + using Process p = new(); + p.StartInfo = new ProcessStartInfo + { + FileName = Manager.Status.ExecutablePath, + Arguments = Manager.Status.ExecutableCallArgs + " show " + details.Package.Id + " --json", + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + RedirectStandardInput = true, + CreateNoWindow = true, + WorkingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), + StandardOutputEncoding = System.Text.Encoding.UTF8 + }; + + IProcessTaskLogger logger = Manager.TaskLogger.CreateNew(LoggableTaskType.LoadPackageDetails, p); + p.Start(); + + string strContents = p.StandardOutput.ReadToEnd(); + logger.AddToStdOut(strContents); + JsonObject? contents = JsonNode.Parse(strContents) as JsonObject; + + details.License = contents?["license"]?.ToString(); + details.Description = contents?["description"]?.ToString(); + + if (Uri.TryCreate(contents?["homepage"]?.ToString() ?? "", UriKind.RelativeOrAbsolute, out var homepageUrl)) + details.HomepageUrl = homepageUrl; + + details.Publisher = (contents?["maintainers"] as JsonArray)?[0]?.ToString(); + details.Author = contents?["author"]?.ToString(); + details.UpdateDate = contents?["time"]?[contents?["dist-tags"]?["latest"]?.ToString() ?? details.Package.VersionString]?.ToString(); + + if (Uri.TryCreate(contents?["dist"]?["tarball"]?.ToString() ?? "", UriKind.RelativeOrAbsolute, out var installerUrl)) + details.InstallerUrl = installerUrl; + + if (int.TryParse(contents?["dist"]?["unpackedSize"]?.ToString() ?? "", NumberStyles.Any, CultureInfo.InvariantCulture, out int installerSize)) + details.InstallerSize = installerSize; + + details.InstallerHash = contents?["dist"]?["integrity"]?.ToString(); + + details.Dependencies.Clear(); + HashSet addedDeps = new(); + foreach (var rawDep in (contents?["dependencies"]?.AsObject() ?? [])) + { + if(addedDeps.Contains(rawDep.Key)) continue; + addedDeps.Add(rawDep.Key); + + details.Dependencies.Add(new() + { + Name = rawDep.Key, + Version = rawDep.Value?.GetValue() ?? "", + Mandatory = true, + }); + } + + foreach (var rawDep in (contents?["devDependencies"]?.AsObject() ?? [])) + { + if(addedDeps.Contains(rawDep.Key)) continue; + addedDeps.Add(rawDep.Key); + + details.Dependencies.Add(new() + { + Name = rawDep.Key, + Version = rawDep.Value?.GetValue() ?? "", + Mandatory = false, + }); + } + + foreach (var rawDep in (contents?["peerDependencies"]?.AsObject() ?? [])) + { + if(addedDeps.Contains(rawDep.Key)) continue; + addedDeps.Add(rawDep.Key); + + details.Dependencies.Add(new() + { + Name = rawDep.Key, + Version = rawDep.Value?.GetValue() ?? "", + Mandatory = false, + }); + } + + logger.AddToStdErr(p.StandardError.ReadToEnd()); + p.WaitForExit(); + logger.Close(p.ExitCode); + } + catch (Exception e) + { + Logger.Error(e); + } + + return; + } + + protected override CacheableIcon? GetIcon_UnSafe(IPackage package) + { + throw new NotImplementedException(); + } + + protected override IReadOnlyList GetScreenshots_UnSafe(IPackage package) + { + throw new NotImplementedException(); + } + + protected override string? GetInstallLocation_UnSafe(IPackage package) + { + if (package.OverridenOptions.Scope is PackageScope.Local) + return Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "node_modules", package.Id); + return Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Roaming", "npm", + "node_modules", package.Id); + } + + protected override IReadOnlyList GetInstallableVersions_UnSafe(IPackage package) + { + using Process p = new() + { + StartInfo = new ProcessStartInfo + { + FileName = Manager.Status.ExecutablePath, + Arguments = + Manager.Status.ExecutableCallArgs + " show " + package.Id + " versions --json", + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + RedirectStandardInput = true, + CreateNoWindow = true, + WorkingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), + StandardOutputEncoding = System.Text.Encoding.UTF8 + } + }; + + IProcessTaskLogger logger = Manager.TaskLogger.CreateNew(LoggableTaskType.LoadPackageVersions, p); + p.Start(); + + string strContents = p.StandardOutput.ReadToEnd(); + logger.AddToStdOut(strContents); + JsonArray? rawVersions = JsonNode.Parse(strContents) as JsonArray; + + List versions = []; + foreach(JsonNode? raw_ver in rawVersions ?? []) + { + if (raw_ver is not null) + versions.Add(raw_ver.ToString()); + } + + logger.AddToStdErr(p.StandardError.ReadToEnd()); + p.WaitForExit(); + logger.Close(p.ExitCode); + + return versions; + } + } +} diff --git a/src/UniGetUI.PackageEngine.Managers.Bun/Helpers/BunPkgOperationHelper.cs b/src/UniGetUI.PackageEngine.Managers.Bun/Helpers/BunPkgOperationHelper.cs new file mode 100644 index 000000000..9c2a2ab82 --- /dev/null +++ b/src/UniGetUI.PackageEngine.Managers.Bun/Helpers/BunPkgOperationHelper.cs @@ -0,0 +1,46 @@ +using UniGetUI.PackageEngine.Classes.Manager.BaseProviders; +using UniGetUI.PackageEngine.Enums; +using UniGetUI.PackageEngine.Interfaces; +using UniGetUI.PackageEngine.Serializable; + +namespace UniGetUI.PackageEngine.Managers.BunManager; +internal sealed class BunPkgOperationHelper : BasePkgOperationHelper +{ + public BunPkgOperationHelper(Bun manager) : base(manager) { } + + protected override IReadOnlyList _getOperationParameters(IPackage package, + InstallOptions options, OperationType operation) + { + List parameters = operation switch { + OperationType.Install => [Manager.Properties.InstallVerb, $"'{package.Id}@{(options.Version == string.Empty? package.VersionString: options.Version)}'"], + OperationType.Update => [Manager.Properties.UpdateVerb, $"'{package.Id}@{package.NewVersionString}'"], + OperationType.Uninstall => [Manager.Properties.UninstallVerb, package.Id], + _ => throw new InvalidDataException("Invalid package operation") + }; + + if (package.OverridenOptions.Scope == PackageScope.Global || + (package.OverridenOptions.Scope is null && options.InstallationScope == PackageScope.Global)) + parameters.Add("--global"); + + if (options.PreRelease) + parameters.AddRange(["--include", "dev"]); + + parameters.AddRange(operation switch + { + OperationType.Update => options.CustomParameters_Update, + OperationType.Uninstall => options.CustomParameters_Uninstall, + _ => options.CustomParameters_Install, + }); + + return parameters; + } + + protected override OperationVeredict _getOperationResult( + IPackage package, + OperationType operation, + IReadOnlyList processOutput, + int returnCode) + { + return returnCode == 0 ? OperationVeredict.Success : OperationVeredict.Failure; + } +} diff --git a/src/UniGetUI.PackageEngine.Managers.Bun/UniGetUI.PackageEngine.Managers.Bun.csproj b/src/UniGetUI.PackageEngine.Managers.Bun/UniGetUI.PackageEngine.Managers.Bun.csproj new file mode 100644 index 000000000..6eb175c04 --- /dev/null +++ b/src/UniGetUI.PackageEngine.Managers.Bun/UniGetUI.PackageEngine.Managers.Bun.csproj @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/UniGetUI.sln b/src/UniGetUI.sln index d71e0676e..342ad7b85 100644 --- a/src/UniGetUI.sln +++ b/src/UniGetUI.sln @@ -107,6 +107,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UniGetUI.PackageEngine.Seri EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UniGetUI.Core.SecureSettings", "UniGetUI.Core.SecureSettings\UniGetUI.Core.SecureSettings.csproj", "{B0E59327-933E-4DB0-BD2D-FB16EB9B4194}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UniGetUI.PackageEngine.Managers.Bun", "UniGetUI.PackageEngine.Managers.Bun\UniGetUI.PackageEngine.Managers.Bun.csproj", "{D592551B-5329-44BA-9446-8ED475D7539A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64