Skip to content

Instantly share code, notes, and snippets.

@assaf
Created March 24, 2015 15:43
Show Gist options
  • Save assaf/ee377a186371e2e269a7 to your computer and use it in GitHub Desktop.
Save assaf/ee377a186371e2e269a7 to your computer and use it in GitHub Desktop.
nvm use <package.json>
#!/usr/bin/env bash
#
# Run nvmish in your current project directory to use the version of io.js/Node
# as specified in package.json. It will also install that version, if not
# already installed.
#
# If package.json specifies engines.iojs, uses the corresponding version of
# io.js, otherwise, if package.json specifies engines.node, uses the
# corresponding version of Node.js.
#
# You can specify an exact version number, or any semver-compatible range, in
# which case it will pick the most recent version available.
#
# $ source ./nvmish
# $ cd <project directory>
# $ cat package.json | jq --raw-output .engines.iojs
# > 1.6.2
# $ nvmish
# > Installing io.js v1.6.2 ...
# > ######################################################################## 100.0%
# > Now using io.js v1.6.2
#
# $ cat package.json | jq --raw-output .engines.node
# > 1.2.x
# $ nvmish
# > Now using node v0.12.0
source $(brew --prefix nvm)/nvm.sh
# Adapted from JSON.sh https://github.com/dominictarr/JSON.sh
nvm_json_throw () {
echo "$*" >&2
exit 1
}
nvm_json_awk_egrep () {
local pattern_string=$1
gawk '{
while ($0) {
start=match($0, pattern);
token=substr($0, start, RLENGTH);
print token;
$0=substr($0, start+RLENGTH);
}
}' pattern=$pattern_string
}
nvm_json_tokenize () {
local GREP
local ESCAPE
local CHAR
if echo "test string" | egrep -ao --color=never "test" &>/dev/null
then
GREP='egrep -ao --color=never'
else
GREP='egrep -ao'
fi
if echo "test string" | egrep -o "test" &>/dev/null
then
ESCAPE='(\\[^u[:cntrl:]]|\\u[0-9a-fA-F]{4})'
CHAR='[^[:cntrl:]"\\]'
else
GREP=nvm_json_awk_egrep
ESCAPE='(\\\\[^u[:cntrl:]]|\\u[0-9a-fA-F]{4})'
CHAR='[^[:cntrl:]"\\\\]'
fi
local STRING="\"$CHAR*($ESCAPE$CHAR*)*\""
local NUMBER='-?(0|[1-9][0-9]*)([.][0-9]*)?([eE][+-]?[0-9]*)?'
local KEYWORD='null|false|true'
local SPACE='[[:space:]]+'
$GREP "$STRING|$NUMBER|$KEYWORD|$SPACE|." | egrep -v "^$SPACE$"
}
_json_parse_array () {
local index=0
local ary=''
read -r token
case "$token" in
']') ;;
*)
while :
do
_json_parse_value "$1" "$index"
index=$((index+1))
ary="$ary""$value"
read -r token
case "$token" in
']') break ;;
',') ary="$ary," ;;
*) nvm_json_throw "EXPECTED , or ] GOT ${token:-EOF}" ;;
esac
read -r token
done
;;
esac
:
}
_json_parse_object () {
local key
local obj=''
read -r token
case "$token" in
'}') ;;
*)
while :
do
case "$token" in
'"'*'"') key=$token ;;
*) nvm_json_throw "EXPECTED string GOT ${token:-EOF}" ;;
esac
read -r token
case "$token" in
':') ;;
*) nvm_json_throw "EXPECTED : GOT ${token:-EOF}" ;;
esac
read -r token
_json_parse_value "$1" "$key"
obj="$obj$key:$value"
read -r token
case "$token" in
'}') break ;;
',') obj="$obj," ;;
*) nvm_json_throw "EXPECTED , or } GOT ${token:-EOF}" ;;
esac
read -r token
done
;;
esac
:
}
_json_parse_value () {
local jpath="${1:+$1,}$2" isleaf=0 isempty=0 print=0
case "$token" in
'{') _json_parse_object "$jpath" ;;
'[') _json_parse_array "$jpath" ;;
# At this point, the only valid single-character tokens are digits.
''|[!0-9]) nvm_json_throw "EXPECTED value GOT ${token:-EOF}" ;;
*) value=$token
isleaf=1
[ "$value" = '""' ] && isempty=1
;;
esac
[ "$value" = '' ] && return
[ "$isleaf" -eq 1 ] && [ $isempty -eq 0 ] && print=1
[ "$print" -eq 1 ] && printf "[%s]\t%s\n" "$jpath" "$value"
:
}
_json_parse () {
read -r token
_json_parse_value
read -r token
case "$token" in
'') ;;
*) nvm_json_throw "EXPECTED EOF GOT $token" ;;
esac
}
nvm_json_extract () {
local parsed=$(nvm_json_tokenize | _json_parse)
echo "$parsed" | grep -e "$1" | awk '{print $2 $3}'
}
# Install io.js/Node version specified in package.json
#
# Exit code:
# 0 Successfully installed and using latest version
# 1 No package.json in current directory
# 2 Found package.json but no engines specified
nvmish () {
if [[ ! -f package.json ]] ; then
echo "No package.json found in current directory"
return 1
fi
local INSTALL_VERSION
local NVM_VERSION_DIR
local IOJS_VERSION=$(nvm_json_extract '\["engines","iojs"\]' < package.json | tr -d '"')
if [[ -n "$IOJS_VERSION" ]] ; then
if ! [[ "$IOJS_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
IOJS_VERSION=$(curl --silent --get --data-urlencode "range=${IOJS_VERSION}" https://semver.herokuapp.com/iojs/resolve)
fi
NVM_VERSION="iojs-v$IOJS_VERSION"
NVM_VERSION_DIR="$(nvm_version_path "$NVM_VERSION")"
if [ ! -d "$NVM_VERSION_DIR" ]; then
echo "Installing io.js v$IOJS_VERSION ..."
nvm install $NVM_VERSION
else
nvm use $NVM_VERSION
fi
return $?
fi
local NODE_VERSION=$(nvm_json_extract '\["engines","node"\]' < package.json | tr -d '"')
if [[ -n "$NODE_VERSION" ]] ; then
if ! [[ "$NODE_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
NODE_VERSION=$(curl --silent --get --data-urlencode "range=${NODE_VERSION}" https://semver.herokuapp.com/node/resolve)
fi
NVM_VERSION="v$NODE_VERSION"
NVM_VERSION_DIR="$(nvm_version_path "$NVM_VERSION")"
if [ ! -d "$NVM_VERSION_DIR" ]; then
echo "Installing node v$NODE_VERSION ..."
nvm install $NVM_VERSION
else
nvm use $NVM_VERSION
fi
return $?
fi
echo "No engines.iojs or engine.node in package.json"
return 2
}
@ljharb
Copy link

ljharb commented Mar 24, 2015

Typically if people put v1.2.x in engines.node, they mean io.js - I've not seen anybody use engines.iojs before. Is there a reason to not support that? The likelihood that a) node will get to v1.0 and create a version conflict b) before the two projects merge, or one dies, is pretty slim

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment