// cmd_github.go implements the 'setup github' command for configuring // GitHub repositories with organization standards. // // Usage: // core setup github [flags] // // Flags: // -r, --repo string Specific repo to setup // -a, --all Setup all repos in registry // -l, --labels Only sync labels // -w, --webhooks Only sync webhooks // -p, --protection Only sync branch protection // -s, --security Only sync security settings // -c, --check Dry-run: show what would change // --config string Path to github.yaml config // --verbose Show detailed output package setup import ( "errors" "os/exec" "path/filepath" "github.com/host-uk/core/pkg/cli" "github.com/host-uk/core/pkg/i18n" "github.com/host-uk/core/pkg/repos" "github.com/spf13/cobra" ) // GitHub command flags var ( ghRepo string ghAll bool ghLabels bool ghWebhooks bool ghProtection bool ghSecurity bool ghCheck bool ghConfigPath string ghVerbose bool ) // addGitHubCommand adds the 'github' subcommand to the setup command. func addGitHubCommand(parent *cobra.Command) { ghCmd := &cobra.Command{ Use: "github", Aliases: []string{"gh"}, Short: i18n.T("cmd.setup.github.short"), Long: i18n.T("cmd.setup.github.long"), RunE: func(cmd *cobra.Command, args []string) error { return runGitHubSetup() }, } ghCmd.Flags().StringVarP(&ghRepo, "repo", "r", "", i18n.T("cmd.setup.github.flag.repo")) ghCmd.Flags().BoolVarP(&ghAll, "all", "a", false, i18n.T("cmd.setup.github.flag.all")) ghCmd.Flags().BoolVarP(&ghLabels, "labels", "l", false, i18n.T("cmd.setup.github.flag.labels")) ghCmd.Flags().BoolVarP(&ghWebhooks, "webhooks", "w", false, i18n.T("cmd.setup.github.flag.webhooks")) ghCmd.Flags().BoolVarP(&ghProtection, "protection", "p", false, i18n.T("cmd.setup.github.flag.protection")) ghCmd.Flags().BoolVarP(&ghSecurity, "security", "s", false, i18n.T("cmd.setup.github.flag.security")) ghCmd.Flags().BoolVarP(&ghCheck, "check", "c", false, i18n.T("cmd.setup.github.flag.check")) ghCmd.Flags().StringVar(&ghConfigPath, "config", "", i18n.T("cmd.setup.github.flag.config")) ghCmd.Flags().BoolVarP(&ghVerbose, "verbose", "v", false, i18n.T("common.flag.verbose")) parent.AddCommand(ghCmd) } func runGitHubSetup() error { // Check gh is available if _, err := exec.LookPath("gh"); err != nil { return errors.New(i18n.T("error.gh_not_found")) } // Check gh is authenticated if !cli.GhAuthenticated() { return errors.New(i18n.T("cmd.setup.github.error.not_authenticated")) } // Find registry registryPath, err := repos.FindRegistry() if err != nil { return cli.Wrap(err, i18n.T("error.registry_not_found")) } reg, err := repos.LoadRegistry(registryPath) if err != nil { return cli.Wrap(err, "failed to load registry") } registryDir := filepath.Dir(registryPath) // Find GitHub config configPath, err := FindGitHubConfig(registryDir, ghConfigPath) if err != nil { return cli.Wrap(err, i18n.T("cmd.setup.github.error.config_not_found")) } config, err := LoadGitHubConfig(configPath) if err != nil { return cli.Wrap(err, "failed to load GitHub config") } if err := config.Validate(); err != nil { return cli.Wrap(err, "invalid GitHub config") } // Print header cli.Print("%s %s\n", dimStyle.Render(i18n.Label("registry")), registryPath) cli.Print("%s %s\n", dimStyle.Render(i18n.Label("config")), configPath) if ghCheck { cli.Print("%s\n", warningStyle.Render(i18n.T("cmd.setup.github.dry_run_mode"))) } // Determine which repos to process var reposToProcess []*repos.Repo // Reject conflicting flags if ghRepo != "" && ghAll { return errors.New(i18n.T("cmd.setup.github.error.conflicting_flags")) } if ghRepo != "" { // Single repo mode repo, ok := reg.Get(ghRepo) if !ok { return errors.New(i18n.T("error.repo_not_found", map[string]interface{}{"Name": ghRepo})) } reposToProcess = []*repos.Repo{repo} } else if ghAll { // All repos mode reposToProcess = reg.List() } else { // No repos specified cli.Print("\n%s\n", i18n.T("cmd.setup.github.no_repos_specified")) cli.Print(" %s\n", i18n.T("cmd.setup.github.usage_hint")) return nil } // Determine which operations to run runAll := !ghLabels && !ghWebhooks && !ghProtection && !ghSecurity runLabels := runAll || ghLabels runWebhooks := runAll || ghWebhooks runProtection := runAll || ghProtection runSecurity := runAll || ghSecurity // Process each repo aggregate := NewAggregate() for i, repo := range reposToProcess { repoFullName := cli.Sprintf("%s/%s", reg.Org, repo.Name) // Show progress cli.Print("\033[2K\r%s %d/%d %s", dimStyle.Render(i18n.T("common.progress.checking")), i+1, len(reposToProcess), repo.Name) changes := NewChangeSet(repo.Name) // Sync labels if runLabels { labelChanges, err := SyncLabels(repoFullName, config, ghCheck) if err != nil { cli.Print("\033[2K\r") cli.Print("%s %s: %s\n", errorStyle.Render(cli.Glyph(":cross:")), repo.Name, err) aggregate.Add(changes) // Preserve partial results continue } changes.Changes = append(changes.Changes, labelChanges.Changes...) } // Sync webhooks if runWebhooks { webhookChanges, err := SyncWebhooks(repoFullName, config, ghCheck) if err != nil { cli.Print("\033[2K\r") cli.Print("%s %s: %s\n", errorStyle.Render(cli.Glyph(":cross:")), repo.Name, err) aggregate.Add(changes) // Preserve partial results continue } changes.Changes = append(changes.Changes, webhookChanges.Changes...) } // Sync branch protection if runProtection { protectionChanges, err := SyncBranchProtection(repoFullName, config, ghCheck) if err != nil { cli.Print("\033[2K\r") cli.Print("%s %s: %s\n", errorStyle.Render(cli.Glyph(":cross:")), repo.Name, err) aggregate.Add(changes) // Preserve partial results continue } changes.Changes = append(changes.Changes, protectionChanges.Changes...) } // Sync security settings if runSecurity { securityChanges, err := SyncSecuritySettings(repoFullName, config, ghCheck) if err != nil { cli.Print("\033[2K\r") cli.Print("%s %s: %s\n", errorStyle.Render(cli.Glyph(":cross:")), repo.Name, err) aggregate.Add(changes) // Preserve partial results continue } changes.Changes = append(changes.Changes, securityChanges.Changes...) } aggregate.Add(changes) } // Clear progress line cli.Print("\033[2K\r") // Print results for _, cs := range aggregate.Sets { cs.Print(ghVerbose || ghCheck) } // Print summary aggregate.PrintSummary() // Suggest permission fix if needed if ghCheck { cli.Print("\n%s\n", i18n.T("cmd.setup.github.run_without_check")) } return nil }