feat: add artifact create-from-plan cli

This commit is contained in:
lda
2026-06-15 03:49:16 +07:00 Verified
parent a77483c628
commit acd599d09f
4 changed files with 180 additions and 1 deletions
+48 -1
View File
@@ -1,12 +1,13 @@
from __future__ import annotations
from pathlib import Path
from typing import Annotated, Literal
import typer
from wf_cli.context import load_cli_context_from_typer as load_cli_context
from wf_cli.formats import ListOutputFormat, emit_list_payload
from wf_cli.io import emit_json
from wf_cli.io import CliInputError, emit_json, parse_bindings, parse_structured_file
from wf_cli.remote_errors import run_cli_operation
app = typer.Typer(
@@ -98,6 +99,52 @@ def _resolve_artifact_version(
return version_arg
@app.command("create-from-plan")
def create_artifact_from_plan(
ctx: typer.Context,
plan_file: Annotated[Path, typer.Argument(exists=True, dir_okay=False)],
artifact_id: Annotated[str, typer.Option("--artifact", help="Artifact id.")],
version: Annotated[int, typer.Option("--version", min=1, help="Artifact version.")],
title: Annotated[str, typer.Option("--title", help="Artifact title.")],
outcome: Annotated[
list[str] | None,
typer.Option("--outcome", help="Artifact outcome. Repeatable."),
] = None,
kind: Annotated[
Literal["workflow", "wrapper"], typer.Option("--kind", help="Artifact kind.")
] = "workflow",
description: Annotated[
str | None, typer.Option("--description", help="Artifact description.")
] = None,
binding: Annotated[
list[str] | None,
typer.Option("--binding", help="Logical=concrete source binding. Repeatable."),
] = None,
) -> None:
"""Create an artifact from a raw JSON/YAML workflow plan file."""
try:
plan = parse_structured_file(plan_file)
source_bindings = parse_bindings(binding or [])
except CliInputError as exc:
raise typer.BadParameter(str(exc)) from exc
context = load_cli_context(ctx)
emit_json(
run_cli_operation(
context,
context.handlers.create_artifact_from_plan(
artifact_id=artifact_id,
version=version,
title=title,
plan=plan,
outcomes=tuple(outcome or ["ok"]),
kind=kind,
description=description,
source_bindings=source_bindings or None,
),
)
)
@app.command("delete")
def delete_artifact(
ctx: typer.Context,
+17
View File
@@ -4,6 +4,8 @@ import json
from pathlib import Path
from typing import Any
import yaml
class CliInputError(ValueError):
"""Raised when CLI JSON/file input cannot be parsed safely."""
@@ -58,6 +60,21 @@ def parse_bindings(bindings: list[str]) -> dict[str, str]:
return parsed
def parse_structured_file(path: Path) -> dict[str, Any]:
"""Parse one JSON/YAML object file for declarative workflow inputs."""
try:
raw = path.read_text(encoding="utf-8")
except OSError as exc:
raise CliInputError(f"could not read file {path!s}: {exc}") from exc
try:
payload = yaml.safe_load(raw)
except yaml.YAMLError as exc:
raise CliInputError(f"invalid YAML/JSON file {path!s}: {exc}") from exc
if not isinstance(payload, dict):
raise CliInputError("structured file must contain an object")
return payload
def _read_input_file(path: Path | None) -> str:
"""Read a required JSON input file."""
if path is None: