Coverage for src / gh_secrets_and_vars_async / init_cmd.py: 97%

69 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-16 17:56 +0000

1"""Lightweight repo bootstrap: ensure .env, apply repo settings, push secrets. 

2 

3For full repository standardization (rulesets, pipeline, pre-commit, renovate, 

4release, dotfiles) use ``/ai-standardize-repo`` from augint-shell. ai-gh init 

5is intentionally minimal. 

6""" 

7 

8import asyncio 

9from pathlib import Path 

10 

11import click 

12from rich import print 

13from rich.panel import Panel 

14from rich.table import Table 

15 

16from .common import configure_logging, get_github_repo, load_env_config 

17from .config import set_repo_settings 

18from .push import perform_update 

19 

20 

21def ensure_env_file(filename: str = ".env") -> str: 

22 """Ensure .env exists with required GH_* values. Prompts interactively if missing. 

23 

24 Returns the filename used. 

25 """ 

26 env_path = Path(filename) 

27 existing_lines: list[str] = [] 

28 existing_keys: set[str] = set() 

29 

30 if env_path.exists(): 

31 existing_lines = env_path.read_text().splitlines() 

32 for line in existing_lines: 

33 stripped = line.strip() 

34 if stripped and not stripped.startswith("#") and "=" in stripped: 

35 key = stripped.split("=", 1)[0].strip() 

36 existing_keys.add(key) 

37 

38 required = { 

39 "GH_ACCOUNT": ("GitHub account/org name", None), 

40 "GH_REPO": ("Repository name", Path.cwd().name), 

41 "GH_TOKEN": ("GitHub token (PAT)", None), 

42 } 

43 

44 new_entries: list[str] = [] 

45 for key, (prompt_text, default) in required.items(): 

46 if key not in existing_keys: 

47 hide = key == "GH_TOKEN" 

48 value = click.prompt(prompt_text, default=default, hide_input=hide) 

49 new_entries.append(f"{key}={value}") 

50 

51 if new_entries: 

52 with env_path.open("a") as f: 

53 if existing_lines and existing_lines[-1].strip(): 

54 f.write("\n") 

55 f.write("\n".join(new_entries) + "\n") 

56 print(f"[green]Updated {filename} with {len(new_entries)} new value(s).[/green]") 

57 

58 return filename 

59 

60 

61@click.command("init") 

62@click.option("--no-config", is_flag=True, help="Skip repo settings step.") 

63@click.option("--no-push", is_flag=True, help="Skip secrets/variables push step.") 

64@click.option("--verbose", "-v", is_flag=True, help="Print detailed output.") 

65@click.option( 

66 "--dry-run", "-d", is_flag=True, help="Show what would be done without making changes." 

67) 

68def init_command(no_config: bool, no_push: bool, verbose: bool, dry_run: bool) -> None: 

69 """Bootstrap a GitHub repository: settings + secrets. 

70 

71 For full standardization (rulesets, pipeline, pre-commit, renovate, etc.) 

72 use /ai-standardize-repo from augint-shell. 

73 """ 

74 configure_logging(verbose) 

75 

76 filename = ensure_env_file() 

77 gh_repo, gh_account, gh_token = load_env_config(filename) 

78 

79 if not gh_repo or not gh_account: 

80 raise click.ClickException("GH_REPO and GH_ACCOUNT are required.") 

81 if not gh_token: 

82 raise click.ClickException("GH_TOKEN is required.") 

83 

84 try: 

85 repo = get_github_repo(gh_account, gh_repo) 

86 except Exception as e: 

87 raise click.ClickException(f"Cannot connect to {gh_account}/{gh_repo}: {e}") from e 

88 

89 print(f"\n[bold]Initializing {gh_account}/{gh_repo}[/bold]\n") 

90 

91 summary = Table(show_header=False, box=None, padding=(0, 2)) 

92 summary.add_column("Setting", style="bold") 

93 summary.add_column("Result") 

94 summary.add_row("Repository", f"{gh_account}/{gh_repo}") 

95 

96 # Repo settings 

97 if not no_config: 

98 set_repo_settings(repo, dry_run=dry_run) 

99 summary.add_row("Merge strategy", "merge commits only") 

100 summary.add_row("Delete branch on merge", "True") 

101 else: 

102 summary.add_row("Repo settings", "skipped") 

103 

104 # Secrets push 

105 if not no_push: 

106 push_results: dict = asyncio.run(perform_update(filename, verbose, dry_run)) 

107 total = len(push_results.get("SECRETS", [])) + len(push_results.get("VARIABLES", [])) 

108 summary.add_row("Secrets/Vars", f"{total} synced") 

109 else: 

110 summary.add_row("Secrets/Vars", "skipped") 

111 

112 print() 

113 print(Panel(summary, title="[bold green]Setup Complete[/bold green]", expand=False)) 

114 print( 

115 "[dim]For rulesets, pipeline, pre-commit, renovate, release, and dotfiles, " 

116 "run /ai-standardize-repo in augint-shell.[/dim]" 

117 )