Harden health checks and refresh GitHub health cache

This commit is contained in:
Julien Bisconti
2026-02-28 00:46:59 +01:00
parent 67cc5caaa5
commit ca2246667c
4 changed files with 2186 additions and 7 deletions

View File

@@ -200,6 +200,12 @@ func healthCmd() *cobra.Command {
for _, e := range errs {
fmt.Printf(" error: %v\n", e)
}
if len(infos) == 0 {
if len(errs) > 0 {
return fmt.Errorf("failed to fetch GitHub metadata for all repositories (%d errors); check network/DNS and GITHUB_TOKEN", len(errs))
}
return fmt.Errorf("no GitHub repositories found in README")
}
scored := scorer.ScoreAll(infos)
cacheEntries := scorer.ToCacheEntries(scored)

2122
config/health_cache.yaml Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -27,21 +27,35 @@ type RepoInfo struct {
// ExtractGitHubRepo extracts owner/name from a GitHub URL.
// Returns false for non-repo URLs (issues, wiki, apps, etc.).
func ExtractGitHubRepo(url string) (owner, name string, ok bool) {
if !strings.HasPrefix(url, "https://github.com/") {
func ExtractGitHubRepo(rawURL string) (owner, name string, ok bool) {
u, err := url.Parse(rawURL)
if err != nil {
return "", "", false
}
path := strings.TrimPrefix(url, "https://github.com/")
path = strings.TrimRight(path, "/")
host := strings.ToLower(u.Hostname())
if host != "github.com" && host != "www.github.com" {
return "", "", false
}
path := strings.Trim(u.Path, "/")
parts := strings.Split(path, "/")
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
return "", "", false
}
// Skip non-repo paths
if parts[0] == "apps" || parts[0] == "features" || parts[0] == "topics" {
// Skip known non-repository top-level routes.
switch parts[0] {
case "apps", "features", "topics":
return "", "", false
}
return parts[0], parts[1], true
name = strings.TrimSuffix(parts[1], ".git")
if name == "" {
return "", "", false
}
return parts[0], name, true
}
func isHTTPURL(raw string) bool {
@@ -52,6 +66,16 @@ func isHTTPURL(raw string) bool {
return u.Scheme == "http" || u.Scheme == "https"
}
func isGitHubAuthError(err error) bool {
if err == nil {
return false
}
s := strings.ToLower(err.Error())
return strings.Contains(s, "401 unauthorized") ||
strings.Contains(s, "bad credentials") ||
strings.Contains(s, "resource not accessible by integration")
}
// PartitionLinks separates URLs into GitHub repos and external HTTP(S) links.
func PartitionLinks(urls []string) (github, external []string) {
for _, url := range urls {
@@ -134,6 +158,9 @@ func (gc *GitHubChecker) CheckRepos(ctx context.Context, urls []string, batchSiz
info, err := gc.CheckRepo(ctx, owner, name)
if err != nil {
errs = append(errs, err)
if isGitHubAuthError(err) {
break
}
continue
}
results = append(results, info)

View File

@@ -1,6 +1,7 @@
package checker
import (
"errors"
"testing"
)
@@ -14,6 +15,10 @@ func TestExtractGitHubRepo(t *testing.T) {
{"https://github.com/docker/compose", "docker", "compose", true},
{"https://github.com/moby/moby", "moby", "moby", true},
{"https://github.com/user/repo/", "user", "repo", true},
{"https://github.com/user/repo?tab=readme-ov-file", "user", "repo", true},
{"https://github.com/user/repo#readme", "user", "repo", true},
{"https://github.com/user/repo.git", "user", "repo", true},
{"https://www.github.com/user/repo", "user", "repo", true},
{"https://github.com/user/repo/issues", "", "", false},
{"https://github.com/user/repo/wiki", "", "", false},
{"https://github.com/apps/dependabot", "", "", false},
@@ -52,3 +57,22 @@ func TestPartitionLinks(t *testing.T) {
t.Errorf("external links = %d, want 2", len(ext))
}
}
func TestIsGitHubAuthError(t *testing.T) {
tests := []struct {
err error
want bool
}{
{errors.New("non-200 OK status code: 401 Unauthorized body: \"Bad credentials\""), true},
{errors.New("Resource not accessible by integration"), true},
{errors.New("dial tcp: lookup api.github.com: no such host"), false},
{errors.New("context deadline exceeded"), false},
}
for _, tt := range tests {
got := isGitHubAuthError(tt.err)
if got != tt.want {
t.Errorf("isGitHubAuthError(%q) = %v, want %v", tt.err, got, tt.want)
}
}
}