Skip to content

Commit

Permalink
feat: hooks
Browse files Browse the repository at this point in the history
  • Loading branch information
jdx committed Nov 29, 2024
1 parent da6db80 commit fb29e8c
Show file tree
Hide file tree
Showing 19 changed files with 600 additions and 49 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ fslock = "0.2.1"
git2 = "<1"
glob = "0.3"
globset = "0.4"
globwalk = "0.9"
heck = "0.5"
home = "0.5"
humantime = "2"
Expand Down
62 changes: 62 additions & 0 deletions docs/hooks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Hooks

You can have mise automatically execute scripts when it runs. The configuration goes into `mise.toml`.

## CD hook

This hook is run anytimes the directory is changed.

```toml
[hooks]
cd = "echo 'I changed directories'"
```

## Enter hook

This hook is run when the project is entered. Changing directories while in the project will not trigger this hook again.

```toml
[hooks]
enter = "echo 'I entered the project'"
```

## Leave hook (not yet implemented)

This hook is run when the project is left. Changing directories while in the project will not trigger this hook.

```toml
[hooks]
leave = "echo 'I left the project'"
```

## Preinstall/postinstall hook

These hooks are run before tools are installed. Unlike other hooks, these hooks do not require `mise activate`.

```toml
[hooks]
preinstall = "echo 'I am about to install tools'"
postinstall = "echo 'I just installed tools'"
```

## Watch files hook

While using `mise activate` you can have mise watch files for changes and execute a script when a file changes.

```bash
[[watch_files]]
patterns = ["src/**/*.rs"]
script = "cargo fmt"
```

This hook will have the following environment variables set:

- `MISE_WATCH_FILES_MODIFIED`: A colon-separated list of the files that have been modified. Colons are escaped with a backslash.

## Hook execution

Hooks are executed with the following environment variables set:

- `MISE_ORIGINAL_CWD`: The directory that the user is in.
- `MISE_PROJECT_DIR`: The root directory of the project.
- `MISE_PREVIOUS_DIR`: The directory that the user was in before the directory change (only if a directory change occurred).
14 changes: 14 additions & 0 deletions e2e/config/test_hooks
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/env bash

#cat <<EOF >mise.toml
#[tools]
#dummy = 'latest'
#[hooks]
#enter = 'echo ENTER'
#leave = 'echo LEAVE'
#cd = 'echo CD'
#preinstall = 'echo PREINSTALL'
#postinstall = 'echo POSTINSTALL'
#EOF
#
#assert "mise i" "PREINSTALL\nPOSTINSTALL\n"
49 changes: 49 additions & 0 deletions schema/mise.json
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,13 @@
}
}
},
"shell": {
"description": "Sets the shell across all mise commands like `mise run`, `mise en`, and watch files.",
"type": "array",
"items": {
"type": "string"
}
},
"shorthands_file": {
"description": "Path to a file containing custom tool shorthands.",
"type": "string"
Expand Down Expand Up @@ -940,6 +947,39 @@
}
}
]
},
"hooks": {
"description": "hooks to run",
"type": "object",
"additionalProperties": {
"description": "script to run",
"type": "string"
}
},
"watch_files": {
"description": "files to watch for changes",
"type": "array",
"items": {
"type": "object",
"description": "file to watch for changes",
"additionalProperties": false,
"properties": {
"run": {
"type": "string",
"description": "script to run when file changes",
"items": {
"type": "string"
}
},
"patterns": {
"type": "array",
"description": "patterns to watch for",
"items": {
"type": "string"
}
}
}
}
}
},
"additionalProperties": false,
Expand Down Expand Up @@ -1024,6 +1064,15 @@
"description": "dev tools to use",
"type": "object"
},
"hooks": {
"$ref": "#/$defs/hooks"
},
"vars": {
"$ref": "#/$defs/vars"
},
"watch_files": {
"$ref": "#/$defs/watch_files"
},
"_": {
"additionalProperties": true
}
Expand Down
10 changes: 10 additions & 0 deletions settings.toml
Original file line number Diff line number Diff line change
Expand Up @@ -762,6 +762,16 @@ type = "Path"
optional = true
description = "Path to the rustup home directory. Defaults to ~/.rustup or %USERPROFILE%\\.rustup"

