Compare commits
3 commits
ax/review-
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0613a3aae8 | ||
|
|
bbd06db2df | ||
|
|
00f30708ac |
7 changed files with 98 additions and 15 deletions
5
cmd.go
5
cmd.go
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"forge.lthn.ai/core/cli/pkg/cli"
|
"forge.lthn.ai/core/cli/pkg/cli"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
@ -46,7 +45,7 @@ Examples:
|
||||||
RunE: runUpdate,
|
RunE: runUpdate,
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCmd.PersistentFlags().StringVar(&updateChannel, "channel", "stable", "Release channel: stable, beta, alpha, or dev")
|
updateCmd.PersistentFlags().StringVar(&updateChannel, "channel", "stable", "Release channel: stable, beta, alpha, prerelease, or dev")
|
||||||
updateCmd.PersistentFlags().BoolVar(&updateForce, "force", false, "Force update even if already on latest version")
|
updateCmd.PersistentFlags().BoolVar(&updateForce, "force", false, "Force update even if already on latest version")
|
||||||
updateCmd.Flags().BoolVar(&updateCheck, "check", false, "Only check for updates, do not apply")
|
updateCmd.Flags().BoolVar(&updateCheck, "check", false, "Only check for updates, do not apply")
|
||||||
updateCmd.Flags().IntVar(&updateWatchPID, "watch-pid", 0, "Internal: watch for parent PID to die then restart")
|
updateCmd.Flags().IntVar(&updateWatchPID, "watch-pid", 0, "Internal: watch for parent PID to die then restart")
|
||||||
|
|
@ -73,7 +72,7 @@ func runUpdate(cmd *cobra.Command, args []string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
currentVersion := cli.AppVersion
|
currentVersion := cli.AppVersion
|
||||||
normalizedChannel := strings.TrimSpace(strings.ToLower(updateChannel))
|
normalizedChannel := normaliseGitHubChannel(updateChannel)
|
||||||
|
|
||||||
cli.Print("%s %s\n", cli.DimStyle.Render("Current version:"), cli.ValueStyle.Render(currentVersion))
|
cli.Print("%s %s\n", cli.DimStyle.Render("Current version:"), cli.ValueStyle.Render(currentVersion))
|
||||||
cli.Print("%s %s/%s\n", cli.DimStyle.Render("Platform:"), runtime.GOOS, runtime.GOARCH)
|
cli.Print("%s %s/%s\n", cli.DimStyle.Render("Platform:"), runtime.GOOS, runtime.GOARCH)
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ type githubClient struct{}
|
||||||
var NewAuthenticatedClient = func(ctx context.Context) *http.Client {
|
var NewAuthenticatedClient = func(ctx context.Context) *http.Client {
|
||||||
token := os.Getenv("GITHUB_TOKEN")
|
token := os.Getenv("GITHUB_TOKEN")
|
||||||
if token == "" {
|
if token == "" {
|
||||||
return NewHTTPClient()
|
return http.DefaultClient
|
||||||
}
|
}
|
||||||
|
|
||||||
ts := oauth2.StaticTokenSource(
|
ts := oauth2.StaticTokenSource(
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,54 @@ func TestDetermineChannel_Good(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCheckForUpdatesByTag_UsesCurrentVersionChannel(t *testing.T) {
|
||||||
|
originalVersion := Version
|
||||||
|
originalCheckForUpdates := CheckForUpdates
|
||||||
|
defer func() {
|
||||||
|
Version = originalVersion
|
||||||
|
CheckForUpdates = originalCheckForUpdates
|
||||||
|
}()
|
||||||
|
|
||||||
|
var gotChannel string
|
||||||
|
CheckForUpdates = func(owner, repo, channel string, forceSemVerPrefix bool, releaseURLFormat string) error {
|
||||||
|
gotChannel = channel
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
Version = "v2.0.0-rc.1"
|
||||||
|
if err := CheckForUpdatesByTag("owner", "repo"); err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if gotChannel != "beta" {
|
||||||
|
t.Fatalf("expected beta channel, got %q", gotChannel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckOnlyByTag_UsesCurrentVersionChannel(t *testing.T) {
|
||||||
|
originalVersion := Version
|
||||||
|
originalCheckOnly := CheckOnly
|
||||||
|
defer func() {
|
||||||
|
Version = originalVersion
|
||||||
|
CheckOnly = originalCheckOnly
|
||||||
|
}()
|
||||||
|
|
||||||
|
var gotChannel string
|
||||||
|
CheckOnly = func(owner, repo, channel string, forceSemVerPrefix bool, releaseURLFormat string) error {
|
||||||
|
gotChannel = channel
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
Version = "v2.0.0-alpha.1"
|
||||||
|
if err := CheckOnlyByTag("owner", "repo"); err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if gotChannel != "alpha" {
|
||||||
|
t.Fatalf("expected alpha channel, got %q", gotChannel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetDownloadURL_Good(t *testing.T) {
|
func TestGetDownloadURL_Good(t *testing.T) {
|
||||||
osName := runtime.GOOS
|
osName := runtime.GOOS
|
||||||
archName := runtime.GOARCH
|
archName := runtime.GOARCH
|
||||||
|
|
|
||||||
14
go.mod
14
go.mod
|
|
@ -1,11 +1,11 @@
|
||||||
module forge.lthn.ai/core/go-update
|
module dappco.re/go/core/update
|
||||||
|
|
||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
forge.lthn.ai/core/cli v0.3.6
|
dappco.re/go/core/cli v0.3.6
|
||||||
forge.lthn.ai/core/go-io v0.1.5
|
dappco.re/go/core/io v0.1.5
|
||||||
forge.lthn.ai/core/go-log v0.0.4
|
dappco.re/go/core/log v0.0.4
|
||||||
github.com/Snider/Borg v0.2.0
|
github.com/Snider/Borg v0.2.0
|
||||||
github.com/minio/selfupdate v0.6.0
|
github.com/minio/selfupdate v0.6.0
|
||||||
github.com/spf13/cobra v1.10.2
|
github.com/spf13/cobra v1.10.2
|
||||||
|
|
@ -16,9 +16,9 @@ require (
|
||||||
require (
|
require (
|
||||||
aead.dev/minisign v0.3.0 // indirect
|
aead.dev/minisign v0.3.0 // indirect
|
||||||
dario.cat/mergo v1.0.0 // indirect
|
dario.cat/mergo v1.0.0 // indirect
|
||||||
forge.lthn.ai/core/go v0.3.1 // indirect
|
dappco.re/go/core v0.3.1 // indirect
|
||||||
forge.lthn.ai/core/go-i18n v0.1.6 // indirect
|
dappco.re/go/core/i18n v0.1.6 // indirect
|
||||||
forge.lthn.ai/core/go-inference v0.1.5 // indirect
|
dappco.re/go/core/inference v0.1.5 // indirect
|
||||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||||
github.com/ProtonMail/go-crypto v1.4.0 // indirect
|
github.com/ProtonMail/go-crypto v1.4.0 // indirect
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||||
|
|
|
||||||
15
service.go
15
service.go
|
|
@ -30,7 +30,8 @@ type UpdateServiceConfig struct {
|
||||||
// repository URL (e.g., "https://github.com/owner/repo") or a base URL
|
// repository URL (e.g., "https://github.com/owner/repo") or a base URL
|
||||||
// for a generic HTTP update server.
|
// for a generic HTTP update server.
|
||||||
RepoURL string
|
RepoURL string
|
||||||
// Channel specifies the release channel to track (e.g., "stable", "prerelease").
|
// Channel specifies the release channel to track (e.g., "stable", "beta", or "prerelease").
|
||||||
|
// "prerelease" is normalised to "beta" to match the GitHub release filter.
|
||||||
// This is only used for GitHub-based updates.
|
// This is only used for GitHub-based updates.
|
||||||
Channel string
|
Channel string
|
||||||
// CheckOnStartup determines the update behavior when the service starts.
|
// CheckOnStartup determines the update behavior when the service starts.
|
||||||
|
|
@ -63,6 +64,7 @@ func NewUpdateService(config UpdateServiceConfig) (*UpdateService, error) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if isGitHub {
|
if isGitHub {
|
||||||
|
config.Channel = normaliseGitHubChannel(config.Channel)
|
||||||
owner, repo, err = ParseRepoURL(config.RepoURL)
|
owner, repo, err = ParseRepoURL(config.RepoURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, coreerr.E("NewUpdateService", "failed to parse GitHub repo URL", err)
|
return nil, coreerr.E("NewUpdateService", "failed to parse GitHub repo URL", err)
|
||||||
|
|
@ -127,3 +129,14 @@ func ParseRepoURL(repoURL string) (owner string, repo string, err error) {
|
||||||
}
|
}
|
||||||
return parts[0], parts[1], nil
|
return parts[0], parts[1], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func normaliseGitHubChannel(channel string) string {
|
||||||
|
channel = strings.ToLower(strings.TrimSpace(channel))
|
||||||
|
if channel == "" {
|
||||||
|
return "stable"
|
||||||
|
}
|
||||||
|
if channel == "prerelease" {
|
||||||
|
return "beta"
|
||||||
|
}
|
||||||
|
return channel
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,13 +12,15 @@ func TestNewUpdateService(t *testing.T) {
|
||||||
config UpdateServiceConfig
|
config UpdateServiceConfig
|
||||||
expectError bool
|
expectError bool
|
||||||
isGitHub bool
|
isGitHub bool
|
||||||
|
wantChannel string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Valid GitHub URL",
|
name: "Valid GitHub URL",
|
||||||
config: UpdateServiceConfig{
|
config: UpdateServiceConfig{
|
||||||
RepoURL: "https://github.com/owner/repo",
|
RepoURL: "https://github.com/owner/repo",
|
||||||
},
|
},
|
||||||
isGitHub: true,
|
isGitHub: true,
|
||||||
|
wantChannel: "stable",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Valid non-GitHub URL",
|
name: "Valid non-GitHub URL",
|
||||||
|
|
@ -27,6 +29,24 @@ func TestNewUpdateService(t *testing.T) {
|
||||||
},
|
},
|
||||||
isGitHub: false,
|
isGitHub: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "GitHub channel is normalised",
|
||||||
|
config: UpdateServiceConfig{
|
||||||
|
RepoURL: "https://github.com/owner/repo",
|
||||||
|
Channel: " Beta ",
|
||||||
|
},
|
||||||
|
isGitHub: true,
|
||||||
|
wantChannel: "beta",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "GitHub prerelease channel maps to beta",
|
||||||
|
config: UpdateServiceConfig{
|
||||||
|
RepoURL: "https://github.com/owner/repo",
|
||||||
|
Channel: " prerelease ",
|
||||||
|
},
|
||||||
|
isGitHub: true,
|
||||||
|
wantChannel: "beta",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Invalid GitHub URL",
|
name: "Invalid GitHub URL",
|
||||||
config: UpdateServiceConfig{
|
config: UpdateServiceConfig{
|
||||||
|
|
@ -45,6 +65,9 @@ func TestNewUpdateService(t *testing.T) {
|
||||||
if err == nil && service.isGitHub != tc.isGitHub {
|
if err == nil && service.isGitHub != tc.isGitHub {
|
||||||
t.Errorf("Expected isGitHub: %v, got: %v", tc.isGitHub, service.isGitHub)
|
t.Errorf("Expected isGitHub: %v, got: %v", tc.isGitHub, service.isGitHub)
|
||||||
}
|
}
|
||||||
|
if err == nil && tc.wantChannel != "" && service.config.Channel != tc.wantChannel {
|
||||||
|
t.Errorf("Expected GitHub channel %q, got %q", tc.wantChannel, service.config.Channel)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -145,14 +145,14 @@ var CheckOnly = func(owner, repo, channel string, forceSemVerPrefix bool, releas
|
||||||
// CheckForUpdatesByTag checks for and applies updates from GitHub based on the channel
|
// CheckForUpdatesByTag checks for and applies updates from GitHub based on the channel
|
||||||
// determined by the current application's version tag (e.g., 'stable' or 'prerelease').
|
// determined by the current application's version tag (e.g., 'stable' or 'prerelease').
|
||||||
var CheckForUpdatesByTag = func(owner, repo string) error {
|
var CheckForUpdatesByTag = func(owner, repo string) error {
|
||||||
channel := determineChannel(Version, false) // isPreRelease is false for current version
|
channel := determineChannel(Version, semver.Prerelease(formatVersionForComparison(Version)) != "")
|
||||||
return CheckForUpdates(owner, repo, channel, true, "")
|
return CheckForUpdates(owner, repo, channel, true, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckOnlyByTag checks for updates from GitHub based on the channel determined by the
|
// CheckOnlyByTag checks for updates from GitHub based on the channel determined by the
|
||||||
// current version tag, without applying them.
|
// current version tag, without applying them.
|
||||||
var CheckOnlyByTag = func(owner, repo string) error {
|
var CheckOnlyByTag = func(owner, repo string) error {
|
||||||
channel := determineChannel(Version, false) // isPreRelease is false for current version
|
channel := determineChannel(Version, semver.Prerelease(formatVersionForComparison(Version)) != "")
|
||||||
return CheckOnly(owner, repo, channel, true, "")
|
return CheckOnly(owner, repo, channel, true, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue