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
4 changes: 4 additions & 0 deletions docs/cli/run.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ Defaults to MISE_AFFECTED_HEAD, CI metadata, or HEAD

Output affected projects and tasks as JSON without running tasks

### `--all`

Open the interactive selector with all tasks from the entire monorepo

### `-c --continue-on-error`

Continue running tasks even if one fails
Expand Down
7 changes: 5 additions & 2 deletions docs/cli/tasks/run.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,7 @@ $ mise run build
Tasks to run
Can specify multiple tasks by separating with `:::`
e.g.: mise run task1 arg1 arg2 ::: task2 arg1 arg2

**Default:** `default`
Defaults to `default` when omitted

### `[ARGS]…`

Expand Down Expand Up @@ -73,6 +72,10 @@ Defaults to MISE_AFFECTED_HEAD, CI metadata, or HEAD

Output affected projects and tasks as JSON without running tasks

### `--all`

Open the interactive selector with all tasks from the entire monorepo

### `-c --continue-on-error`

Continue running tasks even if one fails
Expand Down
35 changes: 35 additions & 0 deletions e2e/tasks/test_task_run_all_picker
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/usr/bin/env bash

export MISE_EXPERIMENTAL=1

cat <<'EOF' >mise.toml
monorepo_root = true

[monorepo]
config_roots = ["projects/frontend"]

[tasks.default]
run = 'echo "default task ran"'
EOF

mkdir -p projects/frontend
cat <<'EOF' >projects/frontend/mise.toml
[tasks.child]
run = 'echo "child task ran"'
EOF

assert "mise run" "default task ran"

# The default task is selected first, so move down once to select the child task.
if [[ $(uname) == Darwin ]]; then
output=$(printf '\033[B\r' | script -q /dev/null mise run --all)
else
output=$(printf '\033[B\r' | script -qec 'mise run --all' /dev/null)
fi
if [[ $output != *"child task ran"* ]]; then
echo "interactive selector did not run the child task"
exit 1
fi

