From dbf1e88debbc3ac8ef550d3198c03bd4d393d0d9 Mon Sep 17 00:00:00 2001 From: Patrick Arminio Date: Tue, 23 Jun 2026 11:30:30 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20cloud=20ci=20print-workflow?= =?UTF-8?q?=20command?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Shortcake-Parent: main --- src/fastapi_cloud_cli/cli.py | 2 + src/fastapi_cloud_cli/commands/ci/__init__.py | 13 ++++++ .../commands/ci/print_workflow.py | 45 +++++++++++++++++++ src/fastapi_cloud_cli/commands/setup_ci.py | 8 +++- tests/test_cli_ci.py | 42 +++++++++++++++++ 5 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 src/fastapi_cloud_cli/commands/ci/__init__.py create mode 100644 src/fastapi_cloud_cli/commands/ci/print_workflow.py create mode 100644 tests/test_cli_ci.py diff --git a/src/fastapi_cloud_cli/cli.py b/src/fastapi_cloud_cli/cli.py index 29c1a90d..f4462863 100644 --- a/src/fastapi_cloud_cli/cli.py +++ b/src/fastapi_cloud_cli/cli.py @@ -8,6 +8,7 @@ from .commands.apps.link import link_app from .commands.apps.unlink import unlink_app from .commands.auth import auth_app +from .commands.ci import ci_app from .commands.deploy import deploy from .commands.deployments import deployments_app from .commands.env import env_app @@ -70,6 +71,7 @@ def cloud_main( cloud_app.add_typer(env_app, name="env") cloud_app.add_typer(auth_app, name="auth") cloud_app.add_typer(apps_app, name="apps") +cloud_app.add_typer(ci_app, name="ci") cloud_app.add_typer(deployments_app, name="deployments") cloud_app.add_typer(teams_app, name="teams") cloud_app.add_typer(tokens_app, name="tokens") diff --git a/src/fastapi_cloud_cli/commands/ci/__init__.py b/src/fastapi_cloud_cli/commands/ci/__init__.py new file mode 100644 index 00000000..6c3c0103 --- /dev/null +++ b/src/fastapi_cloud_cli/commands/ci/__init__.py @@ -0,0 +1,13 @@ +import typer + +from fastapi_cloud_cli.commands.ci.print_workflow import ( + print_workflow as print_workflow_command, +) + +ci_app = typer.Typer( + no_args_is_help=True, + help="Manage CI integration helpers.", +) +ci_app.command("print-workflow")(print_workflow_command) + +__all__ = ["ci_app"] diff --git a/src/fastapi_cloud_cli/commands/ci/print_workflow.py b/src/fastapi_cloud_cli/commands/ci/print_workflow.py new file mode 100644 index 00000000..d3f152cd --- /dev/null +++ b/src/fastapi_cloud_cli/commands/ci/print_workflow.py @@ -0,0 +1,45 @@ +from typing import Annotated, Any + +import typer +from pydantic import BaseModel +from rich_toolkit import RichToolkit + +from fastapi_cloud_cli.commands.setup_ci import ( + DEFAULT_WORKFLOW_PATH, + _get_default_branch, + _get_workflow_content, +) +from fastapi_cloud_cli.utils.cli import get_rich_toolkit +from fastapi_cloud_cli.utils.execution import JsonOutputOption + + +class CIWorkflowOutput(BaseModel): + filename: str + content: str + + +def _render_workflow_output(data: CIWorkflowOutput, toolkit: RichToolkit) -> None: + toolkit.console.print(data.content, markup=False, end="") + + +def print_workflow( + branch: Annotated[ + str | None, + typer.Option( + "--branch", + "-b", + help="Branch that triggers deploys (defaults to the repo's default branch).", + ), + ] = None, + json_output: JsonOutputOption = False, +) -> Any: + """Prints the GitHub Actions workflow YAML without writing files or secrets.""" + + branch = branch or _get_default_branch() + workflow = CIWorkflowOutput( + filename=DEFAULT_WORKFLOW_PATH.name, + content=_get_workflow_content(branch), + ) + + with get_rich_toolkit(minimal=True, json_output=json_output) as toolkit: + toolkit.success(workflow, render_output=_render_workflow_output) diff --git a/src/fastapi_cloud_cli/commands/setup_ci.py b/src/fastapi_cloud_cli/commands/setup_ci.py index 4d00140a..4cfb480c 100644 --- a/src/fastapi_cloud_cli/commands/setup_ci.py +++ b/src/fastapi_cloud_cli/commands/setup_ci.py @@ -132,8 +132,8 @@ def _get_default_branch() -> str: return "main" -def _write_workflow_file(branch: str, workflow_path: Path) -> None: - workflow_content = f"""\ +def _get_workflow_content(branch: str) -> str: + return f"""\ name: Deploy to FastAPI Cloud on: push: @@ -149,6 +149,10 @@ def _write_workflow_file(branch: str, workflow_path: Path) -> None: FASTAPI_CLOUD_TOKEN: ${{{{ secrets.FASTAPI_CLOUD_TOKEN }}}} FASTAPI_CLOUD_APP_ID: ${{{{ secrets.FASTAPI_CLOUD_APP_ID }}}} """ + + +def _write_workflow_file(branch: str, workflow_path: Path) -> None: + workflow_content = _get_workflow_content(branch) workflow_path.parent.mkdir(parents=True, exist_ok=True) workflow_path.write_text(workflow_content) diff --git a/tests/test_cli_ci.py b/tests/test_cli_ci.py new file mode 100644 index 00000000..519cc5d9 --- /dev/null +++ b/tests/test_cli_ci.py @@ -0,0 +1,42 @@ +import json +from unittest.mock import patch + +from typer.testing import CliRunner + +from fastapi_cloud_cli.cli import cloud_app as app +from fastapi_cloud_cli.commands.setup_ci import _get_workflow_content + +runner = CliRunner() + + +def test_print_workflow_uses_default_branch() -> None: + with patch( + "fastapi_cloud_cli.commands.ci.print_workflow._get_default_branch", + return_value="develop", + ): + result = runner.invoke(app, ["ci", "print-workflow"]) + + assert result.exit_code == 0 + assert result.stdout == _get_workflow_content("develop") + assert result.stderr == "" + + +def test_print_workflow_uses_branch_option() -> None: + result = runner.invoke(app, ["ci", "print-workflow", "--branch", "main"]) + + assert result.exit_code == 0 + assert result.stdout == _get_workflow_content("main") + assert result.stderr == "" + + +def test_print_workflow_json_outputs_envelope() -> None: + result = runner.invoke(app, ["ci", "print-workflow", "--branch", "main", "--json"]) + + assert result.exit_code == 0 + assert json.loads(result.stdout) == { + "data": { + "filename": "deploy.yml", + "content": _get_workflow_content("main"), + } + } + assert result.stderr == ""