diff --git a/src/cli/notice.go b/src/cli/notice.go index 58640b441b69..9bd6f99dd6da 100644 --- a/src/cli/notice.go +++ b/src/cli/notice.go @@ -2,9 +2,10 @@ package cli import ( "fmt" + "os" + "github.com/jandedobbeleer/oh-my-posh/src/config" "github.com/jandedobbeleer/oh-my-posh/src/runtime" - "github.com/jandedobbeleer/oh-my-posh/src/upgrade" "github.com/spf13/cobra" ) @@ -23,7 +24,12 @@ var noticeCmd = &cobra.Command{ env.Init(flags) defer env.Close() - if notice, hasNotice := upgrade.Notice(env, false); hasNotice { + sh := os.Getenv("POSH_SHELL") + configFile := config.Path(configFlag) + cfg := config.Load(configFile, sh, false) + cfg.Upgrade.Cache = env.Cache() + + if notice, hasNotice := cfg.Upgrade.Notice(); hasNotice { fmt.Println(notice) } }, diff --git a/src/cli/upgrade.go b/src/cli/upgrade.go index abeb6343bae7..25676b37039e 100644 --- a/src/cli/upgrade.go +++ b/src/cli/upgrade.go @@ -7,6 +7,7 @@ import ( "slices" "github.com/jandedobbeleer/oh-my-posh/src/build" + "github.com/jandedobbeleer/oh-my-posh/src/config" "github.com/jandedobbeleer/oh-my-posh/src/runtime" "github.com/jandedobbeleer/oh-my-posh/src/terminal" "github.com/jandedobbeleer/oh-my-posh/src/upgrade" @@ -32,36 +33,42 @@ var upgradeCmd = &cobra.Command{ return } + sh := os.Getenv("POSH_SHELL") + env := &runtime.Terminal{} env.Init(nil) defer env.Close() - terminal.Init(env.Shell()) + terminal.Init(sh) fmt.Print(terminal.StartProgress()) - latest, err := upgrade.Latest(env) + configFile := config.Path(configFlag) + cfg := config.Load(configFile, sh, false) + cfg.Upgrade.Cache = env.Cache() + + latest, err := cfg.Upgrade.Latest() if err != nil { fmt.Printf("\nā %s\n\n%s", err, terminal.StopProgress()) os.Exit(1) return } + cfg.Upgrade.Version = fmt.Sprintf("v%s", latest) + if force { - executeUpgrade(latest) + executeUpgrade(cfg.Upgrade) return } - version := fmt.Sprintf("v%s", build.Version) - - if upgrade.IsMajorUpgrade(version, latest) { + if upgrade.IsMajorUpgrade(build.Version, latest) { message := terminal.StopProgress() - message += fmt.Sprintf("\nšØ major upgrade available: %s -> %s, use oh-my-posh upgrade --force to upgrade\n\n", version, latest) + message += fmt.Sprintf("\nšØ major upgrade available: v%s -> v%s, use oh-my-posh upgrade --force to upgrade\n\n", build.Version, latest) fmt.Print(message) return } - if version != latest { - executeUpgrade(latest) + if build.Version != latest { + executeUpgrade(cfg.Upgrade) return } @@ -69,8 +76,8 @@ var upgradeCmd = &cobra.Command{ }, } -func executeUpgrade(latest string) { - err := upgrade.Run(latest) +func executeUpgrade(cfg *upgrade.Config) { + err := upgrade.Run(cfg) fmt.Print(terminal.StopProgress()) if err == nil { return diff --git a/src/config/config.go b/src/config/config.go index e01baaaa0d64..24ae767ebb15 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -7,6 +7,7 @@ import ( "github.com/jandedobbeleer/oh-my-posh/src/shell" "github.com/jandedobbeleer/oh-my-posh/src/template" "github.com/jandedobbeleer/oh-my-posh/src/terminal" + "github.com/jandedobbeleer/oh-my-posh/src/upgrade" ) const ( @@ -30,26 +31,27 @@ type Config struct { SecondaryPrompt *Segment `json:"secondary_prompt,omitempty" toml:"secondary_prompt,omitempty"` TransientPrompt *Segment `json:"transient_prompt,omitempty" toml:"transient_prompt,omitempty"` ErrorLine *Segment `json:"error_line,omitempty" toml:"error_line,omitempty"` - ConsoleTitleTemplate string `json:"console_title_template,omitempty" toml:"console_title_template,omitempty"` - Format string `json:"-" toml:"-"` + TerminalBackground color.Ansi `json:"terminal_background,omitempty" toml:"terminal_background,omitempty"` origin string PWD string `json:"pwd,omitempty" toml:"pwd,omitempty"` AccentColor color.Ansi `json:"accent_color,omitempty" toml:"accent_color,omitempty"` Output string `json:"-" toml:"-"` - TerminalBackground color.Ansi `json:"terminal_background,omitempty" toml:"terminal_background,omitempty"` + ConsoleTitleTemplate string `json:"console_title_template,omitempty" toml:"console_title_template,omitempty"` + Format string `json:"-" toml:"-"` + Upgrade *upgrade.Config `json:"upgrade,omitempty" toml:"upgrade,omitempty"` Cycle color.Cycle `json:"cycle,omitempty" toml:"cycle,omitempty"` ITermFeatures terminal.ITermFeatures `json:"iterm_features,omitempty" toml:"iterm_features,omitempty"` Blocks []*Block `json:"blocks,omitempty" toml:"blocks,omitempty"` Tooltips []*Segment `json:"tooltips,omitempty" toml:"tooltips,omitempty"` Version int `json:"version" toml:"version"` - UpgradeNotice bool `json:"upgrade_notice,omitempty" toml:"upgrade_notice,omitempty"` - AutoUpgrade bool `json:"auto_upgrade,omitempty" toml:"auto_upgrade,omitempty"` + AutoUpgrade bool `json:"-" toml:"-"` ShellIntegration bool `json:"shell_integration,omitempty" toml:"shell_integration,omitempty"` MigrateGlyphs bool `json:"-" toml:"-"` PatchPwshBleed bool `json:"patch_pwsh_bleed,omitempty" toml:"patch_pwsh_bleed,omitempty"` EnableCursorPositioning bool `json:"enable_cursor_positioning,omitempty" toml:"enable_cursor_positioning,omitempty"` updated bool FinalSpace bool `json:"final_space,omitempty" toml:"final_space,omitempty"` + UpgradeNotice bool `json:"-" toml:"-"` } func (cfg *Config) MakeColors(env runtime.Environment) color.String { @@ -98,12 +100,12 @@ func (cfg *Config) Features(env runtime.Environment) shell.Features { feats = append(feats, shell.FTCSMarks) } - autoUpgrade := cfg.AutoUpgrade + autoUpgrade := cfg.Upgrade.Auto if _, OK := env.Cache().Get(AUTOUPGRADE); OK { autoUpgrade = true } - upgradeNotice := cfg.UpgradeNotice + upgradeNotice := cfg.Upgrade.DisplayNotice if _, OK := env.Cache().Get(UPGRADENOTICE); OK { upgradeNotice = true } diff --git a/src/config/load.go b/src/config/load.go index edbe066eb513..84dadb918713 100644 --- a/src/config/load.go +++ b/src/config/load.go @@ -15,6 +15,7 @@ import ( "github.com/jandedobbeleer/oh-my-posh/src/log" "github.com/jandedobbeleer/oh-my-posh/src/runtime/path" "github.com/jandedobbeleer/oh-my-posh/src/shell" + "github.com/jandedobbeleer/oh-my-posh/src/upgrade" json "github.com/goccy/go-json" yaml "github.com/goccy/go-yaml" @@ -32,6 +33,19 @@ func Load(configFile, sh string, migrate bool) *Config { cfg.BackupAndMigrate() } + if cfg.Upgrade == nil { + cfg.Upgrade = &upgrade.Config{ + Source: upgrade.CDN, + DisplayNotice: cfg.UpgradeNotice, + Auto: cfg.AutoUpgrade, + Interval: cache.ONEWEEK, + } + } + + if cfg.Upgrade.Interval.IsEmpty() { + cfg.Upgrade.Interval = cache.ONEWEEK + } + if !cfg.ShellIntegration { return cfg } diff --git a/src/segments/upgrade.go b/src/segments/upgrade.go index 53030bac640b..9207e243fd10 100644 --- a/src/segments/upgrade.go +++ b/src/segments/upgrade.go @@ -68,23 +68,29 @@ func (u *Upgrade) cachedLatest(current string) (*UpgradeCache, error) { } func (u *Upgrade) checkUpdate(current string) (*UpgradeCache, error) { - tag, err := upgrade.Latest(u.env) + duration := u.props.GetString(properties.CacheDuration, string(cache.ONEWEEK)) + source := u.props.GetString(Source, string(upgrade.CDN)) + + cfg := &upgrade.Config{ + Source: upgrade.Source(source), + Interval: cache.Duration(duration), + } + + latest, err := cfg.Latest() if err != nil { return nil, err } - latest := tag[1:] cacheData := &UpgradeCache{ Latest: latest, Current: current, } + cacheJSON, err := json.Marshal(cacheData) if err != nil { return nil, err } - // update cache - duration := u.props.GetString(properties.CacheDuration, string(cache.ONEWEEK)) u.env.Cache().Set(UPGRADECACHEKEY, string(cacheJSON), cache.Duration(duration)) return cacheData, nil diff --git a/src/segments/upgrade_test.go b/src/segments/upgrade_test.go index 5dcc2f1032fa..10c264e34190 100644 --- a/src/segments/upgrade_test.go +++ b/src/segments/upgrade_test.go @@ -1,7 +1,6 @@ package segments import ( - "errors" "fmt" "testing" @@ -16,8 +15,10 @@ import ( ) func TestUpgrade(t *testing.T) { + ugc := &upgrade.Config{} + latest, _ := ugc.Latest() + cases := []struct { - Error error Case string CurrentVersion string LatestVersion string @@ -33,33 +34,28 @@ func TestUpgrade(t *testing.T) { }, { Case: "On latest", - CurrentVersion: "1.0.1", - LatestVersion: "1.0.1", - }, - { - Case: "Error on update check", - Error: errors.New("error"), + CurrentVersion: latest, }, { Case: "On previous, from cache", HasCache: true, CurrentVersion: "1.0.2", - LatestVersion: "1.0.3", + LatestVersion: latest, CachedVersion: "1.0.2", ExpectedEnabled: true, }, { Case: "On latest, version changed", HasCache: true, - CurrentVersion: "1.0.2", - LatestVersion: "1.0.2", + CurrentVersion: latest, + LatestVersion: latest, CachedVersion: "1.0.1", }, { Case: "On previous, version changed", HasCache: true, CurrentVersion: "1.0.2", - LatestVersion: "1.0.3", + LatestVersion: latest, CachedVersion: "1.0.1", ExpectedEnabled: true, }, @@ -73,15 +69,13 @@ func TestUpgrade(t *testing.T) { if len(tc.CachedVersion) == 0 { tc.CachedVersion = tc.CurrentVersion } + cacheData := fmt.Sprintf(`{"latest":"%s", "current": "%s"}`, tc.LatestVersion, tc.CachedVersion) cache.On("Get", UPGRADECACHEKEY).Return(cacheData, tc.HasCache) cache.On("Set", testify_.Anything, testify_.Anything, testify_.Anything) build.Version = tc.CurrentVersion - json := fmt.Sprintf(`{"tag_name":"v%s"}`, tc.LatestVersion) - env.On("HTTPRequest", upgrade.RELEASEURL).Return([]byte(json), tc.Error) - ug := &Upgrade{} ug.Init(properties.Map{}, env) diff --git a/src/upgrade/cli.go b/src/upgrade/cli.go index a4d16d3d0043..9b3db13d6cfe 100644 --- a/src/upgrade/cli.go +++ b/src/upgrade/cli.go @@ -39,40 +39,40 @@ type stateMsg state type model struct { error error + config *Config message string - tag string spinner spinner.Model state state } -func initialModel(tag string) *model { +func initialModel(cfg *Config) *model { s := spinner.New() s.Spinner = spinner.Dot s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("170")) - return &model{spinner: s, tag: tag} + return &model{spinner: s, config: cfg} } func (m *model) Init() tea.Cmd { - defer func() { - go func() { - if err := install(m.tag); err != nil { - m.error = err - program.Send(resultMsg(fmt.Sprintf("ā upgrade failed: %v", err))) - return - } + go m.start() - message := "š Upgrade successful" + return m.spinner.Tick +} - current := fmt.Sprintf("v%s", build.Version) - if current != m.tag { - message += ", restart your shell to take full advantage of the new functionality" - } +func (m *model) start() { + if err := install(m.config); err != nil { + m.error = err + program.Send(resultMsg(fmt.Sprintf("ā upgrade failed: %v", err))) + return + } - program.Send(resultMsg(message)) - }() - }() + message := "š Upgrade successful" - return m.spinner.Tick + current := fmt.Sprintf("v%s", build.Version) + if current != m.config.Version { + message += ", restart your shell to take full advantage of the new functionality" + } + + program.Send(resultMsg(message)) } func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { @@ -113,7 +113,7 @@ func (m *model) View() string { message = "Validating current installation" case downloading: m.spinner.Spinner = spinner.Globe - message = "Downloading latest version" + message = fmt.Sprintf("Downloading latest version from %s", m.config.Source.String()) case verifying: m.spinner.Spinner = spinner.Moon message = "Verifying download" @@ -125,19 +125,19 @@ func (m *model) View() string { return title + textStyle.Render(fmt.Sprintf("%s %s", m.spinner.View(), message)) } -func Run(latest string) error { +func Run(cfg *Config) error { titleStyle := lipgloss.NewStyle().Margin(1, 0, 1, 0) title = "š¦ Upgrading Oh My Posh" - current := build.Version + current := fmt.Sprintf("v%s", build.Version) if len(current) == 0 { current = "dev" } - title = fmt.Sprintf("%s from %s to %s", title, current, latest) + title = fmt.Sprintf("%s from %s to %s", title, current, cfg.Version) title = titleStyle.Render(title) - program = tea.NewProgram(initialModel(latest)) + program = tea.NewProgram(initialModel(cfg)) resultModel, _ := program.Run() programModel, OK := resultModel.(*model) diff --git a/src/upgrade/config.go b/src/upgrade/config.go new file mode 100644 index 000000000000..bc4025ae3caf --- /dev/null +++ b/src/upgrade/config.go @@ -0,0 +1,100 @@ +package upgrade + +import ( + "context" + "fmt" + "io" + httplib "net/http" + "strings" + + "github.com/jandedobbeleer/oh-my-posh/src/cache" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/http" +) + +type Config struct { + Cache cache.Cache `json:"-" toml:"-"` + Source Source `json:"source" toml:"source"` + Interval cache.Duration `json:"interval" toml:"interval"` + Version string `json:"-" toml:"-"` + Auto bool `json:"auto" toml:"auto"` + DisplayNotice bool `json:"notice" toml:"notice"` + Force bool `json:"-" toml:"-"` +} + +type Source string + +const ( + GitHub Source = "github" + CDN Source = "cdn" +) + +func (s Source) String() string { + switch s { + case GitHub: + return "github.com" + case CDN: + return "cdn.ohmyposh.dev" + default: + return "Unknown" + } +} + +func (cfg *Config) Latest() (string, error) { + cfg.Version = "latest" + v, err := cfg.DownloadAsset("version.txt") + version := strings.TrimSpace(string(v)) + return strings.TrimPrefix(version, "v"), err +} + +func (cfg *Config) DownloadAsset(asset string) ([]byte, error) { + if len(cfg.Source) == 0 { + cfg.Source = GitHub + } + + switch cfg.Source { + case GitHub: + var url string + + switch cfg.Version { + case "latest": + url = fmt.Sprintf("https://github.com/JanDeDobbeleer/oh-my-posh/releases/latest/download/%s", asset) + default: + url = fmt.Sprintf("https://github.com/JanDeDobbeleer/oh-my-posh/releases/download/%s/%s", cfg.Version, asset) + } + + return cfg.Download(url) + case CDN: + fallthrough + default: + url := fmt.Sprintf("https://cdn.ohmyposh.dev/releases/%s/%s", cfg.Version, asset) + return cfg.Download(url) + } +} + +func (cfg *Config) Download(url string) ([]byte, error) { + req, err := httplib.NewRequestWithContext(context.Background(), "GET", url, nil) + if err != nil { + return nil, err + } + + req.Header.Add("User-Agent", "oh-my-posh") + req.Header.Add("Cache-Control", "max-age=0") + + resp, err := http.HTTPClient.Do(req) + if err != nil { + return nil, err + } + + if resp.StatusCode != httplib.StatusOK { + return nil, fmt.Errorf("failed to download asset: %s", url) + } + + defer resp.Body.Close() + + data, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + return data, nil +} diff --git a/src/upgrade/github.go b/src/upgrade/github.go deleted file mode 100644 index 6efc865e3587..000000000000 --- a/src/upgrade/github.go +++ /dev/null @@ -1,39 +0,0 @@ -package upgrade - -import ( - "context" - "fmt" - "io" - httplib "net/http" - - "github.com/jandedobbeleer/oh-my-posh/src/runtime/http" -) - -func downloadReleaseAsset(tag, asset string) ([]byte, error) { - url := fmt.Sprintf("https://github.com/JanDeDobbeleer/oh-my-posh/releases/download/%s/%s", tag, asset) - - req, err := httplib.NewRequestWithContext(context.Background(), "GET", url, nil) - if err != nil { - return nil, err - } - - req.Header.Add("User-Agent", "oh-my-posh") - - resp, err := http.HTTPClient.Do(req) - if err != nil { - return nil, err - } - - if resp.StatusCode != httplib.StatusOK { - return nil, fmt.Errorf("failed to download asset: %s", url) - } - - defer resp.Body.Close() - - data, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - - return data, nil -} diff --git a/src/upgrade/install.go b/src/upgrade/install.go index d4f0c24ca99e..88d6bcdf9d2d 100644 --- a/src/upgrade/install.go +++ b/src/upgrade/install.go @@ -9,7 +9,7 @@ import ( "path/filepath" ) -func install(tag string) error { +func install(cfg *Config) error { setState(validating) executable, err := os.Executable() @@ -28,7 +28,7 @@ func install(tag string) error { setState(downloading) - data, err := downloadAndVerify(tag) + data, err := downloadAndVerify(cfg) if err != nil { return err } @@ -71,7 +71,5 @@ func install(tag string) error { _ = hideFile(oldPath) } - updateRegistry(tag, executable) - return nil } diff --git a/src/upgrade/install_noop.go b/src/upgrade/install_noop.go index 785e25c9f5d8..ec035bed498e 100644 --- a/src/upgrade/install_noop.go +++ b/src/upgrade/install_noop.go @@ -5,5 +5,3 @@ package upgrade func hideFile(_ string) error { return nil } - -func updateRegistry(_, _ string) {} diff --git a/src/upgrade/install_windows.go b/src/upgrade/install_windows.go index 958665bf195f..c10f3f897cc3 100644 --- a/src/upgrade/install_windows.go +++ b/src/upgrade/install_windows.go @@ -1,14 +1,8 @@ package upgrade import ( - "errors" - "path/filepath" - "strconv" - "strings" "syscall" "unsafe" - - "golang.org/x/sys/windows/registry" ) func hideFile(path string) error { @@ -28,89 +22,3 @@ func hideFile(path string) error { return nil } - -func updateRegistry(version, executable string) { - // needs to be the parent directory of the executable's bin directory - // with a trailing backslash to match the registry key - // in case this wasn't installed with the installer, nothing will match - // and we don't set the registry keys - installLocation := filepath.Dir(executable) - installLocation = filepath.Dir(installLocation) - installLocation += `\` - - key, err := getRegistryKey(installLocation) - if err != nil { - key.Close() - return - } - - version = strings.TrimLeft(version, "v") - - _ = key.SetStringValue("DisplayVersion", version) - _ = key.SetStringValue("DisplayName", "Oh My Posh") - - splitted := strings.Split(version, ".") - if len(splitted) < 3 { - key.Close() - return - } - - if u64, err := strconv.ParseUint(splitted[0], 10, 32); err == nil { - major := uint32(u64) - _ = key.SetDWordValue("MajorVersion", major) - _ = key.SetDWordValue("VersionMajor", major) - } - - if u64, err := strconv.ParseUint(splitted[1], 10, 32); err == nil { - minor := uint32(u64) - _ = key.SetDWordValue("MinorVersion", minor) - _ = key.SetDWordValue("VersionMinor", minor) - } - - key.Close() -} - -// getRegistryKey tries all known registry paths to find the one we need to adjust (if any) -func getRegistryKey(installLocation string) (registry.Key, error) { - knownRegistryPaths := []struct { - Path string - Key registry.Key - }{ - {`Software\Microsoft\Windows\CurrentVersion\Uninstall`, registry.CURRENT_USER}, - {`Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall`, registry.CURRENT_USER}, - {`Software\Microsoft\Windows\CurrentVersion\Uninstall`, registry.LOCAL_MACHINE}, - {`Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall`, registry.LOCAL_MACHINE}, - } - - for _, path := range knownRegistryPaths { - key, ok := tryRegistryKey(path.Key, path.Path, installLocation) - if ok { - return key, nil - } - } - - return registry.CURRENT_USER, errors.New("could not find registry key") -} - -// tryRegistryKey tries to open the registry key for the given path -// and checks if the install location matches with the current executable's location -func tryRegistryKey(key registry.Key, path, installLocation string) (registry.Key, bool) { - path += `\Oh My Posh_is1` - - readKey, err := registry.OpenKey(key, path, registry.READ) - if err != nil { - return key, false - } - - location, _, err := readKey.GetStringValue("InstallLocation") - if err != nil { - return key, false - } - - if location != installLocation { - return key, false - } - - key, err = registry.OpenKey(key, path, registry.WRITE) - return key, err == nil -} diff --git a/src/upgrade/notice.go b/src/upgrade/notice.go index cdf13ab5b391..32880290b996 100644 --- a/src/upgrade/notice.go +++ b/src/upgrade/notice.go @@ -1,100 +1,60 @@ package upgrade import ( - "encoding/json" "fmt" - "time" + "os" "github.com/jandedobbeleer/oh-my-posh/src/build" - "github.com/jandedobbeleer/oh-my-posh/src/cache" - "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/jandedobbeleer/oh-my-posh/src/log" "github.com/jandedobbeleer/oh-my-posh/src/runtime/http" ) -type release struct { - CreatedAt time.Time `json:"created_at"` - PublishedAt time.Time `json:"published_at"` - NodeID string `json:"node_id"` - TagName string `json:"tag_name"` - TarballURL string `json:"tarball_url"` - ZipballURL string `json:"zipball_url"` - DiscussionURL string `json:"discussion_url"` - HTMLURL string `json:"html_url"` - URL string `json:"url"` - UploadURL string `json:"upload_url"` - TargetCommitish string `json:"target_commitish"` - Name string `json:"name"` - Body string `json:"body"` - AssetsURL string `json:"assets_url"` - ID int `json:"id"` - Prerelease bool `json:"prerelease"` - Draft bool `json:"draft"` -} - const ( - RELEASEURL = "https://api.github.com/repos/jandedobbeleer/oh-my-posh/releases/latest" - CACHEKEY = "upgrade_check" + CACHEKEY = "upgrade_check" upgradeNotice = ` -A new release of Oh My Posh is available: %s ā %s +A new release of Oh My Posh is available: v%s ā v%s To upgrade, run: 'oh-my-posh upgrade%s' To enable automated upgrades, run: 'oh-my-posh enable upgrade'. ` ) -func Latest(env runtime.Environment) (string, error) { - body, err := env.HTTPRequest(RELEASEURL, nil, 5000) - if err != nil { - return "", err - } - - var release release - // this can't fail - _ = json.Unmarshal(body, &release) - - if len(release.TagName) == 0 { - return "", fmt.Errorf("failed to get latest release") - } - - return release.TagName, nil -} - // Returns the upgrade notice if a new version is available // that should be displayed to the user. // // The upgrade check is only performed every other week. -func Notice(env runtime.Environment, force bool) (string, bool) { - // do not check when last validation was < 1 week ago - if _, OK := env.Cache().Get(CACHEKEY); OK && !force { +func (cfg *Config) Notice() (string, bool) { + // never validate when we install using the Windows Store + if os.Getenv("POSH_INSTALLER") == "ws" { + log.Debug("skipping upgrade check because we are using the Windows Store") return "", false } - if !http.IsConnected() { + // do not check when last validation was < 1 week ago + if _, OK := cfg.Cache.Get(CACHEKEY); OK && !cfg.Force { return "", false } - // never validate when we install using the Windows Store - if env.Getenv("POSH_INSTALLER") == "ws" { + if !http.IsConnected() { return "", false } - latest, err := Latest(env) + latest, err := cfg.Latest() if err != nil { return "", false } - env.Cache().Set(CACHEKEY, latest, cache.ONEWEEK) + cfg.Cache.Set(CACHEKEY, latest, cfg.Interval) - version := fmt.Sprintf("v%s", build.Version) - if latest == version { + if latest == build.Version { return "", false } var forceUpdate string - if IsMajorUpgrade(version, latest) { + if IsMajorUpgrade(build.Version, latest) { forceUpdate = " --force" } - return fmt.Sprintf(upgradeNotice, version, latest, forceUpdate), true + return fmt.Sprintf(upgradeNotice, build.Version, latest, forceUpdate), true } diff --git a/src/upgrade/notice_test.go b/src/upgrade/notice_test.go index ec95d7362178..6fa5099ddf0e 100644 --- a/src/upgrade/notice_test.go +++ b/src/upgrade/notice_test.go @@ -1,52 +1,47 @@ package upgrade import ( - "fmt" + "os" "testing" "github.com/jandedobbeleer/oh-my-posh/src/build" - cache "github.com/jandedobbeleer/oh-my-posh/src/cache/mock" - "github.com/jandedobbeleer/oh-my-posh/src/runtime" - "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" + cache_ "github.com/jandedobbeleer/oh-my-posh/src/cache/mock" "github.com/stretchr/testify/assert" - - testify "github.com/stretchr/testify/mock" + testify_ "github.com/stretchr/testify/mock" ) func TestCanUpgrade(t *testing.T) { + ugc := &Config{} + latest, _ := ugc.Latest() + cases := []struct { - Error error Case string CurrentVersion string - LatestVersion string - GOOS string Installer string Expected bool Cache bool }{ - {Case: "Up to date", CurrentVersion: "3.0.0", LatestVersion: "v3.0.0"}, - {Case: "Outdated Windows", Expected: true, CurrentVersion: "3.0.0", LatestVersion: "v3.0.1", GOOS: runtime.WINDOWS}, - {Case: "Outdated Linux", Expected: true, CurrentVersion: "3.0.0", LatestVersion: "v3.0.1", GOOS: runtime.LINUX}, - {Case: "Outdated Darwin", Expected: true, CurrentVersion: "3.0.0", LatestVersion: "v3.0.1", GOOS: runtime.DARWIN}, + {Case: "Up to date", CurrentVersion: latest}, + {Case: "Outdated Linux", Expected: true, CurrentVersion: "3.0.0"}, + {Case: "Outdated Darwin", Expected: true, CurrentVersion: "3.0.0"}, {Case: "Cached", Cache: true}, - {Case: "Error", Error: fmt.Errorf("error")}, {Case: "Windows Store", Installer: "ws"}, } for _, tc := range cases { - env := new(mock.Environment) build.Version = tc.CurrentVersion - c := &cache.Cache{} + c := &cache_.Cache{} c.On("Get", CACHEKEY).Return("", tc.Cache) - c.On("Set", testify.Anything, testify.Anything, testify.Anything) - env.On("Cache").Return(c) - env.On("GOOS").Return(tc.GOOS) - env.On("Getenv", "POSH_INSTALLER").Return(tc.Installer) - - json := fmt.Sprintf(`{"tag_name":"%s"}`, tc.LatestVersion) - env.On("HTTPRequest", RELEASEURL).Return([]byte(json), tc.Error) - // ignore the notice - _, canUpgrade := Notice(env, false) + c.On("Set", testify_.Anything, testify_.Anything, testify_.Anything) + ugc.Cache = c + + if len(tc.Installer) > 0 { + os.Setenv("POSH_INSTALLER", tc.Installer) + } + + _, canUpgrade := ugc.Notice() assert.Equal(t, tc.Expected, canUpgrade, tc.Case) + + os.Setenv("POSH_INSTALLER", "") } } diff --git a/src/upgrade/verify.go b/src/upgrade/verify.go index 2af7955be5c3..1292fd959565 100644 --- a/src/upgrade/verify.go +++ b/src/upgrade/verify.go @@ -32,7 +32,7 @@ import ( //go:embed public_key.pem var publicKey []byte -func downloadAndVerify(tag string) ([]byte, error) { +func downloadAndVerify(cfg *Config) ([]byte, error) { extension := "" if stdruntime.GOOS == runtime.WINDOWS { extension = ".exe" @@ -40,14 +40,14 @@ func downloadAndVerify(tag string) ([]byte, error) { asset := fmt.Sprintf("posh-%s-%s%s", stdruntime.GOOS, stdruntime.GOARCH, extension) - data, err := downloadReleaseAsset(tag, asset) + data, err := cfg.DownloadAsset(asset) if err != nil { return nil, err } setState(verifying) - err = verify(tag, asset, data) + err = verify(cfg, asset, data) if err != nil { return nil, err } @@ -55,13 +55,13 @@ func downloadAndVerify(tag string) ([]byte, error) { return data, nil } -func verify(tag, asset string, binary []byte) error { - checksums, err := downloadReleaseAsset(tag, "checksums.txt") +func verify(cfg *Config, asset string, binary []byte) error { + checksums, err := cfg.DownloadAsset("checksums.txt") if err != nil { return err } - signature, err := downloadReleaseAsset(tag, "checksums.txt.sig") + signature, err := cfg.DownloadAsset("checksums.txt.sig") if err != nil { return err } diff --git a/themes/schema.json b/themes/schema.json index a4e7967b059f..1318b394b056 100644 --- a/themes/schema.json +++ b/themes/schema.json @@ -1281,8 +1281,22 @@ "description": "The extensions to look for when determining if a folder is a Fortran workspace", "default": [ "fpm.toml", - "*.f", "*.for", "*.fpp", "*.f77", "*.f90", "*.f95", "*.f03", "*.f08", - "*.F", "*.FOR", "*.FPP", "*.F77", "*.F90", "*.F95", "*.F03", "*.F08" + "*.f", + "*.for", + "*.fpp", + "*.f77", + "*.f90", + "*.f95", + "*.f03", + "*.f08", + "*.F", + "*.FOR", + "*.FPP", + "*.F77", + "*.F90", + "*.F95", + "*.F03", + "*.F08" ], "items": { "type": "string" @@ -5036,17 +5050,36 @@ "description": "https://ohmyposh.dev/docs/configuration/general#general-settings", "default": "" }, - "upgrade_notice": { - "type": "boolean", + "upgrade": { + "type": "object", "title": "Enable Upgrade Notice", "description": "https://ohmyposh.dev/docs/configuration/general#general-settings", - "default": false - }, - "auto_upgrade": { - "type": "boolean", - "title": "Enable automatic upgrades for Oh My Posh (supports Windows/macOS only)", - "description": "https://ohmyposh.dev/docs/configuration/general#general-settings", - "default": false + "default": { + "source": "cdn", + "auto": false, + "notice": false + }, + "properties": { + "interval": { + "$ref": "#/definitions/cache_duration" + }, + "source": { + "type": "string", + "enum": [ + "cdn", + "github" + ], + "default": "cdn" + }, + "auto": { + "type": "boolean", + "default": false + }, + "notice": { + "type": "boolean", + "default": false + } + } }, "patch_pwsh_bleed": { "type": "boolean", diff --git a/website/docs/configuration/general.mdx b/website/docs/configuration/general.mdx index 57224e31920b..b449213fce5d 100644 --- a/website/docs/configuration/general.mdx +++ b/website/docs/configuration/general.mdx @@ -136,8 +136,7 @@ For example, the following is a valid `--config` flag: | `shell_integration` | `boolean` | `false` | enable shell integration using FinalTerm's OSC sequences. Works in bash, cmd (Clink v1.14.25+), fish, powershell and zsh | | `enable_cursor_positioning` | `boolean` | `false` | enable fetching the cursor position in bash and zsh to allow automatic hiding of leading newlines when at the top of the shell | | `patch_pwsh_bleed` | `boolean` | `false` | patch a PowerShell bug where the background colors bleed into the next line at the end of the buffer (can be removed when [this][pwsh-bleed] is merged) | -| `upgrade_notice` | `boolean` | `false` | enable the notice that a new upgrade is available when `auto_upgrade` is disabled | -| `auto_upgrade` | `boolean` | `false` | enable [automatic upgrades][upgrade] for Oh My Posh (supports Windows, macOS and Linux) | +| `upgrade` | `Upgrade` | | enable auto upgrade or the upgrade notice. See [Upgrade] | | `iterm_features` | `[]string` | `false` | enable iTerm2 specific features: