Skip to content

feat: add core dotnet plugin for .NET SDK management#8326

Merged
jdx merged 9 commits intomainfrom
feat/core-dotnet-plugin
Feb 24, 2026
Merged

feat: add core dotnet plugin for .NET SDK management#8326
jdx merged 9 commits intomainfrom
feat/core-dotnet-plugin

Conversation

@jdx
Copy link
Owner

@jdx jdx commented Feb 24, 2026

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 #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

  • mise run render — regenerated settings struct, schema, completions
  • mise run lint-fix — all linters pass
  • mise run build — compiles cleanly
  • 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


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.

Written by Cursor Bugbot for commit e35fbbe. This will update automatically on new commits. Configure here.

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>
Copilot AI review requested due to automatic review settings February 24, 2026 05:47
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @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 mise's ability to manage .NET SDKs by integrating a new core plugin. It addresses the long-standing issue of multi-targeting by allowing multiple .NET SDK versions to coexist seamlessly, aligning with .NET's native side-by-side model. This change provides a more robust and idiomatic way for users to handle .NET development environments within mise.

Highlights

  • Core .NET Plugin: Introduced a native core plugin for .NET SDK management, enabling side-by-side installation of multiple SDK versions under a shared DOTNET_ROOT.
  • global.json Support: Added support for global.json as an idiomatic version file, allowing per-project .NET SDK pinning.
  • Official Installation Method: Utilized Microsoft's official dotnet-install.sh or dotnet-install.ps1 scripts for cross-platform SDK installation.
Changelog
  • docs/lang/dotnet.md
    • New documentation for the core .NET plugin, detailing usage, global.json support, environment variables, and settings.
  • e2e/core/test_dotnet
    • Added an end-to-end test script to verify .NET SDK installation and global.json functionality.
  • registry/dotnet.toml
    • Updated the dotnet backend configuration to prioritize the new core:dotnet plugin.
  • schema/mise.json
    • Added a new dotnet_root property to the dotnet settings schema.
  • settings.toml
    • Introduced the dotnet.dotnet_root setting, allowing users to configure the shared .NET SDK installation directory.
  • src/plugins/core/dotnet.rs
    • Implemented the DotnetPlugin struct, handling remote version listing from Microsoft's API, installation via official scripts, global.json parsing, and environment variable setup.
  • src/plugins/core/mod.rs
    • Registered the new dotnet core plugin within the CORE_PLUGINS map.
Activity
  • The author executed mise run render to regenerate settings, schema, and completions.
  • The author executed mise run lint-fix to ensure all linters passed.
  • The author executed mise run build to confirm clean compilation.
  • The author executed mise run test:unit and confirmed all 481 tests passed.
  • The author plans to execute mise run test:e2e test_dotnet to verify installation, version, and global.json support.
  • The author plans to perform manual testing to confirm side-by-side installation of multiple SDKs.
  • The pull request was generated with Claude Code.
Using Gemini Code Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.


## `global.json` support

Mise recognises `global.json` as an idiomatic version file. If your project contains a `global.json`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For consistency with other documentation in this repository, please use American English spelling. For example, other files use "recognizes" instead of "recognises".

Suggested change
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`

Comment on lines +74 to +98
// 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()
});
}
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
// 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()
});
}
}
}

Comment on lines +134 to +138
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()?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
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()?;

Copy link
Contributor

Copilot AI left a 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 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.json as 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.

Comment on lines +1 to +18
#!/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"
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
impl DotnetPlugin {
pub fn new() -> Self {
Self {
ba: plugins::core::new_backend_arg("dotnet").into(),
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
ba: plugins::core::new_backend_arg("dotnet").into(),
ba: Arc::new(plugins::core::new_backend_arg("dotnet")),

Copilot uses AI. Check for mistakes.
Comment on lines +109 to +112
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)
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
Comment on lines +194 to +196
.or(env::var_path("DOTNET_ROOT"))
.unwrap_or(dirs::DATA.join("dotnet-root"))
}
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
.or(env::var_path("DOTNET_ROOT"))
.unwrap_or(dirs::DATA.join("dotnet-root"))
}
.unwrap_or(dirs::DATA.join("dotnet-root"))
}
}

Copilot uses AI. Check for mistakes.
autofix-ci bot and others added 3 commits February 24, 2026 05:55
- 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>
@jdx jdx changed the title feat(core): add core dotnet plugin for .NET SDK management feat: add core dotnet plugin for .NET SDK management Feb 24, 2026
jdx and others added 2 commits February 24, 2026 06:20
- 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>
@jdx jdx requested a review from Copilot February 24, 2026 06:24
@github-actions
Copy link

github-actions bot commented Feb 24, 2026

Hyperfine Performance

mise x -- echo

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 ⚠️ 117ms -20%
ls (cached) 62ms 69ms -10%
bin-paths (cached) 64ms 71ms -9%
task-ls (cached) 688ms 696ms -1%

⚠️ Warning: install cached performance variance is -20%

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +82 to +108
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)
}
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_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.

Copilot uses AI. Check for mistakes.
Comment on lines +123 to +154
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())?;
}
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +169 to +175
} 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)?;
}
}
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +6 to +9

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.
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.

export MISE_DOTNET_ROOT="$MISE_DATA_DIR/dotnet-root"

assert_contains "mise x dotnet@8.0.408 -- dotnet --version" "8.0.408"
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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"

Copilot uses AI. Check for mistakes.
jdx and others added 2 commits February 24, 2026 06:42
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>
@jdx jdx enabled auto-merge (squash) February 24, 2026 06:59
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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,
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Fix in Cursor Fix in Web

@jdx jdx merged commit cbc5a85 into main Feb 24, 2026
36 checks passed
@jdx jdx deleted the feat/core-dotnet-plugin branch February 24, 2026 07:11
adamliang0 pushed a commit to adamliang0/mise that referenced this pull request Feb 24, 2026
## 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>
mise-en-dev added a commit that referenced this pull request Feb 25, 2026
### 🚀 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)
risu729 pushed a commit to risu729/mise that referenced this pull request Feb 27, 2026
## 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>
risu729 pushed a commit to risu729/mise that referenced this pull request Feb 27, 2026
### 🚀 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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants