package github import ( "context" "fmt" "os" "strings" gh "github.com/google/go-github/v70/github" ) // Client wraps the GitHub REST API. type Client struct { client *gh.Client owner string repo string } // NewClient creates a GitHub client from the GITHUB_TOKEN environment variable. // The repoSlug must be in "owner/repo" format. func NewClient(repoSlug string) (*Client, error) { token := os.Getenv("GITHUB_TOKEN") if token == "" { return nil, fmt.Errorf("GITHUB_TOKEN environment variable is not set") } parts := strings.SplitN(repoSlug, "/", 2) if len(parts) != 2 { return nil, fmt.Errorf("invalid repo slug %q: expected owner/repo", repoSlug) } return &Client{ client: gh.NewClient(nil).WithAuthToken(token), owner: parts[0], repo: parts[1], }, nil } // CreateRelease creates a GitHub release for the given tag. func (c *Client) CreateRelease(ctx context.Context, tag, name, body string, draft, prerelease bool) (int64, error) { rel, _, err := c.client.Repositories.CreateRelease(ctx, c.owner, c.repo, &gh.RepositoryRelease{ TagName: gh.Ptr(tag), Name: gh.Ptr(name), Body: gh.Ptr(body), Draft: gh.Ptr(draft), Prerelease: gh.Ptr(prerelease), }) if err != nil { return 0, fmt.Errorf("creating GitHub release %s: %w", tag, err) } return rel.GetID(), nil } // UploadAsset uploads a file to an existing release. func (c *Client) UploadAsset(ctx context.Context, releaseID int64, filePath string) error { f, err := os.Open(filePath) if err != nil { return fmt.Errorf("opening %s: %w", filePath, err) } defer f.Close() name := strings.Split(filePath, "/") fileName := name[len(name)-1] _, _, err = c.client.Repositories.UploadReleaseAsset(ctx, c.owner, c.repo, releaseID, &gh.UploadOptions{Name: fileName}, f) if err != nil { return fmt.Errorf("uploading %s to release %d: %w", fileName, releaseID, err) } return nil } const commentMarker = "" // UpsertPRComment posts or updates a stamped PR comment (identified by a hidden marker). func (c *Client) UpsertPRComment(ctx context.Context, prNumber int, body string) error { markedBody := commentMarker + "\n" + body // List existing comments to find ours. comments, _, err := c.client.Issues.ListComments(ctx, c.owner, c.repo, prNumber, nil) if err != nil { return fmt.Errorf("listing PR comments: %w", err) } for _, comment := range comments { if strings.Contains(comment.GetBody(), commentMarker) { // Update existing comment. _, _, err = c.client.Issues.EditComment(ctx, c.owner, c.repo, comment.GetID(), &gh.IssueComment{Body: gh.Ptr(markedBody)}) return err } } // Create new comment. _, _, err = c.client.Issues.CreateComment(ctx, c.owner, c.repo, prNumber, &gh.IssueComment{Body: gh.Ptr(markedBody)}) return err }