diff --git a/.editorconfig b/.editorconfig index 32de192..a5403e1 100644 --- a/.editorconfig +++ b/.editorconfig @@ -14,3 +14,15 @@ indent_size = false [Makefile] indent_style = tab + +[test/fast/Unit tests/package_json_templates/*] +indent_style = unset + +[test/generated_semvers.sh] +indent_style = unset +indent_size = unset + +[test/fast/Unit tests/nvm_trim_and_reduce_whitespace_to_one_space] +indent_style = unset +indent_size = unset + diff --git a/nvm.sh b/nvm.sh index d710274..4ebaa12 100644 --- a/nvm.sh +++ b/nvm.sh @@ -299,6 +299,516 @@ nvm_find_up() { nvm_echo "${path_}" } +# NOTE: this function only validates across one line +nvm_string_contains_regexp() { + local string + string="${1-}" + local regexp + regexp="${2-}" + if [ -z "$string" ] || [ -z "$regexp" ]; then + return 1 + fi + # e.g. "nvm_string_contains_regexp abbc ^aa?b+.$" returns 0 + command printf '%s' "$string" | command awk "/$regexp/{ exit 0 }{ exit 1 }" +} + +# Validates that the given semver adheres to the following grammar: +# +# semver ::= comparator_set ( ' || ' comparator_set )* +# comparator_set ::= comparator ( ' ' comparator )* +# comparator ::= ( '<' | '<=' | '>' | '>=' | '' ) [0-9]+ '.' [0-9]+ '.' [0-9]+ +nvm_is_normalized_semver() { + nvm_string_contains_regexp "${1-}" '^( ?(<|<=|>|>=)?[0-9]+\.[0-9]+\.[0-9]+)+( \|\| ( ?(<|<=|>|>=)?[0-9]+\.[0-9]+\.[0-9]+)+)*$' +} + +nvm_trim_and_reduce_whitespace_to_one_space() { + command printf '%s' "${1-}" | + command tr '\n\r\t\v\b' ' ' | + command tr -s ' ' | + command sed 's/^ //; s/ $//; s/^ //' +} + +# Attempts to normalize the given semver to the grammar defined with the function nvm_is_normalized_semver +nvm_normalize_semver() { + # split the semantic version's comparator_set's onto their own lines for iteration + local semver + semver=$(nvm_trim_and_reduce_whitespace_to_one_space "${1-}" | command tr '||' '\n') + if [ -z "$semver" ]; then + return 1 + fi + local comparator_set + local validated_comparator_set + local validated_semver + validated_semver=''; + while [ -n "$semver" ]; do + comparator_set=$(command printf '%s' "$semver" | command head -n1) + semver=$(command printf '%s' "$semver" | command tail -n +2) + [ -n "$comparator_set" ] || continue + + # convert comparators into required grammar + validated_comparator_set=$(command printf ' %s ' "$comparator_set" | + command sed -E " + # exactly 1 space is needed before and after every comparator (including the first and last comparators) + s/\011/ /g;s/ +/ /g; + + # normalize all wildcards to x + s/X|\*/x/g; + + # space out numbers surrounding '-' + s/ ?- ?/ - /g; + + # ' 1 ' => ' 1.x.x ' + # ' ~x ' => ' ~x.x.x ' + s/ (~|\^)?([0-9]+|x) / \1\2.x.x /g; + + # ' 1.2 ' => ' 1.2.x ' + # ' ~1.x ' => ' ~1.x.x ' + # ' ^x.x ' => ' ^x.x.x ' + s/ (~|\^)?(([0-9]+|x)\.([0-9]+|x)) / \1\2.x /g; + + # ' 1.2.3 - 1.2.4 ' => ' >=1.2.3 <=1.2.4 ' + s/ (([0-9]+|x)\.([0-9]+|x)\.([0-9]+|x)) ?\- ?(([0-9]+|x)\.([0-9]+|x)\.([0-9]+|x)) / >=\1 <=\5 /g; + + # ' > 1.2.3 ' => ' >1.2.3 ' + # ' < 1.2.5 ' => ' <1.2.5 ' + # ' <= 1.2.3 ' => ' <=1.2.3 ' + # ' >= 1.2.3 ' => ' >=1.2.3 ' + # ' = 1.2.3 ' => ' =1.2.3 ' + # ' ~ 1.2.3 ' => ' ~1.2.3 ' + # ' ^ 1.2.3 ' => ' ^1.2.3 ' + # ' v 1.2.3 ' => ' v1.2.3 ' + s/ (v|<|>|<=|>=|=|~|\^) (([0-9]+|x)\.([0-9]+|x)\.([0-9]+|x)) / \1\2 /g; + + # ' =1.2.3 ' => ' 1.2.3 ' + # ' v1.2.3 ' => ' 1.2.3 ' + s/ (=|v)//g;" | + command awk '{ + # handle conversions of comparators with ^ or ~ or x into required grammar + # ` ^0.0.1 ` => ` >=0.0.1 <0.0.2 ` + # ` ^0.1.2 ` => ` >=0.1.2 <0.2.0 ` + # ` ^1.2.3 ` => ` >=1.2.3 <2.0.0 ` + # ` ^1.0.0 ` => ` >=1.0.0 <2.0.0 ` + # ` ~0.0.1 ` => ` >=0.0.1 <0.1.0 ` + # ` ~0.1.2 ` => ` >=0.1.2 <0.2.0 ` + # ` ~1.2.3 ` => ` >=1.2.3 <1.3.0 ` + # ` ~1.0.0 ` => ` >=1.0.0 <2.0.0 ` + # ` x.x.x ` => ` >0.0.0 ` + # ` x.x.1 ` => ` >0.0.0 ` + # ` x.1.x ` => ` >0.0.0 ` + # ` x.1.2 ` => ` >0.0.0 ` + # ` 1.2.x ` => ` >=1.2.0 <1.3.0 ` + # ` 1.x.1 ` => ` >=1.0.0 <2.0.0 ` NOTE the last "1" is ignored in this grammar + # ` 1.x.x ` => ` >=1.0.0 <2.0.0 ` + if ( ! match($0, /[\^~x]/) ) { + print $0 + exit 0 + } + split($0, comparators, / /) + output="" + for (i = 1; i <= length(comparators); i++) { + comparator=comparators[i] + if ( match(comparator, /^([\^~>]|>=)?x.[0-9x]+.[0-9x]$/ ) ) { + comparator=">0.0.0" + } else if ( match(comparator, /^([\^~<>]|>=|<=)?[0-9]+.x.[0-9x]$/) ) { + split(comparator, a, /\./); + if ( match(comparator, /^([\^~<>]|>=|<=).*$/ ) ) { + comparator=a[1] ".0.0"; + } else { + comparator=">=" a[1] ".0.0 <" a[1]+1 ".0.0" + } + } else if ( match(comparator, /^([\^~<>]|<=|>=)?[0-9]+.[0-9]+.x$/) ) { + split(comparator, a, /\./); + if ( match(comparator, /^([\^~<>]|<=|>=).*$/ ) ) { + comparator=a[1] "." a[2] ".0"; + } else { + comparator=">=" a[1] "." a[2] ".0 <" a[1] "." a[2]+1 ".0" + } + } + + if ( match(comparator, /^\^/) ) { + if ( match(comparator, /^\^0.0.[0-9]+$/) ) { + version=substr(comparator,2); + split(version, a, /\./); + output=output ">=" version " <0.0." a[3]+1 " "; + } else if ( match(comparator, /^\^0.[0-9]+.[0-9]+$/) ) { + version=substr(comparator,2); + split(version, a, /\./); + output=output ">=" version " <0." a[2]+1 ".0 "; + } else if ( match(comparator, /^\^[0-9]+.[0-9]+.[0-9]+$/) ) { + version=substr(comparator,2); + split(version, a, /\./); + output=output ">=" version " <" a[1]+1 ".0.0 "; + } + } else if ( match(comparator, /^~/) ) { + if ( match(comparator, /^~[0-9]+.0.0$/) ) { + version=substr(comparator,2) + split(version, a, /\./) + output=output ">=" version " <" a[1]+1 ".0.0 " + } else if ( match(comparator, /^~0.0.[0-9]+$/) ) { + version=substr(comparator,2) + split(version, a, /\./) + output=output ">=" version " <0." a[2]+1 ".0 " + } else if ( match(comparator, /^~0.[0-9]+.[0-9]+$/) ) { + version=substr(comparator,2) + split(version, a, /\./) + output=output ">=" version " <0." a[2]+1 ".0 " + } else if ( match(comparator, /^~[0-9]+.[0-9]+.[0-9]+$/) ) { + version=substr(comparator,2) + split(version, a, /\./) + output=output ">=" version " <" a[1] "." a[2]+1 ".0 " + } + } else { + output=output comparator " " + } + } + print output + }' | + command sed -E 's/^ +//;s/ +$//' + ) + + # only comparator_sets composed of the required grammar are marked as valid + if nvm_string_contains_regexp "$validated_comparator_set" '^( ?(<|<=|>|>=)?[0-9]+\.[0-9]+\.[0-9]+)+$'; then + validated_semver="$validated_semver || $validated_comparator_set" + else + return 1 + fi + done + + validated_semver=$(command printf '%s' "$validated_semver" | command sed -E 's/^ \|\| //') + + if nvm_is_normalized_semver "$validated_semver"; then + command printf '%s' "$validated_semver" + return 0 + fi + return 1 +} + +# Given a semver and version list, find the highest compatible version by doing the following: +# - Find the newest compatible version of each comparator set. +# - Resolve to the newest of all the newest compatible versions of each comparator set. +nvm_interpret_complex_semver() { + local semver + semver="${1-}" + local version_list + version_list="${2-}" # expected to be sorted from oldest to newest + if [ -z "$semver" ] || [ -z "$version_list" ] || ! nvm_is_normalized_semver "$semver"; then + return 1 + fi + + # For each comparator_set in the semver: + # - Try to resolve the comparator_set to its newest compatible version. + # - But stop looking for a compatible version for a comparator_set if the current_version being iterated on is lower than an already found compatible version. + # - If by the end of the algorithm, a version other than 0.0.0 is collected in highest_compatible_version, output that version. + semver=$(command printf '%s' "$semver" | command tr '||' '\n') + local version_list_copy + local current_comparator_set + local current_version + local current_comparator_set_copy + local current_comparator + local stripped_version_from_comparator + local highest_compatible_version + highest_compatible_version='0.0.0' + + while [ -n "$semver" ]; do + version_list_copy=$(command printf '%s' "$version_list") + current_comparator_set=$(command printf '%s' "$semver" | command head -n1 | command sed -E 's/^ +//;s/ +$//') + semver=$(command printf '%s' "$semver" | command tail -n +2) + [ -n "$current_comparator_set" ] || continue + + # For each version in the version_list_copy (iterating from newest to oldest): + # - If current_version satisfies all comparators in current_comparator_set, we've found the newest highest version compatible with all comparators in current current_comparator_set. + # - Store the current_version in highest_compatible_version and stop iterating through versions for current_comparator_set. + while [ -n "$version_list_copy" ]; do + current_version=$(command printf '%s' "$version_list_copy" | command tail -n1 | command sed -E 's/^ +//;s/ +$//' | nvm_grep -o '^[0-9]\+\.[0-9]\+\.[0-9]\+$') + version_list_copy=$(command printf '%s' "$version_list_copy" | command sed '$d') + [ -n "$current_version" ] || continue + if [ "$highest_compatible_version" != '0.0.0' ] && nvm_version_greater "$highest_compatible_version" "$current_version"; then + # If we previously found a compatible version that is higher than the current_version, there is no need to continue checking versions. + version_list_copy='' + continue + fi + + # For each comparator in the current_comparator_set_copy: + # - If current_version is compatible with all comparators, we know current_version is the newest compatible version + current_comparator_set_copy=$(command printf '%s' "$current_comparator_set" | command tr ' ' '\n') + while [ -n "$current_comparator_set_copy" ]; do + current_comparator=$(command printf '%s' "$current_comparator_set_copy" | command head -n1 | command sed -E 's/^ +//;s/ +$//') + current_comparator_set_copy=$(command printf '%s' "$current_comparator_set_copy" | command tail -n +2) + [ -n "$current_comparator" ] || continue + + stripped_version_from_comparator=$(command printf '%s' "$current_comparator" | nvm_grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+$') + if [ -z "$stripped_version_from_comparator" ]; then + return 1 + fi + + if nvm_string_contains_regexp "$current_comparator" '^[0-9]+\.[0-9]+\.[0-9]+$'; then + if [ "$stripped_version_from_comparator" = "$current_version" ]; then + # current_comparator is looking for an exact match, and the current_version is the exact match, so this current_comparator is satisfied. + if [ -z "$current_comparator_set_copy" ]; then + # Also, this is the last comparator in the current_comparator_set_copy so we can assume we've found the newest compatible version of the current_comparator_set. + highest_compatible_version="$current_version" + version_list_copy='' + fi + elif nvm_version_greater "$stripped_version_from_comparator" "$current_version"; then + # current_version is less than the stripped_version_from_comparator, so continuing iterating through version_list_copy is pointless. + current_comparator_set_copy='' + version_list_copy='' + else + # current_version is greater than the stripped_version_from_comparator, so we should continuing iterating through version_list_copy. + current_comparator_set_copy='' + fi + + elif nvm_string_contains_regexp "$current_comparator" '^<=[0-9]+\.[0-9]+\.[0-9]+$'; then + if nvm_version_greater_than_or_equal_to "$stripped_version_from_comparator" "$current_version"; then + # current_version is less than or equal to the current_comparator version number so this current_comparator is satisfied. + if [ -z "$current_comparator_set_copy" ]; then + # Also, this is the last comparator in the current_comparator_set_copy so we can assume we've found the newest compatible version of the current_comparator_set. + highest_compatible_version="$current_version" + version_list_copy='' + fi + else + # current_version is greater than the current_comparator version number so we should continue iterating through version_list_copy. + current_comparator_set_copy='' + fi + + elif nvm_string_contains_regexp "$current_comparator" '^>=[0-9]+\.[0-9]+\.[0-9]+$'; then + if nvm_version_greater_than_or_equal_to "$current_version" "$stripped_version_from_comparator"; then + # current_version is greater than or equal to the current_comparator version number so this current_comparator is satisfied. + if [ -z "$current_comparator_set_copy" ]; then + # Also, this is the last comparator in the current_comparator_set_copy so we can assume we've found the newest compatible version of the current_comparator_set. + highest_compatible_version="$current_version" + version_list_copy='' + fi + else + # current_version is less than the current_comparator version number so continuing iterating through version_list_copy is pointless. + current_comparator_set_copy='' + version_list_copy='' + fi + + elif nvm_string_contains_regexp "$current_comparator" '^<[0-9]+\.[0-9]+\.[0-9]+$'; then + if nvm_version_greater "$stripped_version_from_comparator" "$current_version"; then + # current_version is less than the current_comparator version number so this current_comparator is satisfied. + if [ -z "$current_comparator_set_copy" ]; then + # Also, this is the last comparator in the current_comparator_set_copy so we can assume we've found the newest compatible version of the current_comparator_set. + highest_compatible_version="$current_version" + version_list_copy='' + fi + else + # current_version is greater than or equal to the current_comparator version number so we should continue iterating through version_list_copy. + current_comparator_set_copy='' + fi + + elif nvm_string_contains_regexp "$current_comparator" '^>[0-9]+\.[0-9]+\.[0-9]+$'; then + if nvm_version_greater "$current_version" "$stripped_version_from_comparator"; then + # current_version is greater than the current_comparator version number so this current_comparator is satisfied. + if [ -z "$current_comparator_set_copy" ];then + # Also, this is the last comparator in the current_comparator_set_copy so we can assume we've found the newest compatible version of the current_comparator_set. + highest_compatible_version="$current_version" + version_list_copy='' + fi + else + # current_version is less than or equal to the current_comparator version number so continuing iterating through version_list_copy is pointless. + current_comparator_set_copy='' + version_list_copy='' + fi + + else + current_comparator_set_copy='' + fi + done # while [ -n "$current_comparator_set_copy" ]; do + done # while [ -n "$version_list_copy" ]; do + done # while [ -n "$semver" ]; do + + if [ -n "$highest_compatible_version" ] && [ "$highest_compatible_version" != '0.0.0' ]; then + command printf '%s' "$highest_compatible_version" + return 0 + fi + return 1 +} + +# Given a semver and version list, optimize discovery of highest compatible version with this function which quickly interprets some common semvers. +nvm_interpret_simple_semver() { + local semver + semver="${1-}" + local version_list + version_list="${2-}" # expected to be sorted from oldest to newest + if [ -z "$semver" ] || [ -z "$version_list" ] || ! nvm_string_contains_regexp "$semver" '^[<>=]*[0-9]+\.[0-9]+\.[0-9]+$'; then + return 1 + fi + local stripped_version_from_semver + stripped_version_from_semver=$(command printf '%s' "$semver" | nvm_grep -o '^[0-9]\+\.[0-9]\+\.[0-9]\+$') + local newest_version_from_list + newest_version_from_list=$(command printf '%s' "$version_list" | tail -n 1 | nvm_grep -o '^[0-9]\+\.[0-9]\+\.[0-9]\+$') + if [ -z "$stripped_version_from_semver" ] || [ -z "$newest_version_from_list" ]; then + return 1 + fi + local retrieved_version + # if the semver is looking for an exact match, and it exists in the provided list of versions, resolve to that version + if nvm_string_contains_regexp "$semver" '^[0-9]+\.[0-9]+\.[0-9]+$'; then + retrieved_version=$(command printf '%s' "$version_list" | nvm_grep "^$stripped_version_from_semver$") + if [ -n "$retrieved_version" ]; then + command printf '%s' "$retrieved_version" + return 0 + else + command printf '%s' 'STOP' # we have determined no node version will be compatible with the semver + return 1 + fi + + # Semver is looking for the newest version that is <= to a sepcific version, and the version exists in the provided list of versions, resolve to that version + elif nvm_string_contains_regexp "$semver" '^<=[0-9]+\.[0-9]+\.[0-9]+$'; then + retrieved_version=$(command printf '%s' "$version_list" | nvm_grep "^$stripped_version_from_semver$") + if [ -n "$retrieved_version" ]; then + command printf '%s' "$retrieved_version" + return 0 + else + return 1 # go on to try complex semver interpretation + fi + + # Semver is looking for the newest version >= a specific version, and the newest version in the provided list of versions is >= the specified version, resolve to that version. + elif nvm_string_contains_regexp "$semver" '^>=[0-9]+\.[0-9]+\.[0-9]+$'; then + if nvm_version_greater_than_or_equal_to "$newest_version_from_list" "$stripped_version_from_semver"; then + command printf '%s' "$newest_version_from_list" + return 0 + else + command printf '%s' 'STOP' # we have determined no node version will be compatible with the semver + return 1 + fi + + elif nvm_string_contains_regexp "$semver" '^>[0-9]+\.[0-9]+\.[0-9]+$'; then + if nvm_version_greater "$newest_version_from_list" "$stripped_version_from_semver"; then + command printf '%s' "$newest_version_from_list" + return 0 + else + command printf '%s' 'STOP' # we have determined no node version will be compatible with the semver + return 1 + fi + + else + return 1 + fi +} + +# Given a semantic version, resolve it to the newest compatible remote node version. +nvm_interpret_node_semver() { + local semver + semver="${1-}" + if [ -z "$semver" ]; then + return 1 + fi + + # Validate incoming semver and transform it into the grammar that is expected by the following logic + local valid_transformed_semver + valid_transformed_semver=$(nvm_normalize_semver "$semver") + if [ -z "$valid_transformed_semver" ]; then + return 1 + fi + + # list of node versions is sorted from oldest to newest + local remote_node_versions + remote_node_versions=$(nvm_ls_remote | nvm_grep -o 'v[0-9]\+\.[0-9]\+\.[0-9]\+' | sed 's/^v//g') + if [ -z "$remote_node_versions" ]; then + return 1 + fi + + # If semver is a single comparator, use quick algorithm to determine newest compatible version + local resolved_version + resolved_version=$(nvm_interpret_simple_semver "$valid_transformed_semver" "$remote_node_versions") + if [ "$resolved_version" = 'STOP' ]; then + return 1 # nvm_interpret_simple_semver determined no node version will be compatible with the semver + elif [ -n "$resolved_version" ]; then + command printf '%s' "$resolved_version" + return 0 + fi + + # If semver is a semver with > 1 comparator, iterate through each remote node version from newest to oldest until finding the newest version compatible with all comparators. + resolved_version=$(nvm_interpret_complex_semver "$valid_transformed_semver" "$remote_node_versions") + if [ -n "$resolved_version" ]; then + command printf '%s' "$resolved_version" + return 0 + fi + + return 1 +} + +nvm_find_package_json() { + dir="$(nvm_find_up 'package.json')" + if [ -e "${dir}/package.json" ]; then + command printf '%s' "${dir}/package.json" + fi +} + +# extracts engines.node value from package.json exactly except: +# - removes all line breaks and carriage returns +# - normalizes all consecutive whitespace to 1 occurrence +# - semantic expression must match regexp: "[|<> [:alnum:].^=~*-]\+" +nvm_get_node_from_pkg_json() { + local package_json_contents + package_json_contents="${1-}" + local engines_node_value + engines_node_value='' + local open_brackets + open_brackets=0 # a counter variable + local closed_brackets + closed_brackets=0 # a counter variable + local in_quotes + in_quotes=1 # a true/false variable + nvm_trim_and_reduce_whitespace_to_one_space "$package_json_contents" \ + | nvm_grep -o '"engines": \?{ \?".*' \ + | nvm_grep -o '{.*' \ + | nvm_grep -o . \ + | while read -r i; do + engines_node_value="$engines_node_value$i" + if [ "$i" = '"' ]; then + if [ $in_quotes -eq 1 ]; then + in_quotes=0 + else + in_quotes=1 + fi + # spaces are interpretted as '' here but they need to be retained + elif [ "$i" = '' ]; then + engines_node_value="$engines_node_value " + elif [ $in_quotes -eq 1 ]; then + if [ "$i" = '{' ]; then + open_brackets=$((open_brackets+1)) + elif [ "$i" = '}' ]; then + closed_brackets=$((closed_brackets+1)) + fi + fi + if [ "$open_brackets" -ne 0 ] && [ "$open_brackets" -eq "$closed_brackets" ]; then + command printf '%s' "$engines_node_value" \ + | nvm_grep -o '"node": \?"[|<> [:alnum:].^=~*-]\+"' \ + | command tr -d '"' \ + | command awk -F: '{ print $2 }' \ + | command sed 's/^ //; s/ $//; s/^ //' + return 0 + fi + done + return 1 +} + +nvm_package_json_version() { + export RESOLVED_PKG_JSON_VERSION='' + local pkg_json_path + pkg_json_path=$(nvm_find_package_json) + if [ ! -e "${pkg_json_path}" ]; then + nvm_err "No package.json file found" + return 1 + fi + local pkg_json_semver + pkg_json_semver=$(nvm_get_node_from_pkg_json "$(command cat "$pkg_json_path")" || command printf '') + if [ ! -n "${pkg_json_semver}" ]; then + nvm_err "Warning: could not retrieve engines.node semver expression in package.json file found at \"${pkg_json_path}\"" + return 1 + else + nvm_echo "Found '${pkg_json_path}' with semver expression <${pkg_json_semver}>" + # attempt complex semver range evaluation + RESOLVED_PKG_JSON_VERSION=$(nvm_interpret_node_semver "$pkg_json_semver") + if [ ! -n "${RESOLVED_PKG_JSON_VERSION}" ]; then + nvm_err "Warning: could not interpret engines.node semver expression obtained from package.json file." + return 1 + fi + fi +} nvm_find_nvmrc() { local dir @@ -2603,14 +3113,19 @@ nvm() { shift fi else - nvm_rc_version - if [ $version_not_provided -eq 1 ] && [ -z "$NVM_RC_VERSION" ]; then + nvm_rc_version || nvm_package_json_version + if [ -n "${NVM_RC_VERSION-}" ]; then + provided_version="$NVM_RC_VERSION" + elif [ -n "${RESOLVED_PKG_JSON_VERSION-}" ]; then + provided_version="$RESOLVED_PKG_JSON_VERSION" + elif [ $version_not_provided -eq 1 ]; then unset NVM_RC_VERSION + unset RESOLVED_PKG_JSON_VERSION >&2 nvm --help return 127 fi - provided_version="$NVM_RC_VERSION" unset NVM_RC_VERSION + unset RESOLVED_PKG_JSON_VERSION fi elif [ $# -gt 0 ]; then shift @@ -2954,12 +3469,20 @@ nvm() { if [ -n "${NVM_LTS-}" ]; then VERSION="$(nvm_match_version "lts/${NVM_LTS:-*}")" elif [ -z "${PROVIDED_VERSION-}" ]; then - nvm_rc_version + nvm_rc_version || nvm_package_json_version if [ -n "${NVM_RC_VERSION-}" ]; then PROVIDED_VERSION="$NVM_RC_VERSION" - VERSION="$(nvm_version "$PROVIDED_VERSION")" + elif [ -n "${RESOLVED_PKG_JSON_VERSION-}" ]; then + PROVIDED_VERSION="$RESOLVED_PKG_JSON_VERSION" + else + unset NVM_RC_VERSION + unset RESOLVED_PKG_JSON_VERSION + >&2 nvm --help + return 127 fi + VERSION="$(nvm_version "$PROVIDED_VERSION")" unset NVM_RC_VERSION + unset RESOLVED_PKG_JSON_VERSION else VERSION="$(nvm_match_version "$PROVIDED_VERSION")" fi @@ -3550,6 +4073,9 @@ nvm() { nvm_sanitize_path nvm_has_colors nvm_process_parameters \ node_version_has_solaris_binary iojs_version_has_solaris_binary \ nvm_curl_libz_support nvm_command_info \ + nvm_get_node_from_pkg_json nvm_find_package_json nvm_package_json_version \ + nvm_interpret_node_semver nvm_interpret_simple_semver nvm_interpret_complex_semver nvm_normalize_semver \ + nvm_is_normalized_semver nvm_string_contains_regexp nvm_trim_and_reduce_whitespace_to_one_space \ > /dev/null 2>&1 unset NVM_RC_VERSION NVM_NODEJS_ORG_MIRROR NVM_IOJS_ORG_MIRROR NVM_DIR \ NVM_CD_FLAGS NVM_BIN NVM_MAKE_JOBS \ diff --git a/test/fast/Unit tests/nvm_get_node_from_pkg_json b/test/fast/Unit tests/nvm_get_node_from_pkg_json new file mode 100755 index 0000000..1bef708 --- /dev/null +++ b/test/fast/Unit tests/nvm_get_node_from_pkg_json @@ -0,0 +1,118 @@ +#!/bin/sh + +\. ../../../nvm.sh +\. ../../generated_semvers.sh + +# This test suite validates the behavior of extracting the semver from a package.json's engine.node value given valid/invalid semvers and valid/invalid json structures. + +# (TEST 1 POSITIVE TEST CASES) +# SEMVERS: valid +# PACKAGE.JSON TEMPLATES: invalid +test_cases="$VALID_SEMVERS_FOR_PKG_JSON" +if [ -z "$test_cases" ]; then + die 'TEST 1 for nvm_get_node_from_pkg_json given an empty set of test cases' +fi +prev_semver='' +for template_file_name in package_json_templates/_valid_*; do + while [ -n "$test_cases" ]; do + semver=$(echo "$test_cases" | head -n1) + test_cases=$(echo "$test_cases" | tail -n +2) + [ -n "$semver" ] || continue + expectedOutput=$(nvm_trim_and_reduce_whitespace_to_one_space "$semver") + + if [ "$prev_semver" = "$semver" ]; then + die "Problem iterating through test_cases (TEST 1). Encountered the same value twice in a row. prev_semver='$prev_semver' semver='$semver'.\n" + fi + prev_semver="$semver" + + pkg_json_contents=$(sed "s/NODE_SEMVER/$semver/g" "$template_file_name" | tail -n +3) + actual_output=$(nvm_get_node_from_pkg_json "$pkg_json_contents") + if [ "$actual_output" != "$expectedOutput" ] || [ -z "$actual_output" ] || [ -z "$pkg_json_contents" ]; then + die "'nvm_get_node_from_pkg_json' POSITIVE test case failed (TEST 1). Expected '$expectedOutput' but got '$actual_output' when given input '$semver' and template '$template_file_name':\n$pkg_json_contents" + fi + done +done + +# (TEST 2 NEGATIVE TEST CASES) +# SEMVERS: valid +# PACKAGE.JSON TEMPLATES: invalid +test_cases="$VALID_SEMVERS_FOR_PKG_JSON" +if [ -z "$test_cases" ]; then + die 'TEST 2 for nvm_get_node_from_pkg_json given an empty set of test cases' +fi +prev_semver='' +for template_file_name in package_json_templates/_invalid_*; do + while [ -n "$test_cases" ]; do + semver=$(echo "$test_cases" | head -n1) + test_cases=$(echo "$test_cases" | tail -n +2) + [ -n "$semver" ] || continue + + if [ "$prev_semver" = "$semver" ]; then + die "Problem iterating through test_cases (TEST 2). Encountered the same value twice in a row. prev_semver='$prev_semver' semver='$semver'.\n" + fi + prev_semver="$semver" + + pkg_json_contents=$(sed "s/NODE_SEMVER/$semver/g" "$template_file_name" | tail -n +3) + actual_output=$(nvm_get_node_from_pkg_json "$pkg_json_contents") + if [ "$actual_output" != "" ] || [ -z "$semver" ] || [ -z "$pkg_json_contents" ]; then + die "'nvm_get_node_from_pkg_json' NEGATIVE test case failed (TEST 2). Expected to get empty string but got '$actual_output' when given input template '$template_file_name':\n$pkg_json_contents" + fi + done +done + + +# (TEST 3 NEGATIVE TEST CASES) +# SEMVERS: invalid +# PACKAGE.JSON TEMPLATES: valid +test_cases="$INVALID_SEMVERS_FOR_PKG_JSON" +if [ -z "$test_cases" ]; then + die 'TEST 3 for nvm_get_node_from_pkg_json given an empty set of test cases' +fi +prev_semver='' +for template_file_name in package_json_templates/_valid_*; do + while [ -n "$test_cases" ]; do + semver=$(echo "$test_cases" | head -n1) + [ -n "$semver" ] || continue + test_cases=$(echo "$test_cases" | tail -n +2) + + if [ "$prev_semver" = "$semver" ]; then + die "Problem iterating through test_cases (TEST 3). Encountered the same value twice in a row. prev_semver='$prev_semver' semver='$semver'.\n" + fi + prev_semver="$semver" + + pkg_json_contents=$(sed "s/NODE_SEMVER/$semver/g" "$template_file_name" | tail -n +3) + actual_output=$(nvm_get_node_from_pkg_json "$pkg_json_contents") + if [ "$actual_output" != "" ] || [ -z "$semver" ] || [ -z "$pkg_json_contents" ]; then + die "'nvm_get_node_from_pkg_json' NEGATIVE test case failed (TEST 3). Expected to get empty string but got '$actual_output' when given input template '$template_file_name':\n$pkg_json_contents" + fi + done +done + +# (TEST 4 NEGATIVE TEST CASES) +# SEMVERS: invalid +# PACKAGE.JSON TEMPLATES: invalid +test_cases="$INVALID_SEMVERS_FOR_PKG_JSON" +if [ -z "$test_cases" ]; then + die 'TEST 4 for nvm_get_node_from_pkg_json given an empty set of test cases' +fi +prev_semver='' +for template_file_name in package_json_templates/_invalid_*; do + while [ -n "$test_cases" ]; do + semver=$(echo "$test_cases" | head -n1) + [ -n "$semver" ] || continue + test_cases=$(echo "$test_cases" | tail -n +2) + + if [ "$prev_semver" = "$semver" ]; then + die "Problem iterating through test_cases (TEST 4). Encountered the same value twice in a row. prev_semver='$prev_semver' semver='$semver'.\n" + fi + prev_semver="$semver" + + pkg_json_contents=$(sed "s/NODE_SEMVER/$semver/g" "$template_file_name" | tail -n +3) + actual_output=$(nvm_get_node_from_pkg_json "$pkg_json_contents") + if [ "$actual_output" != "" ] || [ -z "$semver" ] || [ -z "$pkg_json_contents" ]; then + die "'nvm_get_node_from_pkg_json' NEGATIVE test case failed (TEST 4). Expected to get empty string but got '$actual_output' when given input template '$template_file_name':\n$pkg_json_contents" + fi + done +done +exit 0 + diff --git a/test/fast/Unit tests/nvm_is_normalized_semver b/test/fast/Unit tests/nvm_is_normalized_semver new file mode 100755 index 0000000..2be8cff --- /dev/null +++ b/test/fast/Unit tests/nvm_is_normalized_semver @@ -0,0 +1,44 @@ +#!/bin/sh + +\. ../../../nvm.sh +\. ../../generated_semvers.sh + +# nvm_is_normalized_semver validates that a given semver adheres to a particular grammar. See grammar with definition of function nvm_is_normalized_semver() in nvm.sh. + +# POSITIVE TEST CASES +positive_test_cases="$VALID_NORMALIZED_SEMVERS" +if [ -z "$positive_test_cases" ]; then + die 'positive test cases are empty' +fi +prev_semver='' +while [ -n "$positive_test_cases" ]; do + semver=$(echo "$positive_test_cases" | head -n1) + if [ -z "$semver" ] || ! nvm_is_normalized_semver "$semver"; then + die "nvm_is_normalized_semver POSITIVE test case failed. semver: '$semver'.\n" + fi + if [ "$prev_semver" = "$semver" ]; then + die "something is wrong. positive test cases received the same test case twice in a row. semver: '$semver'" + fi + prev_semver="$semver" + positive_test_cases=$(echo "$positive_test_cases" | tail -n +2) +done + +# NEGATIVE TEST CASES +negative_test_cases=$(printf "%s\n%s" "$VALID_NON_NORMALIZED_SEMVERS" "$INVALID_SEMVERS_FOR_PKG_JSON") +if [ -z "$negative_test_cases" ]; then + die 'negative test cases are empty' +fi +prev_semver='initialized to non empty string' +while [ -n "$negative_test_cases" ]; do + semver=$(echo "$negative_test_cases" | head -n1) + if nvm_is_normalized_semver "$semver"; then + die "nvm_is_normalized_semver NEGATIVE test case failed. semver: '$semver'.\n" + fi + if [ "$prev_semver" = "$semver" ]; then + die "something is wrong. negative test cases received the same test case twice in a row. semver: '$semver'" + fi + prev_semver="$semver" + negative_test_cases=$(echo "$negative_test_cases" | tail -n +2) +done +exit 0 + diff --git a/test/fast/Unit tests/nvm_normalize_semver b/test/fast/Unit tests/nvm_normalize_semver new file mode 100755 index 0000000..b68da17 --- /dev/null +++ b/test/fast/Unit tests/nvm_normalize_semver @@ -0,0 +1,102 @@ +#!/bin/sh + +\. ../../../nvm.sh +\. ../../generated_semvers.sh + +# This test suite validates the behavior of normalizing a semver from its raw form to a specific grammar. See the grammar defined with nvm_is_normalized_semver() in nvm.sh. + +# TEST 1: Validate that for already normalized semvers, nvm_normalize_semver outputs the same semver. +test_cases="$VALID_NORMALIZED_SEMVERS" +if [ -z "$test_cases" ]; then + die 'nvm_normalize_semver (TEST 1) was given an empty set of test cases' +fi +while [ -n "$test_cases" ]; do + semver=$(echo "$test_cases" | head -n1) + actual_output=$(nvm_normalize_semver "$semver") + if [ -z "$semver" ] || [ "$semver" != "$actual_output" ]; then + die "nvm_normalize_semver (TEST 1) test case failed. Expected output: '$semver'. Actual output: '$actual_output'. Input: '$semver'.\n" + fi + test_cases=$(echo "$test_cases" | tail -n +2) +done + + +# TEST 2: Validate that non normalized valid semvers produce a normalized result +test_cases="$VALID_NON_NORMALIZED_SEMVERS" +if [ -z "$test_cases" ]; then + die 'nvm_normalize_semver (TEST 2) was given an empty set of test cases' +fi +while [ -n "$test_cases" ]; do + semver=$(echo "$test_cases" | head -n1) + actual_output=$(nvm_normalize_semver "$semver") + if [ -z "$semver" ] || [ -z "$actual_output" ] || [ "$semver" = "$actual_output" ]; then + die "nvm_normalize_semver (TEST 2) test case failed. Expected output: '$semver'. Actual output: '$actual_output'. Input: '$semver'.\n" + fi + test_cases=$(echo "$test_cases" | tail -n +2) +done + +# TEST 3: Validate specific outputs for some specific inputs to nvm_normalize_semver. +# input:expected_output +test_cases="1.2.3:1.2.3 +1.2.3 - 1.2.4:>=1.2.3 <=1.2.4 +1.2.3-1.2.4:>=1.2.3 <=1.2.4 +11.22.33 - 11.22.44:>=11.22.33 <=11.22.44 +1.2.x - 1.2.4:>=1.2.0 <=1.2.4 +1.2.xx - 1.2.4: +1.2.3 || 1.2.4:1.2.3 || 1.2.4 +*:>0.0.0 +x:>0.0.0 +X:>0.0.0 +1:>=1.0.0 <2.0.0 +1.2:>=1.2.0 <1.3.0 +< 1.2.3:<1.2.3 +> 1.2.3:>1.2.3 +<= 1.2.3:<=1.2.3 +>= 1.2.3:>=1.2.3 += 1.2.3:1.2.3 +^0.0.1 ^0.1.2 ^1.2.3:>=0.0.1 <0.0.2 >=0.1.2 <0.2.0 >=1.2.3 <2.0.0 +~0.0.1 ~0.1.2 ~1.2.3:>=0.0.1 <0.1.0 >=0.1.2 <0.2.0 >=1.2.3 <1.3.0 +~ 1.2.3:>=1.2.3 <1.3.0 +^ 1.2.3:>=1.2.3 <2.0.0 +1.2.3 || 1.2.4 1.2.5:1.2.3 || 1.2.4 1.2.5 +a: +1 || 2 a: +1 || a: +a || 1.2.3: +1.2.?: +^0.0.1:>=0.0.1 <0.0.2 +=x.1.1:>0.0.0 +>x.1.1:>0.0.0 +~x.1.1:>0.0.0 +^x.1.1:>0.0.0 +11.22.33:11.22.33" + +if [ -z "$test_cases" ]; then + die 'nvm_normalize_semver (TEST 3) was given an empty set of test cases' +fi +while [ -n "$test_cases" ]; do + line=$(echo "$test_cases" | head -n1) + input=$(echo "$line" | awk -F: '{ print $1 }') + expected_output=$(echo "$line" | awk -F: '{ print $2 }') + actual_output=$(nvm_normalize_semver "$input") + if [ -z "$input" ] || [ "$actual_output" != "$expected_output" ]; then + die "nvm_normalize_semver (TEST 3) test case failed. Expected output: '$expected_output'. Actual output: '$actual_output'. Input: '$input'.\n" + fi + test_cases=$(echo "$test_cases" | tail -n +2) +done + +# TEST 4: Validate that invalid semvers with invalid characters that shouldn't be retrieved from the package.json produce no result from nvm_normalize_semver +test_cases="$INVALID_SEMVERS_FOR_PKG_JSON" +if [ -z "$test_cases" ]; then + die 'nvm_normalize_semver (TEST 4) was given an empty set of test cases' +fi +while [ -n "$test_cases" ]; do + semver=$(echo "$test_cases" | head -n1) + actual_output=$(nvm_normalize_semver "$semver") + if [ -z "$semver" ] || [ -n "$actual_output" ]; then + die "nvm_normalize_semver (TEST 4) test case failed. Expected no output but got: '$actual_output'. Input: '$semver'.\n" + fi + test_cases=$(echo "$test_cases" | tail -n +2) +done +exit 0 + diff --git a/test/fast/Unit tests/nvm_string_contains_regexp b/test/fast/Unit tests/nvm_string_contains_regexp new file mode 100755 index 0000000..b3116cf --- /dev/null +++ b/test/fast/Unit tests/nvm_string_contains_regexp @@ -0,0 +1,30 @@ +#!/bin/sh + +\. ../../../nvm.sh +\. ../../generated_semvers.sh +normalized_semver_regexp='^( ?(<|<=|>|>=)?[0-9]+\.[0-9]+\.[0-9]+)+( \|\| ( ?(<|<=|>|>=)?[0-9]+\.[0-9]+\.[0-9]+)+)*$' + +# Validates the behavior of nvm_string_contains_regexp() which returns 0 if the given string contains the given regular expression. + +# POSITIVE TEST CASES +test_cases="$VALID_NORMALIZED_SEMVERS" +while [ -n "$test_cases" ]; do + string=$(echo "$test_cases" | head -n1) + if [ -z "$normalized_semver_regexp" ] || [ -z "$string" ] || ! nvm_string_contains_regexp "$string" "$normalized_semver_regexp"; then + die "nvm_string_contains_regexp POSITIVE test case failed. regexp: '$normalized_semver_regexp'. string: '$string'.\n" + fi + test_cases=$(echo "$test_cases" | tail -n +2) +done + +# NEGATIVE TEST CASES +# string:regexp +test_cases=$(printf "%s\n%s" "$VALID_NON_NORMALIZED_SEMVERS" "$INVALID_SEMVERS_FOR_PKG_JSON") +while [ -n "$test_cases" ]; do + string=$(echo "$test_cases" | head -n1) + if [ -z "$normalized_semver_regexp" ] || nvm_string_contains_regexp "$string" "$normalized_semver_regexp"; then + die "nvm_string_contains_regexp NEGATIVE test case failed. regexp: '$normalized_semver_regexp'. string: '$string'.\n" + fi + test_cases=$(echo "$test_cases" | tail -n +2) +done +exit 0 + diff --git a/test/fast/Unit tests/nvm_trim_and_reduce_whitespace_to_one_space b/test/fast/Unit tests/nvm_trim_and_reduce_whitespace_to_one_space new file mode 100755 index 0000000..358a5f6 --- /dev/null +++ b/test/fast/Unit tests/nvm_trim_and_reduce_whitespace_to_one_space @@ -0,0 +1,53 @@ +#!/bin/sh + +\. ../../../nvm.sh +die () { printf "$@" ; exit 1; } + +# Validates the behavior of nvm_trim_and_reduce_whitespace_to_one_space() which consolidates all consecutive white space to one space, and then trims any spaces from the ends. + +# Test cases that have no new lines +# input:expected_output +test_cases='1:1 + 1 2 3:1 2 3 + 1.1 2.2 :1.1 2.2 + 1.2.3:1.2.3 + 1 1 :1 1 + 2 2 :2 2' + +while [ -n "$test_cases" ]; do + line=$(echo "$test_cases" | head -n1) + input=$(echo "$line" | awk -F: '{ print $1 }') + expected_output=$(echo "$line" | awk -F: '{ print $2 }') + actual_output=$(nvm_trim_and_reduce_whitespace_to_one_space "$input") + if [ -z "$input" ] || [ -z "$expected_output" ] || [ "$expected_output" != "$actual_output" ]; then + die "nvm_reduce_whitespace_to_one_space test case failed. expected_output: '$expected_output'. actual_output: '$actual_output'.\n" + fi + test_cases=$(echo "$test_cases" | tail -n +2) +done + +# Test cases that have new lines +expected_output='1 2 3' +test_case_with_new_line_1=' 1 + 2 3 ' +actual_output=$(nvm_trim_and_reduce_whitespace_to_one_space "$test_case_with_new_line_1") +if [ "$actual_output" != "$expected_output" ]; then + die "nvm_trim_and_reduce_whitespace_to_one_space test with test_case_with_new_line_1 failed. Actual output: '$actual_output' Expected output: '$expected_output'" +fi + +test_case_with_new_line_2=' 1 2 +3 ' +actual_output=$(nvm_trim_and_reduce_whitespace_to_one_space "$test_case_with_new_line_2") +if [ "$actual_output" != "$expected_output" ]; then + die "nvm_trim_and_reduce_whitespace_to_one_space test with test_case_with_new_line_2 failed. Actual output: '$actual_output' Expected output: '$expected_output'" +fi + +test_case_with_new_line_3=' 1 + +2 +3' +actual_output=$(nvm_trim_and_reduce_whitespace_to_one_space "$test_case_with_new_line_3") +if [ "$actual_output" != "$expected_output" ]; then + die "nvm_trim_and_reduce_whitespace_to_one_space test with test_case_with_new_line_3 failed. Actual output: '$actual_output' Expected output: '$expected_output'" +fi +exit 0 + diff --git a/test/fast/Unit tests/package_json_templates/_invalid_missing_engines b/test/fast/Unit tests/package_json_templates/_invalid_missing_engines new file mode 100644 index 0000000..093b795 --- /dev/null +++ b/test/fast/Unit tests/package_json_templates/_invalid_missing_engines @@ -0,0 +1,6 @@ +verifies incompatibility with package.json that is missing 'engines' key + +{ + "name": "fake", + "description": "fake" +} diff --git a/test/fast/Unit tests/package_json_templates/_invalid_missing_node b/test/fast/Unit tests/package_json_templates/_invalid_missing_node new file mode 100644 index 0000000..f3a6922 --- /dev/null +++ b/test/fast/Unit tests/package_json_templates/_invalid_missing_node @@ -0,0 +1,9 @@ +verifies incompatibility with package.json that is missing 'engines.node' key + +{ + "name": "fake", + "engines": { + "npm": "fake" + }, + "description": "fake" +} diff --git a/test/fast/Unit tests/package_json_templates/_invalid_missing_quotes b/test/fast/Unit tests/package_json_templates/_invalid_missing_quotes new file mode 100644 index 0000000..531b5ab --- /dev/null +++ b/test/fast/Unit tests/package_json_templates/_invalid_missing_quotes @@ -0,0 +1,9 @@ +verifies incompatibility with package.json that is missing quotes around the 'engines'node' value + +{ + "name": "fake", + "engines": { + "node": NODE_SEMVER + }, + "description": "fake" +} diff --git a/test/fast/Unit tests/package_json_templates/_valid_with_extra_bracket b/test/fast/Unit tests/package_json_templates/_valid_with_extra_bracket new file mode 100644 index 0000000..6c933c7 --- /dev/null +++ b/test/fast/Unit tests/package_json_templates/_valid_with_extra_bracket @@ -0,0 +1,9 @@ +verifies compatibility with basic package.json with keys in the 'engines' object that have brackets + +{ + "name": "fake", + "engines": { + "potentialFutureKeyWithBracket}": "fake", + "node": "NODE_SEMVER" + } +} diff --git a/test/fast/Unit tests/package_json_templates/_valid_with_spaces b/test/fast/Unit tests/package_json_templates/_valid_with_spaces new file mode 100644 index 0000000..8f273bb --- /dev/null +++ b/test/fast/Unit tests/package_json_templates/_valid_with_spaces @@ -0,0 +1,9 @@ +verifies compatibility with basic package.json indented with spaces + +{ + "name": "fake", + "engines": { + "node": "NODE_SEMVER" + }, + "description": "fake" +} diff --git a/test/fast/Unit tests/package_json_templates/_valid_with_tabs b/test/fast/Unit tests/package_json_templates/_valid_with_tabs new file mode 100644 index 0000000..ccca337 --- /dev/null +++ b/test/fast/Unit tests/package_json_templates/_valid_with_tabs @@ -0,0 +1,9 @@ +verifies compatibility with basic package.json indented with tabs + +{ + "name": "fake", + "engines": { + "node": "NODE_SEMVER" + }, + "description": "fake" +} diff --git a/test/fast/Unit tests/package_json_templates/_valid_with_tabs_in_semver b/test/fast/Unit tests/package_json_templates/_valid_with_tabs_in_semver new file mode 100644 index 0000000..1b1c73c --- /dev/null +++ b/test/fast/Unit tests/package_json_templates/_valid_with_tabs_in_semver @@ -0,0 +1,9 @@ +verifies compatibility with basic package.json with tabs in the 'engines.node' value + +{ + "name": "fake", + "engines": { + "node": " NODE_SEMVER " + }, + "description": "fake" +} diff --git a/test/generated_semvers.sh b/test/generated_semvers.sh new file mode 100755 index 0000000..422f11b --- /dev/null +++ b/test/generated_semvers.sh @@ -0,0 +1,196 @@ +#!/bin/sh + +NEWEST_NODE_VERSION=$(nvm_remote_version | grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+') +NEWEST_NODE_VERSION_5='5.12.0' + +# The only operators prefixing versions that would be acceptable inputs to the semver interpretation logic. +VALID_NORMALIZED_SEMVER_OPERATORS='< +> +<= +>=' + +# Valid semver operators that would be acceptable to find prefixing a semver in a package.json file, but would need to be validated/normalized before interpretting. +VALID_NON_NORMALIZED_SEMVER_OPERATORS='v += +~ +^' + +# Versions (stripped of any operators) that are considered valid inputs to the semver interpretation logic. +VALID_NORMALIZED_VERSIONS='4.1.0 +0.12.18 +8.0.0 +6.11.4 +10.0.0' + +# Semvers that won't be pulled from package.json files because they contain characters that are not included in valid semvers +INVALID_SEMVERS_FOR_PKG_JSON='&1 +# +$ +@ +! +% +& +) +( ++ +@1 +#1 +$1 +%s +1) +1( +1_ +1+ +1] +1[ +1" +1: +1? +1` +1!' + +# Semvers that won't resolve to a node version +INVALID_SEMVERS="$INVALID_SEMVERS_FOR_PKG_JSON +~1 +^1 +- += +^ +1 +a +asdf +1111 +1 1 +1. +1.2 +11.222 +1.2.a +1.*.* +1.x.x +11.22.a +=1.2.3 +1.1.1 2.2.2 +>1.1.1 <1.1.0 +1.2 - 1.3 +10.221.32 - 10.21.33 +10.212 - 10.22 +1.2.3 - 1.2.4 +1.2.3-1.2.4 +1.2 1.3 +10 20 +1||2 +>1000 +<0" + +# Valid semvers that should resolve to a node version and are slightly more complex than the [operator][version] structure +VALID_NORMALIZED_COMPLEX_SEMVERS='10.3.0 || 8.1.1 || 4.1.0 +7.7.2 || >=9.0.0 <=8.9.0 || <8.2.1 +8.2.0 8.2.0 +>4.0.0 <=5.0.0 +8.0.0 || <6.12.0' + +# Valid semvers that should resolve to a node version but need to be validated/normalized before interpretting. +VALID_NON_NORMALIZED_COMPLEX_SEMVERS='x +10 +~10 +^10 +X +* +x.x +X.X +*.* +10.x +~10.x +^10.x +10.X.X +~10.X.X +^10.X.X +x.x.x +~X.X.X +^X.X.X +X.X.X +x.X.* +*.x.X +x.1.2 +>1.1.1 <6.2.2 +> 1.1.1 <6.2.2 +10 - 11 +10-11 +4.2.2||8.1.1 +4.2 || 1.3 +4 || 2' + +# Strings that should be extracted from a package.json engines.node value but don't need to resolve to a node version. +VALID_COMPLEX_SEMVERS_FOR_PKG_JSON="$VALID_NORMALIZED_COMPLEX_SEMVERS +<1.2.3> +<1.2> +<1> +>>1 +<<1 +==1 +** +xx +^^1 +~~1 +10.211.32-10.211.33 +10.211-10.222 + 1 1 + 2 2 " + +die () { printf "$@" ; exit 1; } + +generate_semvers() { + versions="${1-}" + operators="${2-}" + should_add_spacing_permutations=${3-1} + if [ -z "$versions" ] || [ -z "$operators" ]; then + die "Problem generating semvers: Given invalid parameters. versions: '$versions' operators: '$operators'" + fi + while [ -n "$versions" ]; do + version=$(echo "$versions" | head -n1) + versions=$(echo "$versions" | tail -n +2) + + operators_copy="$operators" + while [ -n "$operators_copy" ]; do + operator=$(echo "$operators_copy" | head -n1) + operators_copy=$(echo "$operators_copy" | tail -n +2) + if [ -z "$semvers" ]; then + # NOTE: the third spacing permutation of the operator has a tab between the operator and version. + if [ $should_add_spacing_permutations -eq 0 ]; then + semvers=$(printf "%s\n%s\n%s" "${operator}${version}" "${operator} ${version}" "${operator} ${version}") + else + semvers="${operator}${version}" + fi + else + # NOTE: the third spacing permutation of the operator has a tab between the operator and version. + if [ $should_add_spacing_permutations -eq 0 ]; then + semvers=$(printf "%s\n%s\n%s\n%s" "$semvers" "${operator}${version}" "${operator} ${version}" "${operator} ${version}") + else + semvers=$(printf "%s\n%s" "$semvers" "${operator}${version}") + fi + fi + done + done + echo "$semvers" +} + +VALID_NORMALIZED_SEMVERS=$(printf "%s\n%s\n%s" \ + "$VALID_NORMALIZED_COMPLEX_SEMVERS" \ + "$VALID_NORMALIZED_VERSIONS" \ + "$(generate_semvers "$VALID_NORMALIZED_VERSIONS" "$VALID_NORMALIZED_SEMVER_OPERATORS")" \ +) + +VALID_NON_NORMALIZED_SEMVERS=$(printf "%s\n%s" \ + "$VALID_NON_NORMALIZED_COMPLEX_SEMVERS" \ + "$(generate_semvers "$VALID_NORMALIZED_VERSIONS" "$VALID_NON_NORMALIZED_SEMVER_OPERATORS" 0)" \ +) + +VALID_SEMVERS=$(printf "%s\n%s" \ + "$VALID_NORMALIZED_SEMVERS" \ + "$VALID_NON_NORMALIZED_SEMVERS" \ +) + +VALID_SEMVERS_FOR_PKG_JSON=$(printf "%s\n%s" \ + "$VALID_SEMVERS" \ + "$VALID_COMPLEX_SEMVERS_FOR_PKG_JSON" \ +) diff --git a/test/slow/nvm_interpret_node_semver b/test/slow/nvm_interpret_node_semver new file mode 100755 index 0000000..95a9284 --- /dev/null +++ b/test/slow/nvm_interpret_node_semver @@ -0,0 +1,80 @@ +#!/bin/sh + +die () { printf "$@" ; exit 1; } + +\. ../../nvm.sh +\. ../generated_semvers.sh + +# Verify that all generated valid normalized semvers produce some result +test_cases="$VALID_SEMVERS" +while [ -n "$test_cases" ]; do + semver=$(echo "$test_cases" | head -n1) + test_cases=$(echo "$test_cases" | tail -n +2) + + output=$(nvm_interpret_node_semver "$semver") + if [ -z "$semver" ] || [ -z "$output" ] || ! nvm_string_contains_regexp "$output" '^[0-9]+\.[0-9]+\.[0-9]+$'; then + die "nvm_interpret_node_semver generated positive test case failed expecting a version to be outputted: semver: '$semver' output: '$output'" + fi +done + +# Verify that all generated invalid normalized semvers do not produce a result +test_cases="$INVALID_SEMVERS" +while [ -n "$test_cases" ]; do + semver=$(echo "$test_cases" | head -n1) + test_cases=$(echo "$test_cases" | tail -n +2) + + output=$(nvm_interpret_node_semver "$semver") + if [ -z "$semver" ] || [ -n "$output" ]; then + die "nvm_interpret_node_semver generated negative test case failed: semver: '$semver' output: '$output'" + fi +done + +# Verify actual outputs given some inputs +# input:expected_output +test_cases="*:$NEWEST_NODE_VERSION +^5:$NEWEST_NODE_VERSION_5 +^5.0:$NEWEST_NODE_VERSION_5 +^5.x:$NEWEST_NODE_VERSION_5 +^5.X:$NEWEST_NODE_VERSION_5 +^5.*:$NEWEST_NODE_VERSION_5 +~5:$NEWEST_NODE_VERSION_5 +~5.0:$NEWEST_NODE_VERSION_5 +~5.x:$NEWEST_NODE_VERSION_5 +~5.X:$NEWEST_NODE_VERSION_5 +~5.*:$NEWEST_NODE_VERSION_5 +5:$NEWEST_NODE_VERSION_5 +x:$NEWEST_NODE_VERSION +X:$NEWEST_NODE_VERSION +x.x.x:$NEWEST_NODE_VERSION +0.12.18:0.12.18 +0.11.16:0.11.16 +7.0.0||7.2.1:7.2.1 +7-8:8.0.0 +7.0:7.0.0 +^7.0.0:7.10.1 +~8.1.0:8.1.4 +^7.0.0||6.8.1:7.10.1 +>0.12.18:$NEWEST_NODE_VERSION +>=0.11.16:$NEWEST_NODE_VERSION +7.1.0 || 7.3.0 || 7.2.0:7.3.0 +7.1.0 7.3.0 7.2.0: +5:$NEWEST_NODE_VERSION_5 +5.x:$NEWEST_NODE_VERSION_5 +5.x.x:$NEWEST_NODE_VERSION_5 +5.X:$NEWEST_NODE_VERSION_5 +5.X.X:$NEWEST_NODE_VERSION_5 +7.5.0:7.5.0" + +while [ -n "$test_cases" ]; do + line=$(echo "$test_cases" | head -n1) + input=$(echo "$line" | awk -F: '{ print $1 }') + expected_output=$(echo "$line" | awk -F: '{ print $2 }') + test_cases=$(echo "$test_cases" | tail -n +2) + + actualOutput=$(nvm_interpret_node_semver "$input") + if [ "$actualOutput" != "$expected_output" ] || [ -z "$input" ]; then + die "nvm_interpret_node_semver input/output test case failed. Expected output: '$expected_output'. Actual output: '$actualOutput'. Input: '$input'.\n" + fi +done +exit 0 +