BugSETI/updater/channels.go
Claude 4566e7f62e
feat: extract BugSETI from core/cli into standalone repo
Move distributed bug fixing app from core/cli internal/bugseti/ and
cmd/bugseti/ into its own module. Library code at package root,
app entry point in cmd/, design docs in docs/.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 14:55:58 +00:00

176 lines
4.4 KiB
Go

// Package updater provides auto-update functionality for BugSETI.
package updater
import (
"fmt"
"regexp"
"strings"
)
// Channel represents an update channel.
type Channel string
const (
// ChannelStable is the production release channel.
// Tags: bugseti-vX.Y.Z (e.g., bugseti-v1.0.0)
ChannelStable Channel = "stable"
// ChannelBeta is the pre-release testing channel.
// Tags: bugseti-vX.Y.Z-beta.N (e.g., bugseti-v1.0.0-beta.1)
ChannelBeta Channel = "beta"
// ChannelNightly is the latest development builds channel.
// Tags: bugseti-nightly-YYYYMMDD (e.g., bugseti-nightly-20260205)
ChannelNightly Channel = "nightly"
)
// String returns the string representation of the channel.
func (c Channel) String() string {
return string(c)
}
// DisplayName returns a human-readable name for the channel.
func (c Channel) DisplayName() string {
switch c {
case ChannelStable:
return "Stable"
case ChannelBeta:
return "Beta"
case ChannelNightly:
return "Nightly"
default:
return "Unknown"
}
}
// Description returns a description of the channel.
func (c Channel) Description() string {
switch c {
case ChannelStable:
return "Production releases - most stable, recommended for most users"
case ChannelBeta:
return "Pre-release builds - new features being tested before stable release"
case ChannelNightly:
return "Latest development builds - bleeding edge, may be unstable"
default:
return "Unknown channel"
}
}
// TagPrefix returns the tag prefix used for this channel.
func (c Channel) TagPrefix() string {
switch c {
case ChannelStable:
return "bugseti-v"
case ChannelBeta:
return "bugseti-v"
case ChannelNightly:
return "bugseti-nightly-"
default:
return ""
}
}
// TagPattern returns a regex pattern to match tags for this channel.
func (c Channel) TagPattern() *regexp.Regexp {
switch c {
case ChannelStable:
// Match bugseti-vX.Y.Z but NOT bugseti-vX.Y.Z-beta.N
return regexp.MustCompile(`^bugseti-v(\d+\.\d+\.\d+)$`)
case ChannelBeta:
// Match bugseti-vX.Y.Z-beta.N
return regexp.MustCompile(`^bugseti-v(\d+\.\d+\.\d+-beta\.\d+)$`)
case ChannelNightly:
// Match bugseti-nightly-YYYYMMDD
return regexp.MustCompile(`^bugseti-nightly-(\d{8})$`)
default:
return nil
}
}
// MatchesTag returns true if the given tag matches this channel's pattern.
func (c Channel) MatchesTag(tag string) bool {
pattern := c.TagPattern()
if pattern == nil {
return false
}
return pattern.MatchString(tag)
}
// ExtractVersion extracts the version from a tag for this channel.
func (c Channel) ExtractVersion(tag string) string {
pattern := c.TagPattern()
if pattern == nil {
return ""
}
matches := pattern.FindStringSubmatch(tag)
if len(matches) < 2 {
return ""
}
return matches[1]
}
// AllChannels returns all available channels.
func AllChannels() []Channel {
return []Channel{ChannelStable, ChannelBeta, ChannelNightly}
}
// ParseChannel parses a string into a Channel.
func ParseChannel(s string) (Channel, error) {
switch strings.ToLower(s) {
case "stable":
return ChannelStable, nil
case "beta":
return ChannelBeta, nil
case "nightly":
return ChannelNightly, nil
default:
return "", fmt.Errorf("unknown channel: %s", s)
}
}
// ChannelInfo contains information about an update channel.
type ChannelInfo struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
}
// GetChannelInfo returns information about a channel.
func GetChannelInfo(c Channel) ChannelInfo {
return ChannelInfo{
ID: c.String(),
Name: c.DisplayName(),
Description: c.Description(),
}
}
// GetAllChannelInfo returns information about all channels.
func GetAllChannelInfo() []ChannelInfo {
channels := AllChannels()
info := make([]ChannelInfo, len(channels))
for i, c := range channels {
info[i] = GetChannelInfo(c)
}
return info
}
// IncludesPrerelease returns true if the channel includes pre-release versions.
func (c Channel) IncludesPrerelease() bool {
return c == ChannelBeta || c == ChannelNightly
}
// IncludesChannel returns true if this channel should include releases from the given channel.
// For example, beta channel includes stable releases, nightly includes both.
func (c Channel) IncludesChannel(other Channel) bool {
switch c {
case ChannelStable:
return other == ChannelStable
case ChannelBeta:
return other == ChannelStable || other == ChannelBeta
case ChannelNightly:
return true // Nightly users can see all releases
default:
return false
}
}