Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
2 changes: 1 addition & 1 deletion TASK_CACHE_PARITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ outside this tracker.
- [x] Support reusable and global input groups
- [x] Support global environment inputs and pass-through environment variables
- [x] Support negative input and output patterns
- [ ] Support runtime-command inputs
- [x] Support command-output inputs
- [ ] Include external dependency and lockfile state through explicit input configuration
- [ ] Add per-run cache read/write controls, including local-only and cache-disabled modes
- [ ] Add a configurable local task-cache directory
Expand Down
32 changes: 27 additions & 5 deletions docs/tasks/task-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -501,8 +501,8 @@ outputs = { auto = true } # this is the default when sources is defined

### `cache` <Badge type="warning" text="experimental" />

- **Type**: `{ enabled = bool, env = string[] }`
- **Default**: `{ enabled = false, env = [] }`
- **Type**: `{ enabled = bool, env = string[], command_inputs = string[] }`
- **Default**: `{ enabled = false, env = [], command_inputs = [] }`

Stores successful task results in a content-addressed local cache and reuses them when the same task
inputs are seen again. Declared filesystem outputs are restored after deletion. Tasks with
Expand All @@ -527,6 +527,26 @@ outputs = ["dist"]
cache = { enabled = true, env = ["NODE_ENV"] }
```

Commands listed in `cache.command_inputs` run before cache lookup. Their command text, stdout, and
stderr are included in the cache key. Commands use the same inline shell (including a CLI `--shell`
override), resolved environment and tools, working directory, and sandbox policy as the task. This
is useful when inputs such as compiler versions or generated configuration cannot be represented by
source files alone.

```mise-toml
[tasks.build]
run = "npm run build"
sources = ["package.json", "src/**"]
outputs = ["dist"]
cache = { enabled = true, command_inputs = ["node --version", "npm config get registry"] }
```

A command input must be non-empty and exit successfully. Its output is hashed without being printed
or retained. Command inputs inherit the task timeout, or have a 30-second timeout when the task has
none, and may emit at most 16 MiB across stdout and stderr. They should be fast, deterministic, and
free of side effects because they run whenever mise computes the task's cache key. Command inputs
are not run during dry runs or when caching is disabled for raw or interactive execution.

```mise-toml
[tasks.lint]
run = "eslint ."
Expand All @@ -547,6 +567,7 @@ experimental = true
[task_config.cache]
enabled = true
env = ["NODE_ENV"]
command_inputs = ["node --version"]

[tasks.build]
run = "npm run build"
Expand All @@ -559,9 +580,9 @@ cache = { enabled = false }
```

The cache key includes source contents, the task definition and arguments, resolved task environment,
the values (or absence) of variables named in `cache.env`, resolved tool versions, dependency
artifact keys, and the operating system and architecture. Variables inherited from the ambient
process are ignored unless listed in `cache.env`.
the values (or absence) of variables named in `cache.env`, command-input output, resolved tool
versions, dependency artifact keys, and the operating system and architecture. Variables inherited
from the ambient process are ignored unless listed in `cache.env`.

`task_config.global_env` adds ambient variable names to every enabled task cache in the config
scope, including tasks with a task-local `cache` value. Unlike the default values under
Expand Down Expand Up @@ -893,6 +914,7 @@ Task-local and task-template cache configuration takes precedence, including
[task_config.cache]
enabled = true
env = ["NODE_ENV", "CI"]
command_inputs = ["node --version"]
```

### `task_config.global_env` <Badge type="warning" text="experimental" />
Expand Down
141 changes: 141 additions & 0 deletions e2e/tasks/test_task_artifact_cache_command_inputs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
#!/usr/bin/env bash

cat <<'EOF' >mise.toml
[settings]
experimental = true

[task_config.cache]
enabled = true
command_inputs = ["cat runtime-version"]

[tasks.build]
run = '''
mkdir -p dist
cp input.txt dist/result.txt
printf 'ran\n' >> runs.txt
'''
sources = ["input.txt"]
outputs = ["dist"]
EOF

