Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions e2e/cli/test_hook_env_prefer_offline_no_remote_versions
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,32 @@ EOF

assert_succeed "mise hook-env -s bash --force"
assert_fail "test -f '$HOME/list-all-called'"

credential_marker="$HOME/github-credential-command-called"
credential_command="$HOME/fake-github-credential-command"
cat >"$credential_command" <<EOF
#!/usr/bin/env bash
touch "$credential_marker"
echo fake-token
EOF
chmod +x "$credential_command"

mkdir -p "$MISE_CONFIG_DIR"
cat >"$MISE_CONFIG_DIR/config.toml" <<EOF
[settings]
github.credential_command = "$credential_command"
minimum_release_age = "3d"
EOF

cat >mise.toml <<EOF
[tools]
"github:cli/cli" = "latest"
EOF

assert_succeed "mise hook-env -s bash --force"
assert_fail "test -f '$credential_marker'"

rm -f "$credential_marker"
offline_current="$(MISE_OFFLINE=1 mise current github:cli/cli 2>/dev/null || true)"
assert_not_contains_text "$offline_current" "latest"
assert_fail "test -f '$credential_marker'"
33 changes: 33 additions & 0 deletions e2e/tasks/test_task_skip_tools
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,36 @@ assert "mise run echo-args --skip-tools" "args: --skip-tools"
# verify --skip-tools skips task-level tool installation
# task declares tools = { tiny = "1.0.0" } but --skip-tools prevents install attempt
assert "mise run --skip-tools check-tiny" "ran"

credential_marker="$HOME/github-credential-command-called"
credential_command="$HOME/fake-github-credential-command"
cat >"$credential_command" <<EOF
#!/usr/bin/env bash
touch "$credential_marker"
echo fake-token
EOF
chmod +x "$credential_command"

mkdir -p "$MISE_CONFIG_DIR"
cat >"$MISE_CONFIG_DIR/config.toml" <<EOF
[settings]
auto_install = false
github.credential_command = "$credential_command"
minimum_release_age = "3d"
EOF

cat >mise.toml <<EOF
[tools]
"github:cli/cli" = "latest"

[tasks.nop-test]
run = "echo Hello World!"
EOF

assert "mise run --skip-tools nop-test" "Hello World!"
assert_fail "test -f '$credential_marker'"

rm -f "$credential_marker"

