Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
8 changes: 6 additions & 2 deletions src/flightdeck/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -434,12 +434,16 @@ def report(
console.print_json(json.dumps(dataclasses.asdict(data), default=str))
else:
terminal.render(data, ranked, console)
# --json is a pipeline feature: stdout must stay a single valid JSON document,
# so the "also wrote …" confirmations go to stderr when it is set. Without --json
# they stay on stdout, next to the human-readable report.
notify = err if as_json else console
if html is not None:
html.write_text(html_report.render(org, data, ranked), encoding="utf-8")
console.print(f"[green]✓[/green] dashboard: [bold]{html}[/bold]")
notify.print(f"[green]✓[/green] dashboard: [bold]{html}[/bold]")
if csv is not None:
csv.write_text(csv_export.render(statement or []), encoding="utf-8")
console.print(f"[green]✓[/green] finance statement: [bold]{csv}[/bold]")
notify.print(f"[green]✓[/green] finance statement: [bold]{csv}[/bold]")


@app.command("policy")
Expand Down
24 changes: 24 additions & 0 deletions tests/test_cli_and_html.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,30 @@ def test_run_feedback_report_loop_offline(tmp_path):
assert "chain intact" in result.output


def test_report_json_with_html_keeps_stdout_valid_json(tmp_path):
# --json is documented "for pipelines": `report --json --html x > data.json` must
# leave stdout a single valid JSON document. The "wrote dashboard/statement"
# confirmations must go to stderr, not get appended after the JSON. Run the real
# CLI in a subprocess so stdout/stderr are genuinely separate OS streams (the
# in-process CliRunner does not separate rich's stderr console).
import subprocess
import sys

root = _init(tmp_path)
html_path = tmp_path / "dash.html"
csv_path = tmp_path / "statement.csv"
proc = subprocess.run(
[sys.executable, "-c", "from flightdeck.cli import app; app()",
"report", "--dir", str(root), "--json", "--html", str(html_path), "--csv", str(csv_path)],
capture_output=True, text=True,
)
assert proc.returncode == 0, proc.stderr
data = json.loads(proc.stdout) # stdout (fd 1) is clean JSON, not JSON + confirmations
assert "workflows" in data
assert "dashboard" in proc.stderr # the confirmation went to stderr instead
assert html_path.exists() and csv_path.exists() # the files were still written


def test_report_csv_emits_a_parseable_finance_statement(tmp_path):
root = _init(tmp_path)
result = invoke(
Expand Down
Loading