-
Notifications
You must be signed in to change notification settings - Fork 29
[WIP] Add helper to wrap 'gh' calls using Exec #3952
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| { | ||
| "entries": {} | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,41 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package workflow | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "os" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "os/exec" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "github.com/githubnext/gh-aw/pkg/logger" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var ghHelperLog = logger.New("workflow:gh_helper") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // ExecGH wraps exec.Command for "gh" CLI calls and ensures proper token configuration. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // It sets GH_TOKEN from GITHUB_TOKEN if GH_TOKEN is not already set. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // This ensures gh CLI commands work in environments where GITHUB_TOKEN is set but GH_TOKEN is not. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Usage: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // cmd := ExecGH("api", "/user") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // output, err := cmd.Output() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func ExecGH(args ...string) *exec.Cmd { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cmd := exec.Command("gh", args...) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Check if GH_TOKEN is already set | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ghToken := os.Getenv("GH_TOKEN") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ghToken != "" { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ghHelperLog.Printf("GH_TOKEN is set, using it for gh CLI") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return cmd | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Fall back to GITHUB_TOKEN if available | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| githubToken := os.Getenv("GITHUB_TOKEN") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if githubToken != "" { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ghHelperLog.Printf("GH_TOKEN not set, using GITHUB_TOKEN as fallback for gh CLI") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Set GH_TOKEN in the command's environment | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cmd.Env = append(os.Environ(), "GH_TOKEN="+githubToken) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ghHelperLog.Printf("Neither GH_TOKEN nor GITHUB_TOKEN is set, gh CLI will use default authentication") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+23
to
+39
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Check if GH_TOKEN is already set | |
| ghToken := os.Getenv("GH_TOKEN") | |
| if ghToken != "" { | |
| ghHelperLog.Printf("GH_TOKEN is set, using it for gh CLI") | |
| return cmd | |
| } | |
| // Fall back to GITHUB_TOKEN if available | |
| githubToken := os.Getenv("GITHUB_TOKEN") | |
| if githubToken != "" { | |
| ghHelperLog.Printf("GH_TOKEN not set, using GITHUB_TOKEN as fallback for gh CLI") | |
| // Set GH_TOKEN in the command's environment | |
| cmd.Env = append(os.Environ(), "GH_TOKEN="+githubToken) | |
| } else { | |
| ghHelperLog.Printf("Neither GH_TOKEN nor GITHUB_TOKEN is set, gh CLI will use default authentication") | |
| } | |
| // Prefer GITHUB_TOKEN over GH_TOKEN for consistency with GetGitHubToken() | |
| githubToken := os.Getenv("GITHUB_TOKEN") | |
| if githubToken != "" { | |
| ghHelperLog.Printf("GITHUB_TOKEN is set, using it for gh CLI (sets GH_TOKEN in env)") | |
| cmd.Env = append(os.Environ(), "GH_TOKEN="+githubToken) | |
| return cmd | |
| } | |
| // Fall back to GH_TOKEN if available | |
| ghToken := os.Getenv("GH_TOKEN") | |
| if ghToken != "" { | |
| ghHelperLog.Printf("GITHUB_TOKEN not set, using GH_TOKEN for gh CLI") | |
| // No need to set env, inherited from parent | |
| return cmd | |
| } | |
| ghHelperLog.Printf("Neither GITHUB_TOKEN nor GH_TOKEN is set, gh CLI will use default authentication") |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,149 @@ | ||||||||||||||||||||||||||||||||||||||
| package workflow | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| import ( | ||||||||||||||||||||||||||||||||||||||
| "os" | ||||||||||||||||||||||||||||||||||||||
| "strings" | ||||||||||||||||||||||||||||||||||||||
| "testing" | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| func TestExecGH(t *testing.T) { | ||||||||||||||||||||||||||||||||||||||
| tests := []struct { | ||||||||||||||||||||||||||||||||||||||
| name string | ||||||||||||||||||||||||||||||||||||||
| ghToken string | ||||||||||||||||||||||||||||||||||||||
| githubToken string | ||||||||||||||||||||||||||||||||||||||
| expectGHToken bool | ||||||||||||||||||||||||||||||||||||||
| expectValue string | ||||||||||||||||||||||||||||||||||||||
| }{ | ||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||
| name: "GH_TOKEN is set", | ||||||||||||||||||||||||||||||||||||||
| ghToken: "gh-token-123", | ||||||||||||||||||||||||||||||||||||||
| githubToken: "", | ||||||||||||||||||||||||||||||||||||||
| expectGHToken: false, // Should use existing GH_TOKEN from environment | ||||||||||||||||||||||||||||||||||||||
| expectValue: "", | ||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||
| name: "GITHUB_TOKEN is set, GH_TOKEN is not", | ||||||||||||||||||||||||||||||||||||||
| ghToken: "", | ||||||||||||||||||||||||||||||||||||||
| githubToken: "github-token-456", | ||||||||||||||||||||||||||||||||||||||
| expectGHToken: true, | ||||||||||||||||||||||||||||||||||||||
| expectValue: "github-token-456", | ||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||
| name: "Both GH_TOKEN and GITHUB_TOKEN are set", | ||||||||||||||||||||||||||||||||||||||
| ghToken: "gh-token-123", | ||||||||||||||||||||||||||||||||||||||
| githubToken: "github-token-456", | ||||||||||||||||||||||||||||||||||||||
| expectGHToken: false, // Should prefer existing GH_TOKEN | ||||||||||||||||||||||||||||||||||||||
| expectValue: "", | ||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||
| name: "Neither GH_TOKEN nor GITHUB_TOKEN is set", | ||||||||||||||||||||||||||||||||||||||
| ghToken: "", | ||||||||||||||||||||||||||||||||||||||
| githubToken: "", | ||||||||||||||||||||||||||||||||||||||
| expectGHToken: false, | ||||||||||||||||||||||||||||||||||||||
| expectValue: "", | ||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| for _, tt := range tests { | ||||||||||||||||||||||||||||||||||||||
| t.Run(tt.name, func(t *testing.T) { | ||||||||||||||||||||||||||||||||||||||
| // Save original environment | ||||||||||||||||||||||||||||||||||||||
| originalGHToken := os.Getenv("GH_TOKEN") | ||||||||||||||||||||||||||||||||||||||
| originalGitHubToken := os.Getenv("GITHUB_TOKEN") | ||||||||||||||||||||||||||||||||||||||
| defer func() { | ||||||||||||||||||||||||||||||||||||||
| os.Setenv("GH_TOKEN", originalGHToken) | ||||||||||||||||||||||||||||||||||||||
| os.Setenv("GITHUB_TOKEN", originalGitHubToken) | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+50
to
+54
|
||||||||||||||||||||||||||||||||||||||
| originalGHToken := os.Getenv("GH_TOKEN") | |
| originalGitHubToken := os.Getenv("GITHUB_TOKEN") | |
| defer func() { | |
| os.Setenv("GH_TOKEN", originalGHToken) | |
| os.Setenv("GITHUB_TOKEN", originalGitHubToken) | |
| originalGHToken, hadGHToken := os.LookupEnv("GH_TOKEN") | |
| originalGitHubToken, hadGitHubToken := os.LookupEnv("GITHUB_TOKEN") | |
| defer func() { | |
| if hadGHToken { | |
| os.Setenv("GH_TOKEN", originalGHToken) | |
| } else { | |
| os.Unsetenv("GH_TOKEN") | |
| } | |
| if hadGitHubToken { | |
| os.Setenv("GITHUB_TOKEN", originalGitHubToken) | |
| } else { | |
| os.Unsetenv("GITHUB_TOKEN") | |
| } |
Copilot
AI
Nov 14, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The environment restoration logic has a bug. If the original environment variables were unset (empty string returned by os.Getenv), calling os.Setenv with an empty string will set them to an empty value instead of unsetting them.
Issue:
originalGHToken := os.Getenv("GH_TOKEN") // Returns "" if unset
defer func() {
os.Setenv("GH_TOKEN", originalGHToken) // Sets to "" instead of unsetting
}()Fix:
originalGHToken, hadGHToken := os.LookupEnv("GH_TOKEN")
originalGitHubToken, hadGitHubToken := os.LookupEnv("GITHUB_TOKEN")
defer func() {
if hadGHToken {
os.Setenv("GH_TOKEN", originalGHToken)
} else {
os.Unsetenv("GH_TOKEN")
}
if hadGitHubToken {
os.Setenv("GITHUB_TOKEN", originalGitHubToken)
} else {
os.Unsetenv("GITHUB_TOKEN")
}
}()This ensures proper cleanup regardless of the original environment state.
| originalGHToken := os.Getenv("GH_TOKEN") | |
| originalGitHubToken := os.Getenv("GITHUB_TOKEN") | |
| defer func() { | |
| os.Setenv("GH_TOKEN", originalGHToken) | |
| os.Setenv("GITHUB_TOKEN", originalGitHubToken) | |
| originalGHToken, hadGHToken := os.LookupEnv("GH_TOKEN") | |
| originalGitHubToken, hadGitHubToken := os.LookupEnv("GITHUB_TOKEN") | |
| defer func() { | |
| if hadGHToken { | |
| os.Setenv("GH_TOKEN", originalGHToken) | |
| } else { | |
| os.Unsetenv("GH_TOKEN") | |
| } | |
| if hadGitHubToken { | |
| os.Setenv("GITHUB_TOKEN", originalGitHubToken) | |
| } else { | |
| os.Unsetenv("GITHUB_TOKEN") | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] This appears to be a new empty actions lock file that was auto-generated. It's unclear why this file is being created in the
pkg/cli/.github/aw/directory.Questions:
pkg/cli/directory, or should it be at the repository root?Recommendation: If this file is unrelated to the PR's purpose, consider excluding it from this PR and investigating why it was generated.