printf 'input\n' >input.txt
printf 'one\n' >runtime-version

mise run build
assert "wc -l < runs.txt | tr -d ' '" "1"

# An unchanged command input restores the existing artifact.
rm -rf dist
assert_contains "mise run build 2>&1" "restored outputs from cache"
assert "wc -l < runs.txt | tr -d ' '" "1"

# Runtime output changes the key even though no declared source changed.
printf 'two\n' >runtime-version
rm -rf dist
mise run build
assert "wc -l < runs.txt | tr -d ' '" "2"

# Commands are evaluated in the task directory.
mkdir nested
touch nested/nested-only-input.txt
cat <<'EOF' >nested/mise.toml
[settings]
experimental = true

[tasks.check]
run = "printf 'ran\n' >> runs.txt"
sources = ["input.txt"]
outputs = []
cache = { enabled = true, command_inputs = ["test -f nested-only-input.txt"] }
EOF
touch nested/input.txt
mise -C nested run check
assert "wc -l < nested/runs.txt | tr -d ' '" "1"

cat <<'EOF' >mise.toml
[settings]
experimental = true

[tasks.fail]
run = "true"
sources = ["input.txt"]
outputs = []
cache = { enabled = true, command_inputs = ["exit 7"] }
EOF

assert_fail_contains \
"mise run fail" \
"task fail cache command input failed"

cat <<'EOF' >mise.toml
[settings]
experimental = true

[tasks.empty]
run = "true"
sources = ["input.txt"]
outputs = []
cache = { enabled = true, command_inputs = [" "] }
EOF

assert_fail_contains \
"mise run empty" \
"cache command input must not be empty"

cat <<'EOF' >mise.toml
[settings]
experimental = true

[tasks.dry]
run = "true"
sources = ["input.txt"]
outputs = []
cache = { enabled = true, command_inputs = ["touch command-input-ran"] }
EOF

mise run --dry-run dry
assert "test ! -e command-input-ran && echo clean" "clean"

cat <<'EOF' >mise.toml
[settings]
experimental = true

[tasks.raw]
run = "true"
raw = true
sources = ["input.txt"]
outputs = []
cache = { enabled = true, command_inputs = ["touch command-input-ran"] }
EOF

mise run raw
assert "test ! -e command-input-ran && echo clean" "clean"

cat <<'EOF' >mise.toml
[settings]
experimental = true

[tasks.shell-override]
run = "true"
sources = ["input.txt"]
outputs = []
cache = { enabled = true, command_inputs = ["true"] }
EOF

# Command inputs honor the same CLI shell override as the task body.
MISE_UNIX_DEFAULT_INLINE_SHELL_ARGS=false mise run --shell "sh -c" shell-override

cat <<'EOF' >mise.toml
[settings]
experimental = true

[tasks.sandboxed]
run = "true"
deny_env = true
sources = ["input.txt"]
outputs = []
cache = { enabled = true, command_inputs = ['test -z "${BLOCKED_SECRET-}"'] }
EOF

# Command inputs receive the task's sandbox-filtered environment.
BLOCKED_SECRET=secret mise run sandboxed
7 changes: 7 additions & 0 deletions schema/mise-task.json

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

14 changes: 14 additions & 0 deletions schema/mise.json
Original file line number Diff line number Diff line change
Expand Up @@ -2290,6 +2290,13 @@
"type": "string"
},
"type": "array"
},
"command_inputs": {
"description": "commands whose stdout and stderr affect eligible task cache keys",
"items": {
"type": "string"
},
"type": "array"
}
}
},
Expand Down Expand Up @@ -2968,6 +2975,13 @@
"type": "string"
},
"type": "array"
},
"command_inputs": {
"description": "commands whose stdout and stderr affect the cache key",
"items": {
"type": "string"
},
"type": "array"
}
}
},
Expand Down
Loading
Loading