Initial Code

This commit is contained in:
Corey Butler
2014-09-21 00:19:18 -05:00
parent 57103a3c92
commit 70d972c1e7
6 changed files with 324 additions and 10 deletions

2
.gitignore vendored
View File

@@ -22,3 +22,5 @@ _testmain.go
*.exe
*.test
*.prof
src/v*

View File

@@ -1,4 +1,67 @@
wnvm
====
# Node Version Manager (nvm) for Windows
An experimental node.js version management utility for Windows. Ironically written in Go.
Manage multiple installations of node.js on a Windows computer.
**tl;dr** This is the Windows version of [nvm](https://github.com/creationix/nvm).
There are situations where the ability to switch between different versions of Node.js can be very
useful and save a lot of time. For example, if you want to test a module you're developing with the latest
bleeding edge version without uninstalling the stable version of node, this utility can help.
### Installation
Download the latest from the [releases](https://github.com/coreybutler/nvm/releases) and run the installer.
### Usage
nvm for Windows is a command line tool. Simply type `nvm` in the console for help. The basic commands are:
- `nvm install <version>`: Install a specific version, i.e. `0.10.32`. This will also accept `latest`, which will install the latest stable version.
- `nvm uninstall <version>`: Uninstall a specific version.
- `nvm use <version>`: Switch to a specific version of node.
- `nvm list <installed | available>`: List the versions of node that are currently installed or the versions available from nodejs.org.
- `nvm on`: Turn on nvm management.
- `nvm off`: Turn off nvm entirely.
### Gotcha!
Please note that any global npm modules you may have installed are **not** shared between the various versions of node.js installed.
---
## Why another version manager?
There are several version managers for node.js. Tools like [nvm](https://github.com/creationix/nvm) and [n](https://github.com/visionmedia/n)
are specifically designed for Mac OSX and Linux. [nvmw](https://github.com/hakobera/nvmw) and [nodist](https://github.com/marcelklehr/nodist)
are both designed for Windows. So, why another version manager for Windows?
Right around node 0.10.30, the installation structure changed a little, causing some issues with the other modules. Additionally, some users
struggle to install those modules. The architecture of most node managers on Windows focus primarily around the use of `bat` files, which
do some clever hackery to set environment variables. Some of them use node itself (once it's downloaded), which is admirable, but prone to
problems.
## What's the difference?
First and foremost, this version of nvm has no dependency on node. It's written in [Go](http://golang.org/), which is a much more structured
approach than using a `.bat` file. It does not rely on having an existing node installation. Plus, the potential for creating a
Mac/Linux version is substanially easier than converting a bunch of `.bat --> .sh` logic.
The approach is also quite different. There are two general ideas for supporting multiple node installations and readily switching between them.
One is to modify the system `PATH` any time you switch versions. This always seemed a little hackish to me, and it has some quirks. The other option
is to use a symlink. This concept requires one to put the symlink in the system `PATH`, then just update the symlink to point to whichever node
installation directory you want to use. This is a more straightforward approach, and the one most people recommend.... until they realize just how much
of a pain symlinks are on Windows. In order to create/modify a symlink, you must be running as an admin, and you must get around Windows UAC (that
annoying prompt). Luckily, this is a challenge I already solved with some helper scripts in [node-windows](http://github.com/coreybutler/node-windows).
This version of of nvm for Windows comes with an installer, courtesy of a byproduct of the node-webkit work I did on [Fenix Web Server](http://fenixwebserver.com).
Overall, this project brings together some robust new technology, a few battle-hardened pieces of other modules, and support for newer versions of node.
## Why?
I needed it, plain and simple. Additionally, it's apparent that [support for multiple versions](https://github.com/joyent/node/issues/8075) is not
coming to node core. It was also an excuse to play with Go :)
## License
MIT. See the LICENSE file.

5
src/elevate.cmd Normal file
View File

@@ -0,0 +1,5 @@
@setlocal
@echo off
set CMD=%*
set APP=%1
start wscript //nologo "%~dpn0.vbs" %*

13
src/elevate.vbs Normal file
View File

@@ -0,0 +1,13 @@
Set Shell = CreateObject("Shell.Application")
Set WShell = WScript.CreateObject("WScript.Shell")
Set ProcEnv = WShell.Environment("PROCESS")
cmd = ProcEnv("CMD")
app = ProcEnv("APP")
args= Right(cmd,(Len(cmd)-Len(app)))
If (WScript.Arguments.Count >= 1) Then
Shell.ShellExecute app, args, "", "runas", 0
Else
WScript.Quit
End If

238
src/nvm.go Normal file
View File

@@ -0,0 +1,238 @@
package main
import (
"fmt"
"os"
"os/exec"
"strings"
"path/filepath"
"net/http"
"io"
"io/ioutil"
"regexp"
"bytes"
)
var root = ""
func main() {
args := os.Args
detail := ""
setRootDir(filepath.Dir(args[0]))
// Capture any additional arguments
if (len(args) > 2) {
detail = strings.ToLower(args[2])
}
// Run the appropriate method
switch args[1] {
case "install": install(detail)
case "uninstall": uninstall(detail)
case "use": use(detail)
case "list": list(detail)
case "enable": enable()
case "disable": disable()
//case "root": setRootDir(detail)
default: help()
}
}
func install(version string) {
if version == "" {
fmt.Println("\nInvalid version.\n")
help()
return
}
// If user specifies "latest" version, find out what version is
if version == "latest" {
content := getRemoteTextFile("http://nodejs.org/dist/latest/SHASUMS.txt")
re := regexp.MustCompile("node-v(.+)+msi")
reg := regexp.MustCompile("node-v|-x.+")
version = reg.ReplaceAllString(re.FindString(content),"")
}
// Check to see if the version is already installed
if !isVersionInstalled(version) {
// If the version does not exist, download it to the temp directory.
success := download(version);
// Run the installer
if success {
fmt.Printf("Installing v"+version+"... ")
os.Mkdir(root+"\\v"+version,os.ModeDir)
cmd := exec.Command("msiexec.exe","/i",os.TempDir()+"\\"+"node-v"+version+".msi","INSTALLDIR="+root+"\\v"+version,"/qb")
err := cmd.Run()
if err != nil {
fmt.Println("ERROR")
fmt.Print(err)
os.Exit(1)
}
fmt.Printf("done.")
}
// Clean up
fmt.Printf("\nCleaning up... ")
os.Remove(os.TempDir()+"\\"+"node-v"+version+".msi")
fmt.Printf("done.\n")
return
} else {
fmt.Println("Version "+version+" is already installed.")
return
}
}
func uninstall(version string) {
// Make sure a version is specified
if len(version) == 0 {
fmt.Println("Provide the version you want to uninstall.")
help()
return
}
// Determine if the version exists and skip if it doesn't
if isVersionInstalled(version) {
fmt.Printf("\nUninstalling node v"+version+"...")
e := os.RemoveAll(root+"\\v"+version)
if e != nil {
fmt.Println("Error removing node v"+version)
fmt.Println("Check to assure "+root+"\\v"+version+" no longer exists.")
}
fmt.Printf(" done")
} else {
fmt.Println("node v"+version+" is not installed. Type \"nvm list\" to see what is installed.")
}
return
}
func use(version string) {
// Make sure the version is installed. If not, warn.
if !isVersionInstalled(version) {
fmt.Println("node v"+version+" is not installed.")
return
}
// Create the symlink
c := exec.Command(root+"\\elevate.cmd", "cmd", "/C", "mklink", "/D", root+"\\action", root+"\\v"+version)
var out bytes.Buffer
var stderr bytes.Buffer
c.Stdout = &out
c.Stderr = &stderr
err := c.Run()
if err != nil {
fmt.Println(fmt.Sprint(err) + ": " + stderr.String())
return
}
fmt.Println("Now using node v"+version)
}
func list(listtype string) {
if listtype == "" {
listtype = "installed"
}
if listtype != "installed" && listtype != "available" {
fmt.Println("\nInvalid list option.\n\nPlease use on of the following\n - wnvm list\n - wnvm list installed\n - wnvm list available")
help()
return
}
fmt.Printf("List "+listtype)
}
func enable() {
// Prompt user, warning them what they're going to do
fmt.Printf("Enable by setting the PATH to use the root with a symlink")
}
func disable() {
// Prompt user, warning them what they're going to do
fmt.Printf("Disable by removing the symlink in PATH var")
}
func setRootDir(path string) {
// Prompt user, warning them what they're going to do
rootdir, err := filepath.Abs(filepath.Dir(path+"\\"))
if err != nil {
fmt.Println("Error setting root directory")
os.Exit(1)
}
root = rootdir
fmt.Println("\nSet the root path to "+root)
}
func help() {
fmt.Println("\nUsage:\n")
fmt.Println(" nvm install <version> : The version can be a node.js version or \"latest\" for the latest stable version.")
fmt.Println(" nvm uninstall <version> : The version must be a specific version.")
fmt.Println(" nvm use <version> : Switch to use the specified version.")
fmt.Println(" nvm list [type] : type can be \"available\" (from nodejs.org),")
fmt.Println(" \"installed\" (what is currently on the computer),")
fmt.Println(" or left blank (same as \"installed\").")
fmt.Println(" nvm enable : Enable node.js version management.")
fmt.Println(" nvm disable : Disable node.js version management.")
fmt.Println(" nvm root <path> : Set the directory where wnvm should install different node.js versions.\n")
}
func getRemoteTextFile(url string) string {
response, httperr := http.Get(url)
if httperr != nil {
fmt.Println("\nCould not retrieve "+url+".\n\n")
fmt.Printf("%s", httperr)
os.Exit(1)
} else {
defer response.Body.Close()
contents, readerr := ioutil.ReadAll(response.Body)
if readerr != nil {
fmt.Printf("%s", readerr)
os.Exit(1)
}
return string(contents)
}
os.Exit(1)
return ""
}
// Download an MSI to the temp directory
func download(v string) bool {
url := "http://nodejs.org/dist/v"+v+"/node-v"+v+"-x86.msi"
fileName := os.TempDir()+"\\"+"node-v"+v+".msi"
fmt.Printf("\nDownloading node.js version "+v+"... ")
output, err := os.Create(fileName)
if err != nil {
fmt.Println("Error while creating", fileName, "-", err)
}
defer output.Close()
response, err := http.Get(url)
if err != nil {
fmt.Println("Error while downloading", url, "-", err)
}
defer response.Body.Close()
n, err := io.Copy(output, response.Body)
if err != nil {
fmt.Println("Error while downloading", url, "-", err)
}
if response.Status[0:3] == "200" {
fmt.Println(n, "bytes downloaded.")
} else {
fmt.Println("ERROR")
}
return response.Status[0:3] == "200"
}
func isVersionInstalled(version string) bool {
src, err := os.Stat(root+"\\v"+version)
src = src
return err == nil
}

View File

@@ -1,7 +0,0 @@
package main
import "fmt"
func main() {
fmt.Printf("hello, world\n")
}