mirror of
https://github.com/coreybutler/nvm-windows.git
synced 2026-01-13 06:04:05 +08:00
Added upgrade and reinstall commands
This commit is contained in:
@@ -2,15 +2,18 @@ module nvm
|
||||
|
||||
go 1.18
|
||||
|
||||
// replace github.com/coreybutler/go-fsutil => C:\Users\corey\OneDrive\Documents\workspace\oss\coreybutler\go-fsutil
|
||||
|
||||
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/olekukonko/tablewriter v0.0.5
|
||||
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d
|
||||
golang.org/x/sys v0.1.0
|
||||
)
|
||||
|
||||
require github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||
require (
|
||||
aead.dev/minisign v0.2.0 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||
golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b // indirect
|
||||
)
|
||||
|
||||
16
src/go.sum
16
src/go.sum
@@ -1,3 +1,5 @@
|
||||
aead.dev/minisign v0.2.0 h1:kAWrq/hBRu4AARY6AlciO83xhNnW9UaC8YipS2uhLPk=
|
||||
aead.dev/minisign v0.2.0/go.mod h1:zdq6LdSd9TbuSxchxwhpA9zEb9YXcVGoE8JakuiGaIQ=
|
||||
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=
|
||||
@@ -6,6 +8,8 @@ github.com/coreybutler/go-where v1.0.2 h1:Omit67KeTtKpvSJjezVxnVD4qMtvlXDlItiKpV
|
||||
github.com/coreybutler/go-where v1.0.2/go.mod h1:IqV4saJiDXdNJURfTfVRywDHvY1IG5F+GXb2kmnmEe8=
|
||||
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/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA=
|
||||
@@ -13,17 +17,29 @@ github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj
|
||||
github.com/zmb3/gogetdoc v0.0.0-20190228002656-b37376c5da6a/go.mod h1:ofmGw6LrMypycsiWcyug6516EXpIxSbZ+uI9ppGypfY=
|
||||
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/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/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=
|
||||
|
||||
203
src/nvm.go
203
src/nvm.go
@@ -22,10 +22,12 @@ import (
|
||||
"nvm/encoding"
|
||||
"nvm/file"
|
||||
"nvm/node"
|
||||
"nvm/upgrade"
|
||||
"nvm/web"
|
||||
|
||||
"github.com/blang/semver"
|
||||
// "github.com/fatih/color"
|
||||
|
||||
"github.com/coreybutler/go-where"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
"golang.org/x/sys/windows"
|
||||
@@ -33,7 +35,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
NvmVersion = "1.1.11"
|
||||
NvmVersion = "1.2.0"
|
||||
)
|
||||
|
||||
type Environment struct {
|
||||
@@ -70,10 +72,10 @@ func main() {
|
||||
detail := ""
|
||||
procarch := arch.Validate(env.arch)
|
||||
|
||||
if !isTerminal() {
|
||||
alert("NVM for Windows should be run from a terminal such as CMD or PowerShell.", "Terminal Only")
|
||||
os.Exit(0)
|
||||
}
|
||||
// if !isTerminal() {
|
||||
// alert("NVM for Windows should be run from a terminal such as CMD or PowerShell.", "Terminal Only")
|
||||
// os.Exit(0)
|
||||
// }
|
||||
|
||||
// Capture any additional arguments
|
||||
if len(args) > 2 {
|
||||
@@ -99,6 +101,8 @@ func main() {
|
||||
install(detail, procarch)
|
||||
case "uninstall":
|
||||
uninstall(detail)
|
||||
case "reinstall":
|
||||
reinstall(detail, procarch)
|
||||
case "use":
|
||||
use(detail, procarch)
|
||||
case "list":
|
||||
@@ -167,6 +171,8 @@ func main() {
|
||||
setNpmMirror(detail)
|
||||
case "debug":
|
||||
checkLocalEnvironment()
|
||||
case "upgrade":
|
||||
upgrade.Run(NvmVersion)
|
||||
default:
|
||||
help()
|
||||
}
|
||||
@@ -185,13 +191,13 @@ func setNpmMirror(uri string) {
|
||||
saveSettings()
|
||||
}
|
||||
|
||||
func isTerminal() bool {
|
||||
fileInfo, err := os.Stdout.Stat()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return (fileInfo.Mode() & os.ModeCharDevice) != 0
|
||||
}
|
||||
// func isTerminal() bool {
|
||||
// fileInfo, err := os.Stdout.Stat()
|
||||
// if err != nil {
|
||||
// return false
|
||||
// }
|
||||
// return (fileInfo.Mode() & os.ModeCharDevice) != 0
|
||||
// }
|
||||
|
||||
// const (
|
||||
// MB_YESNOCANCEL = 0x00000003
|
||||
@@ -263,23 +269,23 @@ func isTerminal() bool {
|
||||
// )
|
||||
// }
|
||||
|
||||
func alert(msg string, caption ...string) {
|
||||
user32 := windows.NewLazySystemDLL("user32.dll")
|
||||
mbox := user32.NewProc("MessageBoxW")
|
||||
getForegroundWindow := user32.NewProc("GetForegroundWindow")
|
||||
var hwnd uintptr
|
||||
ret, _, _ := getForegroundWindow.Call()
|
||||
if ret != 0 {
|
||||
hwnd = ret
|
||||
}
|
||||
// func alert(msg string, caption ...string) {
|
||||
// user32 := windows.NewLazySystemDLL("user32.dll")
|
||||
// mbox := user32.NewProc("MessageBoxW")
|
||||
// getForegroundWindow := user32.NewProc("GetForegroundWindow")
|
||||
// var hwnd uintptr
|
||||
// ret, _, _ := getForegroundWindow.Call()
|
||||
// if ret != 0 {
|
||||
// hwnd = ret
|
||||
// }
|
||||
|
||||
title := "Alert"
|
||||
if len(caption) > 0 {
|
||||
title = caption[0]
|
||||
}
|
||||
// title := "Alert"
|
||||
// if len(caption) > 0 {
|
||||
// title = caption[0]
|
||||
// }
|
||||
|
||||
mbox.Call(hwnd, uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(msg))), uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(title))), uintptr(windows.MB_OK))
|
||||
}
|
||||
// mbox.Call(hwnd, uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(msg))), uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(title))), uintptr(windows.MB_OK))
|
||||
// }
|
||||
|
||||
/*
|
||||
func update() {
|
||||
@@ -474,6 +480,7 @@ func install(version string, cpuarch string) {
|
||||
npmv := getNpmVersion(version)
|
||||
fmt.Println("npm v" + npmv + " installed successfully.")
|
||||
fmt.Println("\n\nInstallation complete. If you want to use this version, type\n\nnvm use " + version)
|
||||
// fmt.Printf("Installed to %v\n", filepath.Join(env.root, "v"+version))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -557,7 +564,57 @@ func install(version string, cpuarch string) {
|
||||
fmt.Println("Version " + version + " is already installed.")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func reinstall(version, cpuarch string) {
|
||||
// Make sure a version is specified
|
||||
if len(version) == 0 {
|
||||
fmt.Println("Provide the version you want to uninstall.")
|
||||
help()
|
||||
return
|
||||
}
|
||||
|
||||
if strings.ToLower(version) == "latest" || strings.ToLower(version) == "node" {
|
||||
version = getLatest()
|
||||
} else if strings.ToLower(version) == "lts" {
|
||||
version = getLTS()
|
||||
} else if strings.ToLower(version) == "newest" {
|
||||
installed := node.GetInstalled(env.root)
|
||||
if len(installed) == 0 {
|
||||
fmt.Println("No versions of node.js found. Try installing the latest by typing nvm install latest.")
|
||||
return
|
||||
}
|
||||
|
||||
version = installed[0]
|
||||
}
|
||||
|
||||
version = cleanVersion(version)
|
||||
|
||||
// Determine if the version exists and skip if it doesn't
|
||||
if node.IsVersionInstalled(env.root, version, "32") || node.IsVersionInstalled(env.root, version, "64") {
|
||||
v, _ := node.GetCurrentVersion()
|
||||
|
||||
fmt.Printf("Removing v%v...\n", version)
|
||||
|
||||
if v == version {
|
||||
// _, err := runElevated(fmt.Sprintf(`"%s" cmd /C rmdir "%s"`, filepath.Join(env.root, "elevate.cmd"), filepath.Clean(env.symlink)))
|
||||
_, err := elevatedRun("rmdir", filepath.Clean(env.symlink))
|
||||
if err != nil {
|
||||
fmt.Println(fmt.Sprint(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
e := os.RemoveAll(filepath.Join(env.root, "v"+version))
|
||||
if e != nil {
|
||||
fmt.Printf("error: failed to remove v%v: %v\n", version, e)
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("node v%v is not installed. Type \"nvm list\" to see what is installed.\n", version)
|
||||
}
|
||||
|
||||
install(version, cpuarch)
|
||||
}
|
||||
|
||||
func uninstall(version string) {
|
||||
@@ -1062,11 +1119,9 @@ func checkLocalEnvironment() {
|
||||
} else {
|
||||
consoleTitle := syscall.UTF16ToString(title[:])
|
||||
|
||||
if !strings.Contains(strings.ToLower(consoleTitle), "command prompt") && !strings.Contains(strings.ToLower(consoleTitle), "powershell") {
|
||||
problems = append(problems, fmt.Sprintf("\"%v\" is not an officially supported shell. Some features may not work as expected.\n", consoleTitle))
|
||||
if !strings.Contains(strings.ToLower(consoleTitle), "command prompt") && !strings.Contains(strings.ToLower(consoleTitle), "powershell") && !strings.Contains(strings.ToLower(consoleTitle), "cmd.exe") && !strings.Contains(strings.ToLower(consoleTitle), "pwsh.exe") {
|
||||
problems = append(problems, fmt.Sprintf("\"%v\" not recognized: the Command Prompt and Powershell are the only officially supported consoles. Some features may not work as expected.\n", consoleTitle))
|
||||
}
|
||||
|
||||
fmt.Printf("\n%v", consoleTitle)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1082,7 +1137,7 @@ func checkLocalEnvironment() {
|
||||
}
|
||||
// fmt.Printf(" %d.%d\n", versionInfo.MajorVersion, versionInfo.MinorVersion)
|
||||
maj, min, patch := windows.RtlGetNtVersionNumbers()
|
||||
fmt.Printf("\nWindows Version: %d.%d (Build %d)\n", maj, min, patch)
|
||||
fmt.Printf("\nWindows Version: %d.%d (Build %d)\n", maj, min, patch)
|
||||
|
||||
// SHELL in Linux
|
||||
// TERM in Windows
|
||||
@@ -1119,7 +1174,15 @@ func checkLocalEnvironment() {
|
||||
v := node.GetInstalled(env.root)
|
||||
|
||||
nvmhome := os.Getenv("NVM_HOME")
|
||||
fmt.Printf("\nNVM4W Version: %v\nNVM4W Path: %v\nNVM4W Settings: %v\nNVM_HOME: %v\nNVM_SYMLINK: %v\nNode Installations: %v\n\nTotal Node.js Versions: %v\nActive Node.js Version: %v", NvmVersion, path, home, nvmhome, symlink, env.root, len(v), out)
|
||||
mirrors := "No mirrors configured"
|
||||
if len(env.node_mirror) > 0 && len(env.npm_mirror) > 0 {
|
||||
mirrors = env.node_mirror + " (node) and " + env.npm_mirror + " (npm)"
|
||||
} else if len(env.node_mirror) > 0 {
|
||||
mirrors = env.node_mirror + " (node)"
|
||||
} else if len(env.npm_mirror) > 0 {
|
||||
mirrors = env.npm_mirror + " (npm)"
|
||||
}
|
||||
fmt.Printf("\nNVM4W Version: %v\nNVM4W Path: %v\nNVM4W Settings: %v\nNVM_HOME: %v\nNVM_SYMLINK: %v\nNode Installations: %v\nDefault Architecture: %v-bit\nMirrors: %v\nHTTP Proxy: %v\n\nTotal Node.js Versions: %v\nActive Node.js Version: %v", NvmVersion, path, home, nvmhome, symlink, env.root, env.arch, mirrors, env.proxy, len(v), out)
|
||||
|
||||
if !nvmsymlinkfound {
|
||||
problems = append(problems, "The NVM4W symlink ("+env.symlink+") was not found in the PATH environment variable.")
|
||||
@@ -1138,12 +1201,8 @@ func checkLocalEnvironment() {
|
||||
}
|
||||
} else {
|
||||
if fileInfo.Mode()&os.ModeSymlink != 0 {
|
||||
targetPath, err := os.Readlink(symlink)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
targetFileInfo, err := os.Lstat(targetPath)
|
||||
targetPath, _ := os.Readlink(symlink)
|
||||
targetFileInfo, _ := os.Lstat(targetPath)
|
||||
|
||||
if !targetFileInfo.Mode().IsDir() {
|
||||
problems = append(problems, "NVM_SYMLINK is a symlink linking to a file instead of a directory.")
|
||||
@@ -1153,11 +1212,15 @@ func checkLocalEnvironment() {
|
||||
}
|
||||
}
|
||||
|
||||
if strings.Contains(symlink, home) {
|
||||
problems = append(problems, "Storing the NVM_SYMLINK ("+symlink+") within the NVM_HOME directory ("+home+") has been known to cause problems in many Windows environments. Change NVM_SYMLINK to a different directory that does not already exist.")
|
||||
}
|
||||
|
||||
ipv6, err := web.IsLocalIPv6()
|
||||
if err != nil {
|
||||
problems = append(problems, "Connection type cannot be determined: "+err.Error())
|
||||
} else if !ipv6 {
|
||||
fmt.Println("\nIPv6 is enabled. This can slow downloads significantly.")
|
||||
fmt.Println("\nIPv6 is enabled. This has been known to slow downloads significantly.")
|
||||
}
|
||||
|
||||
nodelist := web.Ping(web.GetFullNodeUrl("index.json"))
|
||||
@@ -1179,6 +1242,7 @@ func checkLocalEnvironment() {
|
||||
if _, err = os.Stat(filepath.Join(env.root, v[i], "node.exe")); err != nil {
|
||||
invalid = append(invalid, v[i])
|
||||
} else if _, err = os.Stat(filepath.Join(env.root, v[i], "npm.cmd")); err != nil {
|
||||
fmt.Println(err)
|
||||
invalidnpm = append(invalid, v[i])
|
||||
}
|
||||
}
|
||||
@@ -1222,6 +1286,36 @@ func checkLocalEnvironment() {
|
||||
}
|
||||
}
|
||||
|
||||
// Check for updates
|
||||
colorize := true
|
||||
if err := upgrade.EnableVirtualTerminalProcessing(); err != nil {
|
||||
colorize = false
|
||||
}
|
||||
update, checkerr := upgrade.Get()
|
||||
if checkerr == nil {
|
||||
if len(update.Warnings) > 0 {
|
||||
fmt.Println("")
|
||||
}
|
||||
for _, warning := range update.Warnings {
|
||||
upgrade.Warn(warning, colorize)
|
||||
}
|
||||
if len(update.Warnings) > 0 {
|
||||
fmt.Println("")
|
||||
}
|
||||
}
|
||||
|
||||
if checkerr != nil {
|
||||
fmt.Println("error checking for updates: " + checkerr.Error())
|
||||
} else {
|
||||
newVersion, available, err := update.Available(NvmVersion)
|
||||
if err != nil {
|
||||
fmt.Println("Error checking for updates: " + err.Error())
|
||||
} else if available {
|
||||
upgrade.Warn(fmt.Sprintf("An upgrade is available: v%s", newVersion), colorize)
|
||||
fmt.Println(" run \"nvm upgrade\" to update.\n")
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("\n" + "Find help at https://github.com/coreybutler/nvm-windows/wiki/Common-Issues")
|
||||
}
|
||||
|
||||
@@ -1244,10 +1338,11 @@ func help() {
|
||||
fmt.Println(" nvm node_mirror [url] : Set the node mirror. Defaults to https://nodejs.org/dist/. Leave [url] blank to use default url.")
|
||||
fmt.Println(" nvm npm_mirror [url] : Set the npm mirror. Defaults to https://github.com/npm/cli/archive/. Leave [url] blank to default url.")
|
||||
fmt.Println(" nvm uninstall <version> : The version must be a specific version.")
|
||||
// fmt.Println(" nvm update : Automatically update nvm to the latest version.")
|
||||
fmt.Println(" nvm upgrade [restore] : Update nvm to the latest version. Use \"restore\" to revert to the previously installed version.")
|
||||
fmt.Println(" nvm use [version] [arch] : Switch to use the specified version. Optionally use \"latest\", \"lts\", or \"newest\".")
|
||||
fmt.Println(" \"newest\" is the latest installed version. Optionally specify 32/64bit architecture.")
|
||||
fmt.Println(" nvm use <arch> will continue using the selected version, but switch to 32/64 bit mode.")
|
||||
fmt.Println(" nvm reinstall <version> : A shortcut method to clean and reinstall a specific version.")
|
||||
fmt.Println(" nvm root [path] : Set the directory where nvm should store different versions of node.js.")
|
||||
fmt.Println(" If <path> is not set, the current root will be displayed.")
|
||||
fmt.Println(" nvm [--]version : Displays the current running version of nvm for Windows. Aliased as v.")
|
||||
@@ -1304,6 +1399,10 @@ func cleanVersion(version string) string {
|
||||
// Given a node.js version, returns the associated npm version
|
||||
func getNpmVersion(nodeversion string) string {
|
||||
_, _, _, _, _, npm := node.GetAvailable()
|
||||
if len(npm) == 0 {
|
||||
fmt.Println("Error looking up versions: Remote host returned no results. This usually indicates a problem with with Node.js web server. Please try again in a few minutes.")
|
||||
os.Exit(0)
|
||||
}
|
||||
return npm[nodeversion]
|
||||
}
|
||||
|
||||
@@ -1316,14 +1415,20 @@ func getLatest() string {
|
||||
}
|
||||
|
||||
func getLTS() string {
|
||||
all, ltsList, current, stable, unstable, npm := node.GetAvailable()
|
||||
fmt.Println(all)
|
||||
fmt.Println(ltsList)
|
||||
fmt.Println(current)
|
||||
fmt.Println(stable)
|
||||
fmt.Println(unstable)
|
||||
fmt.Println(npm)
|
||||
// _, ltsList, _, _, _, _ := node.GetAvailable()
|
||||
// all, ltsList, current, stable, unstable, npm := node.GetAvailable()
|
||||
// fmt.Println(all)
|
||||
// fmt.Println(ltsList)
|
||||
// fmt.Println(current)
|
||||
// fmt.Println(stable)
|
||||
// fmt.Println(unstable)
|
||||
// fmt.Println(npm)
|
||||
_, ltsList, _, _, _, _ := node.GetAvailable()
|
||||
|
||||
if len(ltsList) == 0 {
|
||||
fmt.Println("Error looking up LTS version: Remote host returned no results. This usually indicates a problem with with Node.js web server. Please try again in a few minutes.")
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// ltsList has already been numerically sorted
|
||||
return ltsList[0]
|
||||
}
|
||||
|
||||
765
src/upgrade/upgrade.go
Normal file
765
src/upgrade/upgrade.go
Normal file
@@ -0,0 +1,765 @@
|
||||
package upgrade
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"crypto/md5"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"nvm/semver"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/coreybutler/go-fsutil"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
const (
|
||||
UPDATE_URL = "https://gist.githubusercontent.com/coreybutler/a12af0f17956a0f25b60369b5f8a661a/raw/nvm4w.json"
|
||||
// Color codes
|
||||
yellow = "\033[33m"
|
||||
reset = "\033[0m"
|
||||
|
||||
// Windows console modes
|
||||
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
|
||||
FILE_ATTRIBUTE_HIDDEN = 0x2
|
||||
CREATE_NEW_CONSOLE = 0x00000010 // Create a new console for the child process
|
||||
DETACHED_PROCESS = 0x00000008 // Detach the child process from the parent
|
||||
|
||||
warningIcon = "⚠️"
|
||||
// exclamationIcon = "❗"
|
||||
)
|
||||
|
||||
type Update struct {
|
||||
Version string `json:"version"`
|
||||
Assets []string `json:"assets"`
|
||||
Warnings []string `json:"notices"`
|
||||
VersionWarnings []string `json:"versionNotices"`
|
||||
SourceURL string `json:"sourceTpl"`
|
||||
}
|
||||
|
||||
func (u *Update) Available(sinceVersion string) (string, bool, error) {
|
||||
currentVersion, err := semver.New(sinceVersion)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
|
||||
updateVersion, err := semver.New(u.Version)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
|
||||
if currentVersion.LT(updateVersion) {
|
||||
return u.Version, true, nil
|
||||
}
|
||||
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
func Warn(msg string, colorized ...bool) {
|
||||
if len(colorized) > 0 && colorized[0] {
|
||||
fmt.Println(warningIcon + " " + highlight(msg))
|
||||
} else {
|
||||
fmt.Println(strings.ToUpper(msg))
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
currentPath, err := os.Executable()
|
||||
if err != nil {
|
||||
fmt.Println("error getting updater path:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Create temporary directory for the updater script
|
||||
tempDir := filepath.Dir(currentPath) // Use the same temp dir as the new executable
|
||||
scriptPath := filepath.Join(tempDir, "updater.bat")
|
||||
|
||||
// Temporary batch file that deletes the directory and the scheduled task
|
||||
tmp, err := os.MkdirTemp("", "nvm4w-remove-*")
|
||||
if err != nil {
|
||||
fmt.Printf("error creating temporary directory: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
tempBatchFile := filepath.Join(tmp, "remove_backup.bat")
|
||||
now := time.Now()
|
||||
futureDate := now.AddDate(0, 0, 7)
|
||||
formattedDate := futureDate.Format("01/02/2006")
|
||||
batchContent := fmt.Sprintf(`
|
||||
@echo off
|
||||
schtasks /delete /tn "RemoveNVM4WBackup" /f
|
||||
rmdir /s /q "%s"
|
||||
`, escapeBackslashes(filepath.Join(filepath.Dir(currentPath), ".update")))
|
||||
|
||||
// Write the batch file to a temporary location
|
||||
err = os.WriteFile(tempBatchFile, []byte(batchContent), os.ModePerm)
|
||||
if err != nil {
|
||||
fmt.Printf("error creating temporary batch file: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
updaterScript := fmt.Sprintf(`@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
echo ========= Update Script Started ========= >> error.log
|
||||
echo Started updater script with PID %%1 at %%TIME%% >> error.log
|
||||
echo Source: %%~2 >> error.log
|
||||
echo Target: %%~3 >> error.log
|
||||
|
||||
:wait
|
||||
timeout /t 1 /nobreak >nul
|
||||
tasklist /fi "PID eq %%1" 2>nul | find "%%1" >nul
|
||||
if not errorlevel 1 (
|
||||
echo Waiting for PID %%1 to exit at %%TIME%%... >> error.log
|
||||
goto :wait
|
||||
)
|
||||
|
||||
echo ========= Starting Copy Operation ========= >> error.log
|
||||
echo Checking if source (%%~2) exists... >> error.log
|
||||
if not exist "%%~2" (
|
||||
echo ERROR: Source file does not exist: %%~2 >> error.log
|
||||
exit /b 1
|
||||
)
|
||||
echo Source file exists >> error.log
|
||||
|
||||
del "%%~3" >> error.log
|
||||
|
||||
echo Checking if target location is writable... >> error.log
|
||||
echo Test > "%%~dp3test.txt" 2>>error.log
|
||||
if errorlevel 1 (
|
||||
echo ERROR: Target location is not writable: %%~dp3 >> error.log
|
||||
exit /b 1
|
||||
)
|
||||
del "%%~dp3test.txt"
|
||||
echo Target location is writable >> error.log
|
||||
|
||||
echo Attempting copy at %%TIME%%... >> error.log
|
||||
echo Running: copy /y "%%~2" "%%~3" >> error.log
|
||||
copy /y "%%~2" "%%~3" >> error.log 2>&1
|
||||
if errorlevel 1 (
|
||||
echo ERROR: Copy failed with error level %%errorlevel%% >> error.log
|
||||
exit /b %%errorlevel%%
|
||||
)
|
||||
|
||||
echo Verifying copy... >> error.log
|
||||
if not exist "%%~3" (
|
||||
echo ERROR: Target file does not exist after copy: %%~3 >> error.log
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
del "%%~2" >> error.log
|
||||
if exist "%%~2" (
|
||||
echo ERROR: Source file still exists after deletion: %%~2 >> error.log
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
:: Schedule the task to delete the directory
|
||||
echo schtasks /create /tn "RemoveNVM4WBackup" /tr "cmd.exe /c %s" /sc once /sd %s /st 12:00 /f >> error.log
|
||||
schtasks /create /tn "RemoveNVM4WBackup" /tr "cmd.exe /c %s" /sc once /sd %s /st 12:00 /f
|
||||
if not errorlevel 0 (
|
||||
echo ERROR: Failed to create scheduled task: exit code: %%errorlevel%% >> error.log
|
||||
exit /b %%errorlevel%%
|
||||
)
|
||||
|
||||
echo Update complete >> error.log
|
||||
|
||||
del error.log
|
||||
|
||||
del "%%~f0"
|
||||
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 {
|
||||
fmt.Printf("error creating updater script: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Start the updater script
|
||||
cmd := exec.Command(scriptPath, fmt.Sprintf("%d", os.Getpid()), filepath.Join(tempDir, ".update", "nvm.exe"), currentPath)
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
fmt.Printf("error starting updater script: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Exit the current process (delay for cleanup)
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func escapeBackslashes(path string) string {
|
||||
return strings.Replace(path, "\\", "\\\\", -1)
|
||||
}
|
||||
|
||||
func tree(dir string, title ...string) {
|
||||
if len(title) > 0 {
|
||||
fmt.Println("\n" + highlight(title[0]))
|
||||
}
|
||||
cmd := exec.Command("tree", dir, "/F")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
fmt.Println("Error executing command:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func get(url string, verbose ...bool) ([]byte, error) {
|
||||
if len(verbose) == 0 || verbose[0] {
|
||||
fmt.Printf(" GET %s\n", url)
|
||||
}
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return []byte{}, fmt.Errorf("error: received status code %d\n", resp.StatusCode)
|
||||
}
|
||||
|
||||
return io.ReadAll(resp.Body)
|
||||
}
|
||||
|
||||
func checkForUpdate(url string) (*Update, error) {
|
||||
u := Update{}
|
||||
|
||||
// Make the HTTP GET request
|
||||
body, err := get(url, false)
|
||||
if err != nil {
|
||||
return &u, fmt.Errorf("error: reading response body: %v", err)
|
||||
}
|
||||
|
||||
// Parse JSON into the struct
|
||||
err = json.Unmarshal(body, &u)
|
||||
if err != nil {
|
||||
return &u, fmt.Errorf("error: parsing update: %v", err)
|
||||
}
|
||||
|
||||
return &u, nil
|
||||
}
|
||||
|
||||
func EnableVirtualTerminalProcessing() error {
|
||||
// Get the handle to the standard output
|
||||
handle := windows.Stdout
|
||||
|
||||
// Retrieve the current console mode
|
||||
var mode uint32
|
||||
if err := windows.GetConsoleMode(handle, &mode); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Enable the virtual terminal processing mode
|
||||
mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
||||
if err := windows.SetConsoleMode(handle, mode); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func highlight(message string) string {
|
||||
return fmt.Sprintf("%s%s%s", yellow, message, reset)
|
||||
}
|
||||
|
||||
// Unzip function extracts a zip file to a specified directory
|
||||
func unzip(src string, dest string) error {
|
||||
// Open the zip archive for reading
|
||||
r, err := zip.OpenReader(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
// Iterate over each file in the zip archive
|
||||
for _, f := range r.File {
|
||||
// Build the path for each file in the destination directory
|
||||
fpath := filepath.Join(dest, f.Name)
|
||||
|
||||
// Check if the file is a directory
|
||||
if f.FileInfo().IsDir() {
|
||||
// Create directory if it doesn't exist
|
||||
if err := os.MkdirAll(fpath, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Create directories leading to the file if they don't exist
|
||||
if err := os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Open the file in the zip archive
|
||||
rc, err := f.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
// Create the destination file
|
||||
outFile, err := os.Create(fpath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer outFile.Close()
|
||||
|
||||
// Copy the file contents from the archive to the destination file
|
||||
_, err = io.Copy(outFile, rc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// function to compute the MD5 checksum of a file
|
||||
func computeMD5Checksum(filePath string) (string, error) {
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
hasher := md5.New()
|
||||
_, err = io.Copy(hasher, file)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Return the hex string representation of the MD5 hash
|
||||
return fmt.Sprintf("%x", hasher.Sum(nil)), nil
|
||||
}
|
||||
|
||||
// function to read the checksum from the .checksum.txt file
|
||||
func readChecksumFromFile(checksumFile string) (string, error) {
|
||||
file, err := os.Open(checksumFile)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
var checksum string
|
||||
_, err = fmt.Fscan(file, &checksum)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return checksum, nil
|
||||
}
|
||||
|
||||
func copyFile(src, dst string) error {
|
||||
// Open the source file
|
||||
sourceFile, err := os.Open(src)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open source file: %v", err)
|
||||
}
|
||||
defer sourceFile.Close()
|
||||
|
||||
// Create the destination file
|
||||
destinationFile, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create destination file: %v", err)
|
||||
}
|
||||
defer destinationFile.Close()
|
||||
|
||||
// Copy contents from the source file to the destination file
|
||||
_, err = io.Copy(destinationFile, sourceFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to copy file: %v", err)
|
||||
}
|
||||
|
||||
// Optionally, copy file permissions (this can be skipped if not needed)
|
||||
sourceInfo, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get source file info: %v", err)
|
||||
}
|
||||
|
||||
err = os.Chmod(dst, sourceInfo.Mode())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set file permissions: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// copyDirContents copies all the contents (files and subdirectories) of a source directory to a destination directory.
|
||||
func copyDirContents(srcDir, dstDir string) error {
|
||||
// Ensure destination directory exists
|
||||
err := os.MkdirAll(dstDir, 0755)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create destination directory %s: %v", dstDir, err)
|
||||
}
|
||||
|
||||
// Walk through the source directory recursively
|
||||
err = filepath.Walk(srcDir, func(srcPath string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("error accessing %s: %v", srcPath, err)
|
||||
}
|
||||
|
||||
// Construct the corresponding path in the destination directory
|
||||
relPath, err := filepath.Rel(srcDir, srcPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get relative path for %s: %v", srcPath, err)
|
||||
}
|
||||
|
||||
dstPath := filepath.Join(dstDir, relPath)
|
||||
|
||||
// If it's a directory, ensure it's created in the destination
|
||||
if info.IsDir() {
|
||||
return os.MkdirAll(dstPath, info.Mode())
|
||||
}
|
||||
|
||||
// If it's a file, copy it
|
||||
return copyFile(srcPath, dstPath)
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// zipDirectory zips the contents of a directory.
|
||||
func zipDirectory(sourceDir, outputZip string) error {
|
||||
// Create the zip file.
|
||||
zipFile, err := os.Create(outputZip)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer zipFile.Close()
|
||||
|
||||
// Create a new zip writer.
|
||||
zipWriter := zip.NewWriter(zipFile)
|
||||
defer zipWriter.Close()
|
||||
|
||||
// Walk through the directory.
|
||||
return filepath.Walk(sourceDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get the relative path.
|
||||
relPath, err := filepath.Rel(sourceDir, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Skip the directory itself but include subdirectories.
|
||||
if info.IsDir() {
|
||||
if relPath == "." {
|
||||
return nil
|
||||
}
|
||||
// Add a trailing slash for directories in the zip archive.
|
||||
relPath += "/"
|
||||
}
|
||||
|
||||
// Create a zip header.
|
||||
header, err := zip.FileInfoHeader(info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
header.Name = relPath
|
||||
if info.IsDir() {
|
||||
header.Method = zip.Store
|
||||
} else {
|
||||
header.Method = zip.Deflate
|
||||
}
|
||||
|
||||
// Create a writer for the file in the zip archive.
|
||||
writer, err := zipWriter.CreateHeader(header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If the file is not a directory, copy its contents into the archive.
|
||||
if !info.IsDir() {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
_, err = io.Copy(writer, file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func setHidden(path string) error {
|
||||
// Convert the path to a UTF-16 encoded string
|
||||
lpFileName, err := syscall.UTF16PtrFromString(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encode path: %w", err)
|
||||
}
|
||||
|
||||
// Call the Windows API function
|
||||
ret, _, err := syscall.NewLazyDLL("kernel32.dll").
|
||||
NewProc("SetFileAttributesW").
|
||||
Call(
|
||||
uintptr(unsafe.Pointer(lpFileName)),
|
||||
uintptr(FILE_ATTRIBUTE_HIDDEN),
|
||||
)
|
||||
|
||||
// Check the result
|
||||
if ret == 0 {
|
||||
return fmt.Errorf("failed to set hidden attribute: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user