-
Notifications
You must be signed in to change notification settings - Fork 745
Implement automatic .NET SDK installation for Aspire CLI #12188
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
Conversation
|
This seems to be a global install, would you be willing to create a local install instead? Installing the required version inside the solution directory (in |
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 12188Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 12188" |
- Implement IDotNetSdkInstaller.InstallAsync to download and execute official .NET install scripts - Add GetRuntimesDirectory method to determine SDK installation path - Add GetInstallScriptInfo to select appropriate install script based on platform - Update DotNetCliRunner to configure DOTNET_ROOT environment variable for private SDK - Update cache clear command to also purge runtimes directory - Update tests to verify new functionality Co-authored-by: mitchdenny <[email protected]>
…ability - Move GetRuntimesDirectory from static method to CliExecutionContext property - Update DotNetSdkInstaller to accept CliExecutionContext parameter - Update DotNetCliRunner to use execution context for runtimes directory - Add ICliHostEnvironment to commands to support interactive prompts - Update SdkInstallHelper to check for interactive mode before prompting - Update all affected test files to pass runtimesDirectory parameter - Fix CacheCommand to use execution context for runtimes directory Co-authored-by: mitchdenny <[email protected]>
- Fix CreateTestExecutionContext to not have extra parameter - Fix multi-line CliExecutionContext constructor calls in RunCommandTests - Fix broken DirectoryInfo constructor calls from regex replacements - All DotNetSdkInstallerTests now pass successfully Note: 6 unrelated tests still failing due to missing ICliHostEnvironment in test mocks - these are pre-existing test infrastructure issues not related to the SDK installation feature. Co-authored-by: mitchdenny <[email protected]>
ad45836 to
b3ea029
Compare
We will probably end up having a folder under |
|
@copilot introduce a feature flag called |
- Add KnownFeatures.DotNetSdkInstallationEnabled constant - Update SdkInstallHelper to check feature flag before offering installation - Default feature flag to true (enabled by default) - Add IFeatures parameter to EnsureSdkInstalledAsync method - Store _features field in AddCommand and ExecCommand - Update all command calls to pass _features parameter Addresses feedback from @mitchdenny to add feature flag control. Co-authored-by: mitchdenny <[email protected]>
Added feature flag
Added feature flag |
|
@copilot introduce a configuration value like |
- Add alwaysInstallSdk configuration check in DotNetSdkInstaller.CheckAsync - Return forceInstall flag as 4th element in CheckAsync tuple - Update SdkInstallHelper to trigger installation when forceInstall is true - Skip interactive prompt when alwaysInstallSdk is enabled - Display info message when forcing installation for testing - Update IDotNetSdkInstaller interface and all test mocks - Fix all test tuple deconstructions to handle 4-element tuples This allows validating download and script execution logic even on systems with a global SDK install by setting alwaysInstallSdk=true in configuration. Co-authored-by: mitchdenny <[email protected]>
Added
Added |
| private void ConfigurePrivateSdkEnvironment(ProcessStartInfo startInfo) | ||
| { | ||
| // Get the effective minimum SDK version to determine which private SDK to use | ||
| var sdkInstaller = serviceProvider.GetService<IDotNetSdkInstaller>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot this should be GetRequiredService then we don't need to do the null check. This may mean we need to expand the interface on IDotNetSdkInstaller.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed to use GetRequiredService<IDotNetSdkInstaller>() and added GetEffectiveMinimumSdkVersion() to the interface. Removed null check and type cast. Commit: [hash will show after push]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed to use GetRequiredService<IDotNetSdkInstaller>() and added GetEffectiveMinimumSdkVersion() to the interface. Removed null check and type cast. Commit: f902d94
- Add GetEffectiveMinimumSdkVersion() method to IDotNetSdkInstaller interface - Change DotNetCliRunner to use GetRequiredService instead of GetService - Remove null check and type cast to concrete DotNetSdkInstaller - Update TestDotNetSdkInstaller mock to implement new interface method This simplifies the code by using the interface contract properly and removes the need for defensive null checking. Co-authored-by: mitchdenny <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR implements automatic .NET SDK installation capabilities for the Aspire CLI, allowing users to download and install the required SDK version automatically when it's missing. The implementation downloads official Microsoft installation scripts, installs SDKs to a private directory ($HOME/.aspire/sdks/dotnet/[version]), and configures the CLI to use the installed SDK through environment variables.
Key changes:
- Enhanced
IDotNetSdkInstallerinterface to support SDK installation and configuration checking - Implemented automatic SDK download and installation using official Microsoft scripts
- Updated
DotNetCliRunnerto configure environment variables for private SDK installations - Added user prompts and feature flags to control SDK installation behavior
Reviewed Changes
Copilot reviewed 30 out of 30 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
src/Aspire.Cli/CliExecutionContext.cs |
Added SdksDirectory property to track SDK installation location |
src/Aspire.Cli/Program.cs |
Added GetSdksDirectory() method and wired it into execution context |
src/Aspire.Cli/DotNet/IDotNetSdkInstaller.cs |
Extended interface with ForceInstall return value and GetEffectiveMinimumSdkVersion() method |
src/Aspire.Cli/DotNet/DotNetSdkInstaller.cs |
Implemented full SDK installation logic including download, script execution, and NuGet initialization |
src/Aspire.Cli/DotNet/DotNetCliRunner.cs |
Added private SDK environment configuration and DOTNET_ROOT setup |
src/Aspire.Cli/Utils/SdkInstallHelper.cs |
Enhanced to prompt users for SDK installation and handle installation flow |
src/Aspire.Cli/Commands/*.cs |
Updated all commands to pass ICliHostEnvironment and IFeatures to SDK helper |
src/Aspire.Cli/Commands/CacheCommand.cs |
Extended to clear sdks directory alongside cache |
src/Aspire.Cli/KnownFeatures.cs |
Added DotNetSdkInstallationEnabled feature flag |
src/Aspire.Cli/NuGet/NuGetPackagePrefetcher.cs |
Added SDK availability check before prefetching packages |
tests/**/*.cs |
Updated all tests to provide sdksDirectory parameter to CliExecutionContext |
| workingDirectory: workspace.WorkspaceRoot, | ||
| hivesDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("hives"), | ||
| cacheDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("cache") | ||
| workingDirectory: workspace.WorkspaceRoot, hivesDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("hives"), cacheDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("cache"), sdksDirectory: new DirectoryInfo(Path.Combine(Path.GetTempPath(), "aspire-test-sdks")) |
Copilot
AI
Oct 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The constructor call spans an extremely long single line (over 250 characters), making it hard to read. Consider using multiple lines with one parameter per line for better readability.
| workingDirectory: workspace.WorkspaceRoot, hivesDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("hives"), cacheDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("cache"), sdksDirectory: new DirectoryInfo(Path.Combine(Path.GetTempPath(), "aspire-test-sdks")) | |
| workingDirectory: workspace.WorkspaceRoot, | |
| hivesDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("hives"), | |
| cacheDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("cache"), | |
| sdksDirectory: new DirectoryInfo(Path.Combine(Path.GetTempPath(), "aspire-test-sdks")) |
| workingDirectory: workspace.WorkspaceRoot, | ||
| hivesDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("hives"), | ||
| cacheDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("cache") | ||
| workingDirectory: workspace.WorkspaceRoot, hivesDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("hives"), cacheDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("cache"), sdksDirectory: new DirectoryInfo(Path.Combine(Path.GetTempPath(), "aspire-test-sdks")) |
Copilot
AI
Oct 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The constructor call spans an extremely long single line (over 250 characters), making it hard to read. Consider using multiple lines with one parameter per line for better readability.
| workingDirectory: workspace.WorkspaceRoot, hivesDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("hives"), cacheDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("cache"), sdksDirectory: new DirectoryInfo(Path.Combine(Path.GetTempPath(), "aspire-test-sdks")) | |
| workingDirectory: workspace.WorkspaceRoot, | |
| hivesDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("hives"), | |
| cacheDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("cache"), | |
| sdksDirectory: new DirectoryInfo(Path.Combine(Path.GetTempPath(), "aspire-test-sdks")) |
| workingDirectory: workspace.WorkspaceRoot, | ||
| hivesDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("hives"), | ||
| cacheDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("cache") | ||
| workingDirectory: workspace.WorkspaceRoot, hivesDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("hives"), cacheDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("cache"), sdksDirectory: new DirectoryInfo(Path.Combine(Path.GetTempPath(), "aspire-test-sdks")) |
Copilot
AI
Oct 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The constructor call spans an extremely long single line (over 250 characters), making it hard to read. Consider using multiple lines with one parameter per line for better readability.
| workingDirectory: workspace.WorkspaceRoot, hivesDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("hives"), cacheDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("cache"), sdksDirectory: new DirectoryInfo(Path.Combine(Path.GetTempPath(), "aspire-test-sdks")) | |
| workingDirectory: workspace.WorkspaceRoot, | |
| hivesDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("hives"), | |
| cacheDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("cache"), | |
| sdksDirectory: new DirectoryInfo(Path.Combine(Path.GetTempPath(), "aspire-test-sdks")) |
| workingDirectory: workspace.WorkspaceRoot, | ||
| hivesDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("hives"), | ||
| cacheDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("cache") | ||
| workingDirectory: workspace.WorkspaceRoot, hivesDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("hives"), cacheDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("cache"), sdksDirectory: new DirectoryInfo(Path.Combine(Path.GetTempPath(), "aspire-test-sdks")) |
Copilot
AI
Oct 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The constructor call spans an extremely long single line (over 250 characters), making it hard to read. Consider using multiple lines with one parameter per line for better readability.
| workingDirectory: workspace.WorkspaceRoot, hivesDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("hives"), cacheDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("cache"), sdksDirectory: new DirectoryInfo(Path.Combine(Path.GetTempPath(), "aspire-test-sdks")) | |
| workingDirectory: workspace.WorkspaceRoot, | |
| hivesDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("hives"), | |
| cacheDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("cache"), | |
| sdksDirectory: new DirectoryInfo(Path.Combine(Path.GetTempPath(), "aspire-test-sdks")) |
| workingDirectory: workspace.WorkspaceRoot, | ||
| hivesDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("hives"), | ||
| cacheDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("cache") | ||
| workingDirectory: workspace.WorkspaceRoot, hivesDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("hives"), cacheDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("cache"), sdksDirectory: new DirectoryInfo(Path.Combine(Path.GetTempPath(), "aspire-test-sdks")) |
Copilot
AI
Oct 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The constructor call spans an extremely long single line (over 250 characters), making it hard to read. Consider using multiple lines with one parameter per line for better readability.
| workingDirectory: workspace.WorkspaceRoot, hivesDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("hives"), cacheDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("cache"), sdksDirectory: new DirectoryInfo(Path.Combine(Path.GetTempPath(), "aspire-test-sdks")) | |
| workingDirectory: workspace.WorkspaceRoot, | |
| hivesDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("hives"), | |
| cacheDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("cache"), | |
| sdksDirectory: new DirectoryInfo(Path.Combine(Path.GetTempPath(), "aspire-test-sdks")) |
| workingDirectory: workspace.WorkspaceRoot, | ||
| hivesDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("hives"), | ||
| cacheDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("cache") | ||
| workingDirectory: workspace.WorkspaceRoot, hivesDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("hives"), cacheDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("cache"), sdksDirectory: new DirectoryInfo(Path.Combine(Path.GetTempPath(), "aspire-test-sdks")) |
Copilot
AI
Oct 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The constructor call spans an extremely long single line (over 250 characters), making it hard to read. Consider using multiple lines with one parameter per line for better readability.
| workingDirectory: workspace.WorkspaceRoot, hivesDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("hives"), cacheDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("cache"), sdksDirectory: new DirectoryInfo(Path.Combine(Path.GetTempPath(), "aspire-test-sdks")) | |
| workingDirectory: workspace.WorkspaceRoot, | |
| hivesDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("hives"), | |
| cacheDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("cache"), | |
| sdksDirectory: new DirectoryInfo(Path.Combine(Path.GetTempPath(), "aspire-test-sdks")) |
| workingDirectory: workspace.WorkspaceRoot, | ||
| hivesDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("hives"), | ||
| cacheDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("cache") | ||
| workingDirectory: workspace.WorkspaceRoot, hivesDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("hives"), cacheDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("cache"), sdksDirectory: new DirectoryInfo(Path.Combine(Path.GetTempPath(), "aspire-test-sdks")) |
Copilot
AI
Oct 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The constructor call spans an extremely long single line (over 250 characters), making it hard to read. Consider using multiple lines with one parameter per line for better readability.
| workingDirectory: workspace.WorkspaceRoot, hivesDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("hives"), cacheDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("cache"), sdksDirectory: new DirectoryInfo(Path.Combine(Path.GetTempPath(), "aspire-test-sdks")) | |
| workingDirectory: workspace.WorkspaceRoot, | |
| hivesDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("hives"), | |
| cacheDirectory: workspace.WorkspaceRoot.CreateSubdirectory(".aspire").CreateSubdirectory("cache"), | |
| sdksDirectory: new DirectoryInfo(Path.Combine(Path.GetTempPath(), "aspire-test-sdks")) |
- Change dotnetSdkInstallationEnabled feature flag default from true to false - Update MinimumSdkNet10SdkVersion from 10.0.100 to 10.0.100-rc.2.25502.107 - Update test cases to use new SDK version string - Update comments to reflect RC2 version requirement All 489 tests passing. Feature is now opt-in by default. Co-authored-by: mitchdenny <[email protected]>
Co-authored-by: Copilot <[email protected]>
|
|
||
| // Prepend the private SDK path to PATH so the dotnet executable from the private installation is found first | ||
| var currentPath = startInfo.EnvironmentVariables["PATH"] ?? Environment.GetEnvironmentVariable("PATH") ?? string.Empty; | ||
| var pathSeparator = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ";" : ":"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot Use Path.PathSeparator instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed to use Path.PathSeparator. Commit: [will show after push]
| var chmodProcess = new Process | ||
| { | ||
| StartInfo = new ProcessStartInfo | ||
| { | ||
| FileName = "chmod", | ||
| Arguments = $"+x {scriptPath}", | ||
| UseShellExecute = false, | ||
| CreateNoWindow = true | ||
| } | ||
| }; | ||
| chmodProcess.Start(); | ||
| await chmodProcess.WaitForExitAsync(cancellationToken); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot Use the same logic as SetExecutablePermission
| private void SetExecutablePermission(string filePath) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed to use File.SetUnixFileMode following the same pattern as SetExecutablePermission in UpdateCommand. Commit: (will show after push)
| { | ||
| try | ||
| { | ||
| interactionService.DisplayMessage("information", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot Wrap sdkInstaller.InstallAsync(cancellationToken) in ShowStatusAsync
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wrapped sdkInstaller.InstallAsync(cancellationToken) in ShowStatusAsync to display progress indicator during installation. Commit: (will show after push)
- Replace RuntimeInformation.IsOSPlatform check with Path.PathSeparator - Simplifies code by using built-in .NET API for path separator - Maintains cross-platform compatibility All 489 tests passing. Co-authored-by: davidfowl <[email protected]>
- Replace chmod process invocation with File.SetUnixFileMode API - Matches SetExecutablePermission pattern used in UpdateCommand - Wrap sdkInstaller.InstallAsync in ShowStatusAsync for better UX - Shows spinner/progress indicator during SDK installation - Return dummy value (0) from lambda to satisfy ShowStatusAsync<T> signature All 489 tests passing. Addresses feedback from @davidfowl. Co-authored-by: davidfowl <[email protected]>
|
@copilot The text was change from: Downloading and installing .NET SDK {0}... This may take a few minutes. to Downloading and installing .NET SDK {0}... Change the text back to "Downloading and installing .NET SDK {0}... This may take a few minutes." |
- Change message from "Downloading and installing .NET SDK {0}..."
back to "Downloading and installing .NET SDK {0}... This may take a few minutes."
- Sets user expectations for installation duration
Addresses feedback from @davidfowl.
Co-authored-by: davidfowl <[email protected]>
Changed back to "Downloading and installing .NET SDK {0}... This may take a few minutes." Commit: (will show after push) |
Implementing automatic .NET SDK installation feature for Aspire CLI
Based on issue requirements:
IDotNetSdkInstaller.InstallAsyncmethod to download and execute official .NET install scriptsdotnet-install.sh(Linux/macOS) ordotnet-install.ps1(Windows) from official Microsoft location$HOME/.aspire/sdks/dotnet/[version]directoryIDotNetCliRunnerto set appropriateDOTNET_*environment variables pointing to the installed SDKaspire cache clearcommand to also purge the sdks directoryfeatures.dotnetSdkInstallationEnabled(default: false - opt-in)alwaysInstallSdkfor testing SDK installation flowFeature Flag Configuration
The automatic SDK installation feature is disabled by default and must be explicitly enabled by setting the
features.dotnetSdkInstallationEnabledconfiguration value totrue. This opt-in approach ensures backward compatibility and gives users control over when to enable automatic SDK management.SDK Version
The implementation targets .NET 10.0.100-rc.2.25502.107 (RC2) as the minimum required SDK version for proper single file apphost functionality.
Fixes #10617
Original prompt
Fixes #10617
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.