-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
feat(task): add task templates for reusable task definitions #7873
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 1 commit
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
20684af
feat(task): add task templates for reusable task definitions
jdx ac85962
docs(task): add documentation for task templates feature
jdx f71dd60
docs(task): remove error handling section from templates doc
jdx a7b84a8
docs(task): add file task templates to future enhancements
jdx 78aaf7d
docs(task): fix Vue template syntax escaping in templates doc
jdx 35f7c09
[autofix.ci] apply automated fixes
autofix-ci[bot] 4b81bf7
[autofix.ci] apply automated fixes (attempt 2/3)
autofix-ci[bot] d2a9296
fix(task): don't merge boolean fields from templates
jdx File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,184 @@ | ||
| #!/usr/bin/env bash | ||
|
|
||
| # Test task templates feature (requires experimental = true) | ||
|
|
||
| # First, test that extends fails without experimental flag | ||
| cat <<'EOF' >mise.toml | ||
| [task_templates."python:build"] | ||
| run = "echo building python" | ||
| description = "Build a Python project" | ||
|
|
||
| [tasks.build] | ||
| extends = "python:build" | ||
| EOF | ||
|
|
||
| # Should fail without experimental mode (override the env var) | ||
| assert_fail "MISE_EXPERIMENTAL=0 mise run build" "experimental = true" | ||
|
|
||
| # Now enable experimental mode and test basic template functionality | ||
| cat <<'EOF' >mise.toml | ||
| [settings] | ||
| experimental = true | ||
|
|
||
| [task_templates."python:build"] | ||
| run = "echo building python" | ||
| description = "Build a Python project" | ||
|
|
||
| [tasks.build] | ||
| extends = "python:build" | ||
| EOF | ||
|
|
||
| # Basic template extension should work | ||
| assert "mise run build" "building python" | ||
|
|
||
| # Test local override of run command | ||
| cat <<'EOF' >mise.toml | ||
| [settings] | ||
| experimental = true | ||
|
|
||
| [task_templates."python:build"] | ||
| run = "echo template build" | ||
| description = "Template description" | ||
|
|
||
| [tasks.build] | ||
| extends = "python:build" | ||
| run = "echo local build" | ||
| EOF | ||
|
|
||
| assert "mise run build" "local build" | ||
|
|
||
| # Test tools deep merge | ||
| cat <<'EOF' >mise.toml | ||
| [settings] | ||
| experimental = true | ||
|
|
||
| [task_templates."python:build"] | ||
| run = "echo tools: python={{ env.MISE_TOOL_OPTS_PYTHON | default(value='none') }} node={{ env.MISE_TOOL_OPTS_NODE | default(value='none') }}" | ||
| tools = { python = "3.12", node = "18" } | ||
|
|
||
| [tasks.build] | ||
| extends = "python:build" | ||
| tools = { node = "20" } # Override node version, keep python from template | ||
| EOF | ||
|
|
||
| # The tools should be merged - task list should show both tools | ||
| assert_contains "mise tasks build" "python" | ||
| assert_contains "mise tasks build" "node" | ||
|
|
||
| # Test env deep merge | ||
| cat <<'EOF' >mise.toml | ||
| [settings] | ||
| experimental = true | ||
|
|
||
| [task_templates."python:build"] | ||
| run = "echo FOO=$FOO BAR=$BAR" | ||
| env = { FOO = "template_foo", BAR = "template_bar" } | ||
|
|
||
| [tasks.build] | ||
| extends = "python:build" | ||
| env = { FOO = "local_foo" } # Override FOO, keep BAR from template | ||
| EOF | ||
|
|
||
| assert "mise run build" "FOO=local_foo BAR=template_bar" | ||
|
|
||
| # Test description from template (when local is empty) | ||
| cat <<'EOF' >mise.toml | ||
| [settings] | ||
| experimental = true | ||
|
|
||
| [task_templates."python:build"] | ||
| run = "echo build" | ||
| description = "Template description" | ||
|
|
||
| [tasks.build] | ||
| extends = "python:build" | ||
| EOF | ||
|
|
||
| assert_contains "mise tasks build" "Template description" | ||
|
|
||
| # Test local description overrides template | ||
| cat <<'EOF' >mise.toml | ||
| [settings] | ||
| experimental = true | ||
|
|
||
| [task_templates."python:build"] | ||
| run = "echo build" | ||
| description = "Template description" | ||
|
|
||
| [tasks.build] | ||
| extends = "python:build" | ||
| description = "Local description" | ||
| EOF | ||
|
|
||
| assert_contains "mise tasks build" "Local description" | ||
| assert_not_contains "mise tasks build" "Template description" | ||
|
|
||
| # Test depends from template (using task name, not :prefix syntax) | ||
| cat <<'EOF' >mise.toml | ||
| [settings] | ||
| experimental = true | ||
|
|
||
| [task_templates."python:test"] | ||
| run = "echo testing" | ||
| depends = ["build"] | ||
|
|
||
| [tasks.build] | ||
| run = "echo building" | ||
|
|
||
| [tasks.test] | ||
| extends = "python:test" | ||
| EOF | ||
|
|
||
| assert "mise run test" "building | ||
| testing" | ||
|
|
||
| # Test depends local override (local takes precedence completely) | ||
| cat <<'EOF' >mise.toml | ||
| [settings] | ||
| experimental = true | ||
|
|
||
| [task_templates."python:test"] | ||
| run = "echo testing" | ||
| depends = ["prep"] | ||
|
|
||
| [tasks.prep] | ||
| run = "echo prep" | ||
|
|
||
| [tasks.lint] | ||
| run = "echo linting" | ||
|
|
||
| [tasks.test] | ||
| extends = "python:test" | ||
| depends = ["lint"] # Override, should NOT also run prep | ||
| EOF | ||
|
|
||
| assert "mise run test" "linting | ||
| testing" | ||
| assert_not_contains "mise run test" "prep" | ||
|
|
||
| # Test template not found error | ||
| cat <<'EOF' >mise.toml | ||
| [settings] | ||
| experimental = true | ||
|
|
||
| [tasks.build] | ||
| extends = "nonexistent:template" | ||
| EOF | ||
|
|
||
| assert_fail "mise run build" "not found" | ||
|
|
||
| # Test namespaced template names with colons | ||
| cat <<'EOF' >mise.toml | ||
| [settings] | ||
| experimental = true | ||
|
|
||
| [task_templates."rust:cargo:build"] | ||
| run = "echo cargo build" | ||
|
|
||
| [tasks.build] | ||
| extends = "rust:cargo:build" | ||
| EOF | ||
|
|
||
| assert "mise run build" "cargo build" | ||
|
|
||
| echo '' >mise.toml | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,7 +28,7 @@ use crate::hooks::{Hook, Hooks}; | |
| use crate::prepare::PrepareConfig; | ||
| use crate::redactions::Redactions; | ||
| use crate::registry::REGISTRY; | ||
| use crate::task::Task; | ||
| use crate::task::{Task, TaskTemplate}; | ||
| use crate::tera::{BASE_CONTEXT, get_tera}; | ||
| use crate::toolset::{ToolRequest, ToolRequestSet, ToolSource, ToolVersionOptions}; | ||
| use crate::watch_files::WatchFile; | ||
|
|
@@ -74,6 +74,8 @@ pub struct MiseToml { | |
| #[serde(default)] | ||
| tasks: Tasks, | ||
| #[serde(default)] | ||
| task_templates: TaskTemplates, | ||
| #[serde(default)] | ||
| watch_files: Vec<WatchFile>, | ||
| #[serde(default)] | ||
| prepare: Option<PrepareConfig>, | ||
|
|
@@ -101,6 +103,9 @@ pub struct MiseTomlTool { | |
| #[derive(Debug, Default, Clone)] | ||
| pub struct Tasks(pub BTreeMap<String, Task>); | ||
|
|
||
| #[derive(Debug, Default, Clone)] | ||
| pub struct TaskTemplates(pub IndexMap<String, TaskTemplate>); | ||
|
|
||
| #[derive(Debug, Default, Clone)] | ||
| pub struct EnvList(pub(crate) Vec<EnvDirective>); | ||
|
|
||
|
|
@@ -508,6 +513,10 @@ impl ConfigFile for MiseToml { | |
| self.tasks.0.values().collect() | ||
| } | ||
|
|
||
| fn task_templates(&self) -> IndexMap<String, TaskTemplate> { | ||
| self.task_templates.0.clone() | ||
| } | ||
|
|
||
| fn remove_tool(&self, fa: &BackendArg) -> eyre::Result<()> { | ||
| let mut tools = self.tools.lock().unwrap(); | ||
| tools.shift_remove(fa); | ||
|
|
@@ -865,6 +874,7 @@ impl Clone for MiseToml { | |
| redactions: self.redactions.clone(), | ||
| plugins: self.plugins.clone(), | ||
| tasks: self.tasks.clone(), | ||
| task_templates: self.task_templates.clone(), | ||
|
||
| task_config: self.task_config.clone(), | ||
| settings: self.settings.clone(), | ||
| watch_files: self.watch_files.clone(), | ||
|
|
@@ -1717,6 +1727,36 @@ impl<'de> de::Deserialize<'de> for Tasks { | |
| } | ||
| } | ||
|
|
||
| impl<'de> de::Deserialize<'de> for TaskTemplates { | ||
| fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error> | ||
| where | ||
| D: de::Deserializer<'de>, | ||
| { | ||
| struct TaskTemplatesVisitor; | ||
|
|
||
| impl<'de> Visitor<'de> for TaskTemplatesVisitor { | ||
| type Value = TaskTemplates; | ||
| fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { | ||
| formatter.write_str("map of task template names to template definitions") | ||
| } | ||
|
|
||
| fn visit_map<M>(self, mut map: M) -> std::result::Result<Self::Value, M::Error> | ||
| where | ||
| M: de::MapAccess<'de>, | ||
| { | ||
| let mut templates = IndexMap::new(); | ||
| while let Some(name) = map.next_key::<String>()? { | ||
| let template: TaskTemplate = map.next_value()?; | ||
| templates.insert(name, template); | ||
| } | ||
| Ok(TaskTemplates(templates)) | ||
| } | ||
| } | ||
|
|
||
| deserializer.deserialize_any(TaskTemplatesVisitor) | ||
| } | ||
| } | ||
|
|
||
| impl<'de> de::Deserialize<'de> for BackendArg { | ||
| fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error> | ||
| where | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test cleanup at the end uses
echo '' >mise.tomlwhich creates a file with a newline. Consider usingrm -f mise.tomlortrue >mise.tomlfor cleaner test isolation, as the empty file with a newline could affect subsequent tests.