Commit a5ee40ad authored by Terence Lee's avatar Terence Lee

Merge remote-tracking branch 'origin/versions'

parents 4a8eff92 1d061d0a
......@@ -29,6 +29,24 @@ Example usage:
The buildpack will detect your app as Node.js if it has the file `package.json` in the root. It will use NPM to install your dependencies, and vendors a version of the Node.js runtime into your slug. The `node_modules` directory will be cached between builds to allow for faster NPM install time.
Node.js and npm versions
------------------------
You can specify the versions of Node.js and npm your application requires using `package.json`
{
"name": "myapp",
"engines": {
"node": ">=0.4.7 <0.7.0",
"npm": ">=1.0.0"
}
}
To list the available versions of Node.js and npm, see these manifests:
http://heroku-buildpack-nodejs.s3.amazonaws.com/manifest.nodejs
http://heroku-buildpack-nodejs.s3.amazonaws.com/manifest.npm
Hacking
-------
......@@ -36,20 +54,19 @@ To use this buildpack, fork it on Github. Push up changes to your fork, then cr
To change the vendored binaries for Node.js, NPM, and SCons, use the helper scripts in the `support/` subdirectory. You'll need an S3-enabled AWS account and a bucket to store your binaries in.
For example, you can change the vendored version of Node.js to v0.5.8.
For example, you can change the default version of Node.js to v0.6.7.
First you'll need to build a Heroku-compatible version of Node.js:
$ export AWS_ID=xxx AWS_SECRET=yyy S3_BUCKET=zzz
$ s3 create $S3_BUCKET
$ support/package_node 0.5.8
$ support/package_nodejs 0.6.7
Open `bin/compile` in your editor, and change the following lines:
NODE_VERSION="0.5.8"
DEFAULT_NODE_VERSION="0.6.7"
S3_BUCKET=zzz
Commit and push the changes to your buildpack to your Github fork, then push your sample app to Heroku to test. You should see:
-----> Vendoring node 0.5.8
-----> Vendoring node 0.6.7
#!/usr/bin/env bash
# bin/compile <build-dir> <cache-dir>
mktmpdir() {
# fail fast
set -e
# debug
# set -x
# clean up leaking environment
unset GIT_DIR
# config
SCONS_VERSION="1.2.0"
S3_BUCKET="heroku-buildpack-nodejs"
# parse and derive params
BUILD_DIR=$1
CACHE_DIR=$2
LP_DIR=`cd $(dirname $0); cd ..; pwd`
CACHE_STORE_DIR="$CACHE_DIR/node_modules/$NPM_VERSION"
CACHE_TARGET_DIR="$BUILD_DIR/node_modules"
function error() {
echo " ! $*"
exit 1
}
function mktmpdir() {
dir=$(mktemp -t node-$1-XXXX)
rm -rf $dir
mkdir -p $dir
......@@ -16,8 +41,10 @@ function indent() {
esac
}
run_npm() {
function run_npm() {
command="$1"
cd $BUILD_DIR
HOME="$BUILD_DIR" $VENDORED_NODE/bin/node $VENDORED_NPM/cli.js $command 2>&1 | indent
if [ "${PIPESTATUS[*]}" != "0 0" ]; then
......@@ -26,21 +53,76 @@ run_npm() {
fi
}
# clean up leaking environment
unset GIT_DIR
function manifest_versions() {
curl "http://${S3_BUCKET}.s3.amazonaws.com/manifest.${1}" -s -o - | tr -s '\n' ' '
}
# config
NODE_VERSION="0.4.7"
NPM_VERSION="1.0.94"
SCONS_VERSION="1.2.0"
S3_BUCKET="heroku-buildpack-nodejs"
function resolve_version() {
available_versions="$1"
requested_version="$2"
default_version="$3"
if [ "$2" == "" ]; then
echo $3
else
args=""
for version in $available_versions; do args="${args} -v \"${version}\""; done
for version in $requested_version; do args="${args} -r \"${version}\""; done
eval $bootstrap_node/bin/node $LP_DIR/vendor/node-semver/bin/semver ${args} | tail -n1
fi
}
# parse and derive params
BUILD_DIR=$1
CACHE_DIR=$2
LP_DIR=`cd $(dirname $0); cd ..; pwd`
CACHE_STORE_DIR="$CACHE_DIR/node_modules/$NPM_VERSION"
CACHE_TARGET_DIR="$BUILD_DIR/node_modules"
function package_engine_version() {
version=$(cat $BUILD_DIR/package.json | $bootstrap_node/bin/node $LP_DIR/vendor/json/json engines.$1 2>/dev/null)
if [ $? == 0 ]; then
echo $version
fi
}
function package_resolve_version() {
engine="$1"
resolved_version=$(resolve_version "${engine_versions[$engine]}" "${engine_requests[$engine]}" "${engine_defaults[$engine]}")
if [ "${resolved_version}" == "" ]; then
error "Requested engine $engine version ${engine_requests[$engine]} does not match available versions: ${engine_versions[$engine]}"
else
echo $resolved_version
fi
}
function package_download() {
engine="$1"
version="$2"
location="$3"
mkdir -p $location
package="http://${S3_BUCKET}.s3.amazonaws.com/$engine-$version.tgz"
curl $package -s -o - | tar xzf - -C $location
}
bootstrap_node=$(mktmpdir bootstrap_node)
package_download "nodejs" "0.4.7" $bootstrap_node
# make some associative arrays
declare -A engine_versions
declare -A engine_defaults
declare -A engine_requests
engine_defaults["node"]="0.4.7"
engine_defaults["npm"]="1.0.94"
engine_versions["node"]=$(manifest_versions "nodejs")
engine_requests["node"]=$(package_engine_version "node")
engine_versions["npm"]=$(manifest_versions "npm")
engine_requests["npm"]=$(package_engine_version "npm")
echo "-----> Resolving engine versions"
NODE_VERSION=$(package_resolve_version "node")
echo "Using Node.js version: ${NODE_VERSION}" | indent
NPM_VERSION=$(package_resolve_version "npm")
echo "Using npm version: ${NPM_VERSION}" | indent
# s3 packages
NODE_PACKAGE="http://${S3_BUCKET}.s3.amazonaws.com/nodejs-${NODE_VERSION}.tgz"
......@@ -54,13 +136,13 @@ VENDORED_SCONS="$(mktmpdir scons)"
# download and unpack packages
echo "-----> Fetching Node.js binaries"
mkdir -p $VENDORED_NODE && curl $NODE_PACKAGE -s -o - | tar xzf - -C $VENDORED_NODE
mkdir -p $VENDORED_NPM && curl $NPM_PACKAGE -s -o - | tar xzf - -C $VENDORED_NPM
mkdir -p $VENDORED_SCONS && curl $SCONS_PACKAGE -s -o - | tar xzf - -C $VENDORED_SCONS
package_download "nodejs" "${NODE_VERSION}" "${VENDORED_NODE}"
package_download "npm" "${NPM_VERSION}" "${VENDORED_NPM}"
package_download "scons" "${SCONS_VERSION}" "${VENDORED_SCONS}"
# vendor node into the slug
PATH="$BUILD_DIR/bin:$PATH"
echo "-----> Vendoring node $NODE_VERSION"
echo "-----> Vendoring node into slug"
mkdir -p "$BUILD_DIR/bin"
cp "$VENDORED_NODE/bin/node" "$BUILD_DIR/bin/node"
......@@ -93,12 +175,9 @@ if [ -d $CACHE_STORE_DIR ]; then
fi
# install dependencies with npm
echo "-----> Installing dependencies with npm $NPM_VERSION"
cd $BUILD_DIR
run_npm install
run_npm rebuild
echo "-----> Installing dependencies with npm"
run_npm "install"
run_npm "rebuild"
echo "Dependencies installed" | indent
# repack cache with new assets
......
......@@ -117,14 +117,17 @@ s3_curl() {
# $2 = remote bucket.
# $3 = remote name
# $4 = local name.
# $5 = mime type
local bucket remote date sig md5 arg inout headers
# header handling is kinda fugly, but it works.
bucket="${2:+/${2}}/" # slashify the bucket
remote="$(urlenc "${3}")" # if you don't, strange things may happen.
stdopts="--connect-timeout 10 --fail --silent"
mime="${5}"
[[ $CURL_S3_DEBUG == true ]] && stdopts="${stdopts} --show-error --fail"
case "${1}" in
GET) arg="-o" inout="${4:--}" # stdout if no $4
headers[${#headers[@]}]="x-amz-acl: public-read"
;;
PUT) [[ ${2} ]] || die "PUT can has bucket?"
if [[ ! ${3} ]]; then
......@@ -135,6 +138,9 @@ s3_curl() {
arg="-T" inout="${4}"
headers[${#headers[@]}]="x-amz-acl: public-read"
headers[${#headers[@]}]="Expect: 100-continue"
if [ "$mime" != "" ]; then
headers[${#headers[@]}]="Content-Type: $mime"
fi
else
die "Cannot write non-existing file ${4}"
fi
......@@ -145,7 +151,7 @@ s3_curl() {
*) die "Unknown verb ${1}. It probably would not have worked anyways." ;;
esac
date="$(TZ=UTC date '+%a, %e %b %Y %H:%M:%S %z')"
sig=$(s3_signature_string ${1} "${date}" "${bucket}" "${remote}" "${md5}" "" "x-amz-acl:public-read")
sig=$(s3_signature_string ${1} "${date}" "${bucket}" "${remote}" "${md5}" "${mime}" "x-amz-acl:public-read")
headers[${#headers[@]}]="Authorization: AWS ${AWS_ID}:${sig}"
headers[${#headers[@]}]="Date: ${date}"
......@@ -159,7 +165,8 @@ s3_put() {
# $1 = remote bucket to put it into
# $2 = remote name to put
# $3 = file to put. This must be present if $2 is.
s3_curl PUT "${1}" "${2}" "${3:-${2}}"
# $4 = mime type
s3_curl PUT "${1}" "${2}" "${3:-${2}}" "${4}"
return $?
}
......
#!/bin/sh
set -e
manifest_type="$1"
if [ "$manifest_type" == "" ]; then
echo "usage: $0 <nodejs|npm>"
exit 1
fi
if [ "$AWS_ID" == "" ]; then
echo "must set AWS_ID"
exit 1
fi
if [ "$AWS_SECRET" == "" ]; then
echo "must set AWS_SECRET"
exit 1
fi
if [ "$S3_BUCKET" == "" ]; then
echo "must set S3_BUCKET"
exit 1
fi
basedir="$( cd -P "$( dirname "$0" )" && pwd )"
# make a temp directory
tempdir="$( mktemp -t node_XXXX )"
rm -rf $tempdir
mkdir -p $tempdir
pushd $tempdir
# generate manifest
$basedir/aws/s3 ls $S3_BUCKET \
| grep "^${manifest_type}" \
| sed -e "s/${manifest_type}-\([0-9.]*\)\\.tgz/\\1/" \
| awk 'BEGIN {FS="."} {printf("%03d.%03d.%03d %s\n",$1,$2,$3,$0)}' | sort -r | cut -d" " -f2 \
> manifest.${manifest_type}
# upload manifest to s3
$basedir/aws/s3 put $S3_BUCKET \
manifest.${manifest_type} "" "text/plain"
......@@ -30,14 +30,14 @@ basedir="$( cd -P "$( dirname "$0" )" && pwd )"
tempdir="$( mktemp -t node_XXXX )"
rm -rf $tempdir
mkdir -p $tempdir
pushd $tempdir
cd $tempdir
# download and extract node
curl http://nodejs.org/dist/node-v${node_version}.tar.gz -o node.tgz
curl http://nodejs.org/dist/v${node_version}/node-v${node_version}.tar.gz -o node.tgz
tar xzvf node.tgz
# go into node dir
pushd node-v${node_version}
cd node-v${node_version}
# build and package nodejs for heroku
vulcan build -v -o $tempdir/node-${node_version}.tgz
......@@ -47,7 +47,7 @@ $basedir/aws/s3 put $S3_BUCKET \
nodejs-${node_version}.tgz $tempdir/node-${node_version}.tgz
# go into scons
pushd tools/scons
cd tools/scons
# package scons
scons_version=$(ls | grep "scons-local" | cut -d- -f3)
......@@ -56,3 +56,6 @@ tar czvf $tempdir/scons-${scons_version}.tgz *
# upload scons to s3
$basedir/aws/s3 put $S3_BUCKET \
scons-${scons_version}.tgz $tempdir/scons-${scons_version}.tgz
# generate manifest
$basedir/manifest nodejs
......@@ -48,3 +48,6 @@ tar czvf $tempdir/npm-${npm_version}.tgz *
# upload npm to s3
$basedir/aws/s3 put $S3_BUCKET \
npm-${npm_version}.tgz $tempdir/npm-${npm_version}.tgz
# generate manifest
$basedir/manifest npm
json: https://github.com/trentm/json
node-semver: http://github.com/isaacs/node-semver
#!/usr/bin/env node
//
// json -- pipe in your JSON for nicer output and for extracting data bits
//
// See <https://github.com/trentm/json>.
//
var VERSION = "2.0.4";
var util = require('util');
var pathlib = require('path');
var runInNewContext = require('vm').runInNewContext;
var warn = console.warn;
//--- exports for module usage
exports.main = main;
exports.getVersion = getVersion;
exports.parseLookup = parseLookup;
// As an exported API, these are still experimental:
exports.lookupDatum = lookupDatum;
exports.printDatum = printDatum;
//---- globals and constants
// Output modes.
var OM_JSONY = 1;
var OM_JSON = 2;
var OM_INSPECT = 3;
var OM_COMPACT = 4;
var OM_FROM_NAME = {
"jsony": OM_JSONY,
"json": OM_JSON,
"inspect": OM_INSPECT,
"compact": OM_COMPACT
}
//---- support functions
function getVersion() {
return VERSION;
}
function isArray(ar) {
return ar instanceof Array ||
Array.isArray(ar) ||
(ar && ar !== Object.prototype && isArray(ar.__proto__));
}
function printHelp() {
util.puts("Usage:");
util.puts(" <something generating JSON on stdout> | json [OPTIONS] [LOOKUPS...]");
util.puts("");
util.puts("Pipe in your JSON for nicer output or supply one or more `LOOKUPS`");
util.puts("to extract a subset of the JSON. HTTP header blocks (as from `curl -i`)");
util.puts("are skipped by default.");
util.puts("");
util.puts("Auto-arrayification:");
util.puts(" Adjacent objects or arrays are 'arrayified'. To attempt to avoid");
util.puts(" false positives inside JSON strings, *adjacent* elements must");
util.puts(" have either no whitespace separation or at least a newline");
util.puts(" separation.");
util.puts("");
util.puts("Examples:");
util.puts(" # pretty printing");
util.puts(" $ curl -s http://search.twitter.com/search.json?q=node.js | json");
util.puts("");
util.puts(" # lookup fields");
util.puts(" $ curl -s http://search.twitter.com/search.json?q=node.js | json results[0]");
util.puts(" {");
util.puts(" \"created_at\": \"Tue, 08 Nov 2011 19:07:25 +0000\",");
util.puts(" \"from_user\": \"im2b\",");
util.puts(" ...");
util.puts("");
util.puts(" # array processing");
util.puts(" $ curl -s http://search.twitter.com/search.json?q=node.js | json results \\");
util.puts(" json -a from_user");
util.puts(" im2b");
util.puts(" myalltop_paul");
util.puts(" ...");
util.puts("");
util.puts(" # auto-arrayification")
util.puts(" $ echo '{\"a\":1}{\"b\":2}' | json -o json-0");
util.puts(" [{\"a\":1},{\"b\":2}]");
util.puts(" $ echo '[1,2][3,4]' | json -o json-0");
util.puts(" [{\"a\":1},{\"b\":2}]");
util.puts("");
util.puts("Options:");
util.puts(" -h, --help print this help info and exit");
util.puts(" --version print version of this command and exit");
util.puts(" -q, --quiet don't warn if input isn't valid JSON");
util.puts("");
util.puts(" -H drop any HTTP header block (as from `curl -i ...`)");
util.puts(" -a, --array process input as an array of separate inputs");
util.puts(" and output in tabular form");
util.puts(" -d DELIM delimiter string for tabular output (default is ' ')");
util.puts("");
util.puts(" -o, --output MODE Specify an output mode. One of");
util.puts(" jsony (default): JSON with string quotes elided");
util.puts(" json: JSON output, 2-space indent");
util.puts(" json-N: JSON output, N-space indent, e.g. 'json-4'");
util.puts(" inspect: node.js `util.inspect` output");
util.puts(" -i shortcut for `-o inspect`");
util.puts(" -j shortcut for `-o json`");
util.puts("");
util.puts("See <https://github.com/trentm/json> for more complete docs.");
}
/**
* Parse the command-line options and arguments into an object.
*
* {
* 'args': [...] // arguments
* 'help': true, // true if '-h' option given
* // etc.
* }
*
* @return {Object} The parsed options. `.args` is the argument list.
* @throws {Error} If there is an error parsing argv.
*/
function parseArgv(argv) {
var parsed = {
args: [],
help: false,
quiet: false,
dropHeaders: false,
outputMode: OM_JSONY,
jsonIndent: 2,
delim: ' '
};
// Turn '-iH' into '-i -H', except for argument-accepting options.
var args = argv.slice(2); // drop ['node', 'scriptname']
var newArgs = [];
var optTakesArg = {'d': true, 'o': true};
for (var i = 0; i < args.length; i++) {
if (args[i].charAt(0) === "-" && args[i].charAt(1) !== '-' && args[i].length > 2) {
var splitOpts = args[i].slice(1).split("");
for (var j = 0; j < splitOpts.length; j++) {
newArgs.push('-' + splitOpts[j])
if (optTakesArg[splitOpts[j]]) {
var optArg = splitOpts.slice(j+1).join("");
if (optArg.length) {
newArgs.push(optArg);
}
break;
}
}
} else {
newArgs.push(args[i]);
}
}
args = newArgs;
endOfOptions = false;
while (args.length > 0) {
var arg = args.shift();
switch(arg) {
case "--":
endOfOptions = true;
break;
case "-h": // display help and exit
case "--help":
parsed.help = true;
break;
case "--version":
parsed.version = true;
break;
case "-q":
case "--quiet":
parsed.quiet = true;
break;
case "-H": // drop any headers
parsed.dropHeaders = true;
break;
case "-o":
case "--output":
var name = args.shift();
var idx = name.lastIndexOf('-');
if (idx !== -1) {
var indent = Number(name.slice(idx+1));
if (! isNaN(indent)) {
parsed.jsonIndent = indent;
name = name.slice(0, idx);
}
}
parsed.outputMode = OM_FROM_NAME[name];
if (parsed.outputMode === undefined) {
throw new Error("unknown output mode: '"+name+"'");
}
break;
case "-i": // output with util.inspect
parsed.outputMode = OM_INSPECT;
break;
case "-j": // output with JSON.stringify
parsed.outputMode = OM_JSON;
break;
case "-a":
case "--array":
parsed.array = true;
break;
case "-d":
parsed.delim = args.shift();
break;
default: // arguments
if (!endOfOptions && arg.length > 0 && arg[0] === '-') {
throw new Error("unknown option '"+arg+"'");
}
parsed.args.push(arg);
break;
}
}
//TODO: '--' handling and error on a first arg that looks like an option.
return parsed;
}
function isInteger(s) {
return (s.search(/^-?[0-9]+$/) == 0);
}
// Parse a lookup string into a list of lookup bits. E.g.:
// "a.b.c" -> ["a","b","c"]
// "b['a']" -> ["b","['a']"]
function parseLookup(lookup) {
//var debug = console.warn;
var debug = function () {};
var bits = [];
debug("\n*** "+lookup+" ***")
bits = [];
var bit = "";
var states = [null];
var escaped = false;
var ch = null;
for (var i=0; i < lookup.length; ++i) {
var escaped = (!escaped && ch === '\\');
var ch = lookup[i];
debug("-- i="+i+", ch="+JSON.stringify(ch)+" escaped="+JSON.stringify(escaped))
debug("states: "+JSON.stringify(states))
if (escaped) {
bit += ch;
continue;
}
switch (states[states.length-1]) {
case null:
switch (ch) {
case '"':
case "'":
states.push(ch);
bit += ch;
break;
case '[':
states.push(ch);
if (bit !== "") {
bits.push(bit);
bit = ""
}
bit += ch;
break;
case '.':
if (bit !== "") {
bits.push(bit);
bit = ""
}
break;
default:
bit += ch;
break;
}
break;
case '[':
bit += ch;
switch (ch) {
case '"':
case "'":
case '[':
states.push(ch);
break;
case ']':
states.pop();
if (states[states.length-1] === null) {
bits.push(bit);
bit = ""
}
break;
}
break;
case '"':
bit += ch;
switch (ch) {
case '"':
states.pop();
if (states[states.length-1] === null) {
bits.push(bit);
bit = ""
}
break;
}
break;
case "'":
bit += ch;
switch (ch) {
case "'":
states.pop();
if (states[states.length-1] === null) {
bits.push(bit);
bit = ""
}
break;
}
break;
}
debug("bit: "+JSON.stringify(bit))
debug("bits: "+JSON.stringify(bits))
}
if (bit !== "") {
bits.push(bit);
bit = ""
}
debug(JSON.stringify(lookup)+" -> "+JSON.stringify(bits))
return bits
}
/**
* Parse the given stdin input into:
* {
* "error": ... error object if there was an error ...,
* "datum": ... parsed object if content was JSON ...
* }
*/
function parseInput(buffer) {
try {
return {datum: JSON.parse(buffer)};
} catch(e) {
// Special case: Auto-arrayification of unjoined list of objects:
// {"one": 1}{"two": 2}
// and auto-concatenation of unjoined list of arrays:
// ["a", "b"]["c", "d"]
//
// This can be nice to process a stream of JSON objects generated from
// multiple calls to another tool or `cat *.json | json`.
//
// Rules:
// - Only JS objects and arrays. Don't see strong need for basic
// JS types right now and this limitation simplifies.
// - The break between JS objects has to include a newline:
// {"one": 1}
// {"two": 2}
// or no spaces at all:
// {"one": 1}{"two": 2}
// I.e., not this:
// {"one": 1} {"two": 2}
// This condition should be fine for typical use cases and ensures
// no false matches inside JS strings.
var newBuffer = buffer;
[/(})\s*\n\s*({)/g, /(})({")/g].forEach(function (pat) {
newBuffer = newBuffer.replace(pat, "$1,\n$2");
});
[/(\])\s*\n\s*(\[)/g, /(\])(\[)/g].forEach(function (pat) {
newBuffer = newBuffer.replace(pat, ",\n");
});
if (buffer !== newBuffer) {
newBuffer = newBuffer.trim();
if (newBuffer[0] !== '[') {
newBuffer = '[\n' + newBuffer;
}
if (newBuffer.slice(-1) !== ']') {
newBuffer = newBuffer + '\n]\n';
}
try {
return {datum: JSON.parse(newBuffer)};
} catch (e2) {
}
}
return {error: e}
}
}
/**
* Apply a lookup to the given datum.
*
* @argument datum {Object}
* @argument lookup {Array} The parsed lookup (from
* `parseLookup(<string>)`). Might be empty.
* @returns {Object} The result of the lookup.
*/
function lookupDatum(datum, lookup) {
// Put it back together with some convenience transformations.
var lookupCode = "";
var isJSIdentifier = /^[$A-Za-z_][0-9A-Za-z_]*$/;
for (var i=0; i < lookup.length; i++) {
var bit = lookup[i];
if (bit[0] === '[') {
lookupCode += bit;
} else if (! isJSIdentifier.exec(lookup[i])) {
// Allow a non-JS-indentifier token, e.g. `json foo-bar`.
lookupCode += '["' + lookup[i].replace('"', '\\"') + '"]';
} else {
lookupCode += '.' + lookup[i];
}
}
return runInNewContext("(" + JSON.stringify(datum) + ")" + lookupCode);
}
/**
* Print out a single result, considering input options.
*/
function printDatum(datum, opts, sep, alwaysPrintSep) {
var output = null;
switch (opts.outputMode) {
case OM_INSPECT:
output = util.inspect(datum, false, Infinity, true);
break;
case OM_JSON:
if (typeof datum !== 'undefined') {
output = JSON.stringify(datum, null, opts.jsonIndent);
}
break;
case OM_COMPACT:
// Dev Note: A still relatively experimental attempt at a more
// compact ouput somewhat a la Python's repr of a dict. I.e. try to
// fit elements on one line as much as reasonable.
if (datum === undefined) {
// pass
} else if (isArray(datum)) {
var bits = ['[\n'];
datum.forEach(function (d) {
bits.push(' ')
bits.push(JSON.stringify(d, null, 0).replace(/,"(?![,:])/g, ', "'));
bits.push(',\n');
});
bits.push(bits.pop().slice(0, -2) + '\n') // drop last comma
bits.push(']');
output = bits.join('');
} else {
output = JSON.stringify(datum, null, 0);
}
break;
case OM_JSONY:
if (typeof datum === 'string') {
output = datum;
} else if (typeof datum !== 'undefined') {
output = JSON.stringify(datum, null, opts.jsonIndent);
}
break;
default:
throw new Error("unknown output mode: "+opts.outputMode);
}
if (output && output.length) {
emit(output);
emit(sep);
} else if (alwaysPrintSep) {
emit(sep);
}
}
var stdoutFlushed = true;
function emit(s) {
// TODO:PERF If this is try/catch is too slow (too granular): move up to
// mainline and be sure to only catch this particular error.
try {
stdoutFlushed = process.stdout.write(s);
} catch (e) {
// Handle any exceptions in stdout writing in the "error" event above.
}
}
process.stdout.on("error", function (err) {
if (err.code === "EPIPE") {
// Pass. See <https://github.com/trentm/json/issues/9>.
} else {
warn(err)
drainStdoutAndExit(1);
}
});
/**
* A hacked up version of "process.exit" that will first drain stdout
* before exiting. *WARNING: This doesn't stop event processing.* IOW,
* callers have to be careful that code following this call isn't
* accidentally executed.
*
* In node v0.6 "process.stdout and process.stderr are blocking when they
* refer to regular files or TTY file descriptors." However, this hack might
* still be necessary in a shell pipeline.
*/
function drainStdoutAndExit(code) {
process.stdout.on('drain', function () {
process.exit(code);
});
if (stdoutFlushed) {
process.exit(code);
}
}
//---- mainline
function main(argv) {
var opts;
try {
opts = parseArgv(argv);
} catch (e) {
warn("json: error: %s", e.message)
return drainStdoutAndExit(1);
}
//warn(opts);
if (opts.help) {
printHelp();
return;
}
if (opts.version) {
util.puts("json " + getVersion());
return;
}
var lookupStrs = opts.args;
//XXX ditch this hack
if (lookupStrs.length == 0) {
lookupStrs.push("");
}
var buffer = "";
var stdin = process.openStdin();
stdin.setEncoding('utf8');
stdin.on('data', function (chunk) {
buffer += chunk;
});
stdin.on('end', function () {
// Take off a leading HTTP header if any and pass it through.
while (true) {
if (buffer.slice(0,5) === "HTTP/") {
var index = buffer.indexOf('\r\n\r\n');
var sepLen = 4;
if (index == -1) {
index = buffer.indexOf('\n\n');
sepLen = 2;
}
if (index != -1) {
if (! opts.dropHeaders) {
emit(buffer.slice(0, index+sepLen));
}
var is100Continue = (buffer.slice(0, 21) === "HTTP/1.1 100 Continue");
buffer = buffer.slice(index+sepLen);
if (is100Continue) {
continue;
}
}
}
break;
}
// Expect the remainder to be JSON.
if (! buffer.length) {
return;
}
var input = parseInput(buffer); // -> {datum: <input object>, error: <error object>}
if (input.error) {
// Doesn't look like JSON. Just print it out and move on.
if (! opts.quiet) {
warn("json: error: doesn't look like JSON: %s (input='%s')",
input.error, JSON.stringify(buffer));
}
emit(buffer);
if (buffer.length && buffer[buffer.length-1] !== "\n") {
emit('\n');
}
return drainStdoutAndExit(1);
}
// Process and output the input JSON.
var lookups = lookupStrs.map(parseLookup);
var results = [];
if (opts.array) {
var data = (isArray(input.datum) ? input.datum : [input.datum]);
if (lookups.length === 0) {
results = input.datum;
} else {
for (var j=0; j < data.length; j++) {
var result = [];
for (var i=0; i < lookups.length; i++) {
result.push(lookupDatum(data[j], lookups[i]));
}
results.push(result);
}
}
results.forEach(function (row) {
var c;
for (c = 0; c < row.length-1; c++) {
printDatum(row[c], opts, opts.delim, true);
}
printDatum(row[c], opts, '\n', true);
});
} else {
if (lookups.length === 0) {
results = input.datum;
} else {
for (var i=0; i < lookups.length; i++) {
results.push(lookupDatum(input.datum, lookups[i]));
}
}
results.forEach(function (r) {
printDatum(r, opts, '\n', false);
});
}
});
}
if (require.main === module) {
main(process.argv);
}
Copyright 2009, 2010, 2011 Isaac Z. Schlueter.
All rights reserved.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
semver(1) -- The semantic versioner for npm
===========================================
## Usage
$ npm install semver
semver.valid('1.2.3') // true
semver.valid('a.b.c') // false
semver.clean(' =v1.2.3 ') // '1.2.3'
semver.satisfies('1.2.3', '1.x || >=2.5.0 || 5.0.0 - 7.2.3') // true
semver.gt('1.2.3', '9.8.7') // false
semver.lt('1.2.3', '9.8.7') // true
As a command-line utility:
$ semver -h
Usage: semver -v <version> [-r <range>]
Test if version(s) satisfy the supplied range(s),
and sort them.
Multiple versions or ranges may be supplied.
Program exits successfully if any valid version satisfies
all supplied ranges, and prints all satisfying versions.
If no versions are valid, or ranges are not satisfied,
then exits failure.
Versions are printed in ascending order, so supplying
multiple versions to the utility will just sort them.
## Versions
A version is the following things, in this order:
* a number (Major)
* a period
* a number (minor)
* a period
* a number (patch)
* OPTIONAL: a hyphen, followed by a number (build)
* OPTIONAL: a collection of pretty much any non-whitespace characters
(tag)
A leading `"="` or `"v"` character is stripped off and ignored.
## Comparisons
The ordering of versions is done using the following algorithm, given
two versions and asked to find the greater of the two:
* If the majors are numerically different, then take the one
with a bigger major number. `2.3.4 > 1.3.4`
* If the minors are numerically different, then take the one
with the bigger minor number. `2.3.4 > 2.2.4`
* If the patches are numerically different, then take the one with the
bigger patch number. `2.3.4 > 2.3.3`
* If only one of them has a build number, then take the one with the
build number. `2.3.4-0 > 2.3.4`
* If they both have build numbers, and the build numbers are numerically
different, then take the one with the bigger build number.
`2.3.4-10 > 2.3.4-9`
* If only one of them has a tag, then take the one without the tag.
`2.3.4 > 2.3.4-beta`
* If they both have tags, then take the one with the lexicographically
larger tag. `2.3.4-beta > 2.3.4-alpha`
* At this point, they're equal.
## Ranges
The following range styles are supported:
* `>1.2.3` Greater than a specific version.
* `<1.2.3` Less than
* `1.2.3 - 2.3.4` := `>=1.2.3 <=2.3.4`
* `~1.2.3` := `>=1.2.3 <1.3.0`
* `~1.2` := `>=1.2.0 <2.0.0`
* `~1` := `>=1.0.0 <2.0.0`
* `1.2.x` := `>=1.2.0 <1.3.0`
* `1.x` := `>=1.0.0 <2.0.0`
Ranges can be joined with either a space (which implies "and") or a
`||` (which implies "or").
## Functions
* valid(v): Return the parsed version, or null if it's not valid.
* inc(v, release): Return the version incremented by the release type
(major, minor, patch, or build), or null if it's not valid.
### Comparison
* gt(v1, v2): `v1 > v2`
* gte(v1, v2): `v1 >= v2`
* lt(v1, v2): `v1 < v2`
* lte(v1, v2): `v1 <= v2`
* eq(v1, v2): `v1 == v2` This is true if they're logically equivalent,
even if they're not the exact same string. You already know how to
compare strings.
* neq(v1, v2): `v1 != v2` The opposite of eq.
* cmp(v1, comparator, v2): Pass in a comparison string, and it'll call
the corresponding function above. `"==="` and `"!=="` do simple
string comparison, but are included for completeness. Throws if an
invalid comparison string is provided.
* compare(v1, v2): Return 0 if v1 == v2, or 1 if v1 is greater, or -1 if
v2 is greater. Sorts in ascending order if passed to Array.sort().
* rcompare(v1, v2): The reverse of compare. Sorts an array of versions
in descending order when passed to Array.sort().
### Ranges
* validRange(range): Return the valid range or null if it's not valid
* satisfies(version, range): Return true if the version satisfies the
range.
* maxSatisfying(versions, range): Return the highest version in the list
that satisfies the range, or null if none of them do.
#!/usr/bin/env node
// Standalone semver comparison program.
// Exits successfully and prints matching version(s) if
// any supplied version is valid and passes all tests.
var argv = process.argv.slice(2)
, versions = []
, range = []
, gt = []
, lt = []
, eq = []
, semver = require("../semver")
main()
function main () {
if (!argv.length) return help()
while (argv.length) {
var a
switch (a = argv.shift()) {
case "-v": case "--version":
versions.push(argv.shift())
break
case "-r": case "--range":
range.push(argv.shift())
break
case "-h": case "--help": case "-?":
return help()
default:
versions.push(a)
break
}
}
versions = versions.filter(semver.valid)
for (var i = 0, l = range.length; i < l ; i ++) {
versions = versions.filter(function (v) {
return semver.satisfies(v, range[i])
})
if (!versions.length) return fail()
}
return success(versions)
}
function fail () { process.exit(1) }
function success () {
versions.sort(semver.compare)
.map(semver.clean)
.forEach(function (v,i,_) { console.log(v) })
}
function help () {
console.log(["Usage: semver -v <version> [-r <range>]"
,"Test if version(s) satisfy the supplied range(s),"
,"and sort them."
,""
,"Multiple versions or ranges may be supplied."
,""
,"Program exits successfully if any valid version satisfies"
,"all supplied ranges, and prints all satisfying versions."
,""
,"If no versions are valid, or ranges are not satisfied,"
,"then exits failure."
,""
,"Versions are printed in ascending order, so supplying"
,"multiple versions to the utility will just sort them."
].join("\n"))
}
{ "name" : "semver"
, "version" : "1.0.12"
, "description" : "The semantic version parser used by npm."
, "main" : "semver.js"
, "scripts" : { "test" : "tap test.js" }
, "devDependencies": { "tap" : "0.x >=0.0.4" }
, "license" :
{ "type" : "MIT"
, "url" : "https://github.com/isaacs/semver/raw/master/LICENSE" }
, "repository" : "git://github.com/isaacs/node-semver.git"
, "bin" : { "semver" : "./bin/semver" } }
;(function (exports) { // nothing in here is node-specific.
// See http://semver.org/
// This implementation is a *hair* less strict in that it allows
// v1.2.3 things, and also tags that don't begin with a char.
var semver = "\\s*[v=]*\\s*([0-9]+)" // major
+ "\\.([0-9]+)" // minor
+ "\\.([0-9]+)" // patch
+ "(-[0-9]+-?)?" // build
+ "([a-zA-Z-][a-zA-Z0-9-\.:]*)?" // tag
, exprComparator = "^((<|>)?=?)\s*("+semver+")$|^$"
, xRangePlain = "[v=]*([0-9]+|x|X|\\*)"
+ "(?:\\.([0-9]+|x|X|\\*)"
+ "(?:\\.([0-9]+|x|X|\\*)"
+ "([a-zA-Z-][a-zA-Z0-9-\.:]*)?)?)?"
, xRange = "((?:<|>)?=?)?\\s*" + xRangePlain
, exprSpermy = "(?:~>?)"+xRange
, expressions = exports.expressions =
{ parse : new RegExp("^\\s*"+semver+"\\s*$")
, parsePackage : new RegExp("^\\s*([^\/]+)[-@](" +semver+")\\s*$")
, parseRange : new RegExp(
"^\\s*(" + semver + ")\\s+-\\s+(" + semver + ")\\s*$")
, validComparator : new RegExp("^"+exprComparator+"$")
, parseXRange : new RegExp("^"+xRange+"$")
, parseSpermy : new RegExp("^"+exprSpermy+"$")
}
Object.getOwnPropertyNames(expressions).forEach(function (i) {
exports[i] = function (str) {
return ("" + (str || "")).match(expressions[i])
}
})
exports.rangeReplace = ">=$1 <=$7"
exports.clean = clean
exports.compare = compare
exports.satisfies = satisfies
exports.gt = gt
exports.gte = gte
exports.lt = lt
exports.lte = lte
exports.eq = eq
exports.neq = neq
exports.cmp = cmp
exports.inc = inc
exports.valid = valid
exports.validPackage = validPackage
exports.validRange = validRange
exports.maxSatisfying = maxSatisfying
exports.replaceStars = replaceStars
exports.toComparators = toComparators
function stringify (version) {
var v = version
return [v[1]||'', v[2]||'', v[3]||''].join(".") + (v[4]||'') + (v[5]||'')
}
function clean (version) {
version = exports.parse(version)
if (!version) return version
return stringify(version)
}
function valid (version) {
if (typeof version !== "string") return null
return exports.parse(version) && version.trim().replace(/^[v=]+/, '')
}
function validPackage (version) {
if (typeof version !== "string") return null
return version.match(expressions.parsePackage) && version.trim()
}
// range can be one of:
// "1.0.3 - 2.0.0" range, inclusive, like ">=1.0.3 <=2.0.0"
// ">1.0.2" like 1.0.3 - 9999.9999.9999
// ">=1.0.2" like 1.0.2 - 9999.9999.9999
// "<2.0.0" like 0.0.0 - 1.9999.9999
// ">1.0.2 <2.0.0" like 1.0.3 - 1.9999.9999
var starExpression = /(<|>)?=?\s*\*/g
, starReplace = ""
, compTrimExpression = new RegExp("((<|>)?=?)\\s*("
+semver+"|"+xRangePlain+")", "g")
, compTrimReplace = "$1$3"
function toComparators (range) {
var ret = (range || "").trim()
.replace(expressions.parseRange, exports.rangeReplace)
.replace(compTrimExpression, compTrimReplace)
.split(/\s+/)
.join(" ")
.split("||")
.map(function (orchunk) {
return orchunk
.split(" ")
.map(replaceXRanges)
.map(replaceSpermies)
.map(replaceStars)
.join(" ").trim()
})
.map(function (orchunk) {
return orchunk
.trim()
.split(/\s+/)
.filter(function (c) { return c.match(expressions.validComparator) })
})
.filter(function (c) { return c.length })
return ret
}
function replaceStars (stars) {
return stars.trim().replace(starExpression, starReplace)
}
// "2.x","2.x.x" --> ">=2.0.0- <2.1.0-"
// "2.3.x" --> ">=2.3.0- <2.4.0-"
function replaceXRanges (ranges) {
return ranges.split(/\s+/)
.map(replaceXRange)
.join(" ")
}
function replaceXRange (version) {
return version.trim().replace(expressions.parseXRange,
function (v, gtlt, M, m, p, t) {
var anyX = !M || M.toLowerCase() === "x" || M === "*"
|| !m || m.toLowerCase() === "x" || m === "*"
|| !p || p.toLowerCase() === "x" || p === "*"
, ret = v
if (gtlt && anyX) {
// just replace x'es with zeroes
;(!M || M === "*" || M.toLowerCase() === "x") && (M = 0)
;(!m || m === "*" || m.toLowerCase() === "x") && (m = 0)
;(!p || p === "*" || p.toLowerCase() === "x") && (p = 0)
ret = gtlt + M+"."+m+"."+p+"-"
} else if (!M || M === "*" || M.toLowerCase() === "x") {
ret = "*" // allow any
} else if (!m || m === "*" || m.toLowerCase() === "x") {
// append "-" onto the version, otherwise
// "1.x.x" matches "2.0.0beta", since the tag
// *lowers* the version value
ret = ">="+M+".0.0- <"+(+M+1)+".0.0-"
} else if (!p || p === "*" || p.toLowerCase() === "x") {
ret = ">="+M+"."+m+".0- <"+M+"."+(+m+1)+".0-"
}
//console.error("parseXRange", [].slice.call(arguments), ret)
return ret
})
}
// ~, ~> --> * (any, kinda silly)
// ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0 <3.0.0
// ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0 <2.1.0
// ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0 <1.3.0
// ~1.2.3, ~>1.2.3 --> >=1.2.3 <1.3.0
// ~1.2.0, ~>1.2.0 --> >=1.2.0 <1.3.0
function replaceSpermies (version) {
return version.trim().replace(expressions.parseSpermy,
function (v, gtlt, M, m, p, t) {
if (gtlt) throw new Error(
"Using '"+gtlt+"' with ~ makes no sense. Don't do it.")
if (!M || M.toLowerCase() === "x") {
return ""
}
// ~1 == >=1.0.0- <2.0.0-
if (!m || m.toLowerCase() === "x") {
return ">="+M+".0.0- <"+(+M+1)+".0.0-"
}
// ~1.2 == >=1.2.0- <1.3.0-
if (!p || p.toLowerCase() === "x") {
return ">="+M+"."+m+".0- <"+M+"."+(+m+1)+".0-"
}
// ~1.2.3 == >=1.2.3- <1.3.0-
t = t || "-"
return ">="+M+"."+m+"."+p+t+" <"+M+"."+(+m+1)+".0-"
})
}
function validRange (range) {
range = replaceStars(range)
var c = toComparators(range)
return (c.length === 0)
? null
: c.map(function (c) { return c.join(" ") }).join("||")
}
// returns the highest satisfying version in the list, or undefined
function maxSatisfying (versions, range) {
return versions
.filter(function (v) { return satisfies(v, range) })
.sort(compare)
.pop()
}
function satisfies (version, range) {
version = valid(version)
if (!version) return false
range = toComparators(range)
for (var i = 0, l = range.length ; i < l ; i ++) {
var ok = false
for (var j = 0, ll = range[i].length ; j < ll ; j ++) {
var r = range[i][j]
, gtlt = r.charAt(0) === ">" ? gt
: r.charAt(0) === "<" ? lt
: false
, eq = r.charAt(!!gtlt) === "="
, sub = (!!eq) + (!!gtlt)
if (!gtlt) eq = true
r = r.substr(sub)
r = (r === "") ? r : valid(r)
ok = (r === "") || (eq && r === version) || (gtlt && gtlt(version, r))
if (!ok) break
}
if (ok) return true
}
return false
}
// return v1 > v2 ? 1 : -1
function compare (v1, v2) {
var g = gt(v1, v2)
return g === null ? 0 : g ? 1 : -1
}
function rcompare (v1, v2) {
return compare(v2, v1)
}
function lt (v1, v2) { return gt(v2, v1) }
function gte (v1, v2) { return !lt(v1, v2) }
function lte (v1, v2) { return !gt(v1, v2) }
function eq (v1, v2) { return gt(v1, v2) === null }
function neq (v1, v2) { return gt(v1, v2) !== null }
function cmp (v1, c, v2) {
switch (c) {
case ">": return gt(v1, v2)
case "<": return lt(v1, v2)
case ">=": return gte(v1, v2)
case "<=": return lte(v1, v2)
case "==": return eq(v1, v2)
case "!=": return neq(v1, v2)
case "===": return v1 === v2
case "!==": return v1 !== v2
default: throw new Error("Y U NO USE VALID COMPARATOR!? "+c)
}
}
// return v1 > v2
function num (v) {
return v === undefined ? -1 : parseInt((v||"0").replace(/[^0-9]+/g, ''), 10)
}
function gt (v1, v2) {
v1 = exports.parse(v1)
v2 = exports.parse(v2)
if (!v1 || !v2) return false
for (var i = 1; i < 5; i ++) {
v1[i] = num(v1[i])
v2[i] = num(v2[i])
if (v1[i] > v2[i]) return true
else if (v1[i] !== v2[i]) return false
}
// no tag is > than any tag, or use lexicographical order.
var tag1 = v1[5] || ""
, tag2 = v2[5] || ""
// kludge: null means they were equal. falsey, and detectable.
// embarrassingly overclever, though, I know.
return tag1 === tag2 ? null
: !tag1 ? true
: !tag2 ? false
: tag1 > tag2
}
function inc (version, release) {
version = exports.parse(version)
if (!version) return null
var parsedIndexLookup =
{ 'major': 1
, 'minor': 2
, 'patch': 3
, 'build': 4 }
var incIndex = parsedIndexLookup[release]
if (incIndex === undefined) return null
var current = num(version[incIndex])
version[incIndex] = current === -1 ? 1 : current + 1
for (var i = incIndex + 1; i < 5; i ++) {
if (num(version[i]) !== -1) version[i] = "0"
}
if (version[4]) version[4] = "-" + version[4]
version[5] = ""
return stringify(version)
}
})(typeof exports === "object" ? exports : semver = {})
var tap = require("tap")
, test = tap.test
, semver = require("./semver.js")
, eq = semver.eq
, gt = semver.gt
, lt = semver.lt
, neq = semver.neq
, cmp = semver.cmp
, gte = semver.gte
, lte = semver.lte
, satisfies = semver.satisfies
, validRange = semver.validRange
, inc = semver.inc
, replaceStars = semver.replaceStars
, toComparators = semver.toComparators
tap.plan(8)
test("\ncomparison tests", function (t) {
; [ ["0.0.0", "0.0.0foo"]
, ["0.0.1", "0.0.0"]
, ["1.0.0", "0.9.9"]
, ["0.10.0", "0.9.0"]
, ["0.99.0", "0.10.0"]
, ["2.0.0", "1.2.3"]
, ["v0.0.0", "0.0.0foo"]
, ["v0.0.1", "0.0.0"]
, ["v1.0.0", "0.9.9"]
, ["v0.10.0", "0.9.0"]
, ["v0.99.0", "0.10.0"]
, ["v2.0.0", "1.2.3"]
, ["0.0.0", "v0.0.0foo"]
, ["0.0.1", "v0.0.0"]
, ["1.0.0", "v0.9.9"]
, ["0.10.0", "v0.9.0"]
, ["0.99.0", "v0.10.0"]
, ["2.0.0", "v1.2.3"]
, ["1.2.3", "1.2.3-asdf"]
, ["1.2.3-4", "1.2.3"]
, ["1.2.3-4-foo", "1.2.3"]
, ["1.2.3-5", "1.2.3-5-foo"]
, ["1.2.3-5", "1.2.3-4"]
, ["1.2.3-5-foo", "1.2.3-5-Foo"]
].forEach(function (v) {
var v0 = v[0]
, v1 = v[1]
t.ok(gt(v0, v1), "gt('"+v0+"', '"+v1+"')")
t.ok(lt(v1, v0), "lt('"+v1+"', '"+v0+"')")
t.ok(!gt(v1, v0), "!gt('"+v1+"', '"+v0+"')")
t.ok(!lt(v0, v1), "!lt('"+v0+"', '"+v1+"')")
t.ok(eq(v0, v0), "eq('"+v0+"', '"+v0+"')")
t.ok(eq(v1, v1), "eq('"+v1+"', '"+v1+"')")
t.ok(neq(v0, v1), "neq('"+v0+"', '"+v1+"')")
t.ok(cmp(v1, "==", v1), "cmp('"+v1+"' == '"+v1+"')")
t.ok(cmp(v0, ">=", v1), "cmp('"+v0+"' >= '"+v1+"')")
t.ok(cmp(v1, "<=", v0), "cmp('"+v1+"' <= '"+v0+"')")
t.ok(cmp(v0, "!=", v1), "cmp('"+v0+"' != '"+v1+"')")
})
t.end()
})
test("\nequality tests", function (t) {
; [ ["1.2.3", "v1.2.3"]
, ["1.2.3", "=1.2.3"]
, ["1.2.3", "v 1.2.3"]
, ["1.2.3", "= 1.2.3"]
, ["1.2.3", " v1.2.3"]
, ["1.2.3", " =1.2.3"]
, ["1.2.3", " v 1.2.3"]
, ["1.2.3", " = 1.2.3"]
, ["1.2.3-0", "v1.2.3-0"]
, ["1.2.3-0", "=1.2.3-0"]
, ["1.2.3-0", "v 1.2.3-0"]
, ["1.2.3-0", "= 1.2.3-0"]
, ["1.2.3-0", " v1.2.3-0"]
, ["1.2.3-0", " =1.2.3-0"]
, ["1.2.3-0", " v 1.2.3-0"]
, ["1.2.3-0", " = 1.2.3-0"]
, ["1.2.3-01", "v1.2.3-1"]
, ["1.2.3-01", "=1.2.3-1"]
, ["1.2.3-01", "v 1.2.3-1"]
, ["1.2.3-01", "= 1.2.3-1"]
, ["1.2.3-01", " v1.2.3-1"]
, ["1.2.3-01", " =1.2.3-1"]
, ["1.2.3-01", " v 1.2.3-1"]
, ["1.2.3-01", " = 1.2.3-1"]
, ["1.2.3beta", "v1.2.3beta"]
, ["1.2.3beta", "=1.2.3beta"]
, ["1.2.3beta", "v 1.2.3beta"]
, ["1.2.3beta", "= 1.2.3beta"]
, ["1.2.3beta", " v1.2.3beta"]
, ["1.2.3beta", " =1.2.3beta"]
, ["1.2.3beta", " v 1.2.3beta"]
, ["1.2.3beta", " = 1.2.3beta"]
].forEach(function (v) {
var v0 = v[0]
, v1 = v[1]
t.ok(eq(v0, v1), "eq('"+v0+"', '"+v1+"')")
t.ok(!neq(v0, v1), "!neq('"+v0+"', '"+v1+"')")
t.ok(cmp(v0, "==", v1), "cmp("+v0+"=="+v1+")")
t.ok(!cmp(v0, "!=", v1), "!cmp("+v0+"!="+v1+")")
t.ok(!cmp(v0, "===", v1), "!cmp("+v0+"==="+v1+")")
t.ok(cmp(v0, "!==", v1), "cmp("+v0+"!=="+v1+")")
t.ok(!gt(v0, v1), "!gt('"+v0+"', '"+v1+"')")
t.ok(gte(v0, v1), "gte('"+v0+"', '"+v1+"')")
t.ok(!lt(v0, v1), "!lt('"+v0+"', '"+v1+"')")
t.ok(lte(v0, v1), "lte('"+v0+"', '"+v1+"')")
})
t.end()
})
test("\nrange tests", function (t) {
; [ ["1.0.0 - 2.0.0", "1.2.3"]
, ["1.0.0", "1.0.0"]
, [">=*", "0.2.4"]
, ["", "1.0.0"]
, ["*", "1.2.3"]
, ["*", "v1.2.3-foo"]
, [">=1.0.0", "1.0.0"]
, [">=1.0.0", "1.0.1"]
, [">=1.0.0", "1.1.0"]
, [">1.0.0", "1.0.1"]
, [">1.0.0", "1.1.0"]
, ["<=2.0.0", "2.0.0"]
, ["<=2.0.0", "1.9999.9999"]
, ["<=2.0.0", "0.2.9"]
, ["<2.0.0", "1.9999.9999"]
, ["<2.0.0", "0.2.9"]
, [">= 1.0.0", "1.0.0"]
, [">= 1.0.0", "1.0.1"]
, [">= 1.0.0", "1.1.0"]
, ["> 1.0.0", "1.0.1"]
, ["> 1.0.0", "1.1.0"]
, ["<= 2.0.0", "2.0.0"]
, ["<= 2.0.0", "1.9999.9999"]
, ["<= 2.0.0", "0.2.9"]
, ["< 2.0.0", "1.9999.9999"]
, ["<\t2.0.0", "0.2.9"]
, [">=0.1.97", "v0.1.97"]
, [">=0.1.97", "0.1.97"]
, ["0.1.20 || 1.2.4", "1.2.4"]
, [">=0.2.3 || <0.0.1", "0.0.0"]
, [">=0.2.3 || <0.0.1", "0.2.3"]
, [">=0.2.3 || <0.0.1", "0.2.4"]
, ["||", "1.3.4"]
, ["2.x.x", "2.1.3"]
, ["1.2.x", "1.2.3"]
, ["1.2.x || 2.x", "2.1.3"]
, ["1.2.x || 2.x", "1.2.3"]
, ["x", "1.2.3"]
, ["2.*.*", "2.1.3"]
, ["1.2.*", "1.2.3"]
, ["1.2.* || 2.*", "2.1.3"]
, ["1.2.* || 2.*", "1.2.3"]
, ["*", "1.2.3"]
, ["2", "2.1.2"]
, ["2.3", "2.3.1"]
, ["~2.4", "2.4.0"] // >=2.4.0 <2.5.0
, ["~2.4", "2.4.5"]
, ["~>3.2.1", "3.2.2"] // >=3.2.1 <3.3.0
, ["~1", "1.2.3"] // >=1.0.0 <2.0.0
, ["~>1", "1.2.3"]
, ["~> 1", "1.2.3"]
, ["~1.0", "1.0.2"] // >=1.0.0 <1.1.0
, ["~ 1.0", "1.0.2"]
, [">=1", "1.0.0"]
, [">= 1", "1.0.0"]
, ["<1.2", "1.1.1"]
, ["< 1.2", "1.1.1"]
, ["1", "1.0.0beta"]
, ["~v0.5.4-pre", "0.5.5"]
, ["~v0.5.4-pre", "0.5.4"]
].forEach(function (v) {
t.ok(satisfies(v[1], v[0]), v[0]+" satisfied by "+v[1])
})
t.end()
})
test("\nnegative range tests", function (t) {
; [ ["1.0.0 - 2.0.0", "2.2.3"]
, ["1.0.0", "1.0.1"]
, [">=1.0.0", "0.0.0"]
, [">=1.0.0", "0.0.1"]
, [">=1.0.0", "0.1.0"]
, [">1.0.0", "0.0.1"]
, [">1.0.0", "0.1.0"]
, ["<=2.0.0", "3.0.0"]
, ["<=2.0.0", "2.9999.9999"]
, ["<=2.0.0", "2.2.9"]
, ["<2.0.0", "2.9999.9999"]
, ["<2.0.0", "2.2.9"]
, [">=0.1.97", "v0.1.93"]
, [">=0.1.97", "0.1.93"]
, ["0.1.20 || 1.2.4", "1.2.3"]
, [">=0.2.3 || <0.0.1", "0.0.3"]
, [">=0.2.3 || <0.0.1", "0.2.2"]
, ["2.x.x", "1.1.3"]
, ["2.x.x", "3.1.3"]
, ["1.2.x", "1.3.3"]
, ["1.2.x || 2.x", "3.1.3"]
, ["1.2.x || 2.x", "1.1.3"]
, ["2.*.*", "1.1.3"]
, ["2.*.*", "3.1.3"]
, ["1.2.*", "1.3.3"]
, ["1.2.* || 2.*", "3.1.3"]
, ["1.2.* || 2.*", "1.1.3"]
, ["2", "1.1.2"]
, ["2.3", "2.4.1"]
, ["~2.4", "2.5.0"] // >=2.4.0 <2.5.0
, ["~2.4", "2.3.9"]
, ["~>3.2.1", "3.3.2"] // >=3.2.1 <3.3.0
, ["~>3.2.1", "3.2.0"] // >=3.2.1 <3.3.0
, ["~1", "0.2.3"] // >=1.0.0 <2.0.0
, ["~>1", "2.2.3"]
, ["~1.0", "1.1.0"] // >=1.0.0 <1.1.0
, ["<1", "1.0.0"]
, [">=1.2", "1.1.1"]
, ["1", "2.0.0beta"]
, ["~v0.5.4-beta", "0.5.4-alpha"]
, ["<1", "1.0.0beta"]
, ["< 1", "1.0.0beta"]
].forEach(function (v) {
t.ok(!satisfies(v[1], v[0]), v[0]+" not satisfied by "+v[1])
})
t.end()
})
test("\nincrement versions test", function (t) {
; [ [ "1.2.3", "major", "2.0.0" ]
, [ "1.2.3", "minor", "1.3.0" ]
, [ "1.2.3", "patch", "1.2.4" ]
, [ "1.2.3", "build", "1.2.3-1" ]
, [ "1.2.3-4", "build", "1.2.3-5" ]
, [ "1.2.3tag", "major", "2.0.0" ]
, [ "1.2.3-tag", "major", "2.0.0" ]
, [ "1.2.3tag", "build", "1.2.3-1" ]
, [ "1.2.3-tag", "build", "1.2.3-1" ]
, [ "1.2.3-4-tag", "build", "1.2.3-5" ]
, [ "1.2.3-4tag", "build", "1.2.3-5" ]
, [ "1.2.3", "fake", null ]
, [ "fake", "major", null ]
].forEach(function (v) {
t.equal(inc(v[0], v[1]), v[2], "inc("+v[0]+", "+v[1]+") === "+v[2])
})
t.end()
})
test("\nreplace stars test", function (t) {
; [ [ "", "" ]
, [ "*", "" ]
, [ "> *", "" ]
, [ "<*", "" ]
, [ " >= *", "" ]
, [ "* || 1.2.3", " || 1.2.3" ]
].forEach(function (v) {
t.equal(replaceStars(v[0]), v[1], "replaceStars("+v[0]+") === "+v[1])
})
t.end()
})
test("\nvalid range test", function (t) {
; [ ["1.0.0 - 2.0.0", ">=1.0.0 <=2.0.0"]
, ["1.0.0", "1.0.0"]
, [">=*", ""]
, ["", ""]
, ["*", ""]
, ["*", ""]
, [">=1.0.0", ">=1.0.0"]
, [">1.0.0", ">1.0.0"]
, ["<=2.0.0", "<=2.0.0"]
, ["1", ">=1.0.0- <2.0.0-"]
, ["<=2.0.0", "<=2.0.0"]
, ["<=2.0.0", "<=2.0.0"]
, ["<2.0.0", "<2.0.0"]
, ["<2.0.0", "<2.0.0"]
, [">= 1.0.0", ">=1.0.0"]
, [">= 1.0.0", ">=1.0.0"]
, [">= 1.0.0", ">=1.0.0"]
, ["> 1.0.0", ">1.0.0"]
, ["> 1.0.0", ">1.0.0"]
, ["<= 2.0.0", "<=2.0.0"]
, ["<= 2.0.0", "<=2.0.0"]
, ["<= 2.0.0", "<=2.0.0"]
, ["< 2.0.0", "<2.0.0"]
, ["< 2.0.0", "<2.0.0"]
, [">=0.1.97", ">=0.1.97"]
, [">=0.1.97", ">=0.1.97"]
, ["0.1.20 || 1.2.4", "0.1.20||1.2.4"]
, [">=0.2.3 || <0.0.1", ">=0.2.3||<0.0.1"]
, [">=0.2.3 || <0.0.1", ">=0.2.3||<0.0.1"]
, [">=0.2.3 || <0.0.1", ">=0.2.3||<0.0.1"]
, ["||", "||"]
, ["2.x.x", ">=2.0.0- <3.0.0-"]
, ["1.2.x", ">=1.2.0- <1.3.0-"]
, ["1.2.x || 2.x", ">=1.2.0- <1.3.0-||>=2.0.0- <3.0.0-"]
, ["1.2.x || 2.x", ">=1.2.0- <1.3.0-||>=2.0.0- <3.0.0-"]
, ["x", ""]
, ["2.*.*", null]
, ["1.2.*", null]
, ["1.2.* || 2.*", null]
, ["1.2.* || 2.*", null]
, ["*", ""]
, ["2", ">=2.0.0- <3.0.0-"]
, ["2.3", ">=2.3.0- <2.4.0-"]
, ["~2.4", ">=2.4.0- <2.5.0-"]
, ["~2.4", ">=2.4.0- <2.5.0-"]
, ["~>3.2.1", ">=3.2.1- <3.3.0-"]
, ["~1", ">=1.0.0- <2.0.0-"]
, ["~>1", ">=1.0.0- <2.0.0-"]
, ["~> 1", ">=1.0.0- <2.0.0-"]
, ["~1.0", ">=1.0.0- <1.1.0-"]
, ["~ 1.0", ">=1.0.0- <1.1.0-"]
, ["<1", "<1.0.0-"]
, ["< 1", "<1.0.0-"]
, [">=1", ">=1.0.0-"]
, [">= 1", ">=1.0.0-"]
, ["<1.2", "<1.2.0-"]
, ["< 1.2", "<1.2.0-"]
, ["1", ">=1.0.0- <2.0.0-"]
].forEach(function (v) {
t.equal(validRange(v[0]), v[1], "validRange("+v[0]+") === "+v[1])
})
t.end()
})
test("\ncomparators test", function (t) {
; [ ["1.0.0 - 2.0.0", [[">=1.0.0", "<=2.0.0"]] ]
, ["1.0.0", [["1.0.0"]] ]
, [">=*", [[">=0.0.0-"]] ]
, ["", [[""]]]
, ["*", [[""]] ]
, ["*", [[""]] ]
, [">=1.0.0", [[">=1.0.0"]] ]
, [">=1.0.0", [[">=1.0.0"]] ]
, [">=1.0.0", [[">=1.0.0"]] ]
, [">1.0.0", [[">1.0.0"]] ]
, [">1.0.0", [[">1.0.0"]] ]
, ["<=2.0.0", [["<=2.0.0"]] ]
, ["1", [[">=1.0.0-", "<2.0.0-"]] ]
, ["<=2.0.0", [["<=2.0.0"]] ]
, ["<=2.0.0", [["<=2.0.0"]] ]
, ["<2.0.0", [["<2.0.0"]] ]
, ["<2.0.0", [["<2.0.0"]] ]
, [">= 1.0.0", [[">=1.0.0"]] ]
, [">= 1.0.0", [[">=1.0.0"]] ]
, [">= 1.0.0", [[">=1.0.0"]] ]
, ["> 1.0.0", [[">1.0.0"]] ]
, ["> 1.0.0", [[">1.0.0"]] ]
, ["<= 2.0.0", [["<=2.0.0"]] ]
, ["<= 2.0.0", [["<=2.0.0"]] ]
, ["<= 2.0.0", [["<=2.0.0"]] ]
, ["< 2.0.0", [["<2.0.0"]] ]
, ["<\t2.0.0", [["<2.0.0"]] ]
, [">=0.1.97", [[">=0.1.97"]] ]
, [">=0.1.97", [[">=0.1.97"]] ]
, ["0.1.20 || 1.2.4", [["0.1.20"], ["1.2.4"]] ]
, [">=0.2.3 || <0.0.1", [[">=0.2.3"], ["<0.0.1"]] ]
, [">=0.2.3 || <0.0.1", [[">=0.2.3"], ["<0.0.1"]] ]
, [">=0.2.3 || <0.0.1", [[">=0.2.3"], ["<0.0.1"]] ]
, ["||", [[""], [""]] ]
, ["2.x.x", [[">=2.0.0-", "<3.0.0-"]] ]
, ["1.2.x", [[">=1.2.0-", "<1.3.0-"]] ]
, ["1.2.x || 2.x", [[">=1.2.0-", "<1.3.0-"], [">=2.0.0-", "<3.0.0-"]] ]
, ["1.2.x || 2.x", [[">=1.2.0-", "<1.3.0-"], [">=2.0.0-", "<3.0.0-"]] ]
, ["x", [[""]] ]
, ["2.*.*", [[">=2.0.0-", "<3.0.0-"]] ]
, ["1.2.*", [[">=1.2.0-", "<1.3.0-"]] ]
, ["1.2.* || 2.*", [[">=1.2.0-", "<1.3.0-"], [">=2.0.0-", "<3.0.0-"]] ]
, ["1.2.* || 2.*", [[">=1.2.0-", "<1.3.0-"], [">=2.0.0-", "<3.0.0-"]] ]
, ["*", [[""]] ]
, ["2", [[">=2.0.0-", "<3.0.0-"]] ]
, ["2.3", [[">=2.3.0-", "<2.4.0-"]] ]
, ["~2.4", [[">=2.4.0-", "<2.5.0-"]] ]
, ["~2.4", [[">=2.4.0-", "<2.5.0-"]] ]
, ["~>3.2.1", [[">=3.2.1-", "<3.3.0-"]] ]
, ["~1", [[">=1.0.0-", "<2.0.0-"]] ]
, ["~>1", [[">=1.0.0-", "<2.0.0-"]] ]
, ["~> 1", [[">=1.0.0-", "<2.0.0-"]] ]
, ["~1.0", [[">=1.0.0-", "<1.1.0-"]] ]
, ["~ 1.0", [[">=1.0.0-", "<1.1.0-"]] ]
, ["<1", [["<1.0.0-"]] ]
, ["< 1", [["<1.0.0-"]] ]
, [">=1", [[">=1.0.0-"]] ]
, [">= 1", [[">=1.0.0-"]] ]
, ["<1.2", [["<1.2.0-"]] ]
, ["< 1.2", [["<1.2.0-"]] ]
, ["1", [[">=1.0.0-", "<2.0.0-"]] ]
].forEach(function (v) {
t.equivalent(toComparators(v[0]), v[1], "toComparators("+v[0]+") === "+JSON.stringify(v[1]))
})
t.end()
})
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment