Files
WorldTeacher/go/main.go
2025-11-04 15:28:26 +01:00

382 lines
9.6 KiB
Go

package main
import (
_ "embed"
"fmt"
"html"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"time"
"github.com/spf13/cobra"
"gopkg.in/yaml.v3"
)
// Embedded assets
//
//go:embed ../../licenses/MIT.txt
var mitLicense string
//go:embed ../../licenses/GPL-3.0.txt
var gplLicense string
//go:embed ../../licenses/AGPL-3.0.txt
var agplLicense string
//go:embed ../../licenses/Unlicense.txt
var unlicense string
//go:embed ../../.gitignore
var defaultGitignore string
type LicenseType string
const (
LicenseMIT LicenseType = "MIT"
LicenseGPLv3 LicenseType = "GPLv3"
LicenseAGPLv3 LicenseType = "AGPLv3"
LicenseUnlicense LicenseType = "Unlicense"
)
type DeployType string
const (
DeployDocker DeployType = "docker"
DeployPyPI DeployType = "pypi"
DeployCargo DeployType = "cargo"
DeployGo DeployType = "go"
)
type Config struct {
Owner string `yaml:"owner"`
Name string `yaml:"name"`
License string `yaml:"license"`
DevelopBranch string `yaml:"develop_branch"`
DefaultGitignore bool `yaml:"default_gitignore"`
DefaultGitURL string `yaml:"default_git_url"`
}
func defaultConfig() *Config {
return &Config{
Owner: "",
Name: "",
License: "MIT",
DevelopBranch: "dev",
DefaultGitignore: true,
DefaultGitURL: "https://git.theprivateserver.de/{owner}/{repo}.git",
}
}
func getConfigPath() string {
var home string
if runtime.GOOS == "windows" {
home = os.Getenv("USERPROFILE")
} else {
home = os.Getenv("HOME")
}
return filepath.Join(home, ".config", "GMS", ".config.yaml")
}
func loadConfig() (*Config, error) {
configPath := getConfigPath()
data, err := os.ReadFile(configPath)
if err != nil {
// Create default config
config := defaultConfig()
// Create config directory
dir := filepath.Dir(configPath)
if err := os.MkdirAll(dir, 0755); err != nil {
return nil, err
}
// Write default config
yamlData, err := yaml.Marshal(config)
if err != nil {
return nil, err
}
if err := os.WriteFile(configPath, yamlData, 0644); err != nil {
return nil, err
}
fmt.Printf("Created default config at: %s\n", configPath)
return config, nil
}
config := &Config{}
if err := yaml.Unmarshal(data, config); err != nil {
return nil, err
}
return config, nil
}
func runGitCommand(args []string, check bool) (string, error) {
cmd := exec.Command("git", args...)
output, err := cmd.CombinedOutput()
if err != nil && check {
return "", fmt.Errorf("git command failed: %v", err)
}
return strings.TrimSpace(string(output)), nil
}
func isGitRepo() bool {
_, err := runGitCommand([]string{"rev-parse", "--git-dir"}, false)
return err == nil
}
func getGitUserName() string {
name, _ := runGitCommand([]string{"config", "user.name"}, false)
return name
}
func decodeHTMLEntities(text string) string {
return html.UnescapeString(text)
}
func getLicenseContent(licenseType LicenseType, owner string) string {
var licenseText string
switch licenseType {
case LicenseMIT:
licenseText = mitLicense
case LicenseGPLv3:
licenseText = gplLicense
case LicenseAGPLv3:
licenseText = agplLicense
case LicenseUnlicense:
licenseText = unlicense
default:
licenseText = mitLicense
}
licenseText = decodeHTMLEntities(licenseText)
// Get fullname from git config or use owner
fullname := getGitUserName()
if fullname == "" {
fullname = owner
}
currentYear := fmt.Sprintf("%d", time.Now().Year())
// Substitute placeholders
licenseText = strings.ReplaceAll(licenseText, "{year}", currentYear)
licenseText = strings.ReplaceAll(licenseText, "{fullname}", fullname)
return licenseText
}
func removeAllRemotes() {
remotes, err := runGitCommand([]string{"remote"}, false)
if err != nil {
return
}
for _, remote := range strings.Split(remotes, "\n") {
remote = strings.TrimSpace(remote)
if remote != "" {
runGitCommand([]string{"remote", "remove", remote}, false)
}
}
}
func setupGitRepo(config *Config, forceLicense bool) error {
// Initialize repo if needed
if !isGitRepo() {
if _, err := runGitCommand([]string{"init"}, true); err != nil {
return err
}
fmt.Println("Initialized new git repository")
}
// Ensure main branch exists
branches, _ := runGitCommand([]string{"branch", "--list"}, false)
if !strings.Contains(branches, "main") {
if strings.TrimSpace(branches) == "" {
runGitCommand([]string{"checkout", "-b", "main"}, true)
} else {
runGitCommand([]string{"branch", "-M", "main"}, true)
}
} else {
runGitCommand([]string{"checkout", "main"}, false)
}
// Set up remote
remoteURL := strings.ReplaceAll(config.DefaultGitURL, "{owner}", config.Owner)
remoteURL = strings.ReplaceAll(remoteURL, "{repo}", config.Name)
// Remove all existing remotes and add new origin
removeAllRemotes()
if _, err := runGitCommand([]string{"remote", "add", "origin", remoteURL}, true); err != nil {
return err
}
fmt.Printf("Set remote 'origin' to: %s\n", remoteURL)
// Add LICENSE if missing or forced
licensePath := "LICENSE"
if _, err := os.Stat(licensePath); os.IsNotExist(err) || forceLicense {
var licenseType LicenseType
switch config.License {
case "GPLv3":
licenseType = LicenseGPLv3
case "AGPLv3":
licenseType = LicenseAGPLv3
case "Unlicense":
licenseType = LicenseUnlicense
default:
licenseType = LicenseMIT
}
licenseContent := getLicenseContent(licenseType, config.Owner)
if err := os.WriteFile(licensePath, []byte(licenseContent), 0644); err != nil {
return err
}
fmt.Printf("Added LICENSE: %s\n", config.License)
}
// Add/overwrite .gitignore if enabled
if config.DefaultGitignore {
if err := os.WriteFile(".gitignore", []byte(defaultGitignore), 0644); err != nil {
return err
}
fmt.Println("Added/updated .gitignore")
}
// Stage changes
runGitCommand([]string{"add", "."}, true)
// Commit if there are staged changes
status, _ := runGitCommand([]string{"status", "--porcelain"}, false)
if status != "" {
if _, err := runGitCommand([]string{"commit", "-m", "Initial commit"}, true); err != nil {
return err
}
fmt.Println("Created initial commit")
}
// Create and checkout dev branch
devBranch := config.DevelopBranch
runGitCommand([]string{"checkout", "-b", devBranch}, false)
fmt.Printf("Created and switched to branch: %s\n", devBranch)
// Try to push to remote
_, err1 := runGitCommand([]string{"push", "-u", "origin", "main"}, false)
_, err2 := runGitCommand([]string{"push", "-u", "origin", devBranch}, false)
if err1 == nil && err2 == nil {
fmt.Println("Pushed to remote successfully")
} else {
fmt.Println("⚠️ Warning: Could not push to remote. Network may be unavailable or remote not accessible.")
fmt.Println(" Repository configured locally. Push manually when ready.")
}
return nil
}
func setupGiteaWorkflows(deployType DeployType) {
giteaDir := ".gitea"
workflowsDir := filepath.Join(giteaDir, "workflows")
// Create directories
os.MkdirAll(workflowsDir, 0755)
// TODO: Copy/create workflow files based on deployType
fmt.Printf("Set up Gitea workflows for: %s\n", deployType)
fmt.Println("⚠️ Note: Ensure required secrets are configured in Gitea user/org settings:")
fmt.Println(" - GITEA_TOKEN, TOKEN, REGISTRY, DOCKER_USERNAME")
if deployType == DeployDocker {
fmt.Println("⚠️ Warning: Docker workflows require a Dockerfile in the repository root.")
}
}
var (
owner string
name string
license string
developBranch string
defaultGitignore *bool
defaultGitURL string
force bool
deployType string
)
var rootCmd = &cobra.Command{
Use: "gitreposetup",
Short: "Initialize and configure git repositories with licenses and workflows",
Run: func(cmd *cobra.Command, args []string) {
// Load config
config, err := loadConfig()
if err != nil {
fmt.Fprintf(os.Stderr, "Error loading config: %v\n", err)
os.Exit(1)
}
// Override config with CLI arguments
if owner != "" {
config.Owner = owner
}
if name != "" {
config.Name = name
}
if license != "" {
config.License = license
}
if developBranch != "" {
config.DevelopBranch = developBranch
}
if defaultGitignore != nil {
config.DefaultGitignore = *defaultGitignore
}
if defaultGitURL != "" {
config.DefaultGitURL = defaultGitURL
}
// Validate required fields
if config.Owner == "" || config.Name == "" {
fmt.Fprintln(os.Stderr, "Error: --owner and --name are required (or set in config file)")
os.Exit(1)
}
// Setup git repository
if err := setupGitRepo(config, force); err != nil {
fmt.Fprintf(os.Stderr, "Error setting up git repository: %v\n", err)
os.Exit(1)
}
// Setup workflows if requested
if deployType != "" {
setupGiteaWorkflows(DeployType(deployType))
}
fmt.Println("\n✅ Repository setup complete!")
},
}
func init() {
rootCmd.Flags().StringVar(&owner, "owner", "", "Repository owner/organization name")
rootCmd.Flags().StringVar(&name, "name", "", "Repository name")
rootCmd.Flags().StringVar(&license, "license", "", "License type (MIT, GPLv3, AGPLv3, Unlicense)")
rootCmd.Flags().StringVar(&developBranch, "develop-branch-name", "", "Development branch name (default: dev)")
defaultGitignore = rootCmd.Flags().Bool("default-gitignore", true, "Use default .gitignore")
rootCmd.Flags().StringVar(&defaultGitURL, "default-git-url", "", "Git remote URL template")
rootCmd.Flags().BoolVar(&force, "force", false, "Force overwrite existing LICENSE")
rootCmd.Flags().StringVar(&deployType, "deploy-type", "", "Deployment type for Gitea workflows (docker, pypi, cargo, go)")
}
func main() {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}