feat: consolidate legacy workflows into gyxx-flow

This commit is contained in:
wangyunlong committed 2026-07-28 14:51:15 +08:00
commit c23b62a8c8
374 files changed
+132990

No files matched your search

+101
View File
@@ -0,0 +1,101 @@
"""Built-in executable workflow steps."""
from __future__ import annotations
import subprocess
from dataclasses import dataclass
from pathlib import Path
from typing import Mapping
from gyxx_flow.core.context import RunContext
@dataclass(frozen=True, slots=True)
class StepExecution:
"""One action invocation outcome before retry aggregation."""
exit_code: int
stdout: str = ""
stderr: str = ""
error: str | None = None
skipped: bool = False
reason: str | None = None
@dataclass(frozen=True, slots=True)
class CommandStep:
"""Execute an argv command without shell parsing or implicit process context."""
argv: tuple[str, ...]
cwd: Path
env: Mapping[str, str]
def __post_init__(self) -> None:
object.__setattr__(self, "argv", tuple(self.argv))
object.__setattr__(self, "cwd", Path(self.cwd))
object.__setattr__(self, "env", dict(self.env))
if not self.argv or not all(
isinstance(argument, str) and argument and "\x00" not in argument
for argument in self.argv
):
raise ValueError("argv must contain non-empty strings")
if not all(
isinstance(key, str)
and key
and "\x00" not in key
and isinstance(value, str)
and "\x00" not in value
for key, value in self.env.items()
):
raise ValueError("env must contain valid string keys and values")
def execute(
self,
*,
context: RunContext,
timeout_seconds: float | None,
dry_run: bool,
) -> StepExecution:
del context
if dry_run:
return StepExecution(exit_code=0, skipped=True, reason="dry-run")
try:
completed = subprocess.run(
list(self.argv),
cwd=self.cwd,
env=dict(self.env),
shell=False,
capture_output=True,
text=True,
timeout=timeout_seconds,
check=False,
)
except subprocess.TimeoutExpired as exc:
stdout = _to_text(exc.stdout)
stderr = _to_text(exc.stderr)
return StepExecution(
exit_code=124,
stdout=stdout,
stderr=stderr,
error=f"command timed out after {exc.timeout} seconds",
)
except OSError as exc:
return StepExecution(exit_code=127, error=f"cannot start command: {exc}")
error = None
if completed.returncode != 0:
error = completed.stderr.strip() or f"command exited with code {completed.returncode}"
return StepExecution(
exit_code=completed.returncode,
stdout=completed.stdout,
stderr=completed.stderr,
error=error,
)
def _to_text(value: str | bytes | None) -> str:
if value is None:
return ""
if isinstance(value, bytes):
return value.decode(errors="replace")
return value