sum-cli 3.0.0__py3-none-any.whl → 3.1.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/cli.py CHANGED
@@ -9,7 +9,7 @@ from sum.commands.backup import backup
9
9
  from sum.commands.check import check
10
10
  from sum.commands.init import init
11
11
  from sum.commands.promote import promote
12
- from sum.commands.run import run
12
+ from sum.commands.setup import setup
13
13
  from sum.commands.themes import themes
14
14
  from sum.commands.update import update
15
15
 
@@ -26,14 +26,14 @@ def _get_version() -> str:
26
26
  version=_get_version(), prog_name="sum-platform", message="%(prog)s %(version)s"
27
27
  )
28
28
  def cli() -> None:
29
- """SUM Platform CLI (v2)."""
29
+ """SUM Platform CLI - Deploy and manage client sites."""
30
30
 
31
31
 
32
32
  cli.add_command(backup)
33
33
  cli.add_command(check)
34
34
  cli.add_command(init)
35
35
  cli.add_command(promote)
36
- cli.add_command(run)
36
+ cli.add_command(setup)
37
37
  cli.add_command(themes)
38
38
  cli.add_command(update)
39
39
 
sum/commands/__init__.py CHANGED
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  from sum.commands.check import check
6
6
  from sum.commands.init import init
7
- from sum.commands.run import run
7
+ from sum.commands.setup import setup
8
8
  from sum.commands.themes import themes
9
9
 
10
- __all__ = ["check", "init", "run", "themes"]
10
+ __all__ = ["check", "init", "setup", "themes"]
sum/commands/init.py CHANGED
@@ -7,7 +7,7 @@ Creates a working site on staging at /srv/sum/<name>/ with:
7
7
  - External venv
8
8
  - Systemd service
9
9
  - Caddy configuration