[shell]
env = "MISE_SHELL"
type = "ListString"
optional = true
description = "Sets the shell across all mise commands like `mise run`, `mise en`, and watch files."
docs = """
Sets the shell across all mise commands like `mise run`, `mise en`, and watch files.
This overrides MISE_UNIX_DEFAULT_INLINE_SHELL_ARGS and MISE_WINDOWS_DEFAULT_INLINE_SHELL_ARGS if set.
"""

[shorthands_file]
env = "MISE_SHORTHANDS_FILE"
type = "Path"
Expand Down
13 changes: 8 additions & 5 deletions src/cli/hook_env.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::env::{join_paths, split_paths};
use std::ops::Deref;
use std::path::{Path, PathBuf};
use std::path::PathBuf;

use console::truncate_str;
use eyre::Result;
Expand All @@ -10,9 +10,10 @@ use crate::config::{Config, Settings};
use crate::direnv::DirenvDiff;
use crate::env::{PATH_KEY, TERM_WIDTH, __MISE_DIFF};
use crate::env_diff::{EnvDiff, EnvDiffOperation};
use crate::hook_env::WatchFilePattern;
use crate::shell::{get_shell, ShellType};
use crate::toolset::{Toolset, ToolsetBuilder};
use crate::{dirs, env, hook_env};
use crate::{dirs, env, hook_env, hooks, watch_files};

