diff --git a/bin/nave b/bin/nave new file mode 100755 index 0000000..a5c114a --- /dev/null +++ b/bin/nave @@ -0,0 +1,678 @@ +#!/bin/bash + +# This program contains parts of narwhal's "sea" program, +# as well as bits borrowed from Tim Caswell's "nvm" + +# nave install +# Fetch the version of node and install it in nave's folder. + +# nave use +# Install the if it isn't already, and then start +# a subshell with that version's folder at the start of the +# $PATH + +# nave use program.js +# Like "nave use", but have the subshell start the program.js +# immediately. + +# When told to use a version: +# Ensure that the version exists, install it, and +# then add its prefix to the PATH, and start a subshell. + +if [ "$NAVE_DEBUG" != "" ]; then + set -x +fi + +if [ -z "$BASH" ]; then + cat >&2 <&2 + exit 1 + ;; +esac + +# Use fancy pants globs +shopt -s extglob + +# Try to figure out the os and arch for binary fetching +uname="$(uname -a)" +os= +arch=x86 +case "$uname" in + Linux\ *) os=linux ;; + Darwin\ *) os=darwin ;; + SunOS\ *) os=sunos ;; +esac +case "$uname" in + *x86_64*) arch=x64 ;; +esac + +tar=${TAR-tar} + +main () { + local SELF_PATH DIR SYM + # get the absolute path of the executable + SELF_PATH="$0" + if [ "${SELF_PATH:0:1}" != "." ] && [ "${SELF_PATH:0:1}" != "/" ]; then + SELF_PATH=./"$SELF_PATH" + fi + SELF_PATH=$( cd -P -- "$(dirname -- "$SELF_PATH")" \ + && pwd -P \ + ) && SELF_PATH=$SELF_PATH/$(basename -- "$0") + + # resolve symlinks + while [ -h "$SELF_PATH" ]; do + DIR=$(dirname -- "$SELF_PATH") + SYM=$(readlink -- "$SELF_PATH") + SELF_PATH=$( cd -- "$DIR" \ + && cd -- $(dirname -- "$SYM") \ + && pwd \ + )/$(basename -- "$SYM") + done + + if ! [ -d "$NAVE_DIR" ]; then + if [ -d "$HOME" ]; then + NAVE_DIR="$HOME"/.nave + else + NAVE_DIR=/usr/local/lib/nave + fi + fi + if ! [ -d "$NAVE_DIR" ] && ! mkdir -p -- "$NAVE_DIR"; then + NAVE_DIR="$(dirname -- "$SELF_PATH")" + fi + + # set up the naverc init file. + # For zsh compatibility, we name this file ".zshenv" instead of + # the more reasonable "naverc" name. + # Important! Update this number any time the init content is changed. + local rcversion="#3" + local rcfile="$NAVE_DIR/.zshenv" + if ! [ -f "$rcfile" ] \ + || [ "$(head -n1 "$rcfile")" != "$rcversion" ]; then + + cat > "$rcfile" < "$NAVE_DIR/.zlogout" <&2 + exit $ret + fi +} + +function enquote_all () { + local ARG ARGS + ARGS="" + for ARG in "$@"; do + [ -n "$ARGS" ] && ARGS="$ARGS " + ARGS="$ARGS'""$( echo " $ARG" \ + | cut -c 2- \ + | sed 's/'"'"'/'"'"'"'"'"'"'"'"'/g' \ + )""'" + done + echo "$ARGS" +} + +ensure_dir () { + if ! [ -d "$1" ]; then + mkdir -p -- "$1" || fail "couldn't create $1" + fi +} + +remove_dir () { + if [ -d "$1" ]; then + rm -rf -- "$1" || fail "Could not remove $1" + fi +} + +fail () { + echo "$@" >&2 + exit 1 +} + +nave_fetch () { + local version=$(ver "$1") + if nave_has "$version"; then + echo "already fetched $version" >&2 + return 0 + fi + + local src="$NAVE_SRC/$version" + remove_dir "$src" + ensure_dir "$src" + + local url + local urls=( + "http://nodejs.org/dist/v$version/node-v$version.tar.gz" + "http://nodejs.org/dist/node-v$version.tar.gz" + "http://nodejs.org/dist/node-$version.tar.gz" + ) + for url in "${urls[@]}"; do + curl -#Lf "$url" > "$src".tgz + if [ $? -eq 0 ]; then + $tar xzf "$src".tgz -C "$src" --strip-components=1 + if [ $? -eq 0 ]; then + echo "fetched from $url" >&2 + return 0 + fi + fi + done + + rm "$src".tgz + remove_dir "$src" + echo "Couldn't fetch $version" >&2 + return 1 +} + +build () { + local version="$1" + + # shortcut - try the binary if possible. + if [ -n "$os" ]; then + local binavail + # binaries started with node 0.8.6 + case "$version" in + 0.8.[012345]) binavail=0 ;; + 0.[1234567]) binavail=0 ;; + *) binavail=1 ;; + esac + if [ $binavail -eq 1 ]; then + local t="$version-$os-$arch" + local url="http://nodejs.org/dist/v$version/node-v${t}.tar.gz" + local tgz="$NAVE_SRC/$t.tgz" + curl -#Lf "$url" > "$tgz" + if [ $? -ne 0 ]; then + # binary download failed. oh well. cleanup, and proceed. + rm "$tgz" + echo "Binary download failed, trying source." >&2 + else + # unpack straight into the build target. + $tar xzf "$tgz" -C "$2" --strip-components 1 + if [ $? -ne 0 ]; then + rm "$tgz" + nave_uninstall "$version" + echo "Binary unpack failed, trying source." >&2 + fi + # it worked! + echo "installed from binary" >&2 + return 0 + fi + fi + fi + + nave_fetch "$version" + if [ $? != 0 ]; then + # fetch failed, don't continue and try to build it. + return 1 + fi + + local src="$NAVE_SRC/$version" + local jobs=$NAVE_JOBS + jobs=${jobs:-$JOBS} + jobs=${jobs:-$(sysctl -n hw.ncpu)} + jobs=${jobs:-2} + + ( cd -- "$src" + [ -f ~/.naverc ] && . ~/.naverc || true + if [ "$NAVE_CONFIG" == "" ]; then + NAVE_CONFIG=() + fi + JOBS=$jobs ./configure "${NAVE_CONFIG[@]}" --prefix="$2" \ + || fail "Failed to configure $version" + JOBS=$jobs make -j$jobs \ + || fail "Failed to make $version" + make install || fail "Failed to install $version" + ) || fail "fail" + return $? +} + +nave_usemain () { + if [ ${NAVELVL-0} -gt 0 ]; then + fail "Can't usemain inside a nave subshell. Exit to main shell." + fi + local version=$(ver "$1") + local current=$(node -v || true) + local wn=$(which node || true) + local prefix="/usr/local" + if [ "x$wn" != "x" ]; then + prefix="${wn/\/bin\/node/}" + if [ "x$prefix" == "x" ]; then + prefix="/usr/local" + fi + fi + current="${current/v/}" + if [ "$current" == "$version" ]; then + echo "$version already installed" >&2 + return 0 + fi + + build "$version" "$prefix" +} + +nave_install () { + local version=$(ver "$1") + if nave_installed "$version"; then + echo "Already installed: $version" >&2 + return 0 + fi + local install="$NAVE_ROOT/$version" + ensure_dir "$install" + + build "$version" "$install" + local ret=$? + if [ $ret -ne 0 ]; then + remove_dir "$install" + return $ret + fi +} + +nave_test () { + local version=$(ver "$1") + nave_fetch "$version" + local src="$NAVE_SRC/$version" + ( cd -- "$src" + [ -f ~/.naverc ] && . ~/.naverc || true + if [ "$NAVE_CONFIG" == "" ]; then + NAVE_CONFIG=() + fi + ./configure "${NAVE_CONFIG[@]}" || fail "failed to ./configure" + make test-all || fail "Failed tests" + ) || fail "failed" +} + +nave_ls () { + ls -- $NAVE_SRC | version_list "src" \ + && ls -- $NAVE_ROOT | version_list "installed" \ + && nave_ls_named \ + || return 1 +} + +nave_ls_remote () { + curl -s http://nodejs.org/dist/ \ + | version_list "remote" \ + || return 1 +} + +nave_ls_named () { + echo "named:" + ls -- "$NAVE_ROOT" \ + | egrep -v '[0-9]+\.[0-9]+\.[0-9]+' \ + | sort \ + | while read name; do + echo "$name: $(ver $($NAVE_ROOT/$name/bin/node -v 2>/dev/null))" + done +} + +nave_ls_all () { + nave_ls \ + && (echo ""; nave_ls_remote) \ + || return 1 +} + +ver () { + local version="$1" + local nonames="$2" + version="${version/v/}" + case $version in + latest | stable) nave_$version ;; + +([0-9])\.+([0-9])) nave_version_family "$version" ;; + +([0-9])\.+([0-9])\.+([0-9])) echo $version ;; + *) [ "$nonames" = "" ] && echo $version ;; + esac +} + +nave_version_family () { + local family="$1" + family="${family/v/}" + curl -s http://nodejs.org/dist/ \ + | egrep -o $family'\.[0-9]+' \ + | sort -u -k 1,1n -k 2,2n -k 3,3n -t . \ + | tail -n1 +} + +nave_latest () { + curl -s http://nodejs.org/dist/ \ + | egrep -o '[0-9]+\.[0-9]+\.[0-9]+' \ + | sort -u -k 1,1n -k 2,2n -k 3,3n -t . \ + | tail -n1 +} + +nave_stable () { + curl -s http://nodejs.org/dist/ \ + | egrep -o '[0-9]+\.[0-9]*[02468]\.[0-9]+' \ + | sort -u -k 1,1n -k 2,2n -k 3,3n -t . \ + | tail -n1 +} + +version_list_named () { + egrep -v '[0-9]+\.[0-9]+\.[0-9]+' \ + | sort -u -k 1,1n -k 2,2n -k 3,3n -t . \ + | organize_version_list \ + || return 1 +} + +version_list () { + echo "$1:" + egrep -o '[0-9]+\.[0-9]+\.[0-9]+' \ + | sort -u -k 1,1n -k 2,2n -k 3,3n -t . \ + | organize_version_list \ + || return 1 +} + +organize_version_list () { + local i=0 + local v + while read v; do + if [ $i -eq 8 ]; then + i=0 + echo "$v" + else + let 'i = i + 1' + echo -ne "$v\t" + fi + done + echo "" + [ $i -ne 0 ] && echo "" + return 0 +} + +nave_has () { + local version=$(ver "$1") + [ -x "$NAVE_SRC/$version/configure" ] || return 1 +} + +nave_installed () { + local version=$(ver "$1") + [ -x "$NAVE_ROOT/$version/bin/node" ] || return 1 +} + +nave_use () { + local version=$(ver "$1") + + # if it's not a version number, then treat as a name. + case "$version" in + +([0-9])\.+([0-9])\.+([0-9])) ;; + *) + nave_named "$@" + return $? + ;; + esac + + if [ -z "$version" ]; then + fail "Must supply a version" + fi + + if [ "$version" == "$NAVENAME" ]; then + echo "already using $version" >&2 + if [ $# -gt 1 ]; then + shift + "$@" + fi + return $? + fi + + nave_install "$version" || fail "failed to install $version" + local prefix="$NAVE_ROOT/$version" + local lvl=$[ ${NAVELVL-0} + 1 ] + echo "using $version" >&2 + if [ $# -gt 1 ]; then + shift + nave_exec "$lvl" "$version" "$version" "$prefix" "$@" + return $? + else + nave_login "$lvl" "$version" "$version" "$prefix" + return $? + fi +} + +# internal +nave_exec () { + nave_run "exec" "$@" + return $? +} + +nave_login () { + nave_run "login" "$@" + return $? +} + +nave_run () { + local exec="$1" + shift + local lvl="$1" + shift + local name="$1" + shift + local version="$1" + shift + local prefix="$1" + shift + + local bin="$prefix/bin" + local lib="$prefix/lib/node" + local man="$prefix/share/man" + ensure_dir "$bin" + ensure_dir "$lib" + ensure_dir "$man" + + # now $@ is the command to run, or empty if it's not an exec. + local exit_code + local args=() + local isLogin + + if [ "$exec" == "exec" ]; then + isLogin="" + # source the nave env file, then run the command. + args=("-c" ". $(enquote_all $NAVE_DIR/.zshenv); $(enquote_all "$@")") + elif [ "$shell" == "zsh" ]; then + isLogin="1" + # no need to set rcfile, since ZDOTDIR is set. + args=() + else + isLogin="1" + # bash, use --rcfile argument + args=("--rcfile" "$NAVE_DIR/.zshenv") + fi + + local nave="$version" + if [ "$version" != "$name" ]; then + nave="$name"-"$version" + fi + + NAVELVL=$lvl \ + NAVEPATH="$bin" \ + NAVEVERSION="$version" \ + NAVENAME="$name" \ + NAVE="$nave" \ + npm_config_binroot="$bin"\ + npm_config_root="$lib" \ + npm_config_manroot="$man" \ + npm_config_prefix="$prefix" \ + NODE_PATH="$lib" \ + NAVE_LOGIN="$isLogin" \ + NAVE_DIR="$NAVE_DIR" \ + ZDOTDIR="$NAVE_DIR" \ + "$SHELL" "${args[@]}" + + exit_code=$? + hash -r + return $exit_code +} + +nave_named () { + local name="$1" + shift + + local version=$(ver "$1" NONAMES) + if [ "$version" != "" ]; then + shift + fi + + add_named_env "$name" "$version" || fail "failed to create $name env" + + if [ "$name" == "$NAVENAME" ] && [ "$version" == "$NAVEVERSION" ]; then + echo "already using $name" >&2 + if [ $# -gt 0 ]; then + "$@" + fi + return $? + fi + + if [ "$version" = "" ]; then + version="$(ver "$("$NAVE_ROOT/$name/bin/node" -v 2>/dev/null)")" + fi + + local prefix="$NAVE_ROOT/$name" + + local lvl=$[ ${NAVELVL-0} + 1 ] + # get the version + if [ $# -gt 0 ]; then + nave_exec "$lvl" "$name" "$version" "$prefix" "$@" + return $? + else + nave_login "$lvl" "$name" "$version" "$prefix" + return $? + fi +} + +add_named_env () { + local name="$1" + local version="$2" + local cur="$(ver "$($NAVE_ROOT/$name/bin/node -v 2>/dev/null)" "NONAMES")" + + if [ "$version" != "" ]; then + version="$(ver "$version" "NONAMES")" + else + version="$cur" + fi + + if [ "$version" = "" ]; then + echo "What version of node?" + read -p "stable, latest, x.y, or x.y.z > " version + version=$(ver "$version") + fi + + # if that version is already there, then nothing to do. + if [ "$cur" = "$version" ]; then + return 0 + fi + + echo "Creating new env named '$name' using node $version" >&2 + + nave_install "$version" || fail "failed to install $version" + ensure_dir "$NAVE_ROOT/$name/bin" + ensure_dir "$NAVE_ROOT/$name/lib/node" + ensure_dir "$NAVE_ROOT/$name/lib/node_modules" + ensure_dir "$NAVE_ROOT/$name/share/man" + + ln -sf -- "$NAVE_ROOT/$version/bin/node" "$NAVE_ROOT/$name/bin/node" + ln -sf -- "$NAVE_ROOT/$version/bin/node-waf" "$NAVE_ROOT/$name/bin/node-waf" +} + +nave_clean () { + rm -rf "$NAVE_SRC/$(ver "$1")" "$NAVE_SRC/$(ver "$1")".tgz "$NAVE_SRC/$(ver "$1")"-*.tgz +} + +nave_uninstall () { + remove_dir "$NAVE_ROOT/$(ver "$1")" +} + +nave_help () { + cat < + +Commands: + +install Install the version passed (ex: 0.1.103) +use Enter a subshell where is being used +use Enter a subshell, and run "", then exit +use Create a named env, using the specified version. + If the name already exists, but the version differs, + then it will update the link. +usemain Install in /usr/local/bin (ie, use as your main nodejs) +clean Delete the source code for +uninstall Delete the install for +ls List versions currently installed +ls-remote List remote node versions +ls-all List remote and local node versions +latest Show the most recent dist version +help Output help information + + can be the string "latest" to get the latest distribution. + can be the string "stable" to get the latest stable version. + +EOF +} + +main "$@"