tasktree 0.0.6__tar.gz → 0.0.7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tasktree-0.0.6 → tasktree-0.0.7}/PKG-INFO +2 -47
- {tasktree-0.0.6 → tasktree-0.0.7}/README.md +0 -46
- {tasktree-0.0.6 → tasktree-0.0.7}/pyproject.toml +2 -1
- tasktree-0.0.7/requirements/bug-report-dependency-triggering.md +222 -0
- {tasktree-0.0.6 → tasktree-0.0.7}/schema/tasktree-schema.json +34 -2
- tasktree-0.0.7/src/tasktree/docker.py +413 -0
- {tasktree-0.0.6 → tasktree-0.0.7}/src/tasktree/executor.py +268 -31
- {tasktree-0.0.6 → tasktree-0.0.7}/src/tasktree/graph.py +30 -1
- tasktree-0.0.7/src/tasktree/hasher.py +54 -0
- {tasktree-0.0.6 → tasktree-0.0.7}/src/tasktree/parser.py +74 -8
- {tasktree-0.0.6 → tasktree-0.0.7}/src/tasktree/state.py +1 -1
- {tasktree-0.0.6 → tasktree-0.0.7}/tasktree.yaml +1 -1
- {tasktree-0.0.6 → tasktree-0.0.7}/tests/integration/test_dependency_execution.py +160 -0
- tasktree-0.0.7/tests/unit/test_docker.py +277 -0
- tasktree-0.0.7/tests/unit/test_environment_tracking.py +356 -0
- {tasktree-0.0.6 → tasktree-0.0.7}/tests/unit/test_parser.py +2 -1
- {tasktree-0.0.6 → tasktree-0.0.7}/uv.lock +11 -0
- tasktree-0.0.6/src/tasktree/hasher.py +0 -27
- {tasktree-0.0.6 → tasktree-0.0.7}/.github/workflows/release.yml +0 -0
- {tasktree-0.0.6 → tasktree-0.0.7}/.github/workflows/test.yml +0 -0
- {tasktree-0.0.6 → tasktree-0.0.7}/.gitignore +0 -0
- {tasktree-0.0.6 → tasktree-0.0.7}/.python-version +0 -0
- {tasktree-0.0.6 → tasktree-0.0.7}/CLAUDE.md +0 -0
- {tasktree-0.0.6 → tasktree-0.0.7}/example/source.txt +0 -0
- {tasktree-0.0.6 → tasktree-0.0.7}/example/tasktree.yaml +0 -0
- {tasktree-0.0.6 → tasktree-0.0.7}/requirements/future/docker-task-environments.md +0 -0
- {tasktree-0.0.6 → tasktree-0.0.7}/requirements/implemented/shell-environment-requirements.md +0 -0
- {tasktree-0.0.6 → tasktree-0.0.7}/schema/README.md +0 -0
- {tasktree-0.0.6 → tasktree-0.0.7}/schema/vscode-settings-snippet.json +0 -0
- {tasktree-0.0.6 → tasktree-0.0.7}/src/__init__.py +0 -0
- {tasktree-0.0.6 → tasktree-0.0.7}/src/tasktree/__init__.py +0 -0
- {tasktree-0.0.6 → tasktree-0.0.7}/src/tasktree/cli.py +0 -0
- {tasktree-0.0.6 → tasktree-0.0.7}/src/tasktree/tasks.py +0 -0
- {tasktree-0.0.6 → tasktree-0.0.7}/src/tasktree/types.py +0 -0
- {tasktree-0.0.6 → tasktree-0.0.7}/tests/integration/test_clean_state.py +0 -0
- {tasktree-0.0.6 → tasktree-0.0.7}/tests/integration/test_cli_options.py +0 -0
- {tasktree-0.0.6 → tasktree-0.0.7}/tests/integration/test_end_to_end.py +0 -0
- {tasktree-0.0.6 → tasktree-0.0.7}/tests/integration/test_input_detection.py +0 -0
- {tasktree-0.0.6 → tasktree-0.0.7}/tests/integration/test_missing_outputs.py +0 -0
- {tasktree-0.0.6 → tasktree-0.0.7}/tests/integration/test_nested_imports.py +0 -0
- {tasktree-0.0.6 → tasktree-0.0.7}/tests/integration/test_state_persistence.py +0 -0
- {tasktree-0.0.6 → tasktree-0.0.7}/tests/integration/test_working_directory.py +0 -0
- {tasktree-0.0.6 → tasktree-0.0.7}/tests/unit/test_cli.py +0 -0
- {tasktree-0.0.6 → tasktree-0.0.7}/tests/unit/test_executor.py +0 -0
- {tasktree-0.0.6 → tasktree-0.0.7}/tests/unit/test_graph.py +0 -0
- {tasktree-0.0.6 → tasktree-0.0.7}/tests/unit/test_hasher.py +0 -0
- {tasktree-0.0.6 → tasktree-0.0.7}/tests/unit/test_state.py +0 -0
- {tasktree-0.0.6 → tasktree-0.0.7}/tests/unit/test_tasks.py +0 -0
- {tasktree-0.0.6 → tasktree-0.0.7}/tests/unit/test_types.py +0 -0
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tasktree
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.7
|
|
4
4
|
Summary: A task automation tool with incremental execution
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Requires-Dist: click>=8.1.0
|
|
7
7
|
Requires-Dist: colorama>=0.4.6
|
|
8
|
+
Requires-Dist: pathspec>=0.11.0
|
|
8
9
|
Requires-Dist: pyyaml>=6.0
|
|
9
10
|
Requires-Dist: rich>=13.0.0
|
|
10
11
|
Requires-Dist: typer>=0.9.0
|
|
@@ -108,52 +109,6 @@ Boom! Done. `build` will always run, because there's no sensible way to know wha
|
|
|
108
109
|
|
|
109
110
|
This is a toy example, but you can image how it plays out on a more complex project.
|
|
110
111
|
|
|
111
|
-
## Migrating from v1.x to v2.0
|
|
112
|
-
|
|
113
|
-
Version 2.0 requires all task definitions to be under a top-level `tasks:` key.
|
|
114
|
-
|
|
115
|
-
### Quick Migration
|
|
116
|
-
|
|
117
|
-
Wrap your existing tasks in a `tasks:` block:
|
|
118
|
-
|
|
119
|
-
```yaml
|
|
120
|
-
# Before (v1.x)
|
|
121
|
-
build:
|
|
122
|
-
cmd: cargo build
|
|
123
|
-
|
|
124
|
-
# After (v2.0)
|
|
125
|
-
tasks:
|
|
126
|
-
build:
|
|
127
|
-
cmd: cargo build
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
### Why This Change?
|
|
131
|
-
|
|
132
|
-
1. **Clearer structure**: Explicit separation of tasks from configuration
|
|
133
|
-
2. **No naming conflicts**: You can now create tasks named "imports" or "environments"
|
|
134
|
-
3. **Better error messages**: More helpful validation errors
|
|
135
|
-
4. **Consistency**: All recipe files use the same format
|
|
136
|
-
|
|
137
|
-
### Error Messages
|
|
138
|
-
|
|
139
|
-
If you forget to update, you'll see a clear error:
|
|
140
|
-
|
|
141
|
-
```
|
|
142
|
-
Invalid recipe format in tasktree.yaml
|
|
143
|
-
|
|
144
|
-
Task definitions must be under a top-level "tasks:" key.
|
|
145
|
-
|
|
146
|
-
Found these keys at root level: build, test
|
|
147
|
-
|
|
148
|
-
Did you mean:
|
|
149
|
-
|
|
150
|
-
tasks:
|
|
151
|
-
build:
|
|
152
|
-
cmd: ...
|
|
153
|
-
test:
|
|
154
|
-
cmd: ...
|
|
155
|
-
```
|
|
156
|
-
|
|
157
112
|
## Installation
|
|
158
113
|
|
|
159
114
|
### From PyPI (Recommended)
|
|
@@ -94,52 +94,6 @@ Boom! Done. `build` will always run, because there's no sensible way to know wha
|
|
|
94
94
|
|
|
95
95
|
This is a toy example, but you can image how it plays out on a more complex project.
|
|
96
96
|
|
|
97
|
-
## Migrating from v1.x to v2.0
|
|
98
|
-
|
|
99
|
-
Version 2.0 requires all task definitions to be under a top-level `tasks:` key.
|
|
100
|
-
|
|
101
|
-
### Quick Migration
|
|
102
|
-
|
|
103
|
-
Wrap your existing tasks in a `tasks:` block:
|
|
104
|
-
|
|
105
|
-
```yaml
|
|
106
|
-
# Before (v1.x)
|
|
107
|
-
build:
|
|
108
|
-
cmd: cargo build
|
|
109
|
-
|
|
110
|
-
# After (v2.0)
|
|
111
|
-
tasks:
|
|
112
|
-
build:
|
|
113
|
-
cmd: cargo build
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
### Why This Change?
|
|
117
|
-
|
|
118
|
-
1. **Clearer structure**: Explicit separation of tasks from configuration
|
|
119
|
-
2. **No naming conflicts**: You can now create tasks named "imports" or "environments"
|
|
120
|
-
3. **Better error messages**: More helpful validation errors
|
|
121
|
-
4. **Consistency**: All recipe files use the same format
|
|
122
|
-
|
|
123
|
-
### Error Messages
|
|
124
|
-
|
|
125
|
-
If you forget to update, you'll see a clear error:
|
|
126
|
-
|
|
127
|
-
```
|
|
128
|
-
Invalid recipe format in tasktree.yaml
|
|
129
|
-
|
|
130
|
-
Task definitions must be under a top-level "tasks:" key.
|
|
131
|
-
|
|
132
|
-
Found these keys at root level: build, test
|
|
133
|
-
|
|
134
|
-
Did you mean:
|
|
135
|
-
|
|
136
|
-
tasks:
|
|
137
|
-
build:
|
|
138
|
-
cmd: ...
|
|
139
|
-
test:
|
|
140
|
-
cmd: ...
|
|
141
|
-
```
|
|
142
|
-
|
|
143
97
|
## Installation
|
|
144
98
|
|
|
145
99
|
### From PyPI (Recommended)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "tasktree"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.7"
|
|
4
4
|
description = "A task automation tool with incremental execution"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -10,6 +10,7 @@ dependencies = [
|
|
|
10
10
|
"click>=8.1.0",
|
|
11
11
|
"rich>=13.0.0",
|
|
12
12
|
"colorama>=0.4.6",
|
|
13
|
+
"pathspec>=0.11.0",
|
|
13
14
|
]
|
|
14
15
|
|
|
15
16
|
[project.optional-dependencies]
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# Bug Report: False Dependency Triggering in Incremental Execution
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
Tasks are incorrectly triggered to run when their dependencies execute, even when those dependencies produce no actual changes to their outputs. This violates the principle of incremental execution and causes unnecessary rebuilds.
|
|
6
|
+
|
|
7
|
+
## Current Behaviour
|
|
8
|
+
|
|
9
|
+
The executor performs static analysis of the dependency graph before execution:
|
|
10
|
+
|
|
11
|
+
1. Topologically sorts all tasks
|
|
12
|
+
2. For each task in order, checks if it should run by examining:
|
|
13
|
+
- Whether any dependency has `will_run=True` (static check)
|
|
14
|
+
- Whether inputs have changed (dynamic check via mtime)
|
|
15
|
+
- Other conditions (never run, no outputs, etc.)
|
|
16
|
+
3. Executes all tasks marked with `will_run=True`
|
|
17
|
+
|
|
18
|
+
The problem occurs at step 2: if a dependency is marked `will_run=True`, the current task is immediately marked to run, regardless of whether that dependency actually modified any files.
|
|
19
|
+
|
|
20
|
+
## Expected Behaviour
|
|
21
|
+
|
|
22
|
+
Tasks should run based solely on the runtime state of their inputs:
|
|
23
|
+
|
|
24
|
+
1. Topologically sort all tasks
|
|
25
|
+
2. For each task in execution order:
|
|
26
|
+
- Check if any of its inputs (explicit or implicit) have changed
|
|
27
|
+
- If inputs changed (or other valid conditions met), execute the task
|
|
28
|
+
- After execution, downstream tasks check their inputs against actual file mtimes
|
|
29
|
+
3. A dependency that runs but produces no changes should NOT trigger downstream tasks
|
|
30
|
+
|
|
31
|
+
This matches Make's behaviour: it checks whether dependency output files are newer than the target, not whether the dependency command ran.
|
|
32
|
+
|
|
33
|
+
## Reproduction Test Case
|
|
34
|
+
|
|
35
|
+
The following test demonstrates the bug:
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
def test_dependency_runs_but_produces_no_changes():
|
|
39
|
+
"""
|
|
40
|
+
Test that a task whose dependency runs but produces no output changes
|
|
41
|
+
does NOT trigger re-execution.
|
|
42
|
+
|
|
43
|
+
Scenario:
|
|
44
|
+
- Task 'build' has no inputs, declares outputs (always runs, like cargo/make)
|
|
45
|
+
- Task 'build' runs but creates no new files (simulated by not touching)
|
|
46
|
+
- Task 'package' depends on 'build' (implicitly gets build outputs as inputs)
|
|
47
|
+
- Expected: 'package' should NOT run because build's outputs didn't change
|
|
48
|
+
- Actual (bug): 'package' runs because 'build' has will_run=True
|
|
49
|
+
"""
|
|
50
|
+
import tempfile
|
|
51
|
+
import os
|
|
52
|
+
from pathlib import Path
|
|
53
|
+
from unittest.mock import patch
|
|
54
|
+
import yaml
|
|
55
|
+
|
|
56
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
57
|
+
project_root = Path(tmpdir)
|
|
58
|
+
|
|
59
|
+
# Create recipe file
|
|
60
|
+
recipe = {
|
|
61
|
+
'build': {
|
|
62
|
+
'desc': 'Simulate build tool (cargo/make) with internal dep resolution\nNo inputs - tool does its own dependency checking\nHas outputs so package can implicitly depend on them',
|
|
63
|
+
'outputs': ['build-artifact.txt'],
|
|
64
|
+
'cmd': 'touch build-artifact.txt',
|
|
65
|
+
},
|
|
66
|
+
'package': {
|
|
67
|
+
'desc': 'No explicit inputs - should implicitly inherit build-artifact.txt from build',
|
|
68
|
+
'deps': ['build'],
|
|
69
|
+
'outputs': ['package.tar.gz'],
|
|
70
|
+
'cmd': 'touch package.tar.gz',
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
recipe_path = project_root / 'tasktree.yaml'
|
|
75
|
+
recipe_path.write_text(yaml.dump(recipe))
|
|
76
|
+
|
|
77
|
+
# First run: establish baseline
|
|
78
|
+
# This creates build-artifact.txt and package.tar.gz
|
|
79
|
+
from tasktree.parser import parse_recipe
|
|
80
|
+
from tasktree.state import StateManager
|
|
81
|
+
from tasktree.executor import Executor
|
|
82
|
+
|
|
83
|
+
parsed_recipe = parse_recipe(recipe_path)
|
|
84
|
+
state_manager = StateManager(project_root / '.tasktree-state')
|
|
85
|
+
executor = Executor(parsed_recipe, state_manager)
|
|
86
|
+
|
|
87
|
+
statuses = executor.execute_task('package')
|
|
88
|
+
|
|
89
|
+
assert statuses['build'].will_run # First run, no state
|
|
90
|
+
assert statuses['package'].will_run # First run, no state
|
|
91
|
+
|
|
92
|
+
# Verify files exist
|
|
93
|
+
assert (project_root / 'build-artifact.txt').exists()
|
|
94
|
+
assert (project_root / 'package.tar.gz').exists()
|
|
95
|
+
|
|
96
|
+
# Get actual mtime of build artifact
|
|
97
|
+
build_artifact_path = project_root / 'build-artifact.txt'
|
|
98
|
+
original_mtime = build_artifact_path.stat().st_mtime
|
|
99
|
+
|
|
100
|
+
# Second run: build task runs (no inputs) but produces no changes
|
|
101
|
+
# Simulate this by having build command do nothing
|
|
102
|
+
recipe['build']['cmd'] = 'echo "checking dependencies, nothing to do"'
|
|
103
|
+
recipe_path.write_text(yaml.dump(recipe))
|
|
104
|
+
|
|
105
|
+
parsed_recipe = parse_recipe(recipe_path)
|
|
106
|
+
executor = Executor(parsed_recipe, state_manager)
|
|
107
|
+
|
|
108
|
+
# Patch os.stat to return the original mtime for build-artifact.txt
|
|
109
|
+
# This simulates build running but not modifying its outputs
|
|
110
|
+
original_stat = os.stat
|
|
111
|
+
def patched_stat(path, *args, **kwargs):
|
|
112
|
+
result = original_stat(path, *args, **kwargs)
|
|
113
|
+
if Path(path) == build_artifact_path:
|
|
114
|
+
# Return stat result with original mtime
|
|
115
|
+
import os
|
|
116
|
+
stat_result = os.stat_result((
|
|
117
|
+
result.st_mode,
|
|
118
|
+
result.st_ino,
|
|
119
|
+
result.st_dev,
|
|
120
|
+
result.st_nlink,
|
|
121
|
+
result.st_uid,
|
|
122
|
+
result.st_gid,
|
|
123
|
+
result.st_size,
|
|
124
|
+
result.st_atime,
|
|
125
|
+
original_mtime, # Keep original mtime
|
|
126
|
+
result.st_ctime,
|
|
127
|
+
))
|
|
128
|
+
return stat_result
|
|
129
|
+
return result
|
|
130
|
+
|
|
131
|
+
with patch('os.stat', side_effect=patched_stat):
|
|
132
|
+
with patch('pathlib.Path.stat', side_effect=lambda self: patched_stat(self)):
|
|
133
|
+
statuses = executor.execute_task('package')
|
|
134
|
+
|
|
135
|
+
# Build task should run (has no inputs)
|
|
136
|
+
assert statuses['build'].will_run
|
|
137
|
+
assert statuses['build'].reason == 'no_outputs'
|
|
138
|
+
|
|
139
|
+
# BUG: Package task runs because build ran, even though build's output unchanged
|
|
140
|
+
# EXPECTED: package task should NOT run (implicit inputs haven't changed)
|
|
141
|
+
assert statuses['package'].will_run == False, \
|
|
142
|
+
f"Package should not run when dependency produces no changes, " \
|
|
143
|
+
f"but will_run={statuses['package'].will_run}, reason={statuses['package'].reason}"
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Root Cause Analysis
|
|
147
|
+
|
|
148
|
+
The issue is in the execution planning phase. The code attempts to determine which tasks will run before any tasks execute. This creates a dependency on static analysis of the dependency graph rather than dynamic checking of actual file states.
|
|
149
|
+
|
|
150
|
+
The specific problematic logic:
|
|
151
|
+
- Checking `if any(status.will_run for status in dep_statuses.values())`
|
|
152
|
+
- This happens before the dependency actually runs
|
|
153
|
+
- Therefore, it cannot know what the dependency will actually do to files
|
|
154
|
+
|
|
155
|
+
## Algorithmic Changes Required
|
|
156
|
+
|
|
157
|
+
The execution algorithm needs to shift from "plan then execute" to "check and execute incrementally":
|
|
158
|
+
|
|
159
|
+
**Current approach:**
|
|
160
|
+
```
|
|
161
|
+
for each task in topo order:
|
|
162
|
+
if dependency.will_run:
|
|
163
|
+
mark this task to run
|
|
164
|
+
elif inputs changed:
|
|
165
|
+
mark this task to run
|
|
166
|
+
|
|
167
|
+
for each task marked to run:
|
|
168
|
+
execute task
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**Required approach:**
|
|
172
|
+
```
|
|
173
|
+
for each task in topo order:
|
|
174
|
+
if inputs changed (checking actual file mtimes):
|
|
175
|
+
execute task
|
|
176
|
+
update state
|
|
177
|
+
else:
|
|
178
|
+
skip task
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
The key differences:
|
|
182
|
+
1. No separate planning phase that checks `dep_statuses`
|
|
183
|
+
2. Each task checks only the current runtime state of its inputs
|
|
184
|
+
3. Execution happens immediately when needed, not in a separate loop
|
|
185
|
+
4. State updates happen immediately after execution
|
|
186
|
+
|
|
187
|
+
## Implementation Considerations
|
|
188
|
+
|
|
189
|
+
- The `TaskStatus` dataclass and planning phase may need significant restructuring
|
|
190
|
+
- The `check_task_status` method should not receive or examine `dep_statuses`
|
|
191
|
+
- Input checking should always use current file system state, never cached planning decisions
|
|
192
|
+
- The execution order still requires topological sorting (dependencies run before dependents)
|
|
193
|
+
- Edge cases to preserve:
|
|
194
|
+
- Tasks with no inputs/outputs still run every time
|
|
195
|
+
- `--force` flag behaviour
|
|
196
|
+
- Missing outputs trigger re-execution
|
|
197
|
+
- First-run (no cached state) behaviour
|
|
198
|
+
|
|
199
|
+
## Test Validation
|
|
200
|
+
|
|
201
|
+
The test case should pass after the fix:
|
|
202
|
+
- First run: both tasks execute (establishing baseline)
|
|
203
|
+
- Second run: build runs (no inputs/outputs), package skips (inputs unchanged)
|
|
204
|
+
- The test explicitly verifies build-artifact.txt mtime is unchanged
|
|
205
|
+
- The test asserts package task does NOT run
|
|
206
|
+
|
|
207
|
+
## Related Code Locations
|
|
208
|
+
|
|
209
|
+
- `src/tasktree/executor.py`: `Executor.execute_task()` method
|
|
210
|
+
- `src/tasktree/executor.py`: `Executor.check_task_status()` method
|
|
211
|
+
- Focus on the dependency triggering logic and execution flow
|
|
212
|
+
|
|
213
|
+
## Additional Context
|
|
214
|
+
|
|
215
|
+
This bug fundamentally conflicts with the tool's purpose: intelligent incremental execution. Build tools like cargo, make, and cmake have sophisticated internal dependency tracking. When tt wraps these tools, it should respect their decisions about whether work is needed, not override them with static graph analysis.
|
|
216
|
+
|
|
217
|
+
The current implementation would cause spurious rebuilds in common workflows:
|
|
218
|
+
- `cargo build` (checks Rust dependencies, may do nothing)
|
|
219
|
+
- `make` (checks C/C++ timestamps, may do nothing)
|
|
220
|
+
- `cmake --build` (checks build graph, may do nothing)
|
|
221
|
+
|
|
222
|
+
These tools are designed to be incremental. Forcing downstream tasks to run when these tools decide nothing changed defeats the purpose of incremental builds.
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"properties": {
|
|
41
41
|
"shell": {
|
|
42
42
|
"type": "string",
|
|
43
|
-
"description": "Path to the shell executable (e.g., /bin/bash, python3, pwsh)"
|
|
43
|
+
"description": "Path to the shell executable (e.g., /bin/bash, python3, pwsh). Required for shell environments, optional for Docker environments (defaults to 'sh' inside container)."
|
|
44
44
|
},
|
|
45
45
|
"args": {
|
|
46
46
|
"description": "Arguments to pass to the shell",
|
|
@@ -59,9 +59,41 @@
|
|
|
59
59
|
"preamble": {
|
|
60
60
|
"type": "string",
|
|
61
61
|
"description": "Code to execute before the command (e.g., imports, setup)"
|
|
62
|
+
},
|
|
63
|
+
"dockerfile": {
|
|
64
|
+
"type": "string",
|
|
65
|
+
"description": "Path to Dockerfile (relative to recipe file). When present, indicates a Docker environment."
|
|
66
|
+
},
|
|
67
|
+
"context": {
|
|
68
|
+
"type": "string",
|
|
69
|
+
"description": "Path to Docker build context directory (relative to recipe file). Required if dockerfile is specified."
|
|
70
|
+
},
|
|
71
|
+
"volumes": {
|
|
72
|
+
"description": "Volume mounts for Docker container (format: 'host_path:container_path')",
|
|
73
|
+
"type": "array",
|
|
74
|
+
"items": {
|
|
75
|
+
"type": "string"
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
"ports": {
|
|
79
|
+
"description": "Port mappings for Docker container (format: 'host_port:container_port')",
|
|
80
|
+
"type": "array",
|
|
81
|
+
"items": {
|
|
82
|
+
"type": "string"
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
"env_vars": {
|
|
86
|
+
"description": "Environment variables to set inside the Docker container",
|
|
87
|
+
"type": "object",
|
|
88
|
+
"additionalProperties": {
|
|
89
|
+
"type": "string"
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
"working_dir": {
|
|
93
|
+
"type": "string",
|
|
94
|
+
"description": "Working directory inside the container (for Docker environments) or on the host (for shell environments)"
|
|
62
95
|
}
|
|
63
96
|
},
|
|
64
|
-
"required": ["shell"],
|
|
65
97
|
"additionalProperties": false
|
|
66
98
|
}
|
|
67
99
|
},
|