Skip to content

Commit 63262dc

Browse files
feat(skills): support GHEC with data residency hosts
Widen ValidateSupportedHost to accept tenancy hosts (*.ghe.com) alongside github.com. GHEC with data residency uses these domains, and all skill subcommands (search, install, preview, publish, update) now allow them. GitHub Enterprise Server remains unsupported and is explicitly rejected with a clear error message. Also fix the lockfile writer to use the actual host when constructing SourceURL instead of hardcoding github.com. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 3ad2958 commit 63262dc

11 files changed

Lines changed: 52 additions & 19 deletions

File tree

internal/skills/installer/installer.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ func Install(opts *Options) (*Result, error) {
7676
return nil, fmt.Errorf("failed to install skill %q: %w", skill.InstallName(), err)
7777
}
7878
var warnings []string
79-
if err := lockfile.RecordInstall(skill.InstallName(), opts.Owner, opts.Repo, skill.Path+"/SKILL.md", skill.TreeSHA, opts.PinnedRef); err != nil {
79+
if err := lockfile.RecordInstall(opts.Host, skill.InstallName(), opts.Owner, opts.Repo, skill.Path+"/SKILL.md", skill.TreeSHA, opts.PinnedRef); err != nil {
8080
warnings = append(warnings, fmt.Sprintf("could not record install for %s: %v", skill.InstallName(), err))
8181
}
8282
return &Result{Installed: []string{skill.InstallName()}, Dir: targetDir, Warnings: warnings}, nil
@@ -129,7 +129,7 @@ func Install(opts *Options) (*Result, error) {
129129
}
130130
installed = append(installed, r.name)
131131
skill := opts.Skills[i]
132-
if err := lockfile.RecordInstall(skill.InstallName(), opts.Owner, opts.Repo, skill.Path+"/SKILL.md", skill.TreeSHA, opts.PinnedRef); err != nil {
132+
if err := lockfile.RecordInstall(opts.Host, skill.InstallName(), opts.Owner, opts.Repo, skill.Path+"/SKILL.md", skill.TreeSHA, opts.PinnedRef); err != nil {
133133
warnings = append(warnings, fmt.Sprintf("could not record install for %s: %v", skill.InstallName(), err))
134134
}
135135
}

internal/skills/lockfile/lockfile.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"time"
1111

1212
"github.com/cli/cli/v2/internal/flock"
13+
"github.com/cli/cli/v2/internal/ghinstance"
1314
)
1415

