0
0
mirror of https://github.com/tj/n.git synced 2024-11-29 13:52:34 +01:00
n/bin/n

798 lines
16 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
#
# Setup.
#
VERSION="2.1.12"
N_PREFIX=${N_PREFIX-/usr/local}
BASE_VERSIONS_DIR=$N_PREFIX/n/versions
#
# Log <type> <msg>
#
log() {
printf " \033[36m%10s\033[0m : \e[2m%s\e[22m\033[0m\n" $1 $2
}
#
# Exit with the given <msg ...>
#
abort() {
printf "\n \033[31mError: $@\033[0m\n\n" && exit 1
}
#
# 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)
if [ -n "$PROJECT_NAME" ]; then
BINS+=($PROJECT_NAME)
BIN_NAME+=($PROJECT_NAME)
if [ -z "$PROJECT_URL" ]; then
abort "Must specify PROJECT_URL when supplying PROJECT_NAME"
fi
MIRROR+=(${PROJECT_URL})
VERSIONS_DIR+=($BASE_VERSIONS_DIR/$PROJECT_NAME)
fi
#
# Ensure we have curl or wget support.
#
CURL_PARAMS=( "-L"
"-#")
WGET_PARAMS=( "--no-check-certificate"
"-q"
"-O-")
if [ -n "$HTTP_USER" ];then
if [ -z "$HTTP_PASSWORD" ]; then
abort "Must specify HTTP_PASSWORD when supplying HTTP_USER"
fi
CURL_PARAMS+=("-u $HTTP_USER:$HTTP_PASSWORD")
WGET_PARAMS+=("--http-password=$HTTP_PASSWORD"
"--http-user=$HTTP_USER")
elif [ -n "$HTTP_PASSWORD" ]; then
abort "Must specify HTTP_USER when supplying HTTP_PASSWORD"
fi
GET=
# wget support
command -v wget > /dev/null && GET="wget ${WGET_PARAMS[@]}"
command -v curl > /dev/null && GET="curl ${CURL_PARAMS[@]}" && QUIET=false
test -z "$GET" && abort "curl or wget required"
#
# State
#
DEFAULT=0
QUIET=true
ACTIVATE=true
ARCH=
#
# set_default <BIN_NAME>
#
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
#
# set_arch <arch> to override $(uname -a)
#
set_arch() {
if test ! -z $1; then
ARCH=$1
else
abort "missing -a|--arch value"
fi
}
#
# Functions used when showing versions installed
#
enter_fullscreen() {
tput smcup
stty -echo
}
leave_fullscreen() {
tput rmcup
stty echo
}
handle_sigint() {
leave_fullscreen
S="$?"
kill 0
exit $S
}
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
n project [COMMAND] Uses custom env-variables to use non-official sources
Commands:
n Output versions installed
n latest Install or activate the latest node release
n -a x86 latest As above but force 32 bit architecture
n lts Install or activate the latest LTS node release
n <version> Install node <version>
n use <version> [args ...] Execute node <version> with [args ...]
n bin <version> Output bin path for <version>
n rm <version ...> Remove the given version(s)
n prune Remove all versions except the current version
n --latest Output the latest node version available
n --lts Output the latest LTS node version available
n ls Output the versions of node available
(iojs):
n io latest Install or activate the latest iojs release
n io -a x86 latest As above but force 32 bit architecture
n io <version> Install iojs <version>
n io use <version> [args ...] Execute iojs <version> with [args ...]
n io bin <version> Output bin path for <version>
n io rm <version ...> 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
-a, --arch Override system architecture
Aliases:
which bin
use as
list ls
- rm
stable lts
EOF
}
err_no_installed_print_help() {
printf "\n \033[31mError: no installed version\033[0m\n"
display_help
exit 1
}
#
# 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)
if [ -n "$PROJECT_VERSION_CHECK" ]; then
current=$(node -p "$PROJECT_VERSION_CHECK || process.exit(1)" || node --version)
fi
current=${current#v}
for bin in ${BINS[@]}; do
if diff &> /dev/null \
$BASE_VERSIONS_DIR/$bin/$current/bin/node \
$(command -v 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]+$" \
| sed 's|/|.|' \
| sort -k 1,1 -k 2,2n -k 3,3n -k 4,4n -t . \
| sed 's|\.|/|'
}
#
# Display installed versions with <selected>
#
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 " \e[2m$version\e[22m\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
clear
display_versions_with_selected $active
trap handle_sigint INT
trap handle_sigtstp SIGTSTP
ESCAPE_SEQ=$'\033'
UP=$'A'
DOWN=$'B'
while true; do
read -rsn 1 key
case "$key" in
$ESCAPE_SEQ)
# Handle ESC sequences followed by other characters, i.e. arrow keys
read -rsn 1 -t 1 tmp
if [[ "$tmp" == "[" ]]; then
read -rsn 1 -t 1 arrow
case "$arrow" in
$UP)
clear
display_versions_with_selected $(prev_version_installed)
;;
$DOWN)
clear
display_versions_with_selected $(next_version_installed)
;;
esac
fi
;;
"k")
clear
display_versions_with_selected $(prev_version_installed)
;;
"j")
clear
display_versions_with_selected $(next_version_installed)
;;
"q")
clear
leave_fullscreen
exit
;;
"")
# enter key returns empty string
activate $selected
leave_fullscreen
echo $selected
exit
;;
esac
done
}
#
# Move up a line and erase.
#
erase_line() {
printf "\033[1A\033[2K"
}
#
# Check if the HEAD response of <url> is 200.
#
is_ok() {
if command -v curl > /dev/null; then
$GET -Is $1 | head -n 1 | grep 200 > /dev/null
else
$GET -S --spider 2>&1 $1 | head -n 1 | grep 200 > /dev/null
fi
}
#
# Check if the OSS(Object Storage Service) mirror is ok.
#
is_oss_ok() {
if command -v curl > /dev/null; then
if $GET -Is $1 | head -n 1 | grep 302 > /dev/null; then
is_oss_ok $GET -Is $1 | grep Location | awk -F ': ' '{print $2}'
else
$GET -Is $1 | head -n 1 | grep 200 > /dev/null
fi
else
if $GET -S --spider 2>&1 $1 | head -n 1 | grep 302 > /dev/null; then
is_oss_ok $GET -S --spider 2>&1 $1 | grep Location | awk -F ': ' '{print $2}'
else
$GET -S --spider 2>&1 $1 | head -n 1 | grep 200 > /dev/null
fi
fi
}
#
# Determine tarball url for <version>
#
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
[ ! -z $ARCH ] && arch=$ARCH
echo "${MIRROR[$DEFAULT]}v${version}/${BIN_NAME[$DEFAULT]}-v${version}-${os}-${arch}.tar.gz"
}
#
# Disable PaX mprotect for <binary>
#
disable_pax_mprotect() {
test -z $1 && abort "binary required"
local binary=$1
# try to disable mprotect via XATTR_PAX header
local PAXCTL=$(PATH="/sbin:/usr/sbin:$PATH" which paxctl-ng 2>&1)
local PAXCTL_ERROR=1
if [ -x "$PAXCTL" ]; then
$PAXCTL -l && $PAXCTL -m "$binary" >/dev/null 2>&1
PAXCTL_ERROR="$?"
fi
# try to disable mprotect via PT_PAX header
if [ $PAXCTL_ERROR != 0 ]; then
PAXCTL=$(PATH="/sbin:/usr/sbin:$PATH" which paxctl 2>&1)
if [ -x "$PAXCTL" ]; then
$PAXCTL -Cm "$binary" >/dev/null 2>&1
fi
fi
}
#
# Activate <version>
#
activate() {
local version=$1
check_current_version
if test "$version" != "$active"; then
local dir=$BASE_VERSIONS_DIR/$version
# Remove old npm to avoid potential issues with simple overwrite.
if test -d "$dir/lib/node_modules/npm"; then
if test -d "$N_PREFIX/lib/node_modules/npm"; then
rm -rf "$N_PREFIX/lib/node_modules/npm"
fi
fi
# Copy (lib before bin to avoid error messages on Darwin when cp over dangling link)
for subdir in lib bin include share; do
if test -L "$N_PREFIX/$subdir"; then
find "$dir/$subdir" -mindepth 1 -maxdepth 1 -exec cp -fR "{}" "$N_PREFIX/$subdir" \;
else
cp -fR "$dir/$subdir" $N_PREFIX
fi
done
disable_pax_mprotect "$N_PREFIX/bin/node"
fi
}
#
# Install latest version.
#
install_latest() {
install $(display_latest_version)
}
#
# Install latest stable version.
# (See additional comments for display_latest_stable_version)
#
install_stable() {
check_io_supported "stable"
install $(display_latest_stable_version)
}
#
# Install latest LTS version.
#
install_lts() {
check_io_supported "lts"
install $(display_latest_lts_version)
}
#
# Install <version>
#
install() {
local version=${1#v}
local dots=$(echo $version | sed 's/[^.]*//g')
if test ${#dots} -lt 2; then
version=$($GET 2> /dev/null ${MIRROR[DEFAULT]} \
| egrep "</a>" \
| 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 || is_oss_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-components=1
[ $QUIET == false ] && erase_line
rm -f $dir/n.lock
disable_pax_mprotect bin/node
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 <version ...>
#
remove_versions() {
test -z $1 && abort "version(s) required"
while test $# -ne 0; do
local version=${1#v}
rm -rf ${VERSIONS_DIR[$DEFAULT]}/$version
shift
done
}
#
# Prune non-active versions
#
prune_versions() {
check_current_version
for version in $(versions_paths); do
if [ "$version" != "$active" ]
then
echo $version
rm -rf ${BASE_VERSIONS_DIR[$DEFAULT]}/$version
fi
done
}
#
# Output bin path for <version>
#
display_bin_path_for_version() {
test -z $1 && abort "version required"
local version=${1#v}
if [ "$version" = "latest" ]; then
version=$(display_latest_version)
fi
if [ "$version" = "stable" ]; then
version=$(display_latest_stable_version)
fi
if [ "$version" = "lts" ]; then
version=$(display_latest_lts_version)
fi
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 <version> of node with [args ...]
#
execute_with_version() {
test -z $1 && abort "version required"
local version=${1#v}
if [ "$version" = "latest" ]; then
version=$(display_latest_version)
fi
if [ "$version" = "stable" ]; then
version=$(display_latest_stable_version)
fi
if [ "$version" = "lts" ]; then
version=$(display_latest_lts_version)
fi
local bin=${VERSIONS_DIR[$DEFAULT]}/$version/bin/node
shift # remove version
if test -f $bin; then
exec $bin "$@"
else
abort "$version is not installed"
fi
}
#
# Display the latest release version.
#
display_latest_version() {
$GET 2> /dev/null ${MIRROR[$DEFAULT]} \
| egrep "</a>" \
| 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 . \
| tail -n1
}
#
# Display the latest stable release version.
# Note: Stable is no longer used by nodejs to identify versions,
# so this could be considered deprecated. To avoid breaking
# existing usages and minimise code churn in meantime,
# return the lts version.
# lts is the version recommended for most users.
#
display_latest_stable_version() {
check_io_supported "--stable"
display_latest_lts_version
}
#
# Display the latest lts release version.
#
display_latest_lts_version() {
check_io_supported "--lts"
local folder_name=$($GET 2> /dev/null ${MIRROR[$DEFAULT]} \
| egrep "</a>" \
| egrep -o 'latest-[a-z]{2,}' \
| sort \
| tail -n1)
$GET 2> /dev/null ${MIRROR[$DEFAULT]}/$folder_name/ \
| egrep "</a>" \
| egrep -o '[0-9]+\.[0-9]+\.[0-9]+' \
| head -n1
}
#
# Display the versions available.
#
display_remote_versions() {
check_current_version
local versions=""
versions=$($GET 2> /dev/null ${MIRROR[$DEFAULT]} \
| egrep "</a>" \
| egrep -o '[0-9]+\.[0-9]+\.[0-9]+' \
| 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 " \e[2m$v\e[22m\n"
fi
fi
done
echo
}
#
# Handle arguments.
#
if test $# -eq 0; then
test -z "$(versions_paths)" && err_no_installed_print_help
display_versions
else
while test $# -ne 0; do
case $1 in
-V|--version) display_n_version ;;
-h|--help|help) display_help; exit ;;
-q|--quiet) set_quiet ;;
-d|--download) ACTIVATE=false ;;
--latest) display_latest_version; exit ;;
--stable) display_latest_stable_version; exit ;;
--lts) display_latest_lts_version; exit ;;
io) set_default $1 ;; # set bin and continue
project) DEFAULT=2 ;;
-a|--arch) shift; set_arch $1;; # set arch and continue
bin|which) display_bin_path_for_version $2; exit ;;
as|use) shift; execute_with_version $@; exit ;;
rm|-) shift; remove_versions $@; exit ;;
prune) prune_versions; exit ;;
latest) install_latest; exit ;;
stable) install_stable; exit ;;
lts) install_lts; exit ;;
ls|list) display_remote_versions; exit ;;
*) install $1; exit ;;
esac
shift
done
fi