Merge pull request #13185 from cli/sammorrowdrums/skills-post-merge-review-followups

Address post-merge review feedback for skills commands
This commit is contained in:
Sam Morrow 2026-04-16 18:59:21 +02:00 committed by GitHub
commit d7961f1393
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 126 additions and 133 deletions

View file

@ -42,8 +42,7 @@ type PublishOptions struct {
DryRun bool
Tag string
client *api.Client // injectable for tests; nil means use factory
host string // resolved from config in production
host string // resolved from config in production
}
// publishDiagnostic is a single validation finding.
@ -178,11 +177,10 @@ func publishRun(opts *PublishOptions) error {
canPrompt := opts.IO.CanPrompt()
// Use injected client or create one from the factory HttpClient.
// Initialization is deferred until after local validation so that
// Client initialization is deferred until after local validation so that
// simple errors (missing skills/, bad SKILL.md, etc.) are reported
// without requiring an HTTP client.
client := opts.client
var client *api.Client
host := opts.host
var diagnostics []publishDiagnostic
@ -343,14 +341,11 @@ func publishRun(opts *PublishOptions) error {
hasTopic := false
var existingTags []tagEntry
if owner != "" && repo != "" {
// Create API client from factory if not already injected (tests inject directly).
if client == nil {
httpClient, err := opts.HttpClient()
if err != nil {
return err
}
client = api.NewClientFromHTTP(httpClient)
httpClient, err := opts.HttpClient()
if err != nil {
return err
}
client = api.NewClientFromHTTP(httpClient)
if host == "" && repoInfo != nil {
host = repoInfo.Repo.RepoHost()
@ -658,10 +653,11 @@ func ensurePushed(opts *PublishOptions, dir, remoteName string) error {
}
ref := fmt.Sprintf("HEAD:refs/heads/%s", currentBranch)
fmt.Fprintf(opts.IO.ErrOut, "%s Pushing %s to %s\n", cs.SuccessIcon(), currentBranch, remoteName)
fmt.Fprintf(opts.IO.ErrOut, "Pushing %s to %s...\n", currentBranch, remoteName)
if err := gitClient.Push(ctx, remoteName, ref); err != nil {
return fmt.Errorf("failed to push branch %s: %w", currentBranch, err)
}
fmt.Fprintf(opts.IO.ErrOut, "%s Pushed %s to %s\n", cs.SuccessIcon(), currentBranch, remoteName)
return nil
}
@ -924,7 +920,9 @@ type gitHubRemote struct {
}
// detectGitHubRemote attempts to detect the GitHub owner/repo from git remotes
// in the given directory.
// in the given directory. Remotes are tried in the order returned by
// gitClient.Remotes (upstream > github > origin > rest), so the first
// GitHub-pointing remote wins.
func detectGitHubRemote(gitClient *git.Client, dir string) (*gitHubRemote, error) {
if gitClient == nil {
return nil, nil
@ -933,26 +931,11 @@ func detectGitHubRemote(gitClient *git.Client, dir string) (*gitHubRemote, error
dirClient := gitClient.Copy()
dirClient.RepoDir = dir
// Try origin first
if url, err := dirClient.RemoteURL(context.Background(), "origin"); err == nil {
repo, parseErr := parseGitHubURL(url)
if parseErr != nil {
return nil, parseErr
}
if repo != nil {
return &gitHubRemote{Repo: repo, RemoteName: "origin"}, nil
}
}
// Fall back to any remote that points to GitHub
remotes, err := dirClient.Remotes(context.Background())
if err != nil {
return nil, nil //nolint:nilerr // failing to list remotes is not an error; it just means no repo detected
}
for _, r := range remotes {
if r.Name == "origin" {
continue
}
if url, err := dirClient.RemoteURL(context.Background(), r.Name); err == nil {
repo, parseErr := parseGitHubURL(url)
if parseErr != nil {

View file

@ -10,7 +10,6 @@ import (
"testing"
"github.com/MakeNowJust/heredoc"
"github.com/cli/cli/v2/api"
"github.com/cli/cli/v2/git"
"github.com/cli/cli/v2/internal/prompter"
"github.com/cli/cli/v2/pkg/cmdutil"
@ -162,11 +161,11 @@ func TestPublishRun_UnsupportedHost(t *testing.T) {
ios, _, _, _ := iostreams.Test()
initGitRepo(t, dir, map[string]string{"origin": "https://github.com/monalisa/skills-repo.git"})
err := publishRun(&PublishOptions{
IO: ios,
Dir: dir,
GitClient: &git.Client{},
client: api.NewClientFromHTTP(&http.Client{}),
host: "acme.ghes.com",
IO: ios,
Dir: dir,
GitClient: &git.Client{},
HttpClient: func() (*http.Client, error) { return nil, nil },
host: "acme.ghes.com",
})
require.ErrorContains(t, err, "supports only github.com")
}
@ -286,12 +285,12 @@ func TestPublishRun(t *testing.T) {
opts: func(ios *iostreams.IOStreams, dir string, reg *httpmock.Registry) *PublishOptions {
t.Helper()
return &PublishOptions{
IO: ios,
Dir: dir,
DryRun: true,
GitClient: &git.Client{},
client: api.NewClientFromHTTP(&http.Client{Transport: reg}),
host: "github.com",
IO: ios,
Dir: dir,
DryRun: true,
GitClient: &git.Client{},
HttpClient: func() (*http.Client, error) { return &http.Client{Transport: reg}, nil },
host: "github.com",
}
},
wantStdout: "1 skill(s) validated successfully",
@ -323,12 +322,12 @@ func TestPublishRun(t *testing.T) {
opts: func(ios *iostreams.IOStreams, dir string, reg *httpmock.Registry) *PublishOptions {
t.Helper()
return &PublishOptions{
IO: ios,
Dir: dir,
DryRun: true,
GitClient: &git.Client{},
client: api.NewClientFromHTTP(&http.Client{Transport: reg}),
host: "github.com",
IO: ios,
Dir: dir,
DryRun: true,
GitClient: &git.Client{},
HttpClient: func() (*http.Client, error) { return &http.Client{Transport: reg}, nil },
host: "github.com",
}
},
wantStdout: "1 skill(s) validated successfully",
@ -357,12 +356,12 @@ func TestPublishRun(t *testing.T) {
"origin": "https://github.com/monalisa/skills-repo.git",
})
return &PublishOptions{
IO: ios,
Dir: dir,
DryRun: true,
GitClient: &git.Client{},
client: api.NewClientFromHTTP(&http.Client{Transport: reg}),
host: "github.com",
IO: ios,
Dir: dir,
DryRun: true,
GitClient: &git.Client{},
HttpClient: func() (*http.Client, error) { return &http.Client{Transport: reg}, nil },
host: "github.com",
}
},
wantStdout: "1 skill(s) validated successfully",
@ -416,9 +415,9 @@ func TestPublishRun(t *testing.T) {
Prompter: &prompter.PrompterMock{
ConfirmFunc: func(msg string, def bool) (bool, error) { return true, nil },
},
GitClient: &git.Client{},
client: api.NewClientFromHTTP(&http.Client{Transport: reg}),
host: "github.com",
GitClient: &git.Client{},
HttpClient: func() (*http.Client, error) { return &http.Client{Transport: reg}, nil },
host: "github.com",
}
},
wantStdout: "Published v1.0.1",
@ -564,11 +563,11 @@ func TestPublishRun(t *testing.T) {
"origin": "https://github.com/octocat/secure-repo.git",
})
return &PublishOptions{
IO: ios,
Dir: dir,
GitClient: &git.Client{},
client: api.NewClientFromHTTP(&http.Client{Transport: reg}),
host: "github.com",
IO: ios,
Dir: dir,
GitClient: &git.Client{},
HttpClient: func() (*http.Client, error) { return &http.Client{Transport: reg}, nil },
host: "github.com",
}
},
wantStdout: "secret scanning is not enabled",
@ -617,11 +616,11 @@ func TestPublishRun(t *testing.T) {
"origin": "https://github.com/octocat/tag-repo.git",
})
return &PublishOptions{
IO: ios,
Dir: dir,
GitClient: &git.Client{},
client: api.NewClientFromHTTP(&http.Client{Transport: reg}),
host: "github.com",
IO: ios,
Dir: dir,
GitClient: &git.Client{},
HttpClient: func() (*http.Client, error) { return &http.Client{Transport: reg}, nil },
host: "github.com",
}
},
wantStdout: "tag protection",
@ -680,12 +679,12 @@ func TestPublishRun(t *testing.T) {
"origin": "https://github.com/octocat/code-repo.git",
})
return &PublishOptions{
IO: ios,
Dir: dir,
DryRun: true,
GitClient: &git.Client{},
client: api.NewClientFromHTTP(&http.Client{Transport: reg}),
host: "github.com",
IO: ios,
Dir: dir,
DryRun: true,
GitClient: &git.Client{},
HttpClient: func() (*http.Client, error) { return &http.Client{Transport: reg}, nil },
host: "github.com",
}
},
wantStderr: "code scanning",
@ -745,12 +744,12 @@ func TestPublishRun(t *testing.T) {
"origin": "https://github.com/octocat/dep-repo.git",
})
return &PublishOptions{
IO: ios,
Dir: dir,
DryRun: true,
GitClient: &git.Client{},
client: api.NewClientFromHTTP(&http.Client{Transport: reg}),
host: "github.com",
IO: ios,
Dir: dir,
DryRun: true,
GitClient: &git.Client{},
HttpClient: func() (*http.Client, error) { return &http.Client{Transport: reg}, nil },
host: "github.com",
}
},
wantStderr: "Dependabot",
@ -895,12 +894,12 @@ func TestPublishRun(t *testing.T) {
"upstream": "git@github.com:octocat/repo.git",
})
return &PublishOptions{
IO: ios,
Dir: dir,
DryRun: true,
GitClient: &git.Client{},
client: api.NewClientFromHTTP(&http.Client{Transport: reg}),
host: "github.com",
IO: ios,
Dir: dir,
DryRun: true,
GitClient: &git.Client{},
HttpClient: func() (*http.Client, error) { return &http.Client{Transport: reg}, nil },
host: "github.com",
}
},
wantStderr: "octocat/repo",
@ -987,9 +986,9 @@ func TestPublishRun(t *testing.T) {
Prompter: &prompter.PrompterMock{
ConfirmFunc: func(msg string, def bool) (bool, error) { return true, nil },
},
GitClient: &git.Client{},
client: api.NewClientFromHTTP(&http.Client{Transport: reg}),
host: "github.com",
GitClient: &git.Client{},
HttpClient: func() (*http.Client, error) { return &http.Client{Transport: reg}, nil },
host: "github.com",
}
},
wantStdout: "Added \"agent-skills\" topic",
@ -1059,12 +1058,12 @@ func TestPublishRun(t *testing.T) {
"origin": "https://github.com/monalisa/skills-repo.git",
})
return &PublishOptions{
IO: ios,
Dir: dir,
Tag: "v2.3.5",
GitClient: &git.Client{},
client: api.NewClientFromHTTP(&http.Client{Transport: reg}),
host: "github.com",
IO: ios,
Dir: dir,
Tag: "v2.3.5",
GitClient: &git.Client{},
HttpClient: func() (*http.Client, error) { return &http.Client{Transport: reg}, nil },
host: "github.com",
}
},
wantStdout: "Published v2.3.5",
@ -1091,12 +1090,12 @@ func TestPublishRun(t *testing.T) {
"origin": "https://github.com/monalisa/skills-repo.git",
})
return &PublishOptions{
IO: ios,
Dir: dir,
Tag: "v1.0.0", // same as stubAllSecureRemote's existing tag
GitClient: &git.Client{},
client: api.NewClientFromHTTP(&http.Client{Transport: reg}),
host: "github.com",
IO: ios,
Dir: dir,
Tag: "v1.0.0", // same as stubAllSecureRemote's existing tag
GitClient: &git.Client{},
HttpClient: func() (*http.Client, error) { return &http.Client{Transport: reg}, nil },
host: "github.com",
}
},
wantErr: "tag v1.0.0 already exists",
@ -1124,11 +1123,11 @@ func TestPublishRun(t *testing.T) {
"origin": "https://github.com/monalisa/skills-repo.git",
})
return &PublishOptions{
IO: ios,
Dir: dir,
GitClient: &git.Client{},
client: api.NewClientFromHTTP(&http.Client{Transport: reg}),
host: "github.com",
IO: ios,
Dir: dir,
GitClient: &git.Client{},
HttpClient: func() (*http.Client, error) { return &http.Client{Transport: reg}, nil },
host: "github.com",
}
},
wantStdout: "ok",
@ -1237,9 +1236,9 @@ func TestPublishRun(t *testing.T) {
return "v1.0.0", nil // accept suggested tag
},
},
GitClient: &git.Client{},
client: api.NewClientFromHTTP(&http.Client{Transport: reg}),
host: "github.com",
GitClient: &git.Client{},
HttpClient: func() (*http.Client, error) { return &http.Client{Transport: reg}, nil },
host: "github.com",
}
},
wantStdout: "Published v1.0.0",
@ -1294,9 +1293,9 @@ func TestPublishRun(t *testing.T) {
return "beta-1", nil
},
},
GitClient: &git.Client{},
client: api.NewClientFromHTTP(&http.Client{Transport: reg}),
host: "github.com",
GitClient: &git.Client{},
HttpClient: func() (*http.Client, error) { return &http.Client{Transport: reg}, nil },
host: "github.com",
}
},
wantStdout: "Published beta-1",
@ -1350,9 +1349,9 @@ func TestPublishRun(t *testing.T) {
return "v1.0.1", nil
},
},
GitClient: &git.Client{},
client: api.NewClientFromHTTP(&http.Client{Transport: reg}),
host: "github.com",
GitClient: &git.Client{},
HttpClient: func() (*http.Client, error) { return &http.Client{Transport: reg}, nil },
host: "github.com",
}
},
wantErr: "CancelError",
@ -1414,9 +1413,9 @@ func TestPublishRun(t *testing.T) {
return "v1.0.1", nil
},
},
GitClient: &git.Client{},
client: api.NewClientFromHTTP(&http.Client{Transport: reg}),
host: "github.com",
GitClient: &git.Client{},
HttpClient: func() (*http.Client, error) { return &http.Client{Transport: reg}, nil },
host: "github.com",
}
},
wantStdout: "Enabled immutable releases",
@ -1528,12 +1527,12 @@ func TestPublishRun_DirArgUsesTargetRemote(t *testing.T) {
stubAllSecureRemote(reg, "monalisa", "target-repo")
err := publishRun(&PublishOptions{
IO: ios,
Dir: targetRepo,
DryRun: true,
GitClient: &git.Client{RepoDir: cwdRepo},
client: api.NewClientFromHTTP(&http.Client{Transport: reg}),
host: "github.com",
IO: ios,
Dir: targetRepo,
DryRun: true,
GitClient: &git.Client{RepoDir: cwdRepo},
HttpClient: func() (*http.Client, error) { return &http.Client{Transport: reg}, nil },
host: "github.com",
})
require.NoError(t, err)

View file

@ -772,7 +772,14 @@ func fetchPrimaryPages(client *api.Client, host, query string, displayPage, disp
// deduplicateResults extracts unique (repo, namespace, skill name) triples from code search hits.
func deduplicateResults(items []codeSearchItem) []skillResult {
seen := make(map[string]struct{})
// skillResultKey is a typed map key that deduplicates by (repo, namespace,
// skill name). All fields are lowercased for case-insensitive comparison.
type skillResultKey struct {
repo string
namespace string
skillName string
}
seen := make(map[skillResultKey]struct{})
var results []skillResult
for _, item := range items {
@ -780,7 +787,11 @@ func deduplicateResults(items []codeSearchItem) []skillResult {
if skillName == "" {
continue
}
key := item.Repository.FullName + "/" + namespace + "/" + skillName
key := skillResultKey{
repo: strings.ToLower(item.Repository.FullName),
namespace: strings.ToLower(namespace),
skillName: strings.ToLower(skillName),
}
if _, ok := seen[key]; ok {
continue
}