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

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

8 

9import typer 2LbMbNbObPbQbRb

10from pydantic import BaseModel 2LbMbNbObPbQbRb

11 

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

19 

20logger = logging.getLogger(__name__) 2LbMbNbObPbQbRb

21 

22TOKEN_EXPIRES_DAYS = 365 2LbMbNbObPbQbRb

23DEFAULT_WORKFLOW_PATH = Path(".github/workflows/deploy.yml") 2LbMbNbObPbQbRb

24 

25 

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

35 

36 

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

49 

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 ) 

56 

57 

58class GitHubSecretError(Exception): 2LbMbNbObPbQbRb

59 """Raised when setting a GitHub Actions secret fails.""" 

60 

61 pass 2LbMbNbObPbQbRb

62 

63 

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

67 

68 

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

76 

77 

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

81 

82 

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

86 

87 

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

109 

110 

111def _set_github_secret(name: str, value: str) -> None: 2LbMbNbObPbQbRb

112 """Set a GitHub Actions secret via the gh CLI. 

113 

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

125 

126 

127def _create_token(client: APIClient, app_id: str, token_name: str) -> dict[str, str]: 2LbMbNbObPbQbRb

128 """Create a new deploy token. 

129 

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

139 

140 

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

161 

162 

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""" 

183 

184 

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

189 

190 

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,

194 

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

196 

197 

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

200 

201 

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

211 

212 return workflow_path 2Nch Ocj Pck Qcl Rci Scm Tcn

213 

214 toolkit.print_line() 1aqcwdzeCbtfFgI

215 

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

221 

222 return Path(f".github/workflows/{new_name}") 1acdebfg

223 

224 toolkit.print_line() 1qwzCtFI

225 toolkit.print("Skipped writing workflow file.", emoji="⏭️") 1qwzCtFI

226 toolkit.print_line() 1qwzCtFI

227 

228 return None 1qwzCtFI

229 

230 

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. 

284 

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 """ 

293 

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

295 

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 ) 

301 

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 ) 

308 

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 ) 

314 

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 ) 

322 

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 ) 

329 

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

331 

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

334 

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

339 

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 ) 

352 

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 ) 

359 

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

363 

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

366 

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

372 

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

379 

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

384 

385 if needs_workflow: 2ZbSb0bTb1bUb2bVb3bWb4bXb5bYb

386 toolkit.print(msg_workflow) 2SbTbUbVbWbXbYb

387 

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

401 

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

406 

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

418 

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

423 

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

439 

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

441 

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

471 

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

479 

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 ) 

494 

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

496 

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 ) 

502 

503 if resolved_workflow_path is None: 1haqjcwkdzleCibtmfFngI

504 needs_workflow = False 1qwzCtFI

505 else: 

506 workflow_path = resolved_workflow_path 1hajckdleibmfng

507 

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

510 

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

513 

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

516 

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 ) 

527 

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