Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for NuGet authentication plugins deployed via .NET tools #13242

Merged
merged 70 commits into from
Apr 26, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
786a50a
initial commit
kartheekp-ms Feb 5, 2024
7a7d7cb
intial draft
kartheekp-ms Feb 6, 2024
ee3b3a3
change file extension
kartheekp-ms Feb 6, 2024
47e844a
support for credential providers to use NuGet plugins deployed via .N…
kartheekp-ms Feb 5, 2024
fd465a9
Update accepted/2024/support-nuget-authentication-plugins-dotnet-tool…
kartheekp-ms Feb 14, 2024
a938267
Update accepted/2024/support-nuget-authentication-plugins-dotnet-tool…
kartheekp-ms Feb 14, 2024
356d8b6
Update accepted/2024/support-nuget-authentication-plugins-dotnet-tool…
kartheekp-ms Feb 14, 2024
a8ced14
remove en-us from the link
Feb 15, 2024
edd850c
Formatted markdown so that the source has one sentense per line.
Feb 15, 2024
b33b459
Update workflow for repositories accessing private NuGet feeds
kartheekp-ms Feb 15, 2024
a5e469c
rename directory to any
kartheekp-ms Feb 16, 2024
1fa8abc
updated summary
kartheekp-ms Feb 16, 2024
953d2a1
Update NuGet plugin organization for .NET tools
kartheekp-ms Feb 16, 2024
8653590
update motivation
kartheekp-ms Feb 16, 2024
6c294b3
updated technical specification
kartheekp-ms Feb 16, 2024
742d471
one sentense per line
kartheekp-ms Feb 16, 2024
65b104d
use tree /f command output instead of images
kartheekp-ms Feb 16, 2024
7c50c46
updated drawbacks section
kartheekp-ms Mar 9, 2024
18361fb
one sentense per line.
kartheekp-ms Mar 9, 2024
acd1ae4
updated drawbacks section
kartheekp-ms Mar 9, 2024
953ce5d
Update NuGet plugin folder structure in non-Windows platforms
Mar 12, 2024
2257610
lint issues
Mar 12, 2024
5986371
link issues
Mar 12, 2024
9890ac4
Update NuGet tool installation process
Mar 12, 2024
0585ae5
Update NuGet authentication plugin directory path
Mar 12, 2024
1980809
Update NuGet plugin path in dotnet-codeartifact-creds.exe
Mar 12, 2024
fce39bb
Update NuGet plugin installation process for cross-platform support
kartheekp-ms Mar 14, 2024
319d79d
Update NuGet plugin installation process for cross-platform support
kartheekp-ms Mar 17, 2024
4975f92
Update NuGet plugin installation process for cross-platform support
kartheekp-ms Mar 18, 2024
92947d7
Added issue with NuGet credential providers interfering with workload…
kartheekp-ms Mar 18, 2024
8300839
Fix issue with NuGet credential providers interfering with workload i…
kartheekp-ms Mar 18, 2024
fc0290a
Update NuGet plugin installation process for cross-platform support a…
kartheekp-ms Mar 18, 2024
0addbd4
Update NuGet plugin installation process for cross-platform support a…
kartheekp-ms Mar 18, 2024
54b28ad
Update NuGet plugin installation process for cross-platform support a…
kartheekp-ms Mar 18, 2024
f78f1b2
add update sub-command to dotnet nuget plugin commands
kartheekp-ms Mar 18, 2024
9eba3cb
added roadmap section
kartheekp-ms Mar 18, 2024
3196a90
Update NuGet plugin installation process and add dotnet nuget plugin …
kartheekp-ms Mar 18, 2024
db09e24
Fix NuGet credential provider installation issue and propose workaround
Mar 18, 2024
41f08d4
Proposed workaround for NuGet authentication issue
Mar 18, 2024
14ed702
referred about similarities with dotnet workload command.
Mar 18, 2024
41a8f2d
Merge branch 'dev-kpms-credentialproviders-.nettools' of https://gith…
Mar 19, 2024
d118dbe
Update NuGet credential provider deployment process for .NET tools
Mar 19, 2024
a065b76
remove unnecessary file
Mar 19, 2024
c6127de
arranged the spec for a better flow
Mar 19, 2024
76bb1f4
Update NuGet Client plugin code to support non-.NET plugins
Mar 19, 2024
dfcba32
Update NuGet plugin support for non-.NET languages
Mar 19, 2024
c925ea1
Update support for NuGet plugins developed in non-.NET languages
Mar 19, 2024
827c8f8
specifying tool command name is optional
Mar 20, 2024
81b7197
update spec based on the feedback
kartheekp-ms Mar 27, 2024
ba347ad
fixed typos and added some information about NuGet.exe currently [sca…
kartheekp-ms Mar 27, 2024
c5c80ff
make it readable
kartheekp-ms Mar 27, 2024
2cebea9
added future possibilities section to improve discoverability of NuGe…
kartheekp-ms Mar 27, 2024
0cf2676
improved future possibility
kartheekp-ms Mar 28, 2024
fb3b5b1
work
kartheekp-ms Mar 30, 2024
7560e79
fix lint issues
Apr 1, 2024
23be277
Added lack of .NET local tools support as a drawback
kartheekp-ms Apr 2, 2024
cff6c91
arranged points in drawbacks section
kartheekp-ms Apr 2, 2024
f1abcb5
Add future possibilities section for improved discoverability of NuGe…
kartheekp-ms Apr 2, 2024
a7bf35f
Apply suggestions from code review
kartheekp-ms Apr 2, 2024
96b43ce
updated motivation section based on the feedback.
kartheekp-ms Apr 2, 2024
8f47d91
removed the log sample from codeartifact credential provider
kartheekp-ms Apr 3, 2024
b888c9e
updated security considerations
kartheekp-ms Apr 3, 2024
233bcbf
addressed part of the feedback
Apr 3, 2024
93f04a6
feedback about DotnetToolSettings.xml
kartheekp-ms Apr 3, 2024
4c300cb
Added batch file support and removed it from the future possibility s…
Apr 3, 2024
828f474
removed support for non-.net plugins from the spec as we added suppor…
Apr 3, 2024
5cdeae4
Add note about executing local tool using dotnet CLI
Apr 3, 2024
44a9226
address feedback
Apr 3, 2024
124d3f0
Add support for developing NuGet plugins in non-.NET languages in the…
Apr 4, 2024
643a1ff
Make the content in "Installing NuGet plugins as tool-path .NET Tools…
Apr 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
210 changes: 210 additions & 0 deletions accepted/2024/support-nuget-authentication-plugins-dotnet-tools.md
nkolev92 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
# ***Support for NuGet authentication plugins deployed via .NET tools***

- Author Name: https://github.com/kartheekp-ms
- GitHub Issue: https://github.com/NuGet/Home/issues/12567

## Summary

Currently, NuGet utilizes a [cross-platform plugin model](https://learn.microsoft.com/nuget/reference/extensibility/nuget-cross-platform-plugins#supported-operations) which is primarily used for [authentication against private feeds](https://learn.microsoft.com/en-us/nuget/reference/extensibility/nuget-cross-platform-authentication-plugin). It also supports the [package download](https://github.com/NuGet/Home/wiki/NuGet-Package-Download-Plugin) operation.
kartheekp-ms marked this conversation as resolved.
Show resolved Hide resolved

To accommodate all scenarios involving NuGet client tools, plugin authors will need to create plugins for both `.NET Framework` and `.NET Core`. The following details the combinations of client and framework for these plugins.

| Client tool | Framework |
|-------------|-----------|
| Visual Studio | .NET Framework |
| dotnet.exe | .NET Core |
nkolev92 marked this conversation as resolved.
Show resolved Hide resolved
| NuGet.exe | .NET Framework |
| MSBuild.exe | .NET Framework |
| NuGet.exe on Mono | .NET Framework |

Currently, NuGet maintains `netfx` folder for plugins that will be invoked in `.NET Framework` code paths, `netcore` folder for plugins that will be invoked in `.NET Core` code paths. The proposal is to add a new `tools` folder to store NuGet plugins that are deployed as [tool Path](https://learn.microsoft.com/dotnet/core/tools/global-tools-how-to-use#use-the-tool-as-a-global-tool-installed-in-a-custom-location) .NET tools.
kartheekp-ms marked this conversation as resolved.
Show resolved Hide resolved

| Framework | Root discovery location |
|-----------|------------------------|
| .NET Core | %UserProfile%/.nuget/plugins/netcore |
| .NET Framework | %UserProfile%/.nuget/plugins/netfx |
| .NET Framework & .NET Core [current proposal] | %UserProfile%/.nuget/plugins/tools |

## Motivation

At present, NuGet uses different methods to execute plugins for `.NET Framework` and `.NET Core`. For the `.NET Framework`, it looks for files ending in `*.exe`, while for `.NET Core`, it looks for files ending in `*.dll`. These files are usually stored in two separate folders under the base path for NuGet plugins.

Before `.NET Core 2.0`, this division was necessary because `.NET Core` only supported platform-agnostic DLLs. However, with the latest versions of .NET, this division is no longer necessary, but the NuGet plugin architecture still requires supporting and deploying multiple versions. This behavior is further reinforced by the two plugin path environment variables `NUGET_NETFX_PLUGIN_PATHS` and `NUGET_NETCORE_PLUGIN_PATHS`, which need to be set for each framework version.
kartheekp-ms marked this conversation as resolved.
Show resolved Hide resolved

Another motivating factor for this work is the current story for installing these credential providers, which is not ideal. For instance, let's consider the two cross-platform authentication plugins that I am aware of while writing this specification:

1. **Azure Artifacts Credential Provider** - The [setup](https://github.com/microsoft/artifacts-credprovider/tree/master?tab=readme-ov-file#setup) instructions vary based on the platform.
2. **AWS CodeArtifact Credential Provider** - The AWS team has developed a [.NET tool](https://docs.aws.amazon.com/codeartifact/latest/ug/nuget-cli.html#nuget-configure-cli) to facilitate authentication with their private NuGet feeds. In their current implementation, they've added a subcommand, `codeartifact-creds install`, which copies the credential provider to the NuGet plugins folder. The log below shows all possible subcommands. However, plugin authors could leverage the .NET SDK to allow their customers to install, uninstall, or update the NuGet plugins more efficiently.

```log
~\.nuget\plugins\tools
❯ .\dotnet-codeartifact-creds.exe
Required command was not provided.
zivkan marked this conversation as resolved.
Show resolved Hide resolved

Usage:
dotnet-codeartifact-creds [options] [command]

Options:
--version Show version information
-?, -h, --help Show help and usage information

Commands:
install Installs the AWS CodeArtifact NuGet credential provider into the NuGet plugins folder.
configure Sets or Unsets a configuration
uninstall Uninstalls the AWS CodeArtifact NuGet credential provider from the NuGet plugins folder.
```

## Explanation

### Functional explanation

A deployment solution for .NET is the [.NET tools](https://learn.microsoft.com/dotnet/core/tools). These tools provide a seamless installation and management experience for NuGet packages in the .NET ecosystem. The use of .NET tools as a deployment mechanism has been a recurring request from users and internal partners who need to authenticate with private repositories. Currently, this solution works for the Windows .NET Framework. However, the goal is to extend this support cross-platform for all supported .NET runtimes.

By implementing this specification, we offer plugin authors the option to use .NET tools for plugin deployment. They can install these as a `tool path` global tool. This eliminates the need to maintain separate versions for `.NET Framework` and `.NET Core`. It also simplifies the installation process by removing the necessity for plugin authors to create subcommands like `codeartifact-creds install/uninstall`.

The ideal workflow for repositories that access private NuGet feeds, such as Azure DevOps, would be:
zivkan marked this conversation as resolved.
Show resolved Hide resolved

1. Ensure that the dotnet CLI tools are installed.
2. Execute the command `dotnet tool install Microsoft.CredentialProviders --tool-path "%UserProfile%/.nuget/plugins/tools"` on the Windows platform. For Linux and Mac, run `dotnet tool install Microsoft.CredentialProviders --tool-path $HOME/.nuget/plugins`.
zivkan marked this conversation as resolved.
Show resolved Hide resolved
3. Run `dotnet restore --interactive` with a private endpoint. It should 'just work', meaning the credential providers installed in step 2 are used during credential acquisition and are used to authenticate against private endpoints.

The setup instructions mentioned in step 2 are platform-specific, but they are simpler compared to the current instructions for installing credential providers.

I proposed using a `tool path` .NET tool because, by default, .NET tools are considered [global](https://learn.microsoft.com/dotnet/core/tools/global-tools). This means they are installed in a default directory, such as `%UserProfile%/.dotnet/tools` on Windows, which is then added to the PATH environment variable.
- Global tools can be invoked from any directory on the machine without specifying their location.
- One version of a tool is used for all directories on the machine.
However, NuGet cannot easily determine which tool is a cross-platform authentication or a package download plugin without invoking every single global tool installed on the machine. This could potentially lead to a performance hit for NuGet operations.
zivkan marked this conversation as resolved.
Show resolved Hide resolved
On the other hand, the `tool path` option allows us to install .NET tools as global tools, but with the binaries located in a specified location. This makes it easier to identify and invoke the appropriate tool for NuGet operations.
- The binaries are installed in a location that we specify while installing the tool.
- We can invoke the tool from the installation directory by providing the directory with the command name or by adding the directory to the PATH environment variable.
kartheekp-ms marked this conversation as resolved.
Show resolved Hide resolved
- One version of a tool is used for all directories on the machine.
The `tool path` option aligns well with the design of NuGet plugins architecture, making it the recommended approach for installing and executing NuGet plugins.

### Security considerations

The [.NET SDK docs](https://learn.microsoft.com/dotnet/core/tools/global-tools#check-the-author-and-statistics) clearly state, `.NET tools run in full trust. Don't install a .NET tool unless you trust the author`. This is an important consideration for plugin customers when installing NuGet plugins via .NET Tools in the future.

### Technical explanation

Plugins are discovered through a convention-based directory structure, such as the `%userprofile%/.nuget/plugins` folder on Windows. CI/CD scenarios and power users can use environment variables to override this behavior. Note that only absolute paths are allowed when using these environment variables.

- `NUGET_NETFX_PLUGIN_PATHS`: Defines the plugins used by the .NET Framework-based tooling (NuGet.exe/MSBuild.exe/Visual Studio). This takes precedence over `NUGET_PLUGIN_PATHS`.
- `NUGET_NETCORE_PLUGIN_PATHS`: Defines the plugins used by the .NET Core-based tooling (dotnet.exe). This takes precedence over `NUGET_PLUGIN_PATHS`.
- `NUGET_PLUGIN_PATHS`: Defines the plugins used for the NuGet process, with priority preserved. If this environment variable is set, it overrides the convention-based discovery. It is ignored if either of the framework-specific variables is specified.

I propose the addition of a `NUGET_DOTNET_TOOLS_PLUGIN_PATHS` environment variable. This variable will define the plugins, installed as .NET tools, to be used by both .NET Framework and .NET Core tooling. It will take precedence over `NUGET_PLUGIN_PATHS`.

The plugins specified in the `NUGET_DOTNET_TOOLS_PLUGIN_PATHS` environment variable will be used regardless of whether the `NUGET_NETFX_PLUGIN_PATHS` or `NUGET_NETCORE_PLUGIN_PATHS` environment variables are set. The primary reason for this is that plugins installed as .NET tools can be executed in both .NET Framework and .NET Core tooling.

If customers prefer to install NuGet plugins as a global tool instead of a tool-path tool, they can set the `NUGET_DOTNET_TOOLS_PLUGIN_PATHS` environment variable. This variable should point to the location of the .NET Tool executable that the NuGet Client tooling can invoke when needed.
kartheekp-ms marked this conversation as resolved.
Show resolved Hide resolved

If none of the above environment variables are set, NuGet will default to the conventional method of discovering plugins from predefined directories. In the [current implementation](https://github.com/NuGet/NuGet.Client/blob/8b658e2eee6391936887b9fd1b39f7918d16a9cb/src/NuGet.Core/NuGet.Protocol/Plugins/PluginDiscoveryUtility.cs#L65-L77), the NuGet code looks for plugin files in the `netfx` directory when running on .NET Framework, and in the `netcore` directory when running on .NET Core. This implementation should be updated to include the new `tools` directory. This directory should be added alongside `netcore` in the .NET code paths and `netfx` in the .NET Framework code paths, to ensure backward compatibility.
zivkan marked this conversation as resolved.
Show resolved Hide resolved

In the [current implementation](https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Protocol/Plugins/PluginDiscoveryUtility.cs#L79-L101), the NuGet code searches for plugin files in a plugin-specific subdirectory. For example, in the case of the Azure Artifacts Credential Provider, the code looks for `*.exe` or `*.dll` files under the "CredentialProvider.Microsoft" directory. However, when .NET tools are installed in the `tools` folder, executable files (like `.exe` files on Windows or files with the executable bit set on Linux & Mac) are placed directly in the `tools` directory. The remaining files are stored in a `.store` folder. This arrangement eliminates the need to search in subdirectories.

The new folder structure for NuGet plugins on the Windows platform is as follows: The `tools` folder contains .NET tool plugins, while the `.store` folder houses other necessary files.
kartheekp-ms marked this conversation as resolved.
Show resolved Hide resolved

![plugin-tools-folder-windows](./../../meta/resources/PluginsAsDotNetTools/plugin-tools-folder-windows.png)

![plugin-tools-store-windows](./../../meta/resources/PluginsAsDotNetTools/plugin-tools-store-windows.png)

The new folder structure for NuGet plugins on the Linux platforms is as follows:

```log
{user}:~/.nuget/plugins$ dir
netcore tools
kartheekp-ms marked this conversation as resolved.
Show resolved Hide resolved

{user}:~/.nuget/plugins$ cd tools/
{user}:~/.nuget/plugins/tools$ dir
botsay dotnetsay

{user}:~/.nuget/plugins/tools$ ls -la
total 164
drwxr-xr-x 3 {user} 4096 Feb 10 08:21 .
drwxr-xr-x 4 {user} 4096 Feb 10 08:20 ..
drwxr-xr-x 5 {user} 4096 Feb 10 08:21 .store
-rwxr-xr-x 1 {user} 75632 Feb 10 08:21 botsay
-rwxr-xr-x 1 {user} 75632 Feb 10 08:20 dotnetsay
```

## Drawbacks

The ideal workflow for repositories accessing private NuGet feeds, such as Azure DevOps, involves installing the NuGet plugin as a .NET global tool. However, the current proposal suggests installing the plugin as a tool-path .NET tool. As mentioned in the technical explanation section, customers can opt to install NuGet plugins as a global tool instead of a tool-path tool. To do this, they need to set the `NUGET_DOTNET_TOOLS_PLUGIN_PATHS` environment variable. This variable should point to the location of the .NET Tool executable, which the NuGet Client tooling can invoke when needed.
kartheekp-ms marked this conversation as resolved.
Show resolved Hide resolved

## Rationale and alternatives

### NuGet commands to install credential providers
kartheekp-ms marked this conversation as resolved.
Show resolved Hide resolved

[Andy Zivkovic](https://github.com/zivkan) kindly proposed an alternative design in [[Feature]: Make NuGet credential providers installable via the dotnet cli](https://github.com/NuGet/Home/issues/11325). The recommendation was developing a command like `dotnet nuget credential-provider install Microsoft.Azure.Artifacts.CredentialProvider`. Here are the advantages and disadvantages of this approach:

**Advantages:**
- As Andy suggested in the linked issue, we can utilize nuspec package types to enhance the discovery of credential providers. By introducing a new package type called `CredentialProvider`, the `dotnet nuget credential-provider list` command can filter by `packageType:CredentialProvider` on nuget.org. This will display a list of credential providers available on the feed, similar to the `dotnet list package` command. Although there are currently limited options for credential providers, customers would benefit from the ability to search by `packageType:CredentialProvider` on nuget.org.
kartheekp-ms marked this conversation as resolved.
Show resolved Hide resolved

**Disadvantages:**
zivkan marked this conversation as resolved.
Show resolved Hide resolved
- The NuGet Client team would be required to maintain all the .NET Commands for installing, updating, and uninstalling the plugins. However, these tasks are already handled by the existing commands in the .NET SDK.
- This approach would still require the extraction of plugins into either a `netfx` or a `netcore` folder. As a result, package authors would need to maintain plugins for both of these target frameworks. However, NuGet plugins are executables, and the .NET SDK provides a convenient way for authors to publish an executable that can run on all platforms via .NET Tools. This eliminates the need for a framework-specific approach.
zivkan marked this conversation as resolved.
Show resolved Hide resolved

### Specify the the authentication plugin in NuGet.Config file

To simplify the authentication process with private NuGet feeds, customers can specify the authentication plugins installed as .NET Tools in the `packagesourceCredentials` section of the NuGet.Config file.

Here is an example of how to configure the NuGet.Config file:

```xml
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="ExamplePrivateRepo" value="https://example.com/nuget" />
<!-- Add other package sources as needed -->
</packageSources>

<packageSourceCredentials>
<ExamplePrivateRepo>
<add key="ToolName" value="Microsoft.CredentialProvider" />
<!-- The value here is the .NET tool name installed globally or in a specific tool-path -->
</ExamplePrivateRepo>
<!-- Add credentials for other package sources as needed -->
</packageSourceCredentials>
</configuration>
```

**Advantages:**
- This approach explicitly configures the intent to use the .NET Tool as a plugin for authentication in the NuGet.Config file itself.
- Customers can install the plugins as global .NET Tools, eliminating the need to specify a custom location based on the platform.

**Disadvantages:**
- Configuring the setting per feed can be cumbersome if multiple private feeds can use the same .NET tool for authentication.

An alternative approach to address this disadvantage is to declare the plugins explicitly outside of the package source sections. This approach allows customers to specify the dependency only once, without configuring it per private feed as discussed above.

```xml
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<DotNetToolPlugins>
<Authentication>
<plugin id="CredentialProvider.Microsoft" />
<plugin id="CredentialProvider.GitHub" />
<!-- Allows specifying multiple authentication plugins with additional details -->
</Authentication>
<!-- This format provides flexibility for future plugin types and detailed configurations -->
</DotNetToolPlugins>
</configuration>
```

However, the NuGet Client plugins functionality is a global setting. This means that the plugins are discovered based on the platform and target framework, as discussed earlier, and there is currently no way to configure the dependent plugins via NuGet settings per repository. It could be a future possibility to provide users with an option to manage their plugins per repository instead of loading all the available plugins. However, given the limited number of plugin implementations currently available, this is not a problem that needs to be solved at this point in time, in my understanding.

## Prior Art
kartheekp-ms marked this conversation as resolved.
Show resolved Hide resolved
zivkan marked this conversation as resolved.
Show resolved Hide resolved

The AWS team has developed a [.NET tool](https://docs.aws.amazon.com/codeartifact/latest/ug/nuget-cli.html#nuget-configure-cli) to simplify authentication with their private NuGet feeds. In their current implementation, they've added a subcommand, `codeartifact-creds install`. This command copies the credential provider to the NuGet plugins folder. They also provide an `uninstall` subcommand to remove the files from the NuGet plugin folders. However, due to limitations in the NuGet Client tooling, they've had to maintain plugins for both .NET Framework and .NET Core tooling. Additionally, they've had to provide commands to install and uninstall the NuGet plugins.

## Unresolved Questions

- None as of now.

## Future Possibilities

### Managing NuGet plugins per repository using .NET Local Tools.

- A potential future possibility is mentioned under the `Specify the authentication plugin in NuGet.Config file` section. In addition to that, if we ever plan to provide users with an option to manage their plugins per repository instead of loading all the available plugins, we can consider using [.NET Local tools](https://learn.microsoft.com/dotnet/core/tools/local-tools-how-to-use).
- The advantage of local tools is that the .NET CLI uses manifest files to keep track of tools that are installed locally to a directory.When the manifest file is saved in the root directory of a source code repository, a contributor can clone the repository and invoke a single .NET CLI command such as [`dotnet tool restore`](https://learn.microsoft.com/dotnet/core/tools/dotnet-tool-restore) to install all of the tools listed in the manifest file.
- Having NuGet plugins under the repository folder eliminates the need for NuGet to load plugins from the user profile.
kartheekp-ms marked this conversation as resolved.
Show resolved Hide resolved
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.