Coverage for src/fastapi_cloud_cli/commands/setup_ci.py: 100%
198 statements
« prev ^ index » next coverage.py v7.16.0, created at 2026-11-09 00:25 +0000
« prev ^ index » next coverage.py v7.16.0, created at 2026-11-09 00:25 +0000
1import logging 2LbMbNbObPbQbRb
2import re 2LbMbNbObPbQbRb
3import shutil 2LbMbNbObPbQbRb
4import subprocess 2LbMbNbObPbQbRb
5from datetime import datetime, timezone 2LbMbNbObPbQbRb
6from pathlib import Path, PurePath 2LbMbNbObPbQbRb
7from typing import Annotated, Any 2LbMbNbObPbQbRb
9import typer 2LbMbNbObPbQbRb
10from pydantic import BaseModel 2LbMbNbObPbQbRb
12from fastapi_cloud_cli._app import cloud_app 2LbMbNbObPbQbRb
13from fastapi_cloud_cli.api import APIClient 2LbMbNbObPbQbRb
14from fastapi_cloud_cli.commands._auth import UserCommand, get_user_command_context 2LbMbNbObPbQbRb
15from fastapi_cloud_cli.commands.ci._app import ci_app 2LbMbNbObPbQbRb
16from fastapi_cloud_cli.utils.apps import resolve_app_id_or_fail 2LbMbNbObPbQbRb
17from fastapi_cloud_cli.utils.cli import FastAPIRichToolkit 2LbMbNbObPbQbRb
18from fastapi_cloud_cli.utils.execution import JsonOutputOption 2LbMbNbObPbQbRb
20logger = logging.getLogger(__name__) 2LbMbNbObPbQbRb
22TOKEN_EXPIRES_DAYS = 365 2LbMbNbObPbQbRb
23DEFAULT_WORKFLOW_PATH = Path(".github/workflows/deploy.yml") 2LbMbNbObPbQbRb
26class CISetupOutput(BaseModel): 2LbMbNbObPbQbRb
27 app_id: str 2LbMbNbObPbQbRb
28 repo: str 2LbMbNbObPbQbRb
29 branch: str 2LbMbNbObPbQbRb
30 workflow_path: str 2LbMbNbObPbQbRb
31 created_token: bool 2LbMbNbObPbQbRb
32 set_github_secrets: bool 2LbMbNbObPbQbRb
33 wrote_workflow: bool 2LbMbNbObPbQbRb
34 token_expired_at: str | None = None 2LbMbNbObPbQbRb
37def _render_ci_setup_output(data: CISetupOutput, toolkit: FastAPIRichToolkit) -> None: 2LbMbJ NbObK PbQbRbL
38 if data.wrote_workflow and data.set_github_secrets: 2M W N = xbqbO P Q h a | o p q X - Y @ ybrbJ Z 0 j c ebu v w 1 . 2 [ zbsb3 4 5 k d fbx y z 6 / 7 ] AbtbK 8 9 l e gbA B C R ! S ? BbubT U V i b } r s t # : $ ^ Cbvb% ' ( m f hbD E F ) ; * _ DbwbL + , n g ibG H I
39 toolkit.print("Done! Commit and push to start deploying.", emoji="✅") 1MWNOPQhaX-YJZ0jc1.2345kd6/7K89leR!STUVib#:$%'(mf);*L+,ng
40 elif data.wrote_workflow: 2= xbqb| o p q @ ybrbebu v w [ zbsbfbx y z ] AbtbgbA B C ? Bbub} r s t ^ CbvbhbD E F _ DbwbibG H I
41 toolkit.print( 2= qbo p @ rbu v [ sbx y ] tbA B ? ubr s ^ vbD E _ wbG H
42 "Done — workflow file is ready, but GitHub secrets were not set.",
43 emoji="✅",
44 )
45 elif data.set_github_secrets: 2xb| q ybebw zbfbz AbgbC Bb} t CbhbF DbibI
46 toolkit.print("Done! GitHub Actions secrets are configured.", emoji="✅") 2| q ebw fbz gbC } t hbF ibI
47 else:
48 toolkit.print("Done!", emoji="✅") 2xbybzbAbBbCbDb
50 if data.token_expired_at: 2M W N = xbqbO P Q h a | o p q X - Y @ ybrbJ Z 0 j c ebu v w 1 . 2 [ zbsb3 4 5 k d fbx y z 6 / 7 ] AbtbK 8 9 l e gbA B C R ! S ? BbubT U V i b } r s t # : $ ^ Cbvb% ' ( m f hbD E F ) ; * _ DbwbL + , n g ibG H I
51 toolkit.print_line() 2M W N = O P Q h a | o p q X - Y @ J Z 0 j c ebu v w 1 . 2 [ 3 4 5 k d fbx y z 6 / 7 ] K 8 9 l e gbA B C R ! S ? T U V i b } r s t # : $ ^ % ' ( m f hbD E F ) ; * _ L + , n g ibG H I
52 toolkit.print( 2M W N = O P Q h a | o p q X - Y @ J Z 0 j c ebu v w 1 . 2 [ 3 4 5 k d fbx y z 6 / 7 ] K 8 9 l e gbA B C R ! S ? T U V i b } r s t # : $ ^ % ' ( m f hbD E F ) ; * _ L + , n g ibG H I
53 f"Your deploy token expires on [bold]{data.token_expired_at[:10]}[/bold]. "
54 "Regenerate it from the dashboard or re-run this command before then.",
55 )
58class GitHubSecretError(Exception): 2LbMbNbObPbQbRb
59 """Raised when setting a GitHub Actions secret fails."""
61 pass 2LbMbNbObPbQbRb
64def _get_github_host(origin: str) -> str: 2LbMbNbObPbQbRb
65 match = re.search(r"(?:git@|https://)([^:/]+)", origin) 2/c:c;c=co p ?c@c[c]cu v ^c_c`c{cx y |c}c~cadA B bdcdddedr s fdgdhdidD E jdkdldmdG H
66 return match.group(1) if match else "github.com" 2/c:c;c=co p ?c@c[c]cu v ^c_c`c{cx y |c}c~cadA B bdcdddedr s fdgdhdidD E jdkdldmdG H
69def _repo_slug_from_origin(origin: str) -> str | None: 2LbMbNbObPbQbRb
70 """Extract 'owner/repo' from a GitHub remote URL."""
71 # Handles URLs like: git@github.com:owner/repo.git or https://github.com/owner/repo.git
72 # Also supports GitHub Enterprise hosts like git@github.enterprise.com:owner/repo.git
73 # Match the part after the last : or / (which is owner/repo)
74 match = re.search(r"[:/]([^:/]+/[^/]+?)(?:\.git)?$", origin) 26b7b` 8bEbM W N = xbqbO P ZbSbQ jbh a | o p q 9b!b~ #bFbX - Y @ ybrbJ Z 0bTb0 lbj c ebu v w $b%bab'bGb1 . 2 [ zbsb3 4 1bUb5 mbk d fbx y z (b)bbb*bHb6 / 7 ] AbtbK 8 2bVb9 nbl e gbA B C +b,b{ -bIbR ! S ? BbubT U 3bWbV kbi b } r s t .b/bcb:bJb# : $ ^ Cbvb% ' 4bXb( obm f hbD E F ;b=bdb?bKb) ; * _ DbwbL + 5bYb, pbn g ibG H I
75 return match.group(1) if match else None 26b7b` 8bEbM W N = xbqbO P ZbSbQ jbh a | o p q 9b!b~ #bFbX - Y @ ybrbJ Z 0bTb0 lbj c ebu v w $b%bab'bGb1 . 2 [ zbsb3 4 1bUb5 mbk d fbx y z (b)bbb*bHb6 / 7 ] AbtbK 8 2bVb9 nbl e gbA B C +b,b{ -bIbR ! S ? BbubT U 3bWbV kbi b } r s t .b/bcb:bJb# : $ ^ Cbvb% ' 4bXb( obm f hbD E F ;b=bdb?bKb) ; * _ DbwbL + 5bYb, pbn g ibG H I
78def _check_git_installed() -> bool: 2LbMbNbObPbQbRb
79 """Check if git is installed and available."""
80 return shutil.which("git") is not None 26b7b` 8bEbM W udvdN = xbqbO P ZbSb@bQ jbh a | o [bp q 9b!b~ #bFbX - wdxdY @ ybrbJ Z 0bTb]b0 lbj c ebu ^bv w $b%bab'bGb1 . ydzd2 [ zbsb3 4 1bUb_b5 mbk d fbx `by z (b)bbb*bHb6 / AdBd7 ] AbtbK 8 2bVb{b9 nbl e gbA |bB C +b,b{ -bIbR ! CdDdS ? BbubT U 3bWb}bV kbi b } r ~bs t .b/bcb:bJb# : EdFd$ ^ Cbvb% ' 4bXbac( obm f hbD bcE F ;b=bdb?bKb) ; GdHd* _ DbwbL + 5bYbcc, pbn g ibG dcH I
83def _check_gh_cli_installed() -> bool: 2LbMbNbObPbQbRb
84 """Check if the GitHub CLI (gh) is installed and available."""
85 return shutil.which("gh") is not None 2IdJdKdLdMdNdOdPdQdRdSdTdUdVd
88def _get_remote_origin() -> str: 2LbMbNbObPbQbRb
89 """Get the remote origin URL of the Git repository."""
90 try: 2ecUcfcVcgcWchcXcicYcjcZckc0c
91 # Try gh first (to respect gh repo set-default)
92 result = subprocess.run( 2ecUcfcVcgcWchcXcicYcjcZckc0c
93 ["gh", "repo", "view", "--json", "url", "-q", ".url"],
94 capture_output=True,
95 text=True,
96 check=True,
97 )
98 return result.stdout.strip() 2UcVcWcXcYcZc0c
99 # CalledProcessError if gh command fails, FileNotFoundError if gh is not installed
100 except (subprocess.CalledProcessError, FileNotFoundError): 2ecfcgchcicjckc
101 # Fallback to git command
102 result = subprocess.run( 2ecfcgchcicjckc
103 ["git", "config", "--get", "remote.origin.url"],
104 capture_output=True,
105 text=True,
106 check=True,
107 )
108 return result.stdout.strip() 2ecfcgchcicjckc
111def _set_github_secret(name: str, value: str) -> None: 2LbMbNbObPbQbRb
112 """Set a GitHub Actions secret via the gh CLI.
114 Raises:
115 GitHubSecretError: If setting the secret fails.
116 """
117 try: 2ndsctcoducvcpdwcxcqdyczcrdAcBcsdCcDctdEcFc
118 subprocess.run( 2ndsctcoducvcpdwcxcqdyczcrdAcBcsdCcDctdEcFc
119 ["gh", "secret", "set", name, "--body", value],
120 capture_output=True,
121 check=True,
122 )
123 except (subprocess.CalledProcessError, FileNotFoundError) as e: 2sctcucvcwcxcyczcAcBcCcDcEcFc
124 raise GitHubSecretError(f"Failed to set GitHub secret '{name}'") from e 2sctcucvcwcxcyczcAcBcCcDcEcFc
127def _create_token(client: APIClient, app_id: str, token_name: str) -> dict[str, str]: 2LbMbNbObPbQbRb
128 """Create a new deploy token.
130 Returns token_data dict with 'value' and 'expired_at' keys.
131 """
132 response = client.post( 2` M W N = O P Q jbh a | o p q ~ X - Y @ J Z 0 lbj c ebu v w ab1 . 2 [ 3 4 5 mbk d fbx y z bb6 / 7 ] K 8 9 nbl e gbA B C { R ! S ? T U V kbi b } r s t cb# : $ ^ % ' ( obm f hbD E F db) ; * _ L + , pbn g ibG H I
133 f"/apps/{app_id}/tokens",
134 json={"name": token_name, "expires_in_days": TOKEN_EXPIRES_DAYS},
135 )
136 response.raise_for_status() 2` M W N = O P Q jbh a | o p q ~ X - Y @ J Z 0 lbj c ebu v w ab1 . 2 [ 3 4 5 mbk d fbx y z bb6 / 7 ] K 8 9 nbl e gbA B C { R ! S ? T U V kbi b } r s t cb# : $ ^ % ' ( obm f hbD E F db) ; * _ L + , pbn g ibG H I
137 data = response.json() 2` M W N = O P Q jbh a | o p q ~ X - Y @ J Z 0 lbj c ebu v w ab1 . 2 [ 3 4 5 mbk d fbx y z bb6 / 7 ] K 8 9 nbl e gbA B C { R ! S ? T U V kbi b } r s t cb# : $ ^ % ' ( obm f hbD E F db) ; * _ L + , pbn g ibG H I
138 return {"value": data["value"], "expired_at": data["expired_at"]} 2` M W N = O P Q jbh a | o p q ~ X - Y @ J Z 0 lbj c ebu v w ab1 . 2 [ 3 4 5 mbk d fbx y z bb6 / 7 ] K 8 9 nbl e gbA B C { R ! S ? T U V kbi b } r s t cb# : $ ^ % ' ( obm f hbD E F db) ; * _ L + , pbn g ibG H I
141def _get_default_branch() -> str: 2LbMbNbObPbQbRb
142 """Get the default branch of the Git repository."""
143 try: 2Gc1cHc2cIc3cJc4cKc5cLc6cMc7c
144 result = subprocess.run( 2Gc1cHc2cIc3cJc4cKc5cLc6cMc7c
145 [
146 "gh",
147 "repo",
148 "view",
149 "--json",
150 "defaultBranchRef",
151 "-q",
152 ".defaultBranchRef.name",
153 ],
154 capture_output=True,
155 text=True,
156 check=True,
157 )
158 return result.stdout.strip() 21c2c3c4c5c6c7c
159 except (subprocess.CalledProcessError, FileNotFoundError): 2GcHcIcJcKcLcMc
160 return "main" 2GcHcIcJcKcLcMc
163def _get_workflow_content(branch: str) -> str: 2LbMbNbObPbQbRb
164 return f"""\ 2` EbWdXdYdM W N = qbO P Q h a o p ~ FbZd0d1dX - Y @ rbJ Z 0 j c u v abGb2d3d4d1 . 2 [ sb3 4 5 k d x y bbHb5d6d7d6 / 7 ] tbK 8 9 l e A B { Ib8d9d!dR ! S ? ubT U V i b r s cbJb#d$d%d# : $ ^ vb% ' ( m f D E dbKb'd(d)d) ; * _ wbL + , n g G H
165name: Deploy to FastAPI Cloud
166on:
167 push:
168 branches: [{branch}]
169jobs:
170 deploy:
171 runs-on: ubuntu-latest
172 steps:
173 - uses: actions/checkout@v5
174 # setup-uv v10.3.2
175 - uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e
176 with:
177 version: "latest-known"
178 - run: uv run fastapi deploy
179 env:
180 FASTAPI_CLOUD_TOKEN: ${{{{ secrets.FASTAPI_CLOUD_TOKEN }}}}
181 FASTAPI_CLOUD_APP_ID: ${{{{ secrets.FASTAPI_CLOUD_APP_ID }}}}
182"""
185def _write_workflow_file(branch: str, workflow_path: Path) -> None: 2LbMbNbObPbQbRb
186 workflow_content = _get_workflow_content(branch) 2` EbM W N = qbO P Q h a o p ~ FbX - Y @ rbJ Z 0 j c u v abGb1 . 2 [ sb3 4 5 k d x y bbHb6 / 7 ] tbK 8 9 l e A B { IbR ! S ? ubT U V i b r s cbJb# : $ ^ vb% ' ( m f D E dbKb) ; * _ wbL + , n g G H
187 workflow_path.parent.mkdir(parents=True, exist_ok=True) 2` EbM W N = qbO P Q h a o p ~ FbX - Y @ rbJ Z 0 j c u v abGb1 . 2 [ sb3 4 5 k d x y bbHb6 / 7 ] tbK 8 9 l e A B { IbR ! S ? ubT U V i b r s cbJb# : $ ^ vb% ' ( m f D E dbKb) ; * _ wbL + , n g G H
188 workflow_path.write_text(workflow_content) 2` EbM W N = qbO P Q h a o p ~ FbX - Y @ rbJ Z 0 j c u v abGb1 . 2 [ sb3 4 5 k d x y bbHb6 / 7 ] tbK 8 9 l e A B { IbR ! S ? ubT U V i b r s cbJb# : $ ^ vb% ' ( m f D E dbKb) ; * _ wbL + , n g G H
191def _get_workflow_path(file: str | None) -> Path: 2LbMbNbObPbQbRb
192 if file: 26b7b` 8bEbM W N = xbqbO P ZbSbQ jbh a | o p q 9b!b~ #bFbX - Y @ ybrbJ Z 0bTb0 lbj c ebu v w $b%bab'bGb1 . 2 [ zbsb3 4 1bUb5 mbk d fbx y z (b)bbb*bHb6 / 7 ] AbtbK 8 2bVb9 nbl e gbA B C +b,b{ -bIbR ! S ? BbubT U 3bWbV kbi b } r s t .b/bcb:bJb# : $ ^ Cbvb% ' 4bXb( obm f hbD E F ;b=bdb?bKb) ; * _ DbwbL + 5bYb, pbn g ibG H I
193 return Path(f".github/workflows/{file}") 2` Q ~ 0 ab5 bb9 { V cb( db,
195 return DEFAULT_WORKFLOW_PATH 26b7b8bEbM W N = xbqbO P ZbSbjbh a | o p q 9b!b#bFbX - Y @ ybrbJ Z 0bTblbj c ebu v w $b%b'bGb1 . 2 [ zbsb3 4 1bUbmbk d fbx y z (b)b*bHb6 / 7 ] AbtbK 8 2bVbnbl e gbA B C +b,b-bIbR ! S ? BbubT U 3bWbkbi b } r s t .b/b:bJb# : $ ^ Cbvb% ' 4bXbobm f hbD E F ;b=b?bKb) ; * _ DbwbL + 5bYbpbn g ibG H I
198def _format_workflow_path(workflow_path: PurePath) -> str: 2LbMbNbObPbQbRb
199 return workflow_path.as_posix() 26b7b` EbM W N = xbqbO P ZbSbNcQ *dh a | o p q 9b!b~ FbX - Y @ ybrbJ Z 0bTbOc0 +dj c ebu v w $b%babGb1 . 2 [ zbsb3 4 1bUbPc5 ,dk d fbx y z (b)bbbHb6 / 7 ] AbtbK 8 2bVbQc9 -dl e gbA B C +b,b{ IbR ! S ? BbubT U 3bWbRcV .di b } r s t .b/bcbJb# : $ ^ Cbvb% ' 4bXbSc( /dm f hbD E F ;b=bdbKb) ; * _ DbwbL + 5bYbTc, :dn g ibG H I
202def _resolve_existing_workflow_path( 2LbMbNbObPbQbRb
203 toolkit: FastAPIRichToolkit, workflow_path: Path
204) -> Path | None:
205 if toolkit.confirm( 2Nch a q Ocj c w Pck d z Qcl e C Rci b t Scm f F Tcn g I
206 f"Workflow file [bold]{_format_workflow_path(workflow_path)}[/bold] already exists. Overwrite?",
207 default=False,
208 emoji="🗂️",
209 ):
210 toolkit.print_line() 2Nch Ocj Pck Qcl Rci Scm Tcn
212 return workflow_path 2Nch Ocj Pck Qcl Rci Scm Tcn
214 toolkit.print_line() 1aqcwdzeCbtfFgI
216 if new_name := toolkit.input( 1aqcwdzeCbtfFgI
217 "Enter a new filename (without path) or leave blank to skip writing the workflow file:",
218 emoji="✏️",
219 ).strip():
220 toolkit.print_line() 1acdebfg
222 return Path(f".github/workflows/{new_name}") 1acdebfg
224 toolkit.print_line() 1qwzCtFI
225 toolkit.print("Skipped writing workflow file.", emoji="⏭️") 1qwzCtFI
226 toolkit.print_line() 1qwzCtFI
228 return None 1qwzCtFI
231@cloud_app.command(cls=UserCommand) 2LbMbNbObPbQbRb
232@ci_app.command("setup", cls=UserCommand) 2LbMbNbObPbQbRb
233def setup_ci( 2LbMb;dNbOb=dPbQbRb?d
234 ctx: typer.Context,
235 path: Annotated[
236 Path | None,
237 typer.Argument(
238 help=(
239 "Path to the directory with your app's pyproject.toml "
240 "(defaults to current directory)"
241 )
242 ),
243 ] = None,
244 app_id: str | None = typer.Option(
245 None,
246 "--app-id",
247 help="ID of the app to set up CI for (defaults to the app linked to the directory)",
248 ),
249 branch: str | None = typer.Option(
250 None,
251 "--branch",
252 "-b",
253 help="Branch that triggers deploys (defaults to the repo's default branch)",
254 ),
255 secrets_only: bool = typer.Option(
256 False,
257 "--secrets-only",
258 "-s",
259 help="Provisions token and sets secrets, skips writing the workflow file",
260 show_default=True,
261 ),
262 workflow_only: bool = typer.Option(
263 False,
264 "--workflow-only",
265 help="Writes the workflow file without creating a token or setting secrets",
266 show_default=True,
267 ),
268 dry_run: bool = typer.Option(
269 False,
270 "--dry-run",
271 "-d",
272 help="Prints steps that would be taken without actually performing them",
273 show_default=True,
274 ),
275 file: str | None = typer.Option(
276 None,
277 "--file",
278 "-f",
279 help="Custom workflow filename (written to .github/workflows/)",
280 ),
281 json_output: JsonOutputOption = False,
282) -> Any:
283 """Configures a GitHub Actions workflow for deploying the app on push to the specified branch.
285 Examples:
286 fastapi cloud setup-ci # Provisions token, sets secrets, and writes workflow file for the 'main' branch
287 fastapi cloud setup-ci --branch develop # Same as above but for the 'develop' branch
288 fastapi cloud setup-ci --secrets-only # Only provisions token and sets secrets, does not write workflow file
289 fastapi cloud setup-ci --workflow-only # Only writes the workflow file
290 fastapi cloud setup-ci --dry-run # Prints the steps that would be taken without performing them
291 fastapi cloud setup-ci --file ci.yml # Writes workflow to .github/workflows/ci.yml
292 """
294 toolkit = get_user_command_context(ctx).toolkit 26b7b` 8bEbM W N = xbqbO P ZbSb@bQ jbh a | o lc[b8cp 9cq 9b!b~ #bFbX - Y @ ybrbJ Z 0bTb]b0 lbj c ebu mc^b!cv #cw $b%bab'bGb1 . 2 [ zbsb3 4 1bUb_b5 mbk d fbx nc`b$cy %cz (b)bbb*bHb6 / 7 ] AbtbK 8 2bVb{b9 nbl e gbA oc|b'cB (cC +b,b{ -bIbR ! S ? BbubT U 3bWb}bV kbi b } r pc~b)cs *ct .b/bcb:bJb# : $ ^ Cbvb% ' 4bXbac( obm f hbD qcbc+cE ,cF ;b=bdb?bKb) ; * _ DbwbL + 5bYbcc, pbn g ibG rcdc-cH .cI
296 if secrets_only and workflow_only: 26b7b` 8bEbM W N = xbqbO P ZbSb@bQ jbh a | o lc[b8cp 9cq 9b!b~ #bFbX - Y @ ybrbJ Z 0bTb]b0 lbj c ebu mc^b!cv #cw $b%bab'bGb1 . 2 [ zbsb3 4 1bUb_b5 mbk d fbx nc`b$cy %cz (b)bbb*bHb6 / 7 ] AbtbK 8 2bVb{b9 nbl e gbA oc|b'cB (cC +b,b{ -bIbR ! S ? BbubT U 3bWb}bV kbi b } r pc~b)cs *ct .b/bcb:bJb# : $ ^ Cbvb% ' 4bXbac( obm f hbD qcbc+cE ,cF ;b=bdb?bKb) ; * _ DbwbL + 5bYbcc, pbn g ibG rcdc-cH .cI
297 toolkit.fail( 28c!c$c'c)c+c-c
298 "invalid_input",
299 "--secrets-only and --workflow-only cannot be used together.",
300 )
302 target_app_id = resolve_app_id_or_fail( 26b7b` 8bEbM W N = xbqbO P ZbSb@bQ jbh a | o lc[bp 9cq 9b!b~ #bFbX - Y @ ybrbJ Z 0bTb]b0 lbj c ebu mc^bv #cw $b%bab'bGb1 . 2 [ zbsb3 4 1bUb_b5 mbk d fbx nc`by %cz (b)bbb*bHb6 / 7 ] AbtbK 8 2bVb{b9 nbl e gbA oc|bB (cC +b,b{ -bIbR ! S ? BbubT U 3bWb}bV kbi b } r pc~bs *ct .b/bcb:bJb# : $ ^ Cbvb% ' 4bXbac( obm f hbD qcbcE ,cF ;b=bdb?bKb) ; * _ DbwbL + 5bYbcc, pbn g ibG rcdcH .cI
303 toolkit,
304 app_id=app_id,
305 path=path,
306 hint="Pass --app-id or run `fastapi deploy` first.",
307 )
309 if not _check_git_installed(): 26b7b` 8bEbM W N = xbqbO P ZbSb@bQ jbh a | o lc[bp q 9b!b~ #bFbX - Y @ ybrbJ Z 0bTb]b0 lbj c ebu mc^bv w $b%bab'bGb1 . 2 [ zbsb3 4 1bUb_b5 mbk d fbx nc`by z (b)bbb*bHb6 / 7 ] AbtbK 8 2bVb{b9 nbl e gbA oc|bB C +b,b{ -bIbR ! S ? BbubT U 3bWb}bV kbi b } r pc~bs t .b/bcb:bJb# : $ ^ Cbvb% ' 4bXbac( obm f hbD qcbcE F ;b=bdb?bKb) ; * _ DbwbL + 5bYbcc, pbn g ibG rcdcH I
310 toolkit.fail( 2lcmcncocpcqcrc
311 "not_found",
312 "git is not installed. Please install git to use this command.",
313 )
315 try: 26b7b` 8bEbM W N = xbqbO P ZbSb@bQ jbh a | o [bp q 9b!b~ #bFbX - Y @ ybrbJ Z 0bTb]b0 lbj c ebu ^bv w $b%bab'bGb1 . 2 [ zbsb3 4 1bUb_b5 mbk d fbx `by z (b)bbb*bHb6 / 7 ] AbtbK 8 2bVb{b9 nbl e gbA |bB C +b,b{ -bIbR ! S ? BbubT U 3bWb}bV kbi b } r ~bs t .b/bcb:bJb# : $ ^ Cbvb% ' 4bXbac( obm f hbD bcE F ;b=bdb?bKb) ; * _ DbwbL + 5bYbcc, pbn g ibG dcH I
316 origin = _get_remote_origin() 26b7b` 8bEbM W N = xbqbO P ZbSb@bQ jbh a | o [bp q 9b!b~ #bFbX - Y @ ybrbJ Z 0bTb]b0 lbj c ebu ^bv w $b%bab'bGb1 . 2 [ zbsb3 4 1bUb_b5 mbk d fbx `by z (b)bbb*bHb6 / 7 ] AbtbK 8 2bVb{b9 nbl e gbA |bB C +b,b{ -bIbR ! S ? BbubT U 3bWb}bV kbi b } r ~bs t .b/bcb:bJb# : $ ^ Cbvb% ' 4bXbac( obm f hbD bcE F ;b=bdb?bKb) ; * _ DbwbL + 5bYbcc, pbn g ibG dcH I
317 except subprocess.CalledProcessError: 2@b]b_b{b}baccc
318 toolkit.fail( 2@b]b_b{b}baccc
319 "not_found",
320 "Could not retrieve the git remote origin URL. Make sure you're in a git repository with a remote origin set.",
321 )
323 # Check if it's a GitHub host (github.com or GitHub Enterprise)
324 if "github" not in origin.lower(): 26b7b` 8bEbM W N = xbqbO P ZbSbQ jbh a | o [bp q 9b!b~ #bFbX - Y @ ybrbJ Z 0bTb0 lbj c ebu ^bv w $b%bab'bGb1 . 2 [ zbsb3 4 1bUb5 mbk d fbx `by z (b)bbb*bHb6 / 7 ] AbtbK 8 2bVb9 nbl e gbA |bB C +b,b{ -bIbR ! S ? BbubT U 3bWbV kbi b } r ~bs t .b/bcb:bJb# : $ ^ Cbvb% ' 4bXb( obm f hbD bcE F ;b=bdb?bKb) ; * _ DbwbL + 5bYb, pbn g ibG dcH I
325 toolkit.fail( 2[b^b`b|b~bbcdc
326 "invalid_input",
327 "Remote origin is not a GitHub repository. Please set up a GitHub repo and add it as the remote origin.",
328 )
330 repo_slug = _repo_slug_from_origin(origin) or origin 26b7b` 8bEbM W N = xbqbO P ZbSbQ jbh a | o p q 9b!b~ #bFbX - Y @ ybrbJ Z 0bTb0 lbj c ebu v w $b%bab'bGb1 . 2 [ zbsb3 4 1bUb5 mbk d fbx y z (b)bbb*bHb6 / 7 ] AbtbK 8 2bVb9 nbl e gbA B C +b,b{ -bIbR ! S ? BbubT U 3bWbV kbi b } r s t .b/bcb:bJb# : $ ^ Cbvb% ' 4bXb( obm f hbD E F ;b=bdb?bKb) ; * _ DbwbL + 5bYb, pbn g ibG H I
332 if not branch: 26b7b` 8bEbM W N = xbqbO P ZbSbQ jbh a | o p q 9b!b~ #bFbX - Y @ ybrbJ Z 0bTb0 lbj c ebu v w $b%bab'bGb1 . 2 [ zbsb3 4 1bUb5 mbk d fbx y z (b)bbb*bHb6 / 7 ] AbtbK 8 2bVb9 nbl e gbA B C +b,b{ -bIbR ! S ? BbubT U 3bWbV kbi b } r s t .b/bcb:bJb# : $ ^ Cbvb% ' 4bXb( obm f hbD E F ;b=bdb?bKb) ; * _ DbwbL + 5bYb, pbn g ibG H I
333 branch = _get_default_branch() 2M N = xbqbO P ZbSbQ jbh a | o p q X Y @ ybrbJ Z 0bTb0 lbj c ebu v w 1 2 [ zbsb3 4 1bUb5 mbk d fbx y z 6 7 ] AbtbK 8 2bVb9 nbl e gbA B C R S ? BbubT U 3bWbV kbi b } r s t # $ ^ Cbvb% ' 4bXb( obm f hbD E F ) * _ DbwbL + 5bYb, pbn g ibG H I
335 workflow_path = _get_workflow_path(file) 26b7b` 8bEbM W N = xbqbO P ZbSbQ jbh a | o p q 9b!b~ #bFbX - Y @ ybrbJ Z 0bTb0 lbj c ebu v w $b%bab'bGb1 . 2 [ zbsb3 4 1bUb5 mbk d fbx y z (b)bbb*bHb6 / 7 ] AbtbK 8 2bVb9 nbl e gbA B C +b,b{ -bIbR ! S ? BbubT U 3bWbV kbi b } r s t .b/bcb:bJb# : $ ^ Cbvb% ' 4bXb( obm f hbD E F ;b=bdb?bKb) ; * _ DbwbL + 5bYb, pbn g ibG H I
336 needs_secrets = not workflow_only 26b7b` 8bEbM W N = xbqbO P ZbSbQ jbh a | o p q 9b!b~ #bFbX - Y @ ybrbJ Z 0bTb0 lbj c ebu v w $b%bab'bGb1 . 2 [ zbsb3 4 1bUb5 mbk d fbx y z (b)bbb*bHb6 / 7 ] AbtbK 8 2bVb9 nbl e gbA B C +b,b{ -bIbR ! S ? BbubT U 3bWbV kbi b } r s t .b/bcb:bJb# : $ ^ Cbvb% ' 4bXb( obm f hbD E F ;b=bdb?bKb) ; * _ DbwbL + 5bYb, pbn g ibG H I
337 needs_workflow = not secrets_only 26b7b` 8bEbM W N = xbqbO P ZbSbQ jbh a | o p q 9b!b~ #bFbX - Y @ ybrbJ Z 0bTb0 lbj c ebu v w $b%bab'bGb1 . 2 [ zbsb3 4 1bUb5 mbk d fbx y z (b)bbb*bHb6 / 7 ] AbtbK 8 2bVb9 nbl e gbA B C +b,b{ -bIbR ! S ? BbubT U 3bWbV kbi b } r s t .b/bcb:bJb# : $ ^ Cbvb% ' 4bXb( obm f hbD E F ;b=bdb?bKb) ; * _ DbwbL + 5bYb, pbn g ibG H I
338 has_gh = _check_gh_cli_installed() if needs_secrets and not dry_run else True 26b7b` 8bEbM W N = xbqbO P ZbSbQ jbh a | o p q 9b!b~ #bFbX - Y @ ybrbJ Z 0bTb0 lbj c ebu v w $b%bab'bGb1 . 2 [ zbsb3 4 1bUb5 mbk d fbx y z (b)bbb*bHb6 / 7 ] AbtbK 8 2bVb9 nbl e gbA B C +b,b{ -bIbR ! S ? BbubT U 3bWbV kbi b } r s t .b/bcb:bJb# : $ ^ Cbvb% ' 4bXb( obm f hbD E F ;b=bdb?bKb) ; * _ DbwbL + 5bYb, pbn g ibG H I
340 if (
341 toolkit.mode == "json"
342 and needs_workflow
343 and not dry_run
344 and not file
345 and workflow_path.exists()
346 ):
347 toolkit.fail( 26b7b9b!b$b%b(b)b+b,b.b/b;b=b
348 "invalid_input",
349 f"Workflow file {_format_workflow_path(workflow_path)} already exists.",
350 hint="Pass --file to choose another workflow file or remove the existing file.",
351 )
353 if needs_secrets and not dry_run and toolkit.mode == "json" and not has_gh: 2` 8bEbM W N = xbqbO P ZbSbQ jbh a | o p q ~ #bFbX - Y @ ybrbJ Z 0bTb0 lbj c ebu v w ab'bGb1 . 2 [ zbsb3 4 1bUb5 mbk d fbx y z bb*bHb6 / 7 ] AbtbK 8 2bVb9 nbl e gbA B C { -bIbR ! S ? BbubT U 3bWbV kbi b } r s t cb:bJb# : $ ^ Cbvb% ' 4bXb( obm f hbD E F db?bKb) ; * _ DbwbL + 5bYb, pbn g ibG H I
354 toolkit.fail( 28b#b'b*b-b:b?b
355 "dependency_missing",
356 "GitHub CLI (`gh`) is required to set GitHub Actions secrets.",
357 hint="Install gh or use --workflow-only to write only the workflow file.",
358 )
360 if dry_run: 2` EbM W N = xbqbO P ZbSbQ jbh a | o p q ~ FbX - Y @ ybrbJ Z 0bTb0 lbj c ebu v w abGb1 . 2 [ zbsb3 4 1bUb5 mbk d fbx y z bbHb6 / 7 ] AbtbK 8 2bVb9 nbl e gbA B C { IbR ! S ? BbubT U 3bWbV kbi b } r s t cbJb# : $ ^ Cbvb% ' 4bXb( obm f hbD E F dbKb) ; * _ DbwbL + 5bYb, pbn g ibG H I
361 toolkit.print("[yellow]This is a dry run — no changes will be made[/yellow]") 2ZbSb0bTb1bUb2bVb3bWb4bXb5bYb
362 toolkit.print_line() 2ZbSb0bTb1bUb2bVb3bWb4bXb5bYb
364 toolkit.print_title("Configuring CI") 2` EbM W N = xbqbO P ZbSbQ jbh a | o p q ~ FbX - Y @ ybrbJ Z 0bTb0 lbj c ebu v w abGb1 . 2 [ zbsb3 4 1bUb5 mbk d fbx y z bbHb6 / 7 ] AbtbK 8 2bVb9 nbl e gbA B C { IbR ! S ? BbubT U 3bWbV kbi b } r s t cbJb# : $ ^ Cbvb% ' 4bXb( obm f hbD E F dbKb) ; * _ DbwbL + 5bYb, pbn g ibG H I
365 toolkit.print_line() 2` EbM W N = xbqbO P ZbSbQ jbh a | o p q ~ FbX - Y @ ybrbJ Z 0bTb0 lbj c ebu v w abGb1 . 2 [ zbsb3 4 1bUb5 mbk d fbx y z bbHb6 / 7 ] AbtbK 8 2bVb9 nbl e gbA B C { IbR ! S ? BbubT U 3bWbV kbi b } r s t cbJb# : $ ^ Cbvb% ' 4bXb( obm f hbD E F dbKb) ; * _ DbwbL + 5bYb, pbn g ibG H I
367 toolkit.print( 2` EbM W N = xbqbO P ZbSbQ jbh a | o p q ~ FbX - Y @ ybrbJ Z 0bTb0 lbj c ebu v w abGb1 . 2 [ zbsb3 4 1bUb5 mbk d fbx y z bbHb6 / 7 ] AbtbK 8 2bVb9 nbl e gbA B C { IbR ! S ? BbubT U 3bWbV kbi b } r s t cbJb# : $ ^ Cbvb% ' 4bXb( obm f hbD E F dbKb) ; * _ DbwbL + 5bYb, pbn g ibG H I
368 f"Setting up CI for [bold]{repo_slug}[/bold] (branch: {branch})",
369 emoji="⚙️",
370 )
371 toolkit.print_line() 2` EbM W N = xbqbO P ZbSbQ jbh a | o p q ~ FbX - Y @ ybrbJ Z 0bTb0 lbj c ebu v w abGb1 . 2 [ zbsb3 4 1bUb5 mbk d fbx y z bbHb6 / 7 ] AbtbK 8 2bVb9 nbl e gbA B C { IbR ! S ? BbubT U 3bWbV kbi b } r s t cbJb# : $ ^ Cbvb% ' 4bXb( obm f hbD E F dbKb) ; * _ DbwbL + 5bYb, pbn g ibG H I
373 msg_token = "Created deploy token" 2` EbM W N = xbqbO P ZbSbQ jbh a | o p q ~ FbX - Y @ ybrbJ Z 0bTb0 lbj c ebu v w abGb1 . 2 [ zbsb3 4 1bUb5 mbk d fbx y z bbHb6 / 7 ] AbtbK 8 2bVb9 nbl e gbA B C { IbR ! S ? BbubT U 3bWbV kbi b } r s t cbJb# : $ ^ Cbvb% ' 4bXb( obm f hbD E F dbKb) ; * _ DbwbL + 5bYb, pbn g ibG H I
374 msg_secrets = ( 2` EbM W N = xbqbO P ZbSbQ jbh a | o p q ~ FbX - Y @ ybrbJ Z 0bTb0 lbj c ebu v w abGb1 . 2 [ zbsb3 4 1bUb5 mbk d fbx y z bbHb6 / 7 ] AbtbK 8 2bVb9 nbl e gbA B C { IbR ! S ? BbubT U 3bWbV kbi b } r s t cbJb# : $ ^ Cbvb% ' 4bXb( obm f hbD E F dbKb) ; * _ DbwbL + 5bYb, pbn g ibG H I
375 "Set GitHub Actions secrets [bold blue]FASTAPI_CLOUD_TOKEN[/] "
376 "and [bold blue]FASTAPI_CLOUD_APP_ID[/]"
377 )
378 msg_workflow = f"Wrote [bold]{workflow_path}[/bold] (branch: {branch})" 2` EbM W N = xbqbO P ZbSbQ jbh a | o p q ~ FbX - Y @ ybrbJ Z 0bTb0 lbj c ebu v w abGb1 . 2 [ zbsb3 4 1bUb5 mbk d fbx y z bbHb6 / 7 ] AbtbK 8 2bVb9 nbl e gbA B C { IbR ! S ? BbubT U 3bWbV kbi b } r s t cbJb# : $ ^ Cbvb% ' 4bXb( obm f hbD E F dbKb) ; * _ DbwbL + 5bYb, pbn g ibG H I
380 if dry_run: 2` EbM W N = xbqbO P ZbSbQ jbh a | o p q ~ FbX - Y @ ybrbJ Z 0bTb0 lbj c ebu v w abGb1 . 2 [ zbsb3 4 1bUb5 mbk d fbx y z bbHb6 / 7 ] AbtbK 8 2bVb9 nbl e gbA B C { IbR ! S ? BbubT U 3bWbV kbi b } r s t cbJb# : $ ^ Cbvb% ' 4bXb( obm f hbD E F dbKb) ; * _ DbwbL + 5bYb, pbn g ibG H I
381 if needs_secrets: 2ZbSb0bTb1bUb2bVb3bWb4bXb5bYb
382 toolkit.print(msg_token) 2ZbSb0bTb1bUb2bVb3bWb4bXb5bYb
383 toolkit.print(msg_secrets) 2ZbSb0bTb1bUb2bVb3bWb4bXb5bYb
385 if needs_workflow: 2ZbSb0bTb1bUb2bVb3bWb4bXb5bYb
386 toolkit.print(msg_workflow) 2SbTbUbVbWbXbYb
388 toolkit.success( 2ZbSb0bTb1bUb2bVb3bWb4bXb5bYb
389 CISetupOutput(
390 app_id=target_app_id,
391 repo=repo_slug,
392 branch=branch,
393 workflow_path=_format_workflow_path(workflow_path),
394 created_token=False,
395 set_github_secrets=False,
396 wrote_workflow=False,
397 ),
398 render_output=lambda _data, _toolkit: None,
399 )
400 return 2ZbSb0bTb1bUb2bVb3bWb4bXb5bYb
402 token_expired_at: str | None = None 2` EbM W N = xbqbO P Q jbh a | o p q ~ FbX - Y @ ybrbJ Z 0 lbj c ebu v w abGb1 . 2 [ zbsb3 4 5 mbk d fbx y z bbHb6 / 7 ] AbtbK 8 9 nbl e gbA B C { IbR ! S ? BbubT U V kbi b } r s t cbJb# : $ ^ Cbvb% ' ( obm f hbD E F dbKb) ; * _ DbwbL + , pbn g ibG H I
403 created_token = False 2` EbM W N = xbqbO P Q jbh a | o p q ~ FbX - Y @ ybrbJ Z 0 lbj c ebu v w abGb1 . 2 [ zbsb3 4 5 mbk d fbx y z bbHb6 / 7 ] AbtbK 8 9 nbl e gbA B C { IbR ! S ? BbubT U V kbi b } r s t cbJb# : $ ^ Cbvb% ' ( obm f hbD E F dbKb) ; * _ DbwbL + , pbn g ibG H I
404 set_github_secrets = False 2` EbM W N = xbqbO P Q jbh a | o p q ~ FbX - Y @ ybrbJ Z 0 lbj c ebu v w abGb1 . 2 [ zbsb3 4 5 mbk d fbx y z bbHb6 / 7 ] AbtbK 8 9 nbl e gbA B C { IbR ! S ? BbubT U V kbi b } r s t cbJb# : $ ^ Cbvb% ' ( obm f hbD E F dbKb) ; * _ DbwbL + , pbn g ibG H I
405 wrote_workflow = False 2` EbM W N = xbqbO P Q jbh a | o p q ~ FbX - Y @ ybrbJ Z 0 lbj c ebu v w abGb1 . 2 [ zbsb3 4 5 mbk d fbx y z bbHb6 / 7 ] AbtbK 8 9 nbl e gbA B C { IbR ! S ? BbubT U V kbi b } r s t cbJb# : $ ^ Cbvb% ' ( obm f hbD E F dbKb) ; * _ DbwbL + , pbn g ibG H I
407 if needs_secrets: 2` EbM W N = xbqbO P Q jbh a | o p q ~ FbX - Y @ ybrbJ Z 0 lbj c ebu v w abGb1 . 2 [ zbsb3 4 5 mbk d fbx y z bbHb6 / 7 ] AbtbK 8 9 nbl e gbA B C { IbR ! S ? BbubT U V kbi b } r s t cbJb# : $ ^ Cbvb% ' ( obm f hbD E F dbKb) ; * _ DbwbL + , pbn g ibG H I
408 should_create_token = ( 2` M W N = xbqbO P Q jbh a | o p q ~ X - Y @ ybrbJ Z 0 lbj c ebu v w ab1 . 2 [ zbsb3 4 5 mbk d fbx y z bb6 / 7 ] AbtbK 8 9 nbl e gbA B C { R ! S ? BbubT U V kbi b } r s t cb# : $ ^ Cbvb% ' ( obm f hbD E F db) ; * _ DbwbL + , pbn g ibG H I
409 True
410 if toolkit.mode == "json"
411 else toolkit.confirm(
412 "Create a FastAPI Cloud deploy token for GitHub Actions?",
413 default=True,
414 )
415 )
416 if toolkit.mode != "json": 2` M W N = xbqbO P Q jbh a | o p q ~ X - Y @ ybrbJ Z 0 lbj c ebu v w ab1 . 2 [ zbsb3 4 5 mbk d fbx y z bb6 / 7 ] AbtbK 8 9 nbl e gbA B C { R ! S ? BbubT U V kbi b } r s t cb# : $ ^ Cbvb% ' ( obm f hbD E F db) ; * _ DbwbL + , pbn g ibG H I
417 toolkit.print_line() 2M W N = xbqbO P Q jbh a | o p q X - Y @ ybrbJ Z 0 lbj c ebu v w 1 . 2 [ zbsb3 4 5 mbk d fbx y z 6 / 7 ] AbtbK 8 9 nbl e gbA B C R ! S ? BbubT U V kbi b } r s t # : $ ^ Cbvb% ' ( obm f hbD E F ) ; * _ DbwbL + , pbn g ibG H I
419 if should_create_token: 2` M W N = xbqbO P Q jbh a | o p q ~ X - Y @ ybrbJ Z 0 lbj c ebu v w ab1 . 2 [ zbsb3 4 5 mbk d fbx y z bb6 / 7 ] AbtbK 8 9 nbl e gbA B C { R ! S ? BbubT U V kbi b } r s t cb# : $ ^ Cbvb% ' ( obm f hbD E F db) ; * _ DbwbL + , pbn g ibG H I
420 # Create unique token name with timestamp to avoid duplicates
421 timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC") 2` M W N = O P Q jbh a | o p q ~ X - Y @ J Z 0 lbj c ebu v w ab1 . 2 [ 3 4 5 mbk d fbx y z bb6 / 7 ] K 8 9 nbl e gbA B C { R ! S ? T U V kbi b } r s t cb# : $ ^ % ' ( obm f hbD E F db) ; * _ L + , pbn g ibG H I
422 token_name = f"GitHub Actions — {repo_slug} ({timestamp})" 2` M W N = O P Q jbh a | o p q ~ X - Y @ J Z 0 lbj c ebu v w ab1 . 2 [ 3 4 5 mbk d fbx y z bb6 / 7 ] K 8 9 nbl e gbA B C { R ! S ? T U V kbi b } r s t cb# : $ ^ % ' ( obm f hbD E F db) ; * _ L + , pbn g ibG H I
424 with ( 2` M W N = O P Q jbh a | o p q { R ! S ? T U V kbi b } r s t
425 APIClient() as client,
426 toolkit.progress(
427 title="Generating deploy token...", done_emoji="🔑"
428 ) as progress,
429 client.handle_http_errors(
430 progress, default_message="Error creating deploy token."
431 ),
432 ):
433 token_data = _create_token( 2` M W N = O P Q jbh a | o p q ~ X - Y @ J Z 0 lbj c ebu v w ab1 . 2 [ 3 4 5 mbk d fbx y z bb6 / 7 ] K 8 9 nbl e gbA B C { R ! S ? T U V kbi b } r s t cb# : $ ^ % ' ( obm f hbD E F db) ; * _ L + , pbn g ibG H I
434 client=client, app_id=target_app_id, token_name=token_name
435 )
436 token_expired_at = token_data["expired_at"] 2` M W N = O P Q jbh a | o p q ~ X - Y @ J Z 0 lbj c ebu v w ab1 . 2 [ 3 4 5 mbk d fbx y z bb6 / 7 ] K 8 9 nbl e gbA B C { R ! S ? T U V kbi b } r s t cb# : $ ^ % ' ( obm f hbD E F db) ; * _ L + , pbn g ibG H I
437 created_token = True 2` M W N = O P Q jbh a | o p q ~ X - Y @ J Z 0 lbj c ebu v w ab1 . 2 [ 3 4 5 mbk d fbx y z bb6 / 7 ] K 8 9 nbl e gbA B C { R ! S ? T U V kbi b } r s t cb# : $ ^ % ' ( obm f hbD E F db) ; * _ L + , pbn g ibG H I
438 progress.log(msg_token) 2` M W N = O P Q jbh a | o p q ~ X - Y @ J Z 0 lbj c ebu v w ab1 . 2 [ 3 4 5 mbk d fbx y z bb6 / 7 ] K 8 9 nbl e gbA B C { R ! S ? T U V kbi b } r s t cb# : $ ^ % ' ( obm f hbD E F db) ; * _ L + , pbn g ibG H I
440 toolkit.print_line() 2` M W N = O P Q jbh a | o p q ~ X - Y @ J Z 0 lbj c ebu v w ab1 . 2 [ 3 4 5 mbk d fbx y z bb6 / 7 ] K 8 9 nbl e gbA B C { R ! S ? T U V kbi b } r s t cb# : $ ^ % ' ( obm f hbD E F db) ; * _ L + , pbn g ibG H I
442 if has_gh: 2` M W N = O P Q jbh a | o p q ~ X - Y @ J Z 0 lbj c ebu v w ab1 . 2 [ 3 4 5 mbk d fbx y z bb6 / 7 ] K 8 9 nbl e gbA B C { R ! S ? T U V kbi b } r s t cb# : $ ^ % ' ( obm f hbD E F db) ; * _ L + , pbn g ibG H I
443 should_set_secrets = ( 2` M W N = O P Q jbh a | q ~ X - Y @ J Z 0 lbj c ebw ab1 . 2 [ 3 4 5 mbk d fbz bb6 / 7 ] K 8 9 nbl e gbC { R ! S ? T U V kbi b } t cb# : $ ^ % ' ( obm f hbF db) ; * _ L + , pbn g ibI
444 True
445 if toolkit.mode == "json"
446 else toolkit.confirm(
447 "Set GitHub Actions secrets "
448 "[bold blue]FASTAPI_CLOUD_TOKEN[/] and "
449 "[bold blue]FASTAPI_CLOUD_APP_ID[/] via gh?",
450 default=True,
451 )
452 )
453 if toolkit.mode != "json": 2` M W N = O P Q jbh a | q ~ X - Y @ J Z 0 lbj c ebw ab1 . 2 [ 3 4 5 mbk d fbz bb6 / 7 ] K 8 9 nbl e gbC { R ! S ? T U V kbi b } t cb# : $ ^ % ' ( obm f hbF db) ; * _ L + , pbn g ibI
454 toolkit.print_line() 2M W N = O P Q jbh a | q X - Y @ J Z 0 lbj c ebw 1 . 2 [ 3 4 5 mbk d fbz 6 / 7 ] K 8 9 nbl e gbC R ! S ? T U V kbi b } t # : $ ^ % ' ( obm f hbF ) ; * _ L + , pbn g ibI
455 else:
456 should_set_secrets = False 1opuvxyABrsDEGH
457 secrets_url = ( 1opuvxyABrsDEGH
458 f"https://{_get_github_host(origin)}/{repo_slug}"
459 "/settings/secrets/actions"
460 )
461 toolkit.print( 1opuvxyABrsDEGH
462 "[yellow]gh CLI not found. Set these secrets manually:[/yellow]",
463 )
464 toolkit.print_line() 1opuvxyABrsDEGH
465 toolkit.print(f"Repository: [blue]{secrets_url}[/]") 1opuvxyABrsDEGH
466 toolkit.print_line() 1opuvxyABrsDEGH
467 toolkit.print( 1opuvxyABrsDEGH
468 f"[bold blue]FASTAPI_CLOUD_TOKEN[/] = {token_data['value']}"
469 )
470 toolkit.print(f"[bold blue]FASTAPI_CLOUD_APP_ID[/] = {target_app_id}") 1opuvxyABrsDEGH
472 if should_set_secrets: 2` M W N = O P Q jbh a | o p q ~ X - Y @ J Z 0 lbj c ebu v w ab1 . 2 [ 3 4 5 mbk d fbx y z bb6 / 7 ] K 8 9 nbl e gbA B C { R ! S ? T U V kbi b } r s t cb# : $ ^ % ' ( obm f hbD E F db) ; * _ L + , pbn g ibG H I
473 with toolkit.progress( 2` M W N O P Q jbh a | q ~ X - Y J Z 0 lbj c ebw ab1 . 2 3 4 5 mbk d fbz bb6 / 7 K 8 9 nbl e gbC { R ! S T U V kbi b } t cb# : $ % ' ( obm f hbF db) ; * L + , pbn g ibI
474 title="Setting repo secrets...", done_emoji="🔒"
475 ) as progress:
476 try: 2` M W N O P Q jbh a | q ~ X - Y J Z 0 lbj c ebw ab1 . 2 3 4 5 mbk d fbz bb6 / 7 K 8 9 nbl e gbC { R ! S T U V kbi b } t cb# : $ % ' ( obm f hbF db) ; * L + , pbn g ibI
477 _set_github_secret("FASTAPI_CLOUD_TOKEN", token_data["value"]) 2` M W N O P Q jbh a | q ~ X - Y J Z 0 lbj c ebw ab1 . 2 3 4 5 mbk d fbz bb6 / 7 K 8 9 nbl e gbC { R ! S T U V kbi b } t cb# : $ % ' ( obm f hbF db) ; * L + , pbn g ibI
478 _set_github_secret("FASTAPI_CLOUD_APP_ID", target_app_id) 2` M W N O P Q h a | q ~ X - Y J Z 0 j c ebw ab1 . 2 3 4 5 k d fbz bb6 / 7 K 8 9 l e gbC { R ! S T U V i b } t cb# : $ % ' ( m f hbF db) ; * L + , n g ibI
480 progress.log(msg_secrets) 2` M W N O P Q h a | q ~ X - Y J Z 0 j c ebw ab1 . 2 3 4 5 k d fbz bb6 / 7 K 8 9 l e gbC { R ! S T U V i b } t cb# : $ % ' ( m f hbF db) ; * L + , n g ibI
481 except GitHubSecretError: 2jblbmbnbkbobpb
482 progress.set_error("Failed to set GitHub secrets via gh CLI.") 2jblbmbnbkbobpb
483 toolkit.fail( 2jblbmbnbkbobpb
484 "api_error",
485 "Failed to set GitHub secrets via gh CLI.",
486 )
487 set_github_secrets = True 2` M W N O P Q h a | q ~ X - Y J Z 0 j c ebw ab1 . 2 3 4 5 k d fbz bb6 / 7 K 8 9 l e gbC { R ! S T U V i b } t cb# : $ % ' ( m f hbF db) ; * L + , n g ibI
488 else:
489 toolkit.print("Skipped setting GitHub Actions secrets.", emoji="⏭️") 1=op@uv[xy]AB?rs^DE_GH
490 else:
491 toolkit.print( 2xbqbybrbzbsbAbtbBbubCbvbDbwb
492 "Skipped creating deploy token and GitHub secrets.", emoji="⏭️"
493 )
495 toolkit.print_line() 2` EbM W N = xbqbO P Q h a | o p q ~ FbX - Y @ ybrbJ Z 0 j c ebu v w abGb1 . 2 [ zbsb3 4 5 k d fbx y z bbHb6 / 7 ] AbtbK 8 9 l e gbA B C { IbR ! S ? BbubT U V i b } r s t cbJb# : $ ^ Cbvb% ' ( m f hbD E F dbKb) ; * _ DbwbL + , n g ibG H I
497 if needs_workflow: 2` EbM W N = xbqbO P Q h a | o p q ~ FbX - Y @ ybrbJ Z 0 j c ebu v w abGb1 . 2 [ zbsb3 4 5 k d fbx y z bbHb6 / 7 ] AbtbK 8 9 l e gbA B C { IbR ! S ? BbubT U V i b } r s t cbJb# : $ ^ Cbvb% ' ( m f hbD E F dbKb) ; * _ DbwbL + , n g ibG H I
498 if not file and workflow_path.exists(): 2` EbM W N = qbO P Q h a o p q ~ FbX - Y @ rbJ Z 0 j c u v w abGb1 . 2 [ sb3 4 5 k d x y z bbHb6 / 7 ] tbK 8 9 l e A B C { IbR ! S ? ubT U V i b r s t cbJb# : $ ^ vb% ' ( m f D E F dbKb) ; * _ wbL + , n g G H I
499 resolved_workflow_path = _resolve_existing_workflow_path( 1haqjcwkdzleCibtmfFngI
500 toolkit, workflow_path
501 )
503 if resolved_workflow_path is None: 1haqjcwkdzleCibtmfFngI
504 needs_workflow = False 1qwzCtFI
505 else:
506 workflow_path = resolved_workflow_path 1hajckdleibmfng
508 if needs_workflow: 2` EbM W N = qbO P Q h a o p q ~ FbX - Y @ rbJ Z 0 j c u v w abGb1 . 2 [ sb3 4 5 k d x y z bbHb6 / 7 ] tbK 8 9 l e A B C { IbR ! S ? ubT U V i b r s t cbJb# : $ ^ vb% ' ( m f D E F dbKb) ; * _ wbL + , n g G H I
509 msg_workflow = f"Wrote [bold]{workflow_path}[/bold] (branch: {branch})" 2` EbM W N = qbO P Q h a o p ~ FbX - Y @ rbJ Z 0 j c u v abGb1 . 2 [ sb3 4 5 k d x y bbHb6 / 7 ] tbK 8 9 l e A B { IbR ! S ? ubT U V i b r s cbJb# : $ ^ vb% ' ( m f D E dbKb) ; * _ wbL + , n g G H
511 _write_workflow_file(branch, workflow_path) 2` EbM W N = qbO P Q h a o p ~ FbX - Y @ rbJ Z 0 j c u v abGb1 . 2 [ sb3 4 5 k d x y bbHb6 / 7 ] tbK 8 9 l e A B { IbR ! S ? ubT U V i b r s cbJb# : $ ^ vb% ' ( m f D E dbKb) ; * _ wbL + , n g G H
512 wrote_workflow = True 2` EbM W N = qbO P Q h a o p ~ FbX - Y @ rbJ Z 0 j c u v abGb1 . 2 [ sb3 4 5 k d x y bbHb6 / 7 ] tbK 8 9 l e A B { IbR ! S ? ubT U V i b r s cbJb# : $ ^ vb% ' ( m f D E dbKb) ; * _ wbL + , n g G H
514 toolkit.print(msg_workflow) 2` EbM W N = qbO P Q h a o p ~ FbX - Y @ rbJ Z 0 j c u v abGb1 . 2 [ sb3 4 5 k d x y bbHb6 / 7 ] tbK 8 9 l e A B { IbR ! S ? ubT U V i b r s cbJb# : $ ^ vb% ' ( m f D E dbKb) ; * _ wbL + , n g G H
515 toolkit.print_line() 2` EbM W N = qbO P Q h a o p ~ FbX - Y @ rbJ Z 0 j c u v abGb1 . 2 [ sb3 4 5 k d x y bbHb6 / 7 ] tbK 8 9 l e A B { IbR ! S ? ubT U V i b r s cbJb# : $ ^ vb% ' ( m f D E dbKb) ; * _ wbL + , n g G H
517 output = CISetupOutput( 2` EbM W N = xbqbO P Q h a | o p q ~ FbX - Y @ ybrbJ Z 0 j c ebu v w abGb1 . 2 [ zbsb3 4 5 k d fbx y z bbHb6 / 7 ] AbtbK 8 9 l e gbA B C { IbR ! S ? BbubT U V i b } r s t cbJb# : $ ^ Cbvb% ' ( m f hbD E F dbKb) ; * _ DbwbL + , n g ibG H I
518 app_id=target_app_id,
519 repo=repo_slug,
520 branch=branch,
521 workflow_path=_format_workflow_path(workflow_path),
522 created_token=created_token,
523 set_github_secrets=set_github_secrets,
524 wrote_workflow=wrote_workflow,
525 token_expired_at=token_expired_at,
526 )
528 toolkit.success(output, render_output=_render_ci_setup_output) 2` EbM W N = xbqbO P Q h a | o p q ~ FbX - Y @ ybrbJ Z 0 j c ebu v w abGb1 . 2 [ zbsb3 4 5 k d fbx y z bbHb6 / 7 ] AbtbK 8 9 l e gbA B C { IbR ! S ? BbubT U V i b } r s t cbJb# : $ ^ Cbvb% ' ( m f hbD E F dbKb) ; * _ DbwbL + , n g ibG H I