mirror of
https://github.com/coreybutler/nvm-windows.git
synced 2025-09-04 11:48:53 +00:00
combined updates
This commit is contained in:
104
src/author/bridge.go
Normal file
104
src/author/bridge.go
Normal file
@@ -0,0 +1,104 @@
|
||||
package author
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/coreybutler/go-fsutil"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
const (
|
||||
// Constants for SetWindowPos
|
||||
SWP_NOMOVE = 0x0002
|
||||
SWP_NOZORDER = 0x0004
|
||||
SWP_SHOWWINDOW = 0x0040
|
||||
SW_HIDE = 0 // Hide the window (for the main console window)
|
||||
)
|
||||
|
||||
var (
|
||||
modkernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||
moduser32 = syscall.NewLazyDLL("user32.dll")
|
||||
procGetConsole = modkernel32.NewProc("GetConsoleWindow")
|
||||
procShowWindow = moduser32.NewProc("ShowWindow")
|
||||
)
|
||||
|
||||
// Hide the main console window (the one running the Go app)
|
||||
func hideConsole() {
|
||||
hwnd, _, _ := procGetConsole.Call()
|
||||
if hwnd != 0 {
|
||||
procShowWindow.Call(hwnd, uintptr(SW_HIDE)) // Hide the main console window
|
||||
}
|
||||
}
|
||||
|
||||
func Bridge(args ...string) {
|
||||
exe, _ := os.Executable()
|
||||
bridge := filepath.Join(filepath.Dir(exe), "author-nvm.exe")
|
||||
if !fsutil.Exists(bridge) {
|
||||
fmt.Println("error: author bridge not found")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(args) < 2 {
|
||||
fmt.Printf("error: invalid number of arguments passed to author bridge: %d\n", len(args))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
command := args[0]
|
||||
args = args[1:]
|
||||
|
||||
// fmt.Printf("running author bridge: %s %v\n", command, args)
|
||||
|
||||
hideConsole()
|
||||
|
||||
cmd := exec.Command(bridge, append([]string{command}, args...)...)
|
||||
cmd.SysProcAttr = &windows.SysProcAttr{
|
||||
CreationFlags: windows.CREATE_NEW_PROCESS_GROUP | windows.DETACHED_PROCESS | windows.CREATE_NO_WINDOW,
|
||||
}
|
||||
// Create pipes for Stdout and Stderr
|
||||
stdoutPipe, _ := cmd.StdoutPipe()
|
||||
stderrPipe, _ := cmd.StderrPipe()
|
||||
|
||||
// Start the command
|
||||
if err := cmd.Start(); err != nil {
|
||||
fmt.Println("error starting bridge command:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Stream Stdout
|
||||
go func() {
|
||||
scanner := bufio.NewScanner(stdoutPipe)
|
||||
for scanner.Scan() {
|
||||
fmt.Println(scanner.Text())
|
||||
}
|
||||
}()
|
||||
|
||||
// Stream Stderr
|
||||
go func() {
|
||||
scanner := bufio.NewScanner(stderrPipe)
|
||||
for scanner.Scan() {
|
||||
fmt.Println(scanner.Text())
|
||||
}
|
||||
}()
|
||||
|
||||
if command == "upgrade" {
|
||||
for _, arg := range args {
|
||||
if strings.Contains(arg, "--rollback") {
|
||||
fmt.Println("exiting to rollback nvm.exe...")
|
||||
time.Sleep(1 * time.Second)
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for the command to finish
|
||||
if err := cmd.Wait(); err != nil {
|
||||
fmt.Println("bridge command finished with error:", err)
|
||||
}
|
||||
}
|
14
src/go.mod
14
src/go.mod
@@ -6,14 +6,20 @@ require (
|
||||
github.com/blang/semver v3.5.1+incompatible
|
||||
github.com/coreybutler/go-fsutil v1.2.0
|
||||
github.com/coreybutler/go-where v1.0.2
|
||||
github.com/minio/selfupdate v0.6.0
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4
|
||||
github.com/ncruces/zenity v0.10.14
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d
|
||||
golang.org/x/sys v0.1.0
|
||||
golang.org/x/sys v0.25.0
|
||||
)
|
||||
|
||||
require (
|
||||
aead.dev/minisign v0.2.0 // indirect
|
||||
github.com/akavel/rsrc v0.10.2 // indirect
|
||||
github.com/dchest/jsmin v0.0.0-20220218165748-59f39799265f // indirect
|
||||
github.com/josephspurrier/goversioninfo v1.4.1 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||
golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b // indirect
|
||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
|
||||
github.com/randall77/makefat v0.0.0-20210315173500-7ddd0e42c844 // indirect
|
||||
golang.org/x/image v0.20.0 // indirect
|
||||
)
|
||||
|
49
src/go.sum
49
src/go.sum
@@ -1,46 +1,59 @@
|
||||
aead.dev/minisign v0.2.0 h1:kAWrq/hBRu4AARY6AlciO83xhNnW9UaC8YipS2uhLPk=
|
||||
aead.dev/minisign v0.2.0/go.mod h1:zdq6LdSd9TbuSxchxwhpA9zEb9YXcVGoE8JakuiGaIQ=
|
||||
github.com/akavel/rsrc v0.10.2 h1:Zxm8V5eI1hW4gGaYsJQUhxpjkENuG91ki8B4zCrvEsw=
|
||||
github.com/akavel/rsrc v0.10.2/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
||||
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
|
||||
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||
github.com/coreybutler/go-fsutil v1.2.0 h1:kbm62NSofawglUppEOhpHC3NDf/J7ZpguBirBnsgUwU=
|
||||
github.com/coreybutler/go-fsutil v1.2.0/go.mod h1:B+6ufEkkRZgFwyR2sHEVG9dMzVBU3GbyGyYmCq7YkEk=
|
||||
github.com/coreybutler/go-where v1.0.2 h1:Omit67KeTtKpvSJjezVxnVD4qMtvlXDlItiKpVCdcl4=
|
||||
github.com/coreybutler/go-where v1.0.2/go.mod h1:IqV4saJiDXdNJURfTfVRywDHvY1IG5F+GXb2kmnmEe8=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dchest/jsmin v0.0.0-20220218165748-59f39799265f h1:OGqDDftRTwrvUoL6pOG7rYTmWsTCvyEWFsMjg+HcOaA=
|
||||
github.com/dchest/jsmin v0.0.0-20220218165748-59f39799265f/go.mod h1:Dv9D0NUlAsaQcGQZa5kc5mqR9ua72SmA8VXi4cd+cBw=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4 h1:qZNfIGkIANxGv/OqtnntR4DfOY2+BgwR60cAcu/i3SE=
|
||||
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4/go.mod h1:kW3HQ4UdaAyrUCSSDR4xUzBKW6O2iA4uHhk7AtyYp10=
|
||||
github.com/josephspurrier/goversioninfo v1.4.1 h1:5LvrkP+n0tg91J9yTkoVnt/QgNnrI1t4uSsWjIonrqY=
|
||||
github.com/josephspurrier/goversioninfo v1.4.1/go.mod h1:JWzv5rKQr+MmW+LvM412ToT/IkYDZjaclF2pKDss8IY=
|
||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/minio/selfupdate v0.6.0 h1:i76PgT0K5xO9+hjzKcacQtO7+MjJ4JKA8Ak8XQ9DDwU=
|
||||
github.com/minio/selfupdate v0.6.0/go.mod h1:bO02GTIPCMQFTEvE5h4DjYB58bCoZ35XLeBf0buTDdM=
|
||||
github.com/ncruces/zenity v0.10.14 h1:OBFl7qfXcvsdo1NUEGxTlZvAakgWMqz9nG38TuiaGLI=
|
||||
github.com/ncruces/zenity v0.10.14/go.mod h1:ZBW7uVe/Di3IcRYH0Br8X59pi+O6EPnNIOU66YHpOO4=
|
||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
|
||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/randall77/makefat v0.0.0-20210315173500-7ddd0e42c844 h1:GranzK4hv1/pqTIhMTXt2X8MmMOuH3hMeUR0o9SP5yc=
|
||||
github.com/randall77/makefat v0.0.0-20210315173500-7ddd0e42c844/go.mod h1:T1TLSfyWVBRXVGzWd0o9BI4kfoO9InEgfQe4NV3mLz8=
|
||||
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA=
|
||||
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/zmb3/gogetdoc v0.0.0-20190228002656-b37376c5da6a/go.mod h1:ofmGw6LrMypycsiWcyug6516EXpIxSbZ+uI9ppGypfY=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b h1:QAqMVf3pSa6eeTsuklijukjXBlj7Es2QQplab+/RbQ4=
|
||||
golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/image v0.20.0 h1:7cVCUjQwfL18gyBJOmYvptfSHS8Fb3YUDtfLIZ7Nbpw=
|
||||
golang.org/x/image v0.20.0/go.mod h1:0a88To4CYVBAHp5FXJm8o7QbUl37Vd85ply1vyD8auM=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210228012217-479acdf4ea46/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181207195948-8634b1ecd393/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190825031127-d72b05d2b1b6/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
35
src/manifest.json
Normal file
35
src/manifest.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "nvm",
|
||||
"version": "1.1.12",
|
||||
"description": "Node.js version manager for Windows",
|
||||
"license": "SEE LICENSE IN LICENSE",
|
||||
"author": "coreybutler",
|
||||
"build": "nvm.go",
|
||||
"output": "../bin",
|
||||
"update": true,
|
||||
"upx": false,
|
||||
"tiny": false,
|
||||
"variables": {
|
||||
"main.NvmVersion": "manifest.version",
|
||||
"nvm/web.nvmversion": "manifest.version"
|
||||
},
|
||||
"minify": true,
|
||||
"default_profiles": [
|
||||
"dev"
|
||||
],
|
||||
"profile": {
|
||||
"release": {
|
||||
"upx": false,
|
||||
"variables": {
|
||||
"main.debug": "false",
|
||||
"main.mode": "prod"
|
||||
}
|
||||
},
|
||||
"dev": {
|
||||
"variables": {
|
||||
"main.debug": "true",
|
||||
"main.mode": "dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -34,6 +34,8 @@ func GetCurrentVersion() (string, string) {
|
||||
if err == nil {
|
||||
if string(str) == "x64" {
|
||||
bit = "64"
|
||||
} else if string(str) == "arm64" {
|
||||
bit = "arm64"
|
||||
} else {
|
||||
bit = "32"
|
||||
}
|
||||
|
1083
src/nvm.go
1083
src/nvm.go
File diff suppressed because it is too large
Load Diff
@@ -5,78 +5,78 @@
|
||||
package semver
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
numbers string = "0123456789"
|
||||
alphas = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-"
|
||||
alphanum = alphas + numbers
|
||||
dot = "."
|
||||
hyphen = "-"
|
||||
plus = "+"
|
||||
numbers string = "0123456789"
|
||||
alphas = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-"
|
||||
alphanum = alphas + numbers
|
||||
dot = "."
|
||||
hyphen = "-"
|
||||
plus = "+"
|
||||
)
|
||||
|
||||
// Latest fully supported spec version
|
||||
var SPEC_VERSION = Version{
|
||||
Major: 2,
|
||||
Minor: 0,
|
||||
Patch: 0,
|
||||
Major: 2,
|
||||
Minor: 0,
|
||||
Patch: 0,
|
||||
}
|
||||
|
||||
type Version struct {
|
||||
Major uint64
|
||||
Minor uint64
|
||||
Patch uint64
|
||||
Pre []*PRVersion
|
||||
Build []string //No Precedence
|
||||
Major uint64
|
||||
Minor uint64
|
||||
Patch uint64
|
||||
Pre []*PRVersion
|
||||
Build []string //No Precedence
|
||||
}
|
||||
|
||||
// Version to string
|
||||
func (v *Version) String() string {
|
||||
versionArray := []string{
|
||||
strconv.FormatUint(v.Major, 10),
|
||||
dot,
|
||||
strconv.FormatUint(v.Minor, 10),
|
||||
dot,
|
||||
strconv.FormatUint(v.Patch, 10),
|
||||
}
|
||||
if len(v.Pre) > 0 {
|
||||
versionArray = append(versionArray, hyphen)
|
||||
for i, pre := range v.Pre {
|
||||
if i > 0 {
|
||||
versionArray = append(versionArray, dot)
|
||||
}
|
||||
versionArray = append(versionArray, pre.String())
|
||||
}
|
||||
}
|
||||
if len(v.Build) > 0 {
|
||||
versionArray = append(versionArray, plus, strings.Join(v.Build, dot))
|
||||
}
|
||||
return strings.Join(versionArray, "")
|
||||
versionArray := []string{
|
||||
strconv.FormatUint(v.Major, 10),
|
||||
dot,
|
||||
strconv.FormatUint(v.Minor, 10),
|
||||
dot,
|
||||
strconv.FormatUint(v.Patch, 10),
|
||||
}
|
||||
if len(v.Pre) > 0 {
|
||||
versionArray = append(versionArray, hyphen)
|
||||
for i, pre := range v.Pre {
|
||||
if i > 0 {
|
||||
versionArray = append(versionArray, dot)
|
||||
}
|
||||
versionArray = append(versionArray, pre.String())
|
||||
}
|
||||
}
|
||||
if len(v.Build) > 0 {
|
||||
versionArray = append(versionArray, plus, strings.Join(v.Build, dot))
|
||||
}
|
||||
return strings.Join(versionArray, "")
|
||||
}
|
||||
|
||||
// Checks if v is greater than o.
|
||||
func (v *Version) GT(o *Version) bool {
|
||||
return (v.Compare(o) == 1)
|
||||
return (v.Compare(o) == 1)
|
||||
}
|
||||
|
||||
// Checks if v is greater than or equal to o.
|
||||
func (v *Version) GTE(o *Version) bool {
|
||||
return (v.Compare(o) >= 0)
|
||||
return (v.Compare(o) >= 0)
|
||||
}
|
||||
|
||||
// Checks if v is less than o.
|
||||
func (v *Version) LT(o *Version) bool {
|
||||
return (v.Compare(o) == -1)
|
||||
return (v.Compare(o) == -1)
|
||||
}
|
||||
|
||||
// Checks if v is less than or equal to o.
|
||||
func (v *Version) LTE(o *Version) bool {
|
||||
return (v.Compare(o) <= 0)
|
||||
return (v.Compare(o) <= 0)
|
||||
}
|
||||
|
||||
// Compares Versions v to o:
|
||||
@@ -84,241 +84,242 @@ func (v *Version) LTE(o *Version) bool {
|
||||
// 0 == v is equal to o
|
||||
// 1 == v is greater than o
|
||||
func (v *Version) Compare(o *Version) int {
|
||||
if v.Major != o.Major {
|
||||
if v.Major > o.Major {
|
||||
return 1
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
if v.Minor != o.Minor {
|
||||
if v.Minor > o.Minor {
|
||||
return 1
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
if v.Patch != o.Patch {
|
||||
if v.Patch > o.Patch {
|
||||
return 1
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
if v.Major != o.Major {
|
||||
if v.Major > o.Major {
|
||||
return 1
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
if v.Minor != o.Minor {
|
||||
if v.Minor > o.Minor {
|
||||
return 1
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
if v.Patch != o.Patch {
|
||||
if v.Patch > o.Patch {
|
||||
return 1
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
// Quick comparison if a version has no prerelease versions
|
||||
if len(v.Pre) == 0 && len(o.Pre) == 0 {
|
||||
return 0
|
||||
} else if len(v.Pre) == 0 && len(o.Pre) > 0 {
|
||||
return 1
|
||||
} else if len(v.Pre) > 0 && len(o.Pre) == 0 {
|
||||
return -1
|
||||
} else {
|
||||
// Quick comparison if a version has no prerelease versions
|
||||
if len(v.Pre) == 0 && len(o.Pre) == 0 {
|
||||
return 0
|
||||
} else if len(v.Pre) == 0 && len(o.Pre) > 0 {
|
||||
return 1
|
||||
} else if len(v.Pre) > 0 && len(o.Pre) == 0 {
|
||||
return -1
|
||||
} else {
|
||||
|
||||
i := 0
|
||||
for ; i < len(v.Pre) && i < len(o.Pre); i++ {
|
||||
if comp := v.Pre[i].Compare(o.Pre[i]); comp == 0 {
|
||||
continue
|
||||
} else if comp == 1 {
|
||||
return 1
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
i := 0
|
||||
for ; i < len(v.Pre) && i < len(o.Pre); i++ {
|
||||
if comp := v.Pre[i].Compare(o.Pre[i]); comp == 0 {
|
||||
continue
|
||||
} else if comp == 1 {
|
||||
return 1
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
// If all pr versions are the equal but one has further pr version, this one greater
|
||||
if i == len(v.Pre) && i == len(o.Pre) {
|
||||
return 0
|
||||
} else if i == len(v.Pre) && i < len(o.Pre) {
|
||||
return -1
|
||||
} else {
|
||||
return 1
|
||||
}
|
||||
// If all pr versions are the equal but one has further pr version, this one greater
|
||||
if i == len(v.Pre) && i == len(o.Pre) {
|
||||
return 0
|
||||
} else if i == len(v.Pre) && i < len(o.Pre) {
|
||||
return -1
|
||||
} else {
|
||||
return 1
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validates v and returns error in case
|
||||
func (v *Version) Validate() error {
|
||||
// Major, Minor, Patch already validated using uint64
|
||||
// Major, Minor, Patch already validated using uint64
|
||||
|
||||
if len(v.Pre) > 0 {
|
||||
for _, pre := range v.Pre {
|
||||
if !pre.IsNum { //Numeric prerelease versions already uint64
|
||||
if len(pre.VersionStr) == 0 {
|
||||
return fmt.Errorf("Prerelease can not be empty %q", pre.VersionStr)
|
||||
}
|
||||
if !containsOnly(pre.VersionStr, alphanum) {
|
||||
return fmt.Errorf("Invalid character(s) found in prerelease %q", pre.VersionStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(v.Pre) > 0 {
|
||||
for _, pre := range v.Pre {
|
||||
if !pre.IsNum { //Numeric prerelease versions already uint64
|
||||
if len(pre.VersionStr) == 0 {
|
||||
return fmt.Errorf("Prerelease can not be empty %q", pre.VersionStr)
|
||||
}
|
||||
if !containsOnly(pre.VersionStr, alphanum) {
|
||||
return fmt.Errorf("Invalid character(s) found in prerelease %q", pre.VersionStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(v.Build) > 0 {
|
||||
for _, build := range v.Build {
|
||||
if len(build) == 0 {
|
||||
return fmt.Errorf("Build meta data can not be empty %q", build)
|
||||
}
|
||||
if !containsOnly(build, alphanum) {
|
||||
return fmt.Errorf("Invalid character(s) found in build meta data %q", build)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(v.Build) > 0 {
|
||||
for _, build := range v.Build {
|
||||
if len(build) == 0 {
|
||||
return fmt.Errorf("Build meta data can not be empty %q", build)
|
||||
}
|
||||
if !containsOnly(build, alphanum) {
|
||||
return fmt.Errorf("Invalid character(s) found in build meta data %q", build)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// Alias for Parse, parses version string and returns a validated Version or error
|
||||
func New(s string) (*Version, error) {
|
||||
return Parse(s)
|
||||
return Parse(s)
|
||||
}
|
||||
|
||||
// Parses version string and returns a validated Version or error
|
||||
func Parse(s string) (*Version, error) {
|
||||
if len(s) == 0 {
|
||||
return nil, errors.New("Version string empty")
|
||||
}
|
||||
s = strings.Replace(s, "v", "", 1)
|
||||
if len(s) == 0 {
|
||||
return nil, errors.New("Version string empty")
|
||||
}
|
||||
|
||||
// Split into major.minor.(patch+pr+meta)
|
||||
parts := strings.SplitN(s, ".", 3)
|
||||
if len(parts) != 3 {
|
||||
return nil, errors.New("No Major.Minor.Patch elements found")
|
||||
}
|
||||
// Split into major.minor.(patch+pr+meta)
|
||||
parts := strings.SplitN(s, ".", 3)
|
||||
if len(parts) != 3 {
|
||||
return nil, errors.New("No Major.Minor.Patch elements found")
|
||||
}
|
||||
|
||||
// Major
|
||||
if !containsOnly(parts[0], numbers) {
|
||||
return nil, fmt.Errorf("Invalid character(s) found in major number %q", parts[0])
|
||||
}
|
||||
if hasLeadingZeroes(parts[0]) {
|
||||
return nil, fmt.Errorf("Major number must not contain leading zeroes %q", parts[0])
|
||||
}
|
||||
major, err := strconv.ParseUint(parts[0], 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Major
|
||||
if !containsOnly(parts[0], numbers) {
|
||||
return nil, fmt.Errorf("Invalid character(s) found in major number %q", parts[0])
|
||||
}
|
||||
if hasLeadingZeroes(parts[0]) {
|
||||
return nil, fmt.Errorf("Major number must not contain leading zeroes %q", parts[0])
|
||||
}
|
||||
major, err := strconv.ParseUint(parts[0], 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Minor
|
||||
if !containsOnly(parts[1], numbers) {
|
||||
return nil, fmt.Errorf("Invalid character(s) found in minor number %q", parts[1])
|
||||
}
|
||||
if hasLeadingZeroes(parts[1]) {
|
||||
return nil, fmt.Errorf("Minor number must not contain leading zeroes %q", parts[1])
|
||||
}
|
||||
minor, err := strconv.ParseUint(parts[1], 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Minor
|
||||
if !containsOnly(parts[1], numbers) {
|
||||
return nil, fmt.Errorf("Invalid character(s) found in minor number %q", parts[1])
|
||||
}
|
||||
if hasLeadingZeroes(parts[1]) {
|
||||
return nil, fmt.Errorf("Minor number must not contain leading zeroes %q", parts[1])
|
||||
}
|
||||
minor, err := strconv.ParseUint(parts[1], 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
preIndex := strings.Index(parts[2], "-")
|
||||
buildIndex := strings.Index(parts[2], "+")
|
||||
preIndex := strings.Index(parts[2], "-")
|
||||
buildIndex := strings.Index(parts[2], "+")
|
||||
|
||||
// Determine last index of patch version (first of pre or build versions)
|
||||
var subVersionIndex int
|
||||
if preIndex != -1 && buildIndex == -1 {
|
||||
subVersionIndex = preIndex
|
||||
} else if preIndex == -1 && buildIndex != -1 {
|
||||
subVersionIndex = buildIndex
|
||||
} else if preIndex == -1 && buildIndex == -1 {
|
||||
subVersionIndex = len(parts[2])
|
||||
} else {
|
||||
// if there is no actual pr version but a hyphen inside the build meta data
|
||||
if buildIndex < preIndex {
|
||||
subVersionIndex = buildIndex
|
||||
preIndex = -1 // Build meta data before preIndex found implicates there are no prerelease versions
|
||||
} else {
|
||||
subVersionIndex = preIndex
|
||||
}
|
||||
}
|
||||
// Determine last index of patch version (first of pre or build versions)
|
||||
var subVersionIndex int
|
||||
if preIndex != -1 && buildIndex == -1 {
|
||||
subVersionIndex = preIndex
|
||||
} else if preIndex == -1 && buildIndex != -1 {
|
||||
subVersionIndex = buildIndex
|
||||
} else if preIndex == -1 && buildIndex == -1 {
|
||||
subVersionIndex = len(parts[2])
|
||||
} else {
|
||||
// if there is no actual pr version but a hyphen inside the build meta data
|
||||
if buildIndex < preIndex {
|
||||
subVersionIndex = buildIndex
|
||||
preIndex = -1 // Build meta data before preIndex found implicates there are no prerelease versions
|
||||
} else {
|
||||
subVersionIndex = preIndex
|
||||
}
|
||||
}
|
||||
|
||||
if !containsOnly(parts[2][:subVersionIndex], numbers) {
|
||||
return nil, fmt.Errorf("Invalid character(s) found in patch number %q", parts[2][:subVersionIndex])
|
||||
}
|
||||
if hasLeadingZeroes(parts[2][:subVersionIndex]) {
|
||||
return nil, fmt.Errorf("Patch number must not contain leading zeroes %q", parts[2][:subVersionIndex])
|
||||
}
|
||||
patch, err := strconv.ParseUint(parts[2][:subVersionIndex], 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v := &Version{}
|
||||
v.Major = major
|
||||
v.Minor = minor
|
||||
v.Patch = patch
|
||||
if !containsOnly(parts[2][:subVersionIndex], numbers) {
|
||||
return nil, fmt.Errorf("Invalid character(s) found in patch number %q", parts[2][:subVersionIndex])
|
||||
}
|
||||
if hasLeadingZeroes(parts[2][:subVersionIndex]) {
|
||||
return nil, fmt.Errorf("Patch number must not contain leading zeroes %q", parts[2][:subVersionIndex])
|
||||
}
|
||||
patch, err := strconv.ParseUint(parts[2][:subVersionIndex], 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v := &Version{}
|
||||
v.Major = major
|
||||
v.Minor = minor
|
||||
v.Patch = patch
|
||||
|
||||
// There are PreRelease versions
|
||||
if preIndex != -1 {
|
||||
var preRels string
|
||||
if buildIndex != -1 {
|
||||
preRels = parts[2][subVersionIndex+1 : buildIndex]
|
||||
} else {
|
||||
preRels = parts[2][subVersionIndex+1:]
|
||||
}
|
||||
prparts := strings.Split(preRels, ".")
|
||||
for _, prstr := range prparts {
|
||||
parsedPR, err := NewPRVersion(prstr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v.Pre = append(v.Pre, parsedPR)
|
||||
}
|
||||
}
|
||||
// There are PreRelease versions
|
||||
if preIndex != -1 {
|
||||
var preRels string
|
||||
if buildIndex != -1 {
|
||||
preRels = parts[2][subVersionIndex+1 : buildIndex]
|
||||
} else {
|
||||
preRels = parts[2][subVersionIndex+1:]
|
||||
}
|
||||
prparts := strings.Split(preRels, ".")
|
||||
for _, prstr := range prparts {
|
||||
parsedPR, err := NewPRVersion(prstr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v.Pre = append(v.Pre, parsedPR)
|
||||
}
|
||||
}
|
||||
|
||||
// There is build meta data
|
||||
if buildIndex != -1 {
|
||||
buildStr := parts[2][buildIndex+1:]
|
||||
buildParts := strings.Split(buildStr, ".")
|
||||
for _, str := range buildParts {
|
||||
if len(str) == 0 {
|
||||
return nil, errors.New("Build meta data is empty")
|
||||
}
|
||||
if !containsOnly(str, alphanum) {
|
||||
return nil, fmt.Errorf("Invalid character(s) found in build meta data %q", str)
|
||||
}
|
||||
v.Build = append(v.Build, str)
|
||||
}
|
||||
}
|
||||
// There is build meta data
|
||||
if buildIndex != -1 {
|
||||
buildStr := parts[2][buildIndex+1:]
|
||||
buildParts := strings.Split(buildStr, ".")
|
||||
for _, str := range buildParts {
|
||||
if len(str) == 0 {
|
||||
return nil, errors.New("Build meta data is empty")
|
||||
}
|
||||
if !containsOnly(str, alphanum) {
|
||||
return nil, fmt.Errorf("Invalid character(s) found in build meta data %q", str)
|
||||
}
|
||||
v.Build = append(v.Build, str)
|
||||
}
|
||||
}
|
||||
|
||||
return v, nil
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// PreRelease Version
|
||||
type PRVersion struct {
|
||||
VersionStr string
|
||||
VersionNum uint64
|
||||
IsNum bool
|
||||
VersionStr string
|
||||
VersionNum uint64
|
||||
IsNum bool
|
||||
}
|
||||
|
||||
// Creates a new valid prerelease version
|
||||
func NewPRVersion(s string) (*PRVersion, error) {
|
||||
if len(s) == 0 {
|
||||
return nil, errors.New("Prerelease is empty")
|
||||
}
|
||||
v := &PRVersion{}
|
||||
if containsOnly(s, numbers) {
|
||||
if hasLeadingZeroes(s) {
|
||||
return nil, fmt.Errorf("Numeric PreRelease version must not contain leading zeroes %q", s)
|
||||
}
|
||||
num, err := strconv.ParseUint(s, 10, 64)
|
||||
if len(s) == 0 {
|
||||
return nil, errors.New("Prerelease is empty")
|
||||
}
|
||||
v := &PRVersion{}
|
||||
if containsOnly(s, numbers) {
|
||||
if hasLeadingZeroes(s) {
|
||||
return nil, fmt.Errorf("Numeric PreRelease version must not contain leading zeroes %q", s)
|
||||
}
|
||||
num, err := strconv.ParseUint(s, 10, 64)
|
||||
|
||||
// Might never be hit, but just in case
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v.VersionNum = num
|
||||
v.IsNum = true
|
||||
} else if containsOnly(s, alphanum) {
|
||||
v.VersionStr = s
|
||||
v.IsNum = false
|
||||
} else {
|
||||
return nil, fmt.Errorf("Invalid character(s) found in prerelease %q", s)
|
||||
}
|
||||
return v, nil
|
||||
// Might never be hit, but just in case
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v.VersionNum = num
|
||||
v.IsNum = true
|
||||
} else if containsOnly(s, alphanum) {
|
||||
v.VersionStr = s
|
||||
v.IsNum = false
|
||||
} else {
|
||||
return nil, fmt.Errorf("Invalid character(s) found in prerelease %q", s)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// Is pre release version numeric?
|
||||
func (v *PRVersion) IsNumeric() bool {
|
||||
return v.IsNum
|
||||
return v.IsNum
|
||||
}
|
||||
|
||||
// Compares PreRelease Versions v to o:
|
||||
@@ -326,54 +327,54 @@ func (v *PRVersion) IsNumeric() bool {
|
||||
// 0 == v is equal to o
|
||||
// 1 == v is greater than o
|
||||
func (v *PRVersion) Compare(o *PRVersion) int {
|
||||
if v.IsNum && !o.IsNum {
|
||||
return -1
|
||||
} else if !v.IsNum && o.IsNum {
|
||||
return 1
|
||||
} else if v.IsNum && o.IsNum {
|
||||
if v.VersionNum == o.VersionNum {
|
||||
return 0
|
||||
} else if v.VersionNum > o.VersionNum {
|
||||
return 1
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
} else { // both are Alphas
|
||||
if v.VersionStr == o.VersionStr {
|
||||
return 0
|
||||
} else if v.VersionStr > o.VersionStr {
|
||||
return 1
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
if v.IsNum && !o.IsNum {
|
||||
return -1
|
||||
} else if !v.IsNum && o.IsNum {
|
||||
return 1
|
||||
} else if v.IsNum && o.IsNum {
|
||||
if v.VersionNum == o.VersionNum {
|
||||
return 0
|
||||
} else if v.VersionNum > o.VersionNum {
|
||||
return 1
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
} else { // both are Alphas
|
||||
if v.VersionStr == o.VersionStr {
|
||||
return 0
|
||||
} else if v.VersionStr > o.VersionStr {
|
||||
return 1
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PreRelease version to string
|
||||
func (v *PRVersion) String() string {
|
||||
if v.IsNum {
|
||||
return strconv.FormatUint(v.VersionNum, 10)
|
||||
}
|
||||
return v.VersionStr
|
||||
if v.IsNum {
|
||||
return strconv.FormatUint(v.VersionNum, 10)
|
||||
}
|
||||
return v.VersionStr
|
||||
}
|
||||
|
||||
func containsOnly(s string, set string) bool {
|
||||
return strings.IndexFunc(s, func(r rune) bool {
|
||||
return !strings.ContainsRune(set, r)
|
||||
}) == -1
|
||||
return strings.IndexFunc(s, func(r rune) bool {
|
||||
return !strings.ContainsRune(set, r)
|
||||
}) == -1
|
||||
}
|
||||
|
||||
func hasLeadingZeroes(s string) bool {
|
||||
return len(s) > 1 && s[0] == '0'
|
||||
return len(s) > 1 && s[0] == '0'
|
||||
}
|
||||
|
||||
// Creates a new valid build version
|
||||
func NewBuildVersion(s string) (string, error) {
|
||||
if len(s) == 0 {
|
||||
return "", errors.New("Buildversion is empty")
|
||||
}
|
||||
if !containsOnly(s, alphanum) {
|
||||
return "", fmt.Errorf("Invalid character(s) found in build meta data %q", s)
|
||||
}
|
||||
return s, nil
|
||||
if len(s) == 0 {
|
||||
return "", errors.New("Buildversion is empty")
|
||||
}
|
||||
if !containsOnly(s, alphanum) {
|
||||
return "", fmt.Errorf("Invalid character(s) found in build meta data %q", s)
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
198
src/upgrade/check.go
Normal file
198
src/upgrade/check.go
Normal file
@@ -0,0 +1,198 @@
|
||||
package upgrade
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"nvm/node"
|
||||
"nvm/semver"
|
||||
"nvm/web"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/go-toast/toast"
|
||||
)
|
||||
|
||||
func Check(root string, nvmversion string) {
|
||||
// Store the recognized version to prevent duplicates
|
||||
notices := LoadNotices()
|
||||
defer notices.Save()
|
||||
|
||||
reg := LoadRegistration(os.Args[2:]...)
|
||||
|
||||
// Check for Node.js updates
|
||||
if reg.LTS || reg.Current {
|
||||
buf, err := get(web.GetFullNodeUrl("index.json"))
|
||||
abortOnError(err)
|
||||
|
||||
var data = make([]map[string]interface{}, 0)
|
||||
abortOnError(json.Unmarshal([]byte(buf), &data))
|
||||
|
||||
lastLTS := notices.LastLTS()
|
||||
lastCurrent := notices.LastCurrent()
|
||||
installed := node.GetInstalled(root)
|
||||
changes := 0
|
||||
for _, value := range data {
|
||||
switch value["lts"].(type) {
|
||||
// LTS versions always have the lts attribute as a string
|
||||
case string:
|
||||
if reg.LTS {
|
||||
versionDate, _ := time.Parse("2006-01-02", value["date"].(string))
|
||||
if versionDate.After(lastLTS) && versionDate.After(time.Now().AddDate(0, -1, 0)) && !in(strings.Replace(value["version"].(string), "v", "", 1), installed) {
|
||||
abortOnError(alertRelease(value))
|
||||
changes++
|
||||
}
|
||||
}
|
||||
// Current versions always have the lts attribute as a false boolean
|
||||
case bool:
|
||||
if reg.Current && !value["lts"].(bool) {
|
||||
versionDate, _ := time.Parse("2006-01-02", value["date"].(string))
|
||||
if versionDate.After(lastCurrent) && versionDate.After(time.Now().AddDate(0, -1, 0)) && !in(strings.Replace(value["version"].(string), "v", "", 1), installed) {
|
||||
abortOnError(alertRelease(value))
|
||||
changes++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if changes == 0 {
|
||||
noupdate()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Check for NVM for Windows updates
|
||||
if reg.NVM4W {
|
||||
buf, err := get("https://api.github.com/repos/coreybutler/nvm-windows/releases/latest")
|
||||
abortOnError(err)
|
||||
|
||||
var data map[string]interface{}
|
||||
abortOnError(json.Unmarshal([]byte(buf), &data))
|
||||
|
||||
current, err := semver.New(nvmversion)
|
||||
abortOnError(err)
|
||||
next, err := semver.New(data["tag_name"].(string))
|
||||
abortOnError(err)
|
||||
|
||||
notices.NVM4W = current.String()
|
||||
if !next.GT(current) {
|
||||
alertNvmRelease(current, next, data)
|
||||
notices.NVM4W = next.String()
|
||||
}
|
||||
}
|
||||
|
||||
now := time.Now().Format("2006-01-02")
|
||||
if reg.LTS {
|
||||
notices.LTS = now
|
||||
}
|
||||
if reg.Current {
|
||||
notices.Current = now
|
||||
}
|
||||
}
|
||||
|
||||
func alertNvmRelease(current, next *semver.Version, data map[string]interface{}) {
|
||||
exe, _ := os.Executable()
|
||||
path := filepath.Dir(exe)
|
||||
iconPath := filepath.Join(path, "nodejs.ico")
|
||||
pubDate, _ := time.Parse("2006-01-02T15:04:05Z", data["published_at"].(string))
|
||||
age := humanize.Time(pubDate)
|
||||
|
||||
notification := toast.Notification{
|
||||
AppID: "NVM for Windows",
|
||||
Title: "NVM for Windows Update Available",
|
||||
Message: fmt.Sprintf("Version %s is was released %s.\n(currently using v%s)", next.String(), age, current.String()),
|
||||
Icon: iconPath,
|
||||
Actions: []toast.Action{
|
||||
{"protocol", "Install", "nvm://launch?action=upgrade"},
|
||||
{"protocol", "View", data["html_url"].(string)},
|
||||
},
|
||||
}
|
||||
|
||||
// Display the notification
|
||||
err := notification.Push()
|
||||
if err != nil {
|
||||
abortOnError(err)
|
||||
}
|
||||
}
|
||||
|
||||
func in(item string, set []string) bool {
|
||||
for _, i := range set {
|
||||
if i == item {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func noupdate() {
|
||||
fmt.Println("no new releases detected")
|
||||
}
|
||||
|
||||
func UpgradeCompleteAlert(version string) {
|
||||
exe, _ := os.Executable()
|
||||
path := filepath.Dir(exe)
|
||||
iconPath := filepath.Join(path, "checkmark.ico")
|
||||
|
||||
notification := toast.Notification{
|
||||
AppID: "NVM for Windows",
|
||||
Title: "Upgrade Complete",
|
||||
Message: fmt.Sprintf("The upgrade to NVM for Windows v%s completed successfully.", version),
|
||||
Icon: iconPath,
|
||||
Actions: []toast.Action{
|
||||
{"protocol", "Open PowerShell", "nvm://launch?action=open_terminal&type=pwsh"},
|
||||
{"protocol", "Open CMD Prompt", "nvm://launch?action=open_terminal&type=cmd"},
|
||||
},
|
||||
}
|
||||
|
||||
// Display the notification
|
||||
err := notification.Push()
|
||||
if err != nil {
|
||||
abortOnError(err)
|
||||
}
|
||||
}
|
||||
|
||||
func alertRelease(data map[string]interface{}) error {
|
||||
version, err := semver.New(data["version"].(string))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
exe, _ := os.Executable()
|
||||
path := filepath.Dir(exe)
|
||||
iconPath := filepath.Join(path, "nodejs.ico")
|
||||
releaseName := ""
|
||||
releaseDate, err := time.Parse("2006-01-02", data["date"].(string))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
age := humanize.Time(releaseDate)
|
||||
msg := fmt.Sprintf("with npm v%s & V8 v%s\nReleased %s.", data["npm"].(string), data["v8"].(string), age)
|
||||
|
||||
switch data["lts"].(type) {
|
||||
case string:
|
||||
releaseName = " (LTS " + data["lts"].(string) + ")"
|
||||
}
|
||||
|
||||
if data["security"].(bool) {
|
||||
msg += "\nThis is a security release."
|
||||
}
|
||||
|
||||
title := fmt.Sprintf("Node.js v%s Available%s", version.String(), releaseName)
|
||||
|
||||
notification := toast.Notification{
|
||||
AppID: "NVM for Windows",
|
||||
Title: title,
|
||||
Message: msg,
|
||||
Icon: iconPath,
|
||||
}
|
||||
|
||||
// Display the notification
|
||||
err = notification.Push()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
69
src/upgrade/notification.go
Normal file
69
src/upgrade/notification.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package upgrade
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
type LastNotification struct {
|
||||
outpath string
|
||||
LTS string `json:"lts,omitempty"`
|
||||
Current string `json:"current,omitempty"`
|
||||
NVM4W string `json:"nvm4w,omitempty"`
|
||||
Author string `json:"author,omitempty"`
|
||||
}
|
||||
|
||||
func LoadNotices() *LastNotification {
|
||||
ln := &LastNotification{}
|
||||
noticedata, err := os.ReadFile(ln.File())
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
abortOnError(err)
|
||||
}
|
||||
}
|
||||
|
||||
if noticedata != nil {
|
||||
abortOnError(json.Unmarshal(noticedata, &ln))
|
||||
}
|
||||
|
||||
return ln
|
||||
}
|
||||
|
||||
func (ln *LastNotification) Path() string {
|
||||
if ln.outpath == "" {
|
||||
ln.outpath = filepath.Join(os.Getenv("APPDATA"), ".nvm")
|
||||
}
|
||||
return ln.outpath
|
||||
}
|
||||
|
||||
func (ln *LastNotification) File() string {
|
||||
return filepath.Join(ln.Path(), ".updates.json")
|
||||
}
|
||||
|
||||
func (ln *LastNotification) Save() {
|
||||
output, err := json.Marshal(ln)
|
||||
abortOnError(err)
|
||||
abortOnError(os.MkdirAll(ln.Path(), os.ModePerm))
|
||||
abortOnError(os.WriteFile(ln.File(), output, os.ModePerm))
|
||||
abortOnError(setHidden(ln.Path()))
|
||||
}
|
||||
|
||||
func (ln *LastNotification) LastLTS() time.Time {
|
||||
if ln.LTS == "" {
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
t, _ := time.Parse("2006-01-02", ln.LTS)
|
||||
return t
|
||||
}
|
||||
|
||||
func (ln *LastNotification) LastCurrent() time.Time {
|
||||
if ln.Current == "" {
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
t, _ := time.Parse("2006-01-02", ln.Current)
|
||||
return t
|
||||
}
|
211
src/upgrade/register.go
Normal file
211
src/upgrade/register.go
Normal file
@@ -0,0 +1,211 @@
|
||||
package upgrade
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
NODE_LTS_SCHEDULE_NAME = "NVM for Windows Node.js LTS Update Check"
|
||||
NODE_CURRENT_SCHEDULE_NAME = "NVM for Windows Node.js Current Update Check"
|
||||
NVM4W_SCHEDULE_NAME = "NVM for Windows Update Check"
|
||||
AUTHOR_SCHEDULE_NAME = "NVM for Windows Author Update Check"
|
||||
)
|
||||
|
||||
type Registration struct {
|
||||
LTS bool
|
||||
Current bool
|
||||
NVM4W bool
|
||||
Author bool
|
||||
}
|
||||
|
||||
func LoadRegistration(args ...string) *Registration {
|
||||
reg := &Registration{
|
||||
LTS: false,
|
||||
Current: false,
|
||||
NVM4W: false,
|
||||
Author: false,
|
||||
}
|
||||
|
||||
for _, arg := range args {
|
||||
arg = strings.ToLower(strings.ReplaceAll(arg, "--", ""))
|
||||
switch arg {
|
||||
case "lts":
|
||||
reg.LTS = true
|
||||
case "current":
|
||||
reg.Current = true
|
||||
case "nvm4w":
|
||||
reg.NVM4W = true
|
||||
case "author":
|
||||
reg.Author = true
|
||||
}
|
||||
}
|
||||
|
||||
return reg
|
||||
}
|
||||
|
||||
func abortOnError(err error) {
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.WriteFile("./error.log", []byte(err.Error()), os.ModePerm)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func logError(err error) {
|
||||
fmt.Println(err)
|
||||
if err != nil {
|
||||
os.WriteFile("./error.log", []byte(err.Error()), os.ModePerm)
|
||||
}
|
||||
}
|
||||
|
||||
func Register() {
|
||||
reg := LoadRegistration(os.Args[2:]...)
|
||||
exe, _ := os.Executable()
|
||||
|
||||
if reg.LTS {
|
||||
abortOnError(ScheduleTask(NODE_LTS_SCHEDULE_NAME, fmt.Sprintf(`"%s" checkForUpdates lts`, exe), "HOURLY", "00:30"))
|
||||
}
|
||||
if reg.Current {
|
||||
abortOnError(ScheduleTask(NODE_CURRENT_SCHEDULE_NAME, fmt.Sprintf(`"%s" checkForUpdates current`, exe), "HOURLY", "00:25"))
|
||||
}
|
||||
if reg.NVM4W {
|
||||
abortOnError(ScheduleTask(NVM4W_SCHEDULE_NAME, fmt.Sprintf(`"%s" checkForUpdates nvm4w`, exe), "HOURLY", "00:15"))
|
||||
}
|
||||
if reg.Author {
|
||||
abortOnError(ScheduleTask(AUTHOR_SCHEDULE_NAME, fmt.Sprintf(`"%s" checkForUpdates author`, exe), "HOURLY", "00:45"))
|
||||
}
|
||||
}
|
||||
|
||||
func Unregister() {
|
||||
reg := LoadRegistration(os.Args[2:]...)
|
||||
|
||||
if reg.LTS {
|
||||
abortOnError(UnscheduleTask(NODE_LTS_SCHEDULE_NAME))
|
||||
}
|
||||
if reg.Current {
|
||||
abortOnError(UnscheduleTask(NODE_CURRENT_SCHEDULE_NAME))
|
||||
}
|
||||
if reg.NVM4W {
|
||||
abortOnError(UnscheduleTask(NVM4W_SCHEDULE_NAME))
|
||||
}
|
||||
if reg.Author {
|
||||
abortOnError(UnscheduleTask(AUTHOR_SCHEDULE_NAME))
|
||||
}
|
||||
}
|
||||
|
||||
// interval can be:
|
||||
// MINUTE Runs the task every N minutes. Requires /MO (modifier) to specify the interval.
|
||||
// HOURLY Runs the task every N hours. Requires /MO to specify the interval.
|
||||
// DAILY Runs the task every N days. Requires /MO to specify the interval.
|
||||
// WEEKLY Runs the task every N weeks. Requires /MO and /D (days of the week) to specify the schedule.
|
||||
// MONTHLY Runs the task on specific days of the month. Requires /MO, /D, or /M for further specifics.
|
||||
// ONCE Runs the task only once at the specified time. Requires /ST (start time).
|
||||
// ONSTART Runs the task every time the system starts.
|
||||
// ONLOGON Runs the task every time the user logs on.
|
||||
// ONIDLE Runs the task when the system is idle for a specified amount of time.
|
||||
// EVENT Runs the task when a specific event is triggered. Requires /EC (event log) and /ID (event ID).
|
||||
func ScheduleTask(name string, command string, interval string, startTime ...string) error {
|
||||
switch strings.ToUpper(interval) {
|
||||
case "MINUTE":
|
||||
fallthrough
|
||||
case "HOURLY":
|
||||
fallthrough
|
||||
case "DAILY":
|
||||
fallthrough
|
||||
case "WEEKLY":
|
||||
fallthrough
|
||||
case "MONTHLY":
|
||||
fallthrough
|
||||
case "ONCE":
|
||||
fallthrough
|
||||
case "ONSTART":
|
||||
fallthrough
|
||||
case "ONLOGON":
|
||||
fallthrough
|
||||
case "ONIDLE":
|
||||
fallthrough
|
||||
case "EVENT":
|
||||
interval = strings.ToUpper(interval)
|
||||
default:
|
||||
return fmt.Errorf("scheduling error: invalid interval %q", interval)
|
||||
}
|
||||
|
||||
start := "00:00"
|
||||
if len(startTime) > 0 {
|
||||
start = startTime[0]
|
||||
}
|
||||
|
||||
tmp, err := os.MkdirTemp("", "nvm4w-regitration-*")
|
||||
if err != nil {
|
||||
return fmt.Errorf("scheduling error: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
script := fmt.Sprintf(`
|
||||
@echo off
|
||||
set errorlog="error.log"
|
||||
set output="%s\output.log"
|
||||
schtasks /create /tn "%s" /tr "cmd.exe /c %s" /sc %s /st %s /F > %%output%% 2>&1
|
||||
if not errorlevel 0 (
|
||||
echo ERROR: Failed to create scheduled task: exit code: %%errorlevel%% >> %%errorlog%%
|
||||
type %%output%% >> %%errorlog%%
|
||||
exit /b %%errorlevel%%
|
||||
)
|
||||
`, tmp, name, escapeBackslashes(command), strings.ToLower(interval), start)
|
||||
|
||||
err = os.WriteFile(filepath.Join(tmp, "schedule.bat"), []byte(script), os.ModePerm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("scheduling error: %v", err)
|
||||
}
|
||||
|
||||
cmd := exec.Command(filepath.Join(tmp, "schedule.bat"))
|
||||
|
||||
// Capture standard output and standard error
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("scheduling error: %v\n%s", err, out)
|
||||
}
|
||||
|
||||
// fmt.Sprintf(`"%s" task scheduled successfully!`, name)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func UnscheduleTask(name string) error {
|
||||
tmp, err := os.MkdirTemp("", "nvm4w-registration-*")
|
||||
if err != nil {
|
||||
return fmt.Errorf("scheduling error: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
script := fmt.Sprintf(`
|
||||
@echo off
|
||||
set errorlog="error.log"
|
||||
set output="%s\output.log"
|
||||
schtasks /delete /tn "%s" /f > %%output%% 2>&1
|
||||
if not errorlevel 0 (
|
||||
echo failed to remove scheduled task: exit code: %%errorlevel%% >> %%errorlog%%
|
||||
type %%output%% >> %%errorlog%%
|
||||
exit /b %%errorlevel%%
|
||||
)
|
||||
`, tmp, name)
|
||||
|
||||
err = os.WriteFile(filepath.Join(tmp, "unschedule.bat"), []byte(script), os.ModePerm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unscheduling error: %v", err)
|
||||
}
|
||||
|
||||
cmd := exec.Command(filepath.Join(tmp, "unschedule.bat"))
|
||||
|
||||
// Capture standard output and standard error
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unscheduling error: %v\n%s", err, out)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@@ -7,16 +7,20 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"nvm/author"
|
||||
"nvm/semver"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/coreybutler/go-fsutil"
|
||||
"github.com/ncruces/zenity"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
@@ -36,6 +40,28 @@ const (
|
||||
// exclamationIcon = "❗"
|
||||
)
|
||||
|
||||
type Notification struct {
|
||||
AppID string `json:"app_id"`
|
||||
Title string `json:"title"`
|
||||
Message string `json:"message"`
|
||||
Icon string `json:"icon"`
|
||||
Actions []Action `json:"actions"`
|
||||
Duration string `json:"duration"`
|
||||
Link string `json:"link"`
|
||||
}
|
||||
|
||||
type Action struct {
|
||||
Type string `json:"type"`
|
||||
Label string `json:"label"`
|
||||
URI string `json:"uri"`
|
||||
}
|
||||
|
||||
func display(data Notification) {
|
||||
data.AppID = "NVM for Windows"
|
||||
content, _ := json.Marshal(data)
|
||||
go author.Bridge("notify", string(content))
|
||||
}
|
||||
|
||||
type Update struct {
|
||||
Version string `json:"version"`
|
||||
Assets []string `json:"assets"`
|
||||
@@ -44,6 +70,442 @@ type Update struct {
|
||||
SourceURL string `json:"sourceTpl"`
|
||||
}
|
||||
|
||||
func Run(version string) error {
|
||||
show_progress := false
|
||||
for _, arg := range os.Args[2:] {
|
||||
if strings.ToLower(arg) == "--show-progress-ui" {
|
||||
show_progress = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
status := make(chan Status)
|
||||
|
||||
if !show_progress {
|
||||
// Setup signal handling first
|
||||
signalChan := make(chan os.Signal, 1)
|
||||
signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM)
|
||||
defer signal.Stop(signalChan)
|
||||
|
||||
// Add signal handler
|
||||
go func() {
|
||||
<-signalChan
|
||||
fmt.Println("Installation canceled by user")
|
||||
os.Exit(0)
|
||||
}()
|
||||
|
||||
return run(version, status)
|
||||
}
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
|
||||
var dlg zenity.ProgressDialog
|
||||
var exitCode = 0
|
||||
var u *Update
|
||||
|
||||
// Display visual progress UI
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
for {
|
||||
select {
|
||||
case s := <-status:
|
||||
if s.Cancel {
|
||||
display(Notification{
|
||||
Title: "Installation Canceled",
|
||||
Message: fmt.Sprintf("Installation of NVM for Windows v%s was canceled by the user.", u.Version),
|
||||
Icon: "error",
|
||||
Actions: []Action{
|
||||
{Type: "protocol", Label: "Install Again", URI: fmt.Sprintf("nvm://launch?action=upgrade&version=%s", u.Version)},
|
||||
},
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if s.Err != nil {
|
||||
exitCode = 1
|
||||
display(Notification{
|
||||
Title: "Installation Error",
|
||||
Message: s.Err.Error(),
|
||||
Icon: "error",
|
||||
})
|
||||
|
||||
dlg.Text(fmt.Sprintf("error: %v", s.Err))
|
||||
dlg.Close()
|
||||
|
||||
// err := restore(status, verbose)
|
||||
// if err != nil {
|
||||
// if show_progress {
|
||||
// display(notify.Notification{
|
||||
// Title: "Rollback Error",
|
||||
// Message: err.Error(),
|
||||
// Icon: "error",
|
||||
// })
|
||||
// } else {
|
||||
// fmt.Printf("rollback error: %v\n", err)
|
||||
// }
|
||||
// }
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if s.Done {
|
||||
display(Notification{
|
||||
Title: "Upgrade Complete",
|
||||
Message: fmt.Sprintf("Now running version %s.", u.Version),
|
||||
Icon: "success",
|
||||
})
|
||||
|
||||
dlg.Text("Upgrade complete")
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if s.Warn != "" {
|
||||
display(Notification{
|
||||
Message: s.Warn,
|
||||
Icon: "nvm",
|
||||
})
|
||||
}
|
||||
|
||||
var empty string
|
||||
if s.Text != empty && len(strings.TrimSpace(s.Text)) > 0 {
|
||||
dlg.Text(s.Text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Wait for the prior subroutine to initialize before starting the next dependent thread
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
|
||||
// Run the update
|
||||
go func() {
|
||||
exe, _ := os.Executable()
|
||||
winIco := filepath.Join(filepath.Dir(exe), "nvm.ico")
|
||||
ico := filepath.Join(filepath.Dir(exe), "download.ico")
|
||||
|
||||
var err error
|
||||
u, err = checkForUpdate(UPDATE_URL)
|
||||
if err != nil {
|
||||
display(Notification{
|
||||
Title: "Update Error",
|
||||
Message: err.Error(),
|
||||
Icon: "error",
|
||||
})
|
||||
|
||||
status <- Status{Err: fmt.Errorf("error: failed to obtain update data: %v\n", err)}
|
||||
}
|
||||
|
||||
var perr error
|
||||
dlg, perr = zenity.Progress(
|
||||
zenity.Title(fmt.Sprintf("Installing NVM for Windows v%s", u.Version)),
|
||||
zenity.Icon(ico),
|
||||
zenity.WindowIcon(winIco),
|
||||
zenity.AutoClose(),
|
||||
zenity.NoCancel(),
|
||||
zenity.Pulsate())
|
||||
|
||||
if perr != nil {
|
||||
fmt.Println("Failed to create progress dialog")
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-dlg.Done():
|
||||
if err := dlg.Complete(); err == zenity.ErrCanceled {
|
||||
status <- Status{Cancel: true}
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
status <- Status{Text: "Validating version..."}
|
||||
|
||||
run(version, status, u)
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
os.Exit(exitCode)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func run(version string, status chan Status, updateMetadata ...*Update) error {
|
||||
args := os.Args[2:]
|
||||
|
||||
colorize := true
|
||||
if err := EnableVirtualTerminalProcessing(); err != nil {
|
||||
colorize = false
|
||||
}
|
||||
|
||||
// Retrieve remote metadata
|
||||
var update *Update
|
||||
if len(updateMetadata) > 0 {
|
||||
update = updateMetadata[0]
|
||||
} else {
|
||||
var err error
|
||||
update, err = checkForUpdate(UPDATE_URL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error: failed to obtain update data: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, warning := range update.Warnings {
|
||||
status <- Status{Warn: warning}
|
||||
Warn(warning, colorize)
|
||||
}
|
||||
|
||||
verbose := false
|
||||
// rollback := false
|
||||
for _, arg := range args {
|
||||
switch strings.ToLower(arg) {
|
||||
case "--verbose":
|
||||
verbose = true
|
||||
// case "rollback":
|
||||
// rollback = true
|
||||
}
|
||||
}
|
||||
|
||||
// // Check for a backup
|
||||
// if rollback {
|
||||
// if fsutil.Exists(filepath.Join(".", ".update", "nvm4w-backup.zip")) {
|
||||
// fmt.Println("restoring NVM4W backup...")
|
||||
// rbtmp, err := os.MkdirTemp("", "nvm-rollback-*")
|
||||
// if err != nil {
|
||||
// fmt.Printf("error: failed to create rollback directory: %v\n", err)
|
||||
// os.Exit(1)
|
||||
// }
|
||||
// defer os.RemoveAll(rbtmp)
|
||||
|
||||
// err = unzip(filepath.Join(".", ".update", "nvm4w-backup.zip"), rbtmp)
|
||||
// if err != nil {
|
||||
// fmt.Printf("error: failed to extract backup: %v\n", err)
|
||||
// os.Exit(1)
|
||||
// }
|
||||
|
||||
// // Copy the backup files to the current directory
|
||||
// err = copyDirContents(rbtmp, ".")
|
||||
// if err != nil {
|
||||
// fmt.Printf("error: failed to restore backup files: %v\n", err)
|
||||
// os.Exit(1)
|
||||
// }
|
||||
|
||||
// // Remove the restoration directory
|
||||
// os.RemoveAll(filepath.Join(".", ".update"))
|
||||
|
||||
// fmt.Println("rollback complete")
|
||||
// rbcmd := exec.Command("nvm.exe", "version")
|
||||
// o, err := rbcmd.Output()
|
||||
// if err != nil {
|
||||
// fmt.Println("error running nvm.exe:", err)
|
||||
// os.Exit(1)
|
||||
// }
|
||||
|
||||
// exec.Command("schtasks", "/delete", "/tn", "\"RemoveNVM4WBackup\"", "/f").Run()
|
||||
// fmt.Printf("rollback to v%s complete\n", string(o))
|
||||
// os.Exit(0)
|
||||
// } else {
|
||||
// fmt.Println("no backup available: backups are only available for 7 days after upgrading")
|
||||
// os.Exit(0)
|
||||
// }
|
||||
// }
|
||||
|
||||
currentVersion, err := semver.New(version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
updateVersion, err := semver.New(update.Version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if currentVersion.LT(updateVersion) {
|
||||
if len(update.VersionWarnings) > 0 {
|
||||
if len(update.Warnings) > 0 || len(update.VersionWarnings) > 0 {
|
||||
fmt.Println("")
|
||||
}
|
||||
for _, warning := range update.VersionWarnings {
|
||||
status <- Status{Warn: warning}
|
||||
Warn(warning, colorize)
|
||||
}
|
||||
fmt.Println("")
|
||||
}
|
||||
status <- Status{Text: "downloading..."}
|
||||
fmt.Printf("upgrading from v%s-->%s\n\ndownloading...\n", version, highlight(update.Version))
|
||||
} else {
|
||||
status <- Status{Text: "nvm is up to date", Done: true}
|
||||
fmt.Println("nvm is up to date")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Make temp directory
|
||||
tmp, err := os.MkdirTemp("", "nvm-upgrade-*")
|
||||
if err != nil {
|
||||
status <- Status{Err: err}
|
||||
return fmt.Errorf("error: failed to create temporary directory: %v\n", err)
|
||||
}
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
// Download the new app
|
||||
source := fmt.Sprintf(update.SourceURL, update.Version)
|
||||
// source := fmt.Sprintf(update.SourceURL, "1.1.11") // testing
|
||||
body, err := get(source)
|
||||
if err != nil {
|
||||
status <- Status{Err: err}
|
||||
return fmt.Errorf("error: failed to download new version: %v\n", err)
|
||||
}
|
||||
|
||||
os.WriteFile(filepath.Join(tmp, "assets.zip"), body, os.ModePerm)
|
||||
os.Mkdir(filepath.Join(tmp, "assets"), os.ModePerm)
|
||||
|
||||
source = source + ".checksum.txt"
|
||||
body, err = get(source)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error: failed to download checksum: %v\n", err)
|
||||
}
|
||||
|
||||
os.WriteFile(filepath.Join(tmp, "assets.zip.checksum.txt"), body, os.ModePerm)
|
||||
|
||||
filePath := filepath.Join(tmp, "assets.zip") // path to the file you want to validate
|
||||
checksumFile := filepath.Join(tmp, "assets.zip.checksum.txt") // path to the checksum file
|
||||
|
||||
// Step 1: Compute the MD5 checksum of the file
|
||||
fmt.Println("verifying checksum...")
|
||||
status <- Status{Text: "verifying checksum..."}
|
||||
computedChecksum, err := computeMD5Checksum(filePath)
|
||||
if err != nil {
|
||||
status <- Status{Err: err}
|
||||
return fmt.Errorf("Error computing checksum: %v", err)
|
||||
}
|
||||
|
||||
// Step 2: Read the checksum from the .checksum.txt file
|
||||
storedChecksum, err := readChecksumFromFile(checksumFile)
|
||||
if err != nil {
|
||||
status <- Status{Err: err}
|
||||
return fmt.Errorf("Error readirng checksum from file: %v", err)
|
||||
}
|
||||
|
||||
// Step 3: Compare the computed checksum with the stored checksum
|
||||
if strings.ToLower(computedChecksum) != strings.ToLower(storedChecksum) {
|
||||
status <- Status{Err: fmt.Errorf("cannot validate update file (checksum mismatch)")}
|
||||
return fmt.Errorf("cannot validate update file (checksum mismatch)")
|
||||
}
|
||||
|
||||
fmt.Println("extracting update...")
|
||||
status <- Status{Text: "extracting update..."}
|
||||
if err := unzip(filepath.Join(tmp, "assets.zip"), filepath.Join(tmp, "assets")); err != nil {
|
||||
status <- Status{Err: err}
|
||||
return err
|
||||
}
|
||||
|
||||
// Get any additional assets
|
||||
if len(update.Assets) > 0 {
|
||||
status <- Status{Text: fmt.Sprintf("downloading %d additional assets...", len(update.Assets))}
|
||||
fmt.Printf("downloading %d additional assets...\n", len(update.Assets))
|
||||
for _, asset := range update.Assets {
|
||||
var assetURL string
|
||||
if !strings.HasPrefix(asset, "http") {
|
||||
assetURL = fmt.Sprintf(update.SourceURL, asset)
|
||||
} else {
|
||||
assetURL = asset
|
||||
}
|
||||
assetBody, err := get(assetURL)
|
||||
if err != nil {
|
||||
status <- Status{Err: err}
|
||||
return fmt.Errorf("error: failed to download asset: %v\n", err)
|
||||
}
|
||||
|
||||
assetPath := filepath.Join(tmp, "assets", asset)
|
||||
os.WriteFile(assetPath, assetBody, os.ModePerm)
|
||||
}
|
||||
}
|
||||
|
||||
// Debugging
|
||||
if verbose {
|
||||
tree(tmp, "downloaded files (extracted):")
|
||||
nvmtestcmd := exec.Command(filepath.Join(tmp, "assets", "nvm.exe"), "version")
|
||||
nvmtestcmd.Stdout = os.Stdout
|
||||
nvmtestcmd.Stderr = os.Stderr
|
||||
err = nvmtestcmd.Run()
|
||||
if err != nil {
|
||||
fmt.Println("error running nvm.exe:", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Backup current version to zip
|
||||
fmt.Println("applying update...")
|
||||
status <- Status{Text: "applying update..."}
|
||||
currentExe, _ := os.Executable()
|
||||
currentPath := filepath.Dir(currentExe)
|
||||
bkp, err := os.MkdirTemp("", "nvm-backup-*")
|
||||
if err != nil {
|
||||
status <- Status{Err: fmt.Errorf("error: failed to create backup directory: %v\n", err)}
|
||||
return fmt.Errorf("error: failed to create backup directory: %v\n", err)
|
||||
}
|
||||
defer os.RemoveAll(bkp)
|
||||
|
||||
err = zipDirectory(currentPath, filepath.Join(bkp, "backup.zip"))
|
||||
if err != nil {
|
||||
status <- Status{Err: fmt.Errorf("error: failed to create backup: %v\n", err)}
|
||||
return fmt.Errorf("error: failed to create backup: %v\n", err)
|
||||
}
|
||||
|
||||
os.MkdirAll(filepath.Join(currentPath, ".update"), os.ModePerm)
|
||||
copyFile(filepath.Join(bkp, "backup.zip"), filepath.Join(currentPath, ".update", "nvm4w-backup.zip"))
|
||||
|
||||
// Copy the new files to the current directory
|
||||
// copyFile(currentExe, fmt.Sprintf("%s.%s.bak", currentExe, version))
|
||||
copyDirContents(filepath.Join(tmp, "assets"), currentPath)
|
||||
copyFile(filepath.Join(tmp, "assets", "nvm.exe"), filepath.Join(currentPath, ".update/nvm.exe"))
|
||||
|
||||
if verbose {
|
||||
nvmtestcmd := exec.Command(filepath.Join(currentPath, ".update/nvm.exe"), "version")
|
||||
nvmtestcmd.Stdout = os.Stdout
|
||||
nvmtestcmd.Stderr = os.Stderr
|
||||
err = nvmtestcmd.Run()
|
||||
if err != nil {
|
||||
status <- Status{Err: err}
|
||||
fmt.Println("error running nvm.exe:", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Debugging
|
||||
if verbose {
|
||||
tree(currentPath, "final directory contents:")
|
||||
}
|
||||
|
||||
// Hide the update directory
|
||||
setHidden(filepath.Join(currentPath, ".update"))
|
||||
|
||||
// If an "update.exe" exists, run it
|
||||
if fsutil.IsExecutable(filepath.Join(tmp, "assets", "update.exe")) {
|
||||
err = copyFile(filepath.Join(tmp, "assets", "update.exe"), filepath.Join(currentPath, ".update", "update.exe"))
|
||||
if err != nil {
|
||||
status <- Status{Err: err}
|
||||
fmt.Println(fmt.Errorf("error: failed to copy update.exe: %v\n", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
autoupdate(status)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type Status struct {
|
||||
Text string
|
||||
Err error
|
||||
Done bool
|
||||
Help bool
|
||||
Cancel bool
|
||||
Warn string
|
||||
}
|
||||
|
||||
func (u *Update) Available(sinceVersion string) (string, bool, error) {
|
||||
currentVersion, err := semver.New(sinceVersion)
|
||||
if err != nil {
|
||||
@@ -70,259 +532,14 @@ func Warn(msg string, colorized ...bool) {
|
||||
}
|
||||
}
|
||||
|
||||
func Run(version string) error {
|
||||
args := os.Args[2:]
|
||||
|
||||
colorize := true
|
||||
if err := EnableVirtualTerminalProcessing(); err != nil {
|
||||
colorize = false
|
||||
}
|
||||
|
||||
// Retrieve remote metadata
|
||||
update, err := checkForUpdate(UPDATE_URL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error: failed to obtain update data: %v\n", err)
|
||||
}
|
||||
|
||||
for _, warning := range update.Warnings {
|
||||
Warn(warning, colorize)
|
||||
}
|
||||
|
||||
verbose := false
|
||||
rollback := false
|
||||
for _, arg := range args {
|
||||
switch strings.ToLower(arg) {
|
||||
case "--verbose":
|
||||
verbose = true
|
||||
case "rollback":
|
||||
rollback = true
|
||||
}
|
||||
}
|
||||
|
||||
// Check for a backup
|
||||
if rollback {
|
||||
if fsutil.Exists(filepath.Join(".", ".update", "nvm4w-backup.zip")) {
|
||||
fmt.Println("restoring NVM4W backup...")
|
||||
rbtmp, err := os.MkdirTemp("", "nvm-rollback-*")
|
||||
if err != nil {
|
||||
fmt.Printf("error: failed to create rollback directory: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer os.RemoveAll(rbtmp)
|
||||
|
||||
err = unzip(filepath.Join(".", ".update", "nvm4w-backup.zip"), rbtmp)
|
||||
if err != nil {
|
||||
fmt.Printf("error: failed to extract backup: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Copy the backup files to the current directory
|
||||
err = copyDirContents(rbtmp, ".")
|
||||
if err != nil {
|
||||
fmt.Printf("error: failed to restore backup files: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Remove the restoration directory
|
||||
os.RemoveAll(filepath.Join(".", ".update"))
|
||||
|
||||
fmt.Println("rollback complete")
|
||||
rbcmd := exec.Command("nvm.exe", "version")
|
||||
o, err := rbcmd.Output()
|
||||
if err != nil {
|
||||
fmt.Println("error running nvm.exe:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
exec.Command("schtasks", "/delete", "/tn", "\"RemoveNVM4WBackup\"", "/f").Run()
|
||||
fmt.Printf("rollback to v%s complete\n", string(o))
|
||||
os.Exit(0)
|
||||
} else {
|
||||
fmt.Println("no backup available: backups are only available for 7 days after upgrading")
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
currentVersion, err := semver.New(version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
updateVersion, err := semver.New(update.Version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if currentVersion.LT(updateVersion) {
|
||||
if len(update.VersionWarnings) > 0 {
|
||||
if len(update.Warnings) > 0 || len(update.VersionWarnings) > 0 {
|
||||
fmt.Println("")
|
||||
}
|
||||
for _, warning := range update.VersionWarnings {
|
||||
Warn(warning, colorize)
|
||||
}
|
||||
fmt.Println("")
|
||||
}
|
||||
fmt.Printf("upgrading from v%s-->%s\n\ndownloading...\n", version, highlight(update.Version))
|
||||
} else {
|
||||
fmt.Println("nvm is up to date")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Make temp directory
|
||||
tmp, err := os.MkdirTemp("", "nvm-upgrade-*")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error: failed to create temporary directory: %v\n", err)
|
||||
}
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
// Download the new app
|
||||
// TODO: Replace version with update.Version
|
||||
// source := fmt.Sprintf(update.SourceURL, update.Version)
|
||||
source := fmt.Sprintf(update.SourceURL, "1.1.11")
|
||||
body, err := get(source)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error: failed to download new version: %v\n", err)
|
||||
}
|
||||
|
||||
os.WriteFile(filepath.Join(tmp, "assets.zip"), body, os.ModePerm)
|
||||
os.Mkdir(filepath.Join(tmp, "assets"), os.ModePerm)
|
||||
|
||||
source = source + ".checksum.txt"
|
||||
body, err = get(source)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error: failed to download checksum: %v\n", err)
|
||||
}
|
||||
|
||||
os.WriteFile(filepath.Join(tmp, "assets.zip.checksum.txt"), body, os.ModePerm)
|
||||
|
||||
filePath := filepath.Join(tmp, "assets.zip") // path to the file you want to validate
|
||||
checksumFile := filepath.Join(tmp, "assets.zip.checksum.txt") // path to the checksum file
|
||||
|
||||
// Step 1: Compute the MD5 checksum of the file
|
||||
fmt.Println("verifying checksum...")
|
||||
computedChecksum, err := computeMD5Checksum(filePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error computing checksum: %v", err)
|
||||
}
|
||||
|
||||
// Step 2: Read the checksum from the .checksum.txt file
|
||||
storedChecksum, err := readChecksumFromFile(checksumFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error readirng checksum from file: %v", err)
|
||||
}
|
||||
|
||||
// Step 3: Compare the computed checksum with the stored checksum
|
||||
if strings.ToLower(computedChecksum) != strings.ToLower(storedChecksum) {
|
||||
return fmt.Errorf("cannot validate update file (checksum mismatch)")
|
||||
}
|
||||
|
||||
fmt.Println("extracting update...")
|
||||
if err := unzip(filepath.Join(tmp, "assets.zip"), filepath.Join(tmp, "assets")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get any additional assets
|
||||
if len(update.Assets) > 0 {
|
||||
fmt.Printf("downloading %d additional assets...\n", len(update.Assets))
|
||||
for _, asset := range update.Assets {
|
||||
var assetURL string
|
||||
if !strings.HasPrefix(asset, "http") {
|
||||
assetURL = fmt.Sprintf(update.SourceURL, asset)
|
||||
} else {
|
||||
assetURL = asset
|
||||
}
|
||||
assetBody, err := get(assetURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error: failed to download asset: %v\n", err)
|
||||
}
|
||||
|
||||
assetPath := filepath.Join(tmp, "assets", asset)
|
||||
os.WriteFile(assetPath, assetBody, os.ModePerm)
|
||||
}
|
||||
}
|
||||
|
||||
// Debugging
|
||||
if verbose {
|
||||
tree(tmp, "downloaded files (extracted):")
|
||||
nvmtestcmd := exec.Command(filepath.Join(tmp, "assets", "nvm.exe"), "version")
|
||||
nvmtestcmd.Stdout = os.Stdout
|
||||
nvmtestcmd.Stderr = os.Stderr
|
||||
err = nvmtestcmd.Run()
|
||||
if err != nil {
|
||||
fmt.Println("error running nvm.exe:", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Backup current version to zip
|
||||
fmt.Println("applying update...")
|
||||
currentExe, _ := os.Executable()
|
||||
currentPath := filepath.Dir(currentExe)
|
||||
bkp, err := os.MkdirTemp("", "nvm-backup-*")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error: failed to create backup directory: %v\n", err)
|
||||
}
|
||||
defer os.RemoveAll(bkp)
|
||||
|
||||
err = zipDirectory(currentPath, filepath.Join(bkp, "backup.zip"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error: failed to create backup: %v\n", err)
|
||||
}
|
||||
|
||||
os.MkdirAll(filepath.Join(currentPath, ".update"), os.ModePerm)
|
||||
copyFile(filepath.Join(bkp, "backup.zip"), filepath.Join(currentPath, ".update", "nvm4w-backup.zip"))
|
||||
|
||||
// Copy the new files to the current directory
|
||||
// copyFile(currentExe, fmt.Sprintf("%s.%s.bak", currentExe, version))
|
||||
copyDirContents(filepath.Join(tmp, "assets"), currentPath)
|
||||
copyFile(filepath.Join(tmp, "assets", "nvm.exe"), filepath.Join(currentPath, ".update/nvm.exe"))
|
||||
|
||||
if verbose {
|
||||
nvmtestcmd := exec.Command(filepath.Join(currentPath, ".update/nvm.exe"), "version")
|
||||
nvmtestcmd.Stdout = os.Stdout
|
||||
nvmtestcmd.Stderr = os.Stderr
|
||||
err = nvmtestcmd.Run()
|
||||
if err != nil {
|
||||
fmt.Println("error running nvm.exe:", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: schedule removal of .backup folder for 30 days from now
|
||||
// TODO: warn user that the restore function is available for 30 days
|
||||
|
||||
// Debugging
|
||||
if verbose {
|
||||
tree(currentPath, "final directory contents:")
|
||||
}
|
||||
|
||||
// Hide the update directory
|
||||
setHidden(filepath.Join(currentPath, ".update"))
|
||||
|
||||
// The upgrade process should be able to roll back if there is a failure.
|
||||
// TODO: Upgrade the registry data to reflect the new version
|
||||
// Potentially provide a desktop notification when the upgrade is complete.
|
||||
|
||||
// If an "update.exe" exists, run it
|
||||
if fsutil.IsExecutable(filepath.Join(tmp, "assets", "update.exe")) {
|
||||
err = copyFile(filepath.Join(tmp, "assets", "update.exe"), filepath.Join(currentPath, ".update", "update.exe"))
|
||||
if err != nil {
|
||||
fmt.Println(fmt.Errorf("error: failed to copy update.exe: %v\n", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
autoupdate()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Get() (*Update, error) {
|
||||
return checkForUpdate(UPDATE_URL)
|
||||
}
|
||||
|
||||
func autoupdate() {
|
||||
func autoupdate(status chan Status) {
|
||||
currentPath, err := os.Executable()
|
||||
if err != nil {
|
||||
status <- Status{Err: err}
|
||||
fmt.Println("error getting updater path:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -334,9 +551,12 @@ func autoupdate() {
|
||||
// Temporary batch file that deletes the directory and the scheduled task
|
||||
tmp, err := os.MkdirTemp("", "nvm4w-remove-*")
|
||||
if err != nil {
|
||||
status <- Status{Err: err}
|
||||
fmt.Printf("error creating temporary directory: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// schedule removal of restoration folder for 30 days from now
|
||||
tempBatchFile := filepath.Join(tmp, "remove_backup.bat")
|
||||
now := time.Now()
|
||||
futureDate := now.AddDate(0, 0, 7)
|
||||
@@ -350,6 +570,7 @@ rmdir /s /q "%s"
|
||||
// Write the batch file to a temporary location
|
||||
err = os.WriteFile(tempBatchFile, []byte(batchContent), os.ModePerm)
|
||||
if err != nil {
|
||||
status <- Status{Err: err}
|
||||
fmt.Printf("error creating temporary batch file: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -422,11 +643,13 @@ echo Update complete >> error.log
|
||||
del error.log
|
||||
|
||||
del "%%~f0"
|
||||
start "nvm://launch?action=upgrade_notify"
|
||||
exit /b 0
|
||||
`, escapeBackslashes(tempBatchFile), formattedDate, escapeBackslashes(tempBatchFile), formattedDate)
|
||||
|
||||
err = os.WriteFile(scriptPath, []byte(updaterScript), os.ModePerm) // Use standard Windows file permissions
|
||||
if err != nil {
|
||||
status <- Status{Err: err}
|
||||
fmt.Printf("error creating updater script: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -435,12 +658,15 @@ exit /b 0
|
||||
cmd := exec.Command(scriptPath, fmt.Sprintf("%d", os.Getpid()), filepath.Join(tempDir, ".update", "nvm.exe"), currentPath)
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
status <- Status{Err: err}
|
||||
fmt.Printf("error starting updater script: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Exit the current process (delay for cleanup)
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
status <- Status{Text: "Restarting app...", Done: true}
|
||||
time.Sleep(2 * time.Second)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
|
@@ -23,6 +23,7 @@ import (
|
||||
fs "github.com/coreybutler/go-fsutil"
|
||||
)
|
||||
|
||||
var nvmversion = ""
|
||||
var client = &http.Client{}
|
||||
var nodeBaseAddress = "https://nodejs.org/dist/"
|
||||
var npmBaseAddress = "https://github.com/npm/cli/archive/"
|
||||
@@ -130,8 +131,7 @@ func Download(url string, target string, version string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// TODO: Add version to user agent
|
||||
req.Header.Set("User-Agent", fmt.Sprintf("NVM for Windows %s", version))
|
||||
req.Header.Set("User-Agent", fmt.Sprintf("NVM for Windows %s", nvmversion))
|
||||
|
||||
response, err := client.Do(req)
|
||||
if err != nil {
|
||||
@@ -166,7 +166,7 @@ func Download(url string, target string, version string) bool {
|
||||
} else {
|
||||
_, err = io.Copy(output, response.Body)
|
||||
if err != nil {
|
||||
fmt.Println("Error while downloading", url, "-", err)
|
||||
fmt.Printf("Error while downloading %s: %v\n", url, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user