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
14 changes: 13 additions & 1 deletion docs/hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,19 @@ patterns = ["src/**/*.rs"]
run = "cargo fmt"
```

You can also reference a mise task instead of an inline script:
By default, `run` uses the configured inline shell:
[`unix_default_inline_shell_args`](/configuration/settings.html#unix_default_inline_shell_args)
or [`windows_default_inline_shell_args`](/configuration/settings.html#windows_default_inline_shell_args).
Add `shell = "bash -c"` to choose a different inline shell command for a watch file hook:

```toml
[[watch_files]]
patterns = ["*.js"]
run = "eslint --fix ."
shell = "bash -c"
```

`shell` only applies to `run` hooks. You can also reference a mise task instead of an inline script:

```toml
[[watch_files]]
Expand Down
16 changes: 15 additions & 1 deletion e2e/config/test_schema_tombi
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ cat >"$HOME/workdir/mise.toml" <<'TOML'
env_file = ".env"
env_path = "./bin"

[[watch_files]]
patterns = ["src/**/*.rs"]
run = "cargo fmt"
shell = "bash -c"

[task_templates.base]
quiet = true
dir = "{{config_root}}"
Expand Down Expand Up @@ -119,7 +124,7 @@ strict = true

[[schemas]]
path = "file://$SCHEMA_PATH"
include = ["mise-bad.toml", "mise-bad-age.toml", "mise-bad-env-directive.toml", "mise-bad-tmpl.toml", "mise-bad-os.toml"]
include = ["mise-bad.toml", "mise-bad-age.toml", "mise-bad-env-directive.toml", "mise-bad-tmpl.toml", "mise-bad-os.toml", "mise-bad-watch-files.toml"]

[[schemas]]
path = "file://$TASK_SCHEMA_PATH"
Expand Down Expand Up @@ -160,6 +165,15 @@ TOML

assert_fail "$TOMBI_LINT mise-bad-os.toml"

cat >"$HOME/workdir/mise-bad-watch-files.toml" <<'TOML'
[[watch_files]]
patterns = ["uv.lock"]
task = "sync-deps"
shell = "bash -c"
TOML

assert_fail "$TOMBI_LINT mise-bad-watch-files.toml"

cat >"$HOME/workdir/mise-bad-task-os.toml" <<'TOML'
[build]
run = "echo ok"
Expand Down
49 changes: 49 additions & 0 deletions e2e/config/test_watch_files_shell
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/usr/bin/env bash

cat >"$HOME/watch-fallback-shell" <<'SCRIPT'
#!/usr/bin/env bash
if [[ $1 != "-c" ]]; then
exit 99
fi
echo WATCH_FALLBACK_SHELL
exec bash -c "$2"
SCRIPT
chmod +x "$HOME/watch-fallback-shell"

cat >"$HOME/watch-explicit-shell" <<'SCRIPT'
#!/usr/bin/env bash
if [[ $1 != "-c" ]]; then
exit 99
fi
echo WATCH_EXPLICIT_SHELL
exec bash -c "$2"
SCRIPT
chmod +x "$HOME/watch-explicit-shell"

cat <<EOF >mise.toml
[settings]
unix_default_inline_shell_args = "$HOME/watch-fallback-shell -c"

[[watch_files]]
patterns = ["fallback.txt"]
run = "echo WATCH_FALLBACK_RUN"

[[watch_files]]
patterns = ["explicit.txt"]
run = "echo WATCH_EXPLICIT_RUN"
shell = "$HOME/watch-explicit-shell -c"
EOF

touch fallback.txt explicit.txt
eval "$(mise hook-env)"

sleep 1
printf "changed" >>fallback.txt
printf "changed" >>explicit.txt

mise hook-env >hook-env.out 2>&1

