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
54 changes: 54 additions & 0 deletions e2e/tasks/test_task_includes_glob
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#!/usr/bin/env bash

# Test glob pattern support in task_config.includes
# See: https://github.com/jdx/mise/discussions/7860

# Create a tasks directory with multiple task files
# Note: Standalone task TOML files use a different format than mise.toml
mkdir -p tasks

cat <<EOF >tasks/build.toml
[build]
run = 'echo "building"'
EOF

cat <<EOF >tasks/test.toml
[test]
run = 'echo "testing"'
EOF

cat <<EOF >tasks/deploy.toml
[deploy]
run = 'echo "deploying"'
EOF

# Create mise.toml with glob pattern in includes
cat <<EOF >mise.toml
[task_config]
includes = ["tasks/*.toml"]
EOF

# Test that all tasks are discovered via glob pattern
assert_contains "mise tasks" "build"
assert_contains "mise tasks" "test"
assert_contains "mise tasks" "deploy"

# Test that the tasks actually run
assert_contains "mise run build" "building"
assert_contains "mise run test" "testing"
assert_contains "mise run deploy" "deploying"

# Test mixing glob with literal paths
cat <<EOF >extra-tasks.toml
[extra]
run = 'echo "extra task"'
EOF

cat <<EOF >mise.toml
[task_config]
includes = ["tasks/*.toml", "extra-tasks.toml"]
EOF

assert_contains "mise tasks" "build"
assert_contains "mise tasks" "extra"
assert_contains "mise run extra" "extra task"
74 changes: 55 additions & 19 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2033,6 +2033,50 @@ async fn resolve_git_url_to_path(git_url: &str) -> Result<PathBuf> {
}
}

/// Check if a pattern contains glob metacharacters
fn is_glob_pattern(pattern: &str) -> bool {
// Check for unescaped glob metacharacters: *, ?, [, ], {, }
// Note: This is a simple check that may have false positives with escaped chars,
// but glob() will handle those correctly
pattern.contains('*')
|| pattern.contains('?')
|| pattern.contains('[')
|| pattern.contains(']')
|| pattern.contains('{')
|| pattern.contains('}')
}

/// Expand a task include pattern (which may be a glob) to a list of paths
fn expand_task_include(dir: &Path, pattern: &str) -> Vec<PathBuf> {
if is_glob_pattern(pattern) {
match glob(dir, pattern) {
Ok(paths) => paths,
Err(err) => {
warn!(
"failed to expand glob pattern '{}' in '{}': {}",
pattern,
display_path(dir),
err
);
vec![]
}
}
} else {
// Literal path
let path = PathBuf::from(pattern);
let resolved = if path.is_absolute() {
path
} else {
dir.join(path)
};
if resolved.exists() {
vec![resolved]
} else {
vec![]
}
}
}

async fn load_file_tasks(
config: &Arc<Config>,
cf: Arc<dyn ConfigFile>,
Expand All @@ -2046,14 +2090,17 @@ async fn load_file_tasks(

let mut tasks = vec![];
let config_root = Arc::new(config_root.to_path_buf());
let cf_dir = cf.get_path().parent().unwrap();

for include in includes {
let path = if include.starts_with("git::") {
resolve_git_url_to_path(&include).await?
let paths = if include.starts_with("git::") {
vec![resolve_git_url_to_path(&include).await?]
} else {
cf.get_path().parent().unwrap().join(&include)
expand_task_include(cf_dir, &include)
};
tasks.extend(load_tasks_includes(config, &path, &config_root).await?);
for path in paths {
tasks.extend(load_tasks_includes(config, &path, &config_root).await?);
}
}
Ok(tasks)
}
Expand All @@ -2065,23 +2112,12 @@ pub fn task_includes_for_dir(dir: &Path, config_files: &ConfigMap) -> Vec<PathBu
.find_map(|cf| cf.task_config().includes.clone())
.unwrap_or_else(default_task_includes)
.into_iter()
.filter_map(|p| {
// Git URLs will be handled by load_file_tasks
.flat_map(|p| {
// Git URLs are handled by load_file_tasks, not here
if p.starts_with("git::") {
None
} else {
let path = PathBuf::from(p);
let resolved = if path.is_absolute() {
path
} else {
dir.join(path)
};
if resolved.exists() {
Some(resolved)
} else {
None
}
return vec![];
}
expand_task_include(dir, &p)
})
.unique()
.collect::<Vec<_>>()
Expand Down
Loading