mirror of
https://github.com/JanDeDobbeleer/oh-my-posh.git
synced 2024-11-09 20:44:03 -08:00
feat(cli): add upgrade command
This commit is contained in:
parent
bbe9f9bc02
commit
a0d2289aec
10
.vscode/launch.json
vendored
10
.vscode/launch.json
vendored
|
@ -145,6 +145,16 @@
|
||||||
"git"
|
"git"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Upgrade",
|
||||||
|
"type": "go",
|
||||||
|
"request": "launch",
|
||||||
|
"mode": "debug",
|
||||||
|
"program": "${workspaceRoot}/src",
|
||||||
|
"args": [
|
||||||
|
"upgrade"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "node",
|
"type": "node",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
|
|
|
@ -21,7 +21,7 @@ var noticeCmd = &cobra.Command{
|
||||||
env.Init()
|
env.Init()
|
||||||
defer env.Close()
|
defer env.Close()
|
||||||
|
|
||||||
if notice, hasNotice := upgrade.Notice(env); hasNotice {
|
if notice, hasNotice := upgrade.Notice(env, false); hasNotice {
|
||||||
fmt.Println(notice)
|
fmt.Println(notice)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
43
src/cli/upgrade.go
Normal file
43
src/cli/upgrade.go
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/jandedobbeleer/oh-my-posh/src/platform"
|
||||||
|
"github.com/jandedobbeleer/oh-my-posh/src/upgrade"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var force bool
|
||||||
|
|
||||||
|
// noticeCmd represents the get command
|
||||||
|
var upgradeCmd = &cobra.Command{
|
||||||
|
Use: "upgrade",
|
||||||
|
Short: "Upgrade when a new version is available.",
|
||||||
|
Long: "Upgrade when a new version is available.",
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
Run: func(_ *cobra.Command, _ []string) {
|
||||||
|
if force {
|
||||||
|
upgrade.Run()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
env := &platform.Shell{
|
||||||
|
CmdFlags: &platform.Flags{},
|
||||||
|
}
|
||||||
|
env.Init()
|
||||||
|
defer env.Close()
|
||||||
|
|
||||||
|
if _, hasNotice := upgrade.Notice(env, true); !hasNotice {
|
||||||
|
fmt.Print("\n ✅ no new version available\n\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
upgrade.Run()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
upgradeCmd.Flags().BoolVarP(&force, "force", "f", false, "force the upgrade even if the version is up to date")
|
||||||
|
RootCmd.AddCommand(upgradeCmd)
|
||||||
|
}
|
|
@ -160,6 +160,7 @@ func (e *Engine) ExtraPrompt(promptType ExtraPromptType) string {
|
||||||
Template: getTemplate(prompt.Template),
|
Template: getTemplate(prompt.Template),
|
||||||
Env: e.Env,
|
Env: e.Env,
|
||||||
}
|
}
|
||||||
|
|
||||||
promptText, err := tmpl.Render()
|
promptText, err := tmpl.Render()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
promptText = err.Error()
|
promptText = err.Error()
|
||||||
|
|
|
@ -268,7 +268,7 @@ func PrintInit(env platform.Environment) string {
|
||||||
// only run this for shells that support
|
// only run this for shells that support
|
||||||
// injecting the notice directly
|
// injecting the notice directly
|
||||||
if shell != PWSH && shell != PWSH5 {
|
if shell != PWSH && shell != PWSH5 {
|
||||||
notice, hasNotice = upgrade.Notice(env)
|
notice, hasNotice = upgrade.Notice(env, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
return strings.NewReplacer(
|
return strings.NewReplacer(
|
||||||
|
|
133
src/upgrade/cli.go
Normal file
133
src/upgrade/cli.go
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
package upgrade
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/charmbracelet/bubbles/spinner"
|
||||||
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
|
"github.com/charmbracelet/lipgloss"
|
||||||
|
"github.com/jandedobbeleer/oh-my-posh/src/platform"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
program *tea.Program
|
||||||
|
textStyle = lipgloss.NewStyle().Margin(1, 0, 2, 4)
|
||||||
|
)
|
||||||
|
|
||||||
|
type resultMsg string
|
||||||
|
|
||||||
|
type state int
|
||||||
|
|
||||||
|
const (
|
||||||
|
validating state = iota
|
||||||
|
downloading
|
||||||
|
installing
|
||||||
|
)
|
||||||
|
|
||||||
|
type stateMsg state
|
||||||
|
|
||||||
|
type model struct {
|
||||||
|
spinner spinner.Model
|
||||||
|
message string
|
||||||
|
state state
|
||||||
|
}
|
||||||
|
|
||||||
|
func initialModel() *model {
|
||||||
|
s := spinner.New()
|
||||||
|
s.Spinner = spinner.Dot
|
||||||
|
s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("170"))
|
||||||
|
return &model{spinner: s}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *model) Init() tea.Cmd {
|
||||||
|
defer func() {
|
||||||
|
go func() {
|
||||||
|
if err := install(); err != nil {
|
||||||
|
message := fmt.Sprintf("⚠️ %s", err)
|
||||||
|
program.Send(resultMsg(message))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
program.Send(resultMsg(successMsg))
|
||||||
|
}()
|
||||||
|
}()
|
||||||
|
|
||||||
|
return m.spinner.Tick
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
|
switch msg := msg.(type) {
|
||||||
|
case tea.KeyMsg:
|
||||||
|
switch msg.String() {
|
||||||
|
case "q", "esc", "ctrl+c":
|
||||||
|
return m, tea.Quit
|
||||||
|
default:
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
case resultMsg:
|
||||||
|
m.message = string(msg)
|
||||||
|
return m, tea.Quit
|
||||||
|
|
||||||
|
case stateMsg:
|
||||||
|
m.state = state(msg)
|
||||||
|
return m, nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
var cmd tea.Cmd
|
||||||
|
m.spinner, cmd = m.spinner.Update(msg)
|
||||||
|
return m, cmd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *model) View() string {
|
||||||
|
if len(m.message) > 0 {
|
||||||
|
return textStyle.Render(m.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
var message string
|
||||||
|
m.spinner.Spinner = spinner.Dot
|
||||||
|
switch m.state {
|
||||||
|
case validating:
|
||||||
|
message = "Validating current installation"
|
||||||
|
case downloading:
|
||||||
|
m.spinner.Spinner = spinner.Globe
|
||||||
|
message = "Downloading latest version"
|
||||||
|
case installing:
|
||||||
|
message = "Installing"
|
||||||
|
}
|
||||||
|
|
||||||
|
return textStyle.Render(fmt.Sprintf("%s %s", m.spinner.View(), message))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Run() {
|
||||||
|
program = tea.NewProgram(initialModel())
|
||||||
|
if _, err := program.Run(); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadAsset(asset string) (io.ReadCloser, error) {
|
||||||
|
url := fmt.Sprintf("https://github.com/JanDeDobbeleer/oh-my-posh/releases/latest/download/%s", asset)
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(context.Background(), "GET", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := platform.Client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("Failed to download installer: %s", url)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.Body, nil
|
||||||
|
}
|
48
src/upgrade/cli_unix.go
Normal file
48
src/upgrade/cli_unix.go
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package upgrade
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
var successMsg = "Upgrade successful, restart your shell to take full advantage of the new functionality."
|
||||||
|
|
||||||
|
func install() error {
|
||||||
|
program.Send(stateMsg(validating))
|
||||||
|
executable, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.OpenFile(executable, os.O_WRONLY, 0755)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("we don't have permissions to upgrade oh-my-posh, please use elevated permissions to upgrade")
|
||||||
|
}
|
||||||
|
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
program.Send(stateMsg(downloading))
|
||||||
|
|
||||||
|
asset := fmt.Sprintf("posh-%s-%s", runtime.GOOS, runtime.GOARCH)
|
||||||
|
|
||||||
|
data, err := downloadAsset(asset)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer data.Close()
|
||||||
|
|
||||||
|
program.Send(stateMsg(installing))
|
||||||
|
|
||||||
|
_, err = io.Copy(file, data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
59
src/upgrade/cli_windows.go
Normal file
59
src/upgrade/cli_windows.go
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
package upgrade
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
var successMsg = "Oh My Posh is installing in the background.\nRestart your shell in a minute to take full advantage of the new functionality."
|
||||||
|
|
||||||
|
func install() error {
|
||||||
|
program.Send(stateMsg(downloading))
|
||||||
|
|
||||||
|
temp := os.Getenv("TEMP")
|
||||||
|
if len(temp) == 0 {
|
||||||
|
return errors.New("failed to get TEMP environment variable")
|
||||||
|
}
|
||||||
|
|
||||||
|
path := filepath.Join(temp, "install.exe")
|
||||||
|
|
||||||
|
if _, err := os.Stat(path); err == nil {
|
||||||
|
err := os.Remove(path)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("unable to remove existing installer")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
asset := fmt.Sprintf("install-%s.exe", runtime.GOARCH)
|
||||||
|
|
||||||
|
data, err := downloadAsset(asset)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.Copy(file, data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
data.Close()
|
||||||
|
file.Close()
|
||||||
|
|
||||||
|
// We need to run the installer in a separate process to avoid being blocked by the current process
|
||||||
|
go func() {
|
||||||
|
cmd := exec.Command(path, "/VERYSILENT", "/FORCECLOSEAPPLICATIONS")
|
||||||
|
_ = cmd.Run()
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -58,9 +58,9 @@ func Latest(env platform.Environment) (string, error) {
|
||||||
// that should be displayed to the user.
|
// that should be displayed to the user.
|
||||||
//
|
//
|
||||||
// The upgrade check is only performed every other week.
|
// The upgrade check is only performed every other week.
|
||||||
func Notice(env platform.Environment) (string, bool) {
|
func Notice(env platform.Environment, force bool) (string, bool) {
|
||||||
// do not check when last validation was < 1 week ago
|
// do not check when last validation was < 1 week ago
|
||||||
if _, OK := env.Cache().Get(CACHEKEY); OK {
|
if _, OK := env.Cache().Get(CACHEKEY); OK && !force {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ func TestCanUpgrade(t *testing.T) {
|
||||||
json := fmt.Sprintf(`{"tag_name":"%s"}`, tc.LatestVersion)
|
json := fmt.Sprintf(`{"tag_name":"%s"}`, tc.LatestVersion)
|
||||||
env.On("HTTPRequest", RELEASEURL).Return([]byte(json), tc.Error)
|
env.On("HTTPRequest", RELEASEURL).Return([]byte(json), tc.Error)
|
||||||
// ignore the notice
|
// ignore the notice
|
||||||
_, canUpgrade := Notice(env)
|
_, canUpgrade := Notice(env, false)
|
||||||
assert.Equal(t, tc.Expected, canUpgrade, tc.Case)
|
assert.Equal(t, tc.Expected, canUpgrade, tc.Case)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue