From 6ca837da5f682cd5d2a185dab7a8b04b907abc83 Mon Sep 17 00:00:00 2001 From: Kyle Altendorf Date: Sat, 21 Feb 2026 13:03:24 -0500 Subject: [PATCH] fix(install): validate --locked before --dry-run short-circuit The intended use case for `mise install --locked --dry-run` is a lightweight check that a lockfile is complete for the current mise.toml, without actually installing anything. Previously, --dry-run returned early before the --locked check, so missing lockfile URLs were silently ignored rather than flagged. Move the --locked validation above the dry-run early return so both `--locked` and `--locked --dry-run` consistently fail when lockfile URLs are missing. --- e2e/lockfile/test_lockfile_locked_mode | 40 ++++++++++++++++++++++++++ src/backend/mod.rs | 40 ++++++++++++++------------ 2 files changed, 61 insertions(+), 19 deletions(-) create mode 100644 e2e/lockfile/test_lockfile_locked_mode diff --git a/e2e/lockfile/test_lockfile_locked_mode b/e2e/lockfile/test_lockfile_locked_mode new file mode 100644 index 0000000000..8257cf4e8d --- /dev/null +++ b/e2e/lockfile/test_lockfile_locked_mode @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +# Test --locked mode enforcement during install and dry-run + +export MISE_LOCKFILE=1 + +detect_platform +PLATFORM="$MISE_PLATFORM" + +# Use jq via aqua backend, which supports lockfile URLs +cat <<'EOF' >mise.toml +[tools] +jq = "1.7.1" +EOF + +# Uninstall jq so it's not already present +mise uninstall jq@1.7.1 2>/dev/null || true + +# --- Test 1: --locked fails when no lockfile URL exists --- +cat <mise.lock +[[tools.jq]] +version = "1.7.1" +backend = "aqua:jqlang/jq" +EOF + +assert_fail_contains "mise install --locked 2>&1" "No lockfile URL found" + +# --- Test 2: --locked --dry-run also fails when no lockfile URL exists --- +# This is the key behavior: locked validation must run before dry-run short-circuit +assert_fail_contains "mise install --locked --dry-run 2>&1" "No lockfile URL found" + +# --- Test 3: --locked --dry-run succeeds when lockfile URL exists --- +cat <mise.lock +[[tools.jq]] +version = "1.7.1" +backend = "aqua:jqlang/jq" +"platforms.$PLATFORM" = { url = "https://example.com/jq-1.7.1.tar.gz" } +EOF + +assert_contains "mise install --locked --dry-run 2>&1" "would install" diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 8089e5f040..10a43f4c85 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -861,6 +861,27 @@ pub trait Backend: Debug + Send + Sync { ctx: InstallContext, tv: ToolVersion, ) -> eyre::Result { + // Check for --locked mode: if enabled and no lockfile URL exists, fail early + // Exempt tool stubs from lockfile requirements since they are ephemeral + // Also exempt backends that don't support URL locking (e.g., Rust uses rustup) + // This must run before the dry-run check so that --locked --dry-run still validates + if ctx.locked && !tv.request.source().is_tool_stub() && self.supports_lockfile_url() { + let platform_key = self.get_platform_key(); + let has_lockfile_url = tv + .lock_platforms + .get(&platform_key) + .and_then(|p| p.url.as_ref()) + .is_some(); + if !has_lockfile_url { + bail!( + "No lockfile URL found for {} on platform {} (--locked mode)\n\ + hint: Run `mise lock` to generate lockfile URLs, or disable locked mode", + tv.style(), + platform_key + ); + } + } + // Handle dry-run mode early to avoid plugin installation if ctx.dry_run { use crate::ui::progress_report::ProgressIcon; @@ -896,25 +917,6 @@ pub trait Backend: Debug + Send + Sync { } else if self.is_version_installed(&ctx.config, &tv, true) { return Ok(tv); } - // Check for --locked mode: if enabled and no lockfile URL exists, fail early - // Exempt tool stubs from lockfile requirements since they are ephemeral - // Also exempt backends that don't support URL locking (e.g., Rust uses rustup) - if ctx.locked && !tv.request.source().is_tool_stub() && self.supports_lockfile_url() { - let platform_key = self.get_platform_key(); - let has_lockfile_url = tv - .lock_platforms - .get(&platform_key) - .and_then(|p| p.url.as_ref()) - .is_some(); - if !has_lockfile_url { - bail!( - "No lockfile URL found for {} on platform {} (--locked mode)\n\ - hint: Run `mise lock` to generate lockfile URLs, or disable locked mode", - tv.style(), - platform_key - ); - } - } // Track the installation asynchronously (fire-and-forget) // Do this before install so the request has time to complete during installation