assert_contains "cat hook-env.out" "WATCH_FALLBACK_SHELL"
assert_contains "cat hook-env.out" "WATCH_FALLBACK_RUN"
assert_contains "cat hook-env.out" "WATCH_EXPLICIT_SHELL"
assert_contains "cat hook-env.out" "WATCH_EXPLICIT_RUN"
50 changes: 32 additions & 18 deletions schema/mise.json
Original file line number Diff line number Diff line change
Expand Up @@ -2127,29 +2127,43 @@
"items": {
"type": "object",
"description": "file to watch for changes",
"unevaluatedProperties": false,
"properties": {
"run": {
"type": "string",
"description": "script to run when file changes"
},
"task": {
"type": "string",
"description": "mise task to run when file changes"
},
"patterns": {
"type": "array",
"description": "patterns to watch for",
"items": {
"type": "string"
}
}
},
"oneOf": [
{
"unevaluatedProperties": false,
"properties": {
"run": {
"type": "string",
"description": "script to run when file changes"
},
"shell": {
"type": "string",
"description": "inline shell command used to run script when file changes"
},
"patterns": {
"type": "array",
"description": "patterns to watch for",
"items": {
"type": "string"
}
}
},
"required": ["run"]
},
{
"unevaluatedProperties": false,
"properties": {
"task": {
"type": "string",
"description": "mise task to run when file changes"
},
"patterns": {
"type": "array",
"description": "patterns to watch for",
"items": {
"type": "string"
}
}
},
"required": ["task"]
}
]
Expand Down
5 changes: 5 additions & 0 deletions src/config/config_file/mise_toml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -945,6 +945,11 @@ impl ConfigFile for MiseToml {
.as_ref()
.map(|r| self.parse_template(r))
.transpose()?,
shell: wf
.shell
.as_ref()
.map(|s| self.parse_template(s))
.transpose()?,
task: wf
.task
.as_ref()
Expand Down
23 changes: 18 additions & 5 deletions src/watch_files.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ pub struct WatchFile {
pub patterns: Vec<String>,
#[serde(default)]
pub run: Option<String>,
#[serde(default)]
pub shell: Option<String>,
pub task: Option<String>,
}

Expand Down Expand Up @@ -45,10 +47,13 @@ pub async fn execute_runs(config: &Arc<Config>, ts: &Toolset) {
if wf.task.is_some() && wf.run.is_some() {
warn!("watch_file hook has both run and task set, using task");
}
if wf.task.is_some() && wf.shell.is_some() {
warn!("watch_file hook has both shell and task set, ignoring shell");
}
let result = if let Some(task_name) = &wf.task {
execute_task(config, ts, &root, task_name, files).await
} else if let Some(run) = &wf.run {
execute(config, ts, &root, run, files).await
execute(config, ts, &root, run, wf.shell.as_deref(), files).await
} else {
warn!("watch_file hook has neither run nor task set, skipping");
continue;
Expand All @@ -69,18 +74,26 @@ async fn execute(
ts: &Toolset,
root: &Path,
run: &str,
shell: Option<&str>,
files: Vec<&PathBuf>,
) -> Result<()> {
Settings::get().ensure_experimental("watch_file_hooks")?;
let modified_files_var = files
.iter()
.map(|f| f.to_string_lossy().replace(':', "\\:"))
.join(":");
let shell = Settings::get().default_inline_shell()?;
let shell = match shell {
Some(shell) => shell_words::split(shell)?,
None => Settings::get().default_inline_shell()?,
};
let (program, shell_args) = shell.split_first().ok_or_else(|| {
eyre::eyre!(
"inline shell is empty; check watch_files.shell or unix_default_inline_shell_args / windows_default_inline_shell_args"
)
})?;

let args = shell
let args = shell_args
.iter()
.skip(1)
.map(|s| s.as_str())
.chain(once(run))
.collect_vec();
Expand All @@ -98,7 +111,7 @@ async fn execute(
);
// TODO: this should be different but I don't have easy access to it
// env.insert("MISE_CONFIG_ROOT".to_string(), root.to_string_lossy().to_string());
cmd(&shell[0], args)
cmd(program, args)
.stdout_to_stderr()
// .dir(root)
.full_env(env)
Expand Down
Loading