diff --git a/src/dotnet-bootstrapper/BootstrapperCommandParser.cs b/src/dotnet-bootstrapper/BootstrapperCommandParser.cs index e754a6ae..7921da76 100644 --- a/src/dotnet-bootstrapper/BootstrapperCommandParser.cs +++ b/src/dotnet-bootstrapper/BootstrapperCommandParser.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; @@ -6,7 +6,7 @@ using System.CommandLine.Builder; using System.CommandLine.Invocation; using System.CommandLine.Parsing; -using System.Reflection; +using Microsoft.DotNet.Tools.Bootstrapper.Commands.Search; namespace Microsoft.DotNet.Tools.Bootstrapper { @@ -15,30 +15,16 @@ internal static class BootstrapperCommandParser public static Parser BootstrapParser; public static RootCommand BootstrapperRootCommand = new RootCommand("dotnet bootstrapper"); - - public static readonly Command VersionCommand = new Command("--version"); - - private static readonly Lazy _assemblyVersion = - new Lazy(() => - { - var assembly = Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly(); - var assemblyVersionAttribute = assembly.GetCustomAttribute(); - if (assemblyVersionAttribute == null) - { - return assembly.GetName().Version.ToString(); - } - else - { - return assemblyVersionAttribute.InformationalVersion; - } - }); + public static readonly Command HelpCommand = new("--help"); static BootstrapperCommandParser() { - BootstrapperRootCommand.AddCommand(VersionCommand); - VersionCommand.Handler = CommandHandler.Create(() => + BootstrapperRootCommand.AddCommand(SearchCommandParser.GetCommand()); + BootstrapperRootCommand.AddCommand(HelpCommand); + + HelpCommand.Handler = CommandHandler.Create(() => { - Console.WriteLine(_assemblyVersion.Value); + Console.WriteLine(LocalizableStrings.BootstrapperHelp); }); BootstrapParser = new CommandLineBuilder(BootstrapperRootCommand) diff --git a/src/dotnet-bootstrapper/Commands/CommandBase.cs b/src/dotnet-bootstrapper/Commands/CommandBase.cs new file mode 100644 index 00000000..a14cf73f --- /dev/null +++ b/src/dotnet-bootstrapper/Commands/CommandBase.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.CommandLine.Parsing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.DotNet.Tools.Bootstrapper.Commands +{ + public abstract class CommandBase + { + protected ParseResult _parseResult; + + protected CommandBase(ParseResult parseResult) + { + _parseResult = parseResult; + } + + public abstract int Execute(); + } +} diff --git a/src/dotnet-bootstrapper/Commands/Common.cs b/src/dotnet-bootstrapper/Commands/Common.cs new file mode 100644 index 00000000..b3c1006e --- /dev/null +++ b/src/dotnet-bootstrapper/Commands/Common.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.CommandLine; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.DotNet.Tools.Bootstrapper.Commands +{ + internal static class Common + { + internal static Option AllowPreviewsOptions = new Option( + "--allow-previews", + description: "Include pre-release sdk versions"); + } +} diff --git a/src/dotnet-bootstrapper/Commands/Search/SearchCommand.cs b/src/dotnet-bootstrapper/Commands/Search/SearchCommand.cs new file mode 100644 index 00000000..579b117f --- /dev/null +++ b/src/dotnet-bootstrapper/Commands/Search/SearchCommand.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.CommandLine.Parsing; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Deployment.DotNet.Releases; +using Spectre.Console; + +namespace Microsoft.DotNet.Tools.Bootstrapper.Commands.Search; + +internal class SearchCommand( + ParseResult parseResult) : CommandBase(parseResult) +{ + private string _channel = parseResult.ValueForArgument(SearchCommandParser.ChannelArgument); + private bool _allowPreviews = parseResult.ValueForOption(SearchCommandParser.AllowPreviewsOption); + public override int Execute() + { + List productCollection = [.. ProductCollection.GetAsync().Result]; + productCollection = [.. + productCollection.Where(product => !product.IsOutOfSupport() && (product.SupportPhase != SupportPhase.Preview || _allowPreviews))]; + + if (!string.IsNullOrEmpty(_channel)) + { + productCollection = [.. productCollection.Where(product => product.ProductVersion.Equals(_channel, StringComparison.OrdinalIgnoreCase))]; + } + + foreach (Product product in productCollection) + { + string productHeader = $"{product.ProductName} {product.ProductVersion}"; + Console.WriteLine(productHeader); + + Table productMetadataTable = new Table() + .AddColumn("Version") + .AddColumn("Release Date") + .AddColumn("Latest SDK") + .AddColumn("Runtime") + .AddColumn("ASP.NET Runtime") + .AddColumn("Windows Desktop Runtime"); + + List releases = product.GetReleasesAsync().Result.ToList() + .Where(relase => !relase.IsPreview || _allowPreviews).ToList(); + + foreach (ProductRelease release in releases) + { + // Get release.Sdks latest version + var latestSdk = release.Sdks + .OrderByDescending(sdk => sdk.Version) + .FirstOrDefault(); + + productMetadataTable.AddRow( + release.Version.ToString(), + release.ReleaseDate.ToString("d", CultureInfo.CurrentUICulture), + latestSdk?.DisplayVersion ?? "N/A", + release.Runtime?.DisplayVersion ?? "N/A", + release.AspNetCoreRuntime?.DisplayVersion ?? "N/A", + release.WindowsDesktopRuntime?.DisplayVersion ?? "N/A"); + } + AnsiConsole.Write(productMetadataTable); + Console.WriteLine(); + } + + return 0; + } +} diff --git a/src/dotnet-bootstrapper/Commands/Search/SearchCommandParser.cs b/src/dotnet-bootstrapper/Commands/Search/SearchCommandParser.cs new file mode 100644 index 00000000..7f947e08 --- /dev/null +++ b/src/dotnet-bootstrapper/Commands/Search/SearchCommandParser.cs @@ -0,0 +1,33 @@ +using System.CommandLine; +using System.CommandLine.Invocation; +using System.CommandLine.Parsing; + +namespace Microsoft.DotNet.Tools.Bootstrapper.Commands.Search; + +internal class SearchCommandParser +{ + internal static Argument ChannelArgument = new Argument( + name: "channel", + description: "The channel to list sdks for. If not specified, all sdks will be listed.") + { + Arity = ArgumentArity.ZeroOrOne + }; + + internal static Option AllowPreviewsOption = Common.AllowPreviewsOptions; + + private static readonly Command Command = ConstructCommand(); + public static Command GetCommand() => Command; + private static Command ConstructCommand() + { + Command command = new("search", "Search for SDKs available for installation."); + command.AddArgument(ChannelArgument); + command.AddOption(AllowPreviewsOption); + + command.Handler = CommandHandler.Create((ParseResult parseResult) => + { + return new SearchCommand(parseResult).Execute(); + }); + + return command; + } +} diff --git a/src/dotnet-bootstrapper/GlobalJsonUtilities.cs b/src/dotnet-bootstrapper/GlobalJsonUtilities.cs new file mode 100644 index 00000000..ad808b85 --- /dev/null +++ b/src/dotnet-bootstrapper/GlobalJsonUtilities.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.IO; +using System.Text.Json; +using Microsoft.Deployment.DotNet.Releases; + +namespace Microsoft.DotNet.Tools.Bootstrapper +{ + /// + /// Provides utility methods for working with global.json files in a directory tree. + /// + internal static class GlobalJsonUtilities + { + /// + /// Finds the nearest global.json file path by traversing up the directory tree starting from the specified base path. + /// + /// The starting directory path to search for the global.json file. + /// + /// The full path to the nearest global.json file if found; otherwise, null. + /// + public static string GetNearestGlobalJsonPath(string basePath) + { + // Example implementation: Traverse up the directory tree to find the nearest global.json file. + string currentPath = basePath; + while (!string.IsNullOrEmpty(currentPath)) + { + string globalJsonPath = Path.Combine(currentPath, "global.json"); + if (File.Exists(globalJsonPath)) + { + return globalJsonPath; + } + currentPath = Directory.GetParent(currentPath)?.FullName; + } + // If no global.json file is found, return null. + return null; + } + + /// + /// Retrieves the contents of the nearest global.json file as a JsonElement by traversing up the directory tree. + /// + /// The starting directory path to search for the global.json file. + /// + /// A JsonElement representing the contents of the nearest global.json file. + /// + /// Thrown if no global.json file is found in the directory tree. + /// Thrown if global.json cannot be parsed as json + public static JsonElement GetNearestGlobalJson(string basePath) + { + string globalJsonPath = GetNearestGlobalJsonPath(basePath); + + if (globalJsonPath == null) + { + throw new FileNotFoundException("No global.json file found in the directory tree."); + } + + return JsonDocument.Parse(File.ReadAllText(globalJsonPath)).RootElement; + } + /// + /// Retrieves the major and minor version of the .NET SDK specified in the nearest global.json file + /// within the given base path. + /// + /// The base directory path to search for the global.json file. + /// + /// A string representing the major and minor version (e.g., "6.0") of the .NET SDK specified + /// in the global.json file. + /// + /// + /// Thrown when the required keys ("tools" or "dotnet") are not found in the global.json file. + /// + public static string GetMajorVersionToInstallInDirectory(string basePath) + { + try + { + // Get the nearest global.json file. + JsonElement globalJson = GlobalJsonUtilities.GetNearestGlobalJson(basePath); + string sdkVersion = globalJson + .GetProperty("tools") + .GetProperty("dotnet") + .ToString(); + + ReleaseVersion version = ReleaseVersion.Parse(sdkVersion); + Console.WriteLine($"Found version {version.Major}.{version.Minor} in global.json"); + return $"{version.Major}.{version.Minor}"; + } + catch (KeyNotFoundException e) + { + throw new KeyNotFoundException("The specified key was not found in the global.json", e); + } + } + } +} diff --git a/src/dotnet-bootstrapper/LocalizableStrings.Designer.cs b/src/dotnet-bootstrapper/LocalizableStrings.Designer.cs new file mode 100644 index 00000000..834cf15f --- /dev/null +++ b/src/dotnet-bootstrapper/LocalizableStrings.Designer.cs @@ -0,0 +1,72 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.DotNet.Tools.Bootstrapper { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class LocalizableStrings { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal LocalizableStrings() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.DotNet.Tools.Bootstrapper.LocalizableStrings", typeof(LocalizableStrings).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Bootstrapper help text. + /// + internal static string BootstrapperHelp { + get { + return ResourceManager.GetString("BootstrapperHelp", resourceCulture); + } + } + } +} diff --git a/src/dotnet-bootstrapper/LocalizableStrings.resx b/src/dotnet-bootstrapper/LocalizableStrings.resx index 8b2ff64a..85e4edd0 100644 --- a/src/dotnet-bootstrapper/LocalizableStrings.resx +++ b/src/dotnet-bootstrapper/LocalizableStrings.resx @@ -117,4 +117,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + Bootstrapper help text + \ No newline at end of file diff --git a/src/dotnet-bootstrapper/dotnet-bootstrapper.csproj b/src/dotnet-bootstrapper/dotnet-bootstrapper.csproj index 3c081deb..acd992ab 100644 --- a/src/dotnet-bootstrapper/dotnet-bootstrapper.csproj +++ b/src/dotnet-bootstrapper/dotnet-bootstrapper.csproj @@ -17,14 +17,17 @@ preview Microsoft.DotNet.Tools.Bootstrapper + $(NoWarn);CS8002 + + @@ -39,6 +42,7 @@ ResXFileCodeGenerator LocalizableStrings.Designer.cs + Microsoft.DotNet.Tools.Bootstrapper diff --git a/src/dotnet-bootstrapper/xlf/LocalizableStrings.cs.xlf b/src/dotnet-bootstrapper/xlf/LocalizableStrings.cs.xlf index e028ecc8..62e39f34 100644 --- a/src/dotnet-bootstrapper/xlf/LocalizableStrings.cs.xlf +++ b/src/dotnet-bootstrapper/xlf/LocalizableStrings.cs.xlf @@ -1,6 +1,12 @@  - + + + Bootstrapper help text + Bootstrapper help text + + + \ No newline at end of file diff --git a/src/dotnet-bootstrapper/xlf/LocalizableStrings.de.xlf b/src/dotnet-bootstrapper/xlf/LocalizableStrings.de.xlf index 02f57c3c..b900a85c 100644 --- a/src/dotnet-bootstrapper/xlf/LocalizableStrings.de.xlf +++ b/src/dotnet-bootstrapper/xlf/LocalizableStrings.de.xlf @@ -1,6 +1,12 @@  - + + + Bootstrapper help text + Bootstrapper help text + + + \ No newline at end of file diff --git a/src/dotnet-bootstrapper/xlf/LocalizableStrings.es.xlf b/src/dotnet-bootstrapper/xlf/LocalizableStrings.es.xlf index bd51c90f..1bdf68b3 100644 --- a/src/dotnet-bootstrapper/xlf/LocalizableStrings.es.xlf +++ b/src/dotnet-bootstrapper/xlf/LocalizableStrings.es.xlf @@ -1,6 +1,12 @@  - + + + Bootstrapper help text + Bootstrapper help text + + + \ No newline at end of file diff --git a/src/dotnet-bootstrapper/xlf/LocalizableStrings.fr.xlf b/src/dotnet-bootstrapper/xlf/LocalizableStrings.fr.xlf index 353aa168..3faf4859 100644 --- a/src/dotnet-bootstrapper/xlf/LocalizableStrings.fr.xlf +++ b/src/dotnet-bootstrapper/xlf/LocalizableStrings.fr.xlf @@ -1,6 +1,12 @@  - + + + Bootstrapper help text + Bootstrapper help text + + + \ No newline at end of file diff --git a/src/dotnet-bootstrapper/xlf/LocalizableStrings.it.xlf b/src/dotnet-bootstrapper/xlf/LocalizableStrings.it.xlf index 8521fc88..0fe72bf7 100644 --- a/src/dotnet-bootstrapper/xlf/LocalizableStrings.it.xlf +++ b/src/dotnet-bootstrapper/xlf/LocalizableStrings.it.xlf @@ -1,6 +1,12 @@  - + + + Bootstrapper help text + Bootstrapper help text + + + \ No newline at end of file diff --git a/src/dotnet-bootstrapper/xlf/LocalizableStrings.ja.xlf b/src/dotnet-bootstrapper/xlf/LocalizableStrings.ja.xlf index a4bdbd56..b99ec495 100644 --- a/src/dotnet-bootstrapper/xlf/LocalizableStrings.ja.xlf +++ b/src/dotnet-bootstrapper/xlf/LocalizableStrings.ja.xlf @@ -1,6 +1,12 @@  - + + + Bootstrapper help text + Bootstrapper help text + + + \ No newline at end of file diff --git a/src/dotnet-bootstrapper/xlf/LocalizableStrings.ko.xlf b/src/dotnet-bootstrapper/xlf/LocalizableStrings.ko.xlf index dfe7ff8c..5291085c 100644 --- a/src/dotnet-bootstrapper/xlf/LocalizableStrings.ko.xlf +++ b/src/dotnet-bootstrapper/xlf/LocalizableStrings.ko.xlf @@ -1,6 +1,12 @@  - + + + Bootstrapper help text + Bootstrapper help text + + + \ No newline at end of file diff --git a/src/dotnet-bootstrapper/xlf/LocalizableStrings.pl.xlf b/src/dotnet-bootstrapper/xlf/LocalizableStrings.pl.xlf index edb8d7de..5fb33884 100644 --- a/src/dotnet-bootstrapper/xlf/LocalizableStrings.pl.xlf +++ b/src/dotnet-bootstrapper/xlf/LocalizableStrings.pl.xlf @@ -1,6 +1,12 @@  - + + + Bootstrapper help text + Bootstrapper help text + + + \ No newline at end of file diff --git a/src/dotnet-bootstrapper/xlf/LocalizableStrings.pt-BR.xlf b/src/dotnet-bootstrapper/xlf/LocalizableStrings.pt-BR.xlf index 616bbc89..5275a2d0 100644 --- a/src/dotnet-bootstrapper/xlf/LocalizableStrings.pt-BR.xlf +++ b/src/dotnet-bootstrapper/xlf/LocalizableStrings.pt-BR.xlf @@ -1,6 +1,12 @@  - + + + Bootstrapper help text + Bootstrapper help text + + + \ No newline at end of file diff --git a/src/dotnet-bootstrapper/xlf/LocalizableStrings.ru.xlf b/src/dotnet-bootstrapper/xlf/LocalizableStrings.ru.xlf index 8e47a5f3..f8ddcba8 100644 --- a/src/dotnet-bootstrapper/xlf/LocalizableStrings.ru.xlf +++ b/src/dotnet-bootstrapper/xlf/LocalizableStrings.ru.xlf @@ -1,6 +1,12 @@  - + + + Bootstrapper help text + Bootstrapper help text + + + \ No newline at end of file diff --git a/src/dotnet-bootstrapper/xlf/LocalizableStrings.tr.xlf b/src/dotnet-bootstrapper/xlf/LocalizableStrings.tr.xlf index 770cdf4a..d4074d68 100644 --- a/src/dotnet-bootstrapper/xlf/LocalizableStrings.tr.xlf +++ b/src/dotnet-bootstrapper/xlf/LocalizableStrings.tr.xlf @@ -1,6 +1,12 @@  - + + + Bootstrapper help text + Bootstrapper help text + + + \ No newline at end of file diff --git a/src/dotnet-bootstrapper/xlf/LocalizableStrings.zh-Hans.xlf b/src/dotnet-bootstrapper/xlf/LocalizableStrings.zh-Hans.xlf index 1832bb6c..94caddf0 100644 --- a/src/dotnet-bootstrapper/xlf/LocalizableStrings.zh-Hans.xlf +++ b/src/dotnet-bootstrapper/xlf/LocalizableStrings.zh-Hans.xlf @@ -1,6 +1,12 @@  - + + + Bootstrapper help text + Bootstrapper help text + + + \ No newline at end of file diff --git a/src/dotnet-bootstrapper/xlf/LocalizableStrings.zh-Hant.xlf b/src/dotnet-bootstrapper/xlf/LocalizableStrings.zh-Hant.xlf index 4a6c48aa..28886312 100644 --- a/src/dotnet-bootstrapper/xlf/LocalizableStrings.zh-Hant.xlf +++ b/src/dotnet-bootstrapper/xlf/LocalizableStrings.zh-Hant.xlf @@ -1,6 +1,12 @@  - + + + Bootstrapper help text + Bootstrapper help text + + + \ No newline at end of file diff --git a/src/dotnet-core-uninstall/Shared/Configs/CommandLineConfigs.cs b/src/dotnet-core-uninstall/Shared/Configs/CommandLineConfigs.cs index 863b07c8..23935d08 100644 --- a/src/dotnet-core-uninstall/Shared/Configs/CommandLineConfigs.cs +++ b/src/dotnet-core-uninstall/Shared/Configs/CommandLineConfigs.cs @@ -20,7 +20,7 @@ namespace Microsoft.DotNet.Tools.Uninstall.Shared.Configs { - internal static class CommandLineConfigs + public static class CommandLineConfigs { public static Parser UninstallCommandParser; @@ -28,6 +28,7 @@ internal static class CommandLineConfigs private static readonly string DryRunCommandName = "dry-run"; private static readonly string WhatIfCommandName = "whatif"; private static readonly string RemoveCommandName = "remove"; + private static readonly string UninstallCommandName = "uninstall"; public static readonly RootCommand UninstallRootCommand = new RootCommand( RuntimeInfo.RunningOnWindows ? LocalizableStrings.UninstallNoOptionDescriptionWindows @@ -184,7 +185,7 @@ internal static class CommandLineConfigs ForceOption }; - public static readonly Dictionary VerbosityLevels = new Dictionary + internal static readonly Dictionary VerbosityLevels = new Dictionary { { "q", VerbosityLevel.Quiet }, { "quiet", VerbosityLevel.Quiet }, { "m", VerbosityLevel.Minimal }, { "minimal", VerbosityLevel.Minimal }, @@ -220,6 +221,7 @@ static CommandLineConfigs() UninstallRootCommand.AddCommand(ListCommand); UninstallRootCommand.AddCommand(DryRunCommand); UninstallRootCommand.AddCommand(RemoveCommand); + RemoveCommand.AddAlias(UninstallCommandName); // The verbiage that makes the most sense from the bootstrapper would be 'uninstall', so just adding an alias permits more code sharing UninstallRootCommand.AddCommand(VersionSubcommand); if (RuntimeInfo.RunningOnOSX) @@ -312,7 +314,7 @@ public static Option GetUninstallMainOption(this CommandResult commandResult) return specifiedOption; } - public static BundleType GetTypeSelection(this ParseResult parseResult) + internal static BundleType GetTypeSelection(this ParseResult parseResult) { var supportedBundleTypes = SupportedBundleTypeConfigs.GetSupportedBundleTypes(); @@ -326,7 +328,7 @@ public static BundleType GetTypeSelection(this ParseResult parseResult) typeSelection; } - public static BundleArch GetArchSelection(this ParseResult parseResult) + internal static BundleArch GetArchSelection(this ParseResult parseResult) { var archSelection = new[] { @@ -343,7 +345,7 @@ public static BundleArch GetArchSelection(this ParseResult parseResult) archSelection; } - public static VerbosityLevel GetVerbosityLevel(this CommandResult commandResult) + internal static VerbosityLevel GetVerbosityLevel(this CommandResult commandResult) { var optionResult = commandResult.FindResultFor(VerbosityOption); diff --git a/src/dotnet-core-uninstall/dotnet-core-uninstall.csproj b/src/dotnet-core-uninstall/dotnet-core-uninstall.csproj index fd483523..5f61daa7 100644 --- a/src/dotnet-core-uninstall/dotnet-core-uninstall.csproj +++ b/src/dotnet-core-uninstall/dotnet-core-uninstall.csproj @@ -2,6 +2,7 @@ dotnet-core-uninstall Exe + true win-x86;osx-x64;osx-arm64 true net8.0