flowsh-cli 0.8.0__tar.gz → 0.8.2__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.
- {flowsh_cli-0.8.0 → flowsh_cli-0.8.2}/PKG-INFO +3 -3
- {flowsh_cli-0.8.0 → flowsh_cli-0.8.2}/README.md +2 -2
- {flowsh_cli-0.8.0 → flowsh_cli-0.8.2}/pyproject.toml +4 -1
- {flowsh_cli-0.8.0 → flowsh_cli-0.8.2}/src/flowsh_cli/__init__.py +1 -1
- {flowsh_cli-0.8.0 → flowsh_cli-0.8.2}/src/flowsh_cli/cli.py +0 -1
- {flowsh_cli-0.8.0 → flowsh_cli-0.8.2}/src/flowsh_cli/models.py +31 -3
- {flowsh_cli-0.8.0 → flowsh_cli-0.8.2}/src/flowsh_cli/render.py +21 -8
- {flowsh_cli-0.8.0 → flowsh_cli-0.8.2}/src/flowsh_cli/__main__.py +0 -0
- {flowsh_cli-0.8.0 → flowsh_cli-0.8.2}/src/flowsh_cli/examples.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flowsh-cli
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.2
|
|
4
4
|
Summary: Generate Bash harness scripts from workflow YAML files.
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Classifier: Programming Language :: Python :: 3.11
|
|
@@ -88,10 +88,10 @@ Harness paths are derived from workflow ids. `wf_example` becomes `example.sh` i
|
|
|
88
88
|
|
|
89
89
|
| Type | Purpose | Notes |
|
|
90
90
|
|---|---|---|
|
|
91
|
-
| `vars` |
|
|
91
|
+
| `vars` | Execute shell commands and export their stdout into shell variables | Variable names must be uppercase shell identifiers. |
|
|
92
92
|
| `bash` | Run shell commands | Runs with `bash -euo pipefail`. |
|
|
93
93
|
| `agent` | Call OpenCode | Supports `agent`, `model`, `command`, `expandPrompt`, and `dangerouslySkipPermissions`. |
|
|
94
|
-
| `for` | Iterate over newline-delimited
|
|
94
|
+
| `for` | Iterate over newline-delimited values from a previous `vars` step | Flat iteration only; nested `for` steps are not supported. |
|
|
95
95
|
| `parallel` | Run child steps concurrently | Children run as separate branches and the parent waits for all of them. |
|
|
96
96
|
|
|
97
97
|
## Agent Behavior
|
|
@@ -72,10 +72,10 @@ Harness paths are derived from workflow ids. `wf_example` becomes `example.sh` i
|
|
|
72
72
|
|
|
73
73
|
| Type | Purpose | Notes |
|
|
74
74
|
|---|---|---|
|
|
75
|
-
| `vars` |
|
|
75
|
+
| `vars` | Execute shell commands and export their stdout into shell variables | Variable names must be uppercase shell identifiers. |
|
|
76
76
|
| `bash` | Run shell commands | Runs with `bash -euo pipefail`. |
|
|
77
77
|
| `agent` | Call OpenCode | Supports `agent`, `model`, `command`, `expandPrompt`, and `dangerouslySkipPermissions`. |
|
|
78
|
-
| `for` | Iterate over newline-delimited
|
|
78
|
+
| `for` | Iterate over newline-delimited values from a previous `vars` step | Flat iteration only; nested `for` steps are not supported. |
|
|
79
79
|
| `parallel` | Run child steps concurrently | Children run as separate branches and the parent waits for all of them. |
|
|
80
80
|
|
|
81
81
|
## Agent Behavior
|
|
@@ -4,7 +4,7 @@ build-backend = "uv_build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "flowsh-cli"
|
|
7
|
-
version = "0.8.
|
|
7
|
+
version = "0.8.2"
|
|
8
8
|
description = "Generate Bash harness scripts from workflow YAML files."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -42,3 +42,6 @@ target-version = "py311"
|
|
|
42
42
|
|
|
43
43
|
[tool.ruff.lint]
|
|
44
44
|
select = ["E", "F", "I", "UP", "B", "SIM"]
|
|
45
|
+
|
|
46
|
+
[tool.ruff.lint.per-file-ignores]
|
|
47
|
+
"tests/test_workflow_to_harness.py" = ["E501"]
|
|
@@ -46,6 +46,7 @@ class StrictModel(BaseModel):
|
|
|
46
46
|
|
|
47
47
|
class BaseStep(StrictModel):
|
|
48
48
|
name: str | None = None
|
|
49
|
+
when: str | None = None
|
|
49
50
|
|
|
50
51
|
@field_validator("name")
|
|
51
52
|
@classmethod
|
|
@@ -56,6 +57,15 @@ class BaseStep(StrictModel):
|
|
|
56
57
|
raise ValueError("must not contain control characters")
|
|
57
58
|
return value
|
|
58
59
|
|
|
60
|
+
@field_validator("when")
|
|
61
|
+
@classmethod
|
|
62
|
+
def validate_when(cls, value: str | None) -> str | None:
|
|
63
|
+
if value is not None and value.strip() == "":
|
|
64
|
+
raise ValueError("must not be empty")
|
|
65
|
+
if value is not None and has_unsafe_control_characters(value):
|
|
66
|
+
raise ValueError("must not contain unsafe control characters")
|
|
67
|
+
return value
|
|
68
|
+
|
|
59
69
|
|
|
60
70
|
class BashStep(BaseStep):
|
|
61
71
|
type: Literal["bash"]
|
|
@@ -135,7 +145,14 @@ class AgentStep(BaseStep):
|
|
|
135
145
|
|
|
136
146
|
class VarsStep(BaseStep):
|
|
137
147
|
type: Literal["vars"]
|
|
138
|
-
values: dict[str, str]
|
|
148
|
+
values: dict[str, str] = Field(
|
|
149
|
+
description=(
|
|
150
|
+
"Each value is a shell command. "
|
|
151
|
+
"The command is executed and its stdout (stripped of trailing newline) "
|
|
152
|
+
"becomes the variable value. "
|
|
153
|
+
"It is NOT a literal string."
|
|
154
|
+
)
|
|
155
|
+
)
|
|
139
156
|
|
|
140
157
|
@field_validator("values")
|
|
141
158
|
@classmethod
|
|
@@ -156,8 +173,19 @@ class VarsStep(BaseStep):
|
|
|
156
173
|
|
|
157
174
|
class ForStep(BaseStep):
|
|
158
175
|
type: Literal["for"]
|
|
159
|
-
in_: str = Field(
|
|
160
|
-
|
|
176
|
+
in_: str = Field(
|
|
177
|
+
...,
|
|
178
|
+
validation_alias="in",
|
|
179
|
+
description=(
|
|
180
|
+
"Name of a variable (defined by a preceding vars step) whose value is "
|
|
181
|
+
"a newline-delimited list. "
|
|
182
|
+
"Each line becomes one iteration."
|
|
183
|
+
),
|
|
184
|
+
)
|
|
185
|
+
item: str = Field(
|
|
186
|
+
...,
|
|
187
|
+
description="Name of the shell variable exported into each iteration body.",
|
|
188
|
+
)
|
|
161
189
|
steps: list[Step] = Field(min_length=1)
|
|
162
190
|
|
|
163
191
|
@field_validator("in_", "item")
|
|
@@ -241,7 +241,7 @@ def render_step(index: int, step: Step, used_function_names: set[str] | None = N
|
|
|
241
241
|
inner_title = inner_step.name or default_step_title(i, inner_step)
|
|
242
242
|
prefix_lines.append(section(f"For-inner step ({inner_step.type}): {inner_title}"))
|
|
243
243
|
prefix_lines.append(f"{inner_fn}() {{")
|
|
244
|
-
prefix_lines.extend(_render_step_body(inner_step))
|
|
244
|
+
prefix_lines.extend(_render_step_body(inner_step, inner_title))
|
|
245
245
|
prefix_lines.append("}")
|
|
246
246
|
prefix_lines.append("")
|
|
247
247
|
|
|
@@ -259,7 +259,7 @@ def render_step(index: int, step: Step, used_function_names: set[str] | None = N
|
|
|
259
259
|
child_fns.append(child_fn)
|
|
260
260
|
prefix_lines.append(section(f"Parallel child {i} ({child.type}): {child_title}"))
|
|
261
261
|
prefix_lines.append(f"{child_fn}() {{")
|
|
262
|
-
prefix_lines.extend(_render_step_body(child))
|
|
262
|
+
prefix_lines.extend(_render_step_body(child, child_title))
|
|
263
263
|
prefix_lines.append("}")
|
|
264
264
|
prefix_lines.append("")
|
|
265
265
|
|
|
@@ -271,23 +271,36 @@ def render_step(index: int, step: Step, used_function_names: set[str] | None = N
|
|
|
271
271
|
body_lines.append(f' wait "$pid_{child_fn}" || status=$?')
|
|
272
272
|
body_lines.append(' return "$status"')
|
|
273
273
|
else:
|
|
274
|
-
body_lines = _render_step_body(step)
|
|
274
|
+
body_lines = _render_step_body(step, title)
|
|
275
275
|
|
|
276
276
|
runner = "run_stateful_step" if isinstance(step, (VarsStep, ForStep)) else "run_step"
|
|
277
277
|
outer_lines = [
|
|
278
278
|
section(f"Step {index} ({step.type}): {title}"),
|
|
279
279
|
f"{function_name}() {{",
|
|
280
|
-
*body_lines,
|
|
281
|
-
"}",
|
|
282
|
-
f"{runner} {function_name}",
|
|
283
|
-
"",
|
|
284
280
|
]
|
|
281
|
+
|
|
282
|
+
outer_lines.extend(body_lines)
|
|
283
|
+
outer_lines.extend(
|
|
284
|
+
[
|
|
285
|
+
"}",
|
|
286
|
+
f"{runner} {function_name}",
|
|
287
|
+
"",
|
|
288
|
+
]
|
|
289
|
+
)
|
|
285
290
|
return prefix_lines + outer_lines
|
|
286
291
|
|
|
287
292
|
|
|
288
|
-
def _render_step_body(step: Step) -> list[str]:
|
|
293
|
+
def _render_step_body(step: Step, title: str) -> list[str]:
|
|
289
294
|
"""Return indented body lines for a step function (no wrapper, no run_step call)."""
|
|
290
295
|
lines: list[str] = []
|
|
296
|
+
|
|
297
|
+
if step.when is not None:
|
|
298
|
+
lines.append(f" if ! ({step.when}); then")
|
|
299
|
+
lines.append(f" log INFO {bash_quote(f'Step skipped (when): {title}')}")
|
|
300
|
+
lines.append(" return 0")
|
|
301
|
+
lines.append(" fi")
|
|
302
|
+
lines.append("")
|
|
303
|
+
|
|
291
304
|
if isinstance(step, VarsStep):
|
|
292
305
|
lines.append(" local status=0")
|
|
293
306
|
for name, command in step.values.items():
|
|
File without changes
|
|
File without changes
|