1516
const (
@@ -93,7 +94,7 @@ func writeTo(f *os.File, lf *file) error {
9394
// RecordInstall adds or updates a skill entry in the lock file.
9495
// It uses a file-based lock to prevent concurrent read-modify-write races
9596
// when multiple install processes run simultaneously.
96-
func RecordInstall(skillName, owner, repo, skillPath, treeSHA, pinnedRef string) error {
97+
func RecordInstall(host, skillName, owner, repo, skillPath, treeSHA, pinnedRef string) error {
9798
lockPath, err := lockfilePath()
9899
if err != nil {
99100
return err
@@ -124,7 +125,7 @@ func RecordInstall(skillName, owner, repo, skillPath, treeSHA, pinnedRef string)
124125
f.Skills[skillName] = entry{
125126
Source: owner + "/" + repo,
126127
SourceType: "github",
127-
SourceURL: "https://github.com/" + owner + "/" + repo + ".git",
128+
SourceURL: ghinstance.HostPrefix(host) + owner + "/" + repo + ".git",
128129
SkillPath: skillPath,
129130
SkillFolderHash: treeSHA,
130131
InstalledAt: installedAt,

internal/skills/lockfile/lockfile_test.go

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ func TestRecordInstall(t *testing.T) {
2424
tests := []struct {
2525
name string
2626
setup func(t *testing.T)
27+
host string
2728
skill string
2829
owner string
2930
repo string
@@ -35,6 +36,7 @@ func TestRecordInstall(t *testing.T) {
3536
}{
3637
{
3738
name: "fresh install creates lockfile",
39+
host: "github.com",
3840
skill: "code-review",
3941
owner: "monalisa",
4042
repo: "octocat-skills",
@@ -55,8 +57,25 @@ func TestRecordInstall(t *testing.T) {
5557
assert.Empty(t, e.PinnedRef)
5658
},
5759
},
60+
{
61+
name: "tenancy host uses correct URL",
62+
host: "mycompany.ghe.com",
63+
skill: "code-review",
64+
owner: "monalisa",
65+
repo: "octocat-skills",
66+
skillPath: "skills/code-review/SKILL.md",
67+
treeSHA: "abc123",
68+
verify: func(t *testing.T, lockPath string) {
69+
t.Helper()
70+
f := readTestLockfile(t, lockPath)
71+
require.Contains(t, f.Skills, "code-review")
72+
e := f.Skills["code-review"]
73+
assert.Equal(t, "https://mycompany.ghe.com/monalisa/octocat-skills.git", e.SourceURL)
74+
},
75+
},
5876
{
5977
name: "install with pinned ref",
78+
host: "github.com",
6079
skill: "pr-summary",
6180
owner: "hubot",
6281
repo: "skills-repo",
@@ -73,8 +92,9 @@ func TestRecordInstall(t *testing.T) {
7392
name: "multiple skills coexist",
7493
setup: func(t *testing.T) {
7594
t.Helper()
76-
require.NoError(t, RecordInstall("code-review", "monalisa", "octocat-skills", "skills/code-review/SKILL.md", "sha1", ""))
95+
require.NoError(t, RecordInstall("github.com", "code-review", "monalisa", "octocat-skills", "skills/code-review/SKILL.md", "sha1", ""))
7796
},
97+
host: "github.com",
7898
skill: "issue-triage",
7999
owner: "monalisa",
80100
repo: "octocat-skills",
@@ -107,6 +127,7 @@ func TestRecordInstall(t *testing.T) {
107127
require.NoError(t, err)
108128
t.Cleanup(unlock)
109129
},
130+
host: "github.com",
110131
skill: "code-review",
111132
owner: "monalisa",
112133
repo: "octocat-skills",
@@ -123,6 +144,7 @@ func TestRecordInstall(t *testing.T) {
123144
require.NoError(t, os.MkdirAll(filepath.Dir(lockPath), 0o755))
124145
require.NoError(t, os.WriteFile(lockPath, []byte("{invalid json"), 0o644))
125146
},
147+
host: "github.com",
126148
skill: "code-review",
127149
owner: "monalisa",
128150
repo: "octocat-skills",
@@ -145,6 +167,7 @@ func TestRecordInstall(t *testing.T) {
145167
data, _ := json.Marshal(file{Version: 999, Skills: map[string]entry{"old-skill": {}}})
146168
require.NoError(t, os.WriteFile(lockPath, data, 0o644))
147169
},
170+
host: "github.com",
148171
skill: "code-review",
149172
owner: "monalisa",
150173
repo: "octocat-skills",
@@ -166,7 +189,7 @@ func TestRecordInstall(t *testing.T) {
166189
tt.setup(t)
167190
}
168191

169-
err := RecordInstall(tt.skill, tt.owner, tt.repo, tt.skillPath, tt.treeSHA, tt.pinnedRef)
192+
err := RecordInstall(tt.host, tt.skill, tt.owner, tt.repo, tt.skillPath, tt.treeSHA, tt.pinnedRef)
170193
if tt.wantErr {
171194
require.Error(t, err)
172195
return
@@ -181,10 +204,10 @@ func TestRecordInstall(t *testing.T) {
181204
t.Run("update preserves InstalledAt and updates treeSHA", func(t *testing.T) {
182205
lockPath := setupTestHome(t)
183206

184-
require.NoError(t, RecordInstall("code-review", "monalisa", "octocat-skills", "skills/code-review/SKILL.md", "old-sha", ""))
207+
require.NoError(t, RecordInstall("github.com", "code-review", "monalisa", "octocat-skills", "skills/code-review/SKILL.md", "old-sha", ""))
185208
firstInstalledAt := readTestLockfile(t, lockPath).Skills["code-review"].InstalledAt
186209

187-
require.NoError(t, RecordInstall("code-review", "monalisa", "octocat-skills", "skills/code-review/SKILL.md", "new-sha", ""))
210+
require.NoError(t, RecordInstall("github.com", "code-review", "monalisa", "octocat-skills", "skills/code-review/SKILL.md", "new-sha", ""))
188211
entry := readTestLockfile(t, lockPath).Skills["code-review"]
189212

190213
assert.Equal(t, "new-sha", entry.SkillFolderHash, "treeSHA should be updated")

internal/skills/source/source.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"fmt"
55
"strings"
66

7+
ghauth "github.com/cli/go-gh/v2/pkg/auth"
8+
79
"github.com/cli/cli/v2/internal/ghrepo"
810
)
911

@@ -48,16 +50,21 @@ func ParseMetadataRepo(meta map[string]interface{}) (ghrepo.Interface, bool, err
4850
return repo, true, nil
4951
}
5052

51-
// ValidateSupportedHost rejects hosts that are not supported in public preview.
53+
// ValidateSupportedHost rejects hosts that are not supported.
54+
// Supported hosts are github.com and GHEC with data residency (*.ghe.com).
55+
// GitHub Enterprise Server is not currently supported.
5256
func ValidateSupportedHost(host string) error {
5357
host = normalizeHost(host)
5458
if host == "" {
5559
return fmt.Errorf("could not determine repository host")
5660
}
57-
if host != SupportedHost {
58-
return fmt.Errorf("GitHub Skills currently supports only %s as a host; got %s", SupportedHost, host)
61+
if host == SupportedHost || ghauth.IsTenancy(host) {
62+
return nil
63+
}
64+
if ghauth.IsEnterprise(host) {
65+
return fmt.Errorf("GitHub Skills does not currently support GitHub Enterprise Server; got %s", host)
5966
}
60-
return nil
67+
return fmt.Errorf("unsupported host for GitHub Skills: %s", host)
6168
}
6269

6370
func normalizeHost(host string) string {

internal/skills/source/source_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,5 +72,7 @@ func TestParseMetadataRepo(t *testing.T) {
7272

7373
func TestValidateSupportedHost(t *testing.T) {
7474
require.NoError(t, ValidateSupportedHost("github.com"))
75-
require.ErrorContains(t, ValidateSupportedHost("acme.ghes.com"), "supports only github.com")
75+
require.NoError(t, ValidateSupportedHost("mycompany.ghe.com"), "GHEC data residency tenancy hosts should be accepted")
76+
require.ErrorContains(t, ValidateSupportedHost("acme.ghes.com"), "does not currently support GitHub Enterprise Server")
77+
require.ErrorContains(t, ValidateSupportedHost("github.localhost"), "unsupported host")
7678
}

pkg/cmd/skills/install/install_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1177,7 +1177,7 @@ func TestInstallRun(t *testing.T) {
11771177
SkillName: "git-commit",
11781178
}
11791179
},
1180-
wantErr: "supports only github.com",
1180+
wantErr: "does not currently support GitHub Enterprise Server",
11811181
},
11821182
{
11831183
name: "select all skills in interactive prompt",

pkg/cmd/skills/preview/preview_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ func TestPreviewRun_UnsupportedHost(t *testing.T) {
403403
repo: ghrepo.NewWithHost("github", "awesome-copilot", "acme.ghes.com"),
404404
Telemetry: &telemetry.NoOpService{},
405405
})
406-
require.ErrorContains(t, err, "supports only github.com")
406+
require.ErrorContains(t, err, "does not currently support GitHub Enterprise Server")
407407
}
408408

409409
func TestPreviewRun_Interactive(t *testing.T) {

pkg/cmd/skills/publish/publish.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -968,7 +968,7 @@ func detectGitHubRemote(gitClient *git.Client, dir string) (*gitHubRemote, error
968968
}
969969

970970
// parseGitHubURL extracts owner/repo from a GitHub remote URL.
971-
// Only GitHub.com URLs are recognized.
971+
// Only github.com and GHEC data residency (*.ghe.com) URLs are recognized.
972972
func parseGitHubURL(rawURL string) (ghrepo.Interface, error) {
973973
u, err := git.ParseURL(rawURL)
974974
if err != nil {

pkg/cmd/skills/publish/publish_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ func TestPublishRun_UnsupportedHost(t *testing.T) {
171171
HttpClient: func() (*http.Client, error) { return nil, nil },
172172
host: "acme.ghes.com",
173173
})
174-
require.ErrorContains(t, err, "supports only github.com")
174+
require.ErrorContains(t, err, "does not currently support GitHub Enterprise Server")
175175
}
176176

177177
func TestPublishRun(t *testing.T) {

pkg/cmd/skills/search/search_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func TestSearchRun_UnsupportedHost(t *testing.T) {
3333
HttpClient: func() (*http.Client, error) { return &http.Client{}, nil },
3434
Config: func() (gh.Config, error) { return cfg, nil },
3535
})
36-
require.ErrorContains(t, err, "supports only github.com")
36+
require.ErrorContains(t, err, "does not currently support GitHub Enterprise Server")
3737
}
3838

3939
func TestNewCmdSearch(t *testing.T) {

0 commit comments

Comments
 (0)