#!/usr/bin/env bash # # Setup. # VERSION="1.3.0" UP=$'\033[A' DOWN=$'\033[B' N_PREFIX=${N_PREFIX-/usr/local} BASE_VERSIONS_DIR=$N_PREFIX/n/versions # # All Bin(node/io) configurations # BINS=("node" "io") MIRROR=(${NODE_MIRROR-https://nodejs.org/dist/} ${IO_MIRROR-https://iojs.org/dist/}) BIN_NAME=("node" "iojs") VERSIONS_DIR=($BASE_VERSIONS_DIR/node $BASE_VERSIONS_DIR/io) # # State # DEFAULT=0 QUIET=true ACTIVATE=true # # set_default # set_default() { for (( i=0 ; i<${#BINS[@]} ; i++ )); do if test ${BINS[$i]} = $1; then DEFAULT=$i fi done } for dir in ${VERSIONS_DIR[@]}; do test -d $dir || mkdir -p $dir done # # Log # log() { printf " \033[36m%10s\033[0m : \033[90m%s\033[0m\n" $1 $2 } # # Exit with the given # abort() { printf "\n \033[31mError: $@\033[0m\n\n" && exit 1 } # # Ensure we have curl or wget support. # GET= # wget support (Added --no-check-certificate for Github downloads) command -v wget > /dev/null && GET="wget --no-check-certificate -q -O-" command -v curl > /dev/null && GET="curl -# -L" && QUIET=false test -z "$GET" && abort "curl or wget required" # # Functions used when showing versions installed # enter_fullscreen() { tput smcup stty -echo } leave_fullscreen() { tput rmcup stty echo } handle_sigint() { leave_fullscreen exit $? } handle_sigtstp() { leave_fullscreen kill -s SIGSTOP $$ } # # Output usage information. # display_help() { cat <<-EOF Usage: n [options/env] [COMMAND] [args] Environments: n [COMMAND] [args] Uses default env (node) n io [COMMAND] Sets env as io Commands: n Output versions installed n latest Install or activate the latest node release n stable Install or activate the latest stable node release n Install node n use [args ...] Execute node with [args ...] n bin Output bin path for n rm Remove the given version(s) n prev Revert to the previously activated version n --latest Output the latest node version available n --stable Output the latest stable node version available n ls Output the versions of node available (iojs): n io latest Install or activate the latest iojs release n io Install iojs n io use [args ...] Execute iojs with [args ...] n io bin Output bin path for n io rm Remove the given version(s) n io --latest Output the latest iojs version available n io ls Output the versions of iojs available Options: -V, --version Output current version of n -h, --help Display help information -q, --quiet Disable curl output (if available) -d, --download Download only Aliases: which bin use as list ls - rm EOF exit 0 } # # Hide cursor. # hide_cursor() { printf "\e[?25l" } # # Show cursor. # show_cursor() { printf "\e[?25h" } # # Output version after selected. # next_version_installed() { list_versions_installed | grep $selected -A 1 | tail -n 1 } # # Output version before selected. # prev_version_installed() { list_versions_installed | grep $selected -B 1 | head -n 1 } # # Output n version. # display_n_version() { echo $VERSION && exit 0 } # # Check for installed version, and populate $active # check_current_version() { command -v node &> /dev/null if test $? -eq 0; then local current=$(node --version) current=${current#v} for bin in ${BINS[@]}; do if diff &> /dev/null \ $BASE_VERSIONS_DIR/$bin/$current/bin/node \ $(which node) ; then active=$bin/$current fi done fi } # # Check the operation is supported for io. # check_io_supported() { test $DEFAULT -eq 1 && abort "$1 not supported for io.js" } # # Display sorted versions directories paths. # versions_paths() { find $BASE_VERSIONS_DIR -maxdepth 2 -type d \ | sed 's|'$BASE_VERSIONS_DIR'/||g' \ | egrep "/[0-9]+\.[0-9]+\.[0-9]+$" \ | sort -k 1 -k 2,2n -k 3,3n -t . } # # Display installed versions with # display_versions_with_selected() { selected=$1 echo for version in $(versions_paths); do if test "$version" = "$selected"; then printf " \033[36mο\033[0m $version\033[0m\n" else printf " \033[90m$version\033[0m\n" fi done echo } # # List installed versions. # list_versions_installed() { for version in $(versions_paths); do echo $version done } # # Display current node --version and others installed. # display_versions() { enter_fullscreen check_current_version display_versions_with_selected $active trap handle_sigint INT trap handle_sigtstp SIGTSTP while true; do read -n 3 c case "$c" in $UP) clear display_versions_with_selected $(prev_version_installed) ;; $DOWN) clear display_versions_with_selected $(next_version_installed) ;; *) activate $selected leave_fullscreen exit ;; esac done } # # Move up a line and erase. # erase_line() { printf "\033[1A\033[2K" } # # Check if the HEAD response of is 200. # is_ok() { if command -v curl > /dev/null; then curl -Is $1 | head -n 1 | grep 200 > /dev/null else $GET -S --spider 2>&1 $1 | head -n 1 | grep 200 > /dev/null fi } # # Determine tarball url for # tarball_url() { local version=$1 local uname="$(uname -a)" local arch=x86 local os= # from nave(1) case "$uname" in Linux*) os=linux ;; Darwin*) os=darwin ;; SunOS*) os=sunos ;; esac case "$uname" in *x86_64*) arch=x64 ;; *armv6l*) arch=armv6l ;; *armv7l*) arch=armv7l ;; esac if [ ${arch} = "armv6l" -a ${BIN_NAME[$DEFAULT]} = node ]; then local semver=${version//./ } local major=$(echo $semver | grep -o -E '[0-9]+' | head -1 | sed -e 's/^0\+//') local minor=$(echo $semver | awk '{print $2}' | grep -o -E '[0-9]+' | head -1 | sed -e 's/^0\+//') [[ $major -eq "" && $minor -lt 12 ]] && arch=arm-pi fi echo "${MIRROR[$DEFAULT]}v${version}/${BIN_NAME[$DEFAULT]}-v${version}-${os}-${arch}.tar.gz" } # # Activate # activate() { local version=$1 check_current_version if test "$version" != "$active"; then local dir=$BASE_VERSIONS_DIR/$version echo $active > $BASE_VERSIONS_DIR/.prev cp -fR $dir/bin $dir/lib $dir/share $N_PREFIX [[ -d "$dir/include" ]] && cp -fR $dir/include $N_PREFIX fi } # # Activate previous node. # activate_previous() { test -f $BASE_VERSIONS_DIR/.prev || abort "no previous versions activated" local prev=$(cat $BASE_VERSIONS_DIR/.prev) test -d $BASE_VERSIONS_DIR/$prev || abort "previous version $prev not installed" activate $prev echo log activate $prev echo } # # Install latest version. # install_latest() { install $(display_latest_version) } # # Install latest stable version. # install_stable() { check_io_supported "stable" install $(display_latest_stable_version) } # # Install # install() { local version=${1#v} local dots=$(echo $version | sed 's/[^.]*//g') if test ${#dots} -eq 1; then version=$($GET 2> /dev/null ${MIRROR[DEFAULT]} \ | egrep -o '[0-9]+\.[0-9]+\.[0-9]+' \ | egrep -v '^0\.[0-7]\.' \ | egrep -v '^0\.8\.[0-5]$' \ | sort -u -k 1,1n -k 2,2n -k 3,3n -t . \ | egrep ^$version \ | tail -n1) test $version || abort "invalid version ${1#v}" fi local dir=${VERSIONS_DIR[$DEFAULT]}/$version if test -d $dir; then if [[ ! -e $dir/n.lock ]] ; then if $ACTIVATE ; then activate ${BINS[$DEFAULT]}/$version fi exit fi fi echo log install ${BINS[$DEFAULT]}-v$version local url=$(tarball_url $version) is_ok $url || abort "invalid version $version" log mkdir $dir mkdir -p $dir if [ $? -ne 0 ] ; then abort "sudo required" else touch $dir/n.lock fi cd $dir log fetch $url $GET $url | tar -zx --strip 1 [ $QUIET == false ] && erase_line rm -f $dir/n.lock if $ACTIVATE ; then activate ${BINS[$DEFAULT]}/$version log installed $(node --version) fi echo } # # Set curl to quiet (silent) mode. # set_quiet() { command -v curl > /dev/null && GET="$GET -s" && QUIET=true } # # Remove # remove_versions() { test -z $1 && abort "version(s) required" while test $# -ne 0; do rm -rf ${VERSIONS_DIR[$DEFAULT]}/${1#v} shift done } # # Output bin path for # display_bin_path_for_version() { test -z $1 && abort "version required" local version=${1#v} local bin=${VERSIONS_DIR[$DEFAULT]}/$version/bin/node if test -f $bin; then printf "$bin \n" else abort "$1 is not installed" fi } # # Execute the given of node with [args ...] # execute_with_version() { test -z $1 && abort "version required" local version=${1#v} local bin=${VERSIONS_DIR[$DEFAULT]}/$version/bin/node shift # remove version if test -f $bin; then $bin "$@" else abort "$version is not installed" fi } # # Display the latest release version. # display_latest_version() { $GET 2> /dev/null ${MIRROR[$DEFAULT]} \ | egrep -o '[0-9]+\.[0-9]+\.[0-9]+' \ | sort -u -k 1,1n -k 2,2n -k 3,3n -t . \ | tail -n1 } # # Display the latest stable release version. # display_latest_stable_version() { check_io_supported "--stable" $GET 2> /dev/null ${MIRROR[$DEFAULT]} \ | egrep -o '[0-9]+\.[0-9]*[02468]\.[0-9]+' \ | sort -u -k 1,1n -k 2,2n -k 3,3n -t . \ | tail -n1 } # # Display the versions available. # display_remote_versions() { check_current_version local versions="" versions=$($GET 2> /dev/null ${MIRROR[$DEFAULT]} \ | egrep -o '[0-9]+\.[0-9]+\.[0-9]+' \ | egrep -v '^0\.[0-7]\.' \ | egrep -v '^0\.8\.[0-5]$' \ | sort -u -k 1,1n -k 2,2n -k 3,3n -t . \ | awk '{ print " " $1 }') echo local bin=${BINS[$DEFAULT]} for v in $versions; do if test "$active" = "$bin/$v"; then printf " \033[36mο\033[0m $v \033[0m\n" else if test -d $BASE_VERSIONS_DIR/$bin/$v; then printf " $v \033[0m\n" else printf " \033[90m$v\033[0m\n" fi fi done echo } # # Handle arguments. # if test $# -eq 0; then test -z "$(versions_paths)" && abort "no installed version" display_versions else while test $# -ne 0; do case $1 in -V|--version) display_n_version ;; -h|--help|help) display_help ;; -q|--quiet) set_quiet ;; -d|--download) ACTIVATE=false ;; --latest) display_latest_version; exit ;; --stable) display_latest_stable_version; exit ;; io) set_default $1 ;; # set bin and continue bin|which) display_bin_path_for_version $2; exit ;; as|use) shift; execute_with_version $@; exit ;; rm|-) shift; remove_versions $@; exit ;; latest) install_latest; exit ;; stable) install_stable; exit ;; ls|list) display_remote_versions; exit ;; prev) activate_previous; exit ;; *) install $1; exit ;; esac shift done fi