From 70b630e32c5daa50d9997523db72e873c482eda6 Mon Sep 17 00:00:00 2001 From: jdx <216188+jdx@users.noreply.github.com> Date: Wed, 18 Feb 2026 13:36:57 +0000 Subject: [PATCH 1/3] refactor(config): consolidate flat task_* settings into nested task.* settings Add 9 new nested task.* settings (task.disable_paths, task.output, task.remote_no_cache, task.run_auto_install, task.show_full_cmd, task.skip, task.skip_depends, task.timeout, task.timings) and deprecate the corresponding flat task_* settings with version-gated warnings (deprecated_warn_at="2026.8.0", deprecated_remove_at="2027.2.0"). Extend SettingsMeta and build.rs codegen to support deprecation fields, add warn_deprecated() helper, and migrate all code references from settings.task_xxx to settings.task.xxx. Co-Authored-By: Claude Sonnet 4.6 --- build.rs | 28 ++++++ docs/configuration.md | 2 +- docs/tasks/running-tasks.md | 2 +- e2e/config/test_auto_install_false | 2 +- e2e/tasks/test_task_file_auto_install | 2 +- e2e/tasks/test_task_raw_output | 6 +- e2e/tasks/test_task_skip_deps | 4 +- man/man1/mise.1 | 8 +- mise.usage.kdl | 8 +- schema/mise-settings.json | 8 ++ schema/mise.json | 87 ++++++++++++---- settings.toml | 136 +++++++++++++++++++++----- src/cli/run.rs | 6 +- src/cli/watch.rs | 4 +- src/config/mod.rs | 5 +- src/config/settings.rs | 75 +++++++++++++- src/task/task_executor.rs | 4 +- src/task/task_fetcher.rs | 2 +- src/task/task_output.rs | 2 +- src/task/task_output_handler.rs | 2 +- src/task/task_tool_installer.rs | 4 +- 21 files changed, 320 insertions(+), 77 deletions(-) diff --git a/build.rs b/build.rs index f3b6db5bf7..da323cf9ca 100644 --- a/build.rs +++ b/build.rs @@ -419,6 +419,32 @@ pub static SETTINGS_META: Lazy> = Lazy::new indexmap!{"# .to_string(), ); + let push_deprecated_fields = |lines: &mut Vec, props: &toml::Table| { + let deprecated = props + .get("deprecated") + .map(|v| v.as_str().unwrap().to_string()); + let warn_at = props + .get("deprecated_warn_at") + .map(|v| v.as_str().unwrap().to_string()); + let remove_at = props + .get("deprecated_remove_at") + .map(|v| v.as_str().unwrap().to_string()); + match deprecated { + Some(msg) => lines.push(format!( + " deprecated: Some({}),", + raw_string_literal(&msg) + )), + None => lines.push(" deprecated: None,".to_string()), + } + match warn_at { + Some(v) => lines.push(format!(" deprecated_warn_at: Some({v:?}),")), + None => lines.push(" deprecated_warn_at: None,".to_string()), + } + match remove_at { + Some(v) => lines.push(format!(" deprecated_remove_at: Some({v:?}),")), + None => lines.push(" deprecated_remove_at: None,".to_string()), + } + }; for (name, props) in &settings { let props = props.as_table().unwrap(); if let Some(type_) = props.get("type").map(|v| v.as_str().unwrap()) { @@ -439,6 +465,7 @@ pub static SETTINGS_META: Lazy> = Lazy::new raw_string_literal(&description) )); } + push_deprecated_fields(&mut lines, props); lines.push(" },".to_string()); } } @@ -464,6 +491,7 @@ pub static SETTINGS_META: Lazy> = Lazy::new raw_string_literal(&description) )); } + push_deprecated_fields(&mut lines, props); lines.push(" },".to_string()); } } diff --git a/docs/configuration.md b/docs/configuration.md index 062f5b8a99..e93a97f565 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -338,7 +338,7 @@ raw = false # set to true to directly pipe plugins to stdin/stdout/std yes = false # set to true to automatically answer yes to all prompts not_found_auto_install = true # see MISE_NOT_FOUND_AUTO_INSTALL -task_output = "prefix" # see Tasks Runner for more information +task.output = "prefix" # see Tasks Runner for more information paranoid = false # see MISE_PARANOID shorthands_file = '~/.config/mise/shorthands.toml' # path to the shorthands file, see `MISE_SHORTHANDS_FILE` diff --git a/docs/tasks/running-tasks.md b/docs/tasks/running-tasks.md index 626a7b4bfc..8a05e1663c 100644 --- a/docs/tasks/running-tasks.md +++ b/docs/tasks/running-tasks.md @@ -15,7 +15,7 @@ By default, tasks will execute with a maximum of 4 parallel jobs. Customize this label. By printing line-by-line we avoid interleaving output from parallel executions. However, if --jobs == 1, the output will be set to `interleave`. -To just print stdout/stderr directly, use `--interleave`, the `task_output` setting, or `MISE_TASK_OUTPUT=interleave`. +To just print stdout/stderr directly, use `--interleave`, the `task.output` setting, or `MISE_TASK_OUTPUT=interleave`. Stdin is not read by default. To enable this, set `raw = true` on the task that needs it. This will prevent it running in parallel with any other task—a RWMutex will get a write lock in this case. This also prevents redactions applied to the output. diff --git a/e2e/config/test_auto_install_false b/e2e/config/test_auto_install_false index 193373c0c5..ab34bbe52f 100644 --- a/e2e/config/test_auto_install_false +++ b/e2e/config/test_auto_install_false @@ -29,7 +29,7 @@ cat <mise.toml dummy = '1.0.0' [settings] -task_run_auto_install = false +task.run_auto_install = false [tasks.test] run = 'dummy --version' diff --git a/e2e/tasks/test_task_file_auto_install b/e2e/tasks/test_task_file_auto_install index eba5ea2138..6e95a40187 100644 --- a/e2e/tasks/test_task_file_auto_install +++ b/e2e/tasks/test_task_file_auto_install @@ -11,7 +11,7 @@ cat <mise.toml tiny = "1" [settings] -task_run_auto_install = true +task.run_auto_install = true EOF mkdir -p mise-tasks diff --git a/e2e/tasks/test_task_raw_output b/e2e/tasks/test_task_raw_output index 95168d0e91..c2908fd2df 100755 --- a/e2e/tasks/test_task_raw_output +++ b/e2e/tasks/test_task_raw_output @@ -1,11 +1,11 @@ #!/usr/bin/env bash -# Test that raw tasks get interleave output even when task_output=prefix is set -# This ensures raw=true takes priority over task_output setting +# Test that raw tasks get interleave output even when task.output=prefix is set +# This ensures raw=true takes priority over task.output setting cat <mise.toml [settings] -task_output = "prefix" +task.output = "prefix" [tasks.normal] run = 'echo normal task' diff --git a/e2e/tasks/test_task_skip_deps b/e2e/tasks/test_task_skip_deps index 5f72d53539..ccf6496a83 100755 --- a/e2e/tasks/test_task_skip_deps +++ b/e2e/tasks/test_task_skip_deps @@ -40,10 +40,10 @@ assert_not_contains "mise run --skip-deps c ::: b" "a" assert "mise run --skip-deps e" "a b" -# Test task_skip_depends setting +# Test task.skip_depends setting cat <mise.toml [settings] -task_skip_depends = true +task.skip_depends = true [tasks.a] run = "echo a" diff --git a/man/man1/mise.1 b/man/man1/mise.1 index e64293d4a1..b6bfa367a7 100644 --- a/man/man1/mise.1 +++ b/man/man1/mise.1 @@ -1806,7 +1806,7 @@ Force the tasks to run even if outputs are up to date \fB\-i, \-\-interleave\fR Print directly to stdout/stderr instead of by line Defaults to true if \-\-jobs == 1 -Configure with `task_output` config or `MISE_TASK_OUTPUT` env var +Configure with `task.output` config or `MISE_TASK_OUTPUT` env var .TP \fB\-j, \-\-jobs\fR \fI\fR Number of tasks to run in parallel @@ -1830,7 +1830,7 @@ Change how tasks information is output when running tasks \fB\-p, \-\-prefix\fR Print stdout/stderr by line, prefixed with the task's label Defaults to true if \-\-jobs > 1 -Configure with `task_output` config or `MISE_TASK_OUTPUT` env var +Configure with `task.output` config or `MISE_TASK_OUTPUT` env var .TP \fB\-q, \-\-quiet\fR Don't show extra output @@ -2534,7 +2534,7 @@ Force the tasks to run even if outputs are up to date \fB\-i, \-\-interleave\fR Print directly to stdout/stderr instead of by line Defaults to true if \-\-jobs == 1 -Configure with `task_output` config or `MISE_TASK_OUTPUT` env var +Configure with `task.output` config or `MISE_TASK_OUTPUT` env var .TP \fB\-j, \-\-jobs\fR \fI\fR Number of tasks to run in parallel @@ -2558,7 +2558,7 @@ Change how tasks information is output when running tasks \fB\-p, \-\-prefix\fR Print stdout/stderr by line, prefixed with the task's label Defaults to true if \-\-jobs > 1 -Configure with `task_output` config or `MISE_TASK_OUTPUT` env var +Configure with `task.output` config or `MISE_TASK_OUTPUT` env var .TP \fB\-q, \-\-quiet\fR Don't show extra output diff --git a/mise.usage.kdl b/mise.usage.kdl index 7906fe7e7e..2c0401c8c1 100644 --- a/mise.usage.kdl +++ b/mise.usage.kdl @@ -697,7 +697,7 @@ cmd run restart_token=::: help="Run task(s)" { arg } flag "-f --force" help="Force the tasks to run even if outputs are up to date" - flag "-i --interleave" help="Print directly to stdout/stderr instead of by line\nDefaults to true if --jobs == 1\nConfigure with `task_output` config or `MISE_TASK_OUTPUT` env var" hide=#true + flag "-i --interleave" help="Print directly to stdout/stderr instead of by line\nDefaults to true if --jobs == 1\nConfigure with `task.output` config or `MISE_TASK_OUTPUT` env var" hide=#true flag "-j --jobs" help="Number of tasks to run in parallel\n[default: 4]\nConfigure with `jobs` config or `MISE_JOBS` env var" { arg } @@ -706,7 +706,7 @@ cmd run restart_token=::: help="Run task(s)" { long_help "Change how tasks information is output when running tasks\n\n- `prefix` - Print stdout/stderr by line, prefixed with the task's label\n- `interleave` - Print directly to stdout/stderr instead of by line\n- `replacing` - Stdout is replaced each time, stderr is printed as is\n- `timed` - Only show stdout lines if they are displayed for more than 1 second\n- `keep-order` - Print stdout/stderr by line, prefixed with the task's label, but keep the order of the output\n- `quiet` - Don't show extra output\n- `silent` - Don't show any output including stdout and stderr from the task except for errors" arg } - flag "-p --prefix" help="Print stdout/stderr by line, prefixed with the task's label\nDefaults to true if --jobs > 1\nConfigure with `task_output` config or `MISE_TASK_OUTPUT` env var" hide=#true + flag "-p --prefix" help="Print stdout/stderr by line, prefixed with the task's label\nDefaults to true if --jobs > 1\nConfigure with `task.output` config or `MISE_TASK_OUTPUT` env var" hide=#true flag "-q --quiet" help="Don't show extra output" flag "-r --raw" help="Read/write directly to stdin/stdout/stderr instead of by line\nRedactions are not applied with this option\nConfigure with `raw` config or `MISE_RAW` env var" flag "-s --shell" help="Shell to use to run toml tasks" { @@ -1009,7 +1009,7 @@ cmd tasks help="Manage tasks" { arg } flag "-f --force" help="Force the tasks to run even if outputs are up to date" - flag "-i --interleave" help="Print directly to stdout/stderr instead of by line\nDefaults to true if --jobs == 1\nConfigure with `task_output` config or `MISE_TASK_OUTPUT` env var" hide=#true + flag "-i --interleave" help="Print directly to stdout/stderr instead of by line\nDefaults to true if --jobs == 1\nConfigure with `task.output` config or `MISE_TASK_OUTPUT` env var" hide=#true flag "-j --jobs" help="Number of tasks to run in parallel\n[default: 4]\nConfigure with `jobs` config or `MISE_JOBS` env var" { arg } @@ -1018,7 +1018,7 @@ cmd tasks help="Manage tasks" { long_help "Change how tasks information is output when running tasks\n\n- `prefix` - Print stdout/stderr by line, prefixed with the task's label\n- `interleave` - Print directly to stdout/stderr instead of by line\n- `replacing` - Stdout is replaced each time, stderr is printed as is\n- `timed` - Only show stdout lines if they are displayed for more than 1 second\n- `keep-order` - Print stdout/stderr by line, prefixed with the task's label, but keep the order of the output\n- `quiet` - Don't show extra output\n- `silent` - Don't show any output including stdout and stderr from the task except for errors" arg } - flag "-p --prefix" help="Print stdout/stderr by line, prefixed with the task's label\nDefaults to true if --jobs > 1\nConfigure with `task_output` config or `MISE_TASK_OUTPUT` env var" hide=#true + flag "-p --prefix" help="Print stdout/stderr by line, prefixed with the task's label\nDefaults to true if --jobs > 1\nConfigure with `task.output` config or `MISE_TASK_OUTPUT` env var" hide=#true flag "-q --quiet" help="Don't show extra output" flag "-r --raw" help="Read/write directly to stdin/stdout/stderr instead of by line\nRedactions are not applied with this option\nConfigure with `raw` config or `MISE_RAW` env var" flag "-s --shell" help="Shell to use to run toml tasks" { diff --git a/schema/mise-settings.json b/schema/mise-settings.json index 06580bc587..115ac78b97 100644 --- a/schema/mise-settings.json +++ b/schema/mise-settings.json @@ -45,6 +45,14 @@ "type": "string", "description": "Deprecation message if this setting is deprecated" }, + "deprecated_warn_at": { + "type": "string", + "description": "Mise version at which the deprecation warning starts showing (e.g. \"2026.8.0\")" + }, + "deprecated_remove_at": { + "type": "string", + "description": "Mise version at which the deprecated code should be removed (triggers debug_assert, e.g. \"2027.2.0\")" + }, "description": { "type": "string", "description": "Brief description of what this setting does" diff --git a/schema/mise.json b/schema/mise.json index f5405beaa1..cc32f12f2a 100644 --- a/schema/mise.json +++ b/schema/mise.json @@ -1338,6 +1338,14 @@ "type": "object", "additionalProperties": false, "properties": { + "disable_paths": { + "default": [], + "description": "Paths that mise will not look for tasks in.", + "type": "array", + "items": { + "type": "string" + } + }, "disable_spec_from_run_scripts": { "default": false, "description": "Opt out of parsing task run scripts to infer the usage spec (arguments and flags). When enabled, mise will derive the usage spec only from the `usage` field, ignoring any `arg()`, `option()`, or `flag()` templates used in run scripts. This can restore previous behavior and avoid the extra template pass over run scripts when collecting specs.", @@ -1361,6 +1369,44 @@ "description": "Whether to respect .gitignore files when discovering monorepo subdirectories.", "type": "boolean" }, + "output": { + "description": "Change output style when executing tasks.", + "type": "string", + "enum": [ + "prefix", + "interleave", + "keep-order", + "replacing", + "timed", + "quiet", + "silent" + ] + }, + "remote_no_cache": { + "description": "Mise will always fetch the latest tasks from the remote, by default the cache is used.", + "type": "boolean" + }, + "run_auto_install": { + "default": true, + "description": "Automatically install missing tools when executing tasks.", + "type": "boolean" + }, + "show_full_cmd": { + "description": "Disable truncation of command lines in task execution output. When true, the full command line will be shown.", + "type": "boolean" + }, + "skip": { + "default": [], + "description": "Tasks to skip when running `mise run`.", + "type": "array", + "items": { + "type": "string" + } + }, + "skip_depends": { + "description": "Run only specified tasks skipping all dependencies.", + "type": "boolean" + }, "source_freshness_equal_mtime_is_fresh": { "default": false, "description": "When source mtime equals output mtime, consider sources fresh (use <=). Default false uses strict < comparison.", @@ -1370,13 +1416,21 @@ "default": false, "description": "Use content hashing (blake3) instead of metadata for source freshness. More accurate but slower.", "type": "boolean" + }, + "timeout": { + "description": "Default timeout for tasks. Can be overridden by individual tasks.", + "type": "string" + }, + "timings": { + "description": "Show completion message with elapsed time for each task on `mise run`. Default shows when output type is `prefix`.", + "type": "boolean" } } }, "task_disable_paths": { - "default": [], "description": "Paths that mise will not look for tasks in.", "type": "array", + "deprecated": true, "items": { "type": "string" } @@ -1384,48 +1438,45 @@ "task_output": { "description": "Change output style when executing tasks.", "type": "string", - "enum": [ - "prefix", - "interleave", - "keep-order", - "replacing", - "timed", - "quiet", - "silent" - ] + "deprecated": true }, "task_remote_no_cache": { "description": "Mise will always fetch the latest tasks from the remote, by default the cache is used.", - "type": "boolean" + "type": "boolean", + "deprecated": true }, "task_run_auto_install": { - "default": true, "description": "Automatically install missing tools when executing tasks.", - "type": "boolean" + "type": "boolean", + "deprecated": true }, "task_show_full_cmd": { "description": "Disable truncation of command lines in task execution output. When true, the full command line will be shown.", - "type": "boolean" + "type": "boolean", + "deprecated": true }, "task_skip": { - "default": [], "description": "Tasks to skip when running `mise run`.", "type": "array", + "deprecated": true, "items": { "type": "string" } }, "task_skip_depends": { "description": "Run only specified tasks skipping all dependencies.", - "type": "boolean" + "type": "boolean", + "deprecated": true }, "task_timeout": { "description": "Default timeout for tasks. Can be overridden by individual tasks.", - "type": "string" + "type": "string", + "deprecated": true }, "task_timings": { "description": "Show completion message with elapsed time for each task on `mise run`. Default shows when output type is `prefix`.", - "type": "boolean" + "type": "boolean", + "deprecated": true }, "terminal_progress": { "default": true, diff --git a/settings.toml b/settings.toml index 871086feec..70008e2b01 100644 --- a/settings.toml +++ b/settings.toml @@ -1708,6 +1708,14 @@ env = "MISE_SYSTEM_CONFIG_FILE" optional = true type = "Path" +[task.disable_paths] +default = [] +description = "Paths that mise will not look for tasks in." +env = "MISE_TASK_DISABLE_PATHS" +parse_env = "list_by_colon" +rust_type = "BTreeSet" +type = "ListPath" + [task.disable_spec_from_run_scripts] default = false description = "Opt out of parsing task run scripts to infer the usage spec (arguments and flags). When enabled, mise will derive the usage spec only from the `usage` field, ignoring any `arg()`, `option()`, or `flag()` templates used in run scripts. This can restore previous behavior and avoid the extra template pass over run scripts when collecting specs." @@ -1775,6 +1783,55 @@ when discovering tasks in a monorepo. env = "MISE_TASK_MONOREPO_RESPECT_GITIGNORE" type = "Bool" +[task.output] +description = "Change output style when executing tasks." +docs = """ +Change output style when executing tasks. This controls the output of `mise run`. +""" +enum = [ + { value = "prefix", description = "(default if jobs > 1) print by line with the prefix of the task name" }, + { value = "interleave", description = "(default if jobs == 1 or all tasks run sequentially) print output as it comes in" }, + { value = "keep-order", description = "stream one task's output live while buffering others, printing in definition order as tasks complete" }, + { value = "replacing", description = "replace stdout each time a line is printed-this uses similar logic as `mise install`" }, + { value = "timed", description = "only show stdout lines that take longer than 1s to complete" }, + { value = "quiet", description = "print only stdout/stderr from tasks and nothing from mise" }, + { value = "silent", description = "print nothing from tasks or mise" }, +] +env = "MISE_TASK_OUTPUT" +optional = true +rust_type = "crate::task::TaskOutput" +type = "String" + +[task.remote_no_cache] +description = "Mise will always fetch the latest tasks from the remote, by default the cache is used." +env = "MISE_TASK_REMOTE_NO_CACHE" +optional = true +type = "Bool" + +[task.run_auto_install] +default = true +description = "Automatically install missing tools when executing tasks." +env = "MISE_TASK_RUN_AUTO_INSTALL" +type = "Bool" + +[task.show_full_cmd] +description = "Disable truncation of command lines in task execution output. When true, the full command line will be shown." +env = "MISE_TASK_SHOW_CMD_NO_TRUNC" +type = "Bool" + +[task.skip] +default = [] +description = "Tasks to skip when running `mise run`." +env = "MISE_TASK_SKIP" +parse_env = "set_by_comma" +rust_type = "BTreeSet" +type = "SetString" + +[task.skip_depends] +description = "Run only specified tasks skipping all dependencies." +env = "MISE_TASK_SKIP_DEPENDS" +type = "Bool" + [task.source_freshness_equal_mtime_is_fresh] default = false description = "When source mtime equals output mtime, consider sources fresh (use <=). Default false uses strict < comparison." @@ -1787,72 +1844,99 @@ description = "Use content hashing (blake3) instead of metadata for source fresh env = "MISE_TASK_SOURCE_FRESHNESS_HASH_CONTENTS" type = "Bool" +[task.timeout] +description = "Default timeout for tasks. Can be overridden by individual tasks." +env = "MISE_TASK_TIMEOUT" +optional = true +type = "Duration" + +[task.timings] +description = "Show completion message with elapsed time for each task on `mise run`. Default shows when output type is `prefix`." +env = "MISE_TASK_TIMINGS" +optional = true +type = "Bool" + [task_disable_paths] -default = [] +deprecated = "Use task.disable_paths instead." +deprecated_remove_at = "2027.2.0" +deprecated_warn_at = "2026.8.0" description = "Paths that mise will not look for tasks in." -env = "MISE_TASK_DISABLE_PATHS" -parse_env = "list_by_colon" +hide = true +optional = true rust_type = "BTreeSet" type = "ListPath" [task_output] +deprecated = "Use task.output instead." +deprecated_remove_at = "2027.2.0" +deprecated_warn_at = "2026.8.0" description = "Change output style when executing tasks." -docs = """ -Change output style when executing tasks. This controls the output of `mise run`. -""" -enum = [ - { value = "prefix", description = "(default if jobs > 1) print by line with the prefix of the task name" }, - { value = "interleave", description = "(default if jobs == 1 or all tasks run sequentially) print output as it comes in" }, - { value = "keep-order", description = "stream one task's output live while buffering others, printing in definition order as tasks complete" }, - { value = "replacing", description = "replace stdout each time a line is printed-this uses similar logic as `mise install`" }, - { value = "timed", description = "only show stdout lines that take longer than 1s to complete" }, - { value = "quiet", description = "print only stdout/stderr from tasks and nothing from mise" }, - { value = "silent", description = "print nothing from tasks or mise" }, -] -env = "MISE_TASK_OUTPUT" +hide = true optional = true rust_type = "crate::task::TaskOutput" type = "String" [task_remote_no_cache] +deprecated = "Use task.remote_no_cache instead." +deprecated_remove_at = "2027.2.0" +deprecated_warn_at = "2026.8.0" description = "Mise will always fetch the latest tasks from the remote, by default the cache is used." -env = "MISE_TASK_REMOTE_NO_CACHE" +hide = true optional = true type = "Bool" [task_run_auto_install] -default = true +deprecated = "Use task.run_auto_install instead." +deprecated_remove_at = "2027.2.0" +deprecated_warn_at = "2026.8.0" description = "Automatically install missing tools when executing tasks." -env = "MISE_TASK_RUN_AUTO_INSTALL" +hide = true +optional = true type = "Bool" [task_show_full_cmd] +deprecated = "Use task.show_full_cmd instead." +deprecated_remove_at = "2027.2.0" +deprecated_warn_at = "2026.8.0" description = "Disable truncation of command lines in task execution output. When true, the full command line will be shown." -env = "MISE_TASK_SHOW_CMD_NO_TRUNC" +hide = true +optional = true type = "Bool" [task_skip] -default = [] +deprecated = "Use task.skip instead." +deprecated_remove_at = "2027.2.0" +deprecated_warn_at = "2026.8.0" description = "Tasks to skip when running `mise run`." -env = "MISE_TASK_SKIP" -parse_env = "set_by_comma" +hide = true +optional = true rust_type = "BTreeSet" type = "SetString" [task_skip_depends] +deprecated = "Use task.skip_depends instead." +deprecated_remove_at = "2027.2.0" +deprecated_warn_at = "2026.8.0" description = "Run only specified tasks skipping all dependencies." -env = "MISE_TASK_SKIP_DEPENDS" +hide = true +optional = true type = "Bool" [task_timeout] +deprecated = "Use task.timeout instead." +deprecated_remove_at = "2027.2.0" +deprecated_warn_at = "2026.8.0" description = "Default timeout for tasks. Can be overridden by individual tasks." -env = "MISE_TASK_TIMEOUT" +hide = true optional = true type = "Duration" [task_timings] +deprecated = "Use task.timings instead." +deprecated_remove_at = "2027.2.0" +deprecated_warn_at = "2026.8.0" description = "Show completion message with elapsed time for each task on `mise run`. Default shows when output type is `prefix`." -env = "MISE_TASK_TIMINGS" +hide = true optional = true type = "Bool" diff --git a/src/cli/run.rs b/src/cli/run.rs index bee92de69d..9906ef1ab8 100644 --- a/src/cli/run.rs +++ b/src/cli/run.rs @@ -84,7 +84,7 @@ pub struct Run { /// Print directly to stdout/stderr instead of by line /// Defaults to true if --jobs == 1 - /// Configure with `task_output` config or `MISE_TASK_OUTPUT` env var + /// Configure with `task.output` config or `MISE_TASK_OUTPUT` env var #[clap( long, short, @@ -118,7 +118,7 @@ pub struct Run { /// Print stdout/stderr by line, prefixed with the task's label /// Defaults to true if --jobs > 1 - /// Configure with `task_output` config or `MISE_TASK_OUTPUT` env var + /// Configure with `task.output` config or `MISE_TASK_OUTPUT` env var #[clap( long, short, @@ -293,7 +293,7 @@ impl Run { } if !self.skip_deps { - self.skip_deps = Settings::get().task_skip_depends; + self.skip_deps = Settings::get().task.skip_depends; } time!("run init"); diff --git a/src/cli/watch.rs b/src/cli/watch.rs index 07fa88e155..e6c34fe9f9 100644 --- a/src/cli/watch.rs +++ b/src/cli/watch.rs @@ -1103,13 +1103,13 @@ pub struct WatchexecArgs { // // /// Print stdout/stderr by line, prefixed with the tasks's label // /// Defaults to true if --jobs > 1 - // /// Configure with `task_output` config or `MISE_TASK_OUTPUT` env var + // /// Configure with `task.output` config or `MISE_TASK_OUTPUT` env var // #[clap(long, short, verbatim_doc_comment, overrides_with = "interleave")] // pub prefix: bool, // // /// Print directly to stdout/stderr instead of by line // /// Defaults to true if --jobs == 1 - // /// Configure with `task_output` config or `MISE_TASK_OUTPUT` env var + // /// Configure with `task.output` config or `MISE_TASK_OUTPUT` env var // #[clap(long, short, verbatim_doc_comment, overrides_with = "prefix")] // pub interleave: bool, // diff --git a/src/config/mod.rs b/src/config/mod.rs index 781290bfdc..015fa98712 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -2168,7 +2168,8 @@ async fn load_tasks_includes( .filter(|p| file::is_executable(p)) .filter(|p| { !Settings::get() - .task_disable_paths + .task + .disable_paths .iter() .any(|d| p.starts_with(d)) }) @@ -2189,7 +2190,7 @@ async fn load_tasks_includes( } async fn resolve_git_url_to_path(git_url: &str) -> Result { - let no_cache = Settings::get().task_remote_no_cache.unwrap_or(false); + let no_cache = Settings::get().task.remote_no_cache.unwrap_or(false); let task_file_providers = TaskFileProvidersBuilder::new() .with_cache(!no_cache) .build(); diff --git a/src/config/settings.rs b/src/config/settings.rs index 1de7767953..883c4e1cd3 100644 --- a/src/config/settings.rs +++ b/src/config/settings.rs @@ -47,6 +47,9 @@ pub struct SettingsMeta { // pub key: String, pub type_: SettingsType, pub description: &'static str, + pub deprecated: Option<&'static str>, + pub deprecated_warn_at: Option<&'static str>, + pub deprecated_remove_at: Option<&'static str>, } #[derive( @@ -218,6 +221,32 @@ pub struct SettingsFile { pub settings: SettingsPartial, } +fn warn_deprecated(key: &str) { + if let Some(meta) = SETTINGS_META.get(key) { + if let (Some(msg), Some(warn_at), Some(remove_at)) = ( + meta.deprecated, + meta.deprecated_warn_at, + meta.deprecated_remove_at, + ) { + use versions::Versioning; + let warn_version = Versioning::new(warn_at).unwrap(); + let remove_version = Versioning::new(remove_at).unwrap(); + debug_assert!( + *crate::cli::version::V < remove_version, + "Deprecated setting [{key}] should have been removed in {remove_at}. Please remove this deprecated setting.", + ); + if *crate::cli::version::V >= warn_version { + let id = Box::leak(format!("setting.{key}").into_boxed_str()); + if crate::output::DEPRECATED.lock().unwrap().insert(id) { + warn!( + "deprecated [setting.{key}]: {msg} This will be removed in mise {remove_at}." + ); + } + } + } + } +} + impl Settings { pub fn get() -> Arc { Self::try_get().unwrap() @@ -332,10 +361,51 @@ impl Settings { /// Sets deprecated settings to new names fn set_hidden_configs(&mut self) { + // Migrate task_* settings to task.* (must run before auto_install override below) + if let Some(v) = self.task_disable_paths.take() { + if !v.is_empty() { + warn_deprecated("task_disable_paths"); + self.task.disable_paths.extend(v); + } + } + if let Some(v) = self.task_output.take() { + warn_deprecated("task_output"); + self.task.output = Some(v); + } + if let Some(v) = self.task_remote_no_cache { + warn_deprecated("task_remote_no_cache"); + self.task.remote_no_cache = Some(v); + } + if let Some(v) = self.task_run_auto_install { + warn_deprecated("task_run_auto_install"); + self.task.run_auto_install = v; + } + if let Some(v) = self.task_show_full_cmd { + warn_deprecated("task_show_full_cmd"); + self.task.show_full_cmd = v; + } + if let Some(v) = self.task_skip.take() { + if !v.is_empty() { + warn_deprecated("task_skip"); + self.task.skip.extend(v); + } + } + if let Some(v) = self.task_skip_depends { + warn_deprecated("task_skip_depends"); + self.task.skip_depends = v; + } + if let Some(v) = self.task_timeout.take() { + warn_deprecated("task_timeout"); + self.task.timeout = Some(v); + } + if let Some(v) = self.task_timings { + warn_deprecated("task_timings"); + self.task.timings = Some(v); + } if !self.auto_install { self.exec_auto_install = false; self.not_found_auto_install = false; - self.task_run_auto_install = false; + self.task.run_auto_install = false; } if let Some(false) = self.asdf { self.disable_backends.push("asdf".to_string()); @@ -565,7 +635,8 @@ impl Settings { } pub fn task_timeout_duration(&self) -> Option { - self.task_timeout + self.task + .timeout .as_ref() .and_then(|s| duration::parse_duration(s).ok()) } diff --git a/src/task/task_executor.rs b/src/task/task_executor.rs index fc6a579e7b..1444d5b200 100644 --- a/src/task/task_executor.rs +++ b/src/task/task_executor.rs @@ -104,7 +104,7 @@ impl TaskExecutor { pub fn task_timings(&self) -> bool { let output_mode = self.output_handler.output(None); self.timings - || Settings::get().task_timings.unwrap_or( + || Settings::get().task.timings.unwrap_or( output_mode == TaskOutput::Prefix || output_mode == TaskOutput::Timed || output_mode == TaskOutput::KeepOrder, @@ -119,7 +119,7 @@ impl TaskExecutor { ) -> Result<()> { let prefix = task.estyled_prefix(); let total_start = std::time::Instant::now(); - if Settings::get().task_skip.contains(&task.name) { + if Settings::get().task.skip.contains(&task.name) { if !self.quiet(Some(task)) { self.eprint(task, &prefix, "skipping task"); } diff --git a/src/task/task_fetcher.rs b/src/task/task_fetcher.rs index 16521ee4f7..901e236345 100644 --- a/src/task/task_fetcher.rs +++ b/src/task/task_fetcher.rs @@ -15,7 +15,7 @@ impl TaskFetcher { /// Fetch remote task files, converting remote paths to local cached paths pub async fn fetch_tasks(&self, tasks: &mut Vec) -> Result<()> { - let no_cache = self.no_cache || Settings::get().task_remote_no_cache.unwrap_or(false); + let no_cache = self.no_cache || Settings::get().task.remote_no_cache.unwrap_or(false); let task_file_providers = TaskFileProvidersBuilder::new() .with_cache(!no_cache) .build(); diff --git a/src/task/task_output.rs b/src/task/task_output.rs index 549bc18a76..49d59f5cbf 100644 --- a/src/task/task_output.rs +++ b/src/task/task_output.rs @@ -34,7 +34,7 @@ pub fn trunc(prefix: &str, msg: &str) -> String { let settings = Settings::get(); // Skip width truncation when explicitly disabled - if settings.task_show_full_cmd { + if settings.task.show_full_cmd { return msg.to_string(); } let msg = msg.lines().next().unwrap_or_default(); diff --git a/src/task/task_output_handler.rs b/src/task/task_output_handler.rs index 605fffa10f..86d46cf1a8 100644 --- a/src/task/task_output_handler.rs +++ b/src/task/task_output_handler.rs @@ -283,7 +283,7 @@ impl OutputHandler { TaskOutput::Prefix } else if self.interleave { TaskOutput::Interleave - } else if let Some(output) = Settings::get().task_output { + } else if let Some(output) = Settings::get().task.output { // Silent/quiet from config override raw (output suppression takes precedence) // Other modes (prefix, etc.) allow raw to take precedence for stdin/stdout if output.is_silent() || output.is_quiet() { diff --git a/src/task/task_tool_installer.rs b/src/task/task_tool_installer.rs index 924152f547..97078620bb 100644 --- a/src/task/task_tool_installer.rs +++ b/src/task/task_tool_installer.rs @@ -184,8 +184,8 @@ impl<'a> TaskToolInstaller<'a> { .install_missing_versions( config, &InstallOptions { - missing_args_only: !Settings::get().task_run_auto_install, - skip_auto_install: !Settings::get().task_run_auto_install + missing_args_only: !Settings::get().task.run_auto_install, + skip_auto_install: !Settings::get().task.run_auto_install || !Settings::get().auto_install, ..Default::default() }, From ff657754a3f68525d70dab4d1b0d19f87dc5a5a9 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Wed, 18 Feb 2026 13:43:01 +0000 Subject: [PATCH 2/3] [autofix.ci] apply automated fixes --- src/config/settings.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/config/settings.rs b/src/config/settings.rs index 883c4e1cd3..9070be12d4 100644 --- a/src/config/settings.rs +++ b/src/config/settings.rs @@ -222,8 +222,8 @@ pub struct SettingsFile { } fn warn_deprecated(key: &str) { - if let Some(meta) = SETTINGS_META.get(key) { - if let (Some(msg), Some(warn_at), Some(remove_at)) = ( + if let Some(meta) = SETTINGS_META.get(key) + && let (Some(msg), Some(warn_at), Some(remove_at)) = ( meta.deprecated, meta.deprecated_warn_at, meta.deprecated_remove_at, @@ -244,7 +244,6 @@ fn warn_deprecated(key: &str) { } } } - } } impl Settings { @@ -362,12 +361,11 @@ impl Settings { /// Sets deprecated settings to new names fn set_hidden_configs(&mut self) { // Migrate task_* settings to task.* (must run before auto_install override below) - if let Some(v) = self.task_disable_paths.take() { - if !v.is_empty() { + if let Some(v) = self.task_disable_paths.take() + && !v.is_empty() { warn_deprecated("task_disable_paths"); self.task.disable_paths.extend(v); } - } if let Some(v) = self.task_output.take() { warn_deprecated("task_output"); self.task.output = Some(v); @@ -384,12 +382,11 @@ impl Settings { warn_deprecated("task_show_full_cmd"); self.task.show_full_cmd = v; } - if let Some(v) = self.task_skip.take() { - if !v.is_empty() { + if let Some(v) = self.task_skip.take() + && !v.is_empty() { warn_deprecated("task_skip"); self.task.skip.extend(v); } - } if let Some(v) = self.task_skip_depends { warn_deprecated("task_skip_depends"); self.task.skip_depends = v; From 95b5fc640ba1ad1cea4c24e2991c5e45f4abfbfe Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Wed, 18 Feb 2026 13:48:02 +0000 Subject: [PATCH 3/3] [autofix.ci] apply automated fixes (attempt 2/3) --- src/config/settings.rs | 49 ++++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/src/config/settings.rs b/src/config/settings.rs index 9070be12d4..1582aec7a4 100644 --- a/src/config/settings.rs +++ b/src/config/settings.rs @@ -227,23 +227,24 @@ fn warn_deprecated(key: &str) { meta.deprecated, meta.deprecated_warn_at, meta.deprecated_remove_at, - ) { - use versions::Versioning; - let warn_version = Versioning::new(warn_at).unwrap(); - let remove_version = Versioning::new(remove_at).unwrap(); - debug_assert!( - *crate::cli::version::V < remove_version, - "Deprecated setting [{key}] should have been removed in {remove_at}. Please remove this deprecated setting.", - ); - if *crate::cli::version::V >= warn_version { - let id = Box::leak(format!("setting.{key}").into_boxed_str()); - if crate::output::DEPRECATED.lock().unwrap().insert(id) { - warn!( - "deprecated [setting.{key}]: {msg} This will be removed in mise {remove_at}." - ); - } + ) + { + use versions::Versioning; + let warn_version = Versioning::new(warn_at).unwrap(); + let remove_version = Versioning::new(remove_at).unwrap(); + debug_assert!( + *crate::cli::version::V < remove_version, + "Deprecated setting [{key}] should have been removed in {remove_at}. Please remove this deprecated setting.", + ); + if *crate::cli::version::V >= warn_version { + let id = Box::leak(format!("setting.{key}").into_boxed_str()); + if crate::output::DEPRECATED.lock().unwrap().insert(id) { + warn!( + "deprecated [setting.{key}]: {msg} This will be removed in mise {remove_at}." + ); } } + } } impl Settings { @@ -362,10 +363,11 @@ impl Settings { fn set_hidden_configs(&mut self) { // Migrate task_* settings to task.* (must run before auto_install override below) if let Some(v) = self.task_disable_paths.take() - && !v.is_empty() { - warn_deprecated("task_disable_paths"); - self.task.disable_paths.extend(v); - } + && !v.is_empty() + { + warn_deprecated("task_disable_paths"); + self.task.disable_paths.extend(v); + } if let Some(v) = self.task_output.take() { warn_deprecated("task_output"); self.task.output = Some(v); @@ -383,10 +385,11 @@ impl Settings { self.task.show_full_cmd = v; } if let Some(v) = self.task_skip.take() - && !v.is_empty() { - warn_deprecated("task_skip"); - self.task.skip.extend(v); - } + && !v.is_empty() + { + warn_deprecated("task_skip"); + self.task.skip.extend(v); + } if let Some(v) = self.task_skip_depends { warn_deprecated("task_skip_depends"); self.task.skip_depends = v;