assert "mise run nop-test" "Hello World!"
assert_fail "test -f '$credential_marker'"
22 changes: 15 additions & 7 deletions src/cli/hook_env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::env_diff::{EnvDiff, EnvDiffOperation, EnvMap};
use crate::file::{canonicalize_cached, display_rel_path};
use crate::hook_env::{PREV_SESSION, WatchFilePattern};
use crate::shell::{ShellType, get_shell};
use crate::toolset::Toolset;
use crate::toolset::{ResolveOptions, Toolset, ToolsetBuilder};
use crate::{env, hook_env, hooks, watch_files};
use console::truncate_str;
use eyre::Result;
Expand Down Expand Up @@ -52,7 +52,15 @@ pub struct HookEnv {
impl HookEnv {
pub async fn run(self) -> Result<()> {
let config = Config::get().await?;
let ts = config.get_toolset().await?;
// Shell activation must stay fast and non-networked; missing tools are
// handled by the normal install paths instead of hook-env.
let ts = ToolsetBuilder::new()
.with_resolve_options(ResolveOptions {
offline: true,
..Default::default()
})
.build(&config)
.await?;
time!("hook-env");

// Try to use cached watch_files for early exit check if env_cache is enabled
Expand Down Expand Up @@ -96,7 +104,7 @@ impl HookEnv {

// Create config_paths from user_paths for display_status and build_session
let config_paths: IndexSet<PathBuf> = user_paths.iter().cloned().collect();
self.display_status(&config, ts, &mise_env, &config_paths)
self.display_status(&config, &ts, &mise_env, &config_paths)
.await?;

let mut diff = EnvDiff::new(&env::PRISTINE_ENV, mise_env.clone());
Expand Down Expand Up @@ -142,7 +150,7 @@ impl HookEnv {
patches.push(
self.build_session_operation(
&config,
ts,
&ts,
mise_env,
new_aliases.clone(),
watch_files,
Expand All @@ -167,9 +175,9 @@ impl HookEnv {
hook_env::build_alias_commands(&*shell, &PREV_SESSION.aliases, &new_aliases);
miseprint!("{alias_output}")?;

hooks::run_all_hooks(&config, ts, &*shell).await;
hooks::run_enter_hooks_for_newly_loaded_configs(&config, ts, &*shell).await;
watch_files::execute_runs(&config, ts).await;
hooks::run_all_hooks(&config, &ts, &*shell).await;
hooks::run_enter_hooks_for_newly_loaded_configs(&config, &ts, &*shell).await;
watch_files::execute_runs(&config, &ts).await;

Ok(())
}
Expand Down
10 changes: 9 additions & 1 deletion src/cli/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use crate::task::task_list::{get_task_lists, resolve_depends};
use crate::task::task_output::TaskOutput;
use crate::task::task_output_handler::OutputHandler;
use crate::task::{Deps, Task};
use crate::toolset::{InstallOptions, ToolsetBuilder};
use crate::toolset::{InstallOptions, ResolveOptions, ToolsetBuilder};
use crate::ui::{ctrlc, info, style};
use clap::{CommandFactory, ValueHint};
use eyre::{Result, bail, eyre};
Expand Down Expand Up @@ -369,10 +369,18 @@ impl Run {

// Build and install toolset only after tasks resolve. A naked run that
// does not match any task should fail without installing project tools.
// Task startup should not fetch remote version metadata just to build
// the environment. If tools are missing and auto-install is enabled,
// install_missing_versions re-resolves those specific requests online.
let resolve_options = ResolveOptions {
offline: true,
..Default::default()
};
let mut ts = ToolsetBuilder::new()
.with_args(&self.tool)
.with_default_to_latest(true)
.with_config_files(combined_configs)
.with_resolve_options(resolve_options)
.build(&config)
.await?;

Expand Down
35 changes: 33 additions & 2 deletions src/toolset/tool_version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -369,11 +369,19 @@ impl ToolVersion {

let settings = Settings::get();
let is_offline = settings.offline() || opts.offline;
let prefer_offline = settings.prefer_offline();
let prefer_offline =
settings.prefer_offline() && !matches!(request.source(), ToolSource::Argument);
let prefer_offline_without_offline = prefer_offline && !is_offline;
let should_filter_installed_versions =
opts.filters_installed_versions() && !is_offline && !prefer_offline;

if v == "latest" {
if prefer_offline_without_offline
&& !opts.latest_versions
&& let Some(v) = backend.latest_installed_version(None)?
{
return build(v);
}
if !opts.latest_versions
&& !should_filter_installed_versions
&& let Some(v) = backend.latest_installed_version(None)?
Expand Down Expand Up @@ -421,6 +429,12 @@ impl ToolVersion {
// zig@master), never an unrelated installed release, so we don't
// short-circuit zig@master to a stable version that happens to be
// installed.
if prefer_offline
&& !opts.latest_versions
&& let Some(installed) = backend.latest_installed_channel_version(&v)
{
return build(installed);
}
if !opts.latest_versions
&& !should_filter_installed_versions
&& let Some(installed) = backend.latest_installed_channel_version(&v)
Expand Down Expand Up @@ -456,6 +470,15 @@ impl ToolVersion {
if crate::config::config_file::idiomatic_version::package_json::is_package_json(path)
) && crate::semver::is_npm_semver_range_query(&v)
{
if prefer_offline && !opts.latest_versions {
let installed_versions = backend.list_installed_versions();
if let Some(matches) =
crate::semver::npm_semver_range_filter(&installed_versions, &v)
&& let Some(v) = matches.last()
{
return build(v.clone());
}
}
if !opts.latest_versions && !should_filter_installed_versions {
let installed_versions = backend.list_installed_versions();
if let Some(matches) =
Expand Down Expand Up @@ -582,8 +605,16 @@ impl ToolVersion {
let backend = request.backend()?;
let settings = Settings::get();
let is_offline = settings.offline() || opts.offline;
let prefer_offline =
settings.prefer_offline() && !matches!(request.source(), ToolSource::Argument);
let should_filter_installed_versions =
opts.filters_installed_versions() && !is_offline && !settings.prefer_offline();
opts.filters_installed_versions() && !is_offline && !prefer_offline;
if prefer_offline
&& !opts.latest_versions
&& let Some(v) = backend.list_installed_versions_matching(prefix).last()
{
return Ok(Self::new(request, v.to_string()));
}
if !opts.latest_versions
&& !should_filter_installed_versions
&& let Some(v) = backend.list_installed_versions_matching(prefix).last()
Expand Down
Loading