/// [internal] called by activate hook to update env vars directory change
#[derive(Debug, clap::Args)]
Expand All @@ -36,7 +37,7 @@ impl HookEnv {
let config = Config::try_get()?;
let watch_files = config.watch_files()?;
time!("hook-env");
if hook_env::should_exit_early(&watch_files) {
if hook_env::should_exit_early(watch_files.clone()) {
return Ok(());
}
time!("should_exit_early");
Expand All @@ -58,12 +59,14 @@ impl HookEnv {
let settings = Settings::try_get()?;
patches.extend(self.build_path_operations(&settings, &paths, &__MISE_DIFF.path)?);
patches.push(self.build_diff_operation(&diff)?);
patches.push(self.build_watch_operation(&watch_files)?);
patches.push(self.build_watch_operation(watch_files.clone())?);
patches.push(self.build_dir_operation()?);

let output = hook_env::build_env_commands(&*shell, &patches);
miseprint!("{output}")?;
self.display_status(&config, &ts)?;
hooks::run_all_hooks(&ts);
watch_files::execute_runs(&ts);

Ok(())
}
Expand Down Expand Up @@ -180,7 +183,7 @@ impl HookEnv {

fn build_watch_operation(
&self,
watch_files: impl IntoIterator<Item = impl AsRef<Path>>,
watch_files: impl IntoIterator<Item = WatchFilePattern>,
) -> Result<EnvDiffOperation> {
let watches = hook_env::build_watches(watch_files)?;
Ok(EnvDiffOperation::Add(
Expand Down
8 changes: 4 additions & 4 deletions src/cli/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -578,7 +578,10 @@ impl Run {
prefix: &str,
) -> Result<()> {
let program = program.to_executable();
let mut cmd = CmdLineRunner::new(program.clone()).args(args).envs(env);
let mut cmd = CmdLineRunner::new(program.clone())
.args(args)
.envs(env)
.raw(self.raw(task));
cmd.with_pass_signals();
match self.output {
TaskOutput::Prefix => cmd = cmd.prefix(format!("{prefix} ")),
Expand All @@ -589,9 +592,6 @@ impl Run {
.stderr(Stdio::inherit())
}
}
if self.raw(task) {
cmd.with_raw();
}
let dir = self.cwd(task)?;
if !dir.exists() {
eprintln!(
Expand Down
4 changes: 2 additions & 2 deletions src/cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,8 +247,8 @@ impl<'a> CmdLineRunner<'a> {
self.pr = Some(pr);
self
}
pub fn with_raw(&mut self) -> &mut Self {
self.raw = true;
pub fn raw(mut self, raw: bool) -> Self {
self.raw = raw;
self
}

Expand Down
34 changes: 34 additions & 0 deletions src/config/config_file/mise_toml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ use crate::config::env_directive::{EnvDirective, PathEntry};
use crate::config::settings::SettingsPartial;
use crate::config::{Alias, AliasMap};
use crate::file::{create_dir_all, display_path};
use crate::hooks::{Hook, Hooks};
use crate::registry::REGISTRY;
use crate::task::Task;
use crate::tera::{get_tera, BASE_CONTEXT};
use crate::toolset::{ToolRequest, ToolRequestSet, ToolSource, ToolVersionOptions};
use crate::watch_files::WatchFile;
use crate::{dirs, file};

#[derive(Default, Deserialize)]
Expand All @@ -47,6 +49,8 @@ pub struct MiseToml {
#[serde(skip)]
doc: OnceCell<DocumentMut>,
#[serde(default)]
hooks: IndexMap<Hooks, String>,
#[serde(default)]
tools: IndexMap<BackendArg, MiseTomlToolList>,
#[serde(default)]
plugins: HashMap<String, String>,
Expand All @@ -55,6 +59,8 @@ pub struct MiseToml {
#[serde(default)]
tasks: Tasks,
#[serde(default)]
watch_files: Vec<WatchFile>,
#[serde(default)]
vars: IndexMap<String, String>,
#[serde(default)]
settings: SettingsPartial,
Expand Down Expand Up @@ -432,6 +438,32 @@ impl ConfigFile for MiseToml {
&self.task_config
}

fn watch_files(&self) -> eyre::Result<Vec<WatchFile>> {
self.watch_files
.iter()
.map(|wf| {
Ok(WatchFile {
patterns: wf
.patterns
.iter()
.map(|p| self.parse_template(p))
.collect::<eyre::Result<Vec<String>>>()?,
run: self.parse_template(&wf.run)?,
})
})
.collect()
}

fn hooks(&self) -> eyre::Result<Vec<Hook>> {
self.hooks
.iter()
.map(|(hook, run)| {
let run = self.parse_template(run)?;
Ok(Hook { hook: *hook, run })
})
.collect()
}

fn vars(&self) -> eyre::Result<&IndexMap<String, String>> {
Ok(&self.vars)
}
Expand Down Expand Up @@ -493,11 +525,13 @@ impl Clone for MiseToml {
env_path: self.env_path.clone(),
alias: self.alias.clone(),
doc: self.doc.clone(),
hooks: self.hooks.clone(),
tools: self.tools.clone(),
plugins: self.plugins.clone(),
tasks: self.tasks.clone(),
task_config: self.task_config.clone(),
settings: self.settings.clone(),
watch_files: self.watch_files.clone(),
vars: self.vars.clone(),
}
}
Expand Down
10 changes: 10 additions & 0 deletions src/config/config_file/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@ use crate::config::{AliasMap, Settings};
use crate::errors::Error::UntrustedConfig;
use crate::file::display_path;
use crate::hash::hash_to_str;
use crate::hooks::Hook;
use crate::task::Task;
use crate::toolset::{ToolRequest, ToolRequestSet, ToolSource, ToolVersionList, Toolset};
use crate::ui::{prompt, style};
use crate::watch_files::WatchFile;
use crate::{backend, config, dirs, env, file, hash};

pub mod idiomatic_version;
Expand Down Expand Up @@ -93,6 +95,14 @@ pub trait ConfigFile: Debug + Send + Sync {
static DEFAULT_VARS: Lazy<IndexMap<String, String>> = Lazy::new(IndexMap::new);
Ok(&DEFAULT_VARS)
}

fn watch_files(&self) -> Result<Vec<WatchFile>> {
Ok(Default::default())
}

fn hooks(&self) -> Result<Vec<Hook>> {
Ok(Default::default())
}
}

impl dyn ConfigFile {
Expand Down
Loading

0 comments on commit fb29e8c

Please sign in to comment.