assert_fail_contains "mise run --all default" "cannot be used with"
assert_fail_contains "mise run --all --affected" "cannot be used with"
10 changes: 7 additions & 3 deletions man/man1/mise.1
Original file line number Diff line number Diff line change
Expand Up @@ -3399,6 +3399,9 @@ Defaults to MISE_AFFECTED_HEAD, CI metadata, or HEAD
\fB\-\-affected\-json\fR
Output affected projects and tasks as JSON without running tasks
.TP
\fB\-\-all\fR
Open the interactive selector with all tasks from the entire monorepo
.TP
\fB\-c, \-\-continue\-on\-error\fR
Continue running tasks even if one fails
.TP
Expand Down Expand Up @@ -4222,6 +4225,9 @@ Defaults to MISE_AFFECTED_HEAD, CI metadata, or HEAD
\fB\-\-affected\-json\fR
Output affected projects and tasks as JSON without running tasks
.TP
\fB\-\-all\fR
Open the interactive selector with all tasks from the entire monorepo
.TP
\fB\-c, \-\-continue\-on\-error\fR
Continue running tasks even if one fails
.TP
Expand Down Expand Up @@ -4359,9 +4365,7 @@ Default to always show with `MISE_TASK_TIMINGS=1`
Tasks to run
Can specify multiple tasks by separating with `:::`
e.g.: mise run task1 arg1 arg2 ::: task2 arg1 arg2
.RS
\fIDefault: \fRdefault
.RE
Defaults to `default` when omitted
.TP
\fB<ARGS>\fR
Arguments to pass to the tasks. Use ":::" to separate tasks
Expand Down
5 changes: 4 additions & 1 deletion mise.usage.kdl
Original file line number Diff line number Diff line change
Expand Up @@ -3230,6 +3230,7 @@ Defaults to MISE_AFFECTED_HEAD, CI metadata, or HEAD
arg <REV>
}
flag --affected-json help="Output affected projects and tasks as JSON without running tasks"
flag --all help="Open the interactive selector with all tasks from the entire monorepo"
flag "-c --continue-on-error" help="Continue running tasks even if one fails"
flag "-C --cd" help="Change to this directory before executing the command" {
arg <CD>
Expand Down Expand Up @@ -4085,6 +4086,7 @@ Defaults to MISE_AFFECTED_HEAD, CI metadata, or HEAD
arg <REV>
}
flag --affected-json help="Output affected projects and tasks as JSON without running tasks"
flag --all help="Open the interactive selector with all tasks from the entire monorepo"
flag "-c --continue-on-error" help="Continue running tasks even if one fails"
flag "-C --cd" help="Change to this directory before executing the command" {
arg <CD>
Expand Down Expand Up @@ -4206,7 +4208,8 @@ Default to always show with `MISE_TASK_TIMINGS=1`
Tasks to run
Can specify multiple tasks by separating with `:::`
e.g.: mise run task1 arg1 arg2 ::: task2 arg1 arg2
"""# required=#false default=default
Defaults to `default` when omitted
"""# required=#false
arg "[ARGS]…" help="Arguments to pass to the tasks. Use \":::\" to separate tasks" required=#false var=#true
arg "[-- ARGS_LAST]…" help="Arguments to pass to the tasks. Use \":::\" to separate tasks" required=#false var=#true hide=#true
mount run="mise tasks --usage"
Expand Down
3 changes: 2 additions & 1 deletion src/cli/bootstrap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1619,9 +1619,10 @@ impl Bootstrap {

async fn run_task(&self, task: &str, skip_tools: bool) -> Result<()> {
run::Run {
task: task.into(),
task: Some(task.into()),
args: vec![],
args_last: vec![],
all: false,
affected: false,
affected_base: None,
affected_head: None,
Expand Down
3 changes: 2 additions & 1 deletion src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -781,9 +781,10 @@ impl Cli {
};
if tasks.iter().any(|(_, t)| t.is_match(&task)) {
return Ok(Commands::Run(Box::new(run::Run {
task,
task: Some(task),
args: self.task_args.unwrap_or_default(),
args_last: self.task_args_last,
all: false,
affected: false,
affected_base: None,
affected_head: None,
Expand Down
35 changes: 20 additions & 15 deletions src/cli/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,9 @@ pub struct Run {
/// Tasks to run
/// Can specify multiple tasks by separating with `:::`
/// e.g.: mise run task1 arg1 arg2 ::: task2 arg1 arg2
#[clap(
allow_hyphen_values = true,
verbatim_doc_comment,
default_value = "default"
)]
pub task: String,
/// Defaults to `default` when omitted
#[clap(allow_hyphen_values = true, verbatim_doc_comment)]
pub task: Option<String>,

/// Arguments to pass to the tasks. Use ":::" to separate tasks.
#[clap(allow_hyphen_values = true)]
Expand Down Expand Up @@ -108,6 +105,10 @@ pub struct Run {
)]
pub affected_json: bool,

/// Open the interactive selector with all tasks from the entire monorepo
#[clap(long, conflicts_with_all = ["task", "affected"], verbatim_doc_comment)]
pub all: bool,

/// Continue running tasks even if one fails
#[clap(long, short = 'c', verbatim_doc_comment)]
pub continue_on_error: bool,
Expand Down Expand Up @@ -395,7 +396,7 @@ async fn get_affected_task_list(
.collect::<BTreeSet<_>>();

let args = affected_task_args(args);
let mut tasks = get_task_lists(config, &args, true, only).await?;
let mut tasks = get_task_lists(config, &args, true, only, false).await?;
// Restrict only the task-pattern matches. `Run::run` calls `resolve_depends`
// after this returns, so prerequisites from unaffected projects remain intact.
tasks.retain(|task| {
Expand Down Expand Up @@ -558,15 +559,17 @@ fn display_affected_text(text: &str) -> String {
impl Run {
pub async fn run(mut self) -> Result<()> {
// Check help flags before doing any work
if self.task == "-h" {
if self.task.as_deref() == Some("-h") {
self.get_clap_command().print_help()?;
return Ok(());
}
if self.task == "--help" {
if self.task.as_deref() == Some("--help") {
self.get_clap_command().print_long_help()?;
return Ok(());
}

let task = self.task.clone().unwrap_or_else(|| "default".to_string());

Settings::ensure_not_safe("running tasks")?;

// Unescape task args early so we can check for help flags
Expand All @@ -589,7 +592,7 @@ impl Run {
// Handle task help early to avoid unnecessary toolset/deps work
if has_help_in_task_args {
// Build args list to get the task (filter out --help/-h for task lookup)
let args = once(self.task.clone())
let args = once(task.clone())
.chain(
self.args
.iter()
Expand All @@ -598,7 +601,7 @@ impl Run {
)
.collect_vec();

let task_list = get_task_lists(&config, &args, false, false).await?;
let task_list = get_task_lists(&config, &args, false, false, false).await?;

if let Some(task) = task_list.first() {
// raw_args tasks act as proxies for tools that handle their
Expand Down Expand Up @@ -633,9 +636,11 @@ impl Run {
self.tmpdir = tmpdir.path().to_path_buf();

// Build args list - don't include args_last yet, they'll be added after task resolution
let args = once(self.task.clone())
.chain(self.args.clone())
.collect_vec();
let args = if self.all {
vec![]
} else {
once(task).chain(self.args.clone()).collect_vec()
};

let mut task_list = if self.affected {
get_affected_task_list(
Expand All @@ -649,7 +654,7 @@ impl Run {
)
.await?
} else {
get_task_lists(&config, &args, true, self.skip_deps).await?
get_task_lists(&config, &args, true, self.skip_deps, self.all).await?
};
if self.affected_json {
return Ok(());
Expand Down
3 changes: 2 additions & 1 deletion src/cli/watch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@ impl Watch {
if args.is_empty() {
args.push("default".to_string());
}
let tasks = crate::task::task_list::get_task_lists(&config, &args, false, false).await?;
let tasks =
crate::task::task_list::get_task_lists(&config, &args, false, false, false).await?;
let watched_tasks = if self.skip_deps {
tasks.to_vec()
} else {
Expand Down
19 changes: 15 additions & 4 deletions src/task/task_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -367,9 +367,9 @@ async fn err_no_task(
}

/// Prompt the user to select a task interactively
async fn prompt_for_task() -> Result<Task> {
let config = Config::get().await?;
let tasks = config.tasks().await?;
async fn prompt_for_task(config: &Arc<Config>, load_all: bool) -> Result<Task> {
let ctx = load_all.then(TaskLoadContext::all);
let tasks = config.tasks_with_context(ctx.as_ref()).await?;
ensure!(
!tasks.is_empty(),
"no tasks defined. see {url}",
Expand Down Expand Up @@ -410,7 +410,18 @@ pub async fn get_task_lists(
args: &[String],
prompt: bool,
only: bool,
prompt_all: bool,
) -> Result<Vec<Task>> {
if prompt_all {
let mut task = prompt_for_task(config, true).await?;
if only {
task.depends.clear();
task.depends_post.clear();
task.wait_for.clear();
}
return Ok(vec![task]);
}

let args = args
.iter()
.map(|s| vec![s.to_string()])
Expand Down Expand Up @@ -546,7 +557,7 @@ pub async fn get_task_lists(
tasks.push(task.with_args(args));
continue;
}
tasks.push(prompt_for_task().await?);
tasks.push(prompt_for_task(config, false).await?);
} else {
cur_tasks
.into_iter()
Expand Down