10
- - Git repository (optional)
10
+ - Git repository (optional, specify provider with --git-provider)
11
11
  """
12
12
 
13
13
  from __future__ import annotations
@@ -17,6 +17,7 @@ from types import ModuleType
17
17
  from sum.exceptions import SetupError
18
18
  from sum.setup.infrastructure import check_infrastructure
19
19
  from sum.setup.site_orchestrator import SiteOrchestrator, SiteSetupConfig
20
+ from sum.site_config import GitConfig
20
21
  from sum.system_config import ConfigurationError, get_system_config
21
22
  from sum.utils.output import OutputFormatter
22
23
  from sum.utils.project import validate_project_name
@@ -37,6 +38,11 @@ def run_init(
37
38
  profile: str = "starter",
38
39
  content_path: str | None = None,
39
40
  no_git: bool = False,
41
+ git_provider: str | None = None,
42
+ git_org: str | None = None,
43
+ gitea_url: str | None = None,
44
+ gitea_ssh_port: int = 22,
45
+ gitea_token_env: str = "GITEA_TOKEN",
40
46
  skip_systemd: bool = False,
41
47
  skip_caddy: bool = False,
42
48
  superuser_username: str = "admin",
@@ -51,7 +57,12 @@ def run_init(
51
57
  theme: Theme slug to use.
52
58
  profile: Content profile name to seed.
53
59
  content_path: Optional path to custom content directory.
54
- no_git: Skip GitHub repository creation.
60
+ no_git: Skip git repository creation.
61
+ git_provider: Git provider ("github" or "gitea").
62
+ git_org: Git organization/namespace.
63
+ gitea_url: Gitea instance URL (required if git_provider=gitea).
64
+ gitea_ssh_port: SSH port for Gitea.
65
+ gitea_token_env: Env var name for Gitea API token.
55
66
  skip_systemd: Skip systemd service installation.
56
67
  skip_caddy: Skip Caddy configuration.
57
68
  superuser_username: Username for Django superuser.
@@ -96,11 +107,32 @@ def run_init(
96
107
  OutputFormatter.error(f"Site already exists at {site_dir}")
97
108
  return 1
98
109
 
99
- # Warn about GitHub if gh CLI not available and --no-git not set
100
- if not no_git and not infra.has_gh_cli:
101
- OutputFormatter.warning(
102
- "GitHub CLI (gh) not available. Repository creation will be skipped.\n"
103
- "To enable: install gh CLI and run 'gh auth login'"
110
+ # Build git config from flags
111
+ git_config: GitConfig | None = None
112
+ if not no_git:
113
+ # Validate required git flags
114
+ if not git_provider:
115
+ OutputFormatter.error(
116
+ "Git provider required. Use --git-provider github or --git-provider gitea.\n"
117
+ "Or use --no-git to skip git setup."
118
+ )
119
+ return 1
120
+ if not git_org:
121
+ OutputFormatter.error(
122
+ "Git organization required. Use --git-org <org>.\n"
123
+ "Or use --no-git to skip git setup."
124
+ )
125
+ return 1
126
+ if git_provider == "gitea" and not gitea_url:
127
+ OutputFormatter.error("Gitea URL required. Use --gitea-url <url>.")
128
+ return 1
129
+
130
+ git_config = GitConfig(
131
+ provider=git_provider,
132
+ org=git_org,
133
+ url=gitea_url,
134
+ ssh_port=gitea_ssh_port,
135
+ token_env=gitea_token_env,
104
136
  )
105
137
 
106
138
  # Build setup config
@@ -110,9 +142,9 @@ def run_init(
110
142
  seed_profile=profile,
111
143
  content_path=content_path,
112
144
  superuser_username=superuser_username,
113
- skip_git=no_git,
114
145
  skip_systemd=skip_systemd,
115
146
  skip_caddy=skip_caddy,
147
+ git_config=git_config,
116
148
  )
117
149
 
118
150
  # Run setup
@@ -153,6 +185,11 @@ def _init_command(
153
185
  profile: str,
154
186
  content_path: str | None,
155
187
  no_git: bool,
188
+ git_provider: str | None,
189
+ git_org: str | None,
190
+ gitea_url: str | None,
191
+ gitea_ssh_port: int,
192
+ gitea_token_env: str,
156
193
  skip_systemd: bool,
157
194
  skip_caddy: bool,
158
195
  superuser_username: str,
@@ -164,6 +201,11 @@ def _init_command(
164
201
  profile=profile,
165
202
  content_path=content_path,
166
203
  no_git=no_git,
204
+ git_provider=git_provider,
205
+ git_org=git_org,
206
+ gitea_url=gitea_url,
207
+ gitea_ssh_port=gitea_ssh_port,
208
+ gitea_token_env=gitea_token_env,
167
209
  skip_systemd=skip_systemd,
168
210
  skip_caddy=skip_caddy,
169
211
  superuser_username=superuser_username,
@@ -202,7 +244,36 @@ else:
202
244
  @click.option(
203
245
  "--no-git",
204
246
  is_flag=True,
205
- help="Skip GitHub repository creation (git init only).",
247
+ help="Skip git repository creation (local git init only).",
248
+ )
249
+ @click.option(
250
+ "--git-provider",
251
+ type=click.Choice(["github", "gitea"]),
252
+ default=None,
253
+ help="Git provider: github or gitea. Required unless --no-git.",
254
+ )
255
+ @click.option(
256
+ "--git-org",
257
+ default=None,
258
+ help="Git organization/namespace. Required unless --no-git.",
259
+ )
260
+ @click.option(
261
+ "--gitea-url",
262
+ default=None,
263
+ help="Gitea instance URL. Required if --git-provider=gitea.",
264
+ )
265
+ @click.option(
266
+ "--gitea-ssh-port",
267
+ type=int,
268
+ default=22,
269
+ show_default=True,
270
+ help="SSH port for Gitea.",
271
+ )
272
+ @click.option(
273
+ "--gitea-token-env",
274
+ default="GITEA_TOKEN",
275
+ show_default=True,
276
+ help="Environment variable for Gitea API token.",
206
277
  )
207
278
  @click.option(
208
279
  "--skip-systemd",
@@ -227,6 +298,11 @@ else:
227
298
  profile: str,
228
299
  content_path: str | None,
229
300
  no_git: bool,
301
+ git_provider: str | None,
302
+ git_org: str | None,
303
+ gitea_url: str | None,
304
+ gitea_ssh_port: int,
305
+ gitea_token_env: str,
230
306
  skip_systemd: bool,
231
307
  skip_caddy: bool,
232
308
  superuser_username: str,
@@ -241,15 +317,16 @@ else:
241
317
  - External virtualenv
242
318
  - Systemd service
243
319
  - Caddy reverse proxy
244
- - GitHub repository (optional)
245
-
246
- The site will be accessible at https://<SITE_NAME>.lintel.site
320
+ - Git repository (optional, specify provider with --git-provider)
247
321
 
248
322
  \b
249
323
  Examples:
250
- sudo sum-platform init acme
251
- sudo sum-platform init acme --theme theme_b
252
- sudo sum-platform init acme --content-path /path/to/profiles/acme
324
+ sudo sum-platform init acme --no-git
325
+ sudo sum-platform init acme --git-provider github --git-org acme-corp
326
+ sudo sum-platform init acme --git-provider gitea --git-org clients \\
327
+ --gitea-url https://git.agency.com
328
+ sudo sum-platform init acme --git-provider gitea --git-org clients \\
329
+ --gitea-url https://git.agency.com --gitea-ssh-port 2222
253
330
  """
