mirror of
https://github.com/JanDeDobbeleer/oh-my-posh.git
synced 2024-12-25 11:04:04 -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"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Upgrade",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "debug",
|
||||
"program": "${workspaceRoot}/src",
|
||||
"args": [
|
||||
"upgrade"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
|
|
|
@ -21,7 +21,7 @@ var noticeCmd = &cobra.Command{
|
|||
env.Init()
|
||||
defer env.Close()
|
||||
|
||||
if notice, hasNotice := upgrade.Notice(env); hasNotice {
|
||||
if notice, hasNotice := upgrade.Notice(env, false); hasNotice {
|
||||
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),
|
||||
Env: e.Env,
|
||||
}
|
||||
|
||||
promptText, err := tmpl.Render()
|
||||
if err != nil {
|
||||
promptText = err.Error()
|
||||
|
|
|
@ -268,7 +268,7 @@ func PrintInit(env platform.Environment) string {
|
|||
// only run this for shells that support
|
||||
// injecting the notice directly
|
||||
if shell != PWSH && shell != PWSH5 {
|
||||
notice, hasNotice = upgrade.Notice(env)
|
||||
notice, hasNotice = upgrade.Notice(env, false)
|
||||
}
|
||||
|
||||
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.
|
||||
//
|
||||
// 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
|
||||
if _, OK := env.Cache().Get(CACHEKEY); OK {
|
||||
if _, OK := env.Cache().Get(CACHEKEY); OK && !force {
|
||||
return "", false
|
||||
}
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ func TestCanUpgrade(t *testing.T) {
|
|||
json := fmt.Sprintf(`{"tag_name":"%s"}`, tc.LatestVersion)
|
||||
env.On("HTTPRequest", RELEASEURL).Return([]byte(json), tc.Error)
|
||||
// ignore the notice
|
||||
_, canUpgrade := Notice(env)
|
||||
_, canUpgrade := Notice(env, false)
|
||||
assert.Equal(t, tc.Expected, canUpgrade, tc.Case)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue