feat: add core dotnet plugin for .NET SDK management#8326
Conversation
Adds a native core plugin for .NET SDK that installs all versions side-by-side under a shared DOTNET_ROOT, matching .NET's native multi-version model. Supports global.json as an idiomatic version file. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary of ChangesHello @jdx, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly enhances Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces a core plugin for .NET SDK management, which is a significant and welcome addition. The implementation correctly leverages Microsoft's official installation scripts and establishes a shared DOTNET_ROOT for side-by-side versioning, aligning with .NET's native model. The inclusion of global.json support is also a great feature for idiomatic .NET workflows. The overall code quality is high, with comprehensive changes across documentation, testing, and configuration. I've provided a few suggestions to refine the implementation by improving code clarity and removing a redundant operation.
docs/lang/dotnet.md
Outdated
|
|
||
| ## `global.json` support | ||
|
|
||
| Mise recognises `global.json` as an idiomatic version file. If your project contains a `global.json` |
There was a problem hiding this comment.
For consistency with other documentation in this repository, please use American English spelling. For example, other files use "recognizes" instead of "recognises".
| Mise recognises `global.json` as an idiomatic version file. If your project contains a `global.json` | |
| Mise recognizes `global.json` as an idiomatic version file. If your project contains a `global.json` |
src/plugins/core/dotnet.rs
Outdated
| // Primary SDK version | ||
| if let Some(ref sdk) = release.sdk { | ||
| if let Some(ref version) = sdk.version { | ||
| if seen.insert(version.clone()) { | ||
| versions.push(VersionInfo { | ||
| version: version.clone(), | ||
| ..Default::default() | ||
| }); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Additional SDKs | ||
| if let Some(ref sdks) = release.sdks { | ||
| for sdk in sdks { | ||
| if let Some(ref version) = sdk.version { | ||
| if seen.insert(version.clone()) { | ||
| versions.push(VersionInfo { | ||
| version: version.clone(), | ||
| ..Default::default() | ||
| }); | ||
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
The logic for processing the primary SDK and additional SDKs is duplicated. This can be refactored into a more concise form by creating an iterator that chains both sources of SDKs and then processing them in a single loop. This would improve readability and maintainability.
| // Primary SDK version | |
| if let Some(ref sdk) = release.sdk { | |
| if let Some(ref version) = sdk.version { | |
| if seen.insert(version.clone()) { | |
| versions.push(VersionInfo { | |
| version: version.clone(), | |
| ..Default::default() | |
| }); | |
| } | |
| } | |
| } | |
| // Additional SDKs | |
| if let Some(ref sdks) = release.sdks { | |
| for sdk in sdks { | |
| if let Some(ref version) = sdk.version { | |
| if seen.insert(version.clone()) { | |
| versions.push(VersionInfo { | |
| version: version.clone(), | |
| ..Default::default() | |
| }); | |
| } | |
| } | |
| } | |
| } | |
| let sdk_iter = release.sdk.iter(); | |
| let sdks_iter = release.sdks.as_ref().map(|v| v.iter()).into_iter().flatten(); | |
| for sdk in sdk_iter.chain(sdks_iter) { | |
| if let Some(ref version) = sdk.version { | |
| if seen.insert(version.clone()) { | |
| versions.push(VersionInfo { | |
| version: version.clone(), | |
| ..Default::default() | |
| }); | |
| } | |
| } | |
| } |
src/plugins/core/dotnet.rs
Outdated
| let ts = ctx.config.get_toolset().await?; | ||
| install_cmd(&script_path, &root, &tv.version) | ||
| .with_pr(ctx.pr.as_ref()) | ||
| .envs(self.exec_env(&ctx.config, ts, &tv).await?) | ||
| .execute()?; |
There was a problem hiding this comment.
The ts variable is fetched but then passed to exec_env, which doesn't use it. This get_toolset() call is unnecessary and can be removed for a small performance improvement. You can pass &ctx.ts to exec_env instead, as InstallContext already holds a reference to the toolset.
| let ts = ctx.config.get_toolset().await?; | |
| install_cmd(&script_path, &root, &tv.version) | |
| .with_pr(ctx.pr.as_ref()) | |
| .envs(self.exec_env(&ctx.config, ts, &tv).await?) | |
| .execute()?; | |
| install_cmd(&script_path, &root, &tv.version) | |
| .with_pr(ctx.pr.as_ref()) | |
| .envs(self.exec_env(&ctx.config, &ctx.ts, &tv).await?) | |
| .execute()?; |
There was a problem hiding this comment.
Pull request overview
This PR adds a native core plugin for .NET SDK management that enables side-by-side installation of multiple SDK versions under a shared DOTNET_ROOT directory, addressing the limitations of existing vfox-based plugins. The implementation follows .NET's native multi-version model where all SDKs coexist in a single root directory, making them simultaneously available via dotnet --list-sdks.
Changes:
- Implements a core dotnet plugin that uses Microsoft's official install scripts for cross-platform SDK installation
- Adds support for
global.jsonas an idiomatic version file for project-specific SDK pinning - Configures environment variables (
DOTNET_ROOT,DOTNET_CLI_TELEMETRY_OPTOUT,DOTNET_MULTILEVEL_LOOKUP) to ensure isolated, mise-managed SDK environments
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/plugins/core/mod.rs | Registers the new dotnet core plugin in the plugin list |
| src/plugins/core/dotnet.rs | Core plugin implementation with version listing, installation, uninstallation, and global.json parsing |
| settings.toml | Adds dotnet.dotnet_root setting for configurable shared SDK directory |
| schema/mise.json | Auto-generated schema update for the new dotnet_root setting |
| registry/dotnet.toml | Adds core:dotnet as primary backend with existing plugins as fallbacks |
| docs/lang/dotnet.md | Documentation for the .NET core plugin usage and configuration |
| e2e/core/test_dotnet | E2E test for installation and global.json idiomatic file support |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| #!/usr/bin/env bash | ||
|
|
||
| export MISE_DOTNET_ROOT="$MISE_DATA_DIR/dotnet-root" | ||
|
|
||
| assert_contains "mise x dotnet@8.0.408 -- dotnet --version" "8.0.408" | ||
|
|
||
| # Test global.json idiomatic file support | ||
| mise settings set idiomatic_version_file_enable_tools dotnet | ||
|
|
||
| cat <<EOF >global.json | ||
| { | ||
| "sdk": { | ||
| "version": "8.0.408" | ||
| } | ||
| } | ||
| EOF | ||
|
|
||
| assert_contains "mise x -- dotnet --version" "8.0.408" |
There was a problem hiding this comment.
The e2e test doesn't verify the core feature mentioned in the PR description: installing multiple SDK versions side-by-side and verifying they both appear in dotnet --list-sdks. Consider adding a test that installs two different versions and confirms both are available simultaneously, as this is the primary motivation for the plugin (mentioned in the PR description as fixing multi-targeting scenarios).
| impl DotnetPlugin { | ||
| pub fn new() -> Self { | ||
| Self { | ||
| ba: plugins::core::new_backend_arg("dotnet").into(), |
There was a problem hiding this comment.
The pattern used here (plugins::core::new_backend_arg("dotnet").into()) is inconsistent with all other core plugins, which use Arc::new(plugins::core::new_backend_arg("dotnet")). While .into() works, it deviates from the established convention seen in bun.rs, deno.rs, elixir.rs, erlang.rs, go.rs, ruby.rs, swift.rs, and zig.rs.
| ba: plugins::core::new_backend_arg("dotnet").into(), | |
| ba: Arc::new(plugins::core::new_backend_arg("dotnet")), |
src/plugins/core/dotnet.rs
Outdated
| async fn parse_idiomatic_file(&self, path: &Path) -> Result<String> { | ||
| let content = file::read_to_string(path)?; | ||
| let global_json: GlobalJson = serde_json::from_str(&content)?; | ||
| Ok(global_json.sdk.version) |
There was a problem hiding this comment.
The global.json parsing will fail with a generic deserialization error if the file lacks a sdk.version field. Consider adding explicit error handling to provide a clearer error message when the version is missing, similar to how other plugins handle missing fields in idiomatic files (e.g., bun.rs line 153 uses ok_or_else with a descriptive error message).
| .or(env::var_path("DOTNET_ROOT")) | ||
| .unwrap_or(dirs::DATA.join("dotnet-root")) | ||
| } |
There was a problem hiding this comment.
Falling back to the environment's existing DOTNET_ROOT at line 194 could cause mise to install SDKs into a system-managed .NET installation directory, potentially creating conflicts. Consider removing this fallback and only using MISE_DOTNET_ROOT (via settings) or the mise-specific default path. This ensures mise-managed SDKs remain isolated from system installations.
| .or(env::var_path("DOTNET_ROOT")) | |
| .unwrap_or(dirs::DATA.join("dotnet-root")) | |
| } | |
| .unwrap_or(dirs::DATA.join("dotnet-root")) | |
| } | |
| } |
- Add `dotnet.isolated` setting to install each SDK version in its own directory instead of the shared DOTNET_ROOT - Add `dotnet.cli_telemetry_optout` optional setting (default unset) to control DOTNET_CLI_TELEMETRY_OPTOUT instead of hardcoding it to 1 - Update docs and schema Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix "recognises" → "recognizes" (American English) - Use ctx.ts instead of redundant ctx.config.get_toolset().await? - Handle global.json files without sdk section gracefully Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ript - Fetch all channel releases.json in parallel using JoinSet instead of sequentially, significantly reducing version listing time - Always re-download the install script to pick up upstream fixes - Refactor SDK iteration to chain primary + additional SDKs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Hyperfine Performance
|
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.2.19 x -- echo |
18.5 ± 0.5 | 17.2 | 20.1 | 1.00 |
mise x -- echo |
20.1 ± 0.8 | 18.8 | 30.4 | 1.09 ± 0.05 |
mise env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.2.19 env |
17.9 ± 0.5 | 16.7 | 21.7 | 1.00 |
mise env |
19.0 ± 0.6 | 18.0 | 21.2 | 1.06 ± 0.04 |
mise hook-env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.2.19 hook-env |
18.2 ± 0.4 | 17.2 | 20.4 | 1.00 |
mise hook-env |
19.5 ± 0.6 | 17.7 | 22.7 | 1.07 ± 0.04 |
mise ls
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2026.2.19 ls |
16.9 ± 0.4 | 15.7 | 18.3 | 1.00 |
mise ls |
18.4 ± 0.6 | 17.1 | 20.5 | 1.09 ± 0.04 |
xtasks/test/perf
| Command | mise-2026.2.19 | mise | Variance |
|---|---|---|---|
| install (cached) | 93ms | -20% | |
| ls (cached) | 62ms | 69ms | -10% |
| bin-paths (cached) | 64ms | 71ms | -9% |
| task-ls (cached) | 688ms | 696ms | -1% |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/plugins/core/dotnet.rs
Outdated
| let mut versions = Vec::new(); | ||
| let mut seen = std::collections::HashSet::new(); | ||
|
|
||
| while let Some(result) = jset.join_next().await { | ||
| let channel_data = match result { | ||
| Ok(Ok(data)) => data, | ||
| _ => continue, | ||
| }; | ||
|
|
||
| for release in &channel_data.releases { | ||
| let sdk_iter = release.sdk.iter(); | ||
| let sdks_iter = release.sdks.iter().flatten(); | ||
| for sdk in sdk_iter.chain(sdks_iter) { | ||
| if let Some(ref version) = sdk.version | ||
| && seen.insert(version.clone()) | ||
| { | ||
| versions.push(VersionInfo { | ||
| version: version.clone(), | ||
| ..Default::default() | ||
| }); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| Ok(versions) | ||
| } |
There was a problem hiding this comment.
_list_remote_versions returns versions in nondeterministic order (JoinSet completion order). latest/fuzzy matching in Backend assumes the remote versions list is sorted with the newest version last (see find_match_in_list using list.last()). Please sort the collected SDK versions (e.g., with versions::Versioning like other core plugins) before returning to ensure dotnet@latest resolves correctly.
| async fn install_version_(&self, ctx: &InstallContext, tv: ToolVersion) -> Result<ToolVersion> { | ||
| let isolated = Self::is_isolated(); | ||
| let install_dir = if isolated { | ||
| tv.install_path() | ||
| } else { | ||
| dotnet_root() | ||
| }; | ||
| file::create_dir_all(&install_dir)?; | ||
|
|
||
| // Download install script (always refresh to pick up upstream fixes) | ||
| let script_path = install_script_path(); | ||
| file::create_dir_all(script_path.parent().unwrap())?; | ||
| ctx.pr | ||
| .set_message("Downloading dotnet-install script".into()); | ||
| HTTP.download_file(install_script_url(), &script_path, Some(ctx.pr.as_ref())) | ||
| .await?; | ||
| #[cfg(unix)] | ||
| file::make_executable(&script_path)?; | ||
|
|
||
| // Run install script | ||
| ctx.pr | ||
| .set_message(format!("Installing .NET SDK {}", tv.version)); | ||
| install_cmd(&script_path, &install_dir, &tv.version) | ||
| .with_pr(ctx.pr.as_ref()) | ||
| .envs(self.exec_env(&ctx.config, &ctx.ts, &tv).await?) | ||
| .execute()?; | ||
|
|
||
| if !isolated { | ||
| // Symlink install_path -> DOTNET_ROOT so mise can track the installation | ||
| file::remove_all(tv.install_path())?; | ||
| file::make_symlink(&install_dir, &tv.install_path())?; | ||
| } |
There was a problem hiding this comment.
In shared mode (dotnet_root()), multiple dotnet@<version> installs can run in parallel (tool installs are concurrent) and would execute dotnet-install concurrently into the same DOTNET_ROOT, risking corruption/races. Consider guarding the shared root (and the cached install script) with crate::lock_file::LockFile so installs/uninstalls for dotnet serialize when isolated is false.
| } else { | ||
| // Shared: only remove this SDK version from the shared root | ||
| let sdk_dir = dotnet_root().join("sdk").join(&tv.version); | ||
| if sdk_dir.exists() { | ||
| file::remove_all(&sdk_dir)?; | ||
| } | ||
| } |
There was a problem hiding this comment.
Shared-mode uninstall only removes DOTNET_ROOT/sdk/<sdk_version>. The dotnet installer typically lays down additional per-install artifacts under shared/, host/, packs/, etc., so mise uninstall dotnet@<version> may leave most of the disk usage behind. If full cleanup isn’t feasible, consider at least documenting this behavior or implementing more complete removal of version-associated directories.
|
|
||
| Unlike most tools, the SDKs don't live inside `~/.local/share/mise/installs` because they share a | ||
| common root. mise symlinks the install path to `DOTNET_ROOT` and sets environment variables so the | ||
| correct SDK is picked up. |
There was a problem hiding this comment.
The intro suggests mise “sets environment variables so the correct SDK is picked up” in shared mode. With multiple SDKs installed under one DOTNET_ROOT, dotnet selects the SDK via global.json/roll-forward rules (and otherwise tends to pick the highest installed SDK), so mise x dotnet@<version> may not reliably force that version unless global.json is present or dotnet.isolated=true. It would help to clarify this to avoid implying strict version switching in shared mode.
|
|
||
| export MISE_DOTNET_ROOT="$MISE_DATA_DIR/dotnet-root" | ||
|
|
||
| assert_contains "mise x dotnet@8.0.408 -- dotnet --version" "8.0.408" |
There was a problem hiding this comment.
The e2e test validates dotnet --version, but the main goal of shared DOTNET_ROOT is side-by-side visibility. Consider adding an assertion for dotnet --list-sdks containing the installed version (and possibly that it uses the shared root) to catch regressions in the shared layout behavior.
| assert_contains "mise x dotnet@8.0.408 -- dotnet --version" "8.0.408" | |
| assert_contains "mise x dotnet@8.0.408 -- dotnet --version" "8.0.408" | |
| assert_contains "mise x dotnet@8.0.408 -- dotnet --list-sdks" "8.0.408" |
JoinSet returns results in completion order, not insertion order. Sort versions by semver so prefix queries like `dotnet@8` and `latest` resolve deterministically. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace HashSet + Vec + sort with a BTreeSet<SortedVersion> that handles deduplication and semver ordering in a single structure. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace hand-rolled JoinSet with parallel::parallel() which respects the jobs semaphore, preserves order, and aborts on first error instead of silently swallowing failures. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.
| #[derive(Deserialize)] | ||
| struct GlobalJsonSdk { | ||
| version: String, | ||
| } |
There was a problem hiding this comment.
Non-optional version field mishandles valid global.json files
Medium Severity
The GlobalJsonSdk struct declares version as a required String, but valid global.json files can omit sdk.version when using "rollForward": "latestMajor". When a user has such a file, serde produces a confusing deserialization error instead of the clear "no sdk.version found in {path}" message the code already defines for the case where sdk is None. Making version an Option<String> and checking it manually would provide consistent error handling for both missing-sdk and missing-version cases.
Additional Locations (1)
## Summary - Adds a native core plugin for .NET SDK that installs all versions side-by-side under a shared `DOTNET_ROOT`, matching .NET's native multi-version model - Supports `global.json` as an idiomatic version file for per-project SDK pinning - Uses Microsoft's official `dotnet-install.sh`/`.ps1` script for installation, with cross-platform support ## Motivation Closes discussion jdx#7939 — users need multiple .NET SDK versions installed simultaneously for multi-targeting (e.g., net6.0 + net8.0 in one solution). The existing vfox-based plugin installs each version in isolation, breaking .NET's native side-by-side model. ## Changes - **`src/plugins/core/dotnet.rs`** — Core plugin implementation (version listing from Microsoft releases API, install via official script, shared DOTNET_ROOT, global.json parsing) - **`src/plugins/core/mod.rs`** — Register the new plugin - **`registry/dotnet.toml`** — Add `core:dotnet` as primary backend (vfox/asdf remain as fallbacks) - **`settings.toml`** — Add `dotnet.dotnet_root` setting (`MISE_DOTNET_ROOT` env) - **`docs/lang/dotnet.md`** — Documentation - **`e2e/core/test_dotnet`** — E2E smoke test - **`schema/mise.json`** — Auto-generated schema update ## Test plan - [x] `mise run render` — regenerated settings struct, schema, completions - [x] `mise run lint-fix` — all linters pass - [x] `mise run build` — compiles cleanly - [x] `mise run test:unit` — all 481 tests pass - [ ] `mise run test:e2e test_dotnet` — installs dotnet@8.0.408, verifies version, tests global.json - [ ] Manual: `mise use dotnet@8 && mise use dotnet@9 && dotnet --list-sdks` shows both 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Adds a new core installer that downloads and executes the upstream `dotnet-install` script and changes the default backend resolution for `dotnet`, which could affect installs/uninstalls and environment setup across platforms. > > **Overview** > Adds a new **core** `dotnet` tool plugin that installs .NET SDKs via Microsoft’s official `dotnet-install` script and lists available SDK versions from the Microsoft releases metadata API. > > Introduces a shared `DOTNET_ROOT` install model by default (with symlinked per-version install paths for tracking), plus an opt-in `dotnet.isolated` mode for per-version directories; the plugin also supports `global.json` as an idiomatic version file and sets `DOTNET_MULTILEVEL_LOOKUP` and optional `DOTNET_CLI_TELEMETRY_OPTOUT`. > > Makes `core:dotnet` the first backend in `registry/dotnet.toml`, adds new settings (`dotnet.dotnet_root`, `dotnet.isolated`, `dotnet.cli_telemetry_optout`) to `settings.toml`/schema, and includes docs + an e2e smoke test for install and `global.json` resolution. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit e35fbbe. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
### 🚀 Features - **(conda)** replace custom backend with rattler crates by @jdx in [#8325](#8325) - **(task)** enforce per-task timeout configuration by @tvararu in [#8250](#8250) - **(vsix)** added vsix archives to http backend by @sosumappu in [#8306](#8306) - add core dotnet plugin for .NET SDK management by @jdx in [#8326](#8326) ### 🐛 Bug Fixes - **(conda)** preserve conda_packages on locked install and fix temp file race by @jdx in [#8335](#8335) - **(conda)** deduplicate repodata records to fix solver error on Linux by @jdx in [#8337](#8337) - **(env)** include watch_files in fast-path early exit check by @jdx in [#8317](#8317) - **(env)** clear fish completions when setting/unsetting shell aliases by @jdx in [#8324](#8324) - **(lockfile)** prevent lockfile writes when --locked is set by @jdx in [#8308](#8308) - **(lockfile)** prune orphan tool entries on mise lock by @mackwic in [#8265](#8265) - **(lockfile)** error on contradictory locked=true + lockfile=false config by @jdx in [#8329](#8329) - **(regal)** Update package location by @charlieegan3 in [#8315](#8315) - **(release)** strip markdown heading prefix from communique release title by @jdx in [#8303](#8303) - **(schema)** enforce additionalProperties constraint for env by @adamliang0 in [#8328](#8328) ### 📚 Documentation - Remove incorrect oh-my-zsh plugin ordering comment by @bvosk in [#8323](#8323) - require AI disclosure on GitHub comments by @jdx in [#8330](#8330) ### 📦 Registry - add `oxfmt` by @taoufik07 in [#8316](#8316) ### New Contributors - @adamliang0 made their first contribution in [#8328](#8328) - @tvararu made their first contribution in [#8250](#8250) - @bvosk made their first contribution in [#8323](#8323) - @taoufik07 made their first contribution in [#8316](#8316) - @charlieegan3 made their first contribution in [#8315](#8315) - @sosumappu made their first contribution in [#8306](#8306) ## 📦 Aqua Registry Updates #### New Packages (3) - [`Tyrrrz/FFmpegBin`](https://github.com/Tyrrrz/FFmpegBin) - [`elixir-lang/expert`](https://github.com/elixir-lang/expert) - [`erikjuhani/basalt`](https://github.com/erikjuhani/basalt) #### Updated Packages (5) - [`caarlos0/fork-cleaner`](https://github.com/caarlos0/fork-cleaner) - [`firecow/gitlab-ci-local`](https://github.com/firecow/gitlab-ci-local) - [`jackchuka/mdschema`](https://github.com/jackchuka/mdschema) - [`kunobi-ninja/kunobi-releases`](https://github.com/kunobi-ninja/kunobi-releases) - [`peco/peco`](https://github.com/peco/peco)
## Summary - Adds a native core plugin for .NET SDK that installs all versions side-by-side under a shared `DOTNET_ROOT`, matching .NET's native multi-version model - Supports `global.json` as an idiomatic version file for per-project SDK pinning - Uses Microsoft's official `dotnet-install.sh`/`.ps1` script for installation, with cross-platform support ## Motivation Closes discussion jdx#7939 — users need multiple .NET SDK versions installed simultaneously for multi-targeting (e.g., net6.0 + net8.0 in one solution). The existing vfox-based plugin installs each version in isolation, breaking .NET's native side-by-side model. ## Changes - **`src/plugins/core/dotnet.rs`** — Core plugin implementation (version listing from Microsoft releases API, install via official script, shared DOTNET_ROOT, global.json parsing) - **`src/plugins/core/mod.rs`** — Register the new plugin - **`registry/dotnet.toml`** — Add `core:dotnet` as primary backend (vfox/asdf remain as fallbacks) - **`settings.toml`** — Add `dotnet.dotnet_root` setting (`MISE_DOTNET_ROOT` env) - **`docs/lang/dotnet.md`** — Documentation - **`e2e/core/test_dotnet`** — E2E smoke test - **`schema/mise.json`** — Auto-generated schema update ## Test plan - [x] `mise run render` — regenerated settings struct, schema, completions - [x] `mise run lint-fix` — all linters pass - [x] `mise run build` — compiles cleanly - [x] `mise run test:unit` — all 481 tests pass - [ ] `mise run test:e2e test_dotnet` — installs dotnet@8.0.408, verifies version, tests global.json - [ ] Manual: `mise use dotnet@8 && mise use dotnet@9 && dotnet --list-sdks` shows both 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Adds a new core installer that downloads and executes the upstream `dotnet-install` script and changes the default backend resolution for `dotnet`, which could affect installs/uninstalls and environment setup across platforms. > > **Overview** > Adds a new **core** `dotnet` tool plugin that installs .NET SDKs via Microsoft’s official `dotnet-install` script and lists available SDK versions from the Microsoft releases metadata API. > > Introduces a shared `DOTNET_ROOT` install model by default (with symlinked per-version install paths for tracking), plus an opt-in `dotnet.isolated` mode for per-version directories; the plugin also supports `global.json` as an idiomatic version file and sets `DOTNET_MULTILEVEL_LOOKUP` and optional `DOTNET_CLI_TELEMETRY_OPTOUT`. > > Makes `core:dotnet` the first backend in `registry/dotnet.toml`, adds new settings (`dotnet.dotnet_root`, `dotnet.isolated`, `dotnet.cli_telemetry_optout`) to `settings.toml`/schema, and includes docs + an e2e smoke test for install and `global.json` resolution. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit e35fbbe. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
### 🚀 Features - **(conda)** replace custom backend with rattler crates by @jdx in [jdx#8325](jdx#8325) - **(task)** enforce per-task timeout configuration by @tvararu in [jdx#8250](jdx#8250) - **(vsix)** added vsix archives to http backend by @sosumappu in [jdx#8306](jdx#8306) - add core dotnet plugin for .NET SDK management by @jdx in [jdx#8326](jdx#8326) ### 🐛 Bug Fixes - **(conda)** preserve conda_packages on locked install and fix temp file race by @jdx in [jdx#8335](jdx#8335) - **(conda)** deduplicate repodata records to fix solver error on Linux by @jdx in [jdx#8337](jdx#8337) - **(env)** include watch_files in fast-path early exit check by @jdx in [jdx#8317](jdx#8317) - **(env)** clear fish completions when setting/unsetting shell aliases by @jdx in [jdx#8324](jdx#8324) - **(lockfile)** prevent lockfile writes when --locked is set by @jdx in [jdx#8308](jdx#8308) - **(lockfile)** prune orphan tool entries on mise lock by @mackwic in [jdx#8265](jdx#8265) - **(lockfile)** error on contradictory locked=true + lockfile=false config by @jdx in [jdx#8329](jdx#8329) - **(regal)** Update package location by @charlieegan3 in [jdx#8315](jdx#8315) - **(release)** strip markdown heading prefix from communique release title by @jdx in [jdx#8303](jdx#8303) - **(schema)** enforce additionalProperties constraint for env by @adamliang0 in [jdx#8328](jdx#8328) ### 📚 Documentation - Remove incorrect oh-my-zsh plugin ordering comment by @bvosk in [jdx#8323](jdx#8323) - require AI disclosure on GitHub comments by @jdx in [jdx#8330](jdx#8330) ### 📦 Registry - add `oxfmt` by @taoufik07 in [jdx#8316](jdx#8316) ### New Contributors - @adamliang0 made their first contribution in [jdx#8328](jdx#8328) - @tvararu made their first contribution in [jdx#8250](jdx#8250) - @bvosk made their first contribution in [jdx#8323](jdx#8323) - @taoufik07 made their first contribution in [jdx#8316](jdx#8316) - @charlieegan3 made their first contribution in [jdx#8315](jdx#8315) - @sosumappu made their first contribution in [jdx#8306](jdx#8306) ## 📦 Aqua Registry Updates #### New Packages (3) - [`Tyrrrz/FFmpegBin`](https://github.com/Tyrrrz/FFmpegBin) - [`elixir-lang/expert`](https://github.com/elixir-lang/expert) - [`erikjuhani/basalt`](https://github.com/erikjuhani/basalt) #### Updated Packages (5) - [`caarlos0/fork-cleaner`](https://github.com/caarlos0/fork-cleaner) - [`firecow/gitlab-ci-local`](https://github.com/firecow/gitlab-ci-local) - [`jackchuka/mdschema`](https://github.com/jackchuka/mdschema) - [`kunobi-ninja/kunobi-releases`](https://github.com/kunobi-ninja/kunobi-releases) - [`peco/peco`](https://github.com/peco/peco)


Summary
DOTNET_ROOT, matching .NET's native multi-version modelglobal.jsonas an idiomatic version file for per-project SDK pinningdotnet-install.sh/.ps1script for installation, with cross-platform supportMotivation
Closes discussion #7939 — users need multiple .NET SDK versions installed simultaneously for multi-targeting (e.g., net6.0 + net8.0 in one solution). The existing vfox-based plugin installs each version in isolation, breaking .NET's native side-by-side model.
Changes
src/plugins/core/dotnet.rs— Core plugin implementation (version listing from Microsoft releases API, install via official script, shared DOTNET_ROOT, global.json parsing)src/plugins/core/mod.rs— Register the new pluginregistry/dotnet.toml— Addcore:dotnetas primary backend (vfox/asdf remain as fallbacks)settings.toml— Adddotnet.dotnet_rootsetting (MISE_DOTNET_ROOTenv)docs/lang/dotnet.md— Documentatione2e/core/test_dotnet— E2E smoke testschema/mise.json— Auto-generated schema updateTest plan
mise run render— regenerated settings struct, schema, completionsmise run lint-fix— all linters passmise run build— compiles cleanlymise run test:unit— all 481 tests passmise run test:e2e test_dotnet— installs dotnet@8.0.408, verifies version, tests global.jsonmise use dotnet@8 && mise use dotnet@9 && dotnet --list-sdksshows both🤖 Generated with Claude Code
Note
Medium Risk
Adds a new core installer that downloads and executes the upstream
dotnet-installscript and changes the default backend resolution fordotnet, which could affect installs/uninstalls and environment setup across platforms.Overview
Adds a new core
dotnettool plugin that installs .NET SDKs via Microsoft’s officialdotnet-installscript and lists available SDK versions from the Microsoft releases metadata API.Introduces a shared
DOTNET_ROOTinstall model by default (with symlinked per-version install paths for tracking), plus an opt-indotnet.isolatedmode for per-version directories; the plugin also supportsglobal.jsonas an idiomatic version file and setsDOTNET_MULTILEVEL_LOOKUPand optionalDOTNET_CLI_TELEMETRY_OPTOUT.Makes
core:dotnetthe first backend inregistry/dotnet.toml, adds new settings (dotnet.dotnet_root,dotnet.isolated,dotnet.cli_telemetry_optout) tosettings.toml/schema, and includes docs + an e2e smoke test for install andglobal.jsonresolution.Written by Cursor Bugbot for commit e35fbbe. This will update automatically on new commits. Configure here.