sum-cli 3.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sum/__init__.py +1 -0
- sum/boilerplate/.env.example +124 -0
- sum/boilerplate/.gitea/workflows/ci.yml +33 -0
- sum/boilerplate/.gitea/workflows/deploy-production.yml +98 -0
- sum/boilerplate/.gitea/workflows/deploy-staging.yml +113 -0
- sum/boilerplate/.github/workflows/ci.yml +36 -0
- sum/boilerplate/.github/workflows/deploy-production.yml +102 -0
- sum/boilerplate/.github/workflows/deploy-staging.yml +115 -0
- sum/boilerplate/.gitignore +45 -0
- sum/boilerplate/README.md +259 -0
- sum/boilerplate/manage.py +34 -0
- sum/boilerplate/project_name/__init__.py +5 -0
- sum/boilerplate/project_name/home/__init__.py +5 -0
- sum/boilerplate/project_name/home/apps.py +20 -0
- sum/boilerplate/project_name/home/management/__init__.py +0 -0
- sum/boilerplate/project_name/home/management/commands/__init__.py +0 -0
- sum/boilerplate/project_name/home/management/commands/populate_demo_content.py +644 -0
- sum/boilerplate/project_name/home/management/commands/seed.py +129 -0
- sum/boilerplate/project_name/home/management/commands/seed_showroom.py +1661 -0
- sum/boilerplate/project_name/home/migrations/__init__.py +3 -0
- sum/boilerplate/project_name/home/models.py +13 -0
- sum/boilerplate/project_name/settings/__init__.py +5 -0
- sum/boilerplate/project_name/settings/base.py +348 -0
- sum/boilerplate/project_name/settings/local.py +78 -0
- sum/boilerplate/project_name/settings/production.py +106 -0
- sum/boilerplate/project_name/urls.py +33 -0
- sum/boilerplate/project_name/wsgi.py +16 -0
- sum/boilerplate/pytest.ini +5 -0
- sum/boilerplate/requirements.txt +25 -0
- sum/boilerplate/static/client/.gitkeep +3 -0
- sum/boilerplate/templates/overrides/.gitkeep +3 -0
- sum/boilerplate/tests/__init__.py +3 -0
- sum/boilerplate/tests/test_health.py +51 -0
- sum/cli.py +42 -0
- sum/commands/__init__.py +10 -0
- sum/commands/backup.py +308 -0
- sum/commands/check.py +128 -0
- sum/commands/init.py +265 -0
- sum/commands/promote.py +758 -0
- sum/commands/run.py +96 -0
- sum/commands/themes.py +56 -0
- sum/commands/update.py +301 -0
- sum/config.py +61 -0
- sum/docs/USER_GUIDE.md +663 -0
- sum/exceptions.py +45 -0
- sum/setup/__init__.py +17 -0
- sum/setup/auth.py +184 -0
- sum/setup/database.py +58 -0
- sum/setup/deps.py +73 -0
- sum/setup/git_ops.py +463 -0
- sum/setup/infrastructure.py +576 -0
- sum/setup/orchestrator.py +354 -0
- sum/setup/remote_themes.py +371 -0
- sum/setup/scaffold.py +500 -0
- sum/setup/seed.py +110 -0
- sum/setup/site_orchestrator.py +441 -0
- sum/setup/venv.py +89 -0
- sum/system_config.py +330 -0
- sum/themes_registry.py +180 -0
- sum/utils/__init__.py +25 -0
- sum/utils/django.py +97 -0
- sum/utils/environment.py +76 -0
- sum/utils/output.py +78 -0
- sum/utils/project.py +110 -0
- sum/utils/prompts.py +36 -0
- sum/utils/validation.py +313 -0
- sum_cli-3.0.0.dist-info/METADATA +127 -0
- sum_cli-3.0.0.dist-info/RECORD +72 -0
- sum_cli-3.0.0.dist-info/WHEEL +5 -0
- sum_cli-3.0.0.dist-info/entry_points.txt +2 -0
- sum_cli-3.0.0.dist-info/licenses/LICENSE +29 -0
- sum_cli-3.0.0.dist-info/top_level.txt +1 -0
sum/setup/git_ops.py
ADDED
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
"""Git operations for SUM Platform sites.
|
|
2
|
+
|
|
3
|
+
Handles git init, initial commit, and remote repository creation.
|
|
4
|
+
Supports GitHub (via gh CLI) and Gitea (via REST API).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import shutil
|
|
11
|
+
import subprocess
|
|
12
|
+
from abc import ABC, abstractmethod
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
from sum.exceptions import SetupError
|
|
16
|
+
from sum.system_config import AgencyConfig, get_system_config
|
|
17
|
+
from sum.utils.output import OutputFormatter
|
|
18
|
+
|
|
19
|
+
# =============================================================================
|
|
20
|
+
# Git Provider Abstraction
|
|
21
|
+
# =============================================================================
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class GitProvider(ABC):
|
|
25
|
+
"""Abstract base class for git hosting providers."""
|
|
26
|
+
|
|
27
|
+
@abstractmethod
|
|
28
|
+
def is_available(self) -> bool:
|
|
29
|
+
"""Check if the provider is configured and accessible."""
|
|
30
|
+
...
|
|
31
|
+
|
|
32
|
+
@abstractmethod
|
|
33
|
+
def create_repo(self, app_dir: Path, repo_name: str) -> str:
|
|
34
|
+
"""Create a remote repository and push the code.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
app_dir: Path to the local git repository.
|
|
38
|
+
repo_name: Name for the remote repository.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
URL of the created repository.
|
|
42
|
+
|
|
43
|
+
Raises:
|
|
44
|
+
SetupError: If repo creation or push fails.
|
|
45
|
+
"""
|
|
46
|
+
...
|
|
47
|
+
|
|
48
|
+
@abstractmethod
|
|
49
|
+
def get_repo_url(self, org: str, repo_name: str) -> str:
|
|
50
|
+
"""Get the HTTPS URL for a repository."""
|
|
51
|
+
...
|
|
52
|
+
|
|
53
|
+
@abstractmethod
|
|
54
|
+
def get_clone_url(self, org: str, repo_name: str) -> str:
|
|
55
|
+
"""Get the SSH clone URL for a repository."""
|
|
56
|
+
...
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
@abstractmethod
|
|
60
|
+
def name(self) -> str:
|
|
61
|
+
"""Human-readable provider name."""
|
|
62
|
+
...
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class GitHubProvider(GitProvider):
|
|
66
|
+
"""GitHub provider using gh CLI."""
|
|
67
|
+
|
|
68
|
+
def __init__(self, config: AgencyConfig) -> None:
|
|
69
|
+
self.org = config.github_org or ""
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def name(self) -> str:
|
|
73
|
+
return "GitHub"
|
|
74
|
+
|
|
75
|
+
def is_available(self) -> bool:
|
|
76
|
+
"""Check if gh CLI is available and authenticated."""
|
|
77
|
+
if not shutil.which("gh"):
|
|
78
|
+
return False
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
result = subprocess.run(
|
|
82
|
+
["gh", "auth", "status"],
|
|
83
|
+
capture_output=True,
|
|
84
|
+
text=True,
|
|
85
|
+
)
|
|
86
|
+
return result.returncode == 0
|
|
87
|
+
except subprocess.SubprocessError:
|
|
88
|
+
return False
|
|
89
|
+
|
|
90
|
+
def get_repo_url(self, org: str, repo_name: str) -> str:
|
|
91
|
+
return f"https://github.com/{org}/{repo_name}"
|
|
92
|
+
|
|
93
|
+
def get_clone_url(self, org: str, repo_name: str) -> str:
|
|
94
|
+
return f"git@github.com:{org}/{repo_name}.git"
|
|
95
|
+
|
|
96
|
+
def create_repo(self, app_dir: Path, repo_name: str) -> str:
|
|
97
|
+
"""Create a private GitHub repository and push the code."""
|
|
98
|
+
if not self.is_available():
|
|
99
|
+
raise SetupError(
|
|
100
|
+
"GitHub CLI (gh) is not available or not authenticated. "
|
|
101
|
+
"Run 'gh auth login' to authenticate, or use --no-git to skip."
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
subprocess.run(
|
|
106
|
+
[
|
|
107
|
+
"gh",
|
|
108
|
+
"repo",
|
|
109
|
+
"create",
|
|
110
|
+
f"{self.org}/{repo_name}",
|
|
111
|
+
"--private",
|
|
112
|
+
"--source",
|
|
113
|
+
str(app_dir),
|
|
114
|
+
"--push",
|
|
115
|
+
],
|
|
116
|
+
cwd=app_dir,
|
|
117
|
+
check=True,
|
|
118
|
+
capture_output=True,
|
|
119
|
+
text=True,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
return self.get_repo_url(self.org, repo_name)
|
|
123
|
+
|
|
124
|
+
except subprocess.CalledProcessError as exc:
|
|
125
|
+
stderr = exc.stderr or ""
|
|
126
|
+
if "already exists" in stderr.lower():
|
|
127
|
+
OutputFormatter.warning(
|
|
128
|
+
f"Repository {self.org}/{repo_name} already exists, "
|
|
129
|
+
"attempting to push..."
|
|
130
|
+
)
|
|
131
|
+
return self._push_to_existing_repo(app_dir, repo_name)
|
|
132
|
+
raise SetupError(f"Failed to create GitHub repository: {stderr}") from exc
|
|
133
|
+
|
|
134
|
+
def _push_to_existing_repo(self, app_dir: Path, repo_name: str) -> str:
|
|
135
|
+
"""Push to an existing GitHub repository."""
|
|
136
|
+
repo_url = self.get_repo_url(self.org, repo_name)
|
|
137
|
+
|
|
138
|
+
try:
|
|
139
|
+
result = subprocess.run(
|
|
140
|
+
["git", "remote", "get-url", "origin"],
|
|
141
|
+
cwd=app_dir,
|
|
142
|
+
capture_output=True,
|
|
143
|
+
text=True,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
if result.returncode != 0:
|
|
147
|
+
subprocess.run(
|
|
148
|
+
[
|
|
149
|
+
"git",
|
|
150
|
+
"remote",
|
|
151
|
+
"add",
|
|
152
|
+
"origin",
|
|
153
|
+
self.get_clone_url(self.org, repo_name),
|
|
154
|
+
],
|
|
155
|
+
cwd=app_dir,
|
|
156
|
+
check=True,
|
|
157
|
+
capture_output=True,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
subprocess.run(
|
|
161
|
+
["git", "push", "-u", "origin", "main"],
|
|
162
|
+
cwd=app_dir,
|
|
163
|
+
check=True,
|
|
164
|
+
capture_output=True,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
return repo_url
|
|
168
|
+
|
|
169
|
+
except subprocess.CalledProcessError as exc:
|
|
170
|
+
raise SetupError(
|
|
171
|
+
f"Failed to push to existing repository: {exc.stderr}"
|
|
172
|
+
) from exc
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class GiteaProvider(GitProvider):
|
|
176
|
+
"""Gitea provider using REST API."""
|
|
177
|
+
|
|
178
|
+
def __init__(self, config: AgencyConfig) -> None:
|
|
179
|
+
self.org = config.gitea_org or ""
|
|
180
|
+
self.base_url = (config.gitea_url or "").rstrip("/")
|
|
181
|
+
self.token_env = config.gitea_token_env
|
|
182
|
+
|
|
183
|
+
@property
|
|
184
|
+
def name(self) -> str:
|
|
185
|
+
return "Gitea"
|
|
186
|
+
|
|
187
|
+
def _get_token(self) -> str | None:
|
|
188
|
+
"""Get the API token from environment."""
|
|
189
|
+
return os.environ.get(self.token_env)
|
|
190
|
+
|
|
191
|
+
def is_available(self) -> bool:
|
|
192
|
+
"""Check if Gitea is configured with a valid token."""
|
|
193
|
+
if not self.base_url or not self.org:
|
|
194
|
+
return False
|
|
195
|
+
return self._get_token() is not None
|
|
196
|
+
|
|
197
|
+
def get_repo_url(self, org: str, repo_name: str) -> str:
|
|
198
|
+
return f"{self.base_url}/{org}/{repo_name}"
|
|
199
|
+
|
|
200
|
+
def get_clone_url(self, org: str, repo_name: str) -> str:
|
|
201
|
+
# Extract host from base_url for SSH
|
|
202
|
+
# e.g., https://gitea.example.com -> gitea.example.com
|
|
203
|
+
from urllib.parse import urlparse
|
|
204
|
+
|
|
205
|
+
parsed = urlparse(self.base_url)
|
|
206
|
+
host = parsed.netloc
|
|
207
|
+
return f"git@{host}:{org}/{repo_name}.git"
|
|
208
|
+
|
|
209
|
+
def create_repo(self, app_dir: Path, repo_name: str) -> str:
|
|
210
|
+
"""Create a private Gitea repository and push the code."""
|
|
211
|
+
token = self._get_token()
|
|
212
|
+
if not token:
|
|
213
|
+
raise SetupError(
|
|
214
|
+
f"Gitea API token not found. Set the {self.token_env} environment "
|
|
215
|
+
"variable, or use --no-git to skip repository creation."
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
try:
|
|
219
|
+
import httpx
|
|
220
|
+
except ImportError:
|
|
221
|
+
raise SetupError(
|
|
222
|
+
"httpx is required for Gitea support. "
|
|
223
|
+
"Install with: pip install sum-cli[gitea]"
|
|
224
|
+
) from None
|
|
225
|
+
|
|
226
|
+
# Create repository via API
|
|
227
|
+
api_url = f"{self.base_url}/api/v1/orgs/{self.org}/repos"
|
|
228
|
+
headers = {
|
|
229
|
+
"Authorization": f"token {token}",
|
|
230
|
+
"Content-Type": "application/json",
|
|
231
|
+
}
|
|
232
|
+
payload = {
|
|
233
|
+
"name": repo_name,
|
|
234
|
+
"private": True,
|
|
235
|
+
"auto_init": False,
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
try:
|
|
239
|
+
response = httpx.post(api_url, json=payload, headers=headers, timeout=30.0)
|
|
240
|
+
|
|
241
|
+
if response.status_code == 409:
|
|
242
|
+
# Repository already exists
|
|
243
|
+
OutputFormatter.warning(
|
|
244
|
+
f"Repository {self.org}/{repo_name} already exists, "
|
|
245
|
+
"attempting to push..."
|
|
246
|
+
)
|
|
247
|
+
return self._push_to_existing_repo(app_dir, repo_name)
|
|
248
|
+
|
|
249
|
+
if response.status_code not in (200, 201):
|
|
250
|
+
error_msg = response.text
|
|
251
|
+
try:
|
|
252
|
+
error_data = response.json()
|
|
253
|
+
except ValueError:
|
|
254
|
+
# Response body is not JSON; fall back to raw text message.
|
|
255
|
+
pass
|
|
256
|
+
else:
|
|
257
|
+
error_msg = error_data.get("message", response.text)
|
|
258
|
+
raise SetupError(
|
|
259
|
+
f"Failed to create Gitea repository: {error_msg} "
|
|
260
|
+
f"(HTTP {response.status_code})"
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
# Add remote and push
|
|
264
|
+
return self._setup_remote_and_push(app_dir, repo_name)
|
|
265
|
+
|
|
266
|
+
except httpx.RequestError as exc:
|
|
267
|
+
raise SetupError(
|
|
268
|
+
f"Failed to connect to Gitea at {self.base_url}: {exc}"
|
|
269
|
+
) from exc
|
|
270
|
+
|
|
271
|
+
def _setup_remote_and_push(self, app_dir: Path, repo_name: str) -> str:
|
|
272
|
+
"""Set up git remote and push to Gitea."""
|
|
273
|
+
clone_url = self.get_clone_url(self.org, repo_name)
|
|
274
|
+
|
|
275
|
+
try:
|
|
276
|
+
# Add remote
|
|
277
|
+
subprocess.run(
|
|
278
|
+
["git", "remote", "add", "origin", clone_url],
|
|
279
|
+
cwd=app_dir,
|
|
280
|
+
check=True,
|
|
281
|
+
capture_output=True,
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
# Push
|
|
285
|
+
subprocess.run(
|
|
286
|
+
["git", "push", "-u", "origin", "main"],
|
|
287
|
+
cwd=app_dir,
|
|
288
|
+
check=True,
|
|
289
|
+
capture_output=True,
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
return self.get_repo_url(self.org, repo_name)
|
|
293
|
+
|
|
294
|
+
except subprocess.CalledProcessError as exc:
|
|
295
|
+
raise SetupError(f"Failed to push to Gitea: {exc.stderr}") from exc
|
|
296
|
+
|
|
297
|
+
def _push_to_existing_repo(self, app_dir: Path, repo_name: str) -> str:
|
|
298
|
+
"""Push to an existing Gitea repository."""
|
|
299
|
+
try:
|
|
300
|
+
result = subprocess.run(
|
|
301
|
+
["git", "remote", "get-url", "origin"],
|
|
302
|
+
cwd=app_dir,
|
|
303
|
+
capture_output=True,
|
|
304
|
+
text=True,
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
if result.returncode != 0:
|
|
308
|
+
subprocess.run(
|
|
309
|
+
[
|
|
310
|
+
"git",
|
|
311
|
+
"remote",
|
|
312
|
+
"add",
|
|
313
|
+
"origin",
|
|
314
|
+
self.get_clone_url(self.org, repo_name),
|
|
315
|
+
],
|
|
316
|
+
cwd=app_dir,
|
|
317
|
+
check=True,
|
|
318
|
+
capture_output=True,
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
subprocess.run(
|
|
322
|
+
["git", "push", "-u", "origin", "main"],
|
|
323
|
+
cwd=app_dir,
|
|
324
|
+
check=True,
|
|
325
|
+
capture_output=True,
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
return self.get_repo_url(self.org, repo_name)
|
|
329
|
+
|
|
330
|
+
except subprocess.CalledProcessError as exc:
|
|
331
|
+
raise SetupError(
|
|
332
|
+
f"Failed to push to existing repository: {exc.stderr}"
|
|
333
|
+
) from exc
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
# =============================================================================
|
|
337
|
+
# Provider Factory
|
|
338
|
+
# =============================================================================
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def get_git_provider(config: AgencyConfig | None = None) -> GitProvider:
|
|
342
|
+
"""Get the configured git provider instance.
|
|
343
|
+
|
|
344
|
+
Args:
|
|
345
|
+
config: Optional AgencyConfig. If not provided, loads from system config.
|
|
346
|
+
|
|
347
|
+
Returns:
|
|
348
|
+
GitProvider instance for the configured provider.
|
|
349
|
+
"""
|
|
350
|
+
if config is None:
|
|
351
|
+
config = get_system_config().agency
|
|
352
|
+
|
|
353
|
+
if config.git_provider == "gitea":
|
|
354
|
+
return GiteaProvider(config)
|
|
355
|
+
return GitHubProvider(config)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
# =============================================================================
|
|
359
|
+
# Public API
|
|
360
|
+
# =============================================================================
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def init_git_repo(app_dir: Path) -> None:
|
|
364
|
+
"""Initialize a git repository in the app directory."""
|
|
365
|
+
try:
|
|
366
|
+
subprocess.run(
|
|
367
|
+
["git", "init"],
|
|
368
|
+
cwd=app_dir,
|
|
369
|
+
check=True,
|
|
370
|
+
capture_output=True,
|
|
371
|
+
)
|
|
372
|
+
except subprocess.CalledProcessError as exc:
|
|
373
|
+
raise SetupError(f"Failed to initialize git repository: {exc.stderr}") from exc
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def create_initial_commit(
|
|
377
|
+
app_dir: Path, message: str = "Initial scaffold from sum-platform init"
|
|
378
|
+
) -> None:
|
|
379
|
+
"""Create the initial git commit with all files.
|
|
380
|
+
|
|
381
|
+
Uses the system's git configuration for author identity.
|
|
382
|
+
"""
|
|
383
|
+
try:
|
|
384
|
+
subprocess.run(
|
|
385
|
+
["git", "add", "-A"],
|
|
386
|
+
cwd=app_dir,
|
|
387
|
+
check=True,
|
|
388
|
+
capture_output=True,
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
subprocess.run(
|
|
392
|
+
["git", "commit", "-m", message],
|
|
393
|
+
cwd=app_dir,
|
|
394
|
+
check=True,
|
|
395
|
+
capture_output=True,
|
|
396
|
+
)
|
|
397
|
+
except subprocess.CalledProcessError as exc:
|
|
398
|
+
raise SetupError(f"Failed to create initial commit: {exc.stderr}") from exc
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def setup_git_for_site(
|
|
402
|
+
app_dir: Path,
|
|
403
|
+
site_slug: str,
|
|
404
|
+
skip_remote: bool = False,
|
|
405
|
+
) -> str | None:
|
|
406
|
+
"""Set up git repository for a new site.
|
|
407
|
+
|
|
408
|
+
Args:
|
|
409
|
+
app_dir: Path to the app directory.
|
|
410
|
+
site_slug: The site slug (used as repo name).
|
|
411
|
+
skip_remote: If True, only do local git init, skip remote repo creation.
|
|
412
|
+
|
|
413
|
+
Returns:
|
|
414
|
+
Remote repo URL if created, None if skipped.
|
|
415
|
+
"""
|
|
416
|
+
# Initialize git repo
|
|
417
|
+
init_git_repo(app_dir)
|
|
418
|
+
|
|
419
|
+
# Create initial commit
|
|
420
|
+
create_initial_commit(app_dir)
|
|
421
|
+
|
|
422
|
+
if skip_remote:
|
|
423
|
+
OutputFormatter.info("Skipping remote repository creation (--no-git)")
|
|
424
|
+
return None
|
|
425
|
+
|
|
426
|
+
# Get the configured provider
|
|
427
|
+
provider = get_git_provider()
|
|
428
|
+
|
|
429
|
+
# Check if provider is available
|
|
430
|
+
if not provider.is_available():
|
|
431
|
+
OutputFormatter.warning(
|
|
432
|
+
f"{provider.name} is not available or not configured. "
|
|
433
|
+
"Skipping repository creation."
|
|
434
|
+
)
|
|
435
|
+
return None
|
|
436
|
+
|
|
437
|
+
# Create remote repo
|
|
438
|
+
repo_url = provider.create_repo(app_dir, site_slug)
|
|
439
|
+
return repo_url
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
# =============================================================================
|
|
443
|
+
# Backward Compatibility
|
|
444
|
+
# =============================================================================
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
# Keep old function names for backward compatibility
|
|
448
|
+
def check_gh_cli() -> bool:
|
|
449
|
+
"""Check if gh CLI is available and authenticated.
|
|
450
|
+
|
|
451
|
+
Deprecated: Use get_git_provider().is_available() instead.
|
|
452
|
+
"""
|
|
453
|
+
provider = GitHubProvider(get_system_config().agency)
|
|
454
|
+
return provider.is_available()
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
def create_github_repo(app_dir: Path, repo_name: str) -> str:
|
|
458
|
+
"""Create a private GitHub repository and push the code.
|
|
459
|
+
|
|
460
|
+
Deprecated: Use get_git_provider().create_repo() instead.
|
|
461
|
+
"""
|
|
462
|
+
provider = GitHubProvider(get_system_config().agency)
|
|
463
|
+
return provider.create_repo(app_dir, repo_name)
|