From 8e90e6ae270be28cac532aa428cd73f202448fa9 Mon Sep 17 00:00:00 2001 From: "anthropic-code-agent[bot]" <242468646+Claude@users.noreply.github.com> Date: Sat, 14 Mar 2026 18:27:56 +0000 Subject: [PATCH 1/3] Initial plan From 62070c4826891dcb83a505323a4cdb9634bc8783 Mon Sep 17 00:00:00 2001 From: "anthropic-code-agent[bot]" <242468646+Claude@users.noreply.github.com> Date: Sat, 14 Mar 2026 18:35:57 +0000 Subject: [PATCH 2/3] feat: Add GHES auto-detection helper functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add foundation for GHES auto-configuration in gh aw add-wizard: - isGHESInstance(): Detect GHES vs public GitHub from git remote - getGHESAPIURL(): Get GHES API URL for engine.api-target config - getGHESAllowedDomains(): Get GHES domains for network firewall These helpers parse the git origin remote URL and enable the wizard to: 1. Auto-populate engine.api-target for Copilot on GHES 2. Auto-add GHES domains to network.allowed 3. Support proper gh CLI host configuration Includes comprehensive test coverage for all GHES detection scenarios. Related: #20875 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- .changeset/patch-ghes-auto-detect-helpers.md | 16 ++ pkg/cli/git.go | 42 +++++ pkg/cli/git_ghes_test.go | 166 +++++++++++++++++++ 3 files changed, 224 insertions(+) create mode 100644 .changeset/patch-ghes-auto-detect-helpers.md create mode 100644 pkg/cli/git_ghes_test.go diff --git a/.changeset/patch-ghes-auto-detect-helpers.md b/.changeset/patch-ghes-auto-detect-helpers.md new file mode 100644 index 00000000000..f6d81e417ab --- /dev/null +++ b/.changeset/patch-ghes-auto-detect-helpers.md @@ -0,0 +1,16 @@ +--- +"gh-aw": patch +--- + +Add GHES auto-detection helper functions for wizard configuration. This lays the foundation for auto-configuring GHES-specific settings in `gh aw add-wizard`: + +- Add `isGHESInstance()` to detect GHES instances vs. public GitHub +- Add `getGHESAPIURL()` to get the GHES API URL for engine.api-target configuration +- Add `getGHESAllowedDomains()` to get GHES domains for network firewall configuration + +These functions detect GHES instances by parsing the git origin remote URL and can be used by the wizard to: +1. Auto-populate `engine.api-target` for Copilot on GHES +2. Auto-add GHES domains to `network.allowed` for firewall configuration +3. Enable proper gh CLI host configuration via `GH_HOST` environment variable + +Related to github/gh-aw#20875 and the GHES wizard auto-configuration requirements. diff --git a/pkg/cli/git.go b/pkg/cli/git.go index 0ca4c85b406..459b9e63976 100644 --- a/pkg/cli/git.go +++ b/pkg/cli/git.go @@ -141,6 +141,48 @@ func getHostFromOriginRemote() string { return host } +// isGHESInstance returns true if the git remote origin points to a GitHub Enterprise Server instance +// (not github.com). This is used to enable GHES-specific behavior like auto-configuring api-target +// and network firewall domains. +func isGHESInstance() bool { + host := getHostFromOriginRemote() + isGHES := host != "github.com" + gitLog.Printf("GHES instance check: host=%s, isGHES=%v", host, isGHES) + return isGHES +} + +// getGHESAPIURL returns the API URL for a GHES instance (e.g., "https://ghes.example.com/api/v3") +// or an empty string if not a GHES instance. This is used to auto-populate engine.api-target. +func getGHESAPIURL() string { + host := getHostFromOriginRemote() + if host == "github.com" { + return "" + } + + // GHES API URL format: https:///api/v3 + apiURL := fmt.Sprintf("https://%s/api/v3", host) + gitLog.Printf("GHES API URL: %s", apiURL) + return apiURL +} + +// getGHESAllowedDomains returns a list of domains that should be added to the firewall +// allowed list for GHES instances. This includes the GHES hostname and api.. +func getGHESAllowedDomains() []string { + host := getHostFromOriginRemote() + if host == "github.com" { + return nil + } + + // For GHES, allow both the main host and api subdomain + domains := []string{ + host, + fmt.Sprintf("api.%s", host), + } + + gitLog.Printf("GHES allowed domains: %v", domains) + return domains +} + // getRepositorySlugFromRemote extracts the repository slug (owner/repo) from git remote URL func getRepositorySlugFromRemote() string { gitLog.Print("Getting repository slug from git remote") diff --git a/pkg/cli/git_ghes_test.go b/pkg/cli/git_ghes_test.go new file mode 100644 index 00000000000..04795ab76b8 --- /dev/null +++ b/pkg/cli/git_ghes_test.go @@ -0,0 +1,166 @@ +//go:build !integration + +package cli + +import ( + "os" + "os/exec" + "testing" + + "github.com/github/gh-aw/pkg/testutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestIsGHESInstance(t *testing.T) { + tests := []struct { + name string + remoteURL string + wantIsGHES bool + }{ + { + name: "public GitHub", + remoteURL: "https://github.com/org/repo.git", + wantIsGHES: false, + }, + { + name: "GHES instance", + remoteURL: "https://ghes.example.com/org/repo.git", + wantIsGHES: true, + }, + { + name: "GHES SSH format", + remoteURL: "git@ghes.example.com:org/repo.git", + wantIsGHES: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tmpDir := testutil.TempDir(t, "test-*") + originalDir, err := os.Getwd() + require.NoError(t, err, "Failed to get current directory") + defer func() { + _ = os.Chdir(originalDir) + }() + + require.NoError(t, os.Chdir(tmpDir), "Failed to change to temp directory") + + // Initialize git repo + require.NoError(t, exec.Command("git", "init").Run(), "Failed to init git repo") + exec.Command("git", "config", "user.name", "Test User").Run() + exec.Command("git", "config", "user.email", "test@example.com").Run() + + // Set remote URL + require.NoError(t, exec.Command("git", "remote", "add", "origin", tt.remoteURL).Run(), "Failed to add remote") + defer func() { _ = exec.Command("git", "remote", "remove", "origin").Run() }() + + got := isGHESInstance() + assert.Equal(t, tt.wantIsGHES, got, "isGHESInstance() returned unexpected result") + }) + } +} + +func TestGetGHESAPIURL(t *testing.T) { + tests := []struct { + name string + remoteURL string + wantAPIURL string + }{ + { + name: "public GitHub returns empty", + remoteURL: "https://github.com/org/repo.git", + wantAPIURL: "", + }, + { + name: "GHES instance returns API URL", + remoteURL: "https://ghes.example.com/org/repo.git", + wantAPIURL: "https://ghes.example.com/api/v3", + }, + { + name: "GHES SSH format returns API URL", + remoteURL: "git@contoso.ghe.com:org/repo.git", + wantAPIURL: "https://contoso.ghe.com/api/v3", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tmpDir := testutil.TempDir(t, "test-*") + originalDir, err := os.Getwd() + require.NoError(t, err, "Failed to get current directory") + defer func() { + _ = os.Chdir(originalDir) + }() + + require.NoError(t, os.Chdir(tmpDir), "Failed to change to temp directory") + + // Initialize git repo + require.NoError(t, exec.Command("git", "init").Run(), "Failed to init git repo") + exec.Command("git", "config", "user.name", "Test User").Run() + exec.Command("git", "config", "user.email", "test@example.com").Run() + + // Set remote URL + require.NoError(t, exec.Command("git", "remote", "add", "origin", tt.remoteURL).Run(), "Failed to add remote") + defer func() { _ = exec.Command("git", "remote", "remove", "origin").Run() }() + + got := getGHESAPIURL() + assert.Equal(t, tt.wantAPIURL, got, "getGHESAPIURL() returned unexpected result") + }) + } +} + +func TestGetGHESAllowedDomains(t *testing.T) { + tests := []struct { + name string + remoteURL string + wantDomains []string + }{ + { + name: "public GitHub returns nil", + remoteURL: "https://github.com/org/repo.git", + wantDomains: nil, + }, + { + name: "GHES instance returns host and api subdomain", + remoteURL: "https://ghes.example.com/org/repo.git", + wantDomains: []string{ + "ghes.example.com", + "api.ghes.example.com", + }, + }, + { + name: "GHES SSH format returns domains", + remoteURL: "git@contoso-aw.ghe.com:org/repo.git", + wantDomains: []string{ + "contoso-aw.ghe.com", + "api.contoso-aw.ghe.com", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tmpDir := testutil.TempDir(t, "test-*") + originalDir, err := os.Getwd() + require.NoError(t, err, "Failed to get current directory") + defer func() { + _ = os.Chdir(originalDir) + }() + + require.NoError(t, os.Chdir(tmpDir), "Failed to change to temp directory") + + // Initialize git repo + require.NoError(t, exec.Command("git", "init").Run(), "Failed to init git repo") + exec.Command("git", "config", "user.name", "Test User").Run() + exec.Command("git", "config", "user.email", "test@example.com").Run() + + // Set remote URL + require.NoError(t, exec.Command("git", "remote", "add", "origin", tt.remoteURL).Run(), "Failed to add remote") + defer func() { _ = exec.Command("git", "remote", "remove", "origin").Run() }() + + got := getGHESAllowedDomains() + assert.Equal(t, tt.wantDomains, got, "getGHESAllowedDomains() returned unexpected result") + }) + } +} From b81778696ad49220a2bd2484d2c19a4ccef49ebb Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sat, 14 Mar 2026 12:07:29 -0700 Subject: [PATCH 3/3] [WIP] [67083185060] Fix failing GitHub Actions workflow lint-go (#20977) * Initial plan * Fix lint: replace fmt.Sprintf with string concatenation in git.go Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --- pkg/cli/git.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cli/git.go b/pkg/cli/git.go index 459b9e63976..8b4f9fbf195 100644 --- a/pkg/cli/git.go +++ b/pkg/cli/git.go @@ -176,7 +176,7 @@ func getGHESAllowedDomains() []string { // For GHES, allow both the main host and api subdomain domains := []string{ host, - fmt.Sprintf("api.%s", host), + "api." + host, } gitLog.Printf("GHES allowed domains: %v", domains)