changelog generator & diff tool stormlightlabs.github.io/git-storm/
changelog changeset markdown golang git
at main 2.7 kB view raw
1// package tty provides utilities for detecting terminal (TTY) availability and 2// generating appropriate fallback behavior for non-interactive environments. 3package tty 4 5import ( 6 "errors" 7 "fmt" 8 "os" 9 10 "golang.org/x/term" 11) 12 13// IsTTY checks if the given file descriptor is a terminal. 14func IsTTY(fd uintptr) bool { 15 return term.IsTerminal(int(fd)) 16} 17 18// IsInteractive checks if both stdin and stdout are connected to a terminal. 19// This is the primary check for determining if TUI applications can run. 20func IsInteractive() bool { 21 return IsTTY(os.Stdin.Fd()) && IsTTY(os.Stdout.Fd()) 22} 23 24// IsCI detects if the current environment is a CI system by checking for common 25// CI environment variables. 26func IsCI() bool { 27 ciEnvVars := []string{ 28 "CI", // Generic CI indicator 29 "CONTINUOUS_INTEGRATION", 30 "GITHUB_ACTIONS", 31 "GITLAB_CI", 32 "CIRCLECI", 33 "TRAVIS", 34 "JENKINS_URL", 35 "BUILDKITE", 36 "DRONE", 37 "TEAMCITY_VERSION", 38 } 39 40 for _, envVar := range ciEnvVars { 41 if os.Getenv(envVar) != "" { 42 return true 43 } 44 } 45 46 return false 47} 48 49// GetCIName attempts to identify the specific CI system being used. 50func GetCIName() string { 51 ciMap := map[string]string{ 52 "GITHUB_ACTIONS": "GitHub Actions", 53 "GITLAB_CI": "GitLab CI", 54 "CIRCLECI": "CircleCI", 55 "TRAVIS": "Travis CI", 56 "JENKINS_URL": "Jenkins", 57 "BUILDKITE": "Buildkite", 58 "DRONE": "Drone CI", 59 "TEAMCITY_VERSION": "TeamCity", 60 } 61 62 for envVar, name := range ciMap { 63 if os.Getenv(envVar) != "" { 64 return name 65 } 66 } 67 68 if IsCI() { 69 return "CI" 70 } 71 72 return "" 73} 74 75// ErrorInteractiveRequired returns a formatted error message indicating that the 76// command requires an interactive terminal, with suggestions for alternatives. 77func ErrorInteractiveRequired(commandName string, alternatives []string) error { 78 msg := fmt.Sprintf("command '%s' requires an interactive terminal", commandName) 79 80 if IsCI() { 81 ciName := GetCIName() 82 msg += fmt.Sprintf(" (detected %s environment)", ciName) 83 } else { 84 msg += " (stdin is not a TTY)" 85 } 86 87 if len(alternatives) > 0 { 88 msg += "\n\nAlternatives:" 89 for _, alt := range alternatives { 90 msg += fmt.Sprintf("\n - %s", alt) 91 } 92 } 93 94 return errors.New(msg) 95} 96 97// ErrorInteractiveFlag returns a formatted error message indicating that an 98// interactive flag cannot be used in a non-TTY environment. 99func ErrorInteractiveFlag(flagName string) error { 100 msg := fmt.Sprintf("flag '%s' requires an interactive terminal", flagName) 101 102 if IsCI() { 103 ciName := GetCIName() 104 msg += fmt.Sprintf(" (detected %s environment)", ciName) 105 } else { 106 msg += " (stdin is not a TTY)" 107 } 108 109 return errors.New(msg) 110}