254
331
  _init_command(
255
332
  site_name,
@@ -257,6 +334,11 @@ else:
257
334
  profile=profile,
258
335
  content_path=content_path,
259
336
  no_git=no_git,
337
+ git_provider=git_provider,
338
+ git_org=git_org,
339
+ gitea_url=gitea_url,
340
+ gitea_ssh_port=gitea_ssh_port,
341
+ gitea_token_env=gitea_token_env,
260
342
  skip_systemd=skip_systemd,
261
343
  skip_caddy=skip_caddy,
262
344
  superuser_username=superuser_username,
sum/commands/promote.py CHANGED
@@ -19,8 +19,9 @@ from pathlib import Path
19
19
  from types import ModuleType
20
20
 
21
21
  from sum.exceptions import SetupError
22
- from sum.setup.git_ops import get_git_provider
22
+ from sum.setup.git_ops import get_git_provider_from_config
23
23
  from sum.setup.infrastructure import generate_password, generate_secret_key
24
+ from sum.site_config import GitConfig, SiteConfig, SiteConfigError
24
25
  from sum.system_config import ConfigurationError, SystemConfig, get_system_config
25
26
  from sum.utils.output import OutputFormatter
26
27
 
@@ -206,7 +207,9 @@ WAGTAILADMIN_BASE_URL=https://{domain}
206
207
  return credentials
207
208
 
208
209
 
209
- def _clone_repo_on_prod(site_slug: str, config: SystemConfig) -> None:
210
+ def _clone_repo_on_prod(
211
+ site_slug: str, config: SystemConfig, git_config: GitConfig
212
+ ) -> None:
210
213
  """Clone the site repository on production.
211
214
 
212
215
  Uses HTTPS with token to avoid SSH key requirements on production.
@@ -214,8 +217,8 @@ def _clone_repo_on_prod(site_slug: str, config: SystemConfig) -> None:
214
217
  """
215
218
  ssh_host = config.production.ssh_host
216
219
  site_dir = config.get_site_dir(site_slug, target="prod")
217
- provider = get_git_provider(config.agency)
218
- org = config.agency.org
220
+ provider = get_git_provider_from_config(git_config)
221
+ org = git_config.org
219
222
 
220
223
  q_app_dir = shlex.quote(f"{site_dir}/app")
221
224
  q_site_env = shlex.quote(f"{site_dir}/.env")
@@ -226,7 +229,7 @@ def _clone_repo_on_prod(site_slug: str, config: SystemConfig) -> None:
226
229
 
227
230
  # Try to get token for HTTPS clone (avoids SSH key setup on prod)
228
231
  repo_url = ssh_clone_url # Default to SSH
229
- if config.agency.git_provider == "github":
232
+ if git_config.provider == "github":
230
233
  # Try GitHub CLI token
231
234
  try:
232
235
  result = subprocess.run(
@@ -240,14 +243,14 @@ def _clone_repo_on_prod(site_slug: str, config: SystemConfig) -> None:
240
243
  repo_url = f"https://{gh_token}@github.com/{org}/{site_slug}.git"
241
244
  except (subprocess.SubprocessError, FileNotFoundError):
242
245
  pass
243
- elif config.agency.git_provider == "gitea":
246
+ elif git_config.provider == "gitea":
244
247
  # Try Gitea token from environment
245
248
  import os
246
249
  from urllib.parse import urlparse
247
250
 
248
- gitea_token = os.environ.get(config.agency.gitea_token_env)
249
- if gitea_token and config.agency.gitea_url:
250
- parsed = urlparse(config.agency.gitea_url)
251
+ gitea_token = os.environ.get(git_config.token_env)
252
+ if gitea_token and git_config.url:
253
+ parsed = urlparse(git_config.url)
251
254
  repo_url = f"https://{gitea_token}@{parsed.netloc}/{org}/{site_slug}.git"
252
255
 
253
256
  cmd = f"git clone {shlex.quote(repo_url)} {q_app_dir}"
@@ -558,6 +561,22 @@ def run_promote(
558
561
  OutputFormatter.error(f"Staging site not found: {staging_dir}")
559
562
  return 1
560
563
 
564
+ # Load site config to get git settings
565
+ try:
566
+ site_config = SiteConfig.load(staging_dir)
567
+ except SiteConfigError as exc:
568
+ OutputFormatter.error(str(exc))
569
+ return 1
570
+
571
+ if site_config.git is None:
572
+ OutputFormatter.error(
573
+ f"Site '{site_name}' was created with --no-git. "
574
+ "Cannot promote without git repository."
575
+ )
576
+ return 1
577
+
578
+ git_config = site_config.git
579
+
561
580
  OutputFormatter.header(f"Promoting {site_name} to production")
562
581
  print(f" Domain: {domain}")
563
582
  print()
@@ -601,7 +620,7 @@ def run_promote(
601
620
  OutputFormatter.progress(
602
621
  current_step, total_steps, "Cloning repository on production", "⏳"
603
622
  )
604
- _clone_repo_on_prod(site_name, config)
623
+ _clone_repo_on_prod(site_name, config, git_config)
605
624
  OutputFormatter.progress(current_step, total_steps, "Repository cloned", "✅")
606
625
 
607
626
  # Step 5: Setup venv on production
sum/commands/setup.py ADDED
@@ -0,0 +1,143 @@
1
+ """Setup command for initializing /etc/sum/config.yml."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from pathlib import Path
7
+ from types import ModuleType
8
+
9
+ import yaml
10
+ from sum.system_config import DEFAULT_CONFIG_PATH
11
+ from sum.utils.output import OutputFormatter
12
+
13
+ click_module: ModuleType | None
14
+ try:
15
+ import click as click_module
16
+ except ImportError: # pragma: no cover
17
+ click_module = None
18
+
19
+ click: ModuleType | None = click_module
20
+
21
+
22
+ def _missing_click(*_args: object, **_kwargs: object) -> None:
23
+ raise RuntimeError("click is required to use the setup command")
24
+
25
+
26
+ if click is None:
27
+ setup = _missing_click
28
+ else:
29
+
30
+ @click.command()
31
+ def setup() -> None:
32
+ """Initialize system configuration interactively.
33
+
34
+ Creates /etc/sum/config.yml with infrastructure settings.
35
+ Requires sudo.
36
+
37
+ \b
38
+ The configuration file stores:
39
+ - Agency information
40
+ - Staging server settings
41
+ - Production server settings
42
+ - Infrastructure template paths
43
+ - Default values
44
+
45
+ \b
46
+ Git settings are NOT stored here - they are per-site,
47
+ specified when running 'sum-platform init'.
48
+ """
49
+ if os.geteuid() != 0:
50
+ raise click.ClickException(
51
+ "Setup requires root privileges. Run with: sudo sum-platform setup"
52
+ )
53
+
54
+ if DEFAULT_CONFIG_PATH.exists():
55
+ if not click.confirm(f"{DEFAULT_CONFIG_PATH} exists. Overwrite?"):
56
+ raise SystemExit(0)
57
+
58
+ click.echo("SUM Platform Setup")
59
+ click.echo("=" * 40)
60
+ click.echo()
61
+
62
+ # Agency
63
+ click.echo("Agency Information:")
64
+ agency_name = click.prompt(" Agency name", type=str)
65
+
66
+ click.echo()
67
+
68
+ # Staging
69
+ click.echo("Staging Server Configuration:")
70
+ staging_server = click.prompt(" Hostname", type=str)
71
+ staging_domain_pattern = click.prompt(
72
+ " Domain pattern (use {slug} placeholder)",
73
+ default="{slug}." + staging_server,
74
+ )
75
+ staging_base_dir = click.prompt(" Base directory", default="/srv/sum")
76
+
77
+ click.echo()
78
+
79
+ # Production
80
+ click.echo("Production Server Configuration:")
81
+ prod_server = click.prompt(" Hostname", type=str)
82
+ prod_ssh_host = click.prompt(" SSH host (IP or hostname)", default=prod_server)
83
+ prod_base_dir = click.prompt(" Base directory", default="/srv/sum")
84
+
85
+ click.echo()
86
+
87
+ # Templates
88
+ click.echo("Infrastructure Templates:")
89
+ templates_dir = click.prompt(" Templates directory", type=str)
90
+ systemd_template = click.prompt(
91
+ " Systemd template (relative path)",
92
+ default="systemd/sum-site-gunicorn.service.template",
93
+ )
94
+ caddy_template = click.prompt(
95
+ " Caddy template (relative path)",
96
+ default="caddy/Caddyfile.template",
97
+ )
98
+
99
+ click.echo()
100
+
101
+ # Defaults
102
+ click.echo("Defaults:")
103
+ default_theme = click.prompt(" Default theme", default="theme_a")
104
+ seed_profile = click.prompt(" Seed profile", default="starter")
105
+ deploy_user = click.prompt(" Deploy user", default="deploy")
106
+ postgres_port = click.prompt(" Postgres port", default=5432, type=int)
107
+
108
+ # Build config
109
+ config = {
110
+ "agency": {
111
+ "name": agency_name,
112
+ },
113
+ "staging": {
114
+ "server": staging_server,
115
+ "domain_pattern": staging_domain_pattern,
116
+ "base_dir": staging_base_dir,
117
+ },
118
+ "production": {
119
+ "server": prod_server,
120
+ "ssh_host": prod_ssh_host,
121
+ "base_dir": prod_base_dir,
122
+ },
123
+ "templates": {
124
+ "dir": templates_dir,
125
+ "systemd": systemd_template,
126
+ "caddy": caddy_template,
127
+ },
128
+ "defaults": {
129
+ "theme": default_theme,
130
+ "seed_profile": seed_profile,
131
+ "deploy_user": deploy_user,
132
+ "postgres_port": postgres_port,
133
+ },
134
+ }
135
+
136
+ # Write config
137
+ config_path = Path(DEFAULT_CONFIG_PATH)
138
+ config_path.parent.mkdir(parents=True, exist_ok=True)
139
+ with open(config_path, "w") as f:
140
+ yaml.dump(config, f, default_flow_style=False, sort_keys=False)
141
+
142
+ click.echo()
143
+ OutputFormatter.success(f"Configuration saved to {DEFAULT_CONFIG_PATH}")
sum/setup/git_ops.py CHANGED
@@ -13,7 +13,7 @@ from abc import ABC, abstractmethod
13
13
  from pathlib import Path
14
14
 
15
15
  from sum.exceptions import SetupError
16
- from sum.system_config import AgencyConfig, get_system_config
16
+ from sum.site_config import GitConfig
17
17
  from sum.utils.output import OutputFormatter
18
18
 
19
19
  # =============================================================================
@@ -65,8 +65,8 @@ class GitProvider(ABC):
65
65
  class GitHubProvider(GitProvider):
66
66
  """GitHub provider using gh CLI."""
67
67
 
68
- def __init__(self, config: AgencyConfig) -> None:
69
- self.org = config.github_org or ""
68
+ def __init__(self, org: str) -> None:
69
+ self.org = org
70
70
 
71
71
  @property
72
72
  def name(self) -> str:
@@ -175,10 +175,17 @@ class GitHubProvider(GitProvider):
175
175
  class GiteaProvider(GitProvider):
176
176
  """Gitea provider using REST API."""
177
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
178
+ def __init__(
179
+ self,
180
+ org: str,
181
+ base_url: str,
182
+ ssh_port: int = 22,
183
+ token_env: str = "GITEA_TOKEN",
184
+ ) -> None:
185
+ self.org = org
186
+ self.base_url = base_url.rstrip("/")
187
+ self.ssh_port = ssh_port
188
+ self.token_env = token_env
182
189
 
183
190
  @property
184
191
  def name(self) -> str:
@@ -198,13 +205,22 @@ class GiteaProvider(GitProvider):
198
205
  return f"{self.base_url}/{org}/{repo_name}"
199
206
 
200
207
  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
208
+ """Get SSH clone URL for a repository.
209
+
210
+ Uses ssh:// URL format when custom port is specified,
211
+ otherwise uses standard git@host:path format.
212
+ """
203
213
  from urllib.parse import urlparse
204
214
 
205
215
  parsed = urlparse(self.base_url)
206
216
  host = parsed.netloc
207
- return f"git@{host}:{org}/{repo_name}.git"
217
+
218
+ if self.ssh_port != 22:
219
+ # Non-standard port requires ssh:// URL format
220
+ return f"ssh://git@{host}:{self.ssh_port}/{org}/{repo_name}.git"
221
+ else:
222
+ # Standard port 22 can use short format
223
+ return f"git@{host}:{org}/{repo_name}.git"
208
224
 
209
225
  def create_repo(self, app_dir: Path, repo_name: str) -> str:
210
226
  """Create a private Gitea repository and push the code."""
@@ -338,21 +354,49 @@ class GiteaProvider(GitProvider):
338
354
  # =============================================================================
339
355
 
340
356
 
341
- def get_git_provider(config: AgencyConfig | None = None) -> GitProvider:
342
- """Get the configured git provider instance.
357
+ def get_git_provider(
358
+ provider: str,
359
+ org: str,
360
+ gitea_url: str | None = None,
361
+ gitea_ssh_port: int = 22,
362
+ gitea_token_env: str = "GITEA_TOKEN",
363
+ ) -> GitProvider:
364
+ """Create a git provider instance.
343
365
 
344
366
  Args:
345
- config: Optional AgencyConfig. If not provided, loads from system config.
367
+ provider: "github" or "gitea"
368
+ org: Organization/namespace
369
+ gitea_url: Gitea instance URL (required if provider=gitea)
370
+ gitea_ssh_port: SSH port for Gitea
371
+ gitea_token_env: Env var name for Gitea API token
346
372
 
347
373
  Returns:
348
- GitProvider instance for the configured provider.
374
+ GitProvider instance for the specified provider.
375
+
376
+ Raises:
377
+ ValueError: If provider is gitea but gitea_url not provided.
349
378
  """
350
- if config is None:
351
- config = get_system_config().agency
379
+ if provider == "gitea":
380
+ if not gitea_url:
381
+ raise ValueError("gitea_url required when provider is 'gitea'")
382
+ return GiteaProvider(
383
+ org=org,
384
+ base_url=gitea_url,
385
+ ssh_port=gitea_ssh_port,
386
+ token_env=gitea_token_env,
387
+ )
388
+ return GitHubProvider(org=org)
352
389
 
353
- if config.git_provider == "gitea":
354
- return GiteaProvider(config)
355
- return GitHubProvider(config)
390
+
391
+ def get_git_provider_from_config(git_config: GitConfig) -> GitProvider:
392
+ """Create a git provider from a GitConfig object."""
393
+ return get_git_provider(
394
+ provider=git_config.provider,
395
+ org=git_config.org,
396
+ gitea_url=git_config.url,
397
+ gitea_ssh_port=git_config.ssh_port,
398
+ gitea_token_env=git_config.token_env,
399
+ )
356
400
 
357
401
 
358
402
  # =============================================================================
@@ -401,14 +445,14 @@ def create_initial_commit(
401
445
  def setup_git_for_site(
402
446
  app_dir: Path,
403
447
  site_slug: str,
404
- skip_remote: bool = False,
448
+ git_config: GitConfig | None,
405
449
  ) -> str | None:
406
450
  """Set up git repository for a new site.
407
451
 
408
452
  Args:
409
453
  app_dir: Path to the app directory.
410
454
  site_slug: The site slug (used as repo name).
411
- skip_remote: If True, only do local git init, skip remote repo creation.
455
+ git_config: Git configuration, or None to skip remote.
412
456
 
413
457
  Returns:
414
458
  Remote repo URL if created, None if skipped.
@@ -419,45 +463,20 @@ def setup_git_for_site(
419
463
  # Create initial commit
420
464
  create_initial_commit(app_dir)
421
465
 
422
- if skip_remote:
466
+ if git_config is None:
423
467
  OutputFormatter.info("Skipping remote repository creation (--no-git)")
424
468
  return None
425
469
 
426
- # Get the configured provider
427
- provider = get_git_provider()
470
+ # Get the provider from config
471
+ provider = get_git_provider_from_config(git_config)
428
472
 
429
473
  # Check if provider is available
430
474
  if not provider.is_available():
431
475
  OutputFormatter.warning(
432
- f"{provider.name} is not available or not configured. "
433
- "Skipping repository creation."
476
+ f"{provider.name} is not available. " "Skipping repository creation."
434
477
  )
435
478
  return None
436
479
 
437
480
  # Create remote repo
438
481
  repo_url = provider.create_repo(app_dir, site_slug)
439
482
  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)
sum/setup/orchestrator.py CHANGED
@@ -6,7 +6,7 @@ import shutil
6
6
  import subprocess
7
7
  from collections.abc import Callable
8
8
  from dataclasses import dataclass
9
- from enum import Enum
9
+ from enum import StrEnum
10
10
  from pathlib import Path
11
11
 
12
12
  from sum.config import SetupConfig
@@ -33,7 +33,7 @@ class SetupResult:
33
33
  url: str = "http://127.0.0.1:8000/"
34
34
 
35
35
 
36
- class SetupStep(str, Enum):
36
+ class SetupStep(StrEnum):
37
37
  """Ordered, typed identifiers for setup steps."""
38
38
 
39
39
  SCAFFOLD = "Scaffolding structure"