Skip to content

Commit

Permalink
feat: self update
Browse files Browse the repository at this point in the history
  • Loading branch information
mkloubert committed Jan 2, 2025
1 parent 32436cf commit a7eeca7
Show file tree
Hide file tree
Showing 7 changed files with 300 additions and 4 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Change Log (go-package-manager)

## 0.29.0

- feat: self-update by executing `gpm update --self`

## 0.28.0

- **BREAKING CHANGE**: `bump version` command is now reduced to simple `bump`
Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
- [MacOS / Linux / UNIX](#macos--linux--unix-)
- [Windows](#windows-)
- [Build from source](#build-from-source-)
- [Updates](#updates-)
- [Usage](#usage-)
- [Commands](#commands-)
- [Add alias](#add-alias-)
Expand Down Expand Up @@ -110,6 +111,16 @@ cd gpm
go build . && ./gpm --version
```

## Updates [<a href="#installation-">↑</a>]

A self-update works with

```bash
gpm update --self
```

if you have a valid [sh](https://en.wikipedia.org/wiki/Unix_shell) or [PowerShell](https://en.wikipedia.org/wiki/PowerShell) installed.

## Usage [<a href="#table-of-contents">↑</a>]

### Commands [<a href="#usage-">↑</a>]
Expand Down Expand Up @@ -692,6 +703,7 @@ Environment variables can be loaded from external files, which are handled in th
| `GPM_TERMINAL_FORMATTER` | Default formatter for syntax highlighting in terminal. See [chroma project](https://github.com/alecthomas/chroma/tree/master/formatters) for more information. | `terminal16m` |
| `GPM_TERMINAL_STYLE` | Default style for syntax highlighting in terminal. See [chroma project](https://github.com/alecthomas/chroma/tree/master/styles) for more information. | `monokai` |
| `GPM_UP_COMMAND` | Custom command for [docker compose up](#docker-shorthands-) shorthand. | `docker-compose up` |
| `GPM_UPDATE_SCRIPT` | Custom URL to self-update script | `sh.kloubert.dev/gpm.sh` |
| `OPENAI_API_KEY` | Key which is used for the [API by OpenAI](https://platform.openai.com/docs/api-reference). | `sk-...` |

## Contribution [<a href="#table-of-contents">↑</a>]
Expand Down
2 changes: 2 additions & 0 deletions aliases.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ aliases:
- https://github.com/fatih/color
dotenv:
- https://github.com/joho/godotenv
fake-useragent:
- https://github.com/eddycjy/fake-useragent
glamour:
- github.com/charmbracelet/glamour
mongo:
Expand Down
234 changes: 231 additions & 3 deletions commands/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,28 +23,256 @@
package commands

import (
"bufio"
"bytes"
"fmt"
"io"
"net/http"
"os"
"os/exec"
"runtime"
"strings"

browser "github.com/EDDYCJY/fake-useragent"
"github.com/alecthomas/chroma/quick"
"github.com/mkloubert/go-package-manager/types"
"github.com/mkloubert/go-package-manager/utils"
"github.com/spf13/cobra"
)

func Init_Update_Command(parentCmd *cobra.Command, app *types.AppContext) {
var force bool
var noCleanup bool
var noVersionPrint bool
var powerShell bool
var powerShellBin string
var selfUpdate bool
var updateScript string
var userAgent string

var updateCmd = &cobra.Command{
Use: "update",
Aliases: []string{"upd"},
Short: "Update dependencies",
Long: `Updates all dependencies in this project.`,
Run: func(cmd *cobra.Command, args []string) {
app.RunShellCommandByArgs("go", "get", "-u", "./...")
if selfUpdate {
app.Debug("Will start self-update ...")

consoleFormatter := utils.GetBestChromaFormatterName()
consoleStyle := utils.GetBestChromaStyleName()

customUserAgent := strings.TrimSpace(userAgent)
if customUserAgent == "" {
customUserAgent = browser.Chrome()
}

customPowerShellBin := strings.TrimSpace(powerShellBin)
if customPowerShellBin == "" {
customPowerShellBin = "powershell"
}

customUpdateScript := strings.TrimSpace(updateScript)
if customUpdateScript == "" {
customUpdateScript = strings.TrimSpace(os.Getenv("GPM_UPDATE_SCRIPT"))
}

downloadScript := func(url string) ([]byte, error) {
app.Debug(fmt.Sprintf("Download from '%s' ...", url))
app.Debug(fmt.Sprintf("User agent: %s", customUserAgent))

req, err := http.NewRequest("GET", url, bytes.NewBuffer([]byte{}))
if err != nil {
return []byte{}, err
}

req.Header.Set("User-Agent", customUserAgent)

client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return []byte{}, err
}
defer resp.Body.Close()

if resp.StatusCode != 200 {
return []byte{}, fmt.Errorf("unexpected response: %v", resp.StatusCode)
}

responseData, err := io.ReadAll(resp.Body)

return responseData, err
}

showNewVersion := func() {
if noVersionPrint {
return
}

app.RunShellCommandByArgs("gpm", "--version")
}

if powerShell || utils.IsWindows() {
// PowerShell
app.Debug(fmt.Sprintf("Will use PowerShell '%s' ...", powerShellBin))

scriptUrl := customUpdateScript
if scriptUrl == "" {
scriptUrl = "https://raw.githubusercontent.com/mkloubert/go-package-manager/main/sh.kloubert.dev/gpm.ps1"
} else {
su, err := utils.ToUrlForOpenHandler(scriptUrl)
utils.CheckForError(err)

scriptUrl = su
}

pwshScript, err := downloadScript(scriptUrl)
utils.CheckForError(err)

executeScript := func() {
p := exec.Command(customPowerShellBin, "-NoProfile", "-Command", "-")
p.Dir = app.Cwd
p.Stderr = os.Stderr
p.Stdout = os.Stdout

stdinPipe, err := p.StdinPipe()
utils.CheckForError(err)

err = p.Start()
utils.CheckForError(err)

go func() {
defer stdinPipe.Close()
stdinPipe.Write([]byte(pwshScript))
}()

err = p.Wait()
utils.CheckForError(err)

showNewVersion()
os.Exit(0)
}

if force {
executeScript()
} else {
// ask the user first

err = quick.Highlight(os.Stdout, string(pwshScript), "powershell", consoleFormatter, consoleStyle)
if err != nil {
fmt.Print(string(pwshScript))
}

fmt.Println()
fmt.Println()

reader := bufio.NewReader(os.Stdin)

for {
fmt.Print("Do you really want to run this PowerShell script (Y/n)? ")
userInput, _ := reader.ReadString('\n')
userInput = strings.TrimSpace(strings.ToLower(userInput))

switch userInput {
case "", "y", "yes":
executeScript()
case "n", "no":
os.Exit(0)
}
}
}
} else if utils.IsPOSIXLikeOS() {
// if POSIX-like => sh
app.Debug("Will use UNIX shell ...")

scriptUrl := customUpdateScript
if scriptUrl == "" {
scriptUrl = "https://raw.githubusercontent.com/mkloubert/go-package-manager/main/sh.kloubert.dev/gpm.sh"
} else {
su, err := utils.ToUrlForOpenHandler(scriptUrl)
utils.CheckForError(err)

scriptUrl = su
}

bashScript, err := downloadScript(scriptUrl)
utils.CheckForError(err)

executeScript := func() {
p := exec.Command("sh")
p.Dir = app.Cwd
p.Stderr = os.Stderr
p.Stdout = os.Stdout

stdinPipe, err := p.StdinPipe()
utils.CheckForError(err)

err = p.Start()
utils.CheckForError(err)

go func() {
defer stdinPipe.Close()
stdinPipe.Write([]byte(bashScript))
}()

err = p.Wait()
utils.CheckForError(err)

showNewVersion()
os.Exit(0)
}

if force {
executeScript()
} else {
// ask the user first

err = quick.Highlight(os.Stdout, string(bashScript), "shell", consoleFormatter, consoleStyle)
if err != nil {
fmt.Print(string(bashScript))
}

fmt.Println()
fmt.Println()

reader := bufio.NewReader(os.Stdin)

for {
fmt.Print("Do you really want to run this bash script (Y/n)? ")
userInput, _ := reader.ReadString('\n')
userInput = strings.TrimSpace(strings.ToLower(userInput))

switch userInput {
case "", "y", "yes":
executeScript()
case "n", "no":
os.Exit(0)
}
}
}
} else {
utils.CheckForError(fmt.Errorf("self-update for %s/%s is not supported yet", runtime.GOOS, runtime.GOARCH))
}
} else {
app.Debug("Will start project dependencies ...")

app.RunShellCommandByArgs("go", "get", "-u", "./...")

if !noCleanup {
app.RunShellCommandByArgs("go", "mod", "tidy")
if !noCleanup {
app.RunShellCommandByArgs("go", "mod", "tidy")
}
}
},
}

updateCmd.Flags().BoolVarP(&force, "force", "", false, "force self-update")
updateCmd.Flags().BoolVarP(&noCleanup, "no-cleanup", "", false, "do not cleanup go.mod and go.sum")
updateCmd.Flags().BoolVarP(&noVersionPrint, "no-version-print", "", false, "do not print new version after successful update")
updateCmd.Flags().BoolVarP(&powerShell, "powershell", "", false, "force execution of PowerShell script")
updateCmd.Flags().StringVarP(&powerShellBin, "powershell-bin", "", "", "custom binary of the PowerShell")
updateCmd.Flags().BoolVarP(&selfUpdate, "self", "", false, "update this binary instead")
updateCmd.Flags().StringVarP(&updateScript, "update-script", "", "", "custom URL to update script")
updateCmd.Flags().StringVarP(&userAgent, "user-agent", "", "", "custom string for user agent")

parentCmd.AddCommand(
updateCmd,
Expand Down
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/mkloubert/go-package-manager
go 1.23.4

require (
github.com/EDDYCJY/fake-useragent v0.2.0
github.com/alecthomas/chroma v0.10.0
github.com/atotto/clipboard v0.1.4
github.com/briandowns/spinner v1.23.1
Expand All @@ -22,7 +23,9 @@ require (
)

require (
github.com/PuerkitoBio/goquery v1.10.1 // indirect
github.com/alecthomas/chroma/v2 v2.14.0 // indirect
github.com/andybalholm/cascadia v1.3.3 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/charmbracelet/lipgloss v1.0.0 // indirect
Expand Down
Loading

0 comments on commit a7eeca7

Please sign in to comment.