diff --git a/src/fastapi_cloud_cli/cli.py b/src/fastapi_cloud_cli/cli.py index 29c1a90..f446286 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 0000000..6c3c010 --- /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 0000000..d3f152c --- /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 4d00140..4cfb480 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 0000000..519cc5d